1. 问题背景:从“能跑”到“失控”的Git之痛
2024年初,我们后端团队从5人扩张到30人,业务迭代速度翻倍。但Git仓库开始变得混乱不堪:develop分支平均每天有40+次提交,feature分支平均存活时间超过2周,Code Review经常被git push --force绕过,CI构建在develop上的失败率高达32%。最离谱的一次,一个同事把test环境的配置直接合到了main分支,导致线上事故。
我们意识到,问题不在工具,而在流程。Git本身不提供“团队协作规范”,它只提供命令。我们需要一套强约束的工作流,而不是依赖每个人的自觉。
2. 环境与版本基线
- Git版本:2.44.0(统一要求,避免老版本对
git switch等新语法支持不一致) - 托管平台:GitHub Enterprise Cloud(SAML SSO + 受管用户)
- CI平台:GitHub Actions(Ubuntu 22.04 Runner)
- 包管理器:pnpm 9.x(Monorepo结构,30+个内部包)
- 基础分支:
main(生产)、develop(预发)、release/*(版本分支)
我们放弃纯GitFlow(太重,不适合快速迭代),也放弃纯trunk-based(30人直接提交主干,Review压力太大)。最终选择混合模型:
- 短生命周期特性分支:
feat/xxx,基于develop,必须在3天内合并。 - 长生命周期版本分支:
release/2024.08.x,从develop拉出,只接受bug fix。 - 紧急修复分支:
hotfix/xxx,基于main,合并后同时同步到develop。
3. 方案设计:分支策略与强制规则
核心原则是:所有合并必须经过PR,所有PR必须通过自动化检查,所有检查必须包含人工Review。
我们通过GitHub的branch protection rules强制执行:
main和develop分支:禁止直接push,必须通过PR。- 要求
2个approval,且CODEOWNERS文件指定的核心成员必须approve。 - 要求所有CI check通过(包括单元测试、构建、代码扫描)。
关键设计:我们写了一个自定义的GitHub Action,在PR创建时自动检查分支命名规范和提交信息格式。
# .github/workflows/pr-validator.yml
name: PR Validator
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check branch name
run: |
BRANCH_NAME="${{ github.head_ref }}"
if [[ ! "$BRANCH_NAME" =~ ^(feat|fix|hotfix|release|docs|refactor)/ ]]; then
echo "❌ 分支命名必须以 feat/ fix/ hotfix/ release/ docs/ refactor/ 开头"
exit 1
fi
- name: Check commit message
run: |
COMMIT_MSG=$(git log --format=%s -1 "${{ github.event.pull_request.head.sha }}")
if [[ ! "$COMMIT_MSG" =~ ^(feat|fix|hotfix|docs|refactor|chore)(\([a-z]+\))?: ]]; then
echo "❌ 提交信息不符合 Conventional Commits 规范"
exit 1
fi
这个Action从根上杜绝了wip、asdf、fix bug这类无意义提交。
4. 核心实现:Code Review流程与本地钩子
Code Review流程:我们不仅要求看代码,还要求看变更范围。用Git自带的能力生成差异报告,让Reviewer快速定位风险区。
在PR描述中,我们强制模板包含:
## 变更范围
- [ ] 数据库迁移
- [ ] API 契约变更
- [ ] 依赖升级
- [ ] 配置修改
## 影响分析
- 受影响服务:`user-service`, `order-service`
- 性能影响:新增索引`idx_order_created_at`,预计查询耗时下降40%
本地钩子:为了减轻CI压力,我们在本地强制跑lint-staged。注意,这里有一个关键细节:husky在Git 2.44下需要v9+版本,否则prepare脚本会失效。
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged --concurrent false
// package.json
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write",
"git add"
],
"*.{json,md,yaml}": [
"prettier --write",
"git add"
]
}
}
这里有个坑:最开始我们设置了concurrent: true,导致ESLint和Prettier同时修改文件,产生大量冲突。改为false后,按顺序执行,冲突率下降90%。
5. CI/CD集成:一条流水线,三个环境
我们的CI流水线设计为三段式:test → build → deploy。在GitHub Actions中,通过environment实现环境级保护。
# .github/workflows/ci.yml (核心片段)
name: CI Pipeline
on:
pull_request:
branches: [develop, main]
push:
branches: [develop, main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 关键:拉取全部历史,用于增量测试
- uses: pnpm/action-setup@v4
with:
version: 9.12.0
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run unit tests with coverage
run: pnpm test -- --coverage --maxWorkers=2
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
build:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build all packages
run: pnpm build
- name: Check bundle size
run: |
MAX_SIZE_KB=500
BUNDLE_SIZE=$(du -sk dist | cut -f1)
if [ "$BUNDLE_SIZE" -gt "$MAX_SIZE_KB" ]; then
echo "❌ Bundle size $BUNDLE_SIZE KB exceeds limit $MAX_SIZE_KB KB"
exit 1
fi
deploy:
needs: build
runs-on: ubuntu-latest
environment: production # 这里关联GitHub Environment保护规则
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to AWS ECS
run: |
aws ecs update-service --cluster production --service api-gateway --force-new-deployment
踩坑记录:fetch-depth: 0一开始没加,导致pnpm在Monorepo下无法正确计算变更包,--filter参数失效。加了之后,增量构建时间从4分30秒降到1分50秒。
6. 效果数据与总结
重构三个月后,数据对比如下:
| 指标 | 重构前 | 重构后 | 提升 |
|---|---|---|---|
| develop分支CI通过率 | 68% | 94% | +26% |
| 平均PR存活时间 | 3.2天 | 1.1天 | -65% |
| 代码冲突次数/周 | 15次 | 3次 | -80% |
| 线上事故次数/季度 | 4次 | 1次 | -75% |
最大的感悟:技术债不只是代码里的,流程债更可怕。Git工作流不是选一个模板就完事,而是要根据团队节奏、发布频率、人员水平做定制裁剪。我们的混合模型不一定适合所有人,但强约束自动化的方向一定是对的——把规则写进CI,而不是指望人记住。
最后给同行的建议:如果你的团队超过15人,请立刻检查你的branch protection rules是否覆盖了所有受保护分支,你的CI是否在PR阶段就跑全量测试,你的Review是否只看了Files changed而忽略了Commits。这些细节,决定了你是在用Git,还是被Git用。