第19章 远程版本库

Git作为分布式版本库控制系统,每个人都是本地版本库的主人,可以在本地的版本库中随心所欲地创建分支和里程碑。当需要多人协作时,问题就出现了:

如何避免因为用户把所有的本地分支都推送到共享版本库,从而造成共享版本库上分支的混乱?

如何避免不同用户针对不同特性开发创建了相同名字的分支而造成分支名称的冲突?

如何避免用户随意在共享版本库中创建里程碑而导致里程碑名称上的混乱和冲突?

当用户向共享版本库及其他版本库推送时,每次都需要输入长长的版本库URL,太不方便了。

当用户需要经常从多个不同的他人版本库中获取提交时,有没有办法不要总是输入长长的版本库URL?

如果不带任何其他参数执行git fetch、git pull和git push到底是和哪个远程版本库及哪个分支进行交互?

本章介绍的git remote命令就是用于实现对远程版本库的便捷访问,建立远程分支和本地分支的对应,使得git fetch、git pull和git push能够更为便捷地进行操作。

19.1 远程分支

上一章介绍Git分支的时候,每一个版本库最多只和一个上游版本库(远程共享版本库)进行交互,实际上Git允许一个版本库和任意多的版本库进行交互。首先执行下面的命令,基于hello-world.git版本库再创建几个新的版本库。


$cd/path/to/repos/

$git clone—bare hello-world.git hello-user1.git

Cloning into bare repository hello-user1.git…

done.

$git clone—bare hello-world.git hello-user2.git

Cloning into bare repository hello-user2.git…

done.


现在有了三个共享版本库:hello-world.git、hello-user1.git和hello-user2.git。现在有一个疑问,如果一个本地版本库需要和上面三个版本库进行互操作,三个共享版本库都存在一个master分支,会不会互相干扰、冲突或覆盖呢?

先来看看hello-world远程共享版本库中包含的分支有哪些:


$git ls-remote—heads file:///path/to/repos/hello-world.git

8cffe5f135821e716117ee59bdd53139473bd1d8 refs/heads/hello-1.x

bb4fef88fee435bfac04b8389cf193d9c04105a6 refs/heads/helper/master

cf71ae3515e36a59c7f98b9db825fd0f2a318350 refs/heads/helper/v1.x

c4acab26ff1c1125f5e585ffa8284d27f8ceea55 refs/heads/master


原来远程共享版本库中有四个分支,其中hello-1.x分支是开发者user1创建的。现在重新克隆该版本库,如下:


$cd/path/to/my/workspace/

$git clone file:///path/to/repos/hello-world.git

$cd/path/to/my/workspace/hello-world


执行git branch命令检查分支,会吃惊地看到只有一个分支master。


$git branch

*master


那么远程版本库中的其他分支哪里去了?为什么本地只有一个分支呢?执行git show-ref命令可以看到全部的本地引用。


$git show-ref

c4acab26ff1c1125f5e585ffa8284d27f8ceea55 refs/heads/master

c4acab26ff1c1125f5e585ffa8284d27f8ceea55 refs/remotes/origin/HEAD

8cffe5f135821e716117ee59bdd53139473bd1d8 refs/remotes/origin/hello-1.x

bb4fef88fee435bfac04b8389cf193d9c04105a6 refs/remotes/origin/helper/master

cf71ae3515e36a59c7f98b9db825fd0f2a318350 refs/remotes/origin/helper/v1.x

c4acab26ff1c1125f5e585ffa8284d27f8ceea55 refs/remotes/origin/master

3171561b2c9c57024f7d748a1a5cfd755a26054a refs/tags/jx/v1.0

aaff5676a7c3ae7712af61dfb9ba05618c74bbab refs/tags/jx/v1.0-i18n

e153f83ee75d25408f7e2fd8236ab18c0abf0ec4 refs/tags/jx/v1.1

83f59c7a88c04ceb703e490a86dde9af41de8bcb refs/tags/jx/v1.2

1581768ec71166d540e662d90290cb6f82a43bb0 refs/tags/jx/v1.3

ccca267c98380ea7fffb241f103d1e6f34d8bc01 refs/tags/jx/v2.0

8a5b9934aacdebb72341dcadbb2650cf626d83da refs/tags/jx/v2.1

89b74222363e8cbdf91aab30d005e697196bd964 refs/tags/jx/v2.2

0b4ec63aea44b96d498528dcf3e72e1255d79440 refs/tags/jx/v2.3

60a2f4f31e5dddd777c6ad37388fe6e5520734cb refs/tags/mytag

5dc2fc52f2dcb84987f511481cc6b71ec1b381f7 refs/tags/mytag3

51713af444266d56821fe3302ab44352b8c3eb71 refs/tags/v1.0


从git show-ref的输出中发现了几个不寻常的引用,这些引用以refs/remotes/origin/为前缀,并且名称和远程版本库的分支名一一对应。这些引用实际上就是从远程版本库的分支复制过来的,称为远程分支。

Git的git branch命令也能够查看这些远程分支,不过要加上"-r"参数:


$git branch-r

origin/HEAD->origin/master

origin/hello-1.x

origin/helper/master

origin/helper/v1.x

origin/master


Git这样的设计是非常巧妙的,在从远程版本库执行获取操作时,不是把远程版本库的分支原封不动地复制到本地版本库的分支中,而是复制到另外的命名空间。如在克隆一个版本库时,会将远程分支都复制到目录.git/refs/remotes/origin/下。这样从不同的远程版本库执行获取操作,因为通过命名空间的相互隔离,所以就避免了在本地的相互覆盖。

那么克隆操作产生的远程分支为什么都有一个名为"origin/"的前缀呢?奥秘就在配置文件.git/config中。下面的几行内容出自该配置文件,为了说明方便显示了行号。


6[remote "origin"]

7 fetch=+refs/heads/:refs/remotes/origin/

8 url=file:///path/to/repos/hello-world.git


这个小节可以称为[remote]小节,该小节以origin为名注册了一个远程版本库。该版本库的URL地址由第8行给出,会发现这个URL地址就是执行git clone命令时所用的地址。最具魔法的配置是第7行,这一行设置了执行git fetch origin操作时使用的默认引用表达式:

该引用表达式以加号(+)开头,含义是强制进行引用的替换,即使即将进行的替换是非快进式的。

引用表达式中使用了通配符,冒号前面的含有通配符的引用指的是远程版本库的所有分支,冒号后面的引用含义是复制到本地的远程分支目录中。

正因为有了上面的[remote]配置小节,当执行git fetch origin操作时,就相当于执行了下面的命令,将远程版本库的所有分支复制为本地的远程分支。


git fetch origin+refs/heads/:refs/remotes/origin/


远程分支不是真正意义上的分支,是类似于里程碑一样的引用。如果针对远程分支执行检出命令,会看到大段的错误警告。


$git checkout origin/hello-1.x

Note:checking out 'origin/hello-1.x'.

You are in 'detached HEAD' state.You can look around,make experimental

changes and commit them,and you can discard any commits you make in this

state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create,you may

do so(now or later)by using-b with the checkout command again.Example:

git checkout-b new_branch_name

HEAD is now at 8cffe5f…Merge branch 'hello-1.x' of

file:///path/to/repos/hello-world into hello-1.x


上面大段的错误信息实际上告诉我们一件事,远程分支类似于里程碑,如果检出就会使得头指针HEAD处于分离头指针状态。实际上除了以refs/heads为前缀的引用之外,如果检出任何其他引用,都将使工作区处于分离头指针状态。如果对远程分支进行修改就需要创建新的本地分支。