Git应用详解第六讲:Git协作与Git pull常见问题

前言

前情提要:Git应用详解第五讲:远程仓库Github与Git图形化界面

git除了可以很好地管理个人项目外,最大的一个用处就是实现团队协作开发。况且,linus大神开发git的初衷就是为了维护Linux内核这一开源项目。所以,熟悉使用git进行多人协作开发的一般步骤和方法具有十分重要的意义。这一讲将会为你介绍使用git进行团队协作开发的一般方式以及git pull操作常见问题的解决方法。

一、git协作方式

1.常见开发模式

  • Gitflow:简单来说,就是多种开发模式的总称。例如:使用多少分支,什么时候合并分支等等。这方面篇幅较长,内容较多,之后会进行详细讲解;

  • 基于Git分支的开发模型:一般最少有三个分支:

    • develop分支:频繁变化的分支,供开发人员之间进行协作开发,文件推送与合并;
    • test分支:供测试人员与产品等人员使用的一个分支,变化不是特别频繁;
    • master分支:生产发布分支,变化非常不频繁的一个分支(一般有权限设置,因为直接与生产有关);
    • bugfix(hotfix)分支:用于紧急修复的分支;当出现紧急bug时,在常规的develop分支上修复已经赶不上了。此时可以直接将master分支的代码拉取到bugfix分支上,进行bug修复,修复完之后,再将它合并到master分支上发布;

    合并方向为:develop -> test -> master

2.SVN方式(典型模型)

image-20200416135410464

首先有两位用户ABA的本地仓库不为空B的仓库为空,还有一个远程仓库C

  • A首先将本地仓库的代码推送(push)到C中,此时AC两个仓库的文件一致,如图中1所示;
  • 随后BC的代码拉取(pull)下来,如图中2所示,此时ABC三个仓库中的文件一致;随后AB继续在本地进行开发,并向各自的本地仓库进行了数次提交;
  • 此时,A先向C推送修改过后的本地仓库文件,由于这是远程仓库C的首次修改,C中的文件A中都有,所以可以直接推送,不用先执行git pull,如图中3所示;
  • 随后,在B将修改过的本地仓库文件推送到C的过程中会出现错误。原因在于:此时的C中有A做出的修改,不能让B进行覆盖,此时B要想成功推送,应该先将C中的文件拉取(pull)到本地;如图中4所示,拉取时有两种情况:
    • 成功:说明AB修改的不是同一个文件,采用Fast-forward方式自动合并;
    • 失败:说明AB修改了同一个文件,需要手动解决冲突并合并;
  • B成功将C中的文件拉取到本地合并后,就能将B对本地仓库所做的修改推送(push)到远程仓库C了,如图中的5所示;

在整个过程中,可以发现远程仓库C仅仅是起到代码第三方托管的作用;

3.模拟多人协作

为了模拟多用户协作,可以使用--local来设置每个仓库的用户信息:

git config --local user.name '张三'

--local是一个配置作用域的参数,其他的还有:

  • --global :作用域为每个计算机用户,优先度第二,实际上常用这个参数进行配置;
  • --system :作用域为整个系统,优先级最低;

可以使用:git clone将远程仓库的代码下载到本地某文件夹中,下面使用的是SSH的方式:

image-20200328170839067

还可以通过在链接后面加上一个字符串,重新命名下载到本地的远程仓库文件的名字:

git clone [email protected]:ahuntsun/MY.git mygit2

image-20200328171302207

4.协作的本质

远程仓库通常有多个分支,而在本地仓库进行一次推送时并不是将本地仓库的所有分支都推送到远程仓库,而是选择本地仓库中的一个分支,将其推送到远程仓库的其中一个分支上:

image-20200411182057807

比如本地的master分支,如上图所示,可以选择远程仓库的master、dev、test其中一条分支进行推送。假如想要推送到远程仓库的master分支,如果一开始两个分支没有任何联系,自然要:

  • ①先建立本地master分支与远程master分支的关联(至于如何建立关联,下一节将会详细讲解);
  • ②通过本地master分支与远程master分支的合并,使两条分支的内容第一次达到同步;
  • ③在本地master分支上进行修改,然后将修改推送到对应的远程master分支上。此时,两分支的内容第二次达到同步;

二、git pull

在实际开发中,在推送代码前,往往都要先执行一次git pull将远程仓库的代码拉取到本地并进行合并;从前面的学习中我们知道:git pull = git fetch + git merge

  • git fetch:表示将远程仓库的所有文件拉取到本地版本库;
  • git merge:将远程仓库中的文件与本地仓库中的文件进行合并;

但是,在执行git pull命令时,由于本地仓库与远程仓库历史提交记录的不同,往往会出现各种各样的合并错误;在分析这些错误之前,首先搭建测试环境:

分别创建两个本地仓库mygitmygit2,并且这两个本地仓库与同一个远程仓库建立联系,如下图所示:

image-20200416141016230

1.不发生合并冲突

在本地仓库mygit2中使用--local参数配置新的用户lisi模拟多人协作,随后通过lisi给远程仓库推送一个新的文件。回到mygit后执行git remote show origin指令,会显示如下信息:

image-20200328172158711

表示,本地仓库mygit相对于远程仓库而言已经过时了,即远程仓库中有mygit2推送的,mygit中没有的文件;此时可以在mygit中执行git pull,将远程仓库中的文件拉取到本地仓库mygit中进行合并:

image-20200328172428901

上图中的第二个箭头表示,在pull操作的过程中mygit中的master分支与远程仓库中的master分支采用Fast-forward方式进行了合并,并达到了同步。

这里的本地远程分支origin/master代表着远程master分支,关于本地远程分支将会在下一节进行详细讲解;

关于Fast-forward方式之前已经介绍过了,在上述合并过程中origin/master分支直接指向了最新提交,中间没有其他分支,也就不会出现合并冲突,这种合并方式称为快进。如下图所示:

image-20200407142030574

这是一个理想的情况,很多情况下执行git pull操作时,都会出现合并冲突,需要解决冲突,再进行手动合并;

2.git pull同源合并冲突

所谓同源,指的是本地仓库与远程仓库中的分支从根提交节点开始,有共同的提交历史;简而言之,有共同的根提交节点的两个分支称为同源;如下图所示,两仓库中的master分支有共同的根提交节点A,所以这两个仓库的master分支是同源的:

image-20200410115057180

这种情况下git pull出现的错误为自动合并失败,比如都同时修改了develop.txt文件,错误信息如下:

Auto-merging develop.txt
CONFLICT (content): Merge conflict in develop.txt
Automatic merge failed; fix conflicts and then commit the result.
错误原因

具体情况模拟如下:

mygit中修改hello.txt文件的第二行为1,在mygit2中修改hello.txt文件的第二行为2,即对同一文件的同一处进行了修改。

此时取决于谁先进行git push操作,若mygit先将修改后的hello.txt推送到远程仓库。那么当mygit2再进行推送时会出现如下错误:

image-20200328173921075

提示信息表明:远程仓库中有一些文件是你没有的,无法更新远程仓库;这是因为,mygit先把修改的hello.txt推送到了远程仓库;此时mygit2想要成功进行推送,需要先将远程仓库中经过mygit修改的hello.txt与本地仓库的hello.txt进行合并。

解决方案

可以使用git pull来解决这一问题,那么我们首先执行一次git pull操作:

image-20200328174312298

可以发现git pull指令在进行自动合并时发生了错误,这是因为mygitmygit2都对hello.txt的同一个地方做了修改,git不知道以谁为准,所以会导致自动合并失败,此时需要通过解决冲突三步曲来手动合并:

第一步:

打开冲突文件hello.txt可以看到典型的冲突文件显示方式:

image-20200328180835818

箭头<<<>>>范围内表示的是发生冲突的位置。2mygit2hello.txt的修改,1为远程仓库中hello.txt的内容;

经过协商后,留下第3行,其余删除:

image-20200328181200775

由此手动合并了对文件hello.txt的修改,解决了冲突。

vim指令补充:通过esc进入命令行模式后,通过上下方向键选中某一行,再双击d就可以删除光标所在的行;删除多行时,在命令行中输入:2,4d表示删除第2~4行;

第二步:

再次查看状态:

image-20200328181841135

发现hello.txt处于工作区,git提示我们要通过git add指令将解决冲突时对hello.txt所做的修改纳入暂存区。

第三步:

执行完git add之后,再进行提交git commit:
image-20200328182755564

由此,解决了冲突;

从上图中箭头所指内容可以看出:本地仓库mygit2中的master分支已经比本地远程分支origin/master分支多了两次提交。由于origin/master分支代表着远程仓库的master分支,也就是说本地仓库mygit2中的master分支比远程仓库的master分支领先了两次提交;过程如下图所示:

image-20200407152200173

  • 首先mygit在提交1st的基础上进行了第2次提交(修改hello.txt),之后mygit将本地仓库推送(push)到远程仓库;

  • 此时mygit2同样在本地仓库中进行了一次提交3rd(修改hello.txt),此时推送到远程仓库会出现错误,需要进行pull操作;

  • mygit2执行pull操作,将远程仓库拉取到本地后,由于发生冲突,所以暂时不会将origin/master的指向更新到最新提交;随后,在mygit2中手动解除冲突并进行合并后,mygit2的状态为:

image-20200407151529950

可以看到解决冲突,手动合并后,mygit2已经往前更新了两次提交,而此时origin/master仍然指向提交1st

所以解决冲突后,mygit2中的master分支会比origin/master分支领先两次提交;再次执行git push后,origin/master分支就会指向最新的提交点4th了,此时三个仓库的状态为:

