不久之前,在一个很好的平台上( maintainerati )和一些十分优秀的维护者聊天时,谈到关于如何扩展真正的大型开源项目,以及 GitHub 如何对项目进行扩展。Linux 内核有一个完全不同的模式,不过把项目托管在 GitHub 上的开源项目维护者似乎不明白这个。但我认为这是值得解释的,以及它是如何运作和不同的地方在哪里。
写下这些文字的另一个动机是我的 “Maintainers Don’t Scale” 这个观点在 HN discussion 上引起的讨论,其中最热的评论归结起来可以理解为“为什么这些大项目不会使用现代开发工具”。事实上,一些顶级的内核维护者仍在大力捍卫邮件列表,他们会通过这种方式提交补丁,像 github 的 pull requests 那样,但有些使用图形系统的用户会更喜欢现代化的工具,因为这更容易脚本化。问题是 GitHub 不能支持 Linux 内核扩展到如此大规模贡献者的方式,所以我们不能简单地迁移,即使仅仅是一些子系统。而且这不仅仅涉及到托管 git 数据,这部分当然不会有什么问题,问题主要是如何在 github 上进行 pull requests, forks 以及提 issue 这些操作。
Git 是非常优秀的系统,因为每个人都可以很容易地 fork 和创建分支,并且对代码进行修改,最后你会获得一些好的东西。你可以为主仓库创建 pull request,它会得到审查、测试和合并。GitHub 也是十分优秀的产品,因为它打造了一个 UI,使得 git 这个复杂的工具更友好,并更容易探索和学习,因此使得新人能更容易为项目贡献。
但最终,一个项目取得巨大的成功时,再多的 tag, 标签, 排序, bot-herding 和自动化都会排在仓库中的 pull request 和 issue 之前,现在是时候将事情分解成更可控的部分了。更重要的是,具有一定规模和历史的项目的不同部分需要不同的规则和处理:耀眼的新实验性库具有与主代码不同的稳定性和 CI 标准,也许你还有一些不再被支持的被放弃的插件的垃圾代码块,但你不能删除它们:你需要将你的大规模项目分解成子项目,每个项目都有自己的流程和合并条件,以及备份与 pull 请求和问题跟踪。一般来说,在项目达到必须大规模重组之前,需要几十或几百个全职贡献者。
几乎所有托管在 GitHub 的大型项目都是通过将它们的 monorepo 源码树分解成许多不同的项目实现的,每个项目都具有不同的功能。通常这会导致一堆被认为是核心的东西,加上一堆插件、库和扩展。所有这些都与某种插件或包管理器捆绑在一起,在某些情况下,它们直接从 github 仓库中拉取数据。
由于几乎每个大型项目都是这样做的,所以我不认为有必要深挖这其中的好处。但我想强调一些这样做引起的问题:
Linux 内核是为数不多的不像这样运作的项目之一。在我们研究它是如何工作之前,我们应当了解:内核是一个巨大的项目,没有一些子项目结构根本无法运行。了解为什么 git pull requests 很有意思:在 github 上,pull requests 是一个将不同人的贡献合并的有效方式。但在内核,即使在 git 被广泛采用之后,更改依然被作为补丁提交发送到邮件列表。
然而 git 的最初版本支持 pull requests。内核维护者见证了Git 第一次相当粗暴的发布,它是为了解决 Linus Torvalds 的维护问题而编写的。显然,它发挥了它的作用,但不是用于处理来自个人贡献者的贡献:在今天,pull requests 比当时更多地用于促进整个子系统、同步代码重构或跨项目代码交叉重构。举个例子,Linus 提出的的 4.12 网络是在 Dave S. Miller 的基础上 pull requests 而成的:它包含来自 600 个贡献者的两千多的意见和建议和来自下属维护者 pull requests 的大量合并。几乎所有的补丁都是由维护者从邮件列表中获取,而不是由作者自己打上的。这个内核进程的特点是作者一般不提交到共享存储库,这也是为什么 git 将提交者和作者分开追踪。
Github 的创新和改进都是为了有朝一日所有事情都能使用 pull requests,接纳下至个人的微小贡献。但这不是他们创造的最初目的。
乍一看,内核像一个把一切杂糅到 Linus’ main repo 里的 monorepo 模型,但实际上相差甚远:
起初,这看起来像以一种复杂的方式使用大家根本不关心的内容来填满其磁盘空间,但这样一堆微小改进的组合还是有好处的:
简而言之,我认为严格来说这是一个更强大的模型,因为你总是可以回头在多个不相交的仓库上做一些完全一样的事情。甚至还有一些内核驱动程序保存在他们自己的仓库中,与主内核树已经脱离,就像私有的 Nvidia 驱动程序。那只是一个对 blob 进行的源代码修正,但是由于法律原因它不能包含任何内核的内容,这绝对是一个完美的例子。
是和否。
乍看起来,linux 内核看起来像 monorepo ,因为它包含所有内容。许多人知道,使用 monrepo 真的很痛苦,因为超过一定的大小,他们会停止自适应。
但是进一步分析,这和单 git 仓库有很大的差距。只需看看上游的子系统和驱动程序库就可以有几百个。如果你浏览下整个生态系统,包括硬件供应商、发行版、其他基于 Linux 的操作系统和单独的产品,你会轻松地拥有数千个主代码仓库,并且总共会有许多主仓库。这里不包括计任何仅供个人贡献者使用的私有 git repo 。
关键的区别是,linux 具有一个单一的文件层次结构,它作为跨越所有内容的共享命名空间,但是拥有对于所有不同的代码段和问题的诸多不同的 repo 。它是一个拥有多存储库的单一树,而不是 monorepo 。
在我解释为什么 github 目前无法支持此工作流程之前,反正如果您希望保留 github UI 和集成的优势,我们需要一些实际操作的例子。简单来说,就是在维护者之间完成 git 拉取请求。
简单的情况是传递改变了维护者的层次结构,直到它最终落在树中并移植完毕。这很容易,因为拉取请求只能从一个仓库到下一个仓库,因此可以使用当前的 github UI 完成。
更有趣的是跨子系统的变化,因为拉取请求流将停止变为一个无环图,形成一个网络。第一步是让所有涉及的子系统及其维护者进行审查和测试。在 github 流中,这将是同时提交给多个仓库的拉取请求,其中一个单独的讨论流在它们之间共享。因为这是内核,所以这个步骤是通过补丁提交的,以及一堆不同的邮件列表和维护者作为收件人完成的。
它的评审方式通常不是合并的方式,而是其中一个子系统被选为主导,并应用这些pull请求,只要所有其他维护者赞成该合并路径就可以了。通常情况下,这个子系统是被一系列改动影响最大的,但有时也可能是一个已经有一些其他的工作正在进行与 pull 请求有冲突的子系统。有时也会创建一个全新的存储库和维护人员的队伍,这通常发生在跨越整个版本树的功能上,并且不是整齐地包含在一个地方的几个文件和目录中。最近的一个例子是 DMA 映射树,它试图整合到目前为止已经广泛引用在驱动程序、平台维护者和架构支持组中的工作。
但有时候会有多个子系统会与一组改动相冲突,而且所有子系统都需要解决一些不常见的合并冲突。在这种情况下,补丁并不是直接应用的(即在 github 上执行 rebasing pull 请求),而是基于通用于所有子系统的提交补丁的 pull 请求被合并到所有子系统树中。共同的基线对于避免子系统树被不相关改动影响具有重要作用。由于这个 pull 仅针对特定问题,这些分支通常称为问题分支。
我曾参与的一个例子是添加支持 audio-over-HDMI 的代码,跨越了图形和声音驱动两个子系统。同样的提交请求同时被合并到 Intel 图形驱动程序和 声音 子系统中。
另一个完全不同的例子是,世界上 唯一的其他相关的通用大型操作系统 (其实就是Windows,译者注)也决定使用 monotree,一个类似于 linux 上发生的提交流程。我和工作在这个系统的人交流过,这样一个巨大的树使得他们不得不重写一个全新的 GVFS 虚拟文件系统驱动来支持它...
来源: http://www.tuicool.com/articles/mUjuuiy