跳转到内容

Git/内部结构

来自维基教科书,开放世界中的开放书籍
< Git

了解 Git 的内部结构对于理解 Git 的工作原理至关重要。

裸 Git 结构

[编辑 | 编辑源代码]

以下是一个新初始化的 git v1.9.0 仓库。[1]

.
└── .git/
    ├── HEAD
    ├── branches/
    ├── config
    ├── description
    ├── hooks/
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── prepare-commit-msg.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   └── update.sample
    ├── info/
    │   └── exclude
    ├── objects/
    │   ├── info/
    │   └── pack/
    └── refs/
        ├── heads/
        └── tags/

随着仓库活动,可能会出现其他文件和文件夹。

特定文件

[编辑 | 编辑源代码]

COMMIT_EDITMSG

[编辑 | 编辑源代码]

文本编辑器会将提交消息保存到这里。

FETCH_HEAD

[编辑 | 编辑源代码]

这里保存了最后一次 git-fetch(1) 操作的信息,供之后 git-merge(1) 操作使用。

HEAD 指示当前检出的代码。它通常会指向您当前正在工作的分支。

您还可以进入 Git 所谓的“分离 HEAD”状态,在这种状态下,您不在本地分支上。在这种状态下,HEAD 直接指向提交而不是分支。

此 Git 仓库的配置文件。它可以包含有关如何在本地仓库中管理和存储数据的设置、已知的远程仓库、有关本地用户的详细信息以及其他有关 Git 本身的配置数据。

您可以使用文本编辑器编辑此文件,也可以使用 git-config(1) 命令进行管理。

description

[编辑 | 编辑源代码]

由仓库浏览器工具使用 - 包含有关此项目的描述。在非共享仓库中通常不会更改。

这是暂存区。它以紧凑的形式包含已暂存到下次提交的所有文件更改。

info/exclude

[编辑 | 编辑源代码]

这是您自己的个人排除文件,用于您的仓库副本。

info/refs

[编辑 | 编辑源代码]

如果此文件存在,它将包含分支(本地和远程)以及为仓库定义的标签的定义,每行一个定义,除此之外,还可能在以下文件中定义。refs/headsrefs/tags. 此文件似乎用于包含大量分支或标签的大型仓库。

ORIG_HEAD

[编辑 | 编辑源代码]

更改当前分支上的提交历史记录的操作会将 HEAD 的先前值保存到这里,以便从错误中恢复。

包含其他文件的文件夹

[编辑 | 编辑源代码]

似乎从不使用。

包含在 Git 仓库中发生特定事件时运行的脚本。Git 提供了一组初始示例脚本,这些脚本的名称末尾带有.sample(请参阅上面的树列表);如果您删除了.sample后缀,Git 将在适当的时候运行该脚本。

例如,钩子可用于在创建每个提交之前运行测试、过滤上传的内容以及实现其他此类自定义要求。

Reflogs 保存在这里。

这是所有文件、目录列表、提交等存储的地方。

这里既有此目录下编号目录中的未打包对象,也有“包”,这些包包含压缩在包目录中的许多压缩对象。未压缩对象将定期通过自动“git gc”运行收集到包中。

refs/heads

[编辑 | 编辑源代码]

可以包含一个文件,定义每个本地分支的头部提交(但请参见info/refs上面)。

refs/remotes

[编辑 | 编辑源代码]

可以包含一个子目录,用于每个已定义的远程仓库。在每个子目录中,都包含一个文件,定义该远程仓库上每个分支的尖端提交。

refs/tags

[编辑 | 编辑源代码]

可以包含一个文件,定义与每个标签对应的提交(但请参见info/refs上面)。

如果您使用 git-svn(1) 与 Subversion 服务器通信,则此目录将出现。


基本概念

[编辑 | 编辑源代码]

对象类型

[编辑 | 编辑源代码]

