最近重新學了一遍Git工具的使用,在此記錄一下。
Git是什么?
Git是屬于分布式版本控制系統,同屬于的還有Mercurial、Bazaar等。
Subversion(svn)屬于集中式的版本控制系統,同屬于的還有CVS、Perforce等。
兩種類型版本控制系統有什么區別?
集中式的版本控制系統,以單臺中央服務器為中心,將所有的版本控制歷史都集中放在這臺服務器上,而各個協同開發者的計算機上只保存著一個快照版本,在這種情況下,開發者每次想要提交或者查看文件的歷史等,都需要請求網絡,向剛剛所說的中央服務器獲取或者提交數據。
分布式版本控制系統與集中式版本控制系統不同,它在本地計算機上建立代碼倉庫,保存著所有的版本控制歷史的數據,在合適的時機與遠程服務器代碼倉庫同步。在這種情況下,開發者每次想要提交或者查看文件的歷史等,無需請求網絡,直接向本地獲取或者提交數據即可,因為本地保存著所有的版本歷史數據。
分布式版本控制系統的優點:
開發者所有的Git操作都在本地代碼倉庫進行,不用依賴于網絡。
不用擔心代碼倉庫服務器宕機或者數據丟失等情況,因為每個開發者計算機上都保存有完整的版本歷史數據,可以隨時將開發者主機上的數據恢復至服務器代碼倉庫。
限于篇幅,本文接下去的內容不討論svn等集中式的版本控制系統與分布式版本系統的用法差異。
為了更加貼近于實際的開發,我們將從Git倉庫的創建到版本的提交為順序講述,最后還簡述一下分支的用法和概念。
注意:本文是在本地代碼倉庫上的操作,還未涉及到遠程倉庫的連接,讀者先不要將遠程倉庫的思想混進來,容易造成混亂。
先介紹幾個Git中的基本概念。
Git中的文件區域
在git中總共有三個區域。分別是工作區、暫存區、git倉庫區。
我們所有的文件修改以及變更都是在工作區進行的,然后保存至暫存區(git add命令),最后保存到git倉庫區(git commit命令)。可能有讀者會問git為什么要設計一個暫存區,為什么不支持從工作區直接將文件保存至git倉庫區。這是因為這個暫存區作為一個中間區域,它的作用就是存放即將要提交到git倉庫的文件。
有這個區域的存在,開發者可以將想要提交到倉庫的文件保存到暫存區,不想提交的文件可以繼續存放在工作區,這樣子下次執行提交操作的時候,Git只會將暫存區所有的文件提交到倉庫中。
開發者提交文件更加靈活。開發者假如后悔不想要提交某個文件(此時這個文件已經在暫存區),那么開發者可以將此文件從暫存區中移出(git reset HEAD <file>命令),下次提交就不會提交這個文件。
Git中的文件狀態
在git中,文件有四種狀態。未跟蹤、未修改、已修改、已暫存。
已跟蹤的文件是指本來就被納入版本控制管理的文件,在上次快照中有它們的記錄,工作一段時間后,它們的狀態可能是未修改,已修改或者已暫存。
未跟蹤文件既沒有在git倉庫中存有快照,也還未將文件快照保存到當前的暫存區域。初次克隆某個倉庫時,工作目錄中的所有文件都屬于已跟蹤文件,且狀態為未修改。
Git倉庫的創建
Git倉庫的創建非常簡單,在本地設備有安裝Git情況下,直接在項目根目錄下運行shell命令git init
即可。
$ git init
git status
git status這個命令是用來檢查當前文件的狀態的。為什么我這么早就介紹這個命令?因為這個命令能給我們提供很多有用的信息,幫助我們了解Git。這個命令應該算是有git最常用的命令了,它能夠顯示當前工作區文件的狀態(已跟蹤或未跟蹤)、暫存區文件狀態、文件沖突狀態、當前所在的分支等。
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: benchmarks.rb
從輸出中可以看到當前處于master分支,新創建的文件README
已經被保存至暫存區域,benchmarks.rb
在工作區域已被修改,但是還沒被保存至暫存區域,下次提交時候只會提交README
文件到代碼倉庫。
括號中的文本是git的輔助提示信息。比如這里git告訴你使用git reset HEAD <file> ...
可以將文件從暫存區域移除。git add <file> ...
用來添加或者更新文件到暫存區域等到下次提交。用git checkout --<file>
命令來還原工作區文件的改變,其實就是將工作區某個或某些文件還原為未修改的狀態。
git add
git add
的作用是將工作區的文件保存至暫存區。
重要是事情說三遍!
git 保存的不是文件差異或者變化量,而只是一系列文件快照。
git 保存的不是文件差異或者變化量,而只是一系列文件快照。
git 保存的不是文件差異或者變化量,而只是一系列文件快照。
這個命令的后面可以帶有文件或者文件夾路徑作為參數,當參數為目錄時候,git add
的效果會遞歸至目錄下所有的文件。
- 如果文件在工作區域已被跟蹤,那么直接將文件快照保存至暫存區。
- 執行
git add
命令后,git會計算該文件的校驗和,生成該文件快照,將校驗和保存到暫存區,這里是保存文件快照。
注意:如果執行所要執行git add的目標文件處于未跟蹤狀態,此命令會先將目標文件標識為已跟蹤文件。
在倉庫建立之后,因為此時本地代碼倉庫和暫存區都是空的,并沒有任何文件的快照。所以此時所有的文件狀態都是未跟蹤狀態。
此時執行git status命令:
$ vim README
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" to track)
我們工作目錄下有一個名為README
文件,此時顯示它的狀態就是未跟蹤的。同時git也提示我們可以使用git add
來跟蹤以及保存到暫存區。
此時我們執行git add *
命令(git 命令支持通配符,*表示將當前工作目錄下所有的非忽略文件提交至暫存區,包括所有子文件夾的下的所有文件,當前這里你也可以直接使用git add README
命令,指定只作用于README
文件)。然后再次執行git status
:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
從輸出中我們可以看到暫存區已經保存著README
文件的快照,并且等待提交。
git commit
git commit這個命令是用來將文件提交到本地代碼倉庫的。
當你用Git commit命令執行一個新的提交時候,Git只保存一個指向這次提交快照的索引。
可以看到這里有三個文件,file A、file B、file C。這里以在提交一個新的版本后,git會遍歷一邊所有的文件的指紋信息,如果發現新版本的文件指紋信息與上一個版本不一致,說明該文件發生了變化,此時git會將新的文件快照索引保存至本地的微型數據庫中,如果文件未發生變化,那么會將上個版本的保存的快照索引直接復用寫入微型數據庫中。
注:git所有的數據都保存在本地項目倉庫的.git文件夾下,這個文件夾默認為隱藏文件夾。
實際上,我們執行一次提交操作就是保存一個提交對象。
當執行git commit命令后,git會先計算每一個子目錄的校驗和(注意,git add是計算文件的校驗和),然后在git倉庫中將這些目錄保存為樹對象,然后git會創建一個提交對象,這個提交對象出了包含相關的提交信息以外,還包含著指向這個樹對象的指針,如此它就可以在將來需要的時候,重現此次快照的內容了。git commit會將暫存區的所有文件提交到本地數據庫中。保存的內容如下。
多次提交之后:
分支
分支應該算是版本控制系統最強大的功能之一了。
前面我們說了,在執行git提交時,git會創建并且生成一個提交對象,該對象包含一個指向文件樹的指針,包含本次提交的作者等相關附屬信息,包含零個或者多個指向該提交對象的父對象指針:首次提交時沒有直接祖先的,普通提交有一個祖先,由兩個或者多個分支合并產生的提交則有多個祖先。
其實分支的概念很簡單,分支本質上僅僅是個指向某個提交對象的可變指針。
在創建一個新的代碼倉庫時,git會默認創建一個名為master的分支。我們的每次提交操作,都是基于某個分支的基礎上進行的。在若干次提交操作之后,分支會自動向前移動。
分支的創建很簡單。
git branch testing
這會在當前的提交對象上新建一個新的分支指針。如下:
那么,git是如何知道我們當前是在哪個分支上工作的呢?其實git保存著一個名為HEAD的特別指針。每當我們切換執行切換分支操作的時候,git其實只是將HEAD指針移動到目標分支上,這樣子我們接下去的提交等操作都是針對于這個分支進行的。
在上面命令中,我們執行了
git branch
命令,這個命令僅僅是創建一個分支,而不會切換到新建的這個分支中。要切換分支,可以執行一下命令。
git checkout testing
這樣子,HEAD就指向了testing分支。這樣子就完成了分支的切換。
此時我們如果進行修改一個文件,然后提交。結果如下。
vim test.rb
git commit -a -m 'made a change'
注意,git commit 后面-a表示直接跳過保存至暫存區,直接提交到數據庫。
好了。我們把分支切回master。然后再次修改一個文件,最后提交。
vim test.rb
git commit -a -m 'made other changes'
結果如下:
可以看到,我們的項目提交歷史產生了分叉。這是因為我們的master和testing分支的父提交對象都是
f30ab
,我們后面可以在合適的時機將兩個分支合并。
關于分支的合并見下章,未完待續。