git的"rebase --preserve-merges"命令究竟是什么(以及为什么要使用它)?

405

Git的rebase命令的文档非常简短:


--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

使用--preserve-merges标记时实际上会发生什么?它与默认行为有什么区别(没有该标记时)?"重新创建"合并是什么意思,等等。


39
警告:从Git 2.18(2018年第二季度,5年后)开始,“git --rebase-merges”最终将取代旧的“git --preserve-merges”。请参阅下面的我的回答 - VonC
2
致命错误:从Git 2.35.0(2022年第一季度)开始,rebase-merges已被删除,并将导致许多不同的fatal:消息,具体取决于您如何遇到该选项。存在边缘情况。Visual Studio尚未注意到,特别是对于git pull配置选项。 - Philip Oakley
2
@Philip 你可能是指 --preserve-merges 已经被移除了? - ak2
你们可以编辑问题并将这些警告放在顶部。然后将原始问题放在下面以保留其历史价值。:·) - Guildenstern
@ak2 是的。很容易混淆两个'rebase'选项。其中一个应该查看手册。对此我感到抱歉。 - Philip Oakley
3个回答

505
与普通的git rebase一样,使用--preserve-merges参数的git首先确定一个提交图中的一部分所做的提交列表,然后在另一部分上重播这些提交。与--preserve-merges有关的差异涉及选择哪些提交进行重播以及如何处理合并提交的重播。
更明确地说,普通rebase和保留合并的rebase之间的主要区别是:
  • 保留合并的rebase愿意重播(一些)合并提交,而普通的rebase完全忽略合并提交。
  • 由于它愿意重播合并提交,因此保留合并的rebase必须定义重播合并提交的含义,并处理一些额外的细节
    • 从概念上讲,最有趣的部分可能是选择新提交的合并父项。
    • 重播合并提交还需要显式检出特定提交(git checkout <desired first parent>),而普通的rebase不需要担心这个问题。
  • 保留合并的rebase考虑重播一组较浅的提交:
    • 特别是,它只会考虑自最近的合并基准以来进行的提交 - 即两个分支分开的最近时间 - 而普通的rebase可能会重播追溯到两个分支分开的第一次时间的提交。
    • 为了暂时和不明确,我认为这最终是筛选掉已经被合并提交"合并"的“旧提交”的一种方法。
首先,我将尝试“充分准确地”描述rebase --preserve-merges的操作,然后提供一些示例。当然,如果这似乎更有用,可以从示例开始。 “简要”算法 如果您真的想深入了解,可以下载git源代码并探索文件git-rebase--interactive.sh。(Rebase不是Git的C核心的一部分,而是用bash编写的。在幕后,它与“交互式rebase”共享代码。)
但是在这里,我将勾勒出我认为其本质的东西。为了减少需要考虑的事情数量,我取了一些自由。 (例如,我不尝试以100%的准确度捕获计算发生的精确顺序,并忽略一些不太中心的主题,例如如何处理已经在分支之间进行了cherry-pick的提交。)
首先,请注意,非保留合并的rebase相当简单。它更或多或少是:
Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

--preserve-merges 的变基操作相对较为复杂。以下是我尽力让它简单化,同时又不失重要信息的方式:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

使用--onto C参数进行变基应该非常相似。只是不是从B的HEAD开始提交回放,而是从C的HEAD开始提交回放(并且使用C_new代替B_new)。 示例1 例如,考虑提交图:
  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m是一个合并提交,其父提交为E和G。

假设我们使用普通的非合并保留变基(例如,checkout topic; rebase master)将主题(H)变基到主分支(C)上。在这种情况下,git会选择以下提交进行重放:

  • 挑选D
  • 挑选E
  • 挑选F
  • 挑选G
  • 挑选H

然后,git会更新提交图如下:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D'是D的回放版本,依此类推。)

请注意,合并提交m不会被选择进行重放。

如果我们在C的基础上使用--preserve-merges变基H。(例如,checkout topic; rebase --preserve-merges master)。在这种新情况下,git将选择以下提交进行重放:

  • pick D
  • pick E
  • pick F(进入“subtopic”分支的D')
  • pick G(进入“subtopic”分支的F')
  • pick 合并分支“subtopic”到topic
  • pick H

现在m被选择进行重播。还要注意,在合并提交m之前,合并父级E和G已被选择包含。

以下是生成的提交图:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

再次强调,D'是D的精选版本(即重新创建),E'等也同理。master分支之外的每个提交都已经被重放了。E和G(m的合并父节点)已经被重新创建为E'和G',以作为m'的父节点(在变基后,树历史仍然保持不变)。

示例2

与普通的变基不同,保留合并的变基可以创建多个上游head的子节点。

例如,考虑以下情况:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

如果我们将H(主题)在C(主干)之上进行变基,那么选择重新变基的提交记录如下:
  • 选择D
  • 选择E
  • 选择F
  • 选择G
  • 选择m
  • 选择H
结果如下:
  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

示例3

在上面的示例中,合并提交及其两个父提交都是重播提交,而不是原始合并提交的原始父提交。然而,在其他变基操作中,重演的合并提交可能会最终具有已经存在于合并之前的提交图中的父提交。

例如,请考虑以下情况:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

如果我们将topic分支rebase到master(保留合并记录),那么需要重放的提交如下:
  • 选择合并提交m
  • 选择F
重新编写的提交图将如下所示:
                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

这里重新合并提交m'获取了在提交图中预先存在的父提交,即主分支的HEAD D和原合并提交m的一个父提交E。

示例4

在某些“空提交”情况下,保留合并的变基可能会出现混乱。至少在一些旧版本的git(例如1.7.8)中是这样的。

看下面的提交图:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

请注意,提交 m1 和 m2 应该都包含了 B 和 F 的所有更改。
如果我们尝试将 H(topic)保留合并方式地应用到 D(master),则会选择以下提交进行重放:
- 选择 m1 - 选择 H
请注意,m1 中合并的更改(B、F)应该已经合并到 D 中。 (因为 m2 合并了 B 和 F 的子提交,所以这些更改应该已经合并到 m2 中。)因此,从概念上讲,在 D 的顶部重播 m1 可能是一个空操作或创建一个空提交(即连续修订之间的差异为空的提交)。
然而,Git 可能会拒绝在 D 的顶部重播 m1 的尝试。您可能会收到以下错误:
error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

看起来有人忘了向git传递一个标志,但根本问题在于git不喜欢创建空提交。


6
我注意到带有 --preserve-mergesgit rebase 比没有带的慢得多。这是寻找正确提交的副作用吗?有什么方法可以加快速度吗?(顺便说一句...感谢你提供如此详细的答案!) - David Alan Hjelle
8
听起来你应该总是使用“--preserve-merges”选项。否则,有可能会丢失历史记录,即合并提交。 - DarVar
21
在重新基于一个分支时,你总是会丢失历史记录,因为你声称已经对一个不同的代码库进行了更改,而实际上并非如此。 - Chronial
5
这个“provisional answer”还有效吗? - Andrew Grimm
5
当然,你是正确的,重新贴基总是涉及到失去历史记录,但是DarVar可能是在暗示一个事实,那就是你不仅会失去历史记录,还会失去对代码库的更改。冲突解决包含了信息,无论以何种方式进行重新贴基,这些信息都会丢失。你总是要重新做一遍。有没有办法让git重新做你的冲突解决?为什么git不能挑选合并提交? - Nils_M
显示剩余7条评论

143

Git 2.18(2018年第二季度)将通过添加新选项显著改进--preserve-merge选项。

"git rebase"学会了"--rebase-merges",可以在其他地方移植整个提交图的拓扑结构。

(注意:Git 2.22(2019年第二季度)实际上弃用 --preserve-merge,而Git 2.25(2020年第一季度)停止在"git rebase --help"输出中宣传它

请查看 commit 25cff9f, commit 7543f6f, commit 1131ec9, commit 7ccdf65, commit 537e7d6, commit a9be29c, commit 8f6aed7, commit 1644c73, commit d1e8b01, commit 4c68e7d, commit 9055e40, commit cb5206e, commit a01c2a5, commit 2f6b1d1, commit bf5c057 (2018年4月25日) 由 Johannes Schindelin (dscho) 提交。
请查看 commit f431d73 (2018年4月25日) 由 Stefan Beller (stefanbeller) 提交。
请查看 commit 2429335 (2018年4月25日) 由 Phillip Wood (phillipwood) 提交。
(于2018年5月23日被Junio C Hamano -- gitster --合并至commit 2c18e6a)

pull: 接受 --rebase-merges 以重新创建分支拓扑结构

类似于将 --preserve-merges 选项传递给 rebase 命令的 preserve 模式,merges 模式只是传递了 --rebase-merges 选项。
这将允许用户在拉取新提交时方便地重新创建非平凡提交的拓扑结构,而不会使它们变得扁平化。

git rebase 的 man 页面现在有一个专门介绍合并历史的变基部分

摘录:

There are legitimate reasons why a developer may want to recreate merge commits: to keep the branch structure (or "commit topology") when working on multiple, inter-related branches.

In the following example, the developer works on a topic branch that refactors the way buttons are defined, and on another topic branch that uses that refactoring to implement a "Report a bug" button.
The output of git log --graph --format=%s -5 may look like this:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

The developer might want to rebase those commits to a newer master while keeping the branch topology, for example when the first topic branch is expected to be integrated into master much earlier than the second one, say, to resolve merge conflicts with changes to the DownloadButton class that made it into master.

This rebase can be performed using the --rebase-merges option.


请参见提交 1644c73,其中包含一个小例子:

rebase-helper --make-script:引入一个标志以重定基合并

The sequencer just learned new commands intended to recreate branch structure (similar in spirit to --preserve-merges, but with a substantially less-broken design).

Let's allow the rebase--helper to generate todo lists making use of these commands, triggered by the new --rebase-merges option.
For a commit topology like this (where the HEAD points to C):

- A - B - C (HEAD)
   \   /
     D

the generated todo list would look like this:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

--preserve-merge 有什么区别吗?
Commit 8f6aed7 解释道:

从前,这里的开发人员想到:如果能够将 Git for Windows 在核心 Git 上的补丁表示为一堆分支,并在核心 Git 上重新创建一个樱桃挑选的系列以维护它,那么岂不是很好?

最初的尝试是:git rebase --preserve-merges

然而,那个实验从未被视为交互式选项,而是只在 git rebase --interactive 上搭便车,因为那个命令的实现看起来非常熟悉:那是同一个设计了 --preserve-merges 的人:作者本人。

在“亲笔”一词中,作者指的是他自己:Johannes Schindelin (dscho),他是 Git For Windows 的主要原因(与其他几位英雄 - Hannes、Steffen、Sebastian等人一起 - 即使回到那个时代 - 2009年 - 那也不容易)。
自2015年9月以来,他一直在微软工作since Sept. 2015,这是有道理的,考虑到微软现在大量使用Git并需要他的服务。
实际上,这种趋势始于2013年,当时TFS。从那时起,微软管理地球上最大的Git存储库!而且,自2018年10月以来,微软收购了GitHub

您可以在2018年4月的Git Merge 2018中观看 Johannes的讲解视频

过了一段时间,另一个开发人员(我指的是你,Andreas!;-))决定允许--preserve-merges--interactive相结合(有附带条件!),而Git维护者(实际上是在Junio离开期间的临时Git维护者)同意了。这也是--preserve-merges设计的魅力开始迅速破裂并变得不再美好的时候。

这里的Jonathan谈论的是来自Suse的Andreas Schwab
您可以看到他们在2012年的一些讨论。

原因是什么?在“--preserve-merges”模式下,合并提交的父提交(或任何提交)没有明确说明,而是通过传递给“pick”命令的提交名称隐含表示。这使得例如重新排序提交变得不可能。更不用说将提交移动到其他分支或将主题分支拆分为两个了。
遗憾的是,这些缺点也阻止了该模式(其最初的目的是为Git for Windows的需求服务,并带有额外的希望它也可能对其他人有用)来满足Git for Windows的需求。
五年后,Git for Windows中有一个笨重、大杂烩的部分相关、部分无关的补丁系列,这是基于核心Git的标签不时地进行变基(赢得了那个注定失败的git-remote-hg系列的开发者的不应得的愤怒,该系列首先淘汰了Git for Windows的竞争方法,但后来被放弃了),这真的是不可行的。于是 "Git garden shears" 诞生了:这是一个脚本,附加在交互式变基之上,首先确定要变基的补丁的分支拓扑结构,创建一个伪待办事项列表以供进一步编辑,将结果转换为真正的待办事项列表(大量使用“exec”命令来“实现”缺失的待办事项命令),最后在新的基础提交之上重新创建补丁系列。

(在此补丁中引用了 Git 修剪剪刀脚本,commit 9055e40

那是在2013年。
需要大约三周的时间来设计并实现一个独立的脚本。不用说,这个实现需要很多年才能稳定下来,而设计本身则证明了自己是可靠的。

通过这个补丁,Git修剪剪刀的好处可以应用到git rebase -i
传递--rebase-merges选项将生成一个易于理解的待办���项列表,并且很明显如何重新排序提交
可以通过插入label命令并调用merge <label>来引入新分支。
一旦这种模式变得稳定并被广泛接受,我们就可以废弃设计错误的--preserve-merges


Git 2.19(2018年第三季度)通过使其与--exec一起工作,改进了新的--rebase-merges选项。

在"git rebase --rebase-merges"中使用"--exec"选项会将执行命令放置在错误的位置,这已得到纠正。

请查看 commit 1ace63b(2018年8月9日)和 commit f0880f7(2018年8月6日),作者是Johannes Schindelin (dscho)
(由Junio C Hamano -- gitster --合并于commit 750eb11,2018年8月20日)

rebase --exec:使其与--rebase-merges一起工作

--exec 的想法是在每个 pick 后面附加一个 exec 调用。

自从引入了 fixup!/squash! 提交后,这个想法被扩展到适用于“可能跟随 fixup/squash 链的 pick”,即在 pick 和其对应的任何 fixupsquash 行之间不会插入 exec。

当前的实现使用了一个小技巧来实现这一点:它假设只有 pick/fixup/squash 命令,然后在第一个以外的任何 pick 前插入 exec 行,并追加最后一个 exec 行。

使用 git rebase --rebase-merges 生成的待办事项列表时,这种简单的实现显示出了它的问题:当存在 label、reset 和 merge 命令时,它会产生完全错误的结果。

让我们改变实现方式,确切地做我们想要的事情:查找 pick 行,跳过任何 fixup/squash 链,然后插入 exec 行。反复洗涤。

注意:我们尽可能在注释行之前插入,因为空提交由注释的 pick 行表示(我们希望在这样的行之前插入前一个 pick 的 exec 行,而不是在其后面)。

顺便说一下,在 merge 命令之后也添加 exec 行,因为它们与 pick 命令类似:它们添加新的提交。


Git 2.22(2019 年第二季度)修复了使用 refs/rewritten/ 层次结构来存储 rebase 中间状态的问题,这从本质上使得该层次结构针对每个工作树。

请查看提交 b9317d5, 提交 90d31ff, 提交 09e6564 (2019年3月7日) 由Nguyễn Thái Ngọc Duy (pclouds)完成。
(由Junio C Hamano -- gitster --提交 917f2cd中合并,2019年4月9日)

确保后缀 /refs/rewritten/ 是每个工作树独立的

a9be29c (连续器: 使由 label 命令生成的引用工作树本地化,2018-04-25,Git 2.19)将 refs/rewritten/ 作为每个工作树引用空间添加。
不幸的是(我的错),有几个地方需要更新以确保它真正是每个工作树独立的。

``` жӣҙж–°дәҶ`add_per_worktree_entries_to_dir()`пјҢзЎ®дҝқеј•з”ЁеҲ—иЎЁжҹҘзңӢжҜҸдёӘе·ҘдҪңж ‘зҡ„`refs/rewritten/`иҖҢдёҚжҳҜжҜҸдёӘд»“еә“зҡ„гҖӮ жӣҙж–°дәҶ`common_list[]`пјҢдҪҝеҫ—`git_path()`иҝ”еӣһжӯЈзЎ®зҡ„дҪҚзҪ®гҖӮ иҝҷеҢ…жӢ¬вҖңrev-parse --git-pathвҖқгҖӮ иҝҷдёӘж··д№ұжҳҜжҲ‘еҲӣе»әзҡ„пјҢжҲ‘ејҖе§Ӣе°қиҜ•дҪҝз”Ё`refs/worktree`жқҘдҝ®еӨҚе®ғпјҢе…¶дёӯжүҖжңүеј•з”Ёе°ҶжҲҗдёәжҜҸдёӘе·ҘдҪңж ‘пјҢжІЎжңүзү№ж®ҠеӨ„зҗҶгҖӮ дёҚе№ёзҡ„жҳҜпјҢ`refs/rewritten`еңЁ`refs/worktree`д№ӢеүҚеҮәзҺ°дәҶпјҢжүҖд»ҘиҝҷжҳҜжҲ‘们иғҪеҒҡзҡ„е…ЁйғЁеҶ…е®№гҖӮ ```

在Git 2.24(2019年第4季度)中,"git rebase --rebase-merges"学会了使用不同的合并策略并向它们传递特定于策略的选项。

请查看 476998d 提交记录(2019年9月4日),作者为Elijah Newren (newren)
请查看e1fac53 提交记录, a63f990 提交记录, 5dcdd74 提交记录, e145d99 提交记录, 4e6023b 提交记录, f67336d 提交记录, a9c7107 提交记录, b8c6f24 提交记录, d51b771 提交记录, c248d32 提交记录, 8c1e240 提交记录, 5efed0e 提交记录, 68b54f6 提交记录, 2e7bbac 提交记录, 6180b20 提交记录, d5b581f 提交记录(2019年7月31日),作者为Johannes Schindelin (dscho)
(由Junio C Hamano -- gitster --917a319 提交记录中合并,2019年9月18日)


随着 Git 2.25(2020 年第一季度)的推出,用于区分工作树本地引用和仓库全局引用的逻辑已得到修复,以便更好地实现保留合并。

请查看 提交 f45f88b, 提交 c72fc40, 提交 8a64881, 提交 7cb8c92, 提交 e536b1f (2019年10月21日) 由 SZEDER Gábor (szeder) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 db806d7, 2019年11月10日)

path.c:在trie_find()中不要调用没有值的match函数

签名作者:SZEDER Gábor

'logs/refs' 不���一个工作树特定的路径,但自从 commit b9317d55a3(确保 refs/rewritten/ 是每个工作树专用的,2019-03-07,v2.22.0-rc0)以来,'git rev-parse --git-path' 如果存在尾随 '/',就会返回虚假路径:
$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

我们使用 trie 数据结构来高效地确定路径是否属于公共目录或工作树特定。
正如 b9317d55a3 触发了一个与 trie 实现本身一样古老的 bug,该实现在 4e09cf2acf 中添加("path: optimize common dir checking",2015-08-31,Git v2.7.0-rc0 -- merge 列在 batch #2 中)。
根据描述 trie_find() 的注释,它只应为 trie 包含值的键的 "/-or-\0 结尾前缀" 调用给定的匹配函数 'fn'。
这是不正确的:有三个地方调用了 trie_find() 的匹配函数,但其中一个缺少了对值存在性的检查。 b9317d55a3 添加了两个新键到 trie
- 'logs/refs/rewritten',和 - 'logs/refs/worktree',
紧挨着已经存在的 'logs/refs/bisect'。
这导致了一个路径为 'logs/refs/' 的 trie 节点,它以前不存在,并且没有附加值。查询 'logs/refs/' 会找到此节点,然后命中那个不检查值存在性的匹配函数调用,从而使用 NULL 作为值调用匹配函数。
当使用 NULL 值调用 check_common() 匹配函数时,它返回 0,表示查询的路径不属于公共目录,最终导致上面显示的虚假路径。
将缺失的条件添加到 trie_find() 中,使其永远不会使用不存在的值调用匹配函数。 check_common() 将不再需要检查是否获得了非空值,因此删除该条件。
我相信没有其他路径会导致类似的虚假输出。
据我所知,导致匹配函数使用 NULL 值调用的唯一其他键是 'co'(由于键 'common' 和 'config'),但是,由于它们不在属于公共目录的目录中,因此预期结果是工作树特定路径。
请确保使用 Git 2.34 (Q4 2021) 版本,以避免内存泄漏问题。

请查看 提交 6e65854提交 0c52cf8(2021年10月13日)和提交 e5a917f(2021年10月7日),作者是Ævar Arnfjörð Bjarmason (avar)
请查看提交 9d05b45(2021年10月7日),作者是Junio C Hamano (gitster)
(已于2021年10月25日由Junio C Hamano -- gitster --合并到提交 bfa646c中)

序列控制器:修复do_reset()中的内存泄漏问题

签名作者:Ævar Arnfjörð Bjarmason

修复一个内存泄漏问题,该问题是在9055e40("sequencer: introduce new commands to reset the revision",2018-04-25,Git v2.18.0-rc0 -- merge listed in batch #6)中引入的,该问题调用了setup_unpack_trees_porcelain()但没有相应地调用clear_unpack_trees_porcelain()

Git 2.41(2023年第二季度)简化了--rebase-merges命令行选项处理,并引入了rebase.merges配置变量。

请查看 提交 6605fb7, 提交 33561f5, 提交 7e5dcec (2023年3月25日) 由 Alex Henrie (alexhenrie) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 9142fce, 2023年4月4日)

rebase:添加一个配置选项用于--rebase-merges

签名作者:Alex Henrie

新选项的目的是为了满足那些希望默认开启--rebase-merges并且未来版本中可以轻松开启--rebase-merges而无需配置的用户需求。
将新选项命名为rebase.rebaseMerges,即使有点冗余,但与命令行选项的名称保持一致,并在.gitconfig[rebase]部分中滚动值时清晰明了。
支持将rebase.rebaseMerges设置为非特定值"true",以满足不需要或不想了解rebase-cousins和no-rebase-cousins之间区别的用户需求。
在命令行上使用没有参数的--rebase-merges将覆盖配置中rebase.rebaseMerges的任何值,以保持与其他具有可选参数和相关配置选项的命令行标志的一致性。

git config现在在其man页面中包括:

rebase.rebaseMerges

是否以及如何默认设置--rebase-merges选项。可以是rebase-cousinsno-rebase-cousins或布尔值。将其设置为true或no-rebase-cousins等同于--rebase-merges=no-rebase-cousins,将其设置为rebase-cousins等同于--rebase-merges=rebase-cousins,将其设置为false等同于--no-rebase-merges
在命令行上传递--rebase-merges,带或不带参数,会覆盖任何rebase.rebaseMerges配置。

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

撤销rebase.rebaseMerges配置选项和先前的--rebase-merges


7
我认为这应该是最佳答案,“--preserve-merges”实际上并不能如你所愿地“保留”合并,它非常幼稚。此选项允许您在交互式变基时保留合并提交及其父提交关系,同时保持灵活性。这个新特性很棒,如果不是这篇写得好的S.O.回答,我就不会知道了! - egucciar
1
如果您正在尝试像在此问答中那样移动提交块,那么这将非常有帮助:https://dev59.com/hKPia4cB1Zd3GeqPxV2E - okovko
1
哦,这正是我一直在寻找的!我曾经有一个手动解决方法,用于像这样的情况,其中应该创建一个虚假提交来连接所有合并。 - carnicer
13
典型的Git。如果您敢问一个简单的问题,那么您很可能需要了解Git的历史、内部算法、所有混乱的实现细节,还需要一些图论的主修才能理解发生了什么。 - Dimitrios Menounos
@VonC 哈哈!那正是我经常做的事情。 - Dimitrios Menounos
显示剩余7条评论

5
对于因“拉取”操作而遇到该消息的用户:
git pull
(...)
warning: git rebase --preserve-merges is deprecated. Use --rebase-merges instead.

请查看您的 ~/.gitconfig 和 /etc/gitconfig 文件,并搜索以下选项:

[pull]
  rebase = preserve

接下来前往该文档,根据您的需求进行理解和修复:https://www.git-scm.com/docs/git-config#Documentation/git-config.txt-pullrebase


preserve 在 v2.32.0 版本中被替换为 merges,详情请参阅 https://www.git-scm.com/docs/git-config/2.32.0#Documentation/git-config.txt-pullrebase。 - go2null

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