Git 历史改写:rebase / cherry-pick / stash
更新时间:2026-08-25。本篇覆盖三条"高阶"命令:
git rebase(变基与整理)、git cherry-pick(搬运提交)、git stash(临时保存现场)。它们共同点是在提交与分支之间重组内容,但也都是改写历史的主力,使用须谨慎。
1. git rebase:把提交"重放"到另一个基点上
基本变基
bash
git rebase main # 把当前分支的提交重放到 main 最新之上
git rebase <branch> <dest> # 显式指定
变基后 feature 分支的提交 D'/E' 是全新哈希(内容相同但父提交变了)。历史呈现为一条直线,没有合并分叉。
rebase vs merge
| 维度 | git merge | git rebase |
|---|---|---|
| 历史形态 | 保留分叉 + 合并提交 | 线性,无分叉 |
| 提交哈希 | 原提交不动 | 被重放的提交全部重写 |
| 适用 | 需要保留"真实协作轨迹" | 追求整洁线性的历史 |
| 共享分支上使用 | ✅ | ❌(黄金法则) |
黄金法则:不要 rebase 已经推送到共享分支的提交。rebase 会重写哈希,他人基于旧哈希的合并会变成"平行宇宙",只能靠 force push 强行覆盖——这是协作事故的主要来源之一。
交互式变基:整理提交
bash
git rebase -i HEAD~3 # 整理最近 3 个提交进入编辑器后每行对应一个提交,可改前缀:
| 前缀 | 含义 |
|---|---|
pick | 保留(默认) |
reword | 保留改动,改写提交信息 |
edit | 停下来,修改内容后再继续 |
squash | 合并进上一个提交(提交信息可合并) |
fixup | 合并进上一个提交(丢弃其信息,推荐) |
drop | 删除该提交 |
text
pick 8f3a2b1 feat: 导出功能
fixup d4e5f6a 修复导出格式 # 并进上一个,历史只剩一条
reword a1b2c3d docs: 补充说明 # 改这条的提交信息bash
git rebase --continue # 冲突解决后继续
git rebase --abort # 放弃本次变基,回到之前状态
git rebase --skip # 跳过当前提交冲突处理(与 merge 类似)
rebase 冲突时:编辑冲突文件 → git add <file> → git rebase --continue。区别在于每个提交都可能产生一次冲突,需要逐个重放。
2. git cherry-pick:搬运指定提交
bash
git cherry-pick <commit> # 把某提交的改动应用到当前分支
git cherry-pick A..B # 批量搬运一段区间
git cherry-pick -n <commit> # 只暂存改动,不生成提交
git cherry-pick --abort # 放弃典型场景:
- 修复提交打在了 main,需要同步到 release 分支(同一次修复多处生效);
- 从别的分支挑一个"独立的小改动",不想拉整个分支。
bash
git switch release-1.x
git cherry-pick 8f3a2b1 # 把 main 上的修复搬过来cherry-pick 本质是一次"单提交的变基",也会产生新哈希。若原提交已推送,两处哈希不同是正常现象。
3. git stash:临时保存工作现场
bash
git stash # 保存工作区+暂存区的改动(工作区回到干净)
git stash -u # 连同未跟踪文件一起保存(常用)
git stash list # 查看 stash 列表
git stash show stash@{0} # 查看某个 stash 的改动
git stash pop # 恢复最近一次并删除该记录
git stash apply # 恢复但不删除(可多次应用)
git stash drop stash@{1} # 丢弃某个 stash
git stash branch <name> # 基于该 stash 创建分支(恢复并检出)适用场景:
- 切分支/拉代码前,工作区有未完成改动不想提交;
- 需要临时试验另一套改动,又不想污染当前工作。
bash
git stash -u # 1. 保存现场
git switch main && git pull --rebase # 2. 处理其他事
git switch feature/x
git stash pop # 3. 恢复现场stash 记录存在
.git内部,不随分支走,git stash list全局可见。pop时若有冲突,会保留 stash 记录,解决后手动git stash drop。
三者对比
| 命令 | 本质 | 是否改历史 | 典型场景 |
|---|---|---|---|
rebase | 重放一批提交到新基点 | 是 | 同步主线、整理提交 |
cherry-pick | 重放一个/一段提交 | 是(新哈希) | 跨分支搬修复 |
stash | 暂存工作区快照 | 否 | 切分支前临时保存 |
易错点
- rebase 后 force push:私有分支 rebase 后可
push --force-with-lease;共享分支绝不 rebase。 rebase -i里乱删pick:drop会真正移除提交内容,确认该提交无独有改动。stash pop与stash apply混淆:pop 会删除记录,apply 保留;不确定时用apply先验证。- stash 冲突后忘记 drop:
pop失败时记录仍在,git stash list确认残留并清理。
一句话总结:rebase 把分支历史捋直、cherry-pick 把修复搬来搬去、stash 把现场暂存起来——三者的共同纪律是"改写历史只发生在自己的私有分支上"。