1. 从一次事故说起:分支策略为什么重要
去年Q2的一个周四下午,我正盯着监控面板,突然报警声响起——线上订单系统响应超时。追溯后发现,一位同事将未经过review的hotfix代码直接合到了master,而这条hotfix分支是从一个旧的develop上拉出来的,里面藏着三天前被回滚的一个bug。更糟的是,由于没有CI触发单元测试,这个bug在合并后直接上线。
那次事故让我意识到:没有纪律的分支策略,就是团队协作的定时炸弹。之后我们花了三周重新设计工作流,核心目标是:
- 减少合并冲突(冲突率从35%降到8%)
- 保证每次合并都经过自动化验证
- 让发布节奏可预测
我们最终采用的方案是 Git Flow + Trunk-based 混合模型,下文会详细拆解。
2. 环境与版本:我们用的工具链
- 代码托管:GitHub (Enterprise 3.8)
- CI/CD:GitHub Actions (runs-on: ubuntu-latest)
- 分支保护:GitHub Branch Protection Rules
- 语言/框架:Java 17 + Spring Boot 3.0,但策略通用
- 团队规模:50人,4个前端组 + 3个后端组 + 2个SRE组
版本参考:
- Git 2.39
- actions/checkout@v4
- actions/setup-java@v3 (Java 17)
3. 方案设计:三主干两辅助的分支模型
我们放弃了纯Git Flow(太重,release分支常常滞留一周),也放弃了纯Trunk(对50人团队来说,直接提交trunk太激进)。最终方案是:
永久分支(3个):
- main:生产分支,只有经过CI+Code Review+手动审批的PR才能合并。每次合并自动触发部署到生产环境。
- develop:集成分支,所有feature/分支合并到这里。每天自动部署到测试环境。
- release/next:预发布分支,从develop拉出,冻结后只允许bugfix,不允许新功能。用于UAT验收。
临时分支(3类):
- feature/xxx:从develop拉出,命名如feature/order-export-v2。开发完成后合并回develop。
- bugfix/xxx:从develop或release/next拉出,修复非紧急问题。
- hotfix/xxx:从main拉出,修复生产紧急bug,必须经过双人review,且合并后同步回develop和release/next。
关键约束:
1. 禁止直接push到main/develop/release,只能通过PR。
2. PR必须通过CI检查(编译+单元测试+代码规范扫描),且至少1个Approval。
3. release/next分支生命期不超过3天,否则强制回退并复盘。
4. 核心实现:分支保护 + CI/CD配置
4.1 分支保护规则(GitHub设置)
在 Settings -> Branches -> Add rule 中,对 main 和 develop 分别配置:
# main分支保护规则(UI配置,此处用YAML示意)
Branch name pattern: main
Require a pull request before merging: true
Required approvals: 2
Dismiss stale pull request approvals when new commits are pushed: true
Require status checks to pass before merging: true
Status checks:
- build-and-test (必须通过)
- codeql-analysis (必须通过)
Require branches to be up-to-date before merging: true
Include administrators: true
4.2 CI/CD流水线(可运行的GitHub Actions配置)
下面是我们后端Java项目的完整部署配置。注意两点:
- 缓存策略:actions/cache 缓存Maven依赖,将构建时间从4分钟压缩到1分钟。
- 环境变量注入:通过GitHub Secrets传递数据库密码等敏感信息。
文件名:.github/workflows/deploy.yml
name: Build, Test & Deploy
on:
pull_request:
branches: [ develop, main, release/next ]
push:
branches: [ develop, main, release/next ]
env:
JAVA_VERSION: '17'
MAVEN_OPTS: '-Xmx2g -XX:+TieredCompilation -XX:TieredStopAtLevel=1'
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v3
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: 'temurin'
cache: maven
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build and test
run: mvn clean verify -DskipIntegrationTests=false
env:
DB_URL: ${{ secrets.TEST_DB_URL }}
DB_USER: ${{ secrets.TEST_DB_USER }}
DB_PASS: ${{ secrets.TEST_DB_PASS }}
- name: Upload test report
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: target/surefire-reports/
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker tag myapp:${{ github.sha }} myregistry.azurecr.io/myapp:latest
- name: Push to registry
run: |
docker login myregistry.azurecr.io -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }}
docker push myregistry.azurecr.io/myapp:latest
- name: Deploy to production (K8s)
run: |
kubectl set image deployment/myapp myapp=myregistry.azurecr.io/myapp:latest --namespace=production
kubectl rollout status deployment/myapp --namespace=production --timeout=5m
4.3 Code Review流程(附Checklist)
我们规定每个PR必须附带以下检查:
[ ] 功能是否与需求文档一致
[ ] 是否包含单元测试(覆盖率不低于80%)
[ ] 是否处理了异常边界(空指针、超时、并发)
[ ] 日志是否打印了关键参数和异常栈
[ ] 是否修改了数据库Schema(需要DBA审批)
[ ] 是否有性能影响(循环内调用RPC等)
评审面板使用GitHub内置的审查功能,要求至少2个Approval才能合并(main分支要求3个)。我们还会在PR描述中自动生成变更摘要,使用 actions/github-script 提取commit message。
5. 踩坑与优化:我们改过的三个问题
5.1 合并冲突的“隐形成本”
初期,feature分支存活时间超过5天,合并到develop时常常有10+个文件冲突。解决冲突平均耗时40分钟。
优化:引入“分支存活时间约束”——feature分支超过3天未合并,自动在Slack提醒作者;超过5天强制rebase到最新develop。配合 git merge --no-ff 保留合并记录。
5.2 release分支的“僵尸化”
之前release/1.0分支发布后没人清理,导致一个月后有人误以为它还是活跃分支。
优化:在CI中添加分支清理脚本,release分支在合并到main后自动删除(通过GitHub API)。同时,每次创建release分支时,自动创建对应的Git Tag(如v1.0.0)。
5.3 CI时间太长导致开发等待
早期CI跑完整测试套件(包括集成测试、端到端测试)需要12分钟。开发者提交PR后往往切到其他任务,导致review延迟。
优化:分层流水线——PR阶段只跑单元测试(3分钟),合并到develop后跑集成测试(5分钟),合并到main后跑端到端测试(10分钟)。这样PR反馈速度从12分钟降到3分钟。
6. 效果数据:数字不会说谎
推行该混合工作流6个月后,我们统计了以下指标(对比传统Git Flow时期):
| 指标 | 优化前 | 优化后 | 改善幅度 |
|---|---|---|---|
| 每月合并冲突数 | 12~18次 | 3~5次 | -72% |
| 代码从提交到进入release分支时间 | 2天 | 4小时 | -91% |
| 线上事故(因分支问题) | 3次/月 | 0次/月(最近3个月) | -100% |
| 开发者对工作流满意度(匿名问卷) | 3.2/5 | 4.6/5 | +44% |
最直接的变化是:发布日不再让人焦虑。以前每周五下午发版,大家盯着Slack等到晚上8点;现在每天可以发版2~3次,每次都是经过自动化验证的增量变更。
7. 总结:哪些可以复用,哪些需要定制
如果你也在规划团队的工作流,建议从以下几点入手:
1. 分支越少越好:3个永久分支足矣,不要为了“灵活”而创建多余分支。
2. 自动化是纪律的保障:没有CI的分支策略只是纸上谈兵,GitHub Actions或GitLab CI都可以。
3. Code Review不是走过场:规定Checklist,强制双人Approval,否则不合并。
4. 定期复盘:每两周一次“分支健康度”会议,检查是否有僵尸分支、合并冲突趋势、CI通过率。
最后,没有银弹。如果你团队只有5个人,纯Trunk-based可能更适合;如果是100人以上,可能需要考虑Git Flow的变体(比如加上support分支)。关键是找到团队最能坚持执行的方案——我们选择混合模型,是因为它平衡了隔离性和速度,而数据证明这对我们有效。
你的团队现在用什么分支策略?遇到过最头疼的问题是什么?欢迎在评论区讨论。
本文涉及的配置文件均来自生产环境,已脱敏处理。具体参数(如超时时间、缓存策略)请根据项目实际情况调整。