保持Git中的线性历史记录有哪些优势?

87

当我学习使��Git与中央仓库(Gitorious上的项目)时,我被告知始终使用rebase而不是merge,因为我们希望有线性历史记录。所以,我一直在尝试这种方式。

现在当我想到它时,它真的那么有益吗?将具有许多提交的分支进行变基比简单合并要耗费更多时间。

目前我能想到的有两个优点:

  1. git bisect
  2. 可以将提交历史记录与其他版本控制系统(如SVN)一起提交。

还有其他好处吗?


3
以下是需要翻译的内容:See: https://dev59.com/qHRB5IYBdhLWcg3w9Lvn http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/ http://blog.sourcetreeapp.com/2012/08/21/merge-or-rebase/ http://www.derekgourlay.com/archives/428 - torek
1
这是我所知道的唯二原因。我更喜欢提交冒泡,这样我就可以在它们的原始分支上看到单独分离、有针对性的构建工作。 - Gary Fixler
6个回答

112

据我所知,保持线性历史记录大多是出于美学考虑。对于一位非常有技能、守纪律的开发人员来说,这是整理历史记录并使其易于快速浏览的非常好的方式。在完美的世界里,我们应该拥有完全线性的历史记录,让一切变得清晰明了。

然而,在一个大团队的企业环境中,我通常不建议人为地保持线性历史记录。在一支充满聪明、经验丰富和守纪律的开发人员队伍中保持历史记录的线性是一个好主意,但我经常看到这被描述为“最佳实践”或某种“必须做”的方法。我不赞成这样的想法,因为世界不完美,保持线性历史记录会带来很多成本,而人们并没有做好披露的工作。以下是简要概述:

  • 重写历史可能会导致历史被抹去
  • 并非每个人都可以重新定位
  • 收益往往被夸大

现在,让我们深入研究一下。 警告:篇幅较长,大部分内容是口胡

重写历史可能会导致历史被抹去

将所有提交都重新设置基础以使一切保持线性的问题在于:重新设置基础通常是不可逆的。当您重置基础时,有些信息-实际开发人员完成的真实任务-可能被压缩掉。有时这是好事。如果开发人员发现自己犯了错误,做一个交互式的重新设置基础来整理一下是很好的。已经处理过的捕获并修复错误:我们不需要它们的历史记录。但是有些人与总是搞砸合并冲突的特定个人一起工作。我个人不认识名叫Neal的任何开发人员,所以我们假设这是一个名叫Neal的家伙。Neil正在功能分支上编写一些非常棘手的应收账款代码。Neil在分支上编写的代码是100%正确的,并且按照我们的要求正常工作。Neal已经准备好将他的代码合并到主分支中,但发现现在有合并冲突。如果Neal将主分支合并到他的特性分支中,我们就有了他的代码原始版本以及解决合并冲突后的代码版本历史记录。如果Neal重新设置基础,那么我们只能看到他在重新设置基础后的代码版本。如果Neal在解决合并冲突时犯了错误,那么处理以前的版本比处理以后的版本要容易得多。更糟糕的是,如果Neal以足够不幸的方式搞砸了他的重新设置基础(也许他执行了 git checkout --ours 命令,但忘记了该文件中有重要的更改),我们可能会永久丢失部分代码。

我明白了,我知道了。他的单元测试本应该捕捉到这个错误。代码审核人员本应该捕捉到这个错误。QA 本应该捕捉到这个错误。他不应该在解决合并冲突时搞砸了。唠叨几句,无关紧要。Neal 已经退休了,首席财务官非常生气,因为我们的分类账出了大问题,告诉首席财务官 “根据我们的开发理念,这种情况不应该发生” 可能会让我挨打。

并非每个人都会rebase,兄弟。是的,我听说过:你在一家太空时代的创业公司工作,你的物联网咖啡桌只使用最酷、最现代化、反应迅速、基于区块链的递归神经网络,技术栈也很强大!你们的首席开发人员亲临Go语言发明的现场,而且每个人从11岁起就开始成为Linux内核贡献者。我很想听更多,但是由于经常被问到“如何退出git diff?”等问题,我实在没有时间。每当有人试图通过rebase解决与主分支的冲突时,我就会被问到“为什么它说我的文件是他们的文件”或“为什么我只看到了一部分的改动”,然而大多数开发人员可以处理将主分支合并到他们的分支中而不会出现问题。也许这不应该是这种情况,但事实上确实如此。当你的团队中有初级开发人员和实习生、忙碌的人以及直到已经成为程序员35年才知道什么是源代码控制的人时,要保持历史记录的完整性需要付出很多努力。

好处常常被夸大了

