MODULE OdVCSBase;
IMPORT SYSTEM, Dates, Strings, Files, Out := KernelLog, Clock;
CONST
BaseDir* = "FTP:/WebDAV/repo/";
TmpDFile="FTP:/WebDAV/repo/VCSBaseD.Temp";
FormatLen* = 8;
FormatName* ="dsfantf1";
MakroBit* = 0;
VersionTag = 1X;
DateTag = 2X;
AuthorTag = 3X;
LogTextTag = 4X;
DeltaAddTag = 5X;
DeltaCopyTag = 6X;
AttachmentTag = 7X;
TextTag = 8X;
DiffTag = 9X;
AccessTag = 0AX;
HashLen = 16381;
D = 256;
PrefixLen = 7;
MaxVersions* = 100;
TYPE
PLinkNode = POINTER TO TLinkNode;
TLinkNode = RECORD
next: PLinkNode;
pos: LONGINT;
END;
THashList = ARRAY HashLen OF PLinkNode;
PHashList = POINTER TO THashList;
TWorkBytes = ARRAY PrefixLen OF CHAR;
TData = POINTER TO ARRAY OF CHAR;
TLog* = RECORD
versionID*: LONGINT;
author*: ARRAY 127 OF CHAR;
logText*: ARRAY 256 OF CHAR;
date*: ARRAY 22 OF CHAR;
lenOfDelta: LONGINT;
lenOfOld: LONGINT;
flags* : SET;
END;
TFileName* = ARRAY 256 OF CHAR;
TDeltaEntry* = ARRAY 20 OF CHAR;
TDList* = ARRAY MaxVersions OF TDeltaEntry;
VAR
errMsg*: ARRAY 256 OF CHAR;
formatStr: ARRAY FormatLen+1 OF CHAR;
res: LONGINT;
PROCEDURE splitDirBase(fileName: ARRAY OF CHAR; VAR dir, base: ARRAY OF CHAR);
CONST CollCh = "/"; FileSystemCh = ":";
VAR collPos, len, i: LONGINT;
BEGIN
len := Strings.Length(fileName);
collPos := -1;
LOOP
FOR i := 0 TO len -1 DO
IF fileName[len-i] = CollCh THEN
collPos := len-i;
EXIT;
ELSIF fileName[len-i] = FileSystemCh THEN
EXIT;
END;
END;
END;
IF collPos = -1 THEN
COPY("", dir); COPY(fileName, base);
ELSE
FOR i := 0 TO collPos-1 DO dir[i] := fileName[i]; END;
dir[collPos] := 0X;
FOR i := collPos+1 TO len -1 DO base[i-collPos-1] := fileName[i]; END;
base[i-collPos] := 0X;
END;
END splitDirBase;
PROCEDURE makeDirs(name: ARRAY OF CHAR): LONGINT;
VAR
dir, base: ARRAY 128 OF CHAR;
res: LONGINT;
dirFile: Files.File;
BEGIN
Files.SplitPath(name, dir, base);
dirFile := Files.Old(dir);
IF dirFile = NIL THEN
Files.CreateDirectory(dir, res);
RETURN res;
ELSE
RETURN 0;
END;
END makeDirs;
PROCEDURE DateTime*(VAR s: ARRAY OF CHAR);
VAR date, time: LONGINT; dateTime: Dates.DateTime; timeStr: ARRAY 16 OF CHAR;
BEGIN
Clock.Get(time, date);
dateTime := Dates.OberonToDateTime(date, time);
Strings.DateToStr(dateTime, s); Strings.Append(s, " ");
Strings.TimeToStr(dateTime, timeStr); Strings.Append(s, timeStr);
END DateTime;
PROCEDURE Hash(toHash: TWorkBytes): LONGINT;
VAR
i, h: LONGINT;
BEGIN
h := 0;
FOR i := 0 TO PrefixLen - 1 DO
h := (h * D + ORD(toHash[i])) MOD HashLen;
END;
RETURN h;
END Hash;
PROCEDURE AccessArray(arr: TData; len, left, right: LONGINT; VAR ret: ARRAY OF CHAR);
VAR
i: LONGINT;
BEGIN
IF (arr = NIL) OR (left > len - 1) OR (right > len - 1) THEN RETURN END;
IF left > right THEN i := left; left := right; right := i; END;
FOR i := left TO right DO
ret[i-left] := arr[i];
END;
END AccessArray;
PROCEDURE BuildLinkList(new: TData; lenNew: LONGINT; hashList: PHashList);
VAR
actBytes: TWorkBytes;
i, h: LONGINT;
oldNode, newNode: PLinkNode;
BEGIN
IF new = NIL THEN RETURN END;
FOR i := 0 TO lenNew - PrefixLen - 1 DO
AccessArray(new, lenNew, i, i + PrefixLen - 1, actBytes);
h := Hash(actBytes);
NEW(newNode);
newNode.pos := i;
newNode.next := NIL;
IF hashList[h] = NIL THEN
hashList[h] := newNode;
ELSE
oldNode := hashList[h];
WHILE oldNode.next # NIL DO oldNode := oldNode.next; END;
oldNode.next := newNode;
END;
END;
END BuildLinkList;
PROCEDURE FindLongest(old, new: TData; lenOld, lenNew, oldPos: LONGINT;
VAR copyStart: PLinkNode; VAR copyLen: LONGINT; hashList: PHashList);
VAR
work: TWorkBytes;
h, n: LONGINT;
start: PLinkNode;
BEGIN
AccessArray(old, lenOld, oldPos, oldPos + PrefixLen - 1, work);
h := Hash(work);
start := hashList[h];
copyLen := 0;
WHILE start # NIL DO
n := 0;
WHILE (oldPos+n < lenOld) & (start.pos+n < lenNew) & (old[oldPos+n] = new[start.pos+n]) DO
INC(n);
END;
IF (oldPos+n <= lenOld) & (start.pos+n <= lenNew) & (n > copyLen) THEN
copyLen := n; copyStart := start;
END;
start := start.next;
END;
END FindLongest;
PROCEDURE EmitAdd(old: TData; VAR dr: Files.Rider; offset, length: LONGINT);
VAR
i: LONGINT;
BEGIN
dr.file.Write(dr, DeltaAddTag);
Files.WriteNum(dr, length);
FOR i := 0 TO length-1 DO dr.file.Write(dr, old[offset + i]); END;
END EmitAdd;
PROCEDURE EmitCopy(VAR dr: Files.Rider; offset, length: LONGINT);
BEGIN
dr.file.Write(dr, DeltaCopyTag);
Files.WriteNum(dr, length);
Files.WriteNum(dr, offset);
END EmitCopy;
PROCEDURE CreateDelta*(old, new: TData; VAR dr: Files.Rider;
lenOld, lenNew: LONGINT);
VAR
oldPos, addStart, copyLen: LONGINT;
copyStart: PLinkNode;
hashList: PHashList;
BEGIN
NEW(hashList);
BuildLinkList(new, lenNew, hashList);
oldPos := 0;
addStart := 0;
WHILE oldPos < lenOld - PrefixLen DO
FindLongest(old, new, lenOld, lenNew, oldPos, copyStart, copyLen,
hashList);
IF copyLen >= PrefixLen THEN
IF addStart < oldPos THEN
EmitAdd(old, dr, addStart, (oldPos )-addStart);
END;
EmitCopy(dr, copyStart.pos, copyLen);
oldPos := oldPos + copyLen;
addStart := oldPos;
ELSE
INC(oldPos);
END;
END;
IF addStart < lenOld -1 THEN
EmitAdd(old, dr, addStart, (lenOld )-addStart);
END;
hashList := NIL;
END CreateDelta;
PROCEDURE ApplyDelta*(old, new: TData; dr: Files.Rider);
VAR
oldPos, newPos, len, i, kk: LONGINT;
tag: CHAR;
BEGIN
oldPos := 0;
LOOP
dr.file.Read(dr, tag);
CASE tag OF
DeltaAddTag:
Files.ReadNum(dr, len);
FOR i := 0 TO len-1 DO
dr.file.Read(dr, old[oldPos]); INC(oldPos);
END;
| DeltaCopyTag:
Files.ReadNum(dr, len);
Files.ReadNum(dr, newPos);
FOR i := 0 TO len - 1 DO
old[oldPos] := new[newPos + i]; INC(oldPos);
END;
ELSE
EXIT
END;
END;
END ApplyDelta;
PROCEDURE NameToDelta(name: TFileName; VAR df: TFileName);
VAR
i, ofs: LONGINT;
BEGIN
i := Strings.Pos(":", name);
IF i = -1 THEN
df := BaseDir;
ofs := Strings.Length(df);
ELSE
ofs := 0;
END;
i := 0;
WHILE (name[i] # 0X) DO
df[i+ofs] := name[i];
INC(i);
END;
df[i+ofs] := "."; df[i+ofs+1] := "V"; df[i+ofs+2] := "C"; df[i+ofs+3] := "S"; df[i+ofs+4] := 0X;
END NameToDelta;
PROCEDURE NameToBak(name: TFileName; VAR df: TFileName);
VAR
i: INTEGER;
BEGIN
i := 0;
WHILE (name[i] # 0X) DO
df[i] := name[i];
INC(i);
END;
df[i] := "."; df[i+1] := "B"; df[i+2] := "a"; df[i+3] := "k"; df[i+4] := 0X;
END NameToBak;
PROCEDURE GetTextLen(VAR fr: Files.Rider; f: Files.File): LONGINT;
VAR
len: LONGINT;
BEGIN
f.Set(fr, FormatLen+4+1);
Files.ReadNum(fr, len);
RETURN len
END GetTextLen;
PROCEDURE GetNewestVersion(fr: Files.Rider; f: Files.File): LONGINT;
VAR
newestVersion, len: LONGINT;
tag: CHAR;
df: Files.File;
dfr: Files.Rider;
dfn: TFileName;
BEGIN
f.Set(fr, FormatLen+4+1);
Files.ReadNum(fr, len);
f.Set(fr, fr.file.Pos(fr)+len+1);
Files.ReadNum(fr, len);
Files.ReadNum(fr, len);
fr.file.Read(fr, tag);
Files.ReadNum(fr, newestVersion);
RETURN newestVersion
END GetNewestVersion;
PROCEDURE GetDiffPos(fr: Files.Rider; f: Files.File; n: LONGINT): LONGINT;
VAR
diffPos, nextDiffPos, len, diffLen, version: LONGINT;
tag: CHAR;
df: Files.File;
dfr: Files.Rider;
dfn: TFileName;
BEGIN
f.Set(fr, FormatLen+4+1);
Files.ReadNum(fr, len);
diffPos := fr.file.Pos(fr)+len;
LOOP
f.Set(fr, diffPos+1);
Files.ReadNum(fr, diffLen);
nextDiffPos := fr.file.Pos(fr) + diffLen;
Files.ReadNum(fr, len);
fr.file.Read(fr, tag);
Files.ReadNum(fr, version);
IF version <= n THEN
EXIT;
ELSE
diffPos := nextDiffPos;
END;
END;
RETURN diffPos
END GetDiffPos;
PROCEDURE Init*(name: TFileName): LONGINT;
VAR
newestVersion: LONGINT;
df: Files.File;
dfr: Files.Rider;
dfn: TFileName;
BEGIN
NameToDelta(name, dfn);
df := Files.Old(dfn);
IF df = NIL THEN
errMsg := " *.VCS file not found ";
newestVersion := -1;
ELSE
newestVersion := GetNewestVersion(dfr, df);
END;
RETURN newestVersion
END Init;
PROCEDURE GetLog*(name: TFileName; n: LONGINT; VAR log: TLog);
VAR
diffLen, diffStart, num: LONGINT;
textLen: LONGINT;
dFileName: TFileName;
df: Files.File;
dr: Files.Rider;
tag: CHAR;
BEGIN
NameToDelta(name, dFileName);
df := Files.Old(dFileName);
IF df=NIL THEN
log.author := "";
log.logText := "";
log.versionID := -1;
log.date := "";
log.flags := {};
RETURN;
END;
IF n > GetNewestVersion(dr, df) THEN
log.author := "";
log.logText := "";
log.versionID := -1;
log.date := "";
log.flags := {};
RETURN;
END;
df.Set(dr, FormatLen); Files.ReadSet(dr, log.flags);
textLen := GetTextLen(dr, df);
df.Set(dr, dr.file.Pos(dr)+textLen);
LOOP
dr.file.Read(dr, tag);
IF tag # DiffTag THEN
Out.String("DiffTag expected"); Out.Ln;
END;
Files.ReadNum(dr, diffLen);
diffStart := dr.file.Pos(dr);
Files.ReadNum(dr, log.lenOfOld);
dr.file.Read(dr, tag);
IF tag # VersionTag THEN
Out.String("VersionTag expected"); Out.Ln;
END;
Files.ReadNum(dr, log.versionID);
IF log.versionID > n THEN
df.Set(dr, diffStart+diffLen);
ELSIF log.versionID < n THEN
dr.eof := TRUE; RETURN;
ELSE
LOOP
dr.file.Read(dr, tag);
IF dr.eof THEN RETURN END;
CASE tag OF
DiffTag:
RETURN;
| DateTag:
Files.ReadNum(dr, num);
dr.file.ReadBytes(dr, log.date, 0, num);
| AuthorTag:
Files.ReadNum(dr, num);
dr.file.ReadBytes(dr, log.author, 0, num);
| LogTextTag:
Files.ReadNum(dr, num);
dr.file.ReadBytes(dr, log.logText, 0, num);
RETURN;
ELSE
Files.ReadNum(dr, num);
df.Set(dr, dr.file.Pos(dr)+num);
END;
END;
RETURN;
END;
END;
END GetLog;
PROCEDURE GetDelta*(name: TFileName; n: LONGINT; VAR df: Files.File;
VAR dr: Files.Rider);
VAR
diffStart, diffLen, version: LONGINT;
dFileName: TFileName;
log: TLog;
tag: CHAR; num: LONGINT;
BEGIN
IF df = NIL THEN
NameToDelta(name, dFileName);
df := Files.Old(dFileName);
END;
df.Set(dr, FormatLen+4);
dr.file.Read(dr, tag);
Files.ReadNum(dr, num);
df.Set(dr, dr.file.Pos(dr)+num);
LOOP
dr.file.Read(dr, tag);
IF tag # DiffTag THEN
Out.String("DiffTag expected"); Out.Ln;
END;
Files.ReadNum(dr, diffLen);
diffStart := dr.file.Pos(dr);
Files.ReadNum(dr, num);
dr.file.Read(dr, tag);
IF tag # VersionTag THEN
Out.String("VersionTag expected"); Out.Ln;
END;
Files.ReadNum(dr, version);
IF version > n THEN
df.Set(dr, diffStart+diffLen);
ELSIF version < n THEN
dr.eof := TRUE; RETURN;
ELSE
LOOP
dr.file.Read(dr, tag);
CASE tag OF
DeltaAddTag, DeltaCopyTag:
df.Set(dr, dr.file.Pos(dr)-1); RETURN;
| DiffTag:
dr.eof := TRUE; RETURN;
ELSE
Files.ReadNum(dr, num);
df.Set(dr, dr.file.Pos(dr)+num);
END;
END;
END;
END;
END GetDelta;
PROCEDURE SetLog*(name: TFileName; n: INTEGER; log: TLog);
VAR
nrOfDeltas: INTEGER;
fPos: LONGINT;
dFileName: TFileName;
oldLog: TLog;
df: Files.File;
dr: Files.Rider;
bytes: POINTER TO ARRAY OF CHAR;
BEGIN
NameToDelta(name, dFileName);
df := Files.Old(dFileName);
IF df=NIL THEN RETURN; END;
df.Set(dr, 0);
Files.ReadInt(dr, nrOfDeltas);
IF n > nrOfDeltas THEN RETURN; END;
Files.ReadLInt(dr, fPos);
REPEAT
NEW(bytes, SYSTEM.SIZEOF(TLog));
dr.file.ReadBytes(dr, bytes^, 0, SYSTEM.SIZEOF(TLog));
SYSTEM.MOVE(SYSTEM.ADR(bytes^), SYSTEM.ADR(oldLog), SYSTEM.SIZEOF(TLog));
IF oldLog.versionID=n THEN
df.Set(dr, dr.file.Pos(dr)-SYSTEM.SIZEOF(TLog));
SYSTEM.MOVE(SYSTEM.ADR(log), SYSTEM.ADR(bytes^), SYSTEM.SIZEOF(TLog));
dr.file.WriteBytes(dr, bytes^, 0, SYSTEM.SIZEOF(TLog));
END;
IF oldLog.lenOfDelta > 0 THEN
df.Set(dr, dr.file.Pos(dr)+oldLog.lenOfDelta);
END;
UNTIL oldLog.versionID=n;
Files.Register(df);
END SetLog;
PROCEDURE GetDeltaList*(name: TFileName; VAR list: TDList): INTEGER;
VAR
log: TLog;
i: INTEGER;
BEGIN
i := 1;
GetLog(name, i, log);
WHILE log.versionID # -1 DO
COPY(log.logText, list[i-1]);
INC(i);
GetLog(name, i, log);
END;
RETURN i-1;
END GetDeltaList;
PROCEDURE WriteTag(VAR rdr: Files.Rider;
tag: CHAR; len: LONGINT; VAR data: ARRAY OF CHAR);
BEGIN
rdr.file.Write(rdr, tag);
Files.WriteNum(rdr, len);
rdr.file.WriteBytes(rdr, data, 0, len);
END WriteTag;
PROCEDURE Create*(historyName, name: TFileName; log: TLog; flags: SET): LONGINT;
BEGIN
RETURN newVersion(historyName, name, log, flags)
END Create;
PROCEDURE NewVersion*(name: TFileName; log: TLog; flags: SET): LONGINT;
BEGIN
RETURN newVersion(name, name, log, flags)
END NewVersion;
PROCEDURE newVersion(historyName, name: TFileName; log: TLog; flags: SET): LONGINT;
VAR
i: LONGINT;
ch: CHAR;
tmpDFileName, dFileName, dFileNameBak: TFileName;
old, new: TData;
lenNew, oldDiffStart: LONGINT;
odf, ndf, f, diff: Files.File;
odfRdr, ndfRdr, fRdr, diffRdr: Files.Rider;
msg: ARRAY 256 OF CHAR;
BEGIN
tmpDFileName := TmpDFile;
f := Files.Old(name);
IF f # NIL THEN
f.Set(fRdr, 0);
lenNew := f.Length();
NEW(new, lenNew);
fRdr.file.ReadBytes(fRdr, new^, 0, lenNew);
ELSE
errMsg := " file not found "; RETURN -1;
END;
diff := Files.New("");
diff.Set(diffRdr, 0);
NameToDelta(historyName, dFileName);
NameToBak(dFileName, dFileNameBak);
Out.Enter; Out.String("VCSBase.Create: "); Out.String(name); Out.Char(" "); Out.String(dFileName); Out.Exit;
odf := Files.Old(dFileName);
ndf := Files.New(TmpDFile);
ndf.Set(ndfRdr, 0);
ndfRdr.file.WriteBytes(ndfRdr, formatStr, 0, FormatLen);
Files.WriteSet(ndfRdr, flags);
WriteTag(ndfRdr, TextTag, lenNew, new^);
IF odf # NIL THEN
log.versionID := GetNewestVersion(odfRdr, odf);
INC(log.versionID);
log.lenOfOld := GetTextLen(odfRdr, odf);
oldDiffStart := odfRdr.file.Pos(odfRdr) + log.lenOfOld;
ELSE
log.versionID := 1;
log.lenOfOld := 0;
END;
Files.WriteNum(diffRdr, log.lenOfOld);
diffRdr.file.Write(diffRdr, VersionTag); Files.WriteNum(diffRdr, log.versionID);
IF log.date = "" THEN DateTime(log.date); END;
WriteTag(diffRdr, DateTag, Strings.Length(log.date)+1, log.date);
WriteTag(diffRdr, AuthorTag, Strings.Length(log.author)+1, log.author);
WriteTag(diffRdr, LogTextTag,Strings.Length(log.logText)+1,log.logText);
IF odf # NIL THEN
NEW(old, log.lenOfOld);
odfRdr.file.ReadBytes(odfRdr, old^, 0, log.lenOfOld);
CreateDelta(old, new, diffRdr, log.lenOfOld, lenNew);
END;
ndfRdr.file.Write(ndfRdr, DiffTag);
Files.WriteNum(ndfRdr, diff.Length());
diff.Set(diffRdr, 0);
FOR i := 0 TO diff.Length() - 1 DO
diffRdr.file.Read(diffRdr, ch); ndfRdr.file.Write(ndfRdr, ch);
END;
IF odf # NIL THEN
odf.Set(odfRdr, oldDiffStart);
FOR i := 0 TO odf.Length() - 1 - oldDiffStart DO
odfRdr.file.Read(odfRdr, ch); ndfRdr.file.Write(ndfRdr, ch);
END;
END;
Files.Register(ndf);
IF odf # NIL THEN
Files.Delete(dFileNameBak, res);
Files.Rename(dFileName, dFileNameBak, res);
IF res # 0 THEN
msg := "VCSBase: "; Strings.Append(msg, dFileName); Strings.Append(msg, " => ");
Strings.Append(msg, dFileNameBak); Strings.Append(msg, " = ");
Out.Enter; Out.String(msg); Out.Int(res, 4); Out.Exit;
END;
END;
Files.Rename(tmpDFileName, dFileName, res);
IF res # 0 THEN
errMsg := "VCSBase.Create: 'Error on Rename' "; Strings.Append(errMsg, TmpDFile);
Strings.Append(errMsg, " to "); Strings.Append(errMsg, dFileName);
log.versionID := 0;
END;
RETURN log.versionID;
END newVersion;
PROCEDURE View*(name: TFileName; n: LONGINT; newFileName: TFileName): LONGINT;
VAR
version, res: LONGINT;
ok: BOOLEAN;
dFileName: TFileName;
old, new: TData;
log: TLog;
lenOld, lenNew: LONGINT;
df, f: Files.File;
dr, r: Files.Rider;
BEGIN
NameToDelta(name, dFileName);
df := Files.Old(dFileName);
IF df=NIL THEN errMsg := " file not found "; RETURN -1; END;
version := GetNewestVersion(dr, df);
IF n > version THEN
errMsg := " not so many versions "; RETURN -1;
END;
lenNew := GetTextLen(dr,df);
NEW(new, lenNew);
dr.file.ReadBytes(dr, new^, 0, lenNew);
WHILE version > n DO
GetLog(name, version, log);
GetDelta(name, version, df, dr);
lenOld := log.lenOfOld;
NEW(old, lenOld);
ApplyDelta(old, new, dr);
lenNew := lenOld;
new := old;
DEC(version);
END;
res := makeDirs(newFileName);
IF res = 0 THEN
f := Files.New(newFileName);
IF f = NIL THEN
errMsg := " couldn't create new file "; Strings.Append(errMsg, newFileName);
RETURN -1;
END;
ELSE
errMsg := " couldn't create directories for "; Strings.Append(errMsg, newFileName);
RETURN res;
END;
f.Set(r, 0);
r.file.WriteBytes(r, new^, 0, lenNew);
Files.Register(f);
RETURN version;
END View;
PROCEDURE Extract*(name: TFileName; n: LONGINT): LONGINT;
VAR
version: LONGINT;
ok: BOOLEAN;
tmpDFileName, dFileName, dFileNameBak: TFileName;
old, new: TData;
log: TLog;
lenOld, lenNew, lenDelta, fPos: LONGINT;
tdf, df, f: Files.File;
tdr, dr, r, deltaRdr: Files.Rider;
i: LONGINT; ch: CHAR;
flags: SET;
BEGIN
tmpDFileName := TmpDFile;
f := Files.Old(name);
IF f #NIL THEN
errMsg := " don't want to overwrite working file ";
RETURN -1;
END;
df := Files.Old(dFileName);
IF df=NIL THEN
errMsg := " delta file not found ";
RETURN -1;
END;
version := GetNewestVersion(dr, df);
IF n > version THEN
errMsg := " not so many versions "; RETURN -1;
ELSIF n = version THEN
errMsg := " is newest version "; RETURN -1;
END;
lenNew := GetTextLen(dr,df);
NEW(new, lenNew);
dr.file.ReadBytes(dr, new^, 0, lenNew);
WHILE version>n DO
GetLog(name, version, log);
GetDelta(name, version, df, deltaRdr);
lenOld := log.lenOfOld;
NEW(old, lenOld);
ApplyDelta(old, new, deltaRdr);
lenNew := lenOld;
new := old;
DEC(version);
END;
f := Files.New(name);
f.Set(r, 0);
r.file.WriteBytes(r, new^, 0, lenNew);
Files.Register(f);
tdf := Files.New(TmpDFile);
tdf.Set(tdr, 0);
tdr.file.WriteBytes(tdr, formatStr, 0, FormatLen);
df.Set(dr, FormatLen);
Files.ReadSet(dr, flags); Files.WriteSet(tdr, flags);
WriteTag(tdr, TextTag, lenNew, new^);
df.Set(dr, GetDiffPos(dr, df, n));
LOOP
dr.file.Read(dr, ch);
IF dr.eof THEN EXIT; END;
tdr.file.Write(tdr, ch);
END;
Files.Register(tdf);
Files.Delete(dFileNameBak, res);
Files.Rename(dFileName, dFileNameBak, res);
Files.Rename(tmpDFileName, dFileName, res);
new := NIL; old := NIL;
RETURN version;
END Extract;
BEGIN
formatStr := FormatName;
END OdVCSBase.