1. 问题背景:为什么我们放弃了GitFlow
2019年我们团队从SVN迁移到Git,直接照搬了Atlassian的GitFlow模型。结果三个月内出现了三次严重事故:
- 发布日合并地狱:release分支合并回master时,冲突文件超过200个,6个开发花了4小时才解决
- 热修复双重修复:hotfix修复后忘记cherry-pick到develop,导致同一bug修复两次
- CodeReview形同虚设:因为功能分支存活时间平均11天,reviewer看到代码时已经忘了上下文
我们当时120人、每周发布4次,GitFlow的5层分支结构完全不适合。真正的转折点是2020年Q4,我们连续两周发布失败,CTO在复盘会上直接说:“要么改工作流,要么改流程”。
2. 环境与版本:我们的Git基建
既然决定重构,先列一下我们的基础设施:
- GitLab:从12.5升级到15.3(2022年3月)
- Git版本:2.30.2(统一升级,禁止使用低于2.28的版本以支持
--recurse-submodules) - CI Runner:Kubernetes集群,30个runner,每个限制2并发
- 代码量:主仓库monorepo,Java+Maven+Node,约150万行代码
- 发布频率:每天4次生产发布(8:00, 12:00, 16:00, 20:00)
关键决策:我们选择了TrunkBased的变体——所有功能分支从trunk(我们叫main)拉出,但保留一个release/前缀的短生命周期分支用于发布准备。相比纯TrunkBased,多了一个发布缓冲层,但避免了GitFlow的长期分支问题。
# 分支命名规范(写入.gitmessage模板)
# feature/[需求编号]-[短描述] 例如:feature/TKT-4421-gds-cache
# fix/[bug编号]-[原因] 例如:fix/BUG-8821-npe-on-null-query
# release/[日期]-[版本号] 例如:release/20230515-2.14.0
3. 方案设计:分支策略 + CodeReview 双轨制
3.1 分支策略(核心设计)
我们设计了三条主干路径:
main(受保护,仅merge request)→ staging(自动部署)→ production(手动触发)
但真正的精华在于合并策略。我们强制使用--no-ff合并,并且禁止直接git push到main。所有改动必须通过Merge Request,且每个MR必须关联Jira ticket。
# .gitlab/merge_request_templates/feature.md
## 需求描述
- [ ] 关联Jira: TKT-XXXX
- [ ] 变更类型: feature/fix/refactor
## 测试清单
- [ ] 单元测试通过(覆盖率不低于75%)
- [ ] 集成测试通过(docker-compose执行)
- [ ] 已运行`mvn verify`本地全量
## 部署说明
- [ ] 无DB变更 / 有DB变更,已准备migration脚本
- [ ] 无配置文件变更 / 有变更,已同步至配置中心
3.2 Code Review 流程
我们不是简单的“合并前点通过”,而是设计了两阶段Review:
- 阶段一(代码质量):在MR发起后2小时内,由指定的senior reviewer检查逻辑正确性、性能隐患、安全漏洞。
- 阶段二(业务验收):在合并到staging后,由业务方在staging环境验证功能。
关键工具:我们启用了GitLab的code_quality和license_scanning,但最有效的是在CI中强制执行eslint和spotbugs,任何warning都阻塞合并。
4. 核心实现:CI/CD集成与分支保护
4.1 .gitlab-ci.yml 配置切片
我们用了三层CI流水线:MR验证(MR Pipeline)、主干流水线(main Pipeline)、发布流水线(Release Pipeline)。
# 关键配置(精简版)
stages:
- validate
- test
- build
- deploy
variables:
MAVEN_OPTS: "-Xmx2g -XX:MaxMetaspaceSize=512m"
DOCKER_DRIVER: overlay2
K8S_NAMESPACE: "trip-ticket"
# MR校验阶段:只跑增量测试
mr-validate:
stage: validate
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
- git diff --name-only origin/main...HEAD | grep -E '\.(java|js|ts)$' | xargs -r mvn spotless:check
- ./scripts/check-branch-name.sh # 校验分支命名规则
# 主干流水线:全量测试+构建镜像
main-build:
stage: build
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"'
script:
- mvn clean package -DskipTests=false -Pproduction
- docker build -t registry.internal.com/airticket:$CI_COMMIT_SHORT_SHA .
artifacts:
paths: [target/*.jar]
expire_in: 2 hours
# 发布流水线:手动触发到生产
release-deploy:
stage: deploy
when: manual
rules:
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
script:
- kubectl set image deployment/airticket-web airticket-web=registry.internal.com/airticket:$CI_COMMIT_SHORT_SHA -n $K8S_NAMESPACE
environment:
name: production
4.2 分支保护规则(通过GitLab API)
# 通过GitLab API设置main分支保护
curl -X PUT "https://gitlab.example.com/api/v4/projects/42/protected_branches/main" \
-H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
-d "allowed_to_push=0" \
-d "allowed_to_merge[][access_level]=40" \
-d "code_owner_approval_required=true"
5. 踩坑与优化:三个血泪教训
5.1 坑一:git rerere 解决不了“不存在的冲突”
我们曾依赖git rerere记录冲突解决方案,但发现当双方都改动了相同函数签名时,rerere的自动解决反而产生逻辑错误。最终方案:每周三次rebase main,而不是merge main。
# 团队强制约定:功能分支每2天执行一次
git fetch origin main
git rebase origin/main
git push --force-with-lease origin feature/TKT-4421
5.2 坑二:CodeReview 效率瓶颈在上下文切换
我们最初reviewer轮流担任,结果每人每天要切换4-5个MR的上下文。优化后:按模块分配reviewer,每人只负责自己熟悉的领域,并且要求MR小于400行(超过自动拒绝)。效果:Review时间从平均3天缩短到1.5天。
5.3 坑三:CI构建时间从22分钟到6分30秒
主要优化了三点:
- Maven增量构建:使用
--pl参数只构建变更模块(模块间有依赖关系时用-am) - 缓存策略:
.m2/repository缓存保留2天,target/目录不缓存 - 并发test分片:
mvn test -Dtest=com.ctrip.** -DforkCount=4 -DreuseForks=true
6. 效果数据:我们真的变快了
以2022年5月与2020年5月对比(均为120人规模):
| 指标 | 2020.5 | 2022.5 | 提升 |
|---|---|---|---|
| 发布失败率 | 8.2% | 1.1% | 87% |
| 平均功能分支存活时间 | 11天 | 2.3天 | 79% |
| CodeReview通过时间 | 3.2天 | 1.4天 | 56% |
| CI构建时间 | 22分钟 | 6分30秒 | 70% |
| 每周发布次数 | 4次 | 8次(峰值12次) | 100%+ |
最有说服力的一个数字:2022年全年生产环境零回滚(之前每年至少8次)。这得益于release分支只做版本号修改和配置文件替换,核心代码在staging环境已经验证过。我们的release分支生命周期从不超过24小时。
7. 总结:什么样的工作流才是“对的”
如果你问我GitFlow是不是垃圾?我会说不是,它适合0到50人、发布频率低的团队。但如果你在快速迭代的互联网场景,TrunkBased + 短生命周期release分支是更优解。核心不是选择什么模型,而是让分支存活时间尽可能短。
我们最后悔的不是换了工作流,而是没有更早意识到:Git工作流的本质是控制变更的扩散半径。分支策略、CodeReview、CI/CD,这三者必须配合,单独改任何一个都不会有本质提升。
最后送上一句我们团队贴在墙上的话:“如果你的分支存活超过5个工作日,说明你的需求拆解有问题。”
(全文完,约3000字)