使用 Git 的时候,如果需要重置本地目录到最新一次提交,一般会使用以下命令:
git reset--hard HEAD
一直以来我都以为 HEAD
是最新一次提交,那么 HEAD^
就是倒数第二次提交,和 HEAD^
类似的 HEAD~1
也是最近一次提交, HEAD~2
是倒数第二次提交,这样理解看起来似乎没有问题,因为一次次的使用证明 HEAD^
就是倒数第二次提交,直到在一个比较复杂的项目里想打印最近 10 条提交的 log,使用以下命令:
git log--pretty=format:'%s' HEAD~10..HEAD
结果出现了不止 10 条,才开始怀疑是不是理解错了。
可能因为之前操作的项目不复杂,大多没有很多合并操作,如果有,可能早就能发现 HEAD~2
不是倒数第二次提交,也就没有上面想用 git log --pretty=format:'%s' HEAD~10..HEAD
打印最近 10 条 log 的天真想法。
发现不对后,查了下资料,HEAD 并不是那么简单的指向最后一次提交那么简单,在 Git 的版本管理中,每一次提交 Git 会保存一个提交对象(commit object),而每一次提交产生的提交对象都有一个父对象,即本次提交的上次提交。这是 Git 的数据存储方式。简单了解到这里,详细的请移步至 1.1 起步 - 关于版本控制
Git 中有一个特殊的指针 HEAD
,它指向当前所在的本地分支(也可以认为是本地当前分支的别名),如果所有的提交都在同一个分支(假设是 master)产生,HEAD 应该指向 master 分支,如图所示:
图1
HEAD^
指向其父提交, HEAD^^
指向其父提交的父提交即祖父提交, ^
后面可以跟数字, HEAD^1
意为第一父提交, HEAD^2
为第二父提交,这个语法只有在合并分支时产生的提交中有效,合并分支时产生的提交有两个父提交,第一父提交为合并时所在的分支,第二父提交为合并到的分支。 HEAD~
形式中 ~
后面也可以跟数字, HEAD~1
为父提交, HEAD~2
为父提交的父提交即祖父提交。参见: 祖先引用
如果用以下命令看最新一次提交的更改内容,是可以达到想要的效果的:
git showHEAD
如果在 HEAD 后面加个 ^
,命令如下:
git showHEAD^
同样可以看到倒数第二条的修改内容。也可以加多个 ^
,加几个就是倒数第几条,都没有问题。
同样的,使用这样的命令查看倒数第 N 条的 更改内容,也是没有问题的:
git showHEAD~1 git showHEAD~2 git showHEAD~3 ...
一切看起来都很正常。在这样的一个仓库中,任何一次提交都是没有第二父提交的,因为没有合并产生。
如果有分支合并情况,假设目前的分支状态如下:
图 2
在 qwq3 的时候有分支 test 产生,于 db56 提交后,合并入 master 分支并产生提交 ad0b。
此时 git show HEAD
会显示最近一次提交及合并产生的那一次提交所修改的内容,这个没什么疑问,如果使用 git show HEAD^
呢,是会显示 20af 还是 db56 的修改内容?
根据 Git Pro 中的说明,HEAD 目前指向 master 分支,其第一父提交应为合并前的 db56,第二父提交为 20af。而 HEAD~1
形式的只会拿第一父提交往前追溯,那么基于这样的机制,使用 git show HEAD~10
是不一定出现倒数第 10 次的修改内容的,因为这样的追溯是会拐弯的。
如图 2 中的分支形式, HEAD~3
是指向 fc40 的,而如果按照文章开头的方法打印最近 3 条的日志的话,在 HEAD~3
到 HEAD
之间还有一个 20af 提交,所以并不止 3 条日志出现。
如果想打印最近 10 次提交的日志,正确的方法应该用如下命令:
git log-10
这样会按照提交时间顺序打印最近的 10 条提交的日志。