河套 IT TALK 25:Git 为什么能够成功,而其他的失败了?

河套 IT TALK 25:Git 为什么能够成功,而其他的失败了?

2005年,Linus Torvalds开发Git这个开源的分布式代码版本控制系統软件的时候,他可能没有意识到自己的开发会一统江湖。2022年的一份年度开发者调查问卷中,Git在开发者的使用率压倒性地高达93.9%。那么Git是如何做到成功的呢?它到底有哪些特点是其他版本管理软件没有考虑到的呢?我们今天来谈一下这个话题。

集中还是分布,这是基本理念的差异

Git之所以是被极客Linus Torvalds 开发出来的,而不是被一个大公司开发出来的,因为他们的开发模式有着根本理念的差别:集中式开发和分布式开发。

集中式开发:我一定要用你。你必须保证你的代码是有用的。

分布式开发:我不Care你。你要说服我你的代码是有用的。

集中式开发,不能容忍人力资源的浪费,人员之间的关系也相对固化的,层级关系也是相对严格的。代码责任路径也是金字塔式的。集中式开发,一般发生在已经有固定的雇员的项目团队。这些雇员是签了雇佣合同的,是要领薪水的,是要占用项目的人力成本的,所以,他们必须要为项目创造价值,必须要有绩效考核,每位开发者都不是混饭吃的,每位团队成员都有对应的代码责任田,每个人或者某几个人为某几个代码文件或者文件目录负责。

分布式开发,发生在开源项目里,基本是根据兴趣和利益牵引而诱发的非稳态合作。分布式合作之前,团队各方人员大多并未相识相知。所以这种开发构建的前提就是项目创始人不认识你,你要让他(她)认识你,你先主动来找他(她),来证明你值得他(她)认识和合作,如果你没有说服他(她),仅仅是他(她)的粉丝,那他(她)自然也没有什么问题,或者也能满足其虚荣感:“你愿意拿我的代码开启自嗨模式,我不Care。”这种非稳态的合作关系,必然允许人力资源的浪费(而且,这也是开源项目的常态),也不太讲究人员关系的固化,不存在所谓的雇佣关系。修改的代码是否被别人接受由别人来决定。项目核心或者发起人自己来决定要去合并谁的代码,而这种信任也是动态变化的。

写到这儿,我觉得这个和社交网络(同样是另外一种分布式网络)的微博分享有些类似。Push在这里不那么重要,你可以分享发布微博(Push),但是没有人关注你,没有人给你小爱心,也就是自娱自乐而已。Pull貌似更重要,Pull的主动权在对方手里,对方可以给你点赞,给你评论,甚至Follow你了,以后每次你发布什么都给你点赞,这样你的分享才显得更有意义。而转发,可以类比为fork,如果在你的转发上还有所修改,甚至有新的心得体会评论,这就相当于在你的分支上修改。你觉得谁的评论写的好,再转发助推一次,又相当于Pull,合入到了你的主分支。

由于这种基本理念和开发模式的本质差别,导致代码管理的方式,会产生极大的不同。

针对代码仓的数据备份策略

集中式管理

集中式管理,往往对代码是当作宝贵资源或者价值产出看待的。因为不可能允许团队的开发者都拥有代码。防贼防盗防程序员。项目时时刻刻防着这些开发者,也许哪一天解除了雇佣关系,他们离职了,跳槽了,是绝对不能让他们把代码带离公司的。这是公司的宝贵代码财富,必须要进行各种安全管理。代码仓,每位开发者,一般都是针对自己的代码责任田范围才有访问和修改的权限,而对其他的代码不允许看,不能改。这种权限设置一定是严格的文件级的安全权限管理体系。所以,代码仓,一定是集中化管理的。

集中管理,就一定会使用服务器-客户端的维护模式。服务器存放完整的代码仓。而客户端,仅存放开发者需要关注的责任田代码。服务器一定要做到足够的安全防护,还要考虑到容灾备份,因为一旦服务器出现宕机或者拥塞,对整个团队的工作都会是全局的影响。而且,如果服务器的存储考虑不周,真的出现了硬盘故障,导致代码仓代码丢失,那简直就是晴天霹雳。

分布式管理

分布式管理则不同,因为是开源项目,所以整个项目,凡是已经在官方发布的项目代码仓的内容,所有人可以随便看,随便改,完全可以下载到本地,也就是每个人都有一个项目官方发布的完整备份。

可能有些人会抱怨Git的项目官方发布的代码仓完整下载备份到本地会占用很大的存储空间。这个其实不是开源项目的发起者所关心的。如果想玩开源项目,这点儿存储空间的成本是非常低廉的。另外,这里还有一个非常重要的和集中式管理的数据备份策略问题。分布式存储,会避免集中存储引发的服务器宕机和硬盘损坏导致的重大数据丢失。分布式意味着每个开发者的电脑上都是一个数据“备份”节点。每个开发者本机不仅检查文件的最新快照,还完整地映射了存储库,包括它的完整历史。哪一天,项目发起者的主仓服务器发生了宕机或者硬盘损坏导致部分文件丢失,只需要向项目团队其他成员的电脑中读取,就可以恢复对应的代码仓。

