1. Mechiel Lukkien
  2. hgfs

Commits

Mechiel Lukkien  committed 1e0449b Draft

calculate diffs.

appl/lib/bdiff.[bm] makes binary diffs now (and the delta parsing
code is moved there too). diffs are always line-based (after i
implemented it to break at any boundary. the manifest parsing code
of mainline mercurial apparently expects diffs to be line-based).
bdiff is quite stupid. it splits old & new file into lines, then
walks through lines in new file, finding the longest sequence of
lines (longest in terms of bytes) and does the same again on both
sides of the common sequence of line.

hg/mkdelta creates a delta given two files.
hg/printdelta (previously hg/printpatch) parses & dumps a delta.
hg/applydelta (previously hg/testapply) applies deltas to a base.

  • Participants
  • Parent commits e771a84
  • Branches default

Comments (0)

Files changed (17)

File README

View file
  • Ignore whitespace
 
 # todo
 
+- hg/update: should update to the branch tip, not the repo tip.
+- hg/update: should fail to update without -C when there are two
+  parents, or for cross-branch update (unless the target name specified
+  was the branch?)
+- hg/status (and others probably): fix handling of paths, e.g.
+  "hg/status" in dir1/ should list all changes in the repo, "hg/status ."
+  in dir1/ should list only those in dir1/.
+- hg/mv, hg/cp, hg/push, hg/merge, hg/rollback, hg/bundle, hg/unbundle
 - hg/update: for local modifications (as returned by hg/status),
   only refuse to update if their state is actually different from
   that in new revision.  i.e. for "add", don't complain if new revision
   has that file and it has the same contents.  for "remove", don't
   complain if new revision has that file removed.  for "update", check
   if new revision has same data for file.
+- for abort while in write transaction, for truncates to zero length,
+  remove the files instead.
 - for dirstate, handle needmerge more like modified?  i.e. verify
   that it really changed, especially during commit.
 - for commit,update,etc, handle dirstate with p1 & p2 (merge).
 - read up on all the formats.  dirstate, undo.dirstate undo.branch
   (for rollback), journal.dirstate, journal.branch, wlock;  lock,
   journal, fncache, etc.;  http://mercurial.selenic.com/wiki/FileFormats
+- hg/fs: fix (revert) sizes of files in hg/fs when they have meta-data
+  (cannot use entry.uncsize for that reason, i forgot).
 - library: think about caching of revlogs per repo, caching of
   entries in repo's and perhaps try reading less to become up to date.
 

File appl/cmd/hg/commit.b

View file
  • Ignore whitespace
 	{ init0(args); }
 	exception e {
 	"hg:*" =>
-		repo.xrollback(tr);
+		if(tr != nil)
+			repo.xrollback(tr);
 		fail(e[3:]);
 	}
 }

File appl/cmd/hg/mkfile

View file
  • Ignore whitespace
 
 MODULES=\
 	../../lib/mercurialremote.m\
+	../../lib/bdiff.m\
 
 SYSMODULES=\
 	mercurial.m\

File appl/cmd/hg/pull.b

View file
  • Ignore whitespace
 	max, g32i, hex, hasstr, rev, join, prefix, suffix, readfd, l2a, inssort, warn, fail: import util;
 include "mercurial.m";
 	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry, Patch, Configs: import hg;
+	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry, Configs: import hg;
 include "mhttp.m";
 include "../../lib/mercurialremote.m";
 	hgrem: Mercurialremote;

File appl/cmd/hg/status.b

View file
  • Ignore whitespace
 	ds := hg->xdirstate(repo, 1);
 
 	# first print status for all known files
-	a := l2a(ds.enumerate(repo.xworkdir(), args, 1, 1).t1);
+	a := l2a(ds.enumerate(".", args, 1, 1).t1);
 	inssort(a, statepathge);
 	for(i := 0; i < len a; i++) {
 		f := a[i];

File appl/cmd/test/applydelta.b

View file
  • Ignore whitespace
+implement HgApplydelta;
+
+include "sys.m";
+	sys: Sys;
+	sprint: import sys;
+include "draw.m";
+include "arg.m";
+include "util0.m";
+	util: Util0;
+	rev, l2a, warn, fail, readfile: import util;
+include "../../lib/bdiff.m";
+	bdiff: Bdiff;
+	Patch, Delta: import bdiff;
+
+HgApplydelta: module {
+	init:	fn(nil: ref Draw->Context, args: list of string);
+};
+
+
+dflag: int;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+	sys = load Sys Sys->PATH;
+	arg := load Arg Arg->PATH;
+	util = load Util0 Util0->PATH;
+	util->init();
+	bdiff = load Bdiff Bdiff->PATH;
+	bdiff->init();
+
+	arg->init(args);
+	arg->setusage(arg->progname()+" [-d] base delta1 ...");
+	while((c := arg->opt()) != 0)
+		case c {
+		'd' =>	dflag++;
+		* =>	arg->usage();
+		}
+	args = arg->argv();
+	if(len args < 2)
+		arg->usage();
+	base := readfile(hd args, -1);
+	if(base == nil)
+		fail(sprint("%r"));
+	l: list of array of byte;
+	for(args = tl args; args != nil; args = tl args) {
+		l = readfile(hd args, -1)::l;
+		if(hd l == nil)
+			fail(sprint("%r"));
+	}
+	l = rev(l);
+
+	(r, err) := Delta.applymany(base, l2a(l));
+	if(err != nil)
+		fail(err);
+	if(sys->write(sys->fildes(1), r, len r) != len r)
+		fail(sprint("write: %r"));
+}

File appl/cmd/test/mkdelta.b

View file
  • Ignore whitespace
+implement Mkdelta;
+
+include "sys.m";
+	sys: Sys;
+	sprint: import sys;
+include "draw.m";
+include "arg.m";
+include "util0.m";
+	util: Util0;
+	fail, warn, readfile: import util;
+include "../../lib/bdiff.m";
+	bdiff: Bdiff;
+	Delta: import bdiff;
+
+Mkdelta: module {
+	init:	fn(nil: ref Draw->Context, nil: list of string);
+};
+
+dflag: int;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+	sys = load Sys Sys->PATH;
+	arg := load Arg Arg->PATH;
+	util = load Util0 Util0->PATH;
+	util->init();
+	bdiff = load Bdiff Bdiff->PATH;
+	if(bdiff == nil)
+		fail(sprint("load Bdiff %q: %r", Bdiff->PATH));
+
+	arg->init(args);
+	arg->setusage(arg->progname()+" [-d] file1 file2");
+	while((c := arg->opt()) != 0)
+		case c {
+		'd' =>	dflag++;
+		* =>	arg->usage();
+		}
+	args = arg->argv();
+	if(len args != 2)
+		arg->usage();
+
+	a := readfile(hd args, -1);
+	b := readfile(hd tl args, -1);
+	if(a == nil || b == nil)
+		fail(sprint("%r"));
+	d := bdiff->diff(a, b);
+	buf := d.pack();
+	if(sys->write(sys->fildes(1), buf, len buf) != len buf)
+		fail(sprint("write: %r"));
+}

