版本控制系统的历史



在本文中,我们从技术角度比较最著名的版本控制系统(将来我们计划扩展此列表):

  1. 第一代
  2. 第二代
  3. 第三代

第一代版本控制系统(VCS)跟踪单个文件中的更改,并且仅在本地且一次只有一个用户支持编辑。 该系统是基于所有用户都将在同一公共Unix节点上登录其帐户的假设而构建的。

第二代VCS引入了网络支持,这导致了带有“正式”版本项目的集中存储库。 这是一个重大的进步,因为几个用户可以同时使用代码,并提交到同一中央存储库。 但是,提交需要访问网络。

第三代由分布式VCS组成,其中存储库的所有副本都被认为是相等的,没有中央存储库。 这为提交,分支和合并铺平了道路,这些创建,分支和合并是在本地创建而无需访问网络,并根据需要移动到其他存储库。

VCS发布时间表


对于上下文,以下图表显示了这些工具的出现日期:



SCCS(源代码控制系统):第一代


SCCS被认为是最早成功的版本控制系统之一。 它由Bell Labs的Mark Rochkind在1972年开发。 该系统用C语言编写,旨在跟踪源文件版本。 另外,它极大地方便了在程序中寻找错误源。 SCCS的基本体系结构和语法使您可以了解现代VCS工具的根源。

建筑学


与大多数现代系统一样,SCCS具有一组用于处理文件版本的命令:

  1. 签入文件以在SCCS中跟踪历史记录。
  2. 检出特定版本的文件以供查看或编译。
  3. 提取特定版本进行编辑。
  4. 介绍文件的新版本以及解释更改的注释。
  5. 放弃对提取文件所做的更改。
  6. 主要分支和合并更改。
  7. 文件更改日志。

将跟踪文件添加到SCCS时,会创建一种特殊类型的文件,称为s- 。 它被称为源文件,仅带有s.前缀s. ,并存储在SCCS子目录中。 因此,对于test.txt文件,将在./SCCS/目录中创建一个历史文件test.txt 。 在创建时,历史记录文件包含源文件的初始内容,以及一些有助于跟踪版本的元数据。 校验和存储在此处,以确保内容没有被修改。 历史文件的内容未压缩或编码(如在下一代VCS中一样)。

由于源文件的内容现在存储在历史文件中,因此可以将其提取到工作目录中以进行查看,编译或编辑。 您可以更改历史记录文件,例如添加行,更改和删除,这会增加其版本号。

随后的文件添加仅存储或更改,而不存储其所有内容。 这样可以减少历史记录文件的大小。 每个增量都存储在历史记录文件中的一种称为-的结构- 。 如前所述,文件的实际内容或多或少是逐字复制的,带有特殊的转义序列,用于标记已添加和已删除内容的各个部分的开头和结尾。 因为SCCS历史记录文件不使用压缩,所以它们通常大于跟踪更改的实际文件。 SCCS使用一种称为 的方法,该方法可以保证恒定的检索时间,而不管所检索版本的使用期限如何,也就是说,较旧版本的检索速度与新版本相同。

重要的是要注意,所有文件都是单独跟踪和记录的。 无法像单个Git一样将多个文件中的更改作为单个原子块进行检查。 每个跟踪的文件都有其自己的历史记录文件,在其中存储了其更改历史记录。 在一般情况下,这意味着项目中各种文件的版本号通常不匹配。 但是,可以通过同时编辑项目中的所有文件(甚至不对其进行实际更改)并同时添加所有文件来约定这些版本。 这将同时增加所有文件的版本号,使它们保持一致,但是请注意,这与在Git中在一次提交中包含多个文件不同。 在SCCS中,将单个历史记录添加到每个文件,这与一次大型提交同时包含所有更改的提交不同。

提取文件以便在SCCS中进行编辑时,将在其上放置一个锁,以便其他任何人都无法编辑它。 这可以防止其他用户覆盖更改,但也限制了开发,因为在任何给定时间,只有一个用户可以使用此文件。

SCCS支持将更改序列存储在特定文件中的分支。 您可以将一个分支与原始版本或另一个分支合并。

主要队伍


