记我的第一次GitHub Pull Request(中篇)合并与冲突处理

2020-03-07

Previously On This Serie

我们在上一篇文章中介绍了Git和GitHub,尤其是『提交』和『分支』的概念,还记得我们的主题是介绍一次Pull Request是怎样的,我们还要知道『分支的合并』或者简称『合并』,这个概念.

合并

上一篇文章中,我们提到的案例是这样的,一家企业使用某版本管理系统管理其项目,有提交名为c1,c2,c3,c4,c5和c4’,它们的关系是这样的

提交树

最终我们知道他们还是修复了c3中的漏洞(通过先创建一个最终提交是c3的分支,叫做分支b,然后在分支b进行进行提交,假设叫做提交c4’,那么c4’自然也是在c3的基础上了),但现在问题是,由于c4和c5都是在c3之后的而且还没有修复c3出现的漏洞(记住修复c3漏洞的是分支b相对c3提交的c4’,而不是原来分支a的c4或者c5),换句话说,很明显,c5也是有c3出现的漏洞的(我们假设只是单纯地开发新功能不会就这么简单地把漏洞去掉),当然c4也有,因为c4和c5都是c3的后代,而且他们都没有修复漏洞(他们只是开发新功能),那么……

一句话,c5还要再重复一次c4’所的工作吗?是复制粘贴c4’的代码,还是干脆重新调试程序,完全再做一次c4’的调试工作?如果是这样,那当初切回c3去修复漏洞又有什么意义?

是这样的,我们假设啊,开发新功能的(也就是写c4和c5的),和修复漏洞的(写c4’的),假设他们是两伙人,他们各自负责自己分内的工作,所以刚才专门创建一个分支b指向c3,使得双方互不耽误对方的工作,开发新功能和修复漏洞,都有序地进行.

那这种情况还是有可能出现的,可是该怎么做呢?怎么做才能使得c4’的工作同样能够覆盖到c5上?

一个例子

为了让问题变得更形象,更容易理解,我们来看这样一个例子,我们假设他们这个团队要开发一款计算程序,具体地说,就是计算数字和数字之间的加法啊,减法啊,乘法啊,除法啊之类的,我们假设,到c3的时候,他们已经开发出了程序的加法模块,但是这个加法模块是有问题的(别问我为什么这他们都没发现),看下列python代码:

# 这是c3提交的加法代码,可以看到是有一个没有被发现的bug的
def add(x: int, y: int) -> int:
    return x + y + 1

我们假设,c4’这个提交修复了漏洞,假设修复了这个漏洞之后就暂时没有其他漏洞了,那么c4’提交了之后加法模块的代码应该变成这样子:

# 加法,修复了
def add(x: int, y: int) -> int:
    return x + y

由于c4和c5都没有发现漏洞,只是添加新功能,我们还假设他们做的新功能没有引入新的bug,要不然问题就变得比较复杂难以讨论了,那么c5或许是这个样子:

# 加法(没修复漏洞)
def add(x: int, y: int) -> int:
    return x + y + 1

# 减法
def sub(x: int, y: int) -> int:
    return x - y;

# 乘法
def mul(x: int, y: int) -> int:
    return x * y

有没有办法,把c4’的所在的分支,和c5所在的分支,嗯,怎么说呢,并在一起?在命令行里输入git --help查看git的帮助,哦,原来还真的有merge这个命令,试一试吧?

git help merge

于是我们看到了这样一幅图

git help merge

嗯,和我们的项目还挺相像的,master不就相当于我们的分支a,G不就相当于我们的提交c5,topic不就相当于我们的分支b,c不就相当于我们的提交c4’吗?我们也想要这样的一个提交c6,它即指向c4’又指向c5,就像帮助文档里的提交H,它即指向C又指向G,这就叫博采众长啊!原来git这么一个小小的工具还能教会我们人生道理,来:

git checkout a
git merge b

我顺便在我电脑上创建了一个模拟以上情景的Git仓库,所以这真的有可能发生的,下面这张图片中的输出是用命令

git log --oneline --decorate --graph --all

打出来的

模拟以上情景的Git仓库

我们试一下

git checkout a
git merge b

显示

自动合并 calc.py
冲突(内容):合并冲突于 calc.py
自动合并失败,修正冲突然后提交修正的结果。

嗯…… 冲突于calc.py,也就是这个模拟情景中的团队要开发的计算程序,到底是什么冲突了呢,用vi打开看一下?

Open calc.py by vi

首先我们看到有一对对仗工整的符号

