--- title: 'Git 組成、原理' disqus: kyleAlien --- Git 組成、原理 === ## Overview of Content 在複雜的系統也是基於一些 **核心** 展開;而 Git 的核心任務就是 **版本管理**,也就是針對專案項目的每個文件進行 **有序的管理** [TOC] ## 版本管理 - 抽象概念 以下圖來做版本管理的抽象講解 > ![](https://hackmd.io/_uploads/Skbpjls3h.png) * 上圖是一個三維立體圖;文件關係如下圖 A 可以當作是一個專案目錄,其目錄下有 B、C、D、E、F... 等等檔案資料夾 > ![](https://hackmd.io/_uploads/rk_h6Xihh.png) ### 版本管理器 - 文件紀錄 * 對於不同版本管理系統,同一節點上的不同版本(像是 Bv0.1、Bv0.2、Bv0.3...),會有不同的紀錄方式,常見方案如下 1. **紀錄差異**:紀錄個文件的版本差異 > 取出指定文件的全部內容較為複雜,需要取得上一個版本做比對 2. **完整紀錄**:每個文件都有單獨的全部紀錄 > **用空間換時間** ### 分布式、集中式、司令副官 * 版本管理系統主要有分為幾種管理方式(分布式、集中式、司令副官),其差異如下 * **集中式** 必須需要有一台服務器的支援才可完成提交、合併... 等等操作,也就是說 **本地端必須要取得與服務器的連接可以進行一系列的操作**! > 如果服務器故障、異常,會導致專案進度停滯 > ![](https://hackmd.io/_uploads/B1jN-Vi33.png) * **分布式** 與分布式不同的地方在,每個本地開發者都可以進行提交、合併... 等等操作,它的角色有如下表 | 角色 | 功能概述 | 補充 | | - | - | - | | `blessed repository` | 主倉庫 | - | | `intrgration manager` | 管理員維護共有的倉庫,有時需要接受 PR(從使用者的公開倉庫拉取) | 最終會推向 `blessed repository` | | `developer public` | 開發者 clone 遠端倉庫到本地 | 從 `blessed repository` clone 檔案 | | `developer private` | 開發者需求開發 | 最終會推向 `developer public` | :::info * **Git 採取分布式管理(但這不意味著 Git 不能使用集中式管理)** ::: > ![](https://hackmd.io/_uploads/Bk-huVi2n.png) * **司令副官** 司令副官(`Dicator and Lieutenants Workflow`)這是一種 **多層級的工作方式**,每個位置(司令、副官、軍長、師團長)都有不同權力,這權力逐漸下放,分而治之 > 它比起分布式多了幾層管理分支,通常用在超巨大項目中 > ![](https://hackmd.io/_uploads/ryRu9Eshh.png) ### 安全雜湊 - SHA-1 * SHA (`Secure Hash Algorithm`) 是美國國家標準技術研究院發布的一系列安全演算法,而 `SHA-1` 則是其一,它的特點如下 * **不可逆**:它是 **不可逆 演算法**,通常拿來檢驗資料的完整性 * **長度固定**:不管多長、多短的數據,經過 `SHA-1` 演算法後,都會得到 160 bit(20 byte) 長度的數據 * **無碰撞**:碰撞的意思是兩個 **不同來源的數據算出相同結果**,而 `SHA-1` 的長度為 160 bit,幾乎不會碰撞,這種低概率我們可視為無碰撞可能 > 當然以數學來講是可能碰撞的 :::success * **Git 採用 `SHA-1` 分別物件** Git 不採用文件名來當作比較,而是使用 `SHA-1` 作為計算每個物件的唯一值計算方式,這可以更加保證文件的唯一性 > 文件名則是保存在 `tree` 中 ::: ## Git 組成 Git 由四個重點組成:`bolb`、`tree`、`commit`、`tag` > ![](https://hackmd.io/_uploads/r1OL4Bi2n.png) ### 檔案 - blob 物件 * 在 Git 中,**Bolb 對象用來存處文件內容**;虛擬概念 Code 如下 ```kotlin= class GitBlob { val blob_sha_1 val content } ``` > ![](https://hackmd.io/_uploads/H1_aEBs3h.png) * 而 **Bolb 的文件名** 取得方式如下 1. 將 文件內容進行 `SHA-1` 計算,獲得 160 bit 的數據 2. 對 160 bit 的數據進行校驗、運算 3. **取前兩個字符(16 bit)建立目錄**,剩下的字符 (144 bit) 作為 blob 文件名 :::info * 版本差異儲存: 對於 Git 來說,它會保存每個文件版本的全部數據,而不是只保有差異數據,所以 git 對於空間的需求較高,但相對的速度較快 ::: * **Blob `SHA-1` Code**:Git 對於一個 Blob 對象,使用一個 **全局的 `SHA-1` Code 紀錄該檔案**,透過 `SHA-1` Code 也可以看到該檔案的完整內容 以下測試創建一個文件並 commit 後,查看該檔案的 `SHA-1` Code (並非查看 commit 的 `SHA-1`) 1. 創建文件、commit ```shell= echo "HelloWorld" >> myFile.txt git add . git commit -m "feat: first commit" ``` 2. **查看文件的 `SHA-1` code**:可以透過以下兩個指令查看 | 指令 | 功能 | | - | - | | `git hash-object [檔案]` | 計算指定檔案的 SHA-1 Code | | `git ls-tree <分支/Patch>` | 列出當中節點中所有的檔案的 SHA-1 Code | ```shell= git ls-tree master git ls-tree HEAD git hash-object myFile.txt git show 3da1ec26e9c8512eae062868a9ff9bae47e5625b ``` > ![](https://hackmd.io/_uploads/Hy9HjBs23.png) ### 資料夾 - tree 物件 * 在 Git 中,tree 物件可以記錄多個 blob 物件 (**類似一個目錄概念**),而找到 blob 物件 的方式則是透過 `SHA-1` Code;虛擬概念 Code 如下 > 組成的是一個無環結構 Tree ```kotlin= class GitTree { val tree_sha_1 val list = List<Blob SHA-1 ID> } class GitBlob { val blob_sha_1 val content } ``` > ![](https://hackmd.io/_uploads/HJzLhSs3h.png) * **Tree `SHA-1` Code**:Tree 對象也有自身的 `SHA-1` Code 以下我們來查看 Tree 的 `SHA-1` Code 1. 創建一個資料夾,並當原本的文件放入到目錄中 ```shell= mkdir myDir mv myFile.txt ./myDir/ git add . git commit -m "feat: create a dir" ``` > ![](https://hackmd.io/_uploads/SyHvkUohn.png) 2. 除了使用上小節的範例,我們還可以使用 `cat-file` 指令查看類型、內容... | 指令 | 功能 | | - | - | | `git hash-object [檔案]` | 計算指定檔案的 SHA-1 Code | | `git ls-tree <分支/Patch>` | 列出當中節點中所有的檔案的 SHA-1 Code | | `git cat-file -t <hash id>` | 該 SHA-1 Code 是屬於哪一種類型的物件 | | `git cat-file -p <hash id>` | 該 SHA-1 Code 是內容 | ```shell= # 可以發現 Tree 的 SHA-1 Code git ls-tree HEAD # 原本沒變內容的檔案,有著相同的 SHA-1 Code git hash-object ./myDir/myFile.txt # tree 類型 git cat-file -t 6c77dfbe87cd854f5db2a24a4818821304be7ab5 # 內容 git cat-file -p 6c77dfbe87cd854f5db2a24a4818821304be7ab5 ``` > ![](https://hackmd.io/_uploads/rkx01Uohn.png) ### 包裹 - commit 物件 * Commit 物件代表的是我們每一次下達 `commit` 指令的相關訊息,它可以包含多個 Tree、Blob 物件(也包含了 author 作者、commiter 提交者... 等等訊息);虛擬概念 Code 如下 ```kotlin= class GitCommit { val commit_sha_1 val auth val commiter val tree_list = List<Tree SHA-1 ID> val blob_list = List<Blob SHA-1 ID> } class GitTree { val tree_sha_1 val list = List<Blob SHA-1 ID> } class GitBlob { val blob_sha_1 val content } ``` > ![](https://hackmd.io/_uploads/ryJ1t8sn3.png) * Commit 與 Commit 之間是 **使用 Link 鏈式的方式串接**,可以透過 `parent` 來尋找到上一個 Commit 紀錄;可以透過以下指令查看 commit paraent id 1. 方法一: ```shell= git log --parents ``` > ![](https://hackmd.io/_uploads/HkBu9Li32.png) 2. 方法二: ```shell= git show -s --pretty=raw b752003d7a74ad4664ea380f0d08e5b1f14300bb ``` > ![reference link](https://hackmd.io/_uploads/SJ635Uj33.png) ### 方便記憶 - tag 物件 * 就如同網路上我們不會直接記 IP 而是會透過 DNS 服務一樣,我們也不特別去記 `SHA-1` Code,而是透過 TAG 的方式幫我們記住(記住某個節點) 一個 TAG 物件通常會包含以下訊息 | tag 儲存資訊 | 說明 | | - | - | | `Object` | tag 也有一個屬於自身的 `SHA-1` Code | | `Type` | 物件的類型 | | `Tag` | tag 的名稱 | | `Tagger` | tag 創建者 | | `Message` | tag 額外訊息 | | `Signature` | 創建 tag 時,可以選擇是否簽名 | * 簡單下 tag 的範例 ```shell= git tag "FirstTag" 1329206 git log --oneline -5 git tag "FirstTag" HEAD~1 git log --oneline -5 git tag d "SecondTag" git log --oneline -5 ``` > ![](https://hackmd.io/_uploads/B1o80Ij3n.png) :::warning * **tag 資訊存放在哪**? 對 tag 的具體操作會對應在 `.git` 目錄下的 `ref/tags` 中,每個 tag 都是一個獨立的對象 ```shell= ls -laF ./.git/refs/tags/ ``` > ![](https://hackmd.io/_uploads/SyepCLj23.png) ::: ## Git 三個區域 Git 中的所有文件都有 4 種狀態(`Untracked`、`Unmodified`、`Modofied`、`Staged`),而不同狀態下都有不同的文件儲存位置 > ![](https://i.imgur.com/0AC0JQj.png) 在 Git 管理中,維護了 **三個特殊區域來儲存工作**:**工作目錄(`Working Directory`)**、**暫存區(`Staging area`)**、**倉庫(`Repository`)** > 官方並沒有嚴格的限制,所以並非我們直觀想的三個工作區域配對三個獨立儲存區 ### 工作目錄 * 一般情況下就 **工作目錄(`Working Directory`)就是專案位置**,它與 `.git` 目錄同級,其中存放著開發者可以直接修改的眾多文件! > ![](https://hackmd.io/_uploads/rJq0Kwon2.png) ### 暫存區 * 暫存區域(`Staging Area`)其實是一個 **文件**,它對應的是 `.git` 目錄下的 index 文件 並且在我們 **操作 `git add` 指令後,git 會把文件的狀態切為 `UnModify` 並把內容寫入到 `.git/index` 文件中**;實驗如下(細細觀察,檔案的大小、內容會變) ```shell= # 添加前(`Modify`)查看 index 檔案 echo "Hello git" >> say.txt ls -laF .git/index cat .git/index # 進行添加後(`Unmodify`)再查看一次 git add . ls -laF .git/index cat .git/index ``` > ![](https://hackmd.io/_uploads/HywqqPjh2.png) ### 倉庫 - 壓縮 * 這裡說的倉庫(`Repository`),是開發者的本地倉庫,它會被 **存在 `.git/objects` 目錄下**,對於一個大項目來說會有很多檔案 > ![](https://hackmd.io/_uploads/BJ1E3Ponh.png) * 由於 `.git/objects` 目錄下會儲存很多檔案,如果沒有特別處理會導致 **傳輸效率低**,所以檔案在傳輸時會進行壓縮! * 所以一般來說一開始我們 clone 下來的檔案會先將資料放在 `info`、`pack` 資料夾下 > ![](https://hackmd.io/_uploads/ByJk1_ihh.png) * 我們也可以自己手動壓縮自己的 commit ```shell= git gc ``` > ![](https://hackmd.io/_uploads/r1eUydi3n.png) ## Appendix & FAQ :::info ::: ###### tags: `Git`