数据存储策略的不同

集中式管理的数据存储

从概念上讲,大多数代码版本管理系统将信息存储为基于文件的更改列表,比如:CVS、Subversion、Perforce、Bazaar 、SVN等。这些工具将它们存储的信息视为一组文件以及随时间对每个文件所做的更改进行版本标注(这通常被描述为基于增量的版本控制)。这种好处是对于每个版本的存储都是增量存储。当然缺点也很明显,就是很多存储的版本都不是完整版本。不过反正数据都存在服务器,开发者也不会看到完整的版本,所以这个缺点对于集中式管理而言,也还说得过去。

分布式管理的数据存储

既然是分布式存储,每个开发者都有项目的完整备份,就不能像集中式管理那样来玩数据存储了。于是Git引入了快照(Snapshots)的概念。

Git 认为项目的数据更像是微型文件系统的一系列快照。每次提交或保存项目状态时,Git 基本上都会拍下当时所有文件的样子,并存储对该快照的引用。为了提高效率,如果文件没有更改,Git 不会再次存储该文件,只是指向它已经存储的先前相同文件的链接。Git 将其数据视为快照流(stream of snapshots)。这是 Git 和几乎所有其他版本管理系统之间的一个重要区别。这种做法,让每个开发者都可以在本地有一个完整的项目备份。以至于绝大多数开发者的操作都是本地的,因此这也是Git速度为什么如此超凡脱俗,碾压对手的根本原因。

代码合并策略差异

集中式管理的代码合并策略

集中式代码管理的模式很简单,代码保存在共享代码仓里。你可以checkout代码进行修改,这个时候,代码仓对应的文件会打好标记。对应文件后面又有开发者想要进行修改,他(她)就要进入一种排队机制。要等待你合并完之后,他(她)再更新,再修改合并。也就是一种串行机制。所以如果一个代码责任田由多个人一同维护的话,他们之间就要做好某些事前约定,避免出现上述的尴尬情况,甚至出现不必要的代码覆盖。

分布式管理的代码合并策略

分布式管理则不同,由于开源项目的每个开发者都有一个项目官方发布的完整备份。这种完整备份,会给开发者带来几个好处:

  • 开发者会有机会了解到整个项目的全貌,或者至少可以了解到他(她)希望了解到一些关联文件的源代码到底是怎么写的。
  • 开发者会在本地离线工作,不必担心没有连上互联网无法编辑或者commit提交代码。这些工作都可以在本地完成。如果你在飞机上、或者网络不是很好的环境、或者你人在你根本不信任的公共WiFi环境下,你想干活,你根本不用连上网,你就可以离线提交代码,然后等你连上网,Git会帮你做上传(Git做的这些都是后台默默地干的,你不必关心)。
  • 开发者可以在本地建立多个分支,随便怎么折腾,不用去担心对线上的主分支有什么影响。直到开发者有信心了,他(她)自己再决定是否同意这些分支被其他人看到或者被合并到的可能(push到自己的公共代码仓)。

说到这儿,我不得不补充一下,为什么Linus Torvalds这么执着地要求本地代码仓地完备性,以及要离线工作。在2007年Google Talk上,Torvalds揭秘了他的动因。因为这个哥们特别看中他编码环境的安全性。他正在写的代码,是不希望给任何人看到的,为了保证安全性,这个服务器有3层防火墙,几乎完全和互联网隔绝。所以如果没有本地完整备份,以及大部分操作都在本地完成的话,他在那种几乎离线的状况下貌似也干不了什么。

当然,如果你的修改想要被官方接受,合入到主分支下一个版本发布,你可能要说服这个项目的集成管理员(Integration Manager),给他(她)写一封长长的翔实邮件(至少是有说服力的),告诉他(她)你改的代码有多牛逼,要求他(她)Pull你的修改。当然,你也要做好心理准备:集成管理员可能不鸟你:)因为他(她)有这个权力。

如果你足够厉害(或者幸运),反正被集成管理员看中了,他(她)会合入(merge)你的代码到官方发布的代码仓,并准备下一次发布。我还是喜欢官方发布的代码仓Git的英文名字:Blessed Repository,给这个代码仓笼罩了一种神圣的光环:)在很多小项目中,集成管理员往往就是开源项目的发起人。每个项目发起人可能在合代码的时候,心中也在默默地祈福:希望这个项目一定要成功哈:)

