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

莫尔普

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

我正在尝试学习更多的 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比较两次提交,或者更准确地说,比较两次提交中的快照。我喜欢将这两个提交称为LR,分别代表“左”和“右”,尽管这里没有共同商定的命名约定。

对于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

那么最后一次提交branch2L最后一次提交branch1J最后一次提交masterH提交H实际上是在所有三个分支上,因为在 Git 中,“在一个分支上”的概念只是意味着我们可以从最后开始——就像 Git 所做的那样,向后——然后向后工作以达到给定的提交。L我们可以跳到K,然后H,让犯Hbranch2或者,使用 name master,我们从 开始H,所以提交H是 on master

同时,如果我们采用任何父/子对——比如K-L,就像它出现的那样——branch2我们可以让 Git比较这些快照。对于所有相同的文件,Git 什么也没说。要更改指令KL 该文件什么也不做对于每个不同的文件,Git 会显示一些指令;这些告诉我们如何更改文件的显示方式K,使其显示在L.

如果我们喜欢,我们可以git checkout branch1

          I--J   <-- branch1 (HEAD)
         /
...--G--H   <-- master
         \
          K--L   <-- branch2

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

该改变的指示的程度KL申请,我们可以让Git来应用这些指令。我们可以通过找到提交K的两个哈希 IDL并运行来做到这一点

git diff <hash-of-K> <hash-of-L>

获取这些说明。然后我们可以尝试在我们现在检出的文件上使用这些说明。它们可能不会全部工作,因为可能有些文件不见了,或者我们应该更改第 42 行的某些文件不再有该行。但是我们可以尝试应用这些更改。

要在 Git 中自动执行此操作,我们不必使用git diffgit 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产生任何输出。--indexgit 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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章