当前位置:网站首页 > 历史 > 在 Git 仓库中,文件究竟被存储在哪里? | Linux 中国

在 Git 仓库中,文件究竟被存储在哪里? | Linux 中国

在 Git 仓库中,文件究竟被存储在哪里? | Linux 中国导读:我们知道它存储在 .git 目录中,但具体到 .git 中的哪个位置,各个版本的历史文件又被存储在哪里呢?

译者:ChatGPT

大家好!今天我和一个朋友讨论 Git 的工作原理,我们感到奇怪,Git 是如何存储你的文件的?我们知道它存储在.git目录中,但具体到.git中的哪个位置,各个版本的历史文件又被存储在哪里呢?

以这个博客为例,其文件存储在一个 Git 仓库中,其中有一个文件名为content/post/2019-06-28-brag-doc.markdown。这个文件在我的.git文件夹中具体的位置在哪里?过去的文件版本又被存储在哪里?那么,就让我们通过编写一些简短的 Python 代码来探寻答案吧。

Git 把文件存储在 .git/objects 之中

你的仓库中,每一个文件的历史版本都被储存在.git/objects中。比如,对于这个博客,.git/objects包含了 2700 多个文件。

  1. $ find .git/objects/ -type f | wc -l
  2. 2761

注意:.git/objects包含的信息,不仅仅是 “仓库中每一个文件的所有先前版本”,但我们暂不详细讨论这一内容。

这里是一个简短的 Python 程序(find-git-object.py gist.github.com),它可以帮助我们定位在.git/objects中的特定文件的具体位置。

  1. import hashlib
  2. import sys
  3. def object_path(content):
  4. header = f"blob {len(content)}\0"
  5. data = header.encode() + content
  6. sha1 = hashlib.sha1()
  7. sha1.update(data)
  8. digest = sha1.hexdigest()
  9. return f".git/objects/{digest[:2]}/{digest[2:]}"
  10. with open(sys.argv[1], "rb") as f:
  11. print(object_path(f.read()))

此程序的主要操作如下:

◈读取文件内容

◈计算一个头部(blob 16673\0),并将其与文件内容合并

◈计算出文件的 sha1 校验和(此处为e33121a9af82dd99d6d706d037204251d41d54)

◈将这个 sha1 校验和转换为路径(如.git/objects/e3/3121a9af82dd99d6d706d037204251d41d54)

运行的方法如下:

  1. $ python3 find-git-object.py content/post/2019-06-28-brag-doc.markdown
  2. .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54

术语解释:“内容寻址存储”

这种存储策略的术语为“内容寻址存储(content addressed storage)”,它指的是对象在数据库中的文件名与文件内容的哈希值相同。

内容寻址存储的有趣之处就是,假设我有两份或许多份内容完全相同的文件,在 Git 的数据库中,并不会因此占用额外空间。如果内容的哈希值是aabbbbbbbbbbbbbbbbbbbbbbbbb,它们都会被存储在.git/objects/aa/bbbbbbbbbbbbbbbbbbbbb中。

这些对象是如何进行编码的?

如果我尝试在.git/objects目录下查看这个文件,显示的内容似乎有一些奇怪:

  1. $ cat .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54
  2. x^A<8D><9B>}s<E3>Ƒ<C6><EF>o|<8A>^Q<9D><EC>ju<92><E8><DD><9C><9C>*<89>j<FD>^...

这是怎么回事呢?让我们来运行file命令检查一下:

  1. $ file .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54
  2. .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54: zlib compressed data

原来,它是压缩的!我们可以编写一个小巧的 Python 程序——decompress.py,然后用zlib模块去解压这些数据:

  1. import zlib
  2. import sys
  3. with open(sys.argv[1], "rb") as f:
  4. content = f.read()
  5. print(zlib.decompress(content).decode())

让我们来解压一下看看结果:

  1. $ python3 decompress.py .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54
  2. blob 16673---
  3. title: "Get your work recognized: write a brag document"
  4. date: 2019-06-28T18:46:02Z
  5. url: /blog/brag-documents/
  6. categories: []
  7. ---
  8. ... the entire blog post ...

结果显示,这些数据的编码方式非常简单:首先有blob 16673\0标识,其后就是文件的全部内容。

这里并没有差异性数据(diff)

这里有一件我第一次知道时让我感到惊讶的事:这里并没有任何差异性数据!那个文件是该篇博客文章的第 9 个版本,但 Git 在.git/objects目录中存储的版本是完整文件内容,而并非与前一版本的差异。

尽管 Git 实际上有时候会以差异性数据存储文件(例如,当你运行git gc时,为了提升效率,它可能会将多个不同的文件封装成 “打包文件”),但在我个人经验中,我从未需要关注这个细节,所以我们不在此深入讨论。然而,关于这种格式如何工作,Aditya Mukerjee 有篇优秀的文章 《拆解 Git 的打包文件 codewords.recurse.com》。

博客文章的旧版本在哪?

你可能会好奇:如果在我修复了一些错别字之前,这篇博文已经存在了 8 个版本,那它们在.git/objects目录中的位置是哪里?我们如何找到它们呢?

首先,我们来使用git log命令来查找改动过这个文件的每一个提交

  1. $ git log --oneline content/post/2019-06-28-brag-doc.markdown
  2. c6d4db2d
  3. 423cd76a
  4. 7e91d7d0
  5. f105905a
  6. b6d23643
  7. 998a46dd
  8. 67a26b04
  9. d9999f17
  10. 026c0f52
  11. 72442b67

然后,我们选择一个之前的提交,比如026c0f52。提交也被存储在.git/objects中,我们可以尝试在那里找到它。但是失败了!因为ls .git/objects/02/6c*没有显示任何内容!如果有人告诉你,“我们知道有时 Git 会打包对象来节省空间,我们并不需过多关心它”,但现在,我们需要去面对这个问题了。

那就让我们去解决它吧。

让我们开始解包一些对象

现在我们需要从打包文件中解包出一些对象。我在 Stack Overflow 上查找了一下,看起来我们可以这样进行操作:

  1. $ mv .git/objects/pack/pack-adeb3c14576443e593a3161e7e1b202faba73f54.pack .
  2. $ git unpack-objects < pack-adeb3c14576443e593a3161e7e1b202faba73f54.pack

这种直接对库进行手术式的做法让人有些紧张,但如果我误操作了,我还可以从 Github 上重新克隆这个库,所以我并不太担心。

解包所有的对象文件后,我们得到了更多的对象:大约有 20000 个,而不是原来的大约 2700 个。看起来很酷。

  1. find .git/objects/ -type f | wc -l
  2. 20138

我们回头再看看提交

现在我们可以继续看看我们的提交026c0f52。我们之前说过.git/objects中并不都是文件,其中一部分是提交!为了弄清楚我们的旧文章content/post/2019-06-28-brag-doc.markdown是在哪里被保存的,我们需要深入查看这个提交。

首先,我们需要在.git/objects中查看这个提交。

查看提交的第一步:找到提交

经过解包后,我们现在可以在.git/objects/02/6c0f5208c5ea10608afc9252c4a56c1ac1d7e4中找到提交026c0f52,我们可以用下面的方法去查看它:

  1. $ python3 decompress.py .git/objects/02/6c0f5208c5ea10608afc9252c4a56c1ac1d7e4
  2. commit 211tree 01832a9109ab738dac78ee4e95024c74b9b71c27
  3. parent 72442b67590ae1fcbfe05883a351d822454e3826
  4. author Julia Evans <julia@jvns.ca> 1561998673 -0400
  5. committer Julia Evans <julia@jvns.ca> 1561998673 -0400
  6. brag doc

我们也可以用git cat-file -p 026c0f52命令来获取相同的信息,这个命令能起到相同的作用,但是它在格式化数据时做得更好一些。(-p选项意味着它能够以更友好的方式进行格式化)

查看提交的第二步:找到树

这个提交包含一个树。树是什么呢?让我们看一下。树的 ID 是01832a9109ab738dac78ee4e95024c74b9b71c27,我们可以使用先前的decompress.py脚本查看这个 Git 对象,尽管我不得不移除.decode()才能避免脚本崩溃。

  1. $ python3 decompress.py .git/objects/01/832a9109ab738dac78ee4e95024c74b9b71c27

这个输出的格式有些难以阅读。主要的问题在于,该提交的哈希(\xc3\xf7$8\x9b\x8dO\x19/\x18\xb7}|\xc7\xce\x8e…)是原始字节,而没有进行十六进制的编码,因此我们看到\xc3\xf7$8\x9b\x8d而非c3f76024389b8d。我打算切换至git cat-file -p命令,它能以更友好的方式显示数据,我不想自己编写一个解析器。

  1. $ git cat-file -p 01832a9109ab738dac78ee4e95024c74b9b71c27
  2. 100644 blob c3f76024389b8d4f192f18b77d7cc7ce8e3a68ad .gitignore
  3. 100644 blob 7ebaecb311a05e1ca9a43f1eb90f1c6647960bc1 README.md
  4. 100644 blob 0f21dc9bf1a73afc89634bac586271384e24b2c9 Rakefile
  5. 100644 blob 00b9d54abd71119737d33ee5d29d81ebdcea5a37 config.yaml
  6. 040000 tree 61ad34108a327a163cdd66fa1a86342dcef4518e content <-- 这是我们接下来的目标
  7. 040000 tree 6d8543e9eeba67748ded7b5f88b781016200db6f layouts
  8. 100644 blob 22a321a88157293c81e4ddcfef4844c6c698c26f mystery.rb
  9. 040000 tree 8157dc84a37fca4cb13e1257f37a7dd35cfe391e scripts
  10. 040000 tree 84fe9c4cb9cef83e78e90a7fbf33a9a799d7be60 static
  11. 040000 tree 34fd3aa2625ba784bced4a95db6154806ae1d9ee themes

这是我在这次提交时库的根目录中所有的文件。看起来我曾经不小心提交了一个名为mystery.rb的文件,后来我删除了它。

我们的文件在content目录中,接下来让我们看看那个树:61ad34108a327a163cdd66fa1a86342dcef4518e

查看提交的第三步:又一棵树

  1. $ git cat-file -p 61ad34108a327a163cdd66fa1a86342dcef4518e
  2. 040000 tree 1168078878f9d500ea4e7462a9cd29cbdf4f9a56 about
  3. 100644 blob e06d03f28d58982a5b8282a61c4d3cd5ca793005 newsletter.markdown
  4. 040000 tree 1f94b8103ca9b6714614614ed79254feb1d9676c post <-- 我们接下来的目标!
  5. 100644 blob 2d7d22581e64ef9077455d834d18c209a8f05302 profiler-project.markdown
  6. 040000 tree 06bd3cee1ed46cf403d9d5a201232af5697527bb projects
  7. 040000 tree 65e9357973f0cc60bedaa511489a9c2eeab73c29 talks
  8. 040000 tree 8a9d561d536b955209def58f5255fc7fe9523efd zines

还未结束……

上一篇: 走近一线检察官|这场“烧烤大会”上了央视直播,看得直流口水!
下一篇: 简述大学生如何从个人层面践行爱国,敬业,诚信,友善的社会主义核心价值观

为您推荐

发表评论