检出与重置的区别

reset 一样,checkout 也操纵三棵树,不过它有一点不同,这取决于你是否传给该命令一个文件路径

不带路径检出

运行 git checkout [branch] 与运行 git reset --hard [branch] 非常相似,它会更新所有三棵树使其看起来像 [branch],不过有两点重要的区别

  • 首先不同于 reset --hardcheckout 对工作目录是安全的,它会通过检查来确保不会将已更改的文件吹走。 其实它还更聪明一些。它会在工作目录中先试着简单合并一下,这样所有_还未修改过的_文件都会被更新。 而 reset --hard 则会不做检查就全面地替换所有东西。

  • 第二个重要的区别是如何更新 HEADreset 会移动 HEAD 分支的指向,而 checkout 只会移动 HEAD 自身来指向另一个分支。

例如,假设我们有 masterdevelop 分支,它们分别指向不同的提交;我们现在在 develop 上(所以 HEAD 指向它)。

1
2
3
$ git log --all --oneline --graph
* 3e57e67 (HEAD -> develop) commit B
* a08141d (master) commit A

image-20240403160341122

如果我们运行 git reset master,那么 develop 自身现在会和 master 指向同一个提交:

1
2
3
4
5
6
MINGW64 /d/tmp/git-playground1 (develop)
$ git reset master

MINGW64 /d/tmp/git-playground1 (develop)
$ git log --all --oneline --graph
* a08141d (HEAD -> develop, master) commit A

image-20240403160420938

而如果我们运行 git checkout master 的话,develop 不会移动,HEAD 自身会移动。 现在 HEAD 将会指向 master

1
2
3
4
5
6
7
8
MINGW64 /d/tmp/git-playground2 (develop) # develop分支
$ git checkout master
Switched to branch 'master'

MINGW64 /d/tmp/git-playground2 (master) # master分支
$ git log --all --oneline --graph
* 3e57e67 (develop) commit B
* a08141d (HEAD -> master) commit A

image-20240403160454963

所以,虽然在这两种情况下我们都移动 HEAD 使其指向了提交 A,但_做法_是非常不同的。 reset 会移动 HEAD 分支的指向(比如上面的develop指针),而 checkout 则移动 HEAD 自身,不移动develop指针。

带路径检出

运行 checkout 的另一种方式就是指定一个文件路径,这会像 reset 一样不会移动 HEAD

它就像 git reset [branch] file 那样用该次提交中的那个文件更新索引Index),但是它也会覆盖工作目录中对应的文件。 它就像是 git reset --hard [branch] file(如果 reset 允许你这样运行的话)- 这样对工作目录并不安全,它也不会移动 HEAD

此外,同 git resetgit add 一样,checkout 也接受一个 --patch 选项,允许你根据选择一块一块地恢复文件内容。

总结

希望你现在熟悉并理解了 reset 命令,不过关于它和 checkout 之间的区别,你可能还是会有点困惑,毕竟不太可能记住不同调用的所有规则。

下面的速查表列出了命令对树的影响。 “HEAD” 一列中的 “REF” 表示该命令移动了 HEAD 指向的分支引用,而HEAD 则表示只移动了 HEAD 自身。 特别注意 WD Safe? 一列 - 如果它标记为 NO,那么运行该命令之前请考虑一下。

HEAD Index Workdir WD Safe?
Commit Level
reset --soft [commit] REF NO NO YES
reset [commit] YES
reset --hard [commit] YES NO
checkout [commit] HEAD YES
File Level
reset (commit) [file] NO YES NO YES
checkout (commit) [file] YES NO