一開始我還擔憂 git 的原理會不會很難懂,但在閱讀了官方文檔後我發現其實並不難懂,彷佛能夠動手實現一個簡單的 git,因而就有了下面這篇學習記錄。linux
本文的敘述思路參照了官方文檔Book的原理介紹部分,在一些節點上探討代碼實現,官方文檔連接。git
看完本文你能:1. 瞭解 git 的設計思想。2. 收穫一點快樂?github
編程語言選擇了 go,由於剛學不太熟悉想多使用一下。面試
這是個人倉庫地址,但若是你和我同樣是初學,直接看代碼可能不能快速上手,推薦順着文章看。數據庫
若是文章看得吃力能夠跟着官方文檔的原理部分操做一次再回頭看,可能更易懂?json
1. init
在學習 git 原理以前,咱們先忘掉平時用的 commit,branch,tag 這些炫酷的 git 指令,後面咱們會摸清楚它們的本質的。windows
要知道,git 是 Linus 在寫 Linux 的時候順便寫出來的,用於對 Linux 進行版本管理,因此,記錄文件項目在不一樣版本的變動信息是 git 最核心的功能。緩存
大牛們在設計軟件的時候老是會作相應的抽象,想要理解他們的設計思路,咱們就得在他們的抽象下進行思考。雖說的有點玄乎,可是這些抽象最終都會落實到代碼上的,因此沒必要擔憂,很好理解的。bash
首先,咱們要奠基一個 ojbect 的概念,這是 git 最底層的抽象,你能夠把 git 理解成一個 object 數據庫。
廢話很少說,跟着指令操做,你會對 git 有一個全新的認識。首先咱們在任意目錄下建立一個 git 倉庫:
個人操做環境是 win10 + git bash
$ git init git-test Initialized empty Git repository in C:/git-test/.git/
能夠看到 git 爲咱們建立了一個空的 git 倉庫,裏面有一個.git
目錄,目錄結構以下:
$ ls config description HEAD hooks/ info/ objects/ refs/
在.git
目錄下咱們先重點關注 .git/objects
這個目錄,咱們一開始說 git 是一個 object 數據庫,這個目錄就是 git 存放 object 的地方。
進入.git/objects
目錄後咱們能看到info
和pack
兩個目錄,不過這和核心功能無關,咱們只須要知道如今.git/objects
目錄下除了兩個空目錄其餘啥都沒有就好了。
到這裏咱們停停,先把這部分實現了吧,邏輯很簡單,咱們只須要編寫一個入口函數,解析命令行的參數,在獲得 init 指令後在指定目錄下建立相應的目錄與文件便可。
這裏是個人實現:init
爲了易讀暫時沒有對建立文件/目錄進行錯誤處理。
我給它取了個土一點的名字,叫 jun,呃,其實管它叫啥均可以(⊙ˍ⊙)
。
2.object
接下來咱們進入 git 倉庫目錄並添加一個文件:
$ echo "version1" > file.txt
而後咱們把對這個文件的記錄添加進 git 系統。要注意的是,咱們暫不使用add
指令添加,儘管咱們平時極可能這麼作,但這是一篇揭示原理的文章,這裏咱們要引入一條平時你們可能沒有聽到過的 git 指令git hash-object
。
$ git hash-object -w file.txt 5bdcfc19f119febc749eef9a9551bc335cb965e2
指令執行後返回了一個哈希值,實際上這條指令已經把對 file.txt 的內容以一個 object 的形式添加進 object 數據庫中了,而這個哈希值就對應着這個 object。
爲了驗證 git 把這個 object 寫入了數據庫(以文件的形式保存下來),咱們查看一下.git/objects
目錄:
$ find .git/objects/ -type f #-type用於制定類型,f表示文件 .git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2
發現多了一個文件夾5b
,該文件夾下有一個名爲dcfc19f119febc749eef9a9551bc335cb965e2
的文件,也就是說 git 把該 object 哈希值的前2個字符做爲目錄名,後38個字符做爲文件名,存放到了 object 數據庫中。
關於 git hash-object 指令的官方介紹,這條指令用於計算一個 ojbect 的 ID 值。-w 是可選參數,表示把 object 寫入到 object 數據庫中;還有一個參數是 -t,用於指定 object 的類型,若是不指定類型,默認是 blob 類型。
如今你可能好奇 object 裏面保存了什麼信息,咱們使用git cat-file
指令去查看一下:
$ git cat-file -p 5bdc # -p:查看 object 的內容,咱們能夠只給出哈希值的前綴 version1 $ git cat-file -t 5bdc # -t:查看 object 的類型 blob
有了上面的鋪墊以後,接下來咱們就揭開 git 實現版本控制的祕密!
咱們改變 file.txt 的內容,並從新寫入 object 數據庫中:
$ echo "version2" > file.txt $ git hash-object -w file.txt df7af2c382e49245443687973ceb711b2b74cb4a
控制檯返回了一個新的哈希值,咱們再查看一下 object 數據庫:
$ find .git/objects -type f .git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2 .git/objects/df/7af2c382e49245443687973ceb711b2b74cb4a
(゚Д゚)
發現多了一個 object!咱們查看一下新 object 的內容:
$ git cat-file -p df7a version2 $ git cat-file -t df7a blob
看到這裏,你可能對 git 是一個 object 數據庫的概念有了進一步的認識:git 把文件每一個版本的內容都保存到了一個 object 裏面。
若是你想把 file.txt 恢復到第一個版本的狀態,只須要這樣作:
$ git cat-file -p 5bdc > file.txt
而後查看 file.txt 的內容:
$ cat file.txt version1
至此,一個能記錄文件版本,並能把文件恢復到任何版本狀態的版本控制系統完成(ง •_•)ง
!
是否是感受還行,不是那麼難?你能夠把 git 理解成一個 key - value 數據庫,一個哈希值對應一個 object。
到這裏咱們停停,把這部分實現了吧。
推薦一下本身的linux C/C++交流羣:973961276!整理了一些我的以爲比較好的學習書籍與大廠面試題、有趣的項目和熱門技術教學視頻,感興趣朋友能夠進羣領取哦。正在找工做或者準備跳槽的朋友那便更不能錯過了。
我一開始有點好奇,爲啥查看 object 不直接用 cat 指令,而是本身編了一條 git cat-file 指令呢?後來想了一下,git 確定不會把文件的內容原封不動保存進 object ,應該是作了壓縮,因此咱們還要專門的指令去解壓讀取。
這兩條指令咱們參照官方的思路進行實現,先說 git hash-object,一個 object 存儲的內容是這樣的:
- 首先要構造頭部信息,頭部信息由對象類型,一個空格,數據內容的字節數,一個空字節拼接而成,格式是這樣:
blob 9\u0000
- 而後把頭部信息和原始數據拼接起來,格式是這樣:
blob 9\u0000version1
- 接着用 zlib 把上面拼接好的信息進行壓縮,而後存進 object 文件中。
git cat-file 指令的實現則是相反,先把 object 文件裏存放的數據用 zlib 進行解壓,根據空格和空字節對解壓後的數據進行劃分,而後根據參數 -t 或 -p 返回 object 的內容或者類型。
這裏是個人實現:hash-object and cat-file
採用了簡單粗暴的面向過程實現,可是我已經隱隱約約感到後面會用不少重用的功能,因此先把單元測試寫上,方便後面重構。
3. tree object
在上一章中,細心的小夥伴可能會發現,git 會把咱們的文件內容以 blob 類型的 object 進行保存。這些 blob 類型的 object 彷佛只保存了文件的內容,沒有保存文件名。
並且當咱們在開發項目的時候,不可能只有一個文件,一般狀況下咱們是須要對一個項目進行版本管理的,一個項目會包含多個文件和文件夾。
因此最基礎的 blob object 已經知足不了咱們使用了,咱們須要引入一種新的 object,叫 tree object,它不只能保存文件名,還能將多個文件組織到一塊兒。
可是問題來了,引入概念很容易,可是具體落實到代碼上怎麼寫呢?(T_T)
,我腦殼裏的第一個想法是先在內存裏建立一個 tree objct,而後咱們往這個指定的 tree object 裏面去添加內容。但這樣彷佛很麻煩,每次添加東西都要給出 tree object 的哈希值。並且這樣的話 tree object 就是可變的了,一個可變的 object 已經違背了保存固定版本信息的初衷。
咱們仍是看 git 是怎麼思考這個問題的吧,git 在建立 tree object 的時候引入了一個叫暫存區概念,這是個不錯的主意!你想,咱們的 tree object 是要保存整個項目的版本信息的,項目有不少個文件,因而咱們把文件都放進緩衝區裏,git 根據緩衝區裏的內容一次性建立一個 tree object,這樣不就能記錄版本信息了嗎!
咱們先操做一下 git 的緩衝區加深一下理解,首先引入一條新的指令 git update-index,它能夠人爲地把一個文件加入到一個新的緩衝區中,並且要加上一個 --add 的參數,由於這個文件以前還不存在於緩衝區中。
$ git update-index --add file.txt
而後咱們觀察一下.git
目錄的變化
$ ls config description HEAD hooks/ index info/ objects/ refs/ $ find .git/objects/ -type f objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2 objects/df/7af2c382e49245443687973ceb711b2b74cb4a
發現.git
目錄下多了一個名爲index
的文件,這估計就是咱們的緩衝區了。而objects
目錄下的 object 倒沒什麼變化。
咱們查看一下緩衝區的內容,這裏用到一條指令:git ls-files --stage
$ git ls-files --stage 100644 df7af2c382e49245443687973ceb711b2b74cb4a 0 file.txt
咱們發現緩衝區是這樣來存儲咱們的添加記錄的:一個文件模式的代號,文件內容的 blob object,一個數字和文件的名字。
而後咱們把當前緩衝區的內容以一個 tree object 的形式進行保存。引入一條新的指令:git write-tree
$ git write-tree 907aa76a1e4644e31ae63ad932c99411d0dd9417
輸入指令後,咱們獲得了新生成的 tree object 的哈希值,咱們去驗證一下它是否存在,並看看它的內容:
$ find .git/objects/ -type f .git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2 #文件內容爲 version1 的 blob object .git/objects/90/7aa76a1e4644e31ae63ad932c99411d0dd9417 #新的 tree object .git/objects/df/7af2c382e49245443687973ceb711b2b74cb4a #文件內容爲 version2 的 blob object $ git cat-file -p 907a 100644 blob df7af2c382e49245443687973ceb711b2b74cb4a file.txt
估計看到這裏,你們對暫存區與 tree object 的關係就有了初步的瞭解。
如今咱們進一步瞭解兩點:一個內容未被 git 記錄的文件會被怎樣記錄,一個文件夾又會被怎樣記錄。
下面咱們一步步來,建立一個新的文件,並加入暫存區:
$ echo abc > new.txt $ git update-index --add new.txt $ git ls-files --stage 100644 df7af2c382e49245443687973ceb711b2b74cb4a 0 file.txt 100644 8baef1b4abc478178b004d62031cf7fe6db6f903 0 new.txt
查看緩衝區後,咱們發現新文件的記錄已追加的方式加入了暫存區,並且也對應了一個哈希值。咱們查看一下哈希值的內容:
$ find .git/objects/ -type f .git/objects/5b/dcfc19f119febc749eef9a9551bc335cb965e2 #新的 object .git/objects/8b/aef1b4abc478178b004d62031cf7fe6db6f903 #文件內容爲 version1 的 blob object .git/objects/90/7aa76a1e4644e31ae63ad932c99411d0dd9417 #tree object .git/objects/df/7af2c382e49245443687973ceb711b2b74cb4a #文件內容爲 version2 的 blob object $ git cat-file -p 8bae abc $ git cat-file -t 8bae blob
咱們發現,在把 new.txt 加入到暫存區時,git 自動給 new.txt 的內容建立了一個 blob object。
咱們再嘗試一下建立一個文件夾,並添加到暫存區中:
$ mkdir dir $ git update-index --add dir error: dir: is a directory - add files inside instead fatal: Unable to process path dir
結果 git 告訴咱們不能添加一個空文件夾,須要在文件夾中添加文件,那麼咱們就往文件夾中加一個文件,而後再次添加到暫存區:
$ echo 123 > dir/dirFile.txt $ git update-index --add dir/dirFile.txt
成功了~而後查看暫存區的內容:
$ git ls-files --stage 100644 190a18037c64c43e6b11489df4bf0b9eb6d2c9bf 0 dir/dirFile.txt 100644 df7af2c382e49245443687973ceb711b2b74cb4a 0 file.txt 100644 8baef1b4abc478178b004d62031cf7fe6db6f903 0 new.txt $ git cat-file -t 190a blob
和以前的演示同樣,自動幫咱們爲文件內容建立了一個 blob object。
接下來咱們把當前的暫存區保存成爲一個 tree object:
$ git write-tree dee1f9349126a50a52a4fdb01ba6f573fa309e8f $ git cat-file -p dee1 040000 tree 374e190215e27511116812dc3d2be4c69c90dbb0 dir 100644 blob df7af2c382e49245443687973ceb711b2b74cb4a file.txt 100644 blob 8baef1b4abc478178b004d62031cf7fe6db6f903 new.txt
新的 tree object 保存了暫存區的當前版本信息,值得注意的是,暫存區是以 blob object 的形式記錄dir/dirFile.txt
的,而在保存樹對象的過程當中,git 爲目錄 dir
建立了一個樹對象,咱們驗證一下:
$ git cat-file -p 374e 100644 blob 190a18037c64c43e6b11489df4bf0b9eb6d2c9bf dirFile.txt $ git cat-file -t 374e tree
發現這個爲 dir
目錄而創的樹對象保存了 difFile.txt
的信息,是否是感受似曾類似!這個 tree object 就是對文件目錄的模擬呀!
咱們停停!開始動手!
此次咱們須要實現上述的三條指令:
- git update-index --add
git update-index更新暫存區,官方的這條指令是帶有不少參數的,咱們只實現 --add,也就是添加文件到暫存區。整體的流程是這樣的:若是是第一次添加文件進緩衝區,咱們須要建立一個 index 文件,若是 index 文件已經存在則直接把暫存區的內容讀取出來,注意要有個解壓的過程。而後把新的文件信息添加到暫存區中,把暫存區的內容壓縮後存入 index 文件。
這裏涉及到一個序列化和反序列的操做,請容許我偷懶經過 json 進行模擬ψ(._. )>
。
- git ls-files --stage
git ls-files 用來查看暫存區和工做區的文件信息,一樣有不少參數,咱們只實現 --stage,查看暫存區的內容(不帶參數的 ls-files 指令是列出當前目錄包括子目錄下的全部文件)。實現流程:從 index 文件中讀取暫存區的內容,解壓後按照必定的格式打印到標準輸出。
- git write-tree
git write-tree 用於把暫存區的內容轉換成一個 tree object,根據咱們以前演示的例子,對於文件夾咱們須要遞歸降低解析 tree object,這應該是本章最難實現的地方了。
代碼以下:update-index --add, ls-files --stage, write-tree
感受能夠把 object 抽象一下,因而重構了一下和 object 相關的代碼:refactor object part
當這部分完成後,咱們已經擁有一個可以對文件夾進行版本管理的系統了(ง •_•)ง
。
4.commit object
雖然咱們已經能夠用一個 tree object 來表示整個項目的版本信息了,可是彷佛仍是有些不足的地方:
tree object 只記錄了文件的版本信息,這個版本是誰修改的?是因什麼而修改的?它的上一個版本是誰?這些信息沒有被保存下來。
這個時候,就該 commit object 出場了!怎麼樣,從底層一路向上摸索的感受是否是很爽!?
咱們先用 git 操做一遍,而後再考慮如何實現。下面咱們使用 commit-tree 指令來建立一個 commit object,這個 commit object 指向第三章最後生成的 tree object。
$ git commit-tree dee1 -m 'first commit' 893fba19d63b401ae458c1fc140f1a48c23e4873
因爲生成時間和做者不一樣,你獲得的哈希值會不同,咱們查看一下這個新生成的 commit object:
$ git cat-file -p 893f tree dee1f9349126a50a52a4fdb01ba6f573fa309e8f author liuyj24 <liuyijun2017@email.szu.edu.cn> 1608981484 +0800 committer liuyj24 <liuyijun2017@email.szu.edu.cn> 1608981484 +0800 first commit
能夠看到,這個commit ojbect 指向一個 tree object,第二第三行是做者和提交者的信息,空一行後是提交信息。
下面咱們修改咱們的項目,模擬版本的變動:
$ echo version3 > file.txt $ git update-index --add file.txt $ git write-tree ff998d076c02acaf1551e35d76368f10e78af140
而後咱們建立一個新的提交對象,把它的父對象指向第一個提交對象:
$ git commit-tree ff99 -m 'second commit' -p 893f b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0
咱們再修改咱們的項目,而後建立第三個提交對象:
$ echo version4 >file.txt $ git update-index --add file.txt $ git write-tree 1403e859154aee76360e0082c4b272e5d145e13e $ git commit-tree 1403 -m 'third commit' -p b05c fe2544fb26a26f0412ce32f7418515a66b31b22d
而後咱們執行 git log 指令查看咱們的提交歷史:
$ git log fe25 commit fe2544fb26a26f0412ce32f7418515a66b31b22d Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:36:31 2020 +0800 third commit commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:34:25 2020 +0800 second commit commit 893fba19d63b401ae458c1fc140f1a48c23e4873 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:18:04 2020 +0800 first commit
怎麼樣?是否是有種豁然開朗的感受!
下面咱們停停,把這一部分給實現了。
一共是兩條指令
- commit-tree
建立一個 commit object,讓它指向一個 tree object,添加做者信息,提交者信息,提交信息,再增長一個父節點便可(父節點能夠不指定)。做者信息和提交者信息咱們暫時寫死,這個能夠經過 git config 指令設置,你能夠查看一下.git/config
,其實就是一個讀寫配置文件的操做。
- log
根據傳入的 commit object 的哈希值向上找它的父節點並打印信息,經過遞歸能快速實現。
這裏是個人實現:commit-tree, log
5. references
在前面的四章咱們鋪墊了不少 git 的底層指令,從這章開始,咱們將對 git 的經常使用功能進行講解,這絕對會有一種勢如破竹的感受。
雖然咱們的 commit object 已經可以很完整地記錄版本信息了,可是還有一個致命的缺點:咱們須要經過一個很長的SHA1散列值來定位這個版本,若是在開發的過程當中你和同事說:
嘿!能幫我 review 一下 32h52342 這個版本的代碼嗎?
那他確定會回你:哪。。。哪一個版原本着?(+_+)?
因此咱們要得考慮給咱們的 commit object 起名字,好比起名叫 master。
咱們實際操做一下 git,給咱們最新的提交對象起名叫 master:
$ git update-ref refs/heads/master fe25
而後經過新的名字查看提交記錄:
$ git log master commit fe2544fb26a26f0412ce32f7418515a66b31b22d (HEAD -> master) Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:36:31 2020 +0800 third commit commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:34:25 2020 +0800 second commit commit 893fba19d63b401ae458c1fc140f1a48c23e4873 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:18:04 2020 +0800 first commit
好傢伙(→_→)
,要不咱們給這個功能起個牛逼的名字,就叫分支吧!
這個時候你可能會想,平時咱們在 master 分支上進行提交,都是一個 git commit -m 指令就搞定的,如今背後的原理我彷佛也懂:
- 首先是經過命令 write-tree 把暫存區的記錄寫到一個樹對象裏,獲得樹對象的 SHA1 值。
- 而後經過命令 commit-tree 建立一個新的提交對象。
問題是:commit-tree 指令所用到的的樹對象 SHA1 值,-m 提交信息都有了,可是 -p 父提交對象的 SHA1 值咱們怎麼得到呢?
這就要提到咱們的 HEAD 引用了!你會發現咱們的.git
目錄中有一個HEAD
文件,咱們查看一下它的內容:
$ ls config description HEAD hooks/ index info/ logs/ objects/ refs/ $ cat HEAD ref: refs/heads/master
因此當咱們進行 commit 操做的時候,git 會到 HEAD 文件中取出當前的引用,也就是當前的提交對象的 SHA1 值做爲新提交對象的父對象,這樣整個提交歷史就能串聯起來啦!
看到這裏,你是否是對 git branch 建立分支,git checkout 切換分支也有點感受了呢?!
如今咱們有三個提交對象,咱們嘗試在第二個提交對象上建立分支,一樣先用底層指令完成,咱們使用 git update-ref 指令對第二個提交建立一個 reference:
$ git update-ref refs/heads/bugfix b05c $ git log bugfix commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0 (bugfix) Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:34:25 2020 +0800 second commit commit 893fba19d63b401ae458c1fc140f1a48c23e4873 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:18:04 2020 +0800 first commit
而後咱們改變咱們當前所處的分支,也就是修改 .git/HEAD
文件的值,咱們用到 git symbolic-ref 指令:
git symbolic-ref HEAD refs/heads/bugfix
咱們再次經過 log 指令查看日誌,若是不加參數的話,默認就是查看當前分支:
$ git log commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0 (HEAD -> bugfix) Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:34:25 2020 +0800 second commit commit 893fba19d63b401ae458c1fc140f1a48c23e4873 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:18:04 2020 +0800 first commit
當前分支就切換到 bugfix 啦!
咱們停停,把這部分給實現了,基本都是簡單的文件讀寫操做。
- update-ref
把提交對象的哈希值寫到.git/refs/heads
下指定的文件中。因爲以前 log 指令實現的不夠完善,這裏要重構一下,支持對 ref 名字的查找。
- symbolic-ref
用於修改 ref,咱們就簡單實現吧,對HEAD
文件進行修改。
- commit
有了上面兩條指令打下的基礎,咱們就能夠把 commit 命令給實現了。再重複一遍流程:首先是經過命令 write-tree 把暫存區的記錄寫到一個樹對象裏,獲得樹對象的 SHA1 值。而後經過命令 commit-tree 建立一個新的提交對象,新提交對象的父對象從HEAD
文件中獲取。最後更新對應分支的提交對象信息。
這個是個人實現:update-ref, symbolic-ref, commit
實現到這裏,估計你已經對 checkout,branch 等命令沒啥興趣了,checkout 就是封裝一下 symbolic-ref,branch 就是封裝一下 update-ref。
git 爲了增長指令的靈活性,爲指令提供了很多可選參數,但實際上都是這幾個底層指令的調用。並且有了這些底層指令,你會發現其餘擴展功能很輕鬆地實現,這裏就不展開啦(ง •_•)ง
。
6. tag
完成了上面這些功能,估計你們會對 git 有個較爲深入的認識了,但不知道你們有沒發現一個小問題:
當咱們開發出了分支功能後,咱們會基於分支作版本管理。但隨着分支有了新的提交,分支又會指向新的提交對象,也就是說咱們的分支是變更的。可是咱們總會有一些比較重要的版本須要記錄,咱們須要一些不變的東西來記錄某個提交版本。
又因爲記錄某個提交版本的 SHA1 值不是很好,因此咱們給這些重要的提交版本取個名字,以 tag 的形式進行存儲。估計你們在實現 references 的時候也有留意到.git/refs/
下除了heads
還有一個tags
目錄,其實原理和 reference 同樣,也是記錄一個提交對象的哈希值。咱們用 git 實際操做一下,給當前分支的第一個提交對象打一個 tag:
$ git log commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0 (HEAD -> bugfix) Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:34:25 2020 +0800 second commit commit 893fba19d63b401ae458c1fc140f1a48c23e4873 Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:18:04 2020 +0800 first commit $ git tag v1.0 893f
而後查看一下這個 tag
$ git show v1.0 commit 893fba19d63b401ae458c1fc140f1a48c23e4873 (tag: v1.0) Author: liuyj24 <liuyijun2017@email.szu.edu.cn> Date: Sat Dec 26 19:18:04 2020 +0800 first commit ······
這樣咱們就能經過 v1.0 這個 tag 定位到某個版本了。
這個我就不實現啦,哎(→_→)
。
7. more
這篇文章,我是邊看官方文檔,一邊實現一邊寫的,其實寫到這裏整個 git 的輪廓已經很清晰了。由於 git 自己已經足夠優秀了,咱們也沒有必要重寫一個,本文這種造小輪子的方式意在學習 git 的核心思想,也就是如何搭建一個用於版本管理的 object 數據庫。
其實咱們能夠展望一下 git 的其餘功能(紙上談兵(→_→)
):
- add 指令:其實就是對咱們 update-index 指令的封裝,咱們日常都是直接
add .
把全部修改過的文件添加進緩存區。想要實現這樣的功能能夠遞歸遍歷目錄,使用 diff 工具對修改過的文件執行一次 update-index。 - merge 指令:這個我感受比較難實現,目前思路是這樣的:經過遞歸,藉助 diff 工具,把 merge 項目中多出來的部分追加到被 merge 項目中,若是 diff 指示出現衝突,就讓用戶解決衝突。
- rebase 指令:其實就是修改提交對象的順序,具體實現就是修改它們的 parent 值。相似往鏈表中間插入一個節點或一個鏈表這樣的問題,就是調整鏈表。
- ······
除了這些,git 還有遠程倉庫的概念,而遠程倉庫和本地倉庫的本質是同樣的,不過裏面涉及了不少同步協做的問題。感受如今繼續學 git 的其餘功能輕鬆了一些,更加自信了!
最後是關於本身這個迷你 git 的一些回顧
最後要對本身已經實現的部分做一些總結,和開源代碼比起來有啥要能夠提升改進的地方:
- 沒有實現一個尋址的函數。git 能夠在倉庫的任何目錄下工做,而個人只能工做在倉庫根目錄下。應該實現一個查找當前倉庫下
.git
目錄的函數,這樣整個系統在文件目錄尋址的時候能夠有統一的入口。 - 對 object 的抽象不夠完善。迷你項目只是實現了把版本添加進對象數據庫,不能從對象數據庫中恢復版本,想要實現恢復版本,須要給每一個對象制定相應的反序列化方法,也就是說,object應該實現這樣一套接口:
type obj interface { serialize(Object) []byte deserialize([]byte) Object }
- 目錄分隔符的問題,因爲我用 windows 開發,在 git bash 上測試,全部把分隔符寫死成了
/
,這不太好。 - 目前能夠不停 commit,commit 的時候應該檢查一下暫..存區是否有更新,沒有更新就不讓 commit 了。
- 對命令行參數的判斷有點醜,暫時還沒找到好辦法······
8. end
最後!
感謝閱讀到這裏,有幫助的話不妨點個贊吶!