changeset 1584:b3e94785ab69

merge with crew
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Sun, 11 Dec 2005 15:38:42 -0800
parents 32a4e6802864 (current diff) 63799b01985c (diff)
children d7c4b9bfcc94
files mercurial/util.py
diffstat 42 files changed, 1030 insertions(+), 414 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/bash_completion	Fri Nov 04 11:51:01 2005 -0800
+++ b/contrib/bash_completion	Sun Dec 11 15:38:42 2005 -0800
@@ -2,18 +2,25 @@
 
 _hg_commands()
 {
-    local commands="$(hg -v help | sed -e '1,/^list of commands:/d' \
-				       -e '/^global options:/,$d' \
-				       -e '/^ [^ ]/!d; s/[,:]//g;')"
+    local all commands result
+
+    all=($(hg --debug help | sed -e '1,/^list of commands:/d' \
+				 -e '/^global options:/,$d' \
+				 -e '/^ [^ ]/!d; s/^ //; s/[,:]//g;'))
+
+    commands="${all[*]##debug*}"
+    result=$(compgen -W "${commands[*]}" -- "$cur")
 
     # hide debug commands from users, but complete them if
-    # specifically asked for
-    if [[ "$cur" == de* ]]; then
-	commands="$commands debugcheckstate debugstate debugindex"
-	commands="$commands debugindexdot debugwalk debugdata"
-	commands="$commands debugancestor debugconfig debugrename"
+    # there is no other possible command
+    if [ "$result" = "" ]; then
+	local debug
+	debug=(${all[*]##!(debug*)})
+	debug="${debug[*]/g/debug}"
+	result=$(compgen -W "$debug" -- "$cur")
     fi
-    COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$commands" -- "$cur") )
+
+    COMPREPLY=(${COMPREPLY[@]:-} $result)
 }
 
 _hg_paths()
@@ -161,7 +168,7 @@
 	    fi
 	;;
 	*)
-            COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
+	    COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
 	;;
     esac
 
--- a/contrib/hbisect.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/contrib/hbisect.py	Sun Dec 11 15:38:42 2005 -0800
@@ -26,7 +26,7 @@
             ui.warn("Repository is not clean, please commit or revert\n")
             sys.exit(1)
 
-class bisect:
+class bisect(object):
     """dichotomic search in the DAG of changesets"""
     def __init__(self, ui, repo):
         self.repo = repo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hg-ssh	Sun Dec 11 15:38:42 2005 -0800
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+#
+# Copyright 2005 by Intevation GmbH <intevation@intevation.de>
+# Author(s):
+# Thomas Arendsen Hein <thomas@intevation.de>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""
+hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
+
+To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
+command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ...
+(probably together with these other useful options:
+ no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
+
+This allows pull/push over ssh to to the repositories given as arguments.
+
+If all your repositories are subdirectories of a common directory, you can
+allow shorter paths with:
+command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
+"""
+
+from mercurial import commands
+
+import sys, os
+
+cwd = os.getcwd()
+allowed_paths = [os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
+                 for path in sys.argv[1:]]
+orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
+
+if orig_cmd.startswith('hg -R ') and orig_cmd.endswith(' serve --stdio'):
+    path = orig_cmd[6:-14]
+    repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
+    if repo in allowed_paths:
+        commands.dispatch(['-R', repo, 'serve', '--stdio'])
+    else:
+        sys.stderr.write("Illegal repository %r\n" % repo)
+        sys.exit(-1)
+else:
+    sys.stderr.write("Illegal command %r\n" % orig_cmd)
+    sys.exit(-1)
+
--- a/contrib/zsh_completion	Fri Nov 04 11:51:01 2005 -0800
+++ b/contrib/zsh_completion	Sun Dec 11 15:38:42 2005 -0800
@@ -116,7 +116,7 @@
         '*:file:_files'
     ;;
 
-    (status)
+    (status|st)
         _arguments $includeExclude \
         '(--no-status)-n[hide status prefix]' \
         '(-n)--no-status[hide status prefix]' \
--- a/doc/hg.1.txt	Fri Nov 04 11:51:01 2005 -0800
+++ b/doc/hg.1.txt	Sun Dec 11 15:38:42 2005 -0800
@@ -87,7 +87,7 @@
     New files are ignored if they match any of the patterns in .hgignore. As
     with add, these changes take effect at the next commit.
 
-annotate [-r <rev> -u -n -c] [files ...]::
+annotate [-r <rev> -u -n -c -d] [files ...]::
     List changes in files, showing the revision id responsible for each line
 
     This command is useful to discover who did a change or when a change took
@@ -103,6 +103,7 @@
     -X, --exclude <pat>   exclude names matching the given patterns
     -r, --revision <rev>  annotate the specified revision
     -u, --user            list the author
+    -d, --date            list the commit date
     -c, --changeset       list the changeset
     -n, --number          list the revision number (default)
 
--- a/mercurial/bdiff.c	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/bdiff.c	Sun Dec 11 15:38:42 2005 -0800
@@ -147,7 +147,7 @@
 				break;
 
 		a[i].e = j; /* use equivalence class for quick compare */
-		if(h[j].len <= t)
+		if (h[j].len <= t)
 			a[i].n = h[j].pos; /* point to head of match list */
 		else
 			a[i].n = -1; /* too popular */
@@ -270,7 +270,7 @@
 	if (!l.head || !rl)
 		goto nomem;
 
-	for(h = l.base; h != l.head; h++) {
+	for (h = l.base; h != l.head; h++) {
 		m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2);
 		PyList_SetItem(rl, pos, m);
 		pos++;
@@ -305,7 +305,7 @@
 		goto nomem;
 
 	/* calculate length of output */
-	for(h = l.base; h != l.head; h++) {
+	for (h = l.base; h != l.head; h++) {
 		if (h->a1 != la || h->b1 != lb)
 			len += 12 + bl[h->b1].l - bl[lb].l;
 		la = h->a2;
@@ -320,7 +320,7 @@
 	rb = PyString_AsString(result);
 	la = lb = 0;
 
-	for(h = l.base; h != l.head; h++) {
+	for (h = l.base; h != l.head; h++) {
 		if (h->a1 != la || h->b1 != lb) {
 			len = bl[h->b1].l - bl[lb].l;
 			*(uint32_t *)(encode)     = htonl(al[la].l - al->l);
@@ -353,3 +353,4 @@
 {
 	Py_InitModule3("bdiff", methods, mdiff_doc);
 }
+
--- a/mercurial/commands.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/commands.py	Sun Dec 11 15:38:42 2005 -0800
@@ -15,6 +15,8 @@
 
 class UnknownCommand(Exception):
     """Exception raised if command is not in the command table."""
+class AmbiguousCommand(Exception):
+    """Exception raised if command shortcut matches more than one command."""
 
 def filterfiles(filters, files):
     l = [x for x in files if x in filters]
@@ -31,25 +33,29 @@
         return [util.normpath(os.path.join(cwd, x)) for x in args]
     return args
 
-def matchpats(repo, cwd, pats=[], opts={}, head=''):
+def matchpats(repo, pats=[], opts={}, head=''):
+    cwd = repo.getcwd()
+    if not pats and cwd:
+        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
+        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
+        cwd = ''
     return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
-                        opts.get('exclude'), head)
+                        opts.get('exclude'), head) + (cwd,)
 
-def makewalk(repo, pats, opts, head=''):
-    cwd = repo.getcwd()
-    files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
+def makewalk(repo, pats, opts, node=None, head=''):
+    files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head)
     exact = dict(zip(files, files))
     def walk():
-        for src, fn in repo.walk(files=files, match=matchfn):
+        for src, fn in repo.walk(node=node, files=files, match=matchfn):
             yield src, fn, util.pathto(cwd, fn), fn in exact
     return files, matchfn, walk()
 
-def walk(repo, pats, opts, head=''):
-    files, matchfn, results = makewalk(repo, pats, opts, head)
+def walk(repo, pats, opts, node=None, head=''):
+    files, matchfn, results = makewalk(repo, pats, opts, node, head)
     for r in results:
         yield r
 
-def walkchangerevs(ui, repo, cwd, pats, opts):
+def walkchangerevs(ui, repo, pats, opts):
     '''Iterate over files and the revs they changed in.
 
     Callers most commonly need to iterate backwards over the history
@@ -79,12 +85,7 @@
     if repo.changelog.count() == 0:
         return [], False
 
-    cwd = repo.getcwd()
-    if not pats and cwd:
-        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
-        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
-    files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
-                                        pats, opts)
+    files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
     revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
     wanted = {}
     slowpath = anypats
@@ -387,7 +388,7 @@
         if with_version:
             show_version(ui)
             ui.write('\n')
-        key, i = find(cmd)
+        aliases, i = find(cmd)
         # synopsis
         ui.write("%s\n\n" % i[2])
 
@@ -399,9 +400,8 @@
 
         if not ui.quiet:
             # aliases
-            aliases = ', '.join(key.split('|')[1:])
-            if aliases:
-                ui.write(_("\naliases: %s\n") % aliases)
+            if len(aliases) > 1:
+                ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
 
             # options
             if i[1]:
@@ -482,8 +482,7 @@
 
     The files will be added to the repository at the next commit.
 
-    If no names are given, add all files in the current directory and
-    its subdirectories.
+    If no names are given, add all files in the repository.
     """
 
     names = []
@@ -537,11 +536,20 @@
         cl = repo.changelog.read(repo.changelog.node(rev))
         return trimuser(ui, cl[1], rev, ucache)
 
+    dcache = {}
+    def getdate(rev):
+    	datestr = dcache.get(rev)
+        if datestr is None:
+            cl = repo.changelog.read(repo.changelog.node(rev))
+            datestr = dcache[rev] = util.datestr(cl[2])
+	return datestr
+
     if not pats:
         raise util.Abort(_('at least one file name or pattern required'))
 
-    opmap = [['user', getname], ['number', str], ['changeset', getnode]]
-    if not opts['user'] and not opts['changeset']:
+    opmap = [['user', getname], ['number', str], ['changeset', getnode],
+             ['date', getdate]]
+    if not opts['user'] and not opts['changeset'] and not opts['date']:
         opts['number'] = 1
 
     if opts['rev']:
@@ -624,21 +632,16 @@
     %p   root-relative path name of file being printed
     """
     mf = {}
-    if opts['rev']:
-        change = repo.changelog.read(repo.lookup(opts['rev']))
-        mf = repo.manifest.read(change[0])
-    for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
+    rev = opts['rev']
+    if rev:
+        node = repo.lookup(rev)
+    else:
+        node = repo.changelog.tip()
+    change = repo.changelog.read(node)
+    mf = repo.manifest.read(change[0])
+    for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
         r = repo.file(abs)
-        if opts['rev']:
-            try:
-                n = mf[abs]
-            except (hg.RepoError, KeyError):
-                try:
-                    n = r.lookup(rev)
-                except KeyError, inst:
-                    raise util.Abort(_('cannot find file %s in rev %s'), rel, rev)
-        else:
-            n = r.tip()
+        n = mf[abs]
         fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
         fp.write(r.read(n))
 
@@ -667,7 +670,7 @@
 
     dest = os.path.realpath(dest)
 
-    class Dircleanup:
+    class Dircleanup(object):
         def __init__(self, dir_):
             self.rmtree = shutil.rmtree
             self.dir_ = dir_
@@ -735,6 +738,7 @@
     f = repo.opener("hgrc", "w", text=True)
     f.write("[paths]\n")
     f.write("default = %s\n" % abspath)
+    f.close()
 
     if not opts['noupdate']:
         update(ui, repo)
@@ -747,7 +751,7 @@
     Commit changes to the given files into the repository.
 
     If a list of files is omitted, all changes reported by "hg status"
-    from the root of the repository will be commited.
+    will be commited.
 
     The HGEDITOR or EDITOR environment variables are used to start an
     editor to add a commit comment.
@@ -770,12 +774,7 @@
 
     if opts['addremove']:
         addremove(ui, repo, *pats, **opts)
-    cwd = repo.getcwd()
-    if not pats and cwd:
-        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
-        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
-    fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
-                                    pats, opts)
+    fns, match, anypats, cwd = matchpats(repo, pats, opts)
     if pats:
         c, a, d, u = repo.changes(files=fns, match=match)
         files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
@@ -787,14 +786,10 @@
         raise util.Abort(str(inst))
 
 def docopy(ui, repo, pats, opts):
-    if not pats:
-        raise util.Abort(_('no source or destination specified'))
-    elif len(pats) == 1:
-        raise util.Abort(_('no destination specified'))
-    pats = list(pats)
-    dest = pats.pop()
-    sources = []
-    dir2dir = len(pats) == 1 and os.path.isdir(pats[0])
+    cwd = repo.getcwd()
+    errors = 0
+    copied = []
+    targets = {}
 
     def okaytocopy(abs, rel, exact):
         reasons = {'?': _('is not managed'),
@@ -805,74 +800,133 @@
         else:
             return True
 
-    for src, abs, rel, exact in walk(repo, pats, opts):
-        if okaytocopy(abs, rel, exact):
-            sources.append((abs, rel, exact))
-    if not sources:
-        raise util.Abort(_('no files to copy'))
-
-    cwd = repo.getcwd()
-    absdest = util.canonpath(repo.root, cwd, dest)
-    reldest = util.pathto(cwd, absdest)
-    if os.path.exists(reldest):
-        destisfile = not os.path.isdir(reldest)
-    else:
-        destisfile = not dir2dir and (len(sources) == 1
-                                      or repo.dirstate.state(absdest) != '?')
-
-    if destisfile and len(sources) > 1:
-        raise util.Abort(_('with multiple sources, destination must be a '
-                           'directory'))
-
-    srcpfxlen = 0
-    if dir2dir:
-        srcpfx = util.pathto(cwd, util.canonpath(repo.root, cwd, pats[0]))
-        if os.path.exists(reldest):
-            srcpfx = os.path.split(srcpfx)[0]
-        if srcpfx:
-            srcpfx += os.sep
-        srcpfxlen = len(srcpfx)
-
-    errs, copied = 0, []
-    for abs, rel, exact in sources:
-        if destisfile:
-            mydest = reldest
-        elif dir2dir:
-            mydest = os.path.join(dest, rel[srcpfxlen:])
+    def copy(abssrc, relsrc, target, exact):
+        abstarget = util.canonpath(repo.root, cwd, target)
+        reltarget = util.pathto(cwd, abstarget)
+        prevsrc = targets.get(abstarget)
+        if prevsrc is not None:
+            ui.warn(_('%s: not overwriting - %s collides with %s\n') %
+                    (reltarget, abssrc, prevsrc))
+            return
+        if (not opts['after'] and os.path.exists(reltarget) or
+            opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
+            if not opts['force']:
+                ui.warn(_('%s: not overwriting - file exists\n') %
+                        reltarget)
+                return
+            if not opts['after']:
+                os.unlink(reltarget)
+        if opts['after']:
+            if not os.path.exists(reltarget):
+                return
         else:
-            mydest = os.path.join(dest, os.path.basename(rel))
-        myabsdest = util.canonpath(repo.root, cwd, mydest)
-        myreldest = util.pathto(cwd, myabsdest)
-        if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
-            ui.warn(_('%s: not overwriting - file already managed\n') % myreldest)
-            continue
-        mydestdir = os.path.dirname(myreldest) or '.'
-        if not opts['after']:
+            targetdir = os.path.dirname(reltarget) or '.'
+            if not os.path.isdir(targetdir):
+                os.makedirs(targetdir)
             try:
-                if dir2dir: os.makedirs(mydestdir)
-                elif not destisfile: os.mkdir(mydestdir)
-            except OSError, inst:
-                if inst.errno != errno.EEXIST: raise
-        if ui.verbose or not exact:
-            ui.status(_('copying %s to %s\n') % (rel, myreldest))
-        if not opts['after']:
-            try:
-                shutil.copyfile(rel, myreldest)
-                shutil.copymode(rel, myreldest)
+                shutil.copyfile(relsrc, reltarget)
+                shutil.copymode(relsrc, reltarget)
             except shutil.Error, inst:
                 raise util.Abort(str(inst))
             except IOError, inst:
                 if inst.errno == errno.ENOENT:
-                    ui.warn(_('%s: deleted in working copy\n') % rel)
+                    ui.warn(_('%s: deleted in working copy\n') % relsrc)
                 else:
-                    ui.warn(_('%s: cannot copy - %s\n') % (rel, inst.strerror))
-                errs += 1
-                continue
-        repo.copy(abs, myabsdest)
-        copied.append((abs, rel, exact))
-    if errs:
+                    ui.warn(_('%s: cannot copy - %s\n') %
+                            (relsrc, inst.strerror))
+                    errors += 1
+                    return
+        if ui.verbose or not exact:
+            ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
+        targets[abstarget] = abssrc
+        repo.copy(abssrc, abstarget)
+        copied.append((abssrc, relsrc, exact))
+
+    def targetpathfn(pat, dest, srcs):
+        if os.path.isdir(pat):
+            if pat.endswith(os.sep):
+                pat = pat[:-len(os.sep)]
+            if destdirexists:
+                striplen = len(os.path.split(pat)[0])
+            else:
+                striplen = len(pat)
+            if striplen:
+                striplen += len(os.sep)
+            res = lambda p: os.path.join(dest, p[striplen:])
+        elif destdirexists:
+            res = lambda p: os.path.join(dest, os.path.basename(p))
+        else:
+            res = lambda p: dest
+        return res
+
+    def targetpathafterfn(pat, dest, srcs):
+        if util.patkind(pat, None)[0]:
+            # a mercurial pattern
+            res = lambda p: os.path.join(dest, os.path.basename(p))
+        elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
+            # A directory. Either the target path contains the last
+            # component of the source path or it does not.
+            def evalpath(striplen):
+                score = 0
+                for s in srcs:
+                    t = os.path.join(dest, s[1][striplen:])
+                    if os.path.exists(t):
+                        score += 1
+                return score
+
+            if pat.endswith(os.sep):
+                pat = pat[:-len(os.sep)]
+            striplen = len(pat) + len(os.sep)
+            if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
+                score = evalpath(striplen)
+                striplen1 = len(os.path.split(pat)[0])
+                if striplen1:
+                    striplen1 += len(os.sep)
+                if evalpath(striplen1) > score:
+                    striplen = striplen1
+            res = lambda p: os.path.join(dest, p[striplen:])
+        else:
+            # a file
+            if destdirexists:
+                res = lambda p: os.path.join(dest, os.path.basename(p))
+            else:
+                res = lambda p: dest
+        return res
+
+
+    pats = list(pats)
+    if not pats:
+        raise util.Abort(_('no source or destination specified'))
+    if len(pats) == 1:
+        raise util.Abort(_('no destination specified'))
+    dest = pats.pop()
+    destdirexists = os.path.isdir(dest)
+    if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
+        raise util.Abort(_('with multiple sources, destination must be an '
+                         'existing directory'))
+    if opts['after']:
+        tfn = targetpathafterfn
+    else:
+        tfn = targetpathfn
+    copylist = []
+    for pat in pats:
+        srcs = []
+        for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
+            if okaytocopy(abssrc, relsrc, exact):
+                srcs.append((abssrc, relsrc, exact))
+        if not srcs:
+            continue
+        copylist.append((tfn(pat, dest, srcs), srcs))
+    if not copylist:
+        raise util.Abort(_('no files to copy'))
+
+    for targetpath, srcs in copylist:
+        for abssrc, relsrc, exact in srcs:
+            copy(abssrc, relsrc, targetpath(relsrc), exact)
+
+    if errors:
         ui.warn(_('(consider using --after)\n'))
-    return errs, copied
+    return errors, copied
 
 def copy(ui, repo, *pats, **opts):
     """mark files as copied for the next commit
@@ -1007,7 +1061,7 @@
             change = repo.changelog.read(n)
             m = repo.manifest.read(change[0])
             n = m[relpath(repo, [file])[0]]
-        except hg.RepoError, KeyError:
+        except (hg.RepoError, KeyError):
             n = r.lookup(rev)
     else:
         n = r.tip()
@@ -1030,7 +1084,7 @@
         ui.write("%s\n" % line.rstrip())
 
 def diff(ui, repo, *pats, **opts):
-    """diff working directory (or selected files)
+    """diff repository (or selected files)
 
     Show differences between revisions for the specified files.
 
@@ -1056,7 +1110,7 @@
     if len(revs) > 2:
         raise util.Abort(_("too many revisions to diff"))
 
-    fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts)
+    fns, matchfn, anypats, cwd = matchpats(repo, pats, opts)
 
     dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
            text=opts['text'])
