[Git] 如何将已经执行的修改操作撤销

发布于:2025-05-27 ⋅ 阅读:(22) ⋅ 点赞:(0)

在使用 Git 工作时,经常会遇到这样的情况:写了一段代码,或者对文件做了一些修改,但后来发现这些改动不好,想回到之前的状态。根据你的改动所处的“阶段”(在工作区、暂存区还是已经提交到了版本库),撤销修改的方法是不同的。


撤销修改:根据“修改在哪里”选择方法

Git 非常灵活,它允许你在不同的阶段撤销修改。理解这一点,就能帮助你选择正确的命令。

情况一:修改只在工作区(还没有 git add

场景: 你在工作区(你的项目文件夹里)修改了文件,或者新建了文件,但你还没有执行 git add 命令将这些改动添加到暂存区。此时 git status 会显示 “Changes not staged for commit”(未暂存的修改)。现在,你想丢弃这些在工作区里的改动,让文件回到你上次 git addgit commit 时的状态。

就像你提到的,如果只改了一两行,你当然可以手动删掉。但如果改了很多文件,或者改动非常复杂,手动删除费时费力,还容易出错,特别是你记不清到底改了哪些地方时。

这时,Git 为我们提供了更方便的“撤销”方法。

命令: git checkout -- [文件名]

或者使用 Git 较新版本推荐的命令(功能类似):git restore [文件名]

这里只讲解:git checkout --

注意: 命令中的 -- 非常重要,它是用来分隔 Git 选项和文件列表的。如果省略了 --git checkout 命令就变成了切换分支(我们后面会讲到),而不是撤销文件修改,效果完全不同!

它的原理: git checkout -- [文件名] 命令的意义是:“Git,请你用暂存区(如果文件在暂存区有改动)或版本库中当前分支最新提交(如果文件不在暂存区)的那个版本的文件,来覆盖我工作区的当前文件。”

简单来说,它会把工作区的这个文件“打回原形”,恢复到它最近一次被纳入 Git 快照时的状态。

操作演示:

  1. 我们在 ReadMe 文件中新增一行代码(例如 “This piece of code is like shit”)。
# 查看修改前的文件内容(这是上一次 commit 后的状态)
zz@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3

# 手动编辑 ReadMe,在末尾新增一行
zz@139-159-150-152:~/gitcode$ vim ReadMe
# 编辑后 ReadMe 内容如下:
# ...
# hello version3
# This piece of code is like shit

# 使用 git status 查看,发现改动只在工作区,未暂存
zz@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit: # 未暂存的修改
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified: ReadMe      # ReadMe 文件被修改了

no changes added to commit # 暂存区没有改动
  1. 现在我们想撤销工作区对 ReadMe 的修改。
# 执行撤销命令
zz@139-159-150-152:~/gitcode$ git checkout -- ReadMe

Git 不会给出太多输出,但它已经悄悄地用版本库中最新提交的 ReadMe 文件内容覆盖了你的工作区文件。

  1. 再次查看 ReadMe 内容和 git status 状态:
# 查看 ReadMe 内容
zz@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
# 新增的那一行 "This piece of code is like shit" 不见了!

# 查看 Git 状态
zz@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean # 工作区又干净了

成功了!工作区的修改被撤销了。

记住: git checkout -- [文件名](或 git restore [文件名])命令可以丢弃工作区未暂存的修改。它会用暂存区或版本库中的版本覆盖工作区的文件。

情况二:修改已添加到暂存区(已经 git add,但没有 git commit

场景: 你在工作区做了修改,然后执行了 git add [文件名] 命令,将改动添加到了暂存区。此时 git status 会显示 “Changes to be committed”(待提交的修改)。但你突然发现 add 到暂存区的改动有问题,不应该提交,你想把这个改动从暂存区撤回到工作区(或者彻底丢弃)。

命令: git reset HEAD [文件名]

这里我们再次遇到了 git reset 命令!还记得我们讲版本回退时提到的 --mixed 参数吗?它是 git reset 的默认参数,作用是移动分支指针并重置暂存区,但不改变工作区。

git reset HEAD [文件名] 就是利用了 git reset --mixed 的原理,但它更加精确。

  • HEAD 指的是当前分支的最新提交。
  • [文件名] 表示这个操作只针对指定的文件。
  • 因为 --mixed 是默认的,所以 git reset HEAD [文件名] 等同于 git reset --mixed HEAD [文件名]

它的原理: git reset HEAD [文件名] 命令的意义是:“Git,请你把暂存区里这个文件的状态,回退到 HEAD 指向的那个版本(也就是当前分支最新提交)时的状态。但不要改变我的工作区。”

执行这个命令后,暂存区里这个文件的改动就会被移除,回到它在最新提交时的状态。而你在工作区对这个文件的修改不会丢失,它们会回到“未暂存”(unstaged)的状态,就像刚修改完还没 add 那样。

操作演示:

  1. 我们在 ReadMe 中新增一行代码(和上面一样),然后将其 add 到暂存区。
# 修改 ReadMe 文件,新增一行 "This piece of code is like shit"
zz@139-159-150-152:~/gitcode$ vim ReadMe
# Add 到暂存区
zz@139-159-150-152:~/gitcode$ git add ReadMe

# 查看状态,改动已在暂存区
zz@139-159-150-152:~/gitcode$ git status
On branch master
Changes to be committed: # 待提交的修改
  (use "git restore --staged <file>..." to unstage) # Git 提示你可以用 restore --staged 撤销暂存,它和 git reset HEAD 功能类似
        modified: ReadMe
  1. 现在我们想撤销暂存区的修改。
# 执行撤销暂存命令
zz@139-159-150-152:~/gitcode$ git reset HEAD ReadMe
Unstaged changes after reset: # Git 提示:重置后有未暂存的修改
M ReadMe                     # ReadMe 文件处于 Modified (修改) 状态且未暂存
  1. 再次查看 git status 状态:
zz@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit: # 未暂存的修改
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified: ReadMe      # ReadMe 文件现在处于这个区域了!

no changes added to commit

成功了!ReadMe 的改动已经从暂存区回到了工作区,变成了未暂存的状态。

接下来怎么办? 现在你的修改回到了工作区(处于情况一),你可以选择:

  • 继续编辑这个文件,直到满意为止。
  • 如果想彻底丢弃这个修改,就像情况一那样,再次使用 git checkout -- ReadMe 来丢弃工作区的改动。
# 彻底丢弃工作区的修改
zz@139-159-150-152:~/gitcode$ git checkout -- ReadMe

# 检查状态和文件内容,一切都回到了上一次提交时的干净状态
zz@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
zz@139-159-150-152:~/gitcode$ cat ReadMe
# 内容已经恢复到撤销修改之前

记住: git reset HEAD [文件名] 命令可以撤销暂存区里指定文件的修改,将改动回退到工作区,文件变为未暂存状态。

情况三:修改已提交到版本库(已经 git add,并且 git commit 了)

场景: 你已经把修改通过 git commit 命令提交到了版本库,生成了一个新的版本。但提交后你突然发现,这个版本有问题,你想撤销这次提交,回到上一个版本。

命令: git reset --hard HEAD^ (或其他指定上一个版本的方式)

它的原理: 这就是我们上一篇讲的版本回退操作。git reset --hard HEAD^ 命令的意义是:“Git,请你把当前分支的指针和 HEAD 指针,都移动到当前提交的上一个版本 (HEAD^)。同时,强制性地把暂存区工作区的内容都重置为上一个版本时的状态。”

这样一来,你做的最后一次提交就从当前分支的历史中“消失”了,你的文件内容也回到了上一个提交时的状态。

【重要提示】:就像上一篇强调的,这种方式会丢弃最后一次提交之后所有在工作区的修改(如果它们没有被提交的话)。而且,如果你已经把这个错误的提交推送到了远程仓库(后面我们会讲到远程仓库),这种 reset --hard 会修改共享的历史记录,给团队协作带来麻烦,通常不推荐这样做。只在本地仓库且确认没问题时使用。

操作演示:

  1. 我们在 ReadMe 文件中新增一行,然后 addcommit 这个改动。
# 确保工作区是干净的
zz@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean

# 修改 ReadMe 文件,新增一行 "This piece of code is like shit"
zz@139-159-150-152:~/gitcode$ vim ReadMe
# 内容变成:... hello version3\nThis piece of code is like shit

# Add 到暂存区
zz@139-159-150-152:~/gitcode$ git add ReadMe

# Commit 提交到版本库
zz@139-159-150-152:~/gitcode$ git commit -m"test quash" # 提交消息为 "test quash"
[master 5f71ae1] test quash # 产生了新的 commit id
 1 file changed, 1 insertion(+)

# 查看历史,确认新的提交已存在,并且 HEAD 指向它
zz@139-159-150-152:~/gitcode$ git log --pretty=oneline
5f71ae1f3e2c7b1a8c9e1f0f6bff3015df71a0963004476f5e6cfd54 (HEAD -> master) test quash # 最新提交
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 add version3 # 上一个提交
...
  1. 现在我们想撤销这个 test quash 提交,回到 add version3 那个版本。
# 执行版本回退命令,回到上一个版本,并强制重置工作区和暂存区
zz@139-159-150-152:~/gitcode$ git reset --hard HEAD^
HEAD is now at d95c13f add version3 # Git 告诉你 HEAD 回到了上一个提交

注意:这里的 HEAD^ 就是指 5f71ae1 的父提交,也就是 d95c13f

  1. 检查工作区文件内容:
zz@139-159-150-152:~/gitcode$ cat ReadMe
hello bit
hello git
hello world
hello version1
hello version2
hello version3
# 新增的那一行 "This piece of code is like shit" 消失了!文件回到了 version3 提交时的状态。
  1. 检查提交历史:
zz@139-159-150-152:~/gitcode$ git log --pretty=oneline
d95c13ffc878a55a25a3d04e22abfc7d2e3e1383 (HEAD -> master) add version3 # 最新提交已经是它了
... # 之前的其他提交

那个 “test quash” 的提交在 git log 里看不到了,成功撤销了这次提交。

记住: git reset --hard HEAD^ (或其他 git reset --hard [目标版本]) 可以撤销版本库中的一次或多次提交,并将暂存区和工作区都回退到目标版本。这是一个强大的回退命令,会丢失工作区未提交的修改,且不应用于已推送到远程的共享历史。

撤销修改命令速查表

场景 修改在哪儿? git status 显示? 想达到的效果? 使用命令? 对 各区域 的影响 注意
想丢弃工作区的修改 工作区 Changes not staged for commit 工作区文件恢复到暂存区/版本库 git checkout -- [文件名] 工作区被覆盖,暂存区/版本库不变 -- 很重要!会丢弃工作区未暂存的修改。
想取消暂存区的修改 暂存区 Changes to be committed 暂存区文件回退到工作区(未暂存) git reset HEAD [文件名] 暂存区被重置,工作区不变,版本库不变 实际上是 --mixed 模式的应用。修改回到工作区。
想撤销最近一次提交 版本库 工作区和暂存区通常是干净的 版本库回退到上一个版本,工作区/暂存区也回退 git reset --hard HEAD^ 版本库回退,暂存区/工作区强制重置 非常危险! 丢弃工作区未提交修改,不用于共享历史。
(额外) 想取消暂存所有改动 暂存区 Changes to be committed 暂存区所有文件回退到工作区 git reset HEAD (不加文件名) 暂存区被重置,工作区不变,版本库不变 常用,等同于 git reset --mixed HEAD

通过上面的讲解和速查表,你应该对如何在不同阶段撤销修改有了清晰的认识。选择正确的命令,理解它对工作区、暂存区和版本库的影响,是安全有效地使用 Git 的基础。记住 --hard 的强大和危险,并在必要时使用 git reflog 来找回历史记录!


网站公告

今日签到

点亮在社区的每一天
去签到