为什么有这么多项目更喜欢“ git rebase”而不是“ git merge”?


67

使用DVCS的优点之一是编辑提交合并工作流(通常由CVCS强制执行的编辑合并提交工作)。允许将每个唯一更改记录在与合并无关的存储库中,可确保DAG准确反映项目的真实血统书。

为什么这么多网站谈论要“避免合并提交”?合并合并前合并或合并后合并是否会使隔离回归,还原过去的更改等变得更加困难?

澄清点: DVCS的默认行为创建合并提交。为什么有那么多地方谈论看到隐藏这些合并提交的线性开发历史的愿望?


18
工具并非总能正确解决。当他们弄错了的时候,天哪,他们也弄错了。
Oded

2
@Oded您是指自动合并提交(例如由创建的自动提交git pull)吗?我可以理解为什么这可能会带来问题,但是我的问题是关于所有合并提交的。
杰斯·布朗宁

经常出现的复杂合并冲突是否可能是问题的征兆?“对于人类必须解决的那些内在冲突(例如,两个开发人员更改同一行代码),合并和重新设置应该引起完全相同的冲突……”
t2013年

我在发布答案后就想到了IMO并不完全适合答案:( git pull --rebase == svn up松散等于,而不是严格等于)
Izkata

在诸如SourceSafe之类的集中存储中,将继续不断地检查更改,但是一旦达到稳定的稳定期,我们将标记特定日期的所有文件。那么至少你可以关联的bug之前或之后某某版本,或重订直插式。
Andyz Smith,

Answers:


64

人们希望避免合并提交,因为这会使日志更漂亮。说真的 看起来就像是他们一起成长的集中式日志,在本地他们可以在一个分支中进行所有开发。除了这些美学之外,没有任何好处,除了您提到的那些缺点外,还有其他一些缺点,例如,不通过“中央”服务器直接从同事那里拉动就容易产生冲突。


5
您是唯一正确理解我的问题的人。如何使它更清楚?
杰斯·布朗宁

12
也许改写为“线性发展历史的好处是什么?”
Karl Bielefeldt

11
“通常,您会这样做以确保您的提交可以干净地应用到远程分支上-也许是在您试图贡献但又没有维护的项目中。在这种情况下,您会做自己的工作在分支中,然后在您准备将补丁提交到主项目时将工作重新定位到原始/主服务器上,这样,维护人员就无需进行任何集成工作-只需进行快速转发即可。 ” git-scm.com/book/en/Git-Branching-Rebasing
Andyz Smith

12
您听起来像是在修饰,但实际上是实用的:了解线性历史中发生的情况要比从具有交叉和合并的线的复杂DAG中容易得多。
Daniel Hershcovich 2013年

20
“除了那些美学之外,没有其他好处”-我对此一无所知。当您的历史记录看起来像电路板时,很难理解您的历史记录中发生了什么。(假设有多个团队成员,每个成员都有自己的分支和工作流程。)
Archagon 2013年

46

用两个词: git bisect

线性历史记录可让您查明错误的实际来源。


一个例子。这是我们的初始代码:

def bar(arg=None):
    pass

def foo():
    bar()

分支1进行了一些重构,使其arg不再有效:

def bar():
    pass

def foo():
    bar()

分支2具有需要使用的新功能arg

def bar(arg=None):
    pass

def foo():
    bar(arg=1)

不会有合并冲突,但是引入了一个错误。幸运的是,这一特定程序将在编译步骤中被发现,但是我们并不总是那么幸运。如果该错误表现为意外行为,而不是编译错误,那么我们可能会在一两个星期内找不到它。到那时,git bisect去救援!

真是的 这是它所看到的:

(master: green)
|             \_________________________
|                \                      \
(master: green)  (branch 1: green)     (branch 2: green)
|                 |                     |
|                 |                     |
(master/merge commit: green)            |
|                         ______________/
|                        /
(master/merge commit: red)
|
...days pass...
|
(master: red)

因此,当我们发送git bisect找到破坏构建的提交时,它将查明合并提交。好吧,这有所帮助,但实际上是指一包提交,而不是一个。所有祖先都是绿色的。另一方面,通过重新基准化,您可以获得线性历史记录:

(master: green)
|
(master: green)
|
(all branch 1 commits: green)
|
(some branch 2 commits: green)
|
(branch 2 commit: red)
|
(remaining branch 2 commits: red)
|
...days pass...
|
(master: still red)

现在,git bisect将指向破坏构建的确切功能提交。理想情况下,提交消息将解释什么意图足够好以进行另一次重构并立即修复该错误。

仅当维护人员没有编写所有代码时,这种影响才会在大型项目中加剧,因此他们不必记住为什么要进行任何给定的提交/每个分支的用途。因此,查明确切的提交(然后能够检查其周围的提交)是很有帮助的。


