Git学习之~对象

Git对象库探秘

通过查看日志的详尽输出,会看到非常多的SHA1哈希值。

1
$ git log -l --pretty=raw

图片1

一个提交中居然包含了三个SHA1哈希值表示的对象ID。

  • commit a67c6fdd0a0e809340944432a3bf7d78e7e10f31
    这是本次提交的唯一标识。

  • tree b79fd13fac802739388af44a81d72b6e1d68da89
    这是本次提交所对应的的目录树。

  • parent d3617dadd10c274e6e2b6caf47635d5771b9034c
    这是本次提交的父提交(上一次提交)。

研究Git对象ID的一个重量级武器:git cat-file命令。用下面的命令可以查看这三个ID的类型。

1
2
3
4
5
6
$ git cat-file -t a67c6f
commit
$ git cat-file -t b79f
tree
$ git cat-file d3617
commit

图片2

在引用对象ID的时候,没有必要把整个40位的ID写全,只需要从头开始的几位不冲突即可。
下面再使用git cat-file命令查看一下这几个对象的内容。

  • commit对象a67c6fdd0a0e809340944432a3bf7d78e7e10f31
1
$ git cat-file -p a67c6f

图片3

  • tree对象b79fd13fac802739388af44a81d72b6e1d68da89
1
$ git cat-file -p b79fd

图片4

  • commit对象d3617dadd10c274e6e2b6caf47635d5771b9034c
1
$ git cat-file -p d3617

图片5

在上面目录树(tree)对象中看到了一个新的类型的对象:blob对象。这个对象保存着文件welcome.txt的内容。

  • 该对象的类型为blob。
1
2
$ git cat-file -t d7230d74c5f8d61ff3c1adbfd3b035c09cb62c16
blob
  • 该对象的内容就是welcome.txt文件的内容。
1
2
3
$ git cat-file -p d7230d74c5f8d61ff3c1adbfd3b035c09cb62c16
Hello Git.
很高兴见到你。

图片6

这些对象保存在哪里?当然是Git库中的objects目录下(ID的前两位作为目录名,后38位作为文件名。)

图片7
用下面的命令可以看到这些对象在对象库中的实际位置。

1
for id in a67c6f b79fd d3617 d7230d; do ls .git/objects/${id:0:2}/${id:2}*;done

图片8

通过下面的命令可以看到提交对象之间相互关联的跟踪连。

1
$ git log --pretty=raw --graph a67c

图片9

上面的命令通过--graph参数可以看到提交链路,通过--pretty=raw参数以便显示每个提交对象的parent属性。
最后一个提交没有parent属性,所以跟踪连到此终结,这实际上就是提交的起点。

HEAD和Master探秘

现在先看下工作区的状态。

1
2
$ git status -s -b
## master

图片10

上面在显示工作区状态时,除了使用-s参数以显示精简输出外,还使用了-b参数以便能够同时显示出当前工作分支的名称。
使用git branch也可以显示当前的工作分支。

1
2
$ git branch 
* master

图片11
在master分支名称前面出现一个星号表明这个分支是当前工作分支。
现在连续执行下面三个命令会看到相同的输出:

1
2
3
4
5
$ git log -1 HEAD 

$ git log -1 master

$ git log -1 refs/heads/master

图片12

也就是说在当前版本库中,HEAD、master和refs/heads/master具有相同的指向。让我们去版本库.git目录中看一看。

1
2
3
4
5
$ find .git -name HEAD -o -name master
.git/HEAD
.git/logs/HEAD
.git/logs/refs/heads/master
.git/refs/heads/master

图片13

显示一下.git/HEAD的内容:

1
2
$ cat .git/HEAD
ref: refs/heads/master

图片14
把HEAD的内容翻译过来就是:“指向一个引用:refs/heads/master”。这个引用在文件.git/refs/heads/master

1
2
$ cat .git/refs/heads/master
a67c6fdd0a0e809340944432a3bf7d78e7e10f31

图片15

git cat-file命令查看下a67c6fdd0a0e809340944432a3bf7d78e7e10f31的内容。

  • 显示SHA1哈希值指代的数据类型。
1
2
$ git cat-file -t a67c6
commit
  • 显示提交的内容。
1
2
3
4
5
6
7
$ git cat-file -p a67c6
tree b79fd13fac802739388af44a81d72b6e1d68da89
parent d3617dadd10c274e6e2b6caf47635d5771b9034c
author wanghongbo <270028806@qq.com> 1560174530 +0800
committer wanghongbo <270028806@qq.com> 1560174530 +0800

哪个版本的数据会被提交?

图片16
原来分支master指向的是一个提交ID(最新提交)。这样的分支实现很巧妙:既然可以从任何提交开始建立一条历史跟踪连,
那么用一个文件指向这个链条的最新提交,那么这个文件就可以用于追踪整个提交历史了。这个文件就是.git/refs/heads/master文件。
下面看一个更接近于真实的版本库结构图:

图片17

目录.git/refs是保存引用的命名空间,其中.git/refs/heads目录下的引用又称为分支。对于分支既可以使用正规的长格式表示法,如refs/heads/master
也可以去掉前面的两级目录用master表示。Git有一个底层命令git rev-parse可以用于显示引用对应的提交ID。

1
2
3
4
5
6
7
8
$ git rev-parse master
a67c6fdd0a0e809340944432a3bf7d78e7e10f31

$ git rev-parse refs/heads/master
a67c6fdd0a0e809340944432a3bf7d78e7e10f31

$ git rev-parse HEAD
a67c6fdd0a0e809340944432a3bf7d78e7e10f31

图片18
可以看出它们都指向同一个对象。下面来展示一下提交的SHA1哈希值生成方法。

  • 看看HEAD对应的提交的内容。使用git cat-file命令。
1
2
3
4
5
6
7
$ git cat-file commit HEAD
tree b79fd13fac802739388af44a81d72b6e1d68da89
parent d3617dadd10c274e6e2b6caf47635d5771b9034c
author wanghongbo <270028806@qq.com> 1560174530 +0800
committer wanghongbo <270028806@qq.com> 1560174530 +0800

哪个版本的数据会被提交?

图片19

  • 提交信息中总共包含243个字符。
1
$ git cat-file commit HEAD | wc -c

图片20

  • 在提交信息的前面加上内容commit 243<null>(为空字符),然后执行SHA1哈希算法。
1
2
$ (printf "commit 243\000"; git cat-file commit HEAD) | sha1sum
a67c6fdd0a0e809340944432a3bf7d78e7e10f31 *-

图片21

  • 上面命令得到的哈希值和用git rev-parse看到的是一样的。
1
$ git rev-parse HEAD

图片22

下面看一看文件内容的SHA1哈希值生成方法。

  • 看看版本库中welcome.txt的内容,使用git cat-file命令。
1
2
3
$ git cat-file blob HEAD:welcome.txt
Hello Git.
很高兴见到你。
  • 文件总共包含33字节的内容。
1
2
$ git cat-file blob HEAD:welcome.txt | wc -c 
33
  • 在文件内容的前面加上blob 33<null>的内容,然后执行SHA1哈希算法。
1
2
$ (printf "blob 33\000"; git cat-file blob HEAD:welcome.txt) | sha1sum
d7230d74c5f8d61ff3c1adbfd3b035c09cb62c16 *-
  • 上面命令得到的哈希值和用git rev-parse看到的是一样的。
1
2
$ git rev-parse HEAD:welcome.txt
d7230d74c5f8d61ff3c1adbfd3b035c09cb62c16

图片23

最后再来看看树的SHA1哈希值的形成方法。

  • HEAD对应的树的内容共包含39个字节。
1
2
$ git cat-file tree HEAD^{tree} | wc -c
39
  • 在树的内容的前面加上tree 39<null>的内容,然后执行SHA1哈希算法。
1
2
$ (printf "tree 39\000"; git cat-file tree HEAD^{tree}) | sha1sum
b79fd13fac802739388af44a81d72b6e1d68da89 *-
  • 上面命令得到的哈希值和用git rev-parse看到的是一样的。
1
2
$ git rev-parse HEAD^{tree}
b79fd13fac802739388af44a81d72b6e1d68da89

图片24

Git提供了很多方法可以方便的访问Git库中的对象。

  • 采用部分SHA1哈希值。不必写全40位的哈希值,只采用开头的部分,不和现有其他的冲突即可。
  • 使用master代表分支master中最新的提交,使用全称refs/heads/master亦可。
  • 使用HEAD代表版本库中最近的一次提交。
  • 符号^可以用于指代父提交。例如:
    • HEAD^代表版本库中上一次提交,即最近一次提交的父提交。
    • HEAD^^则代表HEAD^的父提交。
  • 对于一个提交有多个父提交,可以在符号^后面用数字表示是第几个父提交。例如:
    • a5731^2含义是提交a5731的多个父提交中的第二个父提交。
    • HEAD^1相当于HEAD^含义是HEAD多个父提交中的第一个。
    • HEAD^^2含义是HEAD^(HEAD父提交)的多个父提交中的第二个。
  • 符号~<n>也可以用于指代祖先提交。下面两个表达式效果等同:
1
2
a5731~5
a5731^^^^^
  • 提交所对应的树对象,可以用类似如下的语法访问。
1
a5731^{tree}
  • 某一此提交对应的文件对象,可以用如下的语法访问。
1
a5731:path/to/file
  • 暂存区中的文件对象,可以用如下语法访问。
1
:path/to/file

下面使用git rev-parse练习下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git rev-parse HEAD 
a67c6fdd0a0e809340944432a3bf7d78e7e10f31

$ git cat-file -p a67c6f
tree b79fd13fac802739388af44a81d72b6e1d68da89
parent d3617dadd10c274e6e2b6caf47635d5771b9034c
author wanghongbo <270028806@qq.com> 1560174530 +0800
committer wanghongbo <270028806@qq.com> 1560174530 +0800

哪个版本的数据会被提交?

$ git rev-parse a67c6f^{tree}
b79fd13fac802739388af44a81d72b6e1d68da89

$ git rev-parse a67c6f^^{tree}
a070b35c2d55e057a2eead7b8ae1b06c3e4d8e3b

图片25

本文标题:Git学习之~对象

文章作者:王洪博

发布时间:2018年05月24日 - 23:05

最后更新:2019年09月12日 - 10:09

原始链接:http://whb1990.github.io/posts/779ee1e1.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%