Commits

Mechiel Lukkien committed e771a84 Draft

many small changes & fixes.

- add option to library's init function to never write to files, for safety.
- make xheads return the nullnode for empty repo's.
- print summary of file changes after hg/update.
- when ensuring a directory exists, see if the full path exists
first, then try creating all compontents.
- fix caching of entries in a revlog, also after appending to it.
- do not error out when revlog.xstream sees a nodeid it already
knows, that's possible and the node should be ignored.
- unbreak between command when pulling.

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.
+- 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).
+- 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.
 - 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
+- library: think about caching of revlogs per repo, caching of
+  entries in repo's and perhaps try reading less to become up to date.
 
 - cgi/websrv: test with various client versions
 

appl/cmd/hg/add.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [path ...]");

appl/cmd/hg/clone

 dflag=()
 rev=()
 (arg
-	d	{dflag=(-d)}
+	d	{dflag=($dflag -d)}
 	r+	{rev=(-r $arg)}
 	-	$*
 )
 
-echo clone: $#* $*
 if {! ~ $#* 2} {usage}
 
 (remote dest)=$*

appl/cmd/hg/commit.b

 	deflate->init();
 	filtertool = load Filtertool Filtertool->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [-v] [-m msg] [path ...]");
 	say(cmsg);
 	cbuf := array of byte cmsg;
 	cnodeid := hg->xcreatenodeid(cbuf, ds.p1, ds.p2);
+	nheads := len repo.xheads();
 	ce := cl.xappend(repo, tr, cnodeid, ds.p1, ds.p2, link, cbuf);
+	nnheads := len repo.xheads()-nheads;
 
 	nds.p1 = ce.nodeid;
 	repo.xwritedirstate(nds);
 	repo.xcommit(tr);
+	if(nnheads != 0)
+		warn("created new head");
 }
 
 inspect(r, l: list of ref Dsfile, tab: ref Strhash[ref Dsfile], path: string): list of ref Dsfile
 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(1);
 	hgwire = load Mercurialwire Mercurialwire->PATH;
 	hgwire->init();
 

appl/cmd/hg/get.b

 	arg := load Arg Arg->PATH;
 	str = load String String->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [-v] [-r rev]");

appl/cmd/hg/heads.b

 	daytime = load Daytime Daytime->PATH;
 	str = load String String->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [-v]");
 init0()
 {
 	repo := Repo.xfind(hgpath);
-	ents := repo.xchangelog().xentries();
-	heads := repo.xheads();
-
-	for(i := len heads-1; i >= 0; i--)
-		sys->print("%s\n", hg->xentrylogtext(repo, ents, heads[i], vflag));
+	for(l := repo.xheads(); l != nil; l = tl l)
+		sys->print("%s\n", hg->xentrylogtext(repo, hd l, vflag));
 }
 
 fail(s: string)
 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path]");

appl/cmd/hg/log.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [-v] [-r rev] [file ...]");
 init0(args: list of string)
 {
 	repo := Repo.xfind(hgpath);
+	base := repo.xworkdir();
 
-	rev := -1;
+	l := args;
+	args = nil;
+	for(; l != nil; l = tl l)
+		args = hg->xsanitize(base+"/"+hd l)::args;
+
 	if(revstr != nil) {
-		n: string;
-		(rev, n) = repo.xlookup(revstr, 1);
+		(rev, n) := repo.xlookup(revstr, 1);
+		if(!match(args, repo, rev))
+			return;
+		sys->print("%s\n", hg->xentrylogtext(repo, n, vflag));
+		return;
 	}
+
 	ents := repo.xchangelog().xentries();
-
 	for(i := len ents-1; i >= 0; i--) {
-		if(rev >= 0 && ents[i].rev != rev)
+		if(!match(args, repo, ents[i].rev))
 			continue;
-
-		if(args != nil && !filechanged(repo, ents[i], args))
-			continue;
-		sys->print("%s\n", hg->xentrylogtext(repo, ents, ents[i], vflag));
+		sys->print("%s\n", hg->xentrylogtext(repo, ents[i].nodeid, vflag));
 	}
 
 }
 
-filechanged(r: ref Repo, e: ref Entry, args: list of string): int
+match(l: list of string, r: ref Repo, rev: int): int
 {
-	c := r.xchange(e.rev);
-	for(l := args; l != nil; l = tl l)
-		for(ll := c.files; ll != nil; ll = tl ll)
-			if(hd ll == hd l)
-				return 1;
+	if(l == nil)
+		return 1;
+
+	c := r.xchange(rev);
+	for(; l != nil; l = tl l)
+		if(c.hasfile(hd l))
+			return 1;
 	return 0;
 }
 

appl/cmd/hg/pull.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 	hgrem = load Mercurialremote Mercurialremote->PATH;
 	hgrem->init();
 

appl/cmd/hg/readrevlog.b

 	arg := load Arg Arg->PATH;
 	str = load String String->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(1);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-dv] path");

appl/cmd/hg/revert.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(1);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [path ...]");
 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [-f] path ...");

appl/cmd/hg/status.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [path ...]");

appl/cmd/hg/tar.b

 	arg := load Arg Arg->PATH;
 	str = load String String->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-dv] [-r rev] [-h path]");

appl/cmd/hg/update.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [-C] [rev]");
 		}
 	}
 
+	nupdated := nmerged := nremoved := nunresolved := 0;
 	nds := ref Dirstate (1, nodeid, hg->nullnode, nil, nil);
 	oi := ni := 0;
 	for(;;) {
 			if(ofiles[oi].nodeid != nfiles[ni].nodeid || hg->differs(repo, nfiles[ni])) {
 				say(sprint("updating %q", np));
 				ewritefile(np, nfiles[ni].nodeid);
+				nupdated++;
 			}
 			dsadd(nds, np);
 			oi++;
 			say(sprint("removing %q", op));
 			sys->remove(op);
 			removedirs(nfiles, op);
+			nremoved++;
 			oi++;
 		} else if(np < op || op == nil) {
 			say(sprint("creating %q", np));
 			ewritefile(np, nfiles[ni].nodeid);
 			dsadd(nds, np);
 			ni++;
+			nupdated++;
 		}
 	}
 
 	repo.xwritedirstate(nds);
 	if(obranch != nbranch || nbranch != wbranch)
 		repo.xwriteworkbranch(nbranch);
+
+	warn(sprint("files: %d updated, %d merged, %d removed, %d unresolved", nupdated, nmerged, nremoved, nunresolved));
 }
 
 removedirs(mf: array of ref Mfile, p: string)

appl/cmd/hg/verify.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path]");

appl/cmd/test/delta.b

 	arg := load Arg Arg->PATH;
 	str = load String String->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	hgpath := "";
 

appl/cmd/test/printchangegroup.b

 	bufio = load Bufio Bufio->PATH;
 	base16 = load Encoding Encoding->BASE16PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-dv]");

appl/cmd/test/printconfig.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path]");

appl/cmd/test/printpatch.b

 	arg := load Arg Arg->PATH;
 	str = load String String->PATH;
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d]");

appl/cmd/test/testapply.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] base patch1 ...");

appl/lib/mercurial.b

 
 Cachemax:	con 64;  # max number of cached items in a revlog
 
-init()
+init(rdonly: int)
 {
 	sys = load Sys Sys->PATH;
 	bufio = load Bufio Bufio->PATH;
 	filtertool = load Filtertool Filtertool->PATH;
 	util = load Util0 Util0->PATH;
 	util->init();
+
+	readonly = rdonly;
 }
 
 checknodeid(n: string): string
 ensuredirs(base, path: string)
 {
 	s := base;
+	(ok, dir) := sys->stat(base+"/"+str->splitstrr(path, "/").t0);
+	if(ok == 0 && (dir.mode & Sys->DMDIR))
+		return;
 	for(l := sys->tokenize(str->splitstrr(path, "/").t0, "/").t1; l != nil; l = tl l) {
 		s += "/"+hd l;
 		if(sys->create(s, Sys->OREAD, 8r777|Sys->DMDIR) == nil)
 }
 
 
-xentrylogtext(r: ref Repo, ents: array of ref Entry, e: ref Entry, verbose: int): string
+xentrylogtext(r: ref Repo, n: string, verbose: int): string
 {
-	ch := r.xchange(e.rev);
+	cl := r.xchangelog();
+	ents := cl.xentries();
+	rev := p1 := p2 := -1;
+	if(n != nullnode) {
+		e := cl.xfindnodeid(n, 1);
+		rev = e.rev;
+		p1 = e.p1;
+		p2 = e.p2;
+	}
+	ch := r.xchangen(n);
 	s := "";
-	s += entrylogkey("changeset", sprint("%d:%s", e.rev, e.nodeid[:12]));
+	s += entrylogkey("changeset", sprint("%d:%s", rev, n[:12]));
 	(k, branch) := ch.findextra("branch");
 	if(k != nil)
 		s += entrylogkey("branch", branch);
-	for(tags := r.xrevtags(e.nodeid); tags != nil; tags = tl tags)
+	for(tags := r.xrevtags(n); tags != nil; tags = tl tags)
 		s += entrylogkey("tag", (hd tags).name);
-	if((e.p1 >= 0 && e.p1 != e.rev-1) || (e.p2 >= 0 && e.p2 != e.rev-1)) {
-		if(e.p1 >= 0)
-			s += entrylogkey("parent", sprint("%d:%s", ents[e.p1].rev, ents[e.p1].nodeid[:12]));
-		if(e.p2 >= 0)
-			s += entrylogkey("parent", sprint("%d:%s", ents[e.p2].rev, ents[e.p2].nodeid[:12]));
-	} else if(e.p1 < 0 && e.rev != e.p1+1)
+	if((p1 >= 0 && p1 != rev-1) || (p2 >= 0 && p2 != rev-1)) {
+		if(p1 >= 0)
+			s += entrylogkey("parent", sprint("%d:%s", ents[p1].rev, ents[p1].nodeid[:12]));
+		if(p2 >= 0)
+			s += entrylogkey("parent", sprint("%d:%s", ents[p2].rev, ents[p2].nodeid[:12]));
+	} else if(p1 < 0 && rev != p1+1 && rev >= 0)
 		s += entrylogkey("parent", "-1:000000000000");
 	s += entrylogkey("user", ch.who);
 	s += entrylogkey("date", sprint("%s %+d", daytime->text(daytime->gmt(ch.when+ch.tzoff)), ch.tzoff));
 	return (nil, nil);
 }
 
+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;
+	return 0;
+}
+
+Change.findfiles(c: self ref Change, f: string): list of string
+{
+	dir := f+"/";
+	r: list of string;
+	for(l := c.files; l != nil; l = tl l)
+		if(hd l == f || prefix(dir, hd l))
+			r = hd l::r;
+	return rev(r);
+}
+
+
 Change.text(c: self ref Change): string
 {
 	s := "";
 	if(dir.length == rl.ilength && dir.mtime == rl.imtime && dir.qid.vers == rl.ivers)
 		return;
 
-say(sprint("revlog, reopen, path %q", rl.path));
+say(sprint("xreopen, path %q, is dirty, going to read", rl.path));
 
 	# reread the index file.  we also get here for the first open of the revlog.
 	# instead of rereading everything, we could continue at where we left.
 	xreadrevlog(rl, ib);
 	rl.ilength = dir.length;
 	rl.imtime = dir.mtime;
-	rl.imtime = dir.qid.vers;
+	rl.ivers = dir.qid.vers;
 }
 
 # read through the entire revlog, store all entries in rl.entries.
 	#say(sprint("getdata, getting fresh data for rev %d", e.rev));
 	if(rl.bd == nil) {
 		fd := rl.dfd;
-		if(rl.isindexonly())
+		if(isindexonly(rl))
 			fd = rl.ifd;
 		rl.bd = bufio->fopen(fd, Bufio->OREAD);
 	}
 {
 	warn("adding changesets");
 	cl := r.xchangelog();
+	nheads := len r.xheads();
 	nchangesets := cl.xstream(r, tr, b, 1, cl);
+	nnheads := len r.xheads()-nheads;
 
 	warn("adding manifests");
 	ml := r.xmanifestlog();
 		nfiles++;
 	}
 
-	warn(sprint("added %d changesets with %d changes to %d files", nchangesets, nchanges, nfiles));
+	msg := sprint("added %d changesets with %d changes to %d files", nchangesets, nchanges, nfiles);
+	if(nnheads != 0) {
+		if(nnheads > 0)
+			s := "+"+string nnheads;
+		else
+			s = string nnheads;
+		msg += sprint(", %s heads", s);
+	}
+	warn(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
 {
+	if(readonly)
+		error("repository opened readonly");
+
 	xreopen(rl);
 
 	p1rev := p2rev := -1;
 		nrev = orev+1;
 		ee := rl.ents[orev];
 		offset = ee.offset+big ee.csize;
-		if(rl.isindexonly()) {
+		if(isindexonly(rl)) {
 			isize = ee.ioffset+big ee.csize;
 		} else {
 			isize = big (len rl.ents*Entrysize);
 	(base, buf) = rl.xstorebuf(buf, nrev);
 
 	# if we grow a .i-only revlog to beyond 128k, create a .d and rewrite the .i
-	if(rl.isindexonly() && isize+big Entrysize+big len buf >= big (128*1024)) {
+	if(isindexonly(rl) && isize+big Entrysize+big len buf >= big (128*1024)) {
 		say(sprint("no longer indexonly, writing %q", dpath));
 
 		ifd := xopen(ipath, Sys->OREAD);
 	}
 
 	ioffset := big 0;
-	if(rl.isindexonly())
+	if(isindexonly(rl))
 		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, rl.isindexonly());
+	e.xpack(ebuf, isindexonly(rl));
 	nents := array[len rl.ents+1] of ref Entry;
 	nents[:] = rl.ents;
 	nents[len rl.ents] = e;
 	if(sys->pwrite(ifd, ebuf, len ebuf, isize) != len ebuf)
 		error(sprint("write %q: %r", ipath));
 	isize += big Entrysize;
-	if(rl.isindexonly()) {
+	if(isindexonly(rl)) {
 		if(sys->pwrite(ifd, buf, len buf, isize) != len buf)
 			error(sprint("write %q: %r", ipath));
 	} else {
 		if(sys->pwrite(dfd, buf, len buf, dsize) != len buf)
 			error(sprint("write %q: %r", dpath));
 	}
+
+	(ok, dir) := sys->fstat(ifd);
+	if(ok == 0) {
+		rl.ilength = dir.length;
+		rl.imtime = dir.mtime;
+		rl.ivers = dir.qid.vers;
+	}
+
 	return e;
 }
 
 			error(sprint("nodeid mismatch, expected %s saw %s", rev, nodeid));
 
 		if(rl.xfindnodeid(nodeid, 0) != nil)
-			error(sprint("already have nodeid %s", nodeid));
+			continue;
 
 		rl.xappend(r, tr, nodeid, p1, p2, linkrev, buf);
 		nchanges++;
 	return Change.xparse(cd, ce);
 }
 
+Repo.xchangen(r: self ref Repo, n: string): ref Change
+{
+	if(n == nullnode)
+		return ref Change (-1, n, -1, -1, nullnode, "", 0, 0, nil, nil, nil);
+	cl := r.xchangelog();
+	ce := cl.xfindnodeid(n, 1);
+	cd := cl.xget(ce.rev);
+	return Change.xparse(cd, ce);
+}
+
 Repo.xmtime(r: self ref Repo, rl: ref Revlog, rev: int): int
 {
 	e := rl.xfind(rev);
 
 Repo.xwritedirstate(r: self ref Repo, ds: ref Dirstate)
 {
+	if(readonly)
+		error("repository opened readonly");
+
 	n := ds.packedsize();
 	ds.pack(buf := array[n] of byte);
 	path := r.path+"/dirstate";
 {
 	tags: list of ref Tag;
 	tagtab := Strhash[ref Tag].new(31, nil);
-	heads := r.xheads();
-	for(i := len heads-1; i >= 0; i--) {
-		e := heads[i];
-		(nil, m) := r.xrevision(e.rev);
+	for(l := r.xheads(); l != nil; l = tl l) {
+		n := hd l;
+		m := r.xmanifest(n);
 		mf := m.find(".hgtags");
 		if(mf == nil)
 			continue;
 		buf := r.xget(mf.nodeid, ".hgtags");
-		for(l := xparsetags(r, string buf); l != nil; l = tl l) {
-			t := hd l;
+		for(ll := xparsetags(r, string buf); ll != nil; ll = tl ll) {
+			t := hd ll;
 			if(tagtab.find(t.name) == nil) {
 				tags = t::tags;
 				tagtab.add(t.name, t);
 				tags = t::tags;
 		}
 	}
-	if(len ents > 0 && ents[len ents-1].rev == rev)
+	if(len ents == 0 || len ents > 0 && ents[len ents-1].rev == rev)
 		tags = ref Tag ("tip", n, rev)::tags;
 	return tags;
 }
 	return util->rev(l);
 }
 
-Repo.xheads(r: self ref Repo): array of ref Entry
+Repo.xheads(r: self ref Repo): list of string
 {
 	cl := r.xchangelog();
 	a := cl.xentries();
 
+	if(len a == 0)
+		return nullnode::nil;
+
 	for(i := 0; i < len a; i++) {
 		e := a[i];
 		if(e.p1 >= 0)
 			a[e.p2] = nil;
 	}
 
-	hl: list of ref Entry;
+	l: list of string;
 	for(i = 0; i < len a; i++)
 		if(a[i] != nil)
-			hl = a[i]::hl;
-	return l2a(util->rev(hl));
+			l = a[i].nodeid::l;
+	return l;
 }
 
 Repo.xchangelog(r: self ref Repo): ref Revlog
 
 	if(s == "tip" || s == ".") {
 		if(len ents == 0)
-			return (-1, "null");  # should this raise error if need is set?
+			return (-1, "null");
 		e := ents[len ents-1];
 		return (e.rev, e.nodeid);
 	}
 		error(sprint("revlog path %#q not in store path %#q", fullrlpath, pre));
 	fullrlpath = "./"+str->drop(fullrlpath[len pre:], "/");
 	ensuredirs(pre, fullrlpath);
-
 }
 
 find(a: array of string, e: string): int
 
 Repo.xtransact(r: self ref Repo): ref Transact
 {
+	if(readonly)
+		error("repository opened readonly");
+
 	f := r.storedir()+"/undo";
 	tr := ref Transact;
 	tr.fd = xcreate(f, Sys->OWRITE|Sys->OTRUNC, 8r666);

appl/lib/mercurialremote.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(1);
 }
 
 Remrepo.xnew(r: ref Repo, path: string): ref Remrepo
 {
 	if(r.tossh == nil) {
 		(r.tossh, r.fromssh) = xrun(list of {"ssh", r.host, sprint("hg -R %s serve --stdio", r.dir)});
-		#(r.tossh, r.fromssh) = xrun(list of {"ssh", r.host, "echo blah"});
-		#(r.tossh, r.fromssh) = xrun(list of {"echo", "blah"});
 		r.b = bufio->fopen(r.fromssh, Bufio->OREAD);
 		if(r.b == nil)
 			error(sprint("fopen: %r"));
 
 Remrepo.xbetween(rr: self ref Remrepo, pairs: list of ref (string, string)): list of list of string
 {
-	if(len pairs)
+	if(pairs == nil)
 		error("no pairs specified");
 	pairtups: list of string;
 	for(l := pairs; l != nil; l = tl l) {

appl/lib/mercurialwire.b

 	filtertool: Filtertool;
 include "util0.m";
 	util: Util0;
-	l2a, rev: import util;
+	join, l2a, rev: import util;
 include "mercurial.m";
 	hg: Mercurial;
 	Revlog, Repo, Entry, Change, Manifest: import hg;
 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init();
+	hg->init(1);
 }
 
 heads(r: ref Repo): (string, string)
 {
 	{
-		ents := r.xheads();
-
-		s := "";
-		for(i := 0; i < len ents; i++)
-			s += " "+ents[i].nodeid;
-		if(s != nil)
-			s = s[1:];
-		return (s+"\n", nil);
+		return (join(r.xheads(), " ")+"\n", nil);
 	} exception e {
 	"hg:*" =>	return (nil, e[len "hg:":]);
 	}

module/mercurial.m

 Mercurial: module
 {
 	PATH:	con "/dis/lib/mercurial.dis";
-	init:	fn();
+	init:	fn(readonly: int);
 
 	debug:	int;
+	readonly:	int;
 	nullnode:	con "0000000000000000000000000000000000000000";
 
 	checknodeid:	fn(n: string): string;
 	ensuredirs:	fn(root, path: string);
 	xreaduser:	fn(r: ref Repo): string;
 	xreadconfigs:	fn(r: ref Repo): ref Configs;
-	xentrylogtext:	fn(r: ref Repo, ents: array of ref Entry, e: ref Entry, verbose: int): string;
+	xentrylogtext:	fn(r: ref Repo, n: string, verbose: int): string;
 	xopencreate:	fn(f: string, mode, perm: int): ref Sys->FD;
 	xbopencreate:	fn(f: string, mode, perm: int): ref Bufio->Iobuf;
 	xdirstate:	fn(r: ref Repo, all: int): ref Dirstate;
 
 		xparse:		fn(data: array of byte, e: ref Entry): ref Change;
 		findextra:	fn(c: self ref Change, k: string): (string, string);
+		hasfile:	fn(c: self ref Change, f: string): int;
+		findfiles:	fn(c: self ref Change, f: string): list of string;
 		text:	fn(c: self ref Change): string;
 	};
 
 		xmanifest:	fn(r: self ref Repo, n: string): ref Manifest;
 		xlastrev:	fn(r: self ref Repo): int;
 		xchange:	fn(r: self ref Repo, rev: int): ref Change;
+		xchangen:	fn(r: self ref Repo, n: string): ref Change;
 		xmtime:		fn(r: self ref Repo, rl: ref Revlog, rev: int): int;
 		xwritedirstate:	fn(r: self ref Repo, ds: ref Dirstate);
 		xworkdir:	fn(r: self ref Repo): string;
 		xbranches:	fn(r: self ref Repo): list of ref Branch;
 		xworkbranch:	fn(r: self ref Repo): string;
 		xwriteworkbranch:	fn(r: self ref Repo, b: string);
-		xheads:		fn(r: self ref Repo): array of ref Entry;
+		xheads:		fn(r: self ref Repo): list of string;
 		xchangelog:	fn(r: self ref Repo): ref Revlog;
 		xmanifestlog:	fn(r: self ref Repo): ref Revlog;
 		xlookup:	fn(r: self ref Repo, rev: string, need: int): (int, string);