git“rebase --preserve-merges”到底是做什么的(为什么?)

时间:2013-04-10 01:29:50

标签: git git-rebase

Git的documentation for the rebase command非常简短:

--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时会发生什么?它与默认行为(没有该标志)有何不同? “重新创建”合并等意味着什么呢?

3 个答案:

答案 0 :(得分:422)

与普通的git rebase一样,带有--preserve-merges的git首先标识提交图的一部分中提交的提交列表,然后在另一部分之上重放这些提交。与--preserve-merges的差异涉及选择哪些提交进行重放以及重放如何用于合并提交。

更明确地说明正常和合并保留的rebase之间的主要区别:

  • 合并保留rebase愿意重放(某些)合并提交,而普通rebase完全忽略合并提交。
  • 因为它愿意重放合并提交,所以合并保留rebase必须定义意味着什么来重放合并提交,并处理一些额外的皱纹
    • 从概念上讲,最有趣的部分可能是选择新提交的父母应该合并的内容。
    • 重播合并提交还需要明确检出特定提交(git checkout <desired first parent>),而正常的rebase不必担心这一点。
  • 合并保留rebase考虑重放的较浅提交集:
    • 特别是,它只会考虑重放自最近的合并基础以来所做的提交 - 即最近的时间两个分支分歧 - 而正常的rebase可能会重播提交回到第一个时间,两个分支分道扬。
    • 暂时且不清楚,我相信这最终是一种筛选重播和旧提交的方法。已经被&#34;纳入&#34;合并提交。

首先,我将尝试描述&#34;足够准确&#34; rebase --preserve-merges的作用是什么,然后会有一些例子。当然,如果这看起来更有用,可以从示例开始。

&#34;简要&#34;

中的算法

