14

Git应用详解第七讲:Git refspec与远程分支的重要操作

 4 years ago
source link: http://www.cnblogs.com/AhuntSun-blog/p/12721437.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

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

这一节来介绍本地仓库与远程仓库的分支映射关系: git refspec 。彻底弄清楚本地仓库到底是如何与远程仓库进行联系的。

一、 Git refspec

refspecReference Specification 的缩写,字面意思就是 具体的引用 。它其实是 一种格式git 通过这种格式来表示 本地分支远程分支 的映射关系;

在本地仓库创建 master 分支外的其他两个分支 developtest :

q6JBN3r.png!web

develop 分支上执行 git push 命令,出现如下错误:

AfaAJra.png!web

这是由于本地分支 develop 没有与任何的远程分支建立联系导致的。通过 git branch -vv 查看本地与远程分支的关联情况,可见并没有建立任何联系:

jaiiaij.png!web

二、本地远程分支

在讲解如何建立与本地分支关联的远程分支之前,首先我们来介绍期待已久的本地远程分支:

  • git 中其实有三种分支:本地分支、本地远程分支、远程分支;
  • 可以这样理解:本地远程分支是远程分支的一个镜像,并且在本地仓库与远程仓库之间起到一个桥梁的作用;
  • 在没有办法直接查看远程仓库的时候,可以通过本地远程分支观察远程分支的变化情况。比如本地远程分支 origin/develop 就对应着远程分支 develop

1.三分支关系

当本地 master 分支建立了与之关联的远程分支 master 后,查看当前分支状态:

7ZFzEve.png!web

图中的 origin/master 为本地远程分支,代表的是远程仓库的 master 分支,而这个分支是在本地的;也就是说加上远程仓库的 master 分支,一共有三个 master 分支:

mIFnYvm.png!web

并且,当本地仓库中的每一个分支都有与之关联的远程分支之后,本地仓库都会创建对应的 本地远程分支 ,它们所处的位置和关系如下图所示:

yyE7Z3Z.png!web

可以这样理解:本地远程分支 origin/master 为远程分支 master 的本地化形式;

假设远程仓库和本地仓库文件内容是一样的,都只有两次提交,此时三个分支的状态如下图所示:

uY7jUzf.png!web

然后,在本地的 master 分支中新增了提交 3rd ,本地仓库的分支情况变为:

MFbq2ez.png!web

上图中的 git dog 为指令: git log --all --decorate --oneline --graph 的别名,有关内容将在下一节讲解。

分支的示意图如下:

ma6Rnim.png!web

可见本地 master 分支比本地远程分支 origin/master 多了一次提交。这是因为本地远程分支是为了追踪远程分支而存在的,只有在执行 pullpush 操作时它的指向才会更新。比如在执行推送( push )指令时:

  • 首先,本地 master 分支对应的本地远程分支( origin/master )会指向本地 master 分支最新的提交(向前走了几步);

  • 然后,本地 master 分支再将文件推送到远程 master 分支中。完成推送后,三分支的状态为:

    MbMjua3.png!web

回到终端,我们将刚才新增的提交 3rd 推送到远程分支,成功后查看本地分支以及本地远程分支的提交历史:

3mMVneI.png!web

可见,本地远程分支的指向得到了更新,指向了最新的提交 3rd ,由此验证了上述说法。

查看分支关联

可以通过以下指令查看本地分支与本地远程分支的关联情况:

git branch -vv

jaeAZrz.png!web

可以看到:本地的 master 分支有本地远程分支 origin/master 关联,说明本地 master 分支已经和远程 master 分支建立了关联;

其余两个本地分支 popdevelop 并没有与之关联的本地远程分支,所以它们并没有与远程分支建立联系。

简单点说:只要 本地分支 有与之对应的 本地远程分支 ,就有与之对应的 远程分支

总结: origin/master 作用: 追踪远程分支 。当执行 git push/pull 操作时,该分支的指向都会相应地发生变化,用于与远程仓库保持同步;比如:本地仓库在执行 git push 操作的时候,不仅会把本地的修改推送到远程;还会同时修改 origin/master 分支的指向;

2.实战演示

可通过该指令查看本地的所有分支及其 最新的提交信息

git branch -av

2E7Rj2e.png!web

首先,在 master 分支上进行三次提交,并将它推送到与之关联的远程 master 分支,此时各分支的提交历史为:

ymQ7b2E.png!web

三个分支的状态为:

Bne63eQ.png!web

