1. 问题背景:多分支协作的混乱与代价
在2023年初,我们团队采用经典的Git Flow模型:develop、feature、release、hotfix四类分支。但随着团队从8人扩张到20人,问题爆发:
- 分支膨胀:同时活跃的feature分支超过15个,合并冲突每周平均出现12次,解决冲突平均耗时4.5小时
- 发布延迟:release分支合并后,由于代码差异过大(平均滞后develop分支3天),集成测试失败率40%
- Code Review流于形式:PR堆积超过48小时未审核,开发者为赶进度“强行合并”
数据对比(改前一个月):线上事故8起,其中5起是合并导致的回归。
目标:将发布周期压缩到1天以内,冲突率降低70%,Review时效控制在4小时内。
2. 环境与版本
| 组件 | 版本 | 用途 |
|---|---|---|
| Git | 2.39.2 | 核心版本控制 |
| Gerrit | 3.8.0 | Code Review与门禁 |
| Jenkins | 2.440.1 | CI/CD流水线 |
| Trivy | 0.49.0 | 镜像安全扫描 |
| Docker | 24.0.6 | 构建与部署 |
| Kubernetes | 1.28.3 | 生产部署 |
分支模型选择:经过调研,放弃Git Flow,改用Trunk-based Development(主干开发)配合短生命周期特性分支。原因:Git Flow的长周期分支(develop、release)引入大量延迟,而Trunk-based + 特性开关能实现持续集成。
3. 方案设计:三阶段工作流
3.1 分支策略(Trunk-based with Feature Flags)
- master:唯一长期分支,始终可部署。每次提交必须通过CI/CD全链路测试
- feature/xxx:短生命周期分支(存活=80%)
- 分配给2名Reviewer,必须在4小时内完成审查
- Reviewer通过后,需要CI/CD流水线再次验证(包括集成测试、安全扫描)
- 最后通过“提交+1”按钮合并到master
关键配置:Gerrit的project.config中设置:
[access "refs/heads/master"]
push = +2 group CI_Service
push = +1 group Developers
label-Code-Review = -2..+2 group Developers
label-Verified = -1..+1 group CI_Service
submit = merge if no change
3.3 CI/CD集成(Jenkins Pipeline + 自动门禁)
每个push到Gerrit的Change,触发Jenkins的Multibranch Pipeline。流水线包含:
- Stage 1: 代码质量 → lint, security scan (Trivy)
- Stage 2: 单元测试 → pnpm test --coverage,覆盖率0则失败)
- Stage 6: 自动合并 → 当Reviewer给出
+2且Jenkins通过,Gerrit自动merge
4. 核心实现(含可运行代码)
4.1 Jenkinsfile(声明式流水线)
pipeline {
agent any
triggers {
gerritTrigger(
gerritProjects: [[
compareType: 'PLAIN',
pattern: 'my-project'
]],
triggerOnPatchsetUploaded: true,
skipVote: false
)
}
environment {
REGISTRY = 'registry.example.com'
IMAGE_NAME = "${REGISTRY}/my-app:${env.GERRIT_PATCHSET_REVISION}"
}
stages {
stage('Lint & Security') {
parallel {
stage('ESLint') { steps { sh 'pnpm lint' } }
stage('Trivy') { steps { sh 'trivy fs --severity CRITICAL --exit-code 1 .' } }
}
}
stage('Unit Tests') {
steps {
sh 'pnpm test --coverage'
junit 'test-results/*.xml'
}
}
stage('Build Docker') {
steps {
sh "docker build -t ${IMAGE_NAME} ."
}
}
stage('Integration Tests') {
steps {
sh "kubectl set image deployment/my-app-test my-app=${IMAGE_NAME} -n test"
sh 'cypress run --config baseUrl=http://my-app-test.namespace.svc'
}
}
stage('Gerrit Vote') {
steps {
// 给Gerrit Change打上Verified+1
gerritReview(
vote: 'verified',
reviewLabel: 'Code-Review',
category: 'Verified',
value: 1
)
}
}
}
post {
failure {
gerritReview(
vote: 'verified',
category: 'Verified',
value: -1
)
}
}
}
4.2 Gerrit钩子脚本(merge前强制检查)
在Gerrit服务器hooks/commit-msg中添加逻辑,阻止没有关联JIRA Issue的提交:
#!/bin/bash
# 要求commit message包含JIRA编号
MSG_FILE=$1
if ! grep -qE '\[(PROJ|TASK)-\d+\]' "$MSG_FILE"; then
echo "ERROR: Commit message must contain JIRA issue (e.g., [PROJ-123])" >&2
exit 1
fi
# 检查是否包含变更描述
if [ $(wc -l &2
exit 1
fi
exit 0
部署钩子:将脚本放在/hooks/commit-msg,并赋予执行权限。
5. 踩坑与优化
坑1:Gerrit + Jenkins的“死亡循环”
现象:每次CI通过后,Jenkins再次触发自己(因为Gerrit Change状态变更)。解决:在Jenkinsfile中增加skipBuildIfMessage过滤:
when {
beforeAgent true
expression { return !env.GERRIT_CHANGE_COMMIT_MESSAGE?.contains('[CI SKIP]') }
}
坑2:Trivy扫描导致构建失败太频繁
初期设置--severity HIGH,结果每天有3-4次构建因基础镜像漏洞阻塞。调整为:CRITICAL才阻断,HIGH仅记录通知。
坑3:短分支存活时间太短导致Review堆积
强制feature分支存活不超过48小时,但Reviewer不够。优化:引入“Review轮值日历”,每人每天处理不超过3个Review,并设置Slack机器人提醒。
性能优化:并行化CI
将Lint、安全扫描、单元测试改为并行Stage(如上Jenkinsfile所示),单次流水线时间从18分钟降到7分钟。
6. 效果数据(改后3个月)
| 指标 | 改前 | 改后 | 变化 |
|---|---|---|---|
| 每周冲突次数 | 12次 | 3.2次 | ↓73% |
| 平均冲突解决时间 | 4.5h | 1.1h | ↓75% |
| 发布周期 | 3天 | 4小时 | ↓87% |
| Code Review时效 | >48h | 3.2h | ↓93% |
| 线上故障率 | 8次/月 | 3次/月 | ↓62% |
| 开发效率(人均PR/周) | 3.1个 | 5.8个 | ↑87% |
7. 总结与建议
Trunk-based + 短分支 + 自动门禁的组合,本质是把集成负担从“人”转移到“自动化流水线”。关键原则:
- 分支寿命功能分支:永远不要在master外停留超过2天
如果团队规模超过30人,可以考虑引入GitHub Actions / GitLab CI替代Gerrit(Gerrit的UI对新人不够友好),但核心分支模型和门禁逻辑可以复用。
最后送一句:“Master is always deployable”——这不是口号,是流水线强制的结果。