@@ -1177,7 +1231,7 @@
             yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
             begin = lend + 1
 
-    class linestate:
+    class linestate(object):
         def __init__(self, line, linenum, colstart, colend):
             self.line = line
             self.linenum = linenum
@@ -1227,7 +1281,7 @@
 
     fstate = {}
     skip = {}
-    changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
+    changeiter, getchange = walkchangerevs(ui, repo, pats, opts)
     count = 0
     incrementing = False
     for st, rev, fns in changeiter:
@@ -1275,11 +1329,14 @@
     changesets. They are where development generally takes place and
     are the usual targets for update and merge operations.
     """
-    heads = repo.changelog.heads()
+    if opts['rev']:
+        heads = repo.heads(repo.lookup(opts['rev']))
+    else:
+        heads = repo.heads()
     br = None
     if opts['branches']:
         br = repo.branchlookup(heads)
-    for n in repo.changelog.heads():
+    for n in heads:
         show_changeset(ui, repo, changenode=n, brinfo=br)
 
 def identify(ui, repo):
@@ -1461,11 +1518,11 @@
     Print the revision history of the specified files or the entire project.
 
     By default this command outputs: changeset id and hash, tags,
-    parents, user, date and time, and a summary for each commit. The
-    -v switch adds some more detail, such as changed files, manifest
-    hashes or message signatures.
+    non-trivial parents, user, date and time, and a summary for each
+    commit. When the -v/--verbose switch is used, the list of changed
+    files and full commit message is shown.
     """