以下是最常见的SCCS命令的列表。

  • sccs create <filename.ext> :将新文件添加到SCCS并为其创建新的历史记录文件(默认在./SCCS/目录中)。
  • sccs get <filename.ext> :从相应的历史文件中提取文件,并以只读模式将其放在工作目录中。
  • sccs edit <filename.ext> :从相应的历史文件中提取文件以进行编辑。 锁定历史记录文件,以便其他用户无法修改它。
  • sccs delta <filename.ext> :将更改添加到指定的文件。 系统将请求注释,将更改保存到历史记录文件并释放锁定。
  • sccs prt <filename.ext> :显示受监视文件的更改日志。
  • sccs diffs <filename.ext> :显示sccs diffs <filename.ext>的当前工作副本与提取文件时的状态之间的差异。

有关SCCS内部的更多信息,请参见《 Eric Allman 指南》和《 Oracle编程实用程序指南》

SCCS历史记录文件示例


 ^Ah20562 ^As 00001/00001/00002 ^Ad D 1.3 19/11/26 14:37:08 jack 3 2 ^Ac Here is a comment. ^Ae ^As 00002/00000/00001 ^Ad D 1.2 19/11/26 14:36:00 jack 2 1 ^Ac No. ^Ae ^As 00001/00000/00000 ^Ad D 1.1 19/11/26 14:35:27 jack 1 0 ^Ac date and time created 19/11/26 14:35:27 by jack ^Ae ^Au ^AU ^Af e 0 ^At ^AT ^AI 1 Hi there ^AE 1 ^AI 2 ^AD 3 This is a test of SCCS ^AE 2 ^AE 3 ^AI 3 A test of SCCS ^AE 3 

RCS(修订控制系统):第一代


RCS是由Walter Tihey于1982年用C语言编写的,它是SCCS系统的一种替代方法,该系统当时还不是开源的。

建筑学


RCS与它的前身有很多共同点,包括:

  • 每个文件分别进行版本控制。
  • 对多个文件的更改不能分组为一个提交。
  • 跟踪的文件不能被多个用户同时编辑。
  • 没有网络支持。
  • 每个跟踪文件的版本都存储在相应的历史文件中。
  • 分支和合并版本仅适用于单个文件。

首次将文件添加到RCS时,会在本地目录./RCS/中的本地存储中为其创建相应的历史文件。 扩展名,v已添加到该文件,即名为test.txt的文件将被名为test.txt,v的文件跟踪。

RCS使用反向增量方案来存储更改。 添加文件时,其内容的完整快照将保存在历史记录文件中。 修改并再次返回文件后,将基于历史文件的现有内容来计算增量。 旧图片被丢弃,新图片与增量一起保存以返回到旧状态。 这称为 ,因为要检索较旧的版本,RCS会获取最新版本并顺序应用增量,直到达到所需的版本。 由于始终可以使用当前修订的完整快照,因此该方法使您可以非常快速地检索当前版本。 但是,版本越旧,验证所需的时间越长,因为您需要检查越来越多的增量。

在SCCS中,情况有所不同:检索任何版本都需要相同的时间。 另外,校验和未存储在RCS历史记录文件中,因此无法确保文件完整性。

主要队伍


以下是最常见的RCS命令的列表:

  • <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> :显示rcsdiff <filename.ext>的当前工作副本与提取文件时的状态之间的差异。
  • rcsclean :删除未锁定的工作文件。

有关内部RCS组件的更多信息,请参见GNU RCS手册

示例RCS历史记录文件


 head 1.2; access; symbols; locks; strict; comment @# @; 1.2 date 2019.11.25.05.51.55; author jstopak; state Exp; branches; next 1.1; 1.1 date 2019.11.25.05.49.02; author jstopak; state Exp; branches; next ; desc @This is a test. @ 1.2 log @Edited the file. @ text @hi there, you are my bud. You are so cool! The end. @ 1.1 log @Initial revision @ text @d1 5 a5 1 hi there @ 

CVS(并行版本系统):第二代


CVS由Dick Grun于1986年创建,旨在将网络支持添加到版本控制中。 它也是用C语言编写的,标志着第二代VCS工具的诞生,由于这些工具,地理上分散的开发团队才有机会共同致力于项目。

建筑学


CVS是RCS的前端,它具有一组用于与项目中的文件进行交互的新命令,但是在内部,使用了相同的RCS历史记录文件格式和RCS命令。 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手册Dick Grohn的文章

CVS历史记录文件示例


 head 1.1; branch 1.1.1; access ; symbols start:1.1.1.1 jack:1.1.1; locks ; strict; comment @# @; 1.1 date 2019.11.26.18.45.07; author jstopak; state Exp; branches 1.1.1.1; next ; commitid zsEBhVyPc4lonoMB; 1.1.1.1 date 2019.11.26.18.45.07; author jstopak; state Exp; branches ; next ; commitid zsEBhVyPc4lonoMB; desc @@ 1.1 log @Initial revision @ text @hi there @ 1.1.1.1 log @Imported sources @ text @@ 

SVN(Subversion):第二代


Subversion由Collabnet Inc.在2000年创建,目前由Apache Software Foundation支持。 该系统用C编写,并且设计为比CVS更可靠的集中式解决方案。

建筑学


与CVS一样,Subversion使用集中式存储库模型。 远程用户需要网络连接才能提交到中央存储库。

Subversion引入了原子提交的功能,并保证在出现问题时提交完全成功或完全取消。 在CVS中,如果在提交过程中发生故障(例如,由于网络故障),则存储库可能会处于损坏和不一致的状态。 此外,Subversion中的提交或版本可能包含多个文件和目录。 这很重要,因为它允许您将相关更改的集合作为一个分组的块一起跟踪,而不是像过去的系统那样分别跟踪每个文件。

Subversion当前使用FSFS(文件系统顶部的文件系统)文件系统。 在这里,使用与主机文件系统相对应的文件和目录结构来创建数据库。 FSFS的独特功能是它不仅可以跟踪文件和目录,还可以跟踪它们的版本。 这是一个对时间敏感的文件系统。 目录在Subversion中也是完整对象。 您可以将空目录提交给系统,而其余目录(甚至Git)都不会注意到它们。

创建Subversion存储库时,将在其组成中创建一个(几乎)空的文件和文件夹数据库。 创建db/revs目录,该目录存储已添加(已提交)文件的所有版本跟踪信息。 每个提交(可能包括多个文件中的更改)都存储在revs目录中的新文件中,并为其指定名称,该名称带有以1开头的顺序数字标识符。第一个提交将保存文件的全部内容。 将来同一文件的提交将仅保存更改(也称为或deltas)以节省空间。 另外,使用lz4zlib压缩算法压缩增量,以减小大小。

这样的系统仅在特定点之前起作用。 尽管增量节省了空间,但如果有很多增量,则该操作将花费大量时间,因为为了重新创建文件的当前状态,必须处理所有增量。 因此,默认情况下,Subversion每个文件最多保存1023个增量,然后制作该文件的新完整副本。 这样可以很好地平衡存储和速度。

SVN不使用通常的分支和标记系统。 常规的Subversion存储库模板在根目录中包含三个文件夹:

  • trunk/
  • branches/
  • tags/

trunk/目录用于项目的生产版本。 branches/目录-用于存储与各个分支相对应的子文件夹。 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 status :显示工作目录(如果有)中已更改的受监视文件的列表。
  • svn info :有关提取副本的常规信息。
  • svn copy <branch-to-copy> <new-branch-path-and-name> :通过复制现有分支来创建一个新分支。
  • svn switch <existing-branch> :将工作目录切换到现有分支。 这将允许您从那里获取文件。
  • svn merge <existing-branch> :将指定的分支与复制到工作目录的当前分支合并。 请注意,您稍后需要提交。
  • svn log :显示提交的历史记录以及活动分支的相应消息。

有关SVN内部组件的更多信息,请参见Subversion版本控制

样本SVN历史记录文件


 DELTA SVN^B^@^@ ^B ^A<89> hi there ENDREP id: 2-1.0.r1/4 type: file count: 0 text: 1 3 21 9 12f6bb1941df66b8f138a446d4e8670c 279d9035886d4c0427549863c4c2101e4a63e041 0-0/_4 cpath: /trunk/hi.txt copyroot: 0 / DELTA SVN^B^@^@$^B%^A¤$K 6 hi.txt V 15 file 2-1.0.r1/4 END ENDREP id: 0-1.0.r1/6 type: dir count: 0 text: 1 5 48 36 d84cb1c29105ee7739f3e834178e6345 - - cpath: /trunk copyroot: 0 / DELTA SVN^B^@^@'^B#^A¢'K 5 trunk V 14 dir 0-1.0.r1/6 END ENDREP id: 0.0.r1/2 type: dir pred: 0.0.r0/2 count: 1 text: 1 7 46 34 1d30e888ec9e633100992b752c2ff4c2 - - cpath: / copyroot: 0 / _0.0.t0-0 add-dir false false false /trunk _2.0.t0-0 add-file true false false /trunk/hi.txt L2P-INDEX ^A<80>@^A^A^A^M^H^@ä^H÷^Aé^FDÎ^Bzè^AP2L-INDEX ^A<91>^E<80><80>@^A?^@'2^@<8d>»Ý<90>^C§^A^X^@õ ½½^N= ^@ü<8d>Ôã^Ft^V^@<92><9a><89>Ã^E; ^@<8a>åw|I^@<88><83>Î<93>^L`^M^@ù<92>À^Eïú?^[^@^@657 6aad60ec758d121d5181ea4b81a9f5f4 688 75f59082c8b5ab687ae87708432ca406I 

