前情提要:Git应用详解第六讲:Git协做与Git pull常见问题git
这一节来介绍本地仓库与远程仓库的分支映射关系:git refspec
。完全弄清楚本地仓库究竟是如何与远程仓库进行联系的。github
Git refspec
refspec
是Reference Specification
的缩写,字面意思就是具体的引用。它实际上是一种格式,git
经过这种格式来表示本地分支与远程分支的映射关系;vim
在本地仓库建立master
分支外的其余两个分支develop
和test
:bash
在develop
分支上执行git push
命令,出现以下错误:编辑器
这是因为本地分支develop
没有与任何的远程分支创建联系致使的。经过git branch -vv
查看本地与远程分支的关联状况,可见并无创建任何联系:post
在讲解如何创建与本地分支关联的远程分支以前,首先咱们来介绍期待已久的本地远程分支:学习
git
中其实有三种分支:本地分支、本地远程分支、远程分支;origin/develop
就对应着远程分支develop
。当本地master
分支创建了与之关联的远程分支master
后,查看当前分支状态:fetch
图中的origin/master
为本地远程分支,表明的是远程仓库的master
分支,而这个分支是在本地的;也就是说加上远程仓库的master
分支,一共有三个master
分支:ui
而且,当本地仓库中的每个分支都有与之关联的远程分支以后,本地仓库都会建立对应的本地远程分支,它们所处的位置和关系以下图所示:url
能够这样理解:本地远程分支
origin/master
为远程分支master
的本地化形式;
假设远程仓库和本地仓库文件内容是同样的,都只有两次提交,此时三个分支的状态以下图所示:
而后,在本地的master
分支中新增了提交3rd
,本地仓库的分支状况变为:
上图中的
git dog
为指令:git log --all --decorate --oneline --graph
的别名,有关内容将在下一节讲解。
分支的示意图以下:
可见本地master
分支比本地远程分支origin/master
多了一次提交。这是由于本地远程分支是为了追踪远程分支而存在的,只有在执行pull
或push
操做时它的指向才会更新。好比在执行推送(push
)指令时:
首先,本地master
分支对应的本地远程分支(origin/master
)会指向本地master
分支最新的提交(向前走了几步);
而后,本地master
分支再将文件推送到远程master
分支中。完成推送后,三分支的状态为:
回到终端,咱们将刚才新增的提交3rd
推送到远程分支,成功后查看本地分支以及本地远程分支的提交历史:
可见,本地远程分支的指向获得了更新,指向了最新的提交3rd
,由此验证了上述说法。
查看分支关联
能够经过如下指令查看本地分支与本地远程分支的关联状况:
git branch -vv
复制代码
能够看到:本地的master
分支有本地远程分支origin/master
关联,说明本地master
分支已经和远程master
分支创建了关联;
其他两个本地分支pop
和develop
并无与之关联的本地远程分支,因此它们并无与远程分支创建联系。
简单点说:只要本地分支有与之对应的本地远程分支,就有与之对应的远程分支。
总结:origin/master
做用:追踪远程分支。当执行git push/pull
操做时,该分支的指向都会相应地发生变化,用于与远程仓库保持同步;好比:本地仓库在执行git push
操做的时候,不只会把本地的修改推送到远程;还会同时修改origin/master
分支的指向;
可经过该指令查看本地的全部分支及其最新的提交信息:
git branch -av
复制代码
首先,在master
分支上进行三次提交,并将它推送到与之关联的远程master
分支,此时各分支的提交历史为:
三个分支的状态为:
在此基础上,在master
分支上进行一次提交4th
,而后查看状态git status
:
图中提示信息代表,当前分支(master
)已经领先于origin/master
分支一次提交。为了看得更清楚,咱们查看本地各分支的提交历史:
从图中可看出,origin/master
分支确实落后了一次提交,表示远程master
分支落后了一次提交。此时可使用git push
将新增的提交推送到远程master
分支,在这个过程当中会将本地远程分支origin/master
指向最新的提交4th
。成功推送以后,再次查看本地各分支的提交历史:
可见,经过git push
操做本地远程分支确实发生了更新,指向了最新提交4th
。这就验证了执行git push
时进行了两步操做:
master
分支的新提交推送到与之关联的远程master
分支;origin/master
指向本地master
分支的最新提交;git pull
操做同理,也会更新本地远程分支的指向;
也就是说:每次执行push
或pull
操做后,本地分支、本地远程分支、远程分支三个分支的指向都会达到同步。
当切换到origin/master
分支上时,以下图所示:
git
并不会直接将分支切换到origin/master
上,而是切换到最新的一次提交上,即一个游离的提交。这从侧面说明了:git
是禁止咱们直接修改origin/master
分支的,只容许咱们切换到最新的提交上;
也就是说本地远程分支(如origin/master
)是只读的,只能由git
来改变,这就解释了为什么使用git branch
没法查看本地远程分支。
弄清楚了什么是本地远程分支,就能更好地理解接下来所要介绍的,如何创建本地分支与远程分支的联系了。
上图提示信息中的:upstream branch
表示上游分支,即远程仓库的分支。当前的本地分支develop
并无一个远程仓库的develop
分支与之对应;要想推送develop
分支到远程仓库的同名分支,首先要建立对应的远程分支,有如下两种类型四种方法:
**类型一:**创建本地与远程分支追踪关系的。
git push --set-upstream origin <branch>
git push -u origin <branch>
复制代码
使用该类型方法,只需设置一次,以后就可使用简写形式git push
进行推送。
**类型二:**不创建本地与远程分支追踪关系的。
git push origin HEAD
git push origin <branch_name>
复制代码
使用该类型方法,每次推送都须要采用上述的完整写法。
下面就来详细介绍这四种方法:
git push --set-upstream origin <branch>
**方法一:**采用下述指令为本地仓库mygit
的develop
分支建立远程分支:
git push --set-upstream origin develop
复制代码
该命令的做用为:在远程仓库创键一个与本地分支develop
关联的同名分支develop
,并将本地分支develop
的文件推送到该远程分支上。
也就是将本地分支develop
的上游分支设置为远程仓库的develop
分支,并进行文件同步。
执行完上述命令后会有这样的提示:
表示本地的develop
分支已与远程的develop
分支创建联系;此时查看本地分支,会发现多了一个本地远程分支origin/develop
,而且已与本地develop
分支创建了联系:
随后再次执行git push
就不会出现问题了:
此时在github
上查看对应的远程仓库,就能查看到新增的远程分支develop
了:
上图中的
master
分支是远程仓库建立时默认建立的,并无与本地master
分支创建联系。
随后点开branch
能够看到:
当前一共有两个分支,master
分支是default
(默认)分支,是不可以被删除的;活跃的分支为deavelop
;
git push -u origin <branch>
**方法二:**先切换到test
分支,再执行如下命令,为本地仓库mygit
的test
分支建立对应的远程分支:
git push -u origin test
复制代码
-u
与--set-upstream
做用是相似的,都是在远程仓库新建一个新的分支,并与本地分支创建联系。
执行完上述指令后,再次查看本地分支的详细状况,以及分支对应关系,能够发现test
分支已与远程test
分支创建联系:
git push origin HEAD
方法三:
以下图所示,经过该指令成功设置了本地develop
分支对应的远程develop
分支。但没有显示追踪信息,以后不能使用git push
推送。
git push origin <branch>
方法四:
以下图所示,该方法实质上与方法三相同,由于HEAD
指向的就是当前分支。一样没有显示追踪信息,以后也不能使用git push
推送。
**总结:**当本地分支与远程分支同名时,一旦手动创建了它们之间的联系。以后推送本地分支的文件到对应的远程分支时能够采用简写形式:
git push
。这是由于在已经创建三个分支的对应关系并后,再执行
git push
,git
会自动地将同名的本地分支与远程分支进行匹配;而其余状况则要采用完整写法进行推送。关于这些结论,将在第三大点
-u
参数的做用中详细介绍。
主要有如下四种方法,注意:使用每种方法前都须要先切换到对应分支上。
git push --set-upstream origin <branch1>:<branch2>
方法一: 好比当前位于develop
分支,若是采用的是如下简写命令:
git push --set-upstream origin develop
复制代码
则会建立一个同名的远程分支develop
。而若是采用该命令的完整写法,就能够自定义远程分支的名字了,好比设为develop2
:
git push --set-upstream origin develop:develop2
复制代码
执行上述指令后,成功建立了对应的,不一样名的本地远程分支origin/develop2
。表示本地develop
分支已与远程develop2
分支创建联系(由于远程分支与本地远程分支是一一对应的关系):
在github
上查看本地仓库关联的远程仓库MY
,能够看到顺利建立了develop2
分支:
能够发现这么一个规律:在建立远程分支的同时会建立同名的本地远程分支。
git push -u origin <branch1>:<branch2>
方法二:
以下图所示,使用-u
参数也能将本地develop
分支的远程分支自定义为develop2
。
git push origin HEAD:<branch>
方法三:
经过该方法也能成功设置与本地分支关联的,不一样名的远程分支develop2
:
git push origin <branch1>:<branch2>
**方法四:**该方法与方法二实质上是同样的,由于方法二中的HEAD
指针指向的就是当前所在的分支,也就是develop
分支。过程与方法二相似:
上面这四种设置不一样名远程分支的方法,都有一个共同特色:不能使用
git push
进行推送。
若使用git push
都会出现找不到对应远程分支的错误:
缘由在下面第三点的-u
参数做用中会详细讲解。
既然是-u
参数追踪问题,那我加上-u
参数不就好了么?其实这样也行不通:
解决方案:每次推送的时候,指明本地分支与远程分支的对应关系,即采用上述命令的完整写法,好比:
git push --set-upstream origin develop:develop2
git push -u origin develop:develop2
git push origin develop:develop2
git push origin HEAD:develop2
复制代码
采用了完整写法后,成功地进行了推送,以下图所示:
注意:虽然能够自定义远程分支与本地远程分支的名字,可是十分不推荐,由于容易出错。因此,建议本地远程分支和远程分支都使用默认的,与本地分支相同的名字。
以本地分支develop
为例,不难发现:
使用下列简写命令时,远程分支和本地远程分支都会采用默认的,与本地分支相同的名字:
git push --set-upstream origin develop
git push -u origin develop
复制代码
而使用下列命令的完整写法时,就能够自定义远程分支与本地远程分支的名字:
git push --set-upstream origin develop:develop2
git push -u origin develop:develop2
git push origin develop:develop2
git push origin HEAD:develop2
复制代码
git push origin master
与git push -u origin master
的区别第一次将本地仓库的master
分支推送到远程仓库的master
分支上时,使用前者和后者均可以顺利推送,区别在因而否使用了-u
参数:
推送时不使用-u
参数:
推送时使用-u
参数:
注意到推送时使用-u
参数会打印下列提示信息:
Branch 'master' set up to track remote branch 'master' from 'origin'.
复制代码
表示本地的master
分支被设置去追踪远程的master
分支,在第2~n
次推送中,只须要使用git push
这样的简写命令(固然,完整写法效果等同)。git
就会自动将本地的master
分支与远程的master
分支进行匹配,完成推送:
而不使用-u
参数时,没有上述的分支追踪信息。此时使用简写git push
进行推送会出现错误:
错误信息显示:当前分支没有与之对应的远程分支。这个时候想要成功推送,必须采用指明对应关系的完整写法,好比:
git push origin master
复制代码
这就是推送时使不使用-u
参数的区别。而且,根据上面的介绍,使用以下指令进行推送也能达到-u
参数的效果:
git push --set-upstream origin develop
复制代码
以后也可使用简写的git pull
指令进行推送:
细心的你必定发现了,以上都只是本地分支与远程分支同名的状况。不一样名的状况下,上面的两个方法还好使吗?
首先验证方法一:-u
参数:
设置不一样名的远程分支时要注意写成完整形式:
pop:pop2
能够看到,即便建立不一样名的远程分支,-u
参数也同样可以设置追踪关系;可是,奇怪的是git push
却很差使了:
仍是和没使用-u
参数时同样,找不到对应的远程分支,须要采用指明对应关系的完整写法,好比:
git push origin pop:pop2
复制代码
其次验证方法二:--set-upstream
:
一样设置分支对应关系时要使用完整写法。能够看到,该方法也设置了追踪关系。奇怪的是git push
一样无论用:
一样找不到对应的远程分支,须要采用指明对应关系的完整写法,好比:
git push origin bob:bob2
复制代码
因此能够得出结论:
-u
参数的做用是设置本地分支与远程分支的追踪关系,设置了追踪关系后,以后的推送可以使用简写git push
,git
内部会自动进行匹配;--set-upstream
参数与-u
参数效果等同;--set-upstream
参数与-u
参数依然能够设置分支的追踪关系,可是,以后的推送不能使用简写git push
,只能使用指定分支对应关系的完整写法;总结:十分建议将全部的本地分支与对应的远程分支设为同名,而且第一次推送使用--set-upstream
或-u
参数创建分支追踪关系,以后就可使用简写git push
进行推送了!
git push -f
该命令的完整写法为:
git push -f origin master
复制代码
意思为强制推送:直接跳过与远程仓库的master
分支合并的环节,强制覆盖远程仓库上master
分支的内容,即以本地的master
分支内容为准。应慎用该命令,不然将覆盖远程仓库中master
分支上其余人推送的文件(一星期的成果没了)。
-f
强制推送,直接覆盖远程分支上的内容;分两种写法:
**第一种:**已经经过-u
参数等方式,设置了本地分支与远程分支的追踪关系时,采用:
git push -f
复制代码
**第二种:**还未设置追踪关系,采用:
git push -u origin master -f
复制代码
Github
提供了相应的分支保护机制,能够在Settings
选项中进行设置:
能够看到Github
默认是保护分支的:
让有进度的人,再次对被强制覆盖的远程分支执行一次 git push -f
指令,把正确的内容强制推送上去,覆盖前一次 git push -f
所形成的灾难。
假如远程仓库M3Y
中有master
和develop
两个分支,此时新建一个空的本地仓库mygit
,经过如下指令将它的远程仓库地址origin
设置为M3Y
的地址:
git remote add origin git@github.com:AhuntSun/M3Y.git
复制代码
此时两仓库的状态为:
因为mygit
是空仓库与远程仓库M3Y
没有任何公共提交历史,因此在执行git pull
时会出现下图所示的不一样源冲突(上一节中详细介绍过该冲突):
虽然git pull
操做失败了,可是也成功地将远程仓库M3Y
的分支拉取了下来。可是,经过git branch -vv
查看分支追踪关系,发现并无本地分支与这两个远程分支创建了联系:
如何创建这两个本地远程分支对应的本地分支?能够经过如下两种方法:
git checkout -b <branch> origin/<branch>
好比能够经过如下命令,设置本地远程分支origin/master
与本地master
分支的追踪关系:
git checkout -b master origin/master
复制代码
以上为本地master
分支已存在的状况,若是本地分支develop
未建立,能够采用下述命令建立并切换到develop
分支,而且设置origin/develop
与develop
的追踪关系:
git checkout -b develop origin/develop
复制代码
设置了本地分支与远程分支的追踪关系,接下来就能够在本地仓库执行git push
进行推送了:
git checkout --track origin/<branch>
重置条件,新建立一个空的本地仓库mygit2
,一样将其远程地址origin
设置为远程仓库M3Y
的地址。随后在本地仓库mygit2
中执行git pull
操做,将远程仓库M3Y
中的两个分支拉取到本地:
与上次同样,拉取到本地的两个本地远程分支没有与任何本地分支创建追踪关系。此次能够采用另一种方法:
git checkout --track origin/test
复制代码
建立并切换到develop
分支,而且设置该分支与origin/develop
分支的追踪关系:
能够说该方法是方法一的特殊状况,由于该方法没有指明建立的本地分支的名字,因此默认采用与远程分支同样的名字develop
来命名;
若是想在本地创建一个develop2
(不一样名)的分支与本地远程分支origin/test
创建追踪关系,则应采用第一种方法。
能够进入.git
目录,查看储存远程分支信息的文件:
config
文件使用vim
编辑器打开该文件,能够查看到关于远程分支的信息:
能够看到remote
这一栏中有两个信息,第一个是远程仓库的url
,第二个是fetch
信息,这两个信息尤其重要:
refs/heads/*
表示远程仓库的refs/heads
目录下的全部引用都会写入到本地的refs/remotes/origin
目录中;+
号是可选的,加了表示不管是否可以自动合并,便是否为Fast Forward
方式,都将远程仓库全部文件拉取到本地。+
则表示若是不是Fast Forward
方式就不拉取。通常状况下都是加上+
号的,先把文件拉取到本地,不是Fast Forward
方式就手动合并;refs
文件refs
文件夹存储着refspec
的文件,里面维护着三个目录:
第一个目录heads
:存储的是本地仓库的分支信息:
能够查看其中一个分支:
是一个SHA1
值,表示分支就是一个指针,指向当前提交。
第二个目录remotes
:里面存放着远程分支信息,远程仓库中也存在这样的目录与文件;
从上图能够看到,远程分支只有master
,没有develop
(由于以前被删除了)。而且它们本质上也是一个表明提交的SHA1
值:
创建refspec
映射(即本地分支、本地远程分支、远程分支三者间对应关系)后,git
会获取远端上refs/heads
下的全部引用,并将它们写入本地的refs/remotes/origin
目录下。因此,能够经过查看本地远程分支(如origin/master
)的方式查看本地仓库最后一次访问远程仓库时,远程仓库master
分支上的历史提交记录:
//完整写法
git log refs/remotes/origin/master
//进一步简写
git log remotes/origin/master
//继续简写
git log origin/master
复制代码
上述两种省略的写法最终都会转换为完整的写法:
第三个目录tags
:存放标签信息,也是一个SHA1
值:
详细内容将在下一节介绍。
以下图所示,远程仓库有三个分支master
、develop
和test
:
经过前面的学习,咱们知道经过下述指令能够删除本地develop
分支:
git branch -d develop
复制代码
那么如何删除远程分支呢?
首先咱们来看看git push
的完整写法:
git push origin srcBranch:destBranch
复制代码
srcBranch
表示本地的分支,destBranch
表示对应的远程分支;
表示将本地的分支推送到远程分支,这两个分支能够不一样;
之因此能够直接使用git push
是由于咱们设置的本地分支和远程分支的名字是相同的,而且手动创建了联系,因此git
可以自动识别;
明白了这点后,就不难理解下列删除远程分支的两种作法了:
git push origin :destBranch
将空的分支推送到远程分支,这样就能将该远程分支删除;好比删除本地分支develop
的远程分支:
git push origin :develop
复制代码
能够看到成功删除了远程分支develop
以及它所对应的本地远程分支origin/develop
。
**注意:**并不须要切换到须要删除远程分支的本地分支
develop
上,再执行上述指令。也就是说,能够在任意本地分支上删除任意本地分支对应的远程分支。
git push origin --delete destBranch
还能够采用更加直观的--delete
参数,好比删除远程分支develop
:
git push origin --delete develop
复制代码
这两种方式是等价的,可根据需求选择。
git remote prune origin
该方法用于删除无效的远程分支对应的本地远程分支,具体场合以下:
如图所示mygit
与mygit2
共享一个有三个分支的远程仓库:
首先在mygit2
中删除远程仓库的develop
分支,能够看到mygit2
中远程分支develop
对应的本地远程分支origin/develop
被删除了:
而后在mygit
中查看远程分支详细信息:
能够看到提示信息中显示远程分支develop
对应的本地远程分支origin/develop
处于stale
(腐烂,游离)状态,即该分支对于mygit
来讲已经失效,可使用:
git remote prune origin
复制代码
(prune
:裁剪)删除mygit
上这个无效的本地远程分支:
再次查看分支信息,可发现mygit
中的本地远程分支origin/develop
已经被删除了:
**注意:**通常本地远程分支设置了保护措施,不能随意删除;
能够经过如下命令,将本地分支dev
重命名为develop
:
git branch -m dev develop
复制代码
没法直接重命名远程分支,只能经过先删除原来的远程分支,再建立重命名后develop
分支对应的远程分支,过程为:
//删除远程分支dev
git push origin :dev
//建立重命名后develop分支对应的远程分支
git push -u origin develop
复制代码
由此间接地完成了远程分支的重命名。
以上就是本节的所有内容,相信看到这里的你已经十分熟悉
git refspec
了。下一节将介绍git
标签与别名。