Git合并子树的重排操作

13

假设我有以下情景:

    o (master)   
   /       o--o (WIP1)
  /       /
 o--o--o--o--o--o (WIP2)
(X)       \
           o--o (WIP3)

有没有一个Git命令可以创建一个新分支,使它包含分支X后的子树?我想执行一个“大型变基”,我希望将三个WIP分支变基到主分支上。

我知道我可以使用一些Bash脚本来完成这个操作,但我想知道如何使用Git命令来做到这一点。


1
可能是将包括其所有子分支的分支变基的重复问题。 - Mr_and_Mrs_D
1
可能是重定基树(提交/分支及其所有子级)的重复问题。 - carnicer
4个回答

16

没有针对此操作的单一git命令。您需要进行一些手动工作。在您的情况下:

    o (master)   
   /        o--o (WIP1)
  /        /
 X--o--o--B--o--o (WIP2)
           \
            o--o (WIP3)

你首先需要将WIP1变基到主分支(master):

git rebase --onto master X WIP1

这将导致如下结果:

               o--o (WIP1)
 (master)     /
    o--o--o--B’
   /   
  /        
 X--o--o--B--o--o (WIP2)
           \
            o--o (WIP3)

如果你现在运行git rebase --onto master X WIP2,你会得到这个结构:

                o--o (WIP1)
 (master)      /
     o--o--o--B’
    / \
   /   o--o--B’’--o--o (WIP2)
  /        
 X--o--o--B--o--o (WIP3)

这可能不是您想要的,所以现在您应该在B’上变基WIP2和WIP3:

git rebase --onto B’ B WIP2 
git rebase --onto B’ B WIP3 

这将导致以下情况:

                  o--o (WIP1)
(master)         /
    o--X--o--o--B’--o--o (WIP2)
                 \
                  o--o (WIP3)

是的,在提交之前我看到了你的编辑,但那时我已经画好了所有的ASCII艺术^^ - Chronial
2
我必须为逐步ASCII图表点赞。好答案。为什么两个答案都没有得到选中标记,@cd1? - epologee
用合并提交在更复杂的情况下如何进行变基操作? - Andry

3
我已将此问题标记为重复。我会使用您的示例来解释其他答案的内容。
我在这种情况下使用的方法是将所有要移动的分支合并到一个 公共人工 节点中,然后使用带有 --preserve-merges 选项的 rebase 命令。合并所有分支将 暴露 1 个终点,该终点将用作 rebase --onto 的最终输入参数。起点通常很明显,即要移动的子树的原点。
当合并以获取 子树终点 时,应明确避免冲突。因此,合并命令应指示使用 -Xours 选项自动解决它们。合并结果不重要,因为这些 人工 合并节点将在 rebase 后被丢弃。
建议创建一个新的分支 pack,以便不会丢失原始引用。在上面的示例中,将执行以下命令:
$ git checkout -b pack WIP1 # create new branch at 'WIP1'
$ git merge -s recursive -Xours WIP2 # merges WIP2 into pack (node p2)
$ git merge -s recursive -Xours WIP3 # merges WIP3 into pack

以下是这棵树将变成的样子。根据合并操作,创建了两个新的“人造”节点 p2pack
      o (master)
     /
    /          (WIP1)  (p2)
   /        o-----o-----o----o (pack)
  /        /           /    /
 o--o--o--o-----o-----o    / (WIP2)
(X)        \              /
            o------------o (WIP3)

现在是时候进行变基了。由于现在所有分支(pack)都有一个共同的终点,所以可以轻松移动整个子树:
$ git rebase --preserve-merges --onto master X pack

这将产生以下结果:
                      (WIP1') (p2')
                   o-----o-----o----o (pack')
   (master)       /           /    /
 o----o----o--o--o-----o-----o    / (WIP2')
(X)               \              /
                   o------------o (WIP3')

现在是重新排列引用的时候了。我不知道为什么,在某些情况下,引用会被移动,而在其他情况下不会。对于每个引用WIP1、WIP2、WIP3或您需要的任何引用类型,请键入:

$ git checkout WIP1
$ git reset --hard <WIP1' hash>

最后,删除为生成公共子树终节点而创建的人工提交。
$ git branch -D pack
$ git branch -D p2 # if there is any

所以最终的树应该是:
                      (WIP1')
                   o-----o
   (master)       /
 o----o----o--o--o-----o-----o (WIP2')
(X)               \
                   o------------o (WIP3')

1
可以使用 git rebase -i --rebase-merges 来简化此过程,以便您可以省略过程末尾的虚拟合并提交。在这里讨论:https://dev59.com/t2Uq5IYBdhLWcg3wbv5Z#69396585 - Waleed Khan

2

使用git-branchless工具套件,您可以直接重新定位子树:

$ git move -b WIP1 -d master

声明:本人为作者。


如果您想坚持使用基本的Git,您可以使用此答案中的方法,但要使用新的--rebase-merges选项。创建一个虚拟合并提交:

$ git checkout WIP1
$ git merge WIP2 WIP3
...add all conflicts and resolve...

然后进行交互式变基。生成的变基计划将是您想要的,除了应该删除最后一个 merge 命令:

$ git rebase -i master --rebase-merges
...example editor contents below...
label onto

# Branch WIP1
reset abc123 
pick abc456
pick abc789
label abcdef

reset onto
pick bcd123
pick bcd456
pick bcd789

## REMOVE THIS LINE:
# merge -C abcdef

1
   o (master)   
   /       o--o (WIP1)
  /       /
 o--p--p--o--o--o (WIP2)
(X)      (Y)
          \
           o--o (WIP3)

这应该是一个 rebase --onto(你可以在 "如何将某些提交移动到git中的另一个分支?" 中看到一个例子):

 git rebase --onto master X WIP1
 git rebase --onto master X WIP2
 git rebase --onto master X WIP3

根据Chronial测试,这将会给出:

         p'--p'--o--o (WIP2)
        /
 o-----o-----p--p--o--o--o (WIP1)
(X) (master)   (Y')   
        \
         p''--p''--o--o (WIP3)

第一次变基没问题,但你需要获取Y SHA,然后:

 git rebase --onto Y' Y WIP2
 git rebase --onto Y' Y WIP3

1
这将创建三个分支,它们会从主分支分叉出来,是吗?还是这会将 X 后面的结构移动到主分支之上(以便分支稍后分叉)? - Chronial
@Chronial 是的,存在这种风险,特别是考虑到如果在目标分支检测到相同的内容,rebase不会重放提交。我需要测试一下。 - VonC
刚刚测试了一下,结果不一致 :/ - Chronial
@Chronial 意思是 X 后的前两个提交只存在于 WIP1 中?而 WIP2,3 直接从 master 开始? - VonC
不,X之后的第一次提交会被克隆3次——每个WIP都会克隆一次。 - Chronial

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