保姆級教程 | Merge Request 分支合併請求

BeerBear啤酒熊發表於2021-11-11

保姆級教程 | Merge Request 分支合併請求

What is it ?

首先我想先來講講什麼是分支合併請求Merge Request(也可叫Pull Request,下文中全用Merge Request或其縮寫MR指代),以及它有什麼作用(如果你對此概念有所瞭解,你完全可以跳過What is it)。

MR(或者PR)就是指將你開發的程式碼的內容以一種請求合併的方式來合併到它想去的分支上,這個請求的接收人(Reviewer)一般是專案、團隊的負責人或者其他成員。

一般來講,開發團隊都對Code Review(程式碼複審/審查/檢視)的重視程度比較高。因為Code Review的確實能夠提升程式碼的質量以及減少BUG的產生率。

Merge RequestCode review中就是重要的一環。如果使用MR來發起合併請求,那麼在程式碼審查時就完全可以以你本次請求的合併內容為單元進行程式碼審查,如果審查通過那麼就成功合併。審查交由Reviewer進行,他可以是請求的接收人。如果團隊多個成員坐在一起來看你的本次合併內容,那麼自然Reviewer就是這些人了。一份程式碼經過多人的審查,程式碼問題發生率自然會降低,開發者在開發時也會保持良好的編碼習慣,畢竟沒人想被別人指點自己的程式碼。

不過有些團隊可能並不重視Merge Request,最多也就是在dev分支(大家共用的開發分支)上檢出一個新分支,然後在新分支上進行開發,然後commit -> push最後mergedev 分支上就完事了。

下面我們將以Merge Request為目標,從建立倉庫開始講述一個完整的git工作流以及其中的git操作。

How to do?

接下來我們從0開始,以Gitee(碼雲)程式碼託管和研發協作平臺為例,來講講如何在正常的git工作流程中使用Merge request

1.建立一個遠端倉庫,預設建立master分支

image-20211104220309545

2. 建立本地倉庫,並關聯遠端倉庫

初始化本地倉庫後,隨便建立一個檔案,然後提交到遠端倉庫的master分支。

git init
touch README.md
git add README.md
git commit -m "first commit"
git remote add origin https://gitee.com/yaodao666/git-merge-requet.git
git push -u origin master

git push -u的作用就是關聯並推送內容到上遠端倉庫的分支。(後面還有別的關聯遠端分支的方法。)

3. 以master分支為起點建立一個dev分支

我們後面就將以dev分支作為開發分支(倉庫成員共用的一個分支)。

image-20211104222705941

4. 倉庫中再新增另一個成員

該成員將在後面作為Reviewer來處理自己的Merge Request,看我們的提交內容,從而達到程式碼審查的目的。

image-20211104223305466

image-20211104223417246

然後為該使用者設定為程式碼審查人員:

image-20211105000210014

以上的操作都在Gitee倉庫的管理設定選項中。

4. 本地切換到dev分支,並連線遠端dev分支

此時本地是沒有dev分支的。可以用

git branch -a

命令來檢視所有分支。

image-20211104223744003

現在就用

git checkout -b dev

來建立一個dev的本地分支。如果此時執行該命令時沒有加-b引數:

git checkout dev
error: pathspec 'dev' did not match any file(s) known to git

會報錯如上。因為該命令是隻是切換分支,而此時並沒有dev分支,自然無法切換過來會報錯。

image-20211104224132282

OK,現在我們建立了本地dev分支,現在我們讓他關聯上遠端倉庫的dev分支吧。(只有這樣才能進行pullpush操作啊!)

git branch --set-upstream-to=origin/dev

這樣子就好啦!

執行下面的命令

git pull
Already up to date.

出現上述資訊,說明連結上遠端的dev了。

其實還有一種連結遠端倉庫分支的方式,比如我們又在遠端倉庫上以dev分支為起點,建立了test分支,那麼我們在本地建立test分支時,就可以執行:

git checkout -b test origin/test

建立本地test分支的同時又連結到遠端的test分支上。

image-20211104230315250

在後文中5. 新建一個feature(特性)分支中還會有另一種方式來連線遠端倉庫分支。

5. 新建一個feature(特性)分支

在實際開發中,我們往往會新建一個特性分支,該分支專門為你服務,並且它專門用於處理某個bug,或者開發某個新的功能。即當有個新功能需要開發或者有bug以及優化重構部分程式碼時,我們就應該單獨拿出一個新分支來專門處理這些事情。

與上面建立dev的方式相同,不過我們這次不先在遠端倉庫建立分支了,而是在本地直接建立分支後,再將該分支推送到遠端

我們先切換到dev分支,正常工作中,你必須要pull一下,保證你之後寫的程式碼將是建立在dev上最新的程式碼的基礎上,以避免一些不必要的麻煩。

git checkout dev
git pull

