一、问题背景:从15人到300人,主干开发模式崩了
2021年我们团队只有15人,所有人在master上直接提交,一天合并20次代码,发布靠手动打tag。当时觉得Git就是个存代码的工具,直到团队扩张到300人,噩梦开始了:
- 合并地狱:一个特性分支存活超过3天,合并时冲突率高达70%,解决冲突要花半天
- Code Review形同虚设:没有强制机制,很多MR(Merge Request)直接自我审查通过,上线前才发现逻辑漏洞
- 发布失控:每周五发布日,10个特性同时上线,出问题不知道是谁的改动导致的
最惨的一次:一个同事在master上直接push了一个未完成的重构,导致线上支付接口500了4个小时。这就是压垮骆驼的最后一根稻草——我们决定彻底重构Git工作流。
二、环境与版本:我们用的工具链
先交代一下技术栈,方便你对号入座:
- Git版本:2.39.1(强制要求,低于2.33会有安全漏洞)
- GitLab:15.11.3(社区版,用Docker部署在k8s集群)
- CI/CD:Jenkins 2.414.2 + Jenkinsfile(声明式流水线)
- 代码质量:SonarQube 10.2(社区版,配置了Quality Gate)
- 制品管理:Nexus 3.49(存储jar包和docker镜像)
三、方案设计:Trunk-Based + 短期特性分支
我们最终选型是Trunk-Based Development(主干开发)的变体,不是GitFlow——GitFlow太重了,300人团队用GitFlow会死得很惨(release分支和develop分支的合并成本太高)。
核心规则只有四条:
- master永远是可部署状态,任何时刻都可以一键发布
- 新功能开短期特性分支(feature/xxx),存活时间不超过2个工作日
- MR必须通过两级Code Review + 自动化门禁,才能合并
- 紧急修复走hotfix分支,直接修改master,但必须在1小时内补上回归测试
分支命名规范:
feature/PRJ-123-add-payment-timeout
bugfix/PRJ-456-fix-npe-on-order-detail
hotfix/PRJ-789-rollback-config
四、核心实现:分支保护 + MR强制规则 + CI/CD流水线
4.1 GitLab分支保护配置
我们在GitLab上对master设置了严格的推送规则(Settings → Repository → Protected Branches):
- Allowed to merge:Maintainers + Developers(但必须满足MR审批条件)
- Allowed to push:Maintainers 仅此而已,开发者不允许直接push代码到master
关键点:防止绕过MR直接推送,GitLab有个隐藏开关,必须开启:
# 在gitlab.rb中(Omnibus安装方式)
gitlab_rails['gitlab_shell_push_events'] = true
gitlab_rails['gitlab_shell_receive_max_size'] = 200 # 限制单次推送大小,防止误提交大文件
4.2 MR Approval Rules(强制Code Review)
这是整个流程的灵魂。在GitLab项目设置中配置Approval Rules:
- 规则1:至少2个Maintainer批准(其中至少1个非作者本人)
- 规则2:代码质量机器人(SonarQube)必须通过Quality Gate
- 规则3:Jenkins流水线必须绿色通过
配置文件.gitlab/merge_request_templates/default.md:
## MR描述
- 关联Issue:#{issue_id}
- 变更类型:bugfix / feature / refactor
- 测试情况:
- [ ] 单元测试通过(覆盖率不低于80%)
- [ ] 集成测试通过
- [ ] 已手动验证关键路径
## 自检清单
- [ ] 无调试日志残留(如console.log、System.out.println)
- [ ] 无硬编码配置
- [ ] 数据库变更已包含迁移脚本
4.3 CI/CD集成:Jenkins声明式流水线
这是.gitlab-ci.yml的核心片段,我们用的是Jenkins + GitLab插件,但原理一样。关键是在MR阶段就跑完整的构建+测试+静态扫描,而不是合并后再跑:
# .gitlab-ci.yml (GitLab 15.11)
stages:
- build
- test
- quality
- package
variables:
MAVEN_OPTS: "-Xmx2048m -Xms512m"
SONAR_HOST_URL: "http://sonar.internal:9000"
REGISTRY: "registry.internal:5000"
cache:
paths:
- .m2/repository/
build:jar:
stage: build
image: maven:3.9.2-eclipse-temurin-17
script:
- mvn clean compile -DskipTests -q
only:
- merge_requests # 只在MR阶段触发
- master
test:unit:
stage: test
image: maven:3.9.2-eclipse-temurin-17
script:
- mvn test -Pcoverage
artifacts:
reports:
junit: target/surefire-reports/*.xml
paths:
- target/site/jacoco/jacoco.xml
coverage: '/Total.*?([0-9]{1,3})%/'
quality:sonar:
stage: quality
image: sonarsource/sonar-scanner-cli:5.0.1
script:
- sonar-scanner
-Dsonar.projectKey=${CI_PROJECT_NAME}
-Dsonar.sources=src/main/java
-Dsonar.java.binaries=target/classes
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
only:
- merge_requests
package:docker:
stage: package
image: docker:24.0.5
services:
- docker:24.0.5-dind
script:
- docker build -t ${REGISTRY}/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA} .
- docker push ${REGISTRY}/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA}
only:
- master # 只有master合并后才会构建镜像
关键设计思路:only: merge_requests让所有检查都在MR阶段执行,一旦合并到master,流水线只做打包发布,不再重复跑测试——把质量卡在合并前,而不是发布后。
五、踩坑与优化:真实教训
坑1:MR Approval Rules被绕过
最初我们只设置了"至少1个Approval",结果开发者自己approve自己的MR,形同虚设。后来改成了2个Maintainer + 非作者,并在GitLab的Settings → General → Merge request approvals里勾选了 Prevent author from approving own merge request。这个选项在UI里藏得很深,很多团队没发现。
坑2:SonarQube Quality Gate让所有MR卡死
刚开始SonarQube的规则太严格(比如禁止任何TODO注释),导致所有MR都红着,开发者直接忽略红色状态强推合并。优化方案:
- 把Quality Gate设置为新增代码覆盖率≥80%,而不是整体覆盖率(存量代码不管)
- 把阻塞性问题设为A级(阻断)和B级(严重),C级以下不阻塞合并
- 在SonarQube中配置
sonar.coverage.exclusions排除生成的代码(如**/generated/**)
坑3:CI流水线跑30分钟,开发者等烦了
最初流水线包含全量测试、覆盖率统计、静态扫描、安全扫描,跑完要35分钟。开发者为了快速合并,直接推hotfix绕过CI。优化措施:
- 并行化:把单元测试(10分钟)和覆盖率统计(10分钟)并行跑
- 增量构建:用Maven的
-pl参数只构建变更模块(配合git diff判断变更范围) - 缓存依赖:
.m2目录缓存,避免每次下载依赖,省了5分钟
优化后流水线平均耗时8分42秒,开发者接受度直线上升。
六、效果数据:用数字说话
这套体系运行了6个月,数据对比非常明显(内部度量工具统计):
| 指标 | 改造前 | 改造后 | 变化 |
|---|---|---|---|
| 特性分支平均存活时间 | 5.2天 | 1.8天 | 降低65% |
| MR合并冲突率 | 70% | 12% | 降低83% |
| 生产环境回滚率(月均) | 6次 | 2次 | 降低63% |
| 代码评审覆盖率 | 40% | 100%(强制) | 提升150% |
| 单次发布耗时 | 45分钟 | 12分钟 | 降低73% |
最明显的感受:代码质量变好了,但开发速度反而变快了。因为冲突少了,返工少了,虽然每次MR要等CI跑完,但算总账是赚的。
七、总结:这套体系适合谁?不适合谁?
适合:
- 团队规模20人以上,有专职的DevOps或工具链维护者
- 产品迭代节奏快,需要频繁发布(每周1次以上)
- 代码质量要求高,有自动化测试基础
不适合:
- 5人以下小团队,流程会拖慢速度
- 强法规合规场景(如金融核心系统),需要更严格的审批链(建议GitFlow + 人工发布审批)
- 单仓库多团队(建议拆分为monorepo + 独立CI)
最后说一句大实话:Git工作流没有银弹,我们这套体系也是从GitFlow、GitHub Flow一路踩坑踩过来的。关键不是选哪个模型,而是让规则自动化、强制化——把人的不可靠因素降到最低。如果你正在为代码冲突和发布事故头疼,不妨从强制MR Approval和CI门禁开始,这两步投入最少、见效最快。
附:分支保护GitLab API脚本(用命令行配置,比UI批量操作快):
#!/bin/bash
# 批量设置所有项目的分支保护规则
# 依赖: GitLab API v4, 需要PRIVATE_TOKEN
PROJECT_ID=$1
TOKEN=$2
curl --request POST "https://gitlab.internal/api/v4/projects/${PROJECT_ID}/protected_branches" \
--header "PRIVATE-TOKEN: ${TOKEN}" \
--data "name=master" \
--data "push_access_level=40" \
--data "merge_access_level=40" \
--data "allow_force_push=false" \
--data "code_owner_approval_required=true"
这个脚本我写在团队内部的git-toolkit仓库里,配合jq可以批量处理200+项目,强烈建议你搞一个。