File appl/cmd/test/mkfile

View file
  • Ignore whitespace
 
 TARG=\
 	delta.dis\
-	printpatch.dis\
+	printconfig.dis\
+	printdelta.dis\
+	applydelta.dis\
+	mkdelta.dis\
 	printchangegroup.dis\
-	testapply.dis\
-	printconfig.dis\
+
+MODULES=\
+	../../lib/mercurialwire.m\
+	../../lib/mercurialremote.m\
+	../../lib/bdiff.m\
 
 SYSMODULES=\
 	mercurial.m\

File appl/cmd/test/printchangegroup.b

View file
  • Ignore whitespace
 include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Patch, Hunk: import hg;
+	Dirstate, Dsfile, Revlog, Repo, Change: import hg;
+include "../../lib/bdiff.m";
+	bdiff: Bdiff;
+	Delta, Patch: import bdiff;
 
 HgPrintchangegroup: module {
 	init:	fn(nil: ref Draw->Context, args: list of string);
 
 Chunk: adt {
 	n, p1, p2, link:	string;
-	p:	ref Patch;
+	d:	ref Delta;
 };
 
 init(nil: ref Draw->Context, args: list of string)
 	base16 = load Encoding Encoding->BASE16PATH;
 	hg = load Mercurial Mercurial->PATH;
 	hg->init(0);
+	bdiff = load Bdiff Bdiff->PATH;
+	bdiff->init();
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-dv]");
 	sys->print("\tp2:    %q\n", c.p2);
 	sys->print("\tlink:  %q\n", c.link);
 	if(vflag) {
-		sys->print("\thunks:\n");
-		for(l := c.p.l; l != nil; l = tl l) {
-			h := hd l;
-			sys->print("\t\tstart=%d end=%d length=%d buf:\n%s\n\n", h.start, h.end, len h.buf, string h.buf);
+		sys->print("\tpatches:\n");
+		for(l := c.d.l; l != nil; l = tl l) {
+			p := hd l;
+			sys->print("\t\t%s\n", p.text());
+			sys->print("\t\t%s\n", string p.d);
 		}
 	} else {
-		sys->print("\thunks: %d\n", len c.p.l);
+		sys->print("\tpatches: %d\n", len c.d.l);
 	}
 	sys->print("\n");
 }
 		o += 20;
 		c.link = hg->hex(buf[o:o+20]);
 		o += 20;
