跳转至

fork 与多远端仓库下的协作

在以 GitHub 为代表的开源社区平台中,存在着一个较为特别的概念:fork。它的作用是对任意用户的仓库进行“复制”,得到一个属于自己的仓库。在 GitHub 上的开源项目中,fork 是常见的协作方法之一。

根据 GitHub 官方文档《About forks》的介绍,fork 的一个突出用途在于:对于很多开源项目来说,人们可以使用 fork 来得到一个新仓库,并在新仓库里根据自己的想法提交许多修改,然后在本地进行充分的测试之后通过 pull request 来将自己的修改提交到原来的代码仓库中。

为什么需要一个新仓库呢?这是因为原仓库并不公开提供 push 权限,因此即使没有权限的人们可以把仓库克隆下来,在本地创建分支并提交修改,但他们并不能进行 push 操作,自然也就无法直接通过这种方式来提交代码,以及将自己的分支直接合进主分支里。

Info

根据《About collaborative development models》,上述的模式也被称为 Fork and pull model,在开源项目中非常流行。也有一种 Shared repository model,它允许贡献者直接对仓库进行 push。这种模式在私有项目中较为常见。

fork 的功能并不局限于此。一些开源项目有时候会因为各种原因(人力不足、法律约束、理念冲突等),迟迟不能提供一些新功能。这时对这些功能有强烈需求的人们会为此开辟一个 fork,专门在这个 fork 上编写新功能的代码。同时,fork 提供从源仓库拉取新修改的功能,让 fork 出来的项目得以与源项目保持同步。例如,MultiMC5-Cracked 是基于著名 Minecraft(我的世界)游戏启动器 MultiMC5 的 fork 版本。因为后者不支持在不提供正版账号的情况下启动游戏,所以前者诞生并提供了新的功能,让用户能够使用非正版账号启动游戏。

添加远端仓库并 checkout

现在,试想我们就是 MultiMC5-Cracked 的作者,那么又该如何在 fork 出来的仓库里从源仓库拉取最新的修改呢?首先,我们需要在本地仓库执行以下命令:

$ git remote -v
origin  [email protected]:AfoninZ/MultiMC5-Cracked.git (fetch)
origin  [email protected]:AfoninZ/MultiMC5-Cracked.git (push)

我们使用此命令列出了当前仓库的远端仓库地址,其中的 (fetch)(push) 意味着对应的远端仓库可以用来进行拉取和推送 commit。然后我们使用 git remote add 命令来添加新的远端仓库:

$ git remote add upstream [email protected]:MultiMC/MultiMC5.git
$ git remote -v
origin  [email protected]:AfoninZ/MultiMC5-Cracked.git (fetch)
origin  [email protected]:AfoninZ/MultiMC5-Cracked.git (push)
upstream    [email protected]:MultiMC/MultiMC5.git (fetch)
upstream    [email protected]:MultiMC/MultiMC5.git (push)

我们将源仓库 MultiMC5 添加为当前仓库的另一个远端仓库。虽然它同样有一个 (push) 标识,但我们并不能直接将 commit 推送到源仓库,因为我们没有权限。之后我们可以通过 git fetch upstream 来拉取源仓库的分支信息:

remote: Enumerating objects: 365, done.
remote: Counting objects: 100% (333/333), done.
remote: Compressing objects: 100% (99/99), done.
remote: Total 365 (delta 264), reused 303 (delta 234), pack-reused 32
Receiving objects: 100% (365/365), 131.51 KiB | 5.98 MiB/s, done.
Resolving deltas: 100% (266/266), completed with 121 local objects.
From github.com:MultiMC/MultiMC5
 * [new branch]        beature/debrand                           -> upstream/beature/debrand
 * [new branch]        develop                                   -> upstream/develop
// 这里省略了一些无关紧要的分支
 * [new branch]        stable                                    -> upstream/stable
 * [new tag]           0.6.12                                    -> 0.6.12

