1. 先看看你的仓库乱成什么样:分支混乱的典型症状与代价
1.1 一个真实到让人血压升高的场景
我接过不少团队的代码库,每次做技术评估时,第一件事就是打开分支列表。说实话,很多仓库的状态可以用"触目惊心"四个字来形容。
main分支上直接躺着七八个"fix bug"提交,功能代码还是半成品就推上来了;feature分支下面挂着几十个名字叫test2、aaa、new_branch的分支,没人知道这些分支是干什么的,更没人敢删;develop分支和release分支之间的差异已经积累了几百个提交,一合并就是几十个冲突文件,每次发版都像在拆炸弹。
这不是个别现象。我见过太多团队,Git用得很好,但分支管理完全靠自觉。新功能从哪拉分支?看心情。分支叫什么名字?看当天浏览器开哪个标签页。合并之后分支删不删?想起来了再说。release分支和hotfix分支的区别是什么?大多数人说不清楚。
如果你所在的团队也是这种状态,那这篇文章就是为你准备的。接下来我会从"为什么乱"讲起,然后给出可落地的分支策略选型、命名规范、保护机制和事故处理预案,每一条都是我在真实项目里验证过的做法。
1.2 混乱分支仓库的三个典型特征
先说怎么判断一个仓库的分支管理是否已经失控。我一般看三个指标。
第一,分支数量长期只增不减。一个正常的团队,功能分支合并后原则上就该删除,保留的分支应该集中在长期分支(main、develop、release)和当前活跃的功能分支上。如果一个仓库有几百个分支,其中大部分已经三个月以上没有提交,说明分支生命周期完全没有管理。
第二,直接往主干写代码。成员因为"方便"、"小小的修改不用走流程"、"没人管"而直接提交到main,这是最常见的混乱源头。一旦主干变成了一个随意变化的分支,就没有人敢基于主干继续开发,于是大家各自拉分支、各自隔离,最后各自为战。
第三,不知道哪个分支对应哪个版本。发版的时候找不到对应的分支,热修复不知道该从哪个分支拉,线上出了 bug 要在十几个 commit 里翻找。这种情况在缺少 tag 管理和 release 分支规范的项目里非常普遍。
1.3 混乱的代价不只在合并那一刻
有人觉得分支乱一点无所谓,反正代码能跑就行。如果你是一个独自维护小项目的开发者,这种想法没有太大问题。但只要是两人以上的团队,分支混乱的代价会渗透到每天的工作里。
代价首先是合并冲突放大。分支长期不合并,与主干的差异越来越大,等快做完才合并,冲突文件数会呈几何级增长。一个原本二十分钟能解决的问题,可能变成两小时的"手工解冲突马拉松"。
代价其次是代码评审形同虚设。分支命名不清、职责不明,评审者根本不知道这个分支改了哪些模块、动了什么逻辑,所谓 code review 最终退化成"看一下 diff 有没有明显问题"。分支规范的意义之一,就是让评审者在打开分支的第一眼就能理解修改意图。
代价最后是发布流程不可回溯。线上出问题时,如果团队没有清晰的发布分支和 tag 约定,排查版本就要靠猜。谁是上线分支?哪个 tag 对应线上版本?热修复改完往哪合并?这些问题在混乱的仓库里没有固定答案,每次出事故都等于重新考古。
1.4 为什么多数规范最后都执行不下去
这里我想多说一句,我自己带项目时踩过这个坑:定了分支规范,贴在 Wiki 上,开会讲了一遍,大家点头说好,结果两周后一切照旧。
问题往往出在三个地方。一是规则太复杂,记不住。一份几百字的分支规范,规定了六个前缀、五种流程、四种合并方式,正常人是记不住的。二是缺少硬性约束,纯靠自觉。人可以靠自觉遵守规则,但总要有人提醒,而团队里负责提醒的那个人通常很快就不想当恶人了。三是规则和实际工具流程脱节,没有在 Git 平台层面对分支做保护,也没有通过 CI 做校验,违规的代价太低。
理解了这三点,后面设计规范时思路就会清晰很多:规则要简单到不用背,执行要自动到不用人盯,约束要强制到想绕过都费劲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 选对策略比会敲命令更重要:主流分支模型的取舍思路
2.1 分支策略没有标准答案,但要先回答四个问题
很多人一谈到分支管理,首先想到的就是 Git Flow,好像 Git Flow 就是分支管理的唯一正确答案。但 Git Flow 不是银弹,它的复杂度对很多团队来说是过载的。
与其纠结"别人用什么",不如先回答以下四个问题,这些答案会直接决定你该选哪种分支模型。
一是发布节奏。团队是每周发版、每月发版,还是代码合并到主干后随时上线?发布节奏越慢,越适合保留较长的发布分支和开发分支;发布节奏越快,越应该减少分支层级,让代码尽快跑到主干。
二是团队规模与协作模式。十个人的团队和一百人的团队,对分支粒度的需求完全不同。小团队沟通成本低,两层分支足够;大团队则需要清晰的职责边界,避免所有人挤在同一条长分支上互相踩脚。
三是产品类型。交付给客户的软件版本、需要同时维护多个历史版本的产品,和面向 Web 的 SaaS 服务,对 release 分支和 hotfix 分支的需求差异很大。前者对版本隔离的要求极高,后者更关心主干稳定和交付频率。
四是自动化水平。CI/CD 是否成熟,测试覆盖是否足够,直接影响"主干是否值得信任"。如果每次合并都足够轻量、测试足够快,就可以更大胆地采用主干开发;反之,就需要更保守的分支策略,用分支隔离来降低集成风险。
2.2 Git Flow:经典但未必适合你
Git Flow 是 2010 年前后流传最广的分支模型,核心思路是把分支划分为长期分支和短期分支。长期分支是 main(或 master,下同)和 develop;短期分支包括 feature、release、hotfix。
main 分支始终保持可发布状态,每次提交都对应一个生产版本;develop 是集成分支,所有功能开发完成合到这里,积累到一定程度从 develop 拉出 release 分支做发布准备;release 上的改动(如版本号调整、Bug 修复)最终合并回 main 和 develop;线上紧急 bug 用 hotfix 分支处理,修完合并回 main 和 develop。
这个模型的好处是结构清晰、职责分明,尤其适合需要维护多版本并行、按照固定周期发布的产品。但它有一个明显的代价:操作繁琐。一个功能的完整生命周期要经过 feature -> develop -> release -> main 多轮合并,每次发版都需要处理 release 分支的合并回流,对于追求持续交付的团队来说,这套流程太重了。我见过一些团队死搬 Git Flow,结果每天花在分支合并上的时间比写代码还多。
2.3 GitHub Flow 与 GitLab Flow:轻量模型更适合多数团队
GitHub Flow 则把模型压缩到极致:只有一个长期分支 main,功能开发都用独立分支,通过 Pull Request 评审后合并回 main,合并后立即部署。没有 develop、没有 release,只有一排 feature 分支和一个永远可发布的主干。
这个模型非常适合 Web 应用、持续部署场景。它最大的价值是让主干保持极短的存活周期,任何代码在合并进主干的那一刻就处于可部署状态,问题能被极早发现。
GitLab Flow 在 GitHub Flow 的基础上做了一些改良,引入"环境分支"的概念,比如 pre-production、production 分支,让部署节奏可以和开发节奏解耦。它的优点是对发布流程有更强的可观察性,缺点是环境分支多了一层同步成本,需要定期把 main 合并到下游分支。
GitHub Flow 和 GitLab Flow 适合什么样的团队?上线频率高、自动化测试完善、同事之间沟通成本低的小团队。如果你的团队规模不大、没有严格的版本交付要求,我强烈建议优先考虑这类轻量模型。
2.4 Trunk-Based Development:主干开发的上限
再往上走一步就是 Trunk-Based Development(主干开发)。所有开发者直接把代码提交到主干或非常短命的分支上,分支存活时间通常不超过一天,通过 feature flag 控制功能是否对外可见。
采用主干开发的团队,通常已经解决了两个前置问题:一是代码评审以极小的粒度高频进行,二是自动化测试覆盖足够广、执行足够快。这要求团队有很强的纪律性和工程文化。
大部分团队一开始并不适合主干开发。但了解它的存在很有价值,因为它能帮你建立这样一个判断标准:分支的存在本质上是在管理"集成风险",分支层数越多、存活时间越长,集成发生的时刻就越晚,出问题的代价就越大。无论选哪种模型,都应该朝着"让集成更早发生"的方向努力。
2.5 我的选型建议:按团队状态匹配模型
根据我的项目经验,不同阶段的团队适合不同的分支模型。
刚起步三到五人的小团队,直接采用 GitHub Flow 就够了。一条 main 加一批 feature 分支,Pull Request 把关,合并即部署。简单直接,不要给自己加戏。
十人以上、有固定发版节奏的产品团队,GitLab Flow 或改良版 Git Flow 更合适。保留 main 和 develop,通过 release 分支管理发版,hotfix 流程固定下来,应对线上问题有条不紊。
几十人以上的中大型团队,通常需要分层分支策略:同一条主干下按模块或团队维护短期的集成分支,但必须明确这些分支的存活时间,并定期把配置共识同步到团队里。关键不是模型本身有多完美,而是所有人都能说出"我当前的分支从哪来、合并到哪去"。
3. 一套能直接抄的规范流程:命名、生命周期与提交约定
3.1 分支命名规范:让人一眼看懂分支职责
分支命名是分支管理中最容易被低估的环节。名字起得好,团队成员看分支名就能判断这个分支的用途、关联需求和维护者;名字起得随意,等分支多起来就没有任何人能整理清楚。
我推荐一套简单、易记、可扩展的命名规则:<type>/<scope>-<desc> 或 <type>/<desc>,type 表示分支类型,desc 表示简要描述,单词用短横线连接。
常用的 type 前缀包括:
feature/:新功能开发,例如feature/login-pagebugfix/:普通 bug 修复,例如bugfix/fix-null-pointerhotfix/:线上紧急问题修复,例如hotfix/1.2.1-payment-failurerelease/:发布准备分支,例如release/1.3.0chore/:构建、配置、依赖等非业务改动,例如chore/update-webpackrefactor/:代码重构,例如refactor/rename-user-moduledocs/:文档修改,例如docs/update-readmetest/:测试代码改动,例如test/add-login-e2e
desc 部分建议直接关联需求编号,比如 feature/JIRA-123-login-page,这样从分支名就能跳到需求描述,省去反复猜测的时间。
这里有一个很多人问过我的问题:分支名里能不能带斜杠?当然能。斜杠只当作分隔符用,feature/user/login-page 完全合法。但是不建议嵌套太多层,否则分支名会过长,命令行操作起来很麻烦。
3.2 分支生命周期:从创建到清理的完整步骤
命名规范解决的是"这个分支是什么"的问题,生命周期管理解决的是"这个分支什么时候创建、什么时候消失"的问题。
一个标准的 feature 分支生命周期应该是这样的:
-
从最新的主干(假设
dev)拉取分支。切记先git checkout dev && git pull,确保基于最新的主干代码,再执行git checkout -b feature/login-page。从旧代码上拉出来的分支,注定要在合并阶段面对一堆本可以避免的冲突。 -
在开发过程中,根据团队约定定期把主干合并进 feature 分支,或者执行
git rebase。这一点不能偷懒。分支活得越久,与主干的差异越大,最终集成时的冲突就会越可怕。 -
功能完成后,发起 Pull Request / Merge Request,走代码评审流程。评审通过后按照预定方式合并进目标分支。
-
合并完成后立即删除远端和本地的 feature 分支。本地仓库里残留一大堆已合并分支是一件非常影响手感的时,建议配一个清理命令,见 3.4 节。
hotfix 分支的生命周期类似,但有两个特殊点:一是它从 main(或当前生产分支)拉取,而不是从 develop 拉取,确保只包含线上紧急修复;二是修复完成后需要同时合并回 main 和 develop,避免下次发版时把修复丢掉。
release 分支的生命周期比较特殊:它从 develop 拉出,进入只修 bug、不加功能的状态,发布完成后合并回 main 和 develop、打 tag。一个常见的问题是,"release 分支上的修复如何回到 develop?"如果团队忘了合并回流,热修复会在下一次发版时莫名其妙消失。这必须写进 check-list。
3.3 提交信息约定:让历史可读可查
分支管理不只是分支本身,提交信息的质量标准同样重要。试想一下,你打开 git log,满屏都是"update"、"modify"、"commit",你很难回答"这个改动是干什么的"这个问题。
目前社区接受度最高的是 Conventional Commits 规范,简单说就是一个提交信息由 type(scope): subject 组成:
feat(sso): 增加登录页验证码fix(payment): 修复金额精度溢出问题docs(readme): 补充本地开发环境配置步骤refactor(api): 抽取统一异常处理中间件test(login): 补充登录失败场景测试用例
type 的类型一般包括 feat、fix、docs、style、refactor、perf、test、build、ci、chore,scope 表示影响范围,相对自由。如果团队使用 JIRA、禅道等需求管理工具,建议在提交信息中带上需求编号,例如 feat(login): 实现短信登录,关联 #123。
提交信息还有一个容易被忽视的作用:它决定了 changelog 的可读性。通过 git log --pretty=format:"%s" 拉出提交历史,如果每行都是规范的语义化信息,生成 changelog 几乎不需要额外加工;如果提交历史像垃圾桶,做发布计划时你就要逐条翻代码。
3.4 合并方式:merge、squash、rebase 到底怎么选
分支合并方式是最容易引起争论的话题,常见的选项有三种:merge commit(普通合并)、squash merge(压缩合并)、rebase merge(变基合并)。
merge commit 保留完整的提交历史和分支拓扑,适合需要保留功能开发过程全貌的情况,但会留下一条难看的"分叉-合并"网络,git log --graph 看起来像一团毛线。
squash merge 把一个分支上的所有提交压缩成一条提交合并到目标分支,历史非常干净。代价是丢失了开发过程的中间提交,将来如果需要精确定位某个中间改动,会很困难。
rebase merge 是把 feature 分支的提交逐个"搬"到目标分支的最新提交之后,再执行快进合并,历史是一条直线,同时保留了每个提交的独立性。代价是改写提交哈希,对已推送的公共分支执行 rebase 是危险的。
我的建议是:团队早期统一用 squash merge,让主干历史保持线性简洁,再配合规范的提交信息,基本上能覆盖大部分场景。如果团队对提交粒度有更高要求,再用 rebase 方式,但一定要约定"已经推送到远端的分支禁止 rebase"这个铁律。
删除本地和远端已合并分支的常用命令:
bash复制# 删除本地已经合并到当前分支的分支
git branch --merged | grep -v "\*" | xargs -n 1 git branch -d
# 批量删除远端已删除的本地跟踪分支
git fetch --prune
这两条命令值得每个开发者收藏。
4. 用硬约束代替口头约定:分支保护与CI校验的落地配置
4.1 人都不可靠,所以机制要可靠
我曾见过一份写得很详细的分支管理规范,从命名到合并,从 flow 到 tag,堪称教科书。但规范上线一个月后,团队还是有人绕过 Pull Request 直接把代码推到主干,理由五花八门:"这次改动很小""线上在等着""评审流程太麻烦"。
人都会找捷径,尤其是在压力大的时候。要想让规范真正生效,就必须把"人记规则、人执行规则"改成"平台强制、CI 拦截"。这就是分支保护的价值。
4.2 在 GitLab / GitHub 上配置分支保护
以 GitLab 为例,可以在项目的 Settings -> Repository -> Protected branches 中把 main 和 develop 设为保护分支。保护规则通常包括:
- 禁止直接推送(只有 Maintainer 角色可以);
- 允许合并请求(Merge Request);
- 至少需要 1 个 Approver 的批准;
- 合并前必须通过 CI 流水线;
- 讨论必须解决(Resolve threads)。
GitHub 上对应的配置在 Settings -> Branches -> Branch protection rules,核心配置项差不多。
这些硬约束的效果是立竿见影的:从保护分支启用那天起,任何代码改动都必须经过评审和 CI,再也没人能把半成品代码推到主干上。
但这里有一个常见误区:保护分支只管住了 main 和 develop,对 feature 分支完全开放。如果团队规模较大,建议对release/、hotfix/ 这类前缀也开启保护规则,防止有人在发布分支上随意改动。
4.3 用 commitlint 和 CI 强制提交规范
分支保护解决的是"谁能合并"的问题,提交信息规范可以交给自动化工具来约束。
commitlint 是一个检查提交信息是否符合 Conventional Commits 规则的工具,配合 husky 的 commit-msg 钩子,可以做到开发者本地提交时就完成校验。
先安装依赖:
bash复制npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
配置 commitlint 规则(commitlint.config.js):
javascript复制module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']],
'subject-min-length': [2, 'always', 5],
'subject-max-length': [2, 'always', 100],
},
};
husky 配置 prepare 脚本:
bash复制npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
除了本地钩子,务必在 CI 流水线里再加一道校验。为什么?因为总有人会绕过本地钩子(比如 --no-verify),CI 拦截是最后一道防线,确保不规范的提交永远进不了主干。
4.4 值得收藏的一组日常Git操作
规范流程需要工具配合。下面这组命令是我在日常开发中频繁使用的,按场景归类整理:
bash复制# 拉取最新主干并切换到新功能分支(一条命令搞定)
git checkout dev && git pull && git checkout -b feature/login-page
# 开发过程中同步主干
git checkout dev && git pull && git checkout feature/login-page && git merge dev
# 查看未合并到当前分支的提交
git log --oneline --not --remotes
# 查看所有分支的最近提交时间,排查僵尸分支
git for-each-ref --sort=-committerdate refs/heads --format='%(committerdate:short) %(refname:short) %(authorname)'
git for-each-ref 这条命令特别适合做分支巡检。把输出按时间排序后,一眼就能看出哪些分支已经三个月没有动静,可以进入待删除名单。
4.5 别忘了 tag:发布追溯的最后抓手
分支管理规范里,tag 管理往往被忽略。事实上,线上版本定位通常不靠分支,而靠 tag。
建议的 tag 规范是语义化版本号:v1.2.0、v1.2.1。发布流程的末端必须是:合并 release 分支到 main,然后立即打 tag:
bash复制git checkout main && git pull
git tag -a v1.2.0 -m "release v1.2.0"
git push origin v1.2.0
有了 tag,线上就定位到具体的提交,热修复、版本对比、回滚都变得极其清晰。这比在几百个分支里翻找"上次上线用的哪个分支"要可靠一百倍。
5. 事故现场复盘:误删、误合与冲突的定位和处置
5.1 误删分支如何找回:reflog 就是后悔药
无论规范做得多好,意外总会发生。最常见的意外之一就是误删分支。git branch -d 会有保护机制,拒绝删除未合并的分支,但如果你用了 -D 强制删除,或者手滑删了远端分支,恐慌是难免的。
首先记住:本地分支删除后,只要分支上有提交,就还有补救机会。git reflog 会记录 HEAD 的所有移动历史,包括被删除分支最后一次指向的提交。
举个例子:
bash复制# 手滑误删了 feature/login-page
git branch -D feature/login-page
# 查看 reflog,找到删除前分支指向的提交哈希
git reflog
# 假设看到 abc1234 是删除前的最新提交
git branch feature/login-page abc1234
分支就回来了。如果远端分支也被删了,但本地的 reflog 中仍然保留该分支的历史,可以重新推送恢复。前提是你本地有对应的提交记录,否则只能依赖其他人的本地缓存或平台侧回收站功能。
这里有一条非常重要的事后经验:大项目里误删分支之所以恐怖,往往不是分支本身丢了,而是分支名对应的需求上下文没了,你不知道这个分支是干什么的。所以尽量在分支名和提交信息里保留需求编号,这相当于给你留了"查找备份的线索"。
5.2 误合并如何回滚:reset 还是 revert,要分清场景
合并操作出错后的处置是另一个高频事故。注意,不要一上来就执行 git reset。
git reset 会删除提交历史(准确说是移动分支指针),只适用于本地尚未推送的分支。执行 git reset --hard HEAD~1 会丢弃最近一次合并,但同时也丢弃合并后到当前的所有提交,危险系数极高。
git revert 则是新增一个反向提交,安全地撤销某次提交的效果,适用于已推送到远端的公共分支。
比如错误地将漏洞百出的 feature/bad-code 合并进了 main,而 main 是公共分支,应该这样做:
bash复制# 找到错误的 merge commit 哈希
git log --oneline --graph
# 用 -m 1 指定保留主干一侧的历史
git revert -m 1 <merge-commit-hash>
git push origin main
核心区别是,revert 不会改变历史,不会导致远端与他人本地分支的历史不一致。很多新手误用 reset 处理公共分支后,整个团队的分支历史就乱了,最后不得不逐个 reset 到统一提交,代价惨重。
5.3 冲突解决的完整思路:别只会打开编辑器手动改
冲突是 Git 最劝退新手的环节,但理解了它的本质就不难。冲突的本质是"两个分支都修改了同一处内容,Git 无法自动判断应该保留谁的"。所以解决冲突不是在骂 Git,而是在做一次人的决策。
冲突类型最常见的有两种。一种是 <<<<<<<、=======、>>>>>>> 标记的内容冲突,这种只能人工编辑文件决定保留哪个版本;另一种是文件级冲突,比如一个分支删除了文件、另一个分支修改了文件,需要在 git status 的提示下选择 git add 或 git rm。
解决冲突的推荐步骤如下:
- 先明确当前处于什么状态。
git status会列出所有冲突文件,理解每个文件是内容冲突还是文件级冲突。 - 从最容易解决的文件开始,优先处理"两个分支都在此新增内容"的简单冲突,最后处理"双方交错的逻辑改动"。
- 修改后执行
git add <file>,确认没有遗漏的冲突标记。搜索全文<<<<<<<是一个不错的检查习惯。 - 全部 resolve 完成后
git commit,然后立即运行测试,验证合并后的代码是否仍然工作正常。
一个很多人忽略的点:在合入目标分支之前先创建合并预览。即先基于目标分支拉一个本地临时分支做集成测试,避免把问题带上公共分支。
5.4 环境类问题排查:别让环境问题浪费一天时间
分支管理还经常被环境问题打断。最典型的是刚装完 Git 时,在终端里敲 git 报"无法将 git 项识别为 cmdlet"或"不是内部或外部命令",多半是环境变量没配置好。Windows 下安装 Git for Windows 后,如果当时没选"Add to PATH",后续需要手动添加 Git 的 bin 目录到 PATH。
另一个高频问题是访问仓库时报证书错误,比如:
text复制error setting certificate file: d:/git/mingw64/etc/ssl/certs/ca-bundle.crt
这是 Git 使用的 CA 证书路径不对。可以先检查当前配置:
bash复制git config --global --list
如果发现 http.sslcainfo 指向了一个不存在的路径,把它修正为 Git 安装目录下实际的证书文件即可。生产环境访问内网 GitLab 等场景还会遇到自签证书不受信任的问题,此时可以针对特定域名配置 git config --global http.sslverify false,但请谨慎,这只建议在受控内网中使用,不要全局关闭 SSL 校验。
再比如代理相关的问题。公司网络环境下需要走代理访问外部代码托管平台,可以这样设置:
bash复制git config --global http.proxy http://proxy.example.com:8080
git config --global https.proxy http://proxy.example.com:8080
排查这类问题有个通用思路:先确认是网络问题还是 Git 配置问题,再逐步使用 git config --global --list、curl -v 等方式缩小范围,最后才动手改配置。不要一上来就怀疑 Git 本身出了问题。
5.5 从事故中沉淀 check-list
我每次处理完线上事故,都会要求团队把处置过程整理成一份 check-list,沉淀到仓库的 docs 目录。下次再发生同类事故,不要重新拍脑袋,直接照检查单执行。
分支管理相关的 check-list 至少应该包含这几条:
- 操作公共分支前,是否已确认最新的远端状态?
- merge 前是否确认目标分支正确?(
git branch --show-current) - 提交信息是否遵循规范并关联了需求编号?
- 合并后是否清除了 source 分支?
- 发布后是否打了语义化 tag 并推送到远端?
- 涉及 hotfix 时,是否记得合并回 develop?
模板化、清单化,是让团队从"靠经验救火"走向"按流程做事"的关键一步。
6. 让规范从墙上走进日常:团队推行中的实战体会
6.1 关键的第一步不是写文档,而是简化规则
很多团队推行分支规范失败,败在第一步就走错了——一上来就写了三千字文档。
我个人的经验是:规则越少越可能被执行。第一版分支规范不要追求面面俱到,先保住最核心的三条:
- 长期分支只有
main和develop,禁止直接往它们上面推代码; - 新功能必须从
develop拉分支,分支名统一feature/需求号-描述; - 所有 feature 分支合并前必须走 Merge Request 并通过 CI。
这三条覆盖了 80% 的混乱源头,而且每一条都不需要记太多,随口能说出来。等团队适应了,再逐步补充 release 流程、hotfix 流程、提交信息规范,一步步加码。
6.2 用工具把规则焊死在流程里
我在 4.2 节详细讲了分支保护和 CI 的配置,这里强调一下它真正的价值:它把"规范"从文档变成了"流程"。开发者想绕都绕不过去,想违规也违规不了,因为平台不允许。
强制了不少人一周后,"适应"就变成了"习惯"。Branch protection + CI 是推行分支规范时投入产出比最高的两个动作,务必优先做。
6.3 定期巡检,把僵尸分支纳入日常管理
分支规范执行一段时间后,最常出现的问题是僵尸分支积累。功能分支合并后忘删,或者开发到一半需求被砍掉、分支就一直挂在那里。
建议每个迭代或每两周做一次分支巡检。不需要很复杂,跑一条 git for-each-ref --sort=-committerdate 命令,把超过 30 天没有活跃提交的分支列出来。对已经合并的分支直接删;对长期未合并但还有价值的,写一段备注说明该分支在等什么,避免它变成无人认领的孤儿。
这里有一个小心得:删除远端长期不用的分支时,先发一条通知,给团队成员 48 小时认领窗口。曾经有同事在删除时发现某个分支其实承载了他两个月的需求,已经快要上线了,这个"快要上线"的需求差一点就被葬送在巡检流程里。
6.4 分支规范要随着团队成长定期进化
分支规范不是刻在石碑上的法律。团队规模、发布节奏、产品形态变化后,原来的方案可能就不再适用。
比如一个三人小团队用 GitHub Flow 非常顺手,但扩充到二十人后,会发现没有集成分支,所有人都堵在 main 的 Pull Request 排队长龙里。这时候引入 develop 分支、增加 release 流程,就是自然演化。反过来,一个传统团队迁移到持续交付模式后,砍掉冗长的 Git Flow 流程,缩短分支的存活时间,也是合理选择。
我的习惯是每季度回顾一次分支模型:当前的分支数量、平均合并时间、冲突频率、发布失败率,用数据来看现在的模型是否还合适。如果统计结果变差,就主动调整,而不是死守某个流程不放。
6.5 聊聊推行中一定会遇到的阻力
分支规范的推行从来不只是技术问题,更是协作习惯的变革。最常遇到三种阻力,提前有心理准备,处理起来会顺很多。
第一种是"我不用规范也能工作"型。这类人往往是技术能力不弱的老手,他们认为分支管理属于个人风格,不需要别人管。我的应对方法是,不争论对错,只展示数据。把因为分支混乱导致的合并冲突耗时、发布事故统计摆出来,让他们自己判断。
第二种是"规则太多记不住"型。这类反应说明规范颗粒度太细,已经超过团队可承受的复杂度。这时候要做的是砍掉规则,保留核心动作,把可选项留在文档里供团队自行了解。
第三种是"反正没人检查"型。这类反应最危险,它说明规范本身已经失效,或者说执行层面的关键人没有持续推动。推行新规最忌讳"第一周严格、第二周放松、第三周无视"。最好是设置一个周期性的检查点,保障新规被持续落实。
我自己踩过的坑是:优先追求完美方案,忽略了团队现状。越完美的方案往往越复杂,执行成本越高,最终夭折。与其如此,不如先用一个不完美但能坚持下来的方案,跑顺了再逐步进化,效果远好于一步到位。
