这里我们创建一个新的git仓库,来看看当我们新提交一个commit时,git目录文件下objects文件内容是怎么变化的。

新建一个仓库

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
 G:\mygitea\GitLearn 
$ git init learn02
Initialized empty Git repository in G:/mygitea/GitLearn/learn02/.git/

G:\mygitea\GitLearn 
$ cd learn02

G:\mygitea\GitLearn\learn02   master 
$ mkdir doc

G:\mygitea\GitLearn\learn02   master 
$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

G:\mygitea\GitLearn\learn02   master 
$ echo "hello, world!" >> doc\readme.md

G:\mygitea\GitLearn\learn02   master ± 
$ cat doc\readme.md
"hello, world!"

G:\mygitea\GitLearn\learn02   master ± 
$ git status
On branch master

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)
doc/

nothing added to commit but untracked files present (use "git add" to track)

现在看看objects文件夹下有无内容:

1
2
3
4
5
6
7
 G:\mygitea\GitLearn\learn02   master ± 
$ ls -al .git\objects\
total 4
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 ./
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:59 ../
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 info/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 pack/

接着再git add加入暂存区看看objects目录下是否有内容:

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
 G:\mygitea\GitLearn\learn02   master ± 
$ git add doc/readme.md

G:\mygitea\GitLearn\learn02   master ± 
$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: doc/readme.md


G:\mygitea\GitLearn\learn02   master ± 
$ ls -al .git\objects\
total 4
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:14 ./
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:14 ../
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:14 d8/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 info/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 pack/

G:\mygitea\GitLearn\learn02   master ± 
$ ls -al .git\objects\d8\
total 1
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:14 ./
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:14 ../
-r--r--r-- 1 WhiteCookies 197121 33 5月 9 15:14 ed87105b8eba70ea70f4256bff9bf0dc7f22d7

G:\mygitea\GitLearn\learn02   master ± 
$ git cat-file -t d8ed87105b8eba70ea70f4256bff9bf0dc7f22d7
blob

G:\mygitea\GitLearn\learn02   master ± 
$ git cat-file -p d8ed87105b8eba70ea70f4256bff9bf0dc7f22d7
"hello, world!"

可以看到,加入新文件到暂存区后objects目录下生成了一个文件夹d8,存储了一个blob对象,内容刚好是我们添加进入暂存区的readme.md文件内容。

所以,git会先将暂存区的文件创建一个blob对象。

接着,我们提交commit来看看objects下文件变化:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
 G:\mygitea\GitLearn\learn02   master ± 
$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: doc/readme.md


G:\mygitea\GitLearn\learn02   master ± 
$ git commit -m "add readme"
[master (root-commit) 15f598b] add readme
1 file changed, 1 insertion(+)
create mode 100644 doc/readme.md

G:\mygitea\GitLearn\learn02   master 
$ git status
On branch master
nothing to commit, working tree clean

G:\mygitea\GitLearn\learn02   master 
$ tree /f .git\objects\
Folder PATH listing for volume 新加卷
Volume serial number is 406B-D0C3
G:\MYGITEA\GITLEARN\LEARN02\.GIT\OBJECTS
├───15
│ f598be27de83504f6da78d50efa7b5376d2854

├───44
│ 41ff53a05fbbd8442abd5cf60d5c7bbe1ef36f

├───d8
│ ed87105b8eba70ea70f4256bff9bf0dc7f22d7

├───fe
│ 918c8aeeca1c7f91755927447004db23d08b4a

├───info
└───pack

G:\mygitea\GitLearn\learn02   master 
$ git cat-file -t 15f598be
commit

G:\mygitea\GitLearn\learn02   master 
$ git cat-file -p 15f598be
tree 4441ff53a05fbbd8442abd5cf60d5c7bbe1ef36f
author GitHub Whiteco-okie <GitHub 1961663351@qq.com> 1652080699 +0800
committer GitHub Whiteco-okie <GitHub 1961663351@qq.com> 1652080699 +0800

add readme


G:\mygitea\GitLearn\learn02   master 
$ git cat-file -t 4441ff53a05f
tree

G:\mygitea\GitLearn\learn02   master 
$ git cat-file -p 4441ff53a05f
040000 tree fe918c8aeeca1c7f91755927447004db23d08b4a doc

G:\mygitea\GitLearn\learn02   master 
$ git cat-file -p fe918c8ae
100644 blob d8ed87105b8eba70ea70f4256bff9bf0dc7f22d7 readme.md


G:\mygitea\GitLearn\learn02   master 
$ git cat-file -t d8ed87105b8eb
blob


G:\mygitea\GitLearn\learn02   master 
$ git cat-file -t fe918c8aeec
tree

G:\mygitea\GitLearn\learn02   master 
$ git cat-file -p fe918c8aeec
100644 blob d8ed87105b8eba70ea70f4256bff9bf0dc7f22d7 readme.md

可以看到,commit后objects文件下增添了三个文件夹。其中两个是tree对象,有一个是commit对象,最后一个是一个blob对象。正如图所示:

总结

看到这里我得出一个经验,没有文件也就是没有blob对象的目录是不会被git管理的,因为git要对文件进行版本管理,所以没有必要对空目录生成对象。基于这一点,readme文件的全路径是这样:[仓库根目录]/doc/readme。那么tree的数量与全路径中“/”的数量一致。

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
# 针对上述描述进行佐证:

G:\mygitea\GitLearn\learn02   master 
$ mkdir test

G:\mygitea\GitLearn\learn02   master 
$ ls
doc/ test/

G:\mygitea\GitLearn\learn02   master 
$ git status
On branch master
nothing to commit, working tree clean

G:\mygitea\GitLearn\learn02   master 
$ ls -al .git\objects\
total 4
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:18 ./
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:29 ../
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:18 15/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:18 44/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:14 d8/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 15:18 fe/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 info/
drwxr-xr-x 1 WhiteCookies 197121 0 5月 9 14:57 pack/

Q1:老师这里 我实验了一下,当提交一次,对一个文件生成一个 Blob 对象后,在对该文件进行修改提交 ,又会产生一个新的 Blob, 这里是不是对同一文件多次修改提交后,会产生多个 Blob 对象?

你的理解都对,git也考虑到后面这个问题了,所以它会把松散的blob做整理,把内容相近的blob做增量存储。

Q2:最后那个小练习,为什么中间需要有2个tree,一个tree不可以吗?多个tree的原因是什么?

让blob成为基础组件,可以利用树把多个文件特定版本对应的blob进行灵活的组装。 blob(代表文件的内容)可以充分地被复用。

就commit对应的项目所有文件的组织来说,commit相当于根节点,blob相当于叶子节点存储文件值,其他的是tree节点。

Q3:刚刚看了一下具体结构, 关于每个 commit 当次提交文件的区分, 是通过比较当次 commit 和 parent 中所有不同的 tree 和 blob , 然后计算出的当次 commit 涉及的文件么?

不会,平常 Git 存储的完整文件——松散 loose 对象格式,但是 Git 会时不时对这些文件进行打包,删除原始文件,当向远程服务器推送的时候也会执行这个操作。自己可以执行 git gc 主动触发这一操作 详细解答可以看: https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E5%8C%85%E6%96%87%E4%BB%B6