-		p := Patch.xparse(buf[o:]);
-		c.p = p;
+		(d, err) := Delta.parse(buf[o:]);
+		if(err != nil)
+			fail("parsing patch: "+err);
+		c.d = d;
 	} exception x {
 	"hg:*" =>
 		fail("parsing patch: "+x[3:]);

File appl/cmd/test/printdelta.b

View file
  • Ignore whitespace
+implement HgPrintdelta;
+
+include "sys.m";
+	sys: Sys;
+	sprint: import sys;
+include "draw.m";
+include "arg.m";
+include "util0.m";
+	util: Util0;
+	warn, fail, readfd: import util;
+include "../../lib/bdiff.m";
+	bdiff: Bdiff;
+	Delta, Patch: import bdiff;
+
+HgPrintdelta: module {
+	init:	fn(nil: ref Draw->Context, args: list of string);
+};
+
+
+dflag,
+vflag: int;
+
+init(nil: ref Draw->Context, args: list of string)
+{
+	sys = load Sys Sys->PATH;
+	arg := load Arg Arg->PATH;
+	util = load Util0 Util0->PATH;
+	util->init();
+	bdiff = load Bdiff Bdiff->PATH;
+	bdiff->init();
+
+	arg->init(args);
+	arg->setusage(arg->progname()+" [-dv]");
+	while((c := arg->opt()) != 0)
+		case c {
+		'd' =>	dflag++;
+		'v' =>	vflag++;
+		* =>	arg->usage();
+		}
+	args = arg->argv();
+	if(args != nil)
+		arg->usage();
+
+	buf := readfd(sys->fildes(0), -1);
+	if(buf == nil)
+		fail(sprint("%r"));
+	(d, err) := Delta.parse(buf);
+	if(err != nil)
+		fail("parsing delta: "+err);
+	for(l := d.l; l != nil; l = tl l) {
+		p := hd l;
+		sys->print("%s\n", p.text());
+		if(vflag)
+			sys->print("%s\n", string p.d);
+	}
+}

File appl/cmd/test/printpatch.b

  • Ignore whitespace
-implement HgPrintpatch;
-
-include "sys.m";
-	sys: Sys;
-	sprint: import sys;
-include "draw.m";
-include "arg.m";
-include "bufio.m";
-include "string.m";
-	str: String;
-include "tables.m";
-include "mercurial.m";
-	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Patch, Hunk: import hg;
-
-HgPrintpatch: module {
-	init:	fn(nil: ref Draw->Context, args: list of string);
-};
-
-
-dflag: int;
-
-init(nil: ref Draw->Context, args: list of string)
-{
-	sys = load Sys Sys->PATH;
-	arg := load Arg Arg->PATH;
-	str = load String String->PATH;
-	hg = load Mercurial Mercurial->PATH;
-	hg->init(0);
-
-	arg->init(args);
-	arg->setusage(arg->progname()+" [-d]");
-	while((c := arg->opt()) != 0)
-		case c {
-		'd' =>	hg->debug = dflag++;
-		* =>	arg->usage();
-		}
-	args = arg->argv();
-	if(args != nil)
-		arg->usage();
-
-	{
-		buf := get();
-		p := Patch.xparse(buf);
-		for(l := p.l; l != nil; l = tl l) {
-			h := hd l;
-			sys->print("start=%d end=%d buf:\n%s\n", h.start, h.end, string h.buf);
-		}
-	} exception x {
-	"hg:*" =>
-		fail(x[3:]);
-	}
-}
-
-get(): array of byte
-{
-	d := array[32*1024] of byte;
-	buf := array[0] of byte;
-	fd := sys->fildes(0);
-	for(;;) {
-		n := sys->read(fd, d, len d);
-		if(n < 0)
-			fail(sprint("read: %r"));
-		if(n == 0)
-			break;
-		nbuf := array[len buf+n] of byte;
-		nbuf[:] = buf;
-		nbuf[len buf:] = d[:n];
-		buf = nbuf;
-	}
-	return buf;
-}
-
-warn(s: string)
-{
-	sys->fprint(sys->fildes(2), "%s\n", s);
-}
-
-fail(s: string)
-{
-	warn(s);
-	raise "fail:"+s;
-}

File appl/cmd/test/testapply.b

  • Ignore whitespace
-implement HgTestapply;
-
-include "sys.m";
-	sys: Sys;
-	sprint: import sys;
-include "draw.m";
-include "arg.m";
-include "bufio.m";
-include "string.m";
-	str: String;
-include "util0.m";
-	util: Util0;
-	rev, l2a, warn, fail, readfile: import util;
-include "tables.m";
-include "mercurial.m";
-	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Patch, Hunk: import hg;
-
-HgTestapply: module {
-	init:	fn(nil: ref Draw->Context, args: list of string);
-};
-
-
-dflag: int;
-
-init(nil: ref Draw->Context, args: list of string)
-{
-	sys = load Sys Sys->PATH;
-	arg := load Arg Arg->PATH;
-	str = load String String->PATH;
-	util = load Util0 Util0->PATH;
-	util->init();
-	hg = load Mercurial Mercurial->PATH;
-	hg->init(0);
-
-	arg->init(args);
-	arg->setusage(arg->progname()+" [-d] base patch1 ...");
-	while((c := arg->opt()) != 0)
-		case c {
-		'd' =>	hg->debug = dflag++;
-		* =>	arg->usage();
-		}
-	args = arg->argv();
-	if(len args < 2)
-		arg->usage();
-	base := readfile(hd args, -1);
-	if(base == nil)
-		fail(sprint("%r"));
-	l: list of array of byte;
-	for(args = tl args; args != nil; args = tl args) {
-		l = readfile(hd args, -1)::l;
-		if(hd l == nil)
-			fail(sprint("%r"));
-	}
-	l = rev(l);
-	{
-		r := Patch.xapplymany(base, l2a(l));
-		if(sys->write(sys->fildes(1), r, len r) != len r)
-			fail(sprint("write: %r"));
-	} exception x {
-	"hg:*" =>
-		fail(x[3:]);
-	}
-}

File appl/lib/bdiff.b

View file
  • Ignore whitespace
+implement Bdiff;
+
+include "sys.m";
+	sys: Sys;
+	sprint: import sys;
+include "util0.m";
+	util: Util0;
+	g32i, rev, eq, p32i, max: import util;
+include "bdiff.m";
+
+init()
+{
+	sys = load Sys Sys->PATH;
+	util = load Util0 Util0->PATH;
+	util->init();
+}
+
+Patch.text(p: self ref Patch): string
+{
+	#return sprint("<patch s=%d e=%d length=%d buf=%q>", p.s, p.e, len p.d, string p.d);
+	return sprint("<patch s=%d e=%d length=%d>", p.s, p.e, len p.d);
+}
+
+
+Delta.pack(d: self ref Delta): array of byte
+{
+	n := 0;
+	for(l := d.l; l != nil; l = tl l)
+		n += 3*4+len (hd l).d;
+	buf := array[n] of byte;
+	o := 0;
+	for(l = d.l; l != nil; l = tl l) {
+		p := hd l;
+		o = p32i(buf, o, p.s);
+		o = p32i(buf, o, p.e);
+		o = p32i(buf, o, len p.d);
+		buf[o:] = p.d;
+		o += len p.d;
+	}
+	return buf;
+}
+
+Delta.replaces(d: self ref Delta, n: int): int
+{
+	return n == 0 && len d.l == 0 || len d.l == 1 && (p := hd d.l).s == 0 && p.e == n;
+}
+
+Delta.apply(dd: self ref Delta, b: array of byte): array of byte
+{
+	n := len b+dd.sizediff();
+	d := array[n] of byte;
+	ae := be := 0;
+	for(l := dd.l; l != nil; l = tl l) {
+		p := hd l;
+
+		# copy data before hunk from base to dest
+		d[be:] = b[ae:p.s];
+		be += p.s-ae;
+
+		# copy new data to dest, and skip the removed part from base
+		d[be:] = p.d;
+		be += len p.d;
+		ae = p.e;
+	}
+	# and the trailing common data
+	d[be:] = b[ae:];
+	return d;
+}
+
+Group: adt {
+	l:	list of array of byte;
+	length:	int;	# original length of group
+	o:	int;	# offset of hd l
+
+	add:		fn(g: self ref Group, buf: array of byte);
+	copy:		fn(g: self ref Group, sg: ref Group, s, e: int);
+	flatten:	fn(g: self ref Group): array of byte;
+	size:		fn(g: self ref Group): int;
+	apply:		fn(g: ref Group, d: ref Delta): ref Group;
+};
+
+Group.add(g: self ref Group, buf: array of byte)
+{
+	g.l = buf::g.l;
+	g.length += len buf;
+}
+
+Group.copy(g: self ref Group, sg: ref Group, s, e: int)
+{
+	# seek gs to s
+	drop := s-sg.o;
+	while(drop > 0) {
+		b := hd sg.l;
+		sg.l = tl sg.l;
+		if(drop >= len b) {
+			sg.o += len b;
+			drop -= len b;
+		} else {
+			sg.l = b[drop:]::sg.l;
+			sg.o += drop;
+			drop = 0;
+		}
+	}
+	if(sg.o != s) raise "group:bad0";
+
+	# copy from sg into g
+	n := e-s;
+	while(n > 0 && sg.l != nil) {
+		b := hd sg.l;
+		sg.l = tl sg.l;
+		take := len b;
+		if(take > n) {
+			take = n;
+			sg.l = b[take:]::sg.l;
+		}
+		g.add(b[:take]);
+		sg.o += take;
+		n -= take;
+	}
+	if(n != 0) raise "group:bad1";
+}
+
+# note: we destruct g (in Group.copy), keeping g.o & hd g.l in sync.
+# we never have to go back before an offset after having read it.
+Group.apply(g: ref Group, d: ref Delta): ref Group
+{
+	g = ref *g;
+	ng := ref Group (nil, 0, 0);
+	o := 0;
+	for(l := d.l; l != nil; l = tl l) {
+		p := hd l;
+		ng.copy(g, o, p.s);
+		ng.add(p.d);
+		o = p.e;
+	}
+	ng.copy(g, o, g.size());
+	ng.l = rev(ng.l);
+	return ng;
+}
+
+Group.size(g: self ref Group): int
+{
+	return g.length;
+}
+
+Group.flatten(g: self ref Group): array of byte
+{
+	d := array[g.size()] of byte;
+	o := 0;
+	for(l := g.l; l != nil; l = tl l) {
+		d[o:] = hd l;
+		o += len hd l;
+	}
+	return d;
+}
+
+Delta.applymany(base: array of byte, deltas: array of array of byte): (array of byte, string)
+{
+	if(len deltas == 0)
+		return (base, nil);
+
+	g := ref Group (base::nil, len base, 0);
+	for(i := 0; i < len deltas; i++) {
+		(p, err) := Delta.parse(deltas[i]);
+		if(err != nil)
+			return (nil, err);
+		{
+			g = Group.apply(g, p);
+		} exception e {
+		"group:*" =>
+			return (nil, "bad patch: "+e[len "group:":]);
+		}
+	}
+	return (g.flatten(), nil);
+}
+
+Delta.sizediff(d: self ref Delta): int
+{
+	n := 0;
+	for(l := d.l; l != nil; l = tl l) {
+		p := hd l;
+		n += len p.d - (p.e-p.s);
+	}
+	return n;
+}
+
+Delta.parse(d: array of byte): (ref Delta, string)
+{
+	o := 0;
+	l: list of ref Patch;
+	while(o+12 <= len d) {
+		s, e, n: int;
+		(s, o) = g32i(d, o);
+		(e, o) = g32i(d, o);
+		(n, o) = g32i(d, o);
+		if(s > e)
+			return (nil, sprint("bad data, start %d > end %d", s, e));
+		if(o+n > len d)
+			return (nil, sprint("bad data, patch points past buffer, o+length %d+%d > len d %d", o, n, len d));
+		buf := array[n] of byte;
+		buf[:] = d[o:o+n];
+
+		p := ref Patch (s, e, buf);
+		if(l != nil && p.s < (hd l).e)
+			return (nil, sprint("bad delta, patch starts before preceding patch, start %d < end %d", p.s, (hd l).e));
+		l = p::l;
+		o += n;
+	}
+	if(o != len d)
+		return (nil, sprint("leftover bytes in delta, o %d != len d %d", o, len d));
+	return (ref Delta (rev(l)), nil);
+}
+
+Delta.text(d: self ref Delta): string
+{
+	s: string;
+	for(l := d.l; l != nil; l = tl l)
+		s += sprint(" %s", (hd l).text());
+	if(s != nil)
+		s = s[1:];
+	return s;
+}
+
+
+Line: adt {
+	hash,
+	s,
+	e,
+	i:	int;
+};
+
+State: adt {
+	a,
+	b:	array of byte;
+	la,
+	lb:	array of ref Line;
+	tab:	array of list of ref Line;
+	r:	list of ref Patch;
+};
+
+# diff, on lines
+diff(a, b: array of byte): ref Delta
+{
+	la := bounds(a);
+	lb := bounds(b);
+
+	# skip leading & trailing common lines, so the result will start & end in different lines.
+	ea := len la;
+	eb := len lb;
+	for(s := 0; s < ea && s < eb ; s++) {
+		l0 := la[s];
+		l1 := lb[s];
+		if(l0.hash != l1.hash || l0.e != l1.e || !eq(a[l0.s:l0.e], b[l0.s:l0.e]))
+			break;
+	}
+	while(ea > s && eb > s) {
+		l0 := la[--ea];
+		l1 := lb[--eb];
+		if(l0.hash != l1.hash || l0.e-l0.s != l1.e-l1.s || !eq(a[l0.s:l0.e], b[l1.s:l1.e])) {
+			ea++;
+			eb++;
+			break;
+		}
+	}
+
+	tab := array[max(1, ea/8)] of list of ref Line;
+	for(i := s; i < ea; i++) {
+		l0 := la[i];
+		h := l0.hash%len tab;
+		tab[h] = l0::tab[h];
+	}
+
+	st := ref State (a, b, la, lb, tab, nil);
+	diff0(st, s, s, ea, eb);
+	return ref Delta (rev(st.r));
+}
+
+# find line boundaries & hash the lines
+bounds(d: array of byte): array of ref Line
+{
+	l: list of ref Line;
+	s := 0;
+	h := 5381;
+	nd := len d;
+	nl := 0;
+	for(i := 0; i < nd; i++) {
+		c := int d[i];
+		h = (h<<5)+h+c;
+		if(c == '\n') {
+			l = ref Line (h&16r7fffffff, s, i+1, nl++)::l;
+			s = i+1;
+			h = 5381;
+		}
+	}
+	if(s < i)
+		l = ref Line (h&16r7fffffff, s, i, nl)::l;
+	return l2arev(l);
+}
+
+l2arev[T](l: list of T): array of T
+{
+	a := array[len l] of T;
+	i := len a-1;
+	for(; l != nil; l = tl l)
+		a[i--] = hd l;
+	return a;
+}
+
+# find large(st) common substring (whole lines),
+# keep that and diff again on the data before and after
+# sa-ea are the lines from a to look at, ea-eb the lines from b.
+# note that st.tab has all lines from a, so we have to check they are in the range we are looking for.
+diff0(st: ref State, sa, sb: int, ea, eb: int)
+{
+	nla := ea-sa;
+	nlb := eb-sb;
+	if(nla == 0 && nlb == 0)
+		return;
+	if(nla == 0) {
+		s: int;
+		if(len st.la == 0)
+			s = 0;
+		else if(sa == len st.la)
+			s = st.la[sa-1].e;
+		else
+			s = st.la[sa].s;
+		st.r = ref Patch (s, s, st.b[st.lb[sb].s:st.lb[eb-1].e])::st.r;
+		return;
+	}
+	if(nlb == 0) {
+		st.r = ref Patch (st.la[sa].s, st.la[ea-1].e, array[0] of byte)::st.r;
+		return;
+	}
+
+	# length & start+end of largest match so far
+	length := 0;
+	msa, msb, mea, meb: int;
+
+	# for each line in b, for each line from a that matches b, determine the size of
+	# the matching region (in whole lines, extending downwards in file).  keep track
+	# of the largest match while continuing the search.
+	# for each line in b & a, we check whether it has a chance of becoming a larger
+	# match.  if not, we skip it and save ourselves some work.
+	aend := st.la[ea-1].e;
+	bend := st.lb[eb-1].e;
+	for(i := sb; i < eb; i++) {
+		l1 := st.lb[i];
+		if(bend-l1.s <= length)
+			break; # no longer match possible
+
+		for(l := st.tab[l1.hash%len st.tab]; l != nil; l = tl l) {
+			s0 := hd l;
+			s1 := l1;
+			ia := s0.i;
+			ib := s1.i;
+			if(ia >= msa && ia < mea && ib >= msb && ib < meb)
+				continue; # line part of longest match so far
+			if(aend-s0.s <= length)
+				continue; # no longer match possible with this line
+			nlength := 0;
+			for(;;) {
+				if(ia < sa || ia >= ea || ib < sb || ib >= eb)
+					break;
+				l0 := st.la[ia];
+				l1 = st.lb[ib];
+				if(l0.hash != l1.hash || l1.e-l1.s != l0.e-l0.s || !eq(st.a[l0.s:l0.e], st.b[l1.s:l1.e]))
+					break;
+				nlength += l1.e-l1.s;
+				ia++;
+				ib++;
+			}
+			if(nlength > length) {
+				msa = s0.i;
+				mea = ia;
+				msb = s1.i;
+				meb = ib;
+				length = nlength;
+			}
+		}
+	}
+
+	if(length == 0) {
+		st.r = ref Patch (st.la[sa].s, st.la[ea-1].e, st.b[st.lb[sb].s:st.lb[eb-1].e])::st.r;
+		return;
+	}
+
+	diff0(st, sa, sb, msa, msb);
+	diff0(st, mea, meb, ea, eb);
+}

File appl/lib/bdiff.m

View file
  • Ignore whitespace
+Bdiff: module
+{
+	PATH:	con "/dis/lib/bdiff.dis";
+
+	init:	fn();
+
+	Patch: adt {
+		s,
+		e:	int;
+		d:	array of byte;
+
+		text:	fn(p: self ref Patch): string;
+	};
+
+	Delta: adt {
+		l:	list of ref Patch;
+
+		pack:		fn(d: self ref Delta): array of byte;
+		parse:		fn(buf: array of byte): (ref Delta, string);
+		apply:		fn(d: self ref Delta, buf: array of byte): array of byte;
+		applymany:	fn(base: array of byte, deltas: array of array of byte): (array of byte, string);
+		sizediff:	fn(d: self ref Delta): int;
+		replaces:	fn(d: self ref Delta, n: int): int;
+		text:		fn(d: self ref Delta): string;
+	};
+
+	diff:	fn(a, b: array of byte): ref Delta;
+};

File appl/lib/mercurial.b

View file
  • Ignore whitespace
 	Strhash: import tables;
 include "util0.m";
 	util: Util0;
-	g32i, eq, hasstr, p32, p32i, p16, stripws, prefix, suffix, rev, max, l2a, readfile, writefile: import util;
+	g16, g32i, eq, hasstr, p32, p32i, p16, stripws, prefix, suffix, rev, max, l2a, readfile, writefile: import util;
+include "bdiff.m";
+	bdiff: Bdiff;
+	Delta: import bdiff;
 include "mercurial.m";
 
 
 {
 	sys = load Sys Sys->PATH;
 	bufio = load Bufio Bufio->PATH;
-	bufio->open("/dev/null", Bufio->OREAD); # xxx ensure bufio is properly initialized
+	bufio->open("/dev/null", Bufio->OREAD); # ensure bufio is properly initialized
 	env = load Env Env->PATH;
 	keyring = load Keyring Keyring->PATH;
 	str = load String String->PATH;
 	filtertool = load Filtertool Filtertool->PATH;
 	util = load Util0 Util0->PATH;
 	util->init();
+	bdiff = load Bdiff Bdiff->PATH;
+	bdiff->init();
 
 	readonly = rdonly;
 }
 		dsf.state = find(statestrs, sprint("%c", int stb));
 		if(dsf.state < 0)
 			error(sprint("bad state in dirstate at offset %bd, char %#x, %c", off, int stb, int stb));
-		(dsf.mode, o) = g32(buf, o);
-		(dsf.size, o) = g32(buf, o);
-		(dsf.mtime, o) = g32(buf, o);
+		(dsf.mode, o) = g32i(buf, o);
+		(dsf.size, o) = g32i(buf, o);
+		(dsf.mtime, o) = g32i(buf, o);
 		length: int;
-		(length, o) = g32(buf, o);
+		(length, o) = g32i(buf, o);
 		if(length >= 2*1024)
 			error(sprint("implausible path length %d in dirstate", length));
 		n = b.read(namebuf := array[length] of byte, len namebuf);
 Change.hasfile(c: self ref Change, f: string): int
 {
 	dir := f+"/";
-	r: list of string;
 	for(l := c.files; l != nil; l = tl l)
 		if(hd l == f || prefix(dir, hd l))
 			return 1;
 	rl.rlpath = path;
 	rl.path = storedir+"/"+path;
 	rl.fullrev = -1;
+	rl.ncache = 0;
 	rl.cacheall = cacheall;
 
 	rl.ilength = ~big 0;
 	return rl.flags & Indexonly;
 }
 
-xreconstruct(rl: ref Revlog, e: ref Entry, base: array of byte, patches: array of array of byte): array of byte
+xreconstruct(rl: ref Revlog, e: ref Entry, base: array of byte, deltas: array of array of byte): array of byte
 {
-say(sprint("reconstruct, len base %d, len patches %d, e.rev %d", len base, len patches, e.rev));
-
 	# first is base, later are patches
-	d := Patch.xapplymany(base, patches);
+	(d, err) := Delta.applymany(base, deltas);
+	if(err != nil)
+		error("applying patch: "+err);
 
 	# verify data is correct
 	pn1 := pn2 := nullnode;
 	}
 
 	if(rl.bd.seek(e.ioffset, Bufio->SEEKSTART) != e.ioffset)
-		error(sprint("seek %bd: %r", e.ioffset));
+		error(sprint("seek %q %bd: %r", rl.path, e.ioffset));
 	if(breadn(rl.bd, buf := array[e.csize] of byte, len buf) != len buf)
-		error(sprint("read: %r"));
-	#say(sprint("getdata, %d compressed bytes for rev %d", len buf, e.rev));
+		error(sprint("read %q: %r", rl.path));
 	buf = xdecompress(buf);
 	#say(sprint("getdata, %d decompressed bytes for rev %d", len buf, e.rev));
 
 # the head of the result is the base of the data, the other buffers are the delta's
 xgetbufs(rl: ref Revlog, e: ref Entry): array of array of byte
 {
-	#say(sprint("getbufs, rev %d, base %d, fullrev %d", e.rev, e.base, rl.fullrev));
+	say(sprint("getbufs, rev %d, base %d, fullrev %d", e.rev, e.base, rl.fullrev));
 	usefull := rl.fullrev > e.base && rl.fullrev <= e.rev;
 	base := e.base;
 	if(usefull)
 	return d[i:];
 }
 
-# return the revision data, without meta-data
+# return the revision data
 xget(rl: ref Revlog, e: ref Entry, withmeta: int): array of byte
 {
 	if(e.rev == rl.fullrev) {
 
 
 # create delta from prev to rev.  prev may be -1.
-# the typical and easy case is that prev is rev predecessor, and we can use the delta from the revlog.
+# the typical and easy case is that prev is rev's predecessor and rev is not a base,
+# and we can use the delta from the revlog.
 # otherwise we'll have to create a patch.  for prev -1 this simply means making
 # a patch with the entire file contents.
-# for prev >= 0, we should generate a patch.  instead, for now we'll patch over the entire file.
-# xxx
+# for prev >= 0, we generate a delta.
 Revlog.xdelta(rl: self ref Revlog, prev, rev: int): array of byte
 {
 	#say(sprint("delta, prev %d, rev %d", prev, rev));
 		return xgetdata(rl, e);
 
 	buf := xget(rl, e, 1);
-	obuflen := 0;
+	obuf := array[0] of byte;
 	if(prev >= 0) {
 		pe := rl.xfind(prev);
-		obuflen = pe.uncsize;
+		obuf = xget(rl, pe, 1);
 	}
-	#say(sprint("delta with full contents, start %d end %d size %d, e %s", 0, obuflen, len buf, e.text()));
-	delta := array[3*4+len buf] of byte;
-	o := 0;
-	o = p32i(delta, o, 0); # start
-	o = p32i(delta, o, obuflen); # end
-	o = p32i(delta, o, len buf); # size
-	delta[o:] = buf;
-	return delta;
+	delta := bdiff->diff(obuf, buf);
+	return delta.pack();
 }
 
-Revlog.xstorebuf(rl: self ref Revlog, buf: array of byte, rev: int): (int, array of byte)
+deltasize(ents: array of ref Entry): int
 {
-	# xxx actually make the delta, return base != rev.
+	n := 0;
+	for(i := 0; i < len ents; i++)
+		n += ents[i].csize;
+	return n;
+}
+
+Revlog.xstorebuf(rl: self ref Revlog, buf: array of byte, nrev: int): (int, array of byte)
+{
+	prev := nrev-1;
+	if(prev >= 0) {
+		pe := rl.xfind(prev);
+		pbuf := xget(rl, pe, 1);
+		delta := bdiff->diff(pbuf, buf);
+		if(!delta.replaces(len buf)) {
+			dbuf := delta.pack();
+			compr := compress(dbuf);
+			if(len compr < len dbuf*90/100)
+				dbuf = compr;
+			if(deltasize(rl.ents[pe.base+1:nrev])+len dbuf < 2*len buf)
+				return (pe.base, dbuf);
+		}
+	}
 
 	compr := compress(buf);
-	if(len compr < len buf*90/100) {
-		return (rev, compr);
-	} else {
-		nbuf := array[1+len buf] of byte;
-		nbuf[0] = byte 'u';
-		nbuf[1:] = buf;
-		return (rev, nbuf);
-	}
+	if(len compr < len buf*90/100)
+		return (nrev, compr);
+
+	nbuf := array[1+len buf] of byte;
+	nbuf[0] = byte 'u';
+	nbuf[1:] = buf;
+	return (nrev, nbuf);
 }
 
 Revlog.xlength(rl: self ref Revlog, rev: int): big
 
 xstreamin0(r: ref Repo, tr: ref Transact, b: ref Iobuf)
 {
-	warn("adding changesets");
+	sys->fprint(sys->fildes(2), "adding changesets\n");
 	cl := r.xchangelog();
 	nheads := len r.xheads();
 	nchangesets := cl.xstream(r, tr, b, 1, cl);
 	nnheads := len r.xheads()-nheads;
 
-	warn("adding manifests");
+	sys->fprint(sys->fildes(2), "adding manifests\n");
 	ml := r.xmanifestlog();
 	ml.xstream(r, tr, b, 0, cl);
 	
-	warn("adding file changes");
+	sys->fprint(sys->fildes(2), "adding file changes\n");
 	nfiles := 0;
 	nchanges := 0;
 	for(;;) {
 			s = string nnheads;
 		msg += sprint(", %s heads", s);
 	}
-	warn(msg);
+	sys->fprint(sys->fildes(2), "%s\n", msg);
 }
 
 Revlog.xappend(rl: self ref Revlog, r: ref Repo, tr: ref Transact, nodeid, p1, p2: string, link: int, buf: array of byte): ref Entry
 			error(sprint("%q: unexpected length %bd, expected %bd", dpath, dir.length, dsize));
 	}
 
-	uncsize := len buf;
-	base: int;
-	(base, buf) = rl.xstorebuf(buf, nrev);
+	(base, storebuf) := rl.xstorebuf(buf, nrev);
 
 	# if we grow a .i-only revlog to beyond 128k, create a .d and rewrite the .i
-	if(isindexonly(rl) && isize+big Entrysize+big len buf >= big (128*1024)) {
+	convert := isindexonly(rl) && isize+big Entrysize+big len storebuf >= big (128*1024);
+	if(convert && nrev == 0) {
+		rl.flags &= ~Indexonly;
+		tr.add(rl.rlpath+".d", big 0);
+	} else if(convert) {
 		say(sprint("no longer indexonly, writing %q", dpath));
 
 		ifd := xopen(ipath, Sys->OREAD);
 			error(sprint("short read on %q, expected %d, got %d", ipath, n, len ibuf));
 
 		nipath := ipath+".new";
-		r.xensuredirs(nipath);
 		nifd := xcreate(nipath, Sys->OWRITE|Sys->OEXCL, 8r666);
 		ib := bufio->fopen(nifd, Sys->OWRITE);
 
 			if(db.write(ibuf[ioff:ioff+e.csize], e.csize) != e.csize)
 				error(sprint("write %q: %r", dpath));
 
+			isize += big Entrysize;
 			dsize += big e.csize;
-			e.ioffset = big 0;
+			e.ioffset = e.offset;
 		}
-		isize = big (len rl.ents*Entrysize);
 		if(ib.flush() == Bufio->ERROR)
-			error(sprint("write %q: %r", ipath));
+			error(sprint("flush %q: %r", ipath));
 		if(db.flush() == Bufio->ERROR)
-			error(sprint("write %q: %r", dpath));
+			error(sprint("flush %q: %r", dpath));
 
 		# xxx styx cannot do this atomically...
 		say(sprint("removing current, renaming new, %q and %q", ipath, nipath));
 			error(sprint("remove %q and rename of %q failed: %r", ipath, nipath));
 
 		rl.flags &= ~Indexonly;
-		rl.ifd = rl.dfd = nil;
-		rl.bd = nil;
+		rl.ifd = xopen(ipath, Sys->OREAD);
+		rl.dfd = xopen(dpath, Sys->OREAD);
+		rl.bd = bufio->fopen(rl.dfd, Bufio->OREAD);
 
 		tr.add(rl.rlpath+".d", dsize);
 		tr.add(rl.rlpath+".i", isize);
 	}
 
-	ioffset := big 0;
+	ioffset: big;
 	if(isindexonly(rl))
 		ioffset = isize+big Entrysize;
+	else
+		ioffset = dsize;
 
 	flags := 0;
-	e := ref Entry (nrev, offset, ioffset, flags, len buf, uncsize, base, link, p1rev, p2rev, nodeid);
+	e := ref Entry (nrev, offset, ioffset, flags, len storebuf, len buf, base, link, p1rev, p2rev, nodeid);
 say(sprint("revlog %q, will be adding %s", rl.path, e.text()));
 	ebuf := array[Entrysize] of byte;
 	e.xpack(ebuf, isindexonly(rl));
 	nents[:] = rl.ents;
 	nents[len rl.ents] = e;
 	rl.ents = nents;
+
+	ncache := array[len rl.cache+1] of array of byte;
+	ncache[:] = rl.cache;
+	rl.cache = ncache;
+
+	rl.full = buf;
+	rl.fullrev = e.rev;
+
 	rl.tab.add(e.nodeid, e);
 
 	r.xensuredirs(ipath);
 		error(sprint("write %q: %r", ipath));
 	isize += big Entrysize;
 	if(isindexonly(rl)) {
-		if(sys->pwrite(ifd, buf, len buf, isize) != len buf)
+		if(sys->pwrite(ifd, storebuf, len storebuf, isize) != len storebuf)
 			error(sprint("write %q: %r", ipath));
 	} else {
 		if(!tr.has(dpath))
 			tr.add(rl.rlpath+".d", dsize);
 		dfd := xopencreate(dpath, Sys->OWRITE, 8r666);
-		if(sys->pwrite(dfd, buf, len buf, dsize) != len buf)
+		if(sys->pwrite(dfd, storebuf, len storebuf, dsize) != len storebuf)
 			error(sprint("write %q: %r", dpath));
 	}
 
-	(ok, dir) := sys->fstat(ifd);
+	if(rl.ifd == nil)
+		rl.ifd = xopen(ipath, Sys->OREAD);
+	if(!isindexonly(rl) && rl.dfd == nil) {
+		rl.dfd = xopen(dpath, Sys->OREAD);
+		rl.bd = bufio->fopen(rl.dfd, Bufio->OREAD);
+	}
+
+	(ok, dir) := sys->fstat(rl.ifd);
 	if(ok == 0) {
 		rl.ilength = dir.length;
 		rl.imtime = dir.mtime;
 		if(!ischlog && cl.xfindnodeid(link, 0) == nil)
 			error(sprint("nodeid %s references unknown changelog link %s", rev, link));
 
-		p := Patch.xparse(delta);
-		say(sprint("\tpatch, sizediff %d", p.sizediff()));
-		say(sprint("\t%s", p.text()));
+		(d, err) := Delta.parse(delta);
+		if(err != nil)
+			error("parsing patch: "+err);
+		say(sprint("\tdelta, sizediff %d", d.sizediff()));
+		say(sprint("\t%s", d.text()));
 		
 		linkrev := -1;
 		if(!ischlog)
 			if(p1 != nullnode) {
 				buf = rl.xgetnodeid(p1);
 			} else {
-				if(len p.l != 1 || (c := hd p.l).start != 0 || c.end != 0)
+				if(!d.replaces(0))
 					error(sprint("first chunk is not full version"));
 				buf = array[0] of byte;
 			}
 		}
 
-		buf = p.apply(buf);
+		buf = d.apply(buf);
 
 		nodeid := xcreatenodeid(buf, p1, p2);
 		if(nodeid != rev)
 	return rl.xgetnodeid(mf.nodeid);
 }
 
-xgetmanifest(r: ref Repo, n: string): ref Manifest
-{
-	if(n == nullnode)
-		return ref Manifest (n, array[0] of ref Mfile);
-	return r.xrevision(r.xlookup(n, 1).t0).t1;
-}
-
 Repo.xread(r: self ref Repo, path: string, ds: ref Dirstate): array of byte
 {
-	# xxx replace xgetmanifest with r.xmanifest when it accepts a nodeid
 	if(ds.context == nil)
 		ds.context = ref Context;
 
 	if(ds.context.m1 == nil)
-		ds.context.m1 = xgetmanifest(r, ds.p1);
+		ds.context.m1 = r.xmanifest(ds.p1);
 	mf1 := ds.context.m1.find(path);
 	if(mf1 != nil)
 		return r.xopenrevlog(path).xgetnodeid(mf1.nodeid);
 
 	if(ds.context.m2 == nil)
-		ds.context.m2 = xgetmanifest(r, ds.p2);
+		ds.context.m2 = r.xmanifest(ds.p2);
 	mf2 := ds.context.m2.find(path);
 	if(mf2 != nil)
 		return r.xopenrevlog(path).xgetnodeid(mf2.nodeid);
 }
 
 
-Hunk.text(h: self ref Hunk): string
-{
-	#return sprint("<hunk s=%d e=%d length=%d buf=%q>", h.start, h.end, len h.buf, string h.buf);
-	return sprint("<hunk s=%d e=%d length=%d>", h.start, h.end, len h.buf);
-}
-
-Patch.apply(p: self ref Patch, b: array of byte): array of byte
-{
-	n := len b+p.sizediff();
-	d := array[n] of byte;
-	ae := be := 0;
-	for(l := p.l; l != nil; l = tl l) {
-		h := hd l;
-
-		# copy data before hunk from base to dest
-		d[be:] = b[ae:h.start];
-		be += h.start-ae;
-
-		# copy new data to dest, and skip the removed part from base
-		d[be:] = h.buf;
-		be += len h.buf;
-		ae = h.end;
-	}
-	# and the trailing common data
-	d[be:] = b[ae:];
-	return d;
-}
-
-Group: adt {
-	l:	list of array of byte;
-	length:	int;	# original length of group
-	o:	int;	# offset of hd l
-
-	add:	fn(g: self ref Group, buf: array of byte);
-	copy:	fn(g: self ref Group, sg: ref Group, s, e: int);
-	flatten:	fn(g: self ref Group): array of byte;
-	size:	fn(g: self ref Group): int;
-	apply:	fn(g: ref Group, p: ref Patch): ref Group;
-};
-
-Group.add(g: self ref Group, buf: array of byte)
-{
-	g.l = buf::g.l;
-	g.length += len buf;
-}
-
-Group.copy(g: self ref Group, sg: ref Group, s, e: int)
-{
-	# seek gs to s
-	drop := s-sg.o;
-	while(drop > 0) {
-		b := hd sg.l;
-		sg.l = tl sg.l;
-		if(drop >= len b) {
-			sg.o += len b;
-			drop -= len b;
-		} else {
-			sg.l = b[drop:]::sg.l;
-			sg.o += drop;
-			drop = 0;
-		}
-	}
-	if(sg.o != s) raise "group:bad0";
-
-	# copy from sg into g
-	n := e-s;
-	while(n > 0 && sg.l != nil) {
-		b := hd sg.l;
-		sg.l = tl sg.l;
-		take := len b;
-		if(take > n) {
-			take = n;
-			sg.l = b[take:]::sg.l;
-		}
-		g.add(b[:take]);
-		sg.o += take;
-		n -= take;
-	}
-	if(n != 0) raise "group:bad1";
-}
-
-# note: we destruct g (in Group.copy), keeping g.o & hd g.l in sync.
-# we never have to go back before an offset after having read it.
-Group.apply(g: ref Group, p: ref Patch): ref Group
-{
-	g = ref *g;
-	ng := ref Group (nil, 0, 0);
-	o := 0;
-	for(l := p.l; l != nil; l = tl l) {
-		h := hd l;
-		ng.copy(g, o, h.start);
-		ng.add(h.buf);
-		o = h.end;
-	}
-	ng.copy(g, o, g.size());
-	ng.l = util->rev(ng.l);
-	return ng;
-}
-
-Group.size(g: self ref Group): int
-{
-	return g.length;
-}
-
-Group.flatten(g: self ref Group): array of byte
-{
-	d := array[g.size()] of byte;
-	o := 0;
-	for(l := g.l; l != nil; l = tl l) {
-		d[o:] = hd l;
-		o += len hd l;
-	}
-	return d;
-}
-
-Patch.xapplymany(base: array of byte, patches: array of array of byte): array of byte
-{
-	if(len patches == 0)
-		return base;
-
-	g := ref Group (base::nil, len base, 0);
-	for(i := 0; i < len patches; i++) {
-		p := Patch.xparse(patches[i]);
-		{
-			g = Group.apply(g, p);
-		} exception e {
-		"group:*" =>
-			error("bad patch: "+e[len "group:":]);
-		}
-	}
-	return g.flatten();
-}
-
-Patch.sizediff(p: self ref Patch): int
-{
-	n := 0;
-	for(l := p.l; l != nil; l = tl l) {
-		h := hd l;
-		n += len h.buf - (h.end-h.start);
-	}
-	return n;
-}
-
-Patch.xparse(d: array of byte): ref Patch
-{
-	o := 0;
-	l: list of ref Hunk;
-	while(o+12 <= len d) {
-		start, end, length: int;
-		(start, o) = g32(d, o);
-		(end, o) = g32(d, o);
-		(length, o) = g32(d, o);
-		if(start > end)
-			error(sprint("bad data, start %d > end %d", start, end));
-		if(o+length > len d)
-			error(sprint("bad data, hunk points past buffer, o+length %d+%d > len d %d", o, length, len d));
-		buf := array[length] of byte;
-		buf[:] = d[o:o+length];
-
-		h := ref Hunk (start, end, buf);
-		if(l != nil && h.start < (hd l).end)
-			error(sprint("bad patch, hunk starts before preceding hunk, start %d < end %d", h.start, (hd l).end));
-		l = h::l;
-		o += length;
-	}
-	return ref Patch(util->rev(l));
-}
-
-Patch.text(p: self ref Patch): string
-{
-	s: string;
-	for(l := p.l; l != nil; l = tl l)
-		s += sprint(" %s", (hd l).text());
-	if(s != nil)
-		s = s[1:];
-	return s;
-}
-
 nullentry: Entry;
 
 Entry.xpack(e: self ref Entry, buf: array of byte, indexonly: int)
 	(e.offset, o) = g48(buf, o);
 	e.ioffset = e.offset;
 	(e.flags, o) = g16(buf, o);
-	(e.csize, o) = g32(buf, o);
-	(e.uncsize, o) = g32(buf, o);
-	(e.base, o) = g32(buf, o);
-	(e.link, o) = g32(buf, o);
-	(e.p1, o) = g32(buf, o);
-	(e.p2, o) = g32(buf, o);
+	(e.csize, o) = g32i(buf, o);
+	(e.uncsize, o) = g32i(buf, o);
+	(e.base, o) = g32i(buf, o);
+	(e.link, o) = g32i(buf, o);
+	(e.p1, o) = g32i(buf, o);
+	(e.p2, o) = g32i(buf, o);
 	e.nodeid = hex(buf[o:o+20]);
 	o += 20;
 	if(len buf-o != 12)
 	return string r;
 }
 
