給那些不怕類似“有向無環圖”字眼的人簡單介紹git

bruce-accumulate發表於2013-11-21

給那些不怕類似“有向無環圖”這樣字眼的人簡單介紹一下git的內部實現。

儲存:

簡單的說,git的物件儲存就是一堆物件的有向無環圖,一堆被分成幾種型別的物件。這些物件都壓縮儲存,並且以其自身的SHA-1雜湊值作為唯一標示。(順便說下,這個雜湊值不是物件所表示檔案內容的SHA-1,而是物件本身在git中的表示。原文:that,incidently,isn’t the SHA-1 of the contents of the file they represent,but of the representation in git).

git-storage.1

blob:最簡單的物件,一些位元組而已。可能是一個檔案,也可以是一個符號連結或者類似的其他東西。指向blob的物件定義了git中的語義(看到後面大家會理解)。

git-storage.2

tree:目錄被表示成tree物件。他們指向了包含檔案內容的blobs(檔名,訪問許可權等唄儲存在tree物件裡),和其他tree物件(代表了子目錄)。

 

再一個有向無環圖裡,如果一個節點指向另外一個節點,那麼被指向節點就依賴指向它的那個節點:沒有指向節點,被指向節點就不能存在。沒有被指向的節點會被當成垃圾,或被git gc回收,或使用git lost-found復原(這點和檔案系統把沒有檔名指向的inode放到lost+found目錄供以後恢復類似)。

git-storage.3

commit:一個commit指向一個tree物件,這個tree物件代表了commit發生當時的檔案狀態。commit可以指向0..n個其他的commit作為他的父親節點。如果commit有多個父親節點,那意味著這個commit是一個merge;沒有父親節點意味著該commit是一個initial commit。有趣的是git允許有多個initial commit,這意味著多個project的merge。commit物件的主體是commit資訊(message)。

git-storage.4

refs:References,heads或者branches很像是貼在有向無環圖上節點便條。有向無環圖只能新增節點,已有的節點也不會變動。這些“便條”卻可以隨意移動。他們不會儲存到歷史中,它們只是在不同的倉庫中移動。它們又像是書籤,表明了“我在這裡”。(譯者注:這樣我想起了程式排程的current)

git commit命令會在有向無環圖中新增一個節點。之後會移動“便條”到新加的節點上。

HEAD這個ref有點特殊:它指向其他的ref,是一個指向當前活躍分支的指標。一般的ref都在類似heads/xxx這樣的名字空間下。但是一般你可以忽略heads/部分。

git-storage.5

remote refs:remote References可以理解成另外一個一種顏色的便條。和一般ref不同的是他們所在的名字空間。remote refs由遠端的伺服器控制,可以通過git fetch命令更新。

git-storage.6

tag:tag既是有向無環圖中的一個節點,也是一種便條(當然是另外一種顏色)。tag指向一個commit,包含可選的描述資訊和GPG簽名。通過tag的便條可以快速訪問一個tag,如果丟失,可以使用git lost-found命令根據tag的GPG簽名進行恢復。

 

歷史

好了,有了上面關於git歷史版本儲存的知識,我們來看看git如何進行類似merge這樣的操作,看看git和其他利用分支線性變化,管理歷史的工具的區別。

git-history.1

這是一個最簡單的倉庫。我們使用clone克隆了一個遠端的倉庫,並且只有一個commit。

git-history.2

我們使用fetch,接受了來自遠端倉庫的一個commit,但還沒有merge。

git-history.3

我們使用git merge remotes/MYSERVER/master命令之後。這裡的merge是一個快進(fast-forward)merge(因為我們本地沒有新的提交),唯一要做的就是移動“便條”master,相應的改變我們工作目錄中的檔案。

git-history.4

這裡我們在本地進行了一次提交,之後進行了fetch操作。我們得到了一個本地commit一個遠端commit。顯然這裡需要一次merge。

git-history.5

這是git merge remotes/MYSERVER/master之後的結果。因為我們有本地的提交,所以這次的merge不是快進式的。一個新的commit節點e被生成,它有兩個父親節點。

git-history.6

這是一些commit以及一次merge之後兩個分支的情況。看這種”縫合”式的merge,可以看到git儲存了所有“行為”(commit或者merge)的歷史。

git-history.7

這種“縫合”(原文stitching)模式有時讀起來比較冗長。如果你還沒有釋出你的分支,或者確定別人不會在你的分支之上進行開發,那麼你可以選擇rebase你的分支。這時,merge操作不會被執行,你的commit會被另外一個commit代替。這個commit和你的commit有不同的parent,如上圖。你的分支也會被移到新的commit處。

你老的那個commit仍然會在有向無環圖中,直到垃圾收集器回收。(Ignore them for now,but just know there’s a way out if you screwed up totally).注意如果你還有一個便條指向那個老的commit,它仍會繼續指向這個commit,這會讓這個commit永遠存活下去。很令人困惑是吧。

不要rebase 那些別人提交過commit的branch。恢復是有可能的。但是恢復的過程可能會極其痛苦。

git-history.8

這是經過垃圾收集之後,並且在rebased的分支上新加了一個commit的情景。

git-history.9

看rebase命令自己還知道如果處理多次commit的情況。(譯者注:這裡遠端fetch之後多了f和g兩個commit,使用rebase命令,d2變成d3並且指向了g,h變成h2,指向d3)

這就是針對那些不怕電腦科學的人的git簡介,希望有用~

相關文章