分布式版本管理工具的内涵【三】Fossil篇

前面介绍了Git和Mercurial这两款分布式版本管理工具(DVCS),这篇开始介绍Fossil是怎么实现分布式版本管理的。

再介绍一下Fossil,他是SQLite的作者Richard Hipp开发的,集版本管理以及Wiki和问题管理于一身的DVCS。

我们还是以存储格式、文件集以及改动集的方式来介绍Fossil。

Fossil的存储格式

Fossil的存储格式是基于SQLite的,也就是说,不像Git把文件存在磁盘上,Fossil把文件存在SQLite数据库里面。

我们先用fossil new f1创建一个名字叫做f1的版本仓库。然后再SQlite中来查看这个版本仓库。

确保fossil和SQLite3软件包已经安装好了,在f1所在目录执行sqlite命令

sqlite> .open f1

sqlite> .tables
artifact     config       mlink        rcvfrom      ticket
attachment   delta        orphan       reportfmt    ticketchng
backlink     event        phantom      shun         unclustered
blob         filename     plink        tag          unsent
concealed    leaf         private      tagxref      user

fossil把文件存储在artifact试图中,而artifact又是基于blobdelta两个表的:

sqlite> .schema artifact
CREATE VIEW artifact(rid,rcvid,size,atype,srcid,hash,content) AS     SELECT blob
.rid,rcvid,size,1,srcid,uuid,content      FROM blob LEFT JOIN delta ON (blob.rid
=delta.rid)
/* artifact(rid,rcvid,size,atype,srcid,hash,content) */;

sqlite> .schema blob
CREATE TABLE blob(
  rid INTEGER PRIMARY KEY,
  rcvid INTEGER,
  size INTEGER,
  uuid TEXT UNIQUE NOT NULL,
  content BLOB,
  CHECK( length(uuid)>=40 AND rid>0 )
);

sqlite> .schema delta
CREATE TABLE delta(
  rid INTEGER PRIMARY KEY,
  srcid INTEGER NOT NULL REFERENCES blob
);

从上面数据库的结构可以看出一些端倪,blob表存储的是文件或者文件增量的内容。而delta是用来存储文件基线版本和增量内容之间的关系。artifact视图把这两个表统一起来了。

blob表的初始内容

可以看下当前blob表里面的内容:

sqlite> select * from blob;
1|1|166|fbfce048cc789eaa58c4e02fe92f3be4a491ddfdb5de869527f8f960ed510d1b|

blob表有个字段uuid,这个根据文件内容的哈希值所计算出来的id,跟Git和Mercurial的做法如出一辙。fossil - hash policy列出了fossil支持的哈希模式。

因此不难想象,Fossil和Git和Mercurial一样,都是基于哈希值来索引文件内容的。事实上,我们可以用fossil artifact命令外加上uuid来查看条目内容。针对第一个条目:

>fossil artifact fbfce048cc
C initial\sempty\scheck-in
D 2018-07-26T00:56:54.813
R d41d8cd98f00b204e9800998ecf8427e
T *branch * trunk
T *sym-trunk *
U (user) #此处隐去真实用户名
Z dfe7ed34b9d90707c7343523bdc1d524

可以看到有一条默认的条目。该条目的内容是具有某种格式(具体后面会涉及)的文本。大致可以猜到C initial\sempty\scheck-in这一行是Comment行,内容是"initial empty check-in"。

试验:往fossil里面存入文件

> mkdir f1wd # 创建工作目录
> cd f1wd #进入工作目录
> fossil open ../f1 #在工作目录中打开f1仓库

接着我们来提交一个a.txt文件:

> echo a > a.txt
> fossil add a.txt
ADDED  a.txt
> fossil ci -m "add a.txt"

现在来查看一下仓库的内容:

sqlite3 ../f1
sqlite> select * from blob;
1|1|166|fbfce048cc789eaa58c4e02fe92f3be4a491ddfdb5de869527f8f960ed510d1b|
2|2|3|7076b0b947acfd64691706bb8d56ca04a35c8c1b966f42559777528516ecde95|
3|2|259|4f14ad79bec20100104662004cda4c804ed2999a7efaac928317a10fdc51774b|

可以看到blob表一共有三个条目,比之前多了两条。让我们来看看多出的两条是什么内容:

>fossil artifact 7076b0b947
a

>fossil artifact 4f14ad79be
C add\sa.txt
D 2018-07-26T01:18:16.175
F a.txt 7076b0b947acfd64691706bb8d56ca04a35c8c1b966f42559777528516ecde95
P fbfce048cc789eaa58c4e02fe92f3be4a491ddfdb5de869527f8f960ed510d1b
R 641c0ab1e272e0762cba0388472b0cc1
U (user)
Z 04018b85cb966f1a7a059496a3c8af49

可以看出,第二个条目存储的是文件a.txt的内容;第三个存储条目的内容和之前看到的第一个条目的有点相像,说明这个条目的格式很重要。下一个章节,我们来研究下这个格式。

Fossil的文件集和改动集

前面我们看到

>fossil artifact 4f14ad79be
C add\sa.txt
D 2018-07-26T01:18:16.175
F a.txt 7076b0b947acfd64691706bb8d56ca04a35c8c1b966f42559777528516ecde95
P fbfce048cc789eaa58c4e02fe92f3be4a491ddfdb5de869527f8f960ed510d1b
R 641c0ab1e272e0762cba0388472b0cc1
U (user)
Z 04018b85cb966f1a7a059496a3c8af49

这其实是Fossil的一个artifact,类型是manifest。这个manifest即是Fossil的文件集,又是Fossil的改动集,相当于把Git的Tree和Commit两种blob二合一了。

manifest的结构在fossil - file format里面有描述,它包含了以下几种元素(叫做“Card”或者“卡”):

  • B baseline-manifest
  • C checkin-comment
  • D time-and-date-stamp
  • F filename ?hash? ?permissions? ?old-name?
  • N mimetype
  • P artifact-hash+
  • Q (+|-)artifact-hash ?artifact-hash?
  • R repository-checksum
  • T (+|-|*)tag-name * ?value?
  • U user-login
  • Z manifest-checksum

其中,B卡是用来支持manifest的增量存储。

C卡记录的是提交的注释,N卡用来标志注释的格式(Fossil可以允许注释有不同的格式?)。D卡记录的是提交的时间。F卡有多个,记录当前仓库版本中所有的文件名以及对应的uuid

P卡记录当前manifest的父版本。一般一个manifest又一个P卡,但是对于合并而来的manifest来说,则可以有多个。 Q卡和P卡有点类似,不过是用来支持CherryPick功能的。

T卡是用来给分支打标签。在Fossil中,分支和标签基本是同一个东西,参考fossil - Branching, Forking, Merging, and Tagging>

R卡是用来给包含在manifest所有的文件作一个checksum。如果文件数目多的话,这个计算量不小的。Fossil的解释是,它宁愿保守,也不愿出错,参考fossil - Fossil Repository Integrity Self-Checks

U卡用于记录用户信息,Z卡用来记录当前manifest的checksum。

最后,此处是Fossil自身仓库中的一个manifest的示例

小结

  • 总的来说,Fossil和Git、Mercurial对于文件的处理都是一样的,都是将其哈希后存储,只不过存储方式有所不同。Fossil是基于SQLite,但是以fossil的设计,换做其他数据库问题也是不大。
  • Fossil的文件集和Mercurial比较像,都是扁平化的,一个列表里面包含了仓库中所有目录下的文件。而不像Git那样,有tree类型的blob来表示目录。总的来说,扁平化的文件列表存储时会比较耗费空间,除非是用增量方式存储。扁平化的好处是处理起来比较方便,不必像Git那样递归查找目录。
  • Fossil的文件集和改动集是一起的,都是由manifest表示。其实Mercurial也可以这么操作,虽然Mercurial的改动集由changeset表示,而文件集由manifest表示,但是两者同增同减,一致性很强。

(本篇完)