撤销修改
Git 中的撤销可能是复制快照、移动引用,也可能是创建反向修改的新提交。git restore、git reset、git revert 最后能得到相同文件内容,却留下不同索引、分支历史、对象数和恢复指针。
工作区、索引、HEAD -> main 所指提交是三个不同位置。restore 复制文件版本;reset 移动分支,再按模式恢复文件层;revert 新增纠正记录。文件内容相同,不代表历史相同。
三种撤销如何工作
git restore app.txt 默认从索引覆盖工作区,不移动 main、不改索引、不创建对象。加 --staged 后,默认来源变成 HEAD 树,目标变成索引。restore 处理文件层,不改提交历史。
git reset <target> 先解析目标,将旧末端保存到 ORIG_HEAD,再移动 main,模式决定后续复制:
| 模式 | 移动 main | 目标复制到索引 | 目标复制到工作区 | 新提交 |
|---|---|---|---|---|
--soft | 是 | 否 | 否 | 否 |
--mixed | 是 | 是 | 否 | 否 |
--hard | 是 | 是 | 是 | 否 |
soft 可移动分支而保留原索引及工作文件;mixed 再替换索引;hard 还覆盖已跟踪工作文件。ORIG_HEAD 只保存提交 ID,不备份从未暂存或提交的编辑。
git revert HEAD 则比较选中提交与父,计算逆向修改,应用到索引和工作区,再创建新提交并推进 main。被撤销提交仍是祖先。revert 不向后移动分支,而是用新快照记录纠正。
撤销修改逐步演示
默认从未诞生的 main 开始:
edit app.txt只在工作区写mode=safe。git add app.txt保存 blob,让索引指向它。git commit -m "base"写索引树和根提交 C1,再推进 main。三个文件层都为mode=safe。- 第二次编辑只把工作区改为
mode=draft。 git restore app.txt从默认来源索引复制mode=safe,draft 消失,main 和索引不变。- 再编辑工作区为
mode=unsafe。 - add 保存 unsafe blob 并更新索引。
git commit -m "bad-change"创建父为 C1 的 C2,再推进 main,三层都是 unsafe。git reset --soft HEAD^解析 C1,将 C2 写入 ORIG_HEAD,main 后退到 C1。索引和工作区仍为 unsafe,所以相对新 HEAD 已暂存修改。git reset --soft ORIG_HEAD先解析保存的 C2,再用 C1 覆盖 ORIG_HEAD,并把 main 前进到 C2。两文件层不变。必须先读目标再覆盖恢复指针,否则会重置到错误提交。git revert HEAD确认索引、工作区都匹配 C2,由 C2 与 C1 求出mode=unsafe -> mode=safe并应用。可复用 C1 的树,只新增提交 C3,不创建新 blob 或树。main 进到 C3,C2 作为其父仍可达。
最终文件与 C1 同为 safe。hard reset 到 C1 也能得到该内容,但分支向后且只存两个提交;revert 则让分支向前到第三个提交,父还是错误提交。单看文件无法判断操作历史。
每种命令写入哪里
| 命令 | 工作区 | 索引 | main | 对象库 |
|---|---|---|---|---|
restore app.txt | 从索引复制 | 不变 | 不变 | 不变 |
restore --staged app.txt | 不变 | 从 HEAD 复制 | 不变 | 不变 |
reset --soft | 不变 | 不变 | 移动 | 不变 |
reset --mixed | 不变 | 从目标复制 | 移动 | 不变 |
reset --hard | 从目标复制 | 从目标复制 | 移动 | 不变 |
revert HEAD | 逆向结果 | 逆向结果 | 前进 | 本轨迹新增一个提交 |
单文件引擎中,复制或哈希 B 字节为 O(B);单父邻接目标 HEAD^ 的解析为 O(1);分类 main 可达提交为 O(V + E)。这是浏览器模型的成本,不是 Git 索引、pack、版本遍历器或文件系统的性能保证。
边界情况
- 未诞生 HEAD 没有树,
restore --staged无默认来源。 - 根提交没有第一父,
HEAD^解析失败,不等于空树。 - ORIG_HEAD 在本模型首次成功 reset 前未设置。未知或有歧义的短 ID 也会失败且不修改状态。
- 目标已与来源相同,restore 无变化,不额外写对象或引用。
- 索引或工作区与 HEAD 不同时,本模型拒绝 revert,避免模拟未实现的冲突流程。
- hard reset 能丢弃 Git 从未保存成对象的工作字节,提交恢复指针不能恢复这些编辑。
常见错误
- 说 restore 移走文件。它复制快照到目标,来源仍在。
- 说 reset 删除提交。它移动引用,旧提交可能仍由 ORIG_HEAD 等指向,对象清理另行处理。
- 把 ORIG_HEAD 当 reflog。前者只保存一个提交 ID,后者是独立的引用更新历史。
- 说 revert 存补丁对象。它计算逆修改,再把结果树和父记录为新提交。
- 因为提交可恢复,就认为 hard reset 的全部结果都可恢复。被覆盖且未存储的工作编辑可能无法找回。
模型范围
本课只有本地仓库、附着 HEAD -> main、一个普通已跟踪文件、单父历史,展示整文件 restore、三种 reset 和干净的 revert HEAD。身份固定,使用真实 Git 格式 SHA-1 ID。
省略路径集合、补丁模式、删除、重命名、文件模式、未跟踪文件阻挡、分离 HEAD、多分支、合并提交、revert -m、旧提交冲突、revert 序列器、钩子、签名、远程、reflog 过期、垃圾回收及编辑器或文件系统恢复。实际操作的选项和覆盖行为见 git restore、git reset、git revert。