当然,如果你的项目复杂度上升到一定程度,一个集成管理员已经搞不定了,集成管理员就可以升级为司令员(Dictator)(编者注:我这里不能直接翻译英文,但是大家也能从这些命名中看出Linus Torvalds这类极客经常喜欢干的事情,就是自嘲。),他(她)把项目代码仓进行了拆分,比如按照某些子系统模式划分子仓。这里司令员(也就是之前的集成管理员)在之前的工作中,应该能发现某几个开发者在某些领域有特长(而他(她)也往往是根据这种观察而划分的子仓),他(她)就可以授权这些开发者负责某个子仓。这些开发者被称为副官(Lieutenant)。所有的副官其实都是一个针对自己负责子代码仓的集成管理员,按照小项目运作的方式维护对应子仓的master分支。他们也称为仁慈的司令员(benevolent dictator)。最后司令员会把副官的master分支合并到自己的master分支,合并到官方发布的代码仓(Blessed Repository)中去。

为了方便开发者的离线编程,Git还给开发者提供了一种暂存区(Staging area)的机制。这个暂存区给开发者一个再想一想的机会。也就是你的很多修改可以是先提交到暂存区里(git add)的,然后再一并提交到你的代码仓(git commit)。这种机制很暖心,当然,这个特性不应该是分布式开发特有的,在集中式开发管理环境下,这个功能也可以开发出来。

自由与约束

集中式开发和分布式开发在开发者自由与约束方面,基本策略也有差别。

集中式开发

集中式开发是制定了非常多的规则,开发者只能在这些规则下进行,甚至为了确保主分支的稳定性,很多规则已经脱离了版本管理系统本身,而是通过专业的项目经理进行额外的跟进管理,比如提交前是否进行了单元测试,集成测试,是否完整地运行了测试套件,以及要写各种文档和报告,所有代码必须有严谨的命名规范,注释规范,提交步骤,甚至还得有一个故障库关联,让开发者严格地自检自查和修正。可以这么讲吧,在集中式开发的规则下,开发者的自由是很少的,约束是很多的。总结一下,就是:规定你干的才能干,其他的都不能干!

分布式开发

分布式开发很少制定规则,有也是很少的规则,或者通过工具自动支持和实现。一个明显的规则就是开源项目必须得遵守开源许可证协议。目的是留给开发者最大程度的操作自由。当然,在分布式开发的文化下,自驱力比被安排要重要得多,因为团队可以有很多开发者,多你一个不多,少你一个不少。你要想在团队中立足,或者有所建树,就只能靠自己的打拼与贡献,是否被集成管理者,副官或者项目发起人看中。当然,由于互不相识,联系方式单一,你们的这种从不信任到信任的过程建立在邮件沟通过程中一般是漫长的,在项目开发过程中是需要不断磨合的。当然,如果某些开发者只是冲着学习的心态,看看代码,膜拜一下大神,这也是开发者的自由。总结一下,就是:规定你不能干的别干,其他的都能干!

写在最后

和Git同时代的有很多的版本管理系统,包括:SVN、CVS、Subversion、Perforce、Bazaar 、BitKeeper和Monotone。相比于其他的,BitKeeper和Monotone是去中心化的。今天上文谈到的大部分Git的优势都是围绕着集中化和分布式版本管理工具的比较,没有去刻意比较BitKeeper和Monotone。

没有使用BitKeeper,这里面有商业化的原因。当时Torvalds确实动过念头想直接用BitKeeper,这是一个专用软件,当时采用的是 BitMover 许可证,不是真正开源的,所以包括GNU 项目创始人Richard Stallman在内的一些人对使用BitKeeper表示了非常大的意见。甚至Linux 内核邮件列表中还因此爆发了激烈的口水战。Torvalds也是被逼无奈,自己直接开发了一个工具,还戏虐地称为它Git(英式英语俚语“不愉快的人”),并直接把Git通过GNU 通用公共许可证 2.0 版(开源许可证)开源,这才有了后续的故事。这算不算是一种有心栽花花不开,无心插柳柳成荫呢?就由大家来评判了。

当然你可能也还要问,那他为啥也不想用Monotone呢?Monotone也是开源的,而且Monotone也使用 SHA-1来识别修订并确保数据没有因意外损坏而更改,看似没什么大毛病啊?我们要理解Torvalds的性格,他是个追求完美的人。他对C++编写的Monotone的性能表现不满意,他认为当时的 Monotone 还没有达到像 Linux 内核开发这样大的项目所需的性能水平,所以用C语言开发了Git。

最后谈到Git能成功,Torvalds要感谢当时在Google工作的濱野純(Junio C Hamano),他慧眼识珠,把Git的维护工作交给了这位日本软件工程师牵头,而濱野純也确实没有让他失望。Git后来在濱野純的领导下,聚拢了大量的开发者一同支持和共建,不断地改进和完善Git,至今已经出了65个版本或补丁,让它在保持简洁易懂的干净界面前提下,丰富了更多的功能、插件和工具,成长为当之无愧的世界最优秀的开源版本管理软件。