压缩和合并父分支后,如何自动将所有子分支重新设置为 master?

How to automatically rebase all children branches onto master after squashing and merging the parent branch?

的基础上,我有一个工作流程,我不断地在 PR 之上创建 PR,以便其他人更容易审查我的工作。目标是拥有更小的 PR 大小。所以我经常遇到如下情况:

                  G--H--I   <-- branch3
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C       <-- branch1
 /
M          <-- master

branch3 之后的 N 个分支依此类推。问题是,在我压缩并合并 branch1 之后,我必须手动重新设置分支 2、3...N:

                  G--H--I   <-- branch3
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C 
 /
M--S       <-- master, origin/master (branch1 changes are squashed in S)

在上述情况下,我必须运行:

git结帐分店2 git rebase --onto master(C 的 SHA-1)

git结帐分店3 git rebase --onto branch2(F 的 SHA-1)

等等...

有没有办法通过脚本自动变基所有分支来自动执行此过程?我想不通的是一种自动检测正确的 SHA-1 以作为每个 rebase 的参数传递的方法。

有几个基本问​​题,或者可能是一个基本问题,这取决于您如何看待它。即:

  • 分支没有parent/child关系,and/or
  • 分支,在这个意义上,你的意思是,不存在。我们只有 分支名称 。树枝本身就是海市蜃楼之类的东西。 (这看起来并不是正确的看待它的方式,但它有助于摆脱大多数 non-Git 系统采用的更严格的分支视图。)

先从一个看似直截了当的问题开始,但因为Git是Git,其实是个骗人的问题:which branch holds commits A-B-C?

Is there a way to automate this process by rebasing all branches automatically with a script? What I can't figure out is a way to automatically detect the correct SHA-1 to pass as parameter for each rebase.

这个问题没有通用解决方案。但是,如果您完全符合您所画的情况,您的特定情况 有一个特定 解决方案——但您必须写下它你自己。

这个技巧问题的答案是提交 A-B-Cmaster 之外的每个分支上。像 branch3 这样的 分支名称 只标识一个特定的提交,在本例中为提交 Icommit 标识另一个提交,在本例中为提交 H。每个提交总是标识一些先前的提交——或者,在合并提交的情况下,两个或多个先前的提交——并且 Git 只是从最后开始 backwards 。 “结束”正是哈希ID存储在分支名称中的提交。

分支名称缺少 parent/child 关系,因为每个分支名称都可以随时移动或销毁,而无需更改存储在每个其他分支中的哈希 ID。也可以随时创建新名称:创建新名称的唯一限制是您必须选择 some 现有的该名称提交到 point-to.

commits 有 parent/child 关系,但 names 没有。不过,这导致了针对这种特定情况的解决方案。如果提交 Y 是提交 X 的后代,这意味着我们从 Y[=212= 开始有一些向后的路径] 并且可以回到 X。这种关系是有序的——从数学上讲,它在提交集上形成 偏序——因此 XYX 先于 Y,即 X 是 [=143= 的祖先]Y), 然后 YX (Y 成功 X: YX).

的后代

因此,我们采用我们的名称集,将每个名称转换为提交哈希 ID,并执行这些 is-ancestor 测试。 Git 的“is-ancestor”运算符实际测试 ≼(先于或等于),并且 is-equal 情况发生在:

...--X   <-- name1, name2

其中两个名称 select 相同的提交。如果发生这种情况,我们将不得不分析我们的代码可能会如何处理这种情况。事实证明,这通常根本不需要任何特殊工作(尽管我不会费心证明这一点)。

找到“最后”提交——每次提交都在相关提交“之前”的提交——我们现在需要进行变基操作。我们有:

                  G--H--I   <-- branch3
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C 
 /
M--S       <-- master, origin/master (branch1 changes are squashed in S)

就像你展示的那样,我们知道 S 代表 A-B-C 序列,因为当我们 制作S。由于上次提交 提交 I,我们希望像 rebase 一样复制从 DI 的每一次提交,副本着陆在 S 之后。如果在复制操作期间 Git 没有 移动这些分支名称中的 any 可能是最好的,我们可以使用 Git 的 detached HEAD 模式实现:

git checkout --detach branch3  # i.e., commit `I`

或:

git checkout <hash-of-I>       # detach and get to commit `I`

或:

git switch --detach ...        # `git switch` always requires the --detach

这让我们:

                  G--H--I   <-- branch3, HEAD
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C 
 /
M--S       <-- master, origin/master

我们现在 运行 git rebase --onto master branch1 如果 name branch1 仍然可用,或者 git rebase --onto master <hash-of-C> 如果不可用。这会根据需要复制所有内容:

                  G--H--I   <-- branch3
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C 
 /
M--S       <-- master, origin/master
    \
     D'-E'-F'
            \
             G'-H'-I'  <-- HEAD

现在所有(?)我们需要做的就是通过相同的分支名称集返回并计算它们在原始提交链中的距离。由于 Git 的工作方式 - 向后 - 我们将从它们结束的任何地方开始并向后工作以提交 C。对于此特定绘图,branch2 为 3,branch3 为 6。我们也计算我们复制了多少次提交,这当然也是 6。所以我们从 6 中减去 3 branch2,从 6 中减去 6 branch3。这告诉我们现在应该 移动 那些分支名称的位置:从 I' 向后退零步 branch3,从 I' 向后退三步 branch2。所以现在我们根据每个 name 和 re-set 每个名称进行最后一次循环。

(那么我们可能应该选择一些 namgit checkoutgit switch 到。)

这里有一些挑战:

  • 这组名字哪里来的?名称是branch1branch2branch3等,但实际上它们并没有那么明显的关联:为什么我们移动分支fred而不移动分支barney?

  • 我们怎么知道branch1是我们不应该在这里使用,但是应该 用作 git rebase-with-detached-HEAD?

    的“不要复制此提交”参数
  • 我们究竟如何进行 is-ancestor / is-descendant 测试?

    这道题其实是有答案的:git merge-base --is-ancestor就是考试。您给它两个提交哈希 ID,它会报告 left-hand 一个是否是 right-hand 一个的祖先:git merge-base --is-ancestor <em>X Y</em> 测试 <em>X</em> ≼ <em>Y</em>.它的结果是它的退出状态,适用于内置if的shell脚本。

  • 我们如何计算提交?

    这个问题也有答案:git rev-list --count <em>stop</em>..<em>start</em>start 提交开始并向后工作。当它到达 stop 或其任何祖先时,它停止向后工作。然后它报告访问的提交数。

  • 我们如何移动分支名称?我们如何确定要登陆哪个提交?

    这个很简单:git branch -f 让我们移动一个现有的分支名称,只要我们目前没有那个名称 checked-out。由于我们在复制过程后处于分离的 HEAD 上,因此我们有 no 名称 checked-out,因此可以移动所有名称。 Git 本身可以做 counting-back,使用波浪号和数字后缀语法:HEAD~0 是提交 I'HEAD~1 是提交 H'HEAD~2 是提交 G'HEAD~3 是提交 F',依此类推。给定一个数字 $n 我们只写 HEAD~$n,所以 git branch -f $name HEAD~$n 完成了工作。

你还需要解决前两个问题。解决方案将根据您的具体情况而定。

值得指出的是,这可能是没有人为此写出合适的解决方案的原因——我在多年前写了自己的近似解决方案,但在很多年前也放弃了——是因为如果你没有这种非常具体的情况。假设不是:

                  G--H--I   <-- branch3
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C       <-- branch1
 /
M          <-- master

你开始于:

               G--H--I   <-- branch3
              /    
          D--E--F   <-- branch2
         /    
  A--B--C       <-- branch1
 /
M          <-- master

这一次,在提交 I 处结束并复制所有返回的提交,但不包括提交 C 无法复制提交 F 。没有 F' 允许您在将 D-E-G-H-I 复制到 D'-E'-G'-H'-I' 后移动分支名称 branch2

这个问题 相当严重的,回到 twenty-aughts 和 twenty-teens。但是 git rebase 有了新的 -r (--rebase-merges) 交互式 rebase 模式。它现在几乎拥有 multi-branch 变基为 Just Work 的所有机制。这里有一些遗漏的部分仍然有点困难,但如果我们能解决 第一个 两个问题——我们如何知道哪些分支名称到 multi-rebase 首先 ——我们可以编写一个 git multirebase 命令来完成整个工作。