-    class dui:
+    class dui(object):
         # Implement and delegate some ui protocol.  Save hunks of
         # output for later display in the desired order.
         def __init__(self, ui):
@@ -1487,12 +1544,7 @@
                 self.write(*args)
         def __getattr__(self, key):
             return getattr(self.ui, key)
-    cwd = repo.getcwd()
-    if not pats and cwd:
-        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
-        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
-    changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
-                                           pats, opts)
+    changeiter, getchange = walkchangerevs(ui, repo, pats, opts)
     for st, rev, fns in changeiter:
         if st == 'window':
             du = dui(ui)
@@ -1733,7 +1785,9 @@
     This command tries to fix the repository status after an interrupted
     operation. It should only be necessary when Mercurial suggests it.
     """
-    repo.recover()
+    if repo.recover():
+        return repo.verify()
+    return False
 
 def remove(ui, repo, pat, *pats, **opts):
     """remove the specified files on the next commit
@@ -1799,13 +1853,12 @@
 
     If names are given, all files matching the names are reverted.
 
-    If no names are given, all files in the current directory and
-    its subdirectories are reverted.
+    If no arguments are given, all files in the repository are reverted.
     """
     node = opts['rev'] and repo.lookup(opts['rev']) or \
            repo.dirstate.parents()[0]
 
-    files, choose, anypats = matchpats(repo, repo.getcwd(), pats, opts)
+    files, choose, anypats, cwd = matchpats(repo, pats, opts)
     (c, a, d, u) = repo.changes(match=choose)
     repo.forget(a)
     repo.undelete(d)
@@ -1928,9 +1981,8 @@
 def status(ui, repo, *pats, **opts):
     """show changed files in the working directory
 
-    Show changed files in the working directory.  If no names are
-    given, all files are shown.  Otherwise, only files matching the
-    given names are shown.
+    Show changed files in the repository.  If names are
+    given, only files that match are shown.
 
     The codes used to show the status of files are:
     M = modified
@@ -1939,8 +1991,7 @@
     ? = not tracked
     """
 
-    cwd = repo.getcwd()
-    files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
+    files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
     (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
                     for n in repo.changes(files=files, match=matchfn)]
 
@@ -1986,8 +2037,10 @@
     else:
         r = hex(repo.changelog.tip())
 
-    if name.find(revrangesep) >= 0:
-        raise util.Abort(_("'%s' cannot be used in a tag name") % revrangesep)
+    disallowed = (revrangesep, '\r', '\n')
+    for c in disallowed:
+        if name.find(c) >= 0:
+            raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
 
     if opts['local']:
         repo.opener("localtags", "a").write("%s %s\n" % (r, name))
@@ -2138,6 +2191,7 @@
          [('r', 'rev', '', _('annotate the specified revision')),
           ('a', 'text', None, _('treat all files as text')),
           ('u', 'user', None, _('list the author')),
+          ('d', 'date', None, _('list the date')),
           ('n', 'number', None, _('list the revision number (default)')),
           ('c', 'changeset', None, _('list the changeset')),
           ('I', 'include', [], _('include names matching the given patterns')),
@@ -2223,8 +2277,9 @@
          "hg grep [OPTION]... PATTERN [FILE]..."),
     "heads":
         (heads,
-         [('b', 'branches', None, _('find branch info'))],
-         _('hg heads [-b]')),
+         [('b', 'branches', None, _('find branch info')),
+          ('r', 'rev', "", _('show only heads which are descendants of rev'))],
+         _('hg heads [-b] [-r <rev>]')),
     "help": (help_, [], _('hg help [COMMAND]')),
     "identify|id": (identify, [], _('hg identify')),
     "import|patch":
@@ -2374,17 +2429,21 @@
           " debugindex debugindexdot paths")
 
 def find(cmd):
-    choice = []
+    """Return (aliases, command table entry) for command string."""
+    choice = None
     for e in table.keys():
         aliases = e.lstrip("^").split("|")
         if cmd in aliases:
-            return e, table[e]
+            return aliases, table[e]
         for a in aliases:
             if a.startswith(cmd):
-                choice.append(e)
-    if len(choice) == 1:
-        e = choice[0]
-        return e, table[e]
+                if choice:
+                    raise AmbiguousCommand(cmd)
+                else:
+                    choice = aliases, table[e]
+                    break
+    if choice:
+        return choice
 
     raise UnknownCommand(cmd)
 
@@ -2411,18 +2470,11 @@
 
     if args:
         cmd, args = args[0], args[1:]
+        aliases, i = find(cmd)
+        cmd = aliases[0]
         defaults = ui.config("defaults", cmd)
         if defaults:
-            # reparse with command defaults added
-            args = [cmd] + defaults.split() + args
-            try:
-                args = fancyopts.fancyopts(args, globalopts, options)
-            except fancyopts.getopt.GetoptError, inst:
-                raise ParseError(None, inst)
-
-            cmd, args = args[0], args[1:]
-
-        i = find(cmd)[1]
+            args = defaults.split() + args
         c = list(i[1])
     else:
         cmd = None
@@ -2460,7 +2512,7 @@
 
     external = []
     for x in u.extensions():
-        def on_exception(Exception, inst):
+        def on_exception(exc, inst):
             u.warn(_("*** failed to import extension %s\n") % x[1])
             u.warn("%s\n" % inst)
             if "--traceback" in sys.argv[1:]:
@@ -2502,6 +2554,9 @@
             u.warn(_("hg: %s\n") % inst.args[1])
             help_(u, 'shortlist')
         sys.exit(-1)
+    except AmbiguousCommand, inst:
+        u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
+        sys.exit(1)
     except UnknownCommand, inst:
         u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
         help_(u, 'shortlist')
@@ -2620,6 +2675,9 @@
         u.debug(inst, "\n")
         u.warn(_("%s: invalid arguments\n") % cmd)
         help_(u, cmd)
+    except AmbiguousCommand, inst:
+        u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
+        help_(u, 'shortlist')
     except UnknownCommand, inst:
         u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
         help_(u, 'shortlist')
@@ -2629,6 +2687,8 @@
     except:
         u.warn(_("** unknown exception encountered, details follow\n"))
         u.warn(_("** report bug details to mercurial@selenic.com\n"))
+        u.warn(_("** Mercurial Distributed SCM (version %s)\n")
+               % version.get_version())
         raise
 
     sys.exit(-1)
--- a/mercurial/dirstate.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/dirstate.py	Sun Dec 11 15:38:42 2005 -0800
@@ -13,7 +13,7 @@
 from demandload import *
 demandload(globals(), "time bisect stat util re errno")
 
-class dirstate:
+class dirstate(object):
     def __init__(self, opener, ui, root):
         self.opener = opener
         self.root = root
@@ -101,16 +101,15 @@
         try:
             return self.map[key]
         except TypeError:
-            self.read()
+            self.lazyread()
             return self[key]
 
     def __contains__(self, key):
-        if not self.map: self.read()
+        self.lazyread()
         return key in self.map
 
     def parents(self):
-        if not self.pl:
-            self.read()
+        self.lazyread()
         return self.pl
 
     def markdirty(self):
@@ -118,8 +117,7 @@
             self.dirty = 1
 
     def setparents(self, p1, p2=nullid):
-        if not self.pl:
-            self.read()
+        self.lazyread()
         self.markdirty()
         self.pl = p1, p2
 
@@ -129,9 +127,11 @@
         except KeyError:
             return "?"
 
+    def lazyread(self):
+        if self.map is None:
+            self.read()
+
     def read(self):
-        if self.map is not None: return self.map
-
         self.map = {}
         self.pl = [nullid, nullid]
         try:
@@ -154,7 +154,7 @@
             pos += l
 
     def copy(self, source, dest):
-        self.read()
+        self.lazyread()
         self.markdirty()
         self.copies[dest] = source
 
@@ -169,13 +169,13 @@
         a  marked for addition'''
 
         if not files: return
-        self.read()
+        self.lazyread()
         self.markdirty()
         for f in files:
             if state == "r":
                 self.map[f] = ('r', 0, 0, 0)
             else:
-                s = os.lstat(os.path.join(self.root, f))
+                s = os.lstat(self.wjoin(f))
                 st_size = kw.get('st_size', s.st_size)
                 st_mtime = kw.get('st_mtime', s.st_mtime)
                 self.map[f] = (state, s.st_mode, st_size, st_mtime)
@@ -184,7 +184,7 @@
 
     def forget(self, files):
         if not files: return
-        self.read()
+        self.lazyread()
         self.markdirty()
         for f in files:
             try:
@@ -198,7 +198,7 @@
         self.markdirty()
 
     def write(self):
-        st = self.opener("dirstate", "w")
+        st = self.opener("dirstate", "w", atomic=True)
         st.write("".join(self.pl))
         for f, e in self.map.items():
             c = self.copied(f)
@@ -213,7 +213,7 @@
         unknown = []
 
         for x in files:
-            if x is '.':
+            if x == '.':
                 return self.map.copy()
             if x not in self.map:
                 unknown.append(x)
@@ -241,7 +241,7 @@
                 bs += 1
         return ret
 
-    def supported_type(self, f, st, verbose=True):
+    def supported_type(self, f, st, verbose=False):
         if stat.S_ISREG(st.st_mode):
             return True
         if verbose:
@@ -258,7 +258,7 @@
         return False
 
     def statwalk(self, files=None, match=util.always, dc=None):
-        self.read()
+        self.lazyread()
 
         # walk all files by default
         if not files:
@@ -296,7 +296,6 @@
     def walkhelper(self, files, statmatch, dc):
         # recursion free walker, faster than os.walk.
         def findfiles(s):
-            retfiles = []
             work = [s]
             while work:
                 top = work.pop()
@@ -306,7 +305,7 @@
                 nd = util.normpath(top[len(self.root) + 1:])
                 if nd == '.': nd = ''
                 for f in names:
-                    np = os.path.join(nd, f)
+                    np = util.pconvert(os.path.join(nd, f))
                     if seen(np):
                         continue
                     p = os.path.join(top, f)
@@ -317,12 +316,12 @@
                         if statmatch(ds, st):
                             work.append(p)
                         if statmatch(np, st) and np in dc:
-                            yield 'm', util.pconvert(np), st
+                            yield 'm', np, st
                     elif statmatch(np, st):
                         if self.supported_type(np, st):
-                            yield 'f', util.pconvert(np), st
+                            yield 'f', np, st
                         elif np in dc:
-                            yield 'm', util.pconvert(np), st
+                            yield 'm', np, st
 
         known = {'.hg': 1}
         def seen(fn):
@@ -332,13 +331,20 @@
         # step one, find all files that match our criteria
         files.sort()
         for ff in util.unique(files):
-            f = os.path.join(self.root, ff)
+            f = self.wjoin(ff)
             try:
                 st = os.lstat(f)
             except OSError, inst:
-                if ff not in dc: self.ui.warn('%s: %s\n' % (
-                    util.pathto(self.getcwd(), ff),
-                    inst.strerror))
+                nf = util.normpath(ff)
+                found = False
+                for fn in dc:
+                    if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
+                        found = True
+                        break
+                if not found:
+                    self.ui.warn('%s: %s\n' % (
+                                 util.pathto(self.getcwd(), ff),
+                                 inst.strerror))
                 continue
             if stat.S_ISDIR(st.st_mode):
                 cmp1 = (lambda x, y: cmp(x[1], y[1]))
@@ -352,7 +358,7 @@
                     continue
                 self.blockignore = True
                 if statmatch(ff, st):
-                    if self.supported_type(ff, st):
+                    if self.supported_type(ff, st, verbose=True):
                         yield 'f', ff, st
                     elif ff in dc:
                         yield 'm', ff, st
@@ -380,7 +386,7 @@
                 nonexistent = True
                 if not st:
                     try:
-                        f = os.path.join(self.root, fn)
+                        f = self.wjoin(fn)
                         st = os.lstat(f)
                     except OSError, inst:
                         if inst.errno != errno.ENOENT:
--- a/mercurial/fancyopts.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/fancyopts.py	Sun Dec 11 15:38:42 2005 -0800
@@ -1,10 +1,10 @@
 import getopt
 
 def fancyopts(args, options, state):
-    long=[]
-    short=''
-    map={}
-    dt={}
+    long = []
+    short = ''
+    map = {}
+    dt = {}
 
     for s, l, d, c in options:
         pl = l.replace('-', '_')
--- a/mercurial/filelog.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/filelog.py	Sun Dec 11 15:38:42 2005 -0800
@@ -54,11 +54,11 @@
             mt = ""
             if meta:
                 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
-            text = "\1\n" + "".join(mt) + "\1\n" + text
+            text = "\1\n%s\1\n%s" % ("".join(mt), text)
         return self.addrevision(text, transaction, link, p1, p2)
 
     def renamed(self, node):
-        if 0 and self.parents(node)[0] != nullid:
+        if 0 and self.parents(node)[0] != nullid: # XXX
             return False
         m = self.readmeta(node)
         if m and m.has_key("copy"):
--- a/mercurial/hgweb.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/hgweb.py	Sun Dec 11 15:38:42 2005 -0800
@@ -71,7 +71,7 @@
     else:
         return os.stat(hg_path).st_mtime
 
-class hgrequest:
+class hgrequest(object):
     def __init__(self, inp=None, out=None, env=None):
         self.inp = inp or sys.stdin
         self.out = out or sys.stdout
@@ -104,7 +104,7 @@
             headers.append(('Content-length', str(size)))
         self.header(headers)
 
-class templater:
+class templater(object):
     def __init__(self, mapfile, filters={}, defaults={}):
         self.cache = {}
         self.map = {}
@@ -165,7 +165,6 @@
 common_filters = {
     "escape": cgi.escape,
     "strip": lambda x: x.strip(),
-    "rstrip": lambda x: x.rstrip(),
     "age": age,
     "date": lambda x: util.datestr(x),
     "addbreaks": nl2br,
@@ -176,7 +175,7 @@
     "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
     }
 
-class hgweb:
+class hgweb(object):
     def __init__(self, repo, name=None):
         if type(repo) == type(""):
             self.repo = hg.repository(ui.ui(), repo)
@@ -952,14 +951,8 @@
     else:
         return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
 
-def server(path, name, templates, address, port, use_ipv6=False,
-           accesslog=sys.stdout, errorlog=sys.stderr):
-    httpd = create_server(path, name, templates, address, port, use_ipv6,
-                          accesslog, errorlog)
-    httpd.serve_forever()
-
 # This is a stopgap
-class hgwebdir:
+class hgwebdir(object):
     def __init__(self, config):
         def cleannames(items):
             return [(name.strip('/'), path) for name, path in items]
@@ -1000,7 +993,10 @@
                        .replace("//", "/"))
 
                 # update time with local timezone
-                d = (get_mtime(path), util.makedate()[1])
+                try:
+                    d = (get_mtime(path), util.makedate()[1])
+                except OSError:
+                    continue
 
                 yield dict(contact=(get("ui", "username") or # preferred
                                     get("web", "contact") or # deprecated
@@ -1017,7 +1013,12 @@
         if virtual:
             real = dict(self.repos).get(virtual)
             if real:
-                hgweb(real).run(req)
+                try:
+                    hgweb(real).run(req)
+                except IOError, inst:
+                    req.write(tmpl("error", error=inst.strerror))
+                except hg.RepoError, inst:
+                    req.write(tmpl("error", error=str(inst)))
             else:
                 req.write(tmpl("notfound", repo=virtual))
         else:
--- a/mercurial/httprangereader.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/httprangereader.py	Sun Dec 11 15:38:42 2005 -0800
@@ -7,7 +7,7 @@
 
 import byterange, urllib2
 
-class httprangereader:
+class httprangereader(object):
     def __init__(self, url):
         self.url = url
         self.pos = 0
--- a/mercurial/localrepo.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/localrepo.py	Sun Dec 11 15:38:42 2005 -0800
@@ -12,7 +12,7 @@
 from demandload import *
 demandload(globals(), "re lock transaction tempfile stat mdiff errno")
 
-class localrepository:
+class localrepository(object):
     def __init__(self, ui, path=None, create=0):
         if not path:
             p = os.getcwd()
@@ -43,7 +43,7 @@
 
         self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
         try:
-            self.ui.readconfig(os.path.join(self.path, "hgrc"))
+            self.ui.readconfig(self.join("hgrc"))
         except IOError: pass
 
     def hook(self, name, **args):
@@ -225,18 +225,20 @@
         lock = self.lock()
         if os.path.exists(self.join("journal")):
             self.ui.status(_("rolling back interrupted transaction\n"))
-            return transaction.rollback(self.opener, self.join("journal"))
+            transaction.rollback(self.opener, self.join("journal"))
+            return True
         else:
             self.ui.warn(_("no interrupted transaction available\n"))
+            return False
 
     def undo(self):
+        wlock = self.wlock()
         lock = self.lock()
         if os.path.exists(self.join("undo")):
             self.ui.status(_("rolling back last transaction\n"))
             transaction.rollback(self.opener, self.join("undo"))
-            self.dirstate = None
             util.rename(self.join("undo.dirstate"), self.join("dirstate"))
-            self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
+            self.dirstate.read()
         else:
             self.ui.warn(_("no undo information available\n"))
 
@@ -249,6 +251,17 @@
                 return lock.lock(self.join("lock"), wait)
             raise inst
 
+    def wlock(self, wait=1):
+        try:
+            wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
+        except lock.LockHeld, inst:
+            if not wait:
+                raise inst
+            self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
+            wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
+        self.dirstate.read()
+        return wlock
+
     def rawcommit(self, files, text, user, date, p1=None, p2=None):
         orig_parent = self.dirstate.parents()[0] or nullid
         p1 = p1 or self.dirstate.parents()[0] or nullid
@@ -265,6 +278,8 @@
         else:
             update_dirstate = 0
 
+        wlock = self.wlock()
+        lock = self.lock()
         tr = self.transaction()
         mm = m1.copy()
         mfm = mf1.copy()
@@ -353,6 +368,7 @@
         if not self.hook("precommit"):
             return None
 
+        wlock = self.wlock()
         lock = self.lock()
         tr = self.transaction()
 
@@ -446,8 +462,14 @@
 
     def walk(self, node=None, files=[], match=util.always):
         if node:
+            fdict = dict.fromkeys(files)
             for fn in self.manifest.read(self.changelog.read(node)[0]):
-                if match(fn): yield 'm', fn
+                fdict.pop(fn, None)
+                if match(fn):
+                    yield 'm', fn
+            for fn in fdict:
+                self.ui.warn(_('%s: No such file in rev %s\n') % (
+                    util.pathto(self.getcwd(), fn), short(node)))
         else:
             for src, fn in self.dirstate.walk(files, match):
                 yield src, fn
@@ -470,6 +492,10 @@
 
         # are we comparing the working directory?
         if not node2:
+            try:
+                wlock = self.wlock(wait=0)
+            except lock.LockHeld:
+                wlock = None
             l, c, a, d, u = self.dirstate.changes(files, match)
 
             # are we comparing working dir against its parent?
@@ -481,6 +507,8 @@
                     for f in l:
                         if fcmp(f, mf2):
                             c.append(f)
+                        elif wlock is not None:
+                            self.dirstate.update([f], "n")
 
                 for l in c, a, d, u:
                     l.sort()
@@ -524,6 +552,7 @@
         return (c, a, d, u)
 
     def add(self, list):
+        wlock = self.wlock()
         for f in list:
             p = self.wjoin(f)
             if not os.path.exists(p):
@@ -536,6 +565,7 @@
                 self.dirstate.update([f], "a")
 
     def forget(self, list):
+        wlock = self.wlock()
         for f in list:
             if self.dirstate.state(f) not in 'ai':
                 self.ui.warn(_("%s not added!\n") % f)
@@ -549,6 +579,7 @@
                     util.unlink(self.wjoin(f))
                 except OSError, inst:
                     if inst.errno != errno.ENOENT: raise
+        wlock = self.wlock()
         for f in list:
             p = self.wjoin(f)
             if os.path.exists(p):
@@ -566,6 +597,7 @@
         mn = self.changelog.read(p)[0]
         mf = self.manifest.readflags(mn)
         m = self.manifest.read(mn)
+        wlock = self.wlock()
         for f in list:
             if self.dirstate.state(f) not in  "r":
                 self.ui.warn("%s not removed!\n" % f)
@@ -582,12 +614,17 @@
         elif not os.path.isfile(p):
             self.ui.warn(_("copy failed: %s is not a file\n") % dest)
         else:
+            wlock = self.wlock()
             if self.dirstate.state(dest) == '?':
                 self.dirstate.update([dest], "a")
             self.dirstate.copy(source, dest)
 
-    def heads(self):
-        return self.changelog.heads()
+    def heads(self, start=None):
+        heads = self.changelog.heads(start)
+        # sort the output in rev descending order
+        heads = [(-self.changelog.rev(h), h) for h in heads]
+        heads.sort()
+        return [n for (r, n) in heads]
 
     # branchlookup returns a dict giving a list of branches for
     # each head.  A branch is defined as the tag of a node or
@@ -1372,6 +1409,9 @@
             mw[f] = ""
             mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
 
+        if moddirstate:
+            wlock = self.wlock()
+
         for f in d:
             if f in mw: del mw[f]
 
--- a/mercurial/lock.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/lock.py	Sun Dec 11 15:38:42 2005 -0800
@@ -11,11 +11,12 @@
 class LockHeld(Exception):
     pass
 
-class lock:
-    def __init__(self, file, wait=1):
+class lock(object):
+    def __init__(self, file, wait=1, releasefn=None):
         self.f = file
         self.held = 0
         self.wait = wait
+        self.releasefn = releasefn
         self.lock()
 
     def __del__(self):
@@ -43,6 +44,8 @@
     def release(self):
         if self.held:
             self.held = 0
+            if self.releasefn:
+                self.releasefn()
             try:
                 os.unlink(self.f)
             except: pass
--- a/mercurial/manifest.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/manifest.py	Sun Dec 11 15:38:42 2005 -0800
@@ -5,17 +5,16 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import sys, struct
+import struct
 from revlog import *
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "bisect")
+demandload(globals(), "bisect array")
 
 class manifest(revlog):
     def __init__(self, opener):
         self.mapcache = None
         self.listcache = None
-        self.addlist = None
         revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
 
     def read(self, node):
@@ -25,8 +24,9 @@
         text = self.revision(node)
         map = {}
         flag = {}
-        self.listcache = (text, text.splitlines(1))
-        for l in self.listcache[1]:
+        self.listcache = array.array('c', text)
+        lines = text.splitlines(1)
+        for l in lines:
             (f, n) = l.split('\0')
             map[f] = bin(n[:40])
             flag[f] = (n[40:-1] == "x")
@@ -39,57 +39,67 @@
             self.read(node)
         return self.mapcache[2]
 
+    def diff(self, a, b):
+        return mdiff.textdiff(str(a), str(b))
+
     def add(self, map, flags, transaction, link, p1=None, p2=None,
             changed=None):
-        # directly generate the mdiff delta from the data collected during
-        # the bisect loop below
-        def gendelta(delta):
-            i = 0
-            result = []
-            while i < len(delta):
-                start = delta[i][2]
-                end = delta[i][3]
-                l = delta[i][4]
-                if l == None:
-                    l = ""
-                while i < len(delta) - 1 and start <= delta[i+1][2] \
-                          and end >= delta[i+1][2]:
-                    if delta[i+1][3] > end:
-                        end = delta[i+1][3]
-                    if delta[i+1][4]:
-                        l += delta[i+1][4]
+
+        # returns a tuple (start, end).  If the string is found
+        # m[start:end] are the line containing that string.  If start == end
+        # the string was not found and they indicate the proper sorted 
+        # insertion point.  This was taken from bisect_left, and modified
+        # to find line start/end as it goes along.
+        #
+        # m should be a buffer or a string
+        # s is a string
+        #
+        def manifestsearch(m, s, lo=0, hi=None):
+            def advance(i, c):
+                while i < lenm and m[i] != c:
                     i += 1
-                result.append(struct.pack(">lll", start, end, len(l)) +  l)
-                i += 1
-            return result
+                return i
+            lenm = len(m)
+            if not hi:
+                hi = lenm
+            while lo < hi:
+                mid = (lo + hi) // 2
+                start = mid
+                while start > 0 and m[start-1] != '\n':
+                    start -= 1
+                end = advance(start, '\0')
+                if m[start:end] < s:
+                    # we know that after the null there are 40 bytes of sha1
+                    # this translates to the bisect lo = mid + 1
+                    lo = advance(end + 40, '\n') + 1
+                else:
+                    # this translates to the bisect hi = mid
+                    hi = start
+            end = advance(lo, '\0')
+            found = m[lo:end]
+            if cmp(s, found) == 0: 
+                # we know that after the null there are 40 bytes of sha1
+                end = advance(end + 40, '\n')
+                return (lo, end+1)
+            else:
+                return (lo, lo)
 
         # apply the changes collected during the bisect loop to our addlist
-        def addlistdelta(addlist, delta):
-            # apply the deltas to the addlist.  start from the bottom up
+        # return a delta suitable for addrevision
+        def addlistdelta(addlist, x):
+            # start from the bottom up
             # so changes to the offsets don't mess things up.
-            i = len(delta)
+            i = len(x)
             while i > 0:
                 i -= 1
-                start = delta[i][0]
-                end = delta[i][1]
-                if delta[i][4]:
-                    addlist[start:end] = [delta[i][4]]
+                start = x[i][0]
+                end = x[i][1]
+                if x[i][2]:
+                    addlist[start:end] = array.array('c', x[i][2])
                 else:
                     del addlist[start:end]
-            return addlist
-
-        # calculate the byte offset of the start of each line in the
-        # manifest
-        def calcoffsets(addlist):
-            offsets = [0] * (len(addlist) + 1)
-            offset = 0
-            i = 0
-            while i < len(addlist):
-                offsets[i] = offset
-                offset += len(addlist[i])
-                i += 1
-            offsets[i] = offset
-            return offsets
+            return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \
+                            for d in x ])
 
         # if we're using the listcache, make sure it is valid and
         # parented by the same node we're diffing against
@@ -98,15 +108,13 @@
             files = map.keys()
             files.sort()
 
-            self.addlist = ["%s\000%s%s\n" %
+            text = ["%s\000%s%s\n" %
                             (f, hex(map[f]), flags[f] and "x" or '')
                             for f in files]
+            self.listcache = array.array('c', "".join(text))
             cachedelta = None
         else:
-            addlist = self.listcache[1]
-
-            # find the starting offset for each line in the add list
-            offsets = calcoffsets(addlist)
+            addlist = self.listcache
 
             # combine the changed lists into one list for sorting
             work = [[x, 0] for x in changed[0]]
@@ -114,45 +122,52 @@
             work.sort()
 
             delta = []
-            bs = 0
+            dstart = None
+            dend = None
+            dline = [""]
+            start = 0
+            # zero copy representation of addlist as a buffer
+            addbuf = buffer(addlist)
 
+            # start with a readonly loop that finds the offset of
+            # each line and creates the deltas
             for w in work:
                 f = w[0]
                 # bs will either be the index of the item or the insert point
-                bs = bisect.bisect(addlist, f, bs)
-                if bs < len(addlist):
-                    fn = addlist[bs][:addlist[bs].index('\0')]
-                else:
-                    fn = None
+                start, end = manifestsearch(addbuf, f, start)
                 if w[1] == 0:
                     l = "%s\000%s%s\n" % (f, hex(map[f]),
                                           flags[f] and "x" or '')
                 else:
-                    l = None
-                start = bs
-                if fn != f:
-                    # item not found, insert a new one
-                    end = bs
-                    if w[1] == 1:
-                        raise AssertionError(
+                    l = ""
+                if start == end and w[1] == 1:
+                    # item we want to delete was not found, error out
+                    raise AssertionError(
                             _("failed to remove %s from manifest\n") % f)
+                if dstart != None and dstart <= start and dend >= start:
+                    if dend < end:
+                        dend = end
+                    if l:
+                        dline.append(l)
                 else:
-                    # item is found, replace/delete the existing line
-                    end = bs + 1
-                delta.append([start, end, offsets[start], offsets[end], l])
+                    if dstart != None:
+                        delta.append([dstart, dend, "".join(dline)])
+                    dstart = start
+                    dend = end
+                    dline = [l]
 
-            self.addlist = addlistdelta(addlist, delta)
-            if self.mapcache[0] == self.tip():
-                cachedelta = "".join(gendelta(delta))
-            else:
-                cachedelta = None
+            if dstart != None:
+                delta.append([dstart, dend, "".join(dline)])
+            # apply the delta to the addlist, and get a delta for addrevision
+            cachedelta = addlistdelta(addlist, delta)
 
-        text = "".join(self.addlist)
-        if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
-            raise AssertionError(_("manifest delta failure\n"))
-        n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
+            # the delta is only valid if we've been processing the tip revision
+            if self.mapcache[0] != self.tip():
+                cachedelta = None
+            self.listcache = addlist
+
+        n = self.addrevision(buffer(self.listcache), transaction, link, p1,  \
+                             p2, cachedelta)
         self.mapcache = (n, map, flags)
-        self.listcache = (text, self.addlist)
-        self.addlist = None
 
         return n
--- a/mercurial/mdiff.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/mdiff.py	Sun Dec 11 15:38:42 2005 -0800
@@ -32,8 +32,8 @@
         l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn))
         if not l: return ""
         # difflib uses a space, rather than a tab
-        l[0] = l[0][:-2] + "\t" + ad + "\n"
-        l[1] = l[1][:-2] + "\t" + bd + "\n"
+        l[0] = "%s\t%s\n" % (l[0][:-2], ad)
+        l[1] = "%s\t%s\n" % (l[1][:-2], bd)
 
     for ln in xrange(len(l)):
         if l[ln][-1] != '\n':
--- a/mercurial/node.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/node.py	Sun Dec 11 15:38:42 2005 -0800
@@ -7,7 +7,7 @@
 of the GNU General Public License, incorporated herein by reference.
 """
 
