题 你什么时候使用git rebase而不是git merge?


什么时候推荐使用 git rebase 与 git merge

成功变装后我还需要合并吗?


1249
2018-04-29 20:26


起源


看到: stackoverflow.com/questions/457927/... - TStamper
这很好: atlassian.com/git/tutorials/merging-vs-rebasing - stackexchanger
喜欢使用rebase的人的一个问题是它阻止他们定期推送他们的代码。所以想要干净的历史会阻止他们分享他们的代码,我认为这更重要。 - static_rtti
@static_rtti:那不是真的。您正在使用基于rebase的流程错误,如果它阻止您定期推送您的更改。 - juzzlin
这真是一种耻辱 安德鲁阿诺特 回答和 佩斯 答案之前没有发布,因为他们比早先已经积累了很多票的答案更全面地回答了这个问题。 - Mark Booth


答案:


简洁版本

  • Merge在一个分支中获取所有更改,并在一次提交中将它们合并到另一个分支中。
  • Rebase说我希望我能够分到一个新的起点

那么你什么时候使用其中任何一个?

合并

  • 假设您已经创建了一个分支,用于开发单个功能。如果您想将这些更改带回主人,您可能想要 合并 (您不关心维护所有临时提交)。

变基

  • 第二种情况是,如果您开始进行一些开发,然后另一位开发人员进行了无关的更改。你可能想拉,然后 变基 从repo的当前版本开始进行更改。

973
2018-04-29 20:38



@Rob提到在合并时保持临时提交。我相信默认情况下,合并分支B(您正在处理的一个功能分支)到分支M(主分支)将在M中为每个提交创建一个提交,因为两个分支是在B中进行的。但是如果使用--squash选项进行合并,则在分支B上进行的所有提交将“集中在一起”并在分支M上合并为单个提交,从而使主分支上的日志保持良好和干净。如果您有许多开发人员独立工作并合并为主人,那么压缩可能就是您想要的。 - spaaarky21
我相信@ spaaarky21关于合并的假设是不正确的。如果将分支B合并到主M中,则M上将只有一个提交(即使B有多个提交),无论您使用普通还是--squash合并。什么--squash将做的是消除对B作为父母的引用。这里有一个很好的可视化: syntevo.com/smartgithg/howtos.html?page=workflows.merge - jpeskin
@jpeskin那不是我所看到的。我刚做了一个快速测试来验证。用文本文件创建一个目录, init 一个新的回购, add 文件和 commit。结帐新功能分支(checkout -b feature。)更改文本文件,提交并重复,以便在功能分支上有两个新提交。然后 checkout master 和 merge feature。在 log,我看到我对master的初始提交,然后是从feature中合并的两个提交。如果你 merge --squash feature,功能被合并到主服务器但未提交,因此主服务器上唯一的新提交将是您自己创建的。 - spaaarky21
@ spaaarky21看起来我们都是对的。当可以进行快进合并时(如在您的示例中),git将默认包括功能分支B中的所有提交(或者如您所建议的那样,您可以使用--squash组合成单个提交)。但是在你正在合并的有两个不同的分支M和B的情况下,如果合并到M中,git将不包括来自分支B的所有单独提交(无论你是否使用--squash)。 - jpeskin
为什么“(你不关心维持所有的临时提交)”仍然在这个答案中?它在09年没有任何意义,现在没有任何意义。此外,如果另一位开发人员制作,你肯定只想要改变 相关变化 您需要的 - 如果他们进行了无关的更改,您的功能分支应该很容易合并而不会发生冲突,并且您的历史记录将被维护。 - Mark Booth


这很简单,你说使用另一个分支作为新的分支 基础 为了你的工作。

如果你有一个分支 master 并且你创建了一个分支来实现一个新功能,比如说你的名字 cool-feature当然,主分支是您新功能的基础。

现在,在某个时刻,您想要添加您在实现中实现的新功能 master 科。你可以切换到 master 并合并 cool-feature 科:

$git checkout master
$git merge cool-feature

但是这样就添加了一个新的虚拟提交,如果你想避免意大利面条历史 变基

$git checkout cool-feature
$git rebase master

然后将其合并 master

$git checkout master
$git merge cool-feature

这一次,由于主题分支具有相同的master提交以及具有新功能的提交,因此合并将只是一个快进。


289
2018-02-05 06:28



but this way a new dummy commit is added, if you want to avoid spaghetti-history  - 怎么样不好? - アレックス
此外,合并的--no-ff标志非常有用。 - Aldo 'xoen' Giambelluca
@アレックス为用户 Sean Schofield 把它放在评论中:“Rebase也很好,因为一旦你最终将你的东西合并回主人(这已经很简单,就像你已经描述的那样),你就会把它放在你提交历史的”顶部“。写了但几周后合并,你不想只是将它们合并到master中,因为它们被填充到历史记录中的主要方式。我个人喜欢能够做git log并看到最近的功能正确在“顶部。”注意保留提交日期 - rebase不会更改该信息。“ - Adrien Be
我想这里有重复 - 请记住所有这些条款(merge, rebase, fast-forward等等)指的是有向无环图的特定操作。考虑到心理模型,他们更容易推理。 - Roy Tinker
@Aldo对于重新定位的历史,没有什么“干净”或“整洁”。一般来说 污秽 和恕我直言,因为你不知道真正发生了什么。 “最干净”的Git历史就是实际发生的历史。 :) - Marnen Laibow-Koser


补充 我自己的答案 提到 由TSamper

  • 在合并之前做一个rebase通常是个好主意,因为这个想法是你在你的分支中集成 Y 分公司的工作 B 你将合并。
    但是,在合并之前,你再次解决任何冲突 你的 分支(即:“rebase”,如“从分支的最近点开始重播我在我的分支中的工作” B
    如果操作正确,则从分支到分支的后续合并 B 可以快进。

  • 合并直接影响目标分支 B,这意味着合并更好是微不足道,否则那个分支 B 可以很长时间回到稳定状态(你解决所有冲突的时间)


一次变革之后的合并点?

在我描述的情况下,我改变了 B 在我的分支上,只是为了有机会从最近的一点重播我的工作 B,但留在我的分支机构。
在这种情况下,仍然需要合并才能将我的“重放”工作带到 B

另一种情况(在Git Ready中描述 例如),是直接带你的工作 B 通过rebase(它确实保存了所有你提交的内容,甚至让你有机会通过交互式rebase重新排序它们)。
在那种情况下(你在B分支中进行重组),你是对的:不需要进一步合并:

我们没有合并也没有重新定义时默认使用git树

rebase1 

我们通过变基来获得:

rebase3

第二种情况就是:如何将新功能重新带入主服务器。

我的观点是,通过描述第一个rebase场景,提醒大家一个rebase也可以作为一个初步的步骤(即“让新功能重新成为主人”)。
你可以使用rebase首先将master“放入”新功能分支中:rebase将重放来自the-feature的新功能提交 HEAD master,但仍然在新功能分支中,有效地将您的分支起点从旧的主提交移动到 HEAD-master
这允许您解决任何冲突 你的 branch(意思是,孤立地,如果你的冲突解决阶段花费太长时间,允许master继续并行发展)。
然后你可以切换到主和合并 new-feature (或改变 new-feature 到 master 如果你想保留你的提交 new-feature 科)。

所以:

  • “rebase vs. merge”可以被视为导入作品的两种方式,比方说, master
  • 但是“rebase then merge”可以是一个有效的工作流程,可以首先独立解决冲突,然后恢复你的工作。

254
2018-04-29 20:44



在rebase是一个微不足道的快进之后合并而不必解决冲突。 - obecalp
@obelcap:的确,这就是一个想法:你把所有问题都解决了 你的 环境(你的新功能分支中的rebase master),然后是co master,合并新功能:如果master没有进化,那么1皮秒(快进) - VonC
Rebase也很好,因为一旦你最终将你的东西合并回master(这已经很简单了),你可以将它放在你的提交历史的“顶部”。在可以编写功能但几周后合并的大型项目中,您不希望仅将它们合并到主服务器中,因为它们会在历史记录中被“填充”到主程序中。就个人而言,我喜欢能够做git日志,并在“顶部”看到最近的功能。请注意保留提交日期 - rebase不会更改该信息。 - Sean Schofield
@Joe:从心理上讲,你说的是“在我的私人分支中重放我的任何变化(在我的私人分支中独立完成),但是一旦完成rebase,就把我留在我的私人分支”。这是一个清理当地历史,避免“检查站提交”,破坏平分和不正确的责备结果的好机会。请参阅“Git工作流程”: sandofsky.com/blog/git-workflow.html - VonC
@scoarescoare的关键是看你的本地变化是如何兼容的 在上面 最新上游分公司如果您的某个提交引入了冲突,您将立即看到它。合并只引入一个(合并的)提交,这可能会触发很多冲突,而不会轻易地查看在您自己的本地提交中添加所述冲突的哪一个。因此,除了更清晰的历史记录外,您还可以更准确地了解更改 您 引入,提交提交(由rebase重放),而不是 所有 上游分支引入的更改(转储到单个合并中)。 - VonC


这里有很多答案说合并将所有提交转换为一个,因此建议使用rebase来保留你的提交。 这是不正确的。如果你已经提交了你的提交,那就太糟糕了

合并确实如此  删除你的提交。合并保留了历史! (只看gitk)Rebase重写历史,这是一件糟糕的事情  它。

使用merge - 而不是rebase 无论何时你已经推了。

这是Linus'(git的作者)对它的看法。这是一个非常好的阅读。或者您可以在下面阅读我自己的相同想法版本。

在master上重新分支:

  • 提供了如何创建提交的错误想法
  • 使用一堆可能未经过良好测试的中间提交来污染master
  • 实际上可能会在这些中间提交中引入构建中断,因为在创建原始主题分支和重新分配时之间进行了更改。
  • 在高手找到好位置难以结账。
  • 导致提交时间戳与树中的时间顺序不一致。所以你会看到提交A在master中提交B之前,但是提交B首先被创作。 (什么?!)
  • 产生更多冲突是因为主题分支中的各个提交都可能涉及必须单独解决的合并冲突(进一步包含在每个提交中发生的事件的历史记录中)。
  • 是历史的重写。如果被重新分支的分支已被推送到任何地方(与除您之外的任何人共享),那么自从您重写了历史记录以来,您已经搞砸了拥有该分支的所有其他人。

相反,将主题分支合并为master:

  • 保留创建主题分支的历史记录,包括从主分支到主题分支的任何合并,以帮助保持最新。您真正了解开发人员在构建时使用的代码。
  • master是一个主要由合并组成的分支,每个合并提交通常都是历史上可以安全检查的“好点”,因为这是主题分支准备集成的地方。
  • 保留主题分支的所有单个提交,包括它们位于主题分支中的事实,因此隔离这些更改是很自然的,您可以在需要的位置钻取。
  • 合并冲突只需解析一次(在合并时),因此不必单独解决主题分支中的中间提交更改。
  • 可以顺利完成多次。如果您定期将主题分支集成到master,那么人们可以继续构建主题分支,并且可以独立地合并。

153
2018-02-03 22:17



此外,git merge具有“--no-ff”(无快进)选项,允许您非常轻松地还原某个合并引入的所有更改。 - Tiago
只是让它更清楚:你提到“只要你已经推动”的情况 - 这应该是大胆的。链接到Linus帖子很棒,顺便说一句,澄清一下。 - honzajde
@AndrewArnott“大多数主题分支应该能够在没有冲突的情况下合并到他们的目标分支”当20个开发人员在30个分支上工作时,应该怎么做?当你正在为你的工作时会有合并 - 所以当然你必须在创建PR之前从目标更新主题分支...不是吗? - ProblemsOfSumit
通常不是,@苏米特。即使已对其中一个或两个分支进行了更改,Git也可以合并任一方向。只有在两个分支中修改相同的代码行(或非常接近)时才会出现冲突。如果在任何团队中经常发生这种情况,团队应该重新考虑他们如何分配工作,因为解决冲突是一种税收并且会减慢它们的速度。 - Andrew Arnott
链接已死,请更新 - Warpzit


TL; DR

如果您有任何疑问,请使用合并。

简答

rebase和merge之间的唯一区别是:

  • 历史的结果树结构(通常仅在查看提交图时才会显着)是不同的(一个将具有分支,另一个将不具有)。
  • 合并通常会创建一个额外的提交(例如树中的节点)。
  • 合并和rebase将以不同方式处理冲突。 Rebase将一次提交一个提交的冲突,其中merge将立即显示它们。

所以简短的回答是 根据您希望历史记录的样子选择rebase或merge

答案很长

在选择要使用的操作时,您应该考虑几个因素。

您与团队之外的其他开发人员(例如开源,公共)共享更改的分支机构是否正在进行更改?

如果是这样,请不要改变。 Rebase会破坏分支,除非他们使用,否则这些开发人员将拥有破坏/不一致的存储库 git pull --rebase。这是快速打乱其他开发人员的好方法。

您的开发团队技术娴熟吗?

Rebase是一种破坏性的操作。这意味着,如果你没有正确应用它, 您可能会丢失承诺的工作和/或破坏其他开发人员的存储库的一致性。

我曾经在团队中工作,开发人员都来自公司可以负担得起专职人员来处理分支和合并的时候。那些开发人员对Git了解不多,也不想了解太多。在这些团队中,我不会冒任何理由推荐变基。

分支本身是否代表有用的信息

一些团队使用每个功能分支模型,其中每个分支代表一个功能(或错误修复或子功能等)。在此模型中,分支有助于识别相关提交的集合。例如,可以通过恢复该分支的合并来快速恢复特征(公平地说,这是一种罕见的操作)。或者通过比较两个分支(更常见)来区分特征。 Rebase将破坏分支,这不会是直截了当的。

我还参与了使用每个开发人员分支模型的团队(我们都在那里)。在这种情况下,分支本身不传达任何其他信息(提交已经有作者)。变基不会有任何伤害。

您是否想以任何理由还原合并?

与恢复合并相比,恢复(如在撤消中)相当困难和/或不可能(如果rebase存在冲突)。如果您认为有可能需要恢复,请使用合并。

你在团队工作吗?如果是这样,你愿意在这个分支上采取全有或全无的方法吗?

Rebase操作需要相应的拉动 git pull --rebase。如果您自己工作,您可能能够记住在适当的时候应该使用哪些。如果您在团队中工作,那么协调将非常困难。这就是为什么大多数rebase工作流建议为所有合并使用rebase(和 git pull --rebase 所有拉动)。

常见的神话

合并破坏历史(南瓜提交)

假设您有以下合并:

    B -- C
   /      \
  A--------D

有些人会说合并“破坏”提交历史记录,因为如果你只查看主分支(A - D)的日志,你会错过B和C中包含的重要提交消息。

如果这是真的,我们就没有 像这样的问题。基本上,你会看到B和C,除非你明确要求不要看到它们(使用--first-parent)。这很容易为自己尝试。

Rebase允许更安全/更简单的合并

这两种方法的合并方式不同,但目前尚不清楚一种方法总是比另一方更好,而且可能取决于开发人员的工作流程。例如,如果开发人员倾向于定期提交(例如,他们可能每天提交两次,因为他们从工作转移到家庭),那么对于给定的分支可能会有很多提交。许多提交可能看起来不像最终产品(我倾向于每个功能重构一次或两次我的方法)。如果其他人正在处理相关的代码区域并且他们试图改变我的更改,那么这可能是一项相当繁琐的操作。

Rebase更酷/更性感/更专业

如果你喜欢别名 rm 至 rm -rf 为了“节省时间”,那么也许rebase适合你。

我的两分钱

我总是认为有一天我会遇到一个场景,git rebase是解决问题的绝佳工具。就像我想我会遇到一个场景,git reflog是一个很棒的工具,可以解决我的问题。我已经和git合作了五年多了。它没有发生。

对我来说,凌乱的历史从未真正成为问题。我永远不会像一部令人兴奋的小说那样阅读我的提交历史。大多数时候我需要一个历史,我将使用git blame或git bisect。在这种情况下,合并提交对我来说实际上是有用的,因为如果合并引入了对我来说有意义的信息的问题。

更新(4/2017)

虽然我的一般建议仍然存在,但我觉得有义务提到我个人已经软化了使用rebase。我最近和很多人进行了互动 角度2材料 项目。他们使用rebase来保持非常干净的提交历史。这使我能够非常轻松地查看修复给定缺陷的提交内容以及该提交是否包含在发布中。它是正确使用rebase的一个很好的例子。


118
2018-04-13 02:16



这需要更多的赞成。 - Legato
应该是经过验证的答案。 - Mik378
这当然是最好的答案。特别是最新更新中的澄清说明。它有助于保持git历史清洁和清晰,但安全使用它。 - zquintana
我大多喜欢这个答案。但是:Rebase并没有创造一个“干净”的历史。它创造了一个更线性的历史,但这根本不是一回事,因为现在谁知道每个提交隐藏的很多“污垢”?最干净,最清晰的Git历史是保持分支和提交完整性的历史。 - Marnen Laibow-Koser
这是一个非常好的答案 - Derek


合并意味着:创建一个新的提交,将我的更改合并到目标中。

Rebase意味着:使用我当前的提交集作为提示创建一系列全新的提交。换句话说,如果我已经从我正在变相的那一点开始制作它们,那么计算我的变化会是什么样子。因此,在rebase之后,您可能需要重新测试您的更改,并且在rebase期间,您可能会遇到一些冲突。

鉴于此,你为什么要变基?只是为了保持发展历史的清晰。假设您正在使用功能X,当您完成后,您将合并您的更改。目标现在将有一个提交,可以说“添加功能X”。现在,如果您重新组合然后合并,则目标开发历史记录将包含单个逻辑进程中的所有单个提交,而不是合并。这使得稍后审查更改变得更加容易。想象一下,如果有50位开发人员一直在合并各种功能,那么查看开发历史有多难。

也就是说,如果你已经推动了你正在上游工作的分支机构,你不应该改变,而是合并。对于尚未被推送到上游的分支,rebase,test和merge。

你可能想要重新定义的另一个时间是你想要在推送上游之前从你的分支中删除提交。例如:承诺尽早引入一些调试代码,并进一步提交清除代码的其他提交。执行此操作的唯一方法是执行交互式rebase: git rebase -i <branch/commit/tag>

更新:当您使用Git连接到不支持非线性历史记录的版本控制系统(例如subversion)时,您还想使用rebase。使用git-svn桥时,非常重要的是,您合并回subversion的更改是在trunk中最新更改之上的连续变化列表。只有两种方法可以做到这一点:(1)手动重新创建更改和(2)使用rebase命令,这要快得多。

UPDATE2:另一种思考rebase的方法是,它允许从您的开发样式到您承诺的存储库中接受的样式的一种映射。假设您喜欢用小块的小块进行操作。你有一个提交修复错误,一个提交摆脱未使用的代码等等。当你完成了你需要做的事情时,你需要进行一系列的提交。现在让我们说你承诺鼓励大量提交的存储库,所以对于你正在做的工作,人们会期待一个或两个提交。你如何处理你的提交并将它们压缩到预期的位置?您将使用交互式rebase并将您的小提交压缩为更少的更大块。如果需要反向,情况也是如此 - 如果你的风格是一些大型提交,但是repo需要长串的小提交。您也可以使用rebase来做到这一点。如果您已合并,则现在已将提交样式移植到主存储库中。如果有很多开发人员,你可以想象在一段时间后跟踪具有几种不同提交样式的历史记录是多么困难。

UPDATE3: Does one still need to merge after a successful rebase? 是的你是。原因是rebase基本上涉及提交的“转移”。正如我上面所说,这些提交是计算出来的,但是如果从分支开始有14个提交,那么假设你的rebase没有出错,那么你将提前14个提交(这是你要重新调整的点)。 rebase已经完成。你在rebase之前有一个分支。之后你会有一个相同长度的分支。在发布更改之前,您仍需要合并。换句话说,可以根据需要多次重新设置(再次,只有在您没有将更改推送到上游时)。只有在你变基之后合并。


65
2018-02-05 06:47



与主人合并可能会导致快进。在一个功能分支中可能有一些提交,它们有小错误或甚至不编译。如果你只在功能分支中进行单元测试,那么集成中的一些错误就会漏掉。在与master合并之前,需要进行集成测试并显示一些错误。如果这些是固定的,则可以集成该功能。由于您不希望提交有缺陷的代码,因此需要使用rebase以防止全部提交 - 快进。 - mbx
@mbx git merge 支持 --no-ff 强制它进行合并提交的选项。 - g.rocket


合并/ rebase之前:

A <- B <- C    [master]
^
 \
  D <- E       [branch]

git merge master

A <- B <- C
^         ^
 \         \
  D <- E <- F

git rebase master

A <- B <- C <- D' <- E'

(A,B,C,D,E和F是提交)

这个例子以及关于git的更好的图解信息可以在这里找到: http://excess.org/article/2008/07/ogre-git-tutorial/


54
2018-06-07 06:19





这句话得到了:

一般来说,获得两全其美的方法是改变当地   在推送之前,您已做出的更改但尚未共享   为了清理你的故事,但永远不要改变你所推动的任何东西   某处。

资源: http://www.git-scm.com/book/en/v2/Git-Branching-Rebasing#Rebase-vs.-Merge


25
2018-01-05 13:50





虽然合并绝对是集成更改的最简单和最常用的方法,但它并不是唯一的方法: 变基 是一种替代的整合手段。

理解合并更好一点

当Git执行合并时,它会查找三个提交:

  • (1)共同祖先提交如果你遵循项目中两个分支的历史记录,它们总是至少有一个共同的提交:在这个时间点,两个分支具有相同的内容,然后进化不同。
  • (2)+(3)每个分支的端点集成的目标是组合两个分支的当前状态。因此,他们各自的最新修订特别感兴趣。 结合这三个提交将导致我们的目标集成。

快进或合并提交

在非常简单的情况下,自分支发生以来,两个分支中的一个没有任何新的提交 - 它的最新提交仍然是共同的祖先。

enter image description here

在这种情况下,执行集成非常简单:Git可以在共同的祖先提交之上添加另一个分支的所有提交。在Git中,这种最简单的集成形式称为“快进”合并。然后两个分支共享完全相同的历史。

enter image description here

然而,在很多情况下,两个分支都是单独前进的。 enter image description here

要进行集成,Git必须创建一个包含它们之间差异的新提交 - 合并提交。

enter image description here

人类提交和合并提交

通常,提交是由人类仔细创建的。它是一个有意义的单元,仅包含相关更改并使用注释对其进行注释。

合并提交有点不同:它不是由开发人员创建的,而是由Git自动创建的。而不是包装一组相关的更改,其目的是连接两个分支,就像一个结。如果您想稍后了解合并操作,则需要查看两个分支的历史记录和相应的提交图。

与Rebase集成

有些人喜欢没有这样的自动合并提交。相反,他们希望项目的历史看起来好像是在一条直线上演变而来。 没有迹象表明它在某些时候被分成了多个分支。

enter image description here

让我们一步一步地完成一个rebase操作。该场景与前面的示例相同:我们希望将branch-B中的更改集成到branch-A中,但现在使用rebase。

enter image description here

我们将分三步完成

  1. git rebase branch-A // syncs the history with branch-A
  2. git checkout branch-A // change the current branch to branch-A
  3. git merge branch-B // merge/take the changes from branch-B to branch-A 

首先,Git将“撤消”在行开始分支之后发生的分支-A上的所有提交(在共同的祖先提交之后)。但是,当然,它不会丢弃它们:相反,您可以将这些提交视为“暂时保存”。

enter image description here

接下来,它应用我们想要集成的branch-B的提交。此时,两个分支看起来完全相同。

enter image description here

在最后一步中,现在重新应用分支-A上的新提交 - 但是在新位置上,在分支-B的集成提交之上(它们是重新基础的)。 结果看起来发展是直线发生的。代替包含所有组合更改的合并提交,保留了原始提交结构。

enter image description here

最后你得到一个干净的分支 分支-A 没有不需要的和自动生成的提交。

注意: 取自令人敬畏的 岗位 通过 git-tower。该 缺点 的 rebase 也是同一篇文章中的好读物。


23
2017-10-12 11:52





亲git书作为一个非常好的解释 重新定位页面

基本上合并将需要2次提交并将它们组合在一起。

rebase将转到2上的共同祖先,并逐步将更改应用于彼此之上。这使得“更清洁”和更线性的历史。

但是,当你重新放弃以前的提交并创建新的提交时。所以你永远不应该改变一个公开的回购。处理回购的其他人会讨厌你。

仅仅因为这个原因我几乎完全合并。 99%的时间我的分支没有那么大的差别,所以如果有冲突,它只在一两个地方。


15
2017-12-20 21:41



合并不会组合提交 - 这将重写历史记录。 Rebase就是这么做的。 - kellyfj