Skip to main content

Git 分支策略与 rebase 实践

分支策略没有对错,只有「和发布节奏是否匹配」。rebase 与 reset 的区别则是协作里最容易用错、也最容易出事的一对。

一、主干开发与分支发布​

两种主流策略按发布节奏选。Trunk-based(主干开发)是短分支、高频合入主干、靠 CI 与特性开关控制发布,适合一天多次上线的持续交付。Git Flow 是 develop / release / hotfix 长分支、按版本发布,适合版本制、有明确发版窗口的团队。

没有哪种更「规范」——一天发十次的团队用 Git Flow 会被流程拖死,一个季度发一版的团队用 trunk-based 会失去发版缓冲。策略要匹配发布节奏,而不是反过来。

rebase 前:特性分支从旧基点分出C1C2C3特性基点F1F2C4main 在此期间又前进了rebase 后:F1 / F2 被复制成新提交,接到 main 顶端C1C2C3C4F1′F2′
图:rebase 改写的是提交对象本身——旧提交仍在 reflog 里,公共分支上强推会覆盖他人工作

二、重写历史的具体动作​

rebase 的本质是把一串提交在新的基底上重放一遍:找到共同祖先,计算差异,逐个应用,生成新的提交对象。所以历史变线性了,但提交 ID 全变了——这正是它的风险来源。理解「生成新对象」这一点,就能理解为什么已经推送到公共分支的提交不能 rebase:别人基于旧对象的工作会全部错位。

# 把当前分支的提交重放到 main 之上,得到线性历史
git rebase main

# 交互式 rebase:合并、改信息、调顺序,都在本地未推送的提交上做
git rebase -i HEAD~3

三、公开分支上的代价​

风险只有一条原则,但代价很大:不要对已推送且他人基于其工作的分支做 rebase。一旦你 rebase 了公共分支并强推,别人的本地历史就和远端分叉,他们再拉取会冲突、会重复提交,甚至把被你「改写掉」的旧提交又推回来。

多人协作分支上 rebase 还会导致历史分叉,需要强推才能同步——而强推公共分支是绝大多数团队禁止的操作。判断标准就一句:这些提交有没有被别人拿到。没有,随便 rebase;有,只能用别的方式(见下一节)。

如果真的需要对已推送的私人分支 rebase(比如整理刚推上去、还没人拉的提交),用 git push --force-with-lease 而不是 --force:前者会先检查远端有没有别人的新提交,有就拒绝覆盖,避免误删协作者的工作。但能不推就别推——私人分支的 rebase 永远在推送前做最安全。

四、两种回退的差别​

revert 生成一个反向提交,历史完整保留,可以再被 revert,适合已经推送的改动。reset 直接把分支指针移回某处,之后的提交从分支历史上消失,只适合本地还没推送的改动。判断标准就一句:这个改动有没有别人已经拿到——有的话只能 revert。

# revert:新增反向提交,历史不丢,适合已推送
git revert <commit>

# reset:移动指针,之后的提交从分支历史消失,只适合本地
git reset --soft <commit> # 保留改动到暂存区
git reset --hard <commit> # 连工作区一起丢,用前先确认

reset --hard 会丢工作区改动,用前先确认;即使如此,被 reset 掉的提交在 reflog 里还会保留一段时间,可以找回,但不要依赖 reflog 当备份——它会被垃圾回收清掉。

分支保护之外,提交前卡一次自检能省掉大量返工:

# .pre-commit-config.yaml(或 lint-staged):只校验暂存文件,速度快
repos:
- repo: local
hooks:
- id: typecheck
name: typecheck
entry: npx tsc --noEmit
language: system
pass_filenames: false

五、团队层面的约束​

靠机制比靠自觉可靠。几条通用约束:保护主干(禁止强推,必须走 PR 与 CI)、合并策略统一、提交信息规范。

合并策略常用 squash merge:把一个 PR 的多个提交压成一个,主干历史粒度与一个 PR 对应,回滚时能一眼定位到那个改动。它和「保留完整提交历史」是两种取向,选哪种看团队偏好,但必须统一,不要时 squash 时 merge commit。

# 合入前把特性分支压缩成一个提交,主干历史更干净
git checkout main
git merge --squash feature/login
git commit -m "feat(login): 支持手机号一键登录"

分支保护(无论用哪个平台,本质都是「禁止直接推主干、必须 review 通过且 CI 通过才能合入」)是最后一道闸,比任何约定都稳。

还有一条被低估的约束:分支生命周期越短越好。活过一周的特性分支几乎必然和主干分叉、冲突成堆;trunk-based 的核心就是「分支是小时级的、靠特性开关而不是长分支隔离」。分支策略的复杂度应该匹配团队当前规模,而不是两年后的设想——简单的流程被所有人遵守,胜过复杂的流程没人照做。

整理提交时最常用的是交互式 rebase,几个动作记住就够:

# 交互界面里:pick 保留 / squash 并入上一个 / reword 改信息 / drop 丢弃
git rebase -i HEAD~5

# 只改最近一条的提交信息
git commit --amend -m "fix: 修正订单金额取整"

# 把特性分支的提交搬到一个干净的起点上
git rebase --onto main old-base feature

六、rebase 不只是让图好看​

rebase 会改写提交对象,不是单纯的「整理历史」——公共分支上强推会覆盖他人的工作。

reset 之后提交也不是彻底没了,reflog 里还在,一段时间内可恢复,但不要依赖它。

分支策略同样不是越复杂越规范:策略要匹配发布节奏,短分支高频合入往往比长分支更稳。

工具是次要的,团队对「历史能不能改」有没有共识才是主要的。

rebase 的交互命令与风险点见 git-rebase 官方文档,--onto 那节在整理分支时特别有用。