可以看到,Git 将源仓库的各种数据已经拉取了下来,包括各种分支的信息等。现在如果我们需要将源仓库的 stable 分支合并入我们自己的仓库的 stable 分支,只需运行 git merge upstream/stable 即可。

Info

你可能会在使用 git checkout stable 来切换到本地 stable 分支时遇到以下错误:

$ git checkout stable
hint: If you meant to check out a remote tracking branch on, e.g. 'origin',
hint: you can do so by fully qualifying the name with the --track option:
hint:
hint:     git checkout --track origin/<name>
hint:
hint: If you'd like to always have checkouts of an ambiguous <name> prefer
hint: one remote, e.g. the 'origin' remote, consider setting
hint: checkout.defaultRemote=origin in your config.
fatal: 'stable' matched multiple (2) remote tracking branches

这里 Git 是在说:originupstream 上都有名为 stable 的分支,因此我们要么通过 --track 参数手动指定远端仓库,要么通过 git config 来设置默认要选择的远端仓库。

检查他人的 Pull request

假如我们是开源项目的作者,有人向我们发来了一个 Pull request。在这个 PR 里,他声称帮助我们实现了一些新功能,或者修复了一些 bug。要验证他的说法,我们很有可能需要得到他修改的分支对应的源码,然后进行需要的一些步骤,例如编译运行,手动测试等。我们有两种方法来得到 Pull request 对应的源码。

直接 fetch 对应的 Pull request

像 GitHub 这样的平台支持直接在本地仓库运行 git fetch origin pull/ID/head:BRANCHNAME 来将编号为 ID 的 Pull request 对应的分支拉取到本地名为 BRANCHNAME 的分支上。

添加 Refspec

我们需要修改本地仓库的 .git/config 文件,在 [remote "origin"] 栏目下添加新的一行:

fetch = +refs/pull/*/head:refs/pull/origin/*
Info

这项配置被官方文档称为 Refspec,它指定了 Git 应该将远端仓库的哪些 reference 拉取到本地。默认情况下,它的配置为:

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

它意味着在进行 git fetch 时,Git 会将远端仓库上 refs/heads 文件夹下的所有 reference 文件拉取到本地的 refs/remotes/origin/ 文件夹里。为了能够拉取 Pull request,我们可以添加上述的新的一行配置。更多细节请见官方文档。

之后我们运行 git fetch origin,得到了以下输出。我们这里仍以上文提到的 MultiMC5-Cracked 在 GitHub 上的仓库为例:

$ git fetch origin
remote: Enumerating objects: 772, done.
remote: Counting objects: 100% (406/406), done.
remote: Compressing objects: 100% (88/88), done.
remote: Total 772 (delta 289), reused 394 (delta 283), pack-reused 366
Receiving objects: 100% (772/772), 140.63 KiB | 249.00 KiB/s, done.
Resolving deltas: 100% (352/352), completed with 59 local objects.
From github.com:AfoninZ/MultiMC5-Cracked
 * [new ref]           refs/pull/1/head  -> refs/pull/origin/1
 * [new ref]           refs/pull/10/head -> refs/pull/origin/10
 * [new ref]           refs/pull/11/head -> refs/pull/origin/11
 // 以下省略

我们拉取了此仓库的所有 Pull request 对应的分支,其中的数字就是 Pull request 的编号。如果我们想要检查编号为 11 的 Pull request,可以运行以下命令:git checkout -b pr-11 pull/origin/11 来切换到对应的分支。

Warning

refs/pull 目录下的文件都是只读的,这意味着无论我们使用的是方法一还是方法二,在切换到 pr-11 分支之后都无法直接进行 push。不过我们仍然可以通过 git push origin NAME 来将当前分支推送到远端仓库里一个新的叫做 NAME 的分支上。


最后更新: 2021-09-08 22:55:58
本页作者: darkyzhou