主頁 > 後端開發 > git原理學習記錄:從基本指令到背后原理,實作一個簡單的git

git原理學習記錄:從基本指令到背后原理,實作一個簡單的git

2020-12-29 06:41:21 後端開發

好家伙~

一開始我還擔心 git 的原理會不會很難懂,但在閱讀了官方檔案后我發現其實并不難懂,似乎可以動手實作一個簡單的 git,于是就有了下面這篇學習記錄,

本文的敘述思路參照了官方檔案Book的原理介紹部分,在一些節點上探討代碼實作,官方檔案鏈接,

看完本文你能:1. 了解 git 的設計思想,2. 識訓一點快樂?

編程語言選擇了 go,因為剛學不太熟悉想多使用一下,

這是我的倉庫地址,但如果你和我一樣是初學,直接看代碼可能不能快速上手,推薦順著文章看,

迷你git實作--鏈接

如果文章看得吃力可以跟著官方檔案的原理部分操作一次再回頭看,可能更易懂?

1. init

在學習 git 原理之前,我們先忘掉平時用的 commit,branch,tag 這些炫酷的 git 指令,后面我們會摸清楚它們的本質的,

要知道,git 是 Linus 在寫 Linux 的時候順便寫出來的,用于對 Linux 進行版本管理,所以,記錄檔案專案在不同版本的變更資訊是 git 最核心的功能,

大牛們在設計軟體的時候總是會做相應的抽象,想要理解他們的設計思路,我們就得在他們的抽象下進行思考,雖然說的有點玄乎,但是這些抽象最終都會落實到代碼上的,所以不必擔心,很好理解的,

首先,我們要奠定一個 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目錄后我們能看到infopack兩個目錄,不過這和核心功能無關,我們只需要知道現在.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,

到這里我們停停,把這部分實作了吧,

我一開始有點好奇,為啥查看 object 不直接用 cat 指令,而是自己編了一條 git cat-file 指令呢?后來想了一下,git 肯定不會把檔案的內容原封不動保存進 object ,應該是做了壓縮,所以我們還要專門的指令去解壓讀取,

這兩條指令我們參照官方的思路進行實作,先說 git hash-object,一個 object 存盤的內容是這樣的:

  1. 首先要構造頭部資訊,頭部資訊由物件型別,一個空格,資料內容的位元組數,一個空位元組拼接而成,格式是這樣:
blob 9\u0000
  1. 然后把頭部資訊和原始資料拼接起來,格式是這樣:
blob 9\u0000version1
  1. 接著用 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 就是對檔案目錄的模擬呀!

我們停停!開始動手!

這次我們需要實作上述的三條指令:

  1. git update-index --add

git update-index更新暫存區,官方的這條指令是帶有很多引數的,我們只實作 --add,也就是添加檔案到暫存區,總體的流程是這樣的:如果是第一次添加檔案進緩沖區,我們需要創建一個 index 檔案,如果 index 檔案已經存在則直接把暫存區的內容讀取出來,注意要有個解壓的程序,然后把新的檔案資訊添加到暫存區中,把暫存區的內容壓縮后存入 index 檔案,

這里涉及到一個序列化和反序列的操作,請允許我偷懶通過 json 進行模擬ψ(._. )>

  1. git ls-files --stage

git ls-files 用來查看暫存區和作業區的檔案資訊,同樣有很多引數,我們只實作 --stage,查看暫存區的內容(不帶引數的 ls-files 指令是列出當前目錄包括子目錄下的所有檔案),實作流程:從 index 檔案中讀取暫存區的內容,解壓后按照一定的格式列印到標準輸出,

  1. 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 <[email protected]> 1608981484 +0800
committer liuyj24 <[email protected]> 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 <[email protected]>
Date:   Sat Dec 26 19:36:31 2020 +0800

    third commit

commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0
Author: liuyj24 <[email protected]>
Date:   Sat Dec 26 19:34:25 2020 +0800

    second commit

commit 893fba19d63b401ae458c1fc140f1a48c23e4873
Author: liuyj24 <[email protected]>
Date:   Sat Dec 26 19:18:04 2020 +0800

    first commit

怎么樣?是不是有種豁然開朗的感覺!

下面我們停停,把這一部分給實作了,

一共是兩條指令

  1. commit-tree

創建一個 commit object,讓它指向一個 tree object,添加作者資訊,提交者資訊,提交資訊,再增加一個父節點即可(父節點可以不指定),作者資訊和提交者資訊我們暫時寫死,這個可以通過 git config 指令設定,你可以查看一下.git/config,其實就是一個讀寫組態檔的操作,

  1. 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 <[email protected]>
Date:   Sat Dec 26 19:36:31 2020 +0800

    third commit

commit b05c65b6fdd7e13a51aaf1abb8ff3e795835bfb0
Author: liuyj24 <[email protected]>
Date:   Sat Dec 26 19:34:25 2020 +0800

    second commit

