我如何git-将修补程序应用于以前的修订版?

时间:2010-11-07 08:23:40

标签: git

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

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. p>

所以我的问题:

  • 这是根据历史“基线”应用更改的最合理方式吗?
  • 我有没有看到任何缺点?
  • 如何干净利落地完成git?

3 个答案:

答案 0 :(得分:8)

你确实想要利用git的合并能力是对的。还有一些可能性。最实际发生的事情可能是合并,但导致在E之上应用D的修改版本的方法也不是那么糟糕。我们来谈谈如何做到这一点!

如果补丁包含它应用的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确实需要格式修补程序生成的电子邮件样式标头,因此如果修补程序来自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,我很确定你仍然< / em> kludge它。你知道补丁应该适用于哪个版本的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

答案 1 :(得分: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)

答案 2 :(得分: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>^

现在,在适当的位置(应该是第2行),添加以下行:

pick <E-sha1> <E-commit-msg>

保存,让rebase来做它的事情,按照通常的方式解决所有冲突。最后:

git branch -d temporary

由于分支删除不会使补丁提交悬而未决,因此您无需使用git branch -D,而使用-d也提供了一种不错的方法来验证您没有搞砸东西。

希望这对其他与我类似的人有用。