本文不是Git使用教學篇,而是偏向理論方面,旨在更加深入的理解Git,這樣才能更好的使用它,讓工具成爲咱們得力的助手。javascript
Git 是目前世界上最優秀的分佈式版本控制系統。版本控制系統是可以隨着時間的推動記錄一系列文件的變化以便於你之後想要的退回到某個版本的系統。版本控制系統分爲三大類:本地版本控制系統,集中式版本控制系統和分佈式版本控制系統css
本地版本控制(Local Version Control Systems)是將文件的各個版本以必定的數據格式存儲在本地的磁盤(有的VCS 是保存文件的變化補丁,即在文件內容變化時計算出差量保存起來),這種方式在必定程度上解決了手動複製粘貼的問題,但沒法解決多人協做的問題。html
本地版本控制java
集中式版本控制(Centralized Version Control Systems)相比本地版本控制沒有什麼本質的變化,只是多了個一箇中央服務器,各個版本的數據庫存儲在中央服務器,管理員能夠控制開發人員的權限,而開發人員也能夠從中央服務器拉取數據。集中式版本控制雖然解決了團隊協做問題,但缺點也很明顯:全部數據存儲在中央服務器,服務器一旦宕機或者磁盤損壞,會形成不可估量的損失。git
集中式版本控制sql
分佈式版本控制( Distributed Version Control System)與前二者均不一樣。首先,在分佈式版本控制系統中,像 Git,Mercurial,Bazaar 以及 Darcs 等,系統保存的的不是文件變化的差量,而是文件的快照,即把文件的總體複製下來保存,而不關心具體的變化內容。其次,最重要的是分佈式版本控制系統是分佈式的,當你從中央服務器拷貝下來代碼時,你拷貝的是一個完整的版本庫,包括歷史紀錄,提交記錄等,這樣即便某一臺機器宕機也能找到文件的完整備份。數據庫
分佈式版本控制安全
Git是一個分佈式版本控制系統,保存的是文件的完整快照,而不是差別變化或者文件補丁。ruby
保存每一次變化文件的完整內容bash
Git每一次提交都是對項目文件的一個完整拷貝,所以你能夠徹底恢復到之前的任一個提交而不會發生任何區別。這裏有一個問題:若是個人項目大小是10M,那Git佔用的空間是否是隨着提交次數的增長線性增長呢?我提交(commit)了10次,佔用空間是否是100M呢?很顯然不是,Git是很智能的,若是文件沒有變化,它只會保存一個指向上一個版本的文件的指針
,即,對於一個特定版本的文件,Git只會保存一個副本,但能夠有多個指向該文件的指針
。
另外注意,Git最適合保存文本文件,事實上Git就是被設計出來就是爲了保存文本文件的,像各類語言的源代碼,由於Git能夠對文本文件進行很好的壓縮和差別分析(你們都見識過了,Git的差別分析能夠精確到你添加或者刪除了某個字母)。而二進制文件像視頻,圖片等,Git也能管理,但不能取得較好的效果(壓縮比率低,不能差別分析)。實驗證實,一個 500k 的文本文件經Git壓縮後僅 50k 左右,稍微改變內容後兩次提交,會有兩個 50k 左右的文件,沒錯的,保存的是完整快照。而對於二進制文件,像視頻,圖片,壓縮率很是小, Git 佔用空間幾乎隨着提交次數線性增加。
未變化的文件只保存上一個版本的指針
Git工程有三個工做區域:工做目錄,暫存區域,以及本地倉庫。工做目錄是你當前進行工做的區域;暫存區域是你運行git add
命令後文件保存的區域,也是下次提交將要保存的文件(注意:Git 提交實際讀取的是暫存區域的內容,而與工做區域的文件無關,這也是當你修改了文件以後,若是沒有添加git add
到暫存區域,並不會保存到版本庫的緣由);本地倉庫就是版本庫,記錄了你工程某次提交的完整狀態和內容,這意味着你的數據永遠不會丟失。
相應的,文件也有三種狀態:已提交(committed),已修改(modified)和已暫存(staged)。已提交表示該文件已經被安全地保存在本地版本庫中了;已修改表示修改了某個文件,但尚未提交保存;已暫存表示把已修改的文件放在下次提交時要保存的清單中,即暫存區域。因此使用Git的基本工做流程就是:
git add
,將文件快照保存到暫存區域。如今已經明白Git的基本流程,但Git是怎麼完成的呢?Git怎麼區分文件是否發生變化?下面簡單介紹一下Git的基本原理。
Git 是一套內容尋址文件系統。意思就是Git 從核心上來看不過是簡單地存儲鍵值對(key-value
),value
是文件的內容,而key
是文件內容與文件頭信息的 40個字符長度的 SHA-1 校驗和,例如:5453545dccD33565a585ffe5f53fda3e067b84d8
。Git使用該校驗和不是爲了加密,而是爲了數據的完整性,它能夠保證,在不少年後,你從新checkout某個commit時,必定是它多年前的當時的狀態,徹底一摸同樣。當你對文件進行了哪怕一丁點兒的修改,也會計算出徹底不一樣的 SHA-1 校驗和,這種現象叫作「雪崩效應」(Avalanche effect)。
SHA-1 校驗和所以就是上文提到的文件的指針
,這和C語言中的指針
頗有些不一樣:C語言將數據在內存中的地址做爲指針
,Git將文件的 SHA-1 校驗和做爲指針
,目的都是爲了惟一區分不一樣的對象。可是當C語言指針
指向的內存中的內容發生變化時,指針
並不發生變化,但Git指針
指向的文件內容發生變化時,指針
也會發生變化。因此,Git中每個版本的文件,都有一個惟一的指針
指向它。
blob
對象保存的僅僅是文件的內容,tree
對象更像是操做系統中的文件夾,它能夠保存blob
對象和tree
對象。一個單獨的 tree
對象包含一條或多條 tree
記錄,每一條記錄含有一個指向 blob
對象或子 tree
對象的 SHA-1 指針,並附有該對象的權限模式 (mode)、類型和文件名信息等:
當你對文件進行修改並提交時,變化的文件會生成一個新的blob
對象,記錄文件的完整內容(是所有內容,不是變化內容),而後針對該文件有一個惟一的 SHA-1 校驗和,修改這次提交該文件的指針
爲該 SHA-1 校驗和,而對於沒有變化的文件,簡單拷貝上一次版本的指針
即 SHA-1 校驗和,而不會生成一個全新的blob
對象,這也解釋了10M大小的項目進行10次提交總大小遠遠小於100M的緣由。
另外,每次提交可能不只僅只有一個 tree
對象,它們指明瞭項目的不一樣快照,但你必須記住全部對象的 SHA-1 校驗和才能得到完整的快照,並且沒有做者,什麼時候,爲何保存這些快照的緣由。commit
對象就是問了解決這些問題誕生的,commit
對象的格式很簡單:指明瞭該時間點項目快照的頂層tree
對象、做者/提交者信息(從 Git 設置的 user.name 和 user.email中得到)以及當前時間戳、一個空行,上一次的提交對象的ID以及提交註釋信息。你能夠簡單的運行git log
來獲取這新信息:
$ git log
commit 2cb0bb475c34a48957d18f67d0623e3304a26489 Author: lufficc <luffy.lcc@gmail.com> Date: Sun Oct 2 17:29:30 2016 +0800 fix some font size commit f0c8b4b31735b5e5e96e456f9b0c8d5fc7a3e68a Author: lufficc <luffy.lcc@gmail.com> Date: Sat Oct 1 02:55:48 2016 +0800 fix post show css ***********省略***********
上圖的Test.txt是第一次提交以前生成的,第一次它的初始 SHA-1 校驗和以3c4e9c
開頭。隨後對它進行了修改,因此第二次提交時生成了一個全新blob
對象,校驗和以1f7a7a
開頭。而第三次提交時Test.txt並無變化,因此只是保存最近版本的 SHA-1 校驗和而不生成全新的blob
對象。在項目開發過程當中新增長的文件在提交後都會生成一個全新的blob
對象來保存它。注意除了第一次每一個提交對象都有一個指向上一次提交對象的指針。
所以簡單來講,blob
對象保存文件的內容;tree
對象相似文件夾,保存blob
對象和其它tree
對象;commit
對象保存tree
對象,提交信息,做者,郵箱以及上一次的提交對象的ID(第一次提交沒有)。而Git就是經過組織和管理這些對象的狀態以及複雜的關係實現的版本控制以及以及其餘功能如分支。
如今再來看引用,就會很簡單了。若是咱們想要看某個提交記錄以前的完整歷史,就必須記住這個提交ID,但提交ID是一個40位的 SHA-1 校驗和,難記。因此引用就是SHA-1 校驗和的別名,存儲在.git/refs
文件夾中。
最多見的引用也許就是master
了,由於這是Git默認建立的(能夠修改,但通常不修改),它始終指向你項目主分支的最後一次提交記錄。若是在項目根目錄運行cat .git/refs/heads
,會輸出一個SHA-1 校驗和,例如:
$ cat .git/refs/heads/master
4f3e6a6f8c62bde818b4b3d12c8cf3af45d6dc00
所以master
只是一個40位SHA-1 校驗和的別名罷了。
還有一個問題,Git如何知道你當前分支的最後一次的提交ID?在.git
文件夾下有一個HEAD
文件,像這樣:
$ cat .git/HEAD
ref: refs/heads/master
HEAD
文件其實並不包含 SHA-1 值,而是一個指向當前分支的引用,內容會隨着切換分支而變化,內容格式像這樣:ref: refs/heads/<branch-name>
。當你執行git commit
命令時,它就建立了一個commit
對象,把這個commit
對象的父級設置爲HEAD
指向的引用的 SHA-1 值。
再來講說 Git 的 tag,標籤。標籤從某種意義上像是一個引用, 它指向一個 commit
對象而不是一個 tree
,包含一個標籤,一組數據,一個消息和一個commit
對象的指針。可是區別就是引用隨着項目進行它的值在不斷向前推動變化,可是標籤不會變化——永遠指向同一個 commit
,僅僅是提供一個更加友好的名字。
分支是Git的殺手級特徵,並且Git鼓勵在工做流程中頻繁使用分支與合併,哪怕一天以內進行許屢次都沒有關係。由於Git分支很是輕量級,不像其餘的版本控制,建立分支意味着要把項目完整的拷貝一份,而Git建立分支是在瞬間完成的,而與你工程的複雜程度無關。
由於在上文中已經說到,Git保存文件的最基本的對象是blob
對象,Git本質上只是一棵巨大的文件樹,樹的每個節點就是blob
對象,而分支只是樹的一個分叉。說白了,分支就是一個有名字的引用,它包含一個提交對象的的40位校驗和,因此建立分支就是向一個文件寫入 41 個字節(外加一個換行符)那麼簡單,因此天然就快了,並且與項目的複雜程度無關。
Git的默認分支是master,存儲在.git\refs\heads\master
文件中,假設你在master分支運行git branch dev
建立了一個名字爲dev
的分支,那麼git所作的實際操做是:
.git\refs\heads
文件夾下新建一個文件名爲dev
(沒有擴展名)的文本文件。master
)的40位SHA-1 校驗和外加一個換行符寫入dev
文件。建立分支就是這麼簡單,那麼切換分支呢?更簡單:
.git
文件下的HEAD
文件爲ref: refs/heads/<分支名稱>
。記住,HEAD
文件指向當前分支的最後一次提交,同時,它也是以當前分支再次建立一個分支時,將要寫入的內容。
再來講一說合並,首先是Fast-forward,換句話說,若是順着一個分支走下去能夠到達另外一個分支的話,那麼 Git 在合併二者時,只會簡單地把指針右移,由於這種單線的歷史分支不存在任何須要解決的分歧,因此這種合併過程能夠稱爲快進(Fast forward)。好比:
注意箭頭方向,由於每一次提交都有一個指向上一次提交的指針,因此箭頭方向向左,更爲合理
當在master
分支合併dev
分支時,由於他們在一條線上,這種單線的歷史分支不存在任何須要解決的分歧,因此只須要master
分支指向dev
分支便可,因此很是快。
當分支出現分叉時,就有可能出現衝突,而這時Git就會要求你去解決衝突,好比像下面的歷史:
由於master
分支和dev
分支不在一條線上,即v7
不是v5
的直接祖先,Git 不得不進行一些額外處理。就此例而言,Git 會用兩個分支的末端(v7
和 v5
)以及它們的共同祖先(v3
)進行一次簡單的三方合併計算。合併以後會生成一個和並提交v8
:
注意:和並提交有兩個祖先(v7
和v5
)。
rebase
把一個分支中的修改整合到另外一個分支的辦法有兩種:merge
和 rebase
。首先merge
和 rebase
最終的結果是同樣的,但rebase
能產生一個更爲整潔的提交歷史。仍然以上圖爲例,若是簡單的merge
,會生成一個提交對象v8
,如今咱們嘗試使用變基合併分支,切換到dev
:
$ git checkout dev
$ git rebase master
First, rewinding head to replay your work on top of it... Applying: added staged command
這段代碼的意思是:回到兩個分支最近的共同祖先v3
,根據當前分支(也就是要進行變基的分支 dev
)後續的歷次提交對象(包括v4
,v5
),生成一系列文件補丁,而後以基底分支(也就是主幹分支 master
)最後一個提交對象(v7
)爲新的出發點,逐個應用以前準備好的補丁文件,最後會生成兩個新的合併提交對象(v4'
,v5'
),從而改寫 dev
的提交歷史,使它成爲 master 分支的直接下游,以下圖:
如今,就能夠回到master
分支進行快速合併Fast-forward了,由於master
分支和dev
分支在一條線上:
$ git checkout master $ git merge dev
如今的v5'
對應的快照,其實和普通的三方合併,即上個例子中的 v8
對應的快照內容如出一轍。雖然最後整合獲得的結果沒有任何區別,但變基能產生一個更爲整潔的提交歷史。若是視察一個變基過的分支的歷史記錄,看起來會更清楚:彷彿全部修改都是在一根線上前後進行的,儘管實際上它們本來是同時並行發生的。
一、Git保存文件的完整內容,不保存差量變化。
二、Git以儲鍵值對(key-value
)的方式保存文件。
三、每個文件,相同文件的不一樣版本,都有一個惟一的40位的 SHA-1 校驗和與之對應。
四、SHA-1 校驗和是文件的指針,Git依靠它來區分文件。
五、每個文件都會在Git的版本庫裏生成blob
對象來保存。
六、對於沒有變化的文件,Git只會保留上一個版本的指針。
七、Git其實是經過維持複雜的文件樹來實現版本控制的。
八、使用Git的工做流程基本就是就是文件在三個工做區域之間的流動。
九、應該大量使用分支進行團隊協做。
十、分支只是對提交對象的一個引用。
原文地址:http://www.codeceo.com/article/git-core-concept.html