0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Git是如何存儲文件的?Git的工作原理解析

dyquk4xk2p3d ? 來源:網(wǎng)絡(luò)整理 ? 2023-10-31 15:36 ? 次閱讀

大家好!今天我和一個朋友討論 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 -l

2761

注意:.git/objects包含的信息,不僅僅是 “倉庫中每一個文件的所有先前版本”,但我們暫不詳細(xì)討論這一內(nèi)容。

這里是一個簡短的 Python 程序(find-git-object.py gist.github.com),它可以幫助我們定位在.git/objects中的特定文件的具體位置。

import hashlib

import sys

def object_path(content):

header = f"blob {len(content)}"

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()))

此程序的主要操作如下:

?讀取文件內(nèi)容 ?計算一個頭部(blob 16673),并將其與文件內(nèi)容合并 ?計算出文件的 sha1 校驗和(此處為e33121a9af82dd99d6d706d037204251d41d54) ?將這個 sha1 校驗和轉(zhuǎn)換為路徑(如.git/objects/e3/3121a9af82dd99d6d706d037204251d41d54)

運行的方法如下:

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

.git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54

術(shù)語解釋:“內(nèi)容尋址存儲”

這種存儲策略的術(shù)語為“內(nèi)容尋址存儲(content addressed storage)”,它指的是對象在數(shù)據(jù)庫中的文件名與文件內(nèi)容的哈希值相同。

內(nèi)容尋址存儲的有趣之處就是,假設(shè)我有兩份或許多份內(nèi)容完全相同的文件,在 Git 的數(shù)據(jù)庫中,并不會因此占用額外空間。如果內(nèi)容的哈希值是aabbbbbbbbbbbbbbbbbbbbbbbbb,它們都會被存儲在.git/objects/aa/bbbbbbbbbbbbbbbbbbbbb中。

這些對象是如何進(jìn)行編碼的?

如果我嘗試在.git/objects目錄下查看這個文件,顯示的內(nèi)容似乎有一些奇怪:

$ cat .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54

x^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模塊去解壓這些數(shù)據(jù):

import zlib

import sys

with open(sys.argv[1], "rb") as f:

content = f.read()

print(zlib.decompress(content).decode())

讓我們來解壓一下看看結(jié)果:

$ python3 decompress.py .git/objects/8a/e33121a9af82dd99d6d706d037204251d41d54

blob 16673---

title: "Get your work recognized: write a brag document"

date: 2019-06-28T18:46:02Z

url: /blog/brag-documents/

categories: []

---

... the entire blog post ...

結(jié)果顯示,這些數(shù)據(jù)的編碼方式非常簡單:首先有blob 16673標(biāo)識,其后就是文件的全部內(nèi)容。

這里并沒有差異性數(shù)據(jù)(diff)

這里有一件我第一次知道時讓我感到驚訝的事:這里并沒有任何差異性數(shù)據(jù)!那個文件是該篇博客文章的第 9 個版本,但 Git 在.git/objects目錄中存儲的版本是完整文件內(nèi)容,而并非與前一版本的差異。

盡管 Git 實際上有時候會以差異性數(shù)據(jù)存儲文件(例如,當(dāng)你運行g(shù)it gc時,為了提升效率,它可能會將多個不同的文件封裝成 “打包文件”),但在我個人經(jīng)驗中,我從未需要關(guān)注這個細(xì)節(jié),所以我們不在此深入討論。然而,關(guān)于這種格式如何工作,Aditya Mukerjee 有篇優(yōu)秀的文章 《拆解 Git 的打包文件 codewords.recurse.com》。

博客文章的舊版本在哪?

你可能會好奇:如果在我修復(fù)了一些錯別字之前,這篇博文已經(jīng)存在了 8 個版本,那它們在.git/objects目錄中的位置是哪里?我們?nèi)绾握业剿鼈兡兀?/p>

首先,我們來使用git log命令來查找改動過這個文件的每一個提交:

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

c6d4db2d

423cd76a

7e91d7d0

f105905a

b6d23643

998a46dd

67a26b04

d9999f17

026c0f52

72442b67