<<<<<<< HEAD

先叫它左箭头吧,和

>>>>>>> b

叫它右箭头,和中间有一条=号

=======

叫它分割线比较合适,我们还注意到,在左箭头和右箭头包为的区域的内容被分割线分成了两部分,一部分是第4行到第8行的内容,它们是

    return x + y + 1

def sub(x, y):
    return x - y

def mul(x, y):
    return x * y

而另外一部分呢,也就是分割线下面到右箭头的那部分,第12行,是

    return x + y

其实HEAD的意思你可以理解为当前的工作目录,它总是指向最新的提交,所以用了HEAD(头部)这个词(但现在你尝试让它指向两个提交,还要合并!所以产生冲突了).

刚好,我们还注意到,我们就说分割线把中间内容分隔成长的和短的两部分吧,长的是接近HEAD的那部分,短的是接近分支b,我们还注意到:长的那部分,也就是靠近HEAD的那部分,如果和没有被左右箭头包围的内容合并(第1,2行),那刚好就是执行

git merge b

之前的当前目录的,也就是c5的工作目录的状态!c5的时候,我们的calc.py计算机程序文件就是这样子的!不信合并之后你可以看是

# 加法
def add(x, y):
    return x + y + 1

def sub(x, y):
    return x - y

def mul(x, y):
    return x * y

看见了吗刚好是c5!而被分割线分隔成短的那部分,也就是

    return x + y

我们让它和没被左右箭头包围的部分合并,就得到

# 加法
def add(x, y):
    return x + y

正好是分支b的c4’!

现在我们知道了,如果,我们选择分割线靠近HEAD的那部分,而分割线靠近分支b的那部分全部删掉,等于说是丢弃b的更改,继续走当前分支a,也就是HEAD所在的分支的道路;而如果我们把分割线到HEAD那头的内容删掉,只保留左右箭头包围圈外的和分割线到分支b那一头的,我们就保留了c4’做的分支,但是类似的,c4和c5的工作就全被丢掉了!

可是git它也没叫我们二选一把其中的一边全删掉啊!我们可以自己分析两份提交的代码的差异的,等号分割线和左右箭头符号其实就是辅助我们这么做的,这些符号教我们看到两份提交的差异,具体两边的取舍,通过何种方式合并代码,全是交给我们做主的.

处理合并冲突

首先我们看,通过分别做取舍,再对比(这是一种比较笨,但是也比较直接的方法),我们能分别看到两份代码

# 这是保留a而丢弃b的

# 加法
def add(x, y):
    return x + y + 1

def sub(x, y):
    return x - y

def mul(x, y):
    return x * y

# 这是保留b而丢弃a的

# 加法
def add(x, y):
    return x + y

通过对比,我们可以发现啊,分支b确实修复了加法函数,但分支b仅仅是修复了加法函数的bug而没有添加任何功能当然也没有实现减法函数和乘法函数,而分支a到目前的减法函数和乘法函数也都还没看出有什么错误,相信到现在,聪明的你已经知道该怎么做了,分支b的代码可以留下,而分支a中第4行

    return x + y + 1

要删去,当然左右箭头,HEAD字样,b字样,等号分割线也都要删去,删去之后应该变成这样子:

# 加法
def add(x, y):
    return x + y

def sub(x, y):
    return x - y

def mul(x, y):
    return x * y

神奇吧!这样终于就实现了即保留了分支b中修复漏洞的工作,又保留了分支a中的开创性的工作!果然,这种开源的工作方式,确实是博采众长啊!

合并之后我们再用刚才我们打提交日志的命令看一下,会是这样子的

Merged c4&rsquo; and c5

可以看到c6确实就同时指向c4’和c5了,每个星星*对应一个提交,每个星星水平向右会显示这个星星,也就是这个提交对应的提交评论和提交编号,仔细看就能看明白了.

我们这个示例中为了方便起见,提交评论就用了和文中讲解的案例一样的c几之类的字样,这在实际工作中,或者是在参与开源贡献的过程中,都是不推荐的,我们建议你用最多一行字,也就是80个半角英文字符之内,简明扼要地描述你的每一次提交.

小结

今天我们总算学习了Git版本控制系统中『分支合并』的概念,先歇一会儿,下篇文章将讲解Pull Request,也是参与开源社区提交自己的贡献的主要方式之一.感谢大家的阅读.

参考文献

[1] Git SCM

概念普及githubgit

记我的第一次GitHub Pull Request(下篇)拉取与推送

记我的第一次GitHub Pull Request(上篇)提交与分支