如何将补丁应用到之前的版本?使用 git-apply 命令即可。

10

假设我有一个以下历史记录的分支:

A - B - C - D

在B和C之间,我修改了一堆文件,包括一个特定的文件,称为foo.txt。

然后,在修订D时,我修改了同一个文件foo.txt。

与此同时,我的一个朋友在B处拍摄了我的目录快照,并决定以某种方式调整foo.txt。 让我们称其为修订E。 如果它们是同一仓库的一部分,它们将如下所示:

A - B - C - D
     \
      E

但是,它们不是,因此仅在我的存储库中具有A - B - C - D,然后他向我发送了B和E之间差异的补丁。

由于我也混淆了foo.txt,因此无法直接将补丁应用于D,上下文的差异不匹配,期望我在或接近B处。

看起来我想创建我提到的假设存储库树,然后在此存储库中对D和E进行合并,以便按正确顺序播放我的更改C和D。

因此我的问题是:

  • 这是基于历史“基线”应用更改的最明智方法吗?
  • 我没有看到任何缺点吗?
  • 如何在git中干净地完成这个过程?
3个回答

9

您说的没错,您确实想利用git的合并能力。目前有几种可能性。最有可能发生的应该是合并,但是产生一个在D之上应用了修改版本的E 的方法也不错。让我们来谈谈如何做到这一点!

如果补丁包包含它所适用的blob(即git format-patch的输出),那么git am可以尝试进行三方合并!差异中的blob SHA1记录如下:

diff --git a/foo.txt b/foo.txt
index ca1df77..2c98844 100644

如果你已经掌握了这个技巧,那就太幸运了。只需要使用git am --3way <patch>即可。不过,git am确实需要类似电子邮件的头部信息,而这是由format-patch生成的。所以,如果补丁来自于git diff而不是git format-patch,你需要稍微修改一下。你可以自己添加头部信息:

From: Bobby Tables <bobby@drop.org>
Date: 2 Nov 2010
Subject: [PATCH] protect against injection attack

<original diff>
git am应该可以处理这个问题,如果你没有完全按照预期的方式得到它,你总是可以使用git commit --amend来修复它。(你也可以尝试使用git apply --build-fake-ancestor=foo.txt <patch>,但它真的不太友好。我认为欺骗git am更容易。)
如果补丁中不包含任何blob SHA1(即它是通过diff而不是git命令创建的),同样告诉你的朋友如何使用git,我相信你仍然可以把它搞定。你知道补丁应该应用于哪个版本的foo.txt!要从提交B中获取其SHA1,请使用:
git ls-tree <SHA1 of B>:<directory containing foo.txt>

这将是列出的一组blob之一。(我知道有一种直接的方法,但我现在想不起来了。)然后,您可以添加一个虚假的git diff头。假设它的哈希值为abcdef12:

diff --git a/foo.txt b/foo.txt
index abcdef12..

Git实际上只需要第一个哈希值; 尽管git diff输出将具有最终哈希和模式,但am不需要它,因此您可以省略它。(是的,我刚测试了一下。以前我从来没有这样做过!)

这将导致类似于A-B-C-D-E'的历史记录,其中E'是您朋友的补丁,但应用于D; 这是在DB和您朋友的补丁之间进行三方合并的结果。

最后,如果您不想深入研究任何内容,您可以像您所说的那样操作:

git checkout -b bobby <SHA1 of B>
# apply the patch
git commit --author="Bobby Tables <bobby@drop.org>"
git checkout master
git merge bobby
# or `git cherry-pick bobby` to grab the single commit and apply to master
# or `git rebase bobby master` to rebase C and D onto B

你介意帮我看一下这个关于 Git 合并的问题吗?内联链接。谢谢。 - Alexander Cska

1

我已经四处查看了——我发誓在git中一定有自动完成这个的方法,但现在你最好还是按照你说的去做。

假设你在B提交的ID为12345...

$ git checkout 12345
Note: checking out '12345....'.

You are in 'detached HEAD' state. 
[...]
$ git checkout -b friends_change
$ git apply < patchfile.patch
$ git status
[... something interesting ...]
$ git commit -m "Applying friend's patches to foo"
$ git checkout master
$ git merge friends_change

当然,如果您还没有向任何人发布历史记录,您也可以选择变基:

$ git rebase friends_change

这将创建一个像这样的树:

A - B - C - D (old master)
     \
      E - C' - D' (new master)

0

我曾经遇到过一个非常类似的问题,这些答案对我指明了正确的方向,非常有帮助。然而,在我的情况下,我真正想要的是:

A - B - E - C - D

首先,这些提交都没有被推送到任何地方,因此重新设置基础是安全的。如果提交已经被推送,因此有可能被分发,我不会尝试这样做。

以下是我执行此操作的方法(根据楔子中的变量适当替换):

git checkout -b temporary <B-sha1>^
git am <E-patchfile>
git log --oneline -1

现在将SHA1和提交消息复制粘贴。

git checkout <orig-branch>
git rebase -i <B-sha1>^

现在,在适当的位置(应该是第二行),添加这一行:
pick <E-sha1> <E-commit-msg>

保存,让rebase完成它的工作,像通常一样解决任何冲突。最后:

git branch -d temporary

由于分支删除不会使您的补丁提交悬空,因此您不需要使用 git branch -D,而使用 -d 也提供了一种很好的验证方式,以确保您没有搞砸什么。

希望这对其他处于类似情况的人有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接