前言
分支是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-ff 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
中,就存在所謂的"后悔葯"!那么我們下一節再見。