Git rebase命令中的--merge选项是做什么用的?

7

man页面git-rebase(1)上说:

-m
--merge
使用合并策略进行rebase。[...]

但是当然,即使不使用--merge选项,也可能遇到“合并冲突”。因此,在这种情况下也必须有任何“合并策略”来处理这些冲突。

--merge选项对rebase有何影响?

它似乎是相当基本的:对于rebase --merge,Git将其工作文件存储在名为$GIT_DIR/rebase-merge的文件夹中(与交互式rebase一样)。如果不使用--merge选项(且rebase是非交互式的),则该文件夹的名称为$GIT_DIR/rebase-apply


1
有趣的问题。手册建议在上游文件重命名时需要它,但我刚刚测试了一下,普通的变基操作可以自动处理这种情况,并将提交应用于重命名的文件。所以如果有人知道答案,我也很感兴趣。 - joanis
2
通过更多的实验,我现在猜测它可以通过 -s 和/或 -X 来指定合并策略(两者都意味着 -m)。我这么说是因为尽管 -m 在技术上改变了使用的算法,在我测试的几个案例中最终结果是相同的。更令人困惑的是,我能够创建一个场景,在这种情况下 git merge upstreamgit rebase upstream 给出了不同的结果,但在那种情况下,git rebase -m upstream 给出了与 git rebase upstream 相同的结果,尽管日志消息在过程中看起来不同。 - joanis
1
仅供参考,我已经测试过在上游重命名文件,并没有让任何rebase或merge混乱。我还尝试了在上游进行更改,在我的分支中进行相同的更改并撤消,这种情况下合并与上游不同,但是两个rebase行为完全相同:在最终结果中撤消更改,而合并保留更改并在最终结果中展现。 - joanis
2个回答

9
简述:对于git rebase,-m--merge的作用是确保rebase在内部使用git cherry-pick
强制使用cherry-pick的-m标志通常是冗余的,但并非总是如此。特别地,任何交互式rebase都会使用cherry-pick。正如joanis noted in a comment中所指出的那样,指定任何-s-X选项也会强制使用cherry-pick。正如下面所指出的那样,-k也是如此。
长(或至少更长):
在Git中,rebase有着悠久的历史:最初的rebase操作是通过将每个要被rebase的提交格式化为补丁,然后将补丁应用到其他某个提交上来完成的。也就是说,最初的git rebase主要只是:
branch=$(git symbolic-ref --short HEAD)
target=$(git rev-parse ${onto:-$upstream})
git format-patch $upstream..HEAD > $temp_file
git checkout $target
git am -3 $temp_file
git checkout -B $branch HEAD

(除了参数处理、所有错误检查和 git am 可能会因为错误而停止,需要手动修复和 git rebase --continue 之外;此外,上述脚本是我为了易读性而简化的版本,可能与原始脚本不太相似。)
这种类型的变基通常可以很好地处理大多数情况。最常见的一种情况是跨越一些文件重命名的变基,它不能很好地处理。它也无法复制“空”提交——其补丁为空,即不包含任何更改——因为 git format-patch 不允许省略补丁部分。
即使使用 -m,这些空提交通常也会被 git rebase 忽略;您必须添加 -k 以保留它们。要保留它们,git rebase 必须切换到 cherry-pick 变体,如果它还没有这样做的话。
要传递 -s-X 参数,变基必须调用 git cherry-pick 而不是 git am,因此任何这些标志也需要 cherry-pick 变体。

使用 git format-patch 永远不会进行任何重命名检测。因此,如果你要复制的提交流应该针对 HEAD 应用所有重命名检测,则 -m 标志非常重要。举个具体例子,考虑以下一系列提交:

          B--C--D   <-- topic
         /
...--o--A--E--F--G   <-- mainline

假设从 AB,从 BC,以及从 CD 的差异都在名为 lib-foo.ext 的文件中处理。但是在提交 F 中,此文件被重命名为 lib/foo.ext。对于 A..Dgit format-patch 将显示要对文件 lib-foo.ext 进行的更改,其中没有任何一个将正确应用于提交 G,因为不存在 lib-foo.ext 文件。整个变基将失败。
然而,当 HEAD 标识提交 G 时,对提交 B 进行 git cherry-pick 将找到重命名并将 AB 的更改应用于提交 G 中的 lib/foo.ext 版本。
          B--C--D   <-- topic
         /
...--o--A--E--F--G   <-- mainline
                  \
                   B'   <-- HEAD [detached]

下一次 cherry-pick,将选中 HEAD 为 B' 时的 C,会发现 B 到 C 的变化应该应用于重命名后的 lib/foo.ext 中的 libfoo.ext,并且最后一个 cherry-pick 的 D 也会这样做,以便 rebase 成功。
重命名检测代码很慢,因此在没有重命名和“空”提交要保留的 rebase 中,通过 git format-patch | git am 系统运行可以更快。这是原始方法比 cherry-pick 变体更好的唯一方式:在受限情况下速度更快。(但是,速度提高仅在存在许多重命名候选项,但其中没有一个是实际重命名或没有一个重要的情况下才会发生。)
(附注:参数-3,或使用更长的拼写--3way,告诉git am将该标志传递给每个git apply,其中应用程序将尝试进行三方合并(如果需要),并使用差异中index行中的blob哈希。 在某些情况下,似乎这足以处理重命名的文件-特别是如果blob哈希完全匹配。 cherry-pick方法进行了完整的重命名检测,可以处理不精确的匹配; -3无法做到这一点。 另请参见 git cherry-pick和git format-patch之间的区别|git am?,如 Jürgen所指出的。)

谢谢您,torek,提供这个非常有启发性的答案。到目前为止,我也将 cherry-pick 视为先前格式化补丁的应用程序。关于这一点,我发现了这个相关问题的详细答案非常有帮助:"git cherry-pick 和 git format-patch | git am 之间有什么区别?" - Jürgen
1
感谢@torek提供的详细答案,这非常有帮助!不过我仍然想知道:在我的测试中,我模拟了一种应该在“format-path | am”管道中失败的重命名情况,但最终还是成功了。在没有任何开关的情况下,git rebase是否有某些启发式方法,有时会切换到樱桃挑选变体?我的测试是使用一个具有重命名提交的dev.upstream分支,并且当前分支在重命名之前对该文件进行了编辑,git rebase dev.upstream按原样工作,将更改应用于已重命名的文件。 - joanis
2
@joanis:不,没有(或者上次我看的时候没有,可能是2.15左右),我希望至少在某些情况下会失败。但请注意,git am使用git apply -3对blob进行三方匹配,因此纯重命名(而不是带修改的重命名)可能(也许)会被找到。我需要进行一些实验来检查所有细节。 - torek

0
自 Git 2.26(2020 年第一季度)起,"git rebase"(man) 默认使用合并后端(即驱动 "rebase -i" 的机制),同时允许 "--apply" 选项使用应用后端(例如道德等价于 "format-patch piped to am")。
可以设置 rebase.backend 配置变量进行自定义。
这有助于说明 --merge(现在的默认值)和旧的 --apply 之间的区别。

请查看 commit 10cdb9f, commit 2ac0d62, commit 8295ed6, commit 76340c8, commit 980b482, commit c2417d3, commit 6d04ce7, commit 52eb738, commit 8af14f0, commit be50c93, commit befb89c, commit 9a70f3d, commit 93122c9, commit 55d2b6d, commit 8a997ed, commit 7db00f0, commit e98c426, commit d48e5e2 (2020年2月15日),以及 commit a9ae8fd, commit 22a69fd (2020年1月16日) 由 Elijah Newren (newren) 提交。
(由 Junio C Hamano -- gitster -- 合并于 commit 8c22bd9,2020年3月2日)

rebase:将默认后端从“am”更改为“merge”

签名作者:Elijah Newren

am-backend丢失了信息,从而限制了我们的功能:

此外,合并/交互式后端具有更强大的能力,目前似乎有轻微的性能优势,并且比am后端具有更多优化空间(目前正在进行利用其中一些可能性的工作)。

git rebase现在在其手册页面中包括:

可中断性

使用am后端在错误时间按下Ctrl-C尝试中止rebase会存在安全问题, 此时rebase可能进入无法通过后续的git rebase --abort中止的状态。
交互式后端似乎不会遇到同样的缺点。(有关详细信息,请参见此线程。)


从那时起,Git 2.39(2022年第四季度)修复了在变基时的reflog消息中的一些错误,并将"rebase --apply"的reflog消息更改为与"rebase --merge"匹配,以使reflog更易于解析。

再次说明了两个选项之间的另一个差异,现在已经得到解决。

请查看 commit 9a1925b, commit 6159e7a, commit be0d29d, commit 33f2b61, commit 1f2d5dc, commit da1d633, commit 4e5e1b4, commit 57a1498(由Phillip Wood (phillipwood)于2022年10月12日提交)。
请查看commit a524c62(由Junio C Hamano (gitster)于2022年10月17日提交)。
(已于2022年10月30日合并至commit 8851c4b,操作者为Taylor Blau -- ttaylorr --

rebase --apply:使 reflog 消息与 rebase --merge 匹配

签名作者:Phillip Wood

应用后端在开始或完成变基以及挑选提交时创建的reflog消息与合并后端略有不同。这些差异使得解析reflog比必要的更加困难(我有一个脚本,可以从rebase中读取完成消息,但是必须适应两种不同的消息格式,这很麻烦)。虽然可以从reflog消息中确定用于变基的后端,但这些差异并非为此目的而设计。《c2417d3》(“rebase:从交互式变基的reflog中删除'-i'”,2020-02-15,Git v2.26.0-rc0 -- merge列在batch #8中)在不发出警告的情况下消除了两个后端的reflog消息之间的明显区别。
由于合并后端是默认设置,因此它很可能是现有reflog中最常见的格式。因此,将应用后端更改为尽可能接近合并后端的格式来格式化其reflog消息。请注意,仍存在差异,因为在提交冲突解决时,应用后端将使用“(pick)”而不是“(continue)”,因为目前无法更改单个提交的消息。
除了c2417d3之外,我们还在68aa495(“rebase:通过交互式机制实现--merge”,2018-12-11,Git v2.21.0-rc0 -- merge)和2ac0d62(“rebase:将默认后端从am更改为merge”,2020-02-15,Git v2.26.0-rc0 -- merge列在batch #8中)中更改了reflog消息。此提交对“git rebase --applyman进行了与2ac0d62git rebaseman相同的更改,而没有任何特定于后端的选项。由于更改消息以使用现有格式,因此可以解析默认变基后端的reflog消息的任何脚本都不会受到此更改的影响。
已经存在针对两个后端的消息的测试,这些测试已调整以确保它们不会在未来失步。

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