`git rebase --preserve-merges` 的更快方法
Posted
技术标签:
【中文标题】`git rebase --preserve-merges` 的更快方法【英文标题】:A faster way to `git rebase --preserve-merges` 【发布时间】:2018-03-13 01:03:30 【问题描述】:我通过创建一系列功能分支来使用 git,并在使用 git merge --no-ff
完成后将它们合并到 master。这会创建空的合并提交,用于识别先前功能分支的起点和终点。
为了处理多个并发分支,甚至是嵌套分支,我使用 rebase。我从不合并回来,我总是根据最新的提交重新调整我的分支,测试并最终与--no-ff
合并,一旦一切都完成了。对于嵌套分支,我也是这样做的:多个分支依次合并到主分支上,主分支本身最终合并到主分支。
为了保留与嵌套分支合并的信息,我经常使用git rebase --preserve-merges
。这正是我想要的,我的工作流程没有问题。
我对 git 的主要问题是 git rebase --preserve-merges
非常慢(有时每次提交大约需要 2 秒)。阅读What exactly does git's "rebase --preserve-merges" do (and why?) 后,我意识到 git 必须执行大量工作来保留合并,因为 git 必须处理任意图。
我想知道的是:由于我的工作流程几乎会产生一个相当于线性历史的图表,鉴于我保证历史的“线性”,有没有办法以更快的方式执行 git rebase --preserve-merge
等效只有空的合并提交?我不介意使用脚本或奇怪的命令,只要最终结果是正确的。
A-B-C
/ \
(1)--------D-- master
\
\---F-----I-- feature
\ / \ /
E G-H
A-B-C E G-H
/ \ / \ / \
(2)--------D---F-----I-feature
master
tl;博士: 知道基础历史是线性的,如何将 (1) 转换为 (2),这样git rebase --preserve-merges
就不必做太多工作而且速度很快?
【问题讨论】:
【参考方案1】:您可能可以通过将 bash 脚本重写为更聪明的方式来加快速度(例如,在 Python 中,使用一组适当的修订分隔符运行 git rev-list --parents
,并在开始复制操作之前收集所有父信息),但基本上很难。正如其他链接中的答案顺便指出的那样,git rebase -p
也没有完全 preserve 合并,而是 re-create 它们。在完全一般的情况下——我知道你忽略了更有限的特殊情况;我只是为了完整性而提到这一点-git rebase -p
在应用了特殊选项或处理的合并上完全失败(--no-commit
与手动修改合并以产生“邪恶合并”,或与 -X rename-threshold
或 -X ours
运行的合并或类似),因为特殊情况信息实际上只存储在结果树中。变基代码甚至不查找它(这将花费更长的时间:它必须先重现原始合并,才能查看无选项合并是否会重新创建原始结果)。
除此之外,这些选项的速度(或速度不足)取决于您的存储库及其文件的大小,以及您使用的是 Windows(极慢)还是 Unixish 系统(快得多)。我没有理由知道 Windows 运行脚本应该如此缓慢,但显然确实如此,因为 Git 人员不断用 C 重写一些东西,以使它们在 Windows 上执行得可以接受,因为脚本太慢了。
因此,如果您在 Windows 上执行此操作,一种加快速度的方法是停止使用 Windows。 :-) (您可以在变基期间执行此操作,使用 git push
和 git fetch
在 Windows 和 Linux 之间进行协调。)
【讨论】:
不幸的是我在 Ubuntu 上,所以我不认为这取决于它。我上次遇到这个问题的仓库被许多历史混乱的人使用,所以这可能是命令运行缓慢的一个因素。但是,我不同意在我的特殊情况下这很困难:一个简单的git rebase
可以非常快地正确完成;我唯一的区别是它应该复制/重新创建它们,而不是跳过合并提交。应该不会那么难吧?
在一个大存储库中,我看到git merge
本身需要 30 分钟。大概你的不是 this 大,但重复合并可能是罪魁祸首。由于交互式变基是(或主要是,它在 Git 中开始发生变化)一个 shell 脚本,你可以通过设置 -x
标志并观察它的运行来了解延迟在哪里。
git rev-list --parents
说usage: git rev-list [OPTION] <commit-id>... [ -- paths... ]
不起作用
@alper: git rev-list --parents HEAD
适合我。你忘了给git rev-list
一个起点提交吗?我忘了提到这是必需的。 :-)【参考方案2】:
提醒 (2021)
IMPORTANT |
---|
Starting Git 2.22, Q2 2019, the old rebase script is no more |
Git 2.22(2019 年 4 月)已停用旧的 git rebase
脚本。
请参阅 Ævar Arnfjörð Bjarmason (avar
) 的 commit d03ebd4(2019 年 3 月 18 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit 4f3036c,2019 年 4 月 16 日)
Git 2.23(2019 年第三季度)完成清理工作,并指出 git rebase
中唯一剩余的脚本部分是 the --preserve-merges
backend。
参见commit 082ef75、commit c3c003e、commit d4fe60e、commit b2b9a23、commit 311c00a(2019 年 5 月 14 日)Johannes Schindelin (dscho
)。(由Junio C Hamano -- gitster
-- 合并,commit ed7f8ac,201 年 6 月 13 日)
在 Git 2.34(2021 年第四季度)中,“git rebase
”的“--preserve-merges
”选项(man)已被删除。
见commit 17919c3,commit 06aa5e4,commit 82db1f8,commit ff8d6e5,commit 5b55b32,commit 0a159d6,commit a74b350,commit 52f1e82,commit aa4df10,commit ab7c7c2,commit 52f1e82,commit aa4df10,commit ab7c7c2,@9876214) Johannes Schindelin (dscho
).(由 Junio C Hamano -- gitster
-- 合并于 commit 223a1bf,2021 年 10 月 18 日)
2018 年原始答案:
我对 git 的主要问题是
git rebase --preserve-merges
非常慢
Git 2.20+(2018 年第四季度)可能不会那么慢,考虑到它包括对 C 中“rebase”机制的重写。 没有更多的 shell 脚本。
IMPORTANT |
---|
Again, let's be clear: starting Git 2.22, Q2 2019, the old rebase script is no more |
参见Pratik Karki (prertik
)commit ac7f467、commit c7b64aa、commit 55071ea(2018 年 8 月 6 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit 5ae5084,2018 年 11 月 2 日)
rebase:开始将其作为内置实现
此提交模仿用于将
difftool
转换为内置的策略。 我们首先将 shell 脚本git-rebase.sh
重命名为git-legacy-rebase.sh
并引入一个简单地执行 shell 脚本版本的builtin/rebase.c
, 除非配置设置rebase.useBuiltin
设置为true
。这背后的动机是重写所有的功能 前面提到的
rebase.c
中的shell脚本版本,一一对应 能够通过配置方便地测试新功能rebase.useBuiltin
.在原来的
difftool
转换中,如果sane_execvp()
试图 运行以非负状态返回的旧脚本版本, 命令静默退出,没有成功执行任何操作,但是sane_execvp()
不应以非负状态返回 地方,所以我们用die()
来通知这种异常情况。我们故意避免直接读取配置以避免 当我们需要回退到时弄乱
GIT_*
环境变量 exec() 执行 shell 脚本。
参见Ævar Arnfjörð Bjarmason (avar
)commit 62c2393、commit d8d0a54(2018 年 11 月 14 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit 4520c23,2018 年 11 月 18 日)
The documentation 现在声明:
rebase.useBuiltin:
如果
git rebase
,则设置为false
以使用旧版shellscript 实现。 默认为true
,即使用C内置的重写。C 重写首先包含在 Git 2.20 版中。 此选项提供了一个逃生舱口以重新启用旧版本,以防万一 在重写中发现了错误。 此选项和 shellscript 版本
git-rebase
将在未来的某个版本中删除。如果你找到一些理由将此选项设置为
false
而不是一次性测试,你应该将行为差异报告为 git 中的错误。
在 Git 2.21(2019 年 2 月)中,“git rebase --merge
”通过重用用于“git rebase -i
”的内部机制重新实现。
参见commit 68aa495、commit c91c944、commit 7b76ac6、commit 899b49c、commit 45339f7、commit 5400677、commit 72ee673、commit c913c59(2018 年 12 月 11 日)Elijah Newren (newren
)。(由Junio C Hamano -- gitster
-- 合并于commit 8fe9c3f,2019 年 2 月 7 日)
rebase
:通过交互机制实现--merge
作为使 rebase 具有更统一行为的持续努力的一部分,通过在后者之上重新实现合并后端,修改合并后端以使其表现得像交互式后端。
交互式变基是根据cherry-pick而不是merge-recursive内置实现的,但cherry-pick默认情况下也会调用递归合并机制,并且可以接受特殊的合并策略和/或特殊的策略选项。 因此,确实没有必要同时拥有
git-rebase--merge
和git-rebase--interactive
了。 删除git-rebase--merge.sh
,改为在builtin/rebase.c
中实现。
rebase
:定义线性化排序并强制执行
请参阅commit c91c944 了解性能。
此外,仍然是 Git 2.21(2019 年 2 月):“git rebase --merge
”通过重用用于“git rebase -i
”的内部机制重新实现。
参见Elijah Newren (newren
)commit 29d03f8(2019 年 2 月 14 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit 6f07c7b,2019 年 2 月 14 日)
rebase:通过交互机制实现 --merge
作为使 rebase 具有更统一行为的持续努力的一部分,通过在后者之上重新实现合并后端,修改合并后端以使其表现得像交互式后端。
交互式rebase是根据cherry-pick而不是merge-recursive内置实现的,但是cherry-pick也调用了 默认情况下递归合并机制,可以接受特殊的合并策略和/或特殊的策略选项。 因此,确实没有必要同时拥有
git-rebase--merge
和git-rebase--interactive
了。 删除git-rebase--merge.sh
,改为在builtin/rebase.c
中实现。这会导致一些经过深思熟虑但用户可见的微小更改:
进度输出已修改(参见 t3406 和 t3420 示例) 现在修复了一些已知的测试失败问题(参见 t3421) 在 rebase --merge 期间的 bash 提示现在是REBASE-i
而不是REBASE-m
。 原因:提示是后端在使用中的反映;这允许用户使用适当的后端信息向 git 邮件列表报告问题,并允许高级用户 知道在哪里搜索相关的控制文件。 (见 t9903)
由于“git rebase --preserve-merge
”已通过重用用于“git rebase -i
”的内部机制重新实现,因此此 Git 2.22(2019 年第二季度)补丁很有趣:
见commit 460bc3c, commit 297b1e1, commit 0ea0847, commit 73fdc53, commit 3389853, commit 7d3488e, commit c44c246, commit 0609b74, commit 0609b74, commit 6023c92, commit 28dc09d, @17654391@, @92 2019 年)和 Phillip Wood (phillipwood
) 的 commit fc4a673(2019 年 3 月 19 日)。(由 Junio C Hamano -- gitster
-- 合并到 commit 7ba06bc,2019 年 5 月 13 日)
rebase -i
:不分叉运行rebase --interactive
当内置 rebase 启动交互式 rebase 时,它会解析选项,然后重新打包它们并分叉
rebase--interactive
。 将cmd_rebase__interactive()
中的选项解析从业务逻辑中分离出来,以允许通过直接调用run_rebase_interactive()
来运行交互式rebase 而无需分叉rebase__interactive
。在不分叉的情况下启动交互式变基可以轻松调试 测序仪无需担心附加到孩子身上 进程。 Ævar 还报告了一些rebase perf tests are 30% faster。
此补丁还可以轻松删除
cmd_rebase__interactive()
git-legacy-rebase.sh
和git-rebase--preserve-merges.sh
退休的未来。
“git rebase -i
”(和朋友们)过去常常不必要地检查要重新设置的分支的尖端,这已在 Git 2.26(2020 年第一季度)中得到纠正,
参见 Alban Gruin (``) 的 commit 767a9c4(2020 年 1 月 24 日)。(由 Junio C Hamano -- gitster
-- 合并到 commit d8b8d59,2020 年 2 月 14 日)
rebase -i
: 停止检查分支的尖端以变基报告人:SZEDER Gábor签字人:Alban Gruin
使用基于定序器的变基(即
rebase -i
、rebase -r
或rebase -m
)时首先要做的事情之一就是创建一个待办事项列表。这需要知道 rebase 的提交范围。
要获取范围的最后一次提交的 oid,使用
prepare_branch_to_be_rebased()
检出要变基的分支的尖端,然后读取头部的 oid。在此之后,分支的尖端甚至没有被修改。另一方面,“am”后端不签出分支。
在大型存储库中,这是性能损失:
rebase -i', the user may have to wait before editing the todo list while git is extracting the branch silently, and "quiet" rebases will be slower than
am'。由于我们在
opts->orig_head
中已经有了分支尖端的oid,所以切换到这个提交是没有用的。这将删除
do_interactive_rebase()
中对prepare_branch_to_be_rebased()
的调用,并添加一个orig_head' parameter to
get_revision_ranges()`。
prepare_branch_to_be_rebased()
被删除,因为它不再使用。这引入了一个可见的变化:因为我们没有将分支的尖端切换到 rebase,所以在 rebase 的开头没有为它创建 reflog 条目。
对
linux.git
进行的不科学性能测量如下:在这个补丁之前:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50 real 0m8,940s user 0m6,830s sys 0m2,121s
在这个补丁之后:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50 real 0m1,834s user 0m0,916s sys 0m0,206s
作为 Git 2.26(2020 年第一季度)的注释,“git rebase
”已学会默认使用合并后端(即驱动“rebase -i
”的机器),同时允许使用“--apply
”选项“apply
”后端(例如“format-patch piped to am
”的道德等价物)。
rebase.backend
配置变量可以设置为自定义。
见commit 10cdb9f、commit 2ac0d62、commit 8295ed6、commit 76340c8、commit 980b482、commit c2417d3、commit 6d04ce7、commit 52eb738、commit 8af14f0、@987654412、@、@987654412、@989 @、commit 93122c9、commit 55d2b6d、commit 8a997ed、commit 7db00f0、commit e98c426、commit d48e5e2(2020 年 2 月 15 日)和commit a9ae8fd、commit 22a69fd(2020 年 1 月 16 日)@987654。 br>(由 Junio C Hamano -- gitster
-- 合并于 commit 8c22bd9,2020 年 3 月 2 日)
rebase
: 重命名两个主要的 rebase 后端签字人:Elijah Newren
将“交互式”后端重命名为“合并”,因为:
'interactive' 作为一个名字引起了混淆;此后端已用于多种非交互式 rebase,并且考虑到我们将其设为默认值,未来可能会用于更多非交互式 rebase,而不是交互式 rebase。 “交互”不是基本策略;合并是。 存储状态的目录不是.git/rebase-interactive
,而是.git/rebase-merge
。
使用 Git 2.27(2020 年第二季度),您还可以允许“git rebase
”重新应用所有本地提交,即使它们可能已经在上游,而无需先检查。
git rebase --reapply-cherry-picks
这将加速变基过程。
参见Jonathan Tan (jhowtan
)commit 0fcb4f6(2020 年 4 月 11 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit d6d561d,2020 年 4 月 22 日)
rebase --merge
:可选择跳过上游提交签字人:Jonathan Tan签字人:Elijah Newren
当针对自原始分支创建以来已多次提交的上游进行 rebase 时:
O -- O -- ... -- O -- O (upstream) \ -- O (my-dev-branch)
它必须读取每个新的上游提交的内容,除了上游的尖端和合并基础,因为“
git rebase
”试图排除与上游重复的提交。这可能会对性能造成重大影响,尤其是在部分克隆中,其中对对象的读取可能最终成为提取。
将标志
--reapply-cherry-picks
添加到“git rebase
”以允许禁止此功能。 此标志仅在使用“merge
”后端时有效。此标志更改
sequencer_make_script()
的行为,从do_interactive_rebase()
run_rebase_interactive() run_specific_rebase() cmd_rebase() 调用。使用此标志,limit_list()
(从sequencer_make_script()
到prepare_revision_walk()
间接调用)将不再调用cherry_pick_list()
,因此不再设置PATCHSAME
。 避免设置PATCHSAME
既意味着不再读取上游的中间提交(如测试所示),也意味着sequencer_make_script()
不会直接或通过@987654573完成PATCHSAME
导致的提交跳过@。
在 Git 2.30(2021 年第一季度)中,“git-parse-remote
”shell 脚本库已经过时了。
见commit 66d36b9(2020 年 11 月 24 日)Jeff King (peff
)。
请参阅Ævar Arnfjörð Bjarmason (avar
) 的commit a89a2fb、commit e63f7b0、commit 1c15180(2020 年 11 月 14 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit e89ecfb,2020 年 12 月 3 日)
parse-remote
:删除这个现在未使用的库签字人:Ævar Arnfjörð Bjarmason
前两次提交删除了该库中最后一次使用的函数,但其中大部分已经有一段时间是死代码了。 只有
"get_default_remote"
函数仍在使用中。尽管我们有这个库的手册页,但它从未打算(或者实际上我期望)在git.git 之外使用。让我们删除它,如果有人仍然关心这里的功能,他们可以将它们拉入自己的项目中。
最后一次使用
error_on_missing_default_upstream()
: d03ebd411c ("rebase: 移除 rebase.useBuiltin 设置", 2019-03-18)上次使用
get_remote_merge_branch()
:49eb8d39c7 ("Remove> contrib/examples/*", 2018-03-25)https://lore.kernel.org/git/87a6vmhdka.fsf@evledraar.gmail.com/
使用 Git 2.32(2021 年第 2 季度),删除了过去使用脚本化 git rebase 的最终提示。
参见Ævar Arnfjörð Bjarmason (avar
)commit 9bcde4d(2021 年 3 月 23 日)。(由 Junio C Hamano -- gitster
-- 合并于 commit dc2a073,2021 年 3 月 30 日)
rebase
: 移除临时 rebase.useBuiltin 设置和环境签字人:Ævar Arnfjörð Bjarmason签字人:Johannes Schindelin
删除 rebase.useBuiltin 设置和现已过时的
GIT_TEST_REBASE_USE_BUILTIN
测试标志。这是在我的d03ebd4 之后保留的(“
rebase
:删除 rebase.useBuiltin 设置”,2019-03-18,Git v2.22.0-rc0 -- merge 在batch #5 中列出)帮助任何使用实验标志并想知道它是默认标志的人,或者他们应该将他们的测试环境转换为无条件地使用内置 rebase。对于这些用户来说,了解这一点已经足够长了。 所以移除所有在d03ebd4之后留在原地的脚手架。 我还删除了文档条目,如果有人在他们的配置中仍然有这个,他们可以做一些源考古来弄清楚它曾经做什么,这比让每个阅读文档的 Git 用户都暴露于这个遗留配置开关更有意义.
【讨论】:
以上是关于`git rebase --preserve-merges` 的更快方法的主要内容,如果未能解决你的问题,请参考以下文章
git rebase 解决 trailing whitespace errors