大家好,今天我們來聊git當中一個非常非常重要的特性,就是branch。
git branch可以說是git當中最重要的概念了,甚至沒有之一。因為git最重要的使用場景就是協同開發,大家一起在一個項目當中開發不同的功能。正是由於有了分支的概念,可以讓大家在開發的時候互不影響。如果沒有這個功能,git的其他功能做的再好,可能都沒有用。
所以某種程度上可以理解為,學git最重要的就是學習分支的相關內容。當然分支的相關內容和命令非常多,我們想要瞬間全部都學會顯然不太現實。但對這個概念有一些理解,懂得一些基本命令的用法應該還是做得到的。
在理解分支這個概念之前,我們需要先來介紹一下Git的結構。
Git的結構
branch的英文就是樹枝,后來衍生出了分叉、支路等意思。這個單詞非常形象,因為git倉庫的所有提交節點之間的關系,其實就是一棵樹,所以一個分支也可以看成是樹上的一條鏈路。但是這樣有一個小問題是,如果說分支是一條鏈路的話,那么這個鏈路上的每一個節點代表的是一個commit提交,意味着一份代碼快照,有些像是游戲存檔。
一個branch上有多個commit,一個游戲也可以有多個存檔,但是當下顯然只能加載一個。所以git當中用一個指針指向當前加載的commit,也就是說縱向來看一個分支代表的是一連串的提交,但在git當中我們使用的分支其實是一個指針,一個在commit當中切換的指針。
我們來看個例子,比如一開始的時候我們只有默認分支master,它指向當前的一個提交。

現在我們使用git branch test命令創建一個測試分支,執行之后,其實只不過是多了一個指針也指向當前的commit。git當中的結構變成這樣:

當我們在test分支上做了改動提交之后,git會產生一個新的提交,並且移動test指針,而master指針會留在原地。

如果我們再回到master也進行了改動和提交之后,又會產生新的節點,並且這個節點會和test的節點區分開,形成新的鏈路,於是就形成了一棵樹的樣子。

分支與HEAD指針
怎么分支創建,我們剛才已經講過了,可以通過git branch加上我們想要的分支名來完成。使用了這個命令之后,git內部會創建一個新的指針指向當前的commit。
有一個問題是git怎么知道我們當前的代碼在哪里呢?即使知道了代碼在哪個分支上,又怎么確定在哪一個節點呢?其實git內部還有一個特殊的指針叫做HEAD,它指向的是當前代碼倉庫的位置。當我們提交代碼的時候,不止只有分支的這些指針會往前移動,HEAD指針也會隨着移動。
其實HEAD指針不僅可以往前移動,還可以移動到任意節點上,哪怕不再當前的分支上也可以。移動HEAD指針需要用到git checkout命令,它可以指定HEAD指針移動到其他位置。既可以是某一個分支,也可以是根據commit id來確定的節點。
比如我們當前在master分支,我們要切換到test分支上,我們只需要運行:
git checkout master
另外,使用git checkout命令加上參數-b,我們還可以創建分支。比如假如當前test分支不存在,我們可以通過git checkout -b test來創建,並且還會自動切換到新建出的test分支上。

我們在新的分支上做了提交之后,可以通過git log --oneline --decorate命令來查看每一個分支所指向的位置。這里的oneline是將log壓縮到一行展示,decorate用來查看分支所指向的位置。

我們可以發現test和master分支指向的提交不同,並且當前我們的HEAD在test上,說明我們當前在test分支。我們前面說了git checkout命令可以改變HEAD指針指向的位置,假如我們在當前目錄下執行git checkout 18a417,這個18a417對應的是add article 6這個提交。這個提交是在master分支的,是test分支的上游,我們使用命令會自動將HEAD跳轉到master分支。

使用之后我們發現的確到了master分支,這里由於我配置了zsh工具,它會提示我當前所處的位置是比master分支指向的最新位置落后3個提交。
這也驗證了我們說的,HEAD指針可以隨意跳轉。現在想必你們應該能理解上一篇文章當中介紹的,撤銷當前分支的命令git reset HEAD^的含義了,HEAD指的就是HEAD指針,^表示的上一個提交。如果是前多個提交,我們可以用~加數字的形式來表示。比如上圖當中划了紅線標注的master~3,就表示master節點上3個提交。
分支合並
最后來簡單說說分支合並,我們在使用git進行協同開發的過程當中,雖然大家都在各自的分支。但是最后代碼還是要合並到一起的,這樣才可以投入使用。git當中代碼的合並是通過分支合並來體現的。
比如當前的這一篇文章被我加在了test分支當中,這顯然是不行的,因為使用方不可能一一去理解每一個分支做了什么,當中的代碼邏輯。所以大多數的分支只是暫時的,用來暫時完成一項功能的,等功能完成之后,一般都會再合並回master分支,將所有的改動合並進去。
合並的方式非常簡單,我們只需要先checkout我們想要合並的目標分支。比如我們要合並到master,就checkout到master。然后使用git merge test命令,表示和test這個分支合並。

合並之后,如果沒有報錯就算是合並成功了。它會展示出來合並進來的代碼改動,我們注意到日志里有一個fast-forward這個單詞,它表示快速合並。快速合並的意思也很簡單,因為我們test分支是從master分支當中切出去的。后來master分支就再也沒有進行過改動,那么當我們合並的時候,其實只需要移動一下master指針,將它移動到test分支上即可。
我們用圖來展示,合並前:

合並后:

那如果我們在master分支上也有改動,不再是待合並分支的直接上游,會發生什么呢?

上圖當中我們做了一系列操作,首先我們創建了一個叫做test_merge的分支,在其中創建了一個文件叫做a.txt,接着我們切回master分支創建了b.txt。最后我們把兩個分支合並。
合並當然也沒有問題,但是我們來用git log來查看一下日志:

會發現日志里多了一個commit,這個commit並不是我提交的,而是它自動產生的。我們一樣用圖來展示一下,這是合並前:

合並之后:

由於不再擁有直接上下游關系了,所以git創建了一個新的commit用來合並兩個分支的代碼。當我們合並完成之后,我們就可以把沒用的分支都刪除了。刪除的命令是git branch -d test。
當然git merge的時候並不是永遠都一帆風順的,難免會遇到沖突。所謂的沖突也就是兩個人修改了同一份代碼,git會不知道應該保留哪一個,於是提示沖突,讓程序員自己搞定。關於git merge時遇到沖突怎么辦的問題,我們放到下一篇文章當中和大家分享。
今天的文章就到這里,衷心祝願大家每天都有所收獲。如果還喜歡今天的內容的話,請來一個三連支持吧~(點贊、關注、轉發)
本文使用 mdnice 排版
- END -