changeset 2177:6886bc0b77af

merge with crew.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Tue, 02 May 2006 14:37:55 -0700
parents 3044a3fdae76 (diff) 9b42304d9896 (current diff)
children 00205fe76993
files mercurial/appendfile.py mercurial/revlog.py mercurial/sshrepo.py mercurial/util.py
diffstat 37 files changed, 684 insertions(+), 182 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue May 02 14:30:00 2006 -0700
+++ b/.hgignore	Tue May 02 14:37:55 2006 -0700
@@ -9,6 +9,7 @@
 *.swp
 *.prof
 tests/.coverage*
+tests/annotated
 tests/*.err
 build
 dist
--- a/CONTRIBUTORS	Tue May 02 14:30:00 2006 -0700
+++ b/CONTRIBUTORS	Tue May 02 14:37:55 2006 -0700
@@ -14,6 +14,7 @@
 Vadim Lebedev <vadim at mbdsys.com>
 Christopher Li <hg at chrisli.org>
 Chris Mason <mason at suse.com>
+Colin McMillen <mcmillen at cs.cmu.edu>
 Wojciech Milkowski <wmilkowski at interia.pl>
 Chad Netzer <chad.netzer at gmail.com>
 Bryan O'Sullivan <bos at serpentine.com>
--- a/doc/hgrc.5.txt	Tue May 02 14:30:00 2006 -0700
+++ b/doc/hgrc.5.txt	Tue May 02 14:37:55 2006 -0700
@@ -131,11 +131,11 @@
     **.txt = tempfile: unix2dos -n INFILE OUTFILE
 
 hooks::
-  Commands that get automatically executed by various actions such as
-  starting or finishing a commit. Multiple commands can be run for
-  the same action by appending a suffix to the action. Overriding a
-  site-wide hook can be done by changing its value or setting it to
-  an empty string.
+  Commands or Python functions that get automatically executed by
+  various actions such as starting or finishing a commit. Multiple
+  hooks can be run for the same action by appending a suffix to the
+  action. Overriding a site-wide hook can be done by changing its
+  value or setting it to an empty string.
 
   Example .hg/hgrc:
 
@@ -211,6 +211,21 @@
   the environment for backwards compatibility, but their use is
   deprecated, and they will be removed in a future release.
 
+  The syntax for Python hooks is as follows:
+
+    hookname = python:modulename.submodule.callable
+
+  Python hooks are run within the Mercurial process.  Each hook is
+  called with at least three keyword arguments: a ui object (keyword
+  "ui"), a repository object (keyword "repo"), and a "hooktype"
+  keyword that tells what kind of hook is used.  Arguments listed as
+  environment variables above are passed as keyword arguments, with no
+  "HG_" prefix, and names in lower case.
+
+  A Python hook must return a "true" value to succeed.  Returning a
+  "false" value or raising an exception is treated as failure of the
+  hook.
+
 http_proxy::
   Used to access web-based Mercurial repositories through a HTTP
   proxy.
--- a/hgext/gpg.py	Tue May 02 14:30:00 2006 -0700
+++ b/hgext/gpg.py	Tue May 02 14:37:55 2006 -0700
@@ -23,11 +23,11 @@
         """ returns of the good and bad signatures"""
         try:
             # create temporary files
-            fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
+            fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
             fp = os.fdopen(fd, 'wb')
             fp.write(sig)
             fp.close()
-            fd, datafile = tempfile.mkstemp(prefix="hggpgdata")
+            fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
             fp = os.fdopen(fd, 'wb')
             fp.write(data)
             fp.close()
--- a/hgext/patchbomb.py	Tue May 02 14:30:00 2006 -0700
+++ b/hgext/patchbomb.py	Tue May 02 14:37:55 2006 -0700
@@ -62,7 +62,7 @@
 except ImportError: pass
 
 def diffstat(patch):
-    fd, name = tempfile.mkstemp()
+    fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
     try:
         p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
         try:
--- a/mercurial/appendfile.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/appendfile.py	Tue May 02 14:37:55 2006 -0700
@@ -38,7 +38,7 @@
             self.tmpname = tmpname
             self.tmpfp = util.posixfile(self.tmpname, 'ab+')
         else:
-            fd, self.tmpname = tempfile.mkstemp()
+            fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
             os.close(fd)
             self.tmpfp = util.posixfile(self.tmpname, 'ab+')
         self.realfp = fp
--- a/mercurial/archival.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/archival.py	Tue May 02 14:37:55 2006 -0700
@@ -80,8 +80,11 @@
 
     def __init__(self, dest, prefix, compress=True):
         self.prefix = tidyprefix(dest, prefix, ('.zip',))
-        if not isinstance(dest, str) and not hasattr(dest, 'tell'):
-            dest = tellable(dest)
+        if not isinstance(dest, str):
+            try:
+                dest.tell()
+            except (AttributeError, IOError):
+                dest = tellable(dest)
         self.z = zipfile.ZipFile(dest, 'w',
                                  compress and zipfile.ZIP_DEFLATED or
                                  zipfile.ZIP_STORED)
--- a/mercurial/changelog.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/changelog.py	Tue May 02 14:37:55 2006 -0700
@@ -11,7 +11,7 @@
 demandload(globals(), "os time util")
 
 class changelog(revlog):
-    def __init__(self, opener, defversion=0):
+    def __init__(self, opener, defversion=REVLOGV0):
         revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
                         defversion)
 
@@ -41,14 +41,15 @@
         if date:
             # validate explicit (probably user-specified) date and
             # time zone offset. values must fit in signed 32 bits for
-            # current 32-bit linux runtimes.
+            # current 32-bit linux runtimes. timezones go from UTC-12
+            # to UTC+14
             try:
                 when, offset = map(int, date.split(' '))
             except ValueError:
                 raise ValueError(_('invalid date: %r') % date)
             if abs(when) > 0x7fffffff:
                 raise ValueError(_('date exceeds 32 bits: %d') % when)
-            if abs(offset) >= 43200:
+            if offset < -50400 or offset > 43200:
                 raise ValueError(_('impossible time zone offset: %d') % offset)
         else:
             date = "%d %d" % util.makedate()
--- a/mercurial/commands.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/commands.py	Tue May 02 14:37:55 2006 -0700
@@ -19,6 +19,11 @@
 class AmbiguousCommand(Exception):
     """Exception raised if command shortcut matches more than one command."""
 
+def bail_if_changed(repo):
+    modified, added, removed, deleted, unknown = repo.changes()
+    if modified or added or removed or deleted:
+        raise util.Abort(_("outstanding uncommitted changes"))
+
 def filterfiles(filters, files):
     l = [x for x in files if x in filters]
 
@@ -298,7 +303,7 @@
                 raise util.Abort(_("file '%s' already exists"), filename)
             fh = open(filename, "wb")
         else:
-            fd, filename = tempfile.mkstemp(suffix=".hg", prefix="hg-bundle-")
+            fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
             fh = os.fdopen(fd, "wb")
         cleanup = filename
 
@@ -926,10 +931,48 @@
     prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
     if os.path.realpath(dest) == repo.root:
         raise util.Abort(_('repository root cannot be destination'))
-    _, matchfn, _ = matchpats(repo, [], opts)
+    dummy, matchfn, dummy = matchpats(repo, [], opts)
     archival.archive(repo, dest, node, opts.get('type') or 'files',
                     not opts['no_decode'], matchfn, prefix)
 
+def backout(ui, repo, rev, **opts):
+    '''reverse effect of earlier changeset
+
+    Commit the backed out changes as a new changeset.
+
+    If you back out a changeset other than the tip, a new head is
+    created.  The --merge option remembers the parent of the working
+    directory before starting the backout, then merges the new head
+    with it afterwards, to save you from doing this by hand.  The
+    result of this merge is not committed, as for a normal merge.'''
+
+    bail_if_changed(repo)
+    op1, op2 = repo.dirstate.parents()
+    if op2 != nullid:
+        raise util.Abort(_('outstanding uncommitted merge'))
+    node = repo.lookup(rev)
+    parent, p2 = repo.changelog.parents(node)
+    if parent == nullid:
+        raise util.Abort(_('cannot back out a change with no parents'))
+    if p2 != nullid:
+        raise util.Abort(_('cannot back out a merge'))
+    repo.update(node, force=True)
+    revert_opts = opts.copy()
+    revert_opts['rev'] = hex(parent)
+    revert(ui, repo, **revert_opts)
+    commit_opts = opts.copy()
+    commit_opts['addremove'] = False
+    if not commit_opts['message'] and not commit_opts['logfile']:
+        commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
+    commit(ui, repo, **commit_opts)
+    def nice(node):
+        return '%d:%s' % (repo.changelog.rev(node), short(node))
+    ui.status(_('changeset %s backs out changeset %s\n') %
+              (nice(repo.changelog.tip()), nice(node)))
+    if opts['merge'] and op1 != node:
+        ui.status(_('merging with changeset %s\n') % nice(op1))
+        update(ui, repo, hex(op1), **opts)
+
 def bundle(ui, repo, fname, dest="default-push", **opts):
     """create a changegroup file
 
@@ -1572,10 +1615,15 @@
         doexport(ui, repo, cset, seqno, total, revwidth, opts)
 
 def forget(ui, repo, *pats, **opts):
-    """don't add the specified files on the next commit
-
+    """don't add the specified files on the next commit (DEPRECATED)
+
+    (DEPRECATED)
     Undo an 'hg add' scheduled for the next commit.
+
+    This command is now deprecated and will be removed in a future
+    release. Please use revert instead.
     """
+    ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
     forget = []
     for src, abs, rel, exact in walk(repo, pats, opts):
         if repo.dirstate.state(abs) == 'a':
@@ -1792,9 +1840,7 @@
     patches = (patch1,) + patches
 
     if not opts['force']:
-        modified, added, removed, deleted, unknown = repo.changes()
-        if modified or added or removed or deleted:
-            raise util.Abort(_("outstanding uncommitted changes"))
+        bail_if_changed(repo)
 
     d = opts["base"]
     strip = opts["strip"]
@@ -2885,7 +2931,7 @@
           ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
          _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
-    'archive':
+    "archive":
         (archive,
          [('', 'no-decode', None, _('do not pass files through decoders')),
           ('p', 'prefix', '', _('directory prefix for files in archive')),
@@ -2894,6 +2940,17 @@
           ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
          _('hg archive [OPTION]... DEST')),
+    "backout":
+        (backout,
+         [('', 'merge', None,
+           _('merge with old dirstate parent after backout')),
+          ('m', 'message', '', _('use <text> as commit message')),
+          ('l', 'logfile', '', _('read commit message from <file>')),
+          ('d', 'date', '', _('record datecode as commit date')),
+          ('u', 'user', '', _('record user as committer')),
+          ('I', 'include', [], _('include names matching the given patterns')),
+          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         _('hg backout [OPTION]... REV')),
     "bundle":
         (bundle,
          [('f', 'force', None,
@@ -2973,7 +3030,7 @@
           ('a', 'text', None, _('treat all files as text')),
           ('', 'switch-parent', None, _('diff against the second parent'))],
          _('hg export [-a] [-o OUTFILESPEC] REV...')),
-    "forget":
+    "debugforget|forget":
         (forget,
          [('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
@@ -3255,11 +3312,8 @@
 
     raise UnknownCommand(cmd)
 
-class SignalInterrupt(Exception):
-    """Exception raised on SIGTERM and SIGHUP."""
-
 def catchterm(*args):
-    raise SignalInterrupt
+    raise util.SignalInterrupt
 
 def run():
     sys.exit(dispatch(sys.argv[1:]))
@@ -3311,7 +3365,7 @@
         if num: signal.signal(num, catchterm)
 
     try:
-        u = ui.ui()
+        u = ui.ui(traceback='--traceback' in sys.argv[1:])
     except util.Abort, inst:
         sys.stderr.write(_("abort: %s\n") % inst)
         return -1
@@ -3335,7 +3389,7 @@
             external.append(mod)
         except Exception, inst:
             u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
-            if "--traceback" in sys.argv[1:]:
+            if u.traceback:
                 traceback.print_exc()
                 return 1
             continue
@@ -3363,7 +3417,7 @@
             atexit.register(print_time)
 
         u.updateopts(options["verbose"], options["debug"], options["quiet"],
-                not options["noninteractive"])
+                     not options["noninteractive"], options["traceback"])
 
         # enter the debugger before command execution
         if options['debugger']:
@@ -3430,7 +3484,7 @@
             # enter the debugger when we hit an exception
             if options['debugger']:
                 pdb.post_mortem(sys.exc_info()[2])
-            if options['traceback']:
+            if u.traceback:
                 traceback.print_exc()
             raise
     except ParseError, inst:
@@ -3447,7 +3501,7 @@
         u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
         help_(u, 'shortlist')
     except hg.RepoError, inst:
-        u.warn(_("abort: "), inst, "!\n")
+        u.warn(_("abort: %s!\n") % inst)
     except lock.LockHeld, inst:
         if inst.errno == errno.ETIMEDOUT:
             reason = _('timed out waiting for lock held by %s') % inst.locker
@@ -3459,7 +3513,7 @@
                (inst.desc or inst.filename, inst.strerror))
     except revlog.RevlogError, inst:
         u.warn(_("abort: "), inst, "!\n")
-    except SignalInterrupt:
+    except util.SignalInterrupt:
         u.warn(_("killed!\n"))
     except KeyboardInterrupt:
         try:
--- a/mercurial/hgweb.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/hgweb.py	Tue May 02 14:37:55 2006 -0700
@@ -125,7 +125,7 @@
     def archivelist(self, nodeid):
         for i in self.archives:
             if self.repo.ui.configbool("web", "allow" + i, False):
-                yield {"type" : i, "node" : nodeid}
+                yield {"type" : i, "node" : nodeid, "url": ""}
 
     def listfiles(self, files, mf):
         for f in files[:self.maxfiles]:
@@ -293,7 +293,8 @@
         yield self.t('changelog',
                      changenav=changenav,
                      manifest=hex(mf),
-                     rev=pos, changesets=count, entries=changelist)
+                     rev=pos, changesets=count, entries=changelist,
+                     archives=self.archivelist("tip"))
 
     def search(self, query):
 
@@ -727,7 +728,9 @@
             yield self.t("header", **map)
 
         def footer(**map):
-            yield self.t("footer", **map)
+            yield self.t("footer",
+                         motd=self.repo.ui.config("web", "motd", ""),
+                         **map)
 
         def expand_form(form):
             shortcuts = {
@@ -1006,8 +1009,11 @@
         def cleannames(items):
             return [(name.strip(os.sep), path) for name, path in items]
 
+        self.motd = ""
+        self.repos_sorted = ('name', False)
         if isinstance(config, (list, tuple)):
             self.repos = cleannames(config)
+            self.repos_sorted = ('', False)
         elif isinstance(config, dict):
             self.repos = cleannames(config.items())
             self.repos.sort()
@@ -1015,6 +1021,8 @@
             cp = ConfigParser.SafeConfigParser()
             cp.read(config)
             self.repos = []
+            if cp.has_section('web') and cp.has_option('web', 'motd'):
+                self.motd = cp.get('web', 'motd')
             if cp.has_section('paths'):
                 self.repos.extend(cleannames(cp.items('paths')))
             if cp.has_section('collections'):
@@ -1032,14 +1040,20 @@
             yield tmpl("header", **map)
 
         def footer(**map):
-            yield tmpl("footer", **map)
+            yield tmpl("footer", motd=self.motd, **map)
 
         m = os.path.join(templater.templatepath(), "map")
         tmpl = templater.templater(m, templater.common_filters,
                                    defaults={"header": header,
                                              "footer": footer})
 
-        def entries(**map):
+        def archivelist(ui, nodeid, url):
+            for i in ['zip', 'gz', 'bz2']:
+                if ui.configbool("web", "allow" + i, False):
+                    yield {"type" : i, "node": nodeid, "url": url}
+
+        def entries(sortcolumn="", descending=False, **map):
+            rows = []
             parity = 0
             for name, path in self.repos:
                 u = ui.ui()
@@ -1058,16 +1072,37 @@
                 except OSError:
                     continue
 
-                yield dict(contact=(get("ui", "username") or # preferred
-                                    get("web", "contact") or # deprecated
-                                    get("web", "author", "unknown")), # also
-                           name=get("web", "name", name),
+                contact = (get("ui", "username") or # preferred
+                           get("web", "contact") or # deprecated
+                           get("web", "author", "")) # also
+                description = get("web", "description", "")
+                name = get("web", "name", name)
+                row = dict(contact=contact or "unknown",
+                           contact_sort=contact.upper() or "unknown",
+                           name=name,
+                           name_sort=name,
                            url=url,
-                           parity=parity,
-                           shortdesc=get("web", "description", "unknown"),
-                           lastupdate=d)
-
-                parity = 1 - parity
+                           description=description or "unknown",
+                           description_sort=description.upper() or "unknown",
+                           lastchange=d,
+                           lastchange_sort=d[1]-d[0],
+                           archives=archivelist(u, "tip", url))
+                if (not sortcolumn
+                    or (sortcolumn, descending) == self.repos_sorted):
+                    # fast path for unsorted output
+                    row['parity'] = parity
+                    parity = 1 - parity
+                    yield row
+                else:
+                    rows.append((row["%s_sort" % sortcolumn], row))
+            if rows:
+                rows.sort()
+                if descending:
+                    rows.reverse()
+                for key, row in rows:
+                    row['parity'] = parity
+                    parity = 1 - parity
+                    yield row
 
         virtual = req.env.get("PATH_INFO", "").strip('/')
         if virtual:
@@ -1088,4 +1123,20 @@
                 req.write(staticfile(static, fname)
                           or tmpl("error", error="%r not found" % fname))
             else:
-                req.write(tmpl("index", entries=entries))
+                sortable = ["name", "description", "contact", "lastchange"]
+                sortcolumn, descending = self.repos_sorted
+                if req.form.has_key('sort'):
+                    sortcolumn = req.form['sort'][0]
+                    descending = sortcolumn.startswith('-')
+                    if descending:
+                        sortcolumn = sortcolumn[1:]
+                    if sortcolumn not in sortable:
+                        sortcolumn = ""
+
+                sort = [("sort_%s" % column,
+                         "%s%s" % ((not descending and column == sortcolumn)
+                                   and "-" or "", column))
+                        for column in sortable]
+                req.write(tmpl("index", entries=entries,
+                               sortcolumn=sortcolumn, descending=descending,
+                               **dict(sort)))
--- a/mercurial/httprangereader.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/httprangereader.py	Tue May 02 14:37:55 2006 -0700
@@ -18,7 +18,11 @@
         urllib2.install_opener(opener)
         req = urllib2.Request(self.url)
         end = ''
-        if bytes: end = self.pos + bytes
+        if bytes:
+            end = self.pos + bytes - 1
         req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
         f = urllib2.urlopen(req)
-        return f.read()
+        data = f.read()
+        if bytes:
+            data = data[:bytes]
+        return data
--- a/mercurial/localrepo.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/localrepo.py	Tue May 02 14:37:55 2006 -0700
@@ -11,7 +11,8 @@
 from i18n import gettext as _
 from demandload import *
 demandload(globals(), "appendfile changegroup")
-demandload(globals(), "re lock transaction tempfile stat mdiff errno ui revlog")
+demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
+demandload(globals(), "revlog traceback")
 
 class localrepository(object):
     def __del__(self):
@@ -42,7 +43,8 @@
             pass
 
         v = self.ui.revlogopts
-        self.revlogversion = int(v.get('format', 0))
+        self.revlogversion = int(v.get('format', revlog.REVLOGV0))
+        self.revlogv1 = self.revlogversion != revlog.REVLOGV0
         flags = 0
         for x in v.get('flags', "").split():
             flags |= revlog.flagstr(x)
@@ -71,7 +73,59 @@
             os.mkdir(self.join("data"))
 
         self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
+
     def hook(self, name, throw=False, **args):
+        def callhook(hname, funcname):
+            '''call python hook. hook is callable object, looked up as
+            name in python module. if callable returns "true", hook
+            passes, else fails. if hook raises exception, treated as
+            hook failure. exception propagates if throw is "true".'''
+
+            self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
+            d = funcname.rfind('.')
+            if d == -1:
+                raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
+                                 % (hname, funcname))
+            modname = funcname[:d]
+            try:
+                obj = __import__(modname)
+            except ImportError:
+                raise util.Abort(_('%s hook is invalid '
+                                   '(import of "%s" failed)') %
+                                 (hname, modname))
+            try:
+                for p in funcname.split('.')[1:]:
+                    obj = getattr(obj, p)
+            except AttributeError, err:
+                raise util.Abort(_('%s hook is invalid '
+                                   '("%s" is not defined)') %
+                                 (hname, funcname))
+            if not callable(obj):
+                raise util.Abort(_('%s hook is invalid '
+                                   '("%s" is not callable)') %
+                                 (hname, funcname))
+            try:
+                r = obj(ui=ui, repo=repo, hooktype=name, **args)
+            except (KeyboardInterrupt, util.SignalInterrupt):
+                raise
+            except Exception, exc:
+                if isinstance(exc, util.Abort):
+                    self.ui.warn(_('error: %s hook failed: %s\n') %
+                                 (hname, exc.args[0] % exc.args[1:]))
+                else:
+                    self.ui.warn(_('error: %s hook raised an exception: '
+                                   '%s\n') % (hname, exc))
+                if throw:
+                    raise
+                if self.ui.traceback:
+                    traceback.print_exc()
+                return False
+            if not r:
+                if throw:
+                    raise util.Abort(_('%s hook failed') % hname)
+                self.ui.warn(_('error: %s hook failed\n') % hname)
+            return r
+
         def runhook(name, cmd):
             self.ui.note(_("running hook %s: %s\n") % (name, cmd))
             env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
@@ -90,7 +144,10 @@
                  if hname.split(".", 1)[0] == name and cmd]
         hooks.sort()
         for hname, cmd in hooks:
-            r = runhook(hname, cmd) and r
+            if cmd.startswith('python:'):
+                r = callhook(hname, cmd[7:].strip()) and r
+            else:
+                r = runhook(hname, cmd) and r
         return r
 
     def tags(self):
@@ -1320,7 +1377,8 @@
             # Signal that no more groups are left.
             yield changegroup.closechunk()
 
-            self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
+            if msng_cl_lst:
+                self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
 
         return util.chunkbuffer(gengroup())
 
@@ -1766,7 +1824,7 @@
 
         def temp(prefix, node):
             pre = "%s~%s." % (os.path.basename(fn), prefix)
-            (fd, name) = tempfile.mkstemp("", pre)
+            (fd, name) = tempfile.mkstemp(prefix=pre)
             f = os.fdopen(fd, "wb")
             self.wwrite(fn, fl.read(node), f)
             f.close()
@@ -1803,12 +1861,17 @@
         filenodes = {}
         changesets = revisions = files = 0
         errors = [0]
+        warnings = [0]
         neededmanifests = {}
 
         def err(msg):
             self.ui.warn(msg + "\n")
             errors[0] += 1
 
+        def warn(msg):
+            self.ui.warn(msg + "\n")
+            warnings[0] += 1
+
         def checksize(obj, name):
             d = obj.checksize()
             if d[0]:
@@ -1816,6 +1879,18 @@
             if d[1]:
                 err(_("%s index contains %d extra bytes") % (name, d[1]))
 
+        def checkversion(obj, name):
+            if obj.version != revlog.REVLOGV0:
+                if not revlogv1:
+                    warn(_("warning: `%s' uses revlog format 1") % name)
+            elif revlogv1:
+                warn(_("warning: `%s' uses revlog format 0") % name)
+
+        revlogv1 = self.revlogversion != revlog.REVLOGV0
+        if self.ui.verbose or revlogv1 != self.revlogv1:
+            self.ui.status(_("repository uses revlog format %d\n") %
+                           (revlogv1 and 1 or 0))
+
         seen = {}
         self.ui.status(_("checking changesets\n"))
         checksize(self.changelog, "changelog")
@@ -1850,6 +1925,7 @@
 
         seen = {}
         self.ui.status(_("checking manifests\n"))
+        checkversion(self.manifest, "manifest")
         checksize(self.manifest, "manifest")
 
         for i in range(self.manifest.count()):
@@ -1914,6 +1990,7 @@
                 err(_("file without name in manifest %s") % short(n))
                 continue
             fl = self.file(f)
+            checkversion(fl, f)
             checksize(fl, f)
 
             nodes = {nullid: 1}
@@ -1962,6 +2039,8 @@
         self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
                        (files, changesets, revisions))
 
+        if warnings[0]:
+            self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
         if errors[0]:
             self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
             return 1
--- a/mercurial/manifest.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/manifest.py	Tue May 02 14:37:55 2006 -0700
@@ -12,7 +12,7 @@
 demandload(globals(), "bisect array")
 
 class manifest(revlog):
-    def __init__(self, opener, defversion=0):
+    def __init__(self, opener, defversion=REVLOGV0):
         self.mapcache = None
         self.listcache = None
         revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
--- a/mercurial/revlog.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/revlog.py	Tue May 02 14:37:55 2006 -0700
@@ -293,7 +293,7 @@
     remove data, and can use some simple techniques to avoid the need
     for locking while reading.
     """
-    def __init__(self, opener, indexfile, datafile, defversion=0):
+    def __init__(self, opener, indexfile, datafile, defversion=REVLOGV0):
         """
         create a revlog object
 
@@ -333,11 +333,11 @@
                     and st.st_ctime == oldst.st_ctime):
                     return
                 self.indexstat = st
-                if len(i) > 0:
-                    v = struct.unpack(versionformat, i)[0]
+            if len(i) > 0:
+                v = struct.unpack(versionformat, i)[0]
         flags = v & ~0xFFFF
         fmt = v & 0xFFFF
-        if fmt == 0:
+        if fmt == REVLOGV0:
             if flags:
                 raise RevlogError(_("index %s invalid flags %x for format v0" %
                                    (self.indexfile, flags)))
@@ -349,7 +349,7 @@
             raise RevlogError(_("index %s invalid format %d" %
                                (self.indexfile, fmt)))
         self.version = v
-        if v == 0:
+        if v == REVLOGV0:
             self.indexformat = indexformatv0
             shaoffset = v0shaoffset
         else:
@@ -369,7 +369,7 @@
                 # we've already got the entire data file read in, save it
                 # in the chunk data
                 self.chunkcache = (0, i)
-            if self.version != 0:
+            if self.version != REVLOGV0:
                 e = list(self.index[0])
                 type = self.ngtype(e[0])
                 e[0] = self.offset_type(0, type)
@@ -399,7 +399,7 @@
     def ngoffset(self, q):
         if q & 0xFFFF:
             raise RevlogError(_('%s: incompatible revision flag %x') %
-                               (self.indexfile, type))
+                              (self.indexfile, q))
         return long(q >> 16)
 
     def ngtype(self, q):
@@ -441,13 +441,13 @@
         if node == nullid: return (nullid, nullid)
         r = self.rev(node)
         d = self.index[r][-3:-1]
-        if self.version == 0:
+        if self.version == REVLOGV0:
             return d
         return [ self.node(x) for x in d ]
     def start(self, rev):
         if rev < 0:
             return -1
-        if self.version != 0:
+        if self.version != REVLOGV0:
             return self.ngoffset(self.index[rev][0])
         return self.index[rev][0]
 
@@ -456,7 +456,7 @@
     def size(self, rev):
         """return the length of the uncompressed text for a given revision"""
         l = -1
-        if self.version != 0:
+        if self.version != REVLOGV0:
             l = self.index[rev][2]
         if l >= 0:
             return l
@@ -911,7 +911,7 @@
         if t >= 0:
             offset = self.end(t)
 
-        if self.version == 0:
+        if self.version == REVLOGV0:
             e = (offset, l, base, link, p1, p2, node)
         else:
             e = (self.offset_type(offset, 0), l, len(text),
@@ -935,7 +935,7 @@
             f.seek(0, 2)
             transaction.add(self.indexfile, f.tell(), self.count() - 1)
 
-        if len(self.index) == 1 and self.version != 0:
+        if len(self.index) == 1 and self.version != REVLOGV0:
             l = struct.pack(versionformat, self.version)
             f.write(l)
             entry = entry[4:]
@@ -1135,7 +1135,7 @@
                     raise RevlogError(_("consistency error adding group"))
                 textlen = len(text)
             else:
-                if self.version == 0:
+                if self.version == REVLOGV0:
                     e = (end, len(cdelta), base, link, p1, p2, node)
                 else:
                     e = (self.offset_type(end, 0), len(cdelta), textlen, base,
--- a/mercurial/sshrepo.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/sshrepo.py	Tue May 02 14:37:55 2006 -0700
@@ -9,7 +9,7 @@
 from remoterepo import *
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "hg os re stat")
+demandload(globals(), "hg os re stat util")
 
 class sshrepository(remoterepository):
     def __init__(self, ui, path):
--- a/mercurial/ui.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/ui.py	Tue May 02 14:37:55 2006 -0700
@@ -12,7 +12,7 @@
 
 class ui(object):
     def __init__(self, verbose=False, debug=False, quiet=False,
-                 interactive=True, parentui=None):
+                 interactive=True, traceback=False, parentui=None):
         self.overlay = {}
         if parentui is None:
             # this is the parent of all ui children
@@ -24,6 +24,7 @@
             self.verbose = self.configbool("ui", "verbose")
             self.debugflag = self.configbool("ui", "debug")
             self.interactive = self.configbool("ui", "interactive", True)
+            self.traceback = traceback
 
             self.updateopts(verbose, debug, quiet, interactive)
             self.diffcache = None
@@ -45,11 +46,12 @@
         return getattr(self.parentui, key)
 
     def updateopts(self, verbose=False, debug=False, quiet=False,
-                 interactive=True):
+                   interactive=True, traceback=False):
         self.quiet = (self.quiet or quiet) and not verbose and not debug
         self.verbose = (self.verbose or verbose) or debug
         self.debugflag = (self.debugflag or debug)
         self.interactive = (self.interactive and interactive)
+        self.traceback = self.traceback or traceback
 
     def readconfig(self, fn, root=None):
         if isinstance(fn, basestring):
--- a/mercurial/util.py	Tue May 02 14:30:00 2006 -0700
+++ b/mercurial/util.py	Tue May 02 14:37:55 2006 -0700
@@ -16,6 +16,9 @@
 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
 demandload(globals(), "threading time")
 
+class SignalInterrupt(Exception):
+    """Exception raised on SIGTERM and SIGHUP."""
+
 def pipefilter(s, cmd):
     '''filter string S through command CMD, returning its output'''
     (pout, pin) = popen2.popen2(cmd, -1, 'b')
@@ -43,11 +46,11 @@
     the temporary files generated.'''
     inname, outname = None, None
     try:
-        infd, inname = tempfile.mkstemp(prefix='hgfin')
+        infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
         fp = os.fdopen(infd, 'wb')
         fp.write(s)
         fp.close()
-        outfd, outname = tempfile.mkstemp(prefix='hgfout')
+        outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
         os.close(outfd)
         cmd = cmd.replace('INFILE', inname)
         cmd = cmd.replace('OUTFILE', outname)
@@ -676,7 +679,7 @@
 
     def mktempcopy(name):
         d, fn = os.path.split(name)
-        fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
+        fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
         os.close(fd)
         fp = posixfile(temp, "wb")
         try:
--- a/templates/changelog.tmpl	Tue May 02 14:30:00 2006 -0700
+++ b/templates/changelog.tmpl	Tue May 02 14:37:55 2006 -0700
@@ -8,6 +8,7 @@
 <div class="buttons">
 <a href="?cmd=tags">tags</a>
 <a href="?mf=#manifest|short#;path=/">manifest</a>
+#archives%archiveentry#
 <a type="application/rss+xml" href="?style=rss">rss</a>
 </div>
 
--- a/templates/changelogentry.tmpl	Tue May 02 14:30:00 2006 -0700
+++ b/templates/changelogentry.tmpl	Tue May 02 14:37:55 2006 -0700
@@ -1,11 +1,11 @@
-<table class="changelogEntry parity#parity#">
+<table class="logEntry parity#parity#">
  <tr>
-  <th class="age" width="15%">#date|age# ago:</th>
+  <th class="age">#date|age# ago:</th>
   <th class="firstline">#desc|strip|firstline|escape#</th>
  </tr>
  <tr>
-  <th class="changesetRev">changeset #rev#:</th>
-  <td class="changesetNode"><a href="?cs=#node|short#">#node|short#</a></td>
+  <th class="revision">changeset #rev#:</th>
+  <td class="node"><a href="?cs=#node|short#">#node|short#</a></td>
  </tr>
  #parent%changelogparent#
  #child%changelogchild#
--- a/templates/filelogentry.tmpl	Tue May 02 14:30:00 2006 -0700
+++ b/templates/filelogentry.tmpl	Tue May 02 14:37:55 2006 -0700
@@ -1,20 +1,25 @@
-<table class="parity#parity#" width="100%" cellspacing="0" cellpadding="0">
-<tr>
- <td align="right" width="15%"><b>#date|age# ago:&nbsp;</b></td>
- <td><b><a href="?cs=#node|short#">#desc|strip|firstline|escape#</a></b></td></tr>
-<tr>
- <td align="right">revision #filerev#:&nbsp;</td>
- <td><a href="?f=#filenode|short#;file=#file|urlescape#">#filenode|short#</a>
-<a href="?fd=#node|short#;file=#file|urlescape#">(diff)</a>
-<a href="?fa=#filenode|short#;file=#file|urlescape#">(annotate)</a>
-</td></tr>
-#rename%filelogrename#
-<tr>
- <td align="right">author:&nbsp;</td>
- <td>#author|obfuscate#</td></tr>
-<tr>
- <td align="right">date:&nbsp;</td>
- <td>#date|date# (#date|age# ago)</td></tr>
+<table class="logEntry parity#parity#">
+ <tr>
+  <th class="age">#date|age# ago:</th>
+  <th class="firstline"><a href="?cs=#node|short#">#desc|strip|firstline|escape#</a></th>
+ </tr>
+ <tr>
+  <th class="revision">revision #filerev#:</td>
+  <td class="node">
+   <a href="?f=#filenode|short#;file=#file|urlescape#">#filenode|short#</a>
+   <a href="?fd=#node|short#;file=#file|urlescape#">(diff)</a>
+   <a href="?fa=#filenode|short#;file=#file|urlescape#">(annotate)</a>
+  </td>
+ </tr>
+ #rename%filelogrename#
+ <tr>
+  <th class="author">author:</th>
+  <td class="author">#author|obfuscate#</td>
+ </tr>
+ <tr>
+  <th class="date">date:</th>
+  <td class="date">#date|date#</td>
+ </tr>
 </table>
 
 
--- a/templates/footer.tmpl	Tue May 02 14:30:00 2006 -0700
+++ b/templates/footer.tmpl	Tue May 02 14:37:55 2006 -0700
@@ -1,3 +1,4 @@
+#motd#
 <div class="logo">
 powered by<br/>
 <a href="http://www.selenic.com/mercurial/">mercurial</a>
--- a/templates/index.tmpl	Tue May 02 14:30:00 2006 -0700
+++ b/templates/index.tmpl	Tue May 02 14:37:55 2006 -0700
@@ -7,10 +7,10 @@
 
 <table>
     <tr>
-        <td>Name</td>
-        <td>Description</td>
-        <td>Contact</td>
-        <td>Last change</td>
+        <td><a href="?sort=#sort_name#">Name</a></td>
+        <td><a href="?sort=#sort_description#">Description</a></td>
+        <td><a href="?sort=#sort_contact#">Contact</a></td>
+        <td><a href="?sort=#sort_lastchange#">Last change</a></td>
         <td>&nbsp;</td>
     <tr>
     #entries%indexentry#
--- a/templates/map	Tue May 02 14:30:00 2006 -0700
+++ b/templates/map	Tue May 02 14:37:55 2006 -0700
@@ -28,23 +28,23 @@
 changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
 filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
 filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
-filelogrename = '<tr><td align="right">base:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
+filelogrename = '<tr><th>base:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>'
 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
 changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
 changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
 filerevchild = '<tr><td class="metatag">child:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
 tags = tags.tmpl
-tagentry = '<li class="tagEntry parity#parity#"><span class="node">#node#</span> <a href="?cs=#node|short#">#tag|escape#</a></li>'
+tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="?cs=#node|short#">#tag|escape#</a></li>'
 diffblock = '<pre class="parity#parity#">#lines#</pre>'
 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
 changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
 filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
-filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+filelogparent = '<tr><th>parent #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>'
-filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
-indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#shortdesc#</td><td>#contact|obfuscate#</td><td>#lastupdate|age# ago</td><td><a href="#url#?cl=tip;style=rss">RSS</a></td></tr>'
+filelogchild = '<tr><th>child #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>'
+indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#?cl=tip;style=rss">RSS</a> #archives%archiveentry#</td></tr>'
 index = index.tmpl
-archiveentry = '<a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
+archiveentry = '<a href="#url#?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
 notfound = notfound.tmpl
 error = error.tmpl
--- a/templates/map-gitweb	Tue May 02 14:30:00 2006 -0700
+++ b/templates/map-gitweb	Tue May 02 14:37:55 2006 -0700
@@ -36,7 +36,7 @@
 filerevchild = '<tr><td class="metatag">child:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
 tags = tags-gitweb.tmpl
-tagentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> |  <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> |  <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
 diffblock = '#lines#'
 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
 changesettag = '<tr><td>tag</td><td>#tag|escape#</td></tr>'
@@ -45,6 +45,6 @@
 filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
 shortlog = shortlog-gitweb.tmpl
-shortlogentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
-filelogentry = '<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
+shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
+filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
 archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
--- a/templates/static/style-gitweb.css	Tue May 02 14:30:00 2006 -0700
+++ b/templates/static/style-gitweb.css	Tue May 02 14:37:55 2006 -0700
@@ -17,6 +17,7 @@
 a.title:hover { background-color: #d9d8d1; }
 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
 div.log_body { padding:8px 8px 8px 150px; }
+.age { white-space:nowrap; }
 span.age { position:relative; float:left; width:142px; font-style:italic; }
 div.log_link {
 	padding:0px 8px;
--- a/templates/static/style.css	Tue May 02 14:30:00 2006 -0700
+++ b/templates/static/style.css	Tue May 02 14:37:55 2006 -0700
@@ -1,4 +1,6 @@
 a { text-decoration:none; }
+.age { white-space:nowrap; }
+.indexlinks { white-space:nowrap; }
 .parity0 { background-color: #dddddd; }
 .parity1 { background-color: #eeeeee; }
 .lineno { width: 60px; color: #aaaaaa; font-size: smaller; 
@@ -48,16 +50,16 @@
   color: #999;
 }
 
-/* Changelog entries */
-.changelogEntry { width: 100%; }
-.changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; }
-.changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
-.changelogEntry th.firstline { text-align: left; width: inherit; }
+/* Changelog/Filelog entries */
+.logEntry { width: 100%; }
+.logEntry .age { width: 15%; }
+.logEntry th { font-weight: normal; text-align: right; vertical-align: top; }
+.logEntry th.age, .logEntry th.firstline { font-weight: bold; }
+.logEntry th.firstline { text-align: left; width: inherit; }
 
 /* Tag entries */
 #tagEntries { list-style: none; margin: 0; padding: 0; }
 #tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
