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

          昨晚看完 Linus 第一次提交的 Git 代碼后,我失眠了!

          共 5046字,需瀏覽 11分鐘

           ·

          2021-12-01 00:58

          學(xué)習(xí) Git 的內(nèi)部實現(xiàn),最好的辦法是看 Linus 最初的代碼提交,checkout 出 Git 項目的第一次提交節(jié)點,可以看到代碼庫中只有幾個文件:一個 README,一個構(gòu)建腳本 Makefile,剩下幾個 C 源文件。這次 commit 的備注寫的也非常特別:

          commit?e83c5163316f89bfbde7d9ab23ca2e25604af290
          Author:?Linus?Torvalds?
          Date:???Thu?Apr?7?15:13:13?2005?-0700

          ????Initial?revision?of?"git",?the?information?manager?from?hell

          為了把 Git 這條線學(xué)好,我勸你最好把前面兩個章節(jié)回顧一下:

          由于公眾號的文章發(fā)布后不能修改,也沒辦法加個統(tǒng)一的目錄作為索引頁,所以二哥就把《Java 程序員進階之路》的系列文章開源到了 GitHub(點擊閱讀原文可以直接跳轉(zhuǎn)):

          https://github.com/itwanger/toBeBetterJavaer

          每天看著 star 數(shù)(目前已有 741 個 star)的上漲我心里非常的開心,希望越來越多的 Java 愛好者能因為這個開源項目而受益,而越來越多人的 star,也會激勵我繼續(xù)更新下去~

          在 README 中,Linus 詳細描述了 Git 的設(shè)計思路??此茝?fù)雜的 Git 工作,在 Linus 的設(shè)計里,只有兩種對象抽象:

          • 對象數(shù)據(jù)庫(“object database”);
          • 當(dāng)前目錄緩存(“current directory cache”)。

          Git 的本質(zhì)就是一系列的文件對象集合,代碼文件是對象、文件目錄樹是對象、commit 也是對象。這些文件對象的名稱即內(nèi)容的 SHA1 值,SHA1 哈希算法的值為 40 位。Linus 將前二位作為文件夾、后 38 位作為文件名。大家可以在 .git 目錄里的 objects 里看到有很多兩位字母/數(shù)字名稱的目錄,里面存儲了很多 38 位 hash 值名稱的文件,這就是 Git 的所有信息。

          Linus 在設(shè)計對象的數(shù)據(jù)結(jié)構(gòu)時按照 <標(biāo)簽ascii碼表示>(blob/tree/commit) + <空格> + <長度ascii碼表示> + <\0> + <二進制數(shù)據(jù)內(nèi)容> 來定義,大家可以用 xxd 命令看下 objects 目錄里的對象文件(需 zlib 解壓),比如一個 tree 對象文件內(nèi)容如下:

          00000000:?7472?6565?2033?3700?3130?3036?3434?2068??tree?37.100644?h
          00000010:?656c?6c6f?2e74?7874?0027?0c61?1ee7?2c56??ello.txt.'.a..,V
          00000020:?7bc1?b2ab?ec4c?bc34?5bab?9f15?ba??????

          對象有三種:BLOB、TREE、CHANGESET。

          BLOB: 即二進制對象,這就是 Git 存儲的文件,Git 不像某些 VCS (如 SVN)那樣存儲變更 delta 信息,而是存儲文件在每一個版本的完全信息。

          比如先提交了一份 hello.c 進入了 Git 庫,會生成一個 BLOB 文件完整記錄 hello.c 的內(nèi)容;對 hello.c 修改后,再提交 commit,會再生成一個新的 BLOB 文件記錄修改后的 hello.c 全部內(nèi)容。

          Linus 在設(shè)計時,BLOB 中僅記錄文件的內(nèi)容,而不包含文件名、文件屬性等元數(shù)據(jù)信息,這些信息被記錄在第二種對象 TREE 里。

          TREE: 目錄樹對象。在 Linus 的設(shè)計里,TREE 對象就是一個時間切片中的目錄樹信息抽象,包含了文件名、文件屬性及 BLOB 對象的 SHA1 值信息,但沒有歷史信息。這樣的設(shè)計好處是可以快速比較兩個歷史記錄的 TREE 對象,不能讀取內(nèi)容,而根據(jù) SHA1 值顯示一致和差異的文件。

          另外,由于 TREE 上記錄文件名及屬性信息,對于修改文件屬性或修改文件名、移動目錄而不修改文件內(nèi)容的情況,可以復(fù)用 BLOB 對象,節(jié)省存儲資源。而 Git 在后來的開發(fā)演進中又優(yōu)化了 TREE 的設(shè)計,變成了某一時間點文件夾信息的抽象,TREE 包含其子目錄的 TREE 的對象信息(SHA1)。這樣,對于目錄結(jié)構(gòu)很復(fù)雜或?qū)蛹壿^深的 Git 庫 可以節(jié)約存儲資源。歷史信息被記錄在第三種對象 CHANGESET 里。

          CHANGESET:即 Commit 對象。一個 CHANGESET 對象中記錄了該次提交的 TREE 對象信息(SHA1),以及提交者(committer)、提交備注(commit message)等信息。

          跟其他 SCM(軟件配置管理)工具所不同的是,Git 的 CHANGESET 對象不記錄文件重命名和屬性修改操作,也不會記錄文件修改的 Delta 信息等,CHANGESET 中會記錄父節(jié)點 CHANGESET 對象的 SHA1 值,通過比較本節(jié)點和父節(jié)點的 TREE 信息來獲取差異。

          Linus 在設(shè)計 CHANGESET 父節(jié)點時允許一個節(jié)點最多有 16 個父節(jié)點,雖然超過兩個父節(jié)點的合并是很奇怪的事情,但實際上,Git 是支持超過兩個分支的多頭合并的。

          Linus 在三種對象的設(shè)計解釋后著重闡述了可信(TRUST):雖然 Git 在設(shè)計上沒有涉及可信的范疇,但 Git 作為配置管理工具是可以做到可信的。原因是所有的對象都以 SHA1 編碼(Google 實現(xiàn) SHA1 碰撞攻擊是后話,且 Git 社區(qū)也準(zhǔn)備使用更高可靠性的 SHA256 編碼來代替),而簽入對象的過程可信靠簽名工具保證,如 GPG 工具等。

          理解了 Git 的三種基本對象,那么對于 Linus 對于 Git 初始設(shè)計的“對象數(shù)據(jù)庫”和“當(dāng)前目錄緩存”這兩層抽象就很好理解了。加上原本的工作目錄,Git 有三層抽象,如下圖示:一個是當(dāng)前工作區(qū)(Working Directory),也就是我們查看/編寫代碼的地方,一個是 Git 倉庫(Repository),即 Linus 說的對象數(shù)據(jù)庫,我們在 Git 倉看到的 .git 文件夾中存儲的內(nèi)容,Linus 在第一版設(shè)計時命名為 .dircache,在這兩個存儲抽象中還有一層中間的緩存區(qū)(Staging Area),即 .git/index 里存儲的信息,我們在執(zhí)行 git add 命令時,便是將當(dāng)前修改加入到了緩存區(qū)。

          Linus 解釋了“當(dāng)前目錄緩存”的設(shè)計,該緩存就是一個二進制文件,內(nèi)容結(jié)構(gòu)很像 TREE 對象,與 TREE 對象不同的是 index 不會再包含嵌套 index 對象,即當(dāng)前修改目錄樹內(nèi)容都在一個 index 文件里。這樣設(shè)計有兩個好處:

          • 能夠快速的復(fù)原緩存的完整內(nèi)容,即使不小心把當(dāng)前工作區(qū)的文件刪除了,也可以從緩存中恢復(fù)所有文件;


          • 能夠快速找出緩存中和當(dāng)前工作區(qū)內(nèi)容不一致的文件。


          Linus 在 Git 的第一次代碼提交里便完成了 Git 的最基礎(chǔ)功能,并可以編譯使用。代碼極為簡潔,加上 Makefile 一共只有 848 行。感興趣的話可以通過上一段所述方法 checkout Git 最早的 commit 上手編譯玩玩,只要有 Linux 環(huán)境即可。

          因為依賴庫版本的問題,需要對原始 Makefile 腳本做些小修改。Git 第一個版本依賴 openssl 和 zlib 兩個庫,需要手工安裝這兩個開發(fā)庫。在 ubuntu 上執(zhí)行:sudo apt install libssl-dev libz-dev ;然后修改 makefile 在 LIBS= -lssl 行 中的 -lssl 改成 -lcrypto 并增加 -lz ;最后執(zhí)行 make,忽略編譯告警,會發(fā)現(xiàn)編出了7個可執(zhí)行程序文件:init-db, update-cache, write-tree, commit-tree, cat-file, show-diff 和 read-tree。

          下面分別簡要介紹下這些可執(zhí)行程序的實現(xiàn):

          • init-db: 初始化一個 git 本地倉庫,這也就是我們現(xiàn)在每次初始化建立 git 庫式敲擊的 git init 命令。只不過一開始 Linus 建立的倉庫及 cache 文件夾名稱叫 .dircache,而不是我們現(xiàn)在所熟知的 .git 文件夾。
          • update-cache: 輸入文件路徑,將該文件(或多個文件)加入緩沖區(qū)中。具體實現(xiàn)是:校驗路徑合法性,然后將文件計算 SHA1值,將文件內(nèi)容加上 blob 頭信息進行 zlib 壓縮后寫入到對象數(shù)據(jù)庫(.dircache/objects)中;最后將文件路徑、文件屬性及 blob sha1 值更新到 .dircache/index 緩存文件中。
          • write-tree: 將緩存的目錄樹信息生成 TREE 對象,并寫入對象數(shù)據(jù)庫中。TREE 對象的數(shù)據(jù)結(jié)構(gòu)為:‘tree ‘ + 長度 + \0 + 文件樹列表。文件樹列表中按照 文件屬性 + 文件名 + \0 + SHA1 值結(jié)構(gòu)存儲。寫入對象成功后,返回該 TREE 對象的 SHA1 值。
          • commit-tree: 將 TREE 對象信息生成 commit 節(jié)點對象并提交到版本歷史中。具體實現(xiàn)是輸入要提交的 TREE 對象 SHA1 值,并選擇輸入父 commit 節(jié)點(最多 16個),commit 對象信息中包含 TREE、父節(jié)點、committer 及作者的 name、email及日期信息,最后寫入新的 commit 節(jié)點對象文件,并返回 commit 節(jié)點的 SHA1 值。
          • cat-file: 由于所有的對象文件都經(jīng)過 zlib 壓縮,因此想要查看文件內(nèi)容的話需要使用這個工具來解壓生成臨時文件,以便查看對象文件的內(nèi)容。
          • show-diff: 快速比較當(dāng)前緩存與當(dāng)前工作區(qū)的差異,因為文件的屬性信息(包括修改時間、長度等)也保存在緩存的數(shù)據(jù)結(jié)構(gòu)中,因此可以快速比較文件是否有修改,并展示差異部分。
          • read-tree: 根據(jù)輸入的 TREE 對象 SHA1 值輸出打印 TREE 的內(nèi)容信息。

          這就是第一個可用版本的 Git 的全部七個子程序,可能用過 Git 的小伙伴會說:這怎么跟我常用的 Git 命令不一樣呢?Git add, git commit 呢?是的,在最初的 Git 設(shè)計中是沒有我們這些平常所使用的 git 命令的。

          在 Git 的設(shè)計中,有兩種命令:分別是底層命令(Plumbing commands)和高層命令(Porcelain commands)。一開始,Linus 就設(shè)計了這些給開源社區(qū)黑客使用的符合 Unix KISS 原則的命令,因為黑客們本身就是動手高手,水管壞了就擼起袖子去修理,因此這些命令被稱為 plumbing commands。

          后來接手 Git 的 Junio Hamano 覺得這些命令對于普通用戶不太友好,因此在此之上,封裝了更易于使用、接口更精美的高層命令,也就是我們今天每天使用的 git add, git commit 之類。Git add 就是封裝了 update-cache 命令,而 git commit 就是封裝了 write-tree, commit-tree 命令。


          這是《Java 程序員進階之路》專欄的第 74 篇(記得點擊「閱讀原文」鏈接去點個 star 哦)。該專欄風(fēng)趣幽默、通俗易懂,對 Java 愛好者極度友好和舒適??,內(nèi)容包括但不限于 Java 基礎(chǔ)、Java 集合框架、Java IO、Java 并發(fā)編程、Java 虛擬機、Java 企業(yè)級開發(fā)(Maven、Git、SSM、Spring Boot)等核心知識點。

          點擊上方名片,發(fā)送消息「03」 就可以獲取最新版《Java 程序員進階之路》PDF 版了,讓我們一起成為更好的 Java 工程師吧,沖!

          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  大香蕉大香蕉 | 91在线无码精品秘 入口楼乃 | 一本色道久久综合无码人妻88 | 狠狠人妻久久 | 一呦二呦三呦精品网站 |