<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用了5年的Git,你竟然還不曉得它的實現(xiàn)原理!

          共 5750字,需瀏覽 12分鐘

           ·

          2021-02-02 11:08


          作者 |?楊夕

          來源 |?https://zhuanlan.zhihu.com/p/53750883

          越了解事物的本質(zhì)就越接近真相。我發(fā)現(xiàn)學習Git內(nèi)部是如何工作的以及Git的內(nèi)部數(shù)據(jù)結(jié)構這部分內(nèi)容,對于理解Git的用途和強大至關重要。若你理解了Git的思想和基本工作原理,用起來就會知其所以然,游刃有余。這是Git系列的第一篇,主要會介紹Git的特點以及內(nèi)部數(shù)據(jù)結(jié)構設計,和完成一次完整提交流程的時候數(shù)據(jù)是如何變化的。

          Git有什么特點?

          • fast,scalable,distributed revision control system(快速,可擴展的分布式版本控制系統(tǒng))

            • 幾乎所有操作都是本地執(zhí)行
            • 每一個clone都是整個生命周期的完整副本
          • the stupid content tracker(只是一個內(nèi)容追蹤器)

            • Git追蹤的是內(nèi)容而不是文件
            • 如果兩個文件的內(nèi)容相同,無論是否在相同的目錄,Git在對象庫里只保存一份blob對象
          • Immutable(不可變性)

            • Git版本庫中存儲的數(shù)據(jù)對象均為不可變的,一旦創(chuàng)建數(shù)據(jù)對象并放入了數(shù)據(jù)庫中,它們便不可修改。這也意味著存儲在版本數(shù)據(jù)庫中的整個歷史也是不可變的。
          • Porcelain(高層命令)

            • init, add, commit, branch, merge.
          • Plumbing(底層命令)

            • hash-object, update-index, write-tree.

          蘋果開源代碼中驚現(xiàn)“wechat”,老外注釋的吐槽亮了!

          每一個Client端都可以是Server

          Git Version Database是什么?

          Git是一個內(nèi)容尋址文件系統(tǒng)。這意味著,Git的核心部分是一個簡單的鍵值對數(shù)據(jù)庫(key-value data store)。你可以向該數(shù)據(jù)庫插入任意類型的內(nèi)容,它會返回一個鍵值,通過該鍵值可以在任意時刻再次檢索該內(nèi)容。而這些數(shù)據(jù)全部是存儲在objects目錄里。key是一個hash,hash前兩個字符用于命名子目錄,余下的38個字符則用作文件名。如果了解tree樹的朋友應該會想明白之所以這樣處理是因為檢索優(yōu)化策略,提高文件系統(tǒng)效率(如果把太多的文件放入同一個目錄中,一些文件系統(tǒng)會變慢)。而這個hash的內(nèi)容(即hash對應的Value)有四種對象類型,commit(提交),tree(目錄樹),blob(塊),tag(標簽)。

          Git基本概念:

          • Content addressable filesystem(內(nèi)容尋址文件系統(tǒng))

          • Simple key-value data store(鍵值對數(shù)據(jù))

          • Key:SHA-1散列(hash,哈希)

            • Everything is hash
            • 這是一個由40個十六進制字符(0-9和a-f)組成字符串
          • Value:binary files

            • Commit:Actual git commits(提交)
            • Tree:Directoy(目錄樹)
            • Blob:file content(文件內(nèi)容)

          note:可以理解成Commit = Tree + Blob的snapshot

          什么是SHA-1:SHA-1(安全散列函數(shù)),是一種密碼散列函數(shù),美國國家安全局設計,并由美國國家標準技術研究所發(fā)布為聯(lián)邦數(shù)據(jù)處理標準。SHA-1可以生成一個被稱為消息摘要的160位(20字節(jié))散列值,散列值通常的呈現(xiàn)形式為40個十六進制數(shù)。用js來理解就是一個純函數(shù),輸入一定輸出也一定,相同的輸入一定有相同的輸出。不相同的輸入一定有不同的輸出(不考慮碰撞 ,比彗星撞擊地球的概率還低)。

          Git到底是如何工作呢?

          我們知道最簡單的git flow主要有三步:

          1. 在工作目錄中修改文件。
          2. 暫存文件,將文件的快照放入暫存區(qū)域。
          3. 提交更新,找到暫存區(qū)域的文件,將快照永久性存儲到Git倉庫目錄。

          對應高層命令是這樣的:

          $?git?init
          $?git?add?.
          $?git?commit

          在我們看這三個命令到底做了什么之前,先來了解一下幾個概念:

          中國男人の數(shù)據(jù)大賞

          • Working Directory:工作區(qū)(工作目錄)
          • Stageing Area (Index):暫存區(qū)
          • Repository:倉庫區(qū)(本地倉庫)

          Git init

          我們先用Git init來初始化一個項目,并查看項目的目錄結(jié)構。

          $?git?init?demo1?&&?cd?demo1
          $?tree?.git
          .git
          ├──?HEAD
          ├──?config
          ├──?description
          ├──?hooks
          │???├──?applypatch-msg.sample
          │???├──?commit-msg.sample
          │???├──?fsmonitor-watchman.sample
          │???├──?post-update.sample
          │???├──?pre-applypatch.sample
          │???├──?pre-commit.sample
          │???├──?pre-push.sample
          │???├──?pre-rebase.sample
          │???├──?pre-receive.sample
          │???├──?prepare-commit-msg.sample
          │???└──?update.sample
          ├──?info
          │???└──?exclude
          ├──?objects
          │???├──?info
          │???└──?pack
          └──?refs????
          ??????├──?heads????
          ??????└──?tags

          description文件僅供GitWeb程序使用。config文件包含項目特有的配置選項。info目錄包含一個全局性排除文件,用以放置那些不希望被記錄在.gitignore文件中的忽略模式。hooks目錄包含客戶端或服務端的鉤子腳本,這些我們暫時都無需關心。最重要的是:HEAD文件、(尚待創(chuàng)建的)index文件,和objects目錄、refs目錄。這些條目是Git的核心組成部分。objects目錄存儲所有數(shù)據(jù)內(nèi)容(hash);refs目錄存儲指向數(shù)據(jù)(分支)的提交對象的指針(commit hash);HEAD文件指示目前被檢出的分支(refs目錄內(nèi)的分支名);index 文件保存暫存區(qū)信息(git ls-files --stage命令查看當前暫存區(qū)信息)。

          下面我們就用底層命令來實現(xiàn)git init指令(另創(chuàng)建一個demo2目錄)。

          mkdir -p參數(shù)是能直接創(chuàng)建一個不存在的目錄下的子目錄:

          $?mkdir?-p?.git/refs/heads?.git/refs/tags?.git/objects
          $?echo?'ref:?refs/heads/master'?>?.git/HEAD

          可以看到已經(jīng)成功初始化了一個Git項目。

          git add

          $?echo?'hello?git'?>?index.txt
          $?git?add?index.txt

          執(zhí)行完這兩句指令后我們再來看.git文件夾發(fā)生了什么變化(為了顯示效果,簡化目錄結(jié)構,之后tree 都忽略hooks文件夾)

          .git
          ├──?HEAD
          ├──?config
          ├──?description
          ├──?index
          ├──?info
          │???└──?exclude
          ├──?objects
          │???├──?8d
          │???│???└──?0e41234f24b6da002d962a26c2495ea16a425f
          │???├──?info
          │???└──?pack
          └──?refs????
          ??????├──?heads????
          ??????└──?tags

          可以看到多了一個index文件,并且objects目錄里面多了一個8d的文件夾,里面有一個0e41開頭的文件、那這個8d0e4這個是什么呢?其實這個就是index.txt文件內(nèi)容的hash。還記得嘛,剛才寫入文件內(nèi)容是hello git,我們來手動輸出這個內(nèi)容的hash。

          $?echo?'hello?git'?|?git?hash-object?--stdin
          $?8d0e41234f24b6da002d962a26c2495ea16a425f

          可以通過cat-file命令從Git那里取回數(shù)據(jù)。為cat-file指定-p選項可指示該命令自動判斷內(nèi)容的類型,并為我們顯示格式友好的內(nèi)容:

          $?git?cat-file?-p?8d0e
          $?hello?git

          為cat-file指定-t選項可以查看文件的類型:

          $?git?cat-file?-t?8d0e
          $?blob

          git add做了兩件事情:

          • 文件內(nèi)容做一個hash存成blob object
          • 把index放入到Staging Area

          當為index.txt創(chuàng)建一個對象的時候,git并不關心index.txt的文件名,git 只關心文件里面的內(nèi)容。

          按照這個思路,我們用底層命令來實現(xiàn)一下git add指令。

          $?echo?'hello?git'?|?git?hash-object?-w?--stdin


          $?git?update-index?--add?--cacheinfo?100644?8d0e41234f24b6da002d962a26c2495ea16a425f?index.txt

          -w選項指示hash-object命令存儲數(shù)據(jù)對象;若不指定此選項,則該命令僅返回對應的鍵值。我們指定的文件模式為100644,表明這是一個普通文件。其他選擇包括:100755,表示一個可執(zhí)行文件;120000,表示一個符號鏈接。

          這回,不用為Linux命令發(fā)愁了吧

          因為并沒有去創(chuàng)建這個index.txt文件, 所以這邊提示已經(jīng)刪除了,執(zhí)行git checkout -- index.txt取出文件。

          可以看到已經(jīng)成功用底層命名實現(xiàn)了git add的功能。

          到這里,我們自然就會有個疑問了,那文件名怎么辦?

          Git是通過tree對象來跟蹤文件的路徑名的。當使用git add命令時,git會給添加的文件內(nèi)容創(chuàng)建一個blob對象,但是這個時候并不會創(chuàng)建tree對象。而只是更新索引,索引在.git/index中,它跟蹤文件的路徑名和相對應blob,每次執(zhí)行git add 、git rm 、 git mv 的時候,git都會更新索引,我們可以通過命令git ls-files --stage來查看當前的索引信息。

          $?git?ls-files?--s
          $?100644?8d0e41234f24b6da002d962a26c2495ea16a425f?0?index.txt

          git commit

          執(zhí)行git commit -m 'init-1'后,查看tree結(jié)構,發(fā)現(xiàn)object 多出了兩個文件:

          .git
          ├──?COMMIT_EDITMSG
          ├──?HEAD
          ├──?config
          ├──?description
          ├──?index
          ├──?info
          │???└──?exclude
          ├──?logs
          │???├──?HEAD
          │???└──?refs
          │???????└──?heads
          │???????????└──?master
          ├──?objects
          │???├──?75
          │???│???└──?0d7c0f7f998d3e2ce2d71ec801902f69bf6a39
          │???├──?88
          │???│???└──?bc066ebf3d864e34297f7051a0ded16e49813a
          │???├──?8d
          │???│???└──?0e41234f24b6da002d962a26c2495ea16a425f
          │???├──?info
          │???└──?pack
          └──?refs????
          ??????├──?heads????
          ??????│???└──?master
          ??????└──?tags
          $?git?log
          $?commit?750d7c0f7f998d3e2ce2d71ec801902f69bf6a39?(HEAD?->?master)

          查看這個commit 的文件類型,可以看到這是一個commit:

          $?git?cat-file?-t?750d
          $?commit

          $?git?cat-file?-p?750d
          $?tree?88bc066ebf3d864e34297f7051a0ded16e49813a

          但是多出來的88bc是什么呢,其實就是當前目錄的tree對象,所以Git是在commit的時候才創(chuàng)建tree對象的(其實是把索引轉(zhuǎn)化成tree對象)。

          $?git?cat-file?-t?88bc
          $?tree

          $?git?cat-file?-p?88bc
          $?100644?blob?8d0e41234f24b6da002d962a26c2495ea16a425f??index.txt

          這個時候再看HEAD:

          $?cat?.git/HEAD
          $?ref:?refs/heads/master

          繼續(xù)查看refs/heads/master:


          $?cat?.git/refs/heads/master
          $?750d7c0f7f998d3e2ce2d71ec801902f69bf6a39

          所以整個指向關系就是:HEAD里面的內(nèi)容是當前的ref,而當前ref的內(nèi)容是commit hash,commit對象內(nèi)容是tree hash,tree對象的內(nèi)容是文件夾/文件信息,而blob對象存儲著文件的具體內(nèi)容。這樣當完成一次提交的時候,整個狀態(tài)的對應關系也是確定的,所以說commit對象就是當前系統(tǒng)的snapshot。

          再來回顧下一次完整的提交流程:

          如何寫好注釋,讓同事贊不絕口?


          往期推薦

          蘋果開源代碼中驚現(xiàn)“wechat”,老外注釋的吐槽亮了!

          中國男人の數(shù)據(jù)大賞

          這回,不用為Linux命令發(fā)愁了吧

          如何寫好注釋,讓同事贊不絕口?

          IntelliJ IDEA 2020.3.2 正式發(fā)布

          ElasticSearch 面試 4 連炮,你頂?shù)米∶矗?/p>



          瀏覽 32
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐在线视频网站 | 国产精品欧美一区二区三区苍井空 | 一级黄色片。 | 噜噜噜影院 | 蜜臀久久99精品久久宅男 |