image-20200407152431572

在实际开发当中,难免会出现多个人修改了同一个文件的情况,在进行手动合并的过程中一定要与对方协商应该如何合并,而不是直接覆盖

3.git pull不同源合并冲突

所谓不同源,指的是两个仓库中的分支,根提交节点不同,如下图所示:

image-20200410123001021

假如本地master分支要将内容推送到远程master分支。由于本地master分支根提交节点为1st,远程master分支根提交节点为A,两个分支没有公共的父提交节点。所以,无法进行合并。这种情况下执行git pull会出现以下错误:

There is no tracking information for the current branch.
Please specify which branch you want to merge with.
错误原因

简单来说git pull失败的原因有两点:

  • 第一点:两仓库中的master分支由于根提交节点不同,没有共同的提交历史。所以,会导致采用三方合并原则合并分支时,找不到公共提交节点而无法合并:

image-20200411001626440

  • 第二点:本地master分支没有与远程仓库中的任一分支建立关联。因此,本地master分支不知道将文件推送给谁,这样自然会失败;
解决方案

知道了git pull失败的两点原因,解决方案就很清晰了,同样分为三步:

  • 第一步:执行一次git pull将远程仓库的分支拉取到本地:

    image-20200411211023424

    这里的本地远程分支origin/masterorigin/dev是远程分支masterdev的本地形式,代表着它们,内容上与它们一致。虽然git pull失败了,但是我们获得了远程分支的信息,方便进行第二步的合并操作;

  • 第二步:创建两分支的公共提交历史。此时两分支没有公共父节点,不能采用merge方式合并。应该采用rebase变基的方式,将本地master分支追加到远程master分支后面。由于本地远程分支origin/master与远程master分支有这相同的提交历史,所以可以这样写:

    git rebase origin/master
    

    此时,本地master分支的提交历史变为:A <- B <- 1st <- 2nd。这样本地master分支与远程master分支就有了公共的提交历史,即转换为了同源分支:

    image-20200411222958428

    简写:可以将第一步和第二步通过参数的形式合并为一步操作:

    git pull --rebase origin master
    

    image-20200411222302708

  • 第三步:建立本地master分支与远程master分支的关联。常用的有以下三种方式:

    • 方式一:
    //格式
    git branch --set-upstream-to=origin/<branch> master
    //用在这里具体为
    git branch --set-upstream-to=origin/master master
    

    该指令作用为,将本地master分支相关联的远程分支设置为远程master分支,执行该指令后,通过git branch -vv查看分支的关联情况,可见已顺利建立关联:

    image-20200411213556267

    之后就可以进行推送了:

    image-20200411213920987

    • 方式二:
    git push -u origin master
    

    该指令作用为:建立本地master分支与远程master分支的联系,并进行推送:

    image-20200411215931617

    • 方式三:
    git push --set-upstream origin master
    

    作用为:建立本地master分支与远程master分支的联系,并进行推送:

    image-20200411221552193

实战演示
  • 创建两个仓库mygitAmygitB,首先在mygitA中的master分支上添加A.txt,提交信息记为A

image-20200410125735375

  • mygitA中创建并切换到dev分支,添加文件C.txt,并进行提交,提交信息记为C

image-20200410125946568

  • 切换回mygitAmaster分支,添加文件B.txt,并进行提交,提交信息为B

image-20200410130141667

​ 此时mygitA中两分支的状态如下:

image-20200416153057140

  • 随后,建立本地仓库mygitA与远程仓库的关联:

    image-20200410130748140

  • 设置并推送mygitAmaster分支和dev分支,到远程仓库的master分支和dev分支上:

    image-20200410232136808

  • 回到mygitB,添加文件1st.txt2st.txt并进行两次提交1st2nd

    image-20200410232755681

此时三个仓库的状态为:

image-20200410233107154

若想将mygitBmaster分支推送到远程仓库的master分支上,按照上文的讨论,采用简写形式,可通过以下两步进行实现:

  • 第一步:通过rebase合并本地master分支与远程master分支:
git pull --rebase origin master

image-20200411231327155

执行完上述指令后,mygitB的状态为:

image-20200411000848565

  • 第二步:建立本地master分支与远程master分支的联系,并进行推送:
git push -u origin master

image-20200411231531483

如图所示,与远程分支建立了联系,并完成了推送;由此解决了由于不同源造成的pull操作冲突。

以上就是本节的全部内容,细心的你肯定发现了,在这一节中偶尔会提到本地远程分支origin/master,它到底是什么呢?有什么作用?其实它是git进行本地仓库与远程仓库交流的一个重要桥梁。在下一节中将会为你详细介绍本地远程分支的由来和作用,以及最重要的:如何建立本地仓库与远程仓库的分支对应关系?我们下一节再见!

Tags: