在压缩并合并父分支后,如何自动将所有子分支重新建立到主分支上?

giw*_*619 7 git git-merge git-rebase git-squash

基于这个问题,我有一个工作流程,我不断地在 PR 之上制作 PR,以便其他人更容易审查我的工作。目标是缩小 PR 规模。所以我经常会遇到这样的情况:

                  G--H--I   <-- branch3
                 /    
          D--E--F   <-- branch2
         /    
  A--B--C       <-- branch1
 /
M          <-- master
Run Code Online (Sandbox Code Playgroud)

N之后的分支依此类推branch3。问题是,在我压缩和合并之后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)
Run Code Online (Sandbox Code Playgroud)

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

git checkout Branch2 git rebase --onto master (C 的 SHA-1)

git checkoutbranch3 git rebase --ontobranch2(F 的 SHA-1)

等等...

有没有办法通过使用脚本自动重新调整所有分支来自动化此过程?我想不出一种方法来自动检测正确的 SHA-1 以作为每个变基的参数传递。

Jon*_*ski 5

介绍

我知道这个线程很旧,但是有一个新的选项可以专门解决这个问题。这已包含在 2022 年 10 月的 Git 2.38 中。

街区里的新孩子

它被称为--update-refs,它是专门为了解决这个问题而设计的。

解决方案

所以,而不是你的例子:

git checkout branch2
git rebase --onto master
# switch branches and keep on rebasing
Run Code Online (Sandbox Code Playgroud)

现在,您可以执行以下操作:

git checkout branch3 # you need to be on the 'farthest' branch
git rebase master --update-refs # this will rebase the whole tree
git push origin : --force-with-lease # this will push the updated branches
Run Code Online (Sandbox Code Playgroud)

瓦拉!整棵树都会容纳。

只需确保您使用的是 GIT 2.39 或更高版本,因为版本 2.38 在其选项上有一个错误。

也将此选项设置为默认值

如果您希望这是变基时的默认行为,以避免每次都添加该标志,您可以rebase.updateRefs.gitconfig

git config --global --add rebase.updateRefs true
Run Code Online (Sandbox Code Playgroud)


tor*_*rek 0

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

\n
    \n
  • 分支没有父/子关系,和/或
  • \n
  • 分支,从你的意思这个词的意义上来说,不存在。我们所拥有的只是分支名称。树枝本身就是海市蜃楼之类的东西。(这似乎并不是正确的看待方式,但它有助于摆脱大多数非 Git 系统所采用的更严格的分支视图。)
  • \n
\n

让我们从一个看似简单的问题开始,但因为 Git 就是 Git,实际上是一个棘手的问题:哪个分支保存提交A-B-C

\n
\n

有没有办法通过使用脚本自动重新调整所有分支来自动化此过程?我想不出一种方法来自动检测正确的 SHA-1 以作为每个变基的参数传递。

\n
\n

这个问题没有通用的解决方案。但是,如果您确实遇到了您所绘制的情况,那么对于您的具体情况有一个特定的解决方案\xe2\x80\x94 但您必须自己编写。

\n

这个棘手问题的答案是,除了 之外的每个分支A-B-C上都有提交。像这样的分支名称仅标识一个特定的提交,在本例中为 commit 。该提交标识了另一个提交,在本例中为 commit 。每个提交总是标识一些先前的提交\xe2\x80\x94,或者,在合并提交的情况下,两个或多个先前的提交\xe2\x80\x94,并且 Git 只是从末尾向后工作。“结束”正是其哈希 ID 存储在分支名称中的提交。masterbranch3IH

\n

分支名称缺乏父/子关系,因为每个分支名称都可以随时移动或销毁,而无需更改存储在每个其他分支中的哈希 ID。也可以随时创建新名称:创建新名称的唯一限制是您必须为该名称选择一些现有的提交来指向。

\n

提交具有父/子关系,但名称没有。不过,这导致了针对这种特定情况的解决方案。如果提交Y是提交X的后代,则意味着存在一些向后路径,我们从Y开始并可以返回到X。这种关系是有序的\xe2\x80\x94,从数学上来说,它在提交集\xe2\x80\x94上形成偏序,使得X \xe2\x89\xba Y ( X先于Y,即X是Y的祖先),然后Y \xe2\x89\xbb XY继承XY是X的后代)。

\n

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

\n
...--X   <-- name1, name2\n
Run Code Online (Sandbox Code Playgroud)\n

其中两个名称选择相同的提交。如果发生这种情况,我们就必须分析我们的代码在这种情况下可能会做什么。事实证明,这通常根本不需要任何特殊的工作(尽管我不会费心证明这一点)。

\n

找到“最后”提交\xe2\x80\x94(每次提交都在相关提交“之前”)的提交\xe2\x80\x94 后,我们现在需要执行变基操作。我们有:

\n
                  G--H--I   <-- branch3\n                 /    \n          D--E--F   <-- branch2\n         /    \n  A--B--C \n /\nM--S       <-- master, origin/master (branch1 changes are squashed in S)\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所展示的,我们知道S代表序列,因为我们在创建A-B-C时选择了提交C(通过名称branch1)。由于最后一次提交commit ,我们希望复制\xe2\x80\x94,就像 rebase 所做的那样\xe2\x80\x94 从到 的每个提交,副本在 后着陆。如果 Git 在复制操作期间根本不移动任何这些分支名称可能是最好的,我们可以使用 Git 的分离 HEAD模式来实现这一点: SIDIS

