上期漫談版本控制系統中我們談到了版本控制系統的四個演進過程,即悲觀鎖版本 -> 樂觀鎖版本 -> 多分支版本 -> 分佈式版本,目前我們使用最多的是分佈式版本,本期我們就來介紹下分佈式版本控制系統的具體實現Git和GitHub。
Git基礎
Git不用於其他版本控制系統主要體現在以下幾點:1. Git對待數據的方法,直接記錄快照,而非差異比較;2. 暫存區;3.高效的分支模型。
- 直接記錄快照,而非差異比較
CVS、Subversion等版本控制系統會以文件變更列表的方式存儲信息,即以增量形式存儲信息,如下圖所示。而Git全量方式存儲信息,同時爲了減少空間使用,當文件沒有修改時,最新版本中並不會存儲該文件的全量信息,而是保留一個鏈接指向之前存儲的文件。譬如File A
在version2發生了修改,因此全量存儲文件信息,而在version3並沒有修改,因此保留一個鏈接指向A1。
- 工作目錄、暫存區、本地倉庫
Git引入三個工作區域的概念:工作目錄(Working Directory)、暫存區(Staging Area)、本地倉庫,尤其是暫存區具有獨創性。工作目錄是對項目的某個版本提取出來的內容存放到磁盤中;暫存區只是一個文件,保存下次提交的文件列表信息;本地倉庫是Git用來保存項目的元數據和對象數據庫的地方,當進行克隆倉庫時,拷貝的就是這裏的數據。數據在三者之間的基本交互流程:1.在工作目錄中修改文件;2.暫存文件,將文件的快照存放到暫存區域;3.提交更新,找到暫存區域的文件,將快照永久性存儲到Git倉庫中。
- 高效的分支模型
在很多版本控制系統中,創建一個分支意味着需要完全創建一個源代碼目錄的副本,對於大型項目來說,這樣的過程會耗費大量時間,而這對於Git來說是非常輕便的。上文我們已經提到Git對待數據的方法,即直接記錄快照,同時,Git在進行提交操作時,它會保存一個提交對象(commit object),該對象會包含一個指向暫存內容快照的指針,提交者的姓名、郵箱、輸入信息和指向它的父對象的指針。注意:首次提交的提交對象沒有父對象。
爲了幫助大家更好地理解,假設有一個工作目錄,包含三個將要被暫存和提交的文件。暫存操作爲每個文件計算校驗和,然後把當前版本的文件快照保存到Git倉庫中(Git使用blob對象保存它們)。當使用git commit
進行提交操作時,會計算每一個子目錄的校驗和,然後在Git倉庫中將這些校驗和保存爲樹對象,隨後,Git便會創建一個提交對象,它除了包含上面提到的那些信息外,還包含指向這個樹對象的指針。如下圖所示,現在Git倉庫包含五個對象:三個blob對象(保存着文件快照)、一個樹對象(記錄着目錄結構和blob對象索引)以及一個提交對象。
安裝
- Windows系統
可以通過cmder中的choco install git
,也可以通過普通安裝完成 - Mac系統
brew install git
- Linux系統
yum install git
- 檢查安裝是否成功
git --version
初次運行Git前的配置
用戶信息
當安裝完Git之後應該做的第一件事就是設置你的用戶名稱和郵件地址,這樣做很重要,因爲每一次Git提交都會使用這些信息。
git config --global user.name "mukedada"
git config --global user.email mukedada@126.com
注意--global
選項表示全局設置。
檢查配置信息
通過git config --list
命令列出所有Git當前能找到的配置,還可以通過git config <key>
來檢查某一項配置,例如:git config user.name
,結果:
$ git config user.name
mukedada
獲取Git倉庫
目前有兩種方式獲取Git項目倉庫的方法。第一種方法是把現有目錄下的所有文件導入到Git中;第二種方法是一個服務器克隆一個現有的Git倉庫。
在現有目錄中初始化倉庫
$ cd test
$ git init
在test目錄下創建一個名爲.git
的子目錄。
克隆現有的倉庫
克隆倉庫的命令格式是git clone [url]
。比如,要克隆木可大大的code倉庫,可以用下面的命令:
$ git clone git@github.com:mukedada/code.git
這回在當前目錄下創建一個名爲”code”的目錄,並在這個目錄下初始化一個.git
文件夾。
記錄每次更新到倉庫
test目錄下的每個文件都不外乎兩個狀態:已跟蹤或未跟蹤。已跟蹤的文件是指那些被納入了版本控制的文件,在上一次快照中有它們的記錄,在工作一段時間後,它們的狀態可能處於未修改或已修改或一放入暫存區。test目錄下除了已跟蹤的文件以外,剩下的文件都屬於未跟蹤的文件,它們既不存在於上次快照的記錄中,也沒有放入暫存區。初次克隆的test倉庫,此時該工作目錄中的所有文件都屬於已跟蹤文件,並處於未修改狀態。編輯過某些文件之後,由於自上次提交後我們對它們做了修改,Git將它們標記爲已修改文件。我們逐步將這些修改過的文件放入暫存區,然後提交所有暫存區中的修改,如此反覆。
檢查當前文件狀態
使用git status
名稱查看當前文件處於什麼狀態。
$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
nothing to commit,working tree clean
說明當前工作目錄中所有已跟蹤的文件在上次提交之後沒有修改過;On branch master
說明當前處於master
分支。
現在,我們在test項目下創建text.txt文件,再使用git status
命令。
$ echo 'my project' > test.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.txt
nothing added to commit but untracked files present (use "git add" to track)
從中我們可以發現,test.txt在Untracked files
下面,這就意味着該文件並未在之前的快照中,如果我們想讓該文件納入到Git的跟蹤範圍內,需要使用git add
命令。
跟蹤新文件
git add + 文件或目錄
,如果是目錄,則將遞歸跟蹤該目錄下所有的文件。
$ git add test.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: test.txt
只要出現Changes to be committed
說明test.txt已經進入暫存區。
暫存已修改文件
現在我們修改test.txt,發現該文件既出現在Changes to be committed
下面,又出現在Changes not staged for commit
下面,前一句說明該文件在暫存區,而後一句說明該文件在非暫存區,這怎麼可能呢?其實,文件在暫存區是因爲暫存區保存的是我們上一次運行git add
命令時的版本,接着我們沒有運行git commit
命令,而是對文件進行修改,所以出現了Changes not staged for commit
,因此,我們需要對修改後的文件重新運行git add
命令把最新版本重新暫存起來。
$ vim test.txt
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: test.txt
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: test.txt
查看已暫存和未暫存的區別
通過上面代碼我們知道我們對已暫存的test.txt進行了修改,還沒有執行git add
命令,但是我們不知道具體修改了什麼內容,此時,我們就可以用git diff
命令來查看修改的內容。
$ git diff
diff --git a/test.txt b/test.txt
index a159320..b3b1c1b 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
hello 'my project'
+haha~
提交更新
使用 git commit -m "備註"
命令提交存放在暫存區的快照,-m
選項表示提交時備註信息。
跳過使用暫存區域
git commit
加上-a
選項表示跳過git add
步驟直接將文件保存到暫存區。
打標籤
- 列出標籤
輸入git tag
命令:
$ git tag
v0.1
- 創建標籤
$ git tag -a v1.0 -m 'my version 1.0'
$ git tag
v0.1
v1.0
分支
Git分支,本質上就是指向提交對象的可變指針。每當提交一次,都會生成一個新的提交對象,它包含一個指向上次提交對象的指針,如下圖所示。
分支創建
通過使用git branch
命令創建分支,譬如:
$ git branch testing
當有兩個指向相同提交歷史的分支,Git怎麼知道當前在哪一個分支上呢?Git有一個特殊指針HEAD
,它指向當前所在的本地分支。
分支切換
通過使用git checkout
命令進行分支切換,譬如:我們切換到新創建的testing分支上:
$ git checkout testing
此時,HEAD就指向testing
分支。
分支合併
目前Git分支合併主要有兩種方法:merge
和 rebase
。
- merge:把兩個分支的最新快照和以及兩者最近的共同祖先進行三方合併,合併的結果是生成一個新的快照,並提交,如果沒有衝突的話。舉個栗子,首先將hotfix branch
合併到master
分支上,由於當前master
分支所指向的提交是hotfix
分支的之間上游,因此Git知識簡單的將指針向前移動,這就是快進(fast-forward)。
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
由於master
分支和hotfix
分支指向同一個位置,我們可以使用git branch -d
指令刪除分支:
$ git branch -d hotfix
接下來,我們繼續在iss53
工作,最後一個提交對象是C5
,此時,如何合併iss53
分支和C5
分支?
我們需要將HEAD
指向master
,然後執行git merge
命令:
$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)
這和之前合併hotfix
分支看起來有些不同,Git會使用兩個分支的末端所指的快照(C4
和C5
)以及這兩個分支的工作祖先(C2
),做一個簡單的三方合併。
- rebase ,中文名”變基”,即將某一分支上的所有修改都移植到另一分支上,就好像”重新播放”一樣。爲了加深大家的印象,對
C4
分支和C3
分支進行合併,首先提取C4
的修改,然後在C3
上重新播放一遍。
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
執行完上述代碼之後,形成如下結構。
對於上述結構,我們還需要進行一次合併。
$ git checkout master
$ git merge experiment
- 注意:當兩個不同的分支中,對同一個文件的同一個部分進行不同的修改,Git就沒法乾淨的合併它們,此時需要我們手動解決衝突。
GitHub
目前,GitHub是最大的Git版本庫託管商,大部分開源項目都託管在Github,因此學習Github就比不可少了。
- 賬戶的創建和配置
訪問GitHub官網,填寫相關信息完成註冊。
- 配置SSH
首先需要生成本機公鑰,如果是Window,使用指令ssh-keygen -t rsa -C "[email protected]"
生成主機的公私密鑰對,如果是Mac,則使用ssh-keygen
命令上。注意:運行上述指令時,不要輸入任何信息,只需按enter就可以,成功之後,公私鑰文件會存放於當前用戶目錄的.ssh目錄下,例如C:\Users\kwy\.ssh
。接着,將生成出的公鑰放到github中。
- 創建倉庫
- 克隆遠程倉庫到本地:git clone [email protected]:mukedada/artical.git
歡迎關注微信公衆號:木可大大,所有文章都將同步在公衆號上。