如何加入最后N个合并提交到一个?

时间:2015-09-22 21:26:47

标签: git

在我的资料库中,我目前有这段历史:

$ git log --oneline
<commit-id-1> Merge commit '<merged-commit-id-1>' into <branch>
<commit-id-2> Merge commit '<merged-commit-id-2>' into <branch>
...

其中<merged-commit-id-1><merged-commit-id-2>是从我之前创建当前branch的另一个分支合并而来的。现在我想以某种方式加入那些合并提交:

$ git log --oneline
<commit-id> My message about huge successful merge here...
...

我试过

$ git rebase --preserve-merges -i HEAD~2

ps,但收到此错误:

Refusing to squash a merge: ...

(我也在一个新的最简单的存储库中复制它)有没有办法解决它?

我真正想要的是能够通过原始分支(已更改)的ID合并大量提交,同时逐步解决冲突并且不引入数十个合并提交(merge --squash不是一个选项要么,因为我需要保留历史。)

示例:

$ git init
$ echo 1 > 1; git add 1; git commit -m 1
$ git branch branch
$ echo 2 > 2; git add 2; git commit -m 2
$ echo 3 > 3; git add 3; git commit -m 3
$ git checkout branch
$ echo 4 > 4; git add 4; git commit -m 4
$ git log master --oneline
8222... 3
1f03... 2
... 1
$ git merge 1f03
# in real project here goes some work
$ git merge 8222
# and here, can't merge 8222 right away, because it can be difficult
# now need to clean up merge commits OR merge incrementally in a different way
$ git rebase -i --preserve-merges HEAD~2 
p ...
s ...
Refusing to squash a merge: a199... (merge commit for 8222)

torek的解决方案:

git update-ref refs/heads/branch `git commit-tree -p branch~N -p 8222 branch^{tree} -m 'Commit message for a new merge commit'`

1 个答案:

答案 0 :(得分:4)

