Git應用詳解第三講:本地分支的重要操作
前言
前情提要:Git應用詳解第二講:Git刪除、修改、撤銷操作
分支是git
最核心的操作之一,瞭解分支的基本操作能夠大大提高專案開發的效率。這一講就來介紹一些分支的常見操作及其基本原理。
一、分支概述
在開發當中,往往需要分工合作。比如:小紅開發A
功能,小明開發B
功能,小剛開發C
功能。如何才能做到三者並行開發呢?git
為我們提供的分支功能就能實現這一需求,如下圖所示:
在實際的開發過程中,master
分支是用來發布專案穩定版本的。新的功能往往是在一個新建的分支上進行開發,等到新功能開發完畢並經過測試,表現穩定後,才會合併到master
分支上進行版本更新。這樣就可以在保持一款軟體發行的同時,同步進行新功能的開發。
通常來說,遠端倉庫的Git
分支會有如下幾種:master
分支、test
分支、develop
分支,除此之外可能還有緊急修復bug
的hotfix
分支;但是,本地的分支可以有很多;本文主要介紹Git
本地分支的內容。
二、檢視本地分支
1.git branch
檢視當前版本庫中的所有分支:
其中的 *
表示當前處於的分支,可見當前處於master
分支;
使用git init
初始化git
倉庫時,git
會自動建立一個master
分支。但是,如果沒有在master
分支上進行任何提交就切換到其他分支,那麼在切換分支的時候master
分支會被銷燬。並且,無法檢視沒有提交記錄的分支,如下圖所示:
2.git branch -a
檢視所有本地分支,包括本地分支和本地遠端分支:
3.git branch -v
檢視所有本地分支上最近一次的提交記錄:
但是,該指令無法檢視本地遠端分支:
4.git branch -r
-r
引數用於單獨檢視本地遠端分支:
5.git branch -av
該指令不僅可以顯示所有的本地分支,包括本地遠端分支,以及對應分支上的最新提交資訊:
6.git branch -vv
-vv
引數表示檢視所有本地分支與遠端分支的關聯情況。如圖所示,本地master
分支有本地遠端分支origin/master
與之關聯,說明它已與遠端master
分支建立了關聯;
至於上面提到的本地遠端分支,將在下一講中詳細介紹。
三、建立本地分支
1.git branch <branch_name>
可通過上述命令建立新分支new_branch
:
由於是在master
分支上建立的new_branch
分支,所以new_branch
分支與master
分支有著部分共同的提交歷史;所以,master
分支上的檔案,new_branch
分支上都有。但是,在new_branch
分支上新增的new_branch
檔案,不會存在於master
分支上:
此時兩分支的狀態為:
2.git branch -b <branch_name>
通過上述命令可建立並切換到new_branch
分支:
如圖所示,本來所在分支為master
,並且沒有new_branch
分支。執行上述命令後,建立並切換到了new_branch
分支上。
四、切換本地分支
1.git checkout <branch_name>
比如切換到new_branch
分支:
2.git checkout -
切換回上次操作的分支:
五、重新命名本地分支
1.git branch -m <oldName> <newName>
如下圖所示,將本地分支master
重新命名為master2
:
六、刪除本地分支
1.git branch -d new_branch
刪除new_branch
分支:
注意點:
-
不能刪除當前所處的分支;
-
當需要刪除的分支上有
master
分支沒有的內容,並且刪除前沒有進行合併(merge
)時,會報錯:
此時可以通過git branch -D new_branch
使用引數D
,在不合並的情況下強制刪除分支;
七、合併分支
注意:這裡所講的分支指的是有公共提交節點的分支,如下圖中的dev
與master
分支所示,提交節點A
為它們的公共提交節點:
當兩分支沒有公共提交節點,如下圖所示,應採用rebase
進行合併,後面會詳細介紹:
1.git merge <branch_name>
-
首先,建立並切換到新分支
dev
中,併為test.txt
檔案新增內容dev1
:注意:要將
dev
分支上的這一修改提交到版本庫,才能進行後續合併。因為合併的是提交物件鏈,詳情見後面講解的合併原理: -
然後,切換回
master
分支,通過git merge dev
指令,將dev
分支中的內容合併到當前所處的master
分支中;合併後master
分支與dev
分支上test.txt
檔案的內容達到了同步:
2.分支合併的原則
git
分支的合併採用的是三方合併的原則:找到兩分支最新提交A
和B
的公共父節點C
,在這三個節點的基礎上合併為節點D
。這個節點D
就包含了兩個分支上的所有內容:
八、分支的本質
分支:指向一條commit
物件鏈或一條工作記錄線的指標;
快照A~D
分別表示四次提交(commit
),注意提交的順序為:A -> B -> C -> D
:
從圖中可以看到每一次提交的物件內都會儲存上一次提交的commit id
,由此可以從後往前把所有的提交(commit
)串起來形成一條鏈(類似單向連結串列),這條鏈就組成了一條完整的分支資訊:
-
當版本庫中只有一條分支:該分支的最新提交就包含了整條分支的所有內容,代表版本庫的當前狀態。如上圖的快照
D
,裡面包含了快照A~C
中的所有內容,此時快照D
中的內容就是整個版本庫中的內容: -
當版本庫中有多條分支:每條分支上的最新提交包含了所處分支的全部內容,將各個分支的最新提交進行合併。合併的節點就包含了所有分支的內容,也就是現階段的版本庫本身;如下圖中的
d1
、m2
、t3
分別包含了dev
、master
、test
分支上的所有內容:
1.分支 ==
指標
情景一:
從圖中可以看到:
-
HEAD
為一個指標:指向當前分支; -
master
也為一指標:指向提交;
情景二:
上圖中,dev
為master
分支上建立的新分支,可知:
git
在建立新分支時,檔案本身不變化,只需要建立一個代表新分支,並指向當前分支的指標;如圖中的dev
與master
指向同一個提交,檔案沒有發生任何變化;HEAD
指向dev
分支,表示當前所處分支為dev
,相當於執行了:git checkout -b dev
後的狀態;
相信你已經發現:
HEAD
是一個始終指向當前分支的指標;
2.HEAD
識別符號
HEAD
檔案是一個指向當前所在分支的引用識別符號,也可以理解為一個指標,它與分支之間的關係是這樣的:
檢視HEAD
HEAD
檔案中並不包含SHA1
值(每次提交的commit ID
),而是包含一個指向另外一個引用的指標。我們可以檢視.git
目錄下的HEAD
檔案:
可見HEAD
指向的是當前所在的master
分支;
當我們通過git checkout -b dev
建立並切換到dev
分支後,再次進入.git
資料夾檢視HEAD
,會發現此時HEAD
指向了dev
:
由此證明了HEAD
始終指向當前分支;
當執行git commit
命令時,git
會建立一個commit
物件(比如下圖D
)。並且將這個commit
物件的parent
指標指向HEAD
所指向的引用(master
)指向的提交(也就是C
),這樣就能形成一條提交鏈:
我們對於HEAD
修改的任何操作,都會被git reflog
完整記錄下來:
但是手動地修改HEAD
檔案,這些資訊就不會被記錄下來,所以十分不建議手動修改git
相關的配置檔案,而是應該儘量採用命令列的方式來修改。
修改HEAD
實際上,我們可以通過git
的底層命令symbolic-ref
來實現對HEAD
檔案內容的修改;
git
中的命令可分為兩類:高階命令和底層命令;之前介紹的git add
等都是高階命令;
讀取:
寫入:
要注意格式:refs/heads/develop
;
檢視ORIG_HEAD
檔案:
裡面是一個SHA1
值,檢視當前的提交資訊:
可以發現,ORIG_HEAD
裡面的SHA1
值就是最新一次提交的SHA1
值。
檢視FETCH_HEAD
檔案:
裡面有兩個資訊,一個是最新提交的commit ID
,另一個是提交資訊。
所以,對於git
而言commit ID
是十分重要的資訊,通過這個SHA1
值可以回溯或查詢需要的提交。
3.git merge
原理
過程圖解
-
在新分支上進行提交操作
上圖表示在
dev
分支上進行了一次提交,此時:- 分支
master
的提交記錄由:A
、B
和C
組成; - 而分支
dev
的提交歷史則由:A
、B
、C
和D
組成;
- 分支
-
對兩分支進行合併操作
在
master
分支上執行:git merge dev
將dev
分支的內容合併到了master
分支上;這種合併方式叫做:Fast-forward
,沒有衝突,改變的只是master
指標的指向;
詳細過程
在執行合併操作前:
- 在
master
分支上檢視該分支的提交記錄:
- 在
dev
分支上檢視該分支的提交記錄:
可以看到dev
分支只是比master
分支多進行了一次提交(dev1
),兩分支狀態如下圖所示:
執行合併操作:
先切換到master
分支,然後執行git merge dev
合併dev
分支:
可以看到使用了Fast-forward
方式進行合併,合併後兩分支狀態如下圖所示:
合併後,HEAD
同時指向了master
和dev
分支;並且master
和dev
分支的提交歷史完全一致;這就說明了:使用Fast-forward
(快進合併)方式進行分支合併,只會改變master
分支指標的指向;
4.Fast-forward
- 預設情況下,合併分支時
git
會使用Fast-forward
模式; - 在這種模式下,刪除分支會丟棄分支資訊;
- 進行分支合併操作時加上
--no-ff
引數會禁止使用Fast-forward
方式,這樣會多出一次提交記錄;
ff
表示Fast-forward
。
具體演示如下:
使用Fast-forward
首先,檢視master
分支上最新的3
次提交:
此時兩分支的狀態為:
隨後在dev
分支上新增一次提交:dev2
。檢視dev
分支上最新的3
次提交:
此時兩個分支的狀態為:
切換回master
分支,通過git merge dev
合併dev
分支,此時預設採用Fast-forward
方式:
可以看到合併後,master
直接指向了dev
的最新提交,並沒有產生新的提交。合併後兩分支的狀態如下所示:
由此驗證了Fast-forward
方式只會改變分支指標的指向。
禁用Fast-forward
合併時可以通過:
git merge --no-of dev
禁用Fast-forward
模式。
-
繼續在
dev
分支新增一次提交:dev3
。然後檢視dev
分支上最新的3
次提交: -
不修改
master
分支,檢視其最新的3
次提交:此時兩個分支的狀態為:
-
然後,在
master
分支上不使用Fast-forward
方式合併dev
分支。合併命令採用:git merge --no-ff dev
執行後進入如下的
vim
編輯器介面,要求我們填寫提交註釋:
這說明不使用Fast-forward
方式合併分支,會觸發了一次提交。填寫提交註釋後完成提交操作,合併完成後,檢視master
分支的提交記錄:
可以發現禁用了Fast-forward
模式的合併會比dev
分支多產生一次提交:Merge branch 'dev'
,即使合併後的內容是一樣的。此時兩分支的狀態為:
由此驗證了,禁用Fast-forward
方式合併,會多出一個表示合併的提交記錄。
5.合併衝突
合併的兩分支只有一條分支發生了改變,並且其中一分支是基於另一分支建立的。比如上述的master
與dev
分支,兩分支沒有分岔,此時不會出現合併衝突;git
會通過Fast-forward
方式自動完成合並操作;
但是,當合並的兩分支都發生改變時,即分支出現分岔,如下圖所示。此時就需要解決衝突後手動合併分支了:
具體演示如下:
合併前
首先,分別對兩分支上的test.txt
檔案進行修改,並分別將修改提交到各自的分支;
- 在
master
分支上進行新的提交:mas3
,然後檢視檔案test.txt
內容和分支提交記錄:
- 在
dev
分支上進行新的提交:dev1
,然後檢視檔案test.txt
內容和分支提交記錄:
可見兩分支的提交記錄只有最新一次提交不一樣:
合併後
在master
分支上,通過git merge dev
合併dev
分支時,會在共同修改的test.txt
檔案中出現合併衝突,如下圖所示:
出現衝突的原因為:兩個分支都對同一個檔案test.txt
進行了修改,git
合併時並不知道以哪個分支的修改為標準。所以不能採用Fast-forward
方式自動合併,需要解決衝突,手動合併。
手動合併過程
手動合併操作需要分如下三步進行:
- 第一步:選擇需要保留的內容,手動解決合併衝突;
此時進入發生合併衝突的test.txt
檔案:
會出現典型的衝突呈現方式(此時HEAD
指向的是master
分支),其中:
-
<<<HEAD
與>>>dev
之間的內容表示:兩分支上test.txt
檔案的不同之處; -
<<<HEAD
與===
之間的內容表示:當前分支master
對test.txt
檔案的修改; -
===
與>>>dev
之間的內容表示:dev
分支對test.txt
檔案的修改;
此時只需要在test.txt
中保留想要的內容即可,例如:將兩分支對test.txt
的修改都進行儲存,刪除3、5、7
行多餘的內容:
除此之外,還可以通過git mergetool
指令,呼叫vimdiff
工具進入vim
編輯器,來解決test.txt
檔案的衝突:
在實際開發中,我們很少手動進行合併,而是藉助於一些工具來實現。
- 第二步:使用
git add test.txt
將手動解決衝突時對test.txt
的修改提交到暫存區;
編輯完畢後,可以看到此時仍然處於合併過程中(MERGING
)。通過git status
檢視狀態,發現手動解決衝突時對test.txt
檔案的修改操作還在工作區中,需要通過git add test.txt
將這一修改納入暫存區,繼續進行合併:
- 第三步:通過
git commit -m '合併註釋'
將手動解決衝突時對test.txt
的修改進行提交,完成合並操作;
如果需要填寫較多的合併註釋,可以通過git commit
進入vim
編輯器編輯。提交後,完成整個手動合併過程。
完成手動合併分支後,檢視兩分支的提交歷史:
master
分支上的提交歷史:
dev
分支上的提交歷史:
可以發現此時兩分支轉變為了可以通過Fast forward
方式合併的形式了,如圖所示:
- 手動解決衝突前:
-
手動解決衝突後:
同步兩分支
若想將dev
和master
分支上的內容進行同步,只需要在dev
分支中通過git merge master
合併master
分支即可。此時就可以使用Fast-forward
方式進行合併了,合併結果如下圖所示:
驗證:
合併後,檢視dev
分支的提交歷史:
可以看到HEAD
同時指向dev
與master
,即三個指標都指向了最新的一次提交,符合上述分析得出的結論;
經過上面的討論,不難看出:合併分支的實質就是不同提交的合併,以及HEAD
和分支指標的移動;
以上就是今天介紹的本地分支重要操作,相信看到這裡的你已經對
git
本地分支的操作了如指掌了。在下一講中將介紹git
最神奇的功能:版本回退。俗話說得好:世上沒有後悔藥。但是在git
中,就存在所謂的"後悔藥"!那麼我們下一節再見。