一、问题的爆发:从5人到20人,主干开发成了灾难
去年Q3,我们团队从5人扩张到20人,前后端、算法全在一个仓库里。最初大家遵循简单的“主干开发+特性分支”模式,但很快问题集中爆发:
- 冲突地狱:每周平均17次合并冲突,大部分源自多人同时修改配置文件或公共组件。
- 发布失控:主干上的代码永远处于“半成品”状态,每次发版都要从一堆未完成的功能中手工挑拣,导致发布推迟1-2天。
- Review形同虚设:因为分支存活时间短(平均2小时),代码评审沦为“看一下有没有语法错误”,核心逻辑无人把关。
我们最初尝试过Git Flow,但发现其develop分支的长期存在反而加剧了集成延迟。最终决定设计一套“短生命周期特性分支 + 受控主干”的混合策略。
二、环境与版本基线
- Git:2.39.1(重点使用
git switch和git restore命令) - GitLab:15.7.5(社区版,启用Push Rules和Merge Request Approvals)
- Jenkins:2.414.2(Pipeline DSL)
- 仓库规模:1200+文件,平均每天合并请求(MR)50+个
三、分支策略设计:三条命脉
我们最终落地了三条核心分支:
main:唯一长期分支,所有代码必须经过MR合并。feature/*:从main切出,命名规范feature/issue-编号-简短描述。存活周期不超过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任务:
- 静态检查:
eslint+tsc --noEmit(前端),flake8(Python)。 - 单元测试:
pytest与jest,覆盖率阈值设于80%(基于coverage插件)。 - 构建验证:
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规范)
最后建议:不要盲目复制别人的分支模型,先花两周记录团队真实的开发痛点(冲突频率、发布焦虑来源),再定制化你的策略。规则少而严格,比多而松散更有效。