深入理解git版本管理原理

全手打原創,轉載請標明出處:https://www.cnblogs.com/dreamsqin/p/12966398.html, 多謝,=。=~
(如果對你有幫助的話請幫我點個贊啦)

日常項目管理中我們最常使用的git命令有addcommitpushpull,但其他不常使用的命令往往容易誤操作,所以想深入的學習一下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中的內容ahash計算得到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通過索引創建樹狀圖來記錄項目的當前狀態,這個樹狀圖記錄了項目中每個文件的位置和內容)

樹狀圖由兩類對象組成:blobstrees

  • 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 <[email protected]> 1424798436 -0500
committer Mary Rose Cook <[email protected]> 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.txtdata/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 <[email protected]> 1424813101 -0500
committer Mary Rose Cook <[email protected]> 1424813101 -0500

a2
  • first line:指向新的roottree對象;
  • 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則使用特殊的符號例如HEADMERGE_HEADFETCH_HEAD來支持通過用命令行操作提交歷史。
  • .git/objects目錄下的節點是不變的。也就是說,內容只能被編輯,不能被刪除。添加的每個文件內容和創建的每個提交都能在.git/objects目錄中找到。
  • refs是可變的。因此,ref的含義可以改變,master所指向的commit可能是目前項目的最佳版本,但是很快,它就會被更新更好的commit所取代。
  • 通過ref指向的工作副本和commit很容易回索,但其他的commit就不是。意思是最近的歷史記錄更容易找到,但也經常改變。換句話說就是git比較健忘,如果想要查找比較久遠的提交記錄就需要深度索引。

git checkout(檢出commit)

通過git checkout命令+a2提交的hash散列值檢出a2commit。

例如:
~/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文件的條目更新爲2blob文件的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指向mastermaster指向a2,而a2data/number.txt文件的內容爲2
  • deputy指向a3,而a3data/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,並提交爲a4master分支:

~/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,並提交爲b3deputy分支:

~/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所以不同的“族譜”可以通過一個包含兩個parentcommit來連接(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 commitreceiver commitgiver commit生成索引
第四步:git生成一個diff

diff可以理解爲差異文件,其中組合了receiver commitgiver commitbase commit的更改,diff是一個指向被修改文件的路徑列表(文件修改包括:add、remove、modify、conflict)。

git通過獲取出現在base commitreceiver commitgiver commit索引中的所有文件列表,對於每一個都進行比較,確定文件被修改後就向diff寫入一個對應的條目,在本例中,diff有兩個條目。

  • 一個是data/letter.txtbase commit中是areceiver commit中是bgiver commit中是a。git可以看到內容是由
    receiver修改的,而不是giver,所以diffdata/letter.txt對應的條目是一個modify,而不是conflict。
  • 另一個是data/number.txtbase commit中是3receiver commit中是3giver commit中是4,所以diffdata/number.txt對應的條目也是一個modify。
第五步:git將diff中的修改應用於工作副本

data/letter.txt中的內容被設置成bdata/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 <[email protected]> 1425596551 -0500
committer Mary Rose Cook <[email protected]> 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,並提交爲b5deputy分支:

~/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,並提交爲b6master分支:

~/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 commitreceiver commitgiver 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 commitreceiver commitgiver commit生成索引
第四步:git生成一個diff

diff可以理解爲差異文件,其中組合了receiver commitgiver commitbase commit的更改,diff是一個指向被修改文件的路徑列表(文件修改包括:add、remove、modify、conflict)。

git通過獲取出現在base commitreceiver commitgiver commit索引中的所有文件列表,對於每一個都進行比較,確定文件被修改後就向diff寫入一個對應的條目,在本例中,diff只有一個條目。

  • 也就是data/number.txtbase commit中是4receiver commit中是6giver commit中是5。條目被標記爲conflict,因爲data/number.txt的內容在receivergiverbase中是不同的。
第五步: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個新的條目。stage1條目包含了basedata/number.txt)內容的hash散列,stage2條目包含了receiverdata/number.txt)內容的hash散列,stage3條目包含了giverdata/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倉庫,想要把分支masterbravo取過來:

~/bravo $ cd ../alpha
~/alpha $ git fetch bravo master
          Unpacking objects: 100%
          From ../bravo
            * branch master -> FETCH_HEAD

這個過程git有四個步驟:

第一步:獲取masterbravo上所指向的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獲取了master12 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 commitgiver),HEAD指向11 commitreceiver)。git執行合併後將指向master12 commit

從遠程倉庫上pull分支

用戶將master分支從bravo pullalphapull是“fetch FETCH_HEAD和 merge FETCH_HEAD”的縮寫,所以最終git執行兩個命令並反饋master已經是最新的了。

~/alpha $ git pull bravo master
          Already up-to-date.

clone倉庫

用戶移動到上層目錄並clone alphacharlie

~/alpha $ cd ..
      ~ $ git clone alpha charlie
          Cloning into 'charlie'

clonecharlie的結果與之前用戶爲了生成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分支pushcharlie

~/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合併到其中,並將該分支pushcharlie。但實際上,我們是想要一個可以隨時可以push的倉庫,一箇中央存儲庫,用於pushpull,但是沒有人直接進行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 pushdelta

~/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 commitalpha擁有了delta狀態的最新記錄

現在的結構圖如下:

參考文獻

Git from the inside out:https://codewords.recurse.com/issues/two/git-from-the-inside-out

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章