項目人員使用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。