一、问题背景:从15人到40人,Git协作开始失控
2023年Q3,我们团队从15人扩张到40人,产品迭代节奏从双周发版加速到每日发版。最初的master直接开发模式彻底崩溃——高峰期每天有30+个提交直接推到master,代码冲突像俄罗斯方块一样堆叠。
具体痛点:
- 每周平均发生23次合并冲突,解决耗时约4.2小时/人周
- master分支随时处于不可部署状态,发布前需要冻结代码2天
- Code Review形同虚设,因为所有人都在赶工,PR平均存活时间超过3天
我意识到必须重构Git工作流。核心目标是:让每个提交都经过审查,让主分支永远可部署。
二、环境与版本:我们用的具体工具链
- Git:2.39.2(重点用到了
git switch和git restore的交互式提示) - GitLab:15.11.3(Community Edition,我们没用EE的高级功能)
- Jenkins:2.414.2,搭配Blue Ocean插件
- 语言栈:Java 17 + Spring Boot 3.1,但工作流本身语言无关
- 团队规模:40人,分为8个特性小组(每个组5人)
三、方案设计:Trunk-based + 短生命周期分支
我们最终采用了Trunk-based Development变体,但保留Pull Request用于代码审查——这是GitHub Flow和GitLab Flow的混合体。
核心规则:
1. main分支永远是稳定可部署的,任何时刻都可以一键发布
2. 每个功能/修复从main切出短生命周期分支,命名规范:feature/【JIRA单号】-【简要描述】或fix/【JIRA单号】-【修复内容】
3. 分支存活时间严格限制在2个工作日内(超时自动告警)
4. 必须通过CI流水线+至少1人Code Review才能合并
5. 合并采用--squash策略,保持main历史线性
我们特意没有用develop分支——那会增加合并层级,让问题延迟暴露。
# 分支命名示例
feature/PLAT-2345-add-rate-limiter
fix/PLAT-2389-fix-npe-on-user-login
四、核心实现:钩子脚本 + CI流水线 + 合并规则
4.1 pre-push钩子:强制本地检查
团队每个成员必须安装这个钩子(放在.git/hooks/pre-push),它会在git push前运行本地测试和lint:
#!/bin/bash
# .git/hooks/pre-push - 本地质量门禁
echo "🔍 Running pre-push checks..."
# 1. 检查分支命名
BRANCH_NAME=$(git branch --show-current)
if [[ ! $BRANCH_NAME =~ ^(feature|fix|hotfix)/[A-Z]+-[0-9]+- ]]; then
echo "❌ 分支命名不符合规范: $BRANCH_NAME"
echo " 请使用格式: feature/PLAT-1234-short-desc"
exit 1
fi
# 2. 运行单元测试(跳过集成测试,CI会做)
echo "🧪 Running unit tests..."
./mvnw test -Dtest=*UnitTest -DfailIfNoTests=false
if [ $? -ne 0 ]; then
echo "❌ 单元测试失败,禁止推送"
exit 1
fi
# 3. 检查是否有未解决的冲突标记
if grep -rn '^<<<<<<< HEAD' --include="*.java" --include="*.xml" . | head -5; then
echo "❌ 发现未解决的冲突标记"
exit 1
fi
echo "✅ Pre-push checks passed. Ready to push."
4.2 Jenkins CI配置:自动化验证
我们在Jenkinsfile中定义了流水线,每次push都会触发构建。关键点是并行执行单元测试和静态代码分析:
pipeline {
agent { label 'docker' }
options {
timeout(time: 15, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
}
triggers {
// GitLab webhook 自动触发
gitlab(triggerOnPush: true, triggerOnMergeRequest: true)
}
stages {
stage('Parallel Verification') {
parallel {
stage('Unit Tests') {
steps {
sh './mvnw test -DskipITs'
}
}
stage('SonarQube Scan') {
steps {
sh './mvnw sonar:sonar \
-Dsonar.projectKey=${PROJECT_KEY} \
-Dsonar.host.url=${SONAR_HOST} \
-Dsonar.qualitygate.wait=true'
}
}
stage('Dependency Check') {
steps {
sh './mvnw org.owasp:dependency-check-maven:check \
-DfailBuildOnCVSS=7'
}
}
}
}
stage('Integration Tests') {
when { branch 'main' } // 只有合并到main才跑完整集成测试
steps {
sh './mvnw verify -DskipUnitTests'
}
}
}
post {
success {
// 给GitLab MR发成功评论
updateGitlabCommitStatus(name: 'build', state: 'success')
}
failure {
updateGitlabCommitStatus(name: 'build', state: 'failed')
}
}
}
4.3 GitLab Code Review规则
在GitLab项目设置中,我们配置了以下审批规则:
审批规则:
- 至少1人审批(非作者本人)
- 审批人必须是指定CODEOWNERS中的成员
- 禁用“通过合并按钮直接推送”权限
- 合并前必须通过所有CI流水线
- 合并且删除源分支(强制)
.gitlab/CODEOWNERS文件示例:
# 核心模块需要架构师审批
/src/main/java/com/platform/core/** @backend-architect
# 数据库迁移文件需要DBA审批
/db/migrations/** @dba-team
# 其他默认团队负责人
* @backend-lead
五、踩坑与优化:我们经历过的真实问题
坑1:Squash合并的副作用
用了--squash后,git bisect几乎失效——因为每个提交都是一个巨大的变更。优化方案:要求PR描述中必须包含测试步骤和回滚方案,并在MR模板中加入固定格式。
坑2:Code Review阻塞
早期要求3人审批,导致PR排队严重。我们分析数据发现,40%的审批都是“LGTM”式走过场。优化方案:
- 改为1人审批(必须是CODEOWNERS)
- 设置SLA:24小时内未审批自动提醒,36小时未审批自动指派给备份审批人
- 引入“小PR原则”:单个PR变更文件不超过15个,代码行数不超过800行
坑3:CI并发山洪
40人同时push,Jenkins瞬间启动80+个构建任务,导致资源耗尽。优化方案:
- 限制并发构建为4个
- 使用--build-once-per-commit参数
- 将集成测试拆分为独立流水线,只在合并到main时运行
优化效果数据(对比重构前后3个月):
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 平均合并等待时间 | 47分钟 | 8分钟 |
| 每天成功合并次数 | 12次 | 19次 |
| 合并冲突率 | 23次/周 | 5次/周 |
| 线上紧急修复次数 | 4次/月 | 1次/月 |
| 平均代码审查覆盖 | 60% | 97% |
六、总结:Git工作流是持续演进的过程
这套方案我们运行了6个月,效果显著。但要注意:没有银弹。我们团队下一步计划引入git worktree来优化分支切换开销,并尝试用AI代码审查工具辅助人工审查。
最后给三个实用建议:
1. 分支生命周期是你的第一道防线——超过3天的分支必须拆解
2. CI不通过就不允许合并,这条要写进团队契约
3. 定期分析Git仓库指标(用git log --format和自建脚本),用数据驱动流程改进
如果你也在为团队协作头疼,建议先从“减少分支存活时间”和“强制Code Review”开始,这两点收益最大,改动最小。