理解Git的存储结构设计(二)
上一篇文章理解Git的存储结构设计(一)
接下来将逐步分析文件从工作区被添加到暂存区、最后被提交到历史版本库这一过程中git是如何存储文件的。
Git的对象存储
1. Git保存文件的方式
objects目录中存放了Git仓库所有的版本文件对象。上一篇文章中,我们提交了2个文件README.md和src/index.php文件到Git版本库。提交后却发现objects目录下多了5个文件。这是为什么呢?
├── objects │ ├── 23 │ │ └── c6e2c6e5bd959951813965d6cc607dbbd28fee │ ├── 32 │ │ └── 454a9530d7b66fd8b35d2d5b5ea58adbf98e82 │ ├── 46 │ │ └── a2eb9234dc84c7b0c369afe067f6f1c3794500 │ ├── 54 │ │ └── 2123498f1065f5e063576f54acda8d535f5538 │ ├── 9c │ │ └── 2ba432d595da5b3e004fb0a10232be67dc7937
这是Git的存储方式导致的。在Git的存储结构中,有下列4种类型:
- blob:工作区所有的暂存、提交文件都是blob类型,基于二进制格式存储。
- tree:blob的目录结构信息是tree类型。每个tree对象都指向具体的文件blob。
- commit:提交信息是commit类型。信息包含:本次提交的tree、上一次commit id(parent commit id)、作者、提交者、日期等。
- tag:标签类型。
Git把每一种类型都视之为一个单独的存储对象。当提交一个文件时,git会将文件保存为一个blob对象,同时会生成一个tree对象来记录文件的目录结构信息,最后生成一个commit对象来记录整个提交信息。
那么为什么提交2个文件在objects下面会出现5个子目录来分别存放这5个文件呢?Git会为每个对象生成一个40个字符长度的SHA-1哈希值作为对象的唯一标识,前2位字符作为目录名,剩余的38位作为文件名。
2. 暂存文件时Git的存储处理
当我们使用git add
命令将文件提交到暂存区时,Git会先计算文件的SHA-1哈希值,然后将文件按哈希值名称规则保存为相应目录的文件名。
Git中是使用git hash-object
命令来计算SHA-1哈希值的。以我们提交的README.md文件为例:
$ git hash-object README.md 542123498f1065f5e063576f54acda8d535f5538
我们再使用git ls-files
命令可以查看暂存区的文件的哈希值信息:
$ git ls-files -s 100644 542123498f1065f5e063576f54acda8d535f5538 0 README.md 100644 9c2ba432d595da5b3e004fb0a10232be67dc7937 0 src/index.php
从命令的返回结果可以看到,README.md文件在暂存区的哈希值同我们手动计算的哈希值是一致的。在objects文件夹下也能找到和这个哈希值匹配的目录文件objects\54\2123498f1065f5e063576f54acda8d535f5538。
由于Git会以二进制格式blob类型存储文件,我们无法直接查看其内容。但是可以使用git cat-file -p <sha-1>
命令查看:
efrey@EKStudio MINGW64 ~/Desktop/gitdemo (master) $ git cat-file -p 542123498f1065f5e063576f54acda8d535f5538 # This is a demo project.
3. 提交文件时Git的存储处理
当我们使用commit
命令将暂存区文件提交到历史版本库后,Git会位这次提交生成一个唯一标识commit id。我们通过这个commit id入手来看看commit后对象库的存储发生了什么变化。
首先,使用git log
查看提交操作日志:
$ git log commit 32454a9530d7b66fd8b35d2d5b5ea58adbf98e82 (HEAD -> master) Author: Efrey Kong <[email protected]> Date: Sun Sep 1 15:35:56 2019 +0800
获取到本次操作的commit id为32454a9530d7b66fd8b35d2d5b5ea58adbf98e82,按照git对象存储规则,前两位字符32为文件夹名,后38位字符454a9530d7b66fd8b35d2d5b5ea58adbf98e82为文件名。我们刚好在objects文件内可以找到匹配的目录文件。
接下来我们使用git cat-file --p <SHA-1>
命令来查看commit对象中保存了哪些内容(在实际使用中,<SHA-1>参数也可以指定哈希值的前几位字符,一般前7位字符足以唯一标识一个对象):
$ git cat-file -p 32454a9 tree 23c6e2c6e5bd959951813965d6cc607dbbd28fee author Efrey Kong <[email protected]> 1567323356 +0800 committer Efrey Kong <[email protected]> 1567323356 +0800 init
通过命令的返回结果,我们可以看到,commit对象32454a9中保存了一个tree对象的SHA-1哈希值。我们再次来查看tree对象的内容:
$ git cat-file -p 23c6e2c 100644 blob 542123498f1065f5e063576f54acda8d535f5538 README.md 040000 tree 46a2eb9234dc84c7b0c369afe067f6f1c3794500 src
可以看到tree对象23c6e2c中保存了一个blob对象(README.md)和另外一个tree对象的SHA-1哈希值。使用git cat-file -p 5421234
查看blob对象内容可以看到正是README.md文件的内容:
$ git cat-file -p 5421234 # This is a demo project.
最后,我们同样的方式查看tree对象46a2eb9的内容,这个tree对象保存了另外一个blob对象(亦即index.php)的SHA-1哈希值:
$ git cat-file -p 46a2eb9 100644 blob 9c2ba432d595da5b3e004fb0a10232be67dc7937 index.php
我们用一张图来总结以下,这次commit后,git对象库的存储结构: