撤销与回退

#git #checkout #reset #revert #reflog #撤销

撤销与回退#

git checkout#

恢复文件到某个提交#

# 恢复工作目录中的某个文件到某个提交的状态
# <commit> 可以是 commit hash、分支名、标签名
# <filename> 是要恢复的文件路径
git checkout <commit> -- <filename>

# 示例:将 file.txt 恢复到提交 abc123 的状态
git checkout abc123 -- file.txt

Detached HEAD(分离头指针)#

git checkout <commit> 的意思是:将你的工作目录切换到指定的某一次提交的状态。当你 checkout 一个具体的 commit hash(而不是分支名)时,HEAD 不再指向任何分支,而是直接指向那个提交节点。

正常状态:    A --- B --- C (main) ← HEAD 指向 main 分支

执行后:      A --- B --- C (main)

               HEAD → B        ← Detached HEAD!HEAD 悬空了

三种典型用法#

# ==================== 1. 纯查看(只读模式) ====================
# 切到某个历史提交看看
git checkout a1b2c3d

# 看完之后,回到原来的分支
git checkout main

# ✅ 安全操作,只要你不修改文件,随时可以回来。

# ==================== 2. 基于历史提交创建新分支 ====================
# 从旧提交创建一个新分支,避免 Detached HEAD
git checkout -b hotfix-v1.0 a1b2c3d

# ✅ 最佳实践:如果你打算在历史版本上做任何修改,永远先创建新分支。

# ==================== 3. 在 Detached HEAD 下做了修改 ====================
git checkout a1b2c3d
# ... 改了一些文件并 commit 了 ...
# 此时这些提交不属于任何分支!

# 补救方法:赶紧创建分支保存它们
git checkout -b rescue-branch

# ⚠️ 如果直接在 Detached HEAD 下 commit 后又切回 main,
# 那些提交会变成孤儿,可能被 GC 回收丢失!
⚠️ 注意

Detached HEAD 注意事项

  • 在 Detached HEAD 状态下,你仍然可以查看文件、运行命令
  • 如果做了修改并 commit,这些提交没有分支归属
  • 一旦切回其他分支,这些孤儿提交可能被 Git 垃圾回收机制删除
  • 最佳实践:始终在正式分支上工作

git reset#

重置当前分支到特定提交。有三种模式,区别在于对工作区和暂存区的处理方式。

# ==================== 三种重置模式 ====================

# --soft:保留工作区和暂存区的修改,只重置 HEAD 指针
# 最温和的重置,适合撤销 commit 但保留修改
git reset --soft <commit>

# --mixed:保留工作区的修改,重置暂存区和 HEAD 指针(默认选项)
# 中等力度,适合重新组织提交
git reset --mixed <commit>

# --hard:丢弃工作区和暂存区的修改
# 最危险的重置,会丢失所有未提交的修改
git reset --hard <commit>

三种模式对比#

模式HEAD 指针暂存区工作区使用场景
--soft✅ 重置❌ 保留❌ 保留撤销 commit,保留所有修改
--mixed✅ 重置✅ 重置❌ 保留撤销 commit 和 add,保留修改
--hard✅ 重置✅ 重置✅ 重置彻底回退,丢弃所有修改

重置示意图#

重置前:  A --- B --- C --- D (main, HEAD)

执行: git reset --soft B

重置后:  A --- B (main, HEAD)
              \
               C --- D  ← 这两个提交还在 .git/objects 里,
                          但没有指针指向它们了!
🚫 警告

⚠️ git reset —hard 危险警告

--hard 是最危险的选项,它会直接抹掉工作区的修改。使用前请确保:

  1. 所有重要修改已经提交
  2. 已创建备份分支
  3. 确实想要丢弃这些更改

实际应用场景#

场景一:让远程仓库退回某个提交并彻底删除后续提交#

# 第一步:创建安全备份
git branch backup-before-reset D       # 备份当前 HEAD(D)到一个新分支
git push origin backup-before-reset    # 推送备份分支到远程仓库

