Commits

Mechiel Lukkien committed f64ee3a Draft

many changes

- hg/clone is replaced by a script calling hg/init & hg/pull.
- hg/init is replaced by a script too, just a few mkdirs.
- hg/commit's revlog-writing code is in the lib now. hg/pull's
code will soon too. hg/commit now writes an undo file, which can
be used for rollbacks.
- hg/pull now refuses to pull in a new root in a repository with a
root (unless -f was set).
- hg/identify is now hg/id.

  • Participants
  • Parent commits ad75df0

Comments (0)

Files changed (26)

 
 # todo
 
+- hg/diff: allow only a single revision on command-line too.  should be easy with a sort & uniq on output from hg/status & hg/manifestdiff combined.
+- hg/verify: verify that list of modified files in changelog entry matches with the files changed in the manifest.
+- 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 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).
-- make lib out of code for commit,clone,pull
 - 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
 
 ## future
 
-- real fncache support.  have to figure out why the fncache file exists (not sure why repo files are listed, they can be derived and you normally don't need this).  windows special files may be more useful to escape.
+- real fncache support.  have to figure out why the fncache file
+  exists (not sure why repo files are listed, they can be derived and
+  you normally don't need this).  windows special files may be more
+  useful to escape.
 - local tags?
 - ignore files?

appl/cmd/hg/clone

+#!/dis/sh.dis
+
+load std arg
+
+fn warn {echo $* >[2=1]}
+fn error {warn $*; raise error}
+fn usage {warn 'usage: hg/clone [-d] [-r rev] remote dest'; raise usage}
+
+dflag=()
+rev=()
+(arg
+	d	{dflag=(-d)}
+	r+	{rev=(-r $arg)}
+	-	$*
+)
+
+echo clone: $#* $*
+if {! ~ $#* 2} {usage}
+
+(remote dest)=$*
+
+hg/init $dflag $dest &&
+{echo '[paths]'; echo 'default = '$remote} >$dest/.hg/hgrc &&
+hg/pull $dflag $rev -h $dest/.hg/

appl/cmd/hg/clone.b

-implement HgClone;
-
-include "sys.m";
-	sys: Sys;
-	sprint: import sys;
-include "draw.m";
-include "arg.m";
-include "bufio.m";
-	bufio: Bufio;
-	Iobuf: import bufio;
-include "string.m";
-	str: String;
-include "tables.m";
-	tables: Tables;
-	Strhash: import tables;
-include "util0.m";
-	util: Util0;
-	hasstr, kill, min, max, rev, hex, g32i, readfile, l2a, inssort, warn, fail: import util;
-include "filter.m";
-	inflate: Filter;
-	deflate: Filter;
-include "filtertool.m";
-	filtertool: Filtertool;
-include "mercurial.m";
-	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry, Patch: import hg;
-include "mhttp.m";
-include "../../lib/mercurialremote.m";
-	hgrem: Mercurialremote;
-	Remrepo: import hgrem;
-
-HgClone: module {
-	init:	fn(nil: ref Draw->Context, args: list of string);
-};
-
-
-dflag: int;
-Cflag: int;
-hgpath := "";
-revstr: string;
-path: string;
-dest: string;
-
-init(nil: ref Draw->Context, args: list of string)
-{
-	sys = load Sys Sys->PATH;
-	arg := load Arg Arg->PATH;
-	bufio = load Bufio Bufio->PATH;
-	str = load String String->PATH;
-	tables = load Tables Tables->PATH;
-	util = load Util0 Util0->PATH;
-	util->init();
-	inflate = load Filter Filter->INFLATEPATH;
-	inflate->init();
-	deflate = load Filter Filter->DEFLATEPATH;
-	deflate->init();
-	filtertool = load Filtertool Filtertool->PATH;
-	hg = load Mercurial Mercurial->PATH;
-	hg->init();
-	hgrem = load Mercurialremote Mercurialremote->PATH;
-	hgrem->init();
-
-	arg->init(args);
-	arg->setusage(arg->progname()+" [-d] [-r revstr] path [dest]");
-	while((c := arg->opt()) != 0)
-		case c {
-		'd' =>	hg->debug = dflag++;
-		'r' =>	revstr = arg->earg();
-		* =>	arg->usage();
-		}
-	args = arg->argv();
-	if(len args == 0 || len args > 2)
-		arg->usage();
-	path = hd args;
-	if(tl args != nil)
-		dest = hd tl args;
-
-	{ init0(); }
-	exception e {
-	"hg:*" =>
-		fail(e[3:]);
-	}
-}
-
-init0()
-{
-	warn("requesting all changes");
-	rr := Remrepo.xnew(nil, path);
-	if(dest == nil) {
-		dest = rr.xname();
-		if(dest == nil)
-			error("cannot determine destination directory");
-	}
-	dest += "/";
-	if(revstr == nil)
-		cfd := rr.xchangegroup(nil);
-	else {
-		if(!hasstr(rr.xcapabilities(), "changegroupsubset"))
-			error("changegroupsubset not supported");
-		head := rr.xlookup(revstr);
-		cfd = rr.xchangegroupsubset(hg->nullnode::nil, head::nil);
-	}
-
-	create(dest, Sys->OREAD, 8r777|Sys->DMDIR);
-	create(dest+".hg", Sys->OREAD, 8r777|Sys->DMDIR);
-	create(dest+".hg/store", Sys->OREAD, 8r777|Sys->DMDIR);
-	create(dest+".hg/store/data", Sys->OREAD, 8r777|Sys->DMDIR);
-	fd := create(dest+".hg/hgrc", Sys->OWRITE|Sys->OEXCL, 8r666);
-	if(sys->fprint(fd, "[paths]\ndefault = %s\n", path) < 0)
-		error(sprint("write %q: %r", dest+".hg/hgrc"));
-
-	fd = create(dest+".hg/requires", Sys->OWRITE|Sys->OTRUNC, 8r666);
-	reqbuf := array of byte "revlogv1\nstore\n"; # xxx fncache
-	if(sys->write(fd, reqbuf, len reqbuf) != len reqbuf)
-		error(sprint("write .hg/requires: %r"));
-	fd = nil;
-
-	(ncfd, err) := filtertool->push(inflate, "z", cfd, 0);
-	if(err != nil)
-		error(err);
-	b := bufio->fopen(ncfd, Bufio->OREAD);
-	if(b == nil)
-		error("fopen");
-
-	warn("adding changesets");
-	chtab := revlogwrite(b, dest+".hg/store/", "00changelog", nil);
-
-	warn("adding manifests");
-	revlogwrite(b, dest+".hg/store/", "00manifest", chtab);
-	
-
-	filedest := dest+".hg/store/data/";
-	warn("adding file changes");
-	nfiles := 0;
-	nchanges := 0;
-	for(;;) {
-		i := bg32(b);
-		if(i == 0)
-			break;
-
-		namebuf := breadn(b, i-4);
-		name := string namebuf;
-		tab := revlogwrite(b, filedest, hg->escape(name), chtab);
-		nfiles++;
-		nchanges += tablength(tab);
-	}
-
-	case b.getc() {
-	Bufio->EOF =>	;
-	Bufio->ERROR =>	error(sprint("error reading end of changegroup: %r"));
-	* =>		error(sprint("data past end of changegroup..."));
-	}
-
-	warn(sprint("added %d changesets with %d changes to %d files", tablength(chtab), nchanges, nfiles));
-
-	ds := ref Dirstate (1, hg->nullnode, hg->nullnode, nil, nil);
-	repo := Repo.xopen(dest+"/.hg");
-	repo.xwritedirstate(ds);
-}
-
-revlogwrite(b: ref Iobuf, basedir, path: string, chtab: ref Strhash[ref Entry]): ref Strhash[ref Entry]
-{
-	ischlog := chtab == nil;
-
-	tab := Strhash[ref Entry].new(31, nil);
-	if(ischlog)
-		chtab = tab;
-
-	f := basedir+path+".i";
-	hg->ensuredirs(basedir, path);
-	ib := bufio->create(f, Sys->OWRITE|Sys->OEXCL, 8r666);
-	if(ib == nil)
-		error(sprint("creating %q: %r", f));
-	db: ref Iobuf;
-	ents: list of ref Entry;
-	bufs: list of array of byte;
-	totalsize := big 0;
-	current := array[0] of byte;
-	deltasizes := 0;
-	offset := big 0;
-
-	base := 0;
-	nents := 0;
-	ebuf := array[hg->Entrysize] of byte;
-	for(;;) {
-		i := bg32(b);
-		if(i == 0)
-			break;
-
-		(rev, p1, p2, link, delta) := breadchunk(b, i);
-		if(dflag) {
-			say(sprint("\trev=%s", rev));
-			say(sprint("\tp1=%s", p1));
-			say(sprint("\tp2=%s", p2));
-			say(sprint("\tlink=%s", link));
-			say(sprint("\tlen delta=%d", len delta));
-		}
-
-		if(ischlog && rev != link)
-			error(sprint("changelog entry %s with bogus link %s", rev, link));
-		if(!ischlog && chtab.find(link) == nil)
-			error(sprint("entry %s references absent changelog link %s", rev, link));
-
-		p := Patch.xparse(delta);
-		if(dflag) {
-			say(sprint("\tpatch, sizediff %d", p.sizediff()));
-			say(sprint("\t%s", p.text()));
-		}
-		
-		p1rev := p2rev := -1;
-		if(p1 != hg->nullnode)
-			p1rev = findrev("p1", rev, tab, p1);
-		if(p2 != hg->nullnode)
-			p2rev = findrev("p2", rev, tab, p2);
-
-		selfrev := nents++;
-		linkrev := selfrev;
-		if(!ischlog)
-			linkrev = findrev("link", rev, chtab, link);
-
-		if(selfrev == 0 && (len p.l != 1 || (c := hd p.l).start != 0 || c.end != 0))
-			error(sprint("first change in group does not have single chunk in patch"));
-
-		data: array of byte;
-		if(selfrev == 0 || len p.l == 1 && (c = hd p.l).start == 0 && c.end == len current) {
-			base = selfrev;
-			data = current = (hd p.l).buf;
-			deltasizes = 0;
-
-			compr := compress(data);
-			if(len compr < len data*90/100) {
-				data = compr;
-			} else {
-				nd := array[1+len data] of byte;
-				nd[0] = byte 'u';
-				nd[1:] = data;
-				data = nd;
-			}
-		} else {
-			current = p.apply(current);
-			if(deltasizes+len delta > 2*len current) {
-				base = selfrev;
-				data = current;
-				deltasizes = 0;
-
-				compr := compress(data);
-				if(len compr < len data*90/100) {
-					data = compr;
-				} else {
-					nd := array[1+len data] of byte;
-					nd[0] = byte 'u';
-					nd[1:] = data;
-					data = nd;
-				}
-			} else {
-				data = delta;
-				deltasizes += len delta;
-
-				compr := compress(data);
-				if(len compr < len data*90/100)
-					data = compr;
-			}
-		}
-		nrev := hg->xcreatenodeid(current, p1, p2);
-		if(nrev != rev)
-			error(sprint("nodeid mismatch, expected %s saw %s", rev, nrev));
-
-		flags := 0;
-		e := ref Entry(selfrev, offset, big 0, flags, len data, len current, base, linkrev, p1rev, p2rev, rev);
-
-		ntotalsize := totalsize + big hg->Entrysize + big len data;
-		if(ntotalsize >= big (128*1024)) {
-			if(db == nil) {
-				f = basedir+path+".d";
-				db = bufio->create(f, Sys->OWRITE|Sys->OTRUNC, 8r666);
-				if(db == nil)
-					error(sprint("creating %q: %r", f));
-
-				# flush .i and .d contents so far
-				for(l := util->rev(ents); l != nil; l = tl l) {
-					oe := hd l;
-					oe.xpack(ebuf, 0);
-					if(ib.write(ebuf, len ebuf) != len ebuf)
-						error(sprint("write: %r"));
-				}
-
-				for(m := util->rev(bufs); m != nil; m = tl m) {
-					buf := hd m;
-					if(db.write(buf, len buf) != len buf)
-						error(sprint("write: %r"));
-				}
-
-				bufs = nil;
-				ents = nil;
-			}
-
-			e.xpack(ebuf, 0);
-			if(ib.write(ebuf, len ebuf) != len ebuf)
-				error(sprint("write: %r"));
-			if(db.write(data, len data) != len data)
-				error(sprint("write: %r"));
-		} else {
-			bufs = data::bufs;
-			ents = e::ents;
-		}
-
-		offset += big len data;
-		totalsize = ntotalsize;
-		tab.add(e.nodeid, e);
-	}
-
-	# if no .d file, it's time to flush
-	if(totalsize < big (128*1024)) {
-		ents = util->rev(ents);
-		bufs = util->rev(bufs);
-		while(ents != nil) {
-			e := hd ents;
-			buf := hd bufs;
-			ents = tl ents;
-			bufs = tl bufs;
-
-			e.xpack(ebuf, 1);
-			if(ib.write(ebuf, len ebuf) != len ebuf || ib.write(buf, len buf) != len buf)
-				error(sprint("write: %r"));
-		}
-		if(ib.flush() == Bufio->ERROR)
-			error(sprint("write: %r"));
-	} else {
-		if(ib.flush() == Bufio->ERROR || db.flush() == Bufio->ERROR)
-			error(sprint("write: %r"));
-	}
-
-	return tab;
-}
-
-findrev(name, rev: string, tab: ref Strhash[ref Entry], n: string): int
-{
-	e := tab.find(n);
-	if(e == nil)
-		error(sprint("missing %s %s for nodeid %s", name, n, rev));
-	return e.rev;
-}
-
-bg32(b: ref Iobuf): int
-{
-	return g32i(breadn(b, 4), 0).t0;
-}
-
-breadchunk(b: ref Iobuf, n: int): (string, string, string, string, array of byte)
-{
-	n -= 4;
-	if(n < 4*20)
-		error("short chunk");
-	buf := breadn(b, n);
-	o := 0;
-	rev := buf[o:o+20];
-	o += 20;
-	p1 := buf[o:o+20];
-	o += 20;
-	p2 := buf[o:o+20];
-	o += 20;
-	link := buf[o:o+20];
-	o += 20;
-	delta := buf[o:];
-	return (hex(rev), hex(p1), hex(p2), hex(link), delta);
-}
-
-breadn(b: ref Iobuf, n: int): array of byte
-{
-	buf := array[n] of byte;
-	h := 0;
-	while(h < n) {
-		nn := b.read(buf[h:], n-h);
-		if(nn == Bufio->EOF)
-			error("premature eof");
-		if(nn == Bufio->ERROR)
-			error(sprint("reading: %r"));
-		h += nn;
-	}
-	return buf;
-}
-
-create(f: string, mode, perm: int): ref Sys->FD
-{
-	fd := sys->create(f, mode, perm);
-	if(fd == nil)
-		error(sprint("create %q: %r", f));
-	return fd;
-}
-
-compress(d: array of byte): array of byte
-{
-	(nd, err) := filtertool->convert(deflate, "z", d);
-	if(err != nil)
-		error("deflate: "+err);
-	return nd;
-}
-
-tablength(t: ref Strhash[ref Entry]): int
-{
-	n := 0;
-	for(i := 0; i < len t.items; i++)
-		n += len t.items[i];
-	return n;
-}
-
-error(s: string)
-{
-	raise "hg:"+s;
-}
-
-say(s: string)
-{
-	if(dflag)
-		warn(s);
-}

appl/cmd/hg/commit.b

 	filtertool: Filtertool;
 include "mercurial.m";
 	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry: import hg;
+	Transact, Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry: import hg;
 include "util0.m";
 	util: Util0;
 	readfd, rev, join, readfile, l2a, inssort, warn, fail: import util;
 repo: ref Repo;
 hgpath := "";
 msg: string;
+tr: ref Transact;
 
 init(nil: ref Draw->Context, args: list of string)
 {
 	{ init0(args); }
 	exception e {
 	"hg:*" =>
+		repo.xrollback(tr);
 		fail(e[3:]);
 	}
 }
 			error("empty commit message, aborting");
 	}
 
+	tr = repo.xtransact();
+
 	files := l2a(r);
 	inssort(files, pathge);
 	filenodeids := array[len files] of string;
 		rl := repo.xopenrevlog(path);
 
 		fp1 := fp2 := hg->nullnode;
-		if(m1 != nil && (mf1 := m1.find(path)) != nil)
+		if((mf1 := m1.find(path)) != nil)
 			fp1 = mf1.nodeid;
-		if(m2 != nil && (mf2 := m2.find(path)) != nil)
+		if((mf2 := m2.find(path)) != nil)
 			fp2 = mf2.nodeid;
 
 		say(sprint("adding to revlog for file %#q, fp1 %s, fp2 %s", path, fp1, fp2));
-		ne := revlogadd(rl, fp1, fp2, link, buf);
+		ne := rl.xappend(repo, tr, fp1, fp2, link, buf);
 		filenodeids[i] = ne.nodeid;
 		say(sprint("file now at nodeid %s", ne.nodeid));
 
 	say("adding to manifest");
 	ml := repo.xmanifestlog();
 	mbuf := m.xpack();
-	me := revlogadd(ml, m1.nodeid, m2.nodeid, link, mbuf);
+	me := ml.xappend(repo, tr, m1.nodeid, m2.nodeid, link, mbuf);
 
 	say("adding to changelog");
 	cl := repo.xchangelog();
 	cmsg := sprint("%s\n%s\n%d %d\n%s\n\n%s", me.nodeid, user, now, tzoff, join(rev(modfiles), "\n"), msg);
 	say(sprint("change message:"));
 	say(cmsg);
-	ce := revlogadd(cl, ds.p1, ds.p2, link, array of byte cmsg);
+	ce := cl.xappend(repo, tr, ds.p1, ds.p2, link, array of byte cmsg);
 
 	nds.p1 = ce.nodeid;
 	repo.xwritedirstate(nds);
-}
-
-revlogadd(rl: ref Revlog, p1, p2: string, link: int, buf: array of byte): ref Entry
-{
-	ents := rl.xentries();
-	orev := -1;
-	offset := big 0;
-	if(len ents > 0) {
-		ee := ents[len ents-1];
-		orev = ee.rev;
-		offset = ee.offset+big ee.csize;
-	}
-	nrev := orev+1;
-
-	p1rev := p2rev := -1;
-	if(p1 != hg->nullnode) {
-		p1rev = findrev(ents, p1);
-		if(p2 != hg->nullnode)
-			p2rev = findrev(ents, p2);
-	}
-
-	nodeid := hg->xcreatenodeid(buf, p1, p2);
-
-	# xxx should make a patch and use it if patches+newpatch < 2*newsize
-	uncsize := len buf;
-	compr := compress(buf);
-	if(len compr < len buf*90/100) {
-		buf = compr;
-	} else {
-		nbuf := array[1+len buf] of byte;
-		nbuf[0] = byte 'u';
-		nbuf[1:] = buf;
-		buf = nbuf;
-	}
-
-	flags := 0;
-	base := nrev; # xxx fix when we stop inserting full copies
-	isindexonly := rl.isindexonly();
-	e := ref Entry (nrev, offset, big 0, flags, len buf, uncsize, base, link, p1rev, p2rev, nodeid);
-say(sprint("revlog %q, will be adding %s", rl.path, e.text()));
-	ebuf := array[hg->Entrysize] of byte;
-	e.xpack(ebuf, isindexonly);
-
-	# xxx if length current .i file < 128k and length current .i+64+len buf >= 128k, copy all data to .d file and create new .i
-	ipath := rl.path+".i";
-	repo.xensuredirs(ipath);
-	ib := hg->xbopencreate(ipath, Sys->OWRITE, 8r666);
-	if(ib.seek(big 0, Bufio->SEEKEND) < big 0)
-		error(sprint("open %q: %r", ipath));
-	if(ib.write(ebuf, len ebuf) != len ebuf)
-		error(sprint("write %q: %r", ipath));
-	if(isindexonly) {
-		if(ib.write(buf, len buf) != len buf)
-			error(sprint("write %q: %r", ipath));
-	} else {
-		dpath := rl.path+".d";
-		dfd := hg->xopencreate(dpath, Sys->OWRITE, 8r666);
-		if(dfd == nil || ((nil, dir) := sys->fstat(dfd)).t0 != 0)
-			error(sprint("open %q: %r", dpath));
-		if(sys->pwrite(dfd, buf, len buf, dir.length) != len buf)
-			error(sprint("write %q: %r", dpath));
-	}
-	if(ib.flush() == Bufio->ERROR)
-		error(sprint("write %q: %r", ipath));
-
-	return e;
-}
-
-findrev(ents: array of ref Entry, n: string): int
-{
-	for(i := 0; i < len ents; i++)
-		if(ents[i].nodeid == n)
-			return i;
-	error(sprint("no such nodeid %q", n));
-	return -1; # not reached
+	repo.xcommit(tr);
 }
 
 inspect(r, l: list of ref Dsfile, tab: ref Strhash[ref Dsfile], path: string): list of ref Dsfile
 	return a.path >= b.path;
 }
 