Git:第三代


Git系统由Linus Torvalds(Linux的创建者)于2005年开发。 它主要是用C结合一些命令行脚本来编写的。 在功能,灵活性和速度方面与VCS不同。 Torvalds最初是为Linux代码库编写的系统,但是随着时间的流逝,它的范围不断扩大,今天它已成为世界上最受欢迎的版本控制系统。

建筑学


Git是一个分布式系统。 没有中央存储库:创建的所有副本都是相同的,这与第二代VCS完全不同,后者的工作基于在中央存储库中添加和提取文件。 这意味着开发人员可以在将更改合并到正式分支之前立即彼此交换更改。

此外,开发人员可以在不了解其他存储库的情况下对存储库的本地副本进行更改。 这允许提交而无需连接到网络或Internet。 开发人员可以离线进行本地工作,直到准备好与他人共享他们的工作为止。 此时,更改将发送到其他存储库以进行验证,测试或部署。

添加文件以在Git中进行跟踪时,将使用zlib压缩算法对其进行压缩。 使用SHA-1哈希函数对结果进行哈希处理。 这将提供一个唯一匹配该文件内容的唯一哈希。 Git将其存储在 ,该 位于隐藏的文件夹.git/objects 。 文件名是生成的哈希,并且文件包含压缩的内容。 这些文件称为 ,它们是在每次将新文件(或现有文件的修改版本)添加到存储库时创建的。

Git实现了一个暂存索引,它充当准备提交的更改的中间区域。 在准备新的更改时,它们的压缩内容在特殊的索引文件中被引用,该文件采用对象的形式。树是一个Git对象,它将Blob与它们的真实文件名,文件许可权以及与其他树的链接相关联,因此代表了一组特定文件和目录的状态。当所有相关更改都准备提交时,可以将索引树提交到在Git对象数据库中创建对象的存储库中。提交引用特定版本的标题树,以及提交的作者,提交的电子邮件地址,日期和消息。每个提交还存储指向其父提交的链接,因此随着时间的推移,将创建项目开发的历史记录。

如前所述,所有Git对象(斑点,树和提交)都基于其哈希值进行压缩,散列并存储在对象数据库中。他们被称为 (松散的物体)。这里不使用任何差异来节省空间,这使得Git非常快,因为文件的每个版本的完整内容都可以作为自由对象使用。但是,某些操作(例如,将提交发送到远程存储库,存储大量对象或手动运行Git垃圾回收命令)会导致将对象重新打包到 。在打包过程中,将计算逆差异,将其压缩以消除冗余并减小尺寸。结果,将创建.pack具有对象内容的文件,并为每个文件.idx创建一个文件(或索引),并带有指向打包对象及其在批处理文件中位置的链接。

当分支移动到远程存储库或从远程存储库检索时,这些批处理文件将通过网络传输。拉伸或提取分支时,将打包文件解压缩以在对象存储库中创建免费对象。

主要队伍


  • git init:将当前目录初始化为Git存储库(.git创建一个隐藏文件夹及其内容)。
  • git clone <git-url> :在指定的URL下载Git存储库的副本。
  • git add <filename.ext> :将未跟踪或修改的文件添加到暂存区(在对象数据库中创建相应的记录)。
  • git commit -m 'Commit message' :提交一组修改过的文件和文件夹以及一条提交消息。
  • git status :显示工作目录,当前分支,未跟踪的文件,已修改的文件等的状态。
  • git branch <new-branch> :根据当前提取的分支创建一个新分支。
  • git checkout <branch> :将指定的分支提取到工作目录。
  • git merge <branch> :将指定的分支与当前分支合并,然后将其提取到工作目录中。
  • git pull :通过组合远程存储库(而不是工作副本)中存在的已提交更改来更新工作副本。
  • git push :将用于活动分支的本地提交的免费对象打包到打包文件中,并传输到远程存储库。
  • git log :显示提交历史记录和活动分支的相应消息。
  • git stash :将所有未提交的更改从工作目录保存到缓存中,以供以后检索。

