Git作為大家熟悉的,深受歡迎的版本控制工具,和其他同類工具有很多不同之處:
Git始終保存快照而不是文件差異。
任何數據存儲前始終使用SHA-1計算校驗和,保證內容完整性。
使用分布式倉庫設計,讓大多數操作都在本地進行,保證了使用效率。
幾乎所有操作都是向數據庫增加數據,提交之后就很難丟失數據。
它的本質更像一個內容尋址(content-addressable)文件系統,并在此之上提供了一個版本控制系統的用戶界面。
Git 有三種狀態,你的文件可能處于其中之一:已修改(modified)、已暫存(staged)、已提交(committed)。由此引出三個邏輯區域,他們和文件狀態以及部分對應操作的關系如下圖。
高層命令和底層命令:Git 最初是一套面向版本控制系統的工具集,它包含很多用于完成底層工作的命令。這些命令被設計成能以UNIX 命令行的風格連接,或由腳本調用來完成更復雜的工作。這部分一般被稱作“底層(plumbing)”命令,那些對用戶更友好的命令則被稱作“高層(porcelain)”命令。
下面新建兩個空倉庫A 和B,來觀察隱藏在Git常見命令下的實際執行過程。
1.git init
此命令初始化一個新本地倉庫,它在工作目錄下生成一個名為.git的隱藏文件夾。
查看該文件夾結構:
config//文件- 包含一些配置選項
objects//目錄- 存儲所有Git的數據對象
HEAD//文件- 指定當前分支
info //目錄- 存放項目信息,默認包含一個全局exclude文件, 用來放置不希望記錄在.gitignore 中的忽略模式
deion//文件- 僅供GitWeb 程序使用
hooks //目錄- 存放可在某些指令前后觸發運行的鉤子腳本(hook s),默認包含一些腳本樣例
refs//目錄- 存儲各個分支指向的目標提交
branches //目錄- 還沒發現有什么用處
.git 目錄下可能還會包含其他文件,不過對于一個全新的倉庫,這將是你看到的默認結構。
其中有四個條目很重要:HEAD 文件、(尚未創建的)index 文件,和 objects 目錄、refs 目錄。這些條目是Git 的核心組成部分。
本地倉庫剛剛新建,Git的三個區域都為空。
2.git add
在A倉庫的工作目錄創建一個文件file.txt,寫入內容version 1,模擬需要管理的代碼文件。
執行git add,使用git status查看此時的狀態。
然后另外初始化一個空倉庫B,嘗試用底層命令來實現以上效果。
創建相同內容的file.txt,執行 git hash-object,計算文件頭部信息+文件內容的SHA-1編碼,執行后顯示出40位的編碼結果。-w參數表示將內容寫入數據目錄。
查看寫入到數據目錄的Git對象文件:
Git以SHA1編碼前兩位作為子目錄名,剩余位數作為文件名,存儲壓縮后的頭部信息和原文內容。
可以通過 cat-file 命令查看原始數據。為 cat-file 指定 -p 選項可使該命令自動判斷源文件類型。
這種存儲了數據原文的文件在Git對象中屬于blob (Binary Large Object)類型。
此時Git的區域狀態如下:
使用git update-index 命令可以修改暫存區,也就是.git/index文件。
由于此文件在暫存區沒有記錄,需要--add參數。
使用--cacheinfo參數,直接寫入數據文本。如果不加此參數,僅使用git update-index --add file.txt 的方式,則與add命令效果完全相同。
本例中,我們指定的文件模式為100644,表明這是一個普通文件。其他選擇包括:100755,表示一個可執行文件;120000,表示一個符號鏈接。Git的文件模式參考了常見的UNIX 文件模式,但比真正的文件系統簡單許多。
此時暫存區index文件已經生成,直接打開會看到二進制字符,可以用 ls-files 命令解析查看。
顯示出剛剛寫入的內容。
此時Git的區域狀態如下:
使用git status 查看,此時和A倉庫狀態相同。
另外,由于 update-index --cacheinfo是直接寫入文本,我們也可以添加完全不存在的對象名和文件名。
此時B倉庫的狀態:
3.git commit
回到A倉庫,在git add 的基礎上調用commit生成一個提交。
再查看暫存區:
與status的提示不同,提交操作并不會實際清空暫存區,其中始終保存著工作目錄的文件結構。
再查看對象文件夾,發現兩個新增文件。
接下來我們在另一個倉庫重現這個操作。
回到B倉庫,繼續執行 git write-tree。
git 會在此時檢查暫存區內容和數據目錄中對象的對應關系,剛剛添加的不存在的文件導致失敗。
git update-index --remove 命令可以從暫存區刪除這條信息,只有在工作目錄中不存在此文件時,才允許從暫存區直接刪除相關信息。
write-tree 執行成功后同樣返回40位哈希值,此命令將暫存區內容寫入數據目錄,生成一個 tree類型的對象。此對象也可以使用cat-file 命令查看。
使用剛剛生成的tree 對象來繼續生成commit 對象,查看內容。
其中用戶信息使用 git config user.name 和 git config user.email 設置,僅對當前倉庫生效,如未指定則使用全局配置。
查看對象目錄:
和A倉庫直接git commit生成的文件對比,發現其中一個文件名不同。這是由于commit對象中包含執行時間信息,導致生成了不同的哈希編碼。
使用log命令可以看到一個普通的commit信息。
此時Git工作區域的狀態:
繼續使用唯一的tree對象創建另一個提交。-p參數指定繼承關系,作為標識符的hash值沖突概率較低,在git命令中通常使用前幾位簡寫表示。
有繼承關系的commit對象多出一條parent信息:
4.refs和HEAD
回到A倉庫,使用commit 增加C1提交。
檢查到暫存區并未修改,提交失敗。可以使用 --allow-empty 參數放棄檢查。生成以下log。
可以發現,打印的log信息和B倉庫略有不同,并且B 倉庫查看log時必須指定commit對象編碼。如果不指定就會出現以下錯誤。
原因是之前用底層命令生成的提交鏈并不屬于任何分支。分支的本質是指向commit對象的指針,hash編碼無意義難以記憶,分支名更方便靈活管理。
而HEAD 文件中保存著當前工作目錄所在的分支,可以看到在B 倉庫中,HEAD 指向默認分支master,而 master 文件還不存在。
可以手動生成 master文件,并指向最新提交。
雖然可以直接修改分支文件的內容,但這是一種不安全的做法,可以使用git update-ref 命令來達成同一效果。
此時B倉庫和A倉庫的狀態就完全一致了。
-
數據存儲
+關注
關注
5文章
970瀏覽量
50894 -
Git
+關注
關注
0文章
198瀏覽量
15755
發布評論請先 登錄
相關推薦
評論