Git checkout vs reset vs restore vs switch vs revert

2021.09.04

本篇文章没什么技术含量,但是 git restore 和 switch 本身还算是蛮新的指令,今天花了一些功夫理解这些指令的关联和区别,而且没看到特别清楚的相关中文文章,因此还是做一个简单的记录。本篇文章主要参考git pro和这里.

从历史发展的角度看,git checkout 和 reset 都是历史悠久的指令,具有丰富的功能,理解起来稍微复杂一些。例如,最常见的分支切换使用checkout

git checkout <branch>

但是,我们还可以使用checkout去恢复文件,例如

git checkout -- <file>

将使用暂存区内容替换工作区指定文件内容。git checkout HEAD -- <file> 将使用版本库(HEAD)替换暂存区和工作区指定文件。

这样一来,本身就不怎么直观的 git checkout 同时拥有了"安全"的分支切换和"不安全"的文件恢复两方面的功能。即分支切换不会导致未保存的文件变更丢失(除非-f),而文件恢复则很可能使用历史版本覆盖了工作区的更改。而且,git checkout 有明显的歧义,即

git checkout hello

既有可能是切换分支又有可能是干掉名为hello的文件。尽管我们可以使用 -- 来区分分支名和文件名。

git checkout -- hello   (clobber the file)
git checkout hello --   (get the branch)

因此,git自2.23版本(2019年8月)后引入了 git switch 和 git restore 来分割 git checkout 两方面的任务。git switch 较为简单,就是明确的分支切换,如果本地有未commit的更改将报错并停止切换(除非-f)。git switch 使用 -c 参数指定分支的新建。

git restore 则允许我们在一行指令中指定恢复的来源和目标

git restore [<options>] [--source=<tree>] [--staged] [--worktree] --pathspec-from-file=<file> [--pathspec-file-nul]

旧版本的文件恢复指令,例如从HEAD中取出恢复到暂存区和工作区:git checkout -- <file> 可被替换为 git restore --staged --worktree -- <file>。恢复到暂存区(工作区不作变动,实际上是unstage):git reset -- <file> 可被替换为 git restore --staged -- <file>.

目前 git switch 和 restore 仍属于实验性功能。

git reset 也是历史悠久的指令,用于将HEAD移动到指定状态,拥有 --soft/mixed/hard 这三个常见的选项,选项指定了是否要重写暂存区和工作区使其看起来分别像HEAD和暂存区。例如 git reset --hard HEAD~3 弹出最后3个commit. 同 git restore 功能重合的部分可考虑优先使用 git restore.

git revert 功能差别较为明显,将生成恢复指定目标的逆操作commit,例如 git revert HEAD~1,将生成一个新的commit,包含过去两个commit的逆操作。

总的来说,个人感觉原先的checkout更多是从实现角度设计的指令,restore 和 switch 的加入使得相关操作更加用户友好,降低了出错的概率,尽管绝大多数时间我们还是使用GUI工具进行各种操作...