如何真正应用使用 Git diff 创建的补丁?

Möl*_*ölp 1 git patch

我一直在这个网站上阅读很多相关/类似的问题,但它们都不起作用,而且我似乎没有看到相同类型的错误,所以我决定就此提出一个新问题。

我正在尝试学习更多关于 git 的知识,特别是如何应用补丁并从某些分支中提取提交并将其应用到其他分支。我最初想做一个虚拟测试,其中包括从分支中选择一些提交(直到过去的某个时间点)并将这些提交重新应用到过去的同一时间点,以使我回到初始点。

但是,我收到了大量此类“错误:补丁不适用”的错误消息。

我不明白为什么它不起作用。我尝试添加诸如 --whitespace=fix 等选项(在本网站的其他问题中建议),但无济于事。我还尝试使用 -3 来希望我可以手动合并文件,但这只是将错误消息更改为“错误:补丁失败:文件名”,几乎所有文件都再次出现。


为了重现此错误,我使用以下 git 存储库: https: //git.evlproject.org/linux-evl.git

具体来说,有提交的分支是evl/v5.4,没有提交的分支是master。我当时尝试过:

git diff evl/v5.4 master > ../patchfile
git checkout master
git apply ../patchile
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 5

如果有这样的补丁,那就有点令人惊讶了

\n
\n
git diff evl/v5.4 master > ../patchfile\n
Run Code Online (Sandbox Code Playgroud)\n
\n

请记住,git diff比较两个提交,或更准确地说,比较两个提交中的快照。我喜欢将这两个提交称为LR,分别表示“左”和“右”,尽管这里没有共同商定的命名约定。

\n

对于L(左侧)提交,您可以选择要evl/v5.4选择的提交。对于R(右侧)提交,您选择了以下提交master选择的提交。到目前为止这还没有问题。

\n

现在,请记住,输出git diff是一系列指令。如果应用这些指令,将更改提交L中出现的文件集,以生成提交R中出现的文件集。换句话说,它的输出git diff给出的指令将evl/v5.4变为master。一般来说,这将包括以下形式的指令:在 的第 45 行之后添加以下三行path/to/file.ext(出现在此上下文中)删除 的以下行中的一行some/file(出现在以下上下文中))。

\n

上下文是L中的内容,并且指令(如果以及当应用时)产生R中的内容中的内容。

\n
\n
git checkout master\n
Run Code Online (Sandbox Code Playgroud)\n
\n

这获得了提交R。你没有提交L将L更改为R的说明在这里毫无意义。

\n

您可以反向应用该补丁。毕竟,将L转换为R 的指令可以“向后执行”,将R转换为L。好吧,也就是说,只要没有任何指令只是简单地删除文件 F,因为这需要创建一个新文件F。如果指令说删除内容为 ... 的文件 F,我们可以使用它来创建新文件F

\n

关于这个主题的一个变体......

\n
\n

如何...从某些分支中提取提交并将[它们]应用到其他分支

\n
\n

提交一个快照,而不是一组更改。但它不仅仅是一个快照:它是一个快照加上有关该快照的一些信息。此元数据有关数据\xe2\x80\x94(即数据\xe2\x80\x94 的快照)的额外信息包括进行提交的人员的姓名和电子邮件地址。它包括一些日期和时间戳。它包含一条日志消息,该消息几乎是任意的,并且取决于提交的人。但对于 Git 来说重要的是,它还包括一些早期版本的原始哈希 ID 。

\n

Git 通过哈希 ID 查找每个提交。哈希 ID 本质上是提交的“真实名称”。提交的哈希 ID 永远不会改变,提交本身的内容也永远不会改变。(Git 通过将每个内部对象存储在键值数据库中的方式来确保这两个目标中来确保这两个目的,其中键是哈希 ID,哈希 ID 是该键下存储的内容的加密校验和。)

\n

分支名称仅保存最后一个分支的哈希 ID仅保存某些提交链中链条可以非常简单且线性,而且很多都是如此。如果我们使用大写字母来代表哈希 ID,我们会得到如下图所示的图:

\n
... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n

其中最后一次提交是最右边的提交,即 commit H。此提交包含数据(每个文件的完整快照)和元数据:谁创建的、何时创建、为什么创建,以及早期提交的哈希 IDG

\n

我们选择一个要用于find 的 H分支名称,并让 Git 存储提交的实际哈希 IDH在该名称中:

\n
...--F--G--H   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n

我已经停止将提交之间的向后箭头绘制箭头,但它们确实是每次提交中出现的一种箭头。只是,随着提交内容一直冻结,H将永远指向G,并且由于我们知道提交哈希 ID 看起来是随机的,因此G无法知道其未来父级的H哈希 ID 是什么,所以连接必须向后进行。

\n

给定 name master,然后,我们让 GitH通过其哈希 ID(存储在 name 中master)查找提交。给定 commit H,我们可以让 Git 找到G哈希 ID:这是H. 给定G\ 的哈希 ID,我们可以让 Git 找到 commit G。因此,一旦找到最后一次提交,我们就可以回溯到倒数第二次提交。

\n

当然,该提交也嵌入了一个哈希 ID。从G,我们可以跳回到F。只要箭头继续前进,我们就可以保持这种状态,一直回到第一次提交。(作为有史以来的第一次提交,它没有向后的箭头,这就是我们/Git 知道停止返回的方式。)

\n

这意味着存储库中的提交存储库中的历史记录。历史只不过是承诺。提交全部向后连接。存储库只是提交和名称的集合\xe2\x80\x94 分支名称或任何其他名称 \xe2\x80\x94 只是为我们提供了进入提交的方法。

\n

要向此存储库添加提交,我们检查现有提交H

