git原理-本地倉庫認識
項目人員使用git,幾乎70%的工作都是在本地倉庫完成的。由此可見本地倉庫的重要性。
下面我們就通過一些基本的命令講下git的本地倉庫的結構,存儲流程,數據類型,如何存儲……
倉庫結構
大家都曉得提交文件需要先git add 再Git commit。為了曉得add到哪裡了,commit到哪裡了。需要知道git倉庫結構:工作區+暫存區+版本庫。
工作區:就是你在電腦里能看到的項目目錄。你所有本地的改動都是在工作區改動的。工作區是對項目的某個版本獨立提取出來的內容。 是從 Git 倉庫的壓縮資料庫中提取出來的文件,放在磁碟上供你使用或修改。
暫存區:暫存區是一個文件,一般在 Git 倉庫目錄中的index文件(.git/index)中,保存了下次將要提交的文件列表資訊。 git ls-files –stage可查看暫存區內容。保存了所有文件對應的索引,即SHA1值。所以 Git 的術語叫做「索引」,不過一般說法還是叫「暫存區」。
版本庫:.git目錄是 Git 用來保存項目的元數據,版本,分支的地方。 這是 Git 中最重要的部分,從其它電腦克隆倉庫時,複製的就是這裡的數據。其中 .git/objects目錄,被稱為對象資料庫。具體的目錄詳情看下圖。
項目文件的狀態
項目文件有四種狀態,git可以管理的有三種狀態: 已提交(committed)、已修改(modified) 和 已暫存(staged)
第4個狀態:未跟蹤。
-
已修改:表示修改了文件(該文件曾經添加過版本庫),但還沒保存到資料庫中。
-
已暫存:表示對一個已修改文件的當前版本做了標記,使之包含在下次提交的快照中。
-
已提交:表示數據已經安全地保存在本地資料庫中(所以也可稱為未修改狀態)。
- 未跟蹤:新創建的文件如果沒有添加到暫存區,那git沒辦法對其跟蹤,故而是『未跟蹤』狀態。
項目文件狀態與遷移:
當對工作區修改(或新增)的文件執行 git add
命令時,暫存區的目錄樹被更新,同時工作區修改(或新增)的文件內容被寫入到對象庫中的一個新的對象中,而該對象的ID被記錄在暫存區的文件索引中。
當執行提交操作git commit
時,暫存區的目錄樹寫到版本庫(對象庫)中,master 分支會做相應的更新。即 master 指向的目錄樹就是提交時暫存區的目錄樹。
當執行 git reset HEAD
命令時,暫存區的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區不受影響。
當執行 git rm --cached <file>
命令時,會直接從暫存區刪除文件,工作區則不做出改變。
當執行git checkout .
或者 git checkout -- <file>
命令時,會用暫存區全部或指定的文件替換工作區的文件。這個操作很危險,會清除工作區中未添加到暫存區的改動。
當執行 git checkout HEAD ." 或者 "git checkout HEAD <file>
命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區和以及工作區中的文件。這個命令也是極具危險性的,因為不但會清除工作區中未提交的改動,也會清除暫存區中未提交的改動。
項目文件遷移過程拆解
概念了解後,我們實際操作看下git文件管理流程~
一 創建一個git倉庫
- 創建一個目錄,進入目錄
- 通
過git init
命令把這個目錄變成Git可以管理的倉庫 - ls -a 可發現多了一個.git目錄。這個目錄就是Git用來跟蹤管理版本庫的,所謂的git版本庫
平平無奇的目錄/deploy/script/ 搖身一變成了一個git倉庫了~~~
或者Git clone一個已有的遠程倉庫。拷貝的是.git目錄。然後解壓默認指定的master版本數據到工作區。
二 git add 過程拆解
提交文件到暫存區。這一步其實主要包含兩個步驟:
1. git hash –object <file>
工作區修改(或新增)的文件內容被寫入到對象庫中的一個新的對象中
-
生成SHA-1值,並在版本庫的objects目錄下生成目錄跟文件。用SHA-1[0:2]命名目錄,SHA[2:40]命名文件名。
- 存儲文件壓縮後的數據。可通過
git cat-file -p SHA-1
查看object文件內容
2. git update-index <file>
更新index文件中提交文件的SHA-1值,也就是文件指向的objects地址。git ls-files –stage 可查看暫存區(index文件)的內容
三 git commit 過程拆解
git commit提交過程也可分為兩個步驟:
- 計算每一個子目錄(項目根目錄)的校驗和,然後在Git 倉庫中將這些目錄保存為樹(tree)對象。子目錄中包含的是blob對象。
- 相關提交資訊+指向頂層樹對象(項目根目錄)的指針 合併生成一個commit 對象。如此它就可以在將來需要的時候,重現此次快照的內容了。
- 當前 分支會做相應的更新(.git/refs/heads 下文會詳細說明)
git cat-file -p SHA-1 可查看objects文件下存儲的內容,包括commit對象,和tree對象。
git cat-file -t SHA-1 可查看文件類型
例如:一個項目里假如有三個文件ReadME,LICENSE,test.rb,commit後倉庫里會保存五個對象。(如果項目不只三個文件,還有子目錄的話,那每個目錄都會生成一個tree對象。)
對象模型
塊-blob
也叫數據對象,文件的每一個版本表示為一個塊(blob)。一個blob保存一個文件的數據,但不包含任何關於這個文件的元數據,甚至連文件名也沒有。
目錄樹-tree
樹對象,一個目錄樹對象代表一層目錄資訊,它記錄blob標識符、路徑名和在一個目錄里所有文件的一些元數據。
提交-commit
提交對象,一個提交對象保存版本庫中一次變化的元數據
格式很簡單:它先指定一個頂層樹對象,代表當前項目快照; 然後是可能存在的父提交(前面描述的提交對象並不存在任何父提交); 之後是作者/提交者資訊(依據你的 user.name 和 user.email 配置來設定,外加一個時間戳); 留空一行,最後是提交注釋。
標籤-tag
標籤對象,一個標籤對象分配一個任意的且人類可讀的名字給一個特定對象,通常是一個提交對象。
對象存儲
你向 Git 倉庫提交的所有對象都會有個頭部資訊一併被保存 git hash –object <file>
Git 首先會以識別出的對象的類型作為開頭來構造一個頭部資訊。 接著 Git 會在頭部的第一部分添加一個空格,隨後是數據內容的位元組數,最後是一個空位元組(null byte):
比如數據對象 content = “hello world” 類型是『blob』
頭部存儲方式:類型+空格+位元組數+空位元組
頭部資訊是:”blob 11\u0000″
Git 會將上述頭部資訊和原始數據拼接起來
存儲的對象是:”blob 11\u0000hello world”,並計算出這條新內容的 SHA-1 校驗和
壓縮新的數據對像:
SHA-1用來做存儲對象的指針,指向的是.git/objects下的存儲對象。
目錄跟文件名對應SHA-1前2個字元和後38個字元。
存儲的文件內容是壓縮後的數據。
(commit,tree對象存儲同理blob對象。)
本地分支
一 分支存儲目錄
.git/refs/heads/
在我們初始化一個git倉庫的時候,會分配一個指針HEAD(.git/HEAD)表示當前分支的意思。且HEAD默認指向master分支 .git/refs/heads/master
如果切換分支為dev,HEAD就指向.git/refs/heads/dev了。由此可見,所有的分支都是存儲在 .git/refs/heads/目錄下的。同樣也解釋了,為何git切換分支如此迅速,因為知識更改了一個指針。
二 分支存儲的內容
存儲內容是分支最新一次的commit對象。
比如我們從master切換一個分支dev出來,可以看到master的commit對象跟dev的commit對象是完全一樣的。
那如果修改下dev分支的文件內容提交,會發生什麼呢?沒錯,dev分支的內容發生變更了,變成提交對象4b39a了。而master的內容沒變還是之前的。可以git cat-files -p 4b39af8 查看版本快照
三 分支合併機制
我們切到master,合併dev分支。看到提示的內容,從f3c68…變成了4b39a….。也就是說簡單的合併分支,只是把master的提交對象換成了dev的提交對象了。其他什麼也沒動。
這個『快速合併』稱為fast-forward合併策略。如果不希望直接合併可以:git merge –no-ff。
下面這種情況:master分支跟new分支都有改動,就不能快速合併。合併會會生成一個新的commit對象,父對象指向base跟new。