Git 內幕(一)

前言

git 是一種程序員幾乎天天都會用到的工具,給咱們代碼管理帶去了極大的方便。以往的 git 介紹,可能是介紹git 的高級命令,如git rebsegit cherry-pickergit bisect等,少有看到剖析git 內部原理的。緣由也很簡單,即便對 git 的原理不甚瞭解,也並不會影響咱們熟練使用 git。可是不少事咱們不光要知其然,更要知其因此然,方能觸類旁通。php

概念

在介紹 git 的原理以前,先介紹幾個基本概念和會用到的命令,以便你們在閱讀文章時能更加輕鬆地理解。git

  • BLOB (binary large object):二進制大對象,是一個能夠存儲二進制文件的容器。
  • three git status:
  1. Untracked:文件還未被git add,對應工做區(Working Directory)。
  2. Staged:git add 後所處的狀態, 對應暫存區(Stage)。
  3. Committed:git commite後所處的狀態,對應版本庫(Commit History)。
  • git cat-file:查看 git 對象的瑞士軍刀,能轉譯二進制文件爲可讀文件。
  • git hash-object -w filename:查看到 git 已存儲的數據
  • hexdump -C filename:查看二進制文件的十六進制編碼

.git 內幕

當你在一個新目錄或已有目錄內執行 git init 時,git 會建立一個 .git 目錄,幾乎全部 git 存儲和操做的內容都位於該目錄下。記得在剛接觸 git 時,由於對 stagebranch 等概念不夠了解,還作出過備份整個項目的蠢事。其實若是真要作備份,備份當前的 .git 文件便可。程序員

下圖爲剛初始化的 .git 的目錄結構(不一樣版本的 git 會有所不一樣),幾乎全部的 git 操做和存儲都位於該目錄下。本次真要介紹四個核心文件或目錄:HEAD 及 index 文件,objects 及 refs 目錄。算法

簡而言之,git 從核心上來看不過是簡單地存儲鍵值對(key-value)。它容許插入任意類型的內容,並會返回一個鍵值,經過該鍵值能夠在任什麼時候候再取出該內容。shell

git add

先拋出一個問題:一個空的文件夾是否能添加到 git 項目中?不少人可能都知道答案,可是爲何吶?經過對 git add 背後原理的解析,相信你能夠得出一個確切的答案。bash

  1. 初始化
$ mkdir alpha && cd alpha
$ git init
$ mkdir data
複製代碼

經過執行上面的命令,咱們建立了一個名爲 alpha 的文件夾,而且將其初始化爲一個 git 項目,再新建一個 data 空文件夾。經過執行 git add data 命令但願把 data 空文件夾添加到暫存區中,發現執行後並無發生任何變化,執行 git status 查看倉庫的狀態,拋出這樣一段提示:ide

$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
複製代碼

若是在 data 文件夾內再新建一個空文件夾又會如何吶,發現仍是沒有任何變化。這樣就得出了開頭那個問題的答案,空的文件夾是沒法添加到 git 項目中的。再查看一下 .git 目錄,也沒有發生任何變化。工具

  1. 添加文件夾到暫存區
$ echo 'a' > data/letter.txt
$ git status
Untracked files:
  (use "git add <file>..." to include in what will be committed)

	data/

nothing added to commit but untracked files present (use "git add" to track)
複製代碼

data 目錄下新建一個名爲 letter.text 的文件,寫入 a。發現倉庫的狀態發生了變化,可是 .git 目錄並無發生變化,說明在工做區的操做並不會產生 git 歷史記錄。 執行 git add data 命令,發現 .git 目錄終於發生變化了。 分別多了 index 文件和 objects/78/...文件。ui

以前說過,git 的存儲實際上是以鍵值對的形式存在的。index是一個列表,每一行維護一個文件名到 BLOB 哈希值的映射。objects 目錄下存放的就是 value,這個BLOB文件包含了data/letter.txt 文件壓縮後的內容,並之內容的哈希值做爲文件名。哈希是一段算法,它將給定內容轉換爲更小的,且能惟一肯定原內容的值。 我曾經嘗試直接打開這些文件,可是打開都是一串亂碼。能夠經過十六進制編碼的方式打開這些文件,可是畢竟不是機器,想要看明白仍是有一些難度的🤣。 編碼

幸虧 git 提供了一把瑞士軍刀(git cat-file)給咱們使用,能夠將數據內容取回。 打印出 data/letter.txt 中的內容爲 a

