赵走x博客
首页
书籍
软件
工具
古诗词
搜索
登录
12、命令指南
11、单人团队:连接远程仓库
10、单人团队:使用标签
9、单人团队:在仓库中添加更改
8、单人团队:使用分支工作
7、单人团队:创建本地仓库
6、单人团队:基于 issue 的版本控制
5、工作流
4、分支策略
3、访问模型
2、项目治理
1、团队作战
4、分支策略
资源编号:76625
Git团队协作
书籍
热度:37
在版本控制中,分支用于隔离一块代码上产生的不同想法。分支总是起源于代码库中的某 个特定节点。在第 2 章中我们谈到了派生和克隆仓库。分支类似于新工作开始时仓库内部 的分隔。创建分支时你可能想要将工作贡献回去,也可能想要隔离不同的工作。分支本身 并不关心跟踪的是什么更改!它们的职责仅仅是跟踪更改。 使用什么分支策略取决于你的发布管理流程。分支允许你更改项目工作目录中可见的文件, 并且一次只会有一个分支活跃。大多数分支策略是根据粗粒度的想法分隔项目中的工作。 这个想法可以是软件的版本,比如,版本 1、版本 2、版本 3。 这些软件版本或许能使你联 想到分支上正在进行的工作。根据它们代表的功能名称,这些想法被分隔在不同分支。这 些想法可能是一个 bug 修复或是一个新的功能,但它们也可以表示一个完整的小想法。 本章概述了以下内容。 • 如何为你的团队选择一种分支约定 • 主线开发 • 功能分支部署 • 状态分支 • 计划部署 分支的使用方式没有任何限制。这既是好事,又是坏事。一些人为的限制(约定)将会帮 助你考虑团队的无限可能。 # 3.1 理解分支 无需研究 Git 内部的工作原理,基本理解了分支的概念就能够帮助你挑选并应用本章概述 的策略。 每个 Git 仓库都包含一个提交库。这些提交通过元数据相互链接,每个提交包含一个指向 自身父提交的引用。对于合并提交(merge commit)而言, 它可能会引用不止一个父提交。 我通常把分支比作珠串,每个提交 代表串上的一颗珠子。这个比喻不是那么严谨,不过很 好地说明了我们的意图。 Git 中的分支实际上是一个指向某个特定提交的命名指针。(给你 一根魔棒,边说名字边敲一颗珠子。你创建了一个命名分支。)当你签出分支时,你将提 交对象(由指针标识)中储存的数据复制到你的工作目录。在工作被复制到工作目录后, 你可以进行任何操作(新增、编辑和删除文件),并且将更改作为一个新的提交对象储存 到本地仓库。命名指针会自动更新并指向你刚创建的提交对象,同时你的分支也将更新。 你创建的任何提交对象都是本地的,并且只属于你,直到你选择主动将它们共享到一个远程 仓库。这一点 Git 和集中式版本控制模型完全不同,后者提交更改时工作会被自动上传。对 于一些可能出现的冲突,只需记住每个开发者在自己的仓库中都有一根魔棒,拥有全部能力。 开发者创造了关于命名分支和使用分支的约定来避免冲突。这些约定帮助开发者决定在什 么情况下允许工作出现偏离(创建新的分支),以及什么时候合并(合并两个或多个分支 中的提交对象)。一般来说,一种约定包含了两类分支:长期活跃的公开分支;短暂的私 有分支。长期活跃的分支扮演了代码中介的角色,并入大量开发者的贡献。短暂的分支的 作用是隔离一个新想法的开发过程。这些新想法可以是一个 bug 修复、新增功能或实验性 的重构。这完全取决于你! 当你和其他人共享分支时,你可以继续在你的分支副本上添加提交对象;但是,既然分支 已经被共享,可能其他人也在他们的分支副本上添加了提交对象。当你下次想要同步这两 个分支时, Git 作为纯粹的内容跟踪者,会转而借助你的经验来将这两份提交对象合并到 一个共享的历史。自动化流程中的这个中断称为合并冲突(merge confict) 。我承认它听上 去有些吓人。你的任务是解决冲突并为有问题的工作选择最合适的共享历史。 在 3.4 节中, 你将会学到保持分支最新的策略。 在第 7 章中, 你将学到一些实用的命令。 第 7 章同时介绍了如何解决冲突。 不过, 让我们先来看一看开发者在 Git 中维护工作时, 一些最常用的分支命名策略。 # 3.2 挑选约定 约定是大家认同如何做一件事的标准。作为开发者,约定让我们能够快速了解一个软件项 目是如何运转的,并且在整合自己的工作时不影响其他团队成员的流程。形成文档的约定 让新参与者更加容易上手,而其他团队成员现在只需花费更少的工作时间来帮助新人。 为团队挑选合适的分支策略,需要成员们共同讨论希望如何发布你们的工作成果。(从现 在开始,我将使用“软件”来指代你的项目,尽管 Git 也可以用于其他事情,比如写书!) 对于网站来说,你或许会希望每天安排一次发布,但对于可下载的软件产品,你或许会希 望每个月、每个季度甚至每半年安排一次发布。一旦确定如何发布软件以及是否有审计或 跟踪的需求,你就可以挑选一个最合适的分支策略。 如果你已经了解工作方式,那么先花些时间整理一下你的需求,然后再深入研究细节并选 择一个最合适的分支策略。如果你不确定你们的系统会是什么样的,第 4 章将会给你一些 关于如何组织团队内部行为的建议。 只要你的团队将手头的工作形成文档即可, 就不需要再制定硬性规定。 事实上, 如果你 查阅几个开源项目的仓库, 就会看到没有一成不变的做法。 我推荐使用 GitHub 镜像来比 较 Drupal(https://github.com/drupal/drupal) 、Git(https://github.com/git/git) 和 Sass(https:// github.com/sass/sass)使用的分支策略。 这三个非常流行的项目使用了截然不同的分支策略。 不会有版本控制警察站在你门前告诉你做错了,况且你也总是能找到另一个与你风格相近 的团队。但如果你刚接触版本控制,或者你的团队苦于寻找平稳进展的办法,选择本章介 绍的约定或许能够帮助你。 # 3.3 几种约定 对于软件项目来说,团队通常有两种方式可供选择:要么选择“优先合并”,要么查对完 成的工作并一次性发布。介于这两个极端之间有很多不同的工作方式。 本节概述如今开发团队最常使用的一些策略。你可以选择完整地采取其中一种策略,也可 以根据你的需求来调整。不管你选择什么策略,记得把你的决定形成文档。 ### 3.3.1 主线分支开发 主线分支开发是最容易理解的分支策略。在这个策略中只有少量分支。开发者总是将他们 的工作提交到一个中央分支,这个分支随时可部署。换句话说,项目的主分支应该只包含 经过测试的工作,并且绝不应该被破坏。 我通常独自参与一些不怎么需要使用版本控制的业余小项目,比如为杂志写稿。如图 3-1 所示,在这些情况下,我把所有工作提交到默认分支(在 Git 中称为 master) 。如果我同时 进行着两个不相关的想法,我可能会偷懒一起提交,或者保存( stash )一些工作供日后使 用。对于这些简单的项目来说,即使不将想法分隔在不同分支,也能高效地工作。 ![image.png](http://file.handsomemark.com/file/2020/05/15/9d3bf680-966e-11ea-b6ea-00163e12d51c.png) 图 3-1:主线分支开发:将所有提交保存在一个分支 >阅读链式图 图上的每个圆圈代表 Git 仓库中一个可溯及的提交。“链式”提交图的学名叫 作有向无环图(DAG)。我敢保证没有人会考你是否记住了这个名称。但你 若想进一步探讨,这会是一个很有用的术语。 随着项目成熟,会不断出现更多需要考虑的地方,跟踪想法也会变得越来越困难。如果我 想要尝试项目未来的方向,而这些想法还不如当前其他工作那么完善时,我就会开始添加 新的分支。我可能会扩大团队,像图 3-2 中一样,让一两个评审者负责他们自己的独立分 支。当项目(以及团队成员)逐渐成长时,分支的数量也会逐渐增长。但这些分支不会同 时保持活跃。正如《金发姑娘和三只熊》中的故事,你的团队可能会觉得一定数量的分支 类型“不多不少正好”。每个工作单元[或者冲刺(sprint)]中的分支数量可能会存在手风 琴效应。起初,开发者都在做自己的工作,而分支的数量随之增长。后来,当每个开发者 完成自己的工作并将更改与其他人整合后,分支数量又降了下去。 ![image.png](http://file.handsomemark.com/file/2020/05/15/d27b1254-966e-11ea-b6ea-00163e12d51c.png) 图 3-2:使用分支的主线开发:分支隔离了多人贡献的工作 使用单一工作分支的做法被自动化构建过程的团队大量应用。 >为持续部署的团队提供的建议 持续集成是指所有开发者每天多次将自己的工作并入项目主线的实践。持续 交付是指自动进行从开发者的本地工作站到服务器的中间步骤(但不是通过 自动化的流程来部署)的实践。持续部署的自动化最为完备,因为所有代码 通过一系列测试后,直接进入产品服务器。 将工作定期并入中央分支,但偶尔进行部署,这样的做法或许适合你的团队。在开始收集 工作后,你需要将本地的工作和产品服务器上的工作区分开来。如果所有代码都是可部署 的,那么添加一个小的修复然后立即发布不是什么难事。但如果你提交到仓库中的更改只 进行了一半呢?因此,我们不再使用纯粹的持续部署策略,而是使用多个分支的计划部署 策略。 使用鼓励持续集成工作的分支策略有以下优点。 • 整个项目中不会出现很多分支。因此,一项更改的消失不会造成明显的困惑。 • 并入代码库的提交相对较小。如果发现了问题,能够相对更快地撤销错误。 • 紧急修复会变得更少,因为主分支中的代码都是达到部署标准的。部署对于开发者往往 都是压力重重的,他们紧张地看着代码进入产品然后等待用户的反馈。经过频繁的小幅 更新,这个流程慢慢成熟并最终自动化,终端用户几乎感受不到。 使用这个策略也有下面这些缺点。 * 这个策略的前提是主分支中只包含随时可部署的代码。如果你的团队缺乏测试设施,臆 想新的代码不会有问题就过于冒险了,尤其是在项目随着时间变得越来越复杂的时候。 * 部署这个概念更适合指自动装载到用户设备(如网站)的代码。 对于必须手动下载和安 装的软件来说,这个术语不是那么合适。尽管修复问题的更新总是受欢迎的,但如果让 我每天在手机上下载并重新安装一个应用,我也会感到不悦。 * 开发者验证产品代码的方法之一是将功能隐藏在一个开关后。据说 Facebook、Flickr 和 Etsy 都使用了这个方法。不过,这个方法存在的潜在风险是,隐藏在开关后的代码可能 会被废弃,因为被隐藏而未被及时移除的代码会导致技术债务堆积如山。 不幸的是,介绍如何搭建持续部署的基础设施超出了本书的范围,因为这依赖于你所使用 的语言(不同语言拥有自己不同的测试库)和部署工具。如果你希望了解有关理念的更多 内容, Jez Humble 和 David Farley 的《持续交付:发布可靠软件的系统方法》 是一本很好 的入门书。 ### 3.3.2 功能分支部署 为了克服刚才提到的单分支策略的限制,你可以引入两种类型的分支:功能分支和集成分 支。严格地说,它们不是两种类型的分支,而是依据每个分支的职能来区分的约定。 在使用功能分支的部署策略中,所有新的工作都在一个功能分支上完成,这个分支小到恰 好能够容纳一个完整的想法。这些分支通过一个集成分支与其他开发者完成的工作保持同 步。等到软件发布前,构建管理员可以挑选将哪些功能集成到这个版本,并为部署创建一 个新的集成分支。如图 3-3 所示,一次构建不一定会包含上次构建之后所有完成的工作。 ![image.png](http://file.handsomemark.com/file/2020/05/15/4d8a0cca-966f-11ea-b6ea-00163e12d51c.png) 图 3-3:功能分支部署:功能分支之间通过集成分支保持同步 添加多个功能分支和一个集成分支后,你可以保证代码一直是可部署的,但在部署代码前也 会停顿一下。这个模型最流行的描述是 Adam Dymitruk(http://dymitruk.com/blog/2012/02/05/ branch-per-feature/)提出的, 其更早一些的描述由 Scott Chacon 提出并被命名为 GitHub 流 程(http://scottchacon.com/2011/08/31/github-flow.html)。 经过了几次微调(https://twitter.com/ emmajanehw/status/519814560539480064)后, 这个流程如今仍然被 GitHub 使用。 在 GitHub 流程的分支模型中,任何 master 分支上的代码都是可部署的。当编写新的代码 时, GitHub 流程要求开发者创建一个语义化命名的分支,并将他们的工作定期提交到这个 分支。这个分支与 master 同步,并定期推送到共享仓库中对应的分支,使得其他人可以看 到哪些功能正在进行。当开发者认为工作已经完成,或者需要其他人的帮助时,他们会在 master 分支上发起一个拉取请求。工单系统将会出现关于工作方案的讨论。 到目前为止, GitHub 流程和 Dymitruk 模型几乎是相同的。 它们的区别在于部署方式。 在 Dymitruk 模型中,创建一个构建前需要挑选即将集成的功能。在 GitHub 流程模型中,在合 并请求被接受后,即可从功能分支上立即部署工作,在这个意义上,这种策略更接近于主线 开发。早先, GitHub 将自己的功能分支并入 master 分支,然后再部署 master 分支。现在,我 们部署完功能分支,如果上面没有错误,它将被并入 master 分支,如图 3-4 所示。也就是说, 如果功能分支存在问题,我们可以立即重新部署 master, 因为这个分支永远处于工作状态。 ![image.png](http://file.handsomemark.com/file/2020/05/15/82ff5f7c-966f-11ea-b6ea-00163e12d51c.png) 图 3-4:GitHub 流程:功能分支在审查后进行部署,然后被并入到 master 分支中 使用功能分支部署策略有以下两个优点。 • 类似于主线开发,我们力图让代码快速持续部署。 • 区别于主线开发,我们提供一个可选的构建步骤。当使用构建步骤时会出现一个选项, 用于选择应该将哪些功能并入到 master 分支中进行部署。 同样,使用功能分支部署策略有以下三个缺点。 • 如果代码存放在功能分支上,而不是立刻发布到 master 分支,开发者需要进行额外的 维护,保证在将代码并入部署分支前,功能都保持最新。 • 分支的语义化命名对于熟悉系统的开发者有帮助,但如果进行中的功能很多,新参与者 会对这些行话感到困惑。 • 开发者需要定期清理那些已经并入 master 分支的遗留分支。这不算麻烦,但与在单一 的 master 分支上工作,需要这个额外的步骤。 功能分支策略提供了一个介于主线开发和计划部署之间的过渡状态。在某种程度上,计划 部署扩展了这个策略,但使用了特殊的命名约定。 ### 3.3.3 状态分支 和之前提到的策略不同, 状态分支策略引入了分支位置或快照的概念。 我们使用的部署 图通常过于简化, 告诉我们代码从一个环境迁移到另一个环境(见图 3-5) , 但事情并没 有这么简单。图 3-6 显示代码从一个分支合并到另一个分支,且其中每个分支都被部署到 一个特定的环境中。(我们会在以后讨论带标签的发布, 别心急。)如图 3-6 所示, 我们使用的分支名称和部署环境的名称通常不同。(master 是什么意思?这是用于产品发布的 分支吗?还是开发的分支?你确定吗?)这个策略称为 GitLab 流程(https://about.gitlab. com/2014/09/29/gitlab-flow/)模型。 ![image.png](http://file.handsomemark.com/file/2020/05/15/d108ea12-966f-11ea-b6ea-00163e12d51c.png) 图 3-5:部署的谎言:代码其实并没有从本地服务器移动到产品服务器 ![image.png](http://file.handsomemark.com/file/2020/05/15/dde37e8c-966f-11ea-b6ea-00163e12d51c.png) 图 3-6:真实的部署过程使用了集中式的代码托管系统 通过分支的命名约定, GitLab 流程使我们明白什么代码用于什么环境,以及在合并提交前 需要满足哪些条件。比如,你一定不希望将未经测试的代码并入一个叫作 production 的分 支。或者,如果你要将代码交付给“外界”, GitLab 流程会建议你使用预发分支。理想情 况下, 这些预发分支应该遵守语义化版本命名(http://semver.org/)约定, 尽管 GitLab 没 有明确要求这一点。 >在语义化版本命名中 , 如何增加版本号 在语义化版本命名中, 发布的版本号总是应该遵循这样的格式:MAJOR. MINOR.PATCH。 第一个数字(MAJOR,主版本)应该在进行了无法向后兼 容的 API 修改时增加。第二个数字(MINOR,次版本)应该在增加新功能但 是不会打破已有的功能(也就是向后兼容)时增加。第三个数字(PATCH, 修订版本)应该在进行了向后兼容的 bug 修复时增加。 Git 项目使用的分支命名约定(https://www.kernel.org/pub/software/scm/git/docs/gitworkflows. html)是状态分支策略的一个很有意思的变种。 它拥有以下四种专门命名的集成分支。 • maint 这个分支包含了Git最近一次稳定发布的代码以及小数点版本的额外提交(维护)。 • master 这个分支包含应该进入下一次发布的提交。 • next 这个分支用于测试一些主题在进入master分支后的稳定性。 • pu 这个建议的更新(proposedupdates)分支包含尚未准备好合并的提交。 分支之间的关系就如同一个堆叠的金字塔一样。每一个“底层”的分支包含“上层”分支 中没有的提交。如图 3-7 所示, maint 拥有最少的提交,而 pu 拥有最多的提交。一旦代码 进入审查流程,它将被并入下一个集成分支,离进入官方发布又进了一步。 ![image.png](http://file.handsomemark.com/file/2020/05/15/3849ed02-9670-11ea-b6ea-00163e12d51c.png) 图 3-7:Git 项目使用的集成分支 使用状态分支策略具有以下两个优点。 • 分支名称与手头工作的上下文密切相关。 • 无需猜测就能明白每个分支的目的,在合并自己的工作时更容易选择正确的分支。 同时,使用状态分支策略也有以下两个缺点。 • 在没有指导的情况下,并非总是能很容易找到从哪开始新建一个分支。 • 由于分支名与团队的上下文密切相关,在项目中保持一致可能会更困难,使得新人上手 时更加困惑。 在我自己的项目中,我通常选择这个风格的分支策略。我喜欢使用我容易理解的词汇,而 不是其他团队中的其他人熟悉的词语。如果你对细节十分在意,那么请保持一致!除非你 倾向于使用自己的词汇。 ### 3.3.4 计划部署 如果你没有一个完全自动化的测试集,却必须计划一个部署,那么计划部署分支是最适合 你的策略。或许是因为你需要遵循部署窗口期(比如,从不在下午 4 点后部署,也从不在 周五部署),或许是因为要通过额外的机构审查(比如,在将 iOS 应用部署到 App Store 之 前)。只要有人介入审查流程,或其他人对你的部署过程施加了任何限制,你的部署就不 可避免地会出现延期,在等待时你需要想办法中断你的工作。 通过使用不同类型的分支策略,我们不断增加仓库内分支的复杂度。最开始我们只有一个 分支,然后加上了功能和集成分支。在计划部署中,我们也要这么做。但是,计划部署的 分支模式可能会渐渐变得非常复杂。复杂度应该逐渐上升,而且仅在必要时增加。 在本节中, 我将会带你了解如何在团队中实施 GitFlow 分支策略, 并了解其演化历程。 GitFlow 是计划部署策略最流行的实现, 最初由 Vincent Driessen(http://nvie.com/posts/ a-successful-git-branching-model/)提出。 它被世界上无数的团队用于构建软件项目, 其最 终形式可能看上去非常复杂。但幸运的是,软件项目的构建是循序渐进的,不是一蹴而就 的。如果 GitFlow 中某些部分与你的团队无关,你可以在你的项目中忽略它们。 我们一起来仔细了解这个模型。 最初你的软件项目只有一个分支, develop 。 在这个分支上, 程序员创建与之偏离的分支 并在上面添加他们的功能。图 3-8 显示,此时 GitFlow 的状态图与本章先前介绍的模型十 分相似。 在这里, 我会使用广义的“功能”术语。 在理想的团队协作中, 功能将在你开 始工作前在工单中进行描述, 且分支名将会包含这个工单名。 比如, 如果你有一个工单 “1234”,代表一个修复失效链接的 bug 报告。若使用 [ticket_id]-[terse_title] 的命名约 定,那么你的分支名应该是 1234-fixing_links 。 ![image.png](http://file.handsomemark.com/file/2020/05/15/790479fc-9670-11ea-b6ea-00163e12d51c.png) 图 3-8:GitFlow 中使用的开发和功能分支 你的团队不断地工作、工作再工作,直到某一刻宣布“没有新的功能要做了”。我们通常 将这个时间节点叫作功能冻结(feature freeze) 。 此时, 会从开发分支上创建一个新的分 支, 如图 3-9 所示, 只有 bug 修复能够提交到这个分支上。 这些 bug 可能包括性能衰退、 安全漏洞和一些其他问题。在更传统的瀑布流团队结构中, bug 修复的阶段应该由质量保 证团队带领。在更敏捷的团队中,开发者从一系列分支到部署都一直跟进这些问题,甚至 可能会负责测试其他人的工作。我们将会在第 8 章中更详细地讨论评审过程。 功能冻结时或许不是所有功能都完成了,所以仍然有工作被提交至 develop 分支。如果发 现了 bug, 这些 bug 同样需要被“向后”并入 develop 分支。图 3-10 显示了在代码并入两 个不同方向时,我们看到的分支图。你的质量保证阶段越长, develop 分支和预发分支上 越有可能出现同时进行的工作。 ![image.png](http://file.handsomemark.com/file/2020/05/15/b07cfe68-9670-11ea-b6ea-00163e12d51c.png) 图 3-9:GitFlow 中的功能冻结;只允许 bug 修复 ![image.png](http://file.handsomemark.com/file/2020/05/15/ccefd642-9670-11ea-b6ea-00163e12d51c.png) 图 3-10:开发继续,但不会并入预发分支 经过一段时间的测试,所有 bug 会被宣告发现,剩下来的事情就是准备好部署。恭喜你! 此时,所有经过了质量保证测试的代码都被提交到一个新的分支, master 上,这些代码被 打上了软件当前版本号的标签(就像书签一样)。接着这个软件会如图 3-11 所示的那样进 行部署。你的项目经理给你一个心形糖果,或者一个 GIF 动画,然后你就可以下班了。干 得漂亮,队员们!(如果你的项目经理没有这么做,请把我的建议发给他们,我会帮你和 他们谈谈。我们都是朋友,没什么大不了的。) ![image.png](http://file.handsomemark.com/file/2020/05/15/e538a008-9670-11ea-b6ea-00163e12d51c.png) 图 3-11:代码并入一个新的分支 master 并打上标签,从而软件得以发布 当然,在现实中,有些需要立即进行修复的 bug 将会潜入软件中。这些补丁(hotfix)十分 关键,程序员应该在晚上回家前就修复这些 bug。 它们通常是从产品分支拉出的一条新的分 支,当补丁发布后,它们不包含任何上次官方发布后进行的额外工作,如图 3-12 所示。 >在你的团队中定义 “ 紧急 ” 有一次,一个与我合作过的开发者告诉我,如果一个 bug 必须要在他去酒吧 喝杯啤酒之前进行修复,那么必须将这个 bug 标记为补丁。他的话从根本上 改变了我如何理解那些标记为紧急的问题。 我们重新校准了“紧急”的定 义,最终减少了熬夜的次数。同样地,我曾经接触过的一个客户喜欢把工单 标记为“非常重要,但不急着做”。你可以拿你的命名约定开玩笑,但确保 记下了它们的含义,避免由于困扰造成的匆忙赶工。 ![image.png](http://file.handsomemark.com/file/2020/05/15/0e897f9a-9671-11ea-b6ea-00163e12d51c.png) 图 3-12:一个补丁完成后进入 master 分支,现在我们的发布标签是 1.0.1 由于需要在不同场所中继续不同的工作,我们渐渐构建了这些分支。你不需要一开始就建 立所有分支。事实上,最好不要这么做,因为到最后你会发现需要维护的代码更多了。一 旦你同时拥有了产品代码和开发代码, 那么最终会在分支状态图中拥有一大堆活跃的节 点,正如图 3-12 所示。对于新人来说这会是非常麻烦的,但对于伴随项目一路走来的开发 者来说,这是一个自然的过程。如果你选择使用这个约定,先前有过相似经验的新来的开 发者也会感到非常熟悉。 使用计划部署策略具有以下四个优点。 • 计划部署不要求你预先准备好全面的测试设施。 • 软件的构建过程通常伴随着开发、质量保证、生产这些阶段。也就是说,一旦软件开发 者理解了分支约定中如何以及何时进行他们的日常任务,他们便会对 GitFlow 约定感到 非常熟悉。 • 通过严格遵守约定,开发者总能明确自己应该从哪个分支开始工作。 • 这个模型同样适用于版本化管理的软件,如应用商店中下载的应用,这些应用不适合每 隔几天部署一个新版本。 使用计划部署分支策略也有以下三个缺点。 • 对于刚接触到软件部署并且没有经历过整个开发流程的开发者,存在较多的认知负担。 • 如果开发者从错误的分支开始工作,要将所有工作恢复同步是件困难的事情。 • 它不如持续集成那么流行。 计划部署策略提供了最严格的约定,规范了代码应该如何在通过评审后移动。它通常在几 乎没有自动化的代码评审中被使用,并且总是出现在没有应用自动化部署计划的某些项目 中。工作收集到项目且尚未发布时,你将会至少见到本节中描述的一些特征。 # 3.4 更新分支 本章主要介绍用于隔离和合并几股工作的常见策略。这些策略集中于一个单一最佳路径的 场景中,其中不同分支的工作神奇地与其他相关的工作保持同步。在分布式版本控制系统 中,整合外部工作的方法与你选择的分支策略无关。更新分支时,你可以选择其中一种策 略:合并(merge)或变基(rebase)。 在深入了解这两种策略的差异前, 我们先来简单地 了解一下多个仓库之间的连接是如何维持的。 每个 Git 仓库都是一份自治的更改记录。仓库之间的连接通过一个远程的引用建立。这个 应用允许开发者将远程仓库中所有提交对象复制到自己的本地仓库。远程连接通常用于至 少共用部分历史的仓库。比如,第一次使用命令 clone 下载仓库,将会出现一份远程仓库 及其提交对象的副本。 例如,你想要将你的工作添加到同事的分支上。你连接到他们的远程仓库,拉取分支并尝 试添加你的工作。但你不能这么做!如果那是一个本地分支,你可以在分支顶端添加一些 新的提交对象。但是,因为你希望更新的是一个远程分支,所以你无法将一个新的提交对 象放置到你的仓库中那个分支的顶端,因为这个操作只能由远程仓库的所有者完成。与此 相反,你必须先创建一个新的跟踪分支来储存你的更改。 >一些跟踪分支是自动的 clone 命令将会默认创建一个名为 master 的跟踪分支,和与它同名的远程分 支保持一致。 现在你有了一个可以添加新提交的分支的本地副本, 一个不能添加提交的分支的引用副 本,以及一个仍然存在于远程仓库的原分支。当你和你的同事分别在自己的仓库中进行更 改时,这些分支将会不可避免地不再保持同步。 fetch 命令将会更新分支的引用副本,下 载所有新的提交。这个分支的可变跟踪副本,有多种更新方式。这是因为你现在将两个分 支并入一个,这个操作在 Git 中对应了多种策略。有选择就会有分歧,会争论应该使用哪 种方法。 通过远程引用更新跟踪分支的过程通常借助 pull 命令实现。然而, pull 是两个无关步骤 的组合: fetch 和 merge ,或者 fetch 和 rebase 。 pull 命令默认使用 merge 策略来更新本地 分支,但是,通过添加 --rebase 参数,开发者可以选择通过 rebase 策略来更新自己的本 地分支。 > 两种变基的方式 变基在更新一系列提交时有两种方式。第一种,在相关分支上整合新的工作 (更新一个分支)时用于代替合并。第二种,通过增删修改分支上的提交历 史中的特定提交来更改现有分支上的历史,达到使历史更加精简的目的。本 节指的是前一种方式。 变基给人带来的印象是复杂和繁琐的。 但从图形化的角度来说, 变基是可读性最佳的策 略。图 3-13 显示了从一个分支变基至另一个分支前后的变化。一般来说,我们将变基解释 为在一个已有的时间线上重放已有的提交。这个比喻虽然在技术上有些出入,但很好地解 释了合并和变基之间的差异。 ![image.png](http://file.handsomemark.com/file/2020/05/15/7303d398-9672-11ea-b6ea-00163e12d51c.png) 图 3-13:变基两个分支时只改变其中一个分支的历史,而另一个分支看上去没有变化 rebase 命令用于更新分支, 而 merge 命令用于引入全新的工作。 当 merge 命令用于快进 (fast-forward)策略时, 状态图和变基后的图形并无二致。 快进策略只有在合并只包含并 入分支中已有的提交时才能够使用。图 3-14 显示,快进合并的图形和变基一样整洁。 当两个分支上都有新的工作时,如果你希望合并这些工作,那么将会需要在一个新的提交 中储存合并后的工作。 你可以使用不同的合并策略, Git 会选择对你最合适的一种策略。 如果你对不同的合并策略非常感兴趣, Git 介绍合并的帮助页面会告诉你章鱼式合并(两 分支以上的合并)和递归式合并有哪些差别。要阅读文档,请运行 git help merge 命令。 >不知道如何选择合并和变基? 使用快进合并或变基的两个分支所产生的状态图几乎是一样的。因此,选择 哪种策略有些令人困惑。 事实上, 这种选择是如此困惑以至于一些团队选 择混合使用这两个命令。 如果你花费一些时间来理解在什么时候使用哪种 策略, 就将能够敏捷地在不同项目中选择不同的策略使用。 合并还是变基 (http://gitforteams.com/resources/merge-rebase.html)提供了一个决策树来帮助 你认清应该使用其中哪种策略。 ![image.png](http://file.handsomemark.com/file/2020/05/15/1e2b3824-9673-11ea-b6ea-00163e12d51c.png) 图 3-14:使用快进策略合并两个分支和使用变基一样整洁 如果你希望通过合并来让手头的工作保持同步,图形化的历史将会变得难以阅读,因为其 中的连接变成了双向的。也就是说,历史在两个分支间突然转向,因为代码已经同步,而 新的功能也发布到了主分支上。图 3-15 显示了合并是如何记录提交的来源的。如果你正在 将一个功能分支并入项目的主开发分支,那么这会是你希望看到的。但如果你只希望阅读 当前功能的历史,则会感到有些困惑,因为主开发分支将会进入你的历史图,而功能分支 和集成分支合并后的连接都会被画在上面。 由于一些同步的问题,使用 Git 的开发者如果准备将他们的工作提交回项目,那么他们通 常不会在跟踪分支上工作。取而代之,开发者将会创建这个分支的第四份副本(一份是跟 踪分支的副本,跟踪分支是引用分支的副本,而引用分支是远程分支的副本)。不管使用 哪种分支策略,跟踪分支可以映射到任何长期运行的分支(比如主分支或预发分支),且 工作分支是一个功能、工单或补丁分支。 通过分支变基来保持同步使得图像简化,而历史变得更易于阅读。然而,变基也要付出一 些代价,尤其是当你的分支副本中包含刚创建的提交对象时。为了变基一个包含独特提交的分支,你必须将每个提交一个个重放至新分支的顶端, Git 为每个提交分配了一个新的 标识符,因为每个提交都分配到了一个新的父节点。如果在分配到一个新的父节点提交之 前共享至其他远程仓库,那么这可能会造成困惑。除了新的标识符以外,每当你重放一个 提交时,都有可能出现合并冲突,有时解决冲突非常耗时。这有点像维护考勤表,只要你 每天投入一点时间保持你的考勤表同步,就不会出现什么大问题。但如果你实在记不住每 天在考勤表上做记录,那么回顾并补全之前的记录会是非常耗时的。通过变基策略维护一 个同步的分支带来的奖励是一个易于阅读的分支历史。但它真的值得吗?如果 Git 新手还 不怎么习惯解决合并冲突,那么他们会感到非常受挫。 留给你一个课后作业,和你的团队讨论以下哪一点更重要:易用性(选择合并来保持分支 同步),还是易读的历史记录(选择编辑来保持分支同步)。 ![image.png](http://file.handsomemark.com/file/2020/05/15/add764f2-9673-11ea-b6ea-00163e12d51c.png) 图 3-15:不使用快进策略合并两个分支 # 小结 如果你使用 Git 托管系统, 如 GitHub、Bitbucket 或 GitLab, 分支可能被用于分隔一个特 定 bug 或功能工单对应的工作。根据你使用的分支策略,你的目标或许是让分支无限期地 分隔,或者你或许希望经常将这些分别完成的工作合并到一个可部署的分支中。尽管所有 信息都储存在仓库中,但在某一时刻只有一个分支可见。所以,如果你有两个想法正在同 时进行,并且希望它们同时在服务器上出现,你将会需要将这两个分支并入一个常用的分 支,来让它们同时可见。 本章介绍了使用 Git 时的几种分支策略,以及某些团队使用过的这些策略的一些变种,如 下所列。 • 主线开发 • 功能分支部署 • 状态分支 • 计划部署 除了这些策略以外,你还将决定你的团队需要如何将各自的新工作并入共享的分支,并保 持分支最新。对于新手团队,如何保持分支最新并不总是显而易见的。有两种策略可供选 择:变基或合并。变基策略如果不定期执行,就会变得尤为麻烦,但它确实能使你的历史 记录的图像更加整洁,从而易于浏览。通过使用合并来保持分支最新,浏览项目历史将会 变得更困难。因此,如果你的工作起源不重要,可以选择其中任一策略,但如果你要经常 浏览历史记录,变基会使未来的工作更容易(即使现在来看更耗时了)。