更适合年轻人体质的 git 工作流

关于如何使用 git,相信大家都见过下面这张图:

很多人都学习过这张图上的流程并应用在实际工作中,但是慢慢就发现,用起来好像有点不对劲:令人困惑的合并冲突,每次发版前都需要找哪些 commit 需要发布等等。然后突然发现,诶这套流程好像用起来也不太爽,不知道有没有更好的流程可以用。本篇文章就来聊一聊这个问题。

现有 git flow 存在的问题

首先我们来分析一下上面这个流程中都存在哪些问题:

feature 分支要从 dev 分支创建,怎么保证代码是干净的?

举个例子,你要开发一个新功能,从 dev 切出一个新分支后之后发现怎么都跑不起来,群里问了一圈发现有人提交到 dev 的代码有问题,于是你就等到他重新提交了一个 commit 之后,你拉了下代码,这才开始正常开发。

发版时需要从 dev 分支创建 release 分支,怎么保证代码都是干净的?

再举个例子,本轮迭代共提交了 20 个 commit,其中 16 个 commit 需要发布,剩下 4 个 commit 因为还没测试完、bug 没改完不能发。这时候你能准确的把要发布的 commit 检出来么?

如果可以的话,咱们更进一步,本轮迭代由五位同事提交了 40 个 commit,在发版的时候其中两个请假了,这时候你能准确的知道哪些 commit 是要发布的,并准确将其检出来么?

如果还可以的话,那就更更进一步,你检出来之后,发布到 uat 环境,发现代码跑不起来了,结果发现,有个同事偷懒了,某个 commit 因为功能没开发完所以没有检进来,但是恰好这个 commit 里又包含了一些非常关键的代码,没有就跑不起来,这时候你会怎么做?

需要保持 dev 分支和 master 分支的同步,不同步的话可能导致合并冲突

回忆一下,你之前有没有处理过这种合并冲突:冲突的两方代码是完全一样的,但就是冲突了。

这种就是使用了 rebase(非 fast-forward)或者 cherry-pick 后导致的,因为这两种方法会产生代码完全一样,但是 id 不同的新 commit。就导致了 git 产生了混乱。

一个常见场景就是 hotfix 分支的 commit,合并到 master 之后又 cherry-pick 到了 dev 分支。这样下次再从 dev 往 master 合的时候就会出现这种问题。


不知道你什么感受,反正我是已经开始汗流浃背了,那么有没有更简单、更高效、心智负担更低的 git 工作流能解决这些问题呢?当然是有的。

正式介绍一下新的 git flow

首先我们还是以流程图的形式展示一下新的 flow:

和原本 git flow 的区别在于:

  • feature 分支不再从 dev 创建,而是从最稳定的 master 分支创建
  • dev 分支的代码不再向 release 分支合并,由 feature 直接发起到 release 的合并。
  • 当 release 分支测试完成要发版的时候,直接 fast-forward 到 master
  • 定期删除 dev 和 release,然后从 master 创建新的(例如每轮迭代结束之后)

那么这套工作流能解决刚才提到的问题么?答案是肯定的,老的工作流中存在的问题主要就是:

dev 分支过于重要

dev 需要接受来自多个 feat 以及 hotfix、master 的合并,并合并到 release 分支,这就会导致 dev 分支出现冲突的概率是成倍增加的。开发人员越多,其中存在的脏代码就越多,分支就越不稳定,冲突的情况就越多。

而这套新流程中 dev 的职责被弱化了,变得更加纯粹,即只对接测试环境的发布,其他的工作一概不管。也就是说 dev 本身就是合并路径的终点,从而消除了合并 commit 的回环,干掉了很多可能会产生迷惑冲突的场景。

从普通开发人员的视角看一下

现在我们从头开始,以普通开发的身份来走一遍这套流程,看会有什么效果:

  • 昨天版本发布了,master 代码上有了新的 commit,于是你执行了 git fetch会把远程的代码都同步到本地,比如远程的 master 分支同步到本地的 origin/master
  • 早上开会的时候给你安排了功能 a 和功能 b,你决定先做 a,于是你执行了 git checkout -b feat/a origin/master从刚才拉下来的 origin/master 分支创建了一个新分支
  • 你开始开发,随着开发进度的增加,中间可能执行了多次 git addgit commit
  • 几个小时后终于把功能做好了,自测也没问题,你决定发到测试环境让 QA 同事看一下,于是你执行了 git push 并且在远程仓库里提交了 feat/a 到 dev 分支的 pr,合并完成后流水线自动把代码发布到了测试环境。
  • 通知了 QA 之后,你决定开始开发 b 功能,于是你执行了 git checkout -b feat/b origin/master,然后开始开发。
  • 突然 QA 通知你功能 a 有 bug 需要修复,于是你执行了 git stash 把当前手头的工作暂存了起来,然后 git checkout feat/a 开始解决 bug。
  • 解决完了之后,你重新 git commitgit push 到了 dev 分支,QA 开始继续测试,你也切回了 feat/b 分支并 git stash pop 开始继续开发。
  • 过了一会,QA 通知你功能 a 测试没问题了,于是你在远程仓库里找到 feat/a 分支,并直接发起了一个到 release 分支的 pr。此时 release 分支触发了流水线,将功能 a 的代码更新到了预发环境。
  • 搞完之后,你切回 feat/b 分支继续开始功能 b 的开发...

故事到这里就结束了,你可能会好奇:版本发布的时候呢?不需要执行什么操作?

是的不需要。这套流程中发布生产环境极其简单。因为功能测试完成后会直接推到 release 分支。也就是说,只要和 release 分支绑定的环境(例如 uat)测试没问题,那么发布的时候只需要把 release 合并到 master 就行了。不会出现之前那种要在发版前检查很久要发布哪些 commit 的情况。

一些疑问解答

在实践过程中也有很多同事对这套流程产生了或多或少的疑问,这里就记录一下,希望对大家有帮助:

1、代码提交到 release 分支后出现 bug 怎么办?

切换到对应的分支(例如 feat/c),提交新的 commit 之后从 feat/c 合并到 dev,dev 测试没问题后从 feat/c 合并到 release 分支。

2、feat 分支合并到 dev 分支的时候代码冲突了怎么办?

首先,代码冲突很正常,没有任何一个工作流能完全避免代码冲突。我们应该尽力避免因工作流本身的问题产生的"令人困惑"的代码冲突。

比较正规的做法是:从最新的 dev 创建一个新分支,例如 dev-feat/a,然后把你的 feat/a 本地合并到 dev-feat/a 并解决冲突,然后 git push dev-feat/a 并在远程仓库发起 dev-feat/a 到 dev 的 pr。

比较随性的做法是:本地切到 dev 分支,git pull --rebase 拉取最新代码,然后直接 git rebase feat/a 解决冲突后直接 git push 到远程仓库的 dev 分支。

有些人可能会有疑问:"直接 push 到这种环境分支没问题么,之前我们这种分支都是写保护的,只能接受 pr"。

确实,老的工作流对环境分支的保护都是比较严格的,但是这一套工作流没有这些限制,因为最遭的情况也就是你把 dev 分支搞崩了。那直接把远程 dev 分支删掉再从 master 或者 release 分支拉一个就完事了嘛,反正大家的功能都在各自的 feat 分支上。再极端一点,只要你不搞坏其他人的代码,你就算直接 git push --force 强制推送到 dev 分支都没问题。

3、同事 A 和 B 的新功能要基于同事 C 的新代码,这时候怎么办?

假设同事 C 开发的功能在 feat/c,那么同事 A 和 B 的分支就应该从 feat/c 创建并继续开发。而不是等同事 C 合并到 dev 之后再从 dev 创建。

4、既然是 feat 直接合并到指定分支,那么为什么最后一步不是 feat 分支合并到 master 分支呢?

因为这套流程里,最重要的就是保证 master 分支的稳定性。所以 master 分支上的代码必须是经过严格验证的。

并且如果 feat 直接合到 master 的话还会导致一些其他的问题:

  • 有一个同事比较粗心,在提交 pr 的时候本来该合到 dev 分支,结果一不小心点到了 master,审核的人有不注意直接点了同意,这时候 master 就被污染了。
  • 合并到 dev 时如果出现合并冲突的话,那么合并到 release 分支大概率也会再出现一遍,你总不会想合到 master 的时候去解决第三遍吧,而且也无法保证冲突的解决一定是不会出问题的。

所以说,最稳妥,最省心的做法就是直接把 release 分支的代码合并到 master。

5、hotfix master 怎么办?

git flow 里 hotfix 分支中的 commit 一方面要合并到 master,另一方面要同步到 dev。但是由于后续 dev 也要再次更新到 master,这个 hotfix 的 commit 就可能会导致困惑冲突。

但是这套新流程里就不会出现冲突,因为 dev 分支自己就已经是终点了,不会合并到其他分支。所以 hotfix 里的提交无论怎么合并到 dev,不管是 merge、rebase 还是 cherry-pick,都是可以的。甚至不用管也没关系,因为只要是新 feat 合并到 dev,这个 hotfix commit 就被自动携带过来了。

总结

其实这一套工作流其实是 gitlab flow + git flow 的一个调优,使其在保证效率的同时更贴近 git 新手的心理认知。总结一下就是 dev 分支并不会"晋升"到 release 分支。而是由 feat 分支发起到 release 分支的合并,同时 master 只接受来自 release 的合并,由此减少了很多需要遵守的规则和发生冲突的情况。

参考

相关推荐
算你狠 - ZGX2 小时前
Git使用
git
Lojarro8 小时前
【后端】版本控制
git·subversion
MengYiKeNan13 小时前
Git配置与使用
git
shall_zhao13 小时前
修改仓库中子模块并推送到远程仓库的指定分支
git
鱼钓猫的小鱼干16 小时前
Git 安装
git
子洋16 小时前
迁移 Gitlab 到 Forgejo
前端·git·后端
爱串门的小马驹18 小时前
git,ssh免密公钥配置,gitee为例,GitHub,gitlab同理
运维·git·ssh
小小小妮子~18 小时前
掌握Git分布式版本控制工具:从基础到实践
分布式·git
前端与小赵1 天前
什么是Git,有什么特点
git