一、问题的爆发:从5人到20人,主干开发成了灾难

去年Q3,我们团队从5人扩张到20人,前后端、算法全在一个仓库里。最初大家遵循简单的“主干开发+特性分支”模式,但很快问题集中爆发:

  1. 冲突地狱:每周平均17次合并冲突,大部分源自多人同时修改配置文件或公共组件。
  2. 发布失控:主干上的代码永远处于“半成品”状态,每次发版都要从一堆未完成的功能中手工挑拣,导致发布推迟1-2天。
  3. Review形同虚设:因为分支存活时间短(平均2小时),代码评审沦为“看一下有没有语法错误”,核心逻辑无人把关。

我们最初尝试过Git Flow,但发现其develop分支的长期存在反而加剧了集成延迟。最终决定设计一套“短生命周期特性分支 + 受控主干”的混合策略。

二、环境与版本基线

  • Git:2.39.1(重点使用git switchgit restore命令)
  • GitLab:15.7.5(社区版,启用Push Rules和Merge Request Approvals)
  • Jenkins:2.414.2(Pipeline DSL)
  • 仓库规模:1200+文件,平均每天合并请求(MR)50+个

三、分支策略设计:三条命脉

我们最终落地了三条核心分支:

  1. main:唯一长期分支,所有代码必须经过MR合并。
  2. feature/*:从main切出,命名规范feature/issue-编号-简短描述。存活周期不超过3天。
  3. hotfix/*:从main切出,修复后直接合并回main并打Tag。

关键设计决策:我们砍掉了develop分支。所有feature直接合并到main,但通过合并队列(Merge Queue) 机制串行化合并,避免并发合并导致的原子性问题。

# 推荐的分支创建与同步命令
git switch main && git pull origin main
git switch -c feature/issue-1234-add-login-banner
# 每天至少同步一次main
git fetch origin main
git rebase origin/main   # 用rebase保持线性历史,避免merge commit噪音

四、核心实现:保护规则 + Code Review流程

4.1 分支保护规则(GitLab配置)

Settings -> Repository -> Protected Branches中设定:

  • main分支
  • 允许合并:Maintainers + Developers(但需审批)
  • 不允许直接Push(包括Maintainer)
  • Push Rules:提交信息必须匹配正则^(feat|fix|docs|chore): [A-Z]+-[0-9]+,强制关联Jira工单。

4.2 Code Review流程:不低于2人审批

我们在GitLab的Merge Request Approvals中设置:

  • 常规MR:至少1个后端+1个前端审批人(通过CODEOWNERS文件自动指派)。
  • 涉及数据库迁移或依赖升级:需追加1名架构组审批。

同时,我们通过GitLab CI在MR中自动跑三个Pipeline任务:

  1. 静态检查eslint + tsc --noEmit(前端),flake8(Python)。
  2. 单元测试pytestjest,覆盖率阈值设于80%(基于coverage插件)。
  3. 构建验证docker build 确保镜像可生成。
# .gitlab-ci.yml 核心片段 (版本:GitLab 15.7)
stages:
  - lint
  - test
  - build

variables:
  COVERAGE_THRESHOLD: "80"

lint:
  stage: lint
  image: node:18-alpine
  script:
    - npm ci
    - npm run lint
  only:
    - merge_requests

test:
  stage: test
  image: python:3.11
  before_script:
    - pip install pytest pytest-cov
  script:
    - pytest --cov=. --cov-fail-under=$COVERAGE_THRESHOLD
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml
  only:
    - merge_requests

build:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
  only:
    - main

4.3 合并策略:Rebase + 合并队列

我们禁止了普通的Merge Commit,确保历史是线性的。合并MR时,GitLab自动执行rebase,若冲突则要求开发者本地解决。同时开启合并队列(GitLab Premium功能,但社区版可通过merge_train_ref实现近似效果),保证3个并发MR合并时按序构建。

五、踩坑与优化:三个深刻的教训

教训1:Code Owner配置失误导致Review死锁
起初我们用*通配符匹配了所有文件,导致每个MR都需要全部8个核心成员审批,流程阻塞在等待上。后来细化为按目录匹配:

# CODEOWNERS 文件示例
# 前端代码
/src/frontend/* @frontend-lead @frontend-core
# 后端API
/src/api/* @backend-lead

教训2:Rebase vs Merge的反复横跳
最初强制rebase,但开发者经常忘记同步main,导致MR显示大量冲突。我们写了一个pre-push钩子强制检查:

#!/bin/sh
# .git/hooks/pre-push —— 强制同步main分支
current_branch=$(git symbolic-ref --short HEAD)
if [ "$current_branch" != "main" ]; then
  echo "Fetching latest main..."
  git fetch origin main
  commits_behind=$(git rev-list --count main...$current_branch)
  if [ "$commits_behind" -gt 5 ]; then
    echo "Error: Branch is $commits_behind commits behind main. Run 'git rebase origin/main' first."
    exit 1
  fi
fi

教训3:CI资源浪费
最初每次Push都触发全量测试,平均耗时11分钟。优化后,通过paths关键字只在特定路径变更时触发对应测试,耗时降至4分半。

# 仅当后端文件变化时运行Python测试
test-backend:
  stage: test
  script: pytest
  only:
    changes:
      - src/api/**/*

六、效果数据:对比与收益

经过3个月磨合,我们统计了以下数据(对比实施前3个月):

指标 实施前 实施后 提升幅度
每周合并冲突次数 17次 3次 -82%
平均MR从创建到合并时长 2.1天 3.8小时 -81%
线上紧急回滚次数 每月4.5次 每月0.5次 -89%
发布周期(从代码冻结到上线) 2天 3小时 -94%

七、总结与工具链清单

这套体系并非银弹,它牺牲了部分灵活性(如不允许直接从本地Push main),但换取了极高的可追溯性。对于20人左右的团队,这可能是性价比最高的Git协作模式。

核心工具清单:

  • Git 2.39+(原生支持git switch
  • GitLab 15.x(保护分支、MR审批、Push Rules)
  • Jenkins 2.4x(Pipeline + 多分支构建)
  • Husky 8.x(本地Git钩子管理,强制lint与commit规范)

最后建议:不要盲目复制别人的分支模型,先花两周记录团队真实的开发痛点(冲突频率、发布焦虑来源),再定制化你的策略。规则少而严格,比多而松散更有效。