前言
做技術一定要知其然知其所以然,意思就是:知道它是這樣的,更知道它爲什麼是這樣的。我主要通過4塊內容來簡單介紹 Git 原理是什麼樣的。這4塊內容如下:
- Git 存儲目錄結構介紹
- Git 是如何存儲的
- Git 的對象
- Git引用
當然 Git 原理不僅僅包含這些,想要更深入瞭解請查看官方教程 https://git-scm.com/book/zh/v2/。
本文內容是我在 GitChat 分享關於Git 的Chat 《Git實用操作手冊》 摘抄一個章節,關於《Git實用操作手冊》其他內容請訪問 https://gitbook.cn/gitchat/activity/5cb46e9dd877c443a183f9d4。
Git 存儲目錄結構介紹
首先我們先從 Git 存儲目錄說起,通過 git init 創建一個空的 Git 倉庫,具體操作如下圖:
創建完成後進入 .git 目錄,如下圖所示:
- hooks 該目錄用於配置 客戶端執提交操作用於觸發服務端的腳本配置,一般用於自動化部署使用
- info 該目錄用於配置一些不希望被 Git 管控的文件。
- objects 該目錄用於存儲所有數據對象內容,這些數據內容類型有 commit tree blob tag
- refs 該目錄用於存儲 Git 本地以及遠程分支的引用,當然還有一種特殊的引用標籤引用
- config 該文件包含項目特有的配置選項,並且該配置僅對該 Git 倉庫有效
- description 該文件僅供 GitWeb 程序使用
- HEAD 該文件表示當前 Git 倉庫處於哪個分支
- index 該文件保存暫存區信息 (空倉庫下該文件不會顯示一旦執行 git add 操作該文件就會出現)
通過 git config --local 查看 config 文件的變化
我們通過 git config --local 配置僅對於 gitLearn 項目有效,用戶名和郵箱配置如下圖所示:
查看 config 文件,會發現該文件新增用戶信息配置。
Git 是如何存儲的
Git 是一個內容尋址文件系統
其核心部分是一個簡單的鍵值對數據庫(key-value data store)。 你可以向該數據庫插入任意類型的內容,它會返回一個 40 位字符串鍵,通過該 40 位字符串鍵可以在任意時刻再次檢索(retrieve)該內容。
什麼是內容尋址?
每次我們進行提交會通過 SHA-1 算法生成一個長度爲 40 個字符的校驗和(checksum hash)
(也就是我們的 key)然後根據校驗和
去獲取我們文件的內容。這種通過唯一標識的 key(也可以理解爲內容的地址)去獲取我們的內容的操作就是內容尋址
。
15.4 Git 的對象
在 Git 中有四種對象分別爲:
- blob 是具體的文件對象
- tree 是某個時刻提交目錄的內容
- commit 執行一次 commit 就會產生一個 commit 對象
- tag 可以理解成 commit 的別名,一個 tag 對應一個 commit
瞭解 Git 的對象需要使用如下命令進行查看:
- git cat-file -p 對象 hash 值 查看對象的內容
- git cat-file -t 對象 hash值 查看對象的類型
- git ls-files --stage 查看 index 文件內容
- git hash-object 查看文件的 hash 值
查看 commit tree blob 三個對象演示
我們創建一個 first.txt 文件,並將其提交到暫存區中。
進入 .git 文件夾下會發現新增了一個 index 文件。
我們可以通過 git ls-files --stage 查看 index 文件的內容。
進入 objects 目錄發現 9c 文件夾名稱+文件名稱 和 index 文件中的一段字符串內容相同。
我們通過 git cat-file -t 9c59e24b8393179a5d712de4f990178df5734d99 我查看該表示對象類型 如下圖所示表示該標識對象類型是 blob。
執行 git commit -m 將 first.txt 文件提交到本地倉庫中。
執行 git log 查看我們的提交記錄。
如下圖所示我們通過 git cat-file -p commitId 查看我們提交的內容。如下圖所示:我們最新一次提交包含了一個 59b06 開頭的 tree 對象。
在 ./git/objects 目錄中可以找到我們對應的文件。
我們通過 git cat-file -p tree對象哈希值,查看該 tree 對象的內容 。如下圖所示顯示就是我們 git add參生的 blob對象。
在通過 git cat-file -p blob對象哈希值,查看我們 blob對象內容,如下圖所示 blob對象 內容就是我們 first.txt 文件的內容。
我們將 first.txt 文件提交到本地倉庫 會產生一個 commit 一個 tree 和一個 blob 對象。
tag 對象原理演示
首先我們通過 git log 查看最新的提交是 add a.txt 註釋的 commit 如下圖所示。
通過 git tag -a v1.1 -m ‘add a.txt tag’ 爲該 commit 創建一個附註標籤。
在我們的 .git/refs/tags/ 目錄下會新增 v1.1 文件。
v1.1 內容如下:
看到這個你肯定想到了這是一個 Git 對象,我們通過 git cat-file -t 查看這個哈希值對象類型。如下圖所示它是一個 tag。
然後通過 git cat-file -p 查看它的內容,如下圖所示,該tag包含了commit 對象和 標註的信息。
Git 引用
什麼是 Git 的引用?
這裏的引用我們可以理解成一個書籤,你在閱讀一本書的時候可以爲你讀到的部分打上標籤。然後你可以通過這個書籤快速找到你之前閱讀的位置。在 Git 中我們 創建新的 Commit 或者創建分支都會進行一次打標籤的操作。我們各個不同 commit 之間的切換或分支的切換其實就是標籤的切換。
Git 引用的三種類型
在 Git 中有三種類型的的引用 分別是:
- HEAD 引用:用來爲我們的本地倉庫打上標籤使用。
- Tag 引用:用來爲我們的 Git 倉庫 tag 標籤使用。
- 遠程引用:用來爲我們的遠程倉庫打標籤使用。
HEAD 引用
HEAD 引用原理 我的個人理解是一個 Head 頭指針+當前分支 指向當前最新提交的 commit 對象。我們也可以通過 git reset --hard 來切換我們commit 記錄,切換後的 commit 以前的 commit 記錄就沒有了,不過我們可以通過 git reflog 查詢操作記錄將以前 commit 找回。
爲了更好的理解 HEAD,引用建議大家訪問 http://onlywei.github.io/explain-git-with-d3/#zen 執行 Git 相關的操作來理解什麼是 HEAD引用。
如下圖所示,我們的 Head頭指針分支 指向 master分支 同時指向最新的 commit e137e。
我們執行一次 commit Head頭指針分支 + master分支 就指向的是最新的提交 896ee 上。
通過 git branch b1 創建新的分支名稱爲b1,如下圖所示,我們發現 Head 頭指針還是指向 master 。
當執行 git checkout b1 的時候,如下圖所示,此時 Head 指針指向分支 b1。
我們在分支 b1 上執行 commit Head 指針指向了在該分支下的最新提交 3c7acb。
當執行 git checkout master 是 Head 指針指向了 master 上最新的 commit 896ee。
當執行 git merge b1 的時候,會將 b1分支最新 commit 合併到 master,我們 Head指針 + master 同時指向最新的 commit 3c7ba 上。
通過上面的動態演示,這回對 Head 應用如何切換 commit 切換分支有了一定的理解。
這裏非常建議你自己通過訪問
http://onlywei.github.io/explain-git-with-d3/#zen
來親自體驗一下。
接下來我們來通過查看 .git 目錄來介紹 Head 指針是如何實現的。進入 .git 目錄你會發現有一個 HEAD 文件,它的內容是 ref: refs/heads/master,如下圖所示。
refs/heads/master 是具體文件路徑,我們查 refs/heads 目錄下的 master 文件內容,如下圖所示,master 文件內容就是我們最新的 commit。
我們執行 git chekcout demoBranch3。
此時我們的 HEAD 文件內容變成了 ref: refs/heads/demoBranch3。
查看 refs/heads/ 路徑下的 demoBranch3 文件,如下圖所示 demoBranch3 文件記錄是demoBranch3 分支下最新的 commit。
標籤引用
標籤引用是一個特殊的引用,它不像 HEAD 引用可以通過 git checkout 來更改引用的指針指向。標籤引用不會移動它永遠會指向一個 Commit 對象。
標籤引用的演示請參看 Tag 對象原理演示部分。
遠程引用
-
遠程引用是隻讀的我們不能通過切換到遠程引用執行 commit 將提交更新到遠程倉庫中
-
我們在本地分支執行 push 操作,Git 都會幫我們記錄 push 到遠程分支的最新 comit,當執行push 的時候,如果發現遠程分支最新 commit 和我們本地倉庫記錄最後一次 push 的 commit 不同會報 Note about fast-forwards 異常,如下圖所示:
出現 Note about fast-forwards 我們需要執行 git pull 將遠程最新的 commit 拉取下來 然後再執行 git push 操作 或者 直接執行 git puhs -f。這裏強調一下不建議執行 git puhs -f 操作因爲會強制將本地的歷史記錄覆蓋到遠程倉庫的歷史記錄。
通過查看 .git 目錄理解遠程引用
在 .git/refs/remotes/origin 文件夾中 HEAD 文件內容表示內容表示遠程分支處於哪個分支。
在 .git/refs/remotes/origin 文件夾中 master 文件內容,表示最後一次 push 到遠程倉庫的commit,如下圖所示:
在 .git/refs/remotes/origin 文件夾中 demoBranch1 文件表示,最後一次 push 到 demoBranch1 分支提交的 commitId。