版本回退
本章将介绍如何查看提交历史、撤销修改和回退版本。版本回退是 Git 中最常用也是最重要的操作之一,掌握好这些操作能帮你从容应对各种意外情况。
查看提交历史
基本命令
# 查看完整历史
git log
# 简洁格式(一行一个提交,推荐日常使用)
git log --oneline
# 显示分支图(可视化分支结构)
git log --graph --decorate --all
# 查看每次提交的文件差异
git log -p
# 查看文件修改统计
git log --stat
常用筛选选项
# 查看最近 n 条记录
git log -n 5
# 按时间筛选
git log --since="2024-01-01"
git log --after="2 weeks ago"
git log --before="2024-06-01"
# 按作者筛选
git log --author="张三"
# 按提交信息筛选
git log --grep="bug"
# 查看特定文件的修改历史
git log --follow filename.txt
# 查看两个分支之间的提交差异
git log main..feature-branch
# 查看某个文件在哪次提交中被修改
git log -p -- filename.txt
格式化输出
# 自定义格式
git log --pretty=format:"%h - %an, %ar : %s"
# 常用占位符
# %h - 短哈希值
# %H - 完整哈希值
# %an - 作者名
# %ae - 作者邮箱
# %ar - 相对时间(如 3 天前)
# %s - 提交说明
# %d - 引用名(分支、标签)
# 实用组合:图形化 + 简洁 + 所有分支
git log --graph --oneline --all --decorate
版本的表示方式
HEAD 指针
HEAD 是一个指向当前所在提交的指针。理解 HEAD 的位置对于版本回退至关重要。
# 查看当前 HEAD 指向
git log -1 HEAD
git show HEAD
# 查看 HEAD 的详细信息
cat .git/HEAD
HEAD 的位置示意图:
上图中,D 是 HEAD 指向的当前提交。
相对引用
使用相对引用来指定相对于 HEAD 的提交:
# 上一次提交
HEAD~1
HEAD^
# 上 3 次提交
HEAD~3
# 父提交(用于合并提交有两个父提交的情况)
HEAD^1 # 第一个父提交
HEAD^2 # 第二个父提交
相对引用示意图:
哈希值
# 使用完整哈希(40 个字符)
git checkout abc123def456789...
# 使用短哈希(至少 4 位,Git 会自动补全)
git checkout abc1
# 使用标签
git checkout v1.0.0
# 使用分支名
git checkout main
撤销修改
撤销工作区修改
当你在工作区做了修改但还没有添加到暂存区时,可以撤销这些修改:
# 撤销特定文件的修改
git checkout -- filename.txt
# 或使用新命令(Git 2.23+)
git restore filename.txt
# 撤销所有修改
git checkout -- .
git restore .
注意:这会永久丢弃工作区的修改,无法恢复。使用前请确认你真的不需要这些修改。
撤销暂存
当你把文件添加到暂存区后,发现不需要暂存:
# 取消暂存特定文件
git reset filename.txt
git restore --staged filename.txt
# 取消所有暂存
git reset
git restore --staged .
撤销提交
soft 模式
# 撤销提交,保留修改在暂存区
git reset --soft HEAD~1
soft 模式示意图(撤销前):
soft 模式示意图(撤销后):
效果:
- HEAD 移动到上一个提交
- 修改保留在暂存区
- 工作区修改保留
适用场景:想修改最后一次提交的内容或提交信息时,先 soft reset,修改后再重新提交。
mixed 模式(默认)
# 撤销提交,保留修改在工作区
git reset --mixed HEAD~1
# 或简写
git reset HEAD~1
效果:
- HEAD 移动到上一个提交
- 修改保留在工作区(未暂存状态)
- 需要重新
git add后提交
适用场景:想重新组织最后一次提交时,mixed reset 后可以选择性地添加文件。
hard 模式
# 撤销提交,丢弃所有修改
git reset --hard HEAD~1
警告:这会丢失所有修改,请谨慎使用!
效果:
- HEAD 移动到上一个提交
- 所有修改被丢弃
- 不可恢复(除非使用 reflog)
适用场景:确认要彻底回退到之前的状态。
三种模式对比
| 模式 | HEAD 位置 | 暂存区 | 工作区 | 风险 |
|---|---|---|---|---|
--soft | 改变 | 保留 | 保留 | 低 |
--mixed | 改变 | 重置 | 保留 | 低 |
--hard | 改变 | 重置 | 重置 | 高 |
reset 与 revert 的选择
什么时候用 reset
- 本地提交,还没有推送到远程
- 想要彻底删除某些提交
- 想要重新组织提交历史
什么时候用 revert
- 提交已经推送到远程
- 团队协作中,不希望改写公共历史
- 想要保留完整的项目历史
撤销已推送的提交
如果已经推送到远程仓库,应该使用 git revert 而不是 git reset。revert 会创建一个新的提交来撤销之前的修改,不会改写历史。
# 创建新提交来撤销最近一次提交
git revert HEAD
# 撤销指定提交
git revert abc123
# 撤销多个提交(从旧到新逐个撤销)
git revert HEAD~3..HEAD
# 不自动提交,只在暂存区生成修改
git revert --no-commit HEAD~2..HEAD
revert 示意图(撤销前):
revert 示意图(撤销后):
reset vs revert 对比:
| 特性 | reset | revert |
|---|---|---|
| 改写历史 | 是 | 否 |
| 创建新提交 | 否 | 是 |
| 适合本地提交 | 是 | 可以 |
| 适合已推送提交 | 否 | 是 |
| 团队协作安全性 | 低 | 高 |
回退到特定版本
回退到某个提交
# 方式一:重置到某个提交(改写历史)
git reset --hard abc123
# 方式二:创建新提交来撤销(保留历史)
git revert abc123
# 方式三:创建新分支从指定提交开始
git checkout -b old-branch abc123
回退到某个标签
# 检出标签(进入分离 HEAD 状态)
git checkout v1.0.0
# 创建新分支并切换到标签
git checkout -b release-v1.0.0 v1.0.0
恢复误删的提交
使用 reflog
reflog 是 Git 的"后悔药"。它记录了 HEAD 的所有变化,包括提交、切换分支、重置等操作。
# 查看所有操作历史
git reflog
# 输出示例
abc1234 HEAD@{0}: reset: moving to HEAD~1
def5678 HEAD@{1}: commit: 添加重要功能
ghi9012 HEAD@{2}: checkout: moving from main to feature
...
# 恢复到指定操作之前的状态
git checkout HEAD@{1}
# 或
git reset --hard HEAD@{1}
# 创建新分支恢复
git branch recovered HEAD@{1}
reflog 会记录所有 HEAD 的变化,包括:
- 提交
- 切换分支
- 重置
- 合并
- 克隆
注意:reflog 默认保留 90 天,之后过期的条目会被清理。
恢复已删除的分支
# 查找分支最后指向的提交
git reflog
# 创建新分支恢复
git branch recovered-branch HEAD@{2}
比较版本
# 比较两个提交的差异
git diff abc123 def456
# 比较当前版本与某个提交
git diff abc123
# 比较两个分支
git diff main feature-branch
# 比较某个文件在两个版本的差异
git diff abc123 def456 -- filename.txt
# 只显示统计信息
git diff --stat abc123 def456
# 比较暂存区和上次提交
git diff --staged
版本穿梭
# 进入分离 HEAD 状态(查看旧版本)
git checkout abc123
# 在分离 HEAD 状态下创建新分支
git checkout -b hotfix-from-old abc123
# 返回当前分支
git checkout main
# 创建新分支并切换到指定提交
git checkout -b investigate-bug abc123
实战场景
场景一:提交后发现遗漏了文件
# 添加遗漏的文件
git add forgotten-file.txt
# 修改最后一次提交(不创建新提交)
git commit --amend --no-edit
场景二:提交后发现写错了文件
# 撤销最后一次提交,保留修改
git reset --soft HEAD~1
# 从暂存区移除错误的文件
git restore --staged wrong-file.txt
# 重新提交
git commit -m "正确的提交信息"
场景三:想撤销已经推送到远程的提交
# 使用 revert 创建新提交
git revert HEAD
# 推送到远程
git push
场景四:彻底回退到之前的某个版本
# 先备份当前状态(以防万一)
git branch backup-current
# 回退到目标提交
git reset --hard abc123
# 确认无误后删除备份
git branch -d backup-current
场景五:找回误删的提交
# 查看操作历史
git reflog
# 找到目标提交
# 恢复
git reset --hard HEAD@{3}
小结
本章我们学习了:
- 查看提交历史:
git log的各种用法 - 版本表示方式:HEAD、相对引用、哈希值
- 撤销修改:工作区、暂存区、提交区的不同撤销方式
- reset vs revert:何时使用哪种方式
- 回退版本:回退到特定提交或标签
- 恢复误操作:使用 reflog 恢复丢失的提交
- 比较版本:查看不同版本之间的差异
- 实战场景:常见问题的解决方法
练习
- 查看项目的提交历史,练习不同的 log 格式
- 创建一些提交,然后练习 soft、mixed、hard 三种 reset 模式
- 使用 revert 撤销一个已经推送到远程的提交
- 使用 reflog 找回一个被 reset --hard 删除的提交
- 练习版本穿梭和比较不同版本