Git 仓库由以下对象类型组成

  • 一个 blob 保存单个文件的全部内容。它不保存有关文件名或任何其他元数据的任何信息,只保存内容。
  • 一个 tree 表示目录树的状态。它包含所有组件文件的路径名及其模式,以及保存其内容的 blob 的 ID。请注意,没有单独表示目录,因此 Git 仓库无法记录创建或删除子目录的事实,只能记录其中的文件。
  • 一个 commit 指向一个树,表示该提交之后立即源树的状态。它还记录提交的日期/时间、作者/提交者信息,以及指向该提交的任何父提交的指针,表示源树的立即之前状态。
  • 一个 tag 是一个指向提交的名称。这些很有用,例如,用来标记发行版里程碑。标签可以选择进行数字签名,以保证提交的真实性。
  • 一个 branch 是一个指向提交的名称。分支和标签之间的区别在于,当一个分支是当前签出的分支时,添加一个新的提交将自动更新分支指针以指向新的提交。

Blob、树和提交都有 ID,这些 ID 是根据其内容的 SHA-1 哈希计算得出的。这些 ID 允许不同机器上的不同 Git 进程判断它们是否具有相同副本,而无需传输其全部内容。由于 SHA-1 是一种密码学上强健的哈希算法,因此几乎不可能对任何这些对象的內容进行更改而不会更改其 ID。Git 不会阻止您重写历史记录,但您无法隐藏您已经重写历史记录的事实。

一个提交可能具有 0、1 或多个父提交。通常只有一个没有父提交的提交——一个 root commit——这是仓库中的第一个提交。对某个分支进行一些更改的提交将只有一个父提交,即该分支上的前一个提交。从两个或多个分支合并的提交将有两个或多个父提交。

请注意,一个分支指向一个 单个 提交;提交链隐含在该提交的父提交中,以及它们的父提交,等等。

提交的拓扑结构

[编辑 | 编辑源代码]

Git 中的提交历史记录被组织成一个 有向无环图 (DAG)。为了理解这意味着什么,让我们逐步解释这些术语。

仅仅一个图
  • 在数学术语中,一个 是由连接线 () 连接的点 (节点) 组成的。
有向边
  • 一个 有向 图是指每个边都有一个方向的图,这里用箭头表示。请注意,箭头从子节点指向父节点,而不是反过来;是子节点记录了其父节点是谁,父节点没有记录其子节点是谁,因为子节点集合可以随时更改,但父节点不能更改,否则会导致其 SHA-1 哈希无效。
不允许循环
  • 无环 指的是,如果您从任意一点开始,沿着箭头方向遍历边,无论您在任何分支处做出何种选择,您都永远无法回到起点,任何子节点都永远不能是它自己的(直接或间接)父节点!

在 Git 术语中,每个节点代表一个提交,线和箭头代表父子关系。禁止循环仅仅意味着一个提交不能是它自己(直接或间接)的父节点或子节点!

Reflog 记录了未作为提交历史记录一部分保存的更改——比如变基、快进合并、重置等等。每个分支都有一个 reflog。Reflog 不是仓库的公共部分,它严格地特定于您的本地副本,并且信息只在其中保留有限的时间(默认情况下为 2 周)。它提供了一个安全网,允许您从错误中恢复,比如删除或覆盖您不想删除或覆盖的东西。

可达性和垃圾回收

[编辑 | 编辑源代码]

如果一个提交被分支、标签或 reflog 条目指向,或者是一个可达提交的父提交,则该提交是 可达 的。相应地,如果一个树被可达提交指向,则该树是可达的;如果一个 blob 被可达树指向,则该 blob 是可达的。其他提交/树/blob 对象是 不可达 的,除了占用空间之外,它们实际上没有起到任何作用。

您的仓库随着时间的推移积累不可达对象是完全正常的,这可能是由于中止提交、删除不需要的分支、类似的事情造成的。这样的对象将被 git gc 命令从仓库中删除。这也会在一些其他命令执行时定期自动执行,因此很少需要显式调用 git gc

.git 文件夹之外的 Git 文件

[编辑 | 编辑源代码]

放在目录中,它保证它会被提交,即使是空的。

.gitignore

[编辑 | 编辑源代码]

包含要从版本控制中排除的文件和文件夹。例如

/var/
/vendor/
/.env.*.local

.gitattributes

[编辑 | 编辑源代码]

包含一些属性[1]。例如,要忽略 .gitattributes 和 .gitignore 在 "git archive" 导出中的内容

.gitattributes export-ignore
.gitignore export-ignore
  1. ^ 使用 tree v1.5.1.1 通过 tree -AnaF 生成的。
  1. https://git.js.cn/docs/gitattributes
华夏公益教科书