章节 ▾ 第二版

3.5 Git 分支 - 远程分支

远程分支

远程引用是远程仓库中的引用(指针),包括分支、标签等。你可以通过 git ls-remote <remote> 显式获取远程引用的完整列表,或者通过 git remote show <remote> 获取远程分支及更多信息。不过,更常用的方法是利用远程跟踪分支。

远程跟踪分支是远程分支状态的引用。它们是无法移动的本地引用;每当你进行网络通信时,Git 都会自动为你移动这些分支,以确保它们准确地反映远程仓库的状态。你可以把它们想象成书签,用来提醒你上次连接时远程仓库中的分支位于何处。

远程跟踪分支的名称格式为 <remote>/<branch>。例如,如果你想查看 origin 远程仓库上的 master 分支在你上次与它通信时的状态,你可以查看 origin/master 分支。如果你正在与合作伙伴协作处理一个问题,而他们推送了一个 iss53 分支,你可能会有自己的本地 iss53 分支,但服务器上的分支将由远程跟踪分支 origin/iss53 表示。

这可能有点令人困惑,让我们来看一个例子。假设你的网络上有一个 Git 服务器 git.ourcompany.com。如果你从这里克隆,Git 的 clone 命令会自动为你将其命名为 origin,拉取所有数据,创建一个指向其 master 分支的指针,并在本地命名为 origin/master。Git 还会为你创建一个从 origin 的 master 分支起始的本地 master 分支,这样你就有了一个可以开始工作的基础。

注意
“origin”并非什么特殊名称

就像“master”分支名称在 Git 中没有任何特殊含义一样,“origin”也没有。虽然“master”是运行 git init 时默认的起始分支名称,这也是它被广泛使用的唯一原因,但“origin”是运行 git clone 时默认的远程仓库名称。如果你改为运行 git clone -o booyah,那么你默认的远程分支名称就会是 booyah/master

Server and local repositories after cloning
图 30. 克隆后的服务器和本地仓库

如果你在本地的 master 分支上做了一些工作,与此同时,其他人向 git.ourcompany.com 推送并更新了其 master 分支,那么你们的历史记录就会向不同的方向发展。此外,只要你不与 origin 服务器联系,你的 origin/master 指针就不会移动。

Local and remote work can diverge
图 31. 本地工作与远程工作可能产生分歧

要将你的工作与给定的远程仓库同步,可以运行 git fetch <remote> 命令(在我们的例子中是 git fetch origin)。该命令会查找“origin”对应的服务器(在本例中是 git.ourcompany.com),抓取你本地尚无的数据,并更新你的本地数据库,将 origin/master 指针移动到新的、更靠前的位置。

`git fetch` updates your remote-tracking branches
图 32. git fetch 会更新你的远程跟踪分支

为了演示拥有多个远程服务器时这些远程项目分支的样子,假设你还有另一个内部 Git 服务器,仅供你的某个敏捷开发团队使用。该服务器地址为 git.team1.ourcompany.com。你可以运行我们在 Git 基础 中介绍过的 git remote add 命令,将其作为新的远程引用添加到当前项目中。将该远程仓库命名为 teamone,这就会成为该 URL 的简称。

Adding another server as a remote
图 33. 添加另一个服务器作为远程仓库

现在,你可以运行 git fetch teamone 来获取 teamone 远程服务器上你尚无的所有数据。由于该服务器目前仅拥有 origin 服务器上数据的一个子集,Git 不会抓取任何数据,但会设置一个名为 teamone/master 的远程跟踪分支,指向 teamonemaster 分支所在的提交。

Remote-tracking branch for `teamone/master`
图 34. teamone/master 的远程跟踪分支

推送

当你想要与世界分享一个分支时,需要将其推送到你有写入权限的远程仓库。你的本地分支不会自动同步到你写入的远程仓库中——你必须显式推送你想要分享的分支。这样,你就可以在私有分支上进行不想分享的工作,只推送那些你希望协作的主题分支。

如果你有一个名为 serverfix 的分支想要与他人协作,你可以用推送第一个分支的方式推送它。运行 git push <remote> <branch>

$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
 * [new branch]      serverfix -> serverfix

这是一个简写。Git 会自动将 serverfix 分支名展开为 refs/heads/serverfix:refs/heads/serverfix,意思就是:“拿我的 serverfix 本地分支去更新远程的 serverfix 分支。”我们将在 Git 内部原理 中详细介绍 refs/heads/ 部分,但通常你可以省略它。你也可以执行 git push origin serverfix:serverfix,效果是一样的——它表示:“拿我的 serverfix 去作为远程的 serverfix。”你可以使用这种格式将一个本地分支推送到名称不同的远程分支上。如果你不希望它在远程上叫 serverfix,可以改为运行 git push origin serverfix:awesomebranch,将本地的 serverfix 分支推送到远程项目的 awesomebranch 分支上。

注意
不必每次都输入密码

如果你使用 HTTPS URL 进行推送,Git 服务器会要求你输入用户名和密码进行身份验证。默认情况下,它会在终端提示你输入这些信息,以便服务器判断你是否有权推送。

