有人把 Git 的分支模型稱爲它的`‘必殺技特性’',也正由於這一特性,使得 Git 從衆多版本控制系統中脫穎而出。 爲什麼 Git 的分支模型如此出衆呢? Git 處理分支的方式可謂是難以置信的輕量,建立新分支這一操做幾乎能在瞬間完成,而且在不一樣分支之間的切換操做也是同樣便捷。 與許多其它版本控制系統不一樣,Git 鼓勵在工做流程中頻繁地使用分支與合併,哪怕一天以內進行許屢次。 理解和精通這一特性,你便會意識到 Git 是如此的強大而又獨特,而且今後真正改變你的開發方式。html
在進行提交操做時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數據的方式,咱們能夠很天然的想到——該提交對象會包含一個指向暫存內容快照的指針。 但不只僅是這樣,該提交對象還包含了做者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操做產生的提交對象有一個父對象,而由多個分支合併產生的提交對象有多個父對象,git
爲了更加形象地說明,咱們假設如今有一個工做目錄,裏面包含了三個將要被暫存和提交的文件。 暫存操做會爲每個文件計算校驗和(使用咱們在 起步 中提到的 SHA-1 哈希算法),而後會把當前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 對象來保存它們),最終將校驗和加入到暫存區域等待提交:github
$ git add README test.rb LICENSE $ git commit -m 'The initial commit of my project'
當使用 git commit
進行提交操做時,Git 會先計算每個子目錄(本例中只有項目根目錄)的校驗和,而後在 Git 倉庫中這些校驗和保存爲樹對象。 隨後,Git 便會建立一個提交對象,它除了包含上面提到的那些信息外,還包含指向這個樹對象(項目根目錄)的指針。如此一來,Git 就能夠在須要的時候重現這次保存的快照。web
如今,Git 倉庫中有五個對象:三個 blob 對象(保存着文件快照)、一個樹對象(記錄着目錄結構和 blob 對象索引)以及一個提交對象(包含着指向前述樹對象的指針和全部提交信息)。算法
你已經知道,每次提交,Git都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在Git裏,這個分支叫主分支,即master
分支。HEAD
嚴格來講不是指向提交,而是指向master
,master
纔是指向提交的,因此,HEAD
指向的就是當前分支。shell
一開始的時候,master
分支是一條線,Git用master
指向最新的提交,再用HEAD
指向master
,就能肯定當前分支,以及當前分支的提交點:vim
Git 是怎麼建立新分支的呢? 很簡單,它只是爲你建立了一個能夠移動的新的指針。 好比,建立一個 testing 分支, 你須要使用 git branch
命令:bash
$ git branch testing
要切換到一個已存在的分支,你須要使用 git checkout
命令。 咱們如今切換到新建立的 testing
分支去:app
$ git checkout testing
那麼,這樣的實現方式會給咱們帶來什麼好處呢? 如今不妨再提交一次:工具
$ vim test.rb $ git commit -a -m 'made a change'
首先,咱們建立dev
分支,而後切換到dev
分支:
$ git checkout -b dev Switched to a new branch 'dev'
git checkout
命令加上-b
參數表示建立並切換,至關於如下兩條命令:
$ git branch dev $ git checkout dev Switched to branch 'dev'
而後,用git branch
命令查看當前分支:
$ git branch * dev master
git branch
命令會列出全部分支,當前分支前面會標一個*
號。
而後,咱們就能夠在dev
分支上正常提交,好比對readme.txt作個修改,加上一行:
Creating a new branch is quick.
你可使用帶 -d
選項的 git branch
命令來刪除分支:
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).
假設你已經修正了 #53 問題,而且打算將你的工做合併入 master
分支。 爲此,你須要合併 iss53
分支到 master
分支,這和以前你合併 hotfix
分支所作的工做差很少。 你只須要檢出到你想合併入的分支,而後運行 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
分支的時候看起來有一點不同。 在這種狀況下,你的開發歷史從一個更早的地方開始分叉開來(diverged)。 由於,master
分支所在提交併非 iss53
分支所在提交的直接祖先,Git 不得不作一些額外的工做。 出現這種狀況的時候,Git 會使用兩個分支的末端所指的快照(C4
和 C5
)以及這兩個分支的工做祖先(C2
),作一個簡單的三方合併。
有時候合併操做不會如此順利。 若是你在兩個不一樣的分支中,對同一個文件的同一個部分進行了不一樣的修改,Git 就無法乾淨的合併它們。 若是你對 #53 問題的修改和有關 hotfix
的修改都涉及到同一個文件的同一處,在合併它們的時候就會產生合併衝突:
$ git merge iss53 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result.
此時 Git 作了合併,可是沒有自動地建立一個新的合併提交。 Git 會暫停下來,等待你去解決合併產生的衝突。 你能夠在合併衝突後的任意時刻使用 git status
命令來查看那些因包含合併衝突而處於未合併(unmerged)狀態的文件:
$ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: index.html no changes added to commit (use "git add" and/or "git commit -a")
任何因包含合併衝突而有待解決的文件,都會以未合併狀態標識出來。 Git 會在有衝突的文件中加入標準的衝突解決標記,這樣你能夠打開這些包含衝突的文件而後手動解決衝突。 出現衝突的文件會包含一些特殊區段,看起來像下面這個樣子:
<<<<<<< HEAD:index.html <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53:index.html
這表示 HEAD
所指示的版本(也就是你的 master
分支所在的位置,由於你在運行 merge 命令的時候已經檢出到了這個分支)在這個區段的上半部分(=======
的上半部分),而 iss53
分支所指示的版本在 =======
的下半部分。 爲了解決衝突,你必須選擇使用由 =======
分割的兩部分中的一個,或者你也能夠自行合併這些內容。 例如,你能夠經過把這段內容換成下面的樣子來解決衝突:
<div id="footer"> please contact us at email.support@github.com </div>
上述的衝突解決方案僅保留了其中一個分支的修改,而且 <<<<<<<
, =======
, 和 >>>>>>>
這些行被徹底刪除了。 在你解決了全部文件裏的衝突以後,對每一個文件使用 git add
命令來將其標記爲衝突已解決。 一旦暫存這些本來有衝突的文件,Git 就會將它們標記爲衝突已解決。
如今已經建立、合併、刪除了一些分支,讓咱們看看一些經常使用的分支管理工具。
git branch
命令不僅是能夠建立與刪除分支。 若是不加任何參數運行它,會獲得當前全部分支的一個列表:
$ git branch iss53 * master testing
注意 master
分支前的 *
字符:它表明如今檢出的那一個分支(也就是說,當前 HEAD
指針所指向的分支)。 這意味着若是在這時候提交,master
分支將會隨着新的工做向前移動。 若是須要查看每個分支的最後一次提交.
軟件開發中,bug就像屢見不鮮同樣。有了bug就須要修復,在Git中,因爲分支是如此的強大,因此,每一個bug均可以經過一個新的臨時分支來修復,修復後,合併分支,而後將臨時分支刪除。
當你接到一個修復一個代號101的bug的任務時,很天然地,你想建立一個分支issue-101
來修復它,可是,等等,當前正在dev
上進行的工做尚未提交:
$ git status On branch dev Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hello.py 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: readme.txt
並非你不想提交,而是工做只進行到一半,還無法提交,預計完成還需1天時間。可是,必須在兩個小時內修復該bug,怎麼辦?
幸虧,Git還提供了一個stash
功能,能夠把當前工做現場「儲藏」起來,等之後恢復現場後繼續工做:
$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge
如今,用git status
查看工做區,就是乾淨的(除非有沒有被Git管理的文件),所以能夠放心地建立分支來修復bug。
首先肯定要在哪一個分支上修復bug,假定須要在master
分支上修復,就從master
建立臨時分支:
$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use "git push" to publish your local commits) $ git checkout -b issue-101 Switched to a new branch 'issue-101'
如今修復bug,須要把「Git is free software ...」改成「Git is a free software ...」,而後提交:
$ git add readme.txt $ git commit -m "fix bug 101" [issue-101 4c805e2] fix bug 101 1 file changed, 1 insertion(+), 1 deletion(-)
修復完成後,切換到master
分支,並完成合並,最後刪除issue-101
分支:
$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use "git push" to publish your local commits) $ git merge --no-ff -m "merged bug fix 101" issue-101 Merge made by the 'recursive' strategy. readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
太棒了,原計劃兩個小時的bug修復只花了5分鐘!如今,是時候接着回到dev
分支幹活了!
$ git checkout dev Switched to branch 'dev' $ git status On branch dev nothing to commit, working tree clean
工做區是乾淨的,剛纔的工做現場存到哪去了?用git stash list
命令看看:
$ git stash list stash@{0}: WIP on dev: f52c633 add merge
工做現場還在,Git把stash內容存在某個地方了,可是須要恢復一下,有兩個辦法:
一是用git stash apply
恢復,可是恢復後,stash內容並不刪除,你須要用git stash drop
來刪除;
另外一種方式是用git stash pop
,恢復的同時把stash內容也刪了:
$ git stash pop On branch dev Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hello.py 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: readme.txt Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list
查看,就看不到任何stash內容了:
$ git stash list
你能夠屢次stash,恢復的時候,先用git stash list
查看,而後恢復指定的stash,用命令:
$ git stash apply stash@{0}
軟件開發中,總有無窮無盡的新的功能要不斷添加進來。
添加一個新功能時,你確定不但願由於一些實驗性質的代碼,把主分支搞亂了,因此,每添加一個新功能,最好新建一個dev分支,在上面開發,完成後,合併,最後,刪除該dev分支。
如今,你終於接到了一個新任務:開發代號爲Vulcan的新功能,該功能計劃用於下一代星際飛船。
因而準備開發:
$ git checkout -b dev-vulcan Switched to a new branch 'dev-vulcan'
5分鐘後,開發完畢:
$ git add vulcan.py $ git status On branch dev-vulcan Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: vulcan.py $ git commit -m "add feature vulcan" [feature-vulcan 287773e] add feature vulcan 1 file changed, 2 insertions(+) create mode 100644 vulcan.py
切回dev
,準備合併:
$ git checkout dev
一切順利的話,feature分支和bug分支是相似的,合併,而後刪除。
可是!
就在此時,接到上級命令,因經費不足,新功能必須取消!
雖然白乾了,可是這個包含機密資料的分支仍是必須就地銷燬:
$ git branch -d dev-vulcan error: The branch 'dev-vulcan' is not fully merged. If you are sure you want to delete it, run 'git branch -D dev-vulcan'.
銷燬失敗。Git友情提醒,dev-vulcan
分支尚未被合併,若是刪除,將丟失掉修改,若是要強行刪除,須要使用大寫的-D
參數。。
如今咱們強行刪除:
$ git branch -D dev-vulcan
Deleted branch dev-vulcan (was 287773e).
終於刪除成功!
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.