如果你想真正进入杂草,请下载git源并浏览文件git-rebase--interactive.sh。 (Rebase不是Git的C核心的一部分,而是用bash编写的。而且,在幕后,它与&#34;交互式rebase&#34;共享代码。)

但在这里,我将描绘我认为它的本质。为了减少要思考的事物的数量,我采取了一些自由。 (例如,我不会尝试以100%的准确度捕获计算发生的精确顺序,并忽略一些不那么集中的主题,例如,如何处理已经在分支之间挑选的提交)。

首先,请注意非合并保留的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.

Rebase --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参数的Rebase应该非常相似。而不是在B的HEAD处开始提交回放,而是开始在C的HEAD处提交回放。 (并使用C_new而不是B_new。)

示例1

例如,参考提交图

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

m是与父母E和G的合并提交。

假设我们使用普通的非合并保留在主(C)之上重新定位主题(H) 变基。 (例如,结帐主题; rebase master 。)在这种情况下,git会选择 以下提交重播:

  • 选择D
  • 选择E
  • 选择F
  • 选择G
  • 选择H

然后像这样更新提交图:

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

(D&#39;是D等的重播等价物。)

请注意,未选择合并提交m进行重播。

如果我们在C之上做了--preserve-merges H的重组(例如,结帐主题; rebase --preserve-merges master 。)在这个新案例中,git会选择以下提交重播:

  • 选择D
  • 选择E
  • 选择F(在&#39; subtopic&#39;分支中的D&#39;)
  • 选择G(在&#39;子主题&#39;分支中的F&#39;)
  • 选择合并分支&#39;副主题&#39;进入主题
  • 选择H

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

以下是生成的提交图:

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

再次,D&#39;是一个樱桃挑选(即重新创建)版本的D.相同的E&#39;等等。每次不在主人身上的提交都被重播。 E和G(m的合并父母)都被重新创建为E&#39;和G&#39;作为m&#39;的父母。 (在rebase之后,树历史仍然保持不变)。

示例2

与普通的rebase不同,合并保留的rebase可以创建多个 上游人员的孩子们。

例如,考虑:

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

如果我们在C(master)之上重新定义H(主题),那么为rebase选择的提交是:

  • 选择D
  • 选择E
  • 选择F
  • 选择G
  • 选择m
  • 选择H

结果如下:

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

示例3

在上面的示例中,合并提交及其两个父项都是重放提交,而不是原始合并提交所具有的原始父项。但是,在其他rebase中,重放的合并提交最终可能会在合并之前已经在提交图中的父级。

例如,考虑:

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

如果我们将主题重新设置为主(保留合并),那么重播的提交将是

  • 选择合并提交m
  • 选择F

重写的提交图将如下所示:

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

这里重播合并提交m&#39;获取预先存在于提交图中的父母,即D(主人的HEAD)和E(原始合并提交的父母之一)。

示例4

合并保留的rebase可能会在某些“空提交”中感到困惑。案例。至少这只是一些旧版本的git(例如1.7.8。)

拿这个提交图:

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

请注意,提交m1和m2都应包含B和F中的所有更改。

如果我们尝试将H(主题)的git rebase --preserve-merges放到D(主)上,则选择以下提交进行重播:

  • 选择m1
  • 选择H

请注意,在m1中联合的变化(B,F)应该已经合并到D中。(这些变化应该已经合并到m2中,因为m2将B和F的子项合并在一起。)因此,从概念上讲,重放m1在D之上应该是一个no-op或者创建一个空提交(即连续修订之间的差异是空的)。

相反,git可能会拒绝在D之上重播m1的尝试。您可能会收到如下错误:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

看起来好像忘记了将标志传递给git,但是潜在的问题是git不喜欢创建空提交。

答案 1 :(得分:44)

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

&#34; git rebase&#34;学会了#34; --rebase-merges&#34; 移植整个 其他地方提交图的拓扑

(注:Git 2.22,2019年第二季度,实际上是deprecates --preserve-merge

请参阅commit 25cff9fcommit 7543f6fcommit 1131ec9commit 7ccdf65commit 537e7d6commit a9be29ccommit 8f6aed7,{{3} },commit 1644c73commit d1e8b01commit 4c68e7dcommit 9055e40commit cb5206ecommit a01c2a5commit 2f6b1d1(2018年4月25日){ {3}}。
commit bf5c057Johannes Schindelin (dscho)(2018年4月25日) commit f431d73Stefan Beller (stefanbeller)(2018年4月25日) (由commit 2429335合并于Phillip Wood (phillipwood),2018年5月23日)

  

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

     

preserve模式类似,只需传递--preserve-merges   rebase命令的选项,merges模式只是传递   --rebase-merges选项。

     

这将允许用户方便地重新定义非平凡的提交   提取新提交时的拓扑结构,而不会使它们变平。

git rebase手册页现在有Junio C Hamano -- gitster --

提取物:

  

开发人员可能有合理的理由   重新创建合并提交:保持分支结构(或#34;提交   拓扑&#34;)处理多个相互关联的分支时。

     

在以下示例中,开发人员处理主题分支   重构按钮的定义方式,以及另一个主题分支   使用该重构来实现&#34;报告错误&#34;按钮。
  git log --graph --format=%s -5的输出可能如下所示:

*   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
     

开发人员可能希望将这些提交重新绑定到较新的master   同时保持分支拓扑,例如第一个主题时   分支预计将早于master整合到DownloadButton   第二个,比方说,解决合并冲突与改变   master的{​​{1}}课程。

     

可以使用--rebase-merges选项执行此rebase。

请参阅commit 2c18e6a以获取一个小例子:

  

rebase-helper --make-script:为rebase合并引入一个标志

     

序列发生器刚学会了用于重新创建分支的新命令   结构(--preserve-merges精神相似,但有一个   基本上不太破碎的设计)。

     

让我们rebase--helper生成使用的待办事项列表   这些命令由新的--rebase-merges选项触发   对于像这样的提交拓扑(HEAD指向C的位置):

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

生成的待办事项列表如下所示:

# 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有什么不同?
full section dedicated to rebasing history with merges解释说:

  

曾几何时,这位开发人员认为:如果,那不是很好   比方说,Git for Windows&#39;核心Git顶部的补丁可以表示为   一堆树枝,并在核心Git之上重新定位   保持一套樱桃挑选的补丁系列?

     

最初的回答是:git rebase --preserve-merges

     

然而,该实验从未打算作为互动选项,   它只能在git rebase --interactive上捎带,因为那样   命令的实现看起来已经非常非常熟悉了:它确实如此   由设计--preserve-merges的同一个人设计:真实。

并且真的&#34;你的真实&#34;,作者自称: commit 1644c73 ,谁是主要原因(与其他一些英雄 - Hannes,Steffen ,塞巴斯蒂安,...)我们有Git For Windows(即使Commit 8f6aed7) 他现在在微软工作,因为微软现在使用大量Git并需要他的服务 那Johannes Schindelin (dscho)。从那时起,Microsoft管理back in the day -- 2009 -- that was not easy!而且,trend started in 2013 actually, with TFS

您可以在2018年4月看到{GMS Merge 2018的the largest Git repository on the planet

  

一段时间后,其他一些开发者(我正在看着你,Andreas!   ;-))决定允许--preserve-merges为一个好主意   与--interactive(有警告!)和Git维护者结合使用   (好吧,Junio缺席期间的临时Git维护者,就是这样)   同意了,那就是--preserve-merges设计的魅力所在   开始相当迅速和无礼地分崩离析。

Jonathan在这里谈论Suse的 since Oct. 2018, Microsoft acquired GitHub 你可以看到一些Johannes speak in this video

  

原因? --preserve-merges模式下,合并提交的父级(或   就此而言,任何提交)没有明确说明,但是   传递给pick命令的提交名称​​暗示

     

例如,这使得无法重新提交提交   更不用提移动分支之间的提交,或者神禁止,将主题分支分成两部分。

     唉,这些缺点也阻止了这种模式(原来的模式)   目的是为Git for Windows&#39;提供服务。需要,带来额外的希望   从服务Git for Windows&#39;它可能对其他人也有用。   需要。

     

五年之后,当一个人笨重的时候真的站不住脚,   大杂烩补丁系列部分相关,部分无关的补丁   在Git for Windows中,从一开始就重新定位到核心Git的标签上   时间(赢得了不幸的开发者的不应有的愤怒   git-remote-hg系列首次淘汰Git for Windows&#39;竞争   方法,只是在没有维护者的情况下被抛弃)真的   站不住脚,&#34; Andreas Schwab&#34; their discussions back in 2012:一个脚本,   在交互式rebase之上捎带,首先   确定要重新绑定的补丁的分支拓扑,创建一个   伪待办事项列表进行进一步编辑,将结果转换为真实   todo list(大量使用exec命令来实现&#34; the   缺少todo list命令)并最终重新创建补丁系列   新基础提交的顶部。

(在Git garden shears

中的此补丁中引用了Git园林剪切脚本
  

那是在2013年。
  花了大约三个星期的时间来设计并将其作为一个树外脚本来实现。毋庸置疑,实施需要相当长的时间来稳定,而设计本身一直证明自己是健全的。

     

通过此补丁,Git园林剪刀的优点来自git rebase -i本身
  传递--rebase-merges选项将生成   一个易于理解的待办事项清单,以及明显的清单   如何重新提交提交
  可以通过插入label命令并调用merge <label>来引入新分支   一旦此模式变得稳定并被普遍接受,我们就可以弃用--preserve-merges 的设计错误。

Git 2.19(2018年第3季度)通过使其与--rebase-merges一起使用来改进新的--exec选项。

&#34; --exec&#34; &#34; git rebase --rebase-merges&#34;的选项放了执行官 命令在错误的地方,已经纠正。

were borncommit 9055e40(2018年8月9日)和commit 1ace63b(2018年8月6日)commit f0880f7
(由Johannes Schindelin (dscho)合并于Junio C Hamano -- gitster --,2018年8月20日)

  

rebase --exec:使其适用于--rebase-merges

     

--exec的想法是在每个exec之后附加pick来电。

     

自提交fixup! / s quash!提交以来,这一想法得到了扩展   适用于&#34;选择,可能后跟一个修正/壁球链&#34;,即一个   不会在pick和任何相应的fixup之间插入exec   squashexec行。

     

当前实现使用脏技巧来实现:它   假设只有pick / fixup / squash命令,然后   在任何pick之前插入 git rebase --rebase-merges行,但第一行,并追加   最后一个。

     

使用label生成的待办事项列表,这个   简单的实现显示了它的问题:它产生了完全错误   有resetmergepick命令的事情。

     

让我们改变实现以完全符合我们的要求:寻找   exec行,跳过任何fixup / squash链,然后插入exec   线即可。泡沫,冲洗,重复。

     

注意:我们尽可能在注释行之前插入   因为空提交由注释掉的选择线表示(我们   想要在这样一行之前插入前面一个选择的执行行,而不是   之后)。

     

在此期间,还要在merge命令之后添加pick行,因为它们   在精神上类似于label命令:它们添加了新的提交。

Git 2.22(2019年第二季度)修复了refs / rewritten / hierarchy的用法,用于存储rebase中间状态,这固有地构成了每个层次结构 worktree。

commit 750eb11commit b9317d5commit 90d31ffcommit 09e6564(2019年3月7日)。
Nguyễn Thái Ngọc Duy (pclouds)合并于Junio C Hamano -- gitster --,2019年4月9日)

  

确保refs / rewritten /是per-worktree

commit 917f2cd(sequencer:使用refs/rewritten/命令生成引用 worktree-local,2018-04-25,Git 2.19)按工作树添加- add_per_worktree_entries_to_dir() 参考空间。
不幸的是(我的坏)有几个地方 需要更新,以确保它真正的per-worktree。

  

refs/rewritten/已更新,以确保参考列表      查看per-worktree common_list[]而不是per-repo one。

     
      
  • git_path()已更新,以便rev-parse --git-path返回正确的内容   地点。这包括&#34; refs/worktree,&#34;。
  •   
     

这个烂摊子是我创造的   我开始尝试通过引入{{1}}来修复它,其中所有参考都将是每工作树而无需特殊处理。
  在refs / worktree之前出现了不幸的引用/重写,所以这就是我们所能做的。

答案 2 :(得分:2)

对于那些仅仅因为他们拉取并收到该消息而在这里结束的人:

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