# 第二步:重置本地分支到 B
git checkout main                      # 确保在正确的分支上
git reset --hard B                     # 重置本地分支到 B

# 第三步:推送到远程
# 普通的 git push 会被拒绝,因为本地落后于远程
# 命令一:(推荐)安全强制推送
# 只有当远程分支仍在你上次 fetch 的状态时才允许推送
git push --force-with-lease origin main

# 命令二:强制推送(不推荐,可能覆盖别人的提交)
git push --force origin main

# 验证结果
git log origin/main                    # 应该只看到 A --- B

场景二:将多个 commit 合并成一个提交#

# 先克隆仓库(如果还没有的话),保证工作区和暂存区是最新的

# 1. 查看历史,找到目标 commit
git log --oneline

# 2. 重置到那个 commit(保留更改在暂存区)
git reset --soft <目标commit-hash>

# 3. 重新提交(将所有更改合并为一个提交)
git commit -m "合并后的提交"

# 4. 强制推送
git push --force

git revert#

撤销某次提交,但保留历史记录。与 git reset 不同,revert 会创建一个新的反向提交。

# 创建一个新的提交,撤销指定的提交
# <commit> 是要撤销的提交的哈希值
git revert <commit>

revert vs reset 对比#

特性git revertgit reset
操作逻辑新增一个反向提交(Forward)回退分支指针(Backward)
历史记录完整保留,可追溯被改写/截断
已推送的提交安全,不影响他人危险,会导致协作灾难
适用场景公共分支、已推送的错误修复本地未推送的整理、私密分支
类比财务上的「红字冲销」撕掉账本重写

revert 示意图#

执行前:  A --- B --- C (删除了重要文件) --- D (main)

执行: git revert C

执行后:  A --- B --- C --- D --- C' (main)

                         新提交 C' 的内容 = C 的完全反向操作
                         (把删除的文件加回来)
技巧

选择 revert 还是 reset?

  • 已推送到远程的提交:使用 git revert,因为它不会改变历史,对其他协作者安全
  • 本地未推送的提交:可以使用 git reset,因为它更干净,不会产生额外的提交

git reflog#

git reflog 记录了所有 HEAD 的移动。即使提交被删除或重置,也可以通过 reflog 找回。

# 查看 HEAD 的历史操作记录
# 包括 commit、reset、checkout、merge 等操作
git reflog

# 回到三次操作前的状态
# HEAD@{n} 是"HEAD 的移动日记",n 是操作序号
git reset --hard HEAD@{3}

reflog 输出示例#

$ git reflog

a1b2c3d HEAD@{0}: commit: 添加新功能
b2c3d4e HEAD@{1}: reset: moving to HEAD~1
c3d4e5f HEAD@{2}: commit: 修复 bug
d4e5f6g HEAD@{3}: checkout: moving from main to feature
e5f6g7h HEAD@{4}: commit: 初始提交
💡 提示

reflog 的作用

  • 后悔药:误操作后可以找回丢失的提交
  • 时间机器:可以回到任何历史状态
  • 调试工具:查看操作历史,了解发生了什么

注意:reflog 只记录本地操作,且有时间限制(默认 90 天)。

操作选择指南#

┌─────────────────────────────────────────────────────────────┐
│                    Git 撤销操作选择指南                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   场景一:撤销未暂存的修改                                     │
│   → git checkout -- <file>                                  │
│                                                             │
│   场景二:撤销已暂存的修改                                     │
│   → git reset HEAD <file>                                   │
│                                                             │
│   场景三:撤销最近一次 commit(保留修改)                       │
│   → git reset --soft HEAD~1                                 │
│                                                             │
│   场景四:撤销最近一次 commit(丢弃修改)                       │
│   → git reset --hard HEAD~1                                 │
│                                                             │
│   场景五:撤销已推送的 commit                                  │
│   → git revert <commit>                                     │
│                                                             │
│   场景六:找回丢失的提交                                       │
│   → git reflog → git reset --hard HEAD@{n}                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

相关笔记