1. 程式人生 > >Git應用詳解第三講:本地分支的重要操作

Git應用詳解第三講:本地分支的重要操作

前言

前情提要:Git應用詳解第二講:Git刪除、修改、撤銷操作

分支是git最核心的操作之一,瞭解分支的基本操作能夠大大提高專案開發的效率。這一講就來介紹一些分支的常見操作及其基本原理。

一、分支概述

在開發當中,往往需要分工合作。比如:小紅開發A功能,小明開發B功能,小剛開發C功能。如何才能做到三者並行開發呢?git為我們提供的分支功能就能實現這一需求,如下圖所示:

在實際的開發過程中,master分支是用來發布專案穩定版本的。新的功能往往是在一個新建的分支上進行開發,等到新功能開發完畢並經過測試,表現穩定後,才會合併到master分支上進行版本更新。這樣就可以在保持一款軟體發行的同時,同步進行新功能的開發。

通常來說,遠端倉庫的Git分支會有如下幾種:master分支、test分支、develop分支,除此之外可能還有緊急修復bughotfix分支;但是,本地的分支可以有很多;本文主要介紹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,在不合並的情況下強制刪除分支;

七、合併分支

注意:這裡所講的分支指的是有公共提交節點的分支,如下圖中的devmaster分支所示,提交節點A為它們的公共提交節點:

當兩分支沒有公共提交節點,如下圖所示,應採用rebase進行合併,後面會詳細介紹:

1.git merge <branch_name>

  • 首先,建立並切換到新分支dev中,併為test.txt檔案新增內容dev1

    注意:要將dev分支上的這一修改提交到版本庫,才能進行後續合併。因為合併的是提交物件鏈,詳情見後面講解的合併原理:

  • 然後,切換回master分支,通過git merge dev指令,將dev分支中的內容合併到當前所處的master分支中;合併後master分支與dev分支上test.txt檔案的內容達到了同步:

2.分支合併的原則

git分支的合併採用的是三方合併的原則:找到兩分支最新提交AB的公共父節點C,在這三個節點的基礎上合併為節點D。這個節點D就包含了兩個分支上的所有內容:

八、分支的本質

分支:指向一條commit物件鏈或一條工作記錄線的指標;

快照A~D分別表示四次提交(commit),注意提交的順序為:A -> B -> C -> D

從圖中可以看到每一次提交的物件內都會儲存上一次提交的commit id,由此可以從後往前把所有的提交(commit)串起來形成一條鏈(類似單向連結串列),這條鏈就組成了一條完整的分支資訊:

  • 當版本庫中只有一條分支:該分支的最新提交就包含了整條分支的所有內容,代表版本庫的當前狀態。如上圖的快照D,裡面包含了快照A~C中的所有內容,此時快照D中的內容就是整個版本庫中的內容:

  • 當版本庫中有多條分支:每條分支上的最新提交包含了所處分支的全部內容,將各個分支的最新提交進行合併。合併的節點就包含了所有分支的內容,也就是現階段的版本庫本身;如下圖中的d1m2t3分別包含了devmastertest分支上的所有內容:

1.分支 == 指標

情景一:

從圖中可以看到:

  • HEAD為一個指標:指向當前分支;

  • master也為一指標:指向提交;

情景二:

上圖中,devmaster分支上建立的新分支,可知:

  • git在建立新分支時,檔案本身不變化,只需要建立一個代表新分支,並指向當前分支的指標;如圖中的devmaster指向同一個提交,檔案沒有發生任何變化;
  • 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的提交記錄由:ABC組成;
    • 而分支dev的提交歷史則由:ABCD組成;
  • 對兩分支進行合併操作

    master分支上執行:git merge devdev分支的內容合併到了master分支上;這種合併方式叫做:Fast-forward,沒有衝突,改變的只是master指標的指向;

詳細過程

在執行合併操作前:

  • master分支上檢視該分支的提交記錄:

  • dev分支上檢視該分支的提交記錄:

可以看到dev分支只是比master分支多進行了一次提交(dev1),兩分支狀態如下圖所示:

執行合併操作:

先切換到master分支,然後執行git merge dev合併dev分支:

可以看到使用了Fast-forward方式進行合併,合併後兩分支狀態如下圖所示:

合併後,HEAD同時指向了masterdev分支;並且masterdev分支的提交歷史完全一致;這就說明了:使用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.合併衝突

合併的兩分支只有一條分支發生了改變,並且其中一分支是基於另一分支建立的。比如上述的masterdev分支,兩分支沒有分岔,此時不會出現合併衝突;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===之間的內容表示:當前分支mastertest.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方式合併的形式了,如圖所示:

  • 手動解決衝突前:

  • 手動解決衝突後:

同步兩分支

若想將devmaster分支上的內容進行同步,只需要在dev分支中通過git merge master 合併master分支即可。此時就可以使用Fast-forward方式進行合併了,合併結果如下圖所示:

驗證:

合併後,檢視dev分支的提交歷史:

可以看到HEAD同時指向devmaster,即三個指標都指向了最新的一次提交,符合上述分析得出的結論;

經過上面的討論,不難看出:合併分支的實質就是不同提交的合併,以及HEAD和分支指標的移動;

以上就是今天介紹的本地分支重要操作,相信看到這裡的你已經對git本地分支的操作了如指掌了。在下一講中將介紹git最神奇的功能:版本回退。俗話說得好:世上沒有後悔藥。但是在git中,就存在所謂的"後悔藥"!那麼我們下一節再見。