Git工作原理
原文 http://deweixu.me/2016/11/05/how-git-works/
主題 Git
最近在使用git時遇到了一些問題,用google搜索到一篇很好的文章,簡單的翻譯了一下。(原文地址: https://codewords.recurse.com/issues/two/git-from-the-inside-out )
這篇文章解釋了 git
的工作原理,它會使你更深入的理解 git
,更好的使用它來控制項目的版本。
本文重點介紹了支持Git的圖形結構,以及該圖形的屬性指示Git行爲的方式。從基礎開始,同時有實例講解,根據實例建立一個更真實的模型,讓你更好地理解 git
做了什麼。
創建項目
~ $ mkdir alpha ~ $ cd alpha
項目目錄是 alpha
~/alpha $ mkdir data~/alpha $ printf 'a' > data/letter.txt
到目錄 alpha
下創建了一個名爲 data
的目錄,在裏面創建了一個名爲 letter.txt
的文件,其中的內容是一個字符 a
, alpha
目錄結構如下:
alpha└── data └── letter.txt
初始化倉庫
~/alpha $ git init Initialized empty Git repository
git init
使當前目錄變成了 Git
倉庫,爲此,它創建了一個 .git
目錄並向其中寫入了一些文件。這些文件定義了關於Git配置和項目歷史的一切,它們只是普通文件。 用戶可以使用文本編輯器或shell來讀取和編輯它們。 這就是說,用戶可以像他們的項目文件一樣輕鬆地閱讀和編輯他們項目的歷史。
現在 alpha
目錄的結構就像下面這樣
alpha├── data| └── letter.txt └── .git ├── objects etc...
.git
目錄及其內容歸 Git
系統所有,所有其他的文件統稱爲工作副本,歸用戶所有。
添加文件
~/alpha $ git add data/letter.txt
運行上面的命令,有兩個效果。
首先,它在 .git/objects/
目錄中創建了一個新的 blob
文件。
這個 blob
文件包含 data/letter.txt
的壓縮內容。 它的名稱通過文件內容的 Hash
(應該是用的 sha1
)得到。取一段文本的 Hash
值意味着運行一個程序,將其內容變成一塊較小的文本,這塊文本是原始內容的唯一標識。例如, Git
將 aes
轉換爲 2e65efe2a145dda7ee51d1741299f848e5bf752e
,前兩個字符用作對象數據庫中的目錄的名稱: .git/objects/2e/
。 散列的其餘部分用作保存所添加文件的內容的 blob
文件的名稱:
.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e
。
git add
將文件添加到 Git
並將其內容保存到 objects
目錄中。 如果用戶從工作副本中刪除 data/letter.text
,它的內容在 Git
中仍然是安全的。
其次, git add
將文件添加到 索引
。 索引
是一個列表,其中包含 Git
已被告知要跟蹤的每個文件。 它作爲一個文件存儲在 .git/index
。 文件的每一行將被跟蹤的文件映射到其內容的 哈希
。 這是運行 git add
命令後的索引:
data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e
創建一個包含內容 1234
的文件 data/number.txt
~/alpha $ printf '1234' > data/number.txt
目錄結構變成了下面這樣:
alpha└── data └── letter.txt └── number.txt
添加文件到 Git
~/alpha $ git add data
git add
命令創建一個包含 data/number.txt
內容的 blob
對象。 它爲指向 blob
的 data/number.txt
添加一個索引條目。 這是 git add
命令第二次運行後的索引:
data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752edata/number.txt 274c0052dd5408f8ae2bc8440029ff67d79bc5c3
只有數據目錄中的文件被列在索引中,雖然用戶運行了 git add data
。 數據目錄data
不單獨列出。
~/alpha $ printf '1' > data/number.txt ~/alpha $ git add data
當最初創建 data/number.txt
時,想要輸入內容 1
,而不是 1234
.他們進行更正並將文件再次添加到索引。 此命令將使用新內容創建一個新的 blob
。 並且它更新 data/number.txt
的索引條目以指向新的 blob
。
git commit
~/alpha $ git commit -m 'a1' [master (root-commit) 774b54a] a
進行 a1
提交, Git
打印了這次提交的相關信息。
commit
命令有三個步驟。 創建一個樹形圖來表示正在提交的項目版本的內容。 創建一個提交對象。 將當前分支指向新的提交對象。
創建樹形圖
Git
通過從索引創建樹圖來記錄項目的當前狀態。 此樹圖記錄項目中每個文件的位置和內容。
該圖由兩種類型的對象組成: blob
和 樹
。
Blob
是通過 git add
存儲的。 它們表示文件的內容。
在 commit
時存儲樹。 樹表示工作副本中的目錄。
下面是記錄新提交的 data
目錄的內容的樹對象:
100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt
第一行記錄展示了 data/letter.txt
文件的信息。 第一部分是文件的權限。 第二部表示此條目的內容由 blob
而不是 樹
表示。 第三部分描述了 blob
的 Hash
。 第四部分描述文件的名稱。第二行當然就是文件 data/number.txt
文件的信息。
下面是 alpha
的樹對象:
040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data
alpha
樹對象只包含了一個指向 data
樹指針。(譯著:如果 alpha
目錄下還有一個文件, alpha
樹對象就還會多一行,就是指向多出文件的 blob
對象)
在上面的圖中, root
樹指向 data
樹。 data
樹指向 data/letter.txt
和 data/number.txt
的 blob
。
創建一個提交對象
git commit
在創建樹圖後創建一個 提交對象
。 提交對象
只是 .git/objects/
中的另一個文本文件:
tree ffe298c3ce8bb07326f888907996eaa48d266db4 author Mary Rose Cook <[email protected]> 1424798436 -0500committer Mary Rose Cook <[email protected]> 1424798436 -0500a1
第一行指向樹圖。 Hash
是表示工作副本的根的樹對象。 也就是 alpha
目錄。 最後一行是提交消息。
將當前分支指向新的提交
最後,commit命令將當前分支指向新的提交對象。哪個是當前分支? .git/HEAD
文件記錄了當前分支:
ref: refs/heads/master
這說明 HEAD
指向 master
, master
是主分支。
HEAD
和 master
都是 refs
。 ref
是 Git
用來標識特定提交的標籤。
表示 master
引用的文件不存在,因爲這是對倉庫的第一次提交。 Git
在 .git/refs/heads/master
下創建文件,並將其內容設置爲提交對象的哈希值:
74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd
(如果你在閱讀時輸入這些 Git
命令,你的 a1
提交的 哈希值
將不同於我的 哈希值
。 內容對象(如blob和樹)總是散列爲相同的值。 提交不會,因爲它們包括創建者的日期和名稱。)
添加 HEAD
和 master
到樹圖:
HEAD
指向 master
,就像提交之前一樣。 但 maste
r現在存在並指向新的提交對象a1
。
再一次commit
下面是 a1
提交後的 Git
結構圖。 包含工作副本和索引。
工作副本,索引和 a1
提交都具有與 data/letter.txt
和 data/number.txt
相同的內容。 索引和 HEAD
提交都使用 Hash
來引用 blob
對象,但是工作副本內容作爲文本存儲在不同的地方。
~/alpha $ printf '2' > data/number.txt
將 data/number.txt
的內容設置爲 2
.這會更新工作副本,但 索引
和 HEAD
不變。
~/alpha $ git add data/number.txt
將文件添加到 Git
。 這會向 objects
目錄添加一個包含 2
的 blob
。 它指向新blob
的 data/number.txt
的索引條目。
~/alpha $ git commit -m 'a2' [master f0af7e6] a2
提交的步驟與之前相同。
首先,創建一個新的樹形圖來表示索引的內容。
data/number.txt
的索引條目已更改。 舊的數據樹不再反映 data
目錄的索引狀態。 必須創建一個新的 data
樹對象:
100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt
新數據樹與舊數據樹的哈希值不同。 必須創建一個新的根樹以記錄此 Hash
值:
040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data
其次,創建一個新的提交對象。
tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556 parent 774b54a193d6cfdd081e581a007d2e11f784b9fe author Mary Rose Cook <[email protected]> 1424813101 -0500committer Mary Rose Cook <[email protected]> 1424813101 -0500a2
提交對象的第一行指向新的根樹對象。 第二行指向 a1
:新提交的父級。要找到父提交,要跟着 HEAD
和 master
來掌握並發現 a1
的提交哈希。
最後,master分支文件的內容被設置爲新提交的 hash
值。
內容存儲爲對象樹。 這意味着只有diffs存儲在對象數據庫中。 看看上面的圖表。 a2 commit
重用了在 a1
提交之前創建的 blob
。 類似地,如果提交中整個沒有變,則其樹以及其下的所有 blob
和樹可以被重用。 一般來說,提交的內容更改很少。 這意味着 Git
可以在小的空間中存儲大的提交歷史。
每個提交都有一個父級。 這意味着存儲庫可以存儲項目的歷史記錄。
refs
是提交歷史的一部分或另一部分的入口點。 這意味着提交可以被賦予有意義的名稱。 用戶將他們的工作組織到對他們的項目有意義的譜系中,具體的參考如 fix-for-bug-376
。 Git
使用符號引用,如 HEAD
, MERGE_HEAD
和 FETCH_HEAD
來支持操作提交歷史記錄的命令。
objects
目錄中的節點是不可變的。 這意味着內容被編輯,而不是被刪除。 每一次添加的內容和每次提交的對象都是在目錄中.
refs
是可變的。 因此, ref
的含義可以改變。 master
指向的提交可能是當前項目的最佳版本,但是,很快,它將被更新的更好的提交取代。
Check out a commit
~/alpha $ git checkout 37888c2 You are in 'detached HEAD' state...
使用 Hash
值 checkout``a2
的提交(如果你在運行這些git命令,這裏的 hash
值要換成你自己的,使用 git log
查看)
checkout
有四個步驟:
獲取
a2
提交,並獲取指向它的樹圖它將樹形圖中的文件條目寫入工作副本。 這將導致沒有更改。 工作副本已經具有被寫入其中的樹圖的內容,因爲
HEAD
已經通過master
指向a2
提交。將樹圖中的文件條目寫入索引。 這也導致沒有變化。 索引已經具有
a2
提交的內容。HEAD
的內容設置爲a2
提交的哈希:
f0af7e62679e144bb28c627ee3e8f7bdb235eee9
將 HEAD
的內容設置爲 Hash
值會使存儲庫處於分離的 HEAD
狀態。 注意在下面的圖表中, HEAD
直接指向 a2
提交,而不是指向 master
。
~/alpha $ printf '3' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'a3' [detached HEAD 3645a0e] a3
將 data/number.txt
的內容設置爲 3
,並提交更改。 Git
去 HEAD
得到 a3
提交的父級。 而不是找到一個分支ref,它找到並返回 a2
提交的哈希。
Git
更新 HEAD
直接指向新的 a3
提交的哈希。 存儲庫仍處於分離的 HEAD
狀態。 它不在一個分支上,因爲沒有提交指向 a3
或其一個後代。 這意味着它很容易丟失。
創建分支
~/alpha $ git branch deputy
創建一個新分支 deputy
。 這只是在 .git/refs/heads/deputy
創建一個新文件,其中包含 HEAD
指向的哈希, 也就是 a3
提交的哈希。
分支只是refs, refs只是文件。 這意味着Git分支是輕量級的。
deputy
分支的創建將新的 a3
提交安全地放置在分支上。 HEAD
仍然分離,因爲它仍然直接指向一個提交。
切換分支
~/alpha $ git checkout master Switched to branch 'master'
切換到了 master
分支
獲取
a2
提交,並將master
指向獲取提交點的樹圖。樹形圖中的文件條目替換工作副本的文件。 這將使
data/number.txt
的內容設置爲2。將樹圖中的文件條目寫入索引。 這會將
data/number.txt
的條目更新爲2個blob
的散列。改變
HEAD
的值
ref: refs/heads/master
切換到與工作副本不兼容(有改變)的分支
~/alpha $ printf '789' > data/number.txt ~/alpha $ git checkout deputy Your changes to these files would be overwritten by checkout: data/number.txt Commit your changes or stash them before you switch branches.
將 data/number.txt
的內容設置爲 789
, 當 checkout
到 deputy
時, Git
報了一個錯誤。
HEAD
指向 master
, master
指向 a2
,其中 data/number.txt
的內容是 2
。 deputy
指向 a3
,其中 data/number.txt
的內容是 3
。 data/number.txt
在工作副本的內容爲 789
,所有這些版本都不同,差異必須解決。
Git
可以使用要切換分支中提交的版本替換掉工作副本中的版本,這樣可以避免數據丟失。
Git
可以合併工作副本的版本和要切換分支中的版本,但這很複雜。
所以 Git
報了一個錯誤,不能切換分支。
~/alpha $ printf '2' > data/number.txt ~/alpha $ git checkout deputy Switched to branch 'deputy'
把 data/number.txt
的內容變回 2
時,便切換成功了。
合併祖先
~/alpha $ git merge master Already up-to-date.
將主分支 master
和併到 deputy
分支。和並兩個分支實際上是合併兩個提交。第一個提交指向 deputy
,它是接收者。第二個提交指向 master
,它是提交者。可以理解爲把 master
提交到 deputy
。對於這個合併, git
什麼也沒有做,因爲兩個分支的內容是一樣的。
圖中的一系列的提交可以看成是對存儲庫的一系列更改。這也就意味着,在合併中,如果提交者( master
)是接收者( deputy
)的祖先, git
將什麼也不做,因爲這些變化已經存在。
合併後代
~/alpha $ git checkout master Switched to branch 'master'
切換到分支 master
~/alpha $ git merge deputy Fast-forward
合併 deputy
到 master
。 Git
發現接受者的 a2
提是提交者 a3
的祖先,它可以做快進合併。
獲得提交者的提交 a3
並提供指向它的樹圖,將樹圖中的文件條目寫入工作副本和索引。 快進
是指 master
指向 a3
。
在合併中,如果提交者( deputy
分支上的 a3
提價)是接收者( master
上的 a2
提價)的後代,則歷史記錄不改變。 已經有一系列提交描述了要做出的改變( 接收者和提交者之間的提交序列
)。 雖然Git歷史沒有改變,Git圖確實改變。 HEAD指向的具體引用被更新爲指向提交者( master
指向 a3
)。
合併來自兩個不同譜系的分支
~/alpha $ printf '4' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'a4' [master 7b7bd9a] a4
把 number.txt
的內容設置爲 4
,並提交到 master
~/alpha $ git checkout deputy Switched to branch 'deputy'~/alpha $ printf 'b' > data/letter.txt ~/alpha $ git add data/letter.txt ~/alpha $ git commit -m 'b3' [deputy 982dffb] b3
切換到 deputy
分支,把 data/letter.txt
的內容設置爲 b
,並提交到 deputy
。
提交可以共享父級,這意味着可以在提價的歷史中創建新的譜系。
提交可以有多個父級。 這意味着單獨的譜系可以通過具有兩個父的提交來合併:合併提交。
~/alpha $ git merge master -m 'b4' Merge made by the 'recursive' strategy.
合併 master
到 deputy
Git
發現接收者 b3
和提供者 a4
在不同的譜系中。 它做一個合併提交。 這個過程有八個步驟。
Git
將提交者的提交的哈希寫入到alpha/.git/MERGE_HEAD
文件。 這個文件的存在告訴Git
在合併中。Git
查找基本提交:接收者和提交者提交的最近的祖先的共同點。
提交有父級別。 這意味着可以找到兩個譜系起始點。 Git
從 b3
向後跟蹤,找到所有的祖先,從 a4
向後尋找所有的祖先。 它找到兩個譜系共享的最近的祖先 a3
。 這是基本提交。
Git
從接收者和提交者提交的樹圖生成基本的索引。Git
生成一個diff
,它將接收者提交和提交者提交對基礎提交所做的更改合併。 此diff
是指向更改的文件路徑列表:添加,刪除,修改或衝突。
Git
獲取出現在 base
, receiver
或 giver
索引中的所有文件的列表。 比較較索引條目以決定對文件做出的更改。 它將一個相應的條目寫入 diff
。 在這種情況下, diff
有兩個條目。
第一個條目是是 data/letter.txt
。 文件內容在 base
和 receiver
中不同。 但是在 base
和 giver
中是一樣的。 Git
看到內容被 reviceer
修改,但是沒有被 giver
修改。 data/letter.txt
的 diff
條目是一個修改,而不是衝突。
diff
中的第二個條目是 data/number.txt
。 在這種情況下,文件內容在 base
和receiver
中是相同的,並且在 giver
中是不同的。 data/letter.txt
的 diff
條目也是一個修改。
可以找到合併的 base
提交。 這意味着,如果一個文件只是從 receiver
或提 giver
的 base
改變, Git
可以自動解決該文件的合併。 這減少了用戶必須做的工作。
由
diff
中的條目指示的更改將應用於工作副本。data/letter.txt
的內容設置爲b
,data/number.txt
的內容設置爲4
。由
diff
中的條目指示的更改將應用於索引。data/letter.txt
的條目指向b
blob
,data/number.txt
的條目指向4
blob
。更新索引:
tree 20294508aea3fb6f05fcc49adaecc2e6d60f7e7d parent 982dffb20f8d6a25a8554cc8d765fb9f3ff1333b parent 7b7bd9a5253f47360d5787095afc5ba56591bfe7 author Mary Rose Cook <[email protected]> 1425596551 -0500committer Mary Rose Cook <[email protected]> 1425596551 -0500b4
注意:這次提交有兩個父級
將當前分支
deputy
分支指向新的提交。
合併來自不同譜系的兩個提交,這兩個提交都修改同一個文件
切換到 master
分支,並把 deputy
合併到 master
, 快進
到 b4
,現在 master
和 deputy
都指向同一個提交
~/alpha $ git checkout deputy Switched to branch 'deputy'~/alpha $ printf '5' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'b5' [deputy bd797c2] b5
切換到 deputy
分支,把 data/number.txt
的內容設置爲 5
,並提交。
~/alpha $ git checkout master Switched to branch 'master'~/alpha $ printf '6' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m 'b6' [master 4c3ce18] b6
切換到 master
分支,把 data/number.txt
的內容設置爲 6
,並提交。
~/alpha $ git merge deputy CONFLICT in data/number.txt Automatic merge failed; fix conflicts and commit the result.
將 deputy
合併到 master
。存在衝突,並且合併已暫停。衝突合併的過程遵循與未衝突合併的過程相同的前六個步驟:設置 .git/MERGE_HEAD
,查找 base
,生成 base
, receiver
, giver
的索引,創建 diff
,更新工作副本和更新索引。由於衝突,不採取第七提交步驟和第八更新 ref
步驟。讓我們再次看看這些步驟,發生了什麼。
Git
將giver
提交的哈希寫入.git/MERGE_HEAD
文件。
Git
找到base
提交b4
Git
從接收者和提交者提交的樹圖生成基本的索引。Git
生成一個diff
,它將接收者提交和提交者提交對基礎提交所做的更改合併。 此diff
是指向更改的文件路徑列表:添加,刪除,修改或衝突。
在這種情況下, diff
只包含一個條目: data/number.txt
。 該條目被標記爲衝突,因爲 data/number.txt
的內容在接收者,提供者和 base
中是不同的。
由
diff
中的條目指示的更改將應用於工作副本。 對於衝突區域,Git
將兩個版本寫入工作副本中的文件。data/number.txt
的內容設置爲:
<<<<<<< HEAD6=======5>>>>>>> deputy
由
diff
中的條目指示的更改應用於索引。 索引中的條目通過其文件路徑和階段的組合成唯一標識。 未衝突文件的條目具有階段0
.在此合併之前,索引如下所示,其中0
是階段值:
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea7480 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
在合併 diff
被寫入索引之後,索引如下所示:
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea7481 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d97672 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb3 data/number.txt 7813681f5b41c028345ca62a2be376bae70b7f61
在階段 0
的 data/letter.txt
的條目與在合併之前相同。 在階段 0
的 data/number.txt
的條目被去掉了。 它有三個新的條目。 階段 1
的條目具有 base
data/number.txt
內容的散列。 階段 2
的條目具有 receiver
data/number.txt
內容的散列。 階段 3
的條目具有 giver
data/number.txt
內容的散列。 這三個條目的存在告訴 Git
data/number.txt
是衝突的。合併就暫停了。
~/alpha $ printf '11' > data/number.txt ~/alpha $ git add data/number.txt
通過將 data/number.txt
的內容設置爲 11
來合成兩個衝突版本的內容。他們將文件添加到索引。 Git
添加一個包含 11
的 Blob
。添加一個衝突的文件告訴 Git
衝突已解決。 Git
從索引中刪除階段 1
, 2
和 3
的 data/number.txt
條目。 在階段0
的 data/number.txt
的條目中添加新 blob
的散列。 該索引現在爲:
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea7480 data/number.txt 9d607966b721abde8931ddd052181fae905db503
~/alpha $ git commit -m 'b11' [master 251a513] b11
提交。
Git
在存儲庫中看到.git/MERGE_HEAD
,告訴它合併正在進行。 然後檢查索引並發現沒有衝突。 就創建一個新的提交b11
,以記錄解析的合併的內容。 z最後會刪除.git/MERGE_HEAD
文件。 這將完成合並。將當前分支
master
指向新的提交。
移除一個文件
下面的圖包括提交歷史、最近提交的樹和 blob
以及工作副本和索引:
~/alpha $ git rm data/letter.txt rm 'data/letter.txt'
告訴 Git
刪除 data/letter.txt
。 該文件從工作副本中刪除。 該條目從索引中刪除。
~/alpha $ git commit -m '11' [master d14c7d2] 11
提交。 作爲提交的一部分,一如既往,Git構建一個表示索引內容的樹形圖。 data/letter.txt
不包括在樹圖中,因爲它不在索引中。
複製存儲庫
~/alpha $ cd .. ~ $ cp -R alpha bravo
將 alpha/
存儲庫的內容複製到 bravo/
目錄。 這將產生以下目錄結構:
~ ├── alpha | └── data| └── number.txt └── bravo └── data └── number.txt
現在 bravo
目錄中有另一個 Git
圖:
將存儲庫鏈接到另一個存儲庫
~ $ cd alpha ~/alpha $ git remote add bravo ../bravo
移回到 alpha
存儲庫。 他們將 bravo
設置爲 alpha
上的遠程存儲庫。 這會在 alpha/.git/config
文件中添加:
[remote "bravo"] url = ../bravo/
從遠程獲取分支
~/alpha $ cd ../bravo ~/bravo $ printf '12' > data/number.txt ~/bravo $ git add data/number.txt ~/bravo $ git commit -m '12' [master 94cd04d] 12
進入 bravo
存儲庫。 將 data/number.txt
的內容設置爲 12
,並將更改提交到 bravo
上的 master
。
~/bravo $ cd ../alpha ~/alpha $ git fetch bravo master Unpacking objects: 100% From ../bravo * branch master -> FETCH_HEAD
進入 alpha
存儲庫。 從 bravo
獲取 master
到 alpha
。 這個過程有四個步驟。
Git
獲取master
在bravo
上指向的提交的哈希。 這是12
提交的哈希。Git
提供了12
提交所依賴的所有對象的列表:提交對象本身,其樹圖中的對象,12
提交的祖先提交和它們的樹圖中的對象。 它從此列表中刪除alpha
對象數據庫已有的對象。 它將其餘部分複製到alpha/.git/objects/
。將
alpha/.git/refs/remotes/bravo/master
下的具體ref
文件的內容設置爲12提交的哈希值。將
alpha/.git/FETCH_HEAD
的內容設置爲:
94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo
下圖表示了 fetch
命令從 bravo
獲取了 master
的 12
提交
對象是可以複製的,這意味着可以在存儲庫之間共享歷史記錄。
存儲庫可以存儲遠程分支引用,如 alpha/.git/refs/remotes/bravo/master
, 這意味着存儲庫可以在本地記錄在遠程存儲庫上分支的狀態。 在獲取時是正確的,但如果遠程分支改變,它將過期。
合併FETCH_HEAD
~/alpha $ git merge FETCH_HEAD Updating d14c7d2..94cd04d Fast-forward
合併 FETCH_HEAD
, FETCH_HEAD
只是另一個 ref
。 解析了 12
提交, giver
。 master
開始指向 11
提交。 Git
做一個快進合併,並將 master
指向在 12
提交。
從遠程分支Pull
~/alpha $ git pull bravo master Already up-to-date.
將 bravo
的 master
拉到 alpha
。 Pull
是 fetch and merge FETCH_HEAD
的縮寫。
Clone一個存儲庫
~/alpha $ cd .. ~ $ git clone alpha charlie Cloning into 'charlie'
移動到上面的目錄。 clone
alpha
到 charlie
。 clone
到 charlie
具有與生成bravo
存儲庫的 cp
類似的結果。 Git
創建一個名爲 charlie
的新目錄。 它將 charlie
作爲一個Git倉庫,將 alpha
添加爲遠程倉庫被稱爲 origin
,獲取源併合並 FETCH_HEAD
。
Push分支到遠程分支
~ $ cd alpha ~/alpha $ printf '13' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m '13' [master 3238468] 13
返回到 alpha
倉庫,把 data/number.txt
的內容設置爲 13
,並提交。
~/alpha $ git remote add charlie ../charlie
設置 alpha
的遠程倉庫爲 charlie
~/alpha $ git push charlie master Writing objects: 100% remote error: refusing to update checked out branch: refs/heads/master because it will make the index and work tree inconsistent
push
master
到 charlie
.
13
提交所需的所有對象都複製到 charlie
。
此時,推送過程停止。 Git
告訴我們出了什麼問題。 它拒絕推送到遠程分支。 這是有道理的, 因爲推送將更新遠程索引和 HEAD
。 這將導致混亂,如果有人正在編輯遠程的工作副本。(這也有其他的解決辦法,可以google一下)
此時,可以創建一個新的分支,將 13
提交合併到其中,並將該分支推送到 charlie
。但是我們想要一個類似 GitHub
那樣的中央倉庫,無論什麼時候都可以 push
pull
。(中央倉庫爲什麼可以?因爲在初始化倉庫的時候使用的是 git init --bare
, 初始化成一個裸存儲庫,遠程倉庫應該都要這麼初始化。)
Clone 一個裸倉庫
~/alpha $ cd .. ~ $ git clone alpha delta --bare Cloning into bare repository 'delta'
移動到上面的目錄。 將 delta
clone
爲裸存儲庫。 這是一個有兩個區別的普通 clone
。 配置文件指示存儲庫是裸的。 通常存儲在 .git
目錄中的文件存儲在存儲庫的根目錄中如下:
delta├── HEAD ├── config ├── objects └── refs
Push分支到裸存儲庫
~ $ cd alpha ~/alpha $ git remote add delta ../delta
返回到 alpha
存儲庫。 將 delta
設置爲 alpha
上的遠程存儲庫。
~/alpha $ printf '14' > data/number.txt ~/alpha $ git add data/number.txt ~/alpha $ git commit -m '14' [master cb51da8] 14
將 data/number.txt
的內容設置爲 14
,並將更改提交到 alpha
上的 master
~/alpha $ git push delta master Writing objects: 100% To ../delta 3238468..cb51da8 master -> master
push
master
到 delta
,有3個步驟
master
分支上的14
提交所需的所有對象都從alpha/.git/objects/
複製到delta/objects /
。delta/refs/heads/master
被更新爲指向14
提交。alpha/.git/refs/remotes/delta/master
設置爲指向14
提交。alpha
具有delta
的狀態的最新記錄.