30.1 考点:仓库

Git 仓库是一个存储文件和目录元数据的磁盘数据结构,位于项目目录的 .git/ 文件夹中。每次提交数据时,这些数据都会存储在这里,而 .git/ 文件夹则包含所有提交记录。其基本结构如下:

.git/
objects/
refs/

30.2 对象

Git 本质上是一个键值存储系统。当你向 Git 添加数据时,它会构建一个对象,并使用对象内容的 SHA-1 哈希值作为键。因此,Git 中的任何内容都可以通过其哈希值查找:

git cat-file -p 4bb6f98

Git 中有四种类型的对象:

  • blob:表示文件内容。
  • tree:表示目录结构。
  • commit:表示提交记录。
  • tag:表示标签。

30.3 HEAD 引用

HEAD 是一个特殊的引用,始终指向当前对象。你可以通过查看 .git/HEAD 文件来查看它当前指向的位置:

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

通常,HEAD 指向另一个引用,但也可以直接指向一个对象:

$ cat .git/HEAD
4bb6f98a223abc9345a0cef9200562333

这就是所谓的“悬挂 HEAD”——因为 HEAD 不再指向任何引用,而是直接指向一个对象。

30.4 引用

引用本质上是一个指针,它指向一个对象。例如:

"mainline" --> 4bb6f98...

它们以纯文本文件的形式存储在 .git/refs/heads/ 中:

$ cat .git/refs/heads/mainline
4bb6f98a223abc9345a0cef9200562333

这些通常被称为分支。然而,在 Git 中,实际上并不存在“分支”这一概念,只有引用。虽然你可以通过直接跳转到不同对象的哈希值来浏览 Git,但这会非常不方便。引用为你提供了一个方便的名称来引用对象,而不是通过哈希值。

30.5 提交对象

提交对象可能是 Git 用户最熟悉的对象类型,因为它通常是通过 git commit 命令创建的。然而,提交对象并不直接包含任何更改的文件或数据,而是主要包含元数据和指向其他包含实际提交内容的对象的指针。 提交对象包含以下内容:

  • 一个树对象的哈希值
  • 一个父提交对象的哈希值
  • 作者和提交者的姓名和电子邮件
  • 提交信息 你可以通过以下方式查看任何提交的内容:
$ git cat-file commit 5bac93
tree 04d1daef...
parent b7850ef5...
author Geddy Lee <glee@rush.com>
committer Neil Peart <npeart@rush.com>
First commit!

重要说明:树对象存储了项目中的每个文件,并且存储的是整个文件,而不是差异。这意味着每个提交都包含项目的一个快照。

父提交:父提交行包含另一个提交对象的哈希值,可以看作是一个“父指针”,指向“前一个提交”。这隐式地形成了一个称为提交图的提交图,具体来说,这是一个有向无环图(DAG)。

30.6 树对象

树对象基本上表示传统文件系统中的一个文件夹:文件或文件夹的嵌套容器。 树对象包含:

  • 0个或多个 blob 对象
  • 0个或多个树对象 就像你可以使用 ls 或 dir 列出文件夹的内容一样,你也可以列出树对象的内容:
$ git cat-file -p 07b1a631
100644 blob b91bba1b .gitignore
100644 blob cc0956f1 Makefile
040000 tree 92e1ca7e src
...

你可以通过首先找到提交中的树的哈希值,然后查看该树来查找提交中的文件:

$ git cat-file commit 4bb6f93a
tree 07b1a631
parent ...
author ...
committer ...
$ git cat-file -p 07b1a631
100644 blob b91bba1b .gitignore
100644 blob cc0956f1 Makefile
040000 tree 92e1ca7e src
...

30.7 blob 对象

Blob 对象包含任意二进制文件内容。通常,它会是原始文本,如源代码或博客文章。但也可以是 PNG 文件的字节或其他任何内容。 如果你有 blob 的哈希值,你可以查看其内容:

$ git cat-file -p d429810
package com.example.project
class Foo {
...
}
...

例如,你可以像上面那样浏览树对象,然后查看其中的一个 blob:

$ git cat-file -p 07b1a631
100644 blob b91bba1b .gitignore
100644 blob cc0956f1 Makefile
040000 tree 92e1ca7e src
100644 blob cae391ff Readme.txt
$ git cat-file -p cae391ff
Welcome to my project! This is the readme file
...

30.8 创建新提交

git commit 命令执行以下操作:

  1. 创建表示项目目录的 blob 和树对象,并存储在 .git/objects 中。
  2. 创建一个新的提交对象,包含你的作者信息、提交信息以及步骤 1 中的根树,也存储在 .git/objects 中。
  3. 更新 .git/HEAD 中的 HEAD 引用来指向新创建的提交的哈希值。 这将导致 Git 中新增一个项目快照,并与之前的提交状态连接起来。

30.9 移动 HEAD

当你运行 git checkout 命令指向一个提交(通过哈希值或引用指定)时,你告诉 Git 使你的工作目录看起来像快照被创建时的样子。

  1. 更新工作目录中的文件以匹配提交内的树。
  2. 更新 HEAD 以指向指定的哈希值或引用。

30.10 移动引用

运行 git reset --hard 会将引用移动到指定的哈希值或引用。 将 MyBranch 移动到 b8dc53:

$ git checkout MyBranch # 将 HEAD 移动到 MyBranch
$ git reset --hard b8dc53 # 使 MyBranch 指向 b8dc53

30.11 创建新引用

运行 git checkout -b <refname> 会创建一个新的引用,指向当前提交。

$ cat .git/HEAD
1f324a
$ git checkout -b TestBranch
$ cat .git/refs/heads/TestBranch
1f324a