也就是说,我(当前)仍然喜欢合并。重新部署到发行分支将为您提供与配合使用的线性历史记录git bisect,同时保留日常工作的真实历史记录。


2
感谢您的解释。您能否继续说明为什么仍然喜欢合并?我知道很多人在大多数情况下都喜欢合并而不是基础,但是我一直无法理解为什么。
菲利普(Philip)

@Philip我有点犹豫,因为我只在小型个人项目上使用了git几个月,从未在团队中使用过。但是,当跟踪我最近所做的工作时,能够看到跨分支的提交流程gitk --all非常有用(尤其是因为我通常在任何给定时间都拥有3个分支,并且每天根据自己的心情在它们之间进行切换时间)。
2013年

16
但是合并是问题所在,因此您需要进行合并提交bisect。如果您想更深入地了解,可以很容易地找到带有blame或不同的单个提交bisect。有了线性历史记录,合并是在账外完成的,因此您不能再说出错误的合并是问题所在。它看起来像个白痴,使用不存在的参数,特别是如果该参数先前已删除几次提交。当您破坏历史时,试图弄清楚他为什么会这样做会更加困难。
Karl Bielefeldt 2013年

1
确实不同意这个答案。重新设基有时会破坏bisect的效用。除了变基的头提交外,来自变基的提交可能不可运行。示例:您在A分支,并创建了B,C,D,有人推了E。您是B和C并在工作,但基于E的基础,您是B和C无法运行或可运行且不工作。最好的情况是你放弃,最坏的情况下,你认为B或C中含有的问题,而实际上它是D.

4
@Izkata为什么要使用bisect?因为发生错误/错误。这是一个令人谦卑的现实。以前,也许“将鼠标拖动到左侧”并不是自动/手动测试列表的一部分,现在我们注意到该功能已损坏,但我们知道它曾经可以工作。
2014年

16

简而言之,因为合并通常是出错的另一个地方,并且只需出错一次就可以使人们非常害怕再次对其进行处理(如果可能,请害羞两次)。

假设我们正在一个新的“帐户管理”屏幕上工作,事实证明在“新帐户”工作流程中发现了一个错误。好的,我们采取两种不同的方式-您完成帐户管理,然后使用“新帐户”修复该错误。由于我们都在处理帐户,因此我们一直在使用非常相似的代码-也许我们甚至不得不调整相同的代码段。

现在,此刻我们有两个不同但完全正常工作的软件版本。我们都对更改进行了提交,我们都已尽职地测试了代码,并且独立地我们非常有信心我们已经完成了出色的工作。怎么办?

好了,是时候合并了,但是...废话,现在发生了什么?我们很可能会从两个工作软件套件转变为一个统一的,可怕的新漏洞软件,在该错误软件中,您的帐户管理不起作用,新帐户也已失效,我什至不知道旧错误是否仍然存在。

也许该软件很智能,它说存在冲突,并坚持要求我们提供指导。好吧,废话-我坐下来做,看看你添加了一些我不立即理解的复杂代码。我认为这与我所做的更改相冲突...我问你,等一下你检查一下,就会看到你不理解的代码。我们中的一个或两个都必须花时间坐下来,对适当的合并进行哈希处理,并可能重新测试整个dang事物,以确保我们没有破坏它。

同时,其他8个人都像他们的施虐者一样提交代码,在我知道我们发生合并冲突之前,我做了一些小错误修复,然后提交了这些错误,可以肯定,这是个休息的好时机,也许您下午休息或参加会议或其他任何活动。也许我应该去度假。或改变职业。

因此,为了逃避这一噩梦,有些人变得非常害怕承诺(还有什么新东西,对吗?)。在这种情况下,我们很自然地会产生厌恶情绪-除非我们认为自己很烂并且反正会把它搞砸,在这种情况下,人们开始鲁acting地放弃行动。

所以你去了。是的,现代系统的设计旨在减轻这种痛苦,并且应该能够轻松地进行还原,变基,变基,freebase和hanglide等。

但是,这还需要更多工作,我们只想按一下微波炉上的按钮,然后在有时间找到叉子之前就完成4道菜的餐点,这一切实在令人非常难以满足-代码是工作,高效,有意义,但是优雅地处理合并就不算在内。

通常,程序员必须开发出良好的工作记忆,然后趋向于在解决问题后立即忘记所有那些垃圾名称和变量名称以及作用域,并处理合并冲突(或更糟糕的是,错误处理的合并)是提醒您死亡的邀请。


5
先生,措辞惊人!
Southpaw Hare

10
这回答了为什么人们害怕合并,而不是为什么很多人都认为合并提交是不好的。
杰伊

23
这个答案的问题在于,这对于重新部署仍然适用。毕竟,rebase仍在进行合并,在此您更改了已经更改的代码行。我认为这是问题表达方式的问题。
deworde

1
我否决了这个答案,因为它仍然很雄辩,它超出了重点,恕我直言。
尤金(Eugene)

