现象,近年来,我们对版本控制工具的关注点似乎正在改变.起初,我们主要也是唯一的目的就是对代码进行监控,使我们能够安全的返回到旧的版本,以便我们能够诊断代码中的问题.后来,我们的关注点更侧重于如何使人与人之间的合作更为顺畅. 这个关注点并不是要取代对代码的监控,而是以代码监控为基础,并建立于其上的.现在,我们又越来越关注使用这些工具来描述代码的变更,因此就出现了对于重写代码历史命令(history rewriting command)的需求.当然,对代码变更的描述也同样需要建立在前两种关注点之上.
我们可以把版本控制工具的应用分为六个层次:
0. 没有版本控制
完全没有版本控制的解决方案,或者就使用一个共享的文件系统,并对其做定期的备份.一个开发人员,或者最多几个开发人员在没有工具的情况下共享代码,其面临的风险可想而知:
- 在任何时刻,代码可能都是不兼容的.
- 代码可能会由于开发人员的错误面丢失.
- 如果开发人员想要改写覆盖他人所做的修改,那再容易不过了.
1. 初步的探索
- 开发人员拥有了网络上的工作空间,他们无法在线下工作.
- 运行一次代码构建也许就意味着有时间可以去好好吃一顿了.
- 重构即使能够进行,也慢的要死.
- checkout代码可能需要一整夜.
- checkin代码也很慢.
- 没有原子提交.
- Branching和Tagging操作的代价昂贵.
- 个人或是本地建立branch就意味着再次的checkout.
- 集中式的,而不是公布式的.
- 合并点的跟踪很慢或是根本无法使用.
- 这时的工具还没有办法理解重命名的合并.
- 代码库有时会崩溃,需要较高的专家/开发人员比例,如1:10.
这时的工具有了基本的版本控制功能,如checkout,版本记录和锁文件.通常这就意味着开发人员在同一份代码上工作,而代码的同步就会依赖于每个人代码文件的锁状态.这种工具在扩展和长期工作上都会有问题.对资源的重命名难到几乎不可能完成.Branching和Tagging操作则会需要同时操作三份代码的权限,而且可能还需要一个宰好的或者两柱香.如, VSS.
2. 笨拙
- 开发人员有了本地的拷贝,并且可以在线下工作.
- 本地的文件系统意味着构建的速度大大提升.
- 重构的时间只够喝杯茶了.
- checkout的速度已经非常快了.
- checkin也许还是慢一点.
- 仍然没有原子提交.
- Branching和Tagging代价仍然昂贵.
- 集中式的,不是分布式的.
- 合并点的跟踪很慢或是根本无法使用.
- 没有办法合并重命名文件,需要在提交前使用一些扩展的跟进冲突解决机制.
- 代码库有时会崩溃,专家/开发人员比例已经得到优化,如1:20.
如CVS和TFS.
3. 基本成型
- 开发人员拥有本地拷贝并且可以在线下工作.
- 在本地文件系统上可以进行快速的构建.
- 可以快速的重构.
- checkout和checkin的速度都会非常的快.
- 终于有了原子提交.
- 轻量级的Branching和Tagging操作.
- 基本的合并操作.
- 个人/本地的branching操作仍然需要再次checkout.
- 因为仍然是集中式的,而不是分布式的.
- 基本的合并点追踪.
- 没有办法合并重命名文件,需要在提交前使用一些扩展的跟进冲突解决机制.
- 代码库有时会崩溃,专家/开发人员比例已经很低,如1:100.
如Subversion.
4. 有效并且可靠
- 开发人员拥有本地拷贝并且可以在线下工作.
- 在本地文件系统上可以进行快速的构建.
- 可以快速的重构.
- checkout和checkin的速度都会非常的快.
- 无操作的代码同步和更新非常的快速.
- 终于有了原子提交.
- 轻量级的Branching和Tagging操作.
- 高级的Branching和合并操作.
- 个人/本地的branching操作仍然需要再次checkout
- 因为仍然是集中式的,而不是分布式的.
- 完善的合并点追踪.
- 合并重命名文件只能通过配置好的branch映射来实现,否则就需要在提交前进行修订.
- 代码库很少会崩溃,专家/开发人员比例非常低,如1:1000.
如Perforce.
5. 高速,无形,高度可用
- 开发人员拥有本地拷贝并且可以在线下工作.
- 在本地文件系统上可以进行快速的构建.
- 可以快速的重构.
- checkout和checkin的速度都会非常的快.
- 无操作的代码同步和更新非常的快速.
- 终于有了原子提交.
- 轻量级的Branching和Tagging操作.
- 高级的Branching和合并操作.
- 非常高效的个人/本地branching操作.
- 分布式的,而不是集中式的.
- 完善的合并点追踪.
- 无缝合并重命名文件,无需任何配置.
- 代码库很少会崩溃,专家/开发人员比例几乎为零,如1:10000.
如Git和Mercurial.
通过前面版本控制工具的演化过程,我们基本上可以看到分布式工具的特点和优势了.相对于以往的客户-服务器端的集中式系统,它所采用的是一种P2P的方式.客户端不再需要从一个单一的中央代码库同步代码,每一个端点的代码的拷贝都是真正的代码库.分布式的版本控制系统是通过端点之间交换补丁(patch)的方式来同步代码的,而这种方式就决定了分布式系统与集中式系统的几个重要的区别:
- 默认情况下,没有标准的代码库参照;只有工作代码的拷贝.
- 由于不需要与中央服务器进行通信,因此一般的操作(如提交,查看历史和还原修改等)的执行速度非常快.只有在向其它端点push代码更改或者从其它端点pull代码更改的时候才会需要进行通信.
- 每一份代码拷贝都可以作为代码库及其更改历史的一份远程备份,这就为数据丢失提供了天然的保护.
- 鼓励测试性的branch - 创建或者销毁branch的操作简单而且快速.
- 同伴之间的合作变得非常容易
现在的项目用的是Subversion做版本控制,但是我们自己用的是git-svn,这里主要通过这两种有代表性的工具来比较一下集中式和分布式工具的优势.
Subversion提倡单一的中央代码库模型,不提倡大规模的branching.在一个使用持续集成的环境中,其实也就是我们每天工作的环境,这个模型是非常合适的.这也是Subversion为什么这么流行,应用范围这么广的原因之一.
虽然分布式的系统使你拥有足够的灵活性来自己安排自己的工作流程,但是其实大多数人的工作模式仍然是使用持续集成,这也就意味着需要一条可以共享的代码库主线.现在的版本控制系统已经有了神奇的合并工具,但这些合并仍然只限于文本.所以对于语义上的一致,仍然需要持续集成来保证.结果就是即使一个团队在使用分布式的版本控制系统,他们仍然会需要一个主版本库.
即使如此,分布式系统仍拥有一些SVN无法提供的体验.
- 在分布式管理系统中,你可以在自己本地磁盘上拥有代码库的完整拷贝,对代码库的操作不需要通过网络向中央服务器进行请求,因此速度会非常的快.特别是你在进行查看日志,与旧版本代码进行比较或者其它需要完整代码库的操作时,这种速度上的改善会非常明显.对于集中式的系统,在局域网内你也许只会觉得有点慢,但如果当你工作在一个分布式的项目中,你的代码库在另一个大洲的时候,这就会是非常大的问题了.
- 如果你经常在四处奔走,无法随时与代码库建立网络连接,那么一个分布式的管理系统会使你可以随时与代码库一起工作.你可以随时随地提交你的工作,浏览历史,并且在比较版本间的差异.
- 还有一项体验也许并不能说是一个工具问题,而更多的是一个社会问题.分布式版本控制工具鼓励快速的branching和试验.在SVN中,你当然也可以进行branching操作,但是你所做的操作对于其它在这个代码库上工作的人员也是可见的, 这也许并不是什么大问题,但确实会降低人们进行一些实验性工作的欲望.分布式系统则会鼓励你为工作代码进行记录:你可以向你的本地代码库提交未完成的修改,甚至是无法通过测试和无法编译的代码.你同样可以在SVN中进行这些操作,但是在公共空间中创建这些branches总是让人望而却步.
在某种特定情况下,SVN也有其相应的优势,如果你需要对版本控制系统难以合并的二进制文件(如word文档或者ppt)进行管理的话,你就应该回退到独占式checkout的锁机制下,这就需要一个集中式的系统.另外,SVN更容易上手:你有一个代码库,所有的更改都指向这个代码库,如果你知道如何创建,提交以及checkout,那你就可以开始使用它了,而像branching,更新这些操作在使用过程中自然也就慢慢熟悉了.SVN拥有一些非常好用的客户端软件,而且几乎所有的主流IDE都有与SVN集成的插件,这些都能够为你使用SVN提供很大的帮助.
Git则增加了复杂性,似乎总是有两种模式来进行操作,checkout和clone, commit和push...... 你得知道哪些命令是针对本地进行操作的,哪些命令是对服务器或者是主代码库进行操作的. 实际上,Git的命令和思维模式与其它的版本控制系统是有所不同的.Bulter Cole曾这样形容Git:"它是一个神奇的功能强大的东西,它几乎可以做任何你让它做的事情,只有你知道如何让它做".Git的反对者也会抱怨Git缺少可发现性,你很难从它的表面设计推断出它的行为.而Git的支持者则认为这只是因为Git使用了不同于其它系统的思维模式,你需要先忘掉以前那些关于版本控制系统的知识才能更好的来欣赏Git.无论如何,Git对于那些喜欢研究事物内部工作机制的人来说,还是非常有吸引力的.
通常来讲,Git相比Mercurial在处理branching方面表现更好,尤其是用于试验和检查点的短期branch.Mercurial提倡是另一种机制,例如快速的clone一份代码库或者是使用补丁,但是Git的branching模式更为简单好用.Mercurial在处理大型二进制文件时同样有问题.通常的建议是使用SVN来管理二进制文件,如果你只有很少的二进制文件需要管理,不值得建立单独的管理机制的话,Mercurial将就着也能处理.
此外Git之所以能在网络上引起如此多的共鸣,有一个很大的原因是Git对于开源项目来说是完善的选择.你可以新建一个项目分支,向你自己的项目分支提交修改,然后让项目维护人员将你的修改pull过去.有了Git,这一切就变得如此的方便和自然.即使你没有向项目提交修改的权限,你也可以在线上建立你自己的代码库,将你自己的补丁发布出来,任何喜欢你补丁的人都可以将它们pull到他们自己的代码库中,当然也包括项目维护人员.
Git有一个被称为"staging area"的区域.在你向代码库提交之间, 你可以在这个中间区域中构建你的提交.更为重要的是,你可以只提交部分的修改,而不是将所有修改的文件都进行提交.你甚至可以只提交一个文件中修改的一部分.
Git非常的灵活,非常的TIMTOWTDI(There is more than one way to do it). 你可以使用任何你喜欢的工作流程,Git都会对其提供支持.
当前主要的工作流程有以下几种:
1. SVN形式
集中式的工作流程,这也是一种非常普遍的Git的工作流程.如果在你上一次fetch代码之后有其他人进行了提交,那么Git将不允许你向主代码库中push你的代码.
2. 集成管理形式
在这个工作流程中有一个集成管理人员,他向"blessed"代码库进行提交,其他开发人员从这个代码库clone代码,在他们自己的代码库中push修改,并让集成管理人员pull他们的修改.这其实就是大多数开源项目和GitHub所经常使用的开发模式.
3. 独裁者和中尉形式
对于更大规模的项目来说,你可以将开发人员的开发模式设置成类似于Linux内核的开发模式.某些人会负责项目的某个特定的子系统(中尉),并且将所有关于这个子系统的修改都进行合并.另外会有一个集成者(独裁者)可能从他/她的中尉那里pull代码的修改,并将"blessed"代码库进行提交.而所有人都可以从"blessed"代码库进行代码的拷贝.
再次强调,Git对于工作流程的支持非常的灵活,你可以根据自己的需要来匹配,混合,选择这些工作流程.
我们再来看看相对于SVN,Git还有哪些优点吧:
- Git有一个"clean"命令.SVN急需这个命令.
- Git有一个"bisect"命令.
- SVN会在每一个文件夹中创建一个.svn目录.而Git只会创建一个.git目录.
- 在SVN中,每一个文件或文件夹都可能来自于一个不同的版本或是branch.这很可能会引起混乱.
- 无论你什么时候删除了点东西,你都需要告诉SVN一声.Git会自己发现并处理.
- 在Git中,忽略语法很简单,例如*.pyc,它会被应用到所有的子文件夹.当然,如果你只想忽略某个特定文件夹中的内容,也是可以的.在SVN中,很难有什么方法可以将一个忽略模式应用到所有的子文件夹中.
- Git中忽略设置是"private"的,这些设置包含在.git/info/exclude中,并不会影响到其他人.
- Git跟踪的是内容而不是文件,它对于重命名文件的合并有更好的支持.
- Git代码库的大小相对于SVN来说小很多.
- 在Git中,你可以重写历史.在你提交前,这会对准备补丁集以及修改以前的错误提供很大的帮助.
- 另外还有一点是在我们目前这个特殊的环境中所遇到的问题,我们每天都在结对编程,而且我们会经常的更换pair,甚至是在一个story做到一半的时候.而这里代码并没有完整的提交,而且只存在在一台机器上,而这台机器的主人也许会需要去做其它的story.这时如果使用git-svn的话就会很方便.因为我们不需要将没有完成,甚至不能编译的代码提交到SVN上去.
据说目前Git不支持代码库的部分checkout/clone,但是正在开发中,而且已经有submodule方面的支持.SVN则可以根据需要只从代码库中checkout某个子文件夹.SVN的版本号更短并且可以预知,而Git的版本号则是40位的16进制数字串.而Git在Branch方面的处理应该是有很大的优势,但是由于我目前为止几乎没有使用过branch,所以这一部分还没有深刻的体会.