$ git cat-file -p 78981922613b2afb6025042ff6bd878ac1994e85
a
複製代碼

至於 git/index 中的內容,能夠經過 git ls-file 查看。

$ git ls-file --stage
100644 78981922613b2afb6025042ff6bd878ac1994e85 0	data/letter.txt
複製代碼

正如以前所說的,index 文件存放的是一個列表,記錄了哈希值對應的文件。7898 這個 BLOB 文件存儲的是 data/letter.txt 中的內容。objects目錄下的結點是不可變的。這意味着它的內容能夠編輯,但不能刪除。添加的文件內容和建立的提交都保存在 objects 目錄下。(注:git prune 刪除全部不能被ref訪問到的對象,執行此命令可能會丟失數據。)

git commit

$ git commit -m 'a1'
[master (root-commit) b83b660] a1
 1 file changed, 1 insertion(+)
 create mode 100644 data/letter.txt
$ git status
On branch master
nothing to commit, working tree clean
複製代碼

執行 git commit 命令後,文件被保存到了版本庫中,倉庫處於 clean 的狀態。再看看 .git 目錄,發現多了 logs 目錄和 objects 下的三個文件。

提交命令其實包含了三個步驟:

  1. 建立提交版本對應文件的 tree 對象 什麼是 tree 對象,tree 對象能夠存儲文件名,同時也容許存儲一組文件。一個單獨的 tree 對象包含一條或多條 tree 記錄,每一條記錄含有一個指向 BLOB 或子 tree 對象的哈希值。 建立新提交後,對應data目錄的tree對象以下:記錄了 data/letter.txt 文件的全部信息,咱們可使用這些信息來恢復 data/letter.txt 文件。空格分隔的第一部分表示該文件的權限,代表是一個普通文件;第二部分表示該記錄對應的是一個 BLOB 對象,第三部分是該BLOB 的哈希值,第四部分記錄了文件名。
$ git cat-file -p e908cfc6e086c91a073c55a6882adebfc9c4520c

100644 blob 78981922613b2afb6025042ff6bd878ac1994e85	letter.txt
複製代碼

用圖示表式即爲:data目錄對應的 tree 對象指向對應data/letter.txt

那麼問題來了,data 並不是根目錄,文件的指向確定是從根目錄開始。

$ git cat-file -p master^{tree}
040000 tree e908cfc6e086c91a073c55a6882adebfc9c4520c	data
 $ git cat-file -p 9745002f161a1be75bf65f869ab16743da2a6fda

040000 tree e908cfc6e086c91a073c55a6882adebfc9c4520c	data

複製代碼

master^{tree} 表示 master (當前)分支上最新提交指向的 tree 對象。從給出的結果中能夠看到根目錄(root)指向了 data 目錄,圖示以下:

  1. 建立一個提交對象 咱們在提交時輸入了 commit message 等信息,這些信息天然也存放在objects 目錄下:
$ git cat-file -p b83b66096fb5b5225ec0c5741f45d334ceb94046

tree 9745002f161a1be75bf65f869ab16743da2a6fda
author Bill Qiu <fanglaiq@gmail.com> 1536220281 +0800
committer Bill Qiu <fanglaiq@gmail.com> 1536220281 +0800
a1
複製代碼

第一行指向一個tree對象。經過這裏的哈希值,咱們能夠找到對應工做區根目錄(即 alpha 目錄)的 tree 對象,最後一行是提交信息。

  1. 將當前分支指向新提交 到了這裏還存在最後一個問題,分支。 git 怎麼知道如今的提交應該指向哪一個分支?答案在 HEAD 文件中:ref: refs/heads/master。HEAD 指向 master,master 就是咱們當前分支。由於是第一個提交,表明master引用的文件還不存在。git 會自動建立 .git/refs/heads/master ,並寫入提交對象的哈希值:b83b66096fb5b5225ec0c5741f45d334ceb94046

最後,全部 BLOB 文件所存儲的信息都經過 tree 對象串聯了起來,就比如 key-value 的形式。

再來回答下開頭提出的問題吧,一個空的文件夾是否能添加到 git 項目中? 答:不能夠。由於 git 使用的索引機制,是以文件爲最小單位存儲內容,跟蹤變化的。 那麼怎麼作纔可使這個文件夾存在吶?一般的作法是在裏面新建一個名爲 .gitkeep 的文件。

參考連接

相關文章
相關標籤/搜索