`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 pushgit fetch 在 Windows 和 Linux 之间进行协调。)

【讨论】:

不幸的是我在 Ubuntu 上,所以我不认为这取决于它。我上次遇到这个问题的仓库被许多历史混乱的人使用,所以这可能是命令运行缓慢的一个因素。但是,我不同意在我的特殊情况下这很困难:一个简单的git rebase 可以非常快地正确完成;我唯一的区别是它应该复制/重新创建它们,而不是跳过合并提交。应该不会那么难吧? 在一个大存储库中,我看到git merge 本身需要 30 分钟。大概你的不是 this 大,但重复合并可能是罪魁祸首。由于交互式变基是(或主要是,它在 Git 中开始发生变化)一个 shell 脚本,你可以通过设置 -x 标志并观察它的运行来了解延迟在哪里。 git rev-list --parentsusage: 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--mergegit-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--mergegit-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.shgit-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 -irebase -rrebase -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

闲谈 git merge 与 git rebase 的区别

git merge和rebase的区别

git rebase 解决 trailing whitespace errors

Git----拉取远程分支,git pull,git rebase,git pull --rebase的区别

Git----拉取远程分支,git pull,git rebase,git pull --rebase的区别