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 2e65efe2a145dda7ee51d1741299f848e5bf752e
data/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.txt
100664 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 <mary@maryrosecook.com> 1424798436 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500
a1
第一行指向樹圖。 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.txt
100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt
新數據樹與舊數據樹的哈希值不同。 必須創建一個新的根樹以記錄此 Hash 值:
040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data
其次,創建一個新的提交對象。
tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556
parent 774b54a193d6cfdd081e581a007d2e11f784b9fe
author Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
a2
提交對象的第一行指向新的根樹對象。 第二行指向 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 <mary@maryrosecook.com> 1425596551 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500
b4
注意:這次提交有兩個父級
- 將當前分支 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 的內容設置為:
<<<<<<< HEAD
6
=======
5
>>>>>>> deputy
- 由 diff 中的條目指示的更改應用于索引。 索引中的條目通過其文件路徑和階段的組合成唯一標識。 未沖突文件的條目具有階段 0 .在此合并之前,索引如下所示,其中 0 是階段值:
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
在合并 diff 被寫入索引之后,索引如下所示:
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
1 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d9767
2 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
3 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 63d8dbd40c23542e740659a7168a0ce3138ea748
0 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 的狀態的最新記錄.
來自:http://deweixu.me/2016/11/05/how-git-works/