-#tagEntries .tagEntry span.node { font-family: monospace; }
 
 /* Changeset entry */
 #changesetEntry { }
--- a/tests/coverage.py	Tue May 02 14:30:00 2006 -0700
+++ b/tests/coverage.py	Tue May 02 14:37:55 2006 -0700
@@ -350,6 +350,7 @@
         show_missing = settings.get('show-missing')
         directory = settings.get('directory')
         omit = filter(None, settings.get('omit', '').split(','))
+        omit += ['/<'] # Always skip /<string> etc.
 
         if settings.get('report'):
             self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
--- a/tests/run-tests	Tue May 02 14:30:00 2006 -0700
+++ b/tests/run-tests	Tue May 02 14:37:55 2006 -0700
@@ -64,7 +64,7 @@
     exit 1
 }
 
-TESTDIR="$PWD"
+TESTDIR="`pwd`"
 export TESTDIR
 INST="$HGTMP/install"
 PYTHONDIR="$INST/lib/python"
--- a/tests/run-tests.py	Tue May 02 14:30:00 2006 -0700
+++ b/tests/run-tests.py	Tue May 02 14:37:55 2006 -0700
@@ -15,11 +15,18 @@
 
 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
 
-parser = OptionParser()
-parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
-    default=False, help="output verbose messages")
+parser = OptionParser("%prog [options] [tests]")
+parser.add_option("-v", "--verbose", action="store_true",
+    help="output verbose messages")
+parser.add_option("-c", "--cover", action="store_true",
+    help="print a test coverage report")
+parser.add_option("-s", "--cover_stdlib", action="store_true",
+    help="print a test coverage report inc. standard libraries")
+parser.add_option("-C", "--annotate", action="store_true",
+    help="output files annotated with coverage")
 (options, args) = parser.parse_args()
 verbose = options.verbose
