基于this question,我有一个工作流程,在该工作流程中,我不断在PR上创建PR,以使其他人更容易查看我的工作。目标是使PR尺寸更小。因此,我经常遇到以下情况:
G--H--I <-- branch3
/
D--E--F <-- branch2
/
A--B--C <-- branch1
/
M <-- master
对于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)
在上述情况下,我必须运行:
git checkout分支2 git rebase --onto master(C的SHA-1)
git checkout branch3 git rebase --onto branch2(F的SHA-1)
依此类推...
有没有一种方法可以通过使用脚本自动重新定标所有分支来自动执行此过程?我不知道有一种方法可以自动检测正确的SHA-1,以将其作为每个底基的参数传递。
答案 0 :(得分:2)
有两个基本问题,或者一个基本问题,具体取决于您如何看待它。那是:
让我们从一个看似简单的问题开始,但是由于Git是Git,实际上是一个技巧性的问题:哪个分支保存提交A-B-C
?
有没有一种方法可以通过使用脚本自动重新定标所有分支来自动执行此过程?我不知道有一种方法可以自动检测正确的SHA-1,以将其作为每个底基的参数传递。
没有针对此问题的通用解决方案。但是,如果您确实有绘制的情况,则可以使用特定的解决方案来解决您的特定情况,但是您必须自己编写。
这个技巧问题的答案是,提交A-B-C
位于除master
之外的每个分支上。像branch3
这样的分支名称仅标识一个特定的提交,在本例中为I
。该 commit 标识另一个提交,在这种情况下为提交H
。每个提交始终标识一些先前的提交(如果是合并提交,则标识两个或多个先前的提交),而Git从头开始只是向后工作。确切地说,“结束”就是其哈希ID存储在分支名称中的提交。
分支名称缺少父子关系,因为每个分支名称都可以在不更改彼此分支中存储的哈希ID的情况下随时移动或销毁。也可以随时创建新名称:创建新名称的唯一限制是您必须选择现有的 some 提交以将该名称指向。
提交具有父/子关系,但名称则没有。但是,这导致了针对这种特定情况的解决方案。如果commit Y 是commit X 的后代,则意味着存在一些向后的路径,我们可以从 Y 开始,然后可以返回< em> X 。这种关系是有序的-从数学上讲,它在提交集合上形成了偏序-这样, X ≺ Y ( X 在 Y 之前,即 X 是 Y 的祖先),然后是 Y ≻ X ( Y 继 X : Y 是 X 的后代)。
因此,我们获取一组名称,将每个名称转换为提交哈希ID,然后执行这些is-ancestor测试。 Git的“ is-ancestor”运算符实际上测试≼(先于或等于),并且等值情况发生于:
...--X <-- name1, name2
其中两个名称选择相同的提交。如果发生这种情况,我们将不得不分析我们的代码在这种情况下可能会做什么。事实证明,这通常根本不需要任何特殊工作(尽管我不会费力证明这一点)。
已经找到了“最后一个”提交(每个提交都在有问题的提交之前),我们现在需要进行基准操作。我们有:
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
的顺序,因为我们进行时选择了提交C
(通过名称branch1
) S
。由于上次提交是提交I
,因此我们希望像从前一样复制从D
到I
的每个提交,并且副本要在{{之后1}}。最好是在复制操作期间,Git 不完全不移动这些分支名称中的任何,而我们可以使用Git的 detached来实现HEAD 模式:
S
或:
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
,否则运行branch1
。这将根据需要复制所有内容:
git rebase --onto master <hash-of-C>
现在我们需要做的全部(?)是遍历相同的分支名称集,然后计算它们沿着原始提交链的距离。由于Git的工作方式(向后),我们将从它们结束的任何地方开始并向后工作以提交 G--H--I <-- branch3
/
D--E--F <-- branch2
/
A--B--C
/
M--S <-- master, origin/master
\
D'-E'-F'
\
G'-H'-I' <-- HEAD
。对于此特定图形,C
为3,branch2
为6。我们还计算了复制的提交数,当然也是6。因此,branch3
从6中减去3,branch2
从6中减去6。这就告诉我们现在应该在哪里移动这些分支名称:branch3
从I'
向后退零,branch3
从I'
向后退三步}。因此,现在我们对每个名称进行最后一个循环,并根据需要重新设置每个名称。
(然后我们可能应该为branch2
或git checkout
选择一些 name 。)
这里有一些挑战:
我们从哪里获得这组名称?名称为git switch
,branch1
,branch2
,依此类推,但实际上它们并没有那么明显的关联:为什么我们移动分支branch3
但不移动分支fred
?
我们怎么知道barney
是我们不应该在此处使用,而应用作“不复制这个commit“参数到我们的branch1
-with-detached-HEAD?
我们如何精确执行is-ancestor / is-descendant测试?
这个问题实际上有一个答案:git rebase
是测试。您给它提供了两个提交哈希ID,它报告左手是否是右手的祖先:git merge-base --is-ancestor
测试git merge-base --is-ancestor X Y
。其结果是退出状态,适合在内置X ≼ Y
的Shell脚本中使用。
我们如何计算提交次数?
这个问题也有一个答案:if
从 git rev-list --count stop..start
提交开始,然后倒退。当到达 start
或其任何祖先时,它将停止向后工作。然后,它报告已访问的提交数的计数。
我们如何移动分支名称?我们如何确定要提交的承诺?
这很容易:stop
将允许我们移动现有的分支名称,只要我们当前没有将该分支签出即可。复制过程结束后,由于我们位于分离的HEAD上,因此我们已签出 no 名称,因此可以移动所有名称。 Git本身可以使用波浪号和数字后缀语法进行倒数:git branch -f
是提交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'
后,没有branch2
允许您移动分支名称D-E-G-H-I
。
这个问题很严重,早在二十岁和二十岁。但是D'-E'-G'-H'-I'
有了新的git rebase
(-r
)交互式变基模式,变得更加聪明。现在,它几乎具有将分支机构重新建立为Just Work的所有设备。有一些遗漏的片段在这里仍然很难解决,但是如果我们能够解决 first 两个问题-我们如何首先知道要对哪个分支名称进行多基重处理< / strong>-我们可以编写一个--rebase-merges
命令来完成全部工作。