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_qualitylicense_scanning,但最有效的是在CI中强制执行eslintspotbugs,任何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秒

主要优化了三点:

  1. Maven增量构建:使用--pl参数只构建变更模块(模块间有依赖关系时用-am
  2. 缓存策略.m2/repository缓存保留2天,target/目录不缓存
  3. 并发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字)