git學習之三:分支管理

上一節介紹了git管理的文件狀態,本節將介紹git版本控制的核心利器-分支(branch)。

第一節的內容講了三種git管理的對象,可以知道只要知道一個commit對象的信息,我們就可以知道這個版本的內容。我們進行版本控制,也就是要管理這些commit對象。git使用分支這個概念來對commit進行管理,分支其實就是指向了某一個commit,一個分支來索引一個commit。

還是從.git文件夾出發,初始化一個本地倉庫,看看.git下的目錄:

objects保存了我們所有的對象,refs的文件夾全稱references,這個文件夾裏有會儲存三類文件:本地分支、遠程分支、tag。

現在看看refs文件夾的內容:

1.本地分支

heads文件夾裏面的內容是本地分支文件tags文件夾裏的文件也是標識一個commit,不過對這個commit進行了一些包裝,取了一個更好的名字而已。這裏如果關聯了遠程倉庫,refs還存在一個remotes文件夾,用來儲存遠程分支

本地倉庫初始化之後,heads文件夾爲空,也就是目前沒有分支,但是我們提交一個commit之後,git將建立一個默認的名爲master的分支。現在隨意往倉庫加一個文件,使用git add將文件加入暫存區(對於暫存區,下一節將詳細講解),然後使用 git commit將文件提交(git commit -m "first commit"  -m參數可以對本次commit加上一個註釋)。

現在看看refs/heads目錄:裏面多了一個名爲master的文件。

這個master文件,就是我們的分支文件,他可以直接用文本打開,我們可以看看裏面的內容:

使用git log 看看我們的commit日誌:

這個分支文件,指向了當前的commit,可以看到創建一個分支的成本很低,一個文本文件,裏面標識一個commit的key就完成了,git使用分支來管理版本的成本的花費是很低的,新建一個分支的消耗幾乎不存在。

使用git branch 可以查看本地所有分支,git branch [分支名] 可以創建一個分支,每創建一個分支heads文件夾就會多一個文件

可以看到一開始只有一個master分支,然後建立一個名爲develop的分支,分支前面的 * 號,表示目前所在的分支。目前版本的內容還是master分支,git branch只是新建了一個分支,並沒有切換到新的分支,新建的分支文件裏的內容,就是當前所在分支指向的commit。

git何如知道目前版本工作的分支是哪一個呢?我們需要看看.git目錄下的HEAD文件,這個文件記錄了當前版本所在的分支,這個文件也可以直接用文本打開:

git 通過這個文件來標識當前工作的分支。

git checkout [分支名] 可以在分支之間切換,通過分支的切換,也就對我們的版本的內容進行了切換。,每一次切換都會改變HEAD文件的內容,下面我們切換到develop分支:

git branch [分支名] 創建一個分支

git checkout [分支名] 切換到該分支

這兩條git 命令可以用 git checkout -b [分支名]來完成 即創建一個分支 並且馬上切換到該分支。


看看HEAD文件的內容

用一個簡易的圖來表示現在的狀態:

master develop指向同一個commit HEAD目前指向develop,表示目前版本的分支是develop。

現在加一個文件,進行第二次提交,名稱爲second commit,每次commit之後,當前分支內容會更新,指向新的commit。

看看現在的狀態:

如果進行很多次提交,develop會一直向前移動。只要切換到master分支,文件又回到了之前master分支所標識的commit的狀態。回到master上之後

繼續工作,進行新的提交之後,這時在分支上產生了一個分叉:


這樣產生了兩個方向,比如兩個不同功能的開發,在完成之後需要進行分支合併(稍後講解)


2.遠程分支

本地分支只能對自己的本地倉庫進行版本管理,但是多人合作必須利用到遠程倉庫。克隆一個遠程倉庫到本地,在

.git/refs文件夾會有remotes這個文件夾。這個文件我包含了遠程倉庫的信息

看看這個文件夾裏的內容:

克隆之後遠程倉庫名默認爲origin,這個origin文件夾裏的內容就是遠程分支文件。當我們克隆一個遠程倉庫時,我

們默認將遠程倉庫成爲origin,一個本地倉庫可以關聯多個遠程倉庫,在這裏對他們的以倉庫名建立文件夾,該文件夾裏

就是遠程的分支文件

使用git branch -r 可以查看遠程分支。

下面看看遠程分支和本地分支的協作,對於遠程倉庫,每個開發人員都可以克隆一份本地的倉庫,在本地倉庫看

來,遠程分支只是一個標識,告訴我們現在遠程倉庫的狀態。

克隆之後本地只存在一個master分支,與遠程master分支關聯

現在假設遠程倉庫只要一個分支master,該分支只要一個commit A,我們克隆島本地倉庫後的狀況如圖:

經過本地經過B,C,D三次commit之後:

時我們本地的master分支已經有三次提交了,我們可以將新的內容push到遠程倉庫

git push origin [本地分支名]:[遠程分支名] 這個命令來推送我們本地新加的內容到遠程倉庫。

如果遠程分支名不存在,則功能變爲創建一個新的遠程分支。

如果我們的本地分支和遠程分支關聯,則可以省略後面的遠程分支名

如果推送成功那麼遠程倉庫就有了我們所提交的內容,同時遠程master分支也會移動指向commit D。


假設還有另外一個人基於原來的遠程倉庫也創建了本地倉庫,現在我們推送了新內容到了遠程倉庫,那麼這個

人只需要使用git fetch把遠程倉庫新內容拉取到自己的本地倉庫,本地狀態如下:

這時使用git status查看本地的狀態,會被提示在master分支落後origin/master 3個commit,可以使用git pull進行

