git internal for computer scientists

世有因果知因求果發表於2015-09-11

  http://eagain.net/articles/git-for-computer-scientists/

git object storage僅僅是一個DAG of objects, 有幾種型別的物件。他們都被壓縮儲存並以SHA-1 hash來標示。

blob:最簡單的物件,僅僅是一堆位元組的堆砌。常常它就是一個檔案,但是也可以是一個符號連結或者任何其他的東西。指向該blob的物件來指定這個blob的具體語義。

tree: 目錄由tree object來標示。他們引用相關的包含檔案內容的blobs以及指向其他子目錄的trees;

當一個node在DAG中指向另外一個node時,它將依賴於被指向的node: 也就是說這個node不可能在沒有被指向的node情況下存在。無任何其他node指向它的node將被git gc命令回收空間,或者類似檔案系統無檔名指向的inodes緊急救援命令一樣來恢復:git fsck --lost-found

commit:一個commit引用到一個反映在commit執行時檔案集狀態的tree物件。它也引用0..n個作為它的父輩的commits。多於一個父親commit則意味著這是一個merge commit,沒有parent的commit意味著是initial commit,當然也有可能有不止一個initial commit的情況:這通常意味著兩個獨立的專案merge到一起了。commit物件的內容是commit 訊息。

refs: Referrences,或者heads或者branches就像便籤筆記一樣提仔DAG裡面的一個node上。不像DAG的特性只允許向裡面新增node而不允許變更修改,這個便籤筆記是可以任意移動的。他們並不在歷史中記錄,他們並不直接在repo之間傳遞,他們就像bookmark一樣,告示著“i am working here".

git commit命令將向DAG中新增一個節點並且將當前branch的那個便籤向前移動到這個新的node。

HEAD ref有其特別之處,因為它實際上指向另外一個ref.它通常指向當前活動的branch.常見的refs實際上在heads/xxx的namespace,但是你往往忽略heads/這個part

remote refs: Remote referrence被用另外一種顏色來標示出來,它也是一種便籤。和normal refs的區別之處是different namespace,而事實是remote refs通常是由remote server來控制,git fetch操作時將會更新它們。

tag:一個tag不僅是DAG中的一個node並且也是一個便籤。一個tag指向一個commit,並且包含一個可選的訊息和一個GPG signature;

便籤本身僅僅提供了一個快速訪問tag的方法,一旦被丟失了,也可以通過git fsck --lost-found來找回;

在DAG中的節點可以從一個repo移動到另外一個repo,可以以更加高效的方式來儲存(packs),並且不再使用的nodes將會被垃圾清理。

但是最後,一個git repo總是一個DAG以及對應的便籤集合

History:

在有了上面關於git如何儲存版本歷史的知識後,我們如何能夠將類似merges的動作視覺化呢?最好用圖形來表達:

上面是最簡單的一個repo,我們clone了一個remote repo,而該repo只有一個commit在裡面;

上面我們已經fetch了remote,該fetch包含了一個其他人的一個commit,而該commit還沒有merge;

上圖展示了在執行git merge remotes/myserver/master之後的情形。由於merge本事是fast forward(也就是說,我們在local branch上海沒有任何新的變更,所以可以直接fast forward),在merge之後,僅僅的變更時:我們移動了我們的便籤到新的node上去,並且相應地變更了working directory的內容;

上圖中,我們本地有了一個commit並且做了一個git fetch動作,我們不斷本地有一個新的commit而且也有了一個新的remote commit,在這種情況下一個merge是必須的。

git merge remotes/myserver/master執行的結果如上圖。由於我們有新的local commits,這時就不再是fast forward,而必須通過在DAG中建立一個新的commit node來指示這個merge.這時注意merge commit有兩個parent commits.

在上圖中,我們做了幾個新的commits並且有了新的merge。git DAG記錄了任何已經發生過的action歷史;

上面的”縫合“模式有時看起來很讓人生煩。如果你還沒有publish your branch,或者have clearly communicated that others should not base their work on it,你還有另外一個選項:你可以rebase你的branch,而不是merge.這樣做的好處是:你的commit將由另外一個有不同parent的commit來替代,而你的branch將移動到那裡。(這個commit將以remote branch的最新commit為parent commit)

你的老的commit將仍然儲存在DAG中知道git的垃圾回收機制起作用刪除它。當然,如果你有額外的便籤指向老的commit,他們將永遠指向它,並且正因為有人指向那個不用的commit,這樣將使得這個舊的commit得以儲存而不被垃圾清理掉。

不要rebase branches that others have created new commits on top of.

在垃圾回收後的情況如上圖(或者僅僅或略unreachable commit),並且在rebased branch上建立一個新的commit

rebase也知道如何在一個commit中來rebase多個commits

相關文章