通过 .git 目录理解git原理 (一)译

.git 目录有哪些东西

当你使用 git init 命令创建一个 Git 仓库,Git 将创建一个 .git 的文件夹。这个文件夹包含 Git 工作的所需要用到的所有信息。 要清楚,如果你想在你的项目中删除 Git,但需要保留项目文件,你只需要删除 .git 文件夹即可。

如下你在第一次提交前 .git 文件夹里的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|____.git
| |____config
| |____objects
| | |____pack
| | |____info
| |____HEAD
| |____info
| | |____exclude
| |____description
| |____hooks
| | |____commit-msg.sample
| | |____pre-rebase.sample
| | |____pre-commit.sample
| | |____applypatch-msg.sample
| | |____fsmonitor-watchman.sample
| | |____pre-receive.sample
| | |____prepare-commit-msg.sample
| | |____post-update.sample
| | |____pre-applypatch.sample
| | |____pre-push.sample
| | |____update.sample
| |____refs
| | |____heads
| | |____tags

  • HEAD
    下文将详细介绍

  • config
    这个文件包含你的 Git 仓库的配置信息,这里记录了远程的 url、邮箱、用户名等信息,每次你使用 git config --local -l 命令,将展示此文件的配置信息。

  • description
    用于 gitweb (比方说 github) 来展示 Git 仓库的描述信息。

  • hooks
    这是一个非常有趣的特性。他们是 Git 自带的一组脚本。 Git 会在每个有意义的阶段自动运行这些脚本。这些脚本称作为 hooks。他们在执行 commitrebasepull … 之前或之后运行这些脚本,脚本的名称表示了何时将运行他们。举个例子,pre-push 脚本将在执行 git push 命令之前执行,目的是为了做提交到远程前的一些检查,保持远程仓库和本地仓库的一致性。

  • info/exclude
    通常我们是将不希望 Git 管理的文件放在 .gitignore 配置文件中。exclude 文件和.gitignore 文件作用相同,只是不会被共享。例如,你不想让 Git 管理跟踪于自定义的 IDE 产生的相关的文件,又不希望放在 .gitignore 中被提交上去,就可以用这种方式,虽然这种方式实在没有必要。

提交的内容是什么

每次你创建一个文件,并且跟踪他,Git 将它压缩,并用自己的数据结构保存。被压缩的对象有一个唯一的名字和哈希,存储在 objects 文件夹下。
在浏览 objects 文件夹前,我们必须明白是么是一次 commitcommit 是一种你工作目录的快照(snapshot),但这还不止于此。
实际上,当您提交 Git 时,只有两件事可以创建工作目录的快照:

  1. 如果文件没有改变,Git 仅仅将被压缩文件的名称(哈希)记录到快照中。
  2. 如果文件发生改变,Git 将压缩他,并且将压缩文件存储到 objects 文件夹中。最后,它将压缩文件的名称(哈希)添加到快照中。
    这是简化的介绍,整个过程会有点复杂,将在以后的文章中介绍。
    一旦创建了快照,快照也将被压缩并以哈希命名,所有这些压缩对象都存放在 objects 文件夹中。
1
2
3
4
5
6
7
8
|____93
| |____81648a3b1fe0eb310bdd9c87f001f83e132375 // 提交信息
|____86
| |____550c31847e518e1927f95991c949fc14efc711 // 工作目录快照
|____e6
| |____9de29bb2d1d6434b8b29ae775ad8c2e48c5391 // 文件 hash
|____pack // let's ignore that
|____info // let's ignore that too

这是我创建了一个空文件 file_1.txt 并且提交了它之后 objects 文件夹的内容。如果你的文件哈希值是 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391,Git 将存储这个文件到 e6 子文件夹中,并将文件名命名为 9de29bb2d1d6434b8b29ae775ad8c2e48c5391, 这种方法会使 objects 文件夹数量最多为 255 个(00 ~ FF)
这里我们看到 3 个哈希值,一个是我们创建的 file_1.txt 文件,一个是我们 commit 时创建的工作目录快照,那另一个是什么呢? 因为提交本身就是一个对象,所以它也被压缩并存储在 objects 文件夹中。
我们需要记住一次 commit 会创建4个信息:

  1. 各个被 git add,并发生修改、增加、删除文件本身的哈希文件
  2. 工作目录快照哈希文件(修改、增加、删除的文件列表)
  3. 提交者的信息哈希文件,包含作者、commit message
  4. 上一次提交的hash

我们解压缩一下提交的哈希文件

1
2
3
4
5
6
➜ git cat-file -p 9381648a3b1fe0eb310bdd9c87f001f83e132375
tree 86550c31847e518e1927f95991c949fc14efc711
author liuhu <mail.liuhu@gmail.com> 1583582827 +0800
committer liuhu <mail.liuhu@gmail.com> 1583582827 +0800

commit a file

我们看到工作目录快照的哈希信息、作者、和 commit message。
这里有两个信息非常重要:

  1. tree 86550c31847e518e1927f95991c949fc14efc711 工作目录快照信息
  2. 由于是第一次提交,没有上一次提交的哈希

如果你再修改一下这个文件,就可以看到 parent 上一次提交的哈希信息

1
2
3
4
5
6
7
➜ git cat-file -p dbe709db4cda426395cf23fe3063f18128f339e7
tree e2415f5143e84735cbd3d6b8a65c76d2e74c5b61
parent 5d23d6c0468fc867de009ad4eb33ec098c0f5caf
author liuhu <mail.liuhu@gmail.com> 1583585937 +0800
committer liuhu <mail.liuhu@gmail.com> 1583585937 +0800

modify file

工作目录快照信息:

1
2
➜ git cat-file -p 86550c31847e518e1927f95991c949fc14efc711
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file_1.txt

在这里,我们找到了提交文件本身的哈希 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

branch, tags, HEAD 都是指针

我们现在了解了 Git 中的所有内容都是使用正确的哈希值进行访问的。
现在让我们看一下 HEAD 中有什么内容:

1
2
➜ cat HEAD
ref: refs/heads/master

HEAD 表示的是当前分支的指针信息,我们在看看 refs/heads/master 是什么内容:

1
2
➜ cat refs/heads/master
9381648a3b1fe0eb310bdd9c87f001f83e132375

似曾相识的信息,他就是我们第一次提交的哈希信息。这说明分支、标签都是执行提交的指针。这意味着你可以删除所有分支、标签,但他们的提交都保留着,这只会给我们的访问带来一定的困难。如果你想了解更多,你可以阅读 gitbook

原文链接

Git series 1/3: Understanding git for real by exploring the .git directory

坚持原创技术分享,您的支持将鼓励我继续创作!