Git拉取--变基--保留合并

38
需要保留合并是仅在本地提交后显式合并时才需要吗?否则会发生什么?它会重新应用您提交的代码到合并的分支吗?
请解释何时使用git pull --rebase --preserve-merges 和常规的git pull --rebase有用? 我在这里阅读了有关git pull --rebase问题的信息: http://notes.envato.com/developers/rebasing-merge-commits-in-git/ 这可能导致代码更改重复。
我在这里阅读:When will `git pull --rebase` get me in to trouble? 如果您在一些提交被推送后进行基础变形,那么它只会在这种情况下发生。
因此,我不确定何时需要git pull --rebase --preserve-merges,以及与 git pull --rebase 相比是否有坏处。

我现在觉得我理解了,多亏了那个链接。据我所见,如果我不保留合并,它只会将我写的代码添加到最近的当前提交之后的所有提交中,而忽略我创建和合并的任何分支?如果我保留合并,它会将提交向前移动,但保持分叉的分支。 - AturSams
我编辑了这个问题。如果我理解正确,如果你提交(本地),然后获取和合并,然后拉取和变基,它会以某种方式复制你的更改? - AturSams
我使用 git pull --rebase=merges - marbel82
2个回答

58
从技术上讲,我认为 Git 的做法有点愚蠢,pull 脚本(一个 shell 脚本)应该为你执行这个操作。因此,你需要运行 git pull --rebase=preserve 而不是尝试使用 git pull --rebase --preserve-merges。(或者,如我在 Vlad Nikitin's answera comment 中所指出的那样,你可以将 branch.name.rebase 设置为 preserve 来自动获取相同的效果。)
换句话说,你永远不应该运行 git pull --rebase --preserve-merges,因为它会(错误地)将 --preserve-merges 传递给 fetch 步骤,而不是传递给 mergerebase 步骤。但是,你可以运行 git pull --rebase=preserve
“何时(以及是否)使用任何类型的rebase,无论是保留合并还是不保留,这更多是一种观点问题。这意味着它在stackoverflow上并不适合。:-)
尽管如此,我会在这里提出一个声明:只有当您知道(在某种一般意义上)您正在做什么时,才应进行rebase1,如果您确实知道自己在做什么,您可能通常会首选保留合并的rebase,尽管到您决定rebase是个好主意的时候,您可能会发现具有其自身嵌入式分支和合并点的历史记录不一定是正确的“最终重写历史”。
也就是说,如果适合进行rebase,那么被rebase的历史记录本身很可能是线性的,因此保留与否的问题无关紧要。”

编辑:添加绘图

这是提交图的一部分绘图,显示了两个命名分支mainlineexperimentmainlineexperiment的共同基础是提交节点A,而mainline有一个提交G不在experiment分支上:

...--o--A-------------G   <-- mainline
         \
          \ .-C-.
           B     E--F     <-- experiment
            \_D_/

请注意,experiment分支也有一个分支和合并的过程:这两个分支的基础都是B,其中一个分支持有提交C,另一个分支则持有提交D。这两个未命名的分支在合并提交E时会收缩回单一的开发线,并且提交F位于合并提交之上成为experiment分支顶端。如果您在experiment分支上运行git rebase mainline,将会发生以下情况:
$ git rebase mainline
First, rewinding head to replay your work on top of it...
Applying: B
Applying: C
Applying: D
Applying: F

这是当前提交图中的内容:

...--o--A--G               <-- mainline
            \
             B'-C'-D'-F'   <-- experiment

在分支experiment上曾经有一个“结构性分支”,现在已经不存在了。 rebase操作复制了我在提交BCDF中所做的所有更改;这些更改成为新提交B'C'D'F'。 (提交E是一个纯合并,没有更改,不需要复制。我还没有测试如果我重写嵌入更改的合并会发生什么,无论是为了解决冲突还是像一些人所说的那样进行“恶意合并”)。
另一方面,如果我这样做:
$ git rebase --preserve-merges mainline
[git grinds away doing the rebase; this takes a bit longer
than the "flattening" rebase, and there is a progress indicator]
Successfully rebased and updated refs/heads/experiment.

我得到了这张图表:
...--o--A--G               <-- mainline
            \
             \ .-C'.
              B'    E'-F'  <-- experiment
               \_D'/

这样做保留了合并和实验的“内部分支”,这是好的吗?不好吗?还是无所谓?请阅读(非常长的)脚注!

1无论如何,学习“rebase”的作用都是一个好主意,而在git中(唉!)基本上需要在中等水平上学习“它是如何做到的”。基本上,rebase会复制(您早期更改的)提交,然后将其应用于(您或其他人的)后续提交,使其“看起来”像您以某种其他顺序完成了工作。一个简单的例子:两个开发人员,假设是Alice和Bob,都在同一个分支上工作。假设Marketing要求一个名为Strawberry的功能,Alice和Bob都在实现strawberry的工作中,都在名为strawberry的分支上。

Alice和Bob都运行git fetchorigin获取strawberry

Alice发现文件abc需要一些更改来准备新功能。她写下并提交了更改,但还没有推送。

Bob编写了一个新功能的描述,更改了文件README,但没有其他影响。Bob提交了他的更改并推送。

Alice随后更新文件feat以提供实际的功能。她分别编写和提交了这个内容,现在准备推送。但是,糟糕的是,Bob先于她完成了这个操作:

$ git push origin strawberry
...
! [rejected]        strawberry -> strawberry (non-fast-forward)

Alice应该获取更改并查看它们(不仅仅是盲目地合并或变基):
$ git fetch
...
$ git log origin/strawberry

(或者使用gitk或任何其他工具 - 我自己倾向于使用git lola,并根据需要使用git show查看单个提交)。

从这个示例中可以看出,Bob只更改了README,因此她的更改不会受到影响。此时,她可以确定安全地将自己的更改合并到origin/strawberry上:

$ git rebase origin/strawberry

请注意,没有合并来保留,这使得它(在Git历史方面)看起来像是她先等待Bob更新文档,然后才开始实际实现更改 - 这些更改仍然分为两个单独的提交,以便稍后可以轻松地确定文件abc的更改是否会导致其他问题。但是,这两个单独的提交现在是相邻的,因此很容易看出更改abc目的是为了启用对文件feat的更改。由于更改README首先出现,因此更清楚这是更改abc目的。即使Alice只是:
$ git merge origin/strawberry

然而,这样会创建一个合并提交,其唯一的作用似乎是说“Alice在Bob更新README之前开始了abc,并在完成feat后结束”,这并不真正有帮助。

在更复杂的情况下,如果Bob不仅仅更新文档,Alice可能会发现最好将自己的提交(在这种情况下可能超过两个)重新排列成新的、不同的线性历史记录,以便Bob的某些更改(这次可能超过一个提交)“在中间”,例如,就像他们实时合作一样(谁知道,也许他们确实这样做了)。或者她可能会发现,最好将自己的更改保留为单独的开发线,与Bob的更改合并,甚至可能多次合并。

这完全取决于以后是否需要回顾(看起来的,如果是变基,或者实际的,如果不是)事件序列,为某些人提供最有用信息,可能是Alice和Bob,也可能是其他开发者。有时候每个单独的提交都是有用的信息。有时候重新排列和合并提交或删除某些提交更有用,例如证明是个坏主意的更改(但考虑保留它们以指出“这是个坏主意,所以将来不要再尝试”!)


2
一如既往地详细和信息丰富... 等等,今天我已经写过了,对吧? +1 无论如何。 关于如何知道重定基是否“可能”:http://stackoverflow.com/a/21362208/6309 - VonC
1
我不确定何时需要/有帮助地保留(preserve)?这只是一个观点问题吗?这是我不太自信的部分:“也就是说,如果重新设置基础是合适的,那么要重新设置的历史记录本身至少很可能是线性的,因此保留与平铺的问题无关。”。什么是保留而不是平铺?以前在分支之间有意进行的合并? - AturSams
preserve设置保留合并。通常情况下,如果没有明确定义“分支”,则不可能将其描述为“在分支之间”。因为git使术语本身存在歧义。如果需要的话,我可以画出它的操作过程... - torek
1
我猜问题的根源在于我工作的公司和我自己都不喜欢无意义的合并提交。由于我有点谨慎地使用git,而且大多数时候都会使用stash来处理未提交的多个更改,所以我想我永远不会遇到重置会产生不必要结果的情况(而仅会达到避免不必要的空合并提交的目标)。我想现在我知道如何避免rebase中的陷阱了。 - AturSams
@ArthurWulfWhite:如果你避免创建空合并,通常在你要变基的链上就不会有任何合并,因此保留/不保留是翻转一个不影响任何东西的开关。如果你确实意外地创建了一个不想要的空合并,你可以将其变基。 - torek
显示剩余2条评论

-2

git pull --rebasegit fetchgit rebase 是一样的,所以 git pull --rebase --preserve-merges 也就等同于先执行 git fetch,然后再执行 git rebase --preserve-merges。你可以在这里(What exactly does git's "rebase --preserve-merges" do (and why?))了解关于 git rebase --preserve-merges 的更多内容。


2
据我所知,git pull无论如何都不会使用--preserve-merges选项。问题中链接的文章指出,您需要先执行fetch,然后再执行rebase - Hasturkun
2
@Hasturkun:现在有一个相对较新的 --rebase=preserve 选项(或者你可以将 branch.<name>.rebase 设置为 preserve)。 - torek

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