commit 893fba19d63b401ae458c1fc140f1a48c23e4873
Author: liuyj24 <[email protected]>
Date:   Sat Dec 26 19:18:04 2020 +0800

    first commit

好家伙(→_→),要不我們給這個功能起個牛逼的名字,就叫分支吧!

這個時候你可能會想,平時我們在 master 分支上進行提交,都是一個 git commit -m 指令就搞定的,現在背后的原理我似乎也懂:

  1. 首先是通過命令 write-tree 把暫存區的記錄寫到一個樹物件里,得到樹物件的 SHA1 值,
  2. 然后通過命令 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 <[email protected]>
Date:   Sat Dec 26 19:34:25 2020 +0800

    second commit

commit 893fba19d63b401ae458c1fc140f1a48c23e4873
Author: liuyj24 <[email protected]>
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 <[email protected]>
Date:   Sat Dec 26 19:34:25 2020 +0800

    second commit

commit 893fba19d63b401ae458c1fc140f1a48c23e4873
Author: liuyj24 <[email protected]>
Date:   Sat Dec 26 19:18:04 2020 +0800

    first commit

當前分支就切換到 bugfix 啦!

我們停停,把這部分給實作了,基本都是簡單的檔案讀寫操作,

  1. update-ref

把提交物件的哈希值寫到.git/refs/heads下指定的檔案中,由于之前 log 指令實作的不夠完善,這里要重構一下,支持對 ref 名字的查找,

  1. symbolic-ref

用于修改 ref,我們就簡單實作吧,對HEAD檔案進行修改,

  1. 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 <[email protected]>
Date:   Sat Dec 26 19:34:25 2020 +0800

    second commit

commit 893fba19d63b401ae458c1fc140f1a48c23e4873
Author: liuyj24 <[email protected]>
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 <[email protected]>
Date:   Sat Dec 26 19:18:04 2020 +0800

    first commit

······

這樣我們就能通過 v1.0 這個 tag 定位到某個版本了,

這個我就不實作啦,哎(→_→)

7. more

這篇文章,我是邊看官方檔案,一邊實作一邊寫的,其實寫到這里整個 git 的輪廓已經很清晰了,因為 git 本身已經足夠優秀了,我們也沒有必要重寫一個,本文這種造小輪子的方式意在學習 git 的核心思想,也就是如何搭建一個用于版本管理的 object 資料庫,

其實我們可以展望一下 git 的其他功能(紙上談兵(→_→)):

  1. add 指令:其實就是對我們 update-index 指令的封裝,我們平常都是直接add .把所有修改過的檔案添加進快取區,想要實作這樣的功能可以遞回遍歷目錄,使用 diff 工具對修改過的檔案執行一次 update-index,
  2. merge 指令:這個我感覺比較難實作,目前思路是這樣的:通過遞回,借助 diff 工具,把 merge 專案中多出來的部分追加到被 merge 專案中,如果 diff 指示出現沖突,就讓用戶解決沖突,
  3. rebase 指令:其實就是修改提交物件的順序,具體實作就是修改它們的 parent 值,類似往鏈表中間插入一個節點或一個鏈表這樣的問題,就是調整鏈表,
  4. ······

除了這些,git 還有遠程倉庫的概念,而遠程倉庫和本地倉庫的本質是一樣的,不過里面涉及了很多同步協作的問題,感覺現在繼續學 git 的其他功能輕松了一些,更加自信了!

最后是關于自己這個迷你 git 的一些回顧

最后要對自己已經實作的部分作一些總結,和開源代碼比起來有啥要可以提高改進的地方:

  1. 沒有實作一個尋址的函式,git 可以在倉庫的任何目錄下作業,而我的只能作業在倉庫根目錄下,應該實作一個查找當前倉庫下.git目錄的函式,這樣整個系統在檔案目錄尋址的時候可以有統一的入口,
  2. 對 object 的抽象不夠完善,迷你專案只是實作了把版本添加進物件資料庫,不能從物件資料庫中恢復版本,想要實作恢復版本,需要給每個物件制定相應的反序列化方法,也就是說,object應該實作這樣一套介面:
type obj interface {
	serialize(Object) []byte
	deserialize([]byte) Object
}
  1. 目錄分隔符的問題,由于我用 windows 開發,在 git bash 上測驗,所有把分隔符寫死成了/,這不太好,
  2. 目前可以不停 commit,commit 的時候應該檢查一下暫存區是否有更新,沒有更新就不讓 commit 了,
  3. 對命令列引數的判斷有點丑,暫時還沒找到好辦法······

8. end

最后!

感謝閱讀到這里,有幫助的話不妨點個贊吶!(? ?_?)?

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/241697.html

標籤:Go

上一篇:一個20年技術老兵的 2020 年度技術總結

下一篇:[GO] golang happen before 的保證原則

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more