首先,让我们解决双连字符或双破折号,以使其不碍事(特别是因为这个问题不再有标记的重复项)。
Git 主要在the POSIX-approved fashion (see Guideline 10) 中使用它来表示选项参数和非选项参数之间的分界线。由于git checkout 接受分支名称,如git checkout master,以及文件(路径)名称,如git checkout README.txt,您可以使用-- 强制Git 将-- 之后的任何内容解释为文件名称,即使它是一个有效的分支名称。也就是说,如果你有一个 branch 和一个名为 master 的 file:
git checkout master
将检查 分支,但是:
git checkout -- master
将检查 文件(令人困惑的是,从当前 索引)。
接下来,我们需要解决git checkout 的怪癖。从the documentation 可以看出,git checkout 有许多“模式”(文档在概要中列出了六个单独的调用!)。关于 Git 糟糕的“用户体验”模型有各种各样的咆哮(质量参差不齐:Steve Bennet's 在我看来实际上很有用,尽管我自然不会 100% 同意 :-)),包括 git checkout有太多的操作模式。
特别是,您可以git checkout一个分支(切换分支),或git checkout一个或多个文件。后者从特定提交或索引中提取文件。当 Git 从提交中提取文件时,它首先将它们复制到到索引,然后将它们复制到索引,到工作树.
这个序列有一个潜在的实现原因,但它完全显示出来的事实是一个关键元素。我们需要对 Git 的索引有很多了解,因为 git checkout 和 git reset 都使用它,而且有时以不同的方式使用它。
我认为,绘制一个三向图或表格来说明当前(或HEAD)提交、索引和工作树是个好主意。假设:
- 有两个普通的已提交文件
README.md 和file.txt;
- 有一个新的
git add-ed 但未提交new.txt;
- 有一个名为
rmd.txt 的文件已被git rm-ed 但未提交;
- 并且有一个名为
untr.txt 的未跟踪文件。
每个实体(HEAD 提交、索引和工作树)现在都包含三个文件,但每个实体都包含一个不同的文件集。整个状态的表格如下所示:
HEAD index work-tree
-------------------------------
README.md README.md README.md
file.txt file.txt file.txt
new.txt new.txt
rmd.txt
untr.txt
除了这些之外,还有更多可能的状态:事实上,对于每个文件名,“in/not-in” HEAD、索引和工作树有七种种可能的组合(第八个组合是“不是所有三个”,在这种情况下,我们首先在谈论什么文件?!)。
checkout 和 reset 命令
您询问的两个命令git checkout 和git reset 都可以做很多事情。然而,每个特定的调用都将“完成的事情”减少到两个之一,我将在其中添加几个:
-
git checkout -- .:复制从索引,到工作树,仅
-
git checkout HEAD -- .:复制从 HEAD,到索引,然后到工作树
-
git reset --mixed:从 HEAD 重置索引(然后单独保留工作树)
-
git reset --hard:从 HEAD 重置索引,然后从索引重置工作树
这些重叠很多,但有几个至关重要的不同部分。
让我们特别考虑上面名为new.txt 的文件。它现在就在索引中,所以如果我们将从索引复制到工作树,我们将工作树副本替换为索引副本。例如,git checkout -- new.txt 就是这样做的。
相反,如果我们首先从 HEAD 复制到索引,则索引中的 new.txt 不会发生任何事情:new.txt 在HEAD 中不存在。因此,显式的git checkout HEAD -- new.txt 会失败,而git checkout HEAD -- . 会复制HEAD 中的文件并保持现有的两个new.txt 版本不受干扰。
文件rmd.txt 已从索引中消失,所以如果我们git checkout -- .,Git 不会看到它,也不会做任何事情。但是如果我们git checkout HEAD -- .,Git 会将rmd.txt 从HEAD 复制到索引中(现在它回来了),然后从索引复制到工作树(现在它也回到了那里)。
git reset 命令在不使用路径名参数时有一个关键区别。在这里,它从字面上重新设置索引以匹配提交。这意味着对于new.txt,它注意到文件不在HEAD 中,因此它删除 索引条目。如果与--hard 一起使用,它也会删除 工作树条目。同时rmd.txt 是 在HEAD 中,因此它会将其复制回索引,并与--hard 一起复制到工作树。
如果有未暂存的,即仅工作树,则对其他两个文件README.md 和file.txt 的更改,git checkout 和--hard 形式的git reset 消除这些更改。
如果这些文件有 暂存 更改(已复制到索引中的更改),则 git reset 取消暂存它们。 git checkout 的变体也是如此,您将其命名为 HEAD。但是,git checkout 的变体可以将索引文件复制回工作树,从而使这些分阶段的更改处于分阶段状态!
顶级与当前目录
最后,值得注意的是.,表示当前目录,在任何时候都可能与“Git 存储库顶部”不同:
$ git rev-parse --show-toplevel
/home/torek/src/kernel.org/git
$ pwd
/home/torek/src/kernel.org/git/Documentation
$ git rev-parse --show-cdup
../
这里,我在顶级目录git 的Documentation 子目录中,所以. 表示Documentation 及其子目录中的所有内容。例如,使用git checkout -- . 将检出(从索引中)所有Documentation 和Documentation/RelNotes 文件,但不会检出任何../builtin 文件。但是git reset,当不使用路径名时,将重置所有条目,包括..和../builtin的条目。