4. 版本控制与分支策略
版本控制本身不复杂,真正让人头疼的是分支策略。你看,没有分支策略的仓库,就像一个没有交通规则的城市——到处是合并冲突、构建失败、误部署。我在不同团队待过好几次,最惨的一次是十几个人的项目用了七八种分支命名习惯,光找最终发布的版本就花了半天。所以说,分支策略本质上是一套协作契约,每个人遵守它,流水线才能稳定运转。
4.1 为什么流水线离不开分支策略
说白了,CI/CD 流水线就是围绕分支设计的。每次 push 到特定分支,触发不同阶段的自动化任务。举个例子:
- 推送特性分支 → 只运行单元测试 + 代码扫描,快速反馈。
- 推送到 develop → 触发完整的集成测试 + 构建制品。
- 推送到 main → 走发布流水线,包括冒烟测试、签名、部署到预发环境。
我曾经见过一个团队把几十个功能挤在一条分支上,每次推代码都要跑两个小时的完整流水线,开发等结果等到崩溃。后来我们切了短特性分支加 trunk-based 策略,反馈时间降到了 10 分钟以内。
4.2 主流分支模型对比
| 模型 | 核心分支 | 适合场景 | 流水线复杂度 |
|---|---|---|---|
| Git Flow | main, develop, feature, release, hotfix | 版本发布周期固定(按月/季度) | 高(需要多条流水线规则) |
| GitHub Flow | main, feature(通过 PR 合入) | 持续部署,快速迭代 | 低(一条 main 分支 + 特性分支) |
| Trunk-Based | main(trunk),极短期特性分支 | 每日多次合并,对测试信心要求高 | 低(主要保护 trunk) |
4.3 分支命名规范——细节决定成败
分支名字看着是小事,但会影响自动化规则的匹配。我建议用前缀 + 短标识的方式,且全部小写,用斜杠分割。比如:
# 特性分支
feature/jira-1234-user-login
# 缺陷修复
bugfix/jira-5678-null-pointer
# 热修复
hotfix/v1.2.1-security-fix
# 发布分支
release/v2.0.0
# 实验性分支
experiment/perf-tune-query
为什么要统一?因为你可以用 CI 的正则规则自动识别:
# 示例 .gitlab-ci.yml 中的规则
only:
- /^feature\/.*$/
- /^bugfix\/.*$/
except:
- /^release\//
我有一个深刻的教训:之前团队有人用大写字母,有人用下划线,结果 CI 的 branch filter 写了一大堆 or 条件,维护起来痛苦得要命。后来我们推行了强制命名规范,并在 pre-commit hook 里校验,从此这类问题绝迹。
4.4 分支保护规则——谁也不能直接往主分支推
保护分支是流水线安全的基石。没有保护规则,任何人都能绕过 CI 直接合并,那流水线自动化就形同虚设。建议至少设置以下几条:
# 以 GitHub 保护规则为例
Branch name pattern: main
☑ Require pull request before merging
☑ Require status checks (至少包含 CI pipeline 成功)
☑ Require branches to be up-to-date
☑ Include administrators
☑ Require linear history(可选,但推荐)
4.5 合并策略的选择:merge vs squash vs rebase
| 策略 | 提交历史 | 适用场景 |
|---|---|---|
| merge commit | 保留所有分支历史,有 merge 节点 | 团队习惯看到完整分叉,评审时容易追踪 |
| squash merge | 将特性分支所有 commit 压缩成一个 | 特性分支包含大量“wip”、“fix typo”等琐碎提交 |
| rebase + fast-forward | 线性历史,没有 merge 节点 | 追求干净、可读的历史,适合 trunk-based |
我个人倾向在 trunk-based 模型里用 rebase + fast-forward,因为历史像线一样直,回溯问题非常方便。但要注意:rebase 会改写提交,不要在多人共享的分支上乱 rebase。
4.6 分支策略与流水线的联动配置
通过几个典型场景来感受一下具体怎么写。下面是一个 GitLab CI 片段,根据分支类型触发不同 job:
stages:
- lint
- test
- build
- deploy
variables:
DOCKER_TAG: $CI_COMMIT_BRANCH
lint-job:
stage: lint
script: npm run lint
only:
- /^feature\/.*$/
- /^bugfix\/.*$/
- develop
- main
- /^release\//*
test-job:
stage: test
script: npm run test:ci
only:
- develop
- main
- /^release\//*
- /^hotfix\//*
build-job:
stage: build
script: docker build -t myapp:$DOCKER_TAG .
only:
- develop
- main
- /^release\//*
- /^hotfix\//*
deploy-staging:
stage: deploy
script: deploy staging
only:
- develop
environment: staging
deploy-production:
stage: deploy
script: deploy production
only:
- main
when: manual
environment: production
看到没?特性分支只跑 lint,降低反馈成本;develop 跑完整的测试和构建,部署到 staging;main 发布生产,且需要手动确认。这样每一个分支都获得恰到好处的自动化验证。
4.7 常见坑与避坑指南
- 分支数量失控:长期不清理的死分支仍然触发 CI,浪费资源。我建议设置策略:合并后自动删除远程分支,定期扫描超过 30 天的旧分支并提醒。
- 保护规则遗漏管理员:管理员直接 push 导致 CI 被跳过。请设置为“包括管理员”也强制检查。
- 大特性分支长时间不合并:冲突越来越多,CI 通过率越来越低。我建议把特性分支拆成多个小批次,每 1-2 天往主分支方向合并一次。
- 热修复分支直接合到 main 却忘掉 develop:会出现下次发布时修复内容丢失。记得在 hotfix 合并到 main 后,立即 cherry-pick 到 develop。