首先我想先来讲讲什么是分支合并请求Merge Request
(也可叫Pull Request
,下文中全用Merge Request
或其缩写MR
指代),以及它有什么作用(如果你对此概念有所了解,你完全可以跳过What is it
)。
MR
(或者PR
)就是指将你开发的代码的内容以一种请求合并的方式来合并到它想去的分支上,这个请求的接收人(Reviewer
)一般是项目、团队的负责人或者其他成员。
一般来讲,开发团队都对Code Review
(代码复审/审查/检视)的重视程度比较高。因为Code Review
的确实能够提升代码的质量以及减少BUG
的产生率。
Merge Request
在Code review
中就是重要的一环。如果使用MR
来发起合并请求,那么在代码审查时就完全可以以你本次请求的合并内容为单元进行代码审查,如果审查通过那么就成功合并。审查交由Reviewer
进行,他可以是请求的接收人。如果团队多个成员坐在一起来看你的本次合并内容,那么自然Reviewer就是这些人了。一份代码经过多人的审查,代码问题发生率自然会降低,开发者在开发时也会保持良好的编码习惯,毕竟没人想被别人指点
自己的代码。
不过有些团队可能并不重视Merge Request
,最多也就是在dev
分支(大家共用的开发分支)上检出一个新分支,然后在新分支上进行开发,然后commit -> push
最后merge
到 dev
分支上就完事了。
下面我们将以Merge Request
为目标,从建立仓库开始讲述一个完整的git
工作流以及其中的git
操作。
接下来我们从0开始,以Gitee
(码云)代码托管和研发协作平台为例,来讲讲如何在正常的git
工作流程中使用Merge request
。
初始化本地仓库后,随便创建一个文件,然后提交到远程仓库的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
的作用就是关联并推送内容到上远程仓库的分支。(后面还有别的关联远程分支的方法。)
我们后面就将以dev
分支作为开发分支(仓库成员共用的一个分支)。
该成员将在后面作为Reviewer
来处理自己的Merge Request
,看我们的提交内容,从而达到代码审查的目的。
然后为该用户设置为代码审查人员:
以上的操作都在 Gitee
仓库的管理设置选项中。
此时本地是没有dev分支的。可以用
git branch -a
命令来查看所有分支。
现在就用
git checkout -b dev
来创建一个dev的本地分支。如果此时执行该命令时没有加-b参数:
git checkout dev
error: pathspec 'dev' did not match any file(s) known to git
会报错如上。因为该命令是只是切换分支,而此时并没有dev分支,自然无法切换过来会报错。
OK,现在我们创建了本地dev分支,现在我们让他关联上远程仓库的dev
分支吧。(只有这样才能进行pull
、push
操作啊!)
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
分支上。
在后文中5. 新建一个feature(特性)分支
中还会有另一种方式来连接远程仓库分支。
在实际开发中,我们往往会新建一个特性分支,该分支专门为你服务,并且它专门用于处理某个bug,或者开发某个新的功能。即当有个新功能需要开发或者有bug以及优化重构部分代码时,我们就应该单独拿出一个新分支来专门处理这些事情。
与上面创建dev的方式相同,不过我们这次不先在远程仓库创建分支了,而是在本地直接创建分支后,再将该分支推送到远程。
我们先切换到dev分支,正常工作中,你必须要pull一下,保证你之后写的代码将是建立在dev
上最新的代码的基础上,以避免一些不必要的麻烦。
git checkout dev
git pull
用下面的命令创建一个新的特性分支,我们就命名为feature-beer
吧。
git checkout -b feature-beer
随便在readme.md
文件上改点东西。然后执行:
git add -A
git commit -m "这是我第一次在feature-beer分支上提交。"
最后将内容推送到远程的仓库上,
git push -u origin feature-beer
这时候再去远程仓库上看,就会有feature-beer
分支了,并且提交内容也有了。所以git push -u
的作用不仅仅是关联并推送内容到上远程仓库的分支,当没有远程分支时还会创建该分支!
很多时候,有些开发团队根本就在乎使用Merge Request来在合并时进行Code review,那么他们就会直接合并代码到dev
分支。
切换到dev
分支执行merge
命令。
git checkout dev
git pull # 在合并前同样先pull一下dev
git merge feature-beer # 这里的合并是本地合并,将feature-beer中的内容合并到dev中
git push # 将本地内容推送到远程仓库
此时再去远程dev
分支上看一下:
我们在feature-beer
上的修改内容已经放在dev
上了。
不过这种简单粗暴的方式往往会带来很多问题,比如没有人去注意你往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
这样远程上的分支就能看到这次开发的两次提交啦。
Gitee上提供了Pull Request
操作来实现Merge Request。
在创建的Pull Request
中,你必须选中源分支、目标分支。还能看到提交记录和文件改动信息。
点击创建后,Beer Bear
成员就会收到这个请求。
他可以在提交和文件中看到提交的内容。审查通过后,就可以合并了。在前面创建时我们勾选了删除提交分支(不过截图中未勾选,实际操作上是勾选了的),合并后就没有这个分支了。
这时候再看分支和分支内容,发现feature-beer没了,dev中有了合并过来的内容。
这个时候我们就应该删除这个本地分支了。
git checkout dev # 先切换到dev分支
git pull
git branch -d feature-beer
如果这个命令报错,往往是
如果你的远程分支还没有删除(在本文中,远程分支在Merge Request
通过时就一起删除了),可以使用:
git push origin --delete feature-beer
当然你也可以登录到Gitee
的网页上删除远程分支。
为啥要删除呢?继续用不行么?
其实可以继续用,但是不推荐,因为我们创建这个分支的目的就是为了开发一个新模块或者修复一个BUG,当开发工作完成后删除该分支,处理别的事情时再新建一个就好了。
比如有一天,你困意十足,打开编译器直接开始干活了,干了半天才发现这是dev分支,这时候已经有很多代码的改动了,咋整?
现在我们在dev
分支上新增一行,不提交。
然后切换到新分支: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
会怎样呢?
git stash pop
会提示报错:你的本地更改(新分支上的更改)会被覆盖。
这个时候如果:
git stash
git stash pop
那么本地更改将覆盖掉dev
的修改,即dev
上也是下面的信息了。
就像该问题开头说的那样,如果你在dev
上写了不少东西了,那么就先pull
一下最新的代码,然后检出到新分支上继续开发就可以了。
你可以在任意时间回到dev上
直接进行discard changed
(回滚到最初的未修改的版本)操作。
如果实在不放心就等新分支代码都写完了提交了你再到dev
上删除。
这是可以的,并且很多时候我们推荐这么做,比如一个模块需要三天去完成,这三天你可能提交了六七次,而实际上你只是完成了一个新模块的开发而已。
如果在你的几次提交中,有人往dev
推送了代码,那么当你最后向dev
发起合并请求并且成功通过后,commit
的记录会是怎样呢?下面一起来试一下。
我们先把Question1
中的在dev
分支上的修改discard changes
一下(回到未修改的状态,即上一个版本),然后删除刚刚那个分支并创建一个新分支feature-many-commits
。
# use "git checkout -- ..." 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
文件。
现在我们再回到新分支上随便写点内容并提交,然后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
这里提供两个选项,一个是合并分支一个是扁平化分支。看看解释应该能猜出啥意思,第一个的意思是指直接合并,后者则是先把feature-many-commits
上的提交合并成一个新的commit
——这就是我们想要的目标,然后再合并到dev上。
不过我们先来看一下直接合并:
现在我们根据该图显示的提交记录就可以回答开头的那个问题了:“如果在你的几次提交中,有人往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
发现确实成功将那两次commit
合并成一个了!并且提交的时间就是通过Merge Request(Pull Request)
的时间。
总结一下这个问题:直接合并分支会让两个分支的每一次提交都按照commit
的时间进行排序,这样看起来会比较凌乱。我们可以通过gitee
上扁平化分支的方式通过MR
。
确实,这个将多次提交合并为一个提交的操作是在gitee
上进行的,那要是我不去gitee
上进行Pull Request
,直接在本地进行merge
,不就达不到这种效果了么?
需要说明的一点是,实际上像Gitee
、Gitlab
这种代码托管平台是都具备这种功能的,所以如果在它们的平台上通过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 # 推送即可
这个过程还是比较简单的,很多人也在这样去做,在此就不演示了。
现在让我们再来梳理一下整个MR
的关键流程。
假设dev分支就是大家共用的分支,我们要在此基础上检视出新分支进行开发工作,最后通过Merge Request
的方法合并到远程分支。:
feature-beerbear
feature-beerbear
分支推送到远程Merge Request
(或Pull Request
)操作。在第六步时,可能会发生以下情况:
① 如果不通过,那么继续从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
上提交了代码,又恰好导致冲突了吧——如果有,可以买张刮刮乐试试运气了。
这篇关于Merge Request
的文章到此就算结束了!首先,谢谢你看完这篇文章!
其次,我十分希望这篇文章能对你有所帮助,或许帮助不是很大。如果你喜欢这篇文章或者真的有一些收获,请也点赞或者收藏,最好是在评论区留言告诉我!
虽然付出了挺多精力来写这篇文章,但毕竟咱水平一般、能力有限,所以肯定会有一些语句不畅、表意不明甚至错误的地方。
如果你能指出问题、不吝赐教,我将十分感谢,你的建议和指教也将敦促我一直完善此文!欢迎友好交流!