當前位置:
首頁 > 知識 > Git 內部原理之 Git 引用

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 <jing-sam@qq.com> 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技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 ImportNew 的精彩文章:

SpringBoot | 第十六章:web 應用開發
2018 年 JVM 生態報告

TAG:ImportNew |