Commits

Mechiel Lukkien committed 3350c05 Draft

improve/fix path handling.

we now have a clear distinction between root of repository, base
directory (where the command is executed) and files on which we
operate. most commands now print full paths (regardless of base)
when no parameters given. otherwise then print them relative to
base. they always interpret them relative to base.

Comments (0)

Files changed (11)

 
 # todo
 
-- 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

appl/cmd/hg/add.b

 init0(args: list of string)
 {
 	repo = Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 0);
-	root := repo.workroot();
+	untracked := 0;
+	ds := hg->xdirstate(repo, untracked);
+	base := repo.xworkdir();
 
 	if(args == nil) {
-		add(ds, root, ".", 0);
+		diradd(ds, ".");
 	} else {
-		base := repo.xworkdir();
-		say(sprint("base %q", base));
-		for(l := args; l != nil; l = tl l)
-			add(ds, root, hg->xsanitize(base+"/"+hd l), 1);
+		for(l := args; l != nil; l = tl l) {
+			p := repo.patheval(base, hd l);
+			if(p == nil)
+				error(sprint("%q is outside repository", hd l));
+			(ok, dir) := sys->stat(repo.workroot()+"/"+p);
+			if(ok != 0) {
+				warn(sprint("%q: %r", p));
+				continue;
+			}
+			add(ds, p, dir, 1);
+		}
 	}
 
 	if(ds.dirty)
 		repo.xwritedirstate(ds);
 }
 
-add(ds: ref Dirstate, root, path: string, direct: int)
-{
-	(ok, dir) := sys->stat(root+"/"+path);
-	if(ok != 0)
-		return warn(sprint("%q: %r", path));
-	add0(ds, root, path, dir, direct);
-}
-
-add0(ds: ref Dirstate, root, path: string, dir: Sys->Dir, direct: int)
+add(ds: ref Dirstate, path: string, dir: Sys->Dir, direct: int)
 {
 	if(path == ".hg" || str->prefix(".hg/", path))
 		return;
 	if(dir.mode & Sys->DMDIR)
-		return diradd(ds, root, path);
+		return diradd(ds, path);
 
 	dsf := ds.find(path);
-	if(dsf == nil || dsf.state == hg->STuntracked) {
-		add := dsf == nil;
-		if(dsf == nil)
-			dsf = ref Dsfile;
-		*dsf = Dsfile (hg->STadd, dir.mode&8r777, int dir.length, dir.mtime, path, nil, 0);
-		if(add)
-			ds.add(dsf);
+	if(dsf == nil) {
+		dsf = ref Dsfile (hg->STadd, dir.mode&8r777, int dir.length, dir.mtime, path, nil, 0);
+		ds.add(dsf);
 		if(!direct)
 			warn(sprint("%q", path));
 		ds.dirty++;
 		warn(sprint("%q already tracked", path));
 }
 
-diradd(ds: ref Dirstate, root, path: string)
+diradd(ds: ref Dirstate, path: string)
 {
-	(dirs, ok) := readdir->init(root+"/"+path, Readdir->NAME);
+	(dirs, ok) := readdir->init(repo.workroot()+"/"+path, Readdir->NAME);
 	if(ok < 0)
 		return warn(sprint("reading %q: %r", path));
 	for(i := 0; i < len dirs; i++)
 		if(dirs[i].name != ".hg")
-			add0(ds, root, hg->xsanitize(path+"/"+dirs[i].name), *dirs[i], 0);
+			add(ds, hg->xsanitize(path+"/"+dirs[i].name), *dirs[i], 0);
 }
 
 error(s: string)

appl/cmd/hg/commit.b

 dflag: int;
 vflag: int;
 repo: ref Repo;
+repobase: string;
 hgpath := "";
 msg: string;
 tr: ref Transact;
 {
 	repo = Repo.xfind(hgpath);
 	root := repo.workroot();
+	repobase = repo.xworkdir();
 
 	user := hg->xreaduser(repo);
 	now := daytime->now();
 	tzoff := daytime->local(now).tzoff;
 	say(sprint("have user %q, now %d, tzoff %d", user, now, tzoff));
 
-	ds := hg->xdirstate(repo, 0);
+	untracked := 0;
+	ds := hg->xdirstate(repo, untracked);
 
-	r: list of ref Dsfile;
 	pathtab := Strhash[ref Dsfile].new(31, nil);
-	if(args == nil)
-		r = inspect(r, ds.l, pathtab, nil);
-	else
-		for(; args != nil; args = tl args)
-			r = inspect(r, ds.findall(hd args, 0), pathtab, hd args);
+	l := ds.all();
+	if(args != nil) {
+		erroutside := 1;
+		paths := hg->xpathseval(root, repobase, args, erroutside);
+		(nil, l) = ds.enumerate(paths, untracked, 1);
+	}
+	r := inspect(l, pathtab, args!=nil);
 	if(r == nil)
 		error("no changes");
 
 		warn("created new head");
 }
 
-inspect(r, l: list of ref Dsfile, tab: ref Strhash[ref Dsfile], path: string): list of ref Dsfile
+inspect(l: list of ref Dsfile, tab: ref Strhash[ref Dsfile], relative: int): list of ref Dsfile
 {
-	n := 0;
+	r: list of ref Dsfile;
 	for(; l != nil; l = tl l) {
 		dsf := hd l;
 		if(tab.find(dsf.path) != nil)
 			continue;
 say("inspect: "+dsf.text());
+		path := dsf.path;
+		if(relative)
+			path = hg->relpath(repobase, path);
 		case dsf.state {
 		hg->STuntracked =>
 			continue;
 		hg->STneedmerge =>
 			if(dsf.state == hg->STnormal && dsf.size >= 0)
 				continue;
-			warn(sprint("M %q", dsf.path));
+			warn(sprint("M %q", path));
 		hg->STremove =>
-			warn(sprint("R %q", dsf.path));
+			warn(sprint("R %q", path));
 		hg->STadd =>
-			warn(sprint("A %q", dsf.path));
+			warn(sprint("A %q", path));
 		}
 
 		tab.add(dsf.path, dsf);
 		r = dsf::r;
-		n++;
 	}
-	if(n == 0 && path != nil)
-		warn(sprint("%q: no matches", path));
 	return r;
 }
 
 init0()
 {
 	repo := Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 0);
+	untracked := 0;
+	ds := hg->xdirstate(repo, untracked);
 
 	branch := repo.xworkbranch();
 	tags := repo.xrevtags(ds.p1);

appl/cmd/hg/log.b

 init0(args: list of string)
 {
 	repo := Repo.xfind(hgpath);
+	root := repo.workroot();
 	base := repo.xworkdir();
 
-	l := args;
-	args = nil;
-	for(; l != nil; l = tl l)
-		args = hg->xsanitize(base+"/"+hd l)::args;
+	if(args != nil) {
+		untracked := 0;
+		args = hg->xpathseval(root, base, args, untracked);
+	}
 
 	if(revstr != nil) {
 		(rev, n) := repo.xlookup(revstr, 1);

appl/cmd/hg/revert.b

 	util = load Util0 Util0->PATH;
 	util->init();
 	hg = load Mercurial Mercurial->PATH;
-	hg->init(1);
+	hg->init(0);
 
 	arg->init(args);
 	arg->setusage(arg->progname()+" [-d] [-h path] [path ...]");
 init0(args: list of string)
 {
 	repo = Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 0);
+	untracked := 0;
+	ds := hg->xdirstate(repo, untracked);
 	root := repo.workroot();
+	base := repo.xworkdir();
 
-	(nil, l) := ds.enumerate(repo.xworkdir(), args, 0, 1);
+	l := ds.all();
+	if(args != nil) {
+		erroutside := 0;
+		paths := hg->xpathseval(root, base, args, erroutside);
+		(nil, l) = ds.enumerate(paths, untracked, 1);
+	}
 	for(; l != nil; l = tl l) {
 		dsf := hd l;
 		path := dsf.path;
 init0(args: list of string)
 {
 	repo = Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 0);
+	untracked := 0;
+	ds := hg->xdirstate(repo, untracked);
 	root := repo.workroot();
+	base := repo.xworkdir();
 
 	now := daytime->now();
-	for(l := ds.enumerate(repo.xworkdir(), args, 0, 1).t1; l != nil; l = tl l) {
+	erroutside := 0;
+	paths := hg->xpathseval(root, base, args, erroutside);
+	(nil, l) := ds.enumerate(paths, untracked, 1);
+	for(; l != nil; l = tl l) {
 		f := hd l;
 		p := f.path;
 
 			f.size = hg->SZdirty;
 			f.mtime = now;
 			ds.dirty++;
-			if(sys->remove(hg->xsanitize(root+"/"+p)) != 0)
+			if(sys->remove(root+"/"+p) != 0)
 				warn(sprint("removing %q: %r", p));
 		hg->STadd =>
 			if(fflag) {

appl/cmd/hg/status.b

 init0(args: list of string)
 {
 	repo := Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 1);
+	untracked := 1;
+	ds := hg->xdirstate(repo, untracked);
+	root := repo.workroot();
+	base := repo.xworkdir();
 
-	# first print status for all known files
-	a := l2a(ds.enumerate(".", args, 1, 1).t1);
+	l := ds.all();
+	if(args != nil) {
+		erroutside := 0;
+		paths := hg->xpathseval(root, base, args, erroutside);
+		(nil, l) = ds.enumerate(paths, untracked, 1);
+	}
+	a := l2a(l);
 	inssort(a, statepathge);
 	for(i := 0; i < len a; i++) {
 		f := a[i];
 say("dsf "+f.text());
+		path := f.path;
+		if(args != nil)
+			path = hg->relpath(base, path);
 		if(f.missing) {
-			sys->print("! %q\n", f.path);
+			sys->print("! %q\n", path);
 			continue;
 		}
 		case f.state {
 		hg->STneedmerge =>
-			sys->print("M %q\n", f.path);
+			sys->print("M %q\n", path);
 		hg->STremove =>
-			sys->print("R %q\n", f.path);
+			sys->print("R %q\n", path);
 		hg->STadd =>
-			sys->print("A %q\n", f.path);
+			sys->print("A %q\n", path);
 		hg->STnormal =>
 			if(f.size < 0)
-				sys->print("M %q\n", f.path);
+				sys->print("M %q\n", path);
 		hg->STuntracked =>
-			sys->print("? %q\n", f.path);
+			sys->print("? %q\n", path);
 		* =>
 			raise "missing case";
 		}

appl/cmd/hg/update.b

 Cflag: int;
 repo: ref Repo;
 hgpath := "";
+reporoot: string;
+repobase: string;
 
 init(nil: ref Draw->Context, args: list of string)
 {
 init0(revstr: string)
 {
 	repo = Repo.xfind(hgpath);
-	ds := hg->xdirstate(repo, 0);
+	reporoot = repo.workroot();
+	repobase = repo.xworkdir();
+	untracked := 0;
+	ds := hg->xdirstate(repo, untracked);
 	if(revstr == nil)
 		revstr = repo.xworkbranch();
 	if(ds.p1 != hg->nullnode && ds.p2 != hg->nullnode && !Cflag)
 		error("in merge, refusing to update without -C");
 
 	onodeid := ds.p1;
-	(orev, nil) := repo.xlookup(onodeid, 1);
 
 	(nrev, nnodeid) := repo.xlookup(revstr, 1);
 	say(sprint("new rev %d nodeid %q, revstr %q", nrev, nnodeid, revstr));
 			oi++;
 			ni++;
 		} else if(op != nil && op < np || np == nil) {
-			say(sprint("removing %q", op));
-			sys->remove(op);
+			f := reporoot+"/"+op;
+			say(sprint("removing %q", f));
+			sys->remove(f);
 			removedirs(nfiles, op);
 			nremoved++;
 			oi++;
 	if(a == nil || mfhasprefix(mf, a))
 		return;
 	a = a[:len a-1];
-	sys->remove(a);
+	f := reporoot+"/"+a;
+	sys->remove(f);
 	removedirs(mf, a);
 }
 
 	rl := repo.xopenrevlog(path);
 	buf := rl.xgetn(nodeid);
 
-	s := ".";
-	for(l := sys->tokenize(path, "/").t1; len l > 1; l = tl l) {
-		s += "/"+hd l;
-		sys->create(s, Sys->OREAD, 8r777|Sys->DMDIR);
-	}
-
-	fd := sys->create(path, Sys->OWRITE|Sys->OTRUNC, 8r666);
+	hg->ensuredirs(reporoot, path);
+	f := reporoot+"/"+path;
+	fd := sys->create(f, Sys->OWRITE|Sys->OTRUNC, 8r666);
 	if(fd == nil)
-		error(sprint("create %q: %r", path));
+		error(sprint("create %q: %r", f));
 	if(sys->write(fd, buf, len buf) != len buf)
-		error(sprint("write %q: %r", path));
+		error(sprint("write %q: %r", f));
 }
 
 dsadd(ds: ref Dirstate, path: string)
 {
-	(ok, dir) := sys->stat(path);
+	f := reporoot+"/"+path;
+	(ok, dir) := sys->stat(f);
 	if(ok < 0)
-		error(sprint("stat %q: %r", path));
+		error(sprint("stat %q: %r", f));
 	dsf := ref Dsfile (hg->STnormal, dir.mode&8r777, int dir.length, dir.mtime, path, nil, 0);
 	ds.add(dsf);
 }

appl/lib/mercurial.b

 	Strhash: import tables;
 include "util0.m";
 	util: Util0;
-	g16, g32i, eq, hasstr, p32, p32i, p16, stripws, prefix, suffix, rev, max, l2a, readfile, writefile: import util;
+	join, 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;
 
 xsanitize(s: string): string
 {
-	slash := str->prefix("/", s);
+	pre: string;
+	if(str->prefix("/", s))
+		pre = "/";
+	else if(str->prefix("#", s)) {
+		(pre, s) = str->splitstrl(s, "/");
+		pre += "/";
+	}
 	r: list of string;
 	for(l := sys->tokenize(s, "/").t1; l != nil; l = tl l)
 		case hd l {
 		s += "/"+hd l;
 	if(s != nil)
 		s = s[1:];
-	if(slash)
-		s = "/"+s;
+	if(pre != nil)
+		s = pre+s;
 	if(s == nil)
 		s = ".";
 	return s;
 }
 
-ensuredirs(base, path: string)
+# root is of repo, we won't write outside it
+# path is a sanitized plain file (no ".." or ".").
+# we create dirs so the file "path" can be created.
+ensuredirs(root, 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) {
+	dir := str->splitstrr(path, "/").t0;
+	(ok, nil) := sys->stat(root+"/"+dir);
+	if(ok == 0)
+		return;  # doesn't help to check if it's a dir
+
+	s := root;
+	for(l := sys->tokenize(dir, "/").t1; l != nil; l = tl l) {
 		s += "/"+hd l;
 		if(sys->create(s, Sys->OREAD, 8r777|Sys->DMDIR) == nil)
 			say(sprint("create %q failed: %r", s));
 	return b;
 }
 
-xdirstate(r: ref Repo, all: int): ref Dirstate
+xdirstate(r: ref Repo, untracked: int): ref Dirstate
 {
 say("xdirstate");
 	path := r.path+"/dirstate";
 		ds.l = dsf::ds.l;
 	}
 
-	if(all)
+	if(untracked)
 		xdirstatewalk(root, "", ds, tab);
 	ds.l = util->rev(ds.l);
 say("xdirstate done");
 	}
 }
 
+# turn "l", list of paths (from command-line usually) into repository paths given repo "root" and current dir "base".
+# a path may be absolute, in that case root & base are not used.  the resulting path should still be in the repository.
+xpathseval(root, base: string, l: list of string, erroutside: int): list of string
+{
+	paths: list of string;
+	for(; l != nil; l = tl l) {
+		p := patheval(root, base, hd l);
+		if(p == nil) {
+			err := sprint("%#q outside repository", hd l);
+			if(erroutside)
+				error(err);
+			warn(err);
+		} else
+			paths = p::paths;
+	}
+	return rev(paths);
+}
+
+patheval(root, base, p: string): string
+{
+	s: string;
+	if(p != nil && (p[0] == '/' || p[0] == '#'))
+		s = p;
+	else
+		s = root+"/"+base+"/"+p;
+	s = xsanitize(s);
+	if(!str->prefix(root+"/", s) && s != root)
+		return nil;
+	s = str->drop(s[len root:], "/");
+	if(s == nil)
+		s = ".";
+	return s;
+}
+
+# "base" is the repo work dir, no leading or trailing slashes, no "." or ".." in path.
+# "p" is a sanitized path in the repo, rooted at "base".
+# we return the relative path to p when in base.
+# e.g. base "some/dir", p "other/file", we return "../../other/file"
+relpath(base, p: string): string
+{
+	if(base == nil || base == ".")
+		return p;
+	bb := sys->tokenize(base, "/").t1;
+	pp := sys->tokenize(p, "/").t1;
+	while(bb != nil && pp != nil && hd bb == hd pp) {
+		bb = tl bb;
+		pp = tl pp;
+	}
+	base = join(bb, "/");
+	p = join(pp, "/");
+	n := sys->tokenize(base, "/").t0;
+	s := "";
+	while(n-- > 0)
+		s += "../";
+	s = s+p;
+	return s;
+}
 
 getline(b: ref Iobuf): string
 {
 		error(sprint("stat %q: %r", namepath));
 	name := dir.name;
 
-	repo := ref Repo (path, requires, name, -1, -1, nil, nil);
+	workpath := sys->fd2path(xopen(path+"/..", Sys->OREAD));
+	repo := ref Repo (path, workpath, requires, name, -1, -1, nil, nil);
 	if(repo.isstore() && !isdir(path+"/store"))
 		error("missing directory \".hg/store\"");
 	if(!repo.isstore() && !isdir(path+"/data"))
 Repo.xfind(path: string): ref Repo
 {
 	if(path == nil)
-		path = workdir();
+		path = sys->fd2path(xopen(".", Sys->OREAD));
 
 	while(path != nil) {
 		while(path != nil && path[len path-1] == '/')
 	return rev(r);
 }
 
-Dirstate.enumerate(ds: self ref Dirstate, base: string, paths: list of string, untracked, vflag: int): (list of string, list of ref Dsfile)
+Dirstate.all(ds: self ref Dirstate): list of ref Dsfile
 {
-	if(paths == nil) {
-		l := ds.findall(base, untracked);
-		if(l == nil)
-			return (base::nil, nil);
-		return (nil, l);
-	}
+	return ds.l;
+}
 
+Dirstate.enumerate(ds: self ref Dirstate, paths: list of string, untracked, vflag: int): (list of string, list of ref Dsfile)
+{
 	tab := Strhash[ref Dsfile].new(101, nil);
 	r: list of ref Dsfile;
 	nomatch: list of string;
 	for(; paths != nil; paths = tl paths) {
-		p := base+"/"+hd paths;
+		p := hd paths;
 		n := 0;
 		for(l := ds.findall(p, untracked); l != nil; l = tl l) {
 			dsf := hd l;
 		error(err);
 }
 
+# no ending slash
 Repo.workroot(r: self ref Repo): string
 {
-	return r.path[:len r.path-len "/.hg"];
+	return r.workpath;
 }
 
+# nil if path is outside repo
+Repo.patheval(r: self ref Repo, base, p: string): string
+{
+	return patheval(r.workroot(), base, p);
+}
+
+# no leading & no ending slash.  relative to root.
 Repo.xworkdir(r: self ref Repo): string
 {
 	root := r.workroot();
 	cwd := sys->fd2path(sys->open(".", Sys->OREAD));
 	if(!str->prefix(root, cwd))
-		error(sprint("cannot determine current directory in repository, workroot %q, cwd %q", root, cwd));
+		return ".";
 	base := cwd[len root:];
 	base = str->drop(base, "/");
 	if(base == nil)
 	return ok == 0 && dir.mode & Sys->DMDIR;
 }
 
-workdir(): string
-{
-	fd := sys->open(".", Sys->OREAD);
-	if(fd == nil)
-		return nil;
-	return sys->fd2path(fd);
-}
-
 breadn(b: ref Iobuf, buf: array of byte, e: int): int
 {
 	s := 0;

module/mercurial.m

 	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;
+	xdirstate:	fn(r: ref Repo, untracked: int): ref Dirstate;
 	xparsetags:	fn(r: ref Repo, s: string): list of ref Tag;
 	xstreamin:	fn(r: ref Repo, b: ref Bufio->Iobuf);
+	xpathseval:	fn(root, base: string, l: list of string, erroutside: int): list of string;
+	relpath:	fn(base, p: string): string;
 
 	Entrysize:	con 64;
 
 		pack:	fn(e: self ref Dirstate, buf: array of byte);
 		find:	fn(d: self ref Dirstate, path: string): ref Dsfile;
 		findall:	fn(d: self ref Dirstate, pp: string, untracked: int): list of ref Dsfile;
-		enumerate:	fn(d: self ref Dirstate, base: string, paths: list of string, untracked, vflag: int): (list of string, list of ref Dsfile);
+		all:		fn(d: self ref Dirstate): list of ref Dsfile;
+		enumerate:	fn(d: self ref Dirstate, paths: list of string, untracked, vflag: int): (list of string, list of ref Dsfile);
 		add:	fn(d: self ref Dirstate, dsf: ref Dsfile);
 		del:	fn(d: self ref Dirstate, path: string);
 		haschanges:	fn(d: self ref Dirstate): int;
 	};
 
 	Repo: adt {
-		path:	string;
+		path:	string;	# of .hg dir
+		workpath:	string; # of work root, ".." in repo.path
 		requires:	list of string;
 		reponame: 	string;
 		lastrevision:	int;
 		storedir:	fn(r: self ref Repo): string;
 		isstore:	fn(r: self ref Repo): int;
 		isrevlogv1:	fn(r: self ref Repo): int;
+		patheval:	fn(r: self ref Repo, base, path: string): string;
 
 		xopen:		fn(path: string): ref Repo;
 		xfind:		fn(path: string): ref Repo;