fast-forward(快速移動,這個稍後講解),使用git pull之後,第二個人的本地master會移動到commit D,這樣兩個人的代碼

就同步了。


3.分支合併

我們通過分支來管理版本,現在假設有兩個功能需要開發,我們有兩個分支master和develop,完成開發後各自都進行了

很多次commit:


現在兩個功能都開發完成了,這時需要合併兩個分支的內容,合併分支有兩種方式:rebase和merge。

爲了區分兩者,我們將git rebase 稱作衍合,git merge 成爲融合。

git命令:git rebase [分支名], git merge [分支名]可以將分支合併到工作的分支上,現在HEAD指向master分支,說

表明我們的目前的工作在master分支上,使用git rebase develop 就將develop分支的內容合併到了master分支上。

兩個分支合併,git會尋找離兩個分支最近的公共祖先,之前已經講過每一個commit都能完整的表現出所有文

的狀態,這樣可以瞭解到兩個分支分別都做了哪些改變,從何進行合併。

對於git rebase衍合有兩個步驟:1.找到了公共祖先之後,會把develop的commit 依次加入到改祖先之後。2.將

master分支的commit依次加入到第一步執行完之後的commit上。如下圖所示,這樣就可以清晰的知道我們的提交歷史:


對於git merge融合,比git rebase的處理方式要簡單,找到公共祖先的commit,然後比較兩個分支頂點的commit,

進行差異化的比較,生成一個新的commit加入到master分支上 ,這個commit比較特殊會指向兩個父節點:

之前我們提到個fast forward 快速合併,如果不存在A,B,C三個commit,master分支指向init commit節點,這個

時候尋找到的公共祖先節點,其實就是master指向的init commit節點,這個時候就不存在分支的分叉,可以快速合併,

master分支講直接指向commit F來完成分支合併。

分支合併會可能會產生衝突,這時需要解決衝突,才能繼續合併。產生衝突的方式有很多種:如果兩個分支都修

改的同一個文件的同一位值;一個分支刪除了文件,一個分支對該文件進行修改,這樣都會產生衝突。我們以一個具

體例子來解決一個衝突。

現在我們在master分支對1.txt末尾添加一行“master 1”, 在develop分支上在1.txt末尾加一行“develop 1”,這樣兩個

不同分支上的commit對同一個文件同一個位置進行了修改。我們在master分支進行git merch develop操作:

提示conflict,這個時候我們master分支的狀態是merging,我們需要解決衝突,打開1.txt文件:

文件中用 <<< ==== >>>三個符號標識了衝突的內容,我們需要進行對這個文件進行修改,來確認最終的版本

解決衝突之後,需要將1.txt進行add一次,來告訴git我們解決了衝突[ git add 1.txt],這樣就可以接着合併了,對於

merge來說其實是進行新的提交,git commit一次之後就完成了合併。對於git rebase則使用git rebase --continue來完

成合並


4.分支管理

我們在多人合作開發當中,會遇到很多複雜的分支情況。

本地分支的創建 :git branch [分支名](上面的內容已經講解過)

本地分支的刪除 :git branch -d|-D [分支名] 。-d刪除分支的時候,也許該分支還有內容沒有合併,會拒絕刪除分支,如果

確認該內容無用,使用-D進行強制刪除(小心使用)


git fetch ,git pull 兩者都是講遠程分支的代碼拉取到本地,但是fetch只是拉取,不會合並,git pull則會自動merge遠程分支

的內容到自己的分支,也就是git pull = git fetch + git merge. 直接使用git pull可能使自己的分支與遠程分支產生偏離。下面

具體看一個例子。

用一種蠢的情況來說明如何管理分支,現在有多個人進行開發,遠程分支爲origin/master,很多個人都克隆了,

遠程倉庫,在自己本地的master進行開發各自的功能。

現在有人進行了提交,將自己的代碼推送到了遠程分之master上。這時如果我們沒有如何提交,直接git pull 就可以把

別人代碼合併到自己的代碼,自己的本地分支還是與遠程分支對應。這個時候就是快速合併(fast forward)

當然這個時候我們自己很可能在本地分支已經有了自己的提交,因爲本地master已經與遠程master的commit已經不對

應,這個時候我們推送自己的修改到該遠程分支會被拒絕。這時必須把拉取遠程分支的代碼拉取到本地:看看現在的

遠程倉庫情況:commitA 是最原始的提交,現在一個開發人員向遠程分支推送了commit B 和commit C

 

而我們自己本地也進行了提交:本地的狀態,提交了commit D和commit E:


這個時候我們的本地的master分支已經和遠程的master分支不一致了,這個時候如果直接git fetch一次,將遠程倉庫的

修改拉取到本地,git fetch之後:

這個時候爲了與遠程分支保持一致,需要使用git rebase來衍合我們的修改,也就是將B C加入到D之前,如果使用

git merge來進行融合,那麼我們的本地master分支將與遠程的分支產生偏離:這個時候我們本地的master分支將無法

與遠程master關聯了:

直接使用git pull就會造成這種情況,可以使用git pull --rebase 這樣告訴git拉取代碼之後進行的是rebase操作。

這是一種糟糕的合作方式,因爲rebase需要對每一個commit進行操作,產生衝突的概率要大於merge,同時rebase會

改變commit 的sha-1值,這樣的管理很容易造成混亂。


合理的管理方式是,我們對自己的開發內容在原始分支上新建一個分支,完成後將自己的本地分支推送到遠程倉庫,由

git 管理員來管理遠程分支,開發人員只需要在本地拉取遠程倉庫代碼就好了。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章