3.1 版本管理系统

    3.1.1 什么是版本管理系统

    合理、有效地利用版本管理系统是顺利进行团队开发必不可少的、最基础的工作。是否正确理解了版本管理系统的概念及其意义将直接左右团队所发布的产品的质量中最基本的部分。

    究竟什么是版本管理系统?版本管理系统是将什么时候、谁、对文件做了怎样的修改这样的信息以版本的形式保存并进行管理的系统。这里提到的文件当然包括代码,但不是说版本管理系统只能管理代码的版本。

    新建表或导入数据用的 SQL 文件、构筑中间件用的配置文件,甚至是应用程序的说明手册等,只要是文件,都可以用版本管理系统进行管理。

    下面我们来看一下使用版本管理系统所带来的便利之处。

    3.1.2 为什么使用版本管理系统能带来便利

    使用版本管理系统的优点如下所示。

    • 能够保留修改内容这一最基本的记录

    • 能够方便地查看版本之间的差异

    • 能够防止错误地覆盖别人修改的代码

    • 能够还原到任意时间点的状态

    • 能够生成多个派生(分支和标签),保留当时项目状态的截面

    ●…… 能够保留修改内容这一最基本的记录

    什么时候、谁、对文件做了怎样的修改这些信息,虽说是最基本的,但将其作为记录保留下来也是非常重要的。在发生问题时,追踪记录(提交)能够帮助查明问题的原因。

    虽然写在纸上或者使用 Excel 表格来人工进行管理也能达到同样的效果,但这样团队需要的人数就会增加。随着处理的文件种类的增加,很快这个方法就会变得不那么现实了。

    最近使用上述管理方法的项目已经越来越少见了,但是在一些工程师较少的网站制作现场等,还是会有一些人工管理的地方。如果你正好在这样的现场,就算只是为了简化记录操作,也可以考虑试着使用版本管理系统。

    ●…… 能够方便地查看版本之间的差异

    使用 svn diffgit diff 这样的命令就能方便地确认各个版本之间的差异 1 。可以输入命令,从命令行进行确认。还可以通过 TortoiseSVN2 、TortoiseGit3 、SourceTree4 这样的 GUI(Graphical User Interface)工具进行可视化确认。除此之外,还可以使用 Trac 或 GitHub 这样的基于 Web 的代码库浏览器进行确认。

    1 请注意能够简单地确认差异这一点原则上仅适用于文本文件,图片这样的二进制文件则无法简单地确认差异。虽然从技术上来说是能够找出二进制文件的差异的,但是这样的差异不是人类能够理解的,只是二进制数据之间的差异。

    2 http://tortoisesvn.net/

    3 https://code.google.com/p/tortoisegit/

    4 http://www.sourcetreeapp.com/

    能够简单地确认版本间的差异是版本管理系统的优秀特征之一。

    ●…… 能够防止错误地覆盖他人修改的代码

    说起版本管理系统和简单的表格管理的区别,首先想到的就是该功能。版本管理系统将文件的修改记录作为数据库进行管理,所以能够防止多人在同一时间修改同一文件的同一处。

    第 2 章列举了错误地把他人的修改覆盖的例子,如果合理地使用版本管理系统,就不会发生这样的事情。

    由多人修改而造成的冲突也称为 conflict 或 collision5 。为了解决冲突的问题,版本管理系统大致提供了两类机制,分别是“锁-修改-解锁模式”(以下称为锁模式)和“复制-修改-合并模式”(以下称为合并模式)。

    5 该词多用来指网络数据的冲突,所以用来指代码修改的冲突可能并不是那么的合适,但也有不少开发现场是这么称呼的。

    锁模式的做法是:在某人编辑文件期间,将文件锁住,不允许其他人对此文件进行编辑。过去的商用版本管理系统主要采用这种方式。这种方式的特点是简单,对任何人来说都是易于理解、易于操作。但是缺点在于无法多人同时进行并行开发,难以提高开发速度。

    合并模式不会对文件加锁。开发者下载代码的备份进行编辑,然后再提交到代码库。提交时确认差异(diff),如果存在差异,要先对差异进行合并,然后再提交。近几年的版本管理系统基本上都采用这种方式。

    CVS(Concurrent Versions System)、Subversion、Git 这些有名的版本管理系统都属于合并模式。该模式的优势在于不对文件加锁,多人可以同时获取最新的代码而不必等待他人的作业,能够并行地推进开发。和其他团队成员的修改产生冲突时,可以通过合并来消除冲突。

    ◆◆◆

    无论是锁模式还是合并模式,合理使用版本管理系统都能够防止无意识地覆盖他人修改的代码。

    专栏 锁模式和合并模式
    如上所述,版本管理系统解决冲突的方式大致可分为两种,即锁模式和合并模式。
    VSS(Visual Source Safe)和 Peroforce、PVCS6 这样的专有商用版本管理系统多采用锁模式 7 。CVS、Subversion、Git、mercurial 等 OSS(Open Source Software,开源软件)的版本管理系统多采用合并模式。
    两种方法都有各自的长处和短处。近年来使用 Subversion 和 Git 的开发现场较多,合并模式也逐渐成为主流。但在大约 10 年之前,锁模式的商用版本管理系统一直都占据着主流位置。
    当时开源软件还没有像今天这样被大量用于开发现场,因此比较多的是采用商用的版本管理系统。一些由那个时代的工程师主管的开发现场,至今好像依然在使用 VSS 这样的锁模式的版本管理系统。
    有的开发现场虽然使用了 Subversion 或 Git,但思维方式还是基于锁模式,因此还是无法合理运用版本管理系统。
    锁模式的情况下,在某人编辑文件期间,文件将被锁住,所以理论上不会发生冲突。但另一方面,多人同时并行编辑同一文件原则上也变得不可能(图 3.a),这样就大大影响了开发速度。如果有人将文件锁住后去休假了,那么开发就无法进行下去了 8 。
    3.1 版本管理系统 - 图1
    图 3.a 锁模式下无法同时并行地进行编辑
    与之相对,合并模式不需要对文件加锁,所以同一文件可以同时由多人并行地进行开发(图 3.b)。提交时会提醒你确认差异并进行合并,与他人编辑的地方发生重合时也会检测出冲突(图 3.c)。
    3.1 版本管理系统 - 图2
    图 3.b 合并模式下能够同时并行地进行编辑
    3.1 版本管理系统 - 图3
    图 3.c 通过更新(update)来统一本地机器上的环境
    锁模式的情况下,文件在被编辑期间是无法签出(check out)的,所以不会发生冲突。合并模式下任何时候都可以签出文件,但随之而来的是,在你签出文件后到提交前这段时间,如果有人进行了提交,那么你就需要将这部分修改合并到本地代码后再进行提交。
    这时,如果文件编辑的地方重合的话,版本管理系统会检测到冲突,并显示请手动修改冲突这样的错误消息。这是合并模式中版本管理系统的正常动作,但习惯了锁模式的人会对此感到非常奇怪。
    因此,一些维护旧的开发环境的团队有时会根据锁模式和合并模式的这些差异,固执地认为 Subversion 和 Git 无法锁住文件,从而造成冲突频发,无法有效地管理代码。这样的现象在一些习惯了锁模式的开发现场尤为显著。
    实则恰恰相反,锁模式的版本管理系统由于效率较低,无法合理地进行版本管理的情况较多。
    锁模式的版本管理系统在文件加锁的情况下拒绝其他人员对此文件进行编辑,这样的确不会造成冲突。但实际开发中往往不允许这样“慢条斯理”,于是开发人员就会无视文件被锁住,独自在本地进行开发,等待锁解除后再手动合并并提交。
    各个版本管理系统可能有所差异,但多数采用锁模式的产品都没有自动检查差异并进行合并或者检测冲突等功能,即使有也非常弱,因此容易发生手动合并时不小心将他人的修改覆盖的情况。并且加锁也不能说是绝对的,也有将锁强制解除并覆盖提交的功能。
    举一个笔者亲眼所见的例子:一位习惯了锁模式的开发人员在使用 Subversion 这样的系统时,因为讨厌发生冲突,所以没有使用 svn update9 ,而是每次都将代码下载到开发目录以外的目录中,再复制自己编辑过的代码替换原有代码后进行提交。这种做法真是令人吃惊又哭笑不得。
    这样的做法的确不会发生冲突,但随之而来的是将频繁发生他人的修改被覆盖的事情。实际上那个开发现场发生的 bug 和退化实在太多,让人觉得项目已经处于崩溃的边缘。而且笔者还记得当时他还被视为现场比较有经验的开发人员,所以事态就更为复杂了。
    如果大家的工作单位现在还在使用锁模式的版本管理系统,或者像上面那样虽然使用了合并模式的系统但使用方法有误的话,那么您可以先和周围的同事聊一下本专栏的话题,并试着劝说他们改变用法。
    但是也有例外,例如在管理图像这种二进制文件的情况下,因为它和代码这样的文本文件完全不同,无法进行合并,所以用加锁的方法效率往往会高一些。此外,在制作用于重要发布的包时,如果作业时间长达数小时,有时就会特意加锁以保证这段时间内绝对没有其他人员的修正加进来。虽然近年来合并模式已经成为主流,但大多数的版本管理系统对于锁模式也是支持的。
    使用版本管理系统时,理解锁模式和合并模式这两种版本管理系统的差异并合理使用,这才是最重要的。

    6 专有软件(proprietary software),和它相对的是自由软件。——译者注

    7 这些产品现在也已经具备了包括合并模式在内的各种先进的功能。

    8 实际上版本管理系统一般都有强制解除锁的功能,但这需要管理员权限,操作起来也比较麻烦。

    9 从代码库中取得最新的代码合并到本地的命令。Git 的情况下是 git pull origin master

    ●…… 能够还原到任意时间点的状态

    因为保存着过去的修改记录,所以在发生任何问题的情况下,例如在发生退化时或者新添加的功能不再需要时,理所当然地能够立即回退到过去任意时间点(的版本)。

    不同的版本管理系统对于版本的思考方式也有所不同。从历史上来说也大致可分为两类:基于文件和基于变更集(changeset)。

    例如 CVS 是基于文件的版本管理,即对每一个文件分别进行版本管理。与之相对应,Subversion 及之后(包括 Git)的主要的版本管理系统都是基于变更集的。变更集将对多个文件的一次修改看作是理论上的一个单位。基于变更集的版本管理系统就是以此单位来分配版本号的。

    基于文件的情况下,如果要取得过去某个时间点的版本,就需要集齐每一个文件所对应的正确的版本,例如文件 A 是 1.1 版、文件 B 是 1.9 版、文件 C 是 2.3 版。与之相对应,基于变更集的情况下,因为是将修改合并后进行管理的,所以要取得过去某个时间点的版本时,只需要知道其版本号就能够完整地获取整个项目。

    如上所述,不同的版本管理系统对于版本管理的思考方式虽然有所不同,但都可以随时回退到过去的任意时间点。这是使用版本管理系统的一个很大的优势。

    专栏 基于文件和基于变更集
    版本管理的思考方式从历史上来看有基于文件和基于变更集这两种。
    VSS(Visual Source Safe)这样历史悠久的商用产品,以及开源软件中 CVS 这样较老的工具,都是以基于文件的方式来实现的。
    那些太习惯于使用 VSS 和 CVS 而不熟悉 Subversion 及其以后的工具的开发人员,可能是因为他们以基于文件的方式来理解版本管理,所以往往会胡乱地把提交的粒度分得很细——将文件一个一个地进行提交。因为 VSS 和 CVS 是基于文件的方式来管理版本的,所以逐个提交文件和打包一起提交从结果上来看是完全一样的。甚至可以说在 VSS 和 CVS 的情况下,将文件一个个地分别提交更为直观。
    而 Subversion 及其以后的版本管理系统则以变更集为单位进行版本管理。因此如果将文件一个个地分开提交,变更集也会被分为多个,这样一来,变更集原本具有的能够为解决某个问题而进行修正的意义就完全丧失了。
    版本管理系统进化为能够对变更集进行管理的意义在于让每一个版本都有自己的含义。因此含义相同的修改就应该置于同一个变更集中一起提交。
    如果开发团队中有成员一直使用 VSS 或 CVS 等工具,并且对其他的版本管理系统一无所知的话,可以向他介绍本专栏的内容,并试着让他重新认识版本管理系统的使用方法。

    ●…… 能够生成多个派生(分支和标签),保留当时项目状态的断面

    版本管理系统有分支管理和标签管理的功能。

    第 2 章的案例分析中列举了无法高效地在新功能开发和已发布版本的 bug 修正之间切换的问题 10 ,其实只要合理地进行分支管理,就能够解决这样的问题。通过使用分支管理功能,项目就可以在多个方向上创建分支,例如可以分别创建新功能开发分支和已发布版本的分支。

    10 请参考 2.2 节。

    各个版本管理系统在实现的细节上可能有所差异,但通常都具备对分支进行合并的功能。这个功能越是完善,就越有勇气去挑战困难的开发。

    不同的版本管理系统在分支切换、合并的速度以及正确性方面是有差距的,近年来 Git 是在分支管理方面做得最好的。

    标签管理是能够对文件或者变更集任意命名(打上标签)的功能。利用这个功能可以对过去任意时间点的系统快照进行管理。

    可以像阿尔法版、贝塔版、发布版这样,或者像版本 0.1、0.2 这样根据每个产品的外部版本号 11 来打上标签,一般都是在到达某节点时为项目打上标签的。不同版本管理系统的标签功能在细微的动作或规格上可能有所差异,但思考方式大致是相同的。

    11 这里并不是指版本管理系统内部的版本号。