这一系列共有两部分:
这里仍然是继续学习learnGitBranching中的远程分支部分,为了保持博客的独立性,这里再简要介绍一下:learnGitBranching一个可视化+交互式学习Git分支的网站,并且是以关卡的形式呈现,一关一关打怪升级,最关键的是还有中文版,简直不要太友好!
网址:https://learngitbranching.js.org/
Github主页:https://github.com/pcottle/learnGitBranching
Git本身作为一种具有强分享性质的工具,远程操作也是其必不可少的一部分,作为代码共享编辑的利器,更需要了解远程分支的一些操作,从而实现代码的合理协作和共享。所以:
是时候分享你的代码了,让编码变得社交化吧!
前言
这里要先介绍一下远程仓库的相关知识,截取learnGitBranching的图来说明:
一. 基本操作
1. Git Clone
从第一篇博客看过来的读者都知道,我们到目前位置的介绍都聚焦于本地仓库的操作(如branch、merge和rebase等等)。现在要开始介绍远程仓库的操作,其中最基础也是最经常用到一个命令就是git clone
。
git clone
命令的作用是在本地创建一个远程仓库的拷贝(比如从github.com),并建立远程分支,详见下一节。
2. 远程分支
远程分支反映了远程仓库(在上次和它通信时)的状态。这将帮助理解本地工作与公共工作的差别,也是与别人分享工作成果前至关重要的一步。
远程分支有一个特别的属性,在checkout时自动进入分离 HEAD 状态(即直接指向本地其所在的提交记录)。Git 这么做是出于不能直接在这些分支上进行操作的原因,因此必须在别的地方完成工作,(更新了远程分支之后)再用远程分享你的工作成果。
远程分支有一个命名规范,它们的格式是:
<remote name>/<branch name>
其中<remote name>
是远程仓库名,一般默认是origin
,<branch name>
是分支名。
3. Git Fetch
Git远程仓库相关的操作实际可以归纳为两点:向远程仓库传输数据以及从远程仓库获取数据。既然可以能与远程仓库同步,那么就可以分享任何能被Git管理的更新。
git fetch
是一个能从远程仓库获取数据的命令,它将执行如下两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如origin/master)
因此git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态,但它并不会改变本地仓库的状态,也不会更新master
分支,也不会修改磁盘上的文件。
所以,并不是执行了git fetch
之后,本地仓库就与远程仓库同步了。这一步可以理解为单纯的下载操作。
4. Git Pull
前面的git fetch
是获取远程的数据,现在还需要将远程分支的变化合并到本地仓库中去,这一步有很多种方法,其实是可以像合并本地分支一样来合并远程分支的。比如如下命令:
git cherry-pick origin/master
git rebase origin/master
git merge origin/master
- …
但实际上,由于先抓取更新再合并到本地分支的这个流程很常用,因而Git特地提供了一个专门的命令来完成这两个操作,即git pull
。git pull
实际上就是git fetch
和git merge <just-fetched-branch>
的缩写。
5. Git Push
git push
命令负责将本地变更上传到指定的远程仓库,并在远程仓库上合并新提交记录。
一般git push
不带任何参数时的行为与Git中的一个名为push.default
的配置有关。所以在进行项目推送钱,可以检查一下这个配置。
6. 远程分支跟踪
在git clone
执行的过程中,基本上远程分支都会在本地对应绑定一个相应的分支,用于追踪远程分支,即在pull
和push
时默认追踪的源头,即pull
和push
不指定参数时,会默认将本地当前分支与其跟踪的远程分支进行交互。
当然也可以自己指定这种追踪关系。
6.1 跟踪方法一
比如用如下命令,就可以创建一个名为totallyNotMaster
的分支,它跟踪远程分支origin/master
,其实等于绑定。
git checkout -b totallyNotMaster origin/master
此时如果执行git pull
时,先执行git fetch origin/master
的操作,然后在totallyNotMaster
分支上执行git merge origin/master
。
6.2 跟踪方法二
还有另外一种方式,用如下命令:
git branch -u origin/master foo
即可让foo
分支跟踪origin/master
,如果当前就在foo
分支上,则可以省略foo
,即git branch -u origin/master
。
7. 带参数的git pull
,git push
和git fetch
在介绍完远程分支跟踪之后,就能知道前面介绍的不带参数的git push
和git pull
都是如何选择两边的分支进行操作的了。那下面将介绍带参数的这些命令,使得本地与远程的分支交互更为灵活。
7.1 带参数的git push
带参数的git push
为:
git push <remote> <place>
例如git push origin master
指定了将本地的master推送到远端的master上面去。此时会忽略当前的checkout状态。
那如果来源分支和去向分支的名称不同呢?比如想把本地的foo
分支推送到远程仓库中的bar
分支,这是可以用如下命令:
git push origin <source>:<destination>
用来指定源和目的。当远程目的分支不存在的话,Git会先在远程仓库中根据你指定的这个名字先自行创建,而后再执行push操作。
7.2 带参数的git fetch
git fetch <remote> <place>
这是第一种用法,如git fetch origin master
会默认将远端master分支上的内容下载,并更新远程分支origin/master
,注意是不更新本地分支的。
git fetch <remote> <target>:<source>
第二种用法是直接对本地分支进行操作,不过,不能在当前checkout的分支上面做这个操作,其他分支是可以的,当然很少有人这么做,此时相应的远程分支是不会更新的,并且也会忽略当前的checkout状态。如果本地分支没有,则会先创建。
与git push
的操作一样,只不过这里的源和目的反过来了,毕竟一个是下载,一个是上传。而且,如果指定的本地分支不存在,也会先在本地新建分支,在执行fetch操作。
这里要注意的是:与push的不同在于,当git fetch
没有参数时,它会下载所有的提交记录到各个远程分支,而不像git push
一样操作当前checkout的分支及其对应追踪远程分支。
7.3 带参数的git pull
前面也提到过,git pull
其实就是git fetch
和git merge
的缩写,那么带参数的git pull
就同样很好理解了,比如下面的两种方式是等价的:
git pull origin foo
git fetch origin foo; git merge o/foo
git pull origin bar~1:bugFix
git fetch origin bar~1:bugFix; git merge bugFix
git pull 唯一关注的是提交最终合并到哪里,也就是为git fetch提供的destination参数。
8. 什么?没有source?
在Git中有两种关于<source>
的用法是比较诡异的,可以在git push
和git fetch
的时候不指定任何的<source>
,如下:
git push origin :side
git fetch origin :bugFix
8.1 push空的source
在push的时候如果<source>
为空,则会***删除*** 远程仓库中的分支。操作需谨慎啊!
8.2 fetch空的source
在fetch的时候如果<source>
为空,则会在本地***创建***一个新分支。这个操作还是挺友好的。
二. 场景分析
在git push
的过程中,相信大多数人都有遇到过这种情况。
假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。
这时候直接git push
的话,Git是不会允许你进行变更的,所以它会强制你先合并远程最新的代码,然后才允许你push。
这时候可以有两种做法:
git fetch; git merge origin/master; git push
git fetch; git rebase origin/master; git push
但是要敲很多条指令,为了方便,之前提到过可以使用git pull
代替fetch
和merge
,或使用git pull --rebase
代替fetch
和rebase
。
因此由fetch、rebase/merge和push组成的工作流很普遍。
在开发社区里面有很多关于merge和rebase的讨论,以下是关于rebase的优缺点:
- 优点:Rebase能使提交树变得很干净,所有的提交都在一条线上。
- 缺点:Rebase修改了提交树的历史。
所以用merge还是rebase就看个人喜好了。