git flow 管理实践

本文不含 Git Commands 教程,如果需要,可以刷一遍 githug,可以在最快最短的时间内帮你熟悉 git commands 和很多的 case,如果题目没变,可以参考我 15 年写的通关手册(反复谷歌后卡关再看)。

在实践中,我们发现光会 git 指令还不够,还会遇到很多复杂场景的问题,如果没有一个很好的管理策略,就会陷入一团乱麻中。

如果你还不会使用包括 git pull / git push / git rebase / git cherrypick / git checkout 等在内的常用指令,请先从 git 使用开始学习,绕回本文开头。
注:强烈建议每天进行至少一次拉取分支,并且使用 git pull --rebase 取代简单的 git pull 保持简洁。

前置阅读

在 Bitbucket 的文章中,我们可以大致了解一下各种 workflows 大概是怎么做的,他们的主要流程是什么。

从 git flow 说起

尽管 git flow 也不是一个银弹,也有很多的缺陷,但是依旧是一个在广泛使用的工作流,甚至还为其开发了 gitflow 这种命令行辅助工具。

我们来复习一下一个最简化版本的 git flow:

branch.png

这里我们用三种颜色来区分分支,总之,我们的日常是从 develop 分支切出 feat/* 分支进行开发,合并回 develop 后合并回 master 分支上线。这里的 feature A、B、C 表示三个不同的 feature,从图中我们可以看出,三条线是线性的,并且永远是单向合并的。也就是说,develop 永远是 feature 的子集,而 masterdevelop 的子集,如果不是,那么就代表你的 Git 分支管理出现了问题,可能会出现大量 conflict 的情况。

hotfixmaster 切出,并且合入 master,但是为了保证 developmaster 的一致性,我们需要在将同样的代码推入 develop

策略

线性的理论很美好,但是实际上却会遇到很多复杂的业务场景,在下文的 Case 中我们会具体介绍,现在,我们先来理论性的了解一些基本原则:

feature 是什么

feature 代表一个功能块,他可能不是本次的一整个业务,比如实现整个活动页,更有可能的,是某一个功能块,是你被分到的一个任务。它应该尽可能是原子的,是一个最小单元。

从概念上来讲,已经是最小单元的 feature 应该只有一个 commit(因为 commit 也是原子的最小单元),但是实际开发中,我们虽然被分到的是一个任务,比如开发一个「用户注册」的功能,你可能会顺手写一些相对应的 utils,而这些 utils 在开发完成后应该单独的有一个 commit 去描述。(理由在下文的 Case 中会有介绍)

有实意的 commit

在开发过程中,我看到了大量的名叫 fix 或者 bug fix 的 MR,甚至是 review 中提出的意见新建了一个 commit 并提交,此时,commit 变成了离散的无语义的一个散点,对于团队合作杀伤力极强(理由在下文的 Case 中会有介绍)。

develop 与 master 分支

develop 和 master 对于整个 flow 来说是相对稳定的,为了避免出现问题,我们应该保证 master 永远是 develop 的子集,理论上,它们是可以保证 pull 期间没有任何 conflict 需要处理的,可以直接相互 merge 的分支。如果不是,代表 develop 和 master 已经是两个完全不同的分支了,只能想办法去解决这一类问题,带来的成本是极大的。

在这里,我们在 develop 和 master 设立两道防御,避免团队开发内部的冲突:

  1. develop 必须是一个线性(对开发)稳定版本,所有内容都需要从 develop 切出进行开发(除了 hotfix),最终都会归于 develop。
  2. master 也必须是一个线性(对部署)稳定版本,只有在 master 分支的内容可以上线。

按版本回滚

开发的生命中,难免会有这个版本出了问题,需要上下游一起去回滚延迟上线的情况,从概念上来说,就是「回滚到上一个版本」。如果不考虑依赖于发布系统提供的回滚功能,那么在每个发布之后应该打一个 tag

虽然……但是

上文我们说了一些利用分支管理流可以做到的事情以及一些约定,但是在实际实践中,我们可能会发现:诶这种 case 我要怎么满足才能达到「线性」治理。(就跟我们开发过程中强保证单向数据流一样)。

Case 1:可是我要提测

细心的你可能已经发现,整个 git flow 其实并没有考虑到「测试」、「提测」这个 case,那我们到底什么时候提测,要怎么提测呢。

一般的,我们会在 develop 往 master 合并的过程中引入一个新的类型:release/version(或者也可以叫 testing/version)。

因为上文我们说过,develop 是一个对开发稳定版本,因此它应该是一个不断滚动更新的内容,弱化「版本」的概念。从流程上,提测是在上线前,而在开发完成后,因此从语义的角度,同样的也应该在 develop -> master 过程中。

Case 2:我正在做某些不急着上线的东西

刚刚我们解释了怎么样提测,流向也就变成了 develop -> release -> master,但是如果我要做的东西不急着上线呢——按照流程,它就应该被冻结在 feature 分支等待合并。

Case 3:基于别人正在开发的内容开发

又发生了一件事情,某些不急着上线的东西被冻结在了 feature 中,那么如果我对此有依赖怎么办?

这里其实分为了两种 case:

  1. 业务依赖
  2. 通用方法依赖(比如 utils)

首先简单的 case 是业务依赖,既然是业务依赖,那就不会存在「我急着上线,而你不急着上线提测」的情况,那么只要基于对方(称为 A 同学)的 feature 分支进行开发就可以了,在最终的 Merge(Pull) Request 的过程中,Reviewer 只要先 Review A 同学开发的功能并合并,再 Review 你的功能就可以了。

通用方法的依赖就比较麻烦,不过我们可以回忆一下 feature 是什么一节,我们将 commit 内容变成了最小原子单元,也就是说,可以将对应的变更抽离出来,优先提交 Review 并合并到 develop,此时你就可以拉最新的 develop 并进行开发。

从上述内容中就可以看出为什么我们的 commit 必须要保证是有实意的,否则从别人的 MR 抽抽离内容会变成一个非常痛苦的过程。因此如果你还不会 git squash,请尽快学会。

Case 4:多人开发同一个业务

在团队规模增大之后,不可避免的会涉及一个迭代中的业务会有多人开发,这个时候处理 git flow 的复杂度就会上升,高速发展中的团队往往会死于多人开发的合作。

在这里我们回忆一下我们最初对 feature 分支的定义:「你的一个任务」,一种非常常见的错误用法是,在一个迭代中给自己切了一个属于自己的分支,然后疯狂在内部进行开发,而不是基于上述 flow 进行,很快就会陷入混乱之中。

因此我们在上文中已经定义了,feature 必须是原子的,他不是以人为单位,而是以功能为单位的,在 case 3 中的「基于别人正在开发的内容开发」,其实这个别人也可以是你自己。

这里以一个具体的前端栗子,比如这次迭代我要实现一个用户模块,产品提出的基本功能是「登录」、「注册」、「找回密码」、「修改密码」。

那么实际任务可能会变成:

  1. (业务)登录页面
  2. (业务)注册页面
  3. (业务)找回密码页面
  4. (业务)修改密码页面
  5. (通用)获取用户信息

在这里,如果是相互独立的,则直接从 develop 中切出挨个开发,当然,你会发现,仿佛「获取用户信息」,或者是隐藏的一个功能点「登录后跳转」是一个对于注册和登录通用的功能,那么可以先开发这一部分,然后以这个分支为根基进行进一步的开发。

当然,可能还会有更复杂的情况:中途又在这个业务中插了一个新需求,或者只是简单的在业务初期没有拆分好内容,出现了很多隐藏彩蛋,那么需要重新进行一次功能的划分,尤其是区分哪些是通用的部分,哪些是你负责的上层业务的部分。

Case 5:说好的要上线,结果又不上了

这种倒霉的情况可能出现在「提测前夕,由于本身优先级不高,导致测试资源不够用」和「都提测完了,突然因为某些其他问题,延期上限」;但是其他功能还要正常上线,这种时候,就是 revert 登场的时间了。

总结

在上文中我们会发现,表面上是 git 的分支与 git flow 的实践,实际上更是 commit 的管理与任务的拆分,需要负责的同学对于任务划分有所规划,能够将功能块拆解到最小单位,只有这样,才能以最小的成本去治理版本。

git 分支管理(git flow)不仅是一个空洞的概念和模板,而需要根据团队和业务的迭代属性进行不断调整,才能找到属于自己的平衡点,与其说是一门理论学科,更像是一门艺术。

在 2021 年的现在,几乎每个工程师都宣称自己会使用 git,但是用好 git,甚至是利用 git 去管理和指导工程,却是很多工程师的痛点,因此本文整理了一些常见的 case 和解决方案,希望可以成为一些指导方案,当然,也非常欢迎提出更多的 case 一起探讨和解决。

小私货:为了保证分支的干净和统一,我喜欢在所有流程中使用 rebase,并且由本地自己去实现 commit 的管理(rename/pick/squash),包括文章开头所说的 git pull --rebase

植入部分

如果您觉得文章不错,可以通过赞助支持我。

如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。

标签: git

已有 3 条评论

  1. shameless

    增加人气,内容很实在?

  2. HQ

    emmmm,说的有点子抽象,不便于理解,实际开发过程中,很少有人会按照这个执行

  3. 开发人员水平参差不齐,完全执行的话,推广有点难度

添加新评论