\n
git checkout --detach branch3  # i.e., commit `I`\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
git checkout <hash-of-I>       # detach and get to commit `I`\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
git switch --detach ...        # `git switch` always requires the --detach\n
Run Code Online (Sandbox Code Playgroud)\n

这让我们:

\n
                  G--H--I   <-- branch3, HEAD\n                 /    \n          D--E--F   <-- branch2\n         /    \n  A--B--C \n /\nM--S       <-- master, origin/master\n
Run Code Online (Sandbox Code Playgroud)\n

我们现在运行git rebase --onto master branch1名称 branch1是否仍然可用git rebase --onto master <hash-of-C>。这将根据需要复制所有内容:

\n
                  G--H--I   <-- branch3\n                 /    \n          D--E--F   <-- branch2\n         /    \n  A--B--C \n /\nM--S       <-- master, origin/master\n    \\\n     D'-E'-F'\n            \\\n             G'-H'-I'  <-- HEAD\n
Run Code Online (Sandbox Code Playgroud)\n

现在(?)我们需要做的就是返回这些相同的分支名称集并计算它们沿着原始提交链的距离。由于 Git 的工作方式\xe2\x80\x94backwards\xe2\x80\x94,我们将从它们结束的地方开始执行此操作,然后向后提交C。对于这个特定的绘图, 3 为branch2, 6 为branch3。我们还计算了复制的提交数量,当然也是 6。因此,我们从 6 中减去 3 branch2,从 6 中减去 6 branch3。这告诉我们现在应该将这些分支名称移动到I'哪里:从for向后退零步,从forbranch3向后退三步。因此,现在我们对每个名称进行最后一次循环,并根据需要重新设置每个名称。I'branch2

\n

(那么我们可能应该选择一些名字togit checkoutgit switchto。)

\n

这里存在一些挑战:

\n
    \n
  • 我们从哪里得到这组名称?名称是branch1branch2branch3等等,但实际上它们不会有那么明显的相关性:为什么我们移动branchfred而不移动branch barney

    \n
  • \n
  • 我们怎么知道我们不应该branch1在这里使用它,而应该将其用作 -with-detached-HEAD 的“不要复制此提交”参数?git rebase

    \n
  • \n
  • 我们究竟如何进行 is-ancestor / is-descendant 测试?

    \n

    这个问题其实有答案:git merge-base --is-ancestor就是测试。你给它两个提交哈希ID,它会报告左边的哈希ID是否是右边哈希ID的祖先:tests 。它的结果是它的退出状态,适合在内置的 shell 脚本中使用。git merge-base --is-ancestor X YX \xe2\x89\xbc Yif

    \n
  • \n
  • 我们如何计算提交次数?

    \n

    这个问题也有一个答案:从提交开始并向后工作。当它到达或其任何祖先时,它就会停止向后工作。然后它报告访问的提交数量的计数。git rev-list --count stop..startstartstop

    \n
  • \n
  • 我们如何移动分支名称?我们如何确定要登陆哪个承诺?

    \n

    这很简单:git branch -f只要我们当前没有签出该名称,就可以让我们移动现有的分支名称。由于复制过程后我们处于分离的 HEAD 上,因此没有出名称,因此可以移动所有名称。Git 本身可以使用波浪号和数字后缀语法进行倒数:HEAD~0is commit I'HEAD~1is commit H'HEAD~2is commit G'HEAD~3is commitF'等。$n给定一个我们刚刚写的数字HEAD~$ngit branch -f $name HEAD~$n工作也是如此。

    \n
  • \n
\n

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

\n

值得指出的是,可能没有人为此编写正确的解决方案的原因\xe2\x80\x94我多年前编写了自己的近似解决方案,但也多年前放弃了它\xe2\x80\x94是整个过程中断了如果您没有这种非常具体的情况,请下来。假设代替:

\n
                  G--H--I   <-- branch3\n                 /    \n          D--E--F   <-- branch2\n         /    \n  A--B--C       <-- branch1\n /\nM          <-- master\n
Run Code Online (Sandbox Code Playgroud)\n

你从以下开始:

\n
               G--H--I   <-- branch3\n              /    \n          D--E--F   <-- branch2\n         /    \n  A--B--C       <-- branch1\n /\nM          <-- master\n
Run Code Online (Sandbox Code Playgroud)\n

这次,以 commit 结束I并复制所有返回到但不包括的提交, commitC 无法复制 commitF。复制到后不允许F'您移动分支名称。branch2D-E-G-H-ID'-E'-G'-H'-I'

\n

这个问题在二十多岁和二十多岁的时候就相当严重了。但已经通过新奇的( ) 交互式变基模式git rebase变得更加聪明。现在,它几乎拥有将多分支重新定位为“Just Work”的所有机制。这里有一些缺失的部分仍然有点困难,但是如果我们可以解决两个问题\xe2\x80\x94,我们如何知道首先要多重变基的分支名称\xe2\x80\x94we可以编写一个命令来完成整个工作。-r--rebase-mergesgit multirebase

\n