Git 沙盒模拟实战(远程篇)
Git 沙盒模拟实战(远程篇)
>---基础篇
远程仓库
远程仓库并不复杂, 在如今的云计算盛行的世界很容易把远程仓库想象成一个富有魔力的东西, 但实际上它们只是你的仓库在另个一台计算机上的拷贝。你可以通过因特网与这台计算机通信 —— 也就是增加或是获取提交记录
话虽如此, 远程仓库却有一系列强大的特性
- 首先也是最重要的的点, 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后,即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。
- 还有就是, 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)
现在用网站来对远程仓库进行可视化操作变得越发流行了(像 Github ), 但远程仓库永远是这些工具的顶梁柱, 因此理解其概念非常的重要!
使用clone
命令将远程仓库中的项目克隆到本地。
$ git clone [remoteName]
remoteName
: 远程仓库的地址(名称)
Git Fetch
Git
远程仓库相当的操作实际可以归纳为两点:向远程仓库传输数据以及从远程仓库获取数据。既然我们能与远程仓库同步,那么就可以分享任何能被 Git
管理的更新(因此可以分享代码、文件、想法、情书等等)。
本节课我们将学习如何从远程仓库获取数据 —— 命令如其名,它就是 git fetch
。
你会看到当我们从远程仓库获取数据时, 远程分支也会更新以反映最新的远程仓库。在上一了我们已经提及过这一点了。
虚线为远程仓库,实线为本地仓库。
$ git fetch
git fetch 做了些什么
git fetch
完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如
o/master
)
git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。
如果你还记得上一节课程中我们说过的,远程分支反映了远程仓库在你最后一次与它通信时的状态,git fetch
就是你与远程仓库通信的方式了!希望我说的够明白了,你已经了解 git fetch
与远程分支之间的关系了吧。
git fetch
通常通过互联网(使用 http://
或 git://
协议) 与远程仓库通信。
git fetch 不会做的事
git fetch
并不会改变你本地仓库的状态。它不会更新你的 master
分支,也不会修改你磁盘上的文件。
理解这一点很重要,因为许多开发人员误以为执行了 git fetch
以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。我们在后面的课程中将会讲解能完成该操作的命令。
所以, 你可以将 git fetch
的理解为单纯的下载操作。
Git Pull
既然我们已经知道了如何用 git fetch
获取远程的数据, 现在我们学习如何将这些变化更新到我们的工作当中。
其实有很多方法的 —— 当远程分支中有新的提交时,你可以像合并本地分支那样来合并远程分支。也就是说就是你可以执行以下命令:
git cherry-pick o/master
git rebase o/master
git merge o/master
- 等等
实际上,由于先抓取更新再合并到本地分支这个流程很常用,因此 Git
提供了一个专门的命令来完成这两个操作。它就是我们要讲的 git pull
。
$ git pull
git pull
就是 git fetch
和 git merge <just-fetched-branch>
的缩写!
模拟团队合作
这里有一件棘手的事 —— 为了接下来的课程, 我们需要先教你如何制造远程仓库的变更。
这意味着,我们需要“假装”你的同事、朋友、合作伙伴更新了远程仓库,有可能是某个特定的分支,或是几个提交记录。
为了做到这点,我们引入一个自造命令 git fakeTeamwork
!它的名称已经说明了一切,先看演示..
$ git clone
local branch "master" set to track remote branch "o/master"
$ git commit -m "c2"
$ git commit -m "c3"
$ git push
$ git checkout c1
$ git commit -m "c4"
警告!现在是分离 HEAD 状态
$ git checkout master
$ git merge c4
Git Push
OK,我们已经学过了如何从远程仓库获取更新并合并到本地的分支当中。这非常棒……但是我如何与大家分享我的成果呢?
嗯,上传自己分享内容与下载他人的分享刚好相反,那与 git pull
相反的命令是什么呢?git push
!
git push
负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。一旦 git push
完成, 你的朋友们就可以从这个远程仓库下载你分享的成果了!
你可以将 git push
想象成发布你成果的命令。它有许多应用技巧,稍后我们会了解到,但是咱们还是先从基础的开始吧……
注意 —— git push
不带任何参数时的行为与 Git 的一个名为 push.default
的配置有关。它的默认值取决于你正使用的 Git 的版本,但是在教程中我们使用的是 upstream
。 这没什么太大的影响,但是在你的项目中进行推送之前,最好检查一下这个配置。
$ git push
过去了, 远程仓库接收了 C2
,远程仓库中的 master
分支也被更新到指向 C2
了,我们的远程分支 (``o/master`) 也同样被更新了。所有的分支都同步了!
冲突处理
git push
失败是因为你最新提交的 C3
基于远程分支中的 C1
。而远程仓库中该分支已经更新到 C2
了,所以 Git
拒绝了你的推送请求。
$ git fakeTeamwork
$ git fetch
$ git commit -m "c3"
$ git rebase c2
$ git push
用 git fetch
更新了本地仓库中的远程分支,然后用 rebase
将我们的工作移动到最新的提交记录下,最后再用 git push
推送到远程仓库。
origin 和它的周边
合并特性分支
既然你应该很熟悉 fetch
、``pull、
push` 了,现在我们要通过一个新的工作流来测试你的这些技能。
在大型项目中开发人员通常会在(从 master
上分出来的)特性分支上工作,工作完成后只做一次集成。这跟前面课程的描述很相像(把 side
分支推送到远程仓库),不过本节我们会深入一些.
但是有些开发人员只在 master
上做 push
、pull
—— 这样的话 master
总是最新的,始终与远程分支 (o/master
) 保持一致。
对于接下来这个工作流,我们集成了两个步骤:
- 将特性分支集成到
master
上 - 推送并更新远程分支
$ git pull -rebase
$ git push
执行了两个命令:
- 将我们的工作
rebase
到远程分支的最新提交记录 - 向远程仓库推送我们的工作
fetch
远程仓库的更新到本地仓库,进行rebase
合并,最后push
到远程仓库中。
$ git fetch
$ git rebase o/master side1
$ git rebase side1 side2
$ git rebase side2 side3
$ git rebase side3 master
$ git push
为什么不用 merge 呢?
为了 push
新变更到远程仓库,你要做的就是包含远程仓库中最新变更。意思就是只要你的本地分支包含了远程分支(如 o/master
)中的最新变更就可以了,至于具体是用 rebase
还是 merge
,并没有限制。
那么既然没有规定限制,为何前面几节都在着重于 rebase
呢?为什么在操作远程分支时不喜欢用 merge
呢?
在开发社区里,有许多关于 merge
与 rebase
的讨论。以下是关于 rebase
的优缺点:
优点:
Rebase
使你的提交树变得很干净, 所有的提交都在一条线上
缺点:
Rebase
修改了提交树的历史
比如, 提交 C1
可以被 rebase
到 C3
之后。这看起来 C1
中的工作是在 C3
之后进行的,但实际上是在 C3
之前。
一些开发人员喜欢保留提交历史,因此更偏爱 merge
。而其他人(比如我自己)可能更喜欢干净的提交树,于是偏爱 rebase
。仁者见仁,智者见智。
$ git fetch
$ git checkout side1
$ git merge o/master
$ git merge side2
$ git merge side3
$ git checkout master
$ git merge side1
$ git push
远程跟踪分支
在前几节课程中有件事儿挺神奇的,Git
好像知道 master
与 o/master
是相关的。当然这些分支的名字是相似的,可能会让你觉得是依此将远程分支 master
和本地的 master
分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示:
pull
操作时, 提交记录会被先下载到o/master
上,之后再合并到本地的master
分支。隐含的合并目标由这个关联确定的。push
操作时, 我们把工作从master
推到远程仓库中的master
分支(同时会更新远程分支o/master
) 。这个推送的目的地也是由这种关联确定的!
远程跟踪
直接了当地讲,master
和 o/master
的关联关系就是由分支的“remote tracking
”属性决定的。master
被设定为跟踪 o/master
—— 这意味着为 master
分支指定了推送的目的地以及拉取后合并的目标。
你可能想知道 master
分支上这个属性是怎么被设定的,你并没有用任何命令指定过这个属性呀!好吧, 当你克隆仓库的时候, Git
就自动帮你把这个属性设置好了。
当你克隆时, Git
会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/master
)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 master
。
克隆完成后,你会得到一个本地分支(如果没有这个本地分支的话,你的目录就是“空白”的),但是可以查看远程仓库中所有的分支(如果你好奇心很强的话)。这样做对于本地仓库和远程仓库来说,都是最佳选择。
这也解释了为什么会在克隆的时候会看到下面的输出:
local branch "master" set to track remote branch "o/master"
我能自己指定这个属性吗?
当然可以啦!你可以让任意分支跟踪 o/master
, 然后该分支会像 master
分支一样得到隐含的 push
目的地以及 merge 的目标。 这意味着你可以在分支 totallyNotMaster
上执行 git push
,将工作推送到远程仓库的 master
分支上。
有两种方法设置这个属性,第一种就是通过远程分支检出一个新的分支,执行:
git checkout -b totallyNotMaster o/master
就可以创建一个名为 totallyNotMaster
的分支,它跟踪远程分支 o/master
。
在不检出 master
分支的情况下将工作推送到的远程仓库中的 master
分支上。
$ git branch side
$ git checkout side
$ git commit -m "c3"
$ git fetch
$ git rebase c2 side
$ git push
Git Push 的参数
很好! 既然你知道了远程跟踪分支,我们可以开始揭开 git push
、fetch
和 pull
的神秘面纱了。我们会逐个介绍这几个命令,它们在理念上是非常相似的。
首先来看 git push
。在远程跟踪课程中,你已经学到了 Git
是通过当前检出分支的属性来确定远程仓库以及要 push
的目的地的。这是未指定参数时的行为,我们可以为 push
指定参数,语法是:
git push <remote> <place>
<place>
参数是什么意思呢?我们稍后会深入其中的细节, 先看看例子, 这个命令是:
git push origin master
把这个命令翻译过来就是:
切到本地仓库中的“master”分支,获取所有的提交,再到远程仓库“origin”中找到“master”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。
我们通过place
参数来告诉 Git
提交记录来自于 master
, 要推送到远程仓库中的 master
。它实际就是要同步的两个仓库的位置。
需要注意的是,因为我们通过指定参数告诉了 Git
所有它需要的信息, 所以它就忽略了我们所检出的分支的属性!
$ git push origin master
$ git push origin foo
place 参数详解
还记得之前课程说的吧,当为 git push
指定 place
参数为 master
时,我们同时指定了提交记录的来源和去向。
你可能想问 —— 如果来源和去向分支的名称不同呢?比如你想把本地的 foo
分支推送到远程仓库中的 bar
分支。
哎,很遗憾 Git
做不到…… 开个玩笑,别当真!当然是可以的啦 😃 Git
拥有超强的灵活性(有点过于灵活了)
接下来咱们看看是怎么做的……
要同时为源和目的地指定 <place>
的话,只需要用冒号 :
将二者连起来就可以了:
git push origin <source>:<destination>
这个参数实际的值是个 refspec
,refspec
是一个自造的词,意思是 Git
能识别的位置(比如分支 foo
或者 HEAD~1
)
一旦你指定了独立的来源和目的地,就可以组织出言简意赅的远程操作命令了,让我们看看演示!
$ git push origin foo^:master
如果你要推送到的目的分支不存在会怎么样呢?没问题!Git
会在远程仓库中根据你提供的名称帮你创建这个分支!
$ git push origin master^:foo
$ git push origin foo:master
Git fetch 的参数
我们刚学习了 git push
的参数,很酷的 <place>
参数,还有用冒号分隔的 refspecs
(<source>:<destination>
)。 这些参数可以用于 git fetch
吗?
你猜中了!git fetch
的参数和 git push
极其相似。他们的概念是相同的,只是方向相反罢了(因为现在你是下载,而非上传)
让我们逐个讨论下这些概念……
place 参数
如果你像如下命令这样为 git fetch
设置 place
的话:
$ git fetch origin foo
Git
会到远程仓库的 foo
分支上,然后获取所有本地不存在的提交,放到本地的 o/foo
上。
来看个例子(还是前面的例子,只是命令不同了)
我们只下载了远程仓库中 foo
分支中的最新提交记录,并更新了 o/foo
。
你可能会好奇 —— 为何 Git 会将新提交放到 o/foo
而不是放到我本地的 foo
分支呢?之前不是说这样的 place
参数就是同时应用于本地和远程的位置吗?
好吧, 本例中 Git
做了一些特殊处理,因为你可能在 foo
分支上的工作还未完成,你也不想弄乱它。还记得在 git fetch
课程里我们讲到的吗 —— 它不会更新你的本地的非远程分支, 只是下载提交记录(这样, 你就可以对远程分支进行检查或者合并了)。
“如果我们指定 <source>:<destination>
会发生什么呢?”
如果你觉得直接更新本地分支很爽,那你就用冒号分隔的 refspec
吧。不过,你不能在当前检出的分支上干这个事,但是其它分支是可以的。
这里有一点是需要注意的 —— source
现在指的是远程仓库中的位置,而 <destination>
才是要放置提交的本地仓库的位置。它与 git push 刚好相反,这是可以讲的通的,因为我们在往相反的方向传送数据。
理论上虽然行的通,但开发人员很少这么做。我在这里介绍它主要是为了从概念上说明 fetch
和 push
的相似性,只是方向相反罢了。
使用 fetch
时, 你必须指定source
和 destination
。 注意一下目标窗口, 因为提交对象的 ID
可能会变哦!
$ git fetch origin master^1:foo
$ git fetch origin foo:master
$ git checkout foo
$ git merge master
会让人挨揍的 source
Git
有两种关于 source
的用法是比较诡异的,即你可以在 git push
或 git fetch
时不指定任何 source
,方法就是仅保留冒号和 destination
部分,source
部分留空。
-
git push origin :side
如果
push
空source
到远程仓库会如何呢?它会删除远程仓库中的分支!慎重使用,删除别人的远程分支可能会挨揍。 -
git fetch origin :bugFix
如果
fetch
空source
到本地,会在本地创建一个新分支。
Git pull 参数
既然你已经掌握关于 git fetch
和 git push
参数的方方面面了,关于 git pull
几乎没有什么可以讲的了 😃
因为 git pull
到头来就是 fetch
后跟 merge
的缩写。你可以理解为用同样的参数执行 git fetch
,然后再 merge
你所抓取到的提交记录。
还可以和其它更复杂的参数一起使用, 来看一些例子:
以下命令在 Git
中是等效的:
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
实际上就是 fetch
+ merge
的缩写, git pull
唯一关注的是提交最终合并到哪里(也就是为 git fetch
所提供的 destination
参数)。
通过指定 master
我们更新了 o/master
。然后将 o/master
merge
到我们的检出位置,无论我们当前检出的位置(checkout
的位置、HEAD
所在位置)是哪。
需要下载一些提交,然后创建一些新分支,再合并这些分支到其它分支, 但这用不了几个命令。
$ git pull origin bar:foo
$ git pull origin master:side