git 作为当前最流行的版本管理工具,简单、易用,而且功能强大,不过其依托的是一组极为简洁的数据结构。
这里简单介绍其核心概念。
简介
git 维护着一个微型的文件系统,其中的文件也被称作数据对象,所有的数据对象均存储于项目下面的 .git/objects
目录中。
$ git init foobar && cd foobar
$ echo 'Hello World !!!' > README.md
$ git add README.md
此时已经将文件添加到了 .git/objects
目录下了,可以通过如下命令查找。
$ find .git/objects -type f
.git/objects/2a/c7fb025641058bed0a8ebaa7a862d90bbb9522
该对象被称为 Blob 对象,可以通过下面的命令把文件内容打印出来,或者查看文件的类型。
$ git cat-file -p 2ac7fb
Hello World !!!
$ git cat-file -t 2ac7fb
blob
版本库中的每个文件,包括了图片、文本、二进制文件等,都会被映射为一个 Blob 对象。
除了 Blob 对象,还存储着另外三种数据对象:Tree、Commit、Tag 。
常用操作
----- 查看对象的值以及内容,可以指定具体的类型
$ git cat-file -t <SHA1>
$ git cat-file -p <SHA1>
$ git cat-file blob <SHA1>
$ git cat-file commit <SHA1>
可以通过如下命令查看所有对象的类型。
$ find .git/objects -type f -a ! -name "*pack*" | \
awk -F "/" '{printf("%s%s ", $3, $4); system("git cat-file -t "$3$4)}'
查看 blog
对象。
----- 将一个文件生成对应的SHA1值,也就是对象的ID
$ git hash-object filename
----- 查看对象的内容,如果文件不存在则会报错
$ git show <SHA1>
查看 commit
对象。
----- 列出所有的commit信息
$ git log --pretty=oneline
----- 查看commit的内容
$ git show <SHA1>
----- 该commit对象所对应的tree
$ git cat-file -p <SHA1>
Blob 对象
其全称为 Binary Large Object ,一个 Blob 对象就是一段二进制数据。
为了把文件映射为 Blob 对象,Git 会执行如下的步骤:
- 读取文件内容,并添加一段特殊标记到头部
"blob %u\0%s", len, content
,得到新的内容; - 对上述的输出内容执行 SHA-1 哈希加密,此时,会得到一个长度为 40 字符的 hash 值,例如上述的
2ac7fb025641058bed0a8ebaa7a862d90bbb9522
; - 取该 hash 值的前两位作为子目录,剩下的 38 位作为文件名;
- 然后对第一步输出的内容进行 zip 压缩,得到新的二进制内容,存入文件中。
查找过程
如果要查看某个分支的历史提交记录,那么使用过程如下。
首先会找到 HEAD 中对应的索引文件,该文件记录了最近一次 commit 对象的 hash 值。
$ cat .git/HEAD
ref: refs/heads/feature/resource
$ cat .git/refs/heads/feature/resource
8e9c66dca71bd8e6452cf7534d1271e1fc60d54e
$ git log --pretty=oneline
8e9c66dca71bd8e6452cf7534d1271e1fc60d54e Your Commit
... ...
$ git cat-file -p 8e9c66dca71bd8e6452cf7534d1271e1fc60d54e
tree 71a4f5f84f9fd37004204a243c4f1f37aefbe15f
parent 9dfe4f99303977ff25db9880cc457bdad1ad91e7
author foobar <foobar@foobar.com> 1555426113 +0800
committer foobar <foobar@foobar.com> 1555426113 +0800
Your Commit
$ git ls-tree 71a4f5f8 # 当前版本包含的数据
100644 blob 9e2b32adba25a2a1c6de532dc7ab004717c2171e CMakeLists.txt
100644 blob 311e372615b8926986e6a5e4a34f1c58a04ba003 README.md
040000 tree 9d0d57f47b4aaccb220fe8139f92c4bda5c8f54d include
$ git cat-file blob 311e3726 # 查看某个文件对应的内容
Hello World
$ git cat-file -p 9d0d57f4 # 查看某个tree对象对应的内容,或者git ls-tree 9d0d57f4
120000 blob d4715f1f09960daa402fc1b9fdbc0afb2b0927a1 common.h
100644 blob 5c99a3054cc022ad981260b18d1dddebc7c749b6 sockets.h
一个 tree 对象和 parent 对象是关键,tree 表示了当前 commit 对象下的所有内容,而 parent 对象指向了前一个 commit 对象。
$ git rev-parse HEAD
8e9c66dca71bd8e6452cf7534d1271e1fc60d54e
$ git rev-parse HEAD~
9dfe4f99303977ff25db9880cc457bdad1ad91e7
可以看到当前 tree 对象的 parent 对象与前一次的 commit 对应的 hash 值是相同的,这样就可以按照这一关联关系依次查找。
git 中绝大部分对象都是通过 SHA1 来标识,通过
rev-parse
可以将一些习惯用的标识转换为 SHA1 内部 ID ,例如HEAD^
origin/master
。 除此之外,还有一些其它的用法,例如git rev-parse --symbolic --tags
查看所有的 tag 信息,git rev-parse --symbolic --branches
本地所有的分支信息。