「我们是不是该上 gitflow?我看很多团队都用这个。」
「我们几个人?」
「五个。」
「那不用。」
这是上个月我跟一个刚转正的同事的真实对话。我回答得比较干脆,但当时没解释清楚,后来他又来问了一次,我干脆把想法整理下来。
gitflow 到底为谁设计的
gitflow 有五类分支:master、develop、feature、release、hotfix。它诞生在 2010 年,那时候主流的发布模式是「每季度发一个大版本,客户装在自己机器上,同时要维护 1.x 和 2.x 两条线」。
在那个语境下,长期存在的 develop 分支和 release 分支是必要的——你需要一个地方给下个版本攒功能,同时还要能给已发布版本打补丁。
但我们的模式是:一天发布 2 到 5 次,只有一个线上版本,用户永远用最新的。这种情况下 develop 分支的作用是什么?它和 master 之间通常只差几个小时的提交。你维护了一条分支、一套合并规则、一堆同步操作,换来的收益是零。
我见过一个八人团队严格执行 gitflow,结果每周有半天时间花在处理 develop 和 master 的分歧上。这就是典型的用工具的仪式感替代工程判断。
我们现在的做法
非常简单,四条规则:
- 只有一条长期分支 main,它永远可发布。
- 所有改动走短分支 + PR,分支命名
类型/简述,比如fix/coupon-rounding。 - 分支活不过三天。 超过三天说明任务太大,应该拆。
- 发布靠 tag,格式
v2026.08.25.1,回滚就是部署上一个 tag。
第三条是我最想强调的。分支存在的时间越长,合并冲突的概率和痛苦程度就越是指数上升。我统计过我们仓库的数据:活了 1 天的分支,平均冲突文件 0.3 个;活了 5 天以上的,平均 4.7 个,而且经常是那种「两边都重构了同一段代码」的恶性冲突。
要让分支活得短,前提是任务要拆得小。所以这条规则表面上管的是 git,实际上管的是需求拆分能力。这是我觉得它最有价值的地方。
rebase 还是 merge
这个问题在网上能吵三百楼,我的答案是分场景:
- 把 main 的最新代码同步到自己的功能分支:用 rebase。 这样历史是线性的,不会出现一堆「Merge branch main into feature-x」的噪音提交。
- 把功能分支合进 main:用 squash merge。 一个功能在 main 上就是一个提交,回滚方便,
git log读起来是一部业务演进史而不是流水账。 - 任何已经推送到共享分支的提交,绝不 rebase。 这是铁律。
squash 会丢失分支内的细粒度历史,有人不喜欢。我的看法是:那些「修复拼写错误」「再改一下」「真的改好了」的提交,保留下来对未来的人没有任何价值。要看细节可以去 PR 里看,PR 页面本身就是最好的历史归档。
提交信息
我们用了个极简版的约定:类型: 描述,类型只有五个——feat、fix、refactor、docs、chore。没有 scope,没有 body 强制要求,因为强制了大家就会写 feat(core): 更新 这种废话来应付。
唯一的硬要求是:fix 类型必须在描述里写清楚修的是什么现象。「fix: 修复 bug」这种提交,三个月后没有任何人能看懂。我们 review 的时候会打回。
hotfix 怎么办
不需要专门的 hotfix 分支。从 main 拉一个短分支,改完,PR,合并,打 tag,发布。全程可能十五分钟。
能这么快的前提是 main 永远可发布,以及有一套跑得够快的 CI(我们的从提交到可部署大约 6 分钟)。如果你的 CI 要跑四十分钟,那所有工作流讨论都是空谈,先去优化 CI。
几个我们踩过的小坑
- 早期没保护 main 分支,有人直接 push 了未经测试的代码。加上分支保护后就没这问题了,这大概是投入产出比最高的一次配置修改。
.gitignore没管好,有人提交了本地配置文件,导致其他人拉下来之后连不上数据库。现在配置文件全部走环境变量,仓库里只有.env.example。- 有过一次误提交了密钥。删提交是不够的,历史里还在,必须去改密钥本身。这件事之后我们上了提交前的密钥扫描钩子。
最后说句可能有点扫兴的话:工作流这东西,选一个够用的,然后别再折腾了。我见过团队花两个月讨论分支策略,最后产出是一份没人看的文档。能让代码顺畅地从想法走到线上,就是好工作流。