來源:https://zhuanlan.zhihu.com/p/53750883
哈嘍,各位新來的小夥伴們,大家好!由於公眾號做了改版,為了保證公眾號的資源能準時推送到你手裡,大家記得將咱們的公眾號 加星標置頂 ,在此真誠的表示感謝~
正文如下:
越了解事物的本質就越接近真相。我發現學習Git內部是如何工作的以及Git的內部數據結構這部分內容,對於理解Git的用途和強大至關重要。若你理解了Git的思想和基本工作原理,用起來就會知其所以然,遊刃有餘。這是Git系列的第一篇,主要會介紹Git的特點以及內部數據結構設計,和完成一次完整提交流程的時候數據是如何變化的。幾乎所有操作都是本地執行
每一個clone都是整個生命周期的完整副本
init, add, commit, branch, merge.
# Git Version Database是什麼?Git是一個內容尋址文件系統。這意味著,Git的核心部分是一個簡單的鍵值對資料庫(key-value data store)。你可以向該資料庫插入任意類型的內容,它會返回一個鍵值,通過該鍵值可以在任意時刻再次檢索該內容。而這些數據全部是存儲在objects目錄裡。key是一個hash,hash前兩個字符用於命名子目錄,餘下的38個字符則用作文件名。如果了解tree樹的朋友應該會想明白之所以這樣處理是因為檢索優化策略,提高文件系統效率(如果把太多的文件放入同一個目錄中,一些文件系統會變慢)。而這個hash的內容(即hash對應的Value)有四種對象類型,commit(提交),tree(目錄樹),blob(塊),tag(標籤)。note:可以理解成Commit = Tree + Blob的snapshot什麼是SHA-1:SHA-1(安全散列函數),是一種密碼散列函數,美國國家安全局設計,並由美國國家標準技術研究所發布為聯邦數據處理標準。SHA-1可以生成一個被稱為消息摘要的160位(20位元組)散列值,散列值通常的呈現形式為40個十六進位數。用js來理解就是一個純函數,輸入一定輸出也一定,相同的輸入一定有相同的輸出。不相同的輸入一定有不同的輸出(不考慮碰撞 ,比彗星撞擊地球的概率還低)。在工作目錄中修改文件。
暫存文件,將文件的快照放入暫存區域。
提交更新,找到暫存區域的文件,將快照永久性存儲到Git倉庫目錄。
$ git init$ git add .$ git commit在我們看這三個命令到底做了什麼之前,先來了解一下幾個概念:我們先用Git init來初始化一個項目,並查看項目的目錄結構。$ 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 └── tagsdescription文件僅供GitWeb程序使用。config文件包含項目特有的配置選項。info目錄包含一個全局性排除文件,用以放置那些不希望被記錄在.gitignore文件中的忽略模式。hooks目錄包含客戶端或服務端的鉤子腳本,這些我們暫時都無需關心。最重要的是:HEAD文件、(尚待創建的)index文件,和objects目錄、refs目錄。這些條目是Git的核心組成部分。objects目錄存儲所有數據內容(hash);refs目錄存儲指向數據(分支)的提交對象的指針(commit hash);HEAD文件指示目前被檢出的分支(refs目錄內的分支名);index 文件保存暫存區信息(git ls-files --stage命令查看當前暫存區信息)。下面我們就用底層命令來實現git init指令(另創建一個demo2目錄)。mkdir -p參數是能直接創建一個不存在的目錄下的子目錄:$ mkdir -p .git/refs/heads .git/refs/tags .git/objects$ echo 'ref: refs/heads/master' > .git/HEAD$ echo 'hello git' > index.txt$ git add index.txt執行完這兩句指令後我們再來看.git文件夾發生了什麼變化(為了顯示效果,簡化目錄結構,之後tree 都忽略hooks文件夾).git├── HEAD├── config├── description├── index├── info│ └── exclude├── objects│ ├── 8d│ │ └── 0e41234f24b6da002d962a26c2495ea16a425f│ ├── info│ └── pack└── refs ├── heads └── tags可以看到多了一個index文件,並且objects目錄裡面多了一個8d的文件夾,裡面有一個0e41開頭的文件、那這個8d0e4這個是什麼呢?其實這個就是index.txt文件內容的hash。還記得嘛,剛才寫入文件內容是hello git,我們來手動輸出這個內容的hash。$ echo 'hello git' | git hash-object --stdin$ 8d0e41234f24b6da002d962a26c2495ea16a425f可以通過cat-file命令從Git那裡取回數據。為cat-file指定-p選項可指示該命令自動判斷內容的類型,並為我們顯示格式友好的內容:$ git cat-file -p 8d0e$ hello git為cat-file指定-t選項可以查看文件的類型:$ git cat-file -t 8d0e$ blob文件內容做一個hash存成blob object
把index放入到Staging Area
當為index.txt創建一個對象的時候,git並不關心index.txt的文件名,git 只關心文件裡面的內容。按照這個思路,我們用底層命令來實現一下git add指令。$ echo 'hello git' | git hash-object -w --stdin
$ git update-index --add --cacheinfo 100644 8d0e41234f24b6da002d962a26c2495ea16a425f index.txt-w選項指示hash-object命令存儲數據對象;若不指定此選項,則該命令僅返回對我們指定的文件模式為100644,表明這是一個普通文件。其他選擇包括:100755,表示一個可執行文件;120000,表示一個符號連結。因為並沒有去創建這個index.txt文件, 所以這邊提示已經刪除了,執行git checkout -- index.txt取出文件。可以看到已經成功用底層命名實現了git add的功能。Git是通過tree對象來跟蹤文件的路徑名的。當使用git add命令時,git會給添加的文件內容創建一個blob對象,但是這個時候並不會創建tree對象。而只是更新索引,索引在.git/index中,它跟蹤文件的路徑名和相對應blob,每次執行git add 、git rm 、 git mv 的時候,git都會更新索引,我們可以通過命令git ls-files --stage來查看當前的索引信息。
$ git ls-files --s$ 100644 8d0e41234f24b6da002d962a26c2495ea16a425f 0 index.txt執行git commit -m 'init-1'後,查看tree結構,發現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的時候才創建tree對象的(其實是把索引轉化成tree對象)。$ git cat-file -t 88bc$ tree
$ git cat-file -p 88bc$ 100644 blob 8d0e41234f24b6da002d962a26c2495ea16a425f index.txt$ cat .git/HEAD$ ref: refs/heads/master$ cat .git/refs/heads/master$ 750d7c0f7f998d3e2ce2d71ec801902f69bf6a39所以整個指向關係就是:HEAD裡面的內容是當前的ref,而當前ref的內容是commit hash,commit對象內容是tree hash,tree對象的內容是文件夾/文件信息,而blob對象存儲著文件的具體內容。這樣當完成一次提交的時候,整個狀態的對應關係也是確定的,所以說commit對象就是當前系統的snapshot。熱門推薦: