浅谈svn和git

在这里有必要纠正一下Git的发音。一种错误是按照单个字母来发音,另外一种更为普遍的错误是把整个单词读作“技特”,实际上Git中字母G的发音与下列单词中的G类似:GOD、GIVES、GREAT、GIFT。因此Git正确的发音应该听起来像是“歌易特”。

一、版本控制的前世今生

版本控制系统是一个另类。虽然其历史并不短暂,也有几十年,但是它的演进进程却一直在社会的各个角落重复着,而且惊人的相似。有的人从未使用甚至从未听说过版本控制系统,他和他的团队就像停留在黑暗的史前时代,任由数据自生自灭。有的人使用着有几十年历史的CVS或其改良版Subversion,让时间空耗在网络连接的等待中。以Git为代表的分布式版本控制系统已经风靡整个开源社区,正等待你的靠近。

1.1 黑暗的史前时代

实际上,即便是在CVS出现之前的“史前时代”,也已经有了非常好用的用于源码比较和打补丁的工具:diff和patch,它们今天生命力依然顽强。大名鼎鼎的Linus Torvalds先生(Linux之父)也对这两个工具偏爱有加,在1991~2002年之间,Linus一直顽固地使用diff、patch和tar包管理着Linux的代码,虽然不断有人提醒他有CVS的存在。
diff 比较两个文件

1
$ diff -u hello world > diff.txt

diff 比较两个文件

1.2 CVS——开启版本控制大爆发

CVS(Concurrent Versions System)诞生于1985年,是由荷兰阿姆斯特丹VU大学的Dick Grune教授实现的。当时Dick Grune和两个学生共同开发一个项目,但是三个人的工作时间无法协调到一起,迫切需要一个记录和协同开发的工具软件。于是Dick Grune通过脚本语言对RCS(一个针对单独文件的版本管理工具)进行封装,设计出有史以来第一个被大规模使用的版本控制工具。Dick教授的网站上介绍了CVS的这段早期历史。

“在1985年的一个糟糕的秋日里,我在校汽车站等车回家,脑海里一直纠结着一件事——如何处理RCS文件、用户文件(工作区)和Entries文件的复杂关系,有的文件可能会缺失、冲突、删除,等等。我的头有些晕了,于是决定画一个大表,将复杂的关联画在其中,看看出来的结果是什么样的……”

1986年Dick通过新闻组发布了CVS,1989年Brian Berliner用C语言将CVS进行了重写。
从CVS的历史可以看出,CVS不是设计出来的,而是被实际需要“逼”出来的,因此根据“实用为上”的原则,借用了已有的针对单一文件的版本管理工具RCS。CVS采用客户端/服务器架构设计,版本库位于服务器端,实际上就是一个RCS文件容器。每一个RCS文件以“,v”作为文件名后缀,用于保存对应文件的每一次更改历史。RCS文件中只保留一个版本的完全拷贝,其他历次更改仅将差异存储其中,使得存储变得非常有效率。

cvs的简易结构图

  • 服务器端松散的RCS文件导致在建立里程碑或分支时效率不高,服务器端文件越多,速度越慢。
  • 分支和里程碑不可见,因为它们被分散地记录在服务器端的各个RCS文件中。
  • 合并困难重重,因为缺乏对合并的追踪,从而导致重复合并,引发严重冲突。
  • 缺乏对原子提交的支持,会导致客户端向服务器端提交不完整的数据。
  • 不能优化存储内容相同但文件名不同的文件,因为在服务器端每个文件都是单独进行差异存储的。
  • 不能对文件和目录的重命名进行版本控制,虽然直接在服务器端修改RCS文件名可以让改名后的文件保存历史,但是这样做实际上会破坏历史。

CVS的成功导致了版本控制系统的大爆发,各式各样的版本控制系统如雨后春笋般诞生了。新的版本控制系统或多或少地解决了CVS版本控制系统存在的问题。在这些版本控制系统中,最典型的就是Subversion(SVN)。

1.3 SVN——集中式版本控制集大成者

Subversion,由于其命令行工具名为svn,因此通常被简称为SVN。SVN由CollabNet公司于2000年资助并开始开发,目的是创建一个更好用的版本控制系统以取代CVS。SVN的前期开发使用CVS做版本控制,到了2001年,SVN已经可以用于自己的版本控制了。
svn的简易结构图
SVN的每一次提交,都会在服务器端的db/revs和db/revprops目录下各创建一个以顺序数字编号命名的文件。其中,db/revs目录下的文件(即变更集文件)记录了与上一个提交之间的差异(字母A表示新增,M表示修改,D表示删除)。在db/revprops目录下的同名文件(没有在图1-2中体现)则保存着提交日志、作者、提交时间等信息。这样设计的好处有:

  • 拥有全局版本号。每提交一次,SVN的版本号就会自动加一。这为SVN的使用提供了极大的便利。回想CVS时代,每个文件都拥有各自独立的版本号(RCS版本号),要想获得全局版本号,只能通过手工不断地建立里程碑来实现。
  • 实现了原子提交。SVN不会像CVS那样出现文件的部分内容被提交而其余的内容没有被提交的情况。·文件名不受限制。因为服务器端不再需要建立和客户端文件相似的文件名,于是,文件的命名就不再受服务器操作系统的字符集和大小写的限制。
  • 文件和目录重命名也得到了支持。
    *
    SVN最具有特色的功能是轻量级拷贝,例如将目录trunk拷贝为branches/v1.x只相当于在db/revs目录中的变更集文件中用特定的语法做了一下标注,无须真正的文件拷贝。SVN使用轻量级拷贝的功能,轻松地解决了CVS存在的里程碑和分支的创建速度慢又不可见的问题,使用SVN创建里程碑和分支只在眨眼之间。SVN在版本库授权上也有改进,不再像CVS那样依赖操作系统本身对版本库目录和文件进行授权,而是采用授权文件的方式来实现。

但是,相对于CVS,SVN在本质上并没有突破,都属于集中式版本控制系统。即一个项目只有唯一的一个版本库与之对应,所有的项目成员都通过网络向该服务器进行提交。这样的设计除了容易出现单点故障以外,在查看日志和提交数据等操作时的延迟,会让基于广域网协同工作的团队抓狂。除了集中式版本控制系统固有的问题外,SVN的里程碑和分支的设计也被证明是一个错误,虽然这个错误的设计使得SVN拥有了快速创建里程碑和分支的能力,但是这个错误的设计也导致了如下的更多问题。

项目文件在版本库中必须按照一定的目录结构进行部署,否则就可能无法建立里程碑和分支。

我在项目咨询过程中见过很多团队,直接在版本库的根目录下创建项目文件。这样的版本库布局,在需要创建里程碑和分支时就无从下手了,因为根目录是不能拷贝到子目录中的。所以SVN的用户在创建版本库时必须遵守一个古怪的约定:先创建三个顶级目录/trunk、/tags和/branches。

创建里程碑和分支会破坏精心设计的授权。

SVN的授权是基于目录的,分支和里程碑也被视为目录(和其他目录没有分别)。因此每次创建分支或里程碑时,就要将针对/trunk目录及其子目录的授权在新建的分支或里程碑上重建。随着分支和里程碑数量的增多,授权愈加复杂,维护也愈加困难。

svn 的归宿

2009年年底,SVN由CollabNet公司交由Apache社区管理,至此SVN成为了Apache组织的一个子项目。这对SVN到底意味着什么?是开发的停滞?还是新的开始?结果如何我们将拭目以待。

1.4 Git——Linus的第二个伟大作品

Linux之父Linus是坚定的CVS反对者,他也同样地反对SVN。这就是为什么在1991-2002这十余年间,Linus宁可以手工修补文件的方式维护代码,也迟迟不愿使用CVS的原因。我想在当时要想劝说Linus使用CVS只有一个办法:把CVS服务器请进Linus的卧室,并对外配以千兆带宽。2002年至2005年,Linus顶着开源社区精英们口诛笔伐的压力,选择了一个商业版本控制系统BitKeeper作为Linux内核的代码管理工具。BitKeeper不同于CVS和SVN等集中式版本控制工具,而是一款分布式版本控制工具。分布式版本控制系统最大的反传统之处在于,可以不需要集中式的版本库,每个人都工作在通过克隆建立的本地版本库中。也就是说每个人都拥有一个完整的版本库,查看提交日志、提交、创建里程碑和分支、合并分支、回退等所有操作都直接在本地完成而不需要网络连接。每个人都是本地版本库的主人,不再有谁能提交谁不能提交的限制,加上多样的协同工作模型(版本库间推送、拉回,以及补丁文件传送等)让开源项目的参与度有爆发式增长。
2005年发生的一件事最终导致了Git的诞生。在2005年4月,AndrewTridgell(即大名鼎鼎的Samba的作者)试图对BitKeeper进行反向工程,以开发一个能与BitKeeper交互的开源工具。这激怒了BitKeeper软件的所有者BitMover公司,要求收回对Linux社区免费使用BitKeeper的授权。迫不得已,Linus选择了自己开发一个分布式版本控制工具以替代BitKeeper。[此处只有膜拜orz]
以下是Git诞生过程中的大事记:

·2005年4月3日,开始开发Git。
·2005年4月6日,项目发布。
·2005年4月7日,Git就可以作为自身的版本控制工具了。
·2005年4月18日,发生第一个多分支合并。
·2005年4月29日,Git的性能就已经达到了Linus的预期。
·2005年6月16日,Linux内核2.6.12发布,那时Git已经在维护Linux核心的源代码了。

Linus以一个文件系统专家和内核设计者的视角对Git进行了设计,其独特的设计让Git拥有非凡的性能和最为优化的存储能力。完成原型设计后,在2005年7月26日,Linus功成身退,将Git的维护交给另外一个Git的主要贡献者Junio C Hamano,直到现在。
最初的Git除了一些核心命令以外,其他的都用脚本语言开发,而且每个功能都作为一条独立的命令,例如克隆操作用git-clone,提交操作用git-commit。这导致Git拥有庞大的命令集,使用习惯也和其他版本控制系统格格不入。随着Git的开发者和使用者的增加,Git也在逐渐演变,例如到1.5.4版本时,将一百多个独立的命令封装为一个git命令,使它看起来更像是一个独立的工具,也使Git更贴近于普通用户的使用习惯。
经过短短几年的发展,众多的开源项目都纷纷从SVN或其他版本控制系统迁移到Git。虽然版本控制系统的迁移过程是痛苦的,但是因为迁移到Git会带来开发效率的极大提升,以及巨大的效益,所以很快就会忘记迁移的痛苦过程,而且很快就会适应新的工作模式。在Git的官方网站上列出了几个使用Git的重量级项目,每一个都是人们耳熟能详的,除了Git和Linux内核外,还有Perl、Eclipse、Gnome、KDE、Qt、Ruby on Rails、Android、PostgreSQL、Debian、X.org,当然还有GitHub的上百万个项目。Git虽然是在Linux下开发的,但现在已经可以跨平台运行在所有主流的操作系统上,包括Linux、Mac OS X和Windows等。可以说每一个使用计算机的用户都可以分享Git带来的便利和快乐。