前言

背景

大多时候,开发人员仅需使用少量的分支,甚至只需 master 和 develop 两个分支即可完成日常工作。这足以应付小型项目或小规模团队的开发工作,但随着协作人员的增多和项目周期的延长,各样的挑战便会纷至沓来…

代码版本控制的挑战

  1. 并行开发:如何开始一个 feature 的开发,而不影响别的 feature ?
  2. 代码回溯:如何了解每次提交做了哪些工作,如何让提交记录承载软件文档的功能?
  3. 代码回溯:随着时间的流逝,如何快速了解每个分支都做了什么?
  4. 分支管理:由于新分支的创建是廉价的,分支多了之后该如何管理?
  5. 发布管理:如何进行发布管理?发布时如何冻结 Feature 的合并?发布过程中如何修复线上 bug?发布过程中如何并行开发新功能?
  6. 修复管理:线上代码出 Bug 后如何快速修复?修复后的代码如何安全优雅的合并到所有协作者的工作分支中?
  7. 代码维护:如何在多人员的并行开发过程中保证代码质量

面临这些挑战,本文试图提出一种简洁的、清晰的、可执行的分支管理方案,以期能够规避一些常见的版本控制陷阱,提高协作效率,增强知识共享,降低维护成本。

本分支规范的目的

  1. 提高协作效率:基于 git 的分布式实现,发挥并行开发的优势
  2. 增强知识共享:提交记录即文档,使其成为知识的载体之一,使得项目变更具有回溯性
  3. 降低维护成本:通过清晰的分支划分,简化代码集成过程、问题修复过程,使得项目的集成更可控

代码提交规范

好的提交记录是分支管理的基石,在讲述分支管理之前,务必先了解代码提交规范。

git commit 规范及原则

  1. 单一职责原则:细化提交粒度,每次提交内容应少而精,职责清晰,任务单一,拒绝将多个改动汇聚到同一个提交记录中

  2. 基于第一点,每次提交日志应尽可能的准确,详细阐述本次提交的改动内容,必要时可详细阐述改动的原因并给出相关链接。下附提交日志格式,以供参考。

    第一行,简述变更内容,对于简单改动,仅填写此行信息即可。控制在 72 个半角字符以内,避免 Web 端自动换行
    第二行,保持空行
    第三行,详细变更 1,必要时需说明变更原因
    第四行,详细变更 2,必要时需说明变更原因
    第五行,相关链接(Wiki、RFC、技术博客等)...
    
  3. 拒绝空日志、重复日志、无意义的提交记录

  4. 避免不完整的提交,确保推送至远程分支的代码都是可运行的

    • 如有某次提交不完整的情况,可在推送至远端前采用git commit --amend 的方式完善上次提交的内容
  5. 避免过于琐碎的无意义提交,尽量保持提交记录清晰可读

    • 若有多个提交记录所完成的功能是一个原子型任务,则可在推送之远端之前使用 git commit --ammendgit rebase -igit merge --squash 的方式进行整合后,重新提交
    • 此外,也可以使用 git reset --soft 将本地仓库重制到某次操作后,重新提交
  6. 拒绝在合并时使用 fast-forward 方式隐藏合并记录,建议使用git merge --no-ff进行合并操作,确保每次代码合并都是可回溯的

  7. 非特殊情况下,避免使用 git push -f 强制重置远程仓库中的历史记录

小结

代码提交规范是分支管理的基础,不好的提交记录在 PR/MR 阶段将会被拒绝,同时,提交记录也是一份事实上的文档,描述了代码的变更原因与变更内容。一份好的提交记录将会为知识分享、CodeReview 等提供莫大的便利,因此,请务必保持提交记录的整洁性易读性传承性

git-flow 分支模型

git-flow

  1. 图中每个 ● 都表示一个 commit 记录
  2. 图中纵轴表示时间。● 的位置越靠近下方,则表示该 commit 出现的越晚
  3. 图中每列都表示一条分支。上图中从左到右,有 2 条 feature 分支、1 条 develop 分支、1 条 release 分支、1 条 hotfix 分支与 1 条 master 分支

分支规范

开始之前,先来了解一下本文中即将出现的术语,以便对本文将要表达的内容达成一个初步共识

术语对照表

术语git 命令说明
签出git checkout -b $新分支名称以当前工作副本为准,创建新的分支
签入*git merge 或 git rebase 或 git cherry-pick将某次提交或某个分支合并到当前工作副本所在的分支
PR/MR-发起 Pull Request/Merge request 请求,开启 CodeReview 流程
上游*-当前分支的起点,例如:feature 的上游通常是 develop
项目负责人Project Owner,最了解该项目源码的人,非职位

签入:本文中的「签入」一词均可替换为「合并」,它们所表达的意义是基本相同。本文之所以使用「签入」而非「合并」是因为笔者主观的认为「签入」更具普适性。例如:git rebase、git merge、git cherry-pick 均可进行代码集成 — 即「合并」操作。

上游:本文中的「上游」一词并非本地分支所对应的远程分支,而是指当前分支所关注的 BaseLine 基线分支。举个例子:feature/A 、feature/B 分支都是从 develop 分支签出的功能分支 ,那么 develop 便是feature/A 和 feature/B 的基线分支,即「上游」。

规范及原则

分支描述作用约束原则
develop开发分支(长期)开发过程中的基线 Base Line1. 所有的 feature、release 分支均从 develop 签出
2. develop 属于保护分支,任何人不得直接修改develop 分支中的 commit 记录
3. develop 分支的所有 commit 必须通过 PR/MR 的方式签入
4. 关注点分离原则最少知识原则:develop 仅集成本期迭代需要交付的功能与代码
feature功能分支(短期)新的需求或功能
优化与重构
1. 由 develop 签出
2. 单一职责原则,切勿在同一个 feature 分支内做多件事
3. 及时与上游(develop)进行代码同步,持续集成降低集成成本
4. 如有必要,可以在 feature 分支之间进行合并,如:代码整合,功能整合等
5. 开发完成后,发送 PR/MR 给项目负责人进行CodeReview
6. 审核通过后,签入 develop 分支
release预发分支(短期)集成测试
细节微调(配置修改、数据脱敏等)
1. 由 develop 签出
2. 此分支仅做少量更改;如需大量修改,请使用 feature 分支完成
3. 测试、验收通过后,发送 PR/MR 给项目负责人进行 CodeReview
4. 审核通过后,签入 master 分支,必须添加 tags 记录,用以标识本次发布的版本信息
master已发布分支(长期)归档
稳定的
基于 tags 的
可回溯的
1. master 属于保护分支。任何人不得直接修改 master 分支中的 commit 记录、tags等信息
2. master 分支仅为已发布的软件版本提供回溯功能,以便 bug 修复、回归测试、紧急降级等
3. master 分支的上游必须是 release 或 hotfix
4. master 分支中的所有 commit 必须通过 PR/MR 的方式签入
hotfix修复分支(短期)对 master 中已发布版本存在的问题进行修复
1. 由 master 签出
2. 单一职责原则:仅允许对已发布版本(如:v1.0.2、v2.0.0)中存在的 bug 问题进行修复;不允许在 hotfix 中集成新功能
3. bug 修复后,向项目负责人发送 PR/MR 进行CodeReview
4. 审核通过后,同时签入 develop 分支与 master 分支

