游客发表

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

发帖时间:2023-09-25 08:49:50

在 Git 货仓中,仓中储那文献终归被存储在那边?

故故者:Julia Evans 编制 行家好!文献此日我和一个诤友辩说 Git 的终归孟非大赞武磊职业道理,我们认为额外,被存边Git 是仓中储那若何存储你的文献的?我们露出它存储在 .git 目次中,但险些到 .git 华厦哪个职位,文献各个版本的终归汗青文献又被存储在那边呢?来顾念记挂一下吧。

以这个博客为例,被存边其文献存储在一个 Git 货仓中,仓中储那此中有一个文献名为 content/post/2019-06-28-brag-doc.markdown。这个文献在我的终归 .git 文献夹中险些的职位在那边?过去的文献版本又被存储在那边?那么,就让我们通过编纂极少简短的被存边 Python 代码来穷究谜底吧。

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

你的仓中储那货仓中,每一个文献的文献汗青版本都被蓄积在 .git/objects 中。譬喻,终归马虎这个博客,.git/objects 贮蓄了 2700 多个文献。

$ find .git/objects/ -type f | wc -l2761

瞩目:.git/objects 贮蓄的音问,不只是是 “货仓中每一个文献的集体先前版本”,但我们暂节减细辩说这一本色。

这里是一个简短的 Python 顺次(find-git-object.py),它能够附和我们定位在 .git/objects 华厦特定文献的险些职位。

import hashlibimport sysdef object_path(content):    header = f"blob { len(content)}\0"    data = header.encode() + content    sha1 = hashlib.sha1()    sha1.update(data)    digest = sha1.hexdigest()    return f".git/objects/{ digest[:2]}/{ digest[2:]}"with open(sys.argv[1], "rb") as f:    print(object_path(f.read()))

此顺次的症结独霸如下:

  • 读捏文献本色
  • 预备一个头部(blob 16673\0),并将其与文献本色连续
  • 预备出文献的 sha1 校验和(此处为 e33121a9af82dd99d6d706d037204251d41d54
  • 将这个 sha1 校验和退换为阶梯(如 .git/objects/e3/3121a9af82dd99d6d706d037204251d41d54

运转的设施如下:

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

术语阐述:“本色寻址存储”

这种存储计谋的术语为“本色寻址存储content addressed storage”,它指的是目的在数据库华厦文献名与文献本色的哈希值雷同。

本色寻址存储的风趣之处就是,要是我有两份能够多份本色无缺雷同的文献,在 Git 的数据库中,并不会所以占用额外空间。要是本色的哈希值是 aabbbbbbbbbbbbbbbbbbbbbbbbb,它们都邑被存储在 .git/objects/aa/bbbbbbbbbbbbbbbbbbbbb 中。

这些目的是若何进行编码的?

要是我试验在 .git/objects 目次下稽查这个文献,露出的本色顺应有极少额外:

$ cat .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54x^A<8D><9B>}sƑo|<8A>^Q<9D>ju<92>
<9C><9C>*<89>j^...

这是怎样回事呢?让我们来运转 file 夂箢检讨一下:

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

素来,它是缩短的!我们能够编纂一个茂密的 Python 顺次—— decompress.py,而后用 zlib 模块去解压这些数据:

import zlibimport syswith open(sys.argv[1], "rb") as f:    content = f.read()    print(zlib.decompress(content).decode())

让我们来解压一下顾念记挂顾念记挂结果:

$ python3 decompress.py .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54blob 16673---title: "Get your work recognized: write a brag document"date: 2019-06-28T18:46:02Zurl: /blog/brag-documents/categories: []---... the entire blog post ...

结果露出,这些数据的编码花式额外轻省:首先有 blob 16673\0 记号,后来就是文献的险些本色。

这里并莫得迥异性数据(diff)

这里有一件我第一次露出时让我认为骇怪的事:这里并莫得任何迥异性数据!阿谁文献是该篇博客著述的第 9 个版本,但 Git 在 .git/objects 目次中存储的版本是齐备文献本色,而并非与前一版本的迥异。

纵然 Git 本质上有韶华会以迥异性数据存储文献(譬喻,当你运转 git gc 时,为了擢升结果,它能够会将多个迥异的文献封装成 “打包文献”),但在我片面阅历中,我从未须要眷注这个细节,是以我们不在此长远辩说。然而,看待这种花色若何职业,Aditya Mukerjee 有篇显赫的著述 《拆解 Git 的打包文献》。

博客著述的旧版本在哪?

你能够会郁闷:要是在我设立了极少错别字曾经,这篇博文曾经糊口了 8 个版本,那它们在 .git/objects 目次华厦职位是那边?我们若何找到它们呢?

首先,我们来骗捏 git log 夂箢来查找订正过这个文献的每一个提交:

$ git log --oneline  content/post/2019-06-28-brag-doc.markdownc6d4db2d423cd76a7e91d7d0f105905ab6d23643998a46dd67a26b04d9999f17026c0f5272442b67

而后,我们采纳一个曾经的提交,譬喻 026c0f52。提交也被存储在 .git/objects 中,我们能够试验在那里找到它。然而阻难了!鉴于 ls .git/objects/02/6c* 莫得露出任何本色!要是有人通知你,“我们露出有时 Git 会打包目的来节约空间,我们并不需过多珍重它”,但而今,我们须要去面对这个题目了。

那就让我们去解阁阁它吧。

让我们起点解包极少目的

而今我们须要从打包文献中解包出极少目的。我在 Stack Overflow 上查找了一下,顾念记挂起来我们能够这样进行独霸:

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

这种直接对库进行手术式的做法让人有些紧绷,但要是我误独霸了,我还能够从 Github 上重新克隆这个库,是以我并不太顾虑。

解包集体的目的文献后,我们赢得了更多的目的:约莫有 20000 个,而不是素来的约莫 2700 个。顾念记挂起来很酷。

find .git/objects/ -type f | wc -l20138

我们追想再顾念记挂顾念记挂提交

而今我们能够连缀顾念记挂顾念记挂我们的提交 026c0f52。我们曾经说过 .git/objects 中并不都是文献,此中一部门是提交!为了弄领路我们的旧著述 content/post/2019-06-28-brag-doc.markdown 是在那边被覆灭的,我们须要长远稽查这个提交。

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

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

颠末解包后,我们而今能够在 .git/objects/02/6c0f5208c5ea10608afc9252c4a56c1ac1d7e4 中找到提交 026c0f52,我们能够用底下的设施去稽查它:

$ python3 decompress.py .git/objects/02/6c0f5208c5ea10608afc9252c4a56c1ac1d7e4commit 211tree 01832a9109ab738dac78ee4e95024c74b9b71c27parent 72442b67590ae1fcbfe05883a351d822454e3826author Julia Evans  1561998673 -0400committer Julia Evans  1561998673 -0400brag doc

我们也能够用 git cat-file -p 026c0f52 夂箢来赢得捏雷同的音问,这个夂箢能起到雷同的浸染,然而它在花色化数据时做得更好极少。(-p 选项意味着它能够以更有爱的花式进行花色化)

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

这个提交贮蓄一个。树是什么呢?让我们顾念记挂一下。树的 ID 是 01832a9109ab738dac78ee4e95024c74b9b71c27,我们能够骗捏先前的 decompress.py 脚本稽查这个 Git 目的,纵然我不得不移除 .decode() 能力预防脚本溃逃。

$ 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 夂箢,它能以更有爱的花式露出数据,我不想自身编纂一个领路器。

$ git cat-file -p 01832a9109ab738dac78ee4e95024c74b9b71c27100644 blob c3f76024389b8d4f192f18b77d7cc7ce8e3a68ad	.gitignore100644 blob 7ebaecb311a05e1ca9a43f1eb90f1c6647960bc1	README.md100644 blob 0f21dc9bf1a73afc89634bac586271384e24b2c9	Rakefile100644 blob 00b9d54abd71119737d33ee5d29d81ebdcea5a37	config.yaml040000 tree 61ad34108a327a163cdd66fa1a86342dcef4518e	content <-- 这是我们接下来的目的040000 tree 6d8543e9eeba67748ded7b5f88b781016200db6f	layouts100644 blob 22a321a88157293c81e4ddcfef4844c6c698c26f	mystery.rb040000 tree 8157dc84a37fca4cb13e1257f37a7dd35cfe391e	scripts040000 tree 84fe9c4cb9cef83e78e90a7fbf33a9a799d7be60	static040000 tree 34fd3aa2625ba784bced4a95db6154806ae1d9ee	themes

这是我在这次提交时库的根目次中集体的文献。顾念记挂起来我曾经不谨慎提交了一个名为 mystery.rb 的文献,自后我苟简了它。

我们的文献在 content 目次中,接下来让我们顾念记挂顾念记挂阿谁树:61ad34108a327a163cdd66fa1a86342dcef4518e

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

$ git cat-file -p 61ad34108a327a163cdd66fa1a86342dcef4518e040000 tree 1168078878f9d500ea4e7462a9cd29cbdf4f9a56    about100644 blob e06d03f28d58982a5b8282a61c4d3cd5ca793005    newsletter.markdown040000 tree 1f94b8103ca9b6714614614ed79254feb1d9676c    post <-- 我们接下来的目的!100644 blob 2d7d22581e64ef9077455d834d18c209a8f05302    profiler-project.markdown040000 tree 06bd3cee1ed46cf403d9d5a201232af5697527bb    projects040000 tree 65e9357973f0cc60bedaa511489a9c2eeab73c29    talks040000 tree 8a9d561d536b955209def58f5255fc7fe9523efd    zines

还未遣散……

稽查提交的第四步:更多的树……

我们要追求的文献位于 post/ 目次,所以我们须要进一步穷究:

$ git cat-file -p 1f94b8103ca9b6714614614ed79254feb1d9676c.... 节减了豪爽行 ...100644 blob 170da7b0e607c4fd6fb4e921d76307397ab89c1e    2019-02-17-organizing-this-blog-into-categories.markdown100644 blob 7d4f27e9804e3dc80ab3a3912b4f1c890c4d2432    2019-03-15-new-zine--bite-size-networking-.markdown100644 blob 0d1b9fbc7896e47da6166e9386347f9ff58856aa    2019-03-26-what-are-monoidal-categories.markdown100644 blob d6949755c3dadbc6fcbdd20cc0d919809d754e56    2019-06-23-a-few-debugging-resources.markdown100644 blob 3105bdd067f7db16436d2ea85463755c8a772046    2019-06-28-brag-doc.markdown <-- 我们找到了!!!

在此,2019-06-28-brag-doc.markdown 之是以位于列表终末,是鉴于在揭橥时它是最新的博文。

稽查提交的第五步:我们终竟找到它!

颠末致力,我们找到了博文汗青版本方位的目的文献!太棒了!它的哈希值是 3105bdd067f7db16436d2ea85463755c8a772046,所以它位于 git/objects/31/05bdd067f7db16436d2ea85463755c8a772046

我们能够骗捏 decompress.py 来稽查它:

$ python3 decompress.py .git/objects/31/05bdd067f7db16436d2ea85463755c8a772046 | headblob 15924---title: "Get your work recognized: write a brag document"date: 2019-06-28T18:46:02Zurl: /blog/brag-documents/categories: []---... 文献的糟粕部门在此 ...

这就是博文的旧版本!要是我践诺夂箢 git checkout 026c0f52 content/post/2019-06-28-brag-doc.markdown 能够 git restore --source 026c0f52 content/post/2019-06-28-brag-doc.markdown,我就会赢得捏到这个版本。

这样遍历树就是 git log 的运转机制

我们适才通过的通盘进程(找到提交、逐层遍历目次树、采集所需文献名)顾念记挂似繁琐,但本质被骗我们践诺 git log content/post/2019-06-28-brag-doc.markdown 时,背地就是这样在运转。它须要逐个检讨你汗青记载华厦每一个提交,在每个提交中核查 content/post/2019-06-28-brag-doc.markdown 的版本(譬喻在这个案例中为 3105bdd067f7db16436d2ea85463755c8a772046),并稽查它是否自上一提交倚赖有所迁移。

这就是为什么有时 git log FILENAME 会践诺的有些迂缓 —— 我的这个货仓中有 3000 个提交,它须要对每个提交做豪爽的职业,来肯定该文献是否在该提交中爆发过迁移。

我有多少个汗青版本的文献?

而今,我在我的博旅店房中追踪了 1530 个文献:

$ git ls-files | wc -l1530

但汗青文献有多少呢?我们能够列出 .git/objects 中集体的本色,顾念记挂顾念记挂有多少目的文献:

$ find .git/objects/ -type f | grep -v pack | awk -F/ '{ print $3 $4}' | wc -l20135

但并不是集体这些都代表过去版本的文献 —— 正如我们曾经所见,许多都是提交和目次树。然而,我们能够编纂一个小小的 Python 脚本 find-blobs.py,遍历集体目的并检讨是否以 blob 首先:

import zlibimport sysfor line in sys.stdin:    line = line.strip()    filename = f".git/objects/{ line[0:2]}/{ line[2:]}"    with open(filename, "rb") as f:        contents = zlib.decompress(f.read())        if contents.startswith(b"blob"):            print(line)
$ find .git/objects/ -type f | grep -v pack | awk -F/ '{ print $3 $4}' | python3 find-blobs.py | wc -l6713

因而,顾念记挂起来在我的 Git 货仓中寄阁阁的旧文献版本有 6713 - 1530 = 5183 个,Git 会为我覆灭这些文献,以备我想着要规复它们时骗捏。太好了!

就这些啦!

在 这个 gist 中附上了险些的此篇著述所用代码,素来没多少。

我算计我曾经对 Git 的职业花式老友知彼,但我过去从未真确触及过打包文献,是以这次穷究很风趣。我也很少陈想念当我让 git log 追踪一个文献的汗青时,它本质上有多大的职业量,所以也很高兴能长远钻探这个。

行径一个风趣的后续:我提交这篇博文后,Git 就鉴戒我货仓华厦目的太多(我猜 20,000 太多了!),并运转 git gc 将它们险些缩短成打包文献。是以而今我的 .git/objects 目次曾经被缩短得相等小了:

$ find .git/objects/ -type f | wc -l14

(题图:MJ/319a396c-6f3f-4891-b051-261312c8ea9a)

责任编辑:庞桂玉 源泉: Linux诸华 Git文献

    热门排行

    友情链接