在此基础上,在 master 分支上进行一次提交 4th ,然后查看状态 git status

A3Ib2uu.png!web

图中提示信息表明,当前分支( master )已经领先于 origin/master 分支一次提交。为了看得更清楚,我们查看本地各分支的提交历史:

UBbIvyF.png!web

从图中可看出, origin/master 分支确实落后了一次提交,表示远程 master 分支落后了一次提交。此时可以使用 git push 将新增的提交推送到远程 master 分支,在这个过程中会将本地远程分支 origin/master 指向最新的提交 4th 。成功推送之后,再次查看本地各分支的提交历史:

6FnmU3r.png!web

可见,通过 git push 操作本地远程分支确实发生了更新,指向了最新提交 4th 。这就验证了执行 git push 时进行了两步操作:

  • 将本地 master 分支的新提交推送到与之关联的远程 master 分支;
  • 将本地远程分支 origin/master 指向本地 master 分支的最新提交;

git pull 操作同理,也会更新本地远程分支的指向;

也就是说: 每次执行 pushpull 操作后,本地分支、本地远程分支、远程分支三个分支的指向都会达到同步。

当切换到 origin/master 分支上时,如下图所示:

Zj6VBna.png!web

git 并不会直接将分支切换到 origin/master 上,而是切换到最新的一次提交上,即一个游离的提交。这从侧面说明了: git禁止 我们直接修改 origin/master 分支的,只允许我们切换到最新的提交上;

也就是说本地远程分支(如 origin/master )是只读的,只能由 git 来改变,这就解释了为何使用 git branch 无法查看本地远程分支。

fuuEFrR.png!web

三、设置远程分支

弄清楚了什么是本地远程分支,就能更好地理解接下来所要介绍的,如何建立本地分支与远程分支的联系了。

1.设置同名远程分支

上图提示信息中的: 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>

方法一:采用下述指令为本地仓库 mygitdevelop 分支创建远程分支:

git push --set-upstream origin develop

该命令的作用为:在远程仓库创键一个与本地分支 develop 关联 的同名分支 develop ,并将本地分支 develop 的文件推送到该远程分支上。

也就是将本地分支 develop 的上游分支设置为远程仓库的 develop 分支,并进行文件同步。

执行完上述命令后会有这样的提示:

y6jqyuy.png!web

表示本地的 develop 分支已与远程的 develop 分支 建立联系 ;此时查看本地分支,会发现多了一个本地远程分支 origin/develop ,并且已与本地 develop 分支建立了联系:

rmqY3iN.png!web

随后再次执行 git push 就不会出现问题了:

bu22Yz6.png!web

此时在 github 上查看对应的远程仓库,就能查看到新增的远程分支 develop 了:

ZFfMJ3J.png!web

上图中的 master 分支是远程仓库创建时默认创建的,并没有与本地 master 分支建立联系。

随后点开 branch 可以看到:

Bz2qMbq.png!web

当前一共有两个分支, master 分支是 default (默认)分支,是不能够被删除的;活跃的分支为 deavelop

git push -u origin <branch>

方法二:先切换到 test 分支,再执行以下命令,为本地仓库 mygittest 分支创建对应的远程分支:

git push -u origin test

NjQBJvE.png!web

-u--set-upstream 作用是类似的,都是在远程仓库新建一个新的分支,并与本地分支建立联系。

执行完上述指令后,再次查看本地分支的详细情况,以及分支对应关系,可以发现 test 分支已与远程 test 分支建立联系:

ZZj2YzU.png!web

git push origin HEAD

方法三:

如下图所示,通过该指令成功设置了本地 develop 分支对应的远程 develop 分支。但没有显示追踪信息,之后不能使用 git push 推送。

rQZNrye.png!web

git push origin <branch>

方法四:

如下图所示,该方法实质上与方法三相同,因为 HEAD 指向的就是当前分支。同样没有显示追踪信息,之后也不能使用 git push 推送。

z6FRvi3.png!web

总结:当本地分支与远程分支同名时,一旦手动建立了它们之间的联系。之后推送本地分支的文件到对应的远程分支时可以采用简写形式: git push

这是因为在已经建立三个分支的对应关系并后,再执行 git pushgit 会自动地将同名的本地分支与远程分支进行匹配;

而其他情况则要采用完整写法进行推送。关于这些结论,将在第三大点 -u 参数的作用中详细介绍。

2.设置不同名远程分支

主要有以下四种方法,注意:使用每种方法前都需要先切换到对应分支上。