首先是一些背景......在git中,合并有两个功能:

  1. 它比较&#34;合并基础&#34;两个提示的两行开发,然后使用半智能 1 算法组合这些变化。也就是说,git merge other首先计算$base 2 然后计算git diff $base HEADgit diff $base other。这两个差异告诉git你要对$base做什么,例如,在foo.txt添加一行,从bar.tex删除一行,然后在xyz.py中更改一行。然后Git尝试保留所有这些更改,如果添加到foo.txt的行在每个分支中添加相同的方式,或者同一行是bar.tex删除了,只需保留一份的更改。

  2. 为了将来的使用,它记录了两条开发线汇集在一起​​的事实,并且两条线的所有所需更改现在都出现在&#34; merge commit&#34;中。因此,结果提交适合作为新的合并库。我们马上就会看到这样的事情(不完全是这样)。

  3. 这两个功能以完全不同的方式实现。第一个是通过合并工作树中的差异来完成的,留下任何太难的情况让您手动处理。第二个是在您进行合并提交时完成的,方法是列出两个 3 &#34;父提交ID&#34;在新的(合并)提交。

    您需要决定的是以多少方式保留这些内容。我认为在这些情况下,绘制(部分)提交图是非常有用的。

    在这个示例中,您从一个公共基础开始,我将其称为B(对于基础),然后在master上再提交两个提交,在branch上再提交一个:< / p>

    B - C - D    <-- master
      \
        E   <-- branch
    

    现在在branch上,您首先合并提交C

    B - C - D   <-- master
      \   \
        E - F   <-- branch
    

    这是否需要手动干预取决于从BC的更改以及从BE的更改,但无论如何这都会导致新的合并提交F

    然后请求git在提交D中合并。但是,这不再使用commit B作为合并基础,因为向后遵循新的合并F,git会发现合并基础 - branch和{之间的最新提交{1}} - 现在提交master。因此,比较为CCDC。 Git结合了这些(可能不得不停下来得到帮助);当某人(你或git)提交结果时,我们得到:

    F

    现在,你要问(我认为)&#34;壁球&#34; F和G合并为单个合并提交。鉴于git的工作方式,你只能通过创建一个 new 合并提交来实现这一点,然后让B - C - D <-- master \ \ \ E - F - G <-- branch 指向它(删除对branch的引用,因此只有参考到G也是如此。

    进行此新合并提交时需要考虑两件事:

    • 你想要什么树(附加到提交的文件/目录集,将由索引/登台区域组成)?你只能有一个!

    • 您要列出哪些父提交?要在提交F之后立即使用它,你需要它的第一个父亲&#34;是E。要使其成为合并提交,它必须至少有一个其他父级。

    要选择的树很明显:提交E具有所需的合并结果,因此请使用G中的树。

    使用的第二个(也许是第三个)父母不太明显。可以列出G C,并提供章鱼合并D

    O

    但是这并没有任何特别有用的东西,因为B - C = D <-- master \ \ \ E --- O <-- branch C的祖先。如果你做了一个更正常的合并提交D,它只指向M(作为第一个父级)和E)(作为第二个父级),你得到的这样:

    D

    这将是&#34;同样好&#34;作为章鱼合并:它具有相同的树(我们每次都从提交B - C - D <-- master \ \ E ---- M <-- branch 中挑选树)并且它具有相同的第一父(G)。它只有一个其他父级(即E),但这导致D位于其历史记录中,因此它不需要直接与C建立显式连接。 / p>

    可以有一个明确的联系,如果你想要一个,但没有特别好的理由给它一个。)

    这留下了最后一个问题:实际产生此合并的机制(CM,无论您喜欢哪种方式)并让O指向它。

    有一个&#34;管道&#34;假设你现在处于这种状态,直接创建它的命令:

    branch

    此时你可以运行(注意,这些是未经测试的):

    B - C - D   <-- master
      \   \   \
        E - F - G   <-- branch
    

    其中$ commit=$(git commit-tree -p branch~2 -p master branch^{tree} < msg) 是包含所需提交消息的文件。这会创建commit msg(只有两个父项,第一个是提交M,即E,第二个是提交branch~2,即D })。然后:

    master

    可以使用普通的git&#34;瓷器&#34;来创建$ git update-ref refs/heads/branch $commit 。命令,但它需要更多的技巧。首先,我们要保存所需的树,即M

    branch^{tree}

    然后,在分支$ tree=$(git rev-parse branch^{tree}) 上(因为branch将使用当前分支),告诉git备份两个提交。我们在这里使用软复位,硬复位还是混合复位并不重要,我们暂时只是重新标记git reset标签:

    branch

    然后,告诉git我们正在合并$ git reset HEAD~2 ,但无论如何都不承诺。 (这只是为了让git设置master.git/MERGE_MSG - 您可以直接编写这些文件,而不是执行此部分。)

    .git/MERGE_HEAD

    然后清除工作树和索引中的所有内容,将其全部替换为commit $ git merge --no-commit master 中保存的树:

    G

    (为此,您需要位于工作树的顶级目录中)。这为新提交准备了索引,现在只有最后一次提交的提交:

    $ git rm -rf .; git checkout $tree -- .
    

    这使用我们在执行$ git commit 之前获取其ID的树来提交合并。 git reset使git reset的提示提交为提交branch,因此我们的新提交是merge-commit E,就像我们使用了低级管道命令一样

    (注意:M不能做到这一点,它不够聪明,不知道什么时候可以 - 只有当我们在第一次设置好的时候的地方。)

    1 它并不是特别聪明,但它可以很好地处理这些简单案例。

    2 您可以使用git rebase -i -pgit merge-base自行计算。然而,对于一些(不常见但并不像人们希望的那样罕见)的情况,可能存在多个合适的合并基础。默认的合并算法(&#34;递归&#34;)将合并两个合并库以提供一个&#34;虚拟基础&#34;并使用它。因此,如果您尝试手动执行此操作,则存在一些细微差别。

    3 可以有两个以上的父母:&#34;合并提交的定义&#34;是任何具有多个父级的提交。尽管如此(使用&#34;章鱼合并&#34;策略),多向合并(&#34;章鱼和#34;合并)的方式也不同。所以,在大多数情况下,我忽略了这些。