-import sha, binascii
+import binascii
 
 nullid = "\0" * 20
 
--- a/mercurial/remoterepo.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/remoterepo.py	Sun Dec 11 15:38:42 2005 -0800
@@ -5,11 +5,11 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-class remoterepository:
+class remoterepository(object):
     def local(self):
         return False
 
-class remotelock:
+class remotelock(object):
     def __init__(self, repo):
         self.repo = repo
     def release(self):
--- a/mercurial/revlog.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/revlog.py	Sun Dec 11 15:38:42 2005 -0800
@@ -31,15 +31,15 @@
 
 def compress(text):
     """ generate a possibly-compressed representation of text """
-    if not text: return text
+    if not text: return ("", text)
     if len(text) < 44:
-        if text[0] == '\0': return text
-        return 'u' + text
+        if text[0] == '\0': return ("", text)
+        return ('u', text)
     bin = zlib.compress(text)
     if len(bin) > len(text):
-        if text[0] == '\0': return text
-        return 'u' + text
-    return bin
+        if text[0] == '\0': return ("", text)
+        return ('u', text)
+    return ("", bin)
 
 def decompress(bin):
     """ decompress the given input """
@@ -52,7 +52,7 @@
 
 indexformat = ">4l20s20s20s"
 
-class lazyparser:
+class lazyparser(object):
     """
     this class avoids the need to parse the entirety of large indices
 
@@ -71,6 +71,9 @@
         self.all = 0
         self.revlog = revlog
 
+    def trunc(self, pos):
+        self.l = pos/self.s
+
     def load(self, pos=None):
         if self.all: return
         if pos is not None:
@@ -91,7 +94,7 @@
             self.map[e[6]] = i
             i += 1
 
-class lazyindex:
+class lazyindex(object):
     """a lazy version of the index array"""
     def __init__(self, parser):
         self.p = parser
@@ -104,10 +107,14 @@
         return self.p.index[pos]
     def __getitem__(self, pos):
         return self.p.index[pos] or self.load(pos)
+    def __delitem__(self, pos):
+        del self.p.index[pos]
     def append(self, e):
         self.p.index.append(e)
+    def trunc(self, pos):
+        self.p.trunc(pos)
 
-class lazymap:
+class lazymap(object):
     """a lazy version of the node map"""
     def __init__(self, parser):
         self.p = parser
@@ -140,10 +147,12 @@
                 raise KeyError("node " + hex(key))
     def __setitem__(self, key, val):
         self.p.map[key] = val
+    def __delitem__(self, key):
+        del self.p.map[key]
 
 class RevlogError(Exception): pass
 
-class revlog:
+class revlog(object):
     """
     the underlying revision storage object
 
