如何将一系列本地git分支进行变基(rebase)操作?

31

假设我有一串本地的 Git 分支,就像这样:

       master    branch1   branch2
          |         |         |
o----o----o----A----B----C----D

我将上游更改拉取到主分支:

              branch1   branch2
                 |         |
            A----B----C----D
           /
o----o----o----o
               |
            master

我现在将 branch1 进行变基,得到以下结果:

                        branch2
                           |
            A----B----C----D
           /          
o----o----o----o----A'---B'
               |         |
            master    branch1

请注意,由于对branch1进行了变基(rebase),提交A和B已经被重写为A'和B'。

这是我的问题: 现在我想将branch2变基。 显然的语法是git rebase branch1 branch2,但那绝对不起作用。 我想要的是仅在branch1上重新应用C和D,但它却试图调和A和A',并将它们视为冲突。

以下命令可行:

git rebase --onto branch1 branch2^^ branch2

假设我知道branch2比之前的branch1引用多了两个提交。

既然git rebase --onto可以工作,是否有一条单行的git命令可以将branch2重新基于新的rebased branch1,并且我不必确切地知道branch2包含多少次提交? (我想要指定某个神奇的引用而不是branch2^^作为中间参数。)

还是我忽略了其他方法?

我最感兴趣的解决方案是适用于极端情况的解决方案,不仅仅是两个分支 - 假设我有更像是5个本地分支,它们都链接在一起,我想将它们全部合并重建基。


1
当你将branch1基于master进行变基时,是否出现了冲突?通常情况下,git rebase branch1 branch2会按预期工作,但在某些情况下可能不会,例如当你必须解决将branch1基于master进行变基时的冲突。 - janos
6个回答

26
2022年:我在去年八月的 "Git交互式变基:如何自动移动其他分支(refs)?" 中描述了一个新的变基选项 --update-refs (Git 2.38, 2022年第三季度):
引用: 自动强制更新任何指向正在进行变基的提交的分支。 任何在工作树中检出的分支不会以这种方式更新。
              branch1   branch2
                 |         |
            A----B----C----D
           /
o----o----o----o
               |
             main

git switch branch2
git rebase --update-refs main

                    branch1     branch2
                       |           |
                 A'----B'----C'----D'
                /
o----o----o----o
               |
             main

2013年:一行简述:
git rebase --onto branch1 branch1tmp branch2

在对branch1进行变基之前,应该在branch1上创建一个branch1tmp

git checkout branch1
git branch branch1tmp
git rebase master
git rebase --onto branch1 branch1tmp branch2

说了这么多,检查一下ORIG_HEAD引用的是什么。
git rebase man page中可以看到:

ORIG_HEAD被设置为指向重置之前分支的末端。

所以检查一下这个方法是否可行(并且能够更好地扩展):

git checkout branch1
git rebase master
git rebase --onto branch1 ORIG_HEAD branch2
git rebase --onto branch2 ORIG_HEAD branch3
...

1
谢谢!ORIG_HEAD看起来绝对是最自然的方法。 - dmazzoni

10
git rebase master branch1
git rebase --onto HEAD ORIG_HEAD branch2
git rebase --onto HEAD ORIG_HEAD branch3
# ...
git rebase --onto HEAD ORIG_HEAD branchN

我有一个 Git 别名脚本可以这样做:

rebase-chain = "!f() { \
  for i in ${@}; do \
    git rebase --onto HEAD ORIG_HEAD $i; \
  done; \
}; f"

这可以使工作流程如下:

git rebase master branch1 # start with a normal rebase
git rebase-chain branch2 branch3 branch4 ... branchN

奖金

假设您已经安装了gitbash-completion

__git_complete "git rebase-chain" _git_branch

3
这个问题是修改基础分支并一次性变基所有子分支的子集。
我最近开始使用git-chain,这是一个解决此问题的工具。
基本上,您可以指定一个链。
git chain setup -c myfeature master branch1 branch2

一旦建立了此链,您就可以从master中拉取提交,然后运行git chain rebase -c myfeature,工具会为您找出所有引用并将它们很好地重新设置。

作为额外的受益,它还会计算和处理branch1branch2的任何修正或新提交。


1

Git 2.38 新增了一个标志 --update-refs 来解决这个问题。以你的例子为例:

git rebase --update-refs master

例子:

$ git checkout branch2
$ git log --decorate --all --oneline --graph
* 5b08e54 (master) 4
* 6790fdc 3
| * ee84471 (HEAD -> branch2) branch commit 4
| * 9da7c93 branch commit 3
| * c250fc6 (branch1) branch commit 2
| * bd5f222 branch commit 1
|/
* 2ec36a0 2
* ecce5ff 1
* 8bb07ae (tag: root) root
$ git rebase --update-refs master
Successfully rebased and updated refs/heads/branch2.
Updated the following refs with --update-refs:
    refs/heads/branch1
$ git log --decorate --all --oneline --graph
* b37f0c7 (HEAD -> branch2) branch commit 4
* c20fc6f branch commit 3
* a231606 (branch1) branch commit 2
* 0e81768 branch commit 1
* 5b08e54 (master) 4
* 6790fdc 3
* 2ec36a0 2
* ecce5ff 1
* 8bb07ae (tag: root) root

注意:我只将链中的“最后一个”分支(branch2)变基到新的基础上(master),中间的分支(branch1)会自动移动。


1
git-branchless工具套件可以很好地解决这个问题:
$ git move -s A -d master

免责声明:我是作者。
这将变基A,以及它的所有后代,以及指向任何这些提交的任何分支。请参见有关git move的文档

0
我希望的是,在执行 git checkout branch2; git rebase -i master 命令时,可以在编辑器中直接显示 branch1,并且当我保存编辑器时,branch1 也会相应地得到更新。这样,我就可以直接将一个提交从 branch2 移动到 branch1 中,并使其保持不变。也许可以使用“--rebase-branches”或类似的选项来实现这个功能。
所以,我感同身受。
我所做的是一种模拟此缺失功能的方法。在创建长期分支期间,我经常发现自己累积了应该在“此”分支之前合并到主干的内容,如修复编码标准违规、发现和修复明显错误、更新现有代码的文档等等。这就是我要使用你的 branch1 的原因。然后,我的新功能中实际包含的提交则放在 branch2 中。
因此,我创建的不是 branch1 分支,而是创建一个 branch1 伪分支“标记”——一个我稍后将使用 git rebase 抛弃的小提交。我可以这样创建它:
touch marker.master
git add marker.branch1
git commit -m "*** BRANCH1 ***" marker.branch1

现在,当我创建提交时,我会混合应该进入主分支之前的提交和这个实际分支的“新”提交。

当需要清理/变基时,我会使用git rebase -i master。现在,我的*** BRANCH1 ***提交在提交列表中很明显,我可以将要发送到branch1的提交移动到该“行”上方。

因为我没有一个真正的branch1分支,所以git rebase master正好做我想要的事情,它将branch2和我的branch1伪分支标记作为一个单元移动。

当我完成变基并准备推送时,我手动记录*** BRANCH1 ***的提交ID,例如abcdef1,然后执行以下操作:

git checkout -b branch1 abcdef1^

然后:

git checkout branch2
git rebase -i branch1

移除 abcdef1 提交。

这样我就可以在 git rebase -i 过程中轻松地在我的 branch1branch2 分支之间移动提交,而只在最后实际创建一个真正的 git branch1 分支。

但是,我真的希望 git rebase -i 可以为我完成这个操作!


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