幾乎全部的版本控制系統都以分支的方式進行操做,分支是獨立於項目主線的一條支線,咱們能夠在不影響主線代碼的狀況下,在分支下進行工做。對於傳統的一些版本控制工具來講,咱們一般須要花費比較多的時間拷貝主線代碼,建立一個分支,而且對分支的管理效率也愈來愈不使人滿意,而現在備受推崇的Git確實名副其實,Git中的分支很是輕量,咱們能夠隨時隨意建立任意數量的新分支,幾乎感受不到什麼延時,並且對分支的操做也很高效,如,切換分支,暫存內容,分支合併,分支提交等。javascript
上一節咱們提到相對於其餘大多數版本控制系統,Git分支是輕量且高效的,爲何呢?答案在前幾篇已經有提到:傳統的版本控制系統存儲的數據是文件的變動,而Git則是存儲一系列的文件快照(snapshot)。java
Git分支的這些特性,使得分支對咱們幾乎沒有什麼限制,通常針對每個功能或需求均可以隨意建立分支,而在傳統的版本控制系統,這樣幾乎是不現實的。git
當咱們向服務器提交數據時,Git會存儲一個提交對象(commit object),這個存儲對象包括一系列有用信息,詳見上一篇中提交對象。緩存
master,有主人,大師的意思,在Git是一般做爲主幹分支,Git初始化倉庫時,默認建立的分支名就是master,就像默認的遠端主機別名是origin同樣,大多數人不會修改它,這並不說明它與別的分支有什麼區別,你能夠隨意修更名稱。服務器
在Git中,除了默認的master主幹分支,咱們建立的每個分支,通常可分爲兩種:工具
Git中有一個HEAD指針,始終指向當前分支,如圖可見,項目當前處在master分支,以前一共有三次提交:測試
上圖可見,第一行顯示了當前項目全部分支,HEAD -> master
代表當前所處分支爲master,咱們能夠總結以下圖:spa
咱們能夠在項目根目錄.git文件下找到一個HEAD文件:vi .git/HEAD
,其內保存了指向當前分支最新提交的指針:3d
該指針指向refs/heads/分支名文件,咱們進入.git/refs/heads/目錄,其下以分支名爲文件名列出了全部分支:版本控制
咱們查看當前分支文件,執行vi master
:
能夠看到,其內存儲的就是當前分支的最新一次提交對象ID。
接下來,假設有一個需求A,咱們建立一個分支work-a:
git checkout -b 分支名複製代碼
-b
參數聲明爲建立新分支
等價於如下兩條指令:
git branch 分支名
git checkout 分支名複製代碼
git checkout 分支名複製代碼
表示切換到該分支,上文提到指定-b
配置即說明建立新分支。
注:在切換分支前,必定確保當前分支的修改已經提交或者緩存。
咱們常常會遇到同時須要開發多個功能和需求,或者忽然發現線上bug須要緊急處理,咱們只須要提交當前分支修改,而後切換到主幹分支,從其基礎上再切出一個新分支fix-bug1:
能夠看到,在work-a分支上咱們新增了一次提交:b287b8e22470b20cc98e6224a8023708b4cc6989
。
如今咱們在fix-bug1分支上修復bug後,進行提交:
能夠看到,在fix-bug1分支上多了一個提交:ca270e6
,如今整個結構就變成以下圖:
咱們已經修復了某bug或完成了功能開發,這時要作的是把代碼併入主幹,,固然通常公司或團隊都須要通過代碼審查,才能併入主幹,在此略過不談,分支合併相關指令:
git merge 分支名複製代碼
該指令告訴Git將指定分支合併到當前分支,固然是可能出現衝突的,咱們按照指示解決衝突,便可。
如今咱們先切換到master分支,而後把fix-bug1分支併入主幹:
能夠看到執行git merge
指令後,狀態信息顯示:
ca270e6
;如今,咱們再次建立一個分支fix-bug2,並進行幾回修改提交:
屢次提交後,狀態以下:
咱們經過非快速推動方式合併分支進主幹分支:
如上圖,指定--no-ff
即聲明進行非快速推動合併,第二行的Merge made by the 'recursive' strategy
代表經過非快速推動方式合併,咱們發現除了分支上進行的提交記錄外,Git建立了一個新的提交對象:7a657a
,使用git log --graph
指令查看其信息:
如圖,快速推動方式合併入主幹的fix-bug1分支的提交記錄直接併入主線,且不會建立新的提交對象;而對於非快速推動方式合併的fix-bug2分支,其提交歷史也都保存,可是並未進入主線,而是保存了一條支線,同時,在主線上建立一個新的提交對象。
最後描述其結構如圖:
從上例,對比一下兩種方式合併分支的異同:
咱們查看一下新建立提交對象:
能夠看到該提交對象中有兩個指針指向父提交對象,一個指向主線中的父提交對象,一個指向fix-bug2分支合併而來的支線父提交對象。
除了以前提到的兩種合併的狀況,其實還存在這樣一種狀況,就是如今假如我完成了work-a分支的開發,須要將其併入主幹,咱們能看到當前master主幹分支已經推動到7a6576
了,而work-a分支指向b287b8
,二者有共同祖先提交對象6d50f6
,咱們將其合併:
上圖第二行代表這次是經過非快速推動方式合併,咱們查看提交對象記錄圖:
結構如圖:
咱們發現,三路合併結構是在須要合併的兩個分支的最新提交對象的基礎上,建立一個新提交對象(4ae14b),將合併主分支(即執行合併指令時,當前所處分支)的HEAD指針前移指向該提交對象,該提交對象有兩個父提交對象,分別爲合併前待合併分支的最新提交對象(即b287b8和7a657a)。
關於三路合併須要明確:
在合併分支,不可避免會發生衝突,當咱們在兩個分支對同一文件同一部分進行不一樣修改後,發起合併時就會提示有衝突,假設咱們有work-b分支,在其基礎上切出新分支work-b-1,而後在兩分支上分別對README.md文件同一部分進行不一樣修改並提交,而後將work-b-1分支合併到work-b分支:
發現README.md文件有衝突,查看該文件:
如上圖,列出了兩個分支的不一樣修改,HEAD代表當前分支的修改內容,下面是work-b-1分支的修改,咱們選擇須要保留的內容,刪除其餘無關信息和內容,而後保存該文件,查看當前狀態:
根據提示,解決衝突後提交:
對於建立過但並未刪除的分支,咱們能夠查看分支列表,依然使用git branch
指令,不傳入任何參數:
圖中列出了全部分支,前面帶星號的表示當前分支,固然咱們還能夠查看指明最新提交信息的分支列表,能夠添加-v
參數:
除了能夠查看全部分支列表,Git還支持篩選已合併或未合併至當前分支的全部分支:
--merged
參數指明篩選已合併分支;--no-merged
參數指明篩選未合併分支。當分支合併入主幹後,也許咱們再也不須要那個分支了,咱們須要將其刪除,使用指令:
git branch -d 分支名複製代碼
以前介紹到使用git branch
是建立新分支,而指定-d
參數,說明須要刪除該分支:
咱們注意到,前文所講述的分支都是存在本地的,即本地分支,還須要瞭解遠程分支,如[remote]/[branch]這種形式,表示是遠端主機的某分支,關於遠端主機詳情請查看,其實遠程分支和本地分支基本理論概念仍是相同的,區別是有些指令不一樣而已:
git checkout -b test origin/develop複製代碼
以上指令即從遠程分支(遠端主機origin上的develop分支)切出新的本地分支test分支。
前文已經介紹了本地分支和遠程分支的概念及操做,那麼這兩類分支之間應該有某種關係將他們關聯起來,本地項目都須要與遠端主機倉庫同步(pull & push),當咱們從一個遠程分支切出(建立)一個本地分支時,這個分支就叫跟蹤分支(tracking branch),而遠程分支叫上游分支(upstream branch)。
當咱們克隆一個遠端倉庫時,會默認建立一個跟蹤分支master,其上游分支就是遠端主機別名/master
。
建立跟蹤分支指令以下:
git checkout -b 本地分支名 遠端主機別名/遠程分支名複製代碼
固然也能夠不指定分支名,使用遠程分支同名:
git checkout --track 遠端主機別名/遠程分支名複製代碼
有時候,可能須要爲本地分支設置其上游分支,添加-u
參數:
git branch -u 遠端主機別名/遠程分支名複製代碼
以上指令就指明當前分支跟蹤某遠端主機的遠程分支。
使用如下指令查看分支的上游分支:
git branch -vv複製代碼
上圖輸出信息第二行代表master分支跟蹤遠程origin/master分支,ahead 7代表本地有7個提交未推到服務器,其餘分支不是跟蹤分支,沒有上游分支。
對於再也不須要的遠程分支,是能夠刪除的:
git push origin --delete test複製代碼
以上指令刪除遠端主機origin的test分支,可是在垃圾回收以前,Git服務器仍然會保留分支數據,咱們能夠很方便的恢復數據,以後會詳細介紹。
Git中有兩種方式整合不一樣分支的修改:第一種是前文介紹的合併(merge),另外一種就是本節的主題變基(rebase)。
變基其實與前文提到的三路合併(three-way merge)很有淵源:
如圖work-a分支與主幹master分支合併後,建立一個新提交對象,咱們還能夠經過變基完成兩個分支的修改整合,因爲work-a分支已合併到master分支,咱們在work-a分支再提交一次修改e0ae7dc
,而後咱們將work-a分支對master分支進行變基:
執行變基時,因爲兩個分支對同一文件同一部分進行了不一樣修改,會提示衝突,須要解決衝突,咱們修改文件解決衝突,而後查看狀態:
上圖,第一行rebase in progress; onto 4ae14b3
說明當前分支針對4ae14b3
快照進行變基,第三到第五行分別說明:
git rebase --continue
指令繼續變基;git rebase --skip
指令,跳過解決衝突;git rebase --abort
指令,終止變基,回到分支變基前狀態。下面第6到第八行說明:
git reset HEAD <file>
指令撤銷某文件變動;git add <file>
指令標記衝突爲已解決狀態。最後一行no changes added to commit (use "git add" and/or "git commit -a")
,說明還沒有標記衝突,須要使用指令標記變動,在繼續執行變基:
如上圖,變基後,在主線上建立新提交對象640b83
,並修改work-a分支指針指向該提交對象:
以後咱們能夠正常的合併:
如圖,主線分支更新提交對象到640b83a
,第二行Fast-forward
說明這次合併屬於快速推動合併方式,結構以下:
基於上例,三路合併,整合修改變動後會保留分支的原始提交記錄,新建立提交對象有兩個父提交對象,一個在主線上,一個在待合併分支上;而變基則不能保留待合併分支的原始提交記錄,主線上新建的提交對象只有一個位於主線上的父提交對象。更多變基相關內容計劃單獨出文介紹。
至於到底選用哪一種方式整合變動,變基仍是合併,這個一直有爭論,沒有哪種方式絕對合理,咱們只須要把握一個原則:不管變基仍是合併,你應該只操做本地歷史記錄,任何已經推到服務器併入主幹的內容和提交歷史不該該更改。