见过太多团队在两种极端之间摇摆:要么所有人往 main 直接推,要么搞出 develop / release / hotfix / feature 四层分支最后没人记得住规则。

小团队真正需要的只有三件事:主干随时可发布、每个改动可追溯、回滚成本低。

一、只留两条长期分支

main       ──●────────●────────●──→   随时可发布,受保护
             ╲       ╱ ╲      ╱
feature/*     ●──●──●   ●──●──●        短期分支,合并后删除
  • main:受保护分支,禁止直接 push,必须走 PR + 至少一个 approve
  • feature/xxx:从 main 切出,生命周期不超过 3 天

不需要 develop。持续交付的团队里,develop 只会让「已经完成但还没上线」的代码堆在那里,增加合并冲突。

二、功能分支怎么保持同步

分支活得越久,合并冲突越痛苦。规则是:每天至少同步一次 main。

git fetch origin
git rebase origin/main      # 把自己的提交「挪」到最新 main 之上

rebase 的好处是历史是线性的,没有一堆「Merge branch 'main' into feature/xxx」的噪音提交。

如果 rebase 后需要强推(仅限自己的功能分支):

git push --force-with-lease

务必用 --force-with-lease 而不是 --force。前者会在远端有你不知道的新提交时拒绝推送,避免覆盖同事的工作。

⚠️ 铁律:永远不要 rebase 已经合并进 main 或者多人共享的分支。共享分支用 merge,私有分支用 rebase。

三、合并到 main 用 squash

功能分支里可能有「修个 typo」「再试一次」「按评审意见改」这类提交。合进主干时压成一个:

# GitHub / GitLab 合并按钮选 "Squash and merge"

这样 main 上每个提交都是一个完整的、可回滚的功能单元:

a1b2c3d feat(post): 支持 Markdown 表格与任务列表
d4e5f6a fix(auth): 修正会话过期时间计算错误
9h8i7j6 perf(list): 列表查询改用覆盖索引

提交信息的规范(Conventional Commits):

<type>(<scope>): <描述>

type: feat | fix | docs | style | refactor | perf | test | chore

好处是能自动生成 changelog,也方便按类型筛选。

四、评审是流程的核心

PR 的合理大小:400 行以内。超过 800 行的 PR,评审质量会断崖式下降——没人会认真读完。

一个有效的 PR 描述模板:

## 做了什么
把首页文章列表查询从全表扫描改为覆盖索引。

## 为什么
线上 p95 从 40ms 涨到 820ms,见监控链接。

## 怎么验证
- [x] 本地跑通列表、搜索、分页
- [x] 新增 3 个查询单测
- [x] 压测对比:p95 820ms → 12ms

## 风险
需要执行 `0002_add_sort_at.sql` 迁移,回滚方案是删掉索引。

评审者的关注顺序应该是:正确性 → 边界条件 → 可读性 → 风格。不要在风格上花 80% 的评审时间,交给格式化工具。

五、常用的补救命令

改最后一次提交信息:

git commit --amend

把多个提交合成一个:

git rebase -i HEAD~3      # 把后两个的 pick 改成 squash

撤销已经推送的提交(安全做法):

git revert <sha>          # 生成一个反向提交,历史完整保留

误把敏感文件提交了:

# 从历史里彻底删除(重写历史,需要全员重新拉取)
git filter-repo --path .env --invert-paths
# 然后立刻轮换所有泄露的密钥 —— 这一步不能省

找回被 reset 掉的提交:

git reflog                # 找到 sha
git branch rescue <sha>

reflog 是最后的保险绳,几乎所有「提交丢了」都能救回来。

六、自动化守着底线

# .github/workflows/ci.yml
name: CI
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: pnpm }
      - run: pnpm install --frozen-lockfile
      - run: pnpm run lint
      - run: pnpm run typecheck
      - run: pnpm test

CI 不绿不允许合并。把「能不能合」交给机器,把「该不该合」交给人。

一张规则速查表

场景 用什么
从 main 拉功能分支 git switch -c feature/x main
同步主干改动 git rebase origin/main(私有分支)
推送被 rebase 过的分支 git push --force-with-lease
合并到主干 Squash merge
共享分支同步 git merge(不要 rebase)
撤销线上提交 git revert
找回误删提交 git reflog

小结

分支策略的目标不是「看起来专业」,而是让这三件事变简单:看历史能懂、出问题能回滚、多人协作不互相踩。做到这三点,两条分支就够了。