Git内部对象
Linus 在 2007 年 Google Talk 上介绍 Git
- 一个模块,只要数据关系定了,这个模块就定了。
- git的核心需求:保存一个目录树和这个目录树的版本。
Git的四种对象
文件存放在.git/objects 目录下, 文件名由40个16进制字符组成,用SHA-1计算。
blob:存储文件对象,存储二进制内容,为存储文件内容
tree:存储工作目录当中目录的层级结构
commit:每次提交都会写入到object database对象存储系统中
tag:一个hash文件名的别名。
object database是怎样存储对象的
新文件生成公式: new_content = type + ‘ ’ + content.size + \0 + content
hash计算校验: sha = Digest::SHA1.hexdigest(new_content)
文件内容压缩方式: compressed = zlib::deflate(new_content)
压缩路径: path = “.git/objects/82/4aed035c0aa75d64c…”
文件名实际上只有38个字符,前两个字符是目录名。
实验
$ git init test
$ cd test
$ ls .git
HEAD config description hooks info objects refs
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
创建一个blob object
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
$ tree .git/objects
.git/objects
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── info
└── pack
3 directories, 1 file
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
$ ls
用stdin输入,写入一个git object,会在 .git/objects目录下创建一个object,而 当前工作目录是空的,并没有产生文件。
$ echo 'version 1' > test.txt
$ ls
test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
$ tree .git/objects
.git/objects
├── 83
│ └── baae61804e65cc73a7201a7252750c76066a30
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── info
└── pack
4 directories, 2 files
在当前工作目录中创建一个文件,然后把这个文件写入 git object中。
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ tree .git/objects
.git/objects
├── 1f
│ └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
├── 83
│ └── baae61804e65cc73a7201a7252750c76066a30
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── info
└── pack
5 directories, 3 files
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
version 1
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
version 2
修改test.txt的内容后,会重新创建一个git object文件。
$ mkdir tree
$ echo 'tree/version 1' > tree/test.txt
$ git hash-object -w tree/test.txt
$ tree .git/objects
.git/objects
├── 1f
│ └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
├── 83
│ └── baae61804e65cc73a7201a7252750c76066a30
├── 9d
│ └── 5af5d3fd0edf50f267e69760d7b6c0846032c3
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── info
└── pack
6 directories, 4 files
$ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.txt
tree/
nothing added to commit but untracked files present (use "git add" to track)
tree文件
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: test.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
tree/
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ tree .git/objects
.git/objects
├── 1f
│ └── 7a7a472abf3dd9643fd615f6da379c4acb3e3a
├── 83
│ └── baae61804e65cc73a7201a7252750c76066a30
├── 9d
│ └── 5af5d3fd0edf50f267e69760d7b6c0846032c3
├── d6
│ └── 70460b4b4aece5915caf5c68d12f560a9fe3e4
├── d8
│ └── 329fc1cc938780ffdd9f94e0d364e0ea74f579
├── info
└── pack
7 directories, 5 files
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
以上创建了一个树对象,包含一个test文件,文件的内容是83baae
$ git update-index --add --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ echo 'new file' > new.txt
$ git update-index --add new.txt
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
新的树对象包含两条文件记录,同时 test.txt 的 SHA-1 值(1f7a7a)是先前值的“第二版”。
可以将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录。
通过调用 git read-tree 命令,可以把树对象读入暂存区。
本例中,可以通过对该命令指定 –prefix 选项,将一个已有的树对象作为子树读入暂存区:
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
commit 对象
以上操作创建了一些blob文件,tree文件,代表了不同的项目快照。现在,我要知道谁保存了 这些快照,什么时候保存的,为什么保存等等,这就是commit对象。
$ echo 'first commit' | git commit-tree d8329f
f810d42c69f044726b4eca33c308ff0a0a3e2354
$ git cat-file -p f810d42c69f044726b4eca33c308ff0a0a3e2354
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author
committer
first commit
制定parent commit 对象
$ echo 'second commit' | git commit-tree 0155eb -p f810d4
5050785661cc9cf5d31551b555663363eeb97e14
$ echo 'third commit' | git commit-tree 3c4e9c -p 505078
e633db92c1c8ab434acd1c676b2b492f55562fc8
$ git cat-file -p e633db92c1c8ab434acd1c676b2b492f55562fc8
tree 3c4e9cd789d88d8d89c1073707c3585e41b0e614
parent 5050785661cc9cf5d31551b555663363eeb97e14
author
committer
third commit
$ git log --stat e633db
标签引用(tag object)
标签对象(tag object) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。
主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。
它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。