用下面的命令建立一個新的特性分支,我們就命名為feature-beer吧。

git checkout -b feature-beer

6. 在feature-beer分支上開發,並推送到遠端。

隨便在readme.md檔案上改點東西。然後執行:

git add -A
git commit -m "這是我第一次在feature-beer分支上提交。"

最後將內容推送到遠端的倉庫上,

git push -u origin feature-beer

這時候再去遠端倉庫上看,就會有feature-beer分支了,並且提交內容也有了。所以git push -u的作用不僅僅是關聯並推送內容到上遠端倉庫的分支,當沒有遠端分支時還會建立該分支!

image-20211104231520643

7. 直接合併到dev上。

很多時候,有些開發團隊根本就在乎使用Merge Request來在合併時進行Code review,那麼他們就會直接合並程式碼到dev分支。

切換到dev分支執行merge命令。

git checkout dev
git pull                # 在合併前同樣先pull一下dev
git merge feature-beer    # 這裡的合併是本地合併,將feature-beer中的內容合併到dev中
git push                # 將本地內容推送到遠端倉庫

image-20211104233806097

此時再去遠端dev分支上看一下:

image-20211104233830098

我們在feature-beer上的修改內容已經放在dev上了。

8. Merge Request

不過這種簡單粗暴的方式往往會帶來很多問題,比如沒有人去注意你往dev上合併了什麼內容,這種沒人關注自己寫的程式碼情形往往就會導致開發人員在開發時不注意程式碼規範,甚至會提交上很明顯的bug,也懶得去測試,更有甚者則會上傳使得專案啟動失敗的程式碼。

這時候如果能有一個人來幫你再把把關,看看你寫的程式碼咋樣,則會促使自己寫程式碼時更加註意程式碼規範和程式碼健壯性,畢竟誰也不想被別人批評,要是因為程式碼寫的好被表揚就更好了。而Merge Request就可以達到這種效果。

現在,我們就模擬啟動一個Merge Request的過程。

重新切換到feature-beer分支上寫點內容,並分兩次提交,並push到遠端分支上。

git checkout feature-beer
# 改點內容:這是我第二次在feature-beer上進行開發工作,現在是晚上十一點五十一分了。
git add -A
git commit -m "這是第二次在feature-beer上的開發的第一次提交。"
# 改點內容:這是我第二次在feature-beer上進行開發工作,現在是晚上十一點五十二分了。
git add -A
git commit -m "這是第二次在feature-beer上的開發的第二次提交。"
git push

這樣遠端上的分支就能看到這次開發的兩次提交啦。

image-20211104235530606

Gitee上提供了Pull Request操作來實現Merge Request。

image-20211105000041960

在建立的Pull Request中,你必須選中源分支、目標分支。還能看到提交記錄和檔案改動資訊。

image-20211105000659267

點選建立後,Beer Bear成員就會收到這個請求。

image-20211105001102665

他可以在提交和檔案中看到提交的內容。審查通過後,就可以合併了。在前面建立時我們勾選了刪除提交分支(不過截圖中未勾選,實際操作上是勾選了的),合併後就沒有這個分支了

image-20211105001450519

這時候再看分支和分支內容,發現feature-beer沒了,dev中有了合併過來的內容。

9. 刪除本地分支和遠端分支

這個時候我們就應該刪除這個本地分支了。

git checkout dev            # 先切換到dev分支
git pull
git branch -d feature-beer

如果這個命令報錯,往往是

  • 很有可能是你正處於該分支上。
  • 該分支包含了還未合併的工作,可以先合併或者使用 -D引數強制刪除。

如果你的遠端分支還沒有刪除(在本文中,遠端分支在Merge Request通過時就一起刪除了),可以使用:

git push origin --delete feature-beer

當然你也可以登入到Gitee的網頁上刪除遠端分支。

為啥要刪除呢?繼續用不行麼?

其實可以繼續用,但是不推薦,因為我們建立這個分支的目的就是為了開發一個新模組或者修復一個BUG,當開發工作完成後刪除該分支,處理別的事情時再新建一個就好了。

Some Questions

1. 如果本地dev有修改內容,是否可以把這些修改內容帶到新分支上

比如有一天,你睏意十足,開啟編譯器直接開始幹活了,幹了半天才發現這是dev分支,這時候已經有很多程式碼的改動了,咋整?

現在我們在dev分支上新增一行,不提交。

image-20211108213642784

然後切換到新分支:feature-new-beer.

git checkout -b feature-new-beer

再開啟檔案會發現會有這行資訊的。所以這個未提交的資訊是可以帶過來的

那我要是不想帶過來怎麼辦?我們先刪除這個分支換一個分支看一下如何做到不帶過來。

git checkout dev
git branch -d feature-new-beer

我們使用

git stash

將dev分支上的記憶體暫存起來,這時dev上就看不到這一行了。

