我一直在这个网站上阅读了很多相关/类似的问题,但没有一个会起作用,而且我似乎没有看到同样的错误,所以我决定就此提出一个新问题。
我正在尝试学习更多的 git,具体来说,如何应用补丁并从某些分支提取提交并将其应用到其他分支。我最初想做一个虚拟测试,包括从一个分支(直到过去的某个时间点)挑选一些提交,然后将这些提交重新应用到过去的同一点,让我回到最初的点。
但是,我收到了大量“错误:补丁不适用”类型的错误消息。
我不明白为什么它不起作用。我尝试添加诸如 --whitespace=fix 等选项(在本网站的其他问题中建议),但无济于事。我还尝试使用 -3,希望我可以手动合并文件,但这只是将错误消息更改为“错误:补丁失败:文件名”,几乎所有文件都再次出现。
为了重现此错误,我使用了以下 git 存储库:https : //git.evlproject.org/linux-evl.git
具体来说,有commit的分支是evl/v5.4,没有commit的分支是master。我当时试过:
git diff evl/v5.4 master > ../patchfile
git checkout master
git apply ../patchile
如果这样的补丁确实适用,那会有点令人惊讶:
git diff evl/v5.4 master > ../patchfile
请记住,git diff
比较两次提交,或者更准确地说,比较两次提交中的快照。我喜欢将这两个提交称为L和R,分别代表“左”和“右”,尽管这里没有共同商定的命名约定。
对于L(左侧)提交,您选择选择的提交evl/v5.4
。对于R(右侧)提交,您选择了选择的提交master
。到目前为止这没有问题。
现在,请记住, from 的输出git diff
是一系列指令。这些指令(如果应用)将更改出现在提交L中的文件集以生成出现在提交R 中的文件集。换句话说,它的输出git diff
给出的指令将evl/v5.4
变为master
. 通常,这将包括以下path/to/file.ext
形式的说明:在出现在此上下文中的 的第 45 行之后添加以下三行或删除some/file
出现在以下上下文中的以下行中的一行。
所述上下文是什么在大号和指令(如果和当施加)产生什么在ř。
git checkout master
这将获得提交R。你没有犯大号了。将L更改为R的说明在这里毫无意义。
您可以反向应用补丁。毕竟,将L变成R 的指令可以“向后跟踪”,就像将R变成L 一样。嗯,也就是说,只要没有任何指令只是删除文件 F ,因为这需要创建一个新文件F。如果说明说删除内容为 ... 的文件 F,我们可以使用它来创建新文件F。
如何...从某些分支中提取提交并将 [它们] 应用到其他分支
提交是快照,而不是一组更改。但它不仅仅是一个快照:它是一个快照加上一些关于快照的信息。此元数据或有关数据的额外信息(快照即数据)包括进行提交的人员的姓名和电子邮件地址。它包括一些日期和时间戳。它包括一条日志消息,这几乎是任意的,取决于提交的人。但重要的是Git的,它也包括了原始的哈希标识某组的较早的提交。
Git 通过哈希 ID 找到每个提交。哈希 ID 本质上是提交的“真实名称”。提交的哈希 ID 永远不会改变,提交本身的内容也永远不会改变。(Git 通过将其每个内部对象存储在键值数据库中的方式来确保这两者,其中键是哈希 ID,而哈希 ID 是对该键下存储的内容的加密校验和。)
一个分支的名字简单地持有的哈希ID最后在提交的一些连锁提交。链条可以非常简单和线性,而且很多都是。如果我们使用大写字母代表哈希 ID,我们会得到如下图:
... <-F <-G <-H
其中最后一次提交是最右边的一次,即 commit H
。此提交包含数据(每个文件的完整快照)和元数据:谁制作,何时,为什么,以及较早提交的哈希 IDG
。
我们选择一个我们想用来find 的分支名称H
,并让 GitH
在该名称中存储提交的实际哈希 ID :
...--F--G--H <-- master
我已经停止将提交之间的向后箭头绘制为箭头,但它们确实是一种从每次提交中出来的箭头。只是,随着提交内容一直冻结,H
将永远指向G
,并且由于我们知道提交哈希 ID 看起来是随机的,G
无法知道其未来父级H
的哈希 ID 是什么,因此连接必须进行向后。
给定 name master
,然后,我们让 GitH
通过其哈希 ID(存储在 name 中master
)查找提交。给定 commit H
,我们可以让 Git findG
的哈希 ID:这是 .gitignore 中元数据的一部分H
。给定G
的哈希 ID,我们可以让 Git 找到 commit G
。所以,一旦我们找到了最后一次提交,我们就可以返回一跳,到倒数第二次提交。
当然,该提交也嵌入了一个哈希 ID。从G
,我们可以跳回到F
。只要箭头继续前进,我们就可以保持这种状态,一直回到有史以来的第一次提交。(作为有史以来的第一次提交,它没有向后箭头,这就是我们/Git 知道停止返回的方式。)
这意味着存储库中的提交是存储库中的历史记录。历史不过是承诺。提交全部连接,向后。存储库只是提交的集合,名称(分支名称或任何其他名称)只是为我们提供了进入提交的方法。
要向此存储库添加新提交,我们检查现有提交H
:
...--G--H <-- master (HEAD)
它创建master
当前分支并提交H
当前提交,我们可以通过使用特殊名称找到所有这些HEAD
,现在附加到名称master
。
然后,我们进行一些更改一些文件,其实不是在Git的。(Git 中的文件无法更改。)我们让 Git 将这些文件复制到新的提交中,添加一些元数据——包括姓名和电子邮件地址,以及“现在”作为作者和提交者的时间戳,例如实例——并将其全部散列并获得一个新的、唯一的散列 ID。(时间戳有助于确保此提交获得全新的哈希 ID,即使其他所有内容都相同,但通常新提交中的数据与前一次提交中的数据不同......而且,此外,父哈希 ID 将不匹配。但时间也不匹配。)我们新提交的父项将是 commitH
. Git 现在可以写出所有数据和元数据,从而进行新的提交。我们将调用它的大而丑陋的随机哈希 ID I
,并将其绘制出来,并指向H
:
...--F--G--H
\
I
现在来了一个狡猾的技巧:Git 只是将I
的哈希 ID 写入name master
,特殊名称HEAD
附加到该名称中。所以我们I
毕竟不需要自己画一条线:
...--F--G--H--I <-- master
任何现有提交中的任何内容都没有改变。新提交I
是最后一个,它指向H
. 该分支名称改变,或者说,存储的散列ID在分部名称改变。该名称指向最后一次提交——实际上,根据定义。如果我们强制 Git 将名称指向 commit H
, commitI
只会从视图中消失:它仍然存在,但我们再也找不到它了,除非我们将其哈希 ID 保存在某处。
现在,无论发生什么,我们都有这些图形之一,分支名称指向每个链中的最后一次提交。所以如果我们有,说:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2
那么最后一次提交branch2
是L
,最后一次提交branch1
是J
,最后一次提交master
是H
。提交H
实际上是在所有三个分支上,因为在 Git 中,“在一个分支上”的概念只是意味着我们可以从最后开始——就像 Git 所做的那样,向后——然后向后工作以达到给定的提交。从L
我们可以跳到K
,然后H
,让犯H
上branch2
。或者,使用 name master
,我们从 开始H
,所以提交H
是 on master
。
同时,如果我们采用任何父/子对——比如K-L
,就像它出现的那样——branch2
我们可以让 Git比较这些快照。对于所有相同的文件,Git 什么也没说。要更改指令K
到L
该文件是什么也不做。对于每个不同的文件,Git 会显示一些指令;这些告诉我们如何更改文件的显示方式K
,使其显示在L
.
如果我们喜欢,我们可以git checkout branch1
:
I--J <-- branch1 (HEAD)
/
...--G--H <-- master
\
K--L <-- branch2
现在,作为我们可以处理的常规文件,我们拥有J
. Git 基本上将所有文件从commit复制J
到工作区。
该改变的指示的程度K
来L
申请,我们可以让Git来应用这些指令。我们可以通过找到提交K
和的两个哈希 IDL
并运行来做到这一点:
git diff <hash-of-K> <hash-of-L>
获取这些说明。然后我们可以尝试在我们现在检出的文件上使用这些说明。它们可能不会全部工作,因为可能有些文件不见了,或者我们应该更改第 42 行的某些文件不再有该行。但是我们可以尝试应用这些更改。
要在 Git 中自动执行此操作,我们不必使用git diff
和git patch
。相反,我们可以使用git cherry-pick
. 这实际上相当漂亮,因为cherry-pick 使用Git 的内部合并机制来合并更改。但是,就目前而言,您可以将cherry-pick 视为比较父项和子项,找出差异,然后将差异应用于我们现在的任何提交。
因为 Git 有图,并且 commitK
连接(向后)到 commit J
,我们只需要告诉 Git 挑选 commit 的哈希 ID K
:
git cherry-pick <hash-of-K>
有一些更简单、更短的方法来指定特定的提交,不需要输入整个哈希 ID。当然,没有人会首先尝试输入完整的哈希 ID:我们使用剪切和粘贴来复制哈希 ID。打错字太容易了(不过,幸运的是,哈希 ID 足够稀疏,这只会导致 Git 说whaddaya talkin''bout?!)。但我不会在这里讨论。这已经足够了。
[编辑,2021 年 1 月 2 日] 克隆问题中的存储库后,我可以运行以下命令。请注意,当前分支是master
并且工作树最初没有未跟踪的文件。A 不git clean -dfx
产生任何输出。--index
与git apply
下面一起使用很重要;稍后我将解释原因。
$ git diff --no-renames master evl/v5.4 > ../patchfile
$ git apply --index < ../patchfile
<stdin>:18659: space before tab in indent.
int data;
<stdin>:18660: space before tab in indent.
/* Other data fields */
<stdin>:29742: space before tab in indent.
apq8016
<stdin>:29743: space before tab in indent.
apq8074
<stdin>:29744: space before tab in indent.
apq8084
warning: squelched 352 whitespace errors
warning: 357 lines add whitespace errors.
$ git status | head
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: .clang-format
modified: .gitattributes
modified: .gitignore
modified: .mailmap
modified: COPYING
$ git checkout -b tmp && git commit -q -m apply
Switched to a new branch 'tmp'
$ git diff evl/v5.4 tmp
$
正如你所看到的,这个差异(我交换了顺序),应用--index
(使用-3
或--3way
会像他们设置--index
选项一样工作)就足够了。
--index
需要的原因——无论是明确的还是暗示的——是补丁本身创建的文件列在.gitignore
文件中。具体来说,这些tools/perf/lib/include/perf/*
文件都被忽略了。然而,这些文件在 的尖端提交evl/v5.4
,因此作为新文件在差异中。因此,当 Git 应用差异时,它会创建这些文件。
如果您在没有 的情况下 应用差异--index
,Git 会将差异应用于您的工作树(仅限)。然后您必须使用git add
来添加更新的文件。但是由于新创建的文件列在 a 中.gitignore
,如果单独添加它们,它们将被忽略。整个tools/perf/lib/include/perf/
目录不存在,master
因此当前检出提交的索引中没有此类文件。这些文件是在在尖端提交evl/v5.4
,所以如果你运行git checkout evl/v5.4
,他们在Git的指数拉闸:一个git checkout
将所有从选定的文件提交到索引,即使这些文件被忽略名义上。但是我们的git apply
方法没有除非我们使用--index
,否则将那些(新)文件复制到索引中,然后后续git add
* 服从新创建的tools/perf/.gitignore
文件:
$ cat -n tools/perf/.gitignore
1 PERF-CFLAGS
2 PERF-GUI-VARS
3 PERF-VERSION-FILE
4 FEATURE-DUMP
5 perf
6 perf-read-vdso32
7 perf-read-vdsox32
8 perf-help
9 perf-record
10 perf-report
11 perf-stat
12 perf-top
13 perf*.1
14 perf*.xml
15 perf*.html
16 common-cmds.h
17 perf.data
18 perf.data.old
19 output.svg
20 perf-archive
21 perf-with-kcore
22 tags
23 TAGS
24 cscope*
25 config.mak
26 config.mak.autogen
27 *-bison.*
28 *-flex.*
29 *.pyc
30 *.pyo
31 .config-detected
32 util/intel-pt-decoder/inat-tables.c
33 arch/*/include/generated/
34 trace/beauty/generated/
35 pmu-events/pmu-events.c
36 pmu-events/jevents
37 feature/
38 fixdep
39 libtraceevent-dynamic-list
第 5 行告诉 Git 忽略tools/perf/lib/perf
. 所以git add .
忽略它们,新的提交与evl/v5.4
.
我们可以换一种说法:您可以创建一个提交,其文件不会被提交接受。例如,任何顶级目录包含.gitignore
带行*
的提交都不会添加提交中的任何文件。然而,该提交将包含它所包含的文件,检查它会让您提交这些文件。只是将这些文件提取到一个原本为空的存储库中,然后使用git add
, 不会进行存储相同树的提交。您将获得的提交是路径相关的。
我认为这些.gitignore
文件至少是可疑的,并且通常是错误的,尽管有些人认为这很好(因为您可以使用git add -f
覆盖忽略,或暂时将.gitignore
文件移开,或其他)。这个特定的linux-evl
提交就是这样的一个提交,一开始它把我们俩都绊倒了。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句