(图片来源网络,侵删)
当今IT界,VCS版本控制系统的的使用已经成了一种日常不可或缺部分不光是程序员、IT界甚至在其他领域也是逐渐了有了共识通过Github写书也不再是天方夜谭或者技术段子,而是实实在在天天发生的事情本文,我们来一起回顾下历史上主要版本控制系统(VCS)的发展过程,涉及了内容有:SCSS、RCS、CVS、SVN,Git和Mercurial概述回顾VCS的发展历程,总体上可以划分为三个阶段第一代VCS,包括SCSS和RCS立足于对单个文件变化的跟踪,检出的文件一次只能由一个用户在本地进行编辑,用户通过自己的帐户登录到同一共享Unix主机方式实现第二代VCS,包括CVS和SVN通过引入网络,从而形成了包含正式意义上的项目版本的集中式版本存储库相比第一代VSC,有了实质性的发展,可以供多个用户同时检出并使用代码,但是他们都需要重新提交到同一中央存储库存在的问题是严重依赖于中央存储库,对网络和实时性同步要求很大第三代VSC,包括Git和MercurialV到现在发展成为了分布式VCS在分布式VCS中,创建存储库的所有副本都是相同的,无需一个集中的中央存储库无需通过网络实时同步内容,只需本地创建提交,分支和合并打开了路径,在合适时候再推送到远端库VCS重要软件发展历史时间表: SCCS第一代版本管理系统SCCS(Source Code Control System)是最早创建的VCS工具它由贝尔实验室的Marc Rochkind于1972年用C开发SCCS旨在解决源文件修订跟踪的问题此外,它还解决程序bug错误定位的问题SCCS是现代VCS的鼻祖,至此后VCS发展30年到现在茁壮发展功能和大多数现代的VCS一样,SCCS支持一组命令,供开发人员做文件版本控制主要实现的功能有:检入文件以使用SCCS跟踪其历史记录;检出特定的文件修订以供审核或编译;检出特定的文件修订以进行编辑;检入新文件修订以及说明更改的注释;还原检出文件中所做的更改;基本分支和变更合并;提供文件修订历史记录技术实现当添加文件到SCCS进行跟踪时,会创建一种称为s文件或历史文件特殊类型的文件该文件使用以s开头的原始文件名来命名,存储在名为SCCS的子目录中比如,一个名为test.txt的源文件将在./SCCS/目录中创建一个名为s.test.txt的历史文件创建后,历史记录文件将包含原始文件的初始内容以及一些元数据以帮助进行版本跟踪文件校验和存储在历史记录文件中,以验证内容是否遭到篡改历史记录文件的内容未经过压缩或编码由于原始文件的内容存储在历史记录文件中,因此可以将检出到到工作目录进行查看,编译或编辑可以将对文件所做的进一步更改(例如,行添加,修改和删除)检入到历史文件中,并增加修订号SCCS检入仅存储增量或文件更改,而不是每次存储整个文件内容这样可以减小历史记录文件的大小每次检入时,增量都存储在历史记录文件内部的称为增量表的结构中如前所述,实际文件内容或多或少是逐字复制的,带有特殊的控制序列,用于标记已添加和已删除内容的各个部分的开头和结尾由于SCCS历史记录文件不使用压缩,因此它们通常比要跟踪的实际文件大SCCS使用一种称为交错增量的增量方法,支持恒定时间检出,而不管检出的修订版有多老即较旧的修订版检出所花费的时间不会比新的修订版更长需要注意的重要一件事是,所有文件都在SCCS中被单独跟踪和检入没有办法将更改作为一个原子单位的一部分检入多个文件(和Git提交一样)每个跟踪的文件都有一个对应的历史文件,用于存储其修订历史当检出文件以在SCCS中进行编辑时,为了防止更改被其他用户覆盖,文件上将放置一个锁,但是这会限制多用户的编辑从而开发效率SCCS支持可以在特定文件内存储更改序列的分支分支可以与原始版本合并回去,也可以与同一父级的其他分支版本合并基本命令以下是最常见的SCCS命令的列表sccs create <filename.ext>:将新文件检入SCCS并为其创建新的历史记录文件(默认在./SCCS/目录中)sccs get <filename.ext>:从相应的历史文件中检出文件,并以只读模式将其放置在工作目录中sccs edit <filename.ext>:从相应的历史文件中检出文件进行编辑锁定历史记录文件,以便其他用户无法修改它sccs delta <filename.ext>:检入对指定文件的修改将提示填写commit,将更改存储在历史记录文件中,然后删除锁sccs prt <filename.ext>:显示跟踪文件的修订日志sccs diffs <filename.ext>:显示文件的当前工作副本与检出时文件状态之间的差异版本文件一个SCCS历史记录文件示例:RCS版本管理系统RCS(Revision Control System)由Walter Tichy于1982年用C编写,用以替代SCCS,SCCS当时还不是开源的功能RCS与它的前任SCCS有许多共同之处,包括:逐个文件处理修订;跨多个文件的更改不能一起归类为原子提交;跟踪文件旨在一次由一个用户修改;没有网络功能;每个跟踪文件的修订都存储在相应的历史文件中;基本分支和修订在单个文件中的合并技术实现当将文件检入RCS时,会在当前目录下的./RCS/目录中创建相应的历史文件该文件后缀有.v,比如,test.txt的文件将将创建test.txt.v的跟踪文件RCS使用反向增量方案来存储文件更改检入文件后,文件内容的完整快照将存储在历史记录文件中修改文件并再次检入后,将根据现有历史文件内容计算增量旧的快照将被丢弃,新的快照将被保存,并与增量一起恢复到较早的状态之所以称为反向增量,是因为要检出旧版本,RCS需要从文件的最新版本开始并应用连续的增量,直到达到旧版本为止由于始终可以使用当前修订的完整快照,因此该方法可以非常快速地检出当前修改但是,检出版本越老,检出花费的时间越长,因为需要针对当前快照计算越来越多的增量相比较SCCS花费相同的时间来获取任何修改RCS历史记录文件中没有存储校验和,因此无法确保文件完整性基本命令以下是最常见的RCS命令的列表:ci <filename.ext>:将一个新文件检入RCS并为其创建一个新的历史记录文件(默认在./RCS/目录中)co <filename.ext>:从相应的历史文件中检出文件,并以只读模式将其放置在工作目录中co -l <filename.ext>:从相应的历史文件中检出文件以进行编辑锁定历史记录文件,以便其他用户无法修改它ci <filename.ext>:检入文件更改并在其对应的历史文件中为其创建一个新修订merge <file-to-merge-into.ext> <parent.ext> <file-to-merge-from.ext>:合并更改来自同一父文件的两个修改后的子对象rcsdiff <filename.ext>:显示文件的当前工作副本与检出时文件状态之间的差异rcsclean:删除没有锁的工作文件RCS更多操作介绍,详见GNU RCS说明手册版本文件RCS .v历史记录文件样例CVS第二代版本管理系统CVS(Concurrent Versions System)由Dick Grune于1986年创建,主要是在第一代单机版本控制工具基础上添加了网络,使其网络化协作化CVS也是用C语言编写的CVS开启了VCS发展的第二个里程碑,开启了第二代VCS工具的发展CVS的网络化使地理上分散的开发团队可以协同开发功能CVS采用C/S架构体系,其代码都都存储在服务器端,开发者需从服务器上获得一份代码复制到本机,然后开发开发者可随时将新代码提交给服务器,也可以通过更新操作获得最新的代码,保持与其他开发者的一致CVS提供了一组用于与项目中的文件进行交互的命令,但是使用RCS历史文件格式和后台命令VCS历史上,CVS首次允许多个开发人员检出并同时处理相同的文件CVS在处理多人同时修改页面时,采用"先允许修改,再处理冲突"检出:用来下载文件和建立服务器和本机目录之间的对应关系不会修改本机已有文件的"只读"属性;Export输出:用来下载服务器文件到本机,从而进行软件的编译发布;更新:用于获取当前最新版本,也可以用于获取某个特定版本;Edit编辑:仅用来通知服务器,要编辑某个文件unedit:仅用来通知服务器,完成某个文件的编辑了,同时将本机文件置为"只读";Watch:实现监视协作者edit文件状态的情况,要实现这种监控,要求所有人在自己本机修改文件之前,都edit一下,并在commit之后,unedit一下,别人才能收到通知;Commit:类似于检入,不同的是,其不修改本机文件的"只读"属性,commit后你仍然可以继续修改本机文件,必须unedit后文件属性才改完"只读"技术实现CVS通过使用集中式存储库模型,第一步是使用CVS在远程服务器上建立集中式存储库然后就可以将项目导入到存储库中,将项目导入CVS后,每个文件都被转化为.v历史文件,并存储在被称为模块的中央目录中该存储库通常位于可通过本地网络或Internet访问的远程服务器上开发人员通过检出该模块的副本,并复制到本地工作目录中在此过程中不会文件被锁定,因此可以同时无限制的进行文件检出开发人员可以修改检出的文件并根据需要提交更改如果开发人员要提交更改,则其他开发人员将需要在提交更改之前先通过自动合并过程更新其工作副本必要时候,需要先解决合并冲突 CVS还提供了创建和合并分支的功能基本命令export CVSROOT=<path/to/repository>:设置CVS存储库根目录,无需在每个命令中都指定它cvs import -m 'Import module' <module-name> <vendor-tag> <release-tag>:将文件目录导入CVS模块cvs checkout <module-name>:将模块复制到工作目录cvs commit <filename.ext>:将更改的文件提交回模块cvs add <filename.txt>:添加一个新文件以跟踪修订cvs update:通过合并远程存储库中存在的已提交更改而不是工作副本来更新工作目录cvs status:显示有关已检出模块工作副本的常规信息cvs tag <tag-name> <files>:将识别标记添加到单个文件或一组文件中cvs tag -b <new-branch-name>:在存储库中创建一个新分支(必须先检出,然后在本地进行操作)cvs checkout -r <branch-name>:将现有分支检出到工作目录cvs update -j <branch-to-merge>:将现有分支合并到本地工作副本中更多CVS操作,详见GNU CVS手册版本文件CVS历史记录文件示例:SVN,集大成者Subversion由Collabnet公司在2000年创建,后交由Apache 软件基金会维护SVN也是用C编写的,用于改善CVS,实现更强大的集中式解决方案时至当下,仍有大量的公司依赖于SVN实现其项目管理技术实现和CVS一样,SVN也使用集中式存储库模型远程用户必须依赖网络来实现连接才能将其更改提交到中央存储库Subversion引入了原子提交的功能,确保提交将完全成功,或者在发生问题时被完全放弃在CVS中,如果提交操作中途失败(例如,由于网络中断),则存储库可能损坏和不一致的状态Subversion中的提交或修订可以包含多个文件和目录这样可以允许用户以项目为单位的跟踪相关更改集,而无需分别跟踪每个文件的更改Subversion用于跟踪文件的存储模型称为FSFS(File System atop the File System),使用与运行的操作系统文件系统相匹配的文件和目录结构来创建其数据库结构Subversion文件系统的独特之处在于,它不仅可以跟踪其包含的文件和目录,还可以跟踪这些文件和目录的不同版本,并且它们会随着时间变化它是一个具有附加时间维度的文件系统Subversion以文件夹为基本管理单位可以在Subversion中提交空文件夹,而在其它(甚至是Git)VCS中,无法管理空文件夹创建Subversion存储库后,将创建一个空的文件和文件夹数据库作为其一部分将创建一个名为db/revs的目录,其中存储了已检入(已提交)文件的所有修订跟踪信息每次提交(可以包括对多个文件的更改)都存储在revs目录中的新文件中,并以从1开始的顺序数字标识符命名当首次提交文件时,将存储其全部内容为了节省空间,同一文件的在次提交时候将仅存储变化部分,也称为diffs或deltas另外,SVN还使用lz4或zlib压缩算法压缩增量,以进一步减小其大小尽管每次都存储文件增量而不是整个文件确实节省了存储空间,但由于需要将所有增量捆绑在一起以重新创建文件的当前状态,因此增加了检出和提交操作的时间默认情况下,Subversion在存储文件的新完整副本之前,每个文件最多可以存储1023个增量这样可以实现存储和速度之间的良好平衡SVN不使用常规的分支和标记系统一般的Subversion存储库布局是在根目录中包含三个文件夹:trunk/brances/tags/trunk文件夹用于应用程序的生产版本brances文件夹用于保存与各个分支相对应的子文件夹tags文件夹用于保存特定(通常是重要的)项目修订的标签基本命令svn create <path-to-repository>:在指定目录中创建一个新的空存储库svn import <path-to-project> <svn-url>:将文件目录导入指定的Subversion存储库路径svn checkout <svn-path> <path-to-checkout>:将存储的存储库路径复制到所需的工作目录svn commit -m 'Commit message':提交一组更改的文件和文件夹以及描述性的提交消息svn add <filename.txt>:添加一个新文件以跟踪修订svn update:通过合并svn服务器中存在的已提交更改而不是工作副本来更新工作副本svn status:显示工作目录中已更改的跟踪文件的列表svn info:显示有关已检出副本的常规详细信息列表svn copy <branch-to-copy> <new-branch-path-and-name>:通过复制现有分支来创建一个新分支svn switch <existing-branch>:将工作目录切换到现有分支这将检出指定的分支svn merge <existing-branch>:将指定的分支合并到工作目录中检出的当前分支中请注意,这需要在以后提交svn log:显示活动分支的提交历史记录和相关的描述性消息版本文件SVN修订文件示例如下Git第三代版本管理系统Git由Linux之父Linus Torvalds于2005年创建用来替代Linux内核开发用的商业的版本管理软件BitKeeper主要是用C结合一些Shell脚本编写的由于其功能,灵活性和速度,分布式版本管理、协作性,使得Git成长为一个最出色的VCS软件由于GitHub,Gitlab等添加的协作性,社交性功能使得Git风靡于世,被广泛使用技术实现Git是分布式VCS,无需集中式的中央存储卡,就可以正常工作在Git中所有副本都创建为相等,即便是远程Git服务上也是是等价的副本这是与第二代VCS最明显得差异,第二代集中式的版本管理,必须要依靠中央服务器来提供用户检入和检出而在Git中开发人员可以本地任意开发,即使连不到远程库也不受任何影响,只需在网络通畅时候,再将变化推送服务器即可开发人员可以脱机在本地工作,直到准备与他人共享他们的工作为止此时,可以将更改推送到其他存储库以进行检查,测试或部署Blob对象添加文件以使用Git进行跟踪时,Git使用zlib压缩算法对其进行压缩使用SHA-1哈希函数对结果进行哈希处理这将产生一个唯一的哈希值,该值对应于该文件中的内容Git将其存储在位于隐藏的.git/objects文件夹中的对象数据库中文件的名称为生成的哈希值,内容为压缩的内容这些对象文件称为Blob,每次将新文件添加到存储库时会创建Blob对象Git实现了一个staging索引,该索引被设计为在提交的中间区域在准备提交新更改时,它们的压缩内容在特殊的索引文件中被引用,该文件采用树对象的形式树对象是另一种Git对象,对应于文件目录,它将blob对象连接到它们的真实文件名,文件许可权和到其他树的链接,并以此方式表示特定文件和目录集的状态一旦所有相关更改都准备好提交,索引树就可以提交到存储库,该存储库在Git对象数据库中创建一个commit对象commit对象保存特定修订的标题树以及提交作者,电子邮件地址,日期和描述性提交消息每个commit对象还保存对其父提交的引用,因此随着时间的推移,将建立项目开发的历史记录如前所述,所有Git对象(Blob,tree和commit)都根据其哈希值进行压缩,哈希处理并存储在对象数据库中这些被称为松散对象Git实现中没有通过差异来节省空间,而都是全部内容哈希键索引和压缩镜像,所以,Git非常快,因为每个文件修订版的全部内容都可以作为一个松散的对象来访问但是,某些操作(例如,将提交推送到远程存储库,存储太多对象或手动运行Git的垃圾收集命令)可能会导致Git将对象重新打包为打包文件,在打包过程中,采用反向差异并进行压缩以消除多余的内容并减小尺寸该过程将生成包含对象内容的.pack文件,每个文件都有一个对应的.idx索引文件,其中包含对打包对象及其在打包文件中位置的引用当将分支推送到远程存储库或从远程存储库拉出分支时,这些打包文件将通过网络传输提取或获取分支时,将打包文件解压缩以在对象存储库中创建松散对象基本命令git init:将当前目录初始化为Git存储库(创建隐藏的.git文件夹及其内容)git clone <git-url>:在指定的URL下载Git存储库的副本git add <filename.ext>:将未跟踪的文件或更改的文件添加到暂存区(在对象数据库中创建相应的条目)git commit -m '提交消息':提交一组更改的文件和文件夹以及描述性提交消息git status:显示与工作目录,当前分支,未跟踪的文件,已修改的文件等状态有关的信息git branch <new-branch>:基于当前检出的分支创建一个新分支git checkout <branch>:将指定的分支检出到工作目录中git merge <branch>:将指定的分支合并到工作目录中检出的当前分支中git pull:更新工作c通过合并远程存储库中存在的已提交更改而不是工作副本来进行操作git push:将本地活动分支提交的松散对象打包到打包文件中,并传输到远程存储库git log:显示活动分支的提交历史记录和相关的描述性消息git stash:将工作目录中所有未提交的更改保存到缓存中,以便以后可以检索有关Git内部的更多信息,请参阅Pro Git书籍中有关Git内部的章节版本文件Git对象文件示例:Blob对象.git/objects/a8/420ef73065a9e3e57fe8fd2d32dad28e332bd0 表示哈希值a8420ef73065a9e3e57fe8fd2d32dad28e332bd0的Blob对象可以使用:git cat-file -t a8420ef73065a9e3e57fe8fd2d32dad28e332bd0查看器对象类型为blob使用git cat-file -p a8420ef73065a9e3e57fe8fd2d32dad28e332bd0查看器内容为hello,Chongchong!Tree对象.git/objects/ba/0ea2e2c2f9fc822ca16046f8d3f1f24660014c表示哈希值为ba0ea2e2c2f9fc822ca16046f8d3f1f24660014c的tree对象,其类型为tree,值为:040000 tree 887ad439b842a19be9a1922253872427763b0376 helloCommit对象.git/objects/da/7a3c9c71eb9da8022018fb5ce02a4625b753d5表示哈希值为da7a3c9c71eb9da8022018fb5ce02a4625b753d5的commit对象,其类型为commit,值为该次commit关联到da7a3c9c71eb9da8022018fb5ce02a4625b753d5的tree对象,commit消息为initMercurial 即将死去的另外一个选择Mercurial由Matt Mackall于2005年创建,采用 Python编写它也是从托管Linux代码库的目标开始的,是目前次于Git的第二受欢迎的分布式VCS,但使用频率不多随着最近BitBucket宣布将停止对Mercurial的支持,表示着Mercurial即将死去,除了Git后没有了另外的选择技术实现和Git一样,Mercurial是一个分布式版本控制系统,它允许任何数量的开发人员独立于其他人使用他们自己的项目副本 Mercurial利用了许多与Git相同的技术,例如压缩和SHA-1哈希,但是采用了不同的方式当提交新文件以在Mercurial中进行跟踪时,将在隐藏目录.hg/store/data/中为其创建相应的revlog文件可以将revlog(或修订日志)文件看作是较旧的VCS(如CVS,RCS和SCCS)使用的历史记录文件的现代化版本与Git为每个暂存文件的每个版本创建一个新的Blob不同,Mercurial只是在该文件的revlog中创建一个新条目为了节省空间,每个新条目仅包含先前版本的增量(更改)一旦达到阈值数量的增量,将再次存储文件的完整快照在应用许多增量来重建特定文件修订版时,这减少了查找时间这些文件修订日志的名称与它们跟踪的文件匹配,但是后缀为.i和.d扩展名.d文件包含压缩的增量内容.i文件用作索引,以快速跟踪.d文件中的不同修订版本Mercurial使用了另一种类型的修订日志,称为变更日志更改日志包含条目列表,这些条目将每个提交与以下信息相关联:Manifest nodeid:标识特定时间存在的完整文件修订集;父提交节点ID:这使Mercurial可以建立时间表或项目历史记录的分支根据提交的类型(正常vs合并),存储一个或两个父ID;提交人;提交日期;提交信息;每个变更日志条目还会生成一个称为其节点ID的哈希基本命令hg init:将当前目录初始化为Mercurial存储库(创建隐藏的.hg文件夹及其内容)hg clone <hg-url>:在指定的URL下载Mercurial存储库的副本hg add <filename.ext>:添加新文件以进行修订跟踪hg commit -m '提交消息':提交一组更改的文件和文件夹以及描述性提交消息hg status:显示与工作目录,未跟踪文件,已修改文件等状态有关的信息hg update <revision>:将指定的分支检出到工作目录中hg merge <branch>:将指定的分支合并到工作目录中检出的当前分支中hg pull:从远程存储库下载新修订,但不要将其合并到工作目录中hg push:将新修订版本传输到远程存储库hg log:显示活动分支的提交历史记录和相关的描述性消息版本文件Mercurial 版本文件示例:Manifest revloghey.txt208b6e0998e8099b16ad0e43f036ec745d58ec04hi.txt74568dc1a5b9047c8041edd99dd6f566e78d3a42hi.txt74568dc1a5b9047c8041edd99dd6f566e78d3a42revlog总结本文中,我们回顾了VCS版本控制的发展历史,介绍了VCS版本控制系统技术演进过程,对历史上曾经出现的主要VCS软件进行了介绍和比较、还有各自的技术实现以及常见操作命令
0 评论