Git使用
ofollow,noindex">原文連結
集中化版本控制系統
集中化版本控制系統(Centralized Version Control Systems,簡稱CVCS),用於記錄某個時間點對專案做了哪些修改,包括增加、刪除等。如CVS、SVN等,都是集中化版本控制系統。集中化版本控制系統有一個單一的集中管理的伺服器,儲存所有檔案的修訂版本,而協同工作的開發者通過客戶端連線到該伺服器,並且可以從該伺服器獲取資料,修改記錄,或者提交更新。集中化版本控制系統實現效果如下:

螢幕快照 2018-11-26 下午3.53.45.png
集中化版本控制系統使用比較方便,但是其缺點也是比較明顯的。如果集中管理的伺服器故障,那麼在伺服器故障期間內,任何人都無法提交更新,也就無法協同工作。如果伺服器硬碟發生損壞,又沒有做恰當的備份,雖然發生的機率非常小,但是一旦發生,專案的所有資料都將丟失,包括專案源資料和變更歷史。為了解決集中化版本控制系統的缺點,於是就有了分散式版本控制系統。
分散式版本控制系統
分散式版本控制系統(Distributed Version Control System,簡稱DVCS),最出名的就是Git。在分散式版本控制系統中,客戶端從伺服器並不只是提取最新版本的檔案快照,而是把程式碼倉庫完整的映象下來,包括整個專案的提交歷史。這樣一來,即使伺服器發生故障,使用任何一個映象都可以快速的恢復。分散式版本控制系統的實現效果如下:

image
Git分支模型
版本控制系統通常都會支援分支。分支可以使開發者從主線版本脫離出來,來做一些其他嘗試性的工作。在部分版本控制系統中,比如SVN,新建分支需要完全建立一個原始碼目錄的副本。這在一些大型專案中,該過程會耗費幾分鐘,甚至更久的時間。
Git可以很快的建立一個分支,時間通常在幾秒鐘之內,這也是Git為何如此流行的一個原因。這裡再介紹一個Git和其他版本控制系統的區別。很多版本控制系統記錄的是檔案的變化,比如兩次提交之間,SVN就是記錄了兩次提交的不同,也就是檔案變化。Git記錄的不是檔案變化,而是一些列不同時刻的檔案快照。
Git新建一個分支,其實就是新建了一個指標,指標指向了某一時刻的提交。比如使用git branch新建一個分支:
git branch testing
實際上是新建了一個指標,該指標指向當前所在的提交物件,如下圖:

image
新建了testing指標,testing指標和master指標指向了同一個提交物件 f30ab。
之後加入切換到了testing分支,然後對testing分支做了修改和提交,testing指標會隨之向後移動,但是master指標是不會改變的,如下圖:

image
testing指標已經改變,但是master指標並沒有隨之移動。這正是分支的意義所在。
上面圖片中出現了HEAD指標,簡單介紹下HEAD指標。Git中的HEAD指標指向的是當前的分支。拿上圖舉例,如果當前在master分支,那麼HEAD指向的就是master,如果當前在testing分支,那麼HEAD指向的就是testing。
Git命令
配置使用者資訊
使用Git之前需要首先配置個人資訊,包括個人的使用者名稱和電子郵件地址。每次Git提交時,都會引用使用者名稱和電子郵件,說明是誰提交了更新。配置使用者資訊的命令:
// 配置使用者名稱 git config --global user.name "Test" // 配置使用者電子郵箱 git config --global user.email [email protected]
文字編輯器
當Git需要使用者輸入一些額外的資訊時,會自動呼叫一個外部的編輯器給使用者使用。預設會使用作業系統指定的預設編輯器,比如說Vi或者Vim。如果想設定成其他的編輯器,可以通過git config來設定,命令如下:
// 配置編輯器為emacs git config --global core.editor emacs
當然,在設定之前也可以檢視當前的編輯器,命令如下:
// 檢視所有的config配置 git config --list // 檢視user.name的配置 git config user.name // 檢視當前的編輯器 git config core.editor
差異分析工具
在提交程式碼、合併程式碼、解決衝突時,需要用到差異分析工具。差異分析工具也是可以設定的,命令如下:
// 配置差異分析工具為vimdiff git config --global merge.tool vimdiff
Git可以理解vimdiff、gvimdiff、opendiff等工具的輸出資訊。
新建git倉庫
Git中有倉庫(repository)的概念,所有的檔案應該都在倉庫中。可以通過兩種方式新建倉庫:在本地新建倉庫和從遠端伺服器clone一個倉庫。
在本地新建倉庫
在本地新建倉庫非常容易,只需要在對應的目錄下使用git init命令即可。命令如下:
// 新建一個git倉庫 git init
新建倉庫之後,後續就是向倉庫中新增檔案,並提交。新增、提交的命令之後再介紹。
從遠端克隆一個倉庫
從遠端克隆倉庫使用的是git clone命令,比如:
// 從遠端伺服器clone一個git倉庫,會在當前目錄下新建Blogs資料夾 git clone https://github.com/acBool/Blogs.git
當然,clone時也可以指定本地倉庫的名字,命令如下:
// 本地倉庫會被命名為myBlog git clone https://github.com/acBool/Blogs.git myBlog
檢視當前檔案狀態
Git倉庫下的檔案有已修改、未修改、已暫存、未跟蹤幾種狀態,使用status命令可以檢視當前檔案的狀態,命令如下:
// 檢視當前檔案狀態 git status
git status命令輸出的資訊可能有些冗餘,可以使用-s引數得到一個更為簡介的資訊:
// 檢視當前檔案狀態,資訊更為簡潔 git status -s
新增檔案到git倉庫
新建倉庫後,可以向倉庫中新增檔案。但是新增的檔案處於未被跟蹤的狀態,如果要改變檔案為跟蹤狀態,需要使用git add命令,如下:
// 將hello.c的狀態改為跟蹤狀態 git add hello.c
如果add之後的引數是資料夾,會遞迴的跟蹤該目錄下的所有檔案:
// 會遞迴的將Tempdir目錄下的所有檔案改為跟蹤狀態 git add Tempdir
忽略檔案
通常情況下,總會有一些檔案沒必要讓Git來管理,也不希望這些檔案總是出現在未跟蹤檔案列表,比如說一些日誌檔案,或者變異過程中生成的臨時檔案。這種情況下,可以建立.gitignore檔案,在.gitignore檔案中列出要忽略的檔案即可。比如:
// 忽略所有以.a或者.o結尾的檔案 *.[oa] // 忽略所有以~結尾的檔案 *~ // 忽略所有以.a結尾的檔案 *.a // 除了lib.a檔案,使用!取反 !lib.a // 忽略TODO檔案,而不是TODO資料夾 /TODO // 忽略build資料夾下的所有檔案 build/
檢視檔案做了哪些修改
使用git diff命令可以檢視檔案做了哪些修改,實際上就是和原來的檔案做對比,命令如下:
// 檢視未暫存的檔案做了哪些更新 git diff // 檢視已暫存的檔案做了哪些更新 git diff --cached // 檢視已暫存的檔案做了哪些更新 git diff --staged
其中,git diff --staged和git diff --cached的功能是一樣的。
提交更新
提交的命令是 git commit,命令如下:
// 如果只輸入git commit,會彈出文版編輯器讓輸入這次提交的資訊 git commit // 加上-m引數,直接輸入此次提交的資訊 git commit -m 'add file'
移除檔案
從Git中移除某個檔案,使用的命令是git rm,命令如下:
// 移除a.test檔案 git rm a.test
需要注意的是,移除之後,還需要使用commit命令來此次的操作提交。另外,移除後,本地磁碟上的a.test檔案也會被刪除。
如果只想移除倉庫中的a.test檔案,而保留本地磁碟上的a.test檔案,該如何操作呢?實際上,這樣的應用場景是存在的,比如說忘記加.gitignore檔案,將一些不必要的檔案加入到倉庫中,這時候就會有這樣的問題。我們想把倉庫中沒用的檔案刪除,但是本地還想要保留,git對這種情況是支援的。命令如下:
// 從倉庫中移除a.test,但保留在本地磁碟 git rm --cached a.test
重新命名檔案
Git中重新命名檔案可以使用mv命令,如下:
// 將a.test檔案重新命名為b.test git mv a.test b.test
檢視提交日誌
使用git log命令可以檢視一個倉庫的提交日誌,命令如下:
// 預設不帶引數,會按照提交時間列出所有的更新,包括提交人暱稱,郵箱,最新的提交在最上面 git log // 顯示每次提交的差異,即具體更新了哪些內容 git log -p // 顯示最近兩次提交的差異,限制了日誌數量 git log -p -2 // 顯示每次提交簡略的統計資訊 git log --stat
撤消對檔案的修改
如果不小心改了一個檔案,但是不想修改該檔案,也不想把修改過的檔案放入暫存區,提交,Git提供了撤消修改檔案的命令:
// 撤消修改檔案.DS_Store git checkout -- .Ds_Store
將檔案從暫存區移除
當想將一個檔案從暫存區移除時,可以使用reset命令,如下:
// 將.DS_Store檔案從暫存區移除 git reset HEAD .DS_Store
目前為止,我們所介紹的命令都是和本地倉庫相關,提交、新增等,操作的都是本地倉庫,那麼,如何和遠端倉庫互動呢?如何將本地倉庫的修改推送到遠端倉庫呢?
從遠端倉庫拉取
從遠端倉庫中獲取資料,可以使用git fetch或者git pull命令,如下:
// 從遠端倉庫test拉取資料,需要注意的是,使用這種方式拉取的資料,並不會合併到本地倉庫 // 還需要手動add、commit之後,才會合併到本地倉庫 git fetch test // 從遠端倉庫test拉取資料,這種方式拉取的資料,會嘗試合併到本地倉庫 git pull test
推送到遠端倉庫
推送到遠端倉庫使用git push命令即可,如下:
// 推送到遠端倉庫test git push test
執行完這條命令後,可能會讓輸入使用者名稱和密碼,以驗證是否有推送的許可權。
新增遠端倉庫
新增遠端倉庫使用的命令是git remote add命令,如下:
// 新增一個遠端倉庫,遠端倉庫的url是https://github.com/***/***,遠端倉庫的名稱是test git remote add test https://github.com/***/***
移除遠端倉庫
移除遠端倉庫使用git remote remove命令即可:
// 移除遠端倉庫test git remote remove test
重新命名遠端倉庫
重新命名遠端倉庫使用rename命令,命令如下:
// 把遠端倉庫a重新命名成b git remote rename a b
檢視Git標籤
Git提供了標籤的功能,可以在某些重要的節點增加標籤,比如版本上線,可以打上標籤。檢視已有標籤的命令是:
// 會列出所有的標籤 git tag // 只列出v1.1.0的標籤 git tag -l 'v1.1.0'
建立Git標籤
建立Git標籤使用-a命令,如下:
// 建立標籤,標籤名為v1.4,標籤資訊為 version 1.4 git tag -a v1.4 -m 'version 1.4'
建立Git分支
Git中預設的分支名是master。Git中的master分支並不是一個特殊的分支,master分支和其他的分支沒有什麼區別。之所以大多數倉庫都有master分支,是因為git init命令預設建立的分支是master。
Git建立分支的命令很簡單,如下:
// 新建一個testing分支 git branch testing
注意,這種方式只會新建一個testing分支,但是並不會切換到testing分支下。
另外介紹一下Git中的HEAD指標,HEAD指標指向當前所在的本地分支,可以將HEAD指標理解成當前分支的別名。預設是master分支,則HEAD指向的是master分支;如果切換到testing分支下,則HEAD指向的是testing分支。
分支切換
Git中分支切換使用checkout命令,如下:
// 切換到testing目錄下 git checkout testing
此時HEAD指標也指向了testing分支。另外需要注意的是,切換分支時,工作目錄也會對應的改變。
另外一種切換分支的方式是:
// 這種方式適用於不存在testing分支的情況 git checkout -b testing
上述命令實際上是兩條命令的簡寫:
// 新建testing分支 git branch testing // 切換到testing分支 git checkout testing
分支合併
分支合併使用的是git merge命令,假設現在的工作目錄是master,想要合併testing分支,則命令如下:
// 將testing分支合併到master分支 git merge testing
分支刪除
在合併完testing分支之後,可能testing分支對我們來說已經沒有用了,這時可以將testing分支刪除,刪除命令如下:
// 刪除testing分支 git branch -d testing
衝突標示
在合併分支時,不可避免的會出現衝突,出現衝突後git會標示出來,大概如下:
<<<<<<< HEAD:index.html <div id="footer">contact : [email protected]</div> ======= <div id="footer"> please contact us at [email protected] </div> >>>>>>> iss53:index.html
======號上面是HEAD分支,也就是當前分支的內容,======號下面是合併分支,這裡是iss53分支的內容。出現衝突的原因是兩個分支對同一個檔案的同一行做了修改,這種情況下需要解決手動解決衝突。
分支管理
檢視當前分支列表
使用git branch命令可以檢視當前的分支列表,如下:
// 檢視當前分支列表 git branch
列出的分支列表中,如果某個分支名前有*號,表示該分之是目前所處的分支,也就是HEAD所指向的分支。
檢視每個分支最後的一條提交資訊
使用git branch -v命令,能夠看到每個分支最後的提交資訊,如下:
git branch -v
檢視已合併到當前分支的分支
使用git branch --merged命令,可以檢視當前有哪些分支已經合併到當前分支了,如下:
git branch --merged
檢視未合併到當前分支的分支
使用git branch --no-merged,可以檢視當前有哪些分支沒有合併到當前分支,如下:
git branch --no-merged
推送本地分支
可以使用git push (remote) (branch) 來將本地分支推送到遠端倉庫分支,命令如下:
// 推送本地的testing分支到遠端origin倉庫的testing分支 git push origin testing // 推送本地的testing分支到遠端origin倉庫的somebranch分支 git push origin testing:somebranch
跟蹤遠端倉庫分支
當clone一個倉庫時,Git通常會在本地自動建立一個master分支,該master分之跟蹤的是origin/master,即遠端倉庫origin的master分支。當然,也可以跟蹤其他的分支,命令格式是:git checkout -b [branch] [remotename]/[branch],針對該命令,git提供了--track的快捷方式,命令如下:
// 在本地新建一個serverfix分支,該分支跟蹤的是遠端倉庫origin的serverfix分支 git checkout --track origin/serverfix // 該命令和上面所表達的含義一樣 git checkout -b serverfix origin/serverfix
當然,也可以將本地分支的名字和遠端分支的名字設定成不一樣,命令如下:
// 在本地新建一個sf分支,該分之跟蹤的是遠端倉庫origin的serverfix分支 git checkout -b sf origin/serverfix
可以設定本地分支跟蹤某一個遠端分支,也可以修改本地分支正在跟蹤的遠端分支,使用的引數是-u或者--set-upstream-to,命令如下:
// 設定當前本地分支跟蹤遠端倉庫origin的serverfix分支 git branch -u origin/serverfix // 設定當前分支跟蹤遠端倉庫origin的serverfix分支 git branch --set-upstream-to origin/serverfix
檢視所有本地分支正在跟蹤的遠端分支
使用git branch -vv命令,可以檢視所有本地分支正在跟蹤的遠端分支,而且會列出本地分支是否領先,或者落後遠端分支,命令如下:
// 檢視本地分支正在跟蹤的遠端分支 git branch -vv
拉取遠端分支
拉取遠端分支可以使用git fetch命令或者git pull命令。兩者的區別是:git fetch命令拉取下來的資料,不會修改工作目錄中的內容,需要使用者自己合併,也就是使用git merge命令。而git pull相當於將這兩個命令合併成一個命令,先git fetch,然後git merge。
刪除遠端分支
刪除遠端分支使用的命令如下:
// 刪除遠端倉庫origin的serverfix分支 git push origin --delete serverfix
變基
在Git中整合分支除了merge之外,還有一種方法就是變基(rebase)。看下面的例子:

image
該分之從C2開始產生了分叉,目前有master分支和experiment分支,使用git merge命令,當然可以將experiment分支和master分支合併。前面介紹過,使用merge命令,實際上是將兩個分支的最新快照C3、C4以及二者的最近祖先C2進行了三方合併,合併結果進行了一次新的提交,效果如下:

image
除了使用merge之外,還可以使用變基,變基對應的是rebase命令。使用如下的命令:
git checkout experiment git rebase master
解釋一下上述兩條命令達到的效果。首先找到兩個分支的共同祖先,即experiment、master分支的共同祖先C2,然後對比當前分支experiment相對於該祖先的歷次提交,提取相應的修改並存為臨時檔案;然後將當前分支指向目標基底,也就是master分支C3,將之前儲存的臨時檔案依序應用在目標基底上。實際達到了下面的效果:

image
即:將C4中的修改變基到C3上。
注意:到這裡還沒有完成所有工作,還需要進行一次合併:
git checkout master git merge experiment
最終,master分支指向了最新的提交,效果如下圖:

image
和直接使用merge命令最終達到的效果是一樣的。
變基 VS 合併
既然變基和合並最終達到的效果是一樣的,那麼Git為何提供了這兩種方式呢?以及哪種方式更好呢?其實,如果這兩種方式都試一下的話,會發現兩者的區別。
變基和合並雖然在最終的結果上是一樣的,但是過程是不一樣的,這個過程體現在提交歷史。使用合併,兩個分支的提交歷史是參雜在一起的;而使用變基,提交歷史看起來更為整潔,先做了A功能,A功能完成之後又做了B功能。這樣看來,使用變基似乎好一些,其實不然。這兩種方式其實對應了對提交歷史認識的兩種觀點。
- 一種觀點認為,倉庫的提交歷史就是用來記錄 實際發生過什麼 ,提交歷史是不能隨意改動的。從這個觀點來說,使用分支更為合理,因為分支真實的記錄下來了提交歷史。
- 另一種觀點認為,倉庫的提交歷史是 專案中發生的事情 ,記錄越簡潔,越容易理解越好。從這個角度來講,使用變基更為合理,因為變基的提交日誌更為整潔。
這裡不討論使用哪種方式更好,這個問題沒有一個簡單的答案,更多的是根據專案需求來決定。
變基的另一個例子
先來看看下面倉庫的提交歷史:

image
從C2開始產生了server分支,從C3又產生了client分支。假設現在有這樣的需求:將在client分支上,但是不再server分支上的改變,也就是C8和C9合併到master分支上,應該如何做呢?變基提供了這樣的命令:
// 取出client分支,找出處於client分支和server分支共同的祖先之後的修改,然後將這些修改應用到master分支上 git rebase --onto master server client
達到的效果如下:

image
現在可以快速合併到master分支:
git checkout master git merge client