如果您想了解Git代码的工作原理,请查阅《 Git入门指南》有关内部组件的更多信息,请参见Pro Git书中相应章节

示例blob,tree和commit git


带有哈希值的Blob 37d4e6c5c48ba0d245164c4e10d5f41140cab980

 hi there 

带有哈希值的树对象b769f35b07fbe0076dcfc36fd80c121d747ccc04

 100644 blob 37d4e6c5c48ba0d245164c4e10d5f41140cab980hi.txt 

哈希提交dc512627287a61f6111705151f4e53f204fbda9b

 tree b769f35b07fbe0076dcfc36fd80c121d747ccc04 author Jacob Stopak 1574915303 -0800 committer Jacob Stopak 1574915303 -0800 Initial commit 

水星:第三代


Mercurial由Matt McCall于2005年创建,并用Python编写。它还设计用于托管Linux代码库,但最终选择了Git来完成此任务。这是第二受欢迎的版本控制系统,尽管它的使用频率要低得多。

建筑学


Mercurial还是一个分布式系统,允许任何数量的开发人员独立于其他人使用其项目副本。 Mercurial使用了许多与Git相同的技术,包括SHA-1压缩和散列,但其方式有所不同。

当提交新文件以在Mercurial中进行跟踪时,会revlog在隐藏目录中为其创建相应的文件.hg/store/data/。您可以将文件revlog(更改日志)视为升级版本 在较旧的VCS(例如CVS,RCS和SCCS)中。与Git会为每个准备好的文件的每个版本创建一个新的Blob不同,Mercurial只会为此文件创建一个新的revlog条目。为了节省空间,每条新记录仅包含前一版本的增量。当达到阈值数量增量时,将再次保存文件的完整快照。这样可以减少处理大量增量以恢复文件的特定版本时的搜索时间。

每个修订日志均根据其跟踪的文件来命名,但扩展名为.i.d。文件.d包含压缩的增量。文件.i用作索引以快速跟踪文件中的不同版本.d。对于更改很少的小文件,索引和内容存储在files中.i。 Revlog条目经过压缩以提高性能,并对其进行哈希处理以进行标识。这些哈希值称为nodeid

在每次提交时,Mercurial都会通过称为的方式跟踪此提交中文件的所有版本。清单也是一个revlog文件-它存储与存储库的某些状态相对应的条目。清单没有存储诸如revlog之类的单独文件内容,而是存储文件名和节点ID的列表,这些列表确定了项目版本中存在文件的哪个版本。这些清单条目也被压缩和散列。哈希值也称为nodeid

最后,Mercurial使用另一种类型的修订日志,称为变更日志,即变更日志。这是将每个提交与以下信息相关联的条目的列表:

  • manifest nodeid:在特定时间点存在的完整文件版本集。
  • 父提交的一个或两个nodeid:这使Mercurial可以建立项目历史的时间轴或分支。根据提交的类型(常规或合并),存储一个或两个父ID。
  • 提交作者
  • 提交日期
  • 提交讯息

每个变更日志条目还生成一个称为的哈希nodeid

主要队伍


  • hg init:将当前目录初始化为Mercurial存储库(创建一个隐藏文件夹.hg及其内容)。
  • hg clone <hg-url> :在指定的URL下载Mercurial存储库的副本。
  • hg add <filename.ext> :添加新文件以跟踪更改。
  • hg commit -m 'Commit message' :提交一组更改的文件和文件夹以及提交消息。
  • hg status :显示与工作目录,未跟踪文件,已修改文件等状态有关的信息。
  • hg update <revision> :将指定的分支提取到工作目录。
  • hg merge <branch> :将指定的分支与提取到的当前分支合并到工作目录中。
  • hg pull :从远程存储库下载新版本,但不要将它们合并到工作目录中。
  • hg push :将新版本传输到远程存储库。
  • hg log :显示活动分支的提交历史和相关消息。

Mercurial文件示例


宣言:

 hey.txt208b6e0998e8099b16ad0e43f036ec745d58ec04 hi.txt74568dc1a5b9047c8041edd99dd6f566e78d3a42 

变更日志(changelog):

 b8ee947ce6f25b84c22fbefecab99ea918fc0969 Jacob Stopak 1575082451 28800 hey.txt Add hey.txt 

有关Mercurial设备的其他信息:

Source: https://habr.com/ru/post/zh-CN478752/


All Articles