简介:GitHub是目前最火的开源软件代码托管平台,那么Linux内核社区能否迁移到github上呢?Intel的DanielVetter针对这个问题写了一篇博文,他给出的答案是否定的。这个回答是否公平,就留给读者自己判断吧。本文主要根据http://blog.ffwll.ch/2017/08/...进行总结和改编。原作者DanielVetter来自IntelOTC内核开发。Daniel说写这篇文章的动机来自于两件事:一是在maintainerati会议(开源软件维护者的行业聚会)上的一次讨论。Daniel和几位开源软件维护者讨论了如何扩展真正的大型开源项目,以及Github如何强制项目只能以几种特定方式扩展。Linux内核开发使用不同的模型,这是Github上的开源维护者不理解的,所以Daniel在讨论中解释了Linux是如何工作的以及为什么Linux模型不同;第二个是在Daniel的其他博客MaintainersDon'tScale的评论部分,最支持的评论是“...为什么这些恐龙不使用现代开发工具?”。(其实这个问题在作者脑子里盘旋了很久。。。)内核社区的一些顶级维护者捍卫传统的邮件列表和布丁提交方式,抵制使用Githubpullrequest。真的是这一小撮当权者的问题吗?丹尼尔不这么认为。根本原因是Github模式根本无法支持Linux内核这种贡献者数量庞大的开发模式。所以内核社区的哪怕是几个子系统都不可能搬到Github上去开发。根据Daniel的说法,这不仅仅是关于托管git的数据,还有拉取请求、问题和分叉的工作方式。(写到这里,我对这个结论有点怀疑,Github上那么多大型项目,无非就是托管整个社区,说连一个都搞不定,有点傻内核子系统?)Github扩展Git的方式它的伟大之处在于每个人都可以轻松地fork开源项目并在其上创建分支。最后,一旦有好的改进,就可以基于上游主仓库创建pullrequest,对代码进行审查、测试,然后合并。Github也很好,因为它把Git这些复杂的方法用UI简化了,简单易学易用,让新手也能轻松为开源项目做贡献。最后,一个非常成功的开源项目无法通过标记、标记、排序、机器人群和自动化来管理单个代码存储库中的所有拉取请求和问题。分成易于管理的部分。更重要的是,项目的不同部分由于其规模和成熟度需要不同的规则和流程:与主线代码相比,非常新的实验库具有不同的稳定性和CI(持续集成)条件。还有,项目中可能有很多过时和不受支持的代码,但目前无法删除:这需要将一个非常大的项目拆分成多个子项目,每个子项目都有自己的流程风格,合并条件,独立仓库,独立拉取请求和问题跟踪。通常,项目管理可能需要数十名甚至数百名全职贡献者,直到管理开销成为一个痛点并且需要进行大规模重组。几乎所有的Github托管项目都是通过将单个源代码树根据功能拆分成许多不同的项目来解决这个问题的。通常拆分的结果是一些核心代码,再加上一堆插件、库和扩展代码。所有这些都是通过某种插件或包管理器构建在一起的,在某些情况下,它可能会直接从其他Github存储库中拉取来构建。因为几乎所有的大项目都是这样做的,这里大牛就不重复它的好处了,主要是指出这种做法的缺点,这里简单总结如下:社区不必要的划分。大多数贡献者只有他们直接贡献代码的存储库,而忽略了项目的其余部分。这对他们来说是好事,但也会导致不同子项目中并行重复的工作被及时发现并共享成果。项目重构和代码共享存在导致效率低下的障碍:首先需要为新的代码更改发布核心项目的新版本,然后遍历所有其他子项目的代码,更新它们,然后删除核心项目旧的共享代码。理论上,支持多个子项目版本组合是不可持续的。必须通过集成测试来保证。重组同一个更大项目的多个子项目是痛苦的,因为它需要重组Git存储库并决定如何拆分它。在单一代码库中,重组只是维护者信息文件的全新更新。(看到这里,笔者认为,如果一个内部耦合性很强的代码库,如果因为规模大而不得不拆分,就会存在这些问题……但确实,如果项目足够大,其实这种耦合关系是可以被拆分的通过一些过程解决避免了。比如Linux发行版,不就是按照上面的方法拆分出来的多个仓库的集合吗?当然,Linux发行版是由很多独立的开源项目组成的。但是作者之前在SolarisOS上工作过,其实就是把弱耦合的工程拆分成多个库,为了避免接口耦合的问题,在不同的库之间设计了接口稳定性的约定,比如公共接口和私有接口.对于公共接口,要支持多版本,EOL也会有相应的规则。)为什么会有PullRequest?Daniel说Linux内核是他所知道的少数几个没有像之前所说的那样拆分的项目之一。Linux内核是一个庞大的工程,没有子工程规划就无法运行。因此,还值得研究一下为什么Git需要PullRequests:在Github上,PullRequests是合并贡献者代码的真正方式。然而,内核更改是通过向邮件列表提交补丁来完成的,即使在内核项目使用Git之后也是如此。但是在Git的早期版本中,支持PullRequests。PullRequest的最初用户是内核维护者。Git最初是为了解决LinusTorvalds维护内核的问题而设计的。毫无疑问,PullRequests是必要和有用的,但是,它们并不是为了处理单个贡献者的代码更改而设计的:时至今日,PullRequests用于跨子系统转发代码更改,或同步代码重构,或跨多个子系统子项目,同步交付代码变更。例如,在4.12网络子系统维护者DaveS.Miller的PullRequest,由Linus提交:这个commit包含来自600个独立贡献者的2,000多个代码提交,还有一堆代码合并和来自下一级的pullrequest维护者。不过,这里所有的补丁都是从邮件列表中挑选出来,由维护者提交的,而不是原作者提交的。Linux内核进程的一个怪癖是原始作者不向代码库提交代码。这就是Git独立跟踪提交者和作者的原因。Github的创新和改进包括到处使用PullRequests,以及向独立贡献者发布PullRequests。然而,这并不是创建PullRequests的初衷。Linux内核的扩展方式乍一看,Linux是一个单仓库,所有的东西都塞进了Linus的主仓库。但实际上远非如此:很少有人从LinusTorvalds的主存储库运行Linux。如果他们运行上游内核,他们通常使用稳定的内核分支。但更常见的是Linux发行版,这些发行版通常有额外的补丁和向后移植,甚至不由kernel.org托管,而通常由一个完全不同的组织托管。或者,他们使用从硬件制造商那里获得的内核,与主存储库相比,这些内核通常有很多变化。除了Linus自己,没有人直接在Linus的仓库里做开发。每个子系统,甚至是更大的驱动程序,都有自己的Git存储库和自己的邮件列表,用于跟踪彼此独立的补丁提交和问题讨论。跨子系统的工作是在linux-next的集成代码树中完成的,它通常包含数百个来自不同Git代码库的Git分支。所有这些都是通过MAINTAINERS文件和get_maintainers.pl脚本管理的,它会告诉所有关于任何给定代码的一切,谁是维护者,谁是代码审查者,Git存储库在哪里,要使用哪个邮件列表,在哪里报告错误。该工具不仅通过文件位置识别跨子系统更改,而且通过捕获代码的特征,例如,设备树处理、kobject层次结构,由适当的专家处理。大牛认为这种方法有以下优点(自然是和之前Github上的子项目拆分相比),总结如下:重组拆分子项目超级容易,只需要更新MAINTAINERS文件,然后创建一个新的Git存储库就可以了。跨子系统讨论拉取请求和问题,在子项目之间重新分配非常非常容易,只需在电子邮件回复中将Cc:添加到子系统的邮件列表中即可。同样的,做跨子系统的工作也很容易协作,因为同一个PullRequest可以提交给多个子项目,全局讨论只有一个。跨子系统工作,无需协调多个项目之间的发布。直接在自己的仓库修改所有代码即可。它也不会阻止您创建自己的实验性更改,这是多存储库的重要好处之一。直接把代码加在自己的fork里就行了,没有人强制你把代码改回去,或者把你的代码放在一个单独的仓库里,因为没有中央仓库。(说了这么多,Github唯一的问题就是不支持跨repositoryworkflows。。。)Linux模型:monotreewithmultiplerepositories有人可能会质疑Linux模型看起来像一个,就像开头说的本文的一部分,具有扩展问题的单体代码库。接下来,大牛在文章中花了大量篇幅解释,Linux其实是一个单一的代码树,一种多代码仓库的模式(monotreewithmultiplerepositories)。Daniel还以内核社区维护者之间的PullRequest工作流程为例,解释了为什么Linux的这种模式在Github上行不通。总结如下:一个简单的场景是将代码变更散布在内核维护者的层级结构中,直到变更最终落地到代码树中,最终可以发布软件。对于Github,这很容易,你可以直接使用PullRequestUI来完成。更有意思的是,变化跨越了多个子系统,因为PullRequest的工作流会变成一个由无环图及其变体组成的Mesh网格。第一步是由所有子系统的维护人员审查和测试代码更改。在Github工作流中,这将是同时提交到多个代码存储库的合并请求,只要有一个讨论线程,在所有存储库之间共享。在内核社区中,这是通过同时向大量邮件列表和维护者提交补丁来实现的。内核的审查方式通常不是代码最终合并的方式。相反,在所有其他子系统维护者的同意下,其中一个子系统决定接受合并请求。通常,所选子系统是受更改影响最大的子系统,但有时,也可能是因为子系统正在执行与此合并请求冲突的工作。有时,会建立一个全新的代码存储库及其维护者。当更改的功能跨越整个代码树并且不容易包含在一个目录中的几个文件中时,通常会发生这种情况。最新的例子是DMA映射树(DMA映射项目的代码树),它试图将各种驱动程序、平台维护者和处理器架构支持更改结合在一个项目中。但是,有时,多个子系统的代码更改会发生冲突,导致它们都需要花费大量精力来解决合并冲突。在这种情况下,这种跨子系统的补丁通常不会被直接接受(相当于Github上的一个rebasePullRequest),而是修改PullRequest,让这些补丁对所有子系统通用,然后merge到所有子系统。为了避免影响子系统的不相关更改,公共基线非常重要。由于这种PullRequest是针对特定的Topic而存在的,所以这些代码分支通常也被称为Topic分支。大牛还举了微软的OS项目的例子,说是单一的代码树。而且根据他和微软人的交流,这个代码树太大了,微软需要写一个GVFS虚拟文件系统来支持更高效的开发。(笔者认为这个例子略显不妥,微软的OS不仅有内核,还有很多用户态的其他代码,这里的内核代码树就是内核代码。从更全球化、更平等的Linux发行版来看,Linux发行版其实就是多重代码树.....正是大牛在Github上批评了使用多重代码树划分子项目的方法...)亲爱的Github,轮到你了...可惜了,Github不支持前面讨论的跨子系统工作流,至少在本机UI中不支持。当然,这些工作可以用原来的git命令来完成,但是你又要回到通过邮件列表发送补丁的方式,把PullRequest返回到邮件中,然后手动合并。在Daniel看来,这是内核社区无法迁移到github的唯一原因。还有一些小问题(真的是小问题?),一些顶级维护者极度反对Github,但这不是技术问题。而且,不仅仅是Linux内核,这是所有大型Github项目都面临扩展的普遍问题,因为Github没有给他们提供扩展到多个存储库的选项,而是维护一个单一的代码树。简而言之,Daniel向Github提出了一个他认为很简单的新功能请求:请支持跨多个不同的、基于单一代码树的代码存储库的PullRequest和问题跟踪管理。非常简单的想法,但影响巨大。大牛不仅给出了核心思想,还给出了提案的一些细节,总结如下:Repositoryandorganization首先,同一个组织需要支持同一个代码库的多个派生repository。以git.kernel.org为例,上面的大部分仓库都不是个人的。在代码库中使用多个分支不能替代此要求,因为拆分代码库的主要原因是要使拉取请求和问题彼此隔离。另外,需要建立基于既成事实(历史)的代码库推导关系。对于新项目,这不是问题。但是以Linux搬迁为例,这是一个问题:所有的Linux子系统必须一次性搬过来,而Github上已经有大量的Linux代码库,它们之间没有正确的Github派生关系。PullRequestPullRequest需要能够同时提交到多个代码库,还要保持一个讨论线程。除了一次提交所有存储库之外,还需要能够将拉取请求重新分配给存储库的不同分支。此外,每个存储库的PullRequest状态需要不同。代码库的维护者可能会在不合并的情况下关闭拉取请求,因为维护者同意其中一个子系统将拉取拉取请求,并且该维护者将合并并关闭拉取请求。另一个代码树可能不得不将PullRequest作为无效请求关闭,因为PullRequest不适合这个旧版本的代码库,或者特定供应商的衍生代码库。更有趣的是,一个PullRequest可能会在每个子系统的代码库中被合并多次,并使用不同的合并提交(CommitID)。与Issues和PullRequests类似,Issuetracking也需要根据多个代码库进行隔离,并且需要具备移动的能力。举个例子,一个bug可能先上报到某个release版本的内核代码仓库,然后经过bug分析,这个驱动的bug也存在于最新的开发分支,所以这个issue不仅仅和这个代码库有关,还需要添加Mainline上游分支,或许还有更多的代码仓库。问题状态在不同的代码库中应该是独立的,因为一次推送一个仓库不会在所有仓库都可用。甚至,向后移植到较旧的内核和发行版可能需要更多的工作,并且一些代码库可能会认为该错误不值得修复,使用WONTFIX关闭错误,但同时它在其他代码库中被标记为已成功解决。(作者之前的公司都有这些功能,确实,这是商业软件缺陷跟踪系统必备的功能)总结:Monotree,不是MonorepoLinux内核不打算搬上Github。但是Github支持将“单代码树,非单代码库”类型的项目迁移到Github,这对现有的所有巨型项目来说都是大有裨益的。权衡无处不在大牛的文章到此结束,笔者将谈谈自己的看法,仅供参考。首先,因为Github的设计问题,强行把一个紧耦合的代码库拆分成多个子项目确实是个大问题。但是对于本来就是松散耦合的代码,拆分的好处也是显而易见的。每个人都可以按照自己的方向和节奏去发展和保持。从广义上讲,Glibc和Linux内核的关系是这样的。之所以运行良好,稳定的系统调用接口是关键。因此,代码库的边界应该以存在稳定的协议和接口为前提。其次,Github作为全球最大的同性社交平台,解决了个人开源程序爱好者的需求。因此,在管理模式上,Github不支持大型开源项目所要求的“单一代码树,而非单一代码库”模式也就不足为奇了。作为产品定位,或许Github目前的设计是其成功的原因之一。正是它让Git从少数Linux内核开发人员迅速传播到整个程序员社区。最后,根据KISS(keepitsimplestupid)的原则,对主流用户足够傻是Github最大的优势。但随着Github的影响力越来越大,越来越多的企业和组织开始在其上托管项目,也许Daniel的建议值得认真考虑?至少,企业用户可能会支付真金白银。看来我们不仅在技术上,而且在产品开发方向上都面临着各种取舍。谁知道本文批评的Github的缺点,不是Github产品经理深思熟虑后做出的决定吗?而大牛的建议,或许是新形势下大企业的需要?原文链接本文为阿里云原创内容,未经许可不得转载。
