版本控制
一提到版本控制,程序员们就会想到他们工作中用到的各种源代码控制工具,Subversion、Git、Mercurial……实际上源代码之外的许多其他文件也会有版本控制的需要。而版本的想法和对其的管理更是在计算机出现之前的印刷以至手写时代就一直存在。版本是作品生长衍变的徵象。手写和印刷时代的文字作品之还仅是记录它的修改生长。及至计算机和电子文件出现,文件大小、内容复杂程度、版本更新速度都空前增加。除了单纯记录不同版本,版本控制又被提出许多新的功能需求:恢复到任一历史版本、比较两个版本之间的差异、创建多个分支版本、合并不同版本。尤其是对程序员而言,这些功能对于调试错误、了解代码演变、多人合作以及为多个用户和目的开发等任务都大有裨益。
Git
与其他软件工具一样,版本控制系统(VCS即Version Control System,或RCS即Revision Control System)也伴随着实际需要不断演化生长。起初所有被管理文件的数据都保存在本机的存储库(repository)里,后来进化成客户端服务器模式,存储库保存在服务器上,版本控制系统从而由单机版变成了可多人合作的网络版。这样的架构唯一的缺点是只有服务器上保有文件的完整历史数据,每个客户端仅有当前工作的版本。这样在多个版本之间的切换、比较、合并等操作都需要连接服务器查询和传输数据。此外一旦服务器上的存储库被损坏,所有的历史积累将无法挽回。为了更加适应多人自由开发的环境,分布式版本控制系统(DVCS即Distributed Version Control System)诞生了。就像分布式数据库系统在不同节点上有单个数据库的多个副本一样,每个DVCS用户机器上都保存了完整的存储库。在无联网的单机状态下,程序员也能使用版本控制的所有功能,联网时则可以同服务器上的存储库同步。
Git就是一款优秀的分布式版本控制工具。它最初是为了Linux的程序员开发系统而编写的,现在已经成为,特别是开源领域,最流行的版本控制工具。GitHub即依讬此系统成为最大的源代码托管网站。与前驱相比,Git有一些特别的理念,例如每个版本保存的不是文件的差异,而是完整的文件(如果某个文件没有改变,则包含对上个版本文件的引用)。网上有很多优秀的介绍和教材,例如Git官方网站上的文档http://www.git-scm.com/doc还有这篇通俗易懂、图文并茂的git concepts simplified。
Domino
在Notes杂谈系列文章里,我反复提到和讨论了Domino作为专有的(proprietary)开发平台的独特性,就像与世隔绝的科莫多岛上孕育出奇异的科莫多龙一样。在版本控制领域,Domino也有独特的“国情”。
困难
表单视图等设计元素都是以二进制格式保存在数据库里。与文本格式的文件相比,二进制文件在比较版本差别和合并方面都有困难。DXL的发明使得在设计元素和XML文件之间相互转换成为可能。但是长时间来DXL有很多bug没解决,不能保证相互转换的精度。直到R9面世,IBM才对DXL有了信心,在Designer里加入基于它的版本控制特性。
在主流的采用某种编程语言开发的环境里,文件都是文本格式,版本控制系统只需处理文件和文件夹。Domino平台下要达到版本控制系统的这个初始环境就大费周章。Designer先将数据库里的设计元素映射到一个虚拟的文件系统(virtual file system)里,例如一个表单Person就会被映射到数据库路径下的文件Forms\Person.form。Desinger迁移到Eclipse平台上后,一个Domino应用程序作为一个Java项目来校验、编译,利用Eclipse的种种视图和编辑器来显示和修改设计元素,Eclipse读写的就是这个虚拟的文件系统。在设计元素到文件的转换过程中,表单、大纲这些以用户界面为主的元素都经过DXL转换成XML文件;脚本库(Java脚本库是个例外)、Java设计元素、XPages这类基本为了容纳代码的设计元素被解析成两个甚至更多文件,一类是容纳代码和配置本身的文本文件,另一个是记录设计元素元数据的metadata文件。例如一个名为task的XPage会被转换成task.xsp和task.xsp.metadata,前者的内容就与在XPages编辑器的源代码视图里看到的一模一样。
为了让Designer外部的版本控制工具能够访问到这个虚拟文件系统,R9增加了一组features,能够设定一个本机的文件夹,使其中的内容与虚拟文件系统同步。如此,版本控制系统面对的就是与其他项目无异的文件系统,它对此文件系统的内容做的任何改变,如切换版本、合并又会被Designer检测到并自动转换回设计元素。
经过这种对用户透明的设计元素与文本文件之间的自动双向转换,就能对Notes应用程序运用任何版本控制工具。不过二进制格式带来的问题并没有完全解决。版本控制工具可以比较某个表单两个版本之间的差异,但其结果是Notes程序员不习惯和不易阅读的XML。原因就是Domino并没有被改造成以XML的格式来记录设计元素,DXL只是勉强将它们输出为XML,许多内容仅仅是以<rawitemdata>元件将二进制的内容以字符编码显示。例如表单的布局信息记录在$Body域里,转换成XML后看不到HTML那样的DIV、TABLE,而是一堆字符编码。
<item name=‘$Body‘sign=‘true‘><rawitemdata type=‘1‘>gQKC/1oAAQAAAAAAAAAAAKAFAACgBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAJSHAAAAAAAAAAAAAAAAAQAAAAQAAAC7/zIAAQAYANLVpGPEnCQAAAAWAAEABwBbQWRtaW5dAOcAdQIDAAcADAAFAAkwUzBFAIMEAQCF/xUAAQAACkFkbWluV3JpdGVyczoA3QYAAIr//v9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACK/1QAjwABBQYgAgAAAAAAAAIBAAAKJAAAAAAAAAAMAAAAAAAkAAAAFgD0AAEABwBbQWRtaW5dAB8CAwAHAAwABQAJMFMwRQBBZG1pbldyaXRlcnPeBgAAiv+F/wgAAQAACoECgwQBAIX/EAABAAAKV3JpdGVyczrdBgAAiv/+/1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA……</rawitemdata>
这样的XML是有名无实的,与二进制格式差别不大,不是供人阅读的,只有转化回设计元素才能由Notes客户端或服务器解读。版本控制工具比较出的差异若是位于这样的字符编码实体内,程序员根本无法判断其含义。同理,同一设计元素的合并遇到冲突时,程序员也无法直观地了解内容以化解。只有对上举代码类设计元素,比较与合并功能才能发挥威力。
综上可见,Domino应用程序的版本控制,最多只在单人开发时好用,团队开发场景下常见的比较和合并开发人员各自的分支版本都会时时受限。
需求
Domino应用程序的源代码管理从未被很好地解决过,但是Notes程序员似乎不以为意,甚至有很多人从来没意识到Git之类的版本控制系统之需求。原因同样来自Domino平台的特殊性。作为一个快速开发环境,Domino提供了现成的用户管理、权限管理、与邮件的集成以及视图等等基础功能(infrastructure),省去了大量的用户代码。另一方面,数据库与体系的限制使得Domino只适于做某些领域的开发,做出的系统也不会像通用语言如Java的大型项目那样庞大。所以一个Domino应用程序的代码量较少,往往都是一个程序员包办。最后,Domino应用程序的代码高度耦合,XPages引入之前,很多系统的数据设计、用户界面、业务逻辑全都混在一起,即使采用XPages,还是要有良好的设计才不至把视图层和业务层混合。代码模块化程度不高,也给多人同时开发造成障碍。所以即使在高度依赖Domino的大公司,需要团队开发的项目也罕见(当然一个项目团队有多人分别负责需求、测试和开发是很常见的),即使是这样的项目,也是多个数据库分人负责。
总之,一方面Domino应用程序规模小,可视化程度高,多人合作开发的需求不大;另一方面在Domino中应用版本控制系统过去困难重重,现在效果不充分。这并不是说,Domino应用程序就一直没有过版本控制,许多公司也开发了内部使用的系统管理Domino数据库的版本。但这类系统是囫囵吞枣,将整个数据库作为附件或文件保存。既无法高效地切换版本,更不能自动比较差异和合并代码。Domino平台与版本控制系统绝缘了几十年,在可见的将来也会继续独行下去。
Domino+Git
上面分析了,Domino Designer自R9起,对一个应用程序应用外部的版本控制系统成为可能。国外有一些热心人还开发了Designer与Subversion等工具集成的插件。不过我在上面已经泼了冷水,这样的版本控制,单人开发时用来管理历史设计可以,要发挥版本控制的更多功用就会遇到掣肘。这一节就介绍一下Domino+Git的使用方法。
在导航视图内选中要启用版本控制的应用程序,在右键菜单中选择Team Development,子菜单中的第一项Set Up Source Control for this Application,在随后的对话框里选定一个文件夹用来容纳该应用程序被转换输出的文件,如D:\Lotus\Notes\Data\workspace\task。Domino一侧的设置就完成了。该子菜单中的其他几项顾名思义都很好理解。
接下来就可以用Git做上面所说的能做的事。运行Git控制台(Git shell),转入D:\Lotus\Notes\Data\workspace\task(在其中手工创建一个过滤文件.gitignore)。
git init:创建了一个新的存储库。
git add .:将所有文件编入index。
git commit –m initial:第一次提交。你的Domino应用程序已经开始被Git管理了。
git branch:可以看到默认创建的master分支。
在进行版本管理的过程中,有意思的是可以从一组文件的视角来看待Notes应用程序的组成,以及每次修改带来哪些文件的变化。有兴趣的朋友还可以将自己的存储库推送到GitHub上。