1. 问题背景:当feature分支变成定时炸弹

2023年初,我们团队从3人扩张到12人,代码库从单一模块拆分为5个服务。混乱随之而来:每周平均15次合并冲突,release分支经常带着未完成的feature上线,紧急hotfix要在三个分支间手动cherry-pick。最崩溃的一次,某同事把本地未推送的提交直接覆盖了远程master,导致全组停工半天。

版本控制不是git push就完事,它需要一套规则。

2. 环境与版本

  • Git版本:2.39.1(强制要求,低于2.33的不支持git switch
  • 代码托管:Gitea 1.19.3(内网部署,兼容GitHub API)
  • CI/CD:Gitea Actions(兼容GitHub Actions语法,runner版本1.4.0)
  • 基础镜像:Node.js 18-alpine + Go 1.21

3. 方案设计:Trunk-Based + 短生命周期分支

我们最终放弃纯Git Flow(太重)和纯GitHub Flow(没有release管理),采用混合模型:

  • master分支:始终可部署,只接受merge请求
  • feature分支:命名feature/xxx-描述,存活时间不超过3天
  • release分支:命名release/v1.x.x,从master切出,只允许bugfix
  • hotfix分支:命名hotfix/xxx-描述,从master切出,合并回master和当前release

关键规则:
- 任何分支合并必须通过PR(Pull Request)
- PR标题必须包含issue编号
- 禁止直接push到master和release(通过分支保护规则强制)

4. 核心实现:分支策略落地与自动化

4.1 分支保护配置(Gitea)

在Gitea仓库设置中,对master分支启用保护规则:

[protected_branch "master"]
    push_whitelist = ci-bot
    merge_whitelist = tech-lead
    enable_status_check = true
    require_signed_commits = false
    protect_branch = true

这段配置意味着:只有ci-bot账号能push到master,合并权限只给tech-lead角色。每个PR必须通过CI状态检查才能合并。

4.2 CI/CD流水线配置

.gitea/workflows/ci.yml核心部分:

name: CI Pipeline
run-name: CI for ${{ github.ref_name }} by @${{ github.actor }}

on:
  push:
    branches: [master, release/*]
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0  # 关键:获取全部提交历史
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: Install pnpm
        run: npm install -g pnpm@8.15.0
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
      - name: Run ESLint
        run: pnpm lint:check
      - name: Run unit tests
        run: pnpm test:coverage -- --reporter=json
      - name: Upload coverage
        uses: actions/upload-artifact@v3
        with:
          name: coverage-${{ matrix.node-version }}
          path: coverage/

  build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build Docker image
        run: docker build -t registry.internal/app:${{ github.sha }} .
      - name: Push to registry
        run: docker push registry.internal/app:${{ github.sha }}

流水线关键点:
1. fetch-depth: 0 必须设置,否则git diff无法计算变更范围
2. 测试矩阵覆盖Node 18和20,确保兼容性
3. 每次push到master或release分支都会触发构建,PR则只跑lint和test

4.3 Code Review流程

我们在PR模板中强制要求:

## 变更描述
- 关联issue: #
- 变更类型: [feat/fix/docs/refactor]
- 影响范围: [服务名]

## 自检清单
- [ ] 本地测试通过 (pnpm test)
- [ ] 代码符合ESLint规范
- [ ] 无未处理的TODO/FIXME
- [ ] 更新了API文档

## 测试计划
- 单元测试: 
- 集成测试: 
- 手动验证步骤: 

Review规则:
- 每个PR至少1个approve才能合并
- 技术负责人(tech-lead)对涉及数据库迁移的PR必须二次review
- 24小时内未review的PR,机器人自动@提醒

5. 踩坑与优化

5.1 坑:merge commit污染了master历史

现象:master上出现大量Merge branch 'feature/xxx'提交,历史变成一团乱麻。

解决:PR合并策略改为--squash,强制压缩为单个提交。Gitea中配置:

[repository.pull-request]
INPUT_SQUASH = true
INPUT_MERGE = false
INPUT_REBASE = false

这样master历史保持线性,配合git bisect定位问题效率提升50%。

5.2 坑:CI在PR阶段构建了两次

现象:每个PR触发两次CI,浪费大量算力。

原因pull_requestpush事件重复触发。

修复:在CI中增加条件判断:

jobs:
  build:
    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository

这里判断了如果是从fork仓库提交的PR,则跳过构建(因为我们不信任fork的代码),只跑lint和test。

5.3 坑:hotfix分支合回release时冲突

优化:hotfix分支从master切出,但合并时使用git merge --no-ff保留合并提交,并在release分支上定期从master拉取更新:

git checkout release/v1.2.0
git pull origin release/v1.2.0
git merge origin/master --no-ff -m "chore: sync latest fixes"

这个操作由CI自动执行,在release/*分支的流水线中加入同步步骤。

6. 效果数据

实施这套工作流4个月后:

指标 之前 现在
每周合并冲突次数 15次 2次
发布周期 2天 4小时
线上事故数 3次/月 0.5次/月
代码review覆盖率 30% 100%
PR平均响应时长 8小时 2.5小时

特别地,紧急hotfix从发现问题到上线,从原来的2小时缩短至25分钟(包含CI构建和自动部署时间)。

7. 总结与建议

这套方案的核心不是工具,而是共识。分支策略再完美,如果团队不遵守就是废纸。建议分三步推进:

  1. 先立规矩:从分支命名和PR模板开始,强制要求而非建议
  2. 自动化兜底:用分支保护、CI检查把规则固化到流程里
  3. 定期复盘:每月回顾一次流程,看数据说话,持续调整

版本控制的终极目标是让团队专注于业务代码,而不是和Git较劲。我们的下一步是引入git-lfs管理大文件,以及试验gitea + act_runner的缓存加速方案,预计能将CI时间再缩短30%。

别让你的代码成为下一个定时炸弹,从今天开始规范Git工作流吧。