@@ -400,25 +409,28 @@
         assert heads
         return (orderedout, roots, heads)
 
-    def heads(self, stop=None):
-        """return the list of all nodes that have no children"""
-        p = {}
-        h = []
-        stoprev = 0
-        if stop and stop in self.nodemap:
-            stoprev = self.rev(stop)
+    def heads(self, start=None):
+        """return the list of all nodes that have no children
+
+        if start is specified, only heads that are descendants of
+        start will be returned
 
-        for r in range(self.count() - 1, -1, -1):
+        """
+        if start is None:
+            start = nullid
+        reachable = {start: 1}
+        heads = {start: 1}
+        startrev = self.rev(start)
+
+        for r in xrange(startrev + 1, self.count()):
             n = self.node(r)
-            if n not in p:
-                h.append(n)
-            if n == stop:
-                break
-            if r < stoprev:
-                break
             for pn in self.parents(n):
-                p[pn] = 1
-        return h
+                if pn in reachable:
+                    reachable[n] = 1
+                    heads[n] = 1
+                if pn in heads:
+                    del heads[pn]
+        return heads.keys()
 
     def children(self, node):
         """find the children of a given node"""
@@ -543,14 +555,16 @@
             end = self.end(t)
             if not d:
                 prev = self.revision(self.tip())
-                d = self.diff(prev, text)
+                d = self.diff(prev, str(text))
             data = compress(d)
-            dist = end - start + len(data)
+            l = len(data[1]) + len(data[0])
+            dist = end - start + l
 
         # full versions are inserted when the needed deltas
         # become comparable to the uncompressed text
         if not n or dist > len(text) * 2:
             data = compress(text)
+            l = len(data[1]) + len(data[0])
             base = n
         else:
             base = self.base(t)
@@ -559,14 +573,17 @@
         if t >= 0:
             offset = self.end(t)
 
-        e = (offset, len(data), base, link, p1, p2, node)
+        e = (offset, l, base, link, p1, p2, node)
 
         self.index.append(e)
         self.nodemap[node] = n
         entry = struct.pack(indexformat, *e)
 
         transaction.add(self.datafile, e[0])
-        self.opener(self.datafile, "a").write(data)
+        f = self.opener(self.datafile, "a")
+        if data[0]:
+            f.write(data[0])
+        f.write(data[1])
         transaction.add(self.indexfile, n * len(entry))
         self.opener(self.indexfile, "a").write(entry)
 
@@ -784,6 +801,10 @@
                 continue
             delta = chunk[80:]
 
+            for p in (p1, p2):
+                if not p in self.nodemap:
+                    raise RevlogError(_("unknown parent %s") % short(p1))
+
             if not chain:
                 # retrieve the parent revision of the delta chain
                 chain = p1
@@ -797,7 +818,8 @@
             # current size.
 
             if chain == prev:
-                cdelta = compress(delta)
+                tempd = compress(delta)
+                cdelta = tempd[0] + tempd[1]
 
             if chain != prev or (end - start + len(cdelta)) > measure * 2:
                 # flush our writes here so we can read it in revision
@@ -824,6 +846,36 @@
         ifh.close()
         return node
 
+    def strip(self, rev, minlink):
+        if self.count() == 0 or rev >= self.count():
+            return
+
+        # When stripping away a revision, we need to make sure it
+        # does not actually belong to an older changeset.
+        # The minlink parameter defines the oldest revision
+        # we're allowed to strip away.
+        while minlink > self.index[rev][3]:
+            rev += 1
+            if rev >= self.count():
+                return
+
+        # first truncate the files on disk
+        end = self.start(rev)
+        self.opener(self.datafile, "a").truncate(end)
+        end = rev * struct.calcsize(indexformat)
+        self.opener(self.indexfile, "a").truncate(end)
+
+        # then reset internal state in memory to forget those revisions
+        self.cache = None
+        for p in self.index[rev:]:
+            del self.nodemap[p[6]]
+        del self.index[rev:]
+
+        # truncating the lazyindex also truncates the lazymap.
+        if isinstance(self.index, lazyindex):
+            self.index.trunc(end)
+
+
     def checksize(self):
         expected = 0
         if self.count():
--- a/mercurial/transaction.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/transaction.py	Sun Dec 11 15:38:42 2005 -0800
@@ -12,10 +12,9 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 import os
-import util
 from i18n import gettext as _
 
-class transaction:
+class transaction(object):
     def __init__(self, report, opener, journal, after=None):
         self.journal = None
 
--- a/mercurial/ui.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/ui.py	Sun Dec 11 15:38:42 2005 -0800
@@ -10,7 +10,7 @@
 from demandload import *
 demandload(globals(), "re socket sys util")
 
-class ui:
+class ui(object):
     def __init__(self, verbose=False, debug=False, quiet=False,
                  interactive=True):
         self.overlay = {}
--- a/mercurial/util.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/mercurial/util.py	Sun Dec 11 15:38:42 2005 -0800
@@ -106,6 +106,13 @@
 def always(fn): return True
 def never(fn): return False
 
+def patkind(name, dflt_pat='glob'):
+    """Split a string into an optional pattern kind prefix and the
+    actual pattern."""
+    for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
+        if name.startswith(prefix + ':'): return name.split(':', 1)
+    return dflt_pat, name
+
 def globre(pat, head='^', tail='$'):
     "convert a glob pattern into a regexp"
     i, n = 0, len(pat)
