Git 是一套内容寻址文件系统。什么意思呢?

就是Git的核心部分是一个简单的键值数据库(key-value data store)。你可以向该数据库插入任意类型的内容,并会返回一个键值,通过该键值可以在任何时候再取出该内容。

1. Git对象的存放目录

Git中对象都保存在本地版本库的.git/objects 目录(即:对象数据库)中。

首先初使化一个Git仓库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
➜  ~ mkdir gitObject           

➜  ~ cd gitObject       

➜  gitObject git init .             

已初始化空的 Git 仓库于 /Users/taylor/gitObject/.git/

➜  gitObject git:(master) cd .git     

➜  .git git:(master) ll

total 24

-rw-r--r--   1 taylor  staff    23B  9 15 20:30 HEAD

-rw-r--r--   1 taylor  staff   137B  9 15 20:30 config

-rw-r--r--   1 taylor  staff    73B  9 15 20:30 description

drwxr-xr-x  15 taylor  staff   480B  9 15 20:30 hooks

drwxr-xr-x   3 taylor  staff    96B  9 15 20:30 info

drwxr-xr-x   4 taylor  staff   128B  9 15 20:30 objects

drwxr-xr-x   4 taylor  staff   128B  9 15 20:30 refs

➜  .git git:(master) ll -a objects 

total 0

drwxr-xr-x  4 taylor  staff   128B  9 15 20:30 .

drwxr-xr-x  9 taylor  staff   288B  9 15 20:30 ..

drwxr-xr-x  2 taylor  staff    64B  9 15 20:30 info

drwxr-xr-x  2 taylor  staff    64B  9 15 20:30 pack

从上可以看到Git初始化一个本地版本库的时候,就已经初始化了objects目录,并在其中创建了packinfo子目录,但是没有其他常规文件,packinfo子目录中也没有文件。我们只关注objects目录下除了infopack目录之外的变化。

2. Git中对象类型

Git中对象类型有四种:blob(块)对象,tree(目录树)对象,commit(提交)对象和tag(标签)对象,这四种原子对象构成了Git高层数据结构的基础。

对象数据结构及SHA值

首先,对象文件数据结构如下图:

  • content: 表示数据内容
  • head: 对象头部信息
    • object type:对象类型,可选值为 blob, tree, commit
    • whitespace: 一个空格字符
    • content byte size:数据内容的字节数字符串
    • NUL:空字符,ASCII码值为0

然后, 对象的SHA值就是对上面这个数据结构执行SHA1 hash摘要算法得到的。

3种对象不同的是content部分,查看content命令如下:

1
2
3
git cat-file -p SHA-1
# 或者
git show SHA-A

3. blob对象定义

blob对象又叫数据对象。

blob对象是用来存储文本内容的。即把一个文本文件的内容,作为一个blob对象存储在Git系统中。

  • Git中blob对象就是对应文件系统中的文件,确切的说是文件的内容,包含
    键:一个hash值和校验值的组合,
    值:文件的内容。
  • 比较特殊的是:blob对象只存内容,不存文件名,文件名在tree对象中保存。

blob对象存储方式如下图

4. blob对象说明

通过底层命令 git hash-object 来演示,该命令可将任意数据保存于 .git/objects 目录(即 对象数据库)中,并返回指向该数据对象的唯一的键。

1)创建一个新的数据对象,并将它手动存入你的新Git数据库中:

1
2
3
➜ gitObject git:(master) ✗ echo 'git object test content' | git hash-object -w --stdin

cb2eb834126f53952590c448f14fece6cbb1bff3

说明:这是在Git中以最简单的形式存储数据到Git版本库中,git hash-object命令会接受你传给它的东西,而它只会返回,可以存储在Git仓库中数据对象的唯一的键。

命令含义如下:

  • git hash-object:Git底层命令,可以根据传入的文本内容返回表示这些内容的键值。
  • git object test content:为文本文件中的内容。
  • -w选项:表示hash-object命令将数据对象存储到Git数据库中;若不指定此选项,则该命令仅返回对应的键(也就是那串Hash值)
  • --stdin 选项:表示该命令从标准输入(比如键盘)读取内容,若不指定此选项,则须在命令尾部给出待存储文件的路径。例如:git hash-object -w 文件路径

此命令输出一个长度为40个字符的校验和,这是一个SHA-1哈希值,如上cb2eb834126f53952590c448f14fece6cbb1bff3。该值是文件原内容加上特定头部信息拼接起来,做散列计算得到的数值。(只要文本内容相同,计算出的结果都是一样的)

2)在计算机中查看本地版本库.git/objects 目录中的变化。

可以看到.git/objects 目录中多了一个cb文件夹,如下:

1
2
3
4
5
6
7
8
9
➜ gitObject git:(master) ✗ ll -a .git/objects/cb

total 8

drwxr-xr-x  3 taylor  staff    96B  9 15 20:37 .

drwxr-xr-x  5 taylor  staff   160B  9 15 20:37 ..

-r--r--r--  1 taylor  staff    40B  9 15 20:37 2eb834126f53952590c448f14fece6cbb1bff3

3)看本地版本库.git/objects 目录中的变化。

1
2
3
➜ gitObject git:(master) ✗ find .git/objects -type f                         

.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

我们可以看到cb目录和cb目录中的2eb834126f53952590c448f14fece6cbb1bff3文件。

这就是Git中一个blob对象的存储。

5. blob对象存储的方式

Git对象的寻址使用40位的16进制数表示,也就是SHA-1散列码,例如:cb2eb834126f53952590c448f14fece6cbb1bff3

为了管理方便,在文件系统中前两位作为.git/objects/ 子目录的名字,后38为作为文件名字。

提示:你可能感觉用40位作为Git对象的寻址ID

6. 查看blob对象内容

**我们需要根据Hash键读取数据,使用命令git cat-file -p 键**。

-p选项可指示该命令自动判断内容的类型,并为我们使用友好的格式显示内容。

1
2
3
➜ gitObject git:(master) ✗ git cat-file -p cb2e              

git object test content

提示:
cat命令直接读取Git对象文件,为什么是乱码信息?

文件内容是先通过 zlib 压缩,然后将 zlib 压缩后的内容写入磁盘文件(SHA-1 前两个字符作为子目录名称,后 38 个字符作为子目录文件的名称)

7. 查看Git对象的类型

通过git cat-file -t 键命令,可以查看.git/objects 目录中Git对象的类型

1
2
3
➜ gitObject git:(master) ✗ git cat-file -t cb2e                                    

blob

这里也说明,我们之前存储的Git对象是一个blob对象。

8. Git管理文件

至此,你已经掌握了如何向 Git 中存入内容,以及如何将它们取出。

我们同样可以将这些操作应用于文件中的内容。 例如,可以对一个文件进行简单的版本控制。

1)首先,创建一个文件。

1
2
3
4
5
➜ gitObject git:(master) ✗ echo "hello-git.txt v1" > hello-git.txt

➜ gitObject git:(master) ✗  cat hello-git.txt

hello-git.txt v1

此时文件还有被Git管理。

2)将hello-git.txt文件存入Git数据库。

1
2
3
➜  gitObject git:(master) ✗ git hash-object -w ./hello-git.txt

a620c95d3001e1f64cecfc6715f9750cc7bbbf98

3)查看Git数据库内容。

1
2
3
4
5
➜  gitObject git:(master) ✗ find .git/objects -type f

.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98

.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

可以看到有多了一个a6子目录,就说明有新增了一个对象。

4)查看a6对象的内容。

1
2
3
➜  gitObject git:(master) ✗ git cat-file -p a620

hello-git.txt v1

5)查看a6对象的类型。

