建议何时使用Git rebase与Git merge?

成功重新创建数据库后,是否仍需要合并?


当前回答

这很简单。使用rebase时,您可以使用另一个分支作为工作的新基础。

例如,如果您有一个分支主节点,您可以创建一个分支来实现一个新特性,并将其命名为酷特性,当然,主分支是新特性的基础。

现在,在某一点上,您希望添加在主分支中实现的新特性。您可以切换到master并合并酷功能分支:

$ git checkout master
$ git merge cool-feature

但这样就添加了一个新的虚拟提交。如果你想避免意大利面条的历史,你可以重新设定基准:

$ git checkout cool-feature
$ git rebase master

然后将其合并到master中:

$ git checkout master
$ git merge cool-feature

这一次,由于主题分支具有与master相同的提交,再加上具有新特性的提交,因此合并将是一个快速前进。

其他回答

这里的很多答案都说,合并会将所有提交转换为一个,因此建议使用rebase来保存提交。这是不正确的。如果你已经提交了,那是个坏主意。

合并不会消除提交。合并保留历史!(看看gitk)热巴改写了历史,这是你推后的坏事。

使用merge,而不是在已经推送的情况下重新设置基址。

这是Linus(Git的作者)对它的看法(现在托管在我自己的博客上,由Wayback Machine恢复)。这真是一本好书。

或者你可以在下面阅读我自己的版本。

在主节点上重新分配分支:

提供了如何创建提交的错误想法用一堆可能没有经过良好测试的中间提交来污染master实际上可能会在这些中间提交上引入构建中断,因为在创建原始主题分支和重新创建主题分支之间对master进行了更改。这使得在master中找到好地方结账变得困难。导致提交的时间戳与其在树中的时间顺序不一致。因此,您将看到提交A在主提交B之前,但提交B是先编写的。(什么?!)产生更多的冲突,因为主题分支中的每个提交都可能涉及合并冲突,这些冲突必须单独解决(关于每个提交中发生的情况,请进一步查看历史)。是对历史的改写。如果正在重新创建的分支被推到任何地方(与除您之外的任何人共享),那么自您改写历史以来,您已经将拥有该分支的所有其他人都搞砸了。

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

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

TLDR:这取决于什么是最重要的——整洁的历史还是发展顺序的真实再现

如果一个整洁的历史是最重要的,那么你会先重新基址,然后合并你的更改,所以很清楚新代码是什么。如果你已经推送了你的分支,除非你能够处理后果,否则不要重新基址。

如果序列的真实表示是最重要的,那么您可以合并而不重定基础。

合并意味着:创建一个新的提交,将我的更改合并到目标中。注意:这个新的提交将有两个父级:一个是提交字符串中的最新提交,另一个是合并的另一个分支的最新的提交。

Rebase的意思是:使用我当前的一组提交作为提示,创建一系列新的提交。换言之,计算一下如果我从重新基准点开始进行更改,我的更改会是什么样子。因此,在重新基准之后,您可能需要重新测试您的更改,并且在重新基准期间,您可能会发生一些冲突。

既然如此,你为什么要重新基准?只是为了保持发展历史的清晰。假设您正在处理功能X,当您完成后,将您的更改合并到中。目标现在将有一个单独的提交,该提交内容类似于“添加的功能X”。现在,如果您重新创建基础,然后进行合并,那么目标开发历史将包含单个逻辑进程中的所有单独提交,而不是合并。这使得以后查看更改更加容易。想象一下,如果50名开发人员一直在合并各种功能,那么你会发现很难回顾开发历史。

也就是说,如果您已经将正在处理的分支推到了上游,那么不应该重新创建基础,而应该合并。对于尚未向上游推送的分支,请重新基址、测试和合并。

另一次您可能需要重新设置基值,是在向上游推送之前从分支中删除提交。例如:早期引入一些调试代码的提交和进一步清理代码的提交。实现这一点的唯一方法是执行交互式rebase:gitrebase-i<branch/commit/tag>

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

更新2:另一种考虑rebase的方法是,它可以从您的开发风格映射到您提交的存储库中接受的风格。你有一次提交来修复拼写错误,一次提交去清除未使用的代码等等。当你完成你需要做的事情时,你有一系列的提交。现在让我们假设您提交的存储库鼓励大量提交,因此对于您正在进行的工作,可能需要一次或两次提交。如何获取提交字符串并将其压缩到预期值?你可以使用一个交互式的rebase,将你的小提交压缩成更少的大块。如果需要相反的方式,情况也是如此——如果您的风格是几个大的提交,但存储库需要长串的小提交。你也可以使用rebase来实现这一点。如果您进行了合并,那么现在已经将提交样式移植到了主存储库中。如果有很多开发人员,你可以想象一段时间后,用几种不同的提交样式来跟踪历史会有多困难。

更新3:成功重新创建数据库后,是否仍需要合并?是的,你是这样做的。原因是重基基本上涉及到提交的“转移”。如上所述,这些提交是经过计算的,但如果从分支点开始有14个提交,那么假设您的重基没有任何问题,那么在重基完成后,您将提前14个提交(在您重基的点之前)。你在重新开始之前有一个分支。之后将有一个相同长度的分支。您仍然需要在发布更改之前进行合并。换言之,尽可能多次地重新设置基准(同样,只有在您没有将更改推到上游的情况下)。仅在重新基准后合并。

TL;博士

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

简短回答

重新基准和合并之间的唯一区别是:

生成的历史树结构(通常只有在查看提交图时才明显)不同(一个有分支,另一个没有分支)。合并通常会创建一个额外的提交(例如树中的节点)。合并和重新基础将以不同的方式处理冲突。Rebase将一次提交一个冲突,而merge将一次显示所有冲突。

因此,简单的答案是根据您希望的历史外观选择重新基准或合并。

长答案

在选择要使用的操作时,需要考虑以下几个因素。

您从中获得更改的分支是否与团队之外的其他开发人员共享(例如,开源、公共)?

如果是,请不要重新设置基准。Rebase会破坏分支,除非他们使用git pull-Rebase,否则这些开发人员将拥有损坏/不一致的存储库。这是一个很快让其他开发人员感到不安的好方法。

您的开发团队有多熟练?

回扣是一种破坏性操作。这意味着,如果不正确应用它,您可能会丢失已提交的工作和/或破坏其他开发人员存储库的一致性。

我曾在团队中工作过,这些团队中的开发人员都来自一个公司能够负担得起专门的员工来处理分支和合并的时代。这些开发人员对Git了解不多,也不想了解太多。在这些团队中,我不会因为任何原因而冒险建议重新定基。

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

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

我也曾在使用每个开发人员分支模型的团队中工作过(我们都去过)。在这种情况下,分支本身不传递任何附加信息(提交已经包含作者)。换基不会有什么害处。

是否出于任何原因要还原合并?

与恢复合并相比,恢复(如撤消)重基相当困难和/或不可能(如果重基发生冲突)。如果您认为有机会恢复,请使用merge。

你是团队成员吗?如果是的话,你愿意在这一分支机构采取“要么全有要么全无”的方法吗?

需要使用相应的git pull--Rebase来拉动Rebase操作。如果你是自己工作,你可能会记得在适当的时候应该使用哪一个。如果你在一个团队中工作,这将很难协调。这就是为什么大多数rebase工作流建议对所有合并使用rebase(以及git pull--rebase对所有合并)。

常见的神话

合并销毁历史记录(压缩提交)

假设您有以下合并:

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

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

如果这是真的,我们就不会有这样的问题了。基本上,除非您明确要求不要看到B和C(使用--firstparent),否则您将看到它们。这很容易自己尝试。

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

这两种方法的合并方式不同,但并不清楚其中一种总是优于另一种,这可能取决于开发人员的工作流程。例如,如果开发人员倾向于定期提交(例如,当他们从工作过渡到家庭时,他们可能一天提交两次),那么给定分支可能会有很多提交。其中许多提交看起来可能与最终产品不太相似(我倾向于对每个特性进行一次或两次重构)。如果其他人正在处理一个相关的代码领域,他们试图重新调整我的更改,这可能是一个相当乏味的操作。

热巴更酷/更性感/更专业

如果您想将rm别名为rm-rf以“节省时间”,那么可能需要重新设置基准。

我的两美分

我一直认为有一天我会遇到这样一个场景,Git rebase是解决这个问题的绝佳工具。就像我认为我会遇到这样一个场景,Git reflog是一个很好的工具,可以解决我的问题。我已经在Git工作了五年多了。这还没有发生。

混乱的历史对我来说从来都不是一个问题。我从来没有像读一本令人兴奋的小说那样阅读我的承诺历史。大多数时候,我需要一段历史,无论如何,我都会使用Git责备或Git平分。在这种情况下,合并提交实际上对我很有用,因为如果合并引入了问题,那对我来说就是有意义的信息。

