Skip to main content

首页

Git内部对象

Linus 在 2007 年 Google Talk 上介绍 Git

  • 一个模块,只要数据关系定了,这个模块就定了。
  • git的核心需求:保存一个目录树和这个目录树的版本。

Git的四种对象

1

文件存放在.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) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。

主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。

它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。

参考

目录