1. 问题背景:分支海洋与发布灾难
去年Q3,我们支付核心组经历过一次“至暗时刻”:由于长期使用git flow的develop分支,且每个需求都拉独立feature分支,导致develop分支积压了超过300个未合并的提交。在一次紧急发布中,合并develop到release分支时爆发了47个冲突文件,修复耗时6小时,最终引发线上支付超时事故。
核心矛盾:业务迭代速度(每天30+个MR)与代码集成周期(每周1次)严重不匹配。我们需要的不是更多的分支,而是更短的代码存活时间。于是,我们决定转向Trunk-based Development。
2. 环境与版本基线
- Git版本:2.39.1(强制开启
protocol.version=2) - GitLab:15.11.3-JH(使用其Premium版的Merge Request Approvals功能)
- CI Runner:基于Kubernetes的autoscaling runner,缓存挂载在S3
- 代码库规模:2个核心仓库(Java微服务),约120W行代码
3. 方案设计:两条主线的舞蹈
我们最终落地的策略是 “主干开发 + 短期特性分支 + 发布分支” 三轨制。
- 主线(main):唯一的集成主干,所有代码必须通过MR合入。永远保持可发布状态。
- 特性分支(feat/xxx):存活期不超过2天。一旦超过,则要求
git fetch后频繁git rebase origin/main。 - 发布分支(release/vX.Y.Z):仅从
main拉起,只接受bugfix。合并返回main使用--no-ff保留合并记录。
禁止:禁止直接push到main和release/*。禁止在main上直接git commit --amend。
4. 核心实现:用配置强制规矩
4.1 分支命名与保护规则(GitLab配置)
在Settings -> Repository -> Protected Branches中:
main:设置Allowed to merge为Maintainers,Allowed to push为No one。release/*:设置Allowed to merge为Maintainers+Release Manager角色。
4.2 Code Review 强制Approval规则
在GitLab的Settings -> Merge Request Approvals中,我们配置了多级审批:
- 规则A(必选):至少2名来自
Core-Reviewers组的Approval。 - 规则B(条件):若MR涉及
/src/main/java/com/payment/core/下的文件,必须额外获得安全组(Security-Team)的Approval。 - 规则C(非阻塞):若MR包含
test关键字,需由QA Lead确认。
# .gitlab/merge_request_templates/Default.md
## Review Checklist
- [ ] 代码符合 [Google Java Style](link)
- [ ] 包含单元测试(覆盖率增量 > 5%)
- [ ] 无 `System.out.println` 或调试断点
- [ ] 数据库迁移脚本已评审
4.3 CI/CD集成:Pipeline即门禁
下面这个.gitlab-ci.yml是我们Java服务的核心配置。关键在于rules控制流水线阶段,以及合并前必须通过所有测试与构建。
# .gitlab-ci.yml (基于 GitLab 15.11)
stages:
- verify
- test
- build
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
SONAR_TOKEN: $SONAR_TOKEN
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .m2/repository/
- target/
# 阶段1:静态检查与单元测试(仅MR事件触发)
verify:checkstyle:
stage: verify
image: maven:3.8.7-eclipse-temurin-17
script:
- mvn checkstyle:check -Dcheckstyle.config.location=google_checks.xml
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
allow_failure: false
# 阶段2:单元测试 + 覆盖率收集
test:unit:
stage: test
image: maven:3.8.7-eclipse-temurin-17
script:
- mvn test jacoco:report
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
cobertura: target/site/jacoco/jacoco.xml
coverage: '/Total.*?\(([0-9]{1,3})%/'
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "main"'
# 阶段3:构建镜像(仅main分支或tag)
build:image:
stage: build
image: docker:24.0.5
services:
- docker:24.0.5-dind
script:
- docker build -t registry.gitlab.com/xxx/payment-core:$CI_COMMIT_SHORT_SHA .
- docker push registry.gitlab.com/xxx/payment-core:$CI_COMMIT_SHORT_SHA
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
关键点:在GitLab的MR设置中,开启“Pipelines must succeed”和“All threads resolved”为合并必要条件。这意味着,CI的verify和test阶段一旦失败,Merge按钮直接置灰。
4.4 本地钩子:杜绝不规范的提交信息
为了确保CHANGELOG能自动生成,我们写了一个commit-msg钩子脚本,强制Conventional Commits规范:
#!/bin/sh
# .git/hooks/commit-msg
# 检查提交信息格式: type(scope): subject
# 例如: feat(payment): add refund callback
commit_msg=$(cat "$1")
regex="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?: .{1,50}$"
if ! echo "$commit_msg" | grep -qE "$regex"; then
echo "ERROR: Commit message must match Conventional Commits!" >&2
echo "Example: feat(api): add new endpoint" >&2
exit 1
fi
# 禁止直接push到main(通过pre-push钩子实现)
5. 踩坑与优化:血泪教训
坑1:Rebase 还是 Merge?
初期我们强制git pull --rebase,导致新手把main的历史搞乱。后来调整为:只允许在特性分支上执行rebase origin/main,合并回main时一律使用--squash,保持主干线性整洁。
坑2:CI并行任务过多触发GitLab RPS限制
当Runner不足时,Pipeline排队导致MR合并等待时间超过10分钟。优化方案:
- 在GitLab Runner配置中设置concurrent = 10。
- 在.gitlab-ci.yml中利用resource_group控制数据库迁移类Job只能串行执行,避免死锁。
坑3:Code Review流于形式
纯人工Review效率低下。我们引入了go vet(Go服务)和SpotBugs(Java服务)作为第一道自动检查,将人工Review精力聚焦于业务逻辑和设计模式,而非代码风格。
6. 效果数据:从数据看变化
推行该流程3个月后,我们统计了核心仓库的DORA指标:
- 部署频率:从每周1次提升至每天4次(生产环境)。
- 变更失败率:从12%下降至3.5%。
- MR合并等待时间(p95):从45分钟缩短至4.5分钟。
- 主干冲突数:从平均每月15次严重冲突降为0次(非正常操作导致的除外)。
7. 总结与思考
这套工作流并非银弹,它依赖于两个关键前提:自动化测试覆盖率足够高(我们核心模块约78%) 和 团队对主干开发纪律的认同。
对于小团队(<20人),建议砍掉Release分支,直接main打Tag发布即可;对于大型团队,务必引入“特性开关”而非依赖长期分支来管理未完成功能。
最后想说:Git分支策略的本质是沟通机制。当你的规则需要靠“人”去提醒而非工具强制时,就是失败的。把规矩写进CI配置和Git Hook里,让机器当警察,让开发专注于代码本身。