我们都遇到过这样的项目,在使用git log --graph --pretty命令时,突然终端被彩虹色的意大利面条占据了。但是历史并不难读,因为它是非线性的...它难以阅读的原因是它很杂乱。一个每个提交信息都是“。”的杂乱线性历史并不比思考周全的提交信息相对清晰的非线性历史更容易阅读。拥有非线性历史并不意味着你必须让分支在到达主分支之前来回合并多次。也不意味着你的分支必须存活6个月。偶尔在历史图表上出现的分支并不是世界末日。

我也不认为使用非线性历史进行git bisect是更困难的。一个熟练的开发人员应该能够想到很多方法来完成工作。这里有一篇我喜欢的文章,其中有一个像样的示例。 https://blog.smart.ly/2015/02/03/git-bisect-debugging-with-feature-branches/

tldr; 我不是说变基和线性历史不好。我只是想说您需要了解您正在签署的内容,并根据您的团队是否适合做出知情决策。完全线性的历史并非必需,而且肯定不是免费的。在适当的情况下,它确实可以使生活更美好,但并不适用于所有人。


9
我一直有同样的想法,但从来没有像你这样表达得那么好。谢谢你。我会把这个分享给那些总是告诉我“git rebase”是最好的做法的人。 - ericn
6
补充你的回答,如果你启用了诸如“没有得到最低批准数则无法将分支合并到基础分支”的检查点,每当你在master上有新的更改并将你的主题分支rebasemaster时,你的版本控制系统会忘记批准,因为历史已经被重写。最终,如果“批准”和“合并”之间有延迟,开发人员将不得不再次打扰审批者,这是不必要的。 - Anuj Kumar
5
这篇回答有些冗长,并且包含一些轶事或千篇一律的论点。虽然不是说这些都是错误的,但如果我们能够简化它并呈现解决的实际问题,那就更好了。比如,合并提交可能会使将分支分为两个部分变得困难,如果您发现只想发布其中一半工作的话。 - Vargr
3
呸..在Go被发明出来之前,我就已经在那里工作了30年...对于那个开发者,我只能说“在我面前屈膝吧” :) - David V. Corbin
我靠,伙计。你刚刚完全解构并摧毁了我为多个团队 ~ 5 名开发人员努力制定并放置的思维方式和工作流程。虽然我们几乎没有遇到问题,甚至包括初学者,这都归功于强有力的指导方针,但你的论点确实很有说服力,非常感谢你的出色回答!看着也让我大笑:D - Pierre C.
显示剩余3条评论

37
A linear Git history(最好由逻辑步骤组成)有很多优点。除了已经提到的两个方面外,还有以下价值:
3. 为后世留下文档。线性历史通常更易于跟踪。这类似于您希望代码结构良好且有文档:每当有人需要稍后处理它(代码或历史记录)时,能够快速理解发生了什么是非常有价值的。
4. 提高代码审查效率和效果。如果一个主题分支被划分为线性的、逻辑的步骤,那么相比于审查复杂的历史记录或压缩的变更单体(可能会令人不知所措),审查变得更加容易。
5. 当您需要在以后修改历史记录时。例如,整体或部分撤销或挑选某项功能。
6. 可扩展性。除非您在团队扩大(例如数百名贡献者)时努力保持历史记录的线性,否则您的历史记录可能会因交叉分支合并而变得非常臃肿,并且对所有贡献者来说,跟踪正在发生的事情可能会很困难。
总的来说,我认为历史记录越不线性,价值就越小。

17
许多实际应用中,线性代码历史几乎不可能进行有效管理。当许多不同的人一起工作时,如果尝试强制使用线性历史,则随着分支数量增加,事情变得更加复杂和混乱。在我看来,Git的分支/合并功能使其特别强大。 - mattmilten
5
我认为上述提到的优点仍然存在。它们是否比劣势更重要是另一个问题。线性历史通常意味着(稍微)更多的工作,但另一方面,单元测试、代码静态检查、代码注释和文档也需要相同的工作量。这实际上是一个关于项目所需质量要求的问题。 - m-bitsnbites
除了当前时刻,主要的代码仓库提供商都有工具来简单地维护线性或半线性历史记录。Bitbucket
  • 何时进行快进式合并 GitHub
  • 允许变基合并 GitLab
  • 半线性历史记录下的合并提交
  • 快进式合并
- ilyar
3
我同意列出的优点。特别需要注意第五点,即在线性历史中使用git revert和git cherry-pick非常容易。 - ilyar
2
当你有一个由10个以上的2-4名开发人员组成的团队在同一项目上工作(基于特性/主干开发风格),这些东西都不是好处。线性历史记录在我有限的经验中是浪费时间的。你很少/从不需要回去使用它的“好处”。 “无关提交”->人们不知道你可以将“合并”提交的消息重命名为更有用的内容,对吧? - Martin Marconcini
显示剩余2条评论

9

如果您经常将自己的工作与代码库中的最新版本合并,并且没有其他人在该部分进行工作,那么通常情况下这不会引起什么问题。

以下是大致的命令(来源于 这里):

git checkout -b my-new-feature
git push -u origin my-new-feature

# Changes and commits

git rebase origin/master
git push origin my-new-feature --force-with-lease
git merge --no-ff my-new-feature

这里的一些人似乎将合并和合并提交搞混了。我倾向于采用线性历史和合并提交,就像这样。这样你可以在需要时查看单个提交,但也可以从合并到合并跳转。

输入图像描述


5
以下是关于合并时使用变基(rebase)或压缩提交(squash)的优点和缺点列表:
优点:
- 易于阅读的时间线历史记录。 - 历史记录中没有多余的提交。 - 将分支/PR拆分为更小的块以便于审核,无合并提交的情况下更容易进行测试。 - 能够轻松地变基特性分支(例如改变合并顺序)。 - 鼓励有意义的提交信息(但不强制执行)。 - 发布时,可以更轻松地查看历史记录,并将其用于编写变更日志,供测试人员、消费者等使用。 - 每个提交的变基鼓励有较少/更小提交的PR。
缺点:
- 更加复杂,不是所有人都有经验。 - 如果在远程分支上不小心使用,可能会导致问题。例如,丢失提交,在本地环境中存在不一致的历史记录。 - 这是额外要维护的东西(x项目真的需要吗?)。 - 如果对一个具有许多冲突提交的分支进行变基,则需要更多时间。
个人注释:
我之前提到过压缩提交合并,因为它在历史方面有类似的影响。
此外,大多数GitHub和其他服务都有在GUI中进行变基合并和压缩合并的选项。
在某些情况下,这使得PR实际上是免费的操作。

3
如果你要将一个包含许多冲突提交的分支进行rebase操作 - 如果你发现/看到有太多的冲突,建议中止rebase操作,在rebase之前压缩(commits squash)这些提交,然后只针对单个提交进行rebase - 这样做可以将总的冲突数降至只是合并冲突的数量。 - Vlad Bokov
这个想法是将冲突解决压缩到一个提交中,而不是多个提交中,它并没有被省略。 :) - Vargr

4
使用线性历史记录,您可以使用git log --follow轻松跟踪单个文件在重命名过程中的历史记录。引用关于log.follow配置选项的文档:
如果为true,则git log将像使用--follow选项一样工作,当给定单个<path>时。这具有与--follow相同的限制,即无法用于跟踪多个文件,并且在非线性历史记录上效果不佳。

由于重命名文件是一个相当界面化的事情,而不是 Git 的核心(也就是说,Git 实际上并没有以任何方式跟踪底层文件),因此依赖线性历史记录来进行重命名跟踪是浪费 Git 功能的一种方式。但我想这也是其中的一个优点。 - D. Ben Knoble

1

线性/半线性历史的另外两个优点,其他答案中没有提到:

  1. 没有代码更改被隐藏在合并提交中作为冲突解决。每个差异都属于单个提交,其消息解释了更改背后的原因。
  2. 变基期间,冲突在概念上更容易解决比在合并期间,因为解决发生在单个提交的“上下文”中(而不是整个分支)。这减少了潜在的冲突解决错误。

让我通过一个例子来解释。假设以下非线性历史:

*   f17ba26 (HEAD -> master) Merge branch 'topic/feature'
|\
| * 1234bbb (topic/feature) adds feature foo
* | 1234aaa blah
|/
* 03f4f8d previous commit

假设某些代码行的演变被提交1234aaa1234bbb所更改,由合并提交解决了产生的冲突:

Commit   Line contents
03f4f8d  print("Nothing is supported")
1234aaa  print("We now support blah!")
1234bbb  print("We now support foo!")
f17ba26  print("We support plenty of futures now!")

在这种情况下,为什么这行代码会假定其最终状态的逻辑被隐藏在合并提交中的冲突解决之下。合并提交可能会使得跟踪选择代码最终版本的逻辑变得非常困难。此外,在合并提交中的冲突数量可能很大,使得理解和审查变得更加困难。
如果同样的代码通过“变基”演化,并且历史记录是半线性的,就像这样:
*   f17ba26 (HEAD -> master) Merge branch 'topic/feature'
|\
| * 1234bbb (topic/feature) adds feature foo
|/
* 1234aaa blah
* 03f4f8d previous commit

冲突本应在变基的过程中得到解决,这是在应用1234bbb提交时发生的。解决冲突的开发人员将有机会修订特定更改,考虑到已经存在的来自1234aaa的更改,并在提交消息中记录其关于最终代码的决策理由。


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