章节 ▾ 第二版

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 还会给您自己的本地 master 分支,它从与 origin 的 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 remote add 命令将其添加为当前项目的新远程引用,正如我们在Git 基础中介绍的那样。将此远程命名为 teamone,这将是该整个 URL 的简称。

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

现在,您可以运行 git fetch teamone 来获取远程 teamone 服务器上您尚未拥有的所有内容。因为该服务器目前拥有的数据是您 origin 服务器数据的子集,Git 不会获取任何数据,但会设置一个名为 teamone/master 的远程跟踪分支,指向 teamone 作为其 master 分支的提交。

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 轻松设置。

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

下次您的一个协作者从服务器获取时,他们将获得一个引用,指向服务器版本 serverfix 位于远程分支 origin/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 开始工作。

跟踪分支

从远程跟踪分支检出本地分支会自动创建一个所谓的“跟踪分支”(它所跟踪的分支称为“上游分支”)。跟踪分支是与远程分支有直接关系的本地分支。如果您在一个跟踪分支上并键入 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 分支没有跟踪任何远程分支。

需要注意的是,这些数字仅表示自您上次从每个服务器获取以来的情况。此命令不会与服务器通信,它告诉您它从这些服务器本地缓存了什么。如果您想要完全最新的领先和落后数字,您需要在运行此命令之前从所有远程仓库获取数据。您可以这样做

$ git fetch --all; git branch -vv

拉取

虽然 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 服务器通常会在垃圾回收运行之前保留数据一段时间,所以如果意外删除,通常很容易恢复。