歡迎光臨
每天分享高質量文章

Git 內部原理之 Git 取用

(點選上方公眾號,可快速關註)


來源:jingsam ,

jingsam.github.io/2018/10/12/git-reference.html

這篇文章本應該在6月份就完成,拖了4個月之後,終於鼓起勇氣撿起來,實在慚愧。堅持寫文章就像長跑,途中跑起來基本是靠慣性,如果停下來再起跑就很累很困難。

閑話不多說,本篇繼續承接前文講一講Git內部原理,本篇的主題是Git取用的原理。

首先來搞清楚什麼是Git取用,前文講了Git提交物件的雜湊、儲存原理,理論上我們只要知道該物件的hash值,就能往前推出整個提交歷史,例如:

$ git log –pretty=oneline 3ac728ac62f0a7b5ac201fd3ed1f69165df8be31

3ac728ac62f0a7b5ac201fd3ed1f69165df8be31 third commit

d4d2c6cffb408d978cb6f1eb6cfc70e977378a5c second commit

db1d6f137952f2b24e3c85724ebd7528587a067a first commit

現在問題來了,提交物件的這40位hash值不好記憶,Git取用相當於給40位hash值取一個別名,便於識別和讀取。Git取用物件都儲存在.git/refs目錄下,該目錄下有3個子檔案夾heads、tags和remotes,分別對應於HEAD取用、標簽取用和遠端取用,下麵分別講一講每種取用的原理。

HEAD取用

HEAD取用是用來指向每個分支的最後一次提交物件,這樣切換到一個分支之後,才能知道分支的“尾巴”在哪裡。HEAD取用儲存在.git/refs/heads目錄下,有多少個分支,就有相應的同名HEAD取用物件。例如程式碼庫裡面有master和test兩個分支,那麼.git/refs/heads目錄下就存在master和test兩個檔案,分別記錄了分支的最後一次提交。

HEAD取用的內容就是提交物件的hash值,理論上我們可以手動地構造一個HEAD取用:

$ echo “3ac728ac62f0a7b5ac201fd3ed1f69165df8be31” > .git/refs/heads/master

Git提供了一個專有命令update-ref,用來檢視和修改Git取用物件,當然也包括HEAD取用:

$ git update-ref refs/heads/master 3ac728ac62f0a7b5ac201fd3ed1f69165df8be31

$ git update-ref refs/heads/master

3ac728ac62f0a7b5ac201fd3ed1f69165df8be31

上面的命令我們將master分支的HEAD指向了3ac728ac62f0a7b5ac201fd3ed1f69165df8be31,現在用git log檢視下master的提交歷史,可以發現最後一次提交就是所更新的hash值:

$ git log –pretty=oneline master

3ac728ac62f0a7b5ac201fd3ed1f69165df8be31 (HEAD -> master) third commit

d4d2c6cffb408d978cb6f1eb6cfc70e977378a5c second commit

db1d6f137952f2b24e3c85724ebd7528587a067a first commit

同理,可以使用同樣的方法更新test分支的HEAD:

$ git update-ref refs/heads/test d4d2c6cffb408d978cb6f1eb6cfc70e977378a5c

$ git log –pretty=oneline test

d4d2c6cffb408d978cb6f1eb6cfc70e977378a5c (test) second commit

db1d6f137952f2b24e3c85724ebd7528587a067a first commit

.git/refs/heads目錄下儲存了每個分支的HEAD,那怎麼知道程式碼庫當前處於哪個分支呢?這就需要一個程式碼庫級別的HEAD取用。.git/HEAD這個檔案就是整個程式碼庫級別的HEAD取用。我們先檢視一下.git/HEAD檔案的內容:

$ cat .git/HEAD

ref: refs/heads/master

我們發現.git/HEAD檔案的內容不是40位hash值,而像是指向.git/refs/heads/master。嘗試切換到test:

$ git checkout test

$ cat .git/HEAD

ref: refs/heads/test

切換分支後,.git/HEAD檔案的內容也跟著指向.git/refs/heads/test。.git/HEAD也是HEAD取用物件,與一般取用不同的是,它是“符號取用”。符號取用類似於檔案的快捷方式,連結到要取用的物件上。

Git提供專門的命令git symbolic-ref,用來檢視和更新符號取用:

$ git symbolic-ref HEAD refs/heads/master

$ git symbolic-ref HEAD refs/heads/test

至此,我們分析了兩種HEAD取用,一種是分支級別的HEAD取用,用來記錄各分支的最後一次提交,儲存在.git/refs/heads目錄下,使用git update-ref來維護;一種是程式碼庫級別的HEAD取用,用來記錄程式碼庫所處的分支,儲存在.git/HEAD檔案,使用git symbolic-ref來維護。