git push --set-upstream origin <branch1>:<branch2>

方法一:比如当前位于 develop 分支,如果采用的是以下简写命令:

git push --set-upstream origin develop

则会创建一个同名的远程分支 develop 。而如果采用该命令的完整写法,就可以自定义远程分支的名字了,比如设为 develop2

git push --set-upstream origin develop:develop2

euiIBzv.png!web

执行上述指令后,成功创建了对应的,不同名的本地远程分支 origin/develop2 。表示本地 develop 分支已与远程 develop2 分支建立联系(因为远程分支与本地远程分支是一一对应的关系):

6Nbu6rq.png!web

github 上查看本地仓库关联的远程仓库 MY ,可以看到顺利创建了 develop2 分支:

Fbu2aeE.png!web

可以发现这么一个规律:在创建远程分支的同时会创建同名的本地远程分支。

git push -u origin <branch1>:<branch2>

方法二:

如下图所示,使用 -u 参数也能将本地 develop 分支的远程分支自定义为 develop2

7RvYzma.png!web

git push origin HEAD:<branch>

方法三:

通过该方法也能成功设置与本地分支关联的,不同名的远程分支 develop2

j26NbiQ.png!web

git push origin <branch1>:<branch2>

方法四:该方法与方法二实质上是一样的,因为方法二中的 HEAD 指针指向的就是当前所在的分支,也就是 develop 分支。过程与方法二类似:

J3UnUbm.png!web

上面这四种设置不同名远程分支的方法,都有一个共同特点:不能使用git push进行推送。

若使用 git push 都会出现找不到对应远程分支的错误:

zAnemym.png!web

原因在下面第三点的 -u 参数作用中会详细讲解。

既然是 -u 参数追踪问题,那我加上 -u 参数不就行了么?其实这样也行不通:

2AreemA.png!web

解决方案:每次推送的时候,指明本地分支与远程分支的对应关系,即采用上述命令的完整写法,比如:

git push --set-upstream origin develop:develop2
git push -u origin develop:develop2
git push origin develop:develop2
git push origin HEAD:develop2

采用了完整写法后,成功地进行了推送,如下图所示:

UzAVzuZ.png!web

注意:虽然可以自定义远程分支与本地远程分支的名字,但是十分不推荐,因为容易出错。所以,建议 本地远程分支和远程分支 都使用默认的,与本地分支相同的名字。

3.总结

以本地分支 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 mastergit push -u origin master 的区别

第一次将本地仓库的 master 分支推送到远程仓库的 master 分支上时,使用前者和后者都可以顺利推送,区别在于是否使用了 -u 参数:

  • 推送时不使用 -u 参数:

    E7zyI3a.png!web

  • 推送时使用 -u 参数:

    6re2AjN.png!web

注意到推送时使用 -u 参数会打印下列提示信息:

Branch 'master' set up to track remote branch 'master' from 'origin'.

表示本地的 master 分支被设置去追踪远程的 master 分支,在第 2~n 次推送中,只需要使用 git push 这样的简写命令(当然,完整写法效果等同)。 git 就会自动将本地的 master 分支与远程的 master 分支进行匹配,完成推送:

ziiMf27.png!web

而不使用 -u 参数时,没有上述的分支追踪信息。此时使用简写 git push 进行推送会出现错误:

aMV3eqj.png!web

错误信息显示:当前分支没有与之对应的远程分支。这个时候想要成功推送,必须采用指明对应关系的完整写法,比如:

git push origin master

UniYreb.png!web

这就是推送时使不使用 -u 参数的区别。并且,根据上面的介绍,使用如下指令进行推送也能达到 -u 参数的效果:

git push --set-upstream origin develop

rEjE3uQ.png!web

之后也可以使用简写的 git pull 指令进行推送:

yiaMNjy.png!web

细心的你一定发现了,以上都只是本地分支与远程分支同名的情况。不同名的情况下,上面的两个方法还好使吗?

首先验证方法一: -u 参数:

uumu6jb.png!web

设置不同名的远程分支时要注意写成完整形式: pop:pop2

可以看到,即使创建不同名的远程分支, -u 参数也一样能够设置追踪关系;但是,奇怪的是 git push 却不好使了:

FFvmy2v.png!web

还是和没使用 -u 参数时一样,找不到对应的远程分支,需要采用指明对应关系的完整写法,比如:

git push origin pop:pop2

fU3qQzr.png!web

其次验证方法二: --set-upstream

jqUZfiI.png!web