1
2
3
➜  gitObject git:(master) ✗ git cat-file -t a620

blob

可以看到,不管是你存储一个文件还是存储控制台内容到Git中,最终存储到Git数据库中的都是一个blob类型的Git对象。(即:blob对象是存储数据内容的)

9. Git管理修改过的文件

1)我们继续向hello-git.txt文件中添加内容。

1
2
3
4
5
6
7
➜  gitObject git:(master) ✗ echo "hello-git.txt v2" >> hello-git.txt

➜  gitObject git:(master) ✗ cat hello-git.txt  

hello-git.txt v1

hello-git.txt v2

2)查看Git数据库中的对象。

1
2
3
4
5
➜  gitObject git:(master) ✗ find .git/objects -type f

.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98

.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

可以看到还是之间的两个对象cba6,说明我们修改过的文件不会自动的存储到Git数据库中。

我们还需要手动的把修改后的hello-git.txt文件,存储到Git数据库中。

3)把修改后的hello-git.txt文件添加到Git数据库中。

1
2
3
➜  gitObject git:(master) ✗  git hash-object -w ./hello-git.txt

7c320a2d671f2ff177063f98343a0123432521dd

4)再次查看Git数据库中的对象。

1
2
3
4
5
6
7
➜  gitObject git:(master) ✗ find .git/objects -type f

.git/objects/7c/320a2d671f2ff177063f98343a0123432521dd

.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98

.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

我们可以看到Git数据库中多了一个7c对象。

5)查看7c对象存储的内容。

1
2
3
4
5
➜  gitObject git:(master) ✗ git cat-file -p 7c32

hello-git.txt v1

hello-git.txt v2

如上所示,我们可以看到7c对象存储了v1v2的内容,v1内容即在a6对象中,也在7c对象中。所以对于Git来说,存储的并不是文件内容的增量,而是全量文件。只要有变动,校验和不同就会产生一个新的Blob对象

10. blob对象总结

  • Git的核心部分是一个简单的键值数据库(key-value data store),键就是文本内容的Hash,值就是文本内容。
  • blob对象都存储在.git/objects 目录中,子目录+目录中的文件名,就是40位Hash值,也就是对象的键值。
  • 通过这个键就能找到对应的内容。
  • 每个文本内容存储到Git数据库的时候,内容都会进行zlib 压缩再存储。
    - blob对象存储的是文件的内容,相同的内容(校验和相同)不产生新的blob对象,不同就会生成一个新的blob。
  • blob对象并没有存储文件名。

11. 问题

我们对文件做一次修改,存储到Git数据库中,都会在Git数据库中创建一个新的blob对象。而在实际的工作中,我们需要做很多的改动,才提交一个版本,我们是否可以用一个blob对象代表整个项目的一次快照。

不能,只能代表一次存储时,一个文件中的内容,与之前数据内容相同时不新增Git对象,数据内容不同时再次新增blob对象。即:只要有新的内容被Git纳入管理,必定有一个blob对象与之对应。

那么还有如下问题:

  1. 记住文件的每一个版本所对应的SHA-1值并不现实。
  2. blob对象中,文件名并没有被保存,仅保存了文件的内容。

所以,没有文件名就没有办法通过文件名来读取数据,只能用40位Hash值读取,非常的不现实。

解决方案:树对象

提示:以上的操作都是在工作区和本地版本库之间进行操,不涉及暂存区,因为我们直接存储到了本地版本库中。

12. 本文用到的命令总结

Git底层命令:

  • git hash-object -w 文件路径:把工作区的一个文件提交到本地版本库中。
  • find .git/objects -type f:查看Git数据库中的对象。(Linux命令)
  • git cat-file -p 键:查看该Git对象的内容。
  • git cat-file -t 键:查看该Git对象的类型。
  • git show SHA-1: 也可以查看Git对象的内容

参考:

https://www.cnblogs.com/liuyuelinfighting/p/16189429.html