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/
随着仓库活动,可能会出现其他文件和文件夹。
文本编辑器会将提交消息保存到这里。
这里保存了最后一次 git-fetch(1) 操作的信息,供之后 git-merge(1) 操作使用。
HEAD 指示当前检出的代码。它通常会指向您当前正在工作的分支。
您还可以进入 Git 所谓的“分离 HEAD”状态,在这种状态下,您不在本地分支上。在这种状态下,HEAD 直接指向提交而不是分支。
此 Git 仓库的配置文件。它可以包含有关如何在本地仓库中管理和存储数据的设置、已知的远程仓库、有关本地用户的详细信息以及其他有关 Git 本身的配置数据。
您可以使用文本编辑器编辑此文件,也可以使用 git-config(1) 命令进行管理。
由仓库浏览器工具使用 - 包含有关此项目的描述。在非共享仓库中通常不会更改。
这是暂存区。它以紧凑的形式包含已暂存到下次提交的所有文件更改。
这是您自己的个人排除文件,用于您的仓库副本。
如果此文件存在,它将包含分支(本地和远程)以及为仓库定义的标签的定义,每行一个定义,除此之外,还可能在以下文件中定义。refs/heads和refs/tags. 此文件似乎用于包含大量分支或标签的大型仓库。
更改当前分支上的提交历史记录的操作会将 HEAD 的先前值保存到这里,以便从错误中恢复。
似乎从不使用。
包含在 Git 仓库中发生特定事件时运行的脚本。Git 提供了一组初始示例脚本,这些脚本的名称末尾带有.sample(请参阅上面的树列表);如果您删除了.sample后缀,Git 将在适当的时候运行该脚本。
例如,钩子可用于在创建每个提交之前运行测试、过滤上传的内容以及实现其他此类自定义要求。
Reflogs 保存在这里。
这是所有文件、目录列表、提交等存储的地方。
这里既有此目录下编号目录中的未打包对象,也有“包”,这些包包含压缩在包目录中的许多压缩对象。未压缩对象将定期通过自动“git gc”运行收集到包中。
可以包含一个文件,定义每个本地分支的头部提交(但请参见info/refs上面)。
可以包含一个子目录,用于每个已定义的远程仓库。在每个子目录中,都包含一个文件,定义该远程仓库上每个分支的尖端提交。
可以包含一个文件,定义与每个标签对应的提交(但请参见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
。
放在目录中,它保证它会被提交,即使是空的。
包含要从版本控制中排除的文件和文件夹。例如
/var/ /vendor/ /.env.*.local
包含一些属性[1]。例如,要忽略 .gitattributes 和 .gitignore 在 "git archive" 导出中的内容
.gitattributes export-ignore .gitignore export-ignore
- ^ 使用 tree v1.5.1.1 通过
tree -AnaF
生成的。