在过去的几天里,我一直在研究使用 Git 进行变基。大多数关于变基的论点都说它清理了历史并使其更加线性。如果你做简单的合并(例如),你会得到一个历史,显示历史何时分歧以及何时重新组合在一起。据我所知,变基删除了所有历史记录。问题是:你为什么不希望回购历史反映代码开发的所有方式,包括它在哪里以及如何分歧?
5 回答
想象一下,您正在从事一项统治世界的秘密计划。这个阴谋有三个策划者:
- 天才
- 一般
- 电脑黑客
他们都同意在 1 周内来到他们的秘密基地,每个人都有 1 个详细的计划。
电脑黑客,作为一个务实的程序员,建议他们使用 Git 来存储计划的所有文件。每个人都将分叉初始项目回购,他们将在一周内全部合并。
他们都同意,在接下来的几天里,故事是这样的:
天才
他总共进行了 70 次提交,每天 10 次。
一般
他监视了他们同志的回购并制定了击败他们的策略。他在最后一天做了 3 次提交。
电脑黑客
这个务实的程序员使用了分支。他制定了 4 个不同的计划,每个计划都在一个分支上。每个分支都被重新定位为一个提交。
七天过去了,小组再次开会,将所有计划合并为一个杰作。他们都渴望开始,所以他们都试图自己合并所有的东西。
故事是这样的:
天才
他合并了 General 的 repo 和 Computer Hacker 的所有更改。然后,作为一个逻辑爱好者,他看了看日志。他希望看到一个想法的逻辑演变,其中事物是在先前的想法提交的基础上构建的。
但是日志显示的是,无数不同想法的提交都混合在时间线上。一位读者不能仅仅通过阅读提交时间线来真正理解提交的演变和推理。
所以他以一个连天才都无法理解的混乱收场。
一般
将军心想:分而治之!
所以他将 Genius 的 repo 合并到他的 repo 中。他查看了日志,看到了 Genius 想法的一堆提交,这些提交遵循一个不稳定的进展,直到最后一天。最后一天,将军和天才的想法混杂在一起。
他正在监视计算机黑客并且知道 Rebase 解决方案。所以他对自己的想法做了一个rebase,然后再次尝试合并。
现在,日志每天都显示出一个合乎逻辑的进展。
电脑黑客
这位务实的程序员为 Genius 想法创建了一个集成分支,为 General 想法创建了另一个分支,为他自己的想法创建了另一个分支。他对每个分支都做了一个变基。然后他将所有内容合并到master中。
他的所有队友都看到他的日志很棒。这很简单。乍一看,它是不稳定的。
如果一个想法引入了一个问题,那么很清楚是在哪个提交中引入的,因为只有一个。
他们最终征服了整个世界,并且不再使用 Subversion。
所有人都很高兴。
据我所知,变基删除了所有历史记录。
这是不正确的。顾名思义,变基改变了提交的基础。通常在该过程中不会丢失任何提交(除非您没有获得合并提交)。虽然您关于将开发过程的所有内容都保留在历史记录中的观点是正确的,但这通常会导致令人困惑的历史记录。
特别是当与其他人一起工作时,每个人都在自己的分支上工作,同时需要其他人的某些更改才能继续(例如 A 要求 B 实现某些东西以便 A 可以在他自己的开发中使用该功能),这会导致许多合并。例如像这样:
#--#--#--#--*-----*-----------------*---#---\ Branch B
/ / / / \
---#-----#-----#-----#-----#-----#-----#-----#-----* Branch A
在这个例子中,我们有一个单独工作的分支,但不断地从原始分支中提取更改(# 是原始提交,* 是合并)。
现在,如果我们在重新合并之前对分支 B 进行变基,我们可以得到以下结果:
#--#--#--#--#---\ Branch B
/ \
---#---#---#---#---#---#---#---#---------------* Branch A
这代表了相同的实际更改,但是 B 已重新基于 A 上的一些较旧的提交,因此不再需要之前在 B 上完成的所有合并(因为这些更改已经存在于该较旧的提交中)。现在缺少的所有提交都是合并,通常不包含有关开发过程的任何信息。(请注意,在此示例中,您还可以稍后在 A 上重新提交最后一次提交,以获得一条直线,从而有效地删除对第二个分支的任何提示)
您进行变基主要是为了在远程分支(您只是获取)之上重新处理您的本地提交(您尚未推送的提交),以便在本地解决任何冲突(即在您将它们推送回上游仓库之前) )。
请参阅“ git 工作流和 rebase 与合并问题”,并且非常详细:“ git rebase vs git merge ”。
但是 rebase 不限于这种情况,并且结合“--interactive”,它允许对您的历史进行一些本地重新排序和清理。另请参阅“修剪 GIT 签入/压缩 GIT 历史记录”。
你为什么不希望回购历史反映代码开发的所有方式,包括它在哪里以及如何分歧
- 在中心化的 VCS 中,永远不要丢失历史记录是很重要的,它确实应该反映“代码开发的所有方式”。
- 在分布式 VCS 中,您可以在将一些分支发布到上游之前进行各种本地实验,将所有内容保留在历史记录中就没有意义了:并非每个人都需要克隆并查看您的所有分支、测试、替代方案、等等。
组织你的历史是使用 rebase 而不是合并的重点,它非常有价值。
准确反映过去每一次代码更改的 git 历史记录有什么用?您是否需要这样的东西来进行某种认证工作?如果你不这样做,你为什么想要那个?真实发生的过去是混乱且难以理解的。我的意思是,为什么不包括您在编辑文件时编写然后删除的每个字符?
使用 git 历史最常见的方式是阅读它。查找导致问题的提交和探索文件的不同版本可能是两个最常见的用例。当你的 git 历史是直截了当的(而且干净!)时,这两个用例都会变得更加简单和方便。
也许比使用 rebase 与团队其他成员共享更改更重要的是,每个成员都应该使用 rebase 将他们的更改格式化为自包含提交的逻辑集合。开发不会自然而然地发生在彼此直接遵循的逻辑步骤中。有时你只是在你的分支上推送一个提交,只是因为它是一天的结束,你必须离开。将此类信息放入您的 git 历史记录是纯粹的噪音。我经常将一个需要 20 次提交的功能压缩到只有一两次,因为没有必要显示任何最终没有成为成品一部分的东西。
即使您的功能的开发历史是一团糟,您也可以而且绝对应该制作一个乌托邦的 git 历史。你第一次以正确的顺序做对了,你在第 1 天做了功能 A,在第 2 天做了功能 B,没有错误或临时打印语句。你为什么要这样做?因为阅读您的更改的人更容易理解。
如果你将这个想法与 git bisect 结合起来,那么整理你的主历史记录以仅包含通过当时定义的所有测试的提交变得更有帮助。找到错误的起源点很简单,因为 git bisect 可以正常工作。如果您使用合并并将每个分支的整个开发历史上传到 master,那么 bisect 就没有真正有用的机会。
如果你在公共存储库上犯了一个错误,并且还没有人从它分叉/合并/拉出,你可以挽回面子和混乱:
git reset --hard [SHAnumber]
git rebase -f master
git push -f origin HEAD:master
要清空垃圾箱:
git gc