-g16(d: array of byte, o: int): (int, int)
-{
-	return (int d[o]<<8|int d[o+1], o+2);
-}
-
-g32(d: array of byte, o: int): (int, int)
-{
-	return (g16(d, o).t0<<16|g16(d, o+2).t0, o+4);
-}
-
 g48(d: array of byte, o: int): (big, int)
 {
 	return (big g16(d, o).t0<<32|big g16(d, o+2).t0<<16|big g16(d, o+4).t0, o+6);

File appl/lib/mkfile

View file
  • Ignore whitespace
 	mercurial.dis\
 	mercurialwire.dis\
 	mercurialremote.dis\
+	bdiff.dis\
 
 MODULES=\
 	mercurialwire.m\
 	mercurialremote.m\
+	bdiff.m\
 
 SYSMODULES=\
 	mercurial.m\

File module/mercurial.m

View file
  • Ignore whitespace
 		get:	fn(c: self ref Configs, sec, name: string): string;
 		find:	fn(c: self ref Configs, sec, name: string): (int, string);
 	};
-
-
-	Hunk: adt {
-		start,
-		end:	int;
-		buf:	array of byte;
-
-		text:	fn(h: self ref Hunk): string;
-	};
-
-	Patch: adt {
-		l:	list of ref Hunk;
-
-		xparse:	fn(d: array of byte): ref Patch;
-		apply:	fn(p: self ref Patch, d: array of byte): array of byte;
-		xapplymany:	fn(base: array of byte, patches: array of array of byte): array of byte;
-		sizediff:	fn(h: self ref Patch): int;
-		text:	fn(h: self ref Patch): string;
-	};
 };