@@ -158,15 +165,20 @@
     this returns a path in the form used by the local filesystem, not hg.'''
     if not n1: return localpath(n2)
     a, b = n1.split('/'), n2.split('/')
-    a.reverse(), b.reverse()
+    a.reverse()
+    b.reverse()
     while a and b and a[-1] == b[-1]:
-        a.pop(), b.pop()
+        a.pop()
+        b.pop()
     b.reverse()
     return os.sep.join((['..'] * len(a)) + b)
 
 def canonpath(root, cwd, myname):
     """return the canonical path of myname, given cwd and root"""
-    rootsep = root + os.sep
+    if root == os.sep:
+        rootsep = os.sep
+    else:
+    	rootsep = root + os.sep
     name = myname
     if not name.startswith(os.sep):
         name = os.path.join(root, cwd, name)
@@ -218,11 +230,6 @@
     make head regex a rooted bool
     """
 
-    def patkind(name, dflt_pat='glob'):
-        for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
-            if name.startswith(prefix + ':'): return name.split(':', 1)
-        return dflt_pat, name
-
     def contains_glob(name):
         for c in name:
             if c in _globchars: return True
@@ -253,7 +260,7 @@
             try:
                 pat = '(?:%s)' % regex(k, p, tail)
                 matches.append(re.compile(pat).match)
-            except re.error, inst:
+            except re.error:
                 raise Abort("invalid pattern: %s:%s" % (k, p))
 
         def buildfn(text):
@@ -362,7 +369,36 @@
     remote file access from higher level code.
     """
     p = base
-    def o(path, mode="r", text=False):
+
+    def mktempcopy(name):
+        d, fn = os.path.split(name)
+        fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
+        fp = os.fdopen(fd, "wb")
+        try:
+            fp.write(file(name, "rb").read())
+        except:
+            try: os.unlink(temp)
+            except: pass
+            raise
+        fp.close()
+        st = os.lstat(name)
+        os.chmod(temp, st.st_mode)
+        return temp
+
+    class atomicfile(file):
+        """the file will only be copied on close"""
+        def __init__(self, name, mode, atomic=False):
+            self.__name = name
+            self.temp = mktempcopy(name)
+            file.__init__(self, self.temp, mode)
+        def close(self):
+            if not self.closed:
+                file.close(self)
+                rename(self.temp, self.__name)
+        def __del__(self):
+            self.close()
+
+    def o(path, mode="r", text=False, atomic=False):
         f = os.path.join(p, path)
 
         if not text:
@@ -376,19 +412,10 @@
                 if not os.path.isdir(d):
                     os.makedirs(d)
             else:
+                if atomic:
+                    return atomicfile(f, mode)
                 if nlink > 1:
-                    d, fn = os.path.split(f)
-                    fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
-                    fp = os.fdopen(fd, "wb")
-                    try:
-                        fp.write(file(f, "rb").read())
-                    except:
-                        try: os.unlink(temp)
-                        except: pass
-                        raise
-                    fp.close()
-                    rename(temp, f)
-
+                    rename(mktempcopy(f), f)
         return file(f, mode)
 
     return o
@@ -484,6 +511,7 @@
     nulldev = '/dev/null'
 
     def rcfiles(path):
+        print 'checking', path
         rcs = [os.path.join(path, 'hgrc')]
         rcdir = os.path.join(path, 'hgrc.d')
         try:
--- a/setup.py	Fri Nov 04 11:51:01 2005 -0800
+++ b/setup.py	Sun Dec 11 15:38:42 2005 -0800
@@ -72,8 +72,10 @@
 try:
     mercurial.version.remember_version(version)
     cmdclass = {'install_data': install_package_data}
+    py2exe_opts = {}
     if py2exe_for_demandload is not None:
         cmdclass['py2exe'] = py2exe_for_demandload
+        py2exe_opts['console'] = ['hg']
     setup(name='mercurial',
           version=mercurial.version.get_version(),
           author='Matt Mackall',
@@ -90,6 +92,6 @@
                        glob.glob('templates/*.tmpl'))],
           cmdclass=cmdclass,
           scripts=['hg', 'hgmerge'],
-          console = ['hg'])
+          **py2exe_opts)
 finally:
     mercurial.version.forget_version()
--- a/templates/changelogentry-rss.tmpl	Fri Nov 04 11:51:01 2005 -0800
+++ b/templates/changelogentry-rss.tmpl	Sun Dec 11 15:38:42 2005 -0800
@@ -1,5 +1,5 @@
 <item>
-    <title>#desc|strip|firstline|rstrip|escape#</title>
+    <title>#desc|strip|firstline|strip|escape#</title>
     <link>#url#?cs=#node|short#</link>
     <description><![CDATA[#desc|strip|escape|addbreaks#]]></description>
     <author>#author|obfuscate#</author>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/error.tmpl	Sun Dec 11 15:38:42 2005 -0800
@@ -0,0 +1,15 @@
+#header#
+<title>Mercurial Error</title>
+</head>
+<body>
+
+<h2>Mercurial Error</h2>
+
+<p>
+An error occured while processing your request:
+</p>
+<p>
+#error|escape#
+</p>
+
+#footer#
--- a/templates/filelogentry-rss.tmpl	Fri Nov 04 11:51:01 2005 -0800
+++ b/templates/filelogentry-rss.tmpl	Sun Dec 11 15:38:42 2005 -0800
@@ -1,5 +1,5 @@
 <item>
-    <title>#desc|strip|firstline|rstrip|escape#</title>
+    <title>#desc|strip|firstline|strip|escape#</title>
     <link>#url#?f=#filenode|short#;file=#file#</link>
     <description><![CDATA[#desc|strip|escape|addbreaks#]]></description>
     <author>#author|obfuscate#</author>
--- a/templates/map	Fri Nov 04 11:51:01 2005 -0800
+++ b/templates/map	Sun Dec 11 15:38:42 2005 -0800
@@ -39,3 +39,4 @@
 index = index.tmpl
 archiveentry = "<a href="?ca=#node|short#;type=#type#">#type#</a> "
 notfound = notfound.tmpl
+error = error.tmpl
--- a/templates/notfound.tmpl	Fri Nov 04 11:51:01 2005 -0800
+++ b/templates/notfound.tmpl	Sun Dec 11 15:38:42 2005 -0800
@@ -5,7 +5,7 @@
 
 <h2>Mercurial Repositories</h2>
 
-The specified repository "#repo#" is unknown, sorry.
+The specified repository "#repo|escape#" is unknown, sorry.
 
 Please go back to the main repository list page.
 
--- a/templates/tags.tmpl	Fri Nov 04 11:51:01 2005 -0800
+++ b/templates/tags.tmpl	Sun Dec 11 15:38:42 2005 -0800
@@ -1,5 +1,5 @@
 #header#
-<title>#repo#: tags</title>
+<title>#repo|escape#: tags</title>
 </head>
 <body>
 
--- a/tests/run-tests	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/run-tests	Sun Dec 11 15:38:42 2005 -0800
@@ -40,16 +40,11 @@
 }
 
 TESTDIR="$PWD"
-
-if [ -d /usr/lib64 ]; then
-    lib=lib64
-else
-    lib=lib
-fi
-
 INST="$HGTMP/install"
+PYTHONDIR="$INST/lib/python"
 cd ..
-if ${PYTHON-python} setup.py install --home="$INST" > tests/install.err 2>&1
+if ${PYTHON-python} setup.py install --home="$INST" \
+  --install-lib="$PYTHONDIR" > tests/install.err 2>&1
 then
     rm tests/install.err
 else
@@ -59,8 +54,7 @@
 cd "$TESTDIR"
 
 PATH="$INST/bin:$PATH"; export PATH
-PYTHONPATH="$INST/$lib/python"; export PYTHONPATH
-
+PYTHONPATH="$PYTHONDIR"; export PYTHONPATH
 
 run_one() {
     rm -f "$1.err"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-cat	Sun Dec 11 15:38:42 2005 -0800
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+mkdir t
+cd t
+hg init
+echo 0 > a
+echo 0 > b
+hg ci -A -m m -d "0 0"
+hg rm a
+hg cat a
+sleep 1 # make sure mtime is changed
+echo 1 > b
+hg ci -m m -d "0 0"
+echo 2 > b
+hg cat -r 0 a
+hg cat -r 0 b
+hg cat -r 1 a
+hg cat -r 1 b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-cat.out	Sun Dec 11 15:38:42 2005 -0800
@@ -0,0 +1,7 @@
+adding a
+adding b
+0
+0
+0
+a: No such file in rev 551e7cb14b32
+1
--- a/tests/test-grep	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-grep	Sun Dec 11 15:38:42 2005 -0800
@@ -14,7 +14,7 @@
 hg commit -m 2 -u spam -d '2 0'
 echo 'import/export' >> port
 hg commit -m 3 -u eggs -d '3 0'
-head -3 port > port1
+head -n 3 port > port1
 mv port1 port
 hg commit -m 4 -u spam -d '4 0'
 hg grep port port
--- a/tests/test-help.out	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-help.out	Sun Dec 11 15:38:42 2005 -0800
@@ -6,7 +6,7 @@
  annotate   show changeset information per file line
  clone      make a copy of an existing repository
  commit     commit the specified files or all outstanding changes
- diff       diff working directory (or selected files)
+ diff       diff repository (or selected files)
  export     dump the header and diffs for one or more changesets
  init       create a new repository in the given directory
  log        show revision history of entire repository or files
@@ -22,7 +22,7 @@
  annotate   show changeset information per file line
  clone      make a copy of an existing repository
  commit     commit the specified files or all outstanding changes
- diff       diff working directory (or selected files)
+ diff       diff repository (or selected files)
  export     dump the header and diffs for one or more changesets
  init       create a new repository in the given directory
  log        show revision history of entire repository or files
@@ -46,7 +46,7 @@
  clone       make a copy of an existing repository
  commit      commit the specified files or all outstanding changes
  copy        mark files as copied for the next commit
- diff        diff working directory (or selected files)
+ diff        diff repository (or selected files)
  export      dump the header and diffs for one or more changesets
  forget      don't add the specified files on the next commit
  grep        search for a pattern in specified files and revisions
@@ -88,7 +88,7 @@
  clone       make a copy of an existing repository
  commit      commit the specified files or all outstanding changes
  copy        mark files as copied for the next commit
- diff        diff working directory (or selected files)
+ diff        diff repository (or selected files)
  export      dump the header and diffs for one or more changesets
  forget      don't add the specified files on the next commit
  grep        search for a pattern in specified files and revisions
@@ -130,8 +130,7 @@
 
     The files will be added to the repository at the next commit.
 
-    If no names are given, add all files in the current directory and
-    its subdirectories.
+    If no names are given, add all files in the repository.
 
 options:
 
@@ -146,8 +145,7 @@
 
     The files will be added to the repository at the next commit.
 
-    If no names are given, add all files in the current directory and
-    its subdirectories.
+    If no names are given, add all files in the repository.
 
 options:
 
@@ -155,7 +153,7 @@
  -X --exclude  exclude names matching the given patterns
 hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...
 
-diff working directory (or selected files)
+diff repository (or selected files)
 
     Show differences between revisions for the specified files.
 
@@ -181,9 +179,8 @@
 
 show changed files in the working directory
 
-    Show changed files in the working directory.  If no names are
-    given, all files are shown.  Otherwise, only files matching the
-    given names are shown.
+    Show changed files in the repository.  If names are
+    given, only files that match are shown.
 
     The codes used to show the status of files are:
     M = modified
@@ -191,6 +188,8 @@
     R = removed
     ? = not tracked
 
+aliases: st
+
 options:
 
  -m --modified   show only modified files
@@ -213,7 +212,7 @@
  annotate   show changeset information per file line
  clone      make a copy of an existing repository
  commit     commit the specified files or all outstanding changes
- diff       diff working directory (or selected files)
+ diff       diff repository (or selected files)
  export     dump the header and diffs for one or more changesets
  init       create a new repository in the given directory
  log        show revision history of entire repository or files
@@ -234,7 +233,7 @@
  annotate   show changeset information per file line
  clone      make a copy of an existing repository
  commit     commit the specified files or all outstanding changes
- diff       diff working directory (or selected files)
+ diff       diff repository (or selected files)
  export     dump the header and diffs for one or more changesets
  init       create a new repository in the given directory
  log        show revision history of entire repository or files
--- a/tests/test-hgignore	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-hgignore	Sun Dec 11 15:38:42 2005 -0800
@@ -42,4 +42,4 @@
 echo "--" ; hg status
 
 cd dir
-echo "--" ; hg status
+echo "--" ; hg status .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rename	Sun Dec 11 15:38:42 2005 -0800
@@ -0,0 +1,136 @@
+#!/bin/sh
+
+hg init
+mkdir d1 d1/d11 d2
+echo d1/a > d1/a
+echo d1/ba > d1/ba
+echo d1/a1 > d1/d11/a1
+echo d1/b > d1/b
+echo d2/b > d2/b
+hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
+hg commit -m "1" -d "0 0"
+
+echo "# rename a single file"
+hg rename d1/d11/a1 d2/c
+hg status
+hg update -C
+
+echo "# rename --after a single file"
+mv d1/d11/a1 d2/c
+hg rename --after d1/d11/a1 d2/c
+hg status
+hg update -C
+
+echo "# move a single file to an existing directory"
+hg rename d1/d11/a1 d2
+hg status
+hg update -C
+
+echo "# move --after a single file to an existing directory"
+mv d1/d11/a1 d2
+hg rename --after d1/d11/a1 d2
+hg status
+hg update -C
+
+echo "# rename a file using a relative path"
+(cd d1/d11; hg rename ../../d2/b e)
+hg status
+hg update -C
+
+echo "# rename --after a file using a relative path"
+(cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
+hg status
+hg update -C
+
+echo "# rename directory d1 as d3"
+hg rename d1/ d3
+hg status
+hg update -C
+
+echo "# rename --after directory d1 as d3"
+mv d1 d3
+hg rename --after d1 d3
+hg status
+hg update -C
+
+echo "# move a directory using a relative path"
+(cd d2; mkdir d3; hg rename ../d1/d11 d3)
+hg status
+hg update -C
+
+echo "# move --after a directory using a relative path"
+(cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
+hg status
+hg update -C
+
+echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)"
+hg rename d1/d11/ d2
+hg status
+hg update -C
+
+echo "# move directories d1 and d2 to a new directory d3"
+mkdir d3
+hg rename d1 d2 d3
+hg status
+hg update -C
+
+echo "# move --after directories d1 and d2 to a new directory d3"
+mkdir d3
+mv d1 d2 d3
+hg rename --after d1 d2 d3
+hg status
+hg update -C
+
+echo "# move everything under directory d1 to existing directory d2, do not"
+echo "# overwrite existing files (d2/b)"
+hg rename d1/* d2
+hg status
+diff d1/b d2/b
+hg update -C
+
+echo "# attempt to move potentially more than one file into a non-existent"
+echo "# directory"
+hg rename 'glob:d1/**' dx
+
+echo "# move every file under d1 to d2/d21 (glob)"
+mkdir d2/d21
+hg rename 'glob:d1/**' d2/d21
+hg status
+hg update -C
+
+echo "# move --after some files under d1 to d2/d21 (glob)"
+mkdir d2/d21
+mv d1/a d1/d11/a1 d2/d21
+hg rename --after 'glob:d1/**' d2/d21
+hg status
+hg update -C
+
+echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)"
+mkdir d2/d21
+hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
+hg status
+hg update -C
+
+echo "# attempt to overwrite an existing file"
+echo "ca" > d1/ca
+hg rename d1/ba d1/ca
+hg status
+hg update -C
+
+echo "# forced overwrite of an existing file"
+echo "ca" > d1/ca
+hg rename --force d1/ba d1/ca
+hg status
+hg update -C
+
+echo "# replace a symlink with a file"
+ln -s ba d1/ca
+hg rename --force d1/ba d1/ca
+hg status
+hg update -C
+
+echo "# do not copy more than one source file to the same destination file"
+mkdir d3
+hg rename d1/* d2/* d3
+hg status
+hg update -C
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rename.out	Sun Dec 11 15:38:42 2005 -0800
@@ -0,0 +1,183 @@
+# rename a single file
+A d2/c
+R d1/d11/a1
+# rename --after a single file
+A d2/c
+R d1/d11/a1
+# move a single file to an existing directory
+A d2/a1
+R d1/d11/a1
+# move --after a single file to an existing directory
+A d2/a1
+R d1/d11/a1
+# rename a file using a relative path
+A d1/d11/e
+R d2/b
+# rename --after a file using a relative path
+A d1/d11/e
+R d2/b
+# rename directory d1 as d3
+copying d1/a to d3/a
+copying d1/b to d3/b
+copying d1/ba to d3/ba
+copying d1/d11/a1 to d3/d11/a1
+removing d1/a
+removing d1/b
+removing d1/ba
+removing d1/d11/a1
+A d3/a
+A d3/b
+A d3/ba
+A d3/d11/a1
+R d1/a
+R d1/b
+R d1/ba
+R d1/d11/a1
+# rename --after directory d1 as d3
+copying d1/a to d3/a
+copying d1/b to d3/b
+copying d1/ba to d3/ba
+copying d1/d11/a1 to d3/d11/a1
+removing d1/a
+removing d1/b
+removing d1/ba
+removing d1/d11/a1
+A d3/a
+A d3/b
+A d3/ba
+A d3/d11/a1
+R d1/a
+R d1/b
+R d1/ba
+R d1/d11/a1
+# move a directory using a relative path
+copying ../d1/d11/a1 to d3/d11/a1
+removing ../d1/d11/a1
+A d2/d3/d11/a1
+R d1/d11/a1
+# move --after a directory using a relative path
+copying ../d1/d11/a1 to d3/d11/a1
+removing ../d1/d11/a1
+A d2/d3/d11/a1
+R d1/d11/a1
+# move directory d1/d11 to an existing directory d2 (removes empty d1)
+copying d1/d11/a1 to d2/d11/a1
+removing d1/d11/a1
+A d2/d11/a1
+R d1/d11/a1
+# move directories d1 and d2 to a new directory d3
+copying d1/a to d3/d1/a
+copying d1/b to d3/d1/b
+copying d1/ba to d3/d1/ba
+copying d1/d11/a1 to d3/d1/d11/a1
+copying d2/b to d3/d2/b
+removing d1/a
+removing d1/b
+removing d1/ba
+removing d1/d11/a1
+removing d2/b
+A d3/d1/a
+A d3/d1/b
+A d3/d1/ba
+A d3/d1/d11/a1
+A d3/d2/b
+R d1/a
+R d1/b
+R d1/ba
+R d1/d11/a1
+R d2/b
+# move --after directories d1 and d2 to a new directory d3
+copying d1/a to d3/d1/a
+copying d1/b to d3/d1/b
+copying d1/ba to d3/d1/ba
+copying d1/d11/a1 to d3/d1/d11/a1
+copying d2/b to d3/d2/b
+removing d1/a
+removing d1/b
+removing d1/ba
+removing d1/d11/a1
+removing d2/b
+A d3/d1/a
+A d3/d1/b
+A d3/d1/ba
+A d3/d1/d11/a1
+A d3/d2/b
+R d1/a
+R d1/b
+R d1/ba
+R d1/d11/a1
+R d2/b
+# move everything under directory d1 to existing directory d2, do not
+# overwrite existing files (d2/b)
+d2/b: not overwriting - file exists
+copying d1/d11/a1 to d2/d11/a1
+removing d1/d11/a1
+A d2/a
+A d2/ba
+A d2/d11/a1
+R d1/a
+R d1/ba
+R d1/d11/a1
+1c1
+< d1/b
+---
+> d2/b
+# attempt to move potentially more than one file into a non-existent
+# directory
+abort: with multiple sources, destination must be an existing directory
+# move every file under d1 to d2/d21 (glob)
+copying d1/a to d2/d21/a
+copying d1/b to d2/d21/b
+copying d1/ba to d2/d21/ba
+copying d1/d11/a1 to d2/d21/a1
+removing d1/a
+removing d1/b
+removing d1/ba
+removing d1/d11/a1
+A d2/d21/a
+A d2/d21/a1
+A d2/d21/b
+A d2/d21/ba
+R d1/a
+R d1/b
+R d1/ba
+R d1/d11/a1
+# move --after some files under d1 to d2/d21 (glob)
+copying d1/a to d2/d21/a
+copying d1/d11/a1 to d2/d21/a1
+removing d1/a
+removing d1/d11/a1
+A d2/d21/a
+A d2/d21/a1
+R d1/a
+R d1/d11/a1
+# move every file under d1 starting with an 'a' to d2/d21 (regexp)
+copying d1/a to d2/d21/a
+copying d1/d11/a1 to d2/d21/a1
+removing d1/a
+removing d1/d11/a1
+A d2/d21/a
+A d2/d21/a1
+R d1/a
+R d1/d11/a1
+# attempt to overwrite an existing file
+d1/ca: not overwriting - file exists
+? d1/ca
+# forced overwrite of an existing file
+A d1/ca
+R d1/ba
+# replace a symlink with a file
+A d1/ca
+R d1/ba
+# do not copy more than one source file to the same destination file
+copying d1/d11/a1 to d3/d11/a1
+d3/b: not overwriting - d2/b collides with d1/b
+removing d1/d11/a1
+A d3/a
+A d3/b
+A d3/ba
+A d3/d11/a1
+R d1/a
+R d1/b
+R d1/ba
+R d1/d11/a1
--- a/tests/test-symlinks	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-symlinks	Sun Dec 11 15:38:42 2005 -0800
@@ -39,3 +39,4 @@
 mkfifo a.c
 # it should show a.c, dir/a.o and dir/b.o removed
 hg status
+hg status a.c
--- a/tests/test-symlinks.out	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-symlinks.out	Sun Dec 11 15:38:42 2005 -0800
@@ -1,15 +1,11 @@
-bar: unsupported file type (type is symbolic link)
 adding foo
-bar: unsupported file type (type is symbolic link)
-bar: unsupported file type (type is symbolic link)
 adding bomb
-bar: unsupported file type (type is symbolic link)
 adding a.c
 adding dir/a.o
 adding dir/b.o
-a.c: unsupported file type (type is fifo)
-dir/b.o: unsupported file type (type is symbolic link)
 R a.c
 R dir/a.o
 R dir/b.o
 ? .hgignore
+a.c: unsupported file type (type is fifo)
+R a.c
--- a/tests/test-tag	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-tag	Sun Dec 11 15:38:42 2005 -0800
@@ -11,3 +11,7 @@
 echo foo >> .hgtags
 hg tag -d "0 0" "bleah2" || echo "failed"
 
+hg tag -l 'xx
+newline'
+hg tag -l 'xx:xx'
+true
--- a/tests/test-tag.out	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-tag.out	Sun Dec 11 15:38:42 2005 -0800
@@ -18,3 +18,5 @@
 
 abort: working copy of .hgtags is changed (please commit .hgtags manually)
 failed
+abort: '\n' cannot be used in a tag name
+abort: ':' cannot be used in a tag name
--- a/tests/test-walk	Fri Nov 04 11:51:01 2005 -0800
+++ b/tests/test-walk	Sun Dec 11 15:38:42 2005 -0800
@@ -20,14 +20,14 @@
 hg commit -m "commit #0" -d "0 0"
 hg debugwalk
 cd mammals
-hg debugwalk
+hg debugwalk .
 hg debugwalk Procyonidae
 cd Procyonidae
-hg debugwalk
+hg debugwalk .
 hg debugwalk ..
 cd ..
 hg debugwalk ../beans
-hg debugwalk
+hg debugwalk .
 cd ..
 hg debugwalk -Ibeans
 hg debugwalk 'glob:mammals/../beans/b*'