全手打原創,轉載請標明出處:https://www.cnblogs.com/dreamsqin/p/12966398.html, 多謝,=。=~
(如果對你有幫助的話請幫我點個贊啦)
日常項目管理中我們最常使用的git命令有
add
、commit
、push
、pull
,但其他不常使用的命令往往容易誤操作,所以想深入的學習一下git操作命令底層原理到底是怎么樣的,在阮一峰大大的日志里面看到了《Git from the inside out》,全文通過樹狀圖的方式表示各分支節點之間的關系,以示例的方式闡述每種操作命令后底層文件及索引的變化。然而是全英文的,於是乎我只能每天抽點時間來翻譯加學習,前前后后經歷了一周,終於完成了,大家一起學起來吧。
git init
初始化git倉庫(該操作會在當前目錄下創建一個.git
目錄,里面可以放git配置或者項目歷史記錄:.git/objects
)。
例如:
~ $ mkdir alpha
~ $ cd alpha
~/alpha $ mkdir data
~/alpha $ printf 'a' > data/letter.txt
~/alpha $ printf '1234' > data/number.txt
~/alpha $ git init
目錄結構如下:
alpha
├── data
| └── letter.txt
| └── number.txt
└── .git
├── objects
etc...
.git
目錄及其內容是git相關的文件,除此之外所有其他文件統稱為工作副本,為用戶文件。
git add
在git倉庫中添加一些文件。
例如:
~/alpha $ git add data/letter.txt
第一步:在.git/objects
目錄中創建一個新的blob文件(創建的blob文件包含data/letter.txt
的壓縮內容,文件名是由它的內容哈希得到)
-
git將
data/letter.txt
中的內容a
hash計算得到2e65efe2a145dda7ee51d1741299f848e5bf752e
,前兩個字符被用作對象數據庫中的目錄名:.git/objects/2e/
; -
hash散列值的剩余部分用作blob文件(被添加的文件中需要保存的內容)的名稱:
.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e
;
第二步:將文件添加到索引中
-
索引是一個列表,其中包含Git要跟蹤的每個文件,它以
.git/index
文件的形式存儲,其中每行指向跟蹤的blob文件,包含文件內容的hash散列值。例如:data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e
; -
注意:
git add data
命令執行,索引中只列出data目錄中的文件,並不會列出data
目錄; -
以同樣的方法我們將
data/number.txt
文件添加到git倉庫后,當用戶修改data/number.txt
文件中內容,並重新執行git add
命令時,git會根據更新后的內容創建一個新的blob,同時更新data/number.txt
的索引條目以指向新的blob。
例如:
~/alpha $ printf '1' > data/number.txt
~/alpha $ git add data
git commit
通過git commit
命令創建a1
提交。
~/alpha $ git commit -m 'a1'
[master (root-commit) 774b54a] a1
第一步:創建一個樹狀圖來表示提交的項目版本的內容(git通過索引創建樹狀圖來記錄項目的當前狀態,這個樹狀圖記錄了項目中每個文件的位置和內容)
樹狀圖由兩類對象組成:blobs
和trees
-
blobs
:通過git add
存儲,表示文件內容; -
trees
:通過git commit
存儲,表示工作副本中的目錄;
例如:分別對應文件權限、條目類型、blob文件的hash散列值、文件名稱;
100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt
040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data
;
a1
提交的樹狀圖:
root→data→a(data/letter.txt) and 1(data/number.txt)
第二步:創建一個提交對象(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
- first line:指向樹狀圖,其中hash散列值由工作副本的根目錄生成(也就是
alpha
); - last line:提交信息;
a1
的提交對象,指向它的樹狀圖:
a1→root→data→a(data/letter.txt) and 1(data/number.txt)
第三步:將當前分支指向新的提交對象(git在.git/HEAD
的頭文件中查找當前分支)
-
例如:
ref: refs/heads/master
,表明HEAD
指向master
,所以master
為當前分支; -
注意:首次提交時
master
的ref是不存在的,git會創建.git/refs/heads/master
,並將其內容設置為提交對象的hash散列:74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd
;
當前分支指向提交對象
a1
:
HEAD→master→a1→root→data→a(data/letter.txt) and 1(data/number.txt)
git commit(非初次提交)
下面為a1
提交后的結構圖,工作副本及索引已存在,此時三方的data/letter.txt
和data/number.txt
內容一致:
修改data/number.txt
的內容,工作副本更新:
~/alpha $ printf '2' > data/number.txt
執行git add
命令,在.git/objects
目錄中創建一個新的blob文件,並將文件添加到索引中:
~/alpha $ git add data/number.txt
執行git commit
命令:
~/alpha $ git commit -m 'a2'
創建一個新的樹狀圖來表示索引的內容:
100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt
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
- first line:指向新的
root
tree對象; - second line:指向
a1
(為了找到父提交,git轉到HEAD
,跟着它轉到master
,最終找到a1
提交的hash散列); - last line:提交信息;
將當前分支指向新的提交對象:
從結構圖可以看出以下特性:
- 文件內容是以對象樹存儲的。這意味着只有差異會存儲在對象數據庫中,上圖中
a2
提交時復用了a1
提交之前生成的blob(根據內容a生成)。同理,如果整個目錄從一個commit
到另一個commit
時並沒有發生改變,那么它的對象樹以及下面的所有blobs
對象和trees
對象都是可以復用的。通常,從一個commit
到另一個commit
的內容變更較少,這就是git可以在很小的空間中存儲大量提交歷史的原因。 - 每個
commit
都會有一個parent
。這意味着一個倉庫可以存儲一個項目的所有歷史記錄。 refs
是入口,指向了commit
歷史的一部分。每項commit
都有自己獨特的標識,用戶通過類似樹狀結構的“族譜”將他們的工作組織起來,例如:refs
具體為fix-for-bug-376
。git則使用特殊的符號例如HEAD
、MERGE_HEAD
、FETCH_HEAD
來支持通過用命令行操作提交歷史。.git/objects
目錄下的節點是不變的。也就是說,內容只能被編輯,不能被刪除。添加的每個文件內容和創建的每個提交都能在.git/objects
目錄中找到。refs
是可變的。因此,ref
的含義可以改變,master
所指向的commit
可能是目前項目的最佳版本,但是很快,它就會被更新更好的commit
所取代。- 通過
ref
指向的工作副本和commit
很容易回索,但其他的commit
就不是。意思是最近的歷史記錄更容易找到,但也經常改變。換句話說就是git比較健忘,如果想要查找比較久遠的提交記錄就需要深度索引。
git checkout(檢出commit)
通過git checkout
命令+a2
提交的hash散列值檢出a2
commit。
例如:
~/alpha $ git checkout 37888c2
You are in 'detached HEAD' state...
第一步:git獲取到a2
提交及它所指向的樹狀圖。
第二步:git將樹狀圖中的文件條目寫入工作副本。
這時內容並不會發生改變。因為此時HEAD
就是通過master
指向a2
提交,所以a2
對應的樹狀圖內容也已經被寫入工作副本中。
第三步:git將樹圖中的文件條目寫入索引。
這也不會導致任何變化。因為索引已經包含了a2
提交的內容。
第四步:HEAD
的內容被設置為a2
提交的hash散列值。
例如:f0af7e62679e144bb28c627ee3e8f7bdb235eee9
通過設置HEAD
的內容為hash散列值,會使Head
直接指向a2
而不是原本的master
(倉庫將被至於分離的HEAD
):
此時提交的commit
很容易丟失。比如修改number.txt
文件內容為3
並提交修改,git會通過HEAD
去獲取a3
提交的parent
,而不是像之前一樣利用ref
實現跟蹤和查找。最終由HEAD
直接指向a3
的提交對象(倉庫仍處於分離的HEAD
中,無論是a3
還是之后的commit
都沒有在任何分支上)。
例如:
~/alpha $ printf '3' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'a3'
[detached HEAD 3645a0e] a3
樹狀圖結構如下:
git branch(創建分支)
通過git branch
命令創建一個名為deputy
的分支,實際就是在.git/refs/heads/deputy
目錄下創建了一個新文件,其中包含了HEAD
所指向的hash散列值(a3
提交的hash散列)。
~/alpha $ git branch deputy
樹狀圖結構如下(分支deputy
的創建使得a3
提交被添加到該分支,安全性就有了,不至於會丟失。但HEAD
還是在分離狀態,因為它仍然指向了commit
):
git checkout(檢出分支)
通過git checkout
命令檢出master
分支。
~/alpha $ git checkout master
Switched to branch 'master'
第一步:git獲取到master
指向的a2
提交及a2
所指向的樹狀圖。
第二步:git將樹狀圖中的文件條目寫入工作副本。
這時會將data/number.txt
文件內容寫為2
。
第三步:git將樹圖中的文件條目寫入索引。
這時會將data/number.txt
文件的條目更新為2
blob文件的hash散列。
第四步:git將HEAD
的內容由hash散列值修改為ref: refs/heads/master
,使得HEAD
重新指向master
。
樹狀圖結構如下:
git checkout(檢出與工作副本不兼容的分支)
本地修改文件data/number.txt
內容后,通過git checkout
命令檢出deputy
分支。
~/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.
很顯然,checkout被git無情拒絕,原因是此時三方的文件內容不一致,必須先解決差異(git如果做覆蓋操作會使信息丟失,如果做合並又太復雜):
HEAD
指向master
,master
指向a2
,而a2
中data/number.txt
文件的內容為2
;deputy
指向a3
,而a3
中data/number.txt
文件的內容為3
;- 本地工作空間中
data/number.txt
文件的內容為789
;
所以將誤修改復原就可以解決了(假設不是誤修改,那你需要將修改先提交到原分支):
~/alpha $ printf '2' > data/number.txt
~/alpha $ git checkout deputy
Switched to branch 'deputy'
樹狀圖結構如下:
git merge(合並父分支到子分支)
通過git merge
命令將master
分支合並到deputy
,合並兩個分支其實就是合並兩個commit
,對於這樣的合並git什么也不做。
~/alpha $ git merge master
Already up-to-date.
結構圖中一系列的提交其實就是對倉庫文件內容做的一系列修改,所以,如果是將父提交合並至子提交,git什么也不做,因為這些改變其實已經被合並了。
git merge(合並子分支到父分支)
先將分支切換回master
:
~/alpha $ git checkout master
Switched to branch 'master'
通過git merge
命令將deputy
分支合並到master
:
~/alpha $ git merge deputy
Fast-forward
git獲取到子提交及它所指向的樹狀圖,git將樹狀圖中的文件條目寫入工作副本和索引,git的fast-forwards
操作將master
指向了a3
(如前面所說的,結構圖中一系列的提交其實就是對倉庫文件內容做的一系列修改,合並時,如果是將子提交合並至父提交,提交歷史是不會改變的,只是合並雙方之間差了一些修改,所以最終改變的是被合並分支的指向)。
git merge(合並非直接關聯分支)
本地修改文件data/number.txt
內容為4
,並提交為a4
至master
分支:
~/alpha $ printf '4' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'a4'
[master 7b7bd9a] a4
切換至deputy
分支,本地修改文件data/letter.txt
內容為b
,並提交為b3
至deputy
分支:
~/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
從結構圖可以看出以下特性:
commit
可以共享parent
。所以在提交歷史中可以創建新的“族譜”。commit
可以包含多個parent
。所以不同的“族譜”可以通過一個包含兩個parent
的commit
來連接(commit
有兩個parent
的情況是通過merge
實現)。
例如:將master
分支合並至deputy
分支(由於git發現這兩個分支對應的commit
屬於不同的“族譜”,所以需要合並commit
,總共分為8步)
~/alpha $ git merge master -m 'b4'
Merge made by the 'recursive' strategy.
第一步:git將giver commit
(也就是a4
)的hash散列值寫入alpha/.git/MERGE_HEAD
文件
這個文件的存在就是告訴git正在做合並操作。
第二步:git查找base commit
是receiver commit
(也就是b3
)和giver commit
(也就是a4
)在歷史記錄中最近的共同祖先(通俗的說:兩個“族譜”分道揚鑣的節點,也就是a3
)。
第三步:git基於樹狀圖為base commit
、receiver commit
、giver commit
生成索引
第四步:git生成一個diff
diff
可以理解為差異文件,其中組合了receiver commit
和giver commit
對base commit
的更改,diff
是一個指向被修改文件的路徑列表(文件修改包括:add、remove、modify、conflict)。
git通過獲取出現在
base commit
、receiver commit
、giver commit
索引中的所有文件列表,對於每一個都進行比較,確定文件被修改后就向diff
寫入一個對應的條目,在本例中,diff
有兩個條目。
- 一個是
data/letter.txt
,base commit
中是a
,receiver commit
中是b
,giver commit
中是a
。git可以看到內容是由
receiver
修改的,而不是giver
,所以diff
中data/letter.txt
對應的條目是一個modify,而不是conflict。- 另一個是
data/number.txt
,base commit
中是3
,receiver commit
中是3
,giver commit
中是4
,所以diff
中data/number.txt
對應的條目也是一個modify。
第五步:git將diff
中的修改應用於工作副本
data/letter.txt
中的內容被設置成b
,data/number.txt
中的內容被設置成4
。
第六步:git將diff
中的修改寫入索引
data/letter.txt
對應的條目指向b
的blob文件,data/number.txt
對應的條目指向4
的blob文件。
第七步:git提交更新后的索引
可以看到此時的提交就有兩個parent
。
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
第八步:git將當前分支deputy
指向最新的提交b4
(將a4
合並到b3
的遞歸合並結果)
git merge(合並非直接關聯分支,且修改了相同文件)
先切換至master
分支,將deputy
分支合並至master
分支(也就是前面的將子分支合並到父分支,其實只是修改了master
分支的commit
指向):
~/alpha $ git checkout master
Switched to branch 'master'
~/alpha $ git merge deputy
Fast-forward
此時切換至deputy
分支,本地修改文件data/number.txt
內容為5
,並提交為b5
至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
然后切換至master
分支,本地修改文件data/number.txt
內容為6
,並提交為b6
至master
分支:
~/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
最終,將deputy
分支合並到master
分支,很顯然被git拒絕,原因是data/number.txt
文件中的內容沖突了,自動合並失敗:
~/alpha $ git merge deputy
CONFLICT in data/number.txt
Automatic merge failed; fix conflicts and
commit the result.
整個過程與上面合並時的前6步是一致的:alpha/.git/MERGE_HEAD
文件設置;查找base commit
;為base commit
、receiver commit
、giver commit
生成索引;生成diff
文件;將diff
中的修改應用於工作副本;git將diff
中的修改寫入索引;但是由於沖突第7步的提交和第8步的ref
更新不能正常執行。
下面詳細說明一下前面6步到底發生了什么導致最終的結果:
第一步:git將giver commit
(也就是b5
)的hash散列值寫入alpha/.git/MERGE_HEAD
文件
同樣的,這個文件的存在就是告訴git正在做合並操作。
第二步:git查找base commit
是receiver commit
(也就是b6
)和giver commit
(也就是b5
)在歷史記錄中最近的共同祖先(通俗的說:兩個“族譜”分道揚鑣的節點,也就是b4
)。
第三步:git基於樹狀圖為base commit
、receiver commit
、giver commit
生成索引
第四步:git生成一個diff
diff
可以理解為差異文件,其中組合了receiver commit
和giver commit
對base commit
的更改,diff
是一個指向被修改文件的路徑列表(文件修改包括:add、remove、modify、conflict)。
git通過獲取出現在
base commit
、receiver commit
、giver commit
索引中的所有文件列表,對於每一個都進行比較,確定文件被修改后就向diff
寫入一個對應的條目,在本例中,diff
只有一個條目。
- 也就是
data/number.txt
,base commit
中是4
,receiver commit
中是6
,giver commit
中是5
。條目被標記為conflict,因為data/number.txt
的內容在receiver
、giver
和base
中是不同的。
第五步:git將diff
中的修改應用於工作副本
對於沖突的部分,git會將兩個版本都寫入到工作副本的文件中。data/number.txt
中的內容被設置成:
<<<<<<< HEAD
6
=======
5
>>>>>>> deputy
第六步:git將diff
中的修改寫入索引
索引中的條目由其文件路徑和stage
共同組成唯一標識,對於沒有沖突的文件,stage
為0。合並前的索引如下(前面的0就是stage
):
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
stage
為0的data/letter.txt
條目與合並之前的條目相同,但是stage
為0的data/number.txt
條目已經沒有了,在該位置上新增了3個新的條目。stage
1條目包含了base
(data/number.txt
)內容的hash散列,stage
2條目包含了receiver
(data/number.txt
)內容的hash散列,stage
3條目包含了giver
(data/number.txt
)內容的hash散列。這三個條目的存在就是在告訴gitdata/number.txt
是沖突的,所以合並就被中斷了。
此時,如果用戶通過將data/number.txt
的內容設置為11來整合兩個沖突版本的內容,並通過git add
將文件添加至索引中:
~/alpha $ printf '11' > data/number.txt
~/alpha $ git add data/number.txt
整體過程就是:git add
命令創建了一個包含11的blob
文件,該操作就是就是告訴git沖突解決了,此時git就會從索引中移除stage
為1、2、3的條目,並使用新blob
文件的散列為data/number.txt
添加一個stage
為0的條目:
0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 9d607966b721abde8931ddd052181fae905db503
第七步:用戶通過git commit
命令提交最新修改
~/alpha $ git commit -m 'b11'
[master 251a513] b11
git在倉庫中看到.git/MERGE_HEAD
,就知道合並正在進行中,它會檢查索引並查看是否存在沖突,如果沒有就會創建一個新的提交b11來記錄已解決的合並內容,然后刪除.git/MERGE_HEAD
中的文件,此時合並就完成了。
第八步:git將當前分支master
指向新的提交。
git rm(刪除文件)
下面是當前狀態下最新的結構圖:
通過git rm
命令刪除data/letter.txt
文件,文件首先會從本地的工作副本移除,接着文件條目會從索引中移除:
~/alpha $ git rm data/letter.txt
rm 'data/letter.txt'
此時結構圖就變成了:
通過git commit
命令提交變更:
~/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結構圖:
建立兩個倉庫的鏈接
用戶首先返回到alpha
倉庫:
~ $ cd alpha
~/alpha $ git remote add bravo ../bravo
如果要將bravo
設置為alpha
的遠程倉庫,需要在alpha/.git/config
文件中添加一些代碼:
[remote "bravo"]
url = ../bravo/
指定在../bravo
目錄中有一個名為bravo
的遠程倉庫。
從遠程倉庫上fetch分支
用戶首先進入bravo
倉庫,將data/number.txt
的內容設置為12,並將修改提交給bravo
上的master
:
~/alpha $ cd ../bravo
~/bravo $ printf '12' > data/number.txt
~/bravo $ git add data/number.txt
~/bravo $ git commit -m '12'
[master 94cd04d] 12
此時結構圖如下:
然后用戶進入alpha
倉庫,想要把分支master
從bravo
取過來:
~/bravo $ cd ../alpha
~/alpha $ git fetch bravo master
Unpacking objects: 100%
From ../bravo
* branch master -> FETCH_HEAD
這個過程git有四個步驟:
第一步:獲取master
在bravo
上所指向的commit
的散列
也就是12 commit
提交的散列。
第二步:將12 commit
依賴的所有對象(去除alpha
倉庫中已存在的)復制到alpha/.git/objects/
中
包括提交對象本身、樹圖中指向的對象、12 commit
的父提交以及它在樹圖中指向的對象。
第三步:alpha/.git/refs/remotes/bravo/master
中的ref
被設置成12 commit
提交的散列值
第四步:alpha/.git/FETCH_HEAD
的內容被設置成:
94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo
這表明剛剛的fetch
命令從bravo
獲取了master
的12 commit
,此時結構圖就變成了:
從結構圖可以看出以下特性:
- 對象是可以被拷貝的。也就是說歷史記錄可以在倉庫之間共享。
- 一個倉庫可以存儲遠程倉庫分支的
ref
,例如alpha/.git/refs/remotes/bravo/master
。這意味着一個倉庫可以在本地記錄遠程倉庫上分支的狀態。它在獲取時是正確的,但是如果遠程分支發生更改,它就會過期。
合並FETCH_HEAD
用戶通過git merge
命令合並FETCH_HEAD
:
~/alpha $ git merge FETCH_HEAD
Updating d14c7d2..94cd04d
Fast-forward
FETCH_HEAD
只是一個ref
,它解析為12 commit
(giver
),HEAD
指向11 commit
(receiver
)。git執行合並后將指向master
→12 commit
:
從遠程倉庫上pull分支
用戶將master
分支從bravo
pull
到alpha
,pull
是“fetch
FETCH_HEAD和 merge
FETCH_HEAD”的縮寫,所以最終git執行兩個命令並反饋master
已經是最新的了。
~/alpha $ git pull bravo master
Already up-to-date.
clone倉庫
用戶移動到上層目錄並clone
alpha
到charlie
:
~/alpha $ cd ..
~ $ git clone alpha charlie
Cloning into 'charlie'
clone
到charlie
的結果與之前用戶為了生成bravo
倉庫所使用的cp
類似,git創建一個名為charlie
的新目錄,並將它初始化為git倉庫,將alpha
作為一個名為origin
的遠程倉庫,fetch
origin
並合並FETCH_HEAD
。
將分支push到從遠程倉庫中checkout的分支上
用戶回到alpha
倉庫,修改data/number.txt
的值為13並將修改提交到master
分支。
~ $ cd alpha
~/alpha $ printf '13' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m '13'
[master 3238468] 13
將charlie
設置為alpha
的遠程倉庫:
~/alpha $ git remote add charlie ../charlie
將master
分支push
到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
13 commit
關聯的所有對象將被復制到charlie
。但從上面的命令行反饋可以看到,push
過程被中斷,git拒絕push
到遠程檢出(checkout)的分支。其實這是可以理解的,因為這樣的push
將更新遠程索引和HEAD
,如果有人正在編輯遠程上的工作副本,就會導致混亂。
此時,用戶可以創建一個新分支,將13 commit
合並到其中,並將該分支push
到charlie
。但實際上,我們是想要一個可以隨時可以push
的倉庫,一個中央存儲庫,用於push
和pull
,但是沒有人直接進行commit
提交,類似GitHub的遠程倉庫,想要一個裸(bare)存儲庫。
clone一個裸(bare)倉庫
用戶進入到上層目錄,clone
delta
作為裸倉庫:
~/alpha $ cd ..
~ $ git clone alpha delta --bare
Cloning into bare repository 'delta'
跟普通的clone
有兩個不同之處,首先config
文件表明存儲庫是裸倉庫,而原本存儲在.git
目錄下的文件則存儲在倉庫的根目錄下:
delta
├── HEAD
├── config
├── objects
└── refs
此時的結構圖如下:
將分支push到裸(bare)倉庫
用戶回到alpha
倉庫並設置delta
作為它的遠程倉庫:
~ $ cd alpha
~/alpha $ git remote add delta ../delta
修改data/number.txt
的值為14並將修改提交到master
分支:
~/alpha $ printf '14' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m '14'
[master cb51da8] 14
提交后結構圖如下:
接下來將master
push
到delta
:
~/alpha $ git push delta master
Writing objects: 100%
To ../delta
3238468..cb51da8 master -> master
整個過程有3步:
第一步:從alpha/.git/objects/
拷貝14 commit
提交相關的所有對象至delta/objects/
第二步:delta/refs/heads/master
的指向更新為14 commit
第三步:alpha/.git/refs/remotes/delta/master
的指向更新為14 commit
,alpha
擁有了delta
狀態的最新記錄
現在的結構圖如下:
參考文獻
Git from the inside out:https://codewords.recurse.com/issues/two/git-from-the-inside-out