更新(2017年4月)

我觉得有义务提及我个人对使用rebase的态度有所软化,尽管我的一般建议仍然有效。我最近与Angular 2 Material项目进行了很多互动。他们使用rebase来保持非常干净的提交历史。这使我能够非常容易地看到是什么提交修复了给定的缺陷,以及该提交是否包含在发布中。它是正确使用rebase的一个很好的例子。

这很简单。使用rebase时,您可以使用另一个分支作为工作的新基础。

例如,如果您有一个分支主节点,您可以创建一个分支来实现一个新特性,并将其命名为酷特性,当然,主分支是新特性的基础。

现在,在某一点上,您希望添加在主分支中实现的新特性。您可以切换到master并合并酷功能分支:

$ git checkout master
$ git merge cool-feature

但这样就添加了一个新的虚拟提交。如果你想避免意大利面条的历史,你可以重新设定基准:

$ git checkout cool-feature
$ git rebase master

然后将其合并到master中:

$ git checkout master
$ git merge cool-feature

这一次,由于主题分支具有与master相同的提交,再加上具有新特性的提交,因此合并将是一个快速前进。

我刚刚用自己的话为我的团队创建了一个FAQ,以回答这个问题。让我分享一下:

什么是合并?

提交,将不同分支的所有更改合并到当前。

什么是重基?

将当前分支的所有提交重新提交到不同的基本提交上。

合并和再基础之间的主要区别是什么?

merge只执行一次新提交。rebase通常执行多次(当前分支中的提交次数)。merge生成一个新生成的提交(所谓的合并提交)。rebase仅移动现有提交。

在哪些情况下我们应该使用合并?

如果要将分支分支的更改添加回基本分支,请使用merge。

通常,您可以通过单击Pull/Merge请求上的“合并”按钮来完成此操作,例如在GitHub上。

在哪些情况下我们应该使用重基?

每当您想将基本分支的更改添加回分支时,请使用rebase。

通常,只要主分支发生变化,就可以在功能分支中执行此操作。

为什么不使用合并将基本分支中的更改合并到要素分支中?

git历史记录将包含许多不必要的合并提交。如果在一个功能分支中需要多个合并,那么该功能分支甚至可能包含比实际提交更多的合并提交!这就产生了一个循环,它破坏了Git所设计的心理模型,这在Git历史的任何可视化中都会带来麻烦。想象有一条河(例如“尼罗河”)。水流向一个方向(Git历史上的时间方向)。有时,想象那条河有一条支流,假设这些支流中的大部分都汇回了这条河。这就是河流的自然流动的样子。这是有道理的。但想象一下,那条河有一条小支流。然后,由于某种原因,河流汇入分支,分支从那里继续。从技术上讲,这条河现在已经消失了,它现在在支流中。但是,不知怎的,这条支流神奇地汇回了河里。你问哪条河?我不知道。这条河现在应该在支流中,但不知何故它仍然存在,我可以将支流合并回河流中。所以,河在河中。有点说不过去。这正是将基本分支合并到要素分支时发生的情况,然后在要素分支完成后,再次将其合并回基本分支。心智模型被打破了。因此,你最终得到了一个没有太大帮助的分支可视化。

使用merge时的Git历史示例:

请注意,许多提交都是从Merge分支“main”开始的。。。。如果你重新创建数据库,它们甚至都不存在(在那里,你只会有拉请求合并提交)。还有许多视觉分支合并循环(主到要素到主)。

使用rebase时的Git历史示例:

Git历史更加清晰,合并提交更少,没有任何杂乱的可视化分支合并循环。

rebase有什么缺点/缺陷吗?

Yes:

因为重基会移动提交(从技术上讲是重新执行),所以所有移动的提交的提交日期将是重基的时间,而git历史记录看起来可能会丢失初始提交时间。因此,如果出于某种原因,所有工具都需要提交的确切日期,那么合并是更好的选择。但通常情况下,干净的git历史要比准确的提交日期有用得多。如果需要,authordate字段将继续保存原始提交日期。如果重基分支有多个更改同一行的提交,并且该行也在基分支中更改,则可能需要多次解决该行的合并冲突,这在合并时是不需要的。因此,平均而言,需要解决的合并冲突更多。

使用rebase时减少合并冲突的提示:

经常退款。我通常建议每天至少做一次。尽量将同一行中的更改压缩为一次提交。