如何真正应用使用 Git diff 创建的补丁?
How do I really apply a patch created with Git diff?
我已经在这个网站上阅读了很多 related/similar 个问题,但其中 none 个问题是可行的,而且我似乎没有遇到同样的错误,所以我决定在此打开一个新问题。
我正在尝试学习更多 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,用于“左”和“右”,尽管没有通用的 agreed-upon 命名这里的约定。
对于 L (left-side) 提交,您选择 evl/v5.4
选择的提交。对于 R (right-side) 提交,您选择了 master
选择的提交。到目前为止没问题。
现在,请记住 git diff
的输出是一系列指令。如果应用这些指令,将更改出现在提交 L 中的文件集,以生成出现在提交 R 中的文件集。换句话说,此 git diff
的输出给出了将 evl/v5.4
更改为 master
的指令。通常,这将包括形式为 在 path/to/file.ext
的第 45 行之后添加以下三行的说明,这些说明出现在此上下文中 或 删除一行some/file
的以下几行出现在以下上下文中 .
上下文是L中的内容,说明(如果应用) 生成 R.
中的内容
git checkout master
这将获得提交 R。您没有提交 L。将 L 更改为 R 的说明在这里毫无意义。
你可以reverse-apply补丁。毕竟,将 L 变为 R 的指令可以“向后跟随”,可以说是将 R 变成 L。嗯,也就是说,只要none的指令只是删除文件F,因为那需要创建一个新文件F。如果指令说删除内容为...的文件F,我们可以用它来创建新文件F.
关于这个主题的变体...
how to ... extract commits from some branches and apply [them] to other branches
一次提交是一个快照,而不是一组更改。但这不仅仅是只是一个快照:它是一个快照加上一些关于快照的信息。此 元数据 或额外信息 关于 数据(快照即数据)包括提交人的姓名和电子邮件地址。它包括一些 date-and-time-stamps。它包括一条日志消息,这几乎是任意的,取决于提交的人。但对于 Git 来说重要的是,它 还 包括原始的 散列 ID 一些 更早的 ] 提交。
Git 通过哈希 ID 查找每个提交。哈希 ID 本质上是提交的“真实名称”。提交的哈希 ID 永远不会改变,提交本身的内容也永远不会改变。 (Git 通过将其每个内部对象存储在 key-value database 中来确保这两者,其中密钥是哈希 ID,而哈希 ID 是存储在该密钥下的内容的加密校验和.)
A branch name 简单地保存一些提交链中 last 提交的哈希 ID。链条可以非常简单和线性,而且很多都是。如果我们用大写字母来代表哈希ID,我们会得到这样一张图:
... <-F <-G <-H
其中 last 提交是 right-most 提交,即提交 H
。此提交包含数据(每个文件的完整快照)和元数据:创建者、时间和原因,以及 早期提交的哈希 ID G
.
我们选择一个我们想用来 find H
的分支名称,并让 Git 存储提交的实际哈希 ID H
在那个名字中:
...--F--G--H <-- master
我已经停止绘制 backwards-pointing 箭头 between 提交 as 箭头,但它们确实是一种箭头从每次提交中出来。只是,随着提交内容一直被冻结,H
将永远指向 G
,并且由于我们知道提交哈希 ID 看起来是随机的,所以 G
不知道是什么它未来的父 H
的哈希 ID 将是,因此连接 必须 向后。
给定名称 master
,然后,我们 Git 通过哈希 ID(存储在名称 master
中)找到提交 H
。给定提交 H
,我们可以让 Git 找到 G
的哈希 ID:这是 H
中元数据的一部分。给定 G
的哈希 ID,我们可以 Git 找到提交 G
。因此,一旦我们找到了 last 提交,我们就可以返回一跳,到 second-to-last 提交。
当然,该提交也嵌入了一个哈希 ID。从 G
,我们可以跳回到 F
。只要箭头继续前进,我们就可以保持这种状态,一直回到有史以来的第一次提交。 (作为第有史以来第一次提交,它没有 backwards-pointing 箭头,这就是我们/Git 知道停止返回的方式。)
这意味着存储库中的提交是存储库中的历史记录。历史不过是承诺。提交全部连接,向后。存储库只是提交的集合,名称——分支名称,或任何其他名称——只是给我们一种进入提交的方式。
要向此存储库添加 新 提交,我们检查现有提交 H
:
...--G--H <-- master (HEAD)
这使得 master
成为当前的 分支 并提交 H
当前的 commit,所有这些我们可以使用特殊名称 HEAD
查找,该名称现在附加到名称 master
.
然后,我们对一些实际上不在 Git 中的文件进行一些更改。 (Git 中的文件无法更改。)我们已 Git 将这些文件复制到新的提交中,添加一些元数据——包括姓名和电子邮件地址,以及“现在”作为例如,作者和提交者的时间戳——然后将其全部散列并获得一个新的、唯一的散列 ID。 (时间戳有助于确保此提交获得一个全新的哈希 ID,即使 其他所有内容 都相同,但通常新提交中的数据与新提交中的数据不同之前的提交 ... 而且,父哈希 ID 将不匹配。但时间也不匹配。)我们新提交的 parent 将是 commit H
。 Git 现在可以写出所有数据和元数据,从而进行新的提交。我们将它的大丑 random-looking 哈希 ID 称为 I
,并将其绘制进来,指向 H
:
...--F--G--H
\
I
偷偷摸摸的把戏来了:Git 简单地将 I
的哈希 ID 写入 name master
,特殊的已附加名称 HEAD
。所以我们毕竟不需要在自己的线上绘制 I
:
...--F--G--H--I <-- master
现有提交中的任何内容均未更改。 New commit I
是 last 一个,它指向 H
。 分支名称 已更改,或者更确切地说,存储在 中 分支名称的哈希 ID 已更改。该名称指向最后一次提交——实际上是根据定义。如果我们强制 Git 将名称指向提交 H
,提交 I
就会从视图中消失:它仍然存在,但我们无法再找到它,除非我们保存它某处的哈希 ID。
现在,无论发生什么,我们都有这些图形事物之一,分支名称指向每个链中的最后一次提交。所以如果我们有,说:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2
那么 branch2
上的 last 提交是 L
,[=68= 上的 last 提交] 是 J
, 最后 在 master
上的提交是 H
。提交 H
实际上是在 所有三个 分支上,因为在 Git 中,“在一个分支上”的概念只是意味着我们可以从最后开始—— Git 的方式,向后 - 并向后工作以达到给定的提交。从 L
,我们可以跳到 K
,然后跳到 H
,所以提交 H
在 branch2
。或者,使用名称 master
,我们从 H
开始,因此提交 H
在 master
.
同时,如果我们采用任何 parent/child 对——比如 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 基本上将所有文件 从 提交 J
复制到工作区。
在将 K
更改为 L
的说明适用的范围内,我们可以让 Git 应用这些说明。我们可以通过找到提交 K
和 L
以及 运行ning:
的两个哈希 ID 来完成此操作
git diff <hash-of-K> <hash-of-L>
获取这些说明。然后我们可以尝试在我们现在签出的文件上使用这些指令。 它们可能无法正常工作,因为可能有些文件不见了,或者我们应该更改第 42 行的某些文件不再有该行。但我们可以尝试应用这些更改。
要在 Git 中自动执行此操作,我们不必使用 git diff
和 git patch
。相反,我们可以使用 git cherry-pick
。这实际上相当奇妙,因为 cherry-pick 使用 Git 的内部 合并机制 来 合并 变化。但是,现在,您可以将 cherry-pick 视为 比较父项和子项,找出差异,并将差异应用于我们现在的任何提交.
因为Git有图表,提交K
连接(向后)提交J
,我们只需要告诉Git到cherry-pick提交的哈希 ID K
:
git cherry-pick <hash-of-K>
有一些更简单、更短的指定特定提交的方法,不需要输入整个哈希 ID。当然,没有人会首先尝试输入整个哈希 ID:我们使用 cut-and-paste 来复制哈希 ID。打错东西太容易了(不过,幸运的是,哈希 ID 足够稀疏,这只会导致 Git 说 whaddaya talkin' bout?!)。但我不会在这里深入探讨;现在这些就够了。
[编辑,2021 年 1 月 2 日] 克隆问题中的存储库后,我可以 运行 以下操作。请注意,当前分支是 master
并且 work-tree 最初没有未跟踪的文件。 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
末尾的提交 中,因此在 diff 中作为新文件。因此,当 Git 应用差异时,它 创建 这些文件。
如果您应用差异 而没有 --index
,Git 将差异应用于您的 work-tree(仅)。然后您必须使用 git add
添加更新的文件。但是由于新创建的文件列在 .gitignore
中,如果您单独添加它们,它们会 被忽略 。 master
中不存在整个 tools/perf/lib/include/perf/
目录,因此 currently-checked-out 提交的索引中没有此类文件。那些文件 是 在 evl/v5.4
的提交中,所以如果你 运行 git checkout evl/v5.4
,它们会在 Git' s 索引:a 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
和行 *
的提交都不会添加提交中的任何文件。然而,该提交将包含它包含的文件,并且检查它将使您提交这些文件。只是将这些文件提取到 otherwise-empty 存储库,然后使用 git add
,不会进行存储同一树的提交。您将获得的提交是 path-dependent.
我认为这样的 .gitignore
文件至少是可疑的,而且一般来说是错误的,尽管有些人认为它很好(因为你可以使用 git add -f
来覆盖忽略,或者暂时移动 .gitignore
将文件移开,或其他)。这个特定的 linux-evl
提交就是这样的一个提交,它一开始就把我们俩都绊倒了。
我已经在这个网站上阅读了很多 related/similar 个问题,但其中 none 个问题是可行的,而且我似乎没有遇到同样的错误,所以我决定在此打开一个新问题。
我正在尝试学习更多 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,用于“左”和“右”,尽管没有通用的 agreed-upon 命名这里的约定。
对于 L (left-side) 提交,您选择 evl/v5.4
选择的提交。对于 R (right-side) 提交,您选择了 master
选择的提交。到目前为止没问题。
现在,请记住 git diff
的输出是一系列指令。如果应用这些指令,将更改出现在提交 L 中的文件集,以生成出现在提交 R 中的文件集。换句话说,此 git diff
的输出给出了将 evl/v5.4
更改为 master
的指令。通常,这将包括形式为 在 path/to/file.ext
的第 45 行之后添加以下三行的说明,这些说明出现在此上下文中 或 删除一行some/file
的以下几行出现在以下上下文中 .
上下文是L中的内容,说明(如果应用) 生成 R.
中的内容git checkout master
这将获得提交 R。您没有提交 L。将 L 更改为 R 的说明在这里毫无意义。
你可以reverse-apply补丁。毕竟,将 L 变为 R 的指令可以“向后跟随”,可以说是将 R 变成 L。嗯,也就是说,只要none的指令只是删除文件F,因为那需要创建一个新文件F。如果指令说删除内容为...的文件F,我们可以用它来创建新文件F.
关于这个主题的变体...
how to ... extract commits from some branches and apply [them] to other branches
一次提交是一个快照,而不是一组更改。但这不仅仅是只是一个快照:它是一个快照加上一些关于快照的信息。此 元数据 或额外信息 关于 数据(快照即数据)包括提交人的姓名和电子邮件地址。它包括一些 date-and-time-stamps。它包括一条日志消息,这几乎是任意的,取决于提交的人。但对于 Git 来说重要的是,它 还 包括原始的 散列 ID 一些 更早的 ] 提交。
Git 通过哈希 ID 查找每个提交。哈希 ID 本质上是提交的“真实名称”。提交的哈希 ID 永远不会改变,提交本身的内容也永远不会改变。 (Git 通过将其每个内部对象存储在 key-value database 中来确保这两者,其中密钥是哈希 ID,而哈希 ID 是存储在该密钥下的内容的加密校验和.)
A branch name 简单地保存一些提交链中 last 提交的哈希 ID。链条可以非常简单和线性,而且很多都是。如果我们用大写字母来代表哈希ID,我们会得到这样一张图:
... <-F <-G <-H
其中 last 提交是 right-most 提交,即提交 H
。此提交包含数据(每个文件的完整快照)和元数据:创建者、时间和原因,以及 早期提交的哈希 ID G
.
我们选择一个我们想用来 find H
的分支名称,并让 Git 存储提交的实际哈希 ID H
在那个名字中:
...--F--G--H <-- master
我已经停止绘制 backwards-pointing 箭头 between 提交 as 箭头,但它们确实是一种箭头从每次提交中出来。只是,随着提交内容一直被冻结,H
将永远指向 G
,并且由于我们知道提交哈希 ID 看起来是随机的,所以 G
不知道是什么它未来的父 H
的哈希 ID 将是,因此连接 必须 向后。
给定名称 master
,然后,我们 Git 通过哈希 ID(存储在名称 master
中)找到提交 H
。给定提交 H
,我们可以让 Git 找到 G
的哈希 ID:这是 H
中元数据的一部分。给定 G
的哈希 ID,我们可以 Git 找到提交 G
。因此,一旦我们找到了 last 提交,我们就可以返回一跳,到 second-to-last 提交。
当然,该提交也嵌入了一个哈希 ID。从 G
,我们可以跳回到 F
。只要箭头继续前进,我们就可以保持这种状态,一直回到有史以来的第一次提交。 (作为第有史以来第一次提交,它没有 backwards-pointing 箭头,这就是我们/Git 知道停止返回的方式。)
这意味着存储库中的提交是存储库中的历史记录。历史不过是承诺。提交全部连接,向后。存储库只是提交的集合,名称——分支名称,或任何其他名称——只是给我们一种进入提交的方式。
要向此存储库添加 新 提交,我们检查现有提交 H
:
...--G--H <-- master (HEAD)
这使得 master
成为当前的 分支 并提交 H
当前的 commit,所有这些我们可以使用特殊名称 HEAD
查找,该名称现在附加到名称 master
.
然后,我们对一些实际上不在 Git 中的文件进行一些更改。 (Git 中的文件无法更改。)我们已 Git 将这些文件复制到新的提交中,添加一些元数据——包括姓名和电子邮件地址,以及“现在”作为例如,作者和提交者的时间戳——然后将其全部散列并获得一个新的、唯一的散列 ID。 (时间戳有助于确保此提交获得一个全新的哈希 ID,即使 其他所有内容 都相同,但通常新提交中的数据与新提交中的数据不同之前的提交 ... 而且,父哈希 ID 将不匹配。但时间也不匹配。)我们新提交的 parent 将是 commit H
。 Git 现在可以写出所有数据和元数据,从而进行新的提交。我们将它的大丑 random-looking 哈希 ID 称为 I
,并将其绘制进来,指向 H
:
...--F--G--H
\
I
偷偷摸摸的把戏来了:Git 简单地将 I
的哈希 ID 写入 name master
,特殊的已附加名称 HEAD
。所以我们毕竟不需要在自己的线上绘制 I
:
...--F--G--H--I <-- master
现有提交中的任何内容均未更改。 New commit I
是 last 一个,它指向 H
。 分支名称 已更改,或者更确切地说,存储在 中 分支名称的哈希 ID 已更改。该名称指向最后一次提交——实际上是根据定义。如果我们强制 Git 将名称指向提交 H
,提交 I
就会从视图中消失:它仍然存在,但我们无法再找到它,除非我们保存它某处的哈希 ID。
现在,无论发生什么,我们都有这些图形事物之一,分支名称指向每个链中的最后一次提交。所以如果我们有,说:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2
那么 branch2
上的 last 提交是 L
,[=68= 上的 last 提交] 是 J
, 最后 在 master
上的提交是 H
。提交 H
实际上是在 所有三个 分支上,因为在 Git 中,“在一个分支上”的概念只是意味着我们可以从最后开始—— Git 的方式,向后 - 并向后工作以达到给定的提交。从 L
,我们可以跳到 K
,然后跳到 H
,所以提交 H
在 branch2
。或者,使用名称 master
,我们从 H
开始,因此提交 H
在 master
.
同时,如果我们采用任何 parent/child 对——比如 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 基本上将所有文件 从 提交 J
复制到工作区。
在将 K
更改为 L
的说明适用的范围内,我们可以让 Git 应用这些说明。我们可以通过找到提交 K
和 L
以及 运行ning:
git diff <hash-of-K> <hash-of-L>
获取这些说明。然后我们可以尝试在我们现在签出的文件上使用这些指令。 它们可能无法正常工作,因为可能有些文件不见了,或者我们应该更改第 42 行的某些文件不再有该行。但我们可以尝试应用这些更改。
要在 Git 中自动执行此操作,我们不必使用 git diff
和 git patch
。相反,我们可以使用 git cherry-pick
。这实际上相当奇妙,因为 cherry-pick 使用 Git 的内部 合并机制 来 合并 变化。但是,现在,您可以将 cherry-pick 视为 比较父项和子项,找出差异,并将差异应用于我们现在的任何提交.
因为Git有图表,提交K
连接(向后)提交J
,我们只需要告诉Git到cherry-pick提交的哈希 ID K
:
git cherry-pick <hash-of-K>
有一些更简单、更短的指定特定提交的方法,不需要输入整个哈希 ID。当然,没有人会首先尝试输入整个哈希 ID:我们使用 cut-and-paste 来复制哈希 ID。打错东西太容易了(不过,幸运的是,哈希 ID 足够稀疏,这只会导致 Git 说 whaddaya talkin' bout?!)。但我不会在这里深入探讨;现在这些就够了。
[编辑,2021 年 1 月 2 日] 克隆问题中的存储库后,我可以 运行 以下操作。请注意,当前分支是 master
并且 work-tree 最初没有未跟踪的文件。 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
末尾的提交 中,因此在 diff 中作为新文件。因此,当 Git 应用差异时,它 创建 这些文件。
如果您应用差异 而没有 --index
,Git 将差异应用于您的 work-tree(仅)。然后您必须使用 git add
添加更新的文件。但是由于新创建的文件列在 .gitignore
中,如果您单独添加它们,它们会 被忽略 。 master
中不存在整个 tools/perf/lib/include/perf/
目录,因此 currently-checked-out 提交的索引中没有此类文件。那些文件 是 在 evl/v5.4
的提交中,所以如果你 运行 git checkout evl/v5.4
,它们会在 Git' s 索引:a 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
和行 *
的提交都不会添加提交中的任何文件。然而,该提交将包含它包含的文件,并且检查它将使您提交这些文件。只是将这些文件提取到 otherwise-empty 存储库,然后使用 git add
,不会进行存储同一树的提交。您将获得的提交是 path-dependent.
我认为这样的 .gitignore
文件至少是可疑的,而且一般来说是错误的,尽管有些人认为它很好(因为你可以使用 git add -f
来覆盖忽略,或者暂时移动 .gitignore
将文件移开,或其他)。这个特定的 linux-evl
提交就是这样的一个提交,它一开始就把我们俩都绊倒了。