学习前提

理解暂存区、分支与共享历史;先在临时仓库比较撤销方式。

使用Git 2.35+与Bash兼容终端(Windows可用Git Bash)。每章完整代码都会创建独立临时目录;仅在练习仓库运行。git config仅修改该仓库,示例身份不是你的真实提交身份。临时目录不会自动删除,可在完成后自行清理。

本课完成后你能做什么

按修改所在位置选择撤销方式,保留可追踪历史。

先理解机制

先判断修改是否暂存、是否提交、是否共享。restore --staged把暂存内容退回工作区保留的状态;普通restore覆盖工作文件,会丢弃相应未提交编辑。两者不能混淆。

revert通过新提交反向应用已有提交的改动,适合记录共享历史中的修正。它不会删除原提交,也可能发生冲突。reset移动引用等行为不同,尤其--hard会同时覆盖索引和工作区。

reflog记录本地引用曾经指向的位置,可帮助定位误操作之前的提交,但不是永久云端备份,也无法保证恢复从未被Git记录的工作内容。

可运行示例

lab=$(mktemp -d)
cd "$lab"
git init -b main .
git config user.name "Honeysuck Learner"
git config user.email 'learner'@'example.invalid'
git config commit.gpgsign false
git config core.autocrlf false
printf 'Start learning\n' > notes.txt
git add notes.txt
git commit -m "Start learning notes"
printf 'Draft only\n' >> notes.txt
git add notes.txt
git restore --staged notes.txt
git diff -- notes.txt
# The following line intentionally discards this practice-only draft.
git restore notes.txt
printf 'Wrong advice\n' >> notes.txt
git add notes.txt
git commit -m "Add advice to undo"
git revert --no-edit HEAD
cat notes.txt
git log --oneline -3
git reflog -3

推演与验证

第一次restore --staged后Draft only仍在工作区。随后明确丢弃练习草稿。revert后文件回到Start learning,但历史含原始、错误、撤销三次提交。

常见错误与边界

不要在真实未备份工作上尝试丢弃命令。撤销合并提交需要理解主线父提交,不能照搬本课针对普通单父提交的revert。

动手练习

只撤销暂存而保留文字时,应该停在哪一步?

参考解法与理由

停在git restore --staged notes.txt之后。用git diff确认工作区文字仍在,再决定修改或重新add;不要继续普通restore。

深入实验:恢复旧文件与撤销共享提交

共享历史中发现一个错误提交时,revert创建一个新的反向提交,保留原有提交链。restore只是把选定来源的文件内容写到目标位置,默认不会创建提交。下面用一次可观察的历史实验比较二者,不执行reset --hard。

lab=$(mktemp -d)
cd "$lab"
git init -q -b main
git config user.name "Honeysuck Learner"
git config user.email 'learner'@'example.invalid'
git config commit.gpgsign false
git config core.autocrlf false
printf 'correct\n' > answer.txt
git add answer.txt
git commit -q -m "Correct answer"
printf 'wrong\n' > answer.txt
git add answer.txt
git commit -q -m "Mistake"
bad=$(git rev-parse HEAD)
git revert --no-edit "$bad" >/dev/null
cat answer.txt
git rev-list --count HEAD
git merge-base --is-ancestor "$bad" HEAD && printf 'Original commit retained\n'
git restore --source="$bad" --worktree answer.txt
cat answer.txt
git status --short
printf 'Practice directory: %s\n' "$lab"

预期观察

revert后文件为correct,提交数是3,祖先检查成功;restore后工作区变回wrong,短状态为 M answer.txt,提交数仍是3。

原因、取舍与适用边界

revert不是让分支指针回到过去,而是在当前上下文应用反向改动,因此后续编辑可能使它产生冲突。对合并提交撤销还涉及主线父提交选择,不能机械套用本例。reflog帮助查找本地引用曾指向的位置,但会过期,也不是远端备份。真正的操作决策应先问:数据是否已提交、历史是否已共享、需要保留哪些后续改动。

进阶练习

已经把错误提交推给同事,为什么通常优先选择revert而不是强制覆盖分支?

解法与推理

新反向提交允许同事沿原历史正常获取,并保留审查线索。强制改写会使他们的分支与远端历史分叉,需要额外协调。即使采用revert,也应先验证反向改动是否符合当前业务,而不只以命令退出码为验收标准。

进一步阅读