然后,我們選擇一個之前的提交,比如026c0f52。提交也被存儲在.git/objects中,我們可以嘗試在那里找到它。但是失敗了!因為ls .git/objects/02/6c*沒有顯示任何內(nèi)容!如果有人告訴你,“我們知道有時 Git 會打包對象來節(jié)省空間,我們并不需過多關(guān)心它”,但現(xiàn)在,我們需要去面對這個問題了。

那就讓我們?nèi)ソ鉀Q它吧。

讓我們開始解包一些對象

現(xiàn)在我們需要從打包文件中解包出一些對象。我在 Stack Overflow 上查找了一下,看起來我們可以這樣進(jìn)行操作:

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

$ git unpack-objects < pack-adeb3c14576443e593a3161e7e1b202faba73f54.pack

這種直接對庫進(jìn)行手術(shù)式的做法讓人有些緊張,但如果我誤操作了,我還可以從 Github 上重新克隆這個庫,所以我并不太擔(dān)心。

解包所有的對象文件后,我們得到了更多的對象:大約有 20000 個,而不是原來的大約 2700 個??雌饋砗芸?。

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

20138

我們回頭再看看提交

現(xiàn)在我們可以繼續(xù)看看我們的提交026c0f52。我們之前說過.git/objects中并不都是文件,其中一部分是提交!為了弄清楚我們的舊文章content/post/2019-06-28-brag-doc.markdown是在哪里被保存的,我們需要深入查看這個提交。

首先,我們需要在.git/objects中查看這個提交。

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

經(jīng)過解包后,我們現(xiàn)在可以在.git/objects/02/6c0f5208c5ea10608afc9252c4a56c1ac1d7e4中找到提交026c0f52,我們可以用下面的方法去查看它:

$ python3 decompress.py .git/objects/02/6c0f5208c5ea10608afc9252c4a56c1ac1d7e4

commit 211tree 01832a9109ab738dac78ee4e95024c74b9b71c27

parent 72442b67590ae1fcbfe05883a351d822454e3826

author Julia Evans 1561998673 -0400

committer Julia Evans 1561998673 -0400

brag doc

我們也可以用git cat-file -p 026c0f52命令來獲取相同的信息,這個命令能起到相同的作用,但是它在格式化數(shù)據(jù)時做得更好一些。(-p選項意味著它能夠以更友好的方式進(jìn)行格式化)

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

這個提交包含一個樹。樹是什么呢?讓我們看一下。樹的 ID 是01832a9109ab738dac78ee4e95024c74b9b71c27,我們可以使用先前的decompress.py腳本查看這個 Git 對象,盡管我不得不移除.decode()才能避免腳本崩潰。

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

這個輸出的格式有些難以閱讀。主要的問題在于,該提交的哈希(xc3xf7$8x9bx8dOx19/x18xb7}|xc7xcex8e…)是原始字節(jié),而沒有進(jìn)行十六進(jìn)制的編碼,因此我們看到xc3xf7$8x9bx8d而非c3f76024389b8d。我打算切換至git cat-file -p命令,它能以更友好的方式顯示數(shù)據(jù),我不想自己編寫一個解析器。

$ git cat-file -p 01832a9109ab738dac78ee4e95024c74b9b71c27

100644 blob c3f76024389b8d4f192f18b77d7cc7ce8e3a68ad.gitignore

100644 blob 7ebaecb311a05e1ca9a43f1eb90f1c6647960bc1README.md

100644 blob 0f21dc9bf1a73afc89634bac586271384e24b2c9Rakefile

100644 blob 00b9d54abd71119737d33ee5d29d81ebdcea5a37config.yaml

040000 tree 61ad34108a327a163cdd66fa1a86342dcef4518econtent <-- 這是我們接下來的目標(biāo)

040000 tree 6d8543e9eeba67748ded7b5f88b781016200db6flayouts

100644 blob 22a321a88157293c81e4ddcfef4844c6c698c26fmystery.rb

040000 tree 8157dc84a37fca4cb13e1257f37a7dd35cfe391escripts

040000 tree 84fe9c4cb9cef83e78e90a7fbf33a9a799d7be60static

040000 tree 34fd3aa2625ba784bced4a95db6154806ae1d9eethemes

這是我在這次提交時庫的根目錄中所有的文件。看起來我曾經(jīng)不小心提交了一個名為mystery.rb的文件,后來我刪除了它。

我們的文件在content目錄中,接下來讓我們看看那個樹:61ad34108a327a163cdd66fa1a86342dcef4518e

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

$ git cat-file -p 61ad34108a327a163cdd66fa1a86342dcef4518e

040000 tree 1168078878f9d500ea4e7462a9cd29cbdf4f9a56 about

100644 blob e06d03f28d58982a5b8282a61c4d3cd5ca793005 newsletter.markdown

040000 tree 1f94b8103ca9b6714614614ed79254feb1d9676c post <-- 我們接下來的目標(biāo)!

100644 blob 2d7d22581e64ef9077455d834d18c209a8f05302 profiler-project.markdown

040000 tree 06bd3cee1ed46cf403d9d5a201232af5697527bb projects

040000 tree 65e9357973f0cc60bedaa511489a9c2eeab73c29 talks

040000 tree 8a9d561d536b955209def58f5255fc7fe9523efd zines

還未結(jié)束……

查看提交的第四步:更多的樹……

我們要尋找的文件位于post/目錄,因此我們需要進(jìn)一步探索:

$ git cat-file -p 1f94b8103ca9b6714614614ed79254feb1d9676c

.... 省略了大量行 ...

100644 blob 170da7b0e607c4fd6fb4e921d76307397ab89c1e 2019-02-17-organizing-this-blog-into-categories.markdown

100644 blob 7d4f27e9804e3dc80ab3a3912b4f1c890c4d2432 2019-03-15-new-zine--bite-size-networking-.markdown

100644 blob 0d1b9fbc7896e47da6166e9386347f9ff58856aa 2019-03-26-what-are-monoidal-categories.markdown

100644 blob d6949755c3dadbc6fcbdd20cc0d919809d754e56 2019-06-23-a-few-debugging-resources.markdown

100644 blob 3105bdd067f7db16436d2ea85463755c8a772046 2019-06-28-brag-doc.markdown <-- 我們找到了!??!

在此,2019-06-28-brag-doc.markdown之所以位于列表最后,是因為在發(fā)布時它是最新的博文。

查看提交的第五步:我們終于找到它!

經(jīng)過努力,我們找到了博文歷史版本所在的對象文件!太棒了!它的哈希值是3105bdd067f7db16436d2ea85463755c8a772046,因此它位于git/objects/31/05bdd067f7db16436d2ea85463755c8a772046。

我們可以使用decompress.py來查看它:

$ python3 decompress.py .git/objects/31/05bdd067f7db16436d2ea85463755c8a772046 | head

blob 15924---

title: "Get your work recognized: write a brag document"

date: 2019-06-28T18:46:02Z

url: /blog/brag-documents/

categories: []

---

... 文件的剩余部分在此 ...

這就是博文的舊版本!如果我執(zhí)行命令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 的運行機(jī)制

我們剛剛經(jīng)歷的整個過程(找到提交、逐層遍歷目錄樹、搜索所需文件名)看似繁瑣,但實際上當(dāng)我們執(zhí)行g(shù)it log content/post/2019-06-28-brag-doc.markdown時,背后就是這樣在運行。它需要逐個檢查你歷史記錄中的每一個提交,在每個提交中核查content/post/2019-06-28-brag-doc.markdown的版本(例如在這個案例中為3105bdd067f7db16436d2ea85463755c8a772046),并查看它是否自上一提交以來有所改變。

這就是為什么有時git log FILENAME會執(zhí)行的有些緩慢 —— 我的這個倉庫中有 3000 個提交,它需要對每個提交做大量的工作,來判斷該文件是否在該提交中發(fā)生過變化。

我有多少個歷史版本的文件?

目前,我在我的博客倉庫中跟蹤了 1530 個文件:

$ git ls-files | wc -l

1530

但歷史文件有多少呢?我們可以列出.git/objects中所有的內(nèi)容,看看有多少對象文件:

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

20135

但并不是所有這些都代表過去版本的文件 —— 正如我們之前所見,許多都是提交和目錄樹。不過,我們可以編寫一個小小的 Python 腳本find-blobs.py,遍歷所有對象并檢查是否以blob開頭:

import zlib

import sys

for 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 -l

6713

于是,看起來在我的 Git 倉庫中存放的舊文件版本有6713 - 1530 = 5183個,Git 會為我保存這些文件,以備我想著要恢復(fù)它們時使用。太好了!

就這些啦!

在這個 gist gist.github.com中附上了全部的此篇文章所用代碼,其實沒多少。

我以為我已經(jīng)對 Git 的工作方式了如指掌,但我以前從未真正涉及過打包文件,所以這次探索很有趣。我也很少思考當(dāng)我讓git log跟蹤一個文件的歷史時,它實際上有多大的工作量,因此也很開心能深入研究這個。

作為一個有趣的后續(xù):我提交這篇博文后,Git 就警告我倉庫中的對象太多(我猜 20,000 太多了?。?,并運行g(shù)it gc將它們?nèi)繅嚎s成打包文件。所以現(xiàn)在我的.git/objects目錄已經(jīng)被壓縮得十分小了:

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

14

編輯:黃飛

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)據(jù)存儲
    +關(guān)注

    關(guān)注

    5

    文章

    959

    瀏覽量

    50834
  • 倉庫
    +關(guān)注

    關(guān)注

    0

    文章

    20

    瀏覽量

    13518
  • Git
    Git
    +關(guān)注

    關(guān)注

    0

    文章

    196

    瀏覽量

    15720

原文標(biāo)題:在 Git 倉庫中,文件究竟被存儲在哪里?

文章出處:【微信號:良許Linux,微信公眾號:良許Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Git是什么?Git的基本使用資料說明

      Git是什么? Git是目前世界上最先進(jìn)的分布式版本控制系統(tǒng)(沒有之一)。
    發(fā)表于 05-29 17:56 ?1次下載
    <b class='flag-5'>Git</b>是什么?<b class='flag-5'>Git</b>的基本使用資料說明

    簡述Git的一些基礎(chǔ)知識

    ? 簡單地說,Git 究竟是怎樣的一個系統(tǒng)呢?請注意接下來的內(nèi)容非常重要,若你理解Git 的思想和基本工作原理,用起來就會知其所以然,游刃有余。在學(xué)習(xí)
    的頭像 發(fā)表于 09-23 15:43 ?2539次閱讀
    簡述<b class='flag-5'>Git</b>的一些基礎(chǔ)知識

    關(guān)于Git教程解析

    即我們新建git倉庫后在電腦上看到的目錄,此區(qū)域內(nèi)文件改動完全由我們自己掌控,Git不進(jìn)行備份管理,可以隨時把新增工作區(qū)內(nèi)容通過Git命令刪
    的頭像 發(fā)表于 04-27 14:54 ?958次閱讀

    Git是怎樣的一個系統(tǒng) Git工作原理

    執(zhí)行完成了 git commit 命令,究竟做了什么呢? Git 倉庫中的提交記錄保存的是你的目錄下所有文件的快照,就像是把整個目錄復(fù)制,然后再粘貼一樣,但比復(fù)制粘貼優(yōu)雅許多!
    發(fā)表于 02-22 10:41 ?308次閱讀

    探究Git基本原理(下)

    簡單地說,Git 究竟是怎樣的一個系統(tǒng)呢?請注意接下來的內(nèi)容非常重要,若你理解Git 的思想和基本工作原理,用起來就會知其所以然,游刃有余。 在學(xué)習(xí)
    的頭像 發(fā)表于 05-12 15:20 ?564次閱讀
    探究<b class='flag-5'>Git</b>基本原理(下)

    探究Git基本原理(上)

    簡單地說,Git 究竟是怎樣的一個系統(tǒng)呢?請注意接下來的內(nèi)容非常重要,若你理解Git 的思想和基本工作原理,用起來就會知其所以然,游刃有余。 在學(xué)習(xí)
    的頭像 發(fā)表于 05-12 15:20 ?673次閱讀
    探究<b class='flag-5'>Git</b>基本原理(上)

    git rebase與相關(guān)git merge命令比較

    。 #概念 ????首先要理解的是git rebase和git merge解決了同樣的問題。這兩個命令都旨在將更改從一個分支集成到另一個分支 - 它們只是以不同的方式進(jìn)行。試想一下當(dāng)你開始在專用分支中開發(fā)新功能時另一個團(tuán)隊成員以
    的頭像 發(fā)表于 05-26 16:22 ?834次閱讀
    <b class='flag-5'>git</b> rebase與相關(guān)<b class='flag-5'>git</b> merge命令比較

    git的命令和參數(shù)

    ? ? 不知道大家平時都是怎么去學(xué)習(xí)git的,要記憶那么多的命令和參數(shù),我個人是不推薦死記硬背的,以往經(jīng)驗證明卷的越瘋狂忘的也越快! 其實簡單的理解工作原理和熟練運用少部分常用命令,日常開發(fā)問題不大
    的頭像 發(fā)表于 05-31 14:22 ?527次閱讀

    Git的基本概念,及基本框架、工作流程

    版本庫/倉庫(Repository /r??pɑ?z?t??ri/ 倉庫)Git的管理倉庫,管理版本的數(shù)據(jù)庫,記錄文件/目錄狀態(tài)的地方,所有內(nèi)容的修改記錄(版本)都在這里。就是工作區(qū)目錄下的隱藏
    的頭像 發(fā)表于 06-08 16:09 ?2739次閱讀
    <b class='flag-5'>Git</b>的基本概念,及基本框架、<b class='flag-5'>工作</b>流程

    git rebase和git merge的區(qū)別

    ? 解決沖突 git rebase和git merge的區(qū)別 分支合并 git merge是用來合并兩個分支的。 比如:將 b 分支合并到當(dāng)前分支。 同樣git rebase b,也是
    的頭像 發(fā)表于 07-05 09:54 ?605次閱讀
    <b class='flag-5'>git</b> rebase和<b class='flag-5'>git</b> merge的區(qū)別

    Git是什么 Git介紹

    系統(tǒng)以文件變更列表的方式存儲信息,這類系統(tǒng)(CVS、Subversion等)將它們存儲的信息看作是一組基本文件和每個文件隨時間逐步累積的差異
    的頭像 發(fā)表于 07-22 10:50 ?1737次閱讀
    <b class='flag-5'>Git</b>是什么 <b class='flag-5'>Git</b>介紹

    初次運行Git前的配置

    config 的工具來幫助設(shè)置控制 Git 外觀和行為的配置變量。這些變量存儲在三個不同的位置: /etc/gitconfig 文件: 包含系統(tǒng)上每一個用戶及他們倉庫的通用配置。如果在執(zhí)行
    的頭像 發(fā)表于 07-22 10:56 ?756次閱讀

    Git工作原理和基本用法

    本文圖解Git中的最常用命令。如果你稍微理解Git工作原理,這篇文章能夠讓你理解的更透徹。
    的頭像 發(fā)表于 08-07 10:25 ?597次閱讀
    <b class='flag-5'>Git</b>的<b class='flag-5'>工作原理</b>和基本用法

    git基本操作命令用法

    基本用法 上面的四條命令在工作目錄、暫存目錄(也叫做索引)和倉庫之間復(fù)制文件。 git add files把當(dāng)前文件放入暫存區(qū)域。 git
    的頭像 發(fā)表于 09-13 16:29 ?743次閱讀
    <b class='flag-5'>git</b>基本操作命令用法

    如何在 Git 中恢復(fù)隱藏的修改記錄

    git stash 和 git stash pop 這樣的命令是用來擱置(藏匿)和恢復(fù)我們工作目錄中的變化的。在本教程中,我們將學(xué)習(xí)如何在 Git 中恢復(fù)隱藏的修改記錄。 在
    的頭像 發(fā)表于 10-09 14:09 ?946次閱讀