5. checkout 检出
在上一节,我们使用 reset 命令修改了引用分支的游标位置,让分支引用(比如:master 分支) 指向不同的提交 ID ,达到切换不同历史版本内容。我们默认操作的就是 master 分支,因为 .git/HEAD 指向的引用就是 refs/heads/master 。怎么改变 HEAD 的指向呢?改变之后又有什么影响呢?下面通过 checkout 命令的使用来仔细分析一下。
HEAD 的理解 : 是头指针,当有提交时, HEAD 指向的提交将会作为新提交的父级提交(变成老二了,老大是新的提交) 。
首先,看一下目前 HEAD 指向是什么:
image.png
可以看出,目前指向了分支 master ,可以通过如下命令查看目前所在分支信息:
image.png
再看一下我们现有的提交记录:
image.png
现在我们检出 befeee4 这个提交,看看会怎样:
image.png
输出了一大段内容,大概意思如下:
您正处于 “分离头指针” 状态,您可以检查、测试和提交而不影响任何分支。如果想保留此状态下的修改和提交,使用
-c参数调用switch命令来创建新的跟踪分支。如 :git switch -c <new-branch-name>,您现在处于befeee4,提交说明为check staged -- nice to meet you.
查看一下此时的 HEAD 保存的内容:
image.png
可以看到,保存的是一个具体的提交 ID ,而不是保存(分支)引用。
阶段性总结: “分离头指针” 状态指的就是 HEAD 头指针指向了一个提交 ID,而不是一个引用(分支)。
通过 reflog 查看日志时,也可以看到 HEAD 头指针更改了:由指向 master 分支变成了指向一个提交 ID 。
image.png
注意区分是 master 分支的变更还是 HEAD 头指针的变迁记录
我们查一下当前的 HEAD 和 master 对应的提交 ID ,会发现它们的指向并不一样:
image.png
可以看到,master 分支的指向并没有改变,仍旧指向原有的提交 ID ,这也说明了 checkout 命令和 reset 命令不同。
接下来,我们进行一些操作,分析一下输出结果 :
- 创建一个新文件(detached-commit.txt)并添加到暂存区中,查看一下状态:
image.png
输出表明:当前处于 “分离头状态”,其实截图红框已经显示当前不处于任何分支中,而处于某个提交 ID 。
- 进行提交:
image.png
输出表明:提交成功了,此时头指针指向了新的提交 08b3f7e 。
- 再次查看当前的
HEAD内容:
image.png
输出表明:仍然指向一个具体的 ID ,但是这个 ID 变成了最新的提交 ID 了,它的父 ID 是原来的 befeee4。在 befeee4 出现了分叉,分别指向 08b3f7e 和 master 分支原先的提交,因为 08b3f7e 本来就是 master 的历史提交。
- 查看一下提交日志,会发现:
image.png
输出表明:新的提交确实是建立在之前的提交基础上的。
- 切换回到
master分支 :
image.png
输出表明:已经切换到了 master 分支,但是在分离头状态有一个提交,最好通过 branch 命令给这个提交创建一个新分支。
- 再次查看当前的
HEAD内容:
image.png
输出表明:变回 master 引用 ,而不是具体提交 ID 了。
- 查询一下当前的提交历史:
image.png
输出表明:完全是之前 master 的提交历史了,之前在分离头状态的提交查不到了,新增的文件也不见了。
我们之前在分离头状态的提交还存在于版本库的对象库中吗?
我们验证一下:
image.png
可以看到这个提交仍在版本库中。
注意:由于这个提交没有被任何分支跟踪,因此并不能保证这个提交会永久存在。实际上当 reflog 中含有该提交的日志过期后,这个提交随时都会从版本库中移除。
因为没有给提交 ID (08b3f7e)创建分支来跟踪,所以不能通过分支引用来访问,而且随时可能被清除(实际项目中要新建分支来追踪),非常不方便。
如果这个提交是 master 分支所需要的,怎么办呢?
答:不能用 reset 命令,那样会丢掉 master 分支原来的提交,相当于现在历史链条中存在了分叉路,只能使用 merge 命令进行合并操作。
方案确认了,开始进行具体的合并操作:
image.png
先确认了当前处于哪个分支,接着进行 merge 操作,然后查看了文件列表和提交日志。日志表明出现了分叉,31ab701 在master 分支,08b3f7e 在分离头状态下提交的,最终在合并在新的提交 ID 4448fe87 中 。
如果仔细查看一下最新提交,会发现这个提交有两个父提交,这就是合并的奥秘:
image.png
最后,我们再深入了解一下 git checkout 命令:
-
git checkout <branch>这种用法会改变
HEAD头指针,通过切换分支来对提交进行跟踪,主要作用就是切换分支。 -
git checkout [-m] [-b|--orphan <new_branch>] [start_point]这种用法主要是创建并切换到新的分支,新的分支从
start_point指定的提交开始创建(默认从最新提交开始)。新分支和master分支没有什么不一样的,都是refs/heads命名空间下的引用。 -
git checkout branch检出
branch分支 ,更新HEAD指向branch分支,以及用branch指向的树更新暂存区和工作区。 -
git checkout -- filename用暂存区中的
filename文件覆盖工作区中的filename文件 。相当于取消自上次执行git add filename以来的本地修改。 -
git checkout branch -- filenameHEAD的指向不变,用branch所指向的分支的提交中的filename文件替换暂存区和工作中相应的文件,会直接覆盖掉。 -
git checkout .这条命令比较危险,会直接取消本地的所有修改,相当于用暂存区的所有文件直接覆盖本地文件,不给用户任何确认的机会!