【Git & Github】
首先不能混淆两者的概念。git是一个类似于svn的版本管理工具。其可以在本地建立起针对一个项目的众多维度的版本管理体系,提升了开发的效率。
相对的,我们如果想要和别人共同开发一个项目,显然只在本地维护项目的版本相关信息是不够的,这时就需要一个“云版本管理”的东西。提供这样一个云平台的正式github这个网站。git工具本身很好地整合了github网站内的信息,使得整个git体系处于比较和谐统一的状态。
■ 本地仓库
https://blog.csdn.net/hebbely/article/details/51858938
如上所述,git本身是一个基于本地版本管理工具,所以即使不上网依然是可以使用git的。这一部分的git机能全部基于git的本地仓库这样一种实体。
git工具的安装方法网上有很多,我这里不写了。简单来说linux上就用yum或者apt-get这类工具,windows上可能需要去git官网下载一个客户端软件来安装。git的工具具体可以有图形界面的和字符界面的。这里以windows上的字符界面的git工具GIT bash为例子。
首先在任意一个地方建立一个空文件夹,比如我们建立一个名为test的目录。进入之后右键菜单中会有git bash here的提示,直接点击打开字符交互界面。没有的情况也可以去根目录找到git bash的程序手动打开然后cd到目标目录。
如果是第一次使用此工具,那么可以先运行git config --global user.name 'xxx'以及git config --global user.email 'yyy@zzz.com'来设置自己的身份。这些身份信息可以在后续的git使用中标识你以及你所做的修改,方便管理代码时找到责任人等。
● git init
在字符界面中键入git init,表示在当前目录下创建一个git本地库。完成之后可以在资源管理器中看到多出了一个.git的文件夹。由于是.开头的,在linux下可能不可见。这个目录下的文件就是维护了所有本地库以及和远程库交互的信息,一般没特殊需要的话尽量不要改动。
此时test目录下还没有任何实质的文件,我们可以创建一个test.txt文件,内写上hello, world,把这个文件视作我们管理的代码。
● git add/commit
git的版本提交逻辑是这样的: 所有代码的初始版本作为一个基础。当一些代码发生改动,则把这些改动(所在的文件)移入工作缓存区,通过查看工作缓存区的内容我们可以知道哪些内容被修改过了以及具体的修改内容。之后可以通过git add命令将工作缓存区中的文件手动移入到确认缓存区。之后再进行commit将确认缓存区中的内容确认提交,形成一个新的版本。由于commit命令读取的是缓存区中的内容,所以如果再add之后又对文件做出了修改,那么这些修改是不会在这次commit中被提交定版的。如果想要这些修改也被计入,显然在commit之前再add一次就好了。
git add的具体语法是后面要加上一些文件标识,如git add test.txt将test.txt文件add进来。另外,如果在一次修改中涉及到了很多文件,一条一条写add显然不是很现实,可以通过git add .添加当前目录所有内容或者git add -A代表--all这样的方式。
将所有需要提交的文件add了之后,就可以进行git commit来提交了。值得一提的是通常git commit需要添加-m参数来提供一些修改的comment。比如git commit -m "add xxx function and yyy method"等。由于commit是直接做了一个新版本,所以这些comment可以非常直接地表现出每个版本到底做了什么修改。
● git log/status/diff
在commit形成版本之后,如果我想要看一个版本的相关信息怎么办。答案就是git log。这个命令可以给出当前目录对应的那个本地仓库的版本历史。每一条版本,在界面中会显示的信息包括:版本号,一个uuid用来标识一个版本。这个版本号同时也可以像docker中的镜像容器id那样进行不完整引用。如果嫌命令的输出太乱的话也可以加上--pretty参数,比如--pretty=oneline就可以只输出一行,简单地说明每个版本的版本号以及commit时带的说明。
git status可以检查当前本地库中是否有已经修改但为提交的文件存在。比如当前此命令运行得到结果是nothing to commit。如果我们修改了test.txt的内容,那么再运行git status就会看到changed not staged for commit下有test.txt文件存在。如果我们git add test.txt但是不commit,此时再运行git status的话就可以看到changes to be commited下面有test.txt文件了。
git diff 后面可加具体文件名也可不加,这命令现阶段用于查看被修改过但是还没有被add过(即上文中说的处于changed not staged for commit状态的文件)的那些文件里,具体做了什么修改。当然,具体修改指的是文本类型的文件,二进制文件或者图片之类的东西即使修改了,git也只是知道修改过了这个事实而无法看出具体哪里有修改的 。
● git checkout/reset/reflog
上面的三个命令的说明过程中多多少少已经说到了关于提交代码变更中代码文件的几种状态。如果我们做出变更,让文件状态发生变化之后又反悔了,想要回滚文件该怎么办?这里就要用到这些命令了
git checkout -- xxx (此命令的--两个横杠比较重要,如果没有的话checkout命令就会变成一个切换分支的命令了)是当一个文件做出变更但是还没有进行add的时候,可以通过这个命令来把文件的内容回滚到变更前的状态;此外如果这个文件已经add过一次然后又做了些改变,那么这个命令可以将这部分add后的改变撤销,至于add之前的改变是不行的。要对add前的变更做变更的话必须要先撤销staged(或者说add过的)状态。
git reset HEAD xxx 就是可以将尚未commit但处于staged状态的文件撤销其staged状态。然后如果想要继续回退那么可以再跑一下checkout来实现内容的彻底回滚。
如果文件已经commit,形成版本,那么想要回退就只能回退整个版本了。回退版本的方法是
git reset --hard vvvv 其中vvvv是对应的一个版本号,可以不写全。版本号可以git log去查。hard参数表示将整个仓库回退到指定版本号的状态,如果换成soft则表示只是将其回滚到changes to be commited状态,再commit一下又可以定版的。相对的 如果不加hard或者soft参数,那么最终库会回退到changes not staged for commit并且保持文件内容处于输入命令前最新版本的状态。如果此时再add并且commit一下,相当于就是把原先最新版本到reset指定版本之间所有的版本都去除,并且做了一个新版本出来,相当于有个版本的合并压缩。
上面reset的时候有HEAD这个东西,在git命令中,我们可以认为HEAD就是指代了当前版本的版本号。顺便一提,HEAD^代表当前版本的前一个版本,HEAD^^则是前两个版本,HEAD~n则是前n个版本。所以所谓的“从add状态拿出文件”的reset HEAD其实和整个版本回退的reset本质是一样的。那么为什么整个版本回退不能单独指定一个文件呢?这个是git限制了,当带有参数--hard或者--soft,都不能指定特定的文件。不带hard或者soft参数倒是可以成功指定文件回退到特定版本,但是是处于chages not staged for commit状态,相当于之前的变更还是做了的,无法找回未做变更前的状态。
知道了HEAD的含义之后,很明显,面对任何还没有commit定版的变更,都可以通过reset --hard HEAD来将变更撤销掉。
如果某次我们通过reset --hard回退了库,此时git log可以发现log中的记录是初始版本到当前那个版本的历史,而当前版本之后的,被reset回滚的那些版本是不显示的。如果这是误操作,那些变更就找不回来了吗?其实还可以用的命令是git reflog。这个命令记录的是版本库从建立起到当前时间点所做的所有commit,reset等相关操作。
● git rm
git rm的本意是将某个指定的文件从整个工作目录的版本库中删掉,也就是手动指定从下个版本开始,git不再维护某个文件。运行此命令如果成功,那么OS中这个文件本身也会被删掉,并且文件被删除这个变动处于chages not commit状态。
如果某个文件确实已经被删掉,此时git status可以看到,被删除这个变动是处于changes staged for commit状态,此时还要add再commit才能将删除这个变动录入仓库信息中。
当然,如果在OS中误删了某些文件,如果还没有commit那么可以git checkout -- xxx来找回文件,如果已经commit了那么就需要git reset --hard HEAD^来找回了。
● 一些补充
git diff上面说了默认是比对本次修改完成但并没有add和commit的文件内容与前一个版本之间的差别的。实际上git diff可以方便地用来对比任意两个版本之间任意指定文件的差别。
比如 git diff HEAD HEAD^^就是比对当前版本和两个版本以前的文件的差异。需要注意的是,类似于这种指定了版本号的话,那么在当前版本的基础上现在做过的,还未add&commit的变动是不计入比较的。同样这个命令后面加上具体文件名就是比对两个版本间具体的某个文件的差异了。
■ 远程仓库
上述这些功能,其实在SVN中都有类似的(update,check等等操作),而git比svn牛逼的地方就在git有github这个“云端仓库”。github的意义在于1. 为代码的版本管理和内容变化留下变更记录备份,即使本地电脑炸了云端还保留着这些信息,换台电脑可以继续战。2. 多人协作时,有github(其实对于私密性要求比较高的项目也可以自己搭建git服务器,一个意思)时,可以个人的修改及时和团队同步并且留痕。
下面将简单介绍一下如何使用github。
首先需要一个github的账号,因为github现在没有被墙,直接可以上去注册就好了。在有了github账号之后就可以根据github的提示去start a project。这个过程中有个checkbox会让你选择是否start with a readme file。默认是不勾选的,这里暂时也先不勾选,勾选会引起一些麻烦,后面再说。 一个project对应的就是一个repository,也就是一个远程仓库。新建出的这个repo中空空荡荡,没有东西。记录下这个repo的名字后回到本地。
要将我们的代码文件推送到github中去,势必需要有通讯手段。github在这里提供的是SSH。另外由于每次推送都要输密码的话未免麻烦,所以github留出了添加公钥的接口进行免密推送。具体的操作步骤是先再本地生成秘钥对(linux通过ssh-keygen命令,如ssh-keygen -t rsa -C "youremail@example.com"
。windows的话大多数都不会自带ssh-keygen命令,好在git bash工具帮我们带上了,在gitbash的命令行中可以执行ssh-keygen.exe文件,参数是和linux一样的。youremail@example.com要填写注册github时用的邮箱号),然后将公钥的内容填写到github中的账号的Settings - SSH Key里面去。添加完毕后回到git bash的字符界面中来。
● git pull/push
假设根据之前本地仓库测试的流程,我们已经得到了一个已经具有一定内容,进行过commit形成版本的本地仓库。此时在这个仓库的git bash里键入
git remote add origin git@github.com:xxxx/yyyy.git (由于github也支持https协议,所以也可以写https://github.com/xxxx/yyyy.git,但是走https协议的话配置的秘钥就没用,还是要每次输入账密)。xxxx是github中你的用户名,yyyy则是新建的repo的名字,注意后面还有个.git别忘了,origin是我们为远程库在本地取的一个代号,可以自定义但是习惯上对于第一次加远程库都叫origin。这一步命令实际上是将这个本地仓库和远端的一个repo给关联起来。origin是给远程仓库取一个本地的代号。意思是说本地仓库其实可以关联好几个远程仓库,通过这个代号来区分各个远程仓库的不同。
接着是git push -u origin master,如果顺利,那么本地仓库中的所有内容就都推送到了github上的远程仓库了。-u参数是在第一次push的时候将远程的master分支与本地的master分支关联起来,这样以后在本地就只要push,pull而不用额外指定分支了。
这里有个小坑,就是当时创建repo的时候如果勾选了start with readme file的话,新建的repo里会有一个README.md文件。这个文件的存在导致本地仓库和远程仓库的主分支(分支到目前为止完全没说到…下面在细说)不同,从而不能直接push。加上参数-f可以强行推送,但是会导致readme文件丢失,而且-f是使用github时很忌讳的一个参数,尽量不用。其实从这个报错中可以看出,和SVN不同,git对于上传有可能覆盖、删除现有文件的时候,默认策略是上传失败,需要你保证这些文件确实可以被覆盖、删除的。
除了强行上传,更好的解决办法是在push之前先进行git pull origin master --allow-unrelated-histories。最后这个参数是为了不让它报错unrelated history。
如果单纯想把github上的代码给下载到本地又不想用download这种方式的话,可以在一个空目录进行git init开始然后remote add,接着git pull就好了。
■ 分支管理
以上所有的说明和演示,有一个默许的规则那就是我们都是直接对master分支进行操作。
所谓分支,考虑这样一种场景: 某个项目形成了1.0的稳定版本,后来要进行一些其他开发,将版本升级为1.1,这个升级过程从git的角度来说应该怎么做? 根据上面我们所学习到的,可以首先去修改代码,修改完成之后git add 再git commit,这样git就得到了新代码。
如果基于分支来操作就会更加复杂一些。首先,我们要从现有分支A复制(术语是checkout)一个分支B出来,分支B此时就像是分支A的一个副本,两者内容完全一致但互相独立。此时在分支B上修改代码。并进行add和commit。提交完成之后,分支B就变成了内容有别于原分支A的代码的一个独立分支了。
总的来说,这样基于分支的操作可以将修改更加保险,因为修改都是在副本上进行的,万一GG了原分支也还在,不必担心。当然,我们也有手段将分支B的修改内容同步回分支A。所以在多人合作开发的过程中,常见的做法就是每个人开始先从原分支checkout出一个自己的分支,进行开发修改,等完成后合并回原分支并删除开发用的分支,每个人都这么做,保证原分支拥有所有人开发的内容。
● 分支和指针 分支操作
在说具体的分支操作之前,先来看下什么是分支的指针。下面盗用了一些廖雪峰git教程的图。总体而言,对于一个项目的代码库来说,一个版本代表了一个特定的状态,在下面的图中以一个小圆圈表示。不同的版本可以互相串联起来形成一个项目的版本线。而分支,本质上并不包括一个实在的版本,而是一个指向特定版本的指针。所以我们常说,某个分支XX处于某个版本YY,其实就是说名为XX的指针指向的是名为YY的版本的代码。
除了分支是指针之外,这里不得不提到的另一个指针就是HEAD指针。在Git的命令操作中,HEAD代表了当前版本。而其本质实际上是指向了某个分支指针的指针,而那个分支就是当前我们工作所在的分支。
比如新项目默认的分支是master,然后又有HEAD指针的话大概可以画出这个图:
其实如果对上面分支的解释有了深入理解的话,就可以感觉到,其实虽然默认的分支叫做master,但是master和其他被新建出来的分支的地位是平等的。他们都只是一个指向某个具体版本的指针,这些版本的地位平等,所以指针自然也都是平等的。所以并不是说master是干流,其他分支都是支流这种感觉。
基于上面这个图,如果想要创建分支,那么可以运行命令
git branch dev是新建一个名为dev的分支。由于dev分支此时没有任何改变,相当于是创建了另一个名为dev的指针,指向的也是master指针指向的这个版本。
git checkout dev是切换当前分支到dev分支。从指针的意义上来说,master和dev都没变,只是HEAD指针从指向master改为了指向dev。用图来表示此时的状态就是这样的:
如果此后,对dev分支的代码做出了修改,然后做了提交commit,那么很显然dev分支的代码形成了一个新的版本,所以dev指针指向了版本线更加前方的一个节点。与此同时,由于当前的工作分支仍然是dev,所以HEAD指针是跟着dev指针一起迁移到了右边那个节点。
接下来就是要进行分支的合并了。首先我们切回master分支,即git checkout master。这时可以顺便看下那些在dev分支中已经修改过的文件,又变回了master中的原样。这是很理所当然的。因为分支的内容不同,自然就有内容实时的同步。然后进行分支的合并,我们用的命令是这样的:
(在master分支)git merge dev
合并分支时,我们先到达一条分支A,然后git merge B,在这个示例中A指针直接改为指向B指针指向的版本(根据具体情况不同,合并操作有可能比较复杂,这因为情况比较简单,所以只是一个指针的指向改变,这也被称为快速合并)。从命令的角度来看,这更像是“把B合并到A”,所以我们常说把dev分支合并回master分支。但是从指针的角度来看,dev分支的指代dev指针并没有动,只是master分支的master指针移动到了dev指针指向的那个版本。用图来表示merge过程是这样的:
(注意此时当前分支是master,所以才会有git merge dev,所以HEAD是指向master)
之后可以直接把dev分支删掉,那么dev指针就没有了。删除的命令是
git branch -d dev。另外值得一提的是,git对于没有进行merge的分支是不允许直接删除的,如果需要强行删除,那么需要将参数换成-D。
● 分支冲突的合并
上面的分支合并都是很理想的情况。但是在看的过程中想必也想到了,这样简单的合并也会存在很多问题。比如 不同分支冲突时git要如何合并?首先要明确,git对于分支冲突的判断相对是比较智能的。比如对于两个分支各自多出来一些文件,那么git默认就会将这些文件取并集,这样的合并不算有冲突,因此可以顺利合并出来一个新版本,这个新版本就包括了之前两个版本里面各自的那些新文件。再举一个例子,假如一个分支在某个代码文件中新增了一些语句,另一个分支又在同文件的另外一些地方新增了语句,最终合并git是会识别出虽然两者内容不同,但是实际上的修改并没有冲突,因此也可以顺利合并。
比如,当我们从master分支checkout了一个feature分支出来之后,对两个分支中同一个文件的同一行内容进行了不同的修改。比如最开始master中内容是111,然后feature1分支中改成222并提交,原master中改成333并提交。此时,无论在master还是在feature分支中,git log都是给出相同条数的commit信息,即两边版本数量一样,但是最新的一个版本,版本号不同,因为两者做的修改不同。用图来表示,两个分支在同样的一个纵坐标上出现了两条版本线,版本线出现了分叉
此时在master中进行git merge feature,如果只是单纯将master指针指向feature的版本是不合理的。为什么feature做的修改要保留而master的就不要了?
实际上,纵向的指针转指向要经过git的检查。对于无法通过检查的merge请求会被拒绝。当出现冲突的时候,git字符界面原先提示行最后会有一个括号提醒你当前分支是哪个,此时会变成类似于(master|MERGING)之类的提示,提示你当前有一个merge失败了。此时的git status也会给出提示。另外在git的语境中,如果对指定的文件进行cat等操作的话可以看到会以字符的形式给出提示,到底哪些内容发生了冲突。可以注意到,如果git merge --abort则可以放弃merge从而退回到master分支上。
那么如何解决这个冲突,显然需要将两个分支下被修改文件的修改内容进行统一,比如都改成333(或者其中一方改得和另一方一样),然后都add再commit。此时再去merge,则会显示让你输入一个comment,之后就merge成功了。(这里有一个疑惑,处于merging状态下的分支,文件内容的形式是>>>>>HEAD ===== <<<<<类似于这样的一种类似于diff字符界面比较结果的形式。如果直接在这个上修改并且做了提交之后,那么被提交上去的文件是维持这种比较结果形式的文本的。这个就很疑惑了,难道所谓的修改冲突内容是一定要想好修改方案,然后abort掉手动修改再提交的吗?如果冲突内容比较多那怎么可能记得住。。)
这个图还可以通过git log --graph命令看到类似的。如果还看不清可以 git log --graph --pretty=oneline。
上面说到的基本上是文件内容修改的情况。对于文件的增删会如何判断?对于增,只要是两个分支各自增加的独特的文件,就都会被列入合并完的分支中,对于删,只要两者是同步删除的也不会报错。总而言之,只要进行合并的双方达成了某种“一致”,对历史的情况(既存的文件内容和文件本身)不做不同的修改,对于未来的情况又有合理的协商(不创建不同文件内容的新文件),那么就可以做到纵向的合并不报错。
另外再来看下指针的情况吧。进行纵向的合并时,显然出现了一个囊括两个分支的一个新版本,且master和feature两个指针都指到了这个版本。一般此时就可以把feature给删掉了。
● 带有--no-ff参数的分支合并
上面说到的,如果将现有分支比如master直接merge到横向的下一个(或者几个)版本中,那么这个合并是属于快速合并,不需要进行新的commit而只是指针指向的一个简单变化。如果在git merge的时候加入--no-ff参数(意思是no fast forward)
比如之前的示例中,对于dev分支进行了修改并提交之后,checkout回master,此时git merge --no-ff dev -m "xxx"(其实从上面的“纵向合并”中可以看到,这种情况的合并由于是要结合两个不同分支的版本的修改生成一个新版本,因此需要在生成新版本的时候进行commit,所以需要加上提交的comment,因此加上-m参数)。然后我们就可以看到,git log --graph,此时尽管原先master和dev处于同一条版本线上,但是由于没有快速合并而是强行将dev看做是一条分离开master的线,随后又回归了master分支。
总体而言情况如图所示:
● 分支总体管理
总的来说,分支管理过程中,master应该尽量保持稳定,平时仅仅用来发布新版本而不是直接开发。进行开发的线可以单独拉一条dev分支。dev分支通常接受来自各个程序员各自的commit,同时为了避免多人同时commit可能造成的混乱所以一般来说每个开发人员还应该有自己的分支。开发人员完成了开发之后将自己的分支合并到dev分支,然后经由测试稳定之后的dev分支再合并到master分支进行发布。
分支系统管理代码效率比较高,git官方推荐应该多多使用git。
■ git场景
开发过程中会发生各种各样的场景,git有多种成熟的方案来应对这些场景,下面在讲述这些场景可以用到的git工具的同时还可能提到一些git的新命令。
● bug修复
假如你开始进行了今天的工作,做到一半的时候突然来了一个修改单要改bug。你当前的工作可能需要很久才能完成,但是bug马上就要修复。首先当前的工作不能直接提交,不然可能会影响到其他人的工作。其次如果是非常紧急的bug,那么基本可以直接从master上拉出一个紧急bug修复分支,比如叫做issue-101,在Issue-101上修复bug之后直接合并回master。
git stash可以将目前库中已经被修改过但是还没有add和commit的文件状态给暂存下来,然后将库回退到修改开始之前的状态。此时git status查看的话是看不到修改未提交记录的。stash暂存是和分支关联的。stash暂存只支持文件的改,对于文件的增删是无法做到暂存的,这部分需要手动进行存留。
然后我们可以去从master上拉出分支来进行bug的修改,修改后add再commit提交。一切bug修复工作完成之后,再回到我们自己的开发分支来,此时涉及到如何将stash中暂存的内容再读取出来。git stash list命令可以查看到当前各个暂存的列表,恢复暂存的方法可以是
git stash pop,将stash最新的暂存恢复并且从list中删除本暂存记录
git stash apply <num> stash list中其实是可以看到每个stash会有一个编号(如stash@{0})。apply后面加上序号可以恢复指定stash。但是这样做不会默认删除stash。
git stash drop <num> 删除指定的stash记录,可以和apply配合使用。
● 分支的本地与远程
之前我们说过.git remote可以将本地的git仓库和github上的git进行同步和互联。
再将本地的git和远程的某个git库互相关联之后,git remote -v可以查看到稍微具体一点的信息。通常,会显示出本地库关联了哪些远程库并且会在库地址后面加上(fetch)或者(push)之类的提示,有fetch就表示你对这个库有远程pull的权限,push则是有push的权限。
git和github的远程同步是以分支为单位的。比如git push的时候,通常是指定了<远端库> <分支>两个参数的。除了master,dev等其他分支也可以按照需要推送到github上去。
按照前面“分支总体管理”里面说过的应用模式而言,由于dev分支也是一个“集大成”的分支,需要将所有开发人员未经过验证的代码汇集起来,因此最好能够在github上维护期来。也就说,开发人员在进行开发之前先将dev分支pull下来(保证最新版本的代码)然后修改再push上去。而像上面提到的bug修复分支等,这些分支都是零时性质的,完成之后可立刻合并回master分支,所以最终只要推送master即可,这些分支本身无需推送。
● 多人协作时的git
现在我们在另一个文件夹下git init,并且以此进行一些操作以模拟有另一个人也在操作同样的库。
首先要做的是git clone git@github.com:xxx/yyy.git。git clone命令看起来和git pull很像,但是clone的意义首先是在当前目录下创建一个名为yyy的子目录,并且下载目标库的master分支的代码。并且将master分支的相关信息全部都写入yyy子目录的.git目录下。如果进入到yyy子目录再git branch,git log等是可以看到master分支的相关信息的。
因为修改操作不能直接在master,而是要在dev上进行,可是clone默认只复制了master,所以此时应该想办法把github上dev分支也搞下来。办法(务必注意这里是不能git pull origin dev来把dev分支给pull下来的,新手可能会认为这样可以但是实际上这个操作是把远端的dev分支pull到本地作为master分支。所以原master分支会丢失)是
git checkout -b dev origin/dev,相比于纯新建一个dev,参数origin/dev指出可以复制另一个分支。这时我们可以作为这个第二个程序员对代码做出修改,然后add,提交,git push origin dev。
再回到第一个git窗口,我们作为第一个程序员此时如果对代码做出一些另外的修改,此时尝试push时,会出现错误,提示远端已经做出了一些改变但你的本地还没有检查到这些改变。提示中顺便也给出了解决的办法就是进行git pull同步之后再git push。此时git pull,你会发现字符提示符末尾的分支从(dev)变成了(dev|MERGING),上一次我们看到这个符号的时候是git merge时两个分支发生一些冲突的时候。其实这个主要是因为git pull的本意,其实是git fetch + git merge两个命令。fetch是将远端代码下载到本地,但是暂时还不覆盖本地的内容,而后将分支的远端代码和本地的分支进行合并。如果没有冲突自然就合并顺利,如果像这个情况一样冲突了的话,那就会进入merge冲突的处理模式,如何处理冲突见上文所叙述的。
如果反过来,是第一个程序员首先提交了变更,第二个程序员按照上面的方法去先clone,checkout -b dev origin/dev再修改提交,再push的时候,有可能会报错no tracking information,这主要是因为虽然我们把github上的dev分支关联到了本地的dev分支,但是并没有反着关联回去。所以push的时候并不知道是push到哪个分支。解决办法在报错信息中也有提示,运行 git branch --set-upstream-to <本地branch名> origin/<远端branch名> 即可。
下面抄一段:
多人协作的工作模式通常是这样:
首先,可以试图用
git push origin <branch-name>
推送自己的修改;如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并;如果合并有冲突,则解决冲突,并在本地提交;(解决冲突的过程可能要找到改了和你冲突的那个人是谁,和他协商看到底谁的比较合适)
没有冲突或者解决掉冲突后,再用
git push origin <branch-name>
推送就能成功!
如果git pull
提示no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
这就是多人协作的工作模式,一旦熟悉了,就非常简单。