git:文件存储方式

引言

我们知道 git 跟踪文件会经历三个阶段:工作区,暂存区和本地仓库(参考git:理解工作区,暂存区和本地仓库),在这些阶段文件如何被储存?理解 git 文件的存储方式能帮助我们掌握 git 的工作原理。


git 对象

在上述三个阶段,文件会以对象(object)的形式存储在 .git/objects 目录下,对象主要有三类:commit,tree 和 blob。假设初始目录如下:

├── .git
├── file
│   └── c.txt
├── a.txt
└── b.txt

执行 git add . 将工作区所有文件存放到暂存区,查看目录 .git/objects,看到该目录下增加了3个子目录 2e,34,63,每个子目录下有一个由字母数字命名的文件。git 根据文件内容生成 SHA-1 哈希值作为文件的校验和,创建以该校验和前2个字符为名称的子目录,并以剩下的 38 个字符为文件命名。

.git/objects
├── 2e
│   └── 65efe2a145dda7ee51d1741299f848e5bf752e
├── 34
│   └── 10062ba67c5ed59b854387a8bc0ec012479368
├── 63
│   └── d8dbd40c23542e740659a7168a0ce3138ea748
├── info
└── pack

可以使用指令 git cat-file -t %校验和前六位 查看文件类型,git cat-file -p %校验和前六位 查看文件内容。

$ git cat-file -t 2e65ef
blob
$ git cat-file -t 341006
blob
$ git cat-file -t 63d8db
blob
$ git cat-file -p 2e65ef
a
$ git cat-file -p 341006
c
$ git cat-file -p 63d8db
b

因此,当保存工作区的文件到暂存区时,git 会复制每个文件并压缩为 blob 对象,保存在 .git/objects 下。


执行 git commit 将暂存区文件上传到本地仓库,.git/objects 目录下又增加了三个子目录 0c,b1,65,分别查看其对象和内容:

# 0c2dd9 
commit
tree b15ad62733b40ee7f19be69f850fe5b575210edd
author XuanyuXiang  1665807213 +0800
committer XuanyuXiang  1665807213 +0800
first commit

# b15ad6 
tree
100644 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e    a.txt
100644 blob 63d8dbd40c23542e740659a7168a0ce3138ea748    b.txt
040000 tree 653c8359fc980eb3a393a41a1f1cbe4e8ce458f8    file

# 653c83
tree
100644 blob 3410062ba67c5ed59b854387a8bc0ec012479368    c.txt

此时生成了一个 commit 对象,两个 tree 对象。commit 存放 b15ad6 的地址和一些用户信息;b15ad6 是一个 tree,存放其下的 blob 地址和 653c83 的地址;653c83 是一个 tree,存放其下的 blob 地址。分析得出,git commit 生成 commit 对象和 tree 对象,commit 指向整个项目目录,tree 用于构建整个项目的目录结构,同时暂存区的 blob 对象也会移入本地仓库,暂存区清空。其关系如下:

0c2dd9: commit
└── b15ad6: tree
    ├── 653c83: tree
    │   └── 341006: blob
    ├── 2e65ef: blob
    └── 63d8db: blob

现在我们修改 a.txt 的内容并提交一个新的 commit:

echo "helloGit" > a.txt
git add .
git commit -m "modify a.txt"

.git/objects 目录下又增加了3个子目录,7c,46,74,分别查看其类型和内容:

# 466622
commit
tree 7c0ef1dab56cd8d2ac5c3cda20ddadb4765e8311
parent 0c2dd90ef3827e608b3ea9fba424d1ff68353216
author XuanyuXiang  1665809170 +0800
committer XuanyuXiang  1665809170 +0800
new branch modified a.txt

# 7c0ef1
tree
100644 blob 7423c81faa8c815b6e8a1742fcc321d457cfaab0    a.txt
100644 blob 63d8dbd40c23542e740659a7168a0ce3138ea748    b.txt
040000 tree 653c8359fc980eb3a393a41a1f1cbe4e8ce458f8    file

# 7423c8
blob
helloGit

此时的 commit 多了一个 parent 对象,它指向上一个 commit;请注意 7423c8 虽然对应 a.txt,但由于修改了内容,7423c8 是一个全新的 blob 对象(此时 2e65ef 仍然存在)。本地仓库保存的 commit,tree,blob 是永远不会被删除的。新的指向关系如下:

0c2dd9: commit —————————————————— 466622: commit
└── b15ad6: tree                  └── 7c0ef1: tree
    ├── 653c83: tree                  ├── 653c83: tree
    │   └── 341006: blob              │   └── 341006: blob
    ├── 2e65ef: blob                  ├── 7423c8: blob
    └── 63d8db: blob                  └── 63d8db: blob

git 指针

HEAD 可以理解为指向当前分支的指针,查看 .git/HEAD 内容,确实指向当前的 master 分支:

$ cat .git/HEAD
ref: refs/heads/master

分支可以理解为指向当前 commit 的指针,查看 .git/refs/heads/master,它指向 466622,这就是上面的 commit 对象。分支指针通过指向不同 commit 实现版本的切换,HEAD 通过指向不同分支实现分支切换。

参考

Git内部存储原理

你可能感兴趣的:(工具,git,github)