Git + Gerrit 第八课:reset 与 revert 撤销提交

本节目标

这一节学习 Git 中非常重要、也非常容易误用的内容:撤销。

你要理解这些问题:

复制代码
工作区修改怎么撤销?
暂存区修改怎么取消?
提交错了怎么办?
reset 是什么?
revert 是什么?
reset --soft、--mixed、--hard 有什么区别?
为什么团队协作中更推荐 revert?
Gerrit 工作流中应该如何撤销错误提交?

本节重点命令:

复制代码
git status
git restore
git restore --staged
git reset --soft
git reset --mixed
git reset --hard
git revert
git log --oneline
git reflog

1. 先分清三种撤销场景

学习撤销前,先判断你要撤销的是哪一层。

Git 常见撤销场景有三种:

复制代码
1. 文件改错了,还没有 git add
2. 文件已经 git add,但还没有 commit
3. 已经 commit 了,想撤销提交

对应命令大概是:

复制代码
撤销工作区修改      git restore 文件名
取消暂存            git restore --staged 文件名
撤销已经提交的 commit reset 或 revert

这一课重点讲第三种:

复制代码
已经 commit 之后,应该怎么撤销。

2. 复习:工作区和暂存区撤销

如果只是工作区改错,还没有 git add

复制代码
git restore readme.md

作用是:

复制代码
丢弃 readme.md 在工作区里的未暂存修改。

如果已经 git add,但还没有 commit

复制代码
git restore --staged readme.md

作用是:

复制代码
把 readme.md 从暂存区拿出来,但保留文件内容修改。

这两个命令在第二课已经学过。

这一课继续深入:

复制代码
如果已经 commit 了怎么办?

3. reset 是什么

reset 的核心作用是:

复制代码
移动当前分支指针,并根据参数决定是否影响暂存区和工作区。

假设当前提交历史是:

复制代码
A --- B --- C    main

当前分支 main 指向 C

如果执行:

复制代码
git reset B

那么 main 会从 C 移回 B

复制代码
A --- B    main
     \
      C    被移出当前分支历史

所以你可以先简单理解:

复制代码
reset 是让当前分支回到某个提交。

但关键是:

复制代码
reset 有三种常见模式:--soft、--mixed、--hard。

它们对工作区和暂存区的影响不同。

4. HEAD 是什么

学习 reset 前要理解 HEAD

HEAD 可以理解为:

复制代码
当前所在位置。

通常情况下,HEAD 指向当前分支的最新提交。

例如:

复制代码
A --- B --- C    main
              ^
             HEAD

常见写法:

复制代码
HEAD       当前提交
HEAD~1     当前提交的上一个提交
HEAD~2     当前提交往前两个提交
HEAD~3     当前提交往前三个提交

如果当前历史是:

复制代码
A --- B --- C --- D

那么:

复制代码
HEAD     = D
HEAD~1   = C
HEAD~2   = B
HEAD~3   = A

5. reset --soft

命令:

复制代码
git reset --soft HEAD~1

作用是:

复制代码
撤销最近一次 commit,但保留修改在暂存区。

假设历史是:

复制代码
A --- B --- C

执行:

复制代码
git reset --soft HEAD~1

结果:

复制代码
分支回到 B。
C 里的修改还在暂存区。

适合场景:

复制代码
刚提交完,发现 commit message 写错了。
刚提交完,发现还想把这个提交和下一次修改合在一起。
想撤销提交,但保留所有修改,并且保持已暂存状态。

查看状态时,你可能看到:

复制代码
Changes to be committed

说明修改还在暂存区。

6. reset --mixed

命令:

复制代码
git reset --mixed HEAD~1

或者简写:

复制代码
git reset HEAD~1

因为 --mixed 是默认模式。

作用是:

复制代码
撤销最近一次 commit,保留修改在工作区,但取消暂存。

假设历史是:

复制代码
A --- B --- C

执行:

复制代码
git reset HEAD~1