\n
...--G--H   <-- master (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

这使得master当前分支并提交H当前提交,所有这些我们都可以通过使用特殊名称找到HEAD,该名称现在附加到名称上master特殊名称来找到。

\n

然后,我们对一些实际上不在Git中的文件进行一些更改。(Git 中的文件无法更改。)我们让 Git 将这些文件复制到新的提交中,添加一些元数据\xe2\x80\x94,包括姓名和电子邮件地址,以及“现在”作为作者和提交者时间戳,例如\xe2\x80\x94,并将其全部散列并获得一个新的、唯一的散列 ID。(时间戳有助于确保此提交获得全新的哈希 ID,即使其他所有内容都相同,但通常新提交中的数据与前一个提交中的数据不同......而且, ,父哈希 ID 不会匹配。但时间也不会匹配。)我们新提交的父提交将是 commit H。Git 现在可以写出所有数据和元数据,从而进行新的提交。我们将其称为又大又难看的随机哈希 ID I,并将其绘制出来,并指向H

\n
...--F--G--H\n            \\\n             I\n
Run Code Online (Sandbox Code Playgroud)\n

现在出现了一个偷偷摸摸的技巧:Git 只是将I\ 的哈希 ID 写入name master,并附加了特殊名称HEAD。所以我们毕竟不需要画I一条自己的线:

\n
...--F--G--H--I   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n

任何现有提交中都没有发生任何更改。 提交I最后一个提交,它指向H. 分支名称发生了变化,或者更确切地说,分支名称存储的哈希ID发生了变化。实际上,根据定义,该名称指向最后一次提交\xe2\x80\x94。如果我们强制 Git 将名称指向 commit H,则 commitI就会从视图中消失:它仍然存在,但我们无法再找到它,除非我们将其哈希 ID 保存在某处。

\n

现在,无论发生什么,我们都有这些图形事物之一,分支名称指向每个链中的最后一次提交。因此,如果有的话,请说:

\n
          I--J   <-- branch1\n         /\n...--G--H   <-- master\n         \\\n          K--L   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n

那么最后一次提交branch2L最后一次提交branch1J最后一次提交masterH。提交H实际上是在所有三个分支上,因为在 Git 中,“在分支上”的概念只是意味着我们可以像 Git 那样从末尾\xe2\x80\x94 开始,向后\xe2\x80\x94 并向后工作到达到给定的提交。从L,我们可以跳到K,然后跳到H,因此提交H已开启branch2。或者,使用名称master,我们从 开始H,因此提交H为 on master

\n

同时,如果我们采用任何父/子对 \xe2\x80\x94 ,K-Lbranch2\xe2\x80\x94 上所示,我们可以让 Git比较这些快照。对于所有相同的文件,Git 根本不说任何内容。对该文件K进行更改的指令根本不执行任何操作。对于每个不同的文件,Git 会显示一些指令;这些告诉我们如何更改文件中出现的文件,使其成为出现在中的文件L KL

\n

如果我们愿意,我们可以git checkout branch1

\n
          I--J   <-- branch1 (HEAD)\n         /\n...--G--H   <-- master\n         \\\n          K--L   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们有了可以处理的常规文件,J. Git 基本上将所有文件commit复制J到工作区中。

\n

如果要更改的说明K适用L,我们可以让 Git 应用这些说明。我们可以通过查找提交K和的两个哈希 IDL并运行:

\n
git diff <hash-of-K> <hash-of-L>\n
Run Code Online (Sandbox Code Playgroud)\n

以获得这些说明。然后我们可以尝试对我们现在签出的文件使用这些说明。 它们可能无法全部工作,因为可能有些文件已经消失,或者我们应该更改第 42 行的某些文件不再有该行。但我们可以尝试应用这些更改。

\n

要在 Git 中自动执行此操作,我们不必使用git diffgit patch。相反,我们可以使用git cherry-pick. 这实际上相当奇特,因为cherry-pick 使用 Git 的内部合并机制组合更改。但是,现在,您可以将cherry-pick视为比较父级和子级,找到差异,并将差异应用于我们现在所做的任何提交

\n

因为 Git 有图,并且 commitK连接(向后)到 commit J,所以我们只需要告诉 Git 挑选 commit 的哈希 ID K

\n
git cherry-pick <hash-of-K>\n
Run Code Online (Sandbox Code Playgroud)\n

有一些更简单、更简短的方法来指定特定提交,这些方法不需要输入整个哈希 ID。当然,没有人会首先尝试输入整个哈希 ID:我们使用剪切和粘贴来复制哈希 ID。拼写错误太容易了(不过,幸运的是,哈希 ID 足够稀疏,这只会导致 Git 说whaddaya talkin\' \'bout?!)。但我不会在这里讨论这个问题;现在就足够了。

\n
\n

[编辑,2021 年 1 月 2 日]克隆问题中的存储库后,我可以运行以下命令。请注意,当前分支是master并且工作树最初没有未跟踪的文件。Agit clean -dfx不产生输出。--index与使用很重要git apply;我稍后会解释原因。

\n
git diff evl/v5.4 master > ../patchfile\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,这个差异(我交换了顺序),应用--index(使用-3--3way将工作以及他们设置选项--index)就足够了。

\n

之所以--index需要\xe2\x80\x94,无论是显式的还是隐含的\xe2\x80\x94,是因为补丁本身会创建文件中列出的文件.gitignore。具体来说,这些tools/perf/lib/include/perf/*文件都被忽略。然而,这些文件位于顶端的提交evl/v5.4中,因此在 diff 中作为新文件。因此,当 Git 应用差异时,它会创建这些文件。

\n

如果您在不 使用 的情况下应用 diff --index,Git 会将 diff 应用于您的工作树(仅)。然后您必须使用git add添加更新的文件。但由于新创建的文件列在 a 中.gitignore,因此如果单独添加它们,它们将被忽略。整个tools/perf/lib/include/perf/目录不存在,master因此当前签出提交的索引中不存在此类文件。这些文件位于顶端的提交中evl/v5.4,因此如果您运行git checkout evl/v5.4,它们最终会出现在 Git 的索引中:agit checkout将所选提交中的所有文件复制到索引,即使这些文件名义上被忽略。但是我们的git apply方法不会将这些(新)文件复制到索引中,除非我们使用--index,然后后续的git add* 服从新创建的tools/perf/.gitignore文件:

\n
git checkout master\n
Run Code Online (Sandbox Code Playgroud)\n

第 5 行告诉 Git 忽略tools/perf/lib/perf. 因此git add .忽略它们并且新提交与 的提示提交不匹配evl/v5.4

\n

我们可以换句话说:您可以创建一个提交,但该提交不会接受其文件。例如,顶级目录包含.gitignore带有该行的任何提交*都不会添加该提交中的任何文件。然而,该提交将包含它所包含的文件,并且检查它将使您获得包含这些文件的提交。只是将这些文件提取到一个空的存储库中,然后使用git add, 不会进行存储同一棵树的提交。您将获得的提交与路径相关。

\n

我认为这些.gitignore文件至少是可疑的,并且总体上是有缺陷的,尽管有些人认为它很好(因为您可以使用它git add -f来覆盖忽略,或者暂时将.gitignore文件移开,或者其他什么)。这个特别的linux-evl提交就是这样的一个提交,它一开始就让我们俩都绊倒了。

\n