然後我們再次切換到新分支:feature-new-beer.

git checkout -b feature-new-beer

這個時候該分支上也不會有這一行資訊了。但如果此時執行:

git stash pop

會發現那一行又出現了,並且再切換到dev時,dev上也又出現這一行了。

為啥會出現上面的現象呢,其實是因為git中存在工作區和暫存區,這兩個區都是被所有本地分支共享的。

當有內容修改時,修改資訊就會放在工作區中,此時如果直接檢出一個新的分支,就會把工作區的內容都帶過去。

而如果把修改資訊暫存(stash)到暫存區時,都暫存起來了自然就不會帶過去,但是由於該區也是共享的,當pop出暫存內容時,所有分支又同時恢復了這些修改內容。

如果我在dev上進行stash後,檢出新分支,又加了一行,再pop會怎樣呢?

image-20211108215701354

git stash pop

image-20211108215906879

會提示報錯:你的本地更改(新分支上的更改)會被覆蓋。

這個時候如果:

git stash
git stash pop

那麼本地更改將覆蓋掉dev的修改,即dev上也是下面的資訊了。

image-20211108215701354

就像該問題開頭說的那樣,如果你在dev上寫了不少東西了,那麼就先pull一下最新的程式碼,然後檢出到新分支上繼續開發就可以了。

你可以在任意時間回到dev上直接進行discard changed(回滾到最初的未修改的版本)操作。

如果實在不放心就等新分支程式碼都寫完了提交了你再到dev上刪除。

2. 如果我在新分支上有很多次提交,我是否可以合併這些提交到一個提交上再提交

這是可以的,並且很多時候我們推薦這麼做,比如一個模組需要三天去完成,這三天你可能提交了六七次,而實際上你只是完成了一個新模組的開發而已。

如果在你的幾次提交中,有人往dev推送了程式碼,那麼當你最後向dev發起合併請求並且成功通過後,commit的記錄會是怎樣呢?下面一起來試一下。

我們先把Question1中的在dev分支上的修改discard changes一下(回到未修改的狀態,即上一個版本),然後刪除剛剛那個分支並建立一個新分支feature-many-commits

# use "git checkout -- <file>..." to discard changes in working directory 這是官方提示
git checkout -- README.md        # 該檔案回到未修改的狀態
git branch -d feature-new-beer
git checkout -b feature-many-commits

現在我們在新分支上隨便寫點內容並提交。

# 隨便加一行:我在 feature-many-commits 分支上寫東西。現在是晚上22:25,寫完這行立馬提交。
git add -A
git commit -m"feature-many-commits,晚上22:25"

現在我們再到dev分支上寫點東西並提交和push(模擬這是另一個人提交上來的程式碼),不過為了避免衝突,我們就不在README檔案中寫了,新建一個txt檔案。

image-20211108223041824

現在我們再回到新分支上隨便寫點內容並提交,然後push到遠端,並去Gitee上進行MergeRequest

# 隨便加一行:我在 feature-many-commits 分支上寫東西。現在是晚上22:34,寫完這行立馬提交。
git add -A
git commit -m"feature-many-commits,晚上22:34"
git push -u origin feature-many-commits

image-20211108223653127

image-20211108223830938

image-20211108223855435

這裡提供兩個選項,一個是合併分支一個是扁平化分支。看看解釋應該能猜出啥意思,第一個的意思是指直接合並,後者則是先把feature-many-commits上的提交合併成一個新的commit——這就是我們想要的目標,然後再合併到dev上。

不過我們先來看一下直接合並

image-20211108224118720

現在我們根據該圖顯示的提交記錄就可以回答開頭的那個問題了:“如果在你的幾次提交中,有人往dev推送了程式碼,那麼當你最後向dev合併併成功後,commit的記錄會是怎樣呢?”。

正如圖中展示的那樣,dev的提交記錄夾在了新分支上的兩次的提交記錄中間,這樣確實凌亂、不美觀。

所以我們在很多時候應該採用扁平化分支的方式來合併。

寫點內容提交,再用扁平化的方式試一次:

# 隨便加一行:我在 feature-many-commits 分支上寫東西。現在是晚上22:43,寫完這行立馬提交,希望這個提交最後會與接下來的commit被合併到一個commit中。
git add -A
git commit -m"feature-many-commits,晚上22:43"

# 隨便加一行:我在 feature-many-commits 分支上寫東西。現在是晚上22:44,寫完這行立馬提交,希望這個提交最後會與之前的commit被合併到一個commit中。
git add -A
git commit -m"feature-many-commits,晚上22:44"
git push

image-20211108224725647

image-20211108224804382

image-20211108224906307

image-20211108224918291

發現確實成功將那兩次commit合併成一個了!並且提交的時間就是通過Merge Request(Pull Request)的時間。

總結一下這個問題:直接合並分支會讓兩個分支的每一次提交都按照commit的時間進行排序,這樣看起來會比較凌亂。我們可以通過gitee上扁平化分支的方式通過MR

3. 除了Merge Request通過後合併時選擇扁平化分支(將多次提交合併成一個commit)還有啥方法也能實現這個效果麼

確實,這個將多次提交合併為一個提交的操作是在gitee上進行的,那要是我不去gitee上進行Pull Request,直接在本地進行merge,不就達不到這種效果了麼?

需要說明的一點是,實際上像GiteeGitlab這種程式碼託管平臺是都具備這種功能的,所以如果在它們的平臺上通過Merge Request(或Pull Request)去做的話,一定能實現這種效果。

但如果我不去程式碼託管平臺發起合併請求呢?我就想本地開發完了,然後合併到dev上(再由本地dev分支push到遠端dev分支),那該怎麼做呢?這時候一個git rebase命令就呼之欲出了。

這個命令的作用就是將某個分支的多次commit合併成一個commit,然後在merge到另一個分支上的時候就會只有一個提交啦。

這個過程是這樣的(示例是將feature-many-commits分支內容合併到dev上):

# 假設現在開發完畢 並且已經在feature-many-commits上提交了多次
git checkout dev
git pull            # dev保持最新的程式碼
git checkout feature-many-commits
git rebase dev        # 將feature-many-commits上所有的commit,重新在新的dev的HEAD上commit一遍
git checkout dev    # 再次切換到dev上
git merge feature-many-commits # 將feature-many-commits上的內容合併到dev上
git push            # 推送即可

這個過程還是比較簡單的,很多人也在這樣去做,在此就不演示了。

Summary

現在讓我們再來梳理一下整個MR的關鍵流程。

假設dev分支就是大家共用的分支,我們要在此基礎上檢視出新分支進行開發工作,最後通過Merge Request的方法合併到遠端分支。:

  1. 切換到dev分支

    git checkout dev
  2. 更新dev分支程式碼

    git pull
  3. 在dev分支的基礎上,檢視出一個新分支feature-beerbear

    git checkout -b feature-beerbear
  4. 進行開發並提交你的程式碼

    git add -A
    git commit -m"xxxxxx"
  5. 將本地feature-beerbear分支推送到遠端

    git push -u origin feature-beerbear  # 會建立一個遠端feature-beerbear分支

    如果不是第一次推送,可以直接使用

    git push

    4、5步驟將在實際的開發工作中重複執行。

  6. 開發工作完成後,到程式碼託管平臺上進行Merge Request(或Pull Request)操作。
  7. 合併請求通過後。就可以刪除本地分支。

    git branch -b feature-beerbear
  8. 新的任務來了,從1再來。

在第六步時,可能會發生以下情況:

① 如果不通過,那麼繼續從4開始執行,不過不再需要新提出一個新的MR(Merge Request)了。

這時候可能會疑問,為啥不需要重新提一個了呢?

因為這個合併是指分支之間的合併,當你的遠端feature-beerbear分支發生變化時,MR會感知到做出相應變化的。

實際上,如果你要新建的MR的源分支和目標分支和之前的MR中的相同,這樣的話是建立不了的。

你必須關閉掉之前的那個,當然完全沒這個必要。

② 發生衝突

雖然你在建立新分支時pull了一下dev分支,然後在此基礎上建立新分支了,但畢竟你的MR可能是好幾天後發出的,dev分支肯定不是原來的狀態了。所以就會導致在建立MR時提醒衝突。那當提示發生衝突的時候,怎麼辦呢?只需以下幾個命令即可:

git checkout dev
git pull
git checkout feature-many-commits
git merge dev                        # 如果在這一步發生衝突,那麼手動解決衝突即可
git push

實際上就是將dev程式碼更新,然後將dev上的程式碼合併到feature-many-commits上來。

其實我們完全可以在提交MR之前就將上面的幾行命令走一遍,這樣就不會再提MR的時候報出衝突了。

總不至於你剛執行完上面的命令,在你準備提出MR時,就又有人往dev上提交了程式碼,又恰好導致衝突了吧——如果有,可以買張刮刮樂試試運氣了。

Thanks

這篇關於Merge Request的文章到此就算結束了!首先,謝謝你看完這篇文章!

其次,我十分希望這篇文章能對你有所幫助,或許幫助不是很大。如果你喜歡這篇文章或者真的有一些收穫,請也點贊或者收藏,最好是在評論區留言告訴我!

雖然付出了挺多精力來寫這篇文章,但畢竟我們水平一般、能力有限,所以肯定會有一些語句不暢、表意不明甚至錯誤的地方。

如果你能指出問題、不吝賜教,我將十分感謝,你的建議和指教也將敦促我一直完善此文!歡迎友好交流!

相關文章