Git本质上是一个内容寻址文件系统(content-addressable filesystem)。Git的核心部分是一个简单的键值对数据库(key-value data store),可以将任意数据保存到.git/objects目录(即对象数据库),并通过返回的唯一键在任意时刻再次取回该数据。
Git数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
1,对象存储
Git对象存储的核心思想是将对象哈希化(完整性校验和快速查找),以单独文件的形式保存在.git/objects目录下(对象数据库)。每个对象使用文件内容的SHA-1哈希值作为唯一标识(object ID),然后在.git/objects目录下创建对应的子目录和文件,哈希值的前2个字符作为子目录名(避免目录中文件过多降低读取效率),后38个字符作为文件名。
SHA-1(Secure Hash Algorithm 1,安全哈希算法1)是一种密码学安全的哈希函数,在1995年由美国国家安全局正式发布。SHA-1可以生成一个被称为消息摘要的160位(20字节)哈希值,哈希值通常以40个十六进制数字的形式呈现。SHA-1算法已经被正式破解,即在一定计算复杂度内找到一组碰撞(两个不同的消息对应到相同的消息摘要)。在数字签名领域,推荐使用更安全的SHA-2或SHA-3替换SHA-1。为了提高安全性和可靠性,Git最新版本中已经支持SHA-256。
哈希函数(Hash Function)也称为散列函数是一个可以把任意大小的数据转换成固定长度的数据的函数。哈希函数具有以下特点:(1)相同的输入得到相同的输出(2)不同的输入得到不同的输出(理论上存在输出相同的可能性,即哈希值碰撞,但好的哈希函数能够抵抗碰撞,让这种可能性尽量地小),而且只要输入稍微变化输出就会产生巨大的变化(3)从输出推算输入在计算上是困难的。
存储格式
Git中所有对象都会额外加上一个特定头部信息(header),实际对象数据包含头部信息和原始内容。
<ascii-type-without-space> + <space> + <ascii-decimal-size> + <byte\0> + <binary-object-data>
类型标识(blob|tree|commit|tag)+空格+内容长度(字节数)+空字节(null byte)+原始内容
方式1:git hash-object实现数据对象(Blob Object)存储
$ git init git-store1
$ cd git-store1/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
# echo -n避免在输出中添加换行
# git hash-object -w --stdin从标准输入读取内容,写入内容到对象数据库并返回唯一键
$ echo -n "what is up, doc?" | git hash-object -w --stdin
bd9dbf5aae1a3862dd1526723246b20206e5fc37
$ find .git/objects -type f
.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37
$ git cat-file -t bd9dbf5aae1a3862dd1526723246b20206e5fc37
blob
$ git cat-file -p bd9dbf5aae1a3862dd1526723246b20206e5fc37
what is up, doc?
方式2:Go模拟实现数据对象(Blob Object)存储
$ git init git-store2
$ cd git-store2/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ vim main.go
package main
import (
"bytes"
"compress/zlib"
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
func main() {
// 原始内容
content := "what is up, doc?"
// 头部信息
header := fmt.Sprintf("blob %d\000", len(content))
// var header []byte
// header = append(header, "blob"...) // 类型标识
// header = append(header, ' ') // 空格
// header = append(header, fmt.Sprintf("%d", len(content))...) // 内容长度(字节数)
// header = append(header, 0) // 空字节(null byte)
// 将头部信息和原始内容拼接起来
store := []byte(header)
store = append(store, content...)
// 计算新内容的SHA-1哈希值
h := sha1.New()
h.Write(store)
// 前2个字符作为目录名,后38个字符作为文件名
sha1Hash := fmt.Sprintf("%x", h.Sum(nil))
dir := sha1Hash[0:2]
file := sha1Hash[2:]
fmt.Println(sha1Hash)
path := filepath.Join(".git", "objects", dir, file)
// 压缩新内容
var b bytes.Buffer
w := zlib.NewWriter(&b)
if _, err := w.Write(store); err != nil {
fmt.Println("Error compressing data: ", err)
return
}
// 调用Close刷新缓冲并关闭w!!!
w.Close()
// 创建目录
if err := os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm); err != nil {
fmt.Println("Error creating directory: ", err)
return
}
// 打开文件,写入压缩内容
if err := ioutil.WriteFile(path, b.Bytes(), 0644); err != nil {
fmt.Println("Error writing file: ", err)
return
}
}
$ go mod init git-store
$ go mod tidy
$ go run main.go
bd9dbf5aae1a3862dd1526723246b20206e5fc37
$ find .git/objects -type f
.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37
$ git cat-file -t bd9dbf5aae1a3862dd1526723246b20206e5fc37
blob
$ git cat-file -p bd9dbf5aae1a3862dd1526723246b20206e5fc37
what is up, doc?
方式3:Ruby模拟实现数据对象(Blob Object)存储
$ git init git-store3
$ cd git-store3/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ vim main.rb
require 'digest/sha1'
require 'zlib'
require 'fileutils'
# 原始内容
content = "what is up, doc?"
# 头部信息
header = "blob #{content.length}\0"
# 将头部信息和原始内容拼接起来
store = header + content
# 计算新内容的SHA-1哈希值
sha1 = Digest::SHA1.hexdigest(store)
puts sha1
# 前2个字符作为目录名,后38个字符作为文件名
path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
# zlib压缩新内容
zlib_content = Zlib::Deflate.deflate(store)
# 创建目录
FileUtils.mkdir_p(File.dirname(path))
# 打开文件,写入压缩内容
File.open(path, 'w') { |f| f.write zlib_content }
$ ruby main.rb
bd9dbf5aae1a3862dd1526723246b20206e5fc37
$ find .git/objects -type f
.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37
$ git cat-file -t bd9dbf5aae1a3862dd1526723246b20206e5fc37
blob
$ git cat-file -p bd9dbf5aae1a3862dd1526723246b20206e5fc37
what is up, doc?
2,对象类型
Git底层实际上是由一个个对象(Object)组成的,底层对象分为4种:
- 数据对象(Blob Object):存储文件内容(二进制大对象,binary large object)。只存文件内容,不存文件名。
数据对象的文件名是由文件内容的SHA-1哈希值决定的,文件内容由zlib压缩算法决定的。
数据对象具有以下特点:(1)相同的文件内容只会存储一次(2)空目录无法加入Git中(在目录下放一个名为“.keep”或“.gitkeep”的空文件)。
- 树对象(Tree Object):存储目录名和文件名。一个树对象包含一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象(形成目录层次结构)。
用默克尔树(Merkle tree)/哈希树(hash tree)实现。
- 提交对象(Commit Object):存储树对象和提交信息。首次提交产生的提交对象没有父对象,其他提交产生的提交对象有一个父对象(形成提交的有向无环图)。
A commit is usually created by git-commit(1), which creates a commit whose parent is normally the current HEAD, and whose tree is taken from the content currently stored in the index.
- 标签对象(Tag Object):指向一个提交对象。创建一个附注标签才会创建一个标签对象。
说明:Git和其它版本控制系统的主要差别在于对待数据的方式。Git保存的不是文件的变化或者差异,而是一系列不同时刻的快照 (snapshot)。每当提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。为了效率,如果文件没有修改,Git不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git对待数据更像是一个快照流。
代码演示
因为提交对象和标签对象与时间有关,为了演示方便,通过设置环境变量让SHA1哈希值为固定值。
$ export GIT_AUTHOR_DATE="1717248600 +0800"
$ export GIT_AUTHOR_NAME="leitiannet"
$ export GIT_AUTHOR_EMAIL="347341200@qq.com"
$ export GIT_COMMITTER_DATE="1717248600 +0800"
$ export GIT_COMMITTER_NAME="leitiannet"
$ export GIT_COMMITTER_EMAIL="347341200@qq.com"
$ git init git-object
$ cd git-object/
# 创建两个空文件
$ mkdir config; touch config/database.yml
$ touch README.md
# 版本1
$ echo -n 'version 1' > index.html
$ tree --noreport --dirsfirst -F -f
.
|-- ./config/
| `-- ./config/database.yml
|-- ./README.md
`-- ./index.html
$ git add *
$ git commit -m "first commit"
# 版本2
$ echo -n 'version 2' > index.html
$ git add *
$ git commit -m "second commit"
$ git log
commit b8f20f00cdbb36e72639d48f7681200817ccd6fe
Author: leitiannet <347341200@qq.com>
Date: Sat Jun 1 21:30:00 2024 +0800
second commit
commit c4343d3e6f0967c5dbcbb9a6ce3eb7649907e38f
Author: leitiannet <347341200@qq.com>
Date: Sat Jun 1 21:30:00 2024 +0800
first commit
$ git tag -a -m "tag version 1.2" v1.2 b8f20f00cdbb36e72639d48f7681200817ccd6fe
$ git count-objects
9 objects, 0 kilobytes
$ find .git/objects -type f
.git/objects/55/af8e5b36d666efb8281535bd98fe0f84275347
.git/objects/a6/18ce33da8d21bca841f18e6432fcabf15d4477
.git/objects/ad/ab0d71247d7effb8ac272d671664267571fff6
.git/objects/b0/8af892f082f4d3556ef3c969c8f6c43767b9a3
.git/objects/b8/9acddf72fcdf6fa6bf3afdf3cab4ac04217d56
.git/objects/b8/f20f00cdbb36e72639d48f7681200817ccd6fe
.git/objects/c4/343d3e6f0967c5dbcbb9a6ce3eb7649907e38f
.git/objects/e3/2092a83f837140c08e85a60ef16a6b2a208986
.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
# BATCH OUTPUT:%(objectname) %(objecttype) %(objectsize)
# --batch Print object information and contents for each object provided on stdin.
# --batch-check Print object information for each object provided on stdin.
$ git cat-file --batch-check --batch-all-objects
55af8e5b36d666efb8281535bd98fe0f84275347 blob 9
a618ce33da8d21bca841f18e6432fcabf15d4477 tree 40
adab0d71247d7effb8ac272d671664267571fff6 tree 108
b08af892f082f4d3556ef3c969c8f6c43767b9a3 tree 108
b89acddf72fcdf6fa6bf3afdf3cab4ac04217d56 tag 140
b8f20f00cdbb36e72639d48f7681200817ccd6fe commit 220
c4343d3e6f0967c5dbcbb9a6ce3eb7649907e38f commit 171
e32092a83f837140c08e85a60ef16a6b2a208986 blob 9
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 blob 0
$ git cat-file -t 55af8e5b36d666efb8281535bd98fe0f84275347
blob
$ git cat-file -p 55af8e5b36d666efb8281535bd98fe0f84275347
version 2
$ git cat-file -t a618ce33da8d21bca841f18e6432fcabf15d4477
tree
$ git cat-file -p a618ce33da8d21bca841f18e6432fcabf15d4477
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 database.yml
$ git cat-file -t adab0d71247d7effb8ac272d671664267571fff6
tree
$ git cat-file -p adab0d71247d7effb8ac272d671664267571fff6
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 README.md
040000 tree a618ce33da8d21bca841f18e6432fcabf15d4477 config
100644 blob e32092a83f837140c08e85a60ef16a6b2a208986 index.html
$ git cat-file -t b08af892f082f4d3556ef3c969c8f6c43767b9a3
tree
$ git cat-file -p b08af892f082f4d3556ef3c969c8f6c43767b9a3
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 README.md
040000 tree a618ce33da8d21bca841f18e6432fcabf15d4477 config
100644 blob 55af8e5b36d666efb8281535bd98fe0f84275347 index.html
$ git cat-file -t b89acddf72fcdf6fa6bf3afdf3cab4ac04217d56
tag
$ git cat-file -p b89acddf72fcdf6fa6bf3afdf3cab4ac04217d56
object b8f20f00cdbb36e72639d48f7681200817ccd6fe
type commit
tag v1.2
tagger leitiannet <347341200@qq.com> 1717248600 +0800
tag version 1.2
$ git cat-file -t b8f20f00cdbb36e72639d48f7681200817ccd6fe
commit
$ git cat-file -p b8f20f00cdbb36e72639d48f7681200817ccd6fe
tree b08af892f082f4d3556ef3c969c8f6c43767b9a3
parent c4343d3e6f0967c5dbcbb9a6ce3eb7649907e38f
author leitiannet <347341200@qq.com> 1717248600 +0800
committer leitiannet <347341200@qq.com> 1717248600 +0800
second commit
$ git cat-file -t c4343d3e6f0967c5dbcbb9a6ce3eb7649907e38f
commit
$ git cat-file -p c4343d3e6f0967c5dbcbb9a6ce3eb7649907e38f
tree adab0d71247d7effb8ac272d671664267571fff6
author leitiannet <347341200@qq.com> 1717248600 +0800
committer leitiannet <347341200@qq.com> 1717248600 +0800
first commit
$ git cat-file -t e32092a83f837140c08e85a60ef16a6b2a208986
blob
$ git cat-file -p e32092a83f837140c08e85a60ef16a6b2a208986
version 1
$ git cat-file -t e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
blob
$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
$ git-draw --print-only --sha1-length 10 --hide-legend --hide-reflogs --hide-index
digraph structs {
node [shape=record,fontsize=11];
rankdir="TB";
_55af8e5b36 [fillcolor="white", style="filled,rounded", label="{{obj:blob|55af8e5b36}|version\ 2\l}"] _a618ce33da [fillcolor="lightyellow", style="filled,rounded", label="{{obj:tree|a618ce33da}|100644\ blob\ e69de29bb2\ database\.yml\l}"] _a618ce33da -> _e69de29bb2
_adab0d7124 [fillcolor="lightyellow", style="filled,rounded", label="{{obj:tree|adab0d7124}|100644\ blob\ e69de29bb2\ README\.md\l040000\ tree\ a618ce33da\ config\l100644\ blob\ e32092a83f\ index\.html\l}"] _adab0d7124 -> _e69de29bb2
_adab0d7124 -> _a618ce33da
_adab0d7124 -> _e32092a83f
_b08af892f0 [fillcolor="lightyellow", style="filled,rounded", label="{{obj:tree|b08af892f0}|100644\ blob\ e69de29bb2\ README\.md\l040000\ tree\ a618ce33da\ config\l100644\ blob\ 55af8e5b36\ index\.html\l}"] _b08af892f0 -> _e69de29bb2
_b08af892f0 -> _a618ce33da
_b08af892f0 -> _55af8e5b36
_b89acddf72 [fillcolor="white", style="filled,rounded", label="{{obj:tag|b89acddf72}|object\ b8f20f00cd\ltype\ commit\ltag\ v1\.2\ltagger\ leitiannet\ \<347341200\@qq\.com\>\ 1717248600\ \+0800\l\ltag\ version\ 1\.2\l}"] _b89acddf72 -> _b8f20f00cd
_b8f20f00cd [fillcolor="palegreen1", style="filled,rounded", label="{{obj:commit|b8f20f00cd}|tree\ b08af892f0\lparent\ c4343d3e6f\lauthor\ leitiannet\ \<347341200\@qq\.com\>\ 1717248600\ \+0800\lcommitter\ leitiannet\ \<347341200\@qq\.com\>\ 1717248600\ \+0800\l\lsecond\ commit\l}"] _b8f20f00cd -> _b08af892f0
_b8f20f00cd -> _c4343d3e6f
_c4343d3e6f [fillcolor="palegreen1", style="filled,rounded", label="{{obj:commit|c4343d3e6f}|tree\ adab0d7124\lauthor\ leitiannet\ \<347341200\@qq\.com\>\ 1717248600\ \+0800\lcommitter\ leitiannet\ \<347341200\@qq\.com\>\ 1717248600\ \+0800\l\lfirst\ commit\l}"] _c4343d3e6f -> _adab0d7124
_e32092a83f [fillcolor="white", style="filled,rounded", label="{{obj:blob|e32092a83f}|version\ 1\l}"] _e69de29bb2 [fillcolor="white", style="filled,rounded", label="{{obj:blob|e69de29bb2}|}"] _refs___heads___master [style=filled, fillcolor=gray, label="{{ref:local branch|refs\/heads\/master}|b8f20f00cd\l}"]
_refs___heads___master -> _b8f20f00cd
_refs___tags___v1___2 [style=filled, fillcolor=lightyellow, label="{{ref:tag|refs\/tags\/v1\.2}|b89acddf72\l}"]
_refs___tags___v1___2 -> _b89acddf72
_HEAD [style=filled, fillcolor=gray30, fontcolor=white, label="{{ref|HEAD}|refs\/heads\/master\l}"]
_HEAD -> _refs___heads___master
}
各个对象之间的关系
命令汇总
- git add:将当前工作区的文件快照保存下来,创建Blob对象。
- git commit:保存暂存区的目录层次关系和提交者信息,创建Tree对象和Commit对象。
- git tag -m:保存附注标签信息,创建Tag对象。
3,底层命令
Git中命令分为高层封装命令(porcelain,瓷器)和底层核心命令(plumbing,管道)。多数底层命令并不面向最终用户,它们更适合作为新工具的组件和自定义脚本的组成部分。通过底层命令可以窥探Git内部的工作机制,了解Git是如何完成工作的。
代码演示
下面通过底层命令来实现Git提交历史的创建(参考《Pro Git》)。
# 初始化仓库
$ git init git-plumbing
$ cd git-plumbing/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
# 数据对象1:将数据内容写入对象数据库,并返回唯一键
# -w Actually write the object into the object database.
# --stdin Read the object from standard input instead of from a file.
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
# 数据对象2:将文件内容(版本1)写入对象数据库,并返回唯一键
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
# 数据对象3:将文件内容(版本2)写入对象数据库,并返回唯一键---同一个文件,但由于文件内容不同,所以产生两个blob object(文件的两个不同版本)
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
# 可以删除test.txt的本地副本,然后从对象数据库中取回它的第一个版本
$ rm test.txt
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
# 树对象1:将数据对象(test.txt第一个版本)添加到暂存区
# --add If a specified file isn’t in the index already then it’s added. Default behaviour is to ignore new files.
# --cacheinfo <mode> <object> <path> Directly insert the specified info into the index.
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
# 根据当前暂存区状态创建树对象
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
# 树对象2:将数据对象(test.txt第二个版本)和新建文件添加到暂存区
$ git update-index --add --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ echo 'new file' > new.txt; git update-index --add new.txt
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
# 树对象3:将第一个树对象加入第二个树对象,使其成为新的树对象的一个子目录
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
# 为了保证与《Pro Git》中示例一致,需要单独设置用户信息和环境变量让SHA1哈希值为固定值。
$ git config --local user.name "Scott Chacon" # 设置用户名
$ git config --local user.email "schacon@gmail.com" # 设置邮箱地址
# 提交对象1
$ export GIT_AUTHOR_DATE="1243040974 -0700"
$ export GIT_COMMITTER_DATE="1243040974 -0700"
$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
# 提交对象2
$ export GIT_AUTHOR_DATE="1243041269 -0700"
$ export GIT_COMMITTER_DATE="1243041269 -0700"
$ echo 'second commit' | git commit-tree 0155eb4229851634a0f03eb265b69f5a2d56f341 -p fdf4fc3344e67ab068f836878b6c4951e3b15f3d
cac0cab538b970a37ea1e769cbbde608743bc96d
# 提交对象3
$ export GIT_AUTHOR_DATE="1243041324 -0700"
$ export GIT_COMMITTER_DATE="1243041324 -0700"
$ echo 'third commit' | git commit-tree 3c4e9cd789d88d8d89c1073707c3585e41b0e614 -p cac0cab538b970a37ea1e769cbbde608743bc96d
1a410efbd13591db07496601ebc7a059dd55cfe9
# 通过git log可以查看提交历史
$ git log --stat 1a410efbd13591db07496601ebc7a059dd55cfe9
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
third commit
bak/test.txt | 1 +
1 file changed, 1 insertion(+)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:09:34 2009 -0700
first commit
test.txt | 1 +
1 file changed, 1 insertion(+)
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d
$ git cat-file --batch-check --batch-all-objects
0155eb4229851634a0f03eb265b69f5a2d56f341 tree 71
1a410efbd13591db07496601ebc7a059dd55cfe9 commit 225
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob 10
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree 101
83baae61804e65cc73a7201a7252750c76066a30 blob 10
cac0cab538b970a37ea1e769cbbde608743bc96d commit 226
d670460b4b4aece5915caf5c68d12f560a9fe3e4 blob 13
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree 36
fa49b077972391ad58037050f2a75f74e3671e92 blob 9
fdf4fc3344e67ab068f836878b6c4951e3b15f3d commit 177
$ git cat-file -t 0155eb4229851634a0f03eb265b69f5a2d56f341
tree
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git cat-file -t 1a410efbd13591db07496601ebc7a059dd55cfe9
commit
$ git cat-file -p 1a410efbd13591db07496601ebc7a059dd55cfe9
tree 3c4e9cd789d88d8d89c1073707c3585e41b0e614
parent cac0cab538b970a37ea1e769cbbde608743bc96d
author Scott Chacon <schacon@gmail.com> 1243041324 -0700
committer Scott Chacon <schacon@gmail.com> 1243041324 -0700
third commit
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
version 2
$ git cat-file -t 3c4e9cd789d88d8d89c1073707c3585e41b0e614
tree
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git cat-file -t 83baae61804e65cc73a7201a7252750c76066a30
blob
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
version 1
$ git cat-file -t cac0cab538b970a37ea1e769cbbde608743bc96d
commit
$ git cat-file -p cac0cab538b970a37ea1e769cbbde608743bc96d
tree 0155eb4229851634a0f03eb265b69f5a2d56f341
parent fdf4fc3344e67ab068f836878b6c4951e3b15f3d
author Scott Chacon <schacon@gmail.com> 1243041269 -0700
committer Scott Chacon <schacon@gmail.com> 1243041269 -0700
second commit
$ git cat-file -t d670460b4b4aece5915caf5c68d12f560a9fe3e4
blob
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git cat-file -t fa49b077972391ad58037050f2a75f74e3671e92
blob
$ git cat-file -p fa49b077972391ad58037050f2a75f74e3671e92
new file
$ git cat-file -t fdf4fc3344e67ab068f836878b6c4951e3b15f3d
commit
$ git cat-file -p fdf4fc3344e67ab068f836878b6c4951e3b15f3d
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
first commit
$ git-draw --print-only --sha1-length 10 --hide-legend --hide-reflogs --hide-index
digraph structs {
node [shape=record,fontsize=11];
rankdir="TB";
_0155eb4229 [fillcolor="lightyellow", style="filled,rounded", label="{{obj:tree|0155eb4229}|100644\ blob\ fa49b07797\ new\.txt\l100644\ blob\ 1f7a7a472a\ test\.txt\l}"] _0155eb4229 -> _fa49b07797
_0155eb4229 -> _1f7a7a472a
_1a410efbd1 [fillcolor="palegreen1", style="filled,rounded", label="{{obj:commit|1a410efbd1}|tree\ 3c4e9cd789\lparent\ cac0cab538\lauthor\ Scott\ Chacon\ \<schacon\@gmail\.com\>\ 1243041324\ \-0700\lcommitter\ Scott\ Chacon\ \<schacon\@gmail\.com\>\ 1243041324\ \-0700\l\lthird\ commit\l}"] _1a410efbd1 -> _3c4e9cd789
_1a410efbd1 -> _cac0cab538
_1f7a7a472a [fillcolor="white", style="filled,rounded", label="{{obj:blob|1f7a7a472a}|version\ 2\l}"] _3c4e9cd789 [fillcolor="lightyellow", style="filled,rounded", label="{{obj:tree|3c4e9cd789}|040000\ tree\ d8329fc1cc\ bak\l100644\ blob\ fa49b07797\ new\.txt\l100644\ blob\ 1f7a7a472a\ test\.txt\l}"] _3c4e9cd789 -> _d8329fc1cc
_3c4e9cd789 -> _fa49b07797
_3c4e9cd789 -> _1f7a7a472a
_83baae6180 [fillcolor="white", style="filled,rounded", label="{{obj:blob|83baae6180}|version\ 1\l}"] _cac0cab538 [fillcolor="palegreen1", style="filled,rounded", label="{{obj:commit|cac0cab538}|tree\ 0155eb4229\lparent\ fdf4fc3344\lauthor\ Scott\ Chacon\ \<schacon\@gmail\.com\>\ 1243041269\ \-0700\lcommitter\ Scott\ Chacon\ \<schacon\@gmail\.com\>\ 1243041269\ \-0700\l\lsecond\ commit\l}"] _cac0cab538 -> _0155eb4229
_cac0cab538 -> _fdf4fc3344
_d670460b4b [fillcolor="white", style="filled,rounded", label="{{obj:blob|d670460b4b}|test\ content\l}"] _d8329fc1cc [fillcolor="lightyellow", style="filled,rounded", label="{{obj:tree|d8329fc1cc}|100644\ blob\ 83baae6180\ test\.txt\l}"] _d8329fc1cc -> _83baae6180
_fa49b07797 [fillcolor="white", style="filled,rounded", label="{{obj:blob|fa49b07797}|new\ file\l}"] _fdf4fc3344 [fillcolor="palegreen1", style="filled,rounded", label="{{obj:commit|fdf4fc3344}|tree\ d8329fc1cc\lauthor\ Scott\ Chacon\ \<schacon\@gmail\.com\>\ 1243040974\ \-0700\lcommitter\ Scott\ Chacon\ \<schacon\@gmail\.com\>\ 1243040974\ \-0700\l\lfirst\ commit\l}"] _fdf4fc3344 -> _d8329fc1cc
_d670460b4b [style=dotted]
_1a410efbd1 [style=dotted]
_HEAD [style=filled, fillcolor=red, label="{{ref|HEAD}|refs\/heads\/master (referee does not exist)\l}"]
}
各个对象之间的关系
命令汇总
- git hash-object:计算对象ID,根据文件内容创建Blob对象(可选)。
- git cat-file:读取并格式化输出对象。
- git count-objects:计算对象的数量和磁盘消耗。
- git update-index:将工作区中的文件内容添加到暂存区。
- git write-tree:根据暂存区的文件结构创建Tree对象。
- git read-tree:将Tree对象读取到暂存区。
- git commit-tree:根据输入信息创建Commit对象。
- git ls-tree:读取并格式化输出Tree对象。
- git mktag:创建附注型Tag对象。