同样设置分支对应关系时要使用完整写法。可以看到,该方法也设置了追踪关系。奇怪的是 git push 同样不管用:

2aqqqye.png!web

同样找不到对应的远程分支,需要采用指明对应关系的完整写法,比如:

git push origin bob:bob2

IFvArqB.png!web

所以可以得出结论:

  • 本地/远程分支同名时:
    • -u 参数的作用是设置本地分支与远程分支的追踪关系,设置了追踪关系后,之后的推送可使用简写 git pushgit 内部会自动进行匹配;
    • --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 分支上其他人推送的文件(一星期的成果没了)。

1.应用场合

  • 当远程仓库的历史提交记录太乱了,想要重新整理时。 注意: 一定要与其他人协商好再用本地分支强制覆盖远程分支。
  • 只有一个人开发时,代码以本地为准。为了避免推送时繁琐的合并,可以使用 -f 强制推送,直接覆盖远程分支上的内容;

分两种写法:

  • 第一种:已经通过 -u 参数等方式,设置了本地分支与远程分支的追踪关系时,采用:

    git push -f

    uIB3Mbb.png!web

  • 第二种:还未设置追踪关系,采用:

    git push -u origin master -f

    ziM3eiJ.png!web

2.预防措施

Github 提供了相应的分支保护机制,可以在 Settings 选项中进行设置:

iAVJbqV.png!web

可以看到 Github 默认是保护分支的:

ieu63i6.png!web

3.补救措施

让有进度的人,再次对被强制覆盖的远程分支执行一次 git push -f 指令,把正确的内容强制推送上去,覆盖前一次 git push -f 所造成的灾难。

六、设置远程分支对应的本地分支

假如远程仓库 M3Y 中有 masterdevelop 两个分支,此时新建一个空的本地仓库 mygit ,通过以下指令将它的远程仓库地址 origin 设置为 M3Y 的地址:

git remote add origin [email protected]:AhuntSun/M3Y.git

此时两仓库的状态为:

Af6bUnz.png!web

由于 mygit 是空仓库与远程仓库 M3Y 没有任何公共提交历史,所以在执行 git pull 时会出现下图所示的不同源冲突(上一节中详细介绍过该冲突):

jqIR3ya.png!web

虽然 git pull 操作失败了,但是也成功地将远程仓库 M3Y 的分支拉取了下来。但是,通过 git branch -vv 查看分支追踪关系,发现并没有本地分支与这两个远程分支建立了联系:

6ZjeYbB.png!web

如何建立这两个本地远程分支对应的本地分支?可以通过以下两种方法:

1. git checkout -b <branch> origin/<branch>

比如可以通过以下命令,设置本地远程分支 origin/master 与本地 master 分支的追踪关系:

git checkout -b master origin/master

e6fuMnY.png!web

以上为本地 master 分支已存在的情况,如果本地分支 develop 未创建,可以采用下述命令创建并切换到 develop 分支,并且设置 origin/developdevelop 的追踪关系:

git checkout -b develop origin/develop

qAfENzv.png!web

设置了本地分支与远程分支的追踪关系,接下来就可以在本地仓库执行 git push 进行推送了:

jie6Jrf.png!web

2. git checkout --track origin/<branch>

重置条件,新创建一个空的本地仓库 mygit2 ,同样将其远程地址 origin 设置为远程仓库 M3Y 的地址。随后在本地仓库 mygit2 中执行 git pull 操作,将远程仓库 M3Y 中的两个分支拉取到本地:

vQbQjau.png!web

与上次一样,拉取到本地的两个本地远程分支没有与任何本地分支建立追踪关系。这次可以采用另外一种方法:

git checkout --track origin/test

创建并切换到 develop 分支,并且设置该分支与 origin/develop 分支的追踪关系:

rqArI3V.png!web

可以说该方法是方法一的特殊情况,因为该方法没有指明创建的本地分支的名字,所以默认采用与远程分支一样的名字 develop 来命名;

如果想在本地建立一个 develop2 (不同名)的分支与本地远程分支 origin/test 建立追踪关系,则应采用第一种方法。

七、远程分支信息

可以进入 .git 目录,查看储存远程分支信息的文件:

f6fM7rN.png!web

1.查看 config 文件

使用 vim 编辑器打开该文件,可以查看到关于远程分支的信息:

jErEFrq.png!web

可以看到 remote 这一栏中有两个信息,第一个是远程仓库的 url ,第二个是 fetch 信息,这两个信息尤为重要:

  • refs/heads/* 表示远程仓库的 refs/heads 目录下的所有引用都会写入到本地的 refs/remotes/origin 目录中;
  • 其中的 + 号是可选的,加了表示无论是否能够自动合并,即是否为 Fast Forward 方式,都将远程仓库所有文件拉取到本地。
  • 而不加 + 则表示如果不是 Fast Forward 方式就不拉取。一般情况下都是加上 + 号的,先把文件拉取到本地,不是 Fast Forward 方式就手动合并;

2.查看 refs 文件

refs 文件夹存储着 refspec 的文件,里面维护着三个目录:

VryaqiE.png!web

  • 第一个目录 heads :存储的是本地仓库的分支信息:

    vUjyayz.png!web

    可以查看其中一个分支:

    7NVjyim.png!web

    是一个 SHA1 值,表示分支就是一个指针,指向当前提交。

  • 第二个目录 remotes :里面存放着远程分支信息,远程仓库中也存在这样的目录与文件;

    UJfi63A.png!web

    从上图可以看到,远程分支只有 master ,没有 develop (因为之前被删除了)。并且它们本质上也是一个代表提交的 SHA1 值:

    nA7NBja.png!web

    建立 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

    上述两种省略的写法最终都会转换为完整的写法:

    AZrQjuv.png!web

  • 第三个目录 tags :存放标签信息,也是一个 SHA1 值:

    BFnYVbi.png!web

    详细内容将在下一节介绍。

八、删除远程分支

如下图所示,远程仓库有三个分支 masterdeveloptest

uI7ziyQ.png!web

通过前面的学习,我们知道通过下述指令可以删除本地 develop 分支:

git branch -d develop

eY7f6fR.png!web

那么如何删除远程分支呢?

首先我们来看看 git push 的完整写法:

git push origin srcBranch:destBranch
  • srcBranch 表示本地的分支, destBranch 表示对应的远程分支;

  • 表示将本地的分支推送到远程分支,这两个分支可以不同;

  • 之所以可以直接使用 git push 是因为我们设置的本地分支和远程分支的名字是 相同的 ,并且手动建立了联系,所以 git 能够自动识别;

明白了这点后,就不难理解下列删除远程分支的两种做法了:

1. git push origin :destBranch

将空的分支推送到远程分支,这样就能将该远程分支删除;比如删除本地分支 develop 的远程分支:

git push origin :develop

2yYRb2V.png!web

可以看到成功删除了远程分支 develop 以及它所对应的本地远程分支 origin/develop

注意:并不需要切换到需要删除远程分支的本地分支 develop 上,再执行上述指令。也就是说,可以在任意本地分支上删除任意本地分支对应的远程分支。

2. git push origin --delete destBranch

还可以采用更加直观的 --delete 参数,比如删除远程分支 develop

git push origin --delete develop

fuIVRze.png!web

这两种方式是等价的,可根据需求选择。

3. git remote prune origin

该方法用于删除无效的 远程分支 对应的 本地远程分支 ,具体场合如下:

如图所示 mygitmygit2 共享一个有三个分支的远程仓库:

RJrUZbq.png!web

首先在 mygit2 中删除远程仓库的 develop 分支,可以看到 mygit2 中远程分支 develop 对应的本地远程分支 origin/develop 被删除了:

2IF36vE.png!web

然后在 mygit 中查看远程分支详细信息:

qi67Vn3.png!web

可以看到提示信息中显示远程分支 develop 对应的本地远程分支 origin/develop 处于 stale (腐烂,游离)状态,即该分支对于 mygit 来说已经失效,可以使用:

git remote prune origin

prune :裁剪)删除 mygit 上这个无效的本地远程分支:

eeE7buR.png!web

再次查看分支信息,可发现 mygit 中的本地远程分支 origin/develop 已经被删除了:

zI7JNfZ.png!web

注意:一般本地远程分支设置了保护措施,不能随意删除;

九、重命名分支

1.本地分支

可以通过以下命令,将本地分支 dev 重命名为 develop

git branch -m dev develop

6RvQvuy.png!web

2.远程分支

无法直接重命名远程分支,只能通过先删除原来的远程分支,再创建重命名后 develop 分支对应的远程分支,过程为:

//删除远程分支dev
git push origin :dev
//创建重命名后develop分支对应的远程分支
git push -u origin develop

由此间接地完成了远程分支的重命名。

以上就是本节的全部内容,相信看到这里的你已经十分熟悉 git refspec 了。下一节将介绍 git 标签与别名。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK