Git數據存儲的原理淺析

寫做背景

進來在閒暇的時間裏在看一些關係P2P網絡的拓撲發現的內容,重點關注了Markle Tree的知識點,在一篇文章裏( https://www.sdnlab.com/20095....),發現了了一句話 「Merkle DAG的一個常見例子就是Git存儲庫」,因而查找了一些關於git存儲庫的原理,先整理以下。僅供本身和你們參考。

Git存儲庫解析

當時個人疑問:html

  • git怎麼存儲數據的,如何能根據存儲的數據能夠很精確的回退到制定的版本?
  • git存儲和docker的存儲機制相似嗎?是否是都是分層的存儲?
  • git若是不是分層,每次提交都存儲起來,那麼數據量大了會怎麼辦?
解惑
咱們的解惑路線是,重新建一個本地git倉庫開始,一步一步增長數據和提交,觀察內容的具體變化。
  • 首先使用git init新建一個本地倉庫,而後打開倉庫中的.git文件夾git

    • 圖片描述
    • HEAD表示當前提交的指針位置;
    • index是索引文件;
    • refs文件夾中的文件是不一樣分支指向的commitID;
    • logs文件夾中記錄的是每次refs的歷史記錄;
    • objects文件夾中的內容就是用來存放git本地倉庫對象
  • 既然找到了存儲git數據的位置,那麼git數據結構是什麼樣的呢?golang

    • git 是以鍵值的方式存儲的,也就是說任何類型的數據均可以存儲。所以也能夠在任什麼時候候經過鍵取出對應的值;
    • git中底層生成了4中數據的對象:算法

      1. tree對象:能夠看做一個目錄,管理一些「tree」對象或是「blob」對象。它有一串指向「blob」對象或是其它「tree」對象的指針,通常用來表示內容之間的目錄層次關係(就像文件和子目錄)。
      2. blob對象: 一個「blob」一般用來存儲文件的內容。一個「blob」對象就是一塊二進制數據,blob對象的鍵是根據SHA1算法生成的,因此若兩個文件在一個目錄樹或是一個版本倉庫中有一樣的數據內容,那麼它們將會共享同一個「blob」對象,和其所對應的文件所在路徑、文件名是否改被更改都徹底沒有關係。
      3. commit對象:「commit」對象指向一個「tree對象」,而且帶有相關的描述信息,標記項目某一個特定時間點的狀態。它包括一些關於時間點的元數據,如時間戳、最近一次提交的做者、指向上次提交的指針等等。
      4. tag對象: 一個「tag」對象包括一個對象名(SHA1簽名)、對象類型、標籤名、標籤建立人的名字(「tagger」), 還有一條可能包含有簽名(signature)的消息。
  • 當新增一些內容的時候,進行git commit命令會出現什麼變化?docker

    • 當進行一次提交的時候,objects、logs和refs文件夾都會發生變化,咱們主要關注objects文件夾。
    • 每次commit都會對數據進行一次保存,會生成commit對象、tree對象和blob對象;
    • objects文件夾裏面的數據存放的具體規則,對於這三種對象,都會用SHA-1對內容和頭信息生成Hash值,去hash值的前兩位爲objects目錄下面的文件夾的名字,取剩餘38個字符爲文件名,例:8b0c4fe1567a463214c09334b54977e0114c90fe,取8b在objects建立一個文件夾,取0c4fe1567a463214c09334b54977e0114c90fe爲文件名在8b文件夾下建立一個文件。
    • clipboard.png
  • 知識點學習了以後,咱們怎麼去驗證呢?網絡

    • 使用 git cat-file 對咱們提交的內容進行驗證。
    • 我進行了兩次commit,一次是徹底新建的一個README.md文件,裏面是有一行數據(### You Know);第二次commit,新建一個test.py文件和在README.md中新添加了一些數據;
    • git cat-file -t查看對象的類型,git cat-file -p優雅的方式打印對象的內容。
  1. 使用git log --pretty=oneline查看個人兩次提交clipboard.png
  2. 使用git cat-file -t 172b54c8cd3eedca2fc301374286c2cb807d674f查看第一次提交的類型clipboard.png
  3. 使用git cat-file -p 172b54c8cd3eedca2fc301374286c2cb807d674fe查看第一次提交的內容clipboard.png
  4. 使用git cat-file -p 8b0c4fe1567a463214c09334b54977e0114c90fe查看第一次提交的tree對象,能夠看到tree對象中存放的是一個blob對象,就是咱們第一次提交新建的文件README.mdclipboard.png
  5. 使用git cat-file -p 67aeba604cea61ec63d19db0706b19d846c65ba4查看第一次提交的blob對象的內容爲### You Knowclipboard.png
  6. 使用git cat-file -p 03543a4c19023da01b5114d7f7a614d95a1bf084查看第二次提交的內容clipboard.png
  7. 使用git cat-file -p 03543a4c19023da01b5114d7f7a614d95a1bf084查看第二次提交的tree對象內容,包括修改的內容和新增的內容clipboard.png
  8. 使用git cat-file -p 03543a4c19023da01b5114d7f7a614d95a1bf084查看第二次提交的README.md blob對象內容,能夠看到是整個文件的所有內容,而不是僅僅包含修改的數據。clipboard.png
  • 總結問題的答案數據結構

    • git的數據存儲數據結構是鍵值類型,分爲4個對象,而且每次提交都是整個文件的存儲,而不是分層的增長存儲,因此這樣會致使存儲的數據量很大,那git用的方法是使用zlib對數據進行壓縮,因此咱們打開存儲的文件是這樣的數據,那咱們都是用cat-file命令來查看的,怎麼才能這些內容是通過zlib壓縮過的呢?clipboard.png
    • 我用GO語言寫了一個簡單的程序,來驗證這些數據是通過zlib壓縮以後的,運行這個程序的時候帶上你要查看git對象的文件路徑,就能夠看到被還原的內容了;clipboard.png

GO代碼

package main

import (
    "bytes"
    "compress/zlib"
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

//進行zlib壓縮
func DoZlibCompress(src []byte) []byte {
    var in bytes.Buffer
    w := zlib.NewWriter(&in)
    w.Write(src)
    w.Close()
    return in.Bytes()
}

//進行zlib解壓縮
func DoZlibUnCompress(compressSrc []byte) []byte {
    b := bytes.NewReader(compressSrc)
    var out bytes.Buffer
    r, _ := zlib.NewReader(b)
    io.Copy(&out, r)
    return out.Bytes()
}

func main() {
    args := os.Args
    if args == nil || len(args) < 2{
        fmt.Println("Should input zlib file path.")
       return 
    }   
        
    b, err := ioutil.ReadFile(args[1])
    if err != nil {
        fmt.Print(err)
    }

    fmt.Println(string(DoZlibUnCompress(b)))
}

總結

  • 文章是參考了不少前輩博客基礎上寫來的, 也有本身的實踐,因此頗有必要記錄下來。
  • 有問題就想辦法去理解和解決,並經過動手實踐驗證。
相關文章
相關標籤/搜索