5
这似乎根本不会影响到rebase vs merge-merge。
安迪斯·史密斯

5

变基提供了一个移动分支点,该分支点简化了将更改推回基线的过程。这使您可以将长时间运行的分支视为本地更改。如果不重新设置基准,分支将从基准累积更改,这些更改将包含在合并回基准中的更改中。

合并会将基线留在原始分支点。如果您从分支所在的行合并了几个星期的更改,则分支点现在会有很多更改,其中许多更改将在基线中。这使得很难识别分支中的更改。将更改推回基线可能会产生与您的更改无关的冲突。在发生冲突的情况下,可以推动不一致的更改。进行中的合并需要努力进行管理,并且松散更改相对容易。

Rebase将您的分支点移动到基准上的最新修订版。您遇到的任何冲突仅是您的更改。推动变更要简单得多。在本地分支机构中,通过执行其他重新基准处理冲突。在推送冲突的情况下,最后一次推送他们的更改将需要解决他们的更改问题。


1
因此,它所做的就是将新更改降级到一个被认为是“错误的事情”的水平,因为它们是唯一的更改。如果所有旧更改都包含在合并中,则所有更改都将在一个水平上立足,关于它们是否今天是“正确的事情”。
安迪斯·史密斯

@AndyzSmith我不会认为新的更改是错误的事情。当试图确定正在更改的内容并应进行推送时,它们是正确的事情。重新设定基准后,您可以忽略旧的更改,因为它们是基准的一部分。
BillThor

是的,所以如果您不确定基线是什么,即您已经准备好要合并的更改是否是“正确的事情”,那么就不要重新设置基准。
安迪斯·史密斯

而且,如果您从某人那里得到更改请求,并且由于他们的功能原型与现有原型不匹配而中断了构建,则由于重新设置基础,您会立即知道新手是错误的。您不必猜测,也许新手拥有正确的原型,而以前的,没有基础的,未应用的changset更改就是具有错误原型的更改。
安迪斯·史密斯

4

自动化工具在确保合并代码能够编译和运行方面变得越来越好,从而避免了语法冲突,但是它们不能保证不存在合并可能引起的逻辑冲突。因此,“成功”合并会使您产生虚假的信心,而实际上却不能保证任何事情,因此您必须重做所有测试。

正如我所看到的那样,分支和合并的真正问题在于,它把众所周知的东西踢了下去。它可以让您说“我将在自己的小世界里工作”一周,然后解决以后出现的任何问题。但是,修复bug总是更快/更便宜。到所有代码分支开始合并时,您可能已经忘记了所做的某些细微差别。

将上述两个问题放在一起,您可能会发现自己处在一种简单易行的情况下,即每个人都在同一根主干上工作,并在出现冲突时不断解决冲突,即使它们会使主动开发的速度变慢了一点。


4
这回答了为什么人们害怕合并,而不是为什么很多人都认为合并提交是不好的。
杰伊

那么,您所指的这个主干是否涉及重新设置基准?请解释一下。
安迪斯·史密斯

@AndyzSmith“ trunk”是svn的术语,等同于git的“ master”分支
Izkata

@Izkata所以这个答案提倡不使用合并或变基?
安迪斯·史密斯

@AndyzSmith是的,这也是我的阅读方式。我不同意;在按照建议的方式与其他7个开发人员一起工作之后,我不建议这样做。我们应该比以前更多地使用功能分支,但是大多数功能分支都害怕合并(正如BrianDHall在他的回答中所提到的)。
2013年

0

另一个相关的要点是:通过重新定标,我可以轻松地在我的发布分支中挑选或还原功能。


1
在此详细说明吗?
Akavall

当然:-)在git flow中,我们定期创建新的发行分支。如果创建后在开发分支中修复了错误,则git cherry-pick -x <commit>可以将其应用于发布分支。或者,我们可能想使用撤消该版本的某些内容git revert <commit>。如果<commit>是合并,它将变得多毛。据我所知,这可能的,但并不容易。使用rebase时,每个“合并”都是一个巨大但定期的提交,很容易被选中和还原。
布鲁诺·谢珀

0

在任何答案中都没有说三件事:

  • 在分支内扩散:

    • 当存在合并提交时,在分支中的任意一对提交之间进行区分变得非常困难。
  • 一次合并一项:

    • 解决两个分支之间的差异时,合并通常会同时发生,并且任何合并冲突都将由您处理,而无需担心具体冲突由哪个具体提交引起。如果您重新设置基准,则重新基准设置将在发生冲突时停止,并允许您在该情况下解决它。
  • 推送前清理:

    • 如果您犯了一个错误的提交,以后需要修复,如果您尚未推送交互式基础,则可以合并/分割/更改/移动提交。尽管您仍然可以执行合并操作,但是要合并/拆分/更改/移动合并边界将变得非常困难。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.