1

上一篇文章理解Git的存储结构设计(一)

接下来将逐步分析文件从工作区被添加到暂存区、最后被提交到历史版本库这一过程中git是如何存储文件的。

Git的对象存储

1. Git保存文件的方式

objects目录中存放了Git仓库所有的版本文件对象。上一篇文章中,我们提交了2个文件README.mdsrc/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 <efreykong@outlook.com>
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 <efreykong@outlook.com> 1567323356 +0800
committer Efrey Kong <efreykong@outlook.com> 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对象库的存储结构:
图片描述


efrey
13 声望2 粉丝