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

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


当前回答

在合并/重新基础之前:

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

git合并主机后:

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

git rebase master之后:

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

(A、B、C、D、E和F为提交)

在Git基础教程中可以找到这个例子以及更多关于Git的详细信息。

其他回答

简短版本

合并在一个分支中接受所有更改,并在一次提交中将它们合并到另一个分支。Rebase说我希望我的分支点转移到一个新的起点

那你什么时候用这两种?

合并

假设您创建了一个分支来开发单个功能。当您想将这些更改带回master时,可能需要合并。

回扣

第二种情况是,如果您开始进行一些开发,然后另一个开发人员进行了不相关的更改。您可能希望从存储库中提取并重新创建基础,以将当前版本的更改作为基础。

挤压:在这两种情况下都会保留所有提交(例如:“add feature”,然后是“typ”,然后“oops typ again”…)。通过挤压,可以将提交合并为单个提交。挤压可以作为合并或重基操作(--squash标志)的一部分完成,在这种情况下,通常称为挤压合并或挤压重基。

拉取请求:流行的git服务器(Bitbucket、GitLab、GitHub等)允许配置如何在每个回购基础上合并拉取请求。按照惯例,UI可能会显示“合并”按钮,但该按钮可以使用任何标志(关键字:合并、重基、挤压、快进)执行任何操作。

我刚刚用自己的话为我的团队创建了一个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时减少合并冲突的提示:

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

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个提交(在您重基的点之前)。你在重新开始之前有一个分支。之后将有一个相同长度的分支。您仍然需要在发布更改之前进行合并。换言之,尽可能多次地重新设置基准(同样,只有在您没有将更改推到上游的情况下)。仅在重新基准后合并。

当您正在处理分支并在其间合并了一些其他工作时,Rebase是有用的-合并将创建更改,从而使diff受到污染,因此更难阅读。

如果您重新设置分支的基值,那么您的提交将应用于要重新设置基值的分支的顶部,这使得查看更容易,并且diff输出更干净。

为了补充TSamper提到的我自己的答案,

在合并之前,重新创建数据库通常是一个好主意,因为这样做的目的是在分支Y中集成将要合并的分支B的工作。但同样,在合并之前,您要解决分支中的任何冲突(即:“rebase”,如“从分支B的最近一点开始在分支中回放我的工作”)。如果操作正确,后续从分支到分支B的合并可以快速进行。合并直接影响目标分支B,这意味着合并最好是微不足道的,否则分支B可能很长时间才能恢复到稳定状态(是时候解决所有冲突了)


重新基础后合并的意义?

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

另一种场景(例如Git Ready中描述的)是通过重基(rebase)将您的工作直接带到B中(这会保留所有漂亮的提交,甚至会给您机会通过交互式重基重新排序)。在这种情况下(当您在B分支中时重新创建基础),您是正确的:不需要进一步合并:

当我们没有合并或重新基础时,默认情况下是Git树

我们通过重新定基获得:

第二个场景的全部内容是:我如何将新功能恢复到主功能。

通过描述第一个重基场景,我的目的是提醒大家,重基也可以作为实现这一目标的初步步骤(即“将新功能重新引入主功能”)。您可以使用rebase首先将master“带入”新特性分支:rebase将重放来自HEAD master的新特性提交,但仍在新特性分支中,有效地将分支起点从旧的master提交移动到HEAD master。这允许您解决分支中的任何冲突(即,隔离,同时如果冲突解决阶段花费太长时间,则允许主分支继续并行发展)。然后,您可以切换到主功能并合并新功能(如果您想保留在新功能分支中完成的提交,则可以将新功能重新放到主功能上)。

So:

“rebase与merge”可以被视为两种方法来导入一个工作,例如master。但“先重新创建基础,然后合并”可以是一个有效的工作流,它可以先孤立地解决冲突,然后再恢复工作。