结果:

复制代码
分支回到 B。
C 里的修改还在文件里。
但这些修改没有进入暂存区。

查看状态时,你可能看到:

复制代码
Changes not staged for commit

适合场景:

复制代码
提交错了,想重新选择哪些文件提交。
想拆分一个提交。
想重新检查 diff 后再提交。

7. reset --hard

命令:

复制代码
git reset --hard HEAD~1

作用是:

复制代码
撤销最近一次 commit,并丢弃对应修改。

它会同时影响:

复制代码
分支指针
暂存区
工作区

也就是说:

复制代码
提交没了,文件修改也没了。

这是一个危险命令。

例如:

复制代码
git reset --hard HEAD~1

表示:

复制代码
回到上一个提交,并把当前工作区也恢复到那个状态。

适合场景很少:

复制代码
你非常确定最近的提交和修改都不要了。
你在本地做实验,想彻底回到之前状态。

新手要特别记住:

复制代码
不要随便使用 git reset --hard。

8. 三种 reset 对比

命令 撤销 commit 保留暂存区 保留工作区修改 风险
git reset --soft HEAD~1 较低
git reset --mixed HEAD~1 中等
git reset --hard HEAD~1

简单记忆:

复制代码
soft:提交撤了,修改还在暂存区。
mixed:提交撤了,修改还在工作区。
hard:提交撤了,修改也丢了。

9. reset 的重要风险

reset 会改变当前分支历史。

如果提交还只在你本地,没有推送给别人,使用 reset 通常问题不大。

但如果提交已经推送到远程,尤其是别人已经基于它开发:

复制代码
不要随便 reset 后强推。

因为这会重写公共历史。

团队协作中,重写公共历史会导致:

  • 别人的分支对不上

  • 远程提交消失

  • 合并关系混乱

  • 需要别人额外处理冲突

  • Gerrit 或 CI 状态混乱

所以简单原则是:

复制代码
本地未推送提交,可以考虑 reset。
已经推送或共享的提交,优先考虑 revert。

10. revert 是什么

revert 的作用是:

复制代码
创建一个新的提交,用来抵消某个旧提交的修改。

假设历史是:

复制代码
A --- B --- C    main

现在发现 C 有问题。

执行:

复制代码
git revert C

结果不是删除 C,而是新增一个提交 D

复制代码
A --- B --- C --- D    main

其中 D 的作用是:

复制代码
撤销 C 带来的代码变化。

所以:

复制代码
reset 是回到过去。
revert 是新增一个反向提交。

11. revert 为什么适合团队协作

revert 不会重写历史。

原来的提交还在:

复制代码
A --- B --- C --- D

大家的历史仍然一致。

这对团队协作很重要。

优点:

  • 不破坏远程历史

  • 别人的分支不会突然失去提交

  • 可以清楚看到哪个提交被撤销

  • 适合已经合入主分支的代码

  • 适合 Gerrit 或 CI 记录追踪

所以真实团队中经常说:

复制代码
已经共享出去的提交,不要 reset,应该 revert。

12. revert 基本用法

查看历史:

复制代码
git log --oneline

假设看到:

复制代码
def5678 添加错误配置
abc1234 添加基础文件

现在想撤销 def5678

复制代码
git revert def5678

Git 会生成一个新的提交,提交信息通常类似:

复制代码
Revert "添加错误配置"

查看历史:

复制代码
git log --oneline

可能变成:

复制代码
9999999 Revert "添加错误配置"
def5678 添加错误配置
abc1234 添加基础文件

13. revert 可能也会冲突

revert 是反向应用某个提交。

如果后来的代码已经改过同一块内容,revert 也可能冲突。

这时流程类似冲突解决:

复制代码
git status

# 手动解决冲突

git add 冲突文件
git revert --continue

如果不想继续 revert:

复制代码
git revert --abort

注意:

复制代码
revert 冲突解决后,用 git revert --continue。
不是 git commit。

14. reset 和 revert 的核心区别

对比项 reset revert
是否重写历史 不会
是否新增提交 通常不会
适合本地未推送提交 适合 也可以
适合已共享提交 不推荐 推荐
是否容易影响别人 可能会 较安全
常见用途 整理本地提交 撤销已发布或已合入修改

一句话区别:

复制代码
reset 是把历史指针移回去。
revert 是创建一个新提交来抵消旧提交。

15. Gerrit 中如何选择 reset 和 revert

15.1 Change 还没有合入

如果你的 Gerrit Change 还在评审中,没有 Submit。

你发现提交内容有问题,通常使用:

复制代码
git add .
git commit --amend --no-edit
git push origin HEAD:refs/for/master

如果想重新整理本地提交,也可以在本地使用:

复制代码
git reset --soft HEAD~1

或者:

复制代码
git reset HEAD~1

然后重新提交。

因为此时还没合入目标分支,影响范围比较小。

15.2 Change 已经合入

如果 Change 已经 Submit 到 master/main/develop

后来发现它有问题。

这时更推荐:

复制代码
git revert 提交ID

然后把 revert 提交推到 Gerrit 审查:

复制代码
git push origin HEAD:refs/for/master

原因是:

复制代码
已经合入的提交属于共享历史,不应该随便 reset。

16. 撤销最近一次本地提交但保留修改

场景:

复制代码
你刚 commit,发现提交信息不对,或者文件选错了。
还没有 push。

如果想保留修改在暂存区:

复制代码
git reset --soft HEAD~1

如果想保留修改在工作区,重新选择文件:

复制代码
git reset HEAD~1

然后重新检查:

复制代码
git status
git diff

重新提交:

复制代码
git add .
git commit -m "新的提交信息"

17. 彻底丢弃最近一次本地提交

场景:

复制代码
你做了一个实验提交,现在完全不要了。
还没有 push。

可以:

复制代码
git reset --hard HEAD~1

但执行前必须确认:

复制代码
git log --oneline
git status

再强调一次:

复制代码
git reset --hard 会丢弃修改。
新手不要把它当成普通撤销命令。

18. 撤销某个已经合入的提交

场景:

复制代码
某个提交已经进了主分支,现在发现有问题。

先找到提交 ID:

复制代码
git log --oneline

执行:

复制代码
git revert 提交ID

然后推送到 Gerrit:

复制代码
git push origin HEAD:refs/for/master

这会创建一个新的 Change:

复制代码
Revert "原提交标题"

评审通过后合入,主分支就撤销了原提交的效果。

19. git reflog:后悔药

如果你误用了 reset,先不要慌。

Git 有一个很重要的命令:

复制代码
git reflog

它会记录 HEAD 移动历史。

例如:

复制代码
abc1234 HEAD@{0}: reset: moving to HEAD~1
def5678 HEAD@{1}: commit: 添加错误配置

如果你发现刚才 reset 错了,可以尝试回到之前的提交:

复制代码
git reset --hard def5678

注意:

复制代码
reflog 是本地记录。
不是远程共享历史。

虽然 reflog 很有用,但不要依赖它来随便执行危险命令。

20. 实战练习一:reset --soft

创建练习仓库:

复制代码
mkdir git-reset-demo
cd git-reset-demo
git init

创建初始提交:

复制代码
echo "base" > readme.txt
git add readme.txt
git commit -m "添加基础文件"

创建第二次提交:

复制代码
echo "second line" >> readme.txt
git add readme.txt
git commit -m "添加第二行"

查看历史:

复制代码
git log --oneline

撤销最近一次提交,保留暂存:

复制代码
git reset --soft HEAD~1

查看状态:

复制代码
git status

你应该看到修改还在:

复制代码
Changes to be committed

21. 实战练习二:reset --mixed

在上一个练习基础上,如果你想取消暂存:

复制代码
git reset

或者从一个新提交开始:

复制代码
git reset HEAD~1

查看状态:

复制代码
git status

你会看到:

复制代码
Changes not staged for commit

说明:

复制代码
提交撤销了,修改还在工作区,但没有暂存。

22. 实战练习三:reset --hard

先重新提交:

复制代码
git add readme.txt
git commit -m "添加第二行"

确认历史:

复制代码
git log --oneline

彻底丢弃最近一次提交:

复制代码
git reset --hard HEAD~1

查看文件:

复制代码
type readme.txt

你会发现第二行也消失了。

这说明:

复制代码
reset --hard 不仅撤销提交,也丢弃文件修改。

23. 实战练习四:revert

创建一个错误提交:

复制代码
echo "bad config" > config.txt
git add config.txt
git commit -m "添加错误配置"

查看历史:

复制代码
git log --oneline

找到最新提交 ID,然后执行:

复制代码
git revert HEAD

查看历史:

复制代码
git log --oneline

你会看到一个新的提交:

复制代码
Revert "添加错误配置"

这说明:

复制代码
revert 没有删除原提交,而是新增一个反向提交。

24. 常见错误

错误一:把 reset --hard 当普通撤销

不要随便执行:

复制代码
git reset --hard

它会丢弃工作区修改。

错误二:对公共分支 reset 后强推

不要随便对 main/master/develop 做:

复制代码
git reset --hard 某个提交
git push --force

这会破坏团队历史。

错误三:已经合入的提交还用 reset 撤销

已经合入共享分支的提交,优先:

复制代码
git revert 提交ID

错误四:revert 冲突后执行 git commit

revert 冲突解决后应该:

复制代码
git revert --continue

不是直接 git commit

错误五:不知道 HEAD~1 是谁

执行 reset 前先看:

复制代码
git log --oneline

确认你要回到哪里。

25. 本节必须记住的命令

复制代码
git reset --soft HEAD~1
git reset HEAD~1
git reset --hard HEAD~1
git revert 提交ID
git revert --continue
git revert --abort
git reflog

对应含义:

复制代码
git reset --soft HEAD~1    撤销最近提交,修改保留在暂存区
git reset HEAD~1           撤销最近提交,修改保留在工作区
git reset --hard HEAD~1    撤销最近提交,并丢弃修改
git revert 提交ID           新建一个提交,抵消指定提交
git revert --continue      解决 revert 冲突后继续
git revert --abort         放弃当前 revert
git reflog                 查看 HEAD 移动历史

26. 本节总结

这一节你学习了 Git 撤销提交:

  • reset 会移动分支指针,可能重写历史

  • reset --soft 保留修改在暂存区

  • reset --mixed 保留修改在工作区

  • reset --hard 会丢弃修改,风险最高

  • revert 会新增一个反向提交,不重写历史

  • 本地未推送提交可以考虑 reset

  • 已经共享或合入的提交更推荐 revert

  • Gerrit 中已合入的问题通常通过 revert 新建 Change 来撤销

最重要的一句话:

复制代码
本地整理用 reset,团队协作用 revert。

下一节建议学习:

复制代码
Git cherry-pick:如何把某个提交复制到另一个分支,以及 Gerrit 中如何挑选修复提交。
相关推荐
思麟呀1 小时前
Git入门
git
Qres8211 小时前
hexo博客上传github page
git·github·hexo
繁星星繁2 小时前
Git 入门之道:从版本流转到基础操作
大数据·git·elasticsearch
wh_xia_jun18 小时前
Git 分支合并操作备忘录
git
满天星830357719 小时前
【Git】原理及使用(三)(分支管理)
linux·git
像风一样的男人@1 天前
warning: could not find UI helper ‘git-credential-manager-ui‘
git·ui
代钦塔拉1 天前
Git & GitHub 从入门到精通:全流程实战教程
git·github
晚风吹红霞1 天前
Linux下的趣味编程 —— 进度条、Git版本控制与GDB调试实战
linux·运维·git
xlq223221 天前
7.git
git