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

在我的存储库中,我目前有这样的历史:

$ 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: ...

(我也转载它在一个新的最简单的可能的存储库)是否有解决方法?

我真正想要的是能够在逐步解决冲突并且不会引入数十个合并提交时merge --squash来自原始分支(已更改)的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'`

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

  • 它将两条开发线的“合并基础”与两个提示提交进行比较,然后使用半智能1算法结合这些更改。 也就是说, git merge other第一个计算$base ,2然后执行git diff $base HEADgit diff $base other 。 这两个diff会告诉git你想要做什么$base ,例如,添加一行到foo.txt ,从bar.tex删除一行,并在xyz.py更改一行。 然后,Git会尝试保留所有这些更改,并且如果添加到foo.txt的行在每个分支中以相同的方式添加,或者同一行从bar.tex中删除, bar.tex保留一个更改副本。

  • 它记录了将来使用的事实,即两条发展线汇集在一起​​,并且两条线的所有期望变化现在都出现在“合并承诺”中。 例如,结果提交因此适合作为新的合并基础。 一会儿我们会看到类似的东西(不完全是这样)。

  • 这两个功能是以完全不同的方式实现的。 第一种方法是通过在工作树中合并差异来完成的,这让您难以手动处理任何难题。 第二个是在你进行合并提交时完成的,在新的(合并)提交中列出了两个“父提交ID”。

    你需要决定的是以何种方式保存这些内容。 在这些情况下,我认为它非常有助于绘制(部分)提交图。

    这里,在你的例子中,你从一个公共基础开始,我将调用B (基础),然后在master branchbranch上有两个更多的提交:

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

    现在在branch你首先合并提交C

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

    这是否需要手动干预取决于从BC的变化以及从BE的变化,但是在任何情况下都会导致新的合并提交F

    然后你要求git在提交D合并。 然而,这不再使用承诺B作为合并基础,因为按照新的合并F倒退,混帐发现,合并基础最近提交-之间branchmaster -is现在提交C 。 因此比较是CDCF Git结合了这些(也许不得不停下来并获得帮助); 当某人(你或者git)提交结果时,我们得到:

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

    现在,您要求(我认为)将F和G“压扁”为一个合并提交。 考虑到git的工作方式,您只能通过创建一个新的合并提交来实现这一点,然后将branch指向它(将引用放到G ,因此也是对F的唯一引用)。

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

  • 你想要什么树(附加到提交的文件/目录集,将从索引/分段区域中创建)? 你只能有一个!

  • 您想要列出哪些父提交? 为了让它在提交E之后立即出现,你希望它的“第一父”成为E 要使其成为合并提交,它必须至少有一个其他父代。

  • 要选择的树很明显:commit G具有所需的合并结果,所以使用G的树。

    第二个(也许是第三个)父母使用不那么明显。 可以列出CD ,给八章合并O

    B - C = D   <-- master
           
        E --- O   <-- branch
    

    但是这并没有做任何特别有用的事情,因为CD的祖先。 如果你做出了一个更正常的合并提交M ,它只是指向E (作为第一父)和D )(作为第二父),你会得到这个:

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

    这与章鱼合并的“一样好”:它具有相同的树(我们每次都从提交G选择树),它具有相同的第一父( E )。 它只有另一个父节点(即D ),但是这会导致C在其历史记录中,因此它不需要直接连接到C的明确连接。

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

    这留下了最后一个问题:实际产生这种合并的机制( MO ,无论你喜欢什么),并让branch指向它。

    有一个“管道”命令直接创建它,假设你现在处于这种状态:

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

    此时您可以运行(请注意,这些未经测试):

    $ commit=$(git commit-tree -p branch~2 -p master branch^{tree} < msg)
    

    其中msg是一个包含所需提交消息的文件。 这创建提交M (只有两个父母,第一个是提交E ,即branch~2 ,第二个是提交D ,即, master )。 然后:

    $ git update-ref refs/heads/branch $commit
    

    使用普通的git“porcelain”命令可以创建M ,但它需要更多的技巧。 首先我们要保存所需的树,即branch^{tree}

    $ tree=$(git rev-parse branch^{tree})
    

    然后,在分支branch (因为git reset会使用当前分支),告诉git备份两个提交。 无论我们在这里使用软,硬还是混合重置都无关紧要,我们现在只需重新绑定标签branch

    $ git reset HEAD~2
    

    然后,告诉git我们正在合并master ,但不要做任何事情。 (这只是为了让git设置.git/MERGE_MSG.git/MERGE_HEAD - 你可以直接写这些文件,而不是做这个部分。)

    $ git merge --no-commit master
    

    然后清除工作树和索引中的所有内容,将其全部替换为提交G所保存的树:

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

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

    $ git commit
    

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

    (注意: git rebase -i -p不能完全做到这一点,它不够聪明,无法知道什么时候这是好的 - 只有当我们首先将它设置为OK时)。


    1它不是特别聪明,但它处理简单的情况非常好。

    2你可以使用git merge-basebase=$(git merge-base other)自己计算这个值。 对于一些(罕见但并不罕见)的案例,可能有多个合适的合并基础。 默认合并算法(“递归”)将合并两个合并基础以提出“虚拟基础”并使用它。 因此,如果您尝试手动完成此操作,会有一些细微差异。

    3可以有两个以上的父母:“合并提交”的定义是任何提交超过一个父母。 尽管(使用“章鱼合并”策略),多路合并(“章鱼”合并)完成方式不同。 所以,在大多数情况下,我在这里忽略了这些。

    链接地址: http://www.djcxy.com/p/23459.html

    上一篇: How to join last N merge commits into one?

    下一篇: How can I combine 2 'git commit's