Git如何回滚到之前的某个提交版本


在软件开发中,代码回滚是一项必备技能。无论是修复线上 bug,还是撤销不想要的功能,我们都可能需要将代码恢复到之前的某个版本。Git 提供了多种方式来回滚代码,但对于新手来说,git resetgit revertgit checkout 这几个命令的区别可能会让人感到困惑。

别担心,这篇文章会用最通俗易懂的语言,结合代码示例和实际案例,带你彻底搞懂 Git 的回滚操作。

三大回滚命令:resetrevertcheckout

首先,我们来认识一下 Git 中用于回滚的三个主要命令:

  • git reset: 直接移动 HEAD 指针到指定的提交,就像时光倒流一样,可以选择性地保留或丢弃之后的修改。
  • git revert: 创建一个新的提交来抵消某个历史提交的更改,它不会修改原有的提交历史。
  • git checkout: 切换到一个指定的提交版本,但会进入一个“分离头指针”(detached HEAD)的状态,主要用于查看历史版本。

接下来,我们将逐一解析这三个命令的用法和适用场景。

git reset:简单粗暴的“时光机”

git reset 是一个非常强大的命令,它可以将你的分支“重置”到指定的提交。这个命令有三种常用的模式:--soft--mixed--hard

1. git reset --soft <commit_id>

  • 作用:只移动 HEAD 指针到指定的提交,不会修改暂存区(index)和工作区(working directory)的内容。
  • 口语化解析:相当于告诉 Git:“嘿,假装我最新的提交是 <commit_id>,但是我刚刚做的那些修改都还在,并且已经添加到暂存区了,我随时可以重新提交。”
  • 适用场景:当你觉得上一次的提交信息写得不好,或者想将多个提交合并成一个时,--soft 是一个不错的选择。

案例:修改最后一次提交信息

假设你刚刚提交了一个版本,但发现提交信息有错别字。

# 查看提交历史,获取上一个版本的 commit_id
git log --oneline

# 假设最新的提交是 "feat: add new feature",但你想改成 "feat: add user login feature"
# 使用 --soft 回滚到上一个版本
git reset --soft HEAD~1

# 此时,上次提交的更改已经回到了暂存区
# 你可以重新提交并修改提交信息
git commit -m "feat: add user login feature"

2. git reset --mixed <commit_id>

  • 作用:移动 HEAD 指针,并且重置暂存区,但工作区的内容保持不变。 这是 git reset 的默认模式。
  • 口语化解析:就像是说:“回到 <commit_id> 这个版本吧,我之前添加到暂存区的东西都不要了,但是代码文件的修改还是留给我。”
  • 适用场景:当你发现暂存区里有一些不想提交的文件,或者想重新整理暂存区的内容时,可以使用 --mixed

案例:从暂存区移除文件

你不小心 git add 了一些不需要提交的文件。

# 查看暂存区状态
git status

# On branch master
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#       modified:   config.js
#       new file:   temp.log

# 回滚到当前版本,清空暂存区
git reset --mixed HEAD

# 再次查看状态,会发现 config.js 和 temp.log 变成了未暂存的修改
git status

# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#       temp.log
#
# 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:   config.js

# 现在你可以只添加需要的文件并提交
git add config.js
git commit -m "update config"

3. git reset --hard <commit_id>

  • 作用:移动 HEAD 指针,同时重置暂存区和工作区。
  • 口语化解析:这是最彻底的回滚,相当于说:“回到 <commit_id>,之后的所有提交、暂存区的修改和工作区的代码,我全都不要了!”
  • 适用场景:当你确定要完全放弃某个提交之后的所有更改时,可以使用 --hard但请务必谨慎使用,因为它会永久删除你的本地修改

案例:彻底放弃某个功能

你开发了一个新功能,提交了几个版本后发现这个功能完全是个错误,需要彻底废弃。

# 查看提交历史,找到开始开发这个功能之前的那个版本的 commit_id
git log --oneline

# 假设要回滚到 2a3b4c5 这个提交
git reset --hard 2a3b4c5

# 执行后,所有在 2a3b4c5 之后的提交和本地修改都会被删除

git reset 总结

模式 HEAD 指针 暂存区 (Index) 工作区 (Working Directory)
--soft 移动 不变 不变
--mixed 移动 重置 不变
--hard 移动 重置 重置

重要提示:如果你的提交已经推送到了远程仓库,强烈不建议使用 git reset 来修改历史。因为这会造成你本地和远程仓库的历史不一致,给团队协作带来麻烦。在这种情况下,git revert 是更安全的选择。

git revert:安全的“反向操作”

git reset 修改历史记录不同,git revert 会创建一个新的提交,这个新提交的内容是目标提交的“反向”操作。

  • 口语化解析:这就像是你写错了一个字,不是用橡皮擦掉,而是在旁边重新写一个正确的字,并告诉大家“之前那个是错的”。
  • 适用场景:当你需要撤销一个已经推送到远程仓库的提交时,git revert 是最佳选择。 它不会改变项目历史,因此对于协作开发非常安全。

案例:撤销一个已经发布的 bug

你发布了一个新版本,结果发现其中一个提交引入了一个严重的 bug。

# 查看提交历史,找到引入 bug 的那个提交的 commit_id
git log --oneline

# 假设引入 bug 的提交是 785dd5a
git revert 785dd5a

执行上述命令后,Git 会自动创建一个新的提交,这个提交会撤销 785dd5a 所做的所有更改。 Git 会弹出一个编辑器让你编辑这次 revert 操作的提交信息。保存并退出后,一个新的提交就生成了。

之后,你只需要将这个新的提交推送到远程仓库,就可以安全地修复线上的 bug。

git checkout:轻松的“版本穿越”

git checkout 命令通常用于切换分支,但它也可以用来切换到某一个历史提交。

  • 作用:将工作区切换到指定提交的状态,此时 HEAD 指针会指向这个提交,进入“分离头指针”(detached HEAD)状态。
  • 口语化解析:这就像是坐上时光机回到了过去,你可以四处看看当时的代码是什么样的,但你在这个状态下做的任何修改和提交都不会属于任何一个分支。
  • 适用场景:当你只是想查看一下旧版本的代码,或者基于某个旧版本创建一个新的分支时,可以使用 git checkout

案例:查看旧版本的代码

你想看一下上周某个功能的具体实现。

# 查看提交历史,找到你想查看的版本的 commit_id
git log --oneline

# 切换到指定的提交
git checkout a1b2c3d

现在你的工作目录就变成了 a1b2c3d 这个提交时的样子。你可以随意浏览和测试代码。

如果你想在这个版本的基础上进行新的开发,可以创建一个新的分支:

git checkout -b new-feature-branch

当你查看完毕,想回到最新的版本时,只需要切换回原来的分支即可:

git checkout master

总结:我该用哪个?

命令 主要作用 是否修改历史 对远程仓库的影响 适用场景
git reset 移动 HEAD 和分支指针,可以选择性地重置暂存区和工作区 危险,不推荐用于已推送的提交 本地分支的回滚,整理提交历史
git revert 创建一个新的提交来撤销历史提交的更改 安全 撤销已经推送到远程仓库的提交
git checkout 切换到指定的提交,进入“分离头指针”状态 查看历史版本的代码,基于旧版本创建新分支

希望通过这篇文章,你能对 Git 的回滚操作有一个更清晰的认识。记住,多在本地创建实验分支进行练习,才能在实际工作中游刃有余!


  目录