跳到主要内容

版本回退

本章将介绍如何查看提交历史、撤销修改和回退版本。版本回退是 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 resetrevert 会创建一个新的提交来撤销之前的修改,不会改写历史。

# 创建新提交来撤销最近一次提交
git revert HEAD

# 撤销指定提交
git revert abc123

# 撤销多个提交(从旧到新逐个撤销)
git revert HEAD~3..HEAD

# 不自动提交,只在暂存区生成修改
git revert --no-commit HEAD~2..HEAD

revert 示意图(撤销前):

revert 示意图(撤销后):

reset vs revert 对比:

特性resetrevert
改写历史
创建新提交
适合本地提交可以
适合已推送提交
团队协作安全性

回退到特定版本

回退到某个提交

# 方式一:重置到某个提交(改写历史)
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}

小结

本章我们学习了:

  1. 查看提交历史git log 的各种用法
  2. 版本表示方式:HEAD、相对引用、哈希值
  3. 撤销修改:工作区、暂存区、提交区的不同撤销方式
  4. reset vs revert:何时使用哪种方式
  5. 回退版本:回退到特定提交或标签
  6. 恢复误操作:使用 reflog 恢复丢失的提交
  7. 比较版本:查看不同版本之间的差异
  8. 实战场景:常见问题的解决方法

练习

  1. 查看项目的提交历史,练习不同的 log 格式
  2. 创建一些提交,然后练习 soft、mixed、hard 三种 reset 模式
  3. 使用 revert 撤销一个已经推送到远程的提交
  4. 使用 reflog 找回一个被 reset --hard 删除的提交
  5. 练习版本穿梭和比较不同版本

参考资源