標簽取用

標簽取用,顧名思義就是給Git物件打標簽,便於記憶。例如,我們可以將某個提交物件打v1.0標簽,表示是1.0版本。標簽取用都儲存在.git/refs/tags裡面。

標簽取用和HEAD取用本質是Git取用物件,同樣使用git update-ref來檢視和修改:

$ git update-ref refs/tags/v1.0 d4d2c6cffb408d978cb6f1eb6cfc70e977378a5c

$ cat .git/refs/tags/v1.0

d4d2c6cffb408d978cb6f1eb6cfc70e977378a5c

還有一種標簽取用稱為“附註取用”,可以為標簽新增說明資訊。上面的標簽取用打了一個v1.0的標簽表示釋出1.0版本,有時候釋出軟體的時候除了版本號資訊,還要寫更新說明。附註取用就是用來實現打標簽的同時,也可以附帶說明資訊。

附註取用是怎麼實現的呢?與常規標簽取用不同的是,它不直接指向提交物件,而是新建一個Git物件儲存到.git/objects中,用來記錄附註資訊,然後附註標簽指向這個Git物件。

使用git tag建立一個附註標簽:

$ git tag -a v1.1 3ac728ac62f0a7b5ac201fd3ed1f69165df8be31 -m “test tag”

$ cat .git/refs/tags/v1.1

8be4d8e4e8e80711dd7bae304ccfa63b35a6eb8c

使用git cat-file來檢視附註標簽所指向的Git物件:

$ git cat-file -p 8be4d8e4e8e80711dd7bae304ccfa63b35a6eb8c

object 3ac728ac62f0a7b5ac201fd3ed1f69165df8be31

type commit

tag v1.1

tagger jingsam 1529481368 +0800

 

test tag

可以看到,上面的Git物件儲存了我們填寫的附註資訊。

總之,普通的標簽取用和附註取用同樣都是儲存的是40位hash值,指向一個Git物件,所不同的是普通的標簽取用是直接指向提交物件,而附註標簽是指向一個附註物件,附註物件再指向具體的提交物件。

另外,本質上標簽取用並不是隻可以指向提交物件,實際上可以指向任何Git物件,即可以給任何Git物件打標簽。

遠端取用

遠端取用,類似於.git/refs/heads中儲存的本地倉庫各分支的最後一次提交,在.git/refs/remotes是用來記錄多個遠端倉庫各分支的最後一次提交。

我們可以使用git remote來管理遠端分支:

$ git remote add origin git@github.com:jingsam/git-test.git

上面添加了一個origin遠端分支,接下來我們把本地倉庫的master推送到遠端倉庫上:

$ git push origin master

Counting objects: 9, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (5/5), done.

Writing objects: 100% (9/9), 720 bytes | 360.00 KiB/s, done.

Total 9 (delta 0), reused 0 (delta 0)

To github.com:jingsam/git-test.git

 * [new branch]      master -> master

這時候在.git/refs/remotes中的遠端取用就會更新:

$ cat .git/refs/remotes/origin/master

3ac728ac62f0a7b5ac201fd3ed1f69165df8be31

和本地倉庫的master比較一下,發現是一模一樣的,表示遠端分支和本地分支是同步的:

$ cat .git/refs/heads/master

3ac728ac62f0a7b5ac201fd3ed1f69165df8be31

由於遠端取用也是Git取用物件,所以理論上也可以使用git update-ref來手動維護。但是,我們需要先把程式碼與遠端倉庫進行同步,在遠端倉庫中找到對應分支的HEAD,然後使用git update-ref進行更新,過程比較麻煩。而我們在執行git pull或git push這樣的高層命令的時候,遠端取用會自動更新。

總結

到這裡,三種Git取用都已分析完畢。總的來說,三種Git取用都統一儲存到.git/refs目錄下,Git取用中的內容都是40位的hash值,指向某個Git物件,這個物件可以是任意的Git物件,可以是資料物件、樹物件、提交物件。三種Git取用都可以使用git update-ref來手動維護。

三種Git取用物件所不同的是,分別儲存於.git/refs/heads、.git/refs/tags、.git/refs/remotes,儲存的檔案夾不同,賦予了取用物件不同的功能。HEAD取用用來記錄本地分支的最後一次提交,標簽取用用來給任意Git物件打標簽,遠端取用正式用來記錄遠端分支的最後一次提交。

【關於投稿】


如果大家有原創好文投稿,請直接給公號傳送留言。


① 留言格式:
【投稿】+《 文章標題》+ 文章連結

② 示例:
【投稿】《不要自稱是程式員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/

③ 最後請附上您的個人簡介哈~



看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