+coverage = options.cover or options.cover_stdlib or options.annotate
 
 def vlog(*msg):
     if verbose:
@@ -40,66 +47,82 @@
             return name
     return None
 
-# Before we go any further, check for pre-requisite tools
-# stuff from coreutils (cat, rm, etc) are not tested
-for p in required_tools:
-    if os.name == 'nt':
-        p += '.exe'
-    found = find_program(p)
-    if found:
-        vlog("# Found prerequisite", p, "at", found)
-    else:
-        print "WARNING: Did not find prerequisite tool: "+p
-
-# Reset some environment variables to well-known values
-os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
-os.environ['TZ'] = 'GMT'
-
-os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
-os.environ["HGMERGE"]  = sys.executable + ' -c "import sys; sys.exit(0)"'
-os.environ["HGUSER"]   = "test"
-os.environ["HGRCPATH"] = ""
-
-TESTDIR = os.environ["TESTDIR"] = os.getcwd()
-HGTMP   = os.environ["HGTMP"]   = tempfile.mkdtemp("", "hgtests.")
+def check_required_tools():
+    # Before we go any further, check for pre-requisite tools
+    # stuff from coreutils (cat, rm, etc) are not tested
+    for p in required_tools:
+        if os.name == 'nt':
+            p += '.exe'
+        found = find_program(p)
+        if found:
+            vlog("# Found prerequisite", p, "at", found)
+        else:
+            print "WARNING: Did not find prerequisite tool: "+p
 
 def cleanup_exit():
     if verbose:
         print "# Cleaning up HGTMP", HGTMP
     shutil.rmtree(HGTMP, True)
 
-vlog("# Using TESTDIR", TESTDIR)
-vlog("# Using HGTMP", HGTMP)
-
-os.umask(022)
+def install_hg():
+    vlog("# Performing temporary installation of HG")
+    installerrs = os.path.join("tests", "install.err")
 
-vlog("# Performing temporary installation of HG")
-INST = os.path.join(HGTMP, "install")
-BINDIR = os.path.join(INST, "bin")
-PYTHONDIR = os.path.join(INST, "lib", "python")
-installerrs = os.path.join("tests", "install.err")
+    os.chdir("..") # Get back to hg root
+    cmd = '%s setup.py install --home="%s" --install-lib="%s" >%s 2>&1' % \
+        (sys.executable, INST, PYTHONDIR, installerrs)
+    vlog("# Running", cmd)
+    if os.system(cmd) == 0:
+        if not verbose:
+            os.remove(installerrs)
+    else:
+        f = open(installerrs)
+        for line in f:
+            print line,
+        f.close()
+        sys.exit(1)
+    os.chdir(TESTDIR)
+
+    os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
+    os.environ["PYTHONPATH"] = PYTHONDIR
 
-os.chdir("..") # Get back to hg root
-cmd = '%s setup.py install --home="%s" --install-lib="%s" >%s 2>&1' % \
-    (sys.executable, INST, PYTHONDIR, installerrs)
-vlog("# Running", cmd)
-if os.system(cmd) == 0:
-    if not verbose:
-        os.remove(installerrs)
-else:
-    f = open(installerrs)
-    for line in f:
-        print line,
-    f.close()
-    cleanup_exit()
-    sys.exit(1)
-os.chdir(TESTDIR)
+    if coverage:
+        vlog("# Installing coverage wrapper")
+        os.environ['COVERAGE_FILE'] = COVERAGE_FILE
+        if os.path.exists(COVERAGE_FILE):
+            os.unlink(COVERAGE_FILE)
+        # Create a wrapper script to invoke hg via coverage.py
+        os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
+        f = open(os.path.join(BINDIR, 'hg'), 'w')
+        f.write('#!' + sys.executable + '\n')
+        f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
+            '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
+            os.path.join(TESTDIR, 'coverage.py'),
+            os.path.join(BINDIR, '_hg.py')))
+        f.close()
+        os.chmod(os.path.join(BINDIR, 'hg'), 0700)
 
-os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
-os.environ["PYTHONPATH"] = PYTHONDIR
-
-tests = 0
-failed = 0
+def output_coverage():
+    vlog("# Producing coverage report")
+    omit = [BINDIR, TESTDIR, PYTHONDIR]
+    if not options.cover_stdlib:
+        # Exclude as system paths (ignoring empty strings seen on win)
+        omit += [x for x in sys.path if x != '']
+    omit = ','.join(omit)
+    os.chdir(PYTHONDIR)
+    cmd = '"%s" "%s" -r "--omit=%s"' % (
+        sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
+    vlog("# Running: "+cmd)
+    os.system(cmd)
+    if options.annotate:
+        adir = os.path.join(TESTDIR, 'annotated')
+        if not os.path.isdir(adir):
+            os.mkdir(adir)
+        cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
+            sys.executable, os.path.join(TESTDIR, 'coverage.py'),
+            adir, omit)
+        vlog("# Running: "+cmd)
+        os.system(cmd)
 
 def run(cmd, split_lines=True):
     """Run command in a sub-process, capturing the output (stdout and stderr).
@@ -176,17 +199,52 @@
     shutil.rmtree(tmpd, True)
     return ret == 0
 
-for test in os.listdir("."):
-    if test.startswith("test-"):
-        if '~' in test or re.search(r'\.(out|err)$', test):
-            continue
-        if not run_one(test):
-            failed += 1
-        tests += 1
+
+os.umask(022)
+
+check_required_tools()
+
+# Reset some environment variables to well-known values so that
+# the tests produce repeatable output.
+os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
+os.environ['TZ'] = 'GMT'
+
+os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
+os.environ["HGMERGE"]  = sys.executable + ' -c "import sys; sys.exit(0)"'
+os.environ["HGUSER"]   = "test"
+os.environ["HGRCPATH"] = ""
+
+TESTDIR = os.environ["TESTDIR"] = os.getcwd()
+HGTMP   = os.environ["HGTMP"]   = tempfile.mkdtemp("", "hgtests.")
+vlog("# Using TESTDIR", TESTDIR)
+vlog("# Using HGTMP", HGTMP)
 
-print "# Ran %d tests, %d failed." % (tests, failed)
+INST = os.path.join(HGTMP, "install")
+BINDIR = os.path.join(INST, "bin")
+PYTHONDIR = os.path.join(INST, "lib", "python")
+COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
+
+try:
+    install_hg()
+
+    tests = 0
+    failed = 0
 
-cleanup_exit()
+    if len(args) == 0:
+        args = os.listdir(".")
+    for test in args:
+        if test.startswith("test-"):
+            if '~' in test or re.search(r'\.(out|err)$', test):
+                continue
+            if not run_one(test):
+                failed += 1
+            tests += 1
+
+    print "\n# Ran %d tests, %d failed." % (tests, failed)
+    if coverage:
+        output_coverage()
+finally:
+    cleanup_exit()
 
 if failed:
     sys.exit(1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-backout	Tue May 02 14:37:55 2006 -0700
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+echo '# basic operation'
+hg init basic
+cd basic
+echo a > a
+hg commit -d '0 0' -A -m a
+echo b >> a
+hg commit -d '1 0' -m b
+
+hg backout -d '2 0' tip
+cat a
+
+echo '# file that was removed is recreated'
+cd ..
+hg init remove
+cd remove
+
+echo content > a
+hg commit -d '0 0' -A -m a
+
+hg rm a
+hg commit -d '1 0' -m b
+
+hg backout -d '2 0' --merge tip
+cat a
+
+echo '# backout of backout is as if nothing happened'
+
+hg backout -d '3 0' --merge tip
+cat a
+
+echo '# backout with merge'
+cd ..
+hg init merge
+cd merge
+
+echo line 1 > a
+hg commit -d '0 0' -A -m a
+
+echo line 2 >> a
+hg commit -d '1 0' -m b
+
+echo line 3 >> a
+hg commit -d '2 0' -m c
+
+hg backout --merge -d '3 0' 1
+hg commit -d '4 0' -m d
+cat a
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-backout.out	Tue May 02 14:37:55 2006 -0700
@@ -0,0 +1,19 @@
+# basic operation
+adding a
+changeset 2:b38a34ddfd9f backs out changeset 1:a820f4f40a57
+a
+# file that was removed is recreated
+adding a
+adding a
+changeset 2:44cd84c7349a backs out changeset 1:76862dcce372
+content
+# backout of backout is as if nothing happened
+removing a
+changeset 3:0dd8a0ed5e99 backs out changeset 2:44cd84c7349a
+cat: a: No such file or directory
+# backout with merge
+adding a
+changeset 3:6c77ecc28460 backs out changeset 1:314f55b1bf23
+merging with changeset 2:b66ea5b77abb
+merging a
+line 1
--- a/tests/test-help.out	Tue May 02 14:30:00 2006 -0700
+++ b/tests/test-help.out	Tue May 02 14:37:55 2006 -0700
@@ -42,6 +42,7 @@
  addremove   add all new files, delete all missing files
  annotate    show changeset information per file line
  archive     create unversioned archive of a repository revision
+ backout     reverse effect of earlier changeset
  bundle      create a changegroup file
  cat         output the latest or given revisions of files
  clone       make a copy of an existing repository
@@ -49,7 +50,6 @@
  copy        mark files as copied for the next commit
  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
  heads       show current repository heads
  help        show help for a given command or all commands
@@ -85,6 +85,7 @@
  addremove   add all new files, delete all missing files
  annotate    show changeset information per file line
  archive     create unversioned archive of a repository revision
+ backout     reverse effect of earlier changeset
  bundle      create a changegroup file
  cat         output the latest or given revisions of files
  clone       make a copy of an existing repository
@@ -92,7 +93,6 @@
  copy        mark files as copied for the next commit
  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
  heads       show current repository heads
  help        show help for a given command or all commands
--- a/tests/test-hook	Tue May 02 14:30:00 2006 -0700
+++ b/tests/test-hook	Tue May 02 14:37:55 2006 -0700
@@ -87,4 +87,93 @@
 echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc
 hg pull ../a
 
+cat > hooktests.py <<EOF
+from mercurial import util
+
+uncallable = 0
+
+def printargs(args):
+    args.pop('ui', None)
+    args.pop('repo', None)
+    a = list(args.items())
+    a.sort()
+    print 'hook args:'
+    for k, v in a:
+       print ' ', k, v
+    return True
+
+def passhook(**args):
+    printargs(args)
+    return True
+
+def failhook(**args):
+    printargs(args)
+
+class LocalException(Exception):
+    pass
+
+def raisehook(**args):
+    raise LocalException('exception from hook')
+
+def aborthook(**args):
+    raise util.Abort('raise abort from hook')
+
+def brokenhook(**args):
+    return 1 + {}
+
+class container:
+    unreachable = 1
+EOF
+
+echo '# test python hooks'
+PYTHONPATH="`pwd`:$PYTHONPATH"
+export PYTHONPATH
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
+hg pull ../a 2>&1 | grep 'raised an exception'
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
+hg pull ../a 2>&1 | grep 'raised an exception'
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
+hg pull ../a
+
+echo '# make sure --traceback works'
+echo '[hooks]' > .hg/hgrc
+echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
+
+echo a >> a
+hg --traceback commit -A -m a 2>&1 | grep '^Traceback'
+
 exit 0
--- a/tests/test-hook.out	Tue May 02 14:30:00 2006 -0700
+++ b/tests/test-hook.out	Tue May 02 14:37:55 2006 -0700
@@ -89,3 +89,43 @@
 pulling from ../a
 searching for changes
 abort: preoutgoing.forbid hook exited with status 1
+# test python hooks
+error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
+error: preoutgoing.raise hook raised an exception: exception from hook
+pulling from ../a
+searching for changes
+error: preoutgoing.abort hook failed: raise abort from hook
+abort: raise abort from hook
+pulling from ../a
+searching for changes
+hook args:
+  hooktype preoutgoing
+  source pull
+abort: preoutgoing.fail hook failed
+pulling from ../a
+searching for changes
+abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
+pulling from ../a
+searching for changes
+abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
+pulling from ../a
+searching for changes
+abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
+pulling from ../a
+searching for changes
+abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
+pulling from ../a
+searching for changes
+abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
+pulling from ../a
+searching for changes
+hook args:
+  hooktype preoutgoing
+  source pull
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+(run 'hg update' to get a working copy)
+# make sure --traceback works
+Traceback (most recent call last):
--- a/tests/test-nested-repo	Tue May 02 14:30:00 2006 -0700
+++ b/tests/test-nested-repo	Tue May 02 14:37:55 2006 -0700
@@ -14,6 +14,6 @@
 echo '# should print A b/x'
 hg st
 echo '# should forget b/x'
-hg forget
+hg revert
 echo '# should print nothing'
 hg st b
--- a/tests/test-revert	Tue May 02 14:30:00 2006 -0700
+++ b/tests/test-revert	Tue May 02 14:37:55 2006 -0700
@@ -54,4 +54,18 @@
 hg revert -r0 a
 hg st a
 
+hg update -C
+chmod +x c
+hg revert
+echo %% should print non-executable
+test -x c || echo non-executable
+
+chmod +x c
+hg commit -d '1000001 0' -m exe
+
+chmod -x c
+hg revert
+echo %% should print executable
+test -x c && echo executable
+
 true
--- a/tests/test-revert.out	Tue May 02 14:30:00 2006 -0700
+++ b/tests/test-revert.out	Tue May 02 14:37:55 2006 -0700
@@ -45,3 +45,9 @@
 forgetting a
 %% should silently add a
 A a
+reverting c
+%% should print non-executable
+non-executable
+reverting c
+%% should print executable
+executable