Git交互式变基:如何自动移动其他分支(refs)?

6
有时我想做一个rebase,并确保其他引用被更新到新的结构,而不需要手动重置或多次rebase。
有没有一种方法可以一次完成,使得git rebase会更新引用到新的提交,这些提交被选中并在rebase之前已经有了引用?
一个例子:
Rebase之前的情况:
* abc3... commit3 (branch:a, HEAD) 
* abc2... commit2 
* abc1... commit1 (branch:b)
* abc0... base commmit (branch:master)

然后执行 Rebase master -i 命令:pick abc3, abc1, abc2

执行结果如下:(分支 b 保留在其自己的提交分支中)

* abc6... commit2 (branch:a, HEAD) 
* abc5... commit1   
* abc4... commit3
|
| * abc1... commit1 (branch:b)
|/
* abc0... base commmit (branch:master)

我希望您看到的结果是:(分支b已更新为新提交)
* abc6... commit2 (branch:a, HEAD) 
* abc5... commit1 (branch:b)
* abc4... commit3
* abc0... base commmit (branch:master)

没有。我以前想过这样的事情,并编写了一些非常混乱的脚本来处理我关心的一些特殊情况,但通常很难正确完成。 - torek
1
嘿 @torek,你在这里的评论过时了吗?我认为git的新“update-refs”功能解决了这个问题。(在几个月前发布的v2.38中) - Devin Rhode
2
@DevinRhode:是的,它已经过时了,但目前实现的更新引用功能存在问题,所以我不建议使用它。 (问题在于,如果您编辑指令表以删除更新,则Git最终会删除引用。Git 2.39和2.38.2中有一个修复程序,但在至少几个月甚至一年左右之前,我不会推荐使用它。) - torek
哦,我在我的答案中提到了那个。谢谢! - Devin Rhode
我认为我注意到了一个问题,当你在git-rebase-todo中插入一个update-refs行时会出现错误。如果你有一个已经检出了old-pr的工作树,在另一个工作树中你有“new-work-on-old-pr”,并且你正在重新定位“new-work-on-old-pr”内部的工作树,那么在你的git-rebase-todo文件中放置一个update-refs refs/heads/old-pr行会导致old-pr工作树进入一种奇怪的状态,许多“incoming”更改留在工作目录中(相当奇怪的脏git状态)。 - Devin Rhode
我现在正在使用git-branchless,它是围绕git的一个包装器。问题很可能是由于使用它而引起的。 - Devin Rhode
2个回答

7

这是关于git 2.39新的update-refs功能如何帮助我的rebase流程的一般情况。

实际上,这个功能从git 2.38开始就可用,但是它*有一些bug* - 你应该升级到2.39.0

首先,你应该通过以下配置设置来启用这个功能:

git config --global rebase.updateRefs true

在进行交互式变基时,您现在可以轻松地使用分支名称标记任何提交。该分支名称也不需要已经存在。这样可以方便地将一系列大的提交拆分成小的PR。
假设您运行git rebase -i <base sha from master>,在git-rebase-todo文件中:
pick 1688e8706 First
pick d8e19832e Second
pick b34be474e Third

您可以使用新的分支名称为每个提交打上"标签",然后推送这些分支,就像这样:


pick 1688e8706 First
update-ref refs/heads/first

pick d8e19832e Second
update-ref refs/heads/second

pick b34be474e Third
update-ref refs/heads/third


注意:你可以大部分忽略refs/heads/前缀,之后的内容才是实际的分支名称!
这就是Git会为你编辑文件的方式,如果你已经有了这些分支指向这些提交。你只需要做一次[1],将分支名称与提交关联起来。然后,如果你设置了git config rebase.updateRefs true,git-rebase将自动将这些update-ref refs/heads/branch-name行添加到你的git-rebase-todo文件中。
然后,在每次成功完成rebase之后,你会看到:
Successfully rebased and updated refs/heads/third
Updated the following refs with --update-refs:
        refs/heads/first
        refs/heads/second
        refs/heads/third

然后你可以这样推送每个分支:
git push --force-with-lease origin first:refs/heads/first
git push --force-with-lease origin second:refs/heads/second
git push --force-with-lease origin third:refs/heads/third
git push --force-with-lease origin $(git branch --show-current):refs/heads/$(git branch --show-current)

给提交一个稳定的句柄本身并不算是激进的,但这是一个在用户空间手动执行会非常容易出错的步骤。现在git已经实现了这个功能,期待更高级的工具为您提供全新的功能!

[1]: 如果你使用git checkout second然后进行修改,git不会更新third。一般来说,你应该保持在栈顶,即本例中的third。如果你对second进行了修改并最终想要变基third,你应该运行:

git checkout third
git rebase --onto second d8e19832e

注意:在这种情况下,d8e19832e实际上是正确的,根据我的例子,这是来自历史记录中third原始 sha 的second。Git rebase 将会拿走d8e19832e及其以下的所有内容,并尝试将提交“Third”应用到新的second上,你可能需要解决冲突。

1
我提到的功能有很好的说明。已点赞。 - VonC
有没有一种方法可以在不指定之前的引用的情况下推送它们?目前,我们需要使用类似于 git push --atomic origin first second third 这样的命令。 - bric3
@bric3 我不确定你具体在寻找什么 - 也许你想知道 git 是否可以引入一个 rebase.autoPushUpdatedRefs 的功能? - Devin Rhode
@DevinRhode 是的,完全正确。可以选择使用显式推送选项。仅适用于当前分支。 - bric3
Git-branchless是对git的封装,可以实现这样的功能。我也喜欢这个功能!我之前在他们的GitHub上提交了关于此功能的建议,并且他们非常友好 :) https://github.com/arxanas/git-branchless - Devin Rhode

1
“git rebase”会更新被挑选并在重定位之前有引用的新提交的引用吗?
在重定位之前,不会。
但是在重定位过程中呢?可能会。
使用Git 2.38(2022年第三季度),“git rebase -i”(参见man手册)学会了通过“--update-refs”选项更新出现在重定位范围内的分支的末端。
查看 7fefa1b 提交(2022年7月12日)由 Junio C Hamano (gitster)
查看 4611884 提交, aa37f3e 提交, 3113fed 提交, b3b1a21 提交, 89fc0b5 提交, 900b50c 提交, a97d791 提交, d7ce9a2 提交, f57fd48 提交, aa7f2fd 提交, 18ea595 提交, 1bec4d1 提交(2022年7月19日)由 Derrick Stolee (derrickstolee)
(由Junio C Hamano -- gitster --于2022年8月1日合并至3d8e3dc 提交

rebase: 添加--update-refs选项

签名作者:Derrick Stolee

在开发一个大型特性时,将其分成多个小部分并按顺序进行审查可能会很有帮助。在开发或审查过程中,对特性的某一部分进行更改可能会影响多个这些部分。交互式变基可以帮助调整分支的多部分“故事”。
然而,如果有跟踪特性不同部分的分支,则重新设置整个提交列表可能会创建无法从这些“子分支”到达的提交。需要手动更新这些分支。
在 "git rebase -i"(man) 中添加一个新的--update-refs选项,每当正在重新设置的提交被装饰时,在todo文件中添加'update-ref '步骤。最后,重新设置过程会更新所有列出的引用以存储在重新设置操作期间存储的值。
确保在放置任何压缩或修复之后进行迭代。只有在完成这些压缩和修复之后才更新分支。这样,即使修复了该部分中最近的提交,位于特性末端的--fixup提交也可以正确应用于子分支。
此更改更新文档和内置程序,接受--update-refs选项,并使用'update-ref'命令更新todo文件。添加测试以确保在正确的位置添加这些todo命令。
该更改不包括跟踪更新的引用并在重新设置过程结束时编写新的引用值的实际行为。这将推迟到以后的更改中。

git rebase现在在其手册页面中包括以下内容:

--update-refs

--no-update-refs

自动强制更新任何指向正在进行变基的提交的分支。
在工作树中检出的任何分支不会以这种方式更新。

还有:

rebase:从“update-ref”命令更新引用

Signed-off-by: Derrick Stolee

先前的更改引入了 'git rebase --update-refs'(man) 选项,该选项将 'update-ref <ref>' 命令添加到交互式 rebase 的 todo 列表中。
教 Git 在到达这些 'update-ref' 命令时记录 HEAD 的位置。 ref/before/after 三元组存储在 $GIT_DIR/rebase-merge/update-refs 文件中。 先前的更改解析了此文件,以避免在 rebase 进行期间其他进程更新该文件中的引用。
当序列化器到达这些 'update-ref' 命令时,我们不仅会更新文件,还会在 rebase 序列的末尾更新引用本身。 如果在此最后一步之前中止 rebase,则不会更新引用。 before 值用于确保我们不会意外地覆盖同时更新的引用(例如,由较旧版本的 Git 或第三方工具)。
结果:

序列控制器:通知用户 --update-refs 活动

报告者:Elijah Newren
签名者:Derrick Stolee

When the user runs 'git rebase -i --update-refs'(man), the end message still says only

Successfully rebased and updated <HEAD-ref>.

Update the sequencer to collect the successful (and unsuccessful) ref updates due to the --update-refs option, so the end message now says

Successfully rebased and updated <HEAD-ref>.
Updated the following refs with --update-refs:
efs/heads/first
efs/heads/third
Failed to update the following refs with --update-refs:
efs/heads/second

`git rebase --update-refs`(见{{link1:git rebase --update-refs}}man)在Git 2.39(2022年第4季度)已得到修正,不再会在序列器中移除所有 `update-ref` 命令时删除引用。
查看 提交 44da9e0(2022年11月7日),提交人为Victoria Dye (vdye)
(由Taylor Blau -- ttaylorr --合并于提交 35dc2cf,2022年11月18日)

rebase --update-refs:避免意外删除引用

报告人:herr.kaste
协助者:Phillip Wood
协助者:Derrick Stolee
签署者:Victoria Dye
签署者:Taylor Blau

b3b1a21(“sequencer: rewrite update-refs as user edits todo list”,2022年7月19日,Git v2.38.0-rc0--列出在批处理#8中的合并),添加了“ todo_list_filter_update_refs()”步骤来处理从“rebase-todo”中删除“update-ref”行。具体来说,如果没有相应的“update-ref”行,则从“update refs state”中删除潜在的引用更新。然而,由于当'refs_to_oids'列表为空时,'write_update_refs_state()'不会更新状态,所以删除所有'update-ref'行将导致状态保持与初始化时一样(所有引用的“after”OID都为null)。然后,在应用引用更新时,所有引用都将被更新为null,并因此被删除。要解决这个问题,在'refs_to_oids'为空时,删除'update-refs'状态文件。此外,添加一个测试来涵盖“所有update-ref行已删除”的情况。

是的,但要注意一个错误(已在2.39和2.38.2中修复),即update-refs可能会删除一个引用... - torek
@torek,您有该漏洞的参考资料吗? - VonC
不是,但在发布说明中有提到。让我们看看 git log 发现了什么……啊哈,93a7bc8b285eaad24049bc862b4733d595a473f8。哦,我看你也已经找到了! - torek
@torek 但是...那是我在这个答案末尾已经提到的相同提交内容。(为2.38.2挑选) - VonC

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