.git目录的解释【翻译】

你好!本周我在 Mastodon 上发布了一个关于 .git 目录中内容的漫画,有人要求提供文本版本,所以就在这里。我还添加了一些额外的注释。首先,这是图片。它是对 .git 目录每个部分的约 15 个字的解释。

inside.git

如果您想自己运行所有这些示例,可以“git clone https://github.com/jvns/inside-git”。

以下是目录:

  1. HEAD:.git/head
  2. 分支:.git/refs/heads/main
  3. 提交:.git/objects/10/93da429…
  4. 树:.git/objects/9f/83ee7550…
  5. blobs:.git/objects/5a/475762c…
  6. reflog:.git/logs/refs/heads/main
  7. 远程跟踪分支:.git/refs/remotes/origin/main
  8. 标签:.git/refs/tags/v1.0
  9. 存储:.git/refs/stash
  10. .git/config
  11. 钩子:.git/hooks/pre-commit
  12. 暂存区:.git/index
  13. 这不是详尽无遗的
  14. 这并不是要完全解释 git

前5个部分(HEAD、分支、提交、树、bols)是git的核心。

HEAD: .git/head

HEAD是一个小文件,仅包含当前分支的名称。

示例内容:

1
2
$ cat .git/HEAD
ref: refs/heads/main

“HEAD”也可以是提交ID,称为“分离HEAD状态”。

branch: .git/refs/heads/main

A branch is stored as a tiny file that just contains 1 commit ID. It’s stored in a folder called refs/heads.

分支存储为一个小文件,仅包含1提交ID。它存储在名为“refs/heads”的文件夹中。

示例内容:

1
2
$ cat .git/refs/heads/main
1093da429f08e0e54cdc2b31526159e745d98ce0

commit: .git/objects/10/93da429...

提交是一个小文件,包含其父文件、消息、和作者。

示例内容:

1
2
3
4
5
6
7
$ git cat-file -p 1093da429f08e0e54cdc2b31526159e745d98ce0
tree 9f83ee7550919867e9219a75c23624c92ab5bd83
parent 33a0481b440426f0268c613d036b820bc064cdea
author Julia Evans <julia@example.com> 1706120622 -0500
committer Julia Evans <julia@example.com> 1706120622 -0500

add hello.py

这些文件是压缩的,查看对象的最佳方法是使用 git cat-file -p HASH.

tree: .git/objects/9f/83ee7550...

是包含目录列表的小文件。其中的文件称为 blobs

示例内容:

1
2
3
4
$  git cat-file -p 9f83ee7550919867e9219a75c23624c92ab5bd83
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 .gitignore
100644 blob 665c637a360874ce43bf74018768a96d2d4d219a hello.py
040000 tree 24420a1530b1f4ec20ddb14c76df8c78c48f76a6 lib

这里的权限看起来像unix权限,但实际上它们是超级限制的,只允许644和755。

blobs: .git/objects/5a/475762c...

blobs 是包含实际代码的文件

示例内容:

1
2
$ git cat-file -p 665c637a360874ce43bf74018768a96d2d4d219a	
print("hello world!")

存储一个包含每一个更改的新blob可能会变得很大, 因此git gc 定期 打包它们 以提高 .git/objects/pack中的效率。

reflog: .git/logs/refs/heads/main

reflog存储每个分支、标签和HEAD的历史。 对于(大多数) .git/refs中的每个文件,.git/logs/refs`中都有相应的日志。

main 分支的示例内容:

1
2
3
4
5
6
$ tail -n 1 .git/logs/refs/heads/main
33a0481b440426f0268c613d036b820bc064cdea
1093da429f08e0e54cdc2b31526159e745d98ce0
Julia Evans <julia@example.com>
1706119866 -0500
commit: add hello.py

reflog的每一行都有:

  • 提交之前/之后ID
  • 用户
  • 时间戳
  • 日志消息

通常都是一行,为了可读性,我只是在这里将其包装起来。

remote-tracking branches: .git/refs/remotes/origin/main

远程跟踪分支存储远程分支最近看到的 提交ID

示例内容:

1
2
$ cat .git/refs/remotes/origin/main
fcdeb177797e8ad8ad4c5381b97fc26bc8ddd5a2

当git status显示“您与‘origin/main’保持最新状态”时,它只是在查看这个。它经常过时,您可以使用git fetch origin main更新它。

tags: .git/refs/tags/v1.0

标签是 .git/refs/tags中的一个小文件,包含提交ID。

示例内容:

1
2
$ cat .git/refs/tags/v1.0
1093da429f08e0e54cdc2b31526159e745d98ce0

与分支不同,当您进行新的提交时,它不会更新标签。

the stash: .git/refs/stash

存储是一个名为 .git/refs/stash 的小文件。它包含运行 git stash 时创建的提交的提交 ID。

1
2
cat .git/refs/stash
62caf3d918112d54bcfa24f3c78a94c224283a78

存储是一个堆栈,以前的值存储在 .git/logs/refs/stash 中( stash的 reflog)。

1
2
3
cat .git/logs/refs/stash
62caf3d9 e85c950f Julia Evans <julia@example.com> 1706290652 -0500 WIP on main: 1093da4 add hello.py
00000000 62caf3d9 Julia Evans <julia@example.com> 1706290668 -0500 WIP on main: 1093da4 add hello.py

与分支和标签不同,如果您从存储中“git stash pop”提交,它将从引用日志中删除,因此几乎不可能再次找到它。存储是 git 中唯一一个在添加后很快就会被删除的引用日志。(分支引用日志中的条目也会过期,但通常仅在 90 天后)

关于 ref 的说明:

此时,您可能已经注意到,很多东西(分支、远程跟踪分支、标签和存储)都是 .git/refs 中的提交 ID。它们被称为“引用”或“refs”。每个 ref 都是一个提交 ID,但 git 对不同类型的 ref 的处理方式非常不同,因此,我发现分别考虑它们很有用,即使它们都使用相同的文件格式。例如,git 从存储引用日志中删除内容的方式与分支或标签引用日志不同。

.git/config

.git/config 是存储库的配置文件。您可以在这里配置远程服务器。

示例内容:

1
2
3
4
5
6
[remote "origin"] 
url = git@github.com: jvns/int-exposed
fetch = +refs/heads/*: refs/remotes/origin/*
[branch "main"]
remote = origin
merge refs/heads/main

git 有本地和全局设置,本地设置在这里,全局设置位于 ~/.gitconfig 钩子中

hooks: .git/hooks/pre-commit

钩子是可选脚本,您可以设置运行(例如在提交之前)来执行任何您想要的操作。

示例内容:

1
2
#!/bin/bash 
any-commands-you-want

(这显然不是一个真正的预提交钩子)

the staging area: .git/index

暂存区用于存储您准备提交的文件。这是一个二进制文件,与 git 中的许多本质上是纯文本文件的东西不同。

据我所知,查看索引内容的最佳方式是使用“git ls-files –stage”:

1
2
3
4
$ git ls-files --stage
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 .gitignore
100644 665c637a360874ce43bf74018768a96d2d4d219a 0 hello.py
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 lib/empty.py

这并不详尽

.git 中还有一些其他内容,例如 FETCH_HEADworktreesinfo。我只包括了我认为有助于理解的内容。

这并不是要完全解释 git

我听到的关于 git 最常见的建议之一是“只要了解 .git 目录的结构,你就会明白一切!”。

我比任何人都更喜欢了解事物的内部结构,但“.git 目录的结构”并没有解释很多东西,比如:

  • merge和rebases如何工作以及它们可能出错的原因(例如,rebases可能出错的原因 的列表)
  • 您的同事究竟是如何使用 git 的,以及您应该遵循哪些准则才能成功地与他们合作
  • 如何从其他存储库推送/拉取代码
  • 如何处理合并冲突

但愿这对某些人有用。

其他一些参考资料:

1
2
3
4
5
6
本文为个人知识学习,非原创!非作者!如本博客有侵权行为,请与我联系。
摘录以下文章,内容根据个人需求有所删减,尊重知识产出,尊重作者知识劳动成果。

作者:Julia Evans.
链接:https://jvns.ca/blog/2024/01/26/inside-git/
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。