Git的reset与revert原理详解

小豆丁 20天前 ⋅ 13 阅读
聊天记录
头像

git的reset和revert原理?

头像

好的,我们来深入浅出地讲解 Git 中 resetrevert 的原理。这两个命令都用于“撤销”更改,但它们的底层机制和适用场景有本质区别。

理解它们的关键在于理解 Git 的三个核心区域和三个重要指针。

核心概念预备知识

  1. 三个区域

    • 工作目录 (Working Directory): 你当前在编辑器里看到和修改的文件。
    • 暂存区 (Staging Area / Index): 使用 git add 后,更改存放的地方,准备下一次提交。
    • 版本库 (Repository): 使用 git commit 后,更改被永久存储的地方,包含所有的提交历史。
  2. 一个关键指针:HEAD

    • HEAD 是一个指针,它通常指向当前所在的分支指针(比如 maindevelop)。
    • 分支指针(如 main)则指向某个特定的提交
    • 简单理解:HEAD 指向当前的最后一次提交

一、git reset(重置)

核心原理:移动 HEAD 指针(以及可选的分支指针)到指定的提交,从而“丢弃”掉一些提交。

你可以把 git reset 想象成一条时间线,HEAD 是当前你所在的时间点。reset 命令就是让你在时间线上“后退”到某个历史节点。

它有三种模式,区别在于“后退”时,如何处理工作目录暂存区的内容。

三种模式详解

假设我们有如下提交历史:A <- B <- C (HEAD, main)。当前在提交 C

模式 命令 HEAD 的位置 暂存区 (Index) 工作目录 (Working Directory) 效果说明
--soft git reset --soft B 移动到 B 不改变(C的更改仍在暂存区) 不改变 仅移动了HEAD指针。提交 C 中的更改被“取消提交”,但依然处于 git add 后的状态。适合重新提交。
--mixed (默认) git reset --mixed B 移动到 B 更新为提交B的状态(C的更改被移出暂存区) 不改变 最常用模式。移动HEAD指针,并且将暂存区恢复为提交B的状态。提交C的更改变成了未暂存的状态。适合重新修改和提交。
--hard git reset --hard B 移动到 B 更新为提交B的状态 更新为提交B的状态 危险模式。彻底回溯到提交B的那个时间点。提交C的更改以及所有未提交的工作目录更改都会被丢弃

重置示意图 (git reset --hard B)

重置前: A <- B <- C (HEAD -> main)
重置后: A <- B (HEAD -> main)
        (提交C在Git数据库中暂时存在,但会被垃圾回收机制最终删除)

使用场景与警告

  • --soft: 想撤销上次提交,但保留更改以便立即重新提交(比如修改提交信息)。
  • --mixed(默认): 想撤销上次提交,并且想重新检查代码,分多次提交。这是最安全的“撤销提交”方式。
  • --hard彻底丢弃最近的所有更改,让代码库回到某个干净的历史状态。警告:此操作可能造成工作丢失,需极其谨慎。

关键点:git reset 会重写历史。 如果被重置的提交 C 已经被推送到远程仓库,其他人可能已经基于 C 进行了工作,那么你再强制推送 (git push --force) 会导致严重的协作问题。


二、git revert(反转/还原)

核心原理:创建一个新的提交,这个新提交的内容恰好是“撤销”指定提交的更改。

你可以把 revert 想象成一次“外科手术”。它不删除或移动任何历史节点,而是在历史的前端新增一个“负负得正”的提交,来抵消掉之前某个提交的效果。

工作原理详解

同样以历史 A &lt;- B &lt;- C (HEAD, main) 为例。

如果我们想撤销提交 B 引入的更改,但不删除提交 B 本身,我们执行:

BASH
git revert B

Git 会:

  1. 计算出提交 B 与其父提交 A 之间的差异(即 B 改了哪些内容)。
  2. 尝试应用这个差异的逆操作。比如,如果 B 新增了一行代码,revert 就会删除那行代码;如果 B 删除了一行,revert 就会把它加回来。
  3. 创建一个新的提交 R,其中包含了上述逆操作的结果。Git 会让你为这个新提交输入信息(如 “Revert ‘Commit B’…”)。

反转后的历史:

A <- B <- C <- R (HEAD -> main)

提交 R 的内容,等同于直接来自提交 A 的内容(就撤销 B 的更改而言)。历史是线性的、完整的。

使用场景与优点

  • 安全撤销公共历史:当你要撤销的提交已经推送到公共仓库(如 GitHub)时,这是唯一安全的方法。因为它不会重写历史,不会影响其他协作者。
  • 记录撤销操作:撤销行为本身也被记录为一个新的提交,便于审计和追踪为什么某个更改被移除了。

三、核心区别总结

特性 git reset git revert
操作对象 HEAD 指针(和分支指针) 具体某个提交的内容
历史记录 重写历史,丢弃旧的提交 添加新历史,保留所有旧提交
适用分支 主要用于本地分支,未推送的更改 主要用于公共分支,已推送的更改
安全性 风险较高(特别是 --hard),可能丢失工作 非常安全,不会丢失已提交的工作
结果 将分支的顶端移动到另一个提交 创建一个新的提交来抵消之前提交的更改

如何选择?

  • 场景一:我刚做了一个本地提交,但发现有问题,想撤销它。

    • 选择:git reset HEAD~1 (或 git reset --mixed HEAD~1)。这是最直接的方法,让提交记录保持干净。
  • 场景二:我已经把一个有问题的提交推送到了团队的公共仓库(如 main 分支)。

    • 选择:git revert &lt;坏提交的哈希&gt;。这是唯一正确的选择,可以避免破坏团队其他成员的历史记录。
  • 场景三:我git add 了一些文件,但现在我不想提交它们了。

    • 选择:git reset (默认 --mixed)。这将取消暂存这些文件,但保留你在工作目录的修改。
  • 场景四:我想彻底丢弃所有未提交的更改(包括工作目录的修改)。

    • 选择:git reset --hard HEAD。这将让工作目录和暂存区完全回退到最近一次提交的状态。请确保你真的想丢弃这些更改!

全部评论: 0

    我有话说: