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

Chr*_*ris 328 git git-rebase

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).
Run Code Online (Sandbox Code Playgroud)

那么当你使用时会发生什么--preserve-merges?它与默认行为(没有该标志)有何不同?"重新创建"合并等意味着什么?

Chr*_*ris 449

与普通的git rebase一样,git with --preserve-mergesfirst标识了提交图的一部分中提交的提交列表,然后在另一部分之上重放这些提交.--preserve-merges关注哪些提交被选择用于重放以及重放如何用于合并提交的差异.

更加明确正常和合并保留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%的准确度捕获计算发生的精确顺序,并忽略一些不那么集中的主题,例如,如何处理已经在分支之间挑选的提交).

首先,请注意非合并保留的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.
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

--onto C参数的Rebase 应该非常相似.而不是在B的HEAD处开始提交回放,而是开始在C的HEAD处提交回放.(并使用C_new而不是B_new.)

例1

例如,采用提交图

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G
Run Code Online (Sandbox Code Playgroud)

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

假设我们使用普通的非合并保留rebase在master(C)之上重新设置主题(H).(例如,checkout主题; rebase master.)在这种情况下,git会选择以下提交进行重放:

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

然后像这样更新提交图:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic
Run Code Online (Sandbox Code Playgroud)

(D'是D等的重播等价物.)

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

如果我们--preserve-merges在C之上做了一个H 的rebase.(例如,checkout主题; rebase --preserve-merges master.)在这个新的情况下,git将选择以下提交进行重放:

  • 选择D.
  • 选择E.
  • 选择F(到'subtopic'分支中的D')
  • 选择G(在'subtopic'分支中的F'上)
  • 选择合并分支'子主题'到主题
  • 选择H.

现在我选中进行重播.另请注意,合并父项E和G在合并提交之前被选中包含.

以下是生成的提交图:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'
Run Code Online (Sandbox Code Playgroud)

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

例2

与普通的rebase不同,合并保留的rebase可以创建上游头的多个子节点.

例如,考虑:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 
Run Code Online (Sandbox Code Playgroud)

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

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

结果如下:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/
Run Code Online (Sandbox Code Playgroud)

例3

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

例如,考虑:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic
Run Code Online (Sandbox Code Playgroud)

如果我们将主题重新命名为master(保留合并),那么重放的提交将是

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

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

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic
Run Code Online (Sandbox Code Playgroud)

这里重放合并提交m'得到提交图中预先存在的父,即D(主设备的HEAD)和E(原始合并提交的父设备之一).

例4

在某些"空提交"案例中,合并保留的rebase可能会混淆.至少这只是一些旧版本的git(例如1.7.8.)

拿这个提交图:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic
Run Code Online (Sandbox Code Playgroud)

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

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

  • 选择m1
  • 选择H.

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

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

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed
Run Code Online (Sandbox Code Playgroud)

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

  • @DarVar你总是在一个rebase上放松历史记录,因为你声称改变了与实际位置不同的代码库. (19认同)
  • 我注意到`git rebase --preserve-merges`比没有`--preserve-merges`的`rebase`慢很多**.这是找到正确提交的副作用吗?有什么办法可以加快速度吗?(顺便说一下......感谢非常详细的答案!) (6认同)
  • 听起来你应该总是使用--preserve-merges.否则有可能丢失历史记录,即合并提交. (6认同)
  • @Chronial当然你是对的,那种变相总是包含失败的历史,但也许DarVar暗示了这样一个事实:你不仅要放弃历史,还要改变代码库.冲突解决包含可以通过所有可能的方式丢失信息的信息.你总是要重做它.难道让git重做你的冲突解决方案吗?为什么不能git cherry-pick合并提交? (5认同)
  • 这还是一个"临时答案"吗? (4认同)
  • 有没有办法使用git config强制全局保留所有rebase?对于git pull --rebase,我们可以使用`git config --global pull.rebase preserve`,但对于经典的rebase?这会有用吗?`git config --global branch.rebase preserve` (3认同)
  • `--preserve-merges` 已弃用。使用 `--rebase-merges` 或 `-r` 模式,其精神相似并且运行速度更快。 (3认同)

Von*_*onC 67

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

" git rebase"学会了" --rebase-merges"将提交图的整个拓扑移植到其他地方.

请参阅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(--preserve-merge).
Stefan Beller()提交f431d73(2018年4月25日). 见Phillip Wood()提交2429335(2018年4月25日).(由Junio C Hamano合并- -提交2c18e6a,2018年5月23日)git rebase --help
dscho
stefanbeller

phillipwood:accept gitster以重新创建分支拓扑

类似于pull简单地将--rebase-merges 选项传递给preserve命令的--preserve-merges模式,模式只是传递 rebase选项.

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


merges手册页现在有一个完整的部分专门用于通过合并来重新定义历史记录.

提取:

开发人员可能想要重新创建合并提交的正当理由是:在处理多个相互关联的分支时保留分支结构(或"提交拓扑").

在下面的示例中,开发人员处理主题分支,该分支重构按钮的定义方式,以及使用该重构实现"报告错误"按钮的另一个主题分支.
输出--rebase-merges可能如下所示:

*   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
Run Code Online (Sandbox Code Playgroud)

开发人员可能希望git rebase 在保留分支拓扑的同时将这些提交重新设置为更新,例如,当第一个主题分支预期git log --graph --format=%s -5比第二个主题分支更早地集成时,比如,解决合并冲突与对master类的更改 进入master.

可以使用该DownloadButton选项执行此rebase .


请参阅提交1644c73以获取一个小示例:

master --rebase-merges:为rebase合并引入一个标志

序列器刚刚学习了用于重新创建分支结构的新命令(在精神上类似rebase-helper,但设计基本上没那么破碎).

让我们允许--make-script生成使用这些命令的待办事项列表,由新--preserve-merges选项触发.
对于像这样的提交拓扑(HEAD指向C):

- A - B - C (HEAD)
    \   /
      D
Run Code Online (Sandbox Code Playgroud)

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

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

reset branch-point
pick 2345 B
merge -C 3456 D # C
Run Code Online (Sandbox Code Playgroud)

有什么区别rebase--helper
Commit 8f6aed7解释说:

曾几何时,这位开发人员认为:如果说Git for Windows在核心Git之上的补丁可以表示为分支的丛林,并且在核心Git之上进行重新定位,那么这不是很好吗保持一套樱桃挑选的补丁系列?

最初的回答是:--rebase-merges.

然而,这个实验从来没有打算作为一个交互选项,它只是捎带,--preserve-merge因为该命令的实现看起来已经非常非常熟悉:它是由设计的同一个人设计的git rebase --preserve-merges:你的真实.

并以"你的真正",笔者指自己: 约翰内斯Schindelin( )git rebase --interactive,谁是最主要的原因(有一些其他的英雄-汉纳斯,斯特芬,塞巴斯蒂安,...),我们有混帐对于Windows(即使回到2009年的那一天 - 这并不容易).
他现在在微软工作,因为微软管理着这个星球上最大的Git存储库!

您可以 2018年4月的视频中看到Johannes在 Git Merge 2018中发表演讲.

一段时间后,一些其他的开发者(我在看你,安德烈!;-))决定,这将是一个好主意,让--preserve-merges与组合dscho(带警告!)和Git的维护者(当然,临时Git的维护者在Junio缺席期间,那是同意的,那时--preserve-merges设计的魅力开始相当迅速而且不合理地分崩离析.

Jonathan在这里谈论Suse的Andreas Schwab.
你可以在2012年看到他们的一些讨论.

原因?--interactive模式中,合并提交的父级(或任何提交的父级)未明确声明,但是 由传递给命令的提交名称暗示--preserve-merges.

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

唉,这些缺点也阻止了这种模式(其最初目的是为Git提供Windows的需求,并希望它也可能对其他人有用)从服务Git for Windows的需求.

五年后,当它成为真正站不住脚有一个笨重的大大杂烩贴片系列Git中的Windows部分相关,部分不相关的补丁被重订到核心Git的标签不时的(收入开发商的不当之怒命运多舛的 --preserve-merges系列第一废弃的Git用于Windows的竞争办法,只是没维护者被抛弃后)真的站不住脚的' 混帐园林剪诞生:一个脚本,捎带上互动底垫之上,这将首先确定要重新定位的补丁的分支拓扑,创建一个伪待办事项列表以供进一步编辑,将结果转换为真正的待办事项列表(大量使用该pick命令来"实现"丢失的待办事项列表命令),最后在新的基本提交之上重新创建补丁系列.

(Git园林剪切脚本在提交9055e40中的此补丁中引用)

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

有了这个补丁,Git的园林剪的善良来git-remote-hg本身.
传递该exec选项将生成一个可以很容易理解的待办事项列表,并且很明显如何重新提交提交.
可以通过插入git rebase -i命令和调用来引入新分支--rebase-merges.
一旦这种模式变得稳定并被普遍接受,我们就可以弃用设计错误了label.


Git 2.19(2018年第3季度)merge <label>通过使用它来改进新选项--preserve-merges.

" --rebase-merges"选项" --exec"将exec命令放在错误的位置,这已被纠正.

参见commit 1ace63b(2018年8月9日),并由Johannes Schindelin()提交f0880f7(2018年8月6日).(由Junio C Hamano合并- -提交750eb11,2018年8月20日)--exec
git rebase --rebase-merges

dscho:让它工作 gitster

想法rebase --exec--rebase-merges在每个之后追加一个电话--exec.

由于引入的exec/ s的pick提交时,这个想法被扩展到适用于"挑,可能接着是修正/壁球链",即一个exec将不是一个之间插入fixup!任何其相应的 quash!pick线.

当前实现使用一个卑鄙的手段来实现这一目标:它假设有只挑选/修正/壁球命令,然后 插入fixup之前的任何线squash,但第一,并附加最后一节.

随着所产生的待办事项列表exec,这个简单的实现显示了它的问题:它产生的确切错误的事情时有pick,git rebase --rebase-mergeslabel命令.

让我们改变实现,使其完全符合我们的要求:查找 reset行,跳过任何fixup/squash链,然后插入merge.泡沫,冲洗,重复.

注意:我们尽可能注释行之前插入一些注意事项,因为空提交由注释掉的选择线表示(我们希望这样的一行之前插入前一个选择的exec 行,而不是之后).

在它的同时,也可以pickexec命令之后添加行,因为它们在命令上与它们类似exec:它们添加了新的提交.

  • 典型的 Git。如果你敢问一个简单的问题,你很可能必须学习 Git 的历史、内部算法、所有混乱的实现细节,此外你还需要主修图论才能理解正在发生的事情。 (7认同)
  • 我认为这应该是最好的答案,`--preserve-merges`实际上并不会根据需要“保留”合并,这非常幼稚。这使您可以保留合并提交及其父提交关系,同时为您提供交互式变基的灵活性。这个新功能很棒,如果不是写得很好的答案,我将不知道! (2认同)

小智 5

对于那些仅仅因为他们拉动并收到该消息而结束于此的人:

git pull
(...)
warning: git rebase --preserve-merges is deprecated. Use --rebase-merges instead.
Run Code Online (Sandbox Code Playgroud)

查看您的 ~/.gitconfig 和 /etc/gitconfig 并搜索此选项:

[pull]
  rebase = preserve
Run Code Online (Sandbox Code Playgroud)

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