如果你不想每次推送都输入密码,可以设置一个“凭据缓存”。最简单的方法是将其保存在内存中几分钟,可以通过运行 git config --global credential.helper cache 轻松设置。

关于各种可用凭据缓存选项的更多信息,请参阅 凭据存储

下一次你的协作者从服务器抓取数据时,他们会在远程分支 origin/serverfix 下获得服务器版本 serverfix 的引用。

$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
 * [new branch]      serverfix    -> origin/serverfix

需要注意的是,当你执行 fetch 操作并获取新的远程跟踪分支时,你并不会自动获得它们的本地可编辑副本。换句话说,在这种情况下,你并没有一个新的 serverfix 分支——你只有一个无法修改的 origin/serverfix 指针。

要将这些工作合并到你当前的工作分支中,你可以运行 git merge origin/serverfix。如果你想要拥有一个可以工作的本地 serverfix 分支,可以基于远程跟踪分支创建一个:

$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

这会为你提供一个可以工作的本地分支,它从 origin/serverfix 所在的位置开始。

跟踪分支

从远程跟踪分支检出(checkout)一个本地分支,会自动创建一个所谓的“跟踪分支”(它所跟踪的分支被称为“上游分支”)。跟踪分支是与远程分支有直接关系的本地分支。如果你处于跟踪分支上并输入 git pull,Git 会自动知道从哪个服务器抓取数据以及合并到哪个分支。

当你克隆一个仓库时,它通常会自动创建一个跟踪 origin/mastermaster 分支。不过,如果你愿意,也可以设置其他跟踪分支——即跟踪其他远程仓库上的分支,或者不跟踪 master 分支。简单的情况就是刚才看到的例子,运行 git checkout -b <branch> <remote>/<branch>。这是一个非常常见的操作,Git 提供了 --track 的缩写:

$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

事实上,这太常见了,以至于还有更简便的写法。如果你尝试检出的分支名 (a) 不存在,且 (b) 恰好只在某一个远程仓库中有匹配名称,Git 会为你创建一个跟踪分支:

$ git checkout serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'

要设置一个与远程分支名称不同的本地分支,你可以轻松地使用带不同本地分支名的第一个版本:

$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'

现在,你的本地分支 sf 将自动从 origin/serverfix 拉取数据。

如果你已经有一个本地分支,想要将其设置为刚拉取的远程分支,或者想要更改你正在跟踪的上游分支,可以随时使用 git branch-u--set-upstream-to 选项来显式设置。

$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
注意
上游缩写

当你设置好跟踪分支后,可以使用 @{upstream}@{u} 缩写来引用它的上游分支。因此,如果你在 master 分支上,且它正在跟踪 origin/master,如果愿意,你可以输入 git merge @{u} 而不是 git merge origin/master

如果你想查看你设置了哪些跟踪分支,可以使用 git branch-vv 选项。这会列出你的本地分支及更多信息,包括每个分支正在跟踪的内容,以及你的本地分支是领先、落后还是两者皆有。

$ git branch -vv
  iss53     7e424c3 [origin/iss53: ahead 2] Add forgotten brackets
  master    1ae2a45 [origin/master] Deploy index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] This should do it
  testing   5ea463a Try something new

在这里我们可以看到,我们的 iss53 分支正在跟踪 origin/iss53 并且“领先”两个提交,这意味着我们在本地有两个提交尚未推送到服务器。我们还可以看到 master 分支正在跟踪 origin/master 且是最新的。接下来我们可以看到 serverfix 分支正在跟踪 teamone 服务器上的 server-fix-good 分支,并且领先三个提交、落后一个提交,这意味着服务器上有一个提交我们尚未合并,而本地有三个提交尚未推送。最后,我们可以看到 testing 分支没有跟踪任何远程分支。

需要注意的是,这些数字仅代表自上次从每个服务器抓取以来的状态。此命令不会主动连接服务器,它只是告诉你本地从这些服务器缓存了什么。如果你想要完全最新的领先和落后数据,你需要在运行此命令前从所有远程仓库执行 fetch。你可以这样做:

$ git fetch --all; git branch -vv

拉取 (Pulling)

虽然 git fetch 命令会抓取服务器上你尚无的所有更改,但它完全不会修改你的工作目录。它只是为你获取数据,并让你自己决定何时合并。然而,还有一个命令叫 git pull,在大多数情况下,它本质上就是 git fetch 紧接着一个 git merge。如果你像上一节演示的那样设置了跟踪分支(无论是显式设置,还是通过 clonecheckout 命令为你创建的),git pull 都会查找你当前分支正在跟踪的服务器和分支,从该服务器抓取数据,然后尝试合并该远程分支。

删除远程分支

假设你已经不再需要某个远程分支——比如你和你的协作者完成了某个功能,并已将其合并到远程的 master 分支(或者你稳定的代码线所在的任何分支)。你可以使用 git push--delete 选项删除远程分支。如果你想从服务器删除你的 serverfix 分支,可以运行以下命令:

$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
 - [deleted]         serverfix

基本上,这所做的只是从服务器上移除指针。Git 服务器通常会将数据保留一段时间,直到运行垃圾回收(garbage collection),所以如果它是被意外删除的,通常很容易恢复。