checkout 命令区分两种情况(嗯,实际上是“很多”,但让我们从这两种情况开始 :-)):
- “我想‘上一个分支’;这是一个分支名称”:例如,
git checkout branch。
- “我想查看某个特定的修订版本,并关闭任何分支;这是一个不是分支名称的分支标识符”:例如,
git checkout 6240c5c。
(我个人认为这些应该使用不同的命令名称,但这只是我。另一方面,它会消除下面描述的所有奇怪之处。编辑,2020 年 6 月:在 Git 2.23 或稍后,这些 现在 在一个单独的命令中:git switch 是分支转换器,它需要 --detach 去一个分离的 HEAD;git restore 实现后面提到的文件恢复器这个帖子。不过,您仍然可以像在 2.23 之前的 Git 中一样使用现有的 git checkout 命令。)
现在,假设您想要前者。只需写出分支名称,不带refs/heads/ 部分,这是最容易编写的。
如果您想要后者,您可以通过gitrevisions 中列出的任何方法指定修订版,除了任何导致“进入分支”的方法。
无论出于何种原因,此处选择的算法(记录在manual page 中的<branch> 下)是这样的:如果您写了一个名称,在向其添加refs/heads/ 时,名称一个分支,git checkout 将把你放在“那个分支上”。如果您指定@{-<em>N</em>} 或-,它将在HEAD reflog 中查找第N 个较旧的分支(- 表示@{-1})。否则,它会选择第二种方法,为您提供“分离的 HEAD”。 即使名称是 gitrevisions 中为避免歧义而建议的名称,也是如此,即 heads/xyz 当有另一个 xyz 时也是如此。 (但是:您可以添加--detach 以避免“进入分支”的情况,即使它会进入分支。)
这也与 gitrevisions 文档中列出的解析规则相矛盾。为了证明这一点(虽然很难看到),我制作了一个标签和一个同名的分支,derp2:
$ git checkout derp2
warning: refname 'derp2' is ambiguous.
Previous HEAD position was ...
Switched to branch 'derp2'
这将我放在了分支上,而不是分离并转到标记的修订版。
$ git show derp2
warning: refname 'derp2' is ambiguous.
...
这向我展示了标记版本,gitrevisions 说它应该这样做。
附注:“进入分支”实际上意味着“将对分支名称的符号引用放入 git 目录中名为 HEAD 的文件中”。符号引用是文字文本ref: (带有尾随空格),后跟完整的分支名称,例如refs/heads/derp2。 git checkout 要求没有 refs/heads/ 部分的名称以添加 ref: refs/heads/ 部分似乎有点不一致,但那是你的 git。 :-) 这可能有一些历史原因:最初,作为符号引用,HEAD 文件实际上是分支文件的符号链接,它始终是一个文件。这些天来,部分是因为 Windows,部分是因为代码演变,它有 ref: 字符串,并且引用可能会被“打包”,因此无论如何都不能作为单独的文件使用。
相反,“分离的 HEAD”实际上意味着“将原始 SHA-1 放入 HEAD 文件”。除了在这个文件中有一个数值之外,git 的行为方式与“在分支上”时的行为方式相同:添加新提交仍然有效,新提交的父级是当前提交。合并仍然可以完成,合并提交的父母是当前和待合并的提交。 HEAD 文件会在每次新提交发生时更新。1 在任何时候,您都可以创建指向当前提交的新分支或标签标签,以使新的提交链成为即使您关闭“分离的 HEAD”,也可以防止将来的垃圾收集;或者您可以简单地切换并让新的提交(如果有)被通常的垃圾收集取出。 (注意HEAD reflog 会在一段时间内阻止这种情况,我认为默认为 30 天。)
[1如果你在“一个分支上”,同样的自动更新也会发生,它只是发生在HEAD所指的分支上。也就是说,如果你在分支 B 上并且你添加了一个新的提交,HEAD 仍然是 ref: refs/heads/B,但现在你通过 git rev-parse <em>B</em> 获得的提交 ID 是您刚刚添加的新提交。这就是分支“增长”的方式:“在分支上”添加的新提交会导致分支引用自动向前移动。同样,当处于这种“分离的 HEAD”状态时,添加的新提交会导致 HEAD 自动前进。]
为了完整起见,这里列出了 git checkout 可以做的其他事情,如果我有这样的权力,我可能会输入各种单独的命令:
- 查看某些路径的特定版本,通过索引写入:
git checkout revspec -- path ...
- 创建一个新分支:
git checkout -b newbranch(加上git branch 的选项)
- 创建一个新分支,当你对其进行提交时,它将成为根提交:
git checkout --orphan(这会将你置于一个尚不存在的“分支”上,即将ref: refs/heads/<em>branch-name</em> 写入HEAD 但不创建分支branch-name;这也是master 是新存储库中未诞生分支的原因)
- 创建或重新创建合并或合并冲突:
git checkout -m ...
- 通过选择合并的一侧或另一侧来解决合并冲突:
git checkout --ours、git checkout --theirs
- 在存储库对象和工作树文件之间交互式选择补丁,类似于
git add --patch:git checkout --patch