-compress(d: array of byte): array of byte
-{
-	(nd, err) := filtertool->convert(deflate, "z", d);
-	if(err != nil)
-		error("deflate: "+err);
-	return nd;
-}
-
 error(s: string)
 {
 	raise "hg:"+s;
 if {! test -d $fs/files} {mount {hg/fs -h $root} $fs || error 'mount failed'}
 
 if {~ $#revs 0} {
-	rev=${slice 0 12 "{hg/identify -h $root}}
+	rev=${slice 0 12 "{hg/id -h $root}}
 	files=$fs/files/$rev
 	hg/status -h $root | getlines {
 	rescue 'nomatch' {raise continue} {

appl/cmd/hg/get.b

 include "bufio.m";
 include "string.m";
 	str: String;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Revlog, Repo, Change: import hg;

appl/cmd/hg/heads.b

 	daytime: Daytime;
 include "string.m";
 	str: String;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Revlog, Repo, Entry, Change: import hg;
+implement HgIdentify;
+
+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";
+	tables: Tables;
+	Strhash: import tables;
+include "mercurial.m";
+	hg: Mercurial;
+	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry, Tag: import hg;
+include "util0.m";
+	util: Util0;
+	join, readfile, l2a, inssort, warn, fail: import util;
+
+HgIdentify: module {
+	init:	fn(nil: ref Draw->Context, args: list of string);
+};
+
+
+dflag: int;
+hgpath := "";
+
+init(nil: ref Draw->Context, args: list of string)
+{
+	sys = load Sys Sys->PATH;
+	arg := load Arg Arg->PATH;
+	str = load String String->PATH;
+	tables = load Tables Tables->PATH;
+	util = load Util0 Util0->PATH;
+	util->init();
+	hg = load Mercurial Mercurial->PATH;
+	hg->init();
+
+	arg->init(args);
+	arg->setusage(arg->progname()+" [-d] [-h path]");
+	while((c := arg->opt()) != 0)
+		case c {
+		'd' =>	hg->debug = dflag++;
+		'h' =>	hgpath = arg->earg();
+		* =>	arg->usage();
+		}
+	args = arg->argv();
+	if(len args != 0)
+		arg->usage();
+
+	{ init0(); }
+	exception e {
+	"hg:*" =>
+		fail(e[3:]);
+	}
+}
+
+init0()
+{
+	repo := Repo.xfind(hgpath);
+	ds := hg->xdirstate(repo, 0);
+
+	branch := repo.xworkbranch();
+	tags := repo.xrevtags(ds.p1);
+	revtags: list of string;
+	for(l := tags; l != nil; l = tl l)
+		revtags = (hd l).name::revtags;
+	revtags = util->rev(revtags);
+
+	# xxx should we use branch and tag of ds.p2 too?
+	s := ds.p1[:12];
+	if(ds.p2 != hg->nullnode)
+		s += "+"+ds.p2[:12];
+	if(ds.haschanges())
+		s += "+";
+	if(branch != "default")
+		s += sprint(" (%s)", branch);
+	if(revtags != nil)
+		s += " "+join(revtags, "/");
+	sys->print("%s\n", s);
+}
+
+error(s: string)
+{
+	raise "hg:"+s;
+}

appl/cmd/hg/identify.b

-implement HgIdentify;
-
-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";
-	tables: Tables;
-	Strhash: import tables;
-include "mercurial.m";
-	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry, Tag: import hg;
-include "util0.m";
-	util: Util0;
-	join, readfile, l2a, inssort, warn, fail: import util;
-
-HgIdentify: module {
-	init:	fn(nil: ref Draw->Context, args: list of string);
-};
-
-
-dflag: int;
-hgpath := "";
-
-init(nil: ref Draw->Context, args: list of string)
-{
-	sys = load Sys Sys->PATH;
-	arg := load Arg Arg->PATH;
-	str = load String String->PATH;
-	tables = load Tables Tables->PATH;
-	util = load Util0 Util0->PATH;
-	util->init();
-	hg = load Mercurial Mercurial->PATH;
-	hg->init();
-
-	arg->init(args);
-	arg->setusage(arg->progname()+" [-d] [-h path]");
-	while((c := arg->opt()) != 0)
-		case c {
-		'd' =>	hg->debug = dflag++;
-		'h' =>	hgpath = arg->earg();
-		* =>	arg->usage();
-		}
-	args = arg->argv();
-	if(len args != 0)
-		arg->usage();
-
-	{ init0(); }
-	exception e {
-	"hg:*" =>
-		fail(e[3:]);
-	}
-}
-
-init0()
-{
-	repo := Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 0);
-
-	branch := repo.xworkbranch();
-	tags := repo.xrevtags(ds.p1);
-	revtags: list of string;
-	for(l := tags; l != nil; l = tl l)
-		revtags = (hd l).name::revtags;
-	revtags = util->rev(revtags);
-
-	# xxx should we use branch and tag of ds.p2 too?
-	s := ds.p1[:12];
-	if(ds.p2 != hg->nullnode)
-		s += "+"+ds.p2[:12];
-	if(ds.haschanges())
-		s += "+";
-	if(branch != "default")
-		s += sprint(" (%s)", branch);
-	if(revtags != nil)
-		s += " "+join(revtags, "/");
-	sys->print("%s\n", s);
-}
-
-error(s: string)
-{
-	raise "hg:"+s;
-}
+#!/dis/sh.dis
+
+load std string arg
+
+fn warn {echo $* >[2=1]}
+fn error {warn $*; raise error}
+fn usage {warn 'usage: hg/init [-d] [path]'; raise usage}
+
+dflag=0
+(arg
+	d	{dflag=1}
+	-	$*
+)
+
+if {! ~ $#* (0 1)} {usage}
+
+dir=.
+if {~ $#* 1} {
+	dir=$1
+	mkdir $dir || raise error
+}
+
+{mkdir $dir/.hg && mkdir $dir/.hg/store && {echo revlogv1; echo store} >$dir/.hg/requires} || exit bad

appl/cmd/hg/init.b

-implement HgInit;
-
-include "sys.m";
-	sys: Sys;
-	sprint: import sys;
-include "draw.m";
-include "arg.m";
-include "bufio.m";
-include "string.m";
-	str: String;
-include "mercurial.m";
-	hg: Mercurial;
-	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry: import hg;
-include "util0.m";
-	util: Util0;
-	readfile, l2a, inssort, warn, fail: import util;
-
-HgInit: module {
-	init:	fn(nil: ref Draw->Context, args: list of string);
-};
-
-
-dflag: int;
-Cflag: int;
-repo: ref Repo;
-hgpath := "";
-
-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();
-
-	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(len args != 0)
-		arg->usage();
-
-	{ init0(); }
-	exception e {
-	"hg:*" =>
-		fail(e[3:]);
-	}
-}
-
-init0()
-{
-	fd := sys->create(".hg", Sys->OREAD, 8r777|Sys->DMDIR);
-	if(fd == nil)
-		error(sprint("creating .hg/: %r"));
-	fd = sys->create(".hg/store", Sys->OREAD, 8r777|Sys->DMDIR);
-	if(fd == nil)
-		error(sprint("creating .hg/store/: %r"));
-
-	fd = sys->create(".hg/requires", Sys->OWRITE|Sys->OTRUNC, 8r666);
-	if(fd == nil)
-		error(sprint("creating .hg/requires"));
-	buf := array of byte "revlogv1\nstore\n";
-	if(sys->write(fd, buf, len buf) != len buf)
-		error(sprint("write .hg/requires: %r"));
-}
-
-error(s: string)
-{
-	raise "hg:"+s;
-}
-
-say(s: string)
-{
-	if(dflag)
-		warn(s);
-}

appl/cmd/hg/log.b

 include "util0.m";
 	util: Util0;
 	max, fail, warn: import util;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Revlog, Repo, Change, Manifest, Entry: import hg;

appl/cmd/hg/mkfile

 	status.dis\
 	readrevlog.dis\
 	update.dis\
-	identify.dis\
+	id.dis\
 	pull.dis\
-	init.dis\
-	clone.dis\
 	verify.dis\
 	add.dis\
 	rm.dis\

appl/cmd/hg/pull.b

 
 
 dflag: int;
-Cflag: int;
+fflag: int;
 repo: ref Repo;
 hgpath := "";
 revstr: string;
 	hgrem->init();
 
 	arg->init(args);
-	arg->setusage(arg->progname()+" [-d] [-h path] [-r rev] [source]");
+	arg->setusage(arg->progname()+" [-d] [-h path] [-f] [-r rev] [source]");
 	while((c := arg->opt()) != 0)
 		case c {
-		'd' =>	hg->debug = dflag++;
-		'C' =>	Cflag++;
+		'd' =>	hg->debug = hgrem->dflag = dflag++;
 		'h' =>	hgpath = arg->earg();
+		'f' =>	fflag++;
 		'r' =>	revstr = arg->earg();
 		* =>	arg->usage();
 		}
 				say(sprint("base known, scheduling for between"));
 				betweens = ref (tip, base)::betweens;
 			} else if(p1 == hg->nullnode) {
-				say(sprint("base is first revision that we don't know, going to fetch nullnode"));
-				if(!hasstr(cgbases, hg->nullnode))
+				if(repo.xlastrev() >= 0) {
+					if(!fflag)
+						error(sprint("refusing to pull from unrelated repository without -f"));
+					if(!hasstr(cgbases, p1))
+						cgbases = p1::cgbases;
+				} else if(!hasstr(cgbases, hg->nullnode))
 					cgbases = hg->nullnode::cgbases;
 			} else {
 				say(sprint("base is unknown, will be asking for %s and %s in next round", fmtnode(p1), fmtnode(p2)));
 
 	warn("adding changesets");
 	cl := repo.xchangelog();
-	chtab := revlogwrite(b, cl, 1, nil);
+	(chtab, nchangesets) := revlogwrite(b, cl, 1, nil);
 
 	warn("adding manifests");
 	ml := repo.xmanifestlog();
 	revlogwrite(b, ml, 0, chtab);
 	
 	warn("adding file changes");
+	nfiles := 0;
+	nchanges := 0;
 	for(;;) {
 		i := bg32(b);
 		if(i == 0)
 		namebuf := breadn(b, i-4);
 		name := string namebuf;
 		rl := repo.xopenrevlog(name);
-		revlogwrite(b, rl, 0, chtab);
+		(nil, nn) := revlogwrite(b, rl, 0, chtab);
+		nfiles++;
+		nchanges += nn;
 	}
 
 	case b.getc() {
 	* =>		error(sprint("data past end of changegroup..."));
 	}
 
-	#warn(sprint("added %d changesets with %d changes to %d files", tablength(chtab), nchanges, nfiles));
-	warn(sprint("added l changesets with m changes to n files"));
+	warn(sprint("added %d changesets with %d changes to %d files", nchangesets, nchanges, nfiles));
 }
 
 isknown(n: string): int
 	return s[:12];
 }
 
-revlogwrite(b: ref Iobuf, rl: ref Revlog, ischlog: int, chtab: ref Strhash[ref Entry]): ref Strhash[ref Entry]
+revlogwrite(b: ref Iobuf, rl: ref Revlog, ischlog: int, chtab: ref Strhash[ref Entry]): (ref Strhash[ref Entry], int)
 {
 	tab := Strhash[ref Entry].new(31, nil);
 	ents := rl.xentries();
 	}
 	deltasizes := 0;
 
+	nchanges := 0;
 	for(;;) {
 		i = bg32(b);
 		if(i == 0)
 
 		offset += big len data;
 		tab.add(e.nodeid, e);
+		nchanges++;
 	}
 
 	if(ib.flush() == Bufio->ERROR)
 		error(sprint("write: %r"));
 	if(db != nil && db.flush() == Bufio->ERROR)
 		error(sprint("write: %r"));
-	return tab;
+	return (tab, nchanges);
 }
 
 findentry(name, rev: string, tab: ref Strhash[ref Entry], n: string): ref Entry

appl/cmd/hg/readrevlog.b

 include "bufio.m";
 include "string.m";
 	str: String;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Revlog, Repo, Entry, Change: import hg;
 
 init0(path: string)
 {
-	rl := Revlog.xopen(path, 0);
+	rl := Revlog.xopen(nil, path, 0);
 	last := rl.xlastrev();
 
 	for(i := 0; i <= last; i++) {

appl/cmd/hg/status.b

 include "util0.m";
 	util: Util0;
 	fail, warn, l2a, inssort: import util;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Dirstate, Dsfile, Revlog, Repo, Change: import hg;

appl/cmd/hg/tar.b

 include "bufio.m";
 include "string.m";
 	str: String;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Revlog, Repo, Change: import hg;

appl/cmd/test/delta.b

 include "bufio.m";
 include "string.m";
 	str: String;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Dirstate, Dsfile, Revlog, Repo, Change: import hg;

appl/cmd/test/printchangegroup.b

 	str: String;
 include "encoding.m";
 	base16: Encoding;
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Dirstate, Dsfile, Revlog, Repo, Change, Patch, Hunk: import hg;

appl/cmd/test/printconfig.b

 include "draw.m";
 include "arg.m";
 include "bufio.m";
+include "tables.m";
 include "mercurial.m";
 	hg: Mercurial;
 	Configs, Config, Section, Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry: import hg;

appl/cmd/test/printpatch.b

 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;

appl/cmd/test/testapply.b

 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;

appl/lib/mercurial.b

 	env: Env;
 include "filter.m";
 	inflate: Filter;
+	deflate: Filter;
 include "filtertool.m";
 	filtertool: Filtertool;
 include "keyring.m";
 	tables = load Tables Tables->PATH;
 	inflate = load Filter Filter->INFLATEPATH;
 	inflate->init();
+	deflate = load Filter Filter->DEFLATEPATH;
+	deflate->init();
 	filtertool = load Filtertool Filtertool->PATH;
 	util = load Util0 Util0->PATH;
 	util->init();
 	return fd;
 }
 
+xopen(f: string, mode: int): ref Sys->FD
+{
+	fd := sys->open(f, mode);
+	if(fd == nil)
+		error(sprint("open %q: %r", f));
+	return fd;
+}
+
+xcreate(f: string, mode, perm: int): ref Sys->FD
+{
+	fd := sys->create(f, mode, perm);
+	if(fd == nil)
+		error(sprint("create %q: %r", f));
+	return fd;
+}
+
 xbopencreate(f: string, mode, perm: int): ref Iobuf
 {
 	fd := xopencreate(f, mode, perm);
 				if(dsf.size >= 0 && big dsf.size != dir.length) {
 					dsf.mtime = dir.mtime;
 					dsf.size = SZdirty;
-				} else if(dsf.size == SZcheck || dsf.mtime > now-4) {
+				} else if(dsf.size == SZcheck || dsf.mtime != now || dsf.mtime >= now-4) {
 					exp := r.xread(dsf.path, ds);
 					cur := xreadfile(f);
 					if(eq(exp, cur))
 		f := rl.path+".i";
 		rl.ifd = sys->open(f, Sys->OREAD);
 		if(rl.ifd == nil) {
+			rl.flags = Indexonly;
 			err := sprint("open %q: %r", f);
 			(ok, nil) := sys->stat(f);
 			if(ok != 0)
 	(ok, dir) := sys->fstat(rl.ifd);
 	if(ok < 0)
 		error(sprint("%r"));
-	if(dir.length == rl.ilength && dir.mtime == rl.imtime)
+	if(dir.length == rl.ilength && dir.mtime == rl.imtime && dir.qid.vers == rl.ivers)
 		return;
 
 say(sprint("revlog, reopen, path %q", rl.path));
 	rl.dfd = nil;
 	if(!isindexonly(rl)) {
 		dpath := rl.path+".d";
-		rl.dfd = sys->open(dpath, Sys->OREAD);
-		if(rl.dfd == nil)
-			error(sprint("open %q: %r", dpath));
+		rl.dfd = xopen(dpath, Sys->OREAD);
 		# xxx verify .d file is as expected?
 	}
 
 	xreadrevlog(rl, ib);
 	rl.ilength = dir.length;
 	rl.imtime = dir.mtime;
+	rl.imtime = dir.qid.vers;
 }
 
 # read through the entire revlog, store all entries in rl.entries.
 	ib.seek(big 0, Bufio->SEEKSTART);
 
 	l: list of ref Entry;
+	rl.tab = rl.tab.new(101, nil);
 	eb := array[Entrysize] of byte;
 	for(;;) {
 		n := breadn(ib, eb, len eb);
 		}
 
 		l = e::l;
+		rl.tab.add(e.nodeid, e);
 	}
 	rl.ents = l2a(util->rev(l));
 	rl.cache = array[len rl.ents] of array of byte;
 }
 
 
-Revlog.xopen(path: string, cacheall: int): ref Revlog
+Revlog.xopen(storedir, path: string, cacheall: int): ref Revlog
 {
 	say(sprint("revlog.open %q", path));
 	rl := ref Revlog;
-	rl.path = path;
+	rl.storedir = storedir;
+	rl.rlpath = path;
+	rl.path = storedir+"/"+path;
 	rl.fullrev = -1;
 	rl.cacheall = cacheall;
 
 	rl.ilength = ~big 0;
 	rl.imtime = ~0;
+	rl.ivers = ~0;
+	rl.tab = rl.tab.new(101, nil);
+
 	xreopen(rl);
 	return rl;
 }
 	# may be compressed, first byte will tell us.
 	case int d[0] {
 	'u' =>	return d[1:];
-	0 =>	return d;
+	0 =>	return d;	# common case for patches
 	* =>	return xinflate(d);
 	}
 }
 
+compress(d: array of byte): array of byte
+{
+	(nd, err) := filtertool->convert(deflate, "z", d);
+	if(err != nil)
+		error("deflate: "+err);
+	return nd;
+}
+
 xgetdata(rl: ref Revlog, e: ref Entry): array of byte
 {
 	if(rl.cache[e.rev] != nil) {
 	return delta;
 }
 
+Revlog.xstorebuf(rl: self ref Revlog, buf: array of byte, rev: int): (int, array of byte)
+{
+	# xxx actually make the delta, return base != rev.
+
+	compr := compress(buf);
+	if(0 && 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);
+	}
+}
+
 Revlog.xlength(rl: self ref Revlog, rev: int): big
 {
 	return big rl.xfind(rev).uncsize;
 Revlog.xfindnodeid(rl: self ref Revlog, n: string, need: int): ref Entry
 {
 	xreopen(rl);
-	for(i := 0; i < len rl.ents; i++)
-		if(rl.ents[i].nodeid == n)
-			return rl.ents[i];
-	if(need)
+	e := rl.tab.find(n);
+	if(e == nil && need)
 		error(sprint("no nodeid %q", n));
-	return nil;
+	return e;
 }
 
 Revlog.xlastrev(rl: self ref Revlog): int
 	return d;
 }
 
+Revlog.xappend(rl: self ref Revlog, r: ref Repo, tr: ref Transact, p1, p2: string, link: int, buf: array of byte): ref Entry
+{
+	xreopen(rl);
+
+	p1rev := p2rev := -1;
+	if(p1 != nullnode) {
+		p1rev = rl.xfindnodeid(p1, 1).rev;
+		if(p2 != nullnode)
+			p2rev = rl.xfindnodeid(p2, 1).rev;
+	}
+	nodeid := xcreatenodeid(buf, p1, p2);
+
+	e := rl.xfindnodeid(nodeid, 0);
+	if(e != nil)
+		return e;
+
+	ipath := rl.path+".i";
+	dpath := rl.path+".d";
+	isize := dsize := big 0;
+	orev := -1;
+	nrev := 0;
+	offset := big 0;
+	if(len rl.ents > 0) {
+		orev = len rl.ents-1;
+		nrev = orev+1;
+		ee := rl.ents[orev];
+		offset = ee.offset+big ee.csize;
+		if(rl.isindexonly()) {
+			isize = ee.ioffset+big ee.csize;
+		} else {
+			isize = big (len rl.ents*Entrysize);
+			dsize = ee.offset+big ee.csize;
+		}
+	}
+
+	if(!tr.has(ipath))
+		tr.add(rl.rlpath+".i", isize);
+
+	# verify files are what we expect them to be, for sanity
+	if(isize != big 0) {
+		(ok, dir) := sys->stat(ipath);
+		if(ok != 0)
+			error(sprint("%q: %r", ipath));
+		if(dir.length != isize)
+			error(sprint("%q: unexpected length %bd, expected %bd", ipath, dir.length, isize));
+	}
+	if(dsize != big 0) {
+		(ok, dir) := sys->stat(dpath);
+		if(ok != 0)
+			error(sprint("%q: %r", dpath));
+		if(dir.length != dsize)
+			error(sprint("%q: unexpected length %bd, expected %bd", dpath, dir.length, dsize));
+	}
+
+	uncsize := len buf;
+	base: int;
+	(base, buf) = rl.xstorebuf(buf, nrev);
+
+	# if we grow a .i-only revlog to beyond 128k, create a .d and rewrite the .i
+	isindexonly := rl.isindexonly();
+	if(isindexonly && isize+big Entrysize+big len buf >= big (128*1024)) {
+		say(sprint("no longer indexonly, writing %q", dpath));
+
+		ifd := xopen(ipath, Sys->OREAD);
+		n := sys->readn(ifd, ibuf := array[int isize] of byte, len ibuf);
+		if(n < 0)
+			error(sprint("read %q: %r", ipath));
+		if(n != len ibuf)
+			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);
+
+		dfd := xcreate(dpath, Sys->OWRITE|Sys->OEXCL, 8r666);
+		db := bufio->fopen(dfd, Sys->OWRITE);
+		isize = big 0;
+		dsize = big 0;
+		for(i := 0; i < len rl.ents; i++) {
+			e = rl.ents[i];
+
+			if(i == 0)
+				p16(ibuf, 0, 0); # clear the Indexonly bits
+
+			ioff := int e.ioffset;
+			if(ib.write(ibuf[ioff-Entrysize:ioff], Entrysize) != Entrysize)
+				error(sprint("write %q: %r", nipath));
+
+			if(db.write(ibuf[ioff:ioff+e.csize], e.csize) != e.csize)
+				error(sprint("write %q: %r", dpath));
+
+			dsize += big e.csize;
+			e.ioffset = big 0;
+		}
+		isize = big (len rl.ents*Entrysize);
+		if(ib.flush() == Bufio->ERROR)
+			error(sprint("write %q: %r", ipath));
+		if(db.flush() == Bufio->ERROR)
+			error(sprint("write %q: %r", dpath));
+
+		# xxx styx cannot do this atomically...
+		say(sprint("removing current, renaming new, %q and %q", ipath, nipath));
+		ndir := sys->nulldir;
+		ndir.name = str->splitstrr(ipath, "/").t1;
+		if(sys->remove(ipath) != 0 || sys->fwstat(nifd, ndir) != 0)
+			error(sprint("remove %q and rename of %q failed: %r", ipath, nipath));
+
+		isindexonly = 0;
+
+		tr.add(rl.rlpath+".d", dsize);
+		tr.add(rl.rlpath+".i", isize);
+	}
+
+	ioffset := big 0;
+	if(isindexonly)
+		ioffset = isize+big Entrysize;
+
+	flags := 0;
+	e = ref Entry (nrev, offset, ioffset, flags, len buf, uncsize, 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);
+	nents := array[len rl.ents+1] of ref Entry;
+	nents[:] = rl.ents;
+	nents[len rl.ents] = e;
+	rl.ents = nents;
+	rl.tab.add(e.nodeid, e);
+
+	r.xensuredirs(ipath);
+	ifd := xopencreate(ipath, Sys->OWRITE, 8r666);
+	if(sys->pwrite(ifd, ebuf, len ebuf, isize) != len ebuf)
+		error(sprint("write %q: %r", ipath));
+	isize += big Entrysize;
+	if(isindexonly) {
+		if(sys->pwrite(ifd, buf, len buf, isize) != len buf)
+			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)
+			error(sprint("write %q: %r", dpath));
+	}
+	return e;
+}
+
 xreadlines(b: ref Iobuf): list of string
 {
 	l: list of string;
 
 Repo.xopenrevlog(r: self ref Repo, path: string): ref Revlog
 {
-	path = r.storedir()+"/data/"+r.escape(path);
-	return Revlog.xopen(path, 0);
+	return Revlog.xopen(r.storedir(), "data/"+r.escape(path), 0);
 }
 
-
 Repo.xrevision(r: self ref Repo, rev: int): (ref Change, ref Manifest)
 {
 	say("repo.manifest");
 Repo.xchangelog(r: self ref Repo): ref Revlog
 {
 	if(r.cl == nil)
-		r.cl = Revlog.xopen(r.storedir()+"/00changelog", 0);
+		r.cl = Revlog.xopen(r.storedir(), "00changelog", 0);
 	return r.cl;
 }
 
 Repo.xmanifestlog(r: self ref Repo): ref Revlog
 {
 	if(r.ml == nil)
-		r.ml = Revlog.xopen(r.storedir()+"/00manifest", 0);
+		r.ml = Revlog.xopen(r.storedir(), "00manifest", 0);
 	return r.ml;
 }
 
 	return c;
 }
 
+Repo.xtransact(r: self ref Repo): ref Transact
+{
+	f := r.storedir()+"/undo";
+	tr := ref Transact;
+	tr.fd = xcreate(f, Sys->OWRITE|Sys->OTRUNC, 8r666);
+	tr.tab = tr.tab.new(101, nil);
+	return tr;
+}
+
+Repo.xrollback(r: self ref Repo, tr: ref Transact)
+{
+	err: string;
+	seen := tr.tab.new(101, nil);
+	dir := sys->nulldir;
+	storedir := r.storedir();
+	for(l := tr.l; l != nil; l = tl l) {
+		rs := hd l;
+		if(seen.find(rs.path) != nil)
+			continue;
+		dir.length = rs.off;
+		f := storedir+"/"+rs.path;
+		if(sys->wstat(f, dir) != 0 && err == nil)
+			err = sprint("rollback %q to %bd", rs.path, rs.off);
+	}
+	f := r.storedir()+"/undo";
+	if(sys->remove(f) != 0 && err == nil)
+		err = sprint("remove %q: %r", f);
+	if(err != nil)
+		error(err);
+}
+
+Repo.xcommit(r: self ref Repo, nil: ref Transact)
+{
+	f := r.storedir()+"/undo";
+	if(sys->wstat(f, sys->nulldir) != 0)
+		error(sprint("sync: %r"));
+}
+
+Transact.has(tr: self ref Transact, path: string): int
+{
+	return tr.tab.find(path) != nil;
+}
+
+Transact.add(tr: self ref Transact, path: string, off: big)
+{
+	line := array of byte (path+"\0"+string off+"\n");
+	if(sys->write(tr.fd, line, len line) != len line)
+		error(sprint("writing undo: %r"));
+	rs := ref Revlogstate (path, off);
+	if(tr.tab.find(rs.path) == nil)
+		tr.tab.add(rs.path, rs);  # only one entry is enough
+	tr.l = rs::tr.l;
+}
+
 Config.find(c: self ref Config, sec, name: string): (int, string)
 {
 	for(l := c.l; l != nil; l = tl l) {

appl/lib/mercurialremote.b

 	Dirstate, Dsfile, Revlog, Repo, Change, Manifest, Mfile, Entry, Config: import hg;
 include "mercurialremote.m";
 
-dflag: int;
-
 init()
 {
 	sys = load Sys Sys->PATH;

appl/lib/mercurialremote.m

 {
 	PATH:	con "/dis/lib/mercurialremote.dis";
 	init:	fn();
+	dflag:	int;
 
 	Remrepo: adt {
 		r:	ref Repo;

module/mercurial.m

 	Version0, Version1:	con iota;
 
 	Revlog: adt {
-		path:	string;
+		storedir,	# of repo
+		rlpath,		# relative to store
+		path:	string;	# full path
 		ifd:	ref Sys->FD;
 		dfd:	ref Sys->FD;  # nil when .i-only
 		bd:	ref Bufio->Iobuf;
 		version:int;
 		flags:	int;
 		ents:	array of ref Entry;
+		tab:	ref Tables->Strhash[ref Entry];
 
 		# cache of decompressed revisions (delta's).
 		# cacheall caches all of them, for changelog & manifest.
 		# .i time & length of latest reread, for determining freshness
 		ilength:	big;
 		imtime:	int;
+		ivers:	int;
 
-		xopen:		fn(path: string, cacheall: int): ref Revlog;
+		xopen:		fn(storedir, path: string, cacheall: int): ref Revlog;
 		xget:		fn(rl: self ref Revlog, rev: int): array of byte;
 		xgetnodeid:	fn(rl: self ref Revlog, n: string): array of byte;
 		xlastrev:	fn(rl: self ref Revlog): int;
 		xfind:		fn(rl: self ref Revlog, rev: int): ref Entry;
 		xfindnodeid:	fn(rl: self ref Revlog, n: string, need: int): ref Entry;
 		xdelta:		fn(rl: self ref Revlog, prev, rev: int): array of byte;
+		xstorebuf:	fn(rl: self ref Revlog, buf: array of byte, rev: int): (int, array of byte);
 		xpread:		fn(rl: self ref Revlog, rev: int, n: int, off: big): array of byte;
 		xlength:	fn(rl: self ref Revlog, rev: int): big;
 
 		xentries:	fn(rl: self ref Revlog): array of ref Entry;
 		isindexonly:	fn(rl: self ref Revlog): int;
+		xappend:	fn(rl: self ref Revlog, r: ref Repo, tr: ref Transact, p1, p2: string, link: int, buf: array of byte): ref Entry;
+
+		#xstream;	fn(rl: ref Revlog, tr: ref Transact, cg: ref Bufio->Iobuf, ischlog: int, cl: ref Revlog): int;
 	};
 
 	Repo: adt {
 		xunescape:	fn(r: self ref Repo, path: string): string;
 		xensuredirs:	fn(r: self ref Repo, fullrlpath: string);
 		xreadconfig:	fn(r: self ref Repo): ref Config;
+		xtransact:	fn(r: self ref Repo): ref Transact;
+		xrollback:	fn(r: self ref Repo, tr: ref Transact);
+		xcommit:	fn(r: self ref Repo, tr: ref Transact);
+	};
+
+	Revlogstate: adt {
+		path:	string;
+		off:	big;
+	};
+
+	Transact: adt {
+		fd:	ref Sys->FD;
+		tab:	ref Tables->Strhash[ref Revlogstate];
+		l:	list of ref Revlogstate;
+
+		has:	fn(tr: self ref Transact, path: string): int;
+		add:	fn(tr: self ref Transact, path: string, off: big);
 	};
 
 	Section: adt {