Git Reset 和 Git Revert 的区别与用法:掌握 Git 撤销操作的核心工具

发布于 18 天前  41 次阅读


在 Git 版本控制中,撤销更改是开发中常见的操作。无论是误提交了代码、需要回退到某个历史状态,还是想撤销已经推送到远程仓库的提交,Git 提供了多种工具来处理这些场景。本文将重点讲解 git resetgit revert 的区别与用法,帮助你根据实际需求选择合适的工具,高效管理提交历史。


背景:为什么需要撤销操作?

在使用 Git 的过程中,你可能会遇到以下情况:

  • 提交了错误的代码,想完全撤销。
  • 提交历史中包含不必要的提交,想清理。
  • 已经推送到远程仓库的提交需要撤销,但不能影响其他开发者。
  • 需要回退到某个历史状态,但保留后续修改。

Git 提供了两种主要命令来处理撤销操作:git resetgit revert。它们虽然都能“撤销”提交,但作用和适用场景截然不同。


Git Reset 和 Git Revert 的核心区别

1. 功能和行为

  • git reset
    • 作用:将当前分支的 HEAD 回退到指定状态,并(可选地)修改工作目录和暂存区。
    • 特点:重写提交历史,直接移除指定提交及之后的所有提交。
    • 结果:被撤销的提交从历史中消失,仿佛从未发生。
    • 适用场景:适合本地开发中清理未推送的提交,或者完全回退到某个状态。
  • git revert
    • 作用:创建一个新的提交,反向应用指定提交的更改。
    • 特点:不修改现有历史,而是通过新增一个“反向提交”来撤销更改。
    • 结果:提交历史中会多一个新的提交,记录撤销操作。
    • 适用场景:适合撤销已推送到远程仓库的提交,尤其是在多人协作项目中。

2. 历史记录的影响

  • git reset:重写历史,删除指定提交及之后的所有提交。
  • git revert:保留历史,新增一个反向提交,历史记录线性增长。

3. 协作安全性

  • git reset:不安全。如果分支已推送,强制推送(git push --force)会覆盖远程历史,可能导致其他开发者的工作丢失。
  • git revert:安全。新增提交不会破坏历史,其他开发者可以正常拉取和合并。

4. 适用场景对比

特性 git reset git revert
历史记录 重写历史,删除提交 保留历史,新增反向提交
适用场景 本地未推送的提交 已推送的提交或多人协作
协作安全性 不安全(需谨慎使用 --force 安全
操作复杂度 简单,但可能丢失修改 稍复杂,需处理冲突

Git Reset 的用法

git reset 有三种主要模式,根据参数不同,影响范围也不同:

  • --soft:仅移动 HEAD,不修改暂存区和工作目录。
  • --mixed(默认):移动 HEAD,重置暂存区,但不修改工作目录。
  • --hard:移动 HEAD,重置暂存区和工作目录,所有更改丢失。

示例:撤销未推送的提交

假设当前提交历史如下:

* 5d8f9a2 (HEAD -> feature) fix typo
* e7b3c4d add new feature
* 2a1b3c4 initial setup
* 9c8d7e6 (main) initial commit

你发现 5d8f9a2(fix typo)提交是错误的,想撤销。

1. 使用 --soft 撤销

  • 目标:撤销提交,但保留更改在暂存区。
  • 命令
    git reset --soft HEAD^  # 回退到上一个提交
  • 结果
    • 提交历史变为:
    • e7b3c4d (HEAD -> feature) add new feature
    • 2a1b3c4 initial setup
    • 9c8d7e6 initial commit
    • fix typo 的更改仍在暂存区,可以重新提交:
      git status

      输出:Changes to be committed: ...

2. 使用 --mixed 撤销(默认)

  • 目标:撤销提交,将更改放回工作目录(未暂存)。
  • 命令
    git reset --mixed HEAD^  # 或直接 git reset HEAD^
  • 结果
    • 提交历史同上。
    • 更改回到工作目录,未暂存:
      git status

      输出:Changes not staged for commit: ...

3. 使用 --hard 撤销

  • 目标:完全撤销提交,丢弃所有更改。
  • 命令
    git reset --hard HEAD^
  • 结果
    • 提交历史同上。
    • 所有更改(fix typo 的修改)被丢弃,无法恢复。
    • 警告:使用 --hard 会永久丢失未保存的更改,谨慎操作。

4. 撤销到更早的提交

如果需要回退到更早的提交(例如 2a1b3c4):

git reset --hard 2a1b3c4
  • 结果
    * 2a1b3c4 (HEAD -> feature) initial setup
    * 9c8d7e6 initial commit
    • e7b3c4d5d8f9a2 都被移除。

Git Revert 的用法

git revert 通过创建一个反向提交来撤销指定提交的更改,适合已推送的提交。

示例:撤销已推送的提交

假设提交历史如下,且已推送:

* 5d8f9a2 (HEAD -> feature, origin/feature) fix typo
* e7b3c4d add new feature
* 2a1b3c4 initial setup
* 9c8d7e6 (main) initial commit

你发现 e7b3c4d(add new feature)有问题,需要撤销。

1. 运行 git revert

  • 命令
    git revert e7b3c4d
  • 过程

    • Git 会创建一个新的提交,反向应用 e7b3c4d 的更改。
    • 可能会打开编辑器,让你编辑新提交信息,默认信息为:
      
      Revert "add new feature"

    This reverts commit e7b3c4d...

    
    - 保存并关闭。

2. 检查结果

  • 命令
    git log --oneline
  • 输出
    * a1b2c3d (HEAD -> feature) Revert "add new feature"
    * 5d8f9a2 fix typo
    * e7b3c4d add new feature
    * 2a1b3c4 initial setup
    * 9c8d7e6 initial commit
  • 说明
    • 新提交 a1b2c3d 反向应用了 e7b3c4d 的更改。
    • 历史记录完整保留。

3. 推送到远程

  • 命令
    git push
  • 结果:远程分支更新,其他开发者可以正常拉取。

4. 撤销多个提交

如果需要撤销连续的提交(例如 e7b3c4d5d8f9a2):

git revert e7b3c4d..5d8f9a2
  • 注意:需要按时间倒序列出范围(从旧到新)。
  • Git 会为每个提交创建一个反向提交。

解决冲突

git reset 的冲突

  • git reset 通常不会产生冲突,因为它直接重写历史。
  • 但如果有未提交的更改(工作目录或暂存区),可能需要先处理:
    git stash        # 暂存更改
    git reset --hard HEAD^
    git stash pop    # 恢复更改

git revert 的冲突

  • 如果 git revert 遇到冲突,Git 会暂停并提示:
    error: could not revert e7b3c4d... add new feature
    hint: after resolving the conflicts, mark the corrected paths
    hint: with 'git add ' or 'git rm '
    hint: and commit the result with 'git commit'
  • 解决步骤
    1. 打开冲突文件,手动解决冲突。
    2. 添加解决后的文件:
      git add 
    3. 继续 revert:
      git revert --continue
    4. 如果想放弃:
      git revert --abort

注意事项

  1. 备份
    • 在使用 git reset --hard 或推送前,创建备份分支:
      git branch backup-branch
  2. 远程协作
    • 如果使用 git reset 后强制推送(git push --force),确保通知团队成员,避免覆盖他人工作。
    • git revert 是协作中的首选。
  3. 恢复误操作
    • 如果 git reset --hard 后想恢复,使用 git reflog
      git reflog

      输出示例:

      5d8f9a2 HEAD@{0}: reset: moving to HEAD^
      e7b3c4d HEAD@{1}: commit: fix typo

      恢复:

      git reset --hard 5d8f9a2

实践场景对比

场景 1:撤销本地未推送的提交

  • 历史
    * 5d8f9a2 (HEAD -> feature) fix typo
    * e7b3c4d add new feature
    * 2a1b3c4 initial setup
  • 操作:使用 git reset
    git reset --soft HEAD^  # 撤销 fix typo,保留更改
    git commit -m "Add new feature and fix typo"
  • 结果
    * a1b2c3d (HEAD -> feature) Add new feature and fix typo
    * 2a1b3c4 initial setup

场景 2:撤销已推送的提交

  • 历史
    * 5d8f9a2 (HEAD -> feature, origin/feature) fix typo
    * e7b3c4d add new feature
    * 2a1b3c4 initial setup
  • 操作:使用 git revert
    git revert e7b3c4d  # 撤销 add new feature
    git push
  • 结果
    * b2c3d4e (HEAD -> feature, origin/feature) Revert "add new feature"
    * 5d8f9a2 fix typo
    * e7b3c4d add new feature
    * 2a1b3c4 initial setup

总结

  • git reset:适合本地开发,清理未推送的提交,重写历史,灵活但需谨慎。
  • git revert:适合协作环境,撤销已推送的提交,保留历史,安全但可能增加历史记录。
  • 选择建议
    • 如果提交未推送,使用 git reset 更高效。
    • 如果提交已推送,使用 git revert 更安全。
  • 最佳实践
    • 定期备份分支。
    • 熟悉 git reflog 以恢复误操作。
    • 在多人协作中优先使用 git revert

通过熟练掌握 git resetgit revert,你可以灵活应对各种撤销需求,提升 Git 使用的专业性。欢迎在评论区分享你的使用经验!

补充理解

git revert 操作可以理解为,针对某次commit A的内容进行反向的一次新的commit B,以还原为A提交之前的代码状态。且A因为比B旧,所以其他分支有A的提交再合并进来也没用,会被B覆盖。

如果想还原被git revert还原的commit内容,需要对revert的那次commit(如上述的commit B)再进行一次git revert操作,即可还原为最初的状态(包含commit A内容的状态)。

git reset则是对分支进行操作,直接reset到某次commit节点的状态(回到链表的某个节点),中间的所有commit都直接没了。


A Student on the way to full stack of Web3.