补充说明

  • develop

    • develop 分支是软件开发、维护过程中的基线分支,贯穿软件活动的整个生命周期

    • 单一信源 Single Source of Truth :所有的变更、发布只有一个来源,出错的机会便会少很多

    • 所有的 feature/release 分支均从 develop 分支签出

    • 关注点分离原则:develop 分支仅集成本期迭代需要交付的功能(feature),并非所有的 feature 完成后都要立即签入 develop 分支。PS:让子弹飞一会儿

      如果一个项目有多个发布版需要并行开发,且不同的发布版间有巨大的代码差异,则建议使用多个 develop 基线分支进行开发

  • feature

    • 特性分支通常有多个(>= 并行开发的需求或人员的数量),它们都是从 develop 分支签出

    • 特性分支的名称通常为 feature/特性分支名称,每个特性分支对应某一个或某一类的需求或功能

    • 特性分支的名称必须是需求相关的准确的无歧义的自解释的易于理解的

    • 开发过程中,大部分工作在特性分支进行,功能完成后,需通过 PR/MR 的方式将其签入回 develop 分支

    • 持续集成原则:

      1. 确保每次提交的代码都可通过编译,仅向版本库提交可编译、可运行的代码
      2. 及时向远程仓库推送本地变更,避免因本地设备损坏、失窃等造成的代码丢失
      3. 每天至少从上游同步一次代码,尽早对上游的变更作出反应,降低集成成本
    • 发布管理原则:并非每一个 feature 开发完成后都要立即签入至 develop 分支。切记,develop 仅关注本期迭代所需交付的功能,若不确定某 feature 是否需要交付,则不要对其进行合并,延迟合并时机

    • 紧急回退原则:若需从 develop 中移除某个 feature 分支的代码变更,可使用以下方案

      1. 将 develop 重制到该 feature 合并之前,然后依次重新集成其他的 feature 进来,必要时,联系项目负责人进行辅助
      2. 新建 feature/remove-xxx 分支,在该分支中完成该 feature 分支对应的代码变更的反向操作,完成后发起新的 PR/MR

      紧急回退涉及修改 develop 分支的历史记录,务必在项目负责人、 feature 开发人员的共同协作下完成。

  • release

    • 预发布分支的的名称必须是软件版本号相关的递增的可回溯的
    • 预发布分支的名称通常为 release/x.y.zrelease/Vx.y.z(其中x,y,z均为数字,表示软件的语义化版本号)或 release/x.y.z-渠道版
    • 预发布分支是软件交付(上线)相关的,每个预发布分支对应了某一个软件版本的交付(上线)过程,当软件顺利交付后,预发布分支的使命便已完成
    • 当 develop 分支凑齐了本期迭代所需交付的全部 feature 后,便可以从 develop 分支签出 release 分支
    • 预发布分支中仅进行发布前的修改测试工作,例如:参数调节、配置修改、源码混淆、数据脱敏等修改操作;集成测试、端到端测试、验收测试等
    • 预发布分支中的变更记录是非必须的,或许你已经在 develop 分支做足了发布前的全部准备,此时只需直接向 master 分支发起 PR/MR 即可。但预发布分支的存在仍很有意义,原因如下:
      1. 有利于培养 gif-flow 的使用习惯
      2. 提供了潜在的修复机会
      3. 隔绝了预发测试阶段的临时到配置性变更对基线分支即 develop 造成污染,避免将仅适用于预发阶段的临时变更同步给其他分支。
    • 验收通过后,将 release 分支中的变更同时签入到 master 分支和 develop 分支中,同时删除此 release 分支,并在签入操作的提交记录上添加符合语义化版本号的 tag 名称(如:v1.1.0、v2.0.0),便于今后的溯源与问题修复
  • master

    • 此分支的代码是稳定的、不易变动的,可以将 master 分支视为已发布软件代码的归档记录
    • master 分支仅纳入新版本发布(release)、问题修复的(hotfix)的变更记录
  • hotfix

    • hotfix 由 master 分支签出
    • hotfix 分支仅用来进行紧急修复工作,新分支的名称通常为 hotfix/xxx
    • 当 bug 修复完成后,将 hotfix/xxx分支同时签入到 master 分支与 develop 分支中,同时删除此 hotfix 分支,并在签入操作的提交记录上添加符合语义化版本号的 tag 名称(如:v1.0.1、v2.0.3),便于今后的溯源与问题修复

小结

git-flow 分支模型并不复杂,若仍对其感到困惑,可以通过文末的相关链接来进一步了解。此外,本节开始的git-flow 大图也非常有用,它足以表达出本文的大部分内容,熟悉它将有利于你对 git-flow 的理解,请将它存储到你的硬盘中,方便后续查看。

软件工程中没有银弹,git-flow 也并非万能。更多时候,还需要我们根据具体项目,因地制宜的作出一些变通。但至少 gif-flow 是一个有意义的开端,不是吗?

如仍对 gif-flow 的使用感到疑问,不妨在下方提出问题或意见。

相关链接

以下推荐几篇 gif-flow 相关的文章,其中 Vincent Driessen 是 git-flow 分支模型的提出者,首先推荐他的文章。

扩展阅读

官方文档

  • GitHubFlow:Github 对 git-flow 的改进,开创性的提出 FORK/PR 的方式,进一步发挥 git 的分布式优势,于 2011 年发布
  • GitLabFlow:Gitlab 对 git-flow 和 github-flow 的改进,于 2014 年发布
  • Trunk Based Development:SVN 时代流传下来的分支模型

网络博文