1. 问题背景:从“能跑”到“跑不动”的Git之痛
2021年,我们团队还维持着“单分支+直接push”的野路子。当时20人,日均提交80次,冲突靠人肉协调,发版靠手动打tag。但2022年团队扩张到200人,业务线拆成6个,事故开始频发:
- 合并地狱:多人同时改
UserService.java,每天光解决冲突就要花掉1.5小时。 - 代码审查形同虚设:直接在
master上提交,reviewer只能事后看diff,发现问题时代码已上线。 - 发布不可控:没有 staging 环境,
master即生产,一次错误提交直接导致线上P0事故。
我们意识到,Git工作流不是“选一个模式”,而是“设计一套约束系统”——包括分支命名、合并策略、审查门槛、自动化检查,缺一不可。
2. 环境与版本:工具链选型
- Git:2.39.1(使用
git worktree支持并行开发) - GitLab:15.11.3(Community Edition,开启Merge Request功能)
- CI/CD:Jenkins 2.414.2 + GitLab Plugin,流水线即代码(
Jenkinsfile) - 代码质量:SonarQube 9.9(社区版,强制Quality Gate)
- 制品库:Nexus 3.49(存储jar包与docker镜像)
为了兼容老习惯,我们保留了master分支,但将其设为“只读保护分支”,所有变更必须通过Merge Request进入。
3. 方案设计:分支策略与角色权限
我们最终采用了“Trunk-based + 短期特性分支”的折中方案,抛弃了纯GitFlow的长期分支(太笨重)。
分支体系:
- master(生产分支,受保护,禁止直接push)
- develop(集成分支,所有特性分支合并到这里,跑完整CI)
- feature/xxx(从master拉出,生命周期不超过3天)
- hotfix/xxx(从master拉出,必须附带JIRA工单号)
关键规则:
1. feature分支必须每日同步master(git rebase),防止长期偏离。
2. 合并到develop采用--squash压缩为一次提交,保持历史线性。
3. develop通过CI与自动化测试后,由Release Manager合并到master(禁用--no-ff,强制线性历史)。
权限矩阵:
- 开发者:对feature/*有push权限,对develop仅能发起MR。
- 技术Leader:有develop合并权,但必须通过2人Code Review。
- Release Manager:唯一有master合并权限的人,且必须通过全部流水线。
4. 核心实现:Code Review流水线与CI/CD配置
4.1 强制Code Review:GitLab机器人 + 规则
我们通过GitLab的Code Owner机制,强制核心模块必须有指定人审查:
# .gitlab/CODEOWNERS
# 每个文件匹配规则,至少需要1个Owner批准才能合并
src/main/java/com/company/payment/** @alice @bob
src/main/java/com/company/risk/** @carol
同时,通过GitLab的Merge Request Approval规则(Settings → Merge Requests)设置:
- 最低批准人数:2(非作者)
- 禁止作者批准自己的MR
- 过期后自动重新请求审查(当有新push时)
4.2 CI/CD集成:.gitlab-ci.yaml(片段)
这是我们的精简版流水线,包含静态检查→单元测试→集成测试→构建镜像四个阶段:
# .gitlab-ci.yml
stages:
- lint
- test
- build
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
DOCKER_REGISTRY: "registry.example.com:5000"
cache:
paths:
- .m2/repository/
lint-job:
stage: lint
image: maven:3.8.6-openjdk-11
script:
- mvn checkstyle:check # 自定义规则,禁止System.out.println
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # 只在MR时运行
unit-test-job:
stage: test
image: maven:3.8.6-openjdk-11
services:
- mysql:8.0
script:
- mvn test -Dtest=UnitTestSuite
artifacts:
when: always
reports:
junit: target/surefire-reports/TEST-*.xml
build-docker:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
script:
- docker build -t $DOCKER_REGISTRY/myapp:$CI_COMMIT_SHORT_SHA .
- docker push $DOCKER_REGISTRY/myapp:$CI_COMMIT_SHORT_SHA
rules:
- if: '$CI_COMMIT_BRANCH == "develop"' # 只有develop分支才构建镜像
4.3 Jenkins流水线(用于复杂发布流程)
GitLab CI负责MR内的快速反馈(2分钟内跑完),而发布到生产用Jenkins,因为我们需要手动审批+蓝绿部署:
// Jenkinsfile
pipeline {
agent any
parameters {
string(name: 'RELEASE_VERSION', defaultValue: '1.2.0', description: '版本号')
}
stages {
stage('Checkout') {
steps {
git branch: 'master',
credentialsId: 'gitlab-ssh-key',
url: 'git@gitlab:company/app.git'
}
}
stage('Quality Gate') {
steps {
// 调用SonarQube,质量阈值:覆盖率>80%,bug=0
sh 'mvn sonar:sonar -Dsonar.qualitygate.wait=true'
}
}
stage('Deploy to Staging') {
steps {
sh "docker-compose -f docker-compose.staging.yml up -d"
// 运行冒烟测试脚本(检查健康端点)
sh 'bash scripts/smoke_test.sh http://staging.example.com/health'
}
}
stage('Manual Approval') {
steps {
input message: '确认部署到生产?', ok: 'Deploy'
}
}
stage('Deploy to Production') {
steps {
sh "kubectl set image deployment/myapp myapp=$DOCKER_REGISTRY/myapp:$RELEASE_VERSION -n production"
sh "kubectl rollout status deployment/myapp -n production --timeout=5m"
}
}
}
}
5. 踩坑与优化:三个真实教训
坑1:Rebase vs Merge的拉锯战
- 最开始我们用git merge保持“真实历史”,但develop分支出现了大量无意义的Merge branch 'master' into feature/x提交,图形一团乱麻。
- 解决方案:强制使用git pull --rebase,并在GitLab设置Merge method: Merge commit的同时,要求开发者本地先rebase。配合git config --global pull.rebase true,从源头杜绝。
坑2:CI在MR上跑得太慢
- 单元测试+集成测试全量跑需要8分钟,开发者等到花儿都谢了。
- 优化:使用rules条件只跑变更相关的测试;集成测试拆分成独立Stage,仅在develop分支执行。最终MR内反馈时间缩短到1分40秒。
坑3:Code Owner匹配失效
- 我们最初在CODEOWNERS里写了*.java @team,但Java文件有500+,导致每个MR都要等5个人批准,效率极低。
- 修正:改为按模块目录精确匹配(如src/main/java/com/company/payment/),并把Owner人数限制为2人。现在平均MR等待审批时间为4小时(可接受)。
6. 效果数据:从混乱到有序
推行这套工作流4个月后,我们收集了关键指标:
- 合并冲突:从日均17次降至日均2次(主要是同时改配置文件)。
- 发布频率:从每周1次升至每周3-4次(通过自动化,减少了人工检查)。
- 线上事故:由每月3起降至每季度1起(因为Code Review+CI质量门禁拦截了问题)。
- 开发者满意度:内部调查显示,73%的人认为“提交-审查-合并”流程清晰,不再害怕提交代码。
7. 总结:工作流是“活”的,不是“抄”的
这套体系并非万能——它牺牲了部分灵活性(比如禁止长期分支),但换来了可预测性与安全感。如果你要落地,请记住三点:
- 机器人优先:所有能自动化检查的(格式、单元测试、依赖漏洞),绝不用人工。
- 分支生命周期最短:超过3天的特性分支是坏味道,及时拆解。
- Code Review不是过场:用CODEOWNERS强制核心模块的专家审查,否则形同虚设。
最后,工具只是载体,真正改变的是团队对“变更”的敬畏。如果你们也在扩张,不妨从这里开始。