卷2:第6章 Git
作者:Susan Potter,翻译:张吉
6.1 Git概述
Git能够让不同的协作者通过一个点对点的仓库网络对数据内容(通常是代码,当然不仅限于代码)进行维护。它支持分布式的工作流程,能够让数据内容临时分离,并最终合并到一起。
本章将阐述Git的内部实现是如何提供以上功能的,以及它和其他版本控制系统(VCS)的区别。
6.2 Git起源
为了更好地理解Git的设计思想,我们有必要先了解一下Git项目的发源地——Linux内核开发社区——所面临的问题。
Linux内核开发与其他商业软件项目有很大不同,因为它的开发者众多,且每个开发者的参与程度和对Linux内核代码的理解有很大差异。多年以来,内核代码一直都是以Tar压缩文件以及补丁的形式维护的,而当时的核心开发团队一直在寻找一个能够满足他们各方面需求的版本控制系统。
Git就是在这样的背景下于2005年作为一款开源软件诞生的。当时,Linux内核代码通过两种版本控制系统进行维护,BitKeeper和CVS,分别由两组核心开发团队使用。BitKeeper相较于当时颇为流行的CVS,提供了一种不同的历史展示方式。
当BitKeeper的所有者BitMover决定收回Linux内核开发人员的使用许可时,Linux Torvalds紧急开启了一个项目,也就是后来的Git。一开始,他通过编写一组Shell脚本来帮助他将邮件中的补丁按顺序应用到代码中。这组原始脚本能够在代码合并过程中迅速中断,让维护者能够进行人工干预,修改代码,然后继续合并。
从项目开始之初,Torvalds就为Git制定了一个目标——要和CVS的做法完全相反——同时还包含了以下三条设计目标:
- 支持分布式的协作流程,类似BitKeeper
- 预防代码错乱
- 高性能
这些设计目标都被实现了,我会在下文中通过解析Git的各种做法来阐述,包括在内容管理中使用有向无环图(DAG),头指针引用,对象模型,远程协议,以及Git如何追踪合并树。
虽然Git设计之初受到了很多BitKeeper的影响,但是两者还是有根本上的区别的,如Git提供了更多分布式和本地开发流程,这点是BitKeeper做不到的。Monotone,2003年启动的一个开源分布式版本控制系统,也对Git的早期开发产生了影响。
分布式版本控制系统在提供更灵活的工作流程的同时,往往会增加它的复杂程度。分布式模型的独特优点有:
- 能够线下进行增量提交
- 开发者可以决定自己的代码何时能够开放出来
- 能够线下浏览历史
- 可以将工作成果发布到不同的仓库,以不同的分支、不同的提交粒度展现出来
在Git项目的开发期间,诞生了其他三个开源分布式版本控制系统(其中Mercurial可以参见《开源软件架构》的第一卷)。这些分布式版本控制系统(dVCS)都提供了非常灵活的工作流程,这是先前的集中式版本控制系统做不到的。注意:Subversion有一款插件名为SVK,由不同的开发者维护,提供了服务器之间的同步功能。
目前流行的dVCS包括Bazaar, Darcs, Fossil, Git, Mercurial, 以及Veracity。
6.3 版本控制系统的设计
现在让我们回过头来看看Git之外的其他版本控制系统是如何设计的。通过比较他们和Git之间的区别,可以帮助我们去理解Git在架构设计中的选择。
版本控制系统通常有三项核心功能(需求):
- 保存内容
- 记录变更历史(包括具体的合并信息)
- 向协作者分发内容和变更历史
注意:第三项并不是所有版本控制系统的核心功能。
保存内容
在VCS中保存内容,最普遍的做法是保存增量的修改,或使用有向无环图(DAG)。
增量修改可以反映出两个版本之间的内容差异,以及一些额外的信息。使用有向无环图保存内容则是将特定对象构造成一种树状结构,作为某一次提交的快照保存下来(树状结构中未发生变化的对象是可以重用的)。Git使用有向无环图来保存内容,它所使用的不同对象类型会在本文的“对象数据库”一节中有所描述。
提交和合并的历史
在保存历史、记录变化方面,大部分VCS使用以下方式之一:
- 线性历史
- 有向无环图
Git使用的还是有向无环图,这次则是用来保存历史。每次提交包含了它父节点的元信息——Git中的一次提交可以拥有0个或多个父节点(理论上没有个数限制)。例如,Git仓库的第一次提交就没有父节点,而一次三头合并则有三个父节点。
Git和SVN线性提交的另一个重要区别是Git可以直接进行分支的创建,并记录下大部分合并历史。
libgit2。一开始它并不流行,直到一个名叫Vincent Marti的学生在谷歌编程夏令营中使用了它。从那以后,Vincent和Github持续对libgit2类库贡献代码,并为其他语言编写了相应类库,包括Ruby,Python,PHP,.NET,Lua,Object-C等。
Shawn Pearce还开启了一个名为JGit的BSD项目,使用纯Java语言实现,能够对Git版本库进行基本的操作。该类库现在由Eclipse基金会维护,用于Eclipse IDE的Git插件中。
还有其他一些有趣的周边项目,带有实验性质,使用各类数据源来保存Git对象,如:
- jgit_cassandra 使用Apache Cassandra作为Git对象数据库。它是一种混合型的数据源,提供了动态的BigTable式的数据模型。
- jgit_hbase 能够将Git对象保存在HBase中,一种KV型分布式数据库。
- libgit2-backends 由libgit2项目衍生而来,致力于提供其他种类的数据源,如Memcached,Redis,SQLite,MySQL。
以上这些都是独立于Git核心工具包之外的项目。
如你所见,我们可以用各种方式来使用Git,它的表现形式不再只有命令行这一种了,而是成为一种版本控制系统的协议。
在本文撰写之时,这些项目都还没有发布稳定版本,所以还是有很多工作要做,但整体看来未来是光明的。
6.10 经验教训
在软件设计中,任何一个决定都有正反两面。作为一个在日常工作中大量使用Git,并且还为Git对象数据库开发了周边软件的程序员,我觉得Git目前的组织方式非常棒。因此,下文提到的“经验教训”更多的是来自其他开发者对于Git目前设计方式的不满,主要归咎于Git核心开发者当初做出的决定。
最常见的问题在于Git相较于其他CVS不能很好地和IDE进行整合,因为Git是基于工具包设计的,整合起来会比较具有挑战性。
早期Git的实现是采用shell脚本的方式,不能很好地跨平台,特别是对于Windows操作系统。虽然我相信Git开发者不会因为这个问题而寝食难安,但这的确阻碍了Git在大型公司内的推广。现在,有一个名为Git for Windows的项目由志愿者发起,及时地将最新的Git开发成果移植到Windows平台上。
Git工具包的设计方式所带来的另一个间接影响是,他的底层命令繁多,会让初学者陷入困境,难以理解Git出错时抛出的异常信息,最后无可适从。这就使得Git在某些开发团队中的推广受到阻碍。
即便如此,我仍然对Git核心项目以及其周边项目的开发充满信心。
更多建议: