reset、revert、reflog 实操
目标
分别在独立仓库中执行 reset 的三种模式、amend、revert 和 reflog,比较结果,并用速查表整理不同情境下应该使用什么。产物放在 /root/gitx2/ 下。
为什么重要
大多数撤销事故并不是因为不知道命令,而是因为不了解该命令会覆盖到什么范围就贸然执行。--soft 不会丢失任何内容;默认的 --mixed 只会丢失暂存状态;--hard 则会丢失所有尚未 commit 的变更。评估风险时不应看命令名称,而应判断“这条命令是否会覆盖未 commit 的变更”。此外还有第二个维度:如果某个 commit 已经存在于其他人的仓库中,任何删除该 commit 的方法都会破坏他人的历史。此时正确答案不是修改历史,而是向历史追加内容。本实验不靠文字说明,而是通过 commit 数量和文件是否存在来验证这一差异。
步骤
**没有全局 git 身份。**每次创建仓库时,都必须设置本地 user.email / user.name 才能 commit。
- 创建
/root/gitx2/mkrepo.sh并执行chmod +x。在第一个参数($1)指定的路径初始化仓库(不存在时先创建),分支为main,并生成 5 个 commit。第 n 个 commit 添加fn.txt(f1.txt … f5.txt),标题为c1…c5。评分器会在临时目录中执行此脚本进行检查,因此不要硬编码路径,必须使用"$1"。 - 用脚本在
/root/gitx2/amend创建仓库,并且只把最后一个 commit 的标题改为c5 fixed。commit 数量必须仍为 5 个;reflog 中必须留下 amend 记录;修改前的哈希与当前哈希必须不同。 - 在
/root/gitx2/soft创建仓库并撤销一个 commit。结果:4 个 commit;f5.txt在索引中保持暂存状态;工作树中也存在f5.txt。 - 在
/root/gitx2/mixed创建仓库并撤销一个 commit。结果:4 个 commit;f5.txt未被暂存,但必须仍保留在工作树中(在git status --porcelain中显示为??或M)。 - 在
/root/gitx2/hard创建仓库并撤销一个 commit。结果:4 个 commit;f5.txt消失;f4.txt仍存在;工作树必须完全干净。 - 在
/root/gitx2/revert创建仓库,且只抵消c3commit 的变更。结果:6 个 commit;存在标题以Revert开头的 commit;原来的c3commit 也完整保留在历史中;f3.txt消失,而f1/f2/f4/f5.txt保留。 - 在
/root/gitx2/reflog创建仓库。撤销之前,将当前 HEAD 哈希保存到/root/gitx2/before.txt(文件中只能有哈希字符串)。然后强制丢弃 2 个 commit,再查看引用日志并恢复到原位置。结果:HEAD 与先前记录的哈希相同;commit 数量为 5 个;reflog 中必须留下 reset 记录。 - 在
/root/gitx2/undo.md编写速查表。正文必须出现--soft、--mixed、--hard、revert、reflog,并且必须包含以下三行。pushed=— 撤销已经推送且被他人拉取的 commit 时应使用的命令(一个单词)staging_only=— 只取消暂存、保留文件的 reset 选项worktree_wiped=— 连同未 commit 的变更一起丢弃的 reset 选项
参考
- 脚本骨架:
mkdir -p "$1"→git -C "$1" init -q -b main→git -C "$1" config user.email ...→ 使用循环创建文件、执行git add、执行git commit -m "c$i"。 - 保存哈希:执行
git rev-parse HEAD > /root/gitx2/before.txt时,文件中只会写入哈希。若直接写入git log输出,则无法通过格式检查。 - 查找丢失的 commit:在
git reflog中使用HEAD@{1}这样的表示法,或直接使用左侧显示的哈希。 - 常见错误 1:在同一个仓库中连续完成第 3~5 步。每一步都必须在单独的目录中从干净状态开始。
- 常见错误 2:第 6 步使用
git reset。如果 commit 数量减少,就会失败。revert 不会删除过去,而是再追加一个反向 commit。 git revert可能打开编辑器。添加--no-edit即可直接执行。
创建仓库生成脚本
创建 /root/gitx2/mkrepo.sh 并执行 chmod +x。在第一个参数($1)指定的路径初始化仓库(不存在时先创建),分支为 main,并生成 5 个 commit。第 n 个 commit 添加 fn.txt(f1.txt … f5.txt),标题为 c1 … c5。评分器会在临时目录中执行此脚本进行检查,因此不要硬编码路径,必须使用 "$1"。
/root/gitx2/mkrepo.sh 会在第一个参数指定的路径中新建仓库。路径可能尚不存在,因此需要先创建;由于没有全局身份,必须为每个仓库设置本地身份才能 commit。不要忘记 chmod +x。
修正最后一个 commit 消息
用脚本在 /root/gitx2/amend 创建仓库,并且只把最后一个 commit 的标题改为 c5 fixed。commit 数量必须仍为 5 个;reflog 中必须留下 amend 记录;修改前的哈希与当前哈希必须不同。
在 /root/gitx2/amend 生成仓库,只修改最后一个 commit 的标题。amend 不会增加 commit 数量,但会生成新的 commit 对象,因此哈希会改变,旧哈希则保留在 reflog 中。
仅撤销 commit 并保留暂存状态
在 /root/gitx2/soft 创建仓库并撤销一个 commit。结果:4 个 commit;f5.txt 在索引中保持暂存状态;工作树中也存在 f5.txt。
在 /root/gitx2/soft 中撤销一个 commit,同时让变更内容原样保留在索引中。这是三种模式中完全不会丢失任何内容的模式。
同时重置索引
在 /root/gitx2/mixed 创建仓库并撤销一个 commit。结果:4 个 commit;f5.txt 未被暂存,但必须仍保留在工作树中(在 git status --porcelain 中显示为 ?? 或 M)。
在 /root/gitx2/mixed 中撤销 commit,同时取消暂存,但文件必须保留在工作树中。不指定选项时,默认行为正是如此。
连同工作树一起重置
在 /root/gitx2/hard 创建仓库并撤销一个 commit。结果:4 个 commit;f5.txt 消失;f4.txt 仍存在;工作树必须完全干净。
在 /root/gitx2/hard 中,文件必须随 commit 一起消失。这是唯一会覆盖未 commit 变更的模式,因此执行前务必再三确认。
在保留历史的同时撤销
在 /root/gitx2/revert 创建仓库,且只抵消 c3 commit 的变更。结果:6 个 commit;存在标题以 Revert 开头的 commit;原来的 c3 commit 也完整保留在历史中;f3.txt 消失,而 f1/f2/f4/f5.txt 保留。
在 /root/gitx2/revert 中只抵消一个中间 commit 的变更。历史不能缩短,反而应增加一个 commit。对于已经发送给其他人的 commit,只有这种方式是安全的。
找回丢失的 commit
在 /root/gitx2/reflog 创建仓库。撤销之前,将当前 HEAD 哈希保存到 /root/gitx2/before.txt(文件中只能有哈希字符串)。然后强制丢弃 2 个 commit,再查看引用日志并恢复到原位置。结果:HEAD 与先前记录的哈希相同;commit 数量为 5 个;reflog 中必须留下 reset 记录。
在 /root/gitx2/reflog 中,先将撤销前的哈希写入文件,再故意丢弃 commit,之后查看引用日志并返回原位置。before.txt 中只能有哈希字符串。
整理各情境的速查表
在 /root/gitx2/undo.md 编写速查表。正文必须出现 --soft、--mixed、--hard、revert、reflog,并且必须包含以下三行。
pushed=— 撤销已经推送且被他人拉取的 commit 时应使用的命令(一个单词)staging_only=— 只取消暂存、保留文件的 reset 选项worktree_wiped=— 连同未 commit 的变更一起丢弃的 reset 选项
在 /root/gitx2/undo.md 中提及三种模式以及 revert、reflog,并通过 pushed= / staging_only= / worktree_wiped 三行写出各情境的正确答案。对于已经推送的 commit,不要使用改写历史的命令。