Merge with crew-stable
authorPatrick Mezard <pmezard@gmail.com>
Sun, 02 Dec 2007 19:45:38 +0100
changeset 5585 b34028d52e7e
parent 5583 1b5b81d9039b (diff)
parent 5584 d2831a5d5947 (current diff)
child 5586 b90b72729a72
Merge with crew-stable
mercurial/hgweb/hgwebdir_mod.py
--- a/CONTRIBUTORS	Sun Dec 02 19:39:27 2007 +0100
+++ b/CONTRIBUTORS	Sun Dec 02 19:45:38 2007 +0100
@@ -1,4 +1,7 @@
-Andrea Arcangeli <andrea at suse.de>
+[This file is here for historical purposes, all recent contributors
+should appear in the changelog directly]
+
+Andrea Arcangeli <andrea at suse.de>
 Thomas Arendsen Hein <thomas at intevation.de>
 Goffredo Baroncelli <kreijack at libero.it>
 Muli Ben-Yehuda <mulix at mulix.org>
@@ -36,5 +39,3 @@
 Rafael Villar Burke <pachi at mmn-arquitectos.com>
 Tristan Wibberley <tristan at wibberley.org>
 Mark Williamson <mark.williamson at cl.cam.ac.uk>
-
-If you are a contributor and don't see your name here, please let me know.
--- a/contrib/bash_completion	Sun Dec 02 19:39:27 2007 +0100
+++ b/contrib/bash_completion	Sun Dec 02 19:45:38 2007 +0100
@@ -305,6 +305,15 @@
     _hg_ext_mq_patchlist qunapplied
 }
 
+_hg_cmd_qgoto()
+{
+    if [[ "$prev" = @(-n|--name) ]]; then
+	_hg_ext_mq_queues
+	return
+    fi
+    _hg_ext_mq_patchlist qseries
+}
+
 _hg_cmd_qdelete()
 {
     local qcmd=qunapplied
--- a/contrib/hgk	Sun Dec 02 19:39:27 2007 +0100
+++ b/contrib/hgk	Sun Dec 02 19:45:38 2007 +0100
@@ -649,7 +649,7 @@
     if {$stuffsaved} return
     if {![winfo viewable .]} return
     catch {
-	set f [open "~/.gitk-new" w]
+	set f [open "~/.hgk-new" w]
 	puts $f [list set mainfont $mainfont]
 	puts $f [list set curidfont $curidfont]
 	puts $f [list set textfont $textfont]
@@ -687,7 +687,7 @@
 	puts $f "#"
 	puts $f "set authorcolors {$authorcolors}"
 	close $f
-	file rename -force "~/.gitk-new" "~/.gitk"
+	file rename -force "~/.hgk-new" "~/.hgk"
     }
     set stuffsaved 1
 }
@@ -3847,7 +3847,7 @@
     deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
 }
 
-catch {source ~/.gitk}
+catch {source ~/.hgk}
 
 if {$curidfont == ""} {  # initialize late based on current mainfont
     set curidfont "$mainfont bold italic underline"
--- a/doc/hgrc.5.txt	Sun Dec 02 19:39:27 2007 +0100
+++ b/doc/hgrc.5.txt	Sun Dec 02 19:45:38 2007 +0100
@@ -17,7 +17,9 @@
 
 Mercurial reads configuration data from several files, if they exist.
 The names of these files depend on the system on which Mercurial is
-installed.
+installed. Windows registry keys contain PATH-like strings, every
+part must reference a Mercurial.ini file or be a directory where *.rc
+files will be read.
 
 (Unix)    <install-root>/etc/mercurial/hgrc.d/*.rc::
 (Unix)    <install-root>/etc/mercurial/hgrc::
@@ -29,6 +31,8 @@
 
 (Unix)    /etc/mercurial/hgrc.d/*.rc::
 (Unix)    /etc/mercurial/hgrc::
+(Windows) HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial::
+  or::
 (Windows) C:\Mercurial\Mercurial.ini::
     Per-system configuration files, for the system on which Mercurial
     is running.  Options in these files apply to all Mercurial
--- a/hg	Sun Dec 02 19:39:27 2007 +0100
+++ b/hg	Sun Dec 02 19:45:38 2007 +0100
@@ -10,5 +10,11 @@
 # enable importing on demand to reduce startup time
 from mercurial import demandimport; demandimport.enable()
 
+import sys
+import mercurial.util
 import mercurial.dispatch
+
+for fp in (sys.stdin, sys.stdout, sys.stderr):
+    mercurial.util.set_binary(fp)
+
 mercurial.dispatch.run()
--- a/hgext/convert/__init__.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/__init__.py	Sun Dec 02 19:45:38 2007 +0100
@@ -5,12 +5,12 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from common import NoRepo, SKIPREV, converter_source, converter_sink
+from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
 from cvs import convert_cvs
 from darcs import darcs_source
 from git import convert_git
 from hg import mercurial_source, mercurial_sink
-from subversion import svn_source, debugsvnlog
+from subversion import debugsvnlog, svn_source, svn_sink
 import filemap
 
 import os, shutil
@@ -29,6 +29,7 @@
 
 sink_converters = [
     ('hg', mercurial_sink),
+    ('svn', svn_sink),
     ]
 
 def convertsource(ui, path, type, rev):
@@ -61,23 +62,10 @@
         self.ui = ui
         self.opts = opts
         self.commitcache = {}
-        self.revmapfile = revmapfile
-        self.revmapfilefd = None
         self.authors = {}
         self.authorfile = None
 
-        self.maporder = []
-        self.map = {}
-        try:
-            origrevmapfile = open(self.revmapfile, 'r')
-            for l in origrevmapfile:
-                sv, dv = l[:-1].split()
-                if sv not in self.map:
-                    self.maporder.append(sv)
-                self.map[sv] = dv
-            origrevmapfile.close()
-        except IOError:
-            pass
+        self.map = mapfile(ui, revmapfile)
 
         # Read first the dst author map if any
         authorfile = self.dest.authorfile()
@@ -165,16 +153,6 @@
 
         return s
 
-    def mapentry(self, src, dst):
-        if self.revmapfilefd is None:
-            try:
-                self.revmapfilefd = open(self.revmapfile, "a")
-            except IOError, (errno, strerror):
-                raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
-        self.map[src] = dst
-        self.revmapfilefd.write("%s %s\n" % (src, dst))
-        self.revmapfilefd.flush()
-
     def writeauthormap(self):
         authorfile = self.authorfile
         if authorfile:
@@ -221,7 +199,7 @@
                 dest = SKIPREV
             else:
                 dest = self.map[changes]
-            self.mapentry(rev, dest)
+            self.map[rev] = dest
             return
         files, copies = changes
         parents = [self.map[r] for r in commit.parents]
@@ -249,13 +227,14 @@
                         self.dest.copyfile(copyf, f)
 
         newnode = self.dest.putcommit(filenames, parents, commit)
-        self.mapentry(rev, newnode)
+        self.source.converted(rev, newnode)
+        self.map[rev] = newnode
 
     def convert(self):
         try:
             self.source.before()
             self.dest.before()
-            self.source.setrevmap(self.map, self.maporder)
+            self.source.setrevmap(self.map)
             self.ui.status("scanning source...\n")
             heads = self.source.getheads()
             parents = self.walktree(heads)
@@ -285,7 +264,7 @@
                 # write another hash correspondence to override the previous
                 # one so we don't end up with extra tag heads
                 if nrev:
-                    self.mapentry(c, nrev)
+                    self.map[c] = nrev
 
             self.writeauthormap()
         finally:
@@ -296,8 +275,7 @@
             self.dest.after()
         finally:
             self.source.after()
-        if self.revmapfilefd:
-            self.revmapfilefd.close()
+        self.map.close()
 
 def convert(ui, src, dest=None, revmapfile=None, **opts):
     """Convert a foreign SCM repository to a Mercurial one.
@@ -311,6 +289,7 @@
 
     Accepted destination formats:
     - Mercurial
+    - Subversion (history on branches is not preserved)
 
     If no revision is given, all revisions will be converted. Otherwise,
     convert will only import up to the named revision (given in a format
@@ -353,6 +332,24 @@
     The 'rename' directive renames a file or directory.  To rename from a
     subdirectory into the root of the repository, use '.' as the path to
     rename to.
+
+    Back end options:
+
+    --config convert.hg.clonebranches=False   (boolean)
+        hg target: XXX not documented
+    --config convert.hg.saverev=True          (boolean)
+        hg source: allow target to preserve source revision ID
+    --config convert.hg.tagsbranch=default    (branch name)
+        hg target: XXX not documented
+    --config convert.hg.usebranchnames=True   (boolean)
+        hg target: preserve branch names
+
+    --config convert.svn.branches=branches    (directory name)
+        svn source: specify the directory containing branches
+    --config convert.svn.tags=tags            (directory name)
+        svn source: specify the directory containing tags
+    --config convert.svn.trunk=trunk          (directory name)
+        svn source: specify the name of the trunk branch
     """
 
     util._encoding = 'UTF-8'
--- a/hgext/convert/common.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/common.py	Sun Dec 02 19:45:38 2007 +0100
@@ -1,6 +1,8 @@
 # common code for the convert extension
-import base64
+import base64, errno
 import cPickle as pickle
+from mercurial import util
+from mercurial.i18n import _
 
 def encodeargs(args):
     def encodearg(s):
@@ -15,6 +17,11 @@
     s = base64.decodestring(s)
     return pickle.loads(s)
 
+def checktool(exe, name=None):
+    name = name or exe
+    if not util.find_exe(exe):
+        raise util.Abort('cannot find required "%s" tool' % name)
+
 class NoRepo(Exception): pass
 
 SKIPREV = 'SKIP'
@@ -33,7 +40,7 @@
 class converter_source(object):
     """Conversion source interface"""
 
-    def __init__(self, ui, path, rev=None):
+    def __init__(self, ui, path=None, rev=None):
         """Initialize conversion source (or raise NoRepo("message")
         exception if path is not a valid repository)"""
         self.ui = ui
@@ -48,11 +55,8 @@
     def after(self):
         pass
 
-    def setrevmap(self, revmap, order):
-        """set the map of already-converted revisions
-        
-        order is a list with the keys from revmap in the order they
-        appear in the revision map file."""
+    def setrevmap(self, revmap):
+        """set the map of already-converted revisions"""
         pass
 
     def getheads(self):
@@ -111,6 +115,11 @@
         """
         raise NotImplementedError()
 
+    def converted(self, rev, sinkrev):
+        '''Notify the source that a revision has been converted.'''
+        pass
+
+
 class converter_sink(object):
     """Conversion sink (target) interface"""
 
@@ -184,3 +193,105 @@
         filter empty revisions.
         """
         pass
+
+    def before(self):
+        pass
+
+    def after(self):
+        pass
+
+
+class commandline(object):
+    def __init__(self, ui, command):
+        self.ui = ui
+        self.command = command
+
+    def prerun(self):
+        pass
+
+    def postrun(self):
+        pass
+
+    def _run(self, cmd, *args, **kwargs):
+        cmdline = [self.command, cmd] + list(args)
+        for k, v in kwargs.iteritems():
+            if len(k) == 1:
+                cmdline.append('-' + k)
+            else:
+                cmdline.append('--' + k.replace('_', '-'))
+            try:
+                if len(k) == 1:
+                    cmdline.append('' + v)
+                else:
+                    cmdline[-1] += '=' + v
+            except TypeError:
+                pass
+        cmdline = [util.shellquote(arg) for arg in cmdline]
+        cmdline += ['<', util.nulldev]
+        cmdline = ' '.join(cmdline)
+        self.ui.debug(cmdline, '\n')
+
+        self.prerun()
+        try:
+            return util.popen(cmdline)
+        finally:
+            self.postrun()
+
+    def run(self, cmd, *args, **kwargs):
+        fp = self._run(cmd, *args, **kwargs)
+        output = fp.read()
+        self.ui.debug(output)
+        return output, fp.close()
+
+    def checkexit(self, status, output=''):
+        if status:
+            if output:
+                self.ui.warn(_('%s error:\n') % self.command)
+                self.ui.warn(output)
+            msg = util.explain_exit(status)[0]
+            raise util.Abort(_('%s %s') % (self.command, msg))
+
+    def run0(self, cmd, *args, **kwargs):
+        output, status = self.run(cmd, *args, **kwargs)
+        self.checkexit(status, output)
+        return output
+
+
+class mapfile(dict):
+    def __init__(self, ui, path):
+        super(mapfile, self).__init__()
+        self.ui = ui
+        self.path = path
+        self.fp = None
+        self.order = []
+        self._read()
+
+    def _read(self):
+        try:
+            fp = open(self.path, 'r')
+        except IOError, err:
+            if err.errno != errno.ENOENT:
+                raise
+            return
+        for line in fp:
+            key, value = line[:-1].split(' ', 1)
+            if key not in self:
+                self.order.append(key)
+            super(mapfile, self).__setitem__(key, value)
+        fp.close()
+            
+    def __setitem__(self, key, value):
+        if self.fp is None:
+            try:
+                self.fp = open(self.path, 'a')
+            except IOError, err:
+                raise util.Abort(_('could not open map file %r: %s') %
+                                 (self.path, err.strerror))
+        self.fp.write('%s %s\n' % (key, value))
+        self.fp.flush()
+        super(mapfile, self).__setitem__(key, value)
+
+    def close(self):
+        if self.fp:
+            self.fp.close()
+            self.fp = None
--- a/hgext/convert/cvs.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/cvs.py	Sun Dec 02 19:45:38 2007 +0100
@@ -1,9 +1,10 @@
 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
 
 import os, locale, re, socket
+from cStringIO import StringIO
 from mercurial import util
 
-from common import NoRepo, commit, converter_source
+from common import NoRepo, commit, converter_source, checktool
 
 class convert_cvs(converter_source):
     def __init__(self, ui, path, rev=None):
@@ -13,6 +14,9 @@
         if not os.path.exists(cvs):
             raise NoRepo("%s does not look like a CVS checkout" % path)
 
+        for tool in ('cvsps', 'cvs'):
+            checktool(tool)
+
         self.changeset = {}
         self.files = {}
         self.tags = {}
@@ -206,6 +210,20 @@
         return self.heads
 
     def _getfile(self, name, rev):
+
+        def chunkedread(fp, count):
+            # file-objects returned by socked.makefile() do not handle
+            # large read() requests very well.
+            chunksize = 65536
+            output = StringIO()
+            while count > 0:
+                data = fp.read(min(count, chunksize))
+                if not data:
+                    raise util.Abort("%d bytes missing from remote file" % count)
+                count -= len(data)
+                output.write(data)
+            return output.getvalue()
+
         if rev.endswith("(DEAD)"):
             raise IOError
 
@@ -224,14 +242,14 @@
                 self.readp.readline() # entries
                 mode = self.readp.readline()[:-1]
                 count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
+                data = chunkedread(self.readp, count)
             elif line.startswith(" "):
                 data += line[1:]
             elif line.startswith("M "):
                 pass
             elif line.startswith("Mbinary "):
                 count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
+                data = chunkedread(self.readp, count)
             else:
                 if line == "ok\n":
                     return (data, "x" in mode and "x" or "")
--- a/hgext/convert/darcs.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/darcs.py	Sun Dec 02 19:45:38 2007 +0100
@@ -1,6 +1,6 @@
 # darcs support for the convert extension
 
-from common import NoRepo, commit, converter_source
+from common import NoRepo, checktool, commandline, commit, converter_source
 from mercurial.i18n import _
 from mercurial import util
 import os, shutil, tempfile
@@ -17,15 +17,18 @@
             except ImportError: ElementTree = None
 
 
-class darcs_source(converter_source):
+class darcs_source(converter_source, commandline):
     def __init__(self, ui, path, rev=None):
-        super(darcs_source, self).__init__(ui, path, rev=rev)
+        converter_source.__init__(self, ui, path, rev=rev)
+        commandline.__init__(self, ui, 'darcs')
 
         # check for _darcs, ElementTree, _darcs/inventory so that we can
         # easily skip test-convert-darcs if ElementTree is not around
         if not os.path.exists(os.path.join(path, '_darcs')):
             raise NoRepo("%s does not look like a darcs repo" % path)
 
+        checktool('darcs')
+
         if ElementTree is None:
             raise util.Abort(_("Python ElementTree module is not available"))
 
@@ -45,7 +48,8 @@
         output, status = self.run('init', repodir=self.tmppath)
         self.checkexit(status)
 
-        tree = self.xml('changes', '--xml-output', '--summary')
+        tree = self.xml('changes', xml_output=True, summary=True,
+                        repodir=self.path)
         tagname = None
         child = None
         for elt in tree.findall('patch'):
@@ -65,31 +69,9 @@
         self.ui.debug('cleaning up %s\n' % self.tmppath)
         shutil.rmtree(self.tmppath, ignore_errors=True)
 
-    def _run(self, cmd, *args, **kwargs):
-        cmdline = ['darcs', cmd, '--repodir', kwargs.get('repodir', self.path)]
-        cmdline += args
-        cmdline = [util.shellquote(arg) for arg in cmdline]
-        cmdline += ['<', util.nulldev]
-        cmdline = ' '.join(cmdline)
-        self.ui.debug(cmdline, '\n')
-        return util.popen(cmdline)
-
-    def run(self, cmd, *args, **kwargs):
-        fp = self._run(cmd, *args, **kwargs)
-        output = fp.read()
-        return output, fp.close()
-
-    def checkexit(self, status, output=''):
-        if status:
-            if output:
-                self.ui.warn(_('darcs error:\n'))
-                self.ui.warn(output)
-            msg = util.explain_exit(status)[0]
-            raise util.Abort(_('darcs %s') % msg)
-        
-    def xml(self, cmd, *opts):
+    def xml(self, cmd, **kwargs):
         etree = ElementTree()
-        fp = self._run(cmd, *opts)
+        fp = self._run(cmd, **kwargs)
         etree.parse(fp)
         self.checkexit(fp.close())
         return etree.getroot()
@@ -105,15 +87,15 @@
                       desc=desc.strip(), parents=self.parents[rev])
 
     def pull(self, rev):
-        output, status = self.run('pull', self.path, '--all',
-                                  '--match', 'hash %s' % rev,
-                                  '--no-test', '--no-posthook',
-                                  '--external-merge', '/bin/false',
+        output, status = self.run('pull', self.path, all=True,
+                                  match='hash %s' % rev,
+                                  no_test=True, no_posthook=True,
+                                  external_merge='/bin/false',
                                   repodir=self.tmppath)
         if status:
             if output.find('We have conflicts in') == -1:
                 self.checkexit(status, output)
-            output, status = self.run('revert', '--all', repodir=self.tmppath)
+            output, status = self.run('revert', all=True, repodir=self.tmppath)
             self.checkexit(status, output)
 
     def getchanges(self, rev):
--- a/hgext/convert/filemap.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/filemap.py	Sun Dec 02 19:45:38 2007 +0100
@@ -7,7 +7,7 @@
 import shlex
 from mercurial.i18n import _
 from mercurial import util
-from common import SKIPREV
+from common import SKIPREV, converter_source
 
 def rpairs(name):
     e = len(name)
@@ -110,9 +110,9 @@
 #   touch files we're interested in, but also merges that merge two
 #   or more interesting revisions.
 
-class filemap_source(object):
+class filemap_source(converter_source):
     def __init__(self, ui, baseconverter, filemap):
-        self.ui = ui
+        super(filemap_source, self).__init__(ui)
         self.base = baseconverter
         self.filemapper = filemapper(ui, filemap)
         self.commits = {}
@@ -128,7 +128,7 @@
         self.children = {}
         self.seenchildren = {}
 
-    def setrevmap(self, revmap, order):
+    def setrevmap(self, revmap):
         # rebuild our state to make things restartable
         #
         # To avoid calling getcommit for every revision that has already
@@ -143,7 +143,7 @@
         seen = {SKIPREV: SKIPREV}
         dummyset = util.set()
         converted = []
-        for rev in order:
+        for rev in revmap.order:
             mapped = revmap[rev]
             wanted = mapped not in seen
             if wanted:
@@ -157,7 +157,7 @@
                 arg = None
             converted.append((rev, wanted, arg))
         self.convertedorder = converted
-        return self.base.setrevmap(revmap, order)
+        return self.base.setrevmap(revmap)
 
     def rebuild(self):
         if self._rebuilt:
@@ -344,9 +344,3 @@
 
     def gettags(self):
         return self.base.gettags()
-
-    def before(self):
-        pass
-
-    def after(self):
-        pass
--- a/hgext/convert/git.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/git.py	Sun Dec 02 19:45:38 2007 +0100
@@ -3,7 +3,7 @@
 import os
 from mercurial import util
 
-from common import NoRepo, commit, converter_source
+from common import NoRepo, commit, converter_source, checktool
 
 class convert_git(converter_source):
     # Windows does not support GIT_DIR= construct while other systems
@@ -31,6 +31,9 @@
             path += "/.git"
         if not os.path.exists(path + "/objects"):
             raise NoRepo("%s does not look like a Git repo" % path)
+
+        checktool('git-rev-parse', 'git')
+
         self.path = path
 
     def getheads(self):
--- a/hgext/convert/hg.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/hg.py	Sun Dec 02 19:45:38 2007 +0100
@@ -1,10 +1,16 @@
 # hg backend for convert extension
 
-# Note for hg->hg conversion: Old versions of Mercurial didn't trim
-# the whitespace from the ends of commit messages, but new versions
-# do.  Changesets created by those older versions, then converted, may
-# thus have different hashes for changesets that are otherwise
-# identical.
+# Notes for hg->hg conversion:
+#
+# * Old versions of Mercurial didn't trim the whitespace from the ends
+#   of commit messages, but new versions do.  Changesets created by
+#   those older versions, then converted, may thus have different
+#   hashes for changesets that are otherwise identical.
+#
+# * By default, the source revision is stored in the converted
+#   revision.  This will cause the converted revision to have a
+#   different identity than the source.  To avoid this, use the
+#   following option: "--config convert.hg.saverev=false"
 
 
 import os, time
@@ -24,8 +30,6 @@
         if os.path.isdir(path) and len(os.listdir(path)) > 0:
             try:
                 self.repo = hg.repository(self.ui, path)
-                ui.status(_('destination %s is a Mercurial repository\n') %
-                          path)
             except hg.RepoError, err:
                 ui.print_exc()
                 raise NoRepo(err.args[0])
@@ -183,6 +187,7 @@
 class mercurial_source(converter_source):
     def __init__(self, ui, path, rev=None):
         converter_source.__init__(self, ui, path, rev)
+        self.saverev = ui.configbool('convert', 'hg.saverev', True)
         try:
             self.repo = hg.repository(self.ui, path)
             # try to provoke an exception if this isn't really a hg
@@ -195,6 +200,7 @@
         self.lastrev = None
         self.lastctx = None
         self._changescache = None
+        self.convertfp = None
 
     def changectx(self, rev):
         if self.lastrev != rev:
@@ -240,8 +246,12 @@
     def getcommit(self, rev):
         ctx = self.changectx(rev)
         parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
+        if self.saverev:
+            crev = rev
+        else:
+            crev = None
         return commit(author=ctx.user(), date=util.datestr(ctx.date()),
-                      desc=ctx.description(), parents=parents,
+                      desc=ctx.description(), rev=crev, parents=parents,
                       branch=ctx.branch(), extra=ctx.extra())
 
     def gettags(self):
@@ -258,3 +268,9 @@
 
         return changes[0] + changes[1] + changes[2]
 
+    def converted(self, rev, destrev):
+        if self.convertfp is None:
+            self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
+                                  'a')
+        self.convertfp.write('%s %s\n' % (destrev, rev))
+        self.convertfp.flush()
--- a/hgext/convert/subversion.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/convert/subversion.py	Sun Dec 02 19:45:38 2007 +0100
@@ -17,9 +17,13 @@
 
 import locale
 import os
+import re
 import sys
 import cPickle as pickle
-from mercurial import util
+import tempfile
+
+from mercurial import strutil, util
+from mercurial.i18n import _
 
 # Subversion stuff. Works best with very recent Python SVN bindings
 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
@@ -28,6 +32,7 @@
 from cStringIO import StringIO
 
 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
+from common import commandline, converter_sink, mapfile
 
 try:
     from svn.core import SubversionException, Pool
@@ -149,9 +154,15 @@
         self.head = self.revid(self.last_changed)
         self._changescache = None
 
-    def setrevmap(self, revmap, order):
+        if os.path.exists(os.path.join(url, '.svn/entries')):
+            self.wc = url
+        else:
+            self.wc = None
+        self.convertfp = None
+
+    def setrevmap(self, revmap):
         lastrevs = {}
-        for revid in revmap.keys():
+        for revid in revmap.iterkeys():
             uuid, module, revnum = self.revsplit(revid)
             lastrevnum = lastrevs.setdefault(module, revnum)
             if revnum > lastrevnum:
@@ -293,6 +304,15 @@
             self.ui.note('no tags found at revision %d\n' % start)
         return tags
 
+    def converted(self, rev, destrev):
+        if not self.wc:
+            return
+        if self.convertfp is None:
+            self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
+                                  'a')
+        self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
+        self.convertfp.flush()
+
     # -- helper functions --
 
     def revid(self, revnum, module=None):
@@ -664,3 +684,219 @@
         pool = Pool()
         rpath = '/'.join([self.base, path]).strip('/')
         return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
+
+pre_revprop_change = '''#!/bin/sh
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
+
+echo "Changing prohibited revision property" >&2
+exit 1
+'''
+
+class svn_sink(converter_sink, commandline):
+    commit_re = re.compile(r'Committed revision (\d+).', re.M)
+
+    def prerun(self):
+        if self.wc:
+            os.chdir(self.wc)
+
+    def postrun(self):
+        if self.wc:
+            os.chdir(self.cwd)
+
+    def join(self, name):
+        return os.path.join(self.wc, '.svn', name)
+        
+    def revmapfile(self):
+        return self.join('hg-shamap')
+
+    def authorfile(self):
+        return self.join('hg-authormap')
+
+    def __init__(self, ui, path):
+        converter_sink.__init__(self, ui, path)
+        commandline.__init__(self, ui, 'svn')
+        self.delete = []
+        self.wc = None
+        self.cwd = os.getcwd()
+
+        path = os.path.realpath(path)
+
+        created = False
+        if os.path.isfile(os.path.join(path, '.svn', 'entries')):
+            self.wc = path
+            self.run0('update')
+        else:
+            wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
+
+            if os.path.isdir(os.path.dirname(path)):
+                if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
+                    ui.status(_('initializing svn repo %r\n') %
+                              os.path.basename(path))
+                    commandline(ui, 'svnadmin').run0('create', path)
+                    created = path
+                path = path.replace('\\', '/')
+                if not path.startswith('/'):
+                    path = '/' + path
+                path = 'file://' + path
+            
+            ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
+            self.run0('checkout', path, wcpath)
+
+            self.wc = wcpath
+        self.opener = util.opener(self.wc)
+        self.wopener = util.opener(self.wc)
+        self.childmap = mapfile(ui, self.join('hg-childmap'))
+        self.is_exec = util.checkexec(self.wc) and util.is_exec or None
+
+        if created:
+            hook = os.path.join(created, 'hooks', 'pre-revprop-change')
+            fp = open(hook, 'w')
+            fp.write(pre_revprop_change)
+            fp.close()
+            util.set_exec(hook, True)
+
+        xport = transport.SvnRaTransport(url=geturl(path))
+        self.uuid = svn.ra.get_uuid(xport.ra)
+
+    def wjoin(self, *names):
+        return os.path.join(self.wc, *names)
+
+    def putfile(self, filename, flags, data):
+        if 'l' in flags:
+            self.wopener.symlink(data, filename)
+        else:
+            try:
+                if os.path.islink(self.wjoin(filename)):
+                    os.unlink(filename)
+            except OSError:
+                pass
+            self.wopener(filename, 'w').write(data)
+
+            if self.is_exec:
+                was_exec = self.is_exec(self.wjoin(filename))
+            else:
+                # On filesystems not supporting execute-bit, there is no way
+                # to know if it is set but asking subversion. Setting it
+                # systematically is just as expensive and much simpler.
+                was_exec = 'x' not in flags
+
+            util.set_exec(self.wjoin(filename), 'x' in flags)
+            if was_exec:
+                if 'x' not in flags:
+                    self.run0('propdel', 'svn:executable', filename)
+            else:
+                if 'x' in flags:
+                    self.run0('propset', 'svn:executable', '*', filename)
+            
+    def delfile(self, name):
+        self.delete.append(name)
+
+    def copyfile(self, source, dest):
+        # SVN's copy command pukes if the destination file exists, but
+        # our copyfile method expects to record a copy that has
+        # already occurred.  Cross the semantic gap.
+        wdest = self.wjoin(dest)
+        exists = os.path.exists(wdest)
+        if exists:
+            fd, tempname = tempfile.mkstemp(
+                prefix='hg-copy-', dir=os.path.dirname(wdest))
+            os.close(fd)
+            os.unlink(tempname)
+            os.rename(wdest, tempname)
+        try:
+            self.run0('copy', source, dest)
+        finally:
+            if exists:
+                try:
+                    os.unlink(wdest)
+                except OSError:
+                    pass
+                os.rename(tempname, wdest)
+
+    def dirs_of(self, files):
+        dirs = set()
+        for f in files:
+            if os.path.isdir(self.wjoin(f)):
+                dirs.add(f)
+            for i in strutil.rfindall(f, '/'):
+                dirs.add(f[:i])
+        return dirs
+
+    def add_files(self, files):
+        add_dirs = [d for d in self.dirs_of(files)
+                    if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
+        if add_dirs:
+            add_dirs.sort()
+            self.run('add', non_recursive=True, quiet=True, *add_dirs)
+        if files:
+            self.run('add', quiet=True, *files)
+        return files.union(add_dirs)
+        
+    def tidy_dirs(self, names):
+        dirs = list(self.dirs_of(names))
+        dirs.sort(reverse=True)
+        deleted = []
+        for d in dirs:
+            wd = self.wjoin(d)
+            if os.listdir(wd) == '.svn':
+                self.run0('delete', d)
+                deleted.append(d)
+        return deleted
+
+    def addchild(self, parent, child):
+        self.childmap[parent] = child
+
+    def revid(self, rev):
+        return u"svn:%s@%s" % (self.uuid, rev)
+        
+    def putcommit(self, files, parents, commit):
+        for parent in parents:
+            try:
+                return self.revid(self.childmap[parent])
+            except KeyError:
+                pass
+        entries = set(self.delete)
+        if self.delete:
+            self.run0('delete', *self.delete)
+            self.delete = []
+        files = util.frozenset(files)
+        entries.update(self.add_files(files.difference(entries)))
+        entries.update(self.tidy_dirs(entries))
+        fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
+        fp = os.fdopen(fd, 'w')
+        fp.write(commit.desc)
+        fp.close()
+        try:
+            output = self.run0('commit',
+                               username=util.shortuser(commit.author),
+                               file=messagefile,
+                               *list(entries))
+            try:
+                rev = self.commit_re.search(output).group(1)
+            except AttributeError:
+                self.ui.warn(_('unexpected svn output:\n'))
+                self.ui.warn(output)
+                raise util.Abort(_('unable to cope with svn output'))
+            if commit.rev:
+                self.run('propset', 'hg:convert-rev', commit.rev,
+                         revprop=True, revision=rev)
+            if commit.branch and commit.branch != 'default':
+                self.run('propset', 'hg:convert-branch', commit.branch,
+                         revprop=True, revision=rev)
+            for parent in parents:
+                self.addchild(parent, rev)
+            return self.revid(rev)
+        finally:
+            os.unlink(messagefile)
+
+    def puttags(self, tags):
+        self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
--- a/hgext/gpg.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgext/gpg.py	Sun Dec 02 19:45:38 2007 +0100
@@ -249,7 +249,7 @@
     message = opts['message']
     if not message:
         message = "\n".join([_("Added signature for changeset %s")
-                             % hgnode.hex(n)
+                             % hgnode.short(n)
                              for n in nodes])
     try:
         repo.commit([".hgsigs"], message, opts['user'], opts['date'])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/highlight.py	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,145 @@
+"""
+This is Mercurial extension for syntax highlighting in the file
+revision view of hgweb.
+
+It depends on the pygments syntax highlighting library:
+http://pygments.org/
+
+To enable the extension add this to hgrc:
+
+[extensions]
+hgext.highlight =
+
+There is a single configuration option:
+
+[web]
+pygments_style = <style>
+
+The default is 'colorful'.  If this is changed the corresponding CSS
+file should be re-generated by running
+
+# pygmentize -f html -S <newstyle>
+
+
+-- Adam Hupp <adam@hupp.org>
+
+
+"""
+
+from mercurial import demandimport
+demandimport.ignore.extend(['pkgutil',
+                            'pkg_resources',
+                            '__main__',])
+
+import mimetypes
+
+from mercurial.hgweb import hgweb_mod
+from mercurial.hgweb.hgweb_mod import hgweb
+from mercurial import util
+from mercurial.hgweb.common import paritygen
+from mercurial.node import hex
+
+from pygments import highlight
+from pygments.util import ClassNotFound
+from pygments.lexers import guess_lexer_for_filename, TextLexer
+from pygments.formatters import HtmlFormatter
+
+SYNTAX_CSS = ('\n<link rel="stylesheet" href="#staticurl#highlight.css" '
+              'type="text/css" />')
+
+class StripedHtmlFormatter(HtmlFormatter):
+    def __init__(self, stripecount, *args, **kwargs):
+        super(StripedHtmlFormatter, self).__init__(*args, **kwargs)
+        self.stripecount = stripecount
+
+    def wrap(self, source, outfile):
+        yield 0, "<div class='highlight'>"
+        yield 0, "<pre>"
+        parity = paritygen(self.stripecount)
+
+        for n, i in source:
+            if n == 1:
+                i = "<div class='parity%s'>%s</div>" % (parity.next(), i)
+            yield n, i
+
+        yield 0, "</pre>"
+        yield 0, "</div>"
+
+
+def pygments_format(filename, rawtext, forcetext=False, stripecount=1,
+                    style='colorful'):
+    if not forcetext:
+        try:
+            lexer = guess_lexer_for_filename(filename, rawtext)
+        except ClassNotFound:
+            lexer = TextLexer()
+    else:
+        lexer = TextLexer()
+
+    formatter = StripedHtmlFormatter(stripecount, style=style,
+                                     linenos='inline')
+
+    return highlight(rawtext, lexer, formatter)
+
+
+def filerevision_pygments(self, fctx):
+    """Reimplement hgweb.filerevision to use syntax highlighting"""
+    filename = fctx.path()
+
+    rawtext = fctx.data()
+    text = rawtext
+
+    mt = mimetypes.guess_type(filename)[0]
+
+    if util.binary(text):
+        mt = mt or 'application/octet-stream'
+        text = "(binary:%s)" % mt
+
+        # don't parse (binary:...) as anything
+        forcetext = True
+    else:
+        mt = mt or 'text/plain'
+        forcetext = False
+
+    def lines(text):
+        for line in text.splitlines(True):
+            yield {"line": line}
+
+    style = self.config("web", "pygments_style", "colorful")
+
+    text_formatted = lines(pygments_format(filename, text,
+                                           forcetext=forcetext,
+                                           stripecount=self.stripecount,
+                                           style=style))
+
+    # override per-line template
+    self.t.cache['fileline'] = '#line#'
+
+    # append a <link ...> to the syntax highlighting css
+    old_header = ''.join(self.t('header'))
+    if SYNTAX_CSS not in old_header:
+        new_header =  old_header + SYNTAX_CSS
+        self.t.cache['header'] = new_header
+
+    yield self.t("filerevision",
+                 file=filename,
+                 path=hgweb_mod._up(filename), # fixme: make public
+                 text=text_formatted,
+                 raw=rawtext,
+                 mimetype=mt,
+                 rev=fctx.rev(),
+                 node=hex(fctx.node()),
+                 author=fctx.user(),
+                 date=fctx.date(),
+                 desc=fctx.description(),
+                 parent=self.siblings(fctx.parents()),
+                 child=self.siblings(fctx.children()),
+                 rename=self.renamelink(fctx.filelog(),
+                                        fctx.filenode()),
+                 permissions=fctx.manifest().flags(filename))
+
+
+# monkeypatch in the new version
+# should be safer than overriding the method in a derived class
+# and then patching the class
+hgweb.filerevision = filerevision_pygments
--- a/hgweb.cgi	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgweb.cgi	Sun Dec 02 19:45:38 2007 +0100
@@ -22,10 +22,7 @@
 #os.environ["HGENCODING"] = "UTF-8"
 
 from mercurial.hgweb.hgweb_mod import hgweb
-from mercurial.hgweb.request import wsgiapplication
 import mercurial.hgweb.wsgicgi as wsgicgi
 
-def make_web_app():
-    return hgweb("/path/to/repo", "repository name")
-
-wsgicgi.launch(wsgiapplication(make_web_app))
+application = hgweb("/path/to/repo", "repository name")
+wsgicgi.launch(application)
--- a/hgwebdir.cgi	Sun Dec 02 19:39:27 2007 +0100
+++ b/hgwebdir.cgi	Sun Dec 02 19:45:38 2007 +0100
@@ -22,7 +22,6 @@
 #os.environ["HGENCODING"] = "UTF-8"
 
 from mercurial.hgweb.hgwebdir_mod import hgwebdir
-from mercurial.hgweb.request import wsgiapplication
 import mercurial.hgweb.wsgicgi as wsgicgi
 
 # The config file looks like this.  You can have paths to individual
@@ -44,7 +43,5 @@
 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
 # or use a dictionary with entries like 'virtual/path': '/real/path'
 
-def make_web_app():
-    return hgwebdir("hgweb.config")
-
-wsgicgi.launch(wsgiapplication(make_web_app))
+application = hgwebdir('hgweb.config')
+wsgicgi.launch(application)
--- a/mercurial/bundlerepo.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/bundlerepo.py	Sun Dec 02 19:45:38 2007 +0100
@@ -48,7 +48,7 @@
                 continue
             for p in (p1, p2):
                 if not p in self.nodemap:
-                    raise revlog.LookupError(_("unknown parent %s") % short(p1))
+                    raise revlog.LookupError(hex(p1), _("unknown parent %s") % short(p1))
             if linkmapper is None:
                 link = n
             else:
--- a/mercurial/cmdutil.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/cmdutil.py	Sun Dec 02 19:45:38 2007 +0100
@@ -571,26 +571,26 @@
         def showcopies(**args):
             c = [{'name': x[0], 'source': x[1]} for x in copies]
             return showlist('file_copy', c, plural='file_copies', **args)
-
-        if self.ui.debugflag:
-            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
-            def showfiles(**args):
-                return showlist('file', files[0], **args)
-            def showadds(**args):
-                return showlist('file_add', files[1], **args)
-            def showdels(**args):
-                return showlist('file_del', files[2], **args)
-            def showmanifest(**args):
-                args = args.copy()
-                args.update(dict(rev=self.repo.manifest.rev(changes[0]),
-                                 node=hex(changes[0])))
-                return self.t('manifest', **args)
-        else:
-            def showfiles(**args):
-                return showlist('file', changes[3], **args)
-            showadds = ''
-            showdels = ''
-            showmanifest = ''
+        
+        files = []
+        def getfiles():
+            if not files: 
+                files[:] = self.repo.status(
+                    log.parents(changenode)[0], changenode)[:3]
+            return files
+        def showfiles(**args):
+            return showlist('file', changes[3], **args)
+        def showmods(**args):
+            return showlist('file_mod', getfiles()[0], **args)
+        def showadds(**args):
+            return showlist('file_add', getfiles()[1], **args)
+        def showdels(**args):
+            return showlist('file_del', getfiles()[2], **args)
+        def showmanifest(**args):
+            args = args.copy()
+            args.update(dict(rev=self.repo.manifest.rev(changes[0]),
+                             node=hex(changes[0])))
+            return self.t('manifest', **args)
 
         defprops = {
             'author': changes[1],
@@ -599,6 +599,7 @@
             'desc': changes[4].strip(),
             'file_adds': showadds,
             'file_dels': showdels,
+            'file_mods': showmods,
             'files': showfiles,
             'file_copies': showcopies,
             'manifest': showmanifest,
--- a/mercurial/commands.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/commands.py	Sun Dec 02 19:45:38 2007 +0100
@@ -1629,7 +1629,7 @@
                 if opts.get('exact'):
                     if hex(n) != nodeid:
                         repo.rollback()
-                        raise util.Abort(_('patch is damaged' +
+                        raise util.Abort(_('patch is damaged'
                                            ' or loses information'))
             finally:
                 os.unlink(tmpname)
@@ -1937,7 +1937,7 @@
         if len(heads) == 1:
             msg = _('there is nothing to merge')
             if parent != repo.lookup(repo.workingctx().branch()):
-                msg = _('%s - use "hg update" instead' % msg)
+                msg = _('%s - use "hg update" instead') % msg
             raise util.Abort(msg)
 
         if parent not in heads:
--- a/mercurial/context.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/context.py	Sun Dec 02 19:45:38 2007 +0100
@@ -100,13 +100,13 @@
             try:
                 return self._manifest[path], self._manifest.flags(path)
             except KeyError:
-                raise revlog.LookupError(_("'%s' not found in manifest") % path)
+                raise revlog.LookupError(path, _("'%s' not found in manifest") % path)
         if '_manifestdelta' in self.__dict__ or path in self.files():
             if path in self._manifestdelta:
                 return self._manifestdelta[path], self._manifestdelta.flags(path)
         node, flag = self._repo.manifest.find(self._changeset[0], path)
         if not node:
-            raise revlog.LookupError(_("'%s' not found in manifest") % path)
+            raise revlog.LookupError(path, _("'%s' not found in manifest") % path)
 
         return node, flag
 
--- a/mercurial/dispatch.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/dispatch.py	Sun Dec 02 19:45:38 2007 +0100
@@ -125,7 +125,7 @@
             ui.warn("\n%r\n" % util.ellipsis(inst[1]))
     except ImportError, inst:
         m = str(inst).split()[-1]
-        ui.warn(_("abort: could not import module %s!\n" % m))
+        ui.warn(_("abort: could not import module %s!\n") % m)
         if m in "mpatch bdiff".split():
             ui.warn(_("(did you forget to compile extensions?)\n"))
         elif m in "zlib".split():
--- a/mercurial/hgweb/common.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/hgweb/common.py	Sun Dec 02 19:45:38 2007 +0100
@@ -6,7 +6,24 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetypes
+import errno, mimetypes, os
+
+class ErrorResponse(Exception):
+    def __init__(self, code, message=None):
+        Exception.__init__(self)
+        self.code = code
+        if message:
+            self.message = message
+        else:
+            self.message = _statusmessage(code)
+
+def _statusmessage(code):
+    from BaseHTTPServer import BaseHTTPRequestHandler
+    responses = BaseHTTPRequestHandler.responses
+    return responses.get(code, ('Error', 'Unknown error'))[0]
+    
+def statusmessage(code):
+    return '%d %s' % (code, _statusmessage(code))
 
 def get_mtime(repo_path):
     store_path = os.path.join(repo_path, ".hg")
@@ -40,9 +57,13 @@
         req.header([('Content-type', ct),
                     ('Content-length', str(os.path.getsize(path)))])
         return file(path, 'rb').read()
-    except (TypeError, OSError):
-        # illegal fname or unreadable file
-        return ""
+    except TypeError:
+        raise ErrorResponse(500, 'illegal file name')
+    except OSError, err:
+        if err.errno == errno.ENOENT:
+            raise ErrorResponse(404)
+        else:
+            raise ErrorResponse(500, err.strerror)
 
 def style_map(templatepath, style):
     """Return path to mapfile for a given style.
--- a/mercurial/hgweb/hgweb_mod.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Sun Dec 02 19:45:38 2007 +0100
@@ -6,13 +6,14 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetypes, re, zlib, mimetools, cStringIO, sys
+import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
 import tempfile, urllib, bz2
 from mercurial.node import *
 from mercurial.i18n import gettext as _
 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
 from mercurial import revlog, templater
-from common import get_mtime, staticfile, style_map, paritygen
+from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
+from request import wsgirequest
 
 def _up(p):
     if p[0] != "/":
@@ -478,6 +479,9 @@
                 short = os.path.basename(remain)
                 files[short] = (f, n)
 
+        if not files:
+            raise ErrorResponse(404, 'Path not found: ' + path)
+
         def filelist(**map):
             fl = files.keys()
             fl.sort()
@@ -668,10 +672,12 @@
         if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
             raise RuntimeError("This function is only intended to be called while running as a CGI script.")
         import mercurial.hgweb.wsgicgi as wsgicgi
-        from request import wsgiapplication
-        def make_web_app():
-            return self
-        wsgicgi.launch(wsgiapplication(make_web_app))
+        wsgicgi.launch(self)
+
+    def __call__(self, env, respond):
+        req = wsgirequest(env, respond)
+        self.run_wsgi(req)
+        return req
 
     def run_wsgi(self, req):
         def header(**map):
@@ -720,37 +726,21 @@
         def rewrite_request(req):
             '''translate new web interface to traditional format'''
 
-            def spliturl(req):
-                def firstitem(query):
-                    return query.split('&', 1)[0].split(';', 1)[0]
-
-                def normurl(url):
-                    inner = '/'.join([x for x in url.split('/') if x])
-                    tl = len(url) > 1 and url.endswith('/') and '/' or ''
-
-                    return '%s%s%s' % (url.startswith('/') and '/' or '',
-                                       inner, tl)
-
-                root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
-                pi = normurl(req.env.get('PATH_INFO', ''))
-                if pi:
-                    # strip leading /
-                    pi = pi[1:]
-                    if pi:
-                        root = root[:root.rfind(pi)]
-                    if req.env.has_key('REPO_NAME'):
-                        rn = req.env['REPO_NAME'] + '/'
-                        root += rn
-                        query = pi[len(rn):]
-                    else:
-                        query = pi
-                else:
-                    root += '?'
-                    query = firstitem(req.env['QUERY_STRING'])
-
-                return (root, query)
-
-            req.url, query = spliturl(req)
+            req.url = req.env['SCRIPT_NAME']
+            if not req.url.endswith('/'):
+                req.url += '/'
+            if req.env.has_key('REPO_NAME'):
+                req.url += req.env['REPO_NAME'] + '/'
+            
+            if req.env.get('PATH_INFO'):
+                parts = req.env.get('PATH_INFO').strip('/').split('/')
+                repo_parts = req.env.get('REPO_NAME', '').split('/')
+                if parts[:len(repo_parts)] == repo_parts:
+                    parts = parts[len(repo_parts):]
+                query = '/'.join(parts)
+            else:
+                query = req.env['QUERY_STRING'].split('&', 1)[0]
+                query = query.split(';', 1)[0]
 
             if req.form.has_key('cmd'):
                 # old style
@@ -845,14 +835,20 @@
 
             cmd = req.form['cmd'][0]
 
-            method = getattr(self, 'do_' + cmd, None)
-            if method:
-                try:
-                    method(req)
-                except (hg.RepoError, revlog.RevlogError), inst:
-                    req.write(self.t("error", error=str(inst)))
-            else:
-                req.write(self.t("error", error='No such method: ' + cmd))
+            try:
+                method = getattr(self, 'do_' + cmd)
+                method(req)
+            except revlog.LookupError, err:
+                req.respond(404, self.t(
+                    'error', error='revision not found: %s' % err.name))
+            except (hg.RepoError, revlog.RevlogError), inst:
+                req.respond('500 Internal Server Error',
+                            self.t('error', error=str(inst)))
+            except ErrorResponse, inst:
+                req.respond(inst.code, self.t('error', error=inst.message))
+            except AttributeError:
+                req.respond(400,
+                            self.t('error', error='No such method: ' + cmd))
         finally:
             self.t = None
 
@@ -1038,7 +1034,8 @@
             self.archive(req, req.form['node'][0], type_)
             return
 
-        req.write(self.t("error"))
+        req.respond(400, self.t('error',
+                                error='Unsupported archive type: %s' % type_))
 
     def do_static(self, req):
         fname = req.form['file'][0]
@@ -1047,8 +1044,7 @@
         static = self.config("web", "static",
                              os.path.join(self.templatepath, "static"),
                              untrusted=False)
-        req.write(staticfile(static, fname, req)
-                  or self.t("error", error="%r not found" % fname))
+        req.write(staticfile(static, fname, req))
 
     def do_capabilities(self, req):
         caps = ['lookup', 'changegroupsubset']
@@ -1198,7 +1194,11 @@
                 else:
                     filename = ''
                 error = getattr(inst, 'strerror', 'Unknown error')
-                req.write('%s: %s\n' % (error, filename))
+                if inst.errno == errno.ENOENT:
+                    code = 404
+                else:
+                    code = 500
+                req.respond(code, '%s: %s\n' % (error, filename))
         finally:
             fp.close()
             os.unlink(tempname)
--- a/mercurial/hgweb/hgwebdir_mod.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py	Sun Dec 02 19:45:38 2007 +0100
@@ -9,8 +9,9 @@
 import os, mimetools, cStringIO
 from mercurial.i18n import gettext as _
 from mercurial import ui, hg, util, templater
-from common import get_mtime, staticfile, style_map, paritygen
+from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
 from hgweb_mod import hgweb
+from request import wsgirequest
 
 # This is a stopgap
 class hgwebdir(object):
@@ -60,10 +61,12 @@
         if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
             raise RuntimeError("This function is only intended to be called while running as a CGI script.")
         import mercurial.hgweb.wsgicgi as wsgicgi
-        from request import wsgiapplication
-        def make_web_app():
-            return self
-        wsgicgi.launch(wsgiapplication(make_web_app))
+        wsgicgi.launch(self)
+
+    def __call__(self, env, respond):
+        req = wsgirequest(env, respond)
+        self.run_wsgi(req)
+        return req
 
     def run_wsgi(self, req):
         def header(**map):
@@ -88,15 +91,11 @@
         def config(section, name, default=None, untrusted=True):
             return parentui.config(section, name, default, untrusted)
 
-        url = req.env['REQUEST_URI'].split('?')[0]
+        url = req.env.get('SCRIPT_NAME', '')
         if not url.endswith('/'):
             url += '/'
-        pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
-        base = url[:len(url) - len(pathinfo)]
-        if not base.endswith('/'):
-            base += '/'
 
-        staticurl = config('web', 'staticurl') or base + 'static/'
+        staticurl = config('web', 'staticurl') or url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
 
@@ -155,8 +154,10 @@
                 if u.configbool("web", "hidden", untrusted=True):
                     continue
 
-                url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
-                       .replace("//", "/")) + '/'
+                parts = [req.env['PATH_INFO'], name]
+                if req.env['SCRIPT_NAME']:
+                	parts.insert(0, req.env['SCRIPT_NAME'])
+                url = ('/'.join(parts).replace("//", "/")) + '/'
 
                 # update time with local timezone
                 try:
@@ -215,46 +216,47 @@
                            **dict(sort)))
 
         try:
-            virtual = req.env.get("PATH_INFO", "").strip('/')
-            if virtual.startswith('static/'):
-                static = os.path.join(templater.templatepath(), 'static')
-                fname = virtual[7:]
-                req.write(staticfile(static, fname, req) or
-                          tmpl('error', error='%r not found' % fname))
-            elif virtual:
-                repos = dict(self.repos)
-                while virtual:
-                    real = repos.get(virtual)
-                    if real:
-                        req.env['REPO_NAME'] = virtual
-                        try:
-                            repo = hg.repository(parentui, real)
-                            hgweb(repo).run_wsgi(req)
-                        except IOError, inst:
-                            req.write(tmpl("error", error=inst.strerror))
-                        except hg.RepoError, inst:
-                            req.write(tmpl("error", error=str(inst)))
-                        return
+            try:
+                virtual = req.env.get("PATH_INFO", "").strip('/')
+                if virtual.startswith('static/'):
+                    static = os.path.join(templater.templatepath(), 'static')
+                    fname = virtual[7:]
+                    req.write(staticfile(static, fname, req))
+                elif virtual:
+                    repos = dict(self.repos)
+                    while virtual:
+                        real = repos.get(virtual)
+                        if real:
+                            req.env['REPO_NAME'] = virtual
+                            try:
+                                repo = hg.repository(parentui, real)
+                                hgweb(repo).run_wsgi(req)
+                                return
+                            except IOError, inst:
+                                raise ErrorResponse(500, inst.strerror)
+                            except hg.RepoError, inst:
+                                raise ErrorResponse(500, str(inst))
 
-                    # browse subdirectories
-                    subdir = virtual + '/'
-                    if [r for r in repos if r.startswith(subdir)]:
-                        makeindex(req, subdir)
-                        return
+                        # browse subdirectories
+                        subdir = virtual + '/'
+                        if [r for r in repos if r.startswith(subdir)]:
+                            makeindex(req, subdir)
+                            return
+
+                        up = virtual.rfind('/')
+                        if up < 0:
+                            break
+                        virtual = virtual[:up]
 
-                    up = virtual.rfind('/')
-                    if up < 0:
-                        break
-                    virtual = virtual[:up]
-
-                req.write(tmpl("notfound", repo=virtual))
-            else:
-                if req.form.has_key('static'):
-                    static = os.path.join(templater.templatepath(), "static")
-                    fname = req.form['static'][0]
-                    req.write(staticfile(static, fname, req)
-                              or tmpl("error", error="%r not found" % fname))
+                    req.respond(404, tmpl("notfound", repo=virtual))
                 else:
-                    makeindex(req)
+                    if req.form.has_key('static'):
+                        static = os.path.join(templater.templatepath(), "static")
+                        fname = req.form['static'][0]
+                        req.write(staticfile(static, fname, req))
+                    else:
+                        makeindex(req)
+            except ErrorResponse, err:
+                req.respond(err.code, tmpl('error', error=err.message or ''))
         finally:
             tmpl = None
--- a/mercurial/hgweb/request.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/hgweb/request.py	Sun Dec 02 19:45:38 2007 +0100
@@ -8,16 +8,10 @@
 
 import socket, cgi, errno
 from mercurial.i18n import gettext as _
-
-class wsgiapplication(object):
-    def __init__(self, destmaker):
-        self.destmaker = destmaker
+from common import ErrorResponse, statusmessage
 
-    def __call__(self, wsgienv, start_response):
-        return _wsgirequest(self.destmaker(), wsgienv, start_response)
-
-class _wsgirequest(object):
-    def __init__(self, destination, wsgienv, start_response):
+class wsgirequest(object):
+    def __init__(self, wsgienv, start_response):
         version = wsgienv['wsgi.version']
         if (version < (1, 0)) or (version >= (2, 0)):
             raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
@@ -32,7 +26,6 @@
         self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
         self.start_response = start_response
         self.headers = []
-        destination.run_wsgi(self)
 
     out = property(lambda self: self)
 
@@ -42,25 +35,32 @@
     def read(self, count=-1):
         return self.inp.read(count)
 
-    def write(self, *things):
+    def respond(self, status, *things):
         for thing in things:
             if hasattr(thing, "__iter__"):
                 for part in thing:
-                    self.write(part)
+                    self.respond(status, part)
             else:
                 thing = str(thing)
                 if self.server_write is None:
                     if not self.headers:
                         raise RuntimeError("request.write called before headers sent (%s)." % thing)
-                    self.server_write = self.start_response('200 Script output follows',
+                    if isinstance(status, ErrorResponse):
+                        status = statusmessage(status.code)
+                    elif isinstance(status, int):
+                        status = statusmessage(status)
+                    self.server_write = self.start_response(status,
                                                             self.headers)
                     self.start_response = None
-                    self.headers = None
+                    self.headers = []
                 try:
                     self.server_write(thing)
                 except socket.error, inst:
                     if inst[0] != errno.ECONNRESET:
                         raise
+        
+    def write(self, *things):
+        self.respond('200 Script output follows', *things)
 
     def writelines(self, lines):
         for line in lines:
@@ -84,3 +84,9 @@
         if length:
             headers.append(('Content-length', str(length)))
         self.header(headers)
+
+def wsgiapplication(app_maker):
+	application = app_maker()
+	def run_wsgi(env, respond):
+		application(env, respond)
+	return run_wsgi
--- a/mercurial/hgweb/server.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/hgweb/server.py	Sun Dec 02 19:45:38 2007 +0100
@@ -10,7 +10,6 @@
 from mercurial import ui, hg, util, templater
 from hgweb_mod import hgweb
 from hgwebdir_mod import hgwebdir
-from request import wsgiapplication
 from mercurial.i18n import gettext as _
 
 def _splitURI(uri):
@@ -85,6 +84,7 @@
         env['SERVER_NAME'] = self.server.server_name
         env['SERVER_PORT'] = str(self.server.server_port)
         env['REQUEST_URI'] = self.path
+        env['SCRIPT_NAME'] = ''
         env['PATH_INFO'] = path_info
         env['REMOTE_HOST'] = self.client_address[0]
         env['REMOTE_ADDR'] = self.client_address[0]
@@ -121,10 +121,7 @@
         self.saved_headers = []
         self.sent_headers = False
         self.length = None
-        req = self.server.reqmaker(env, self._start_response)
-        for data in req:
-            if data:
-                self._write(data)
+        self.server.application(env, self._start_response)
 
     def send_headers(self):
         if not self.saved_status:
@@ -250,7 +247,7 @@
                     raise hg.RepoError(_("There is no Mercurial repository here"
                                          " (.hg not found)"))
                 return hgwebobj
-            self.reqmaker = wsgiapplication(make_handler)
+            self.application = make_handler()
 
             addr = address
             if addr in ('', '::'):
--- a/mercurial/hgweb/wsgicgi.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/hgweb/wsgicgi.py	Sun Dec 02 19:45:38 2007 +0100
@@ -16,6 +16,7 @@
     util.set_binary(sys.stdout)
 
     environ = dict(os.environ.items())
+    environ.setdefault('PATH_INFO', '')
     environ['wsgi.input'] = sys.stdin
     environ['wsgi.errors'] = sys.stderr
     environ['wsgi.version'] = (1, 0)
@@ -61,13 +62,4 @@
         headers_set[:] = [status, response_headers]
         return write
 
-    result = application(environ, start_response)
-    try:
-        for data in result:
-            if data:    # don't send headers until body appears
-                write(data)
-        if not headers_sent:
-            write('')   # send headers now if body was empty
-    finally:
-        if hasattr(result,'close'):
-            result.close()
+    application(environ, start_response)
--- a/mercurial/localrepo.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/localrepo.py	Sun Dec 02 19:45:38 2007 +0100
@@ -1710,6 +1710,8 @@
             # Go through all our files in order sorted by name.
             for fname in changedfiles:
                 filerevlog = self.file(fname)
+                if filerevlog.count() == 0:
+                    raise util.abort(_("empty or missing revlog for %s") % fname)
                 # Toss out the filenodes that the recipient isn't really
                 # missing.
                 if msng_filenode_set.has_key(fname):
@@ -1794,6 +1796,8 @@
 
             for fname in changedfiles:
                 filerevlog = self.file(fname)
+                if filerevlog.count() == 0:
+                    raise util.abort(_("empty or missing revlog for %s") % fname)
                 nodeiter = gennodelst(filerevlog)
                 nodeiter = list(nodeiter)
                 if nodeiter:
--- a/mercurial/revlog.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/revlog.py	Sun Dec 02 19:45:38 2007 +0100
@@ -31,8 +31,13 @@
 
 class RevlogError(Exception):
     pass
+
 class LookupError(RevlogError):
-    pass
+    def __init__(self, name, message=None):
+        if message is None:
+            message = _('not found: %s') % name
+        RevlogError.__init__(self, message)
+        self.name = name
 
 def getoffset(q):
     return int(q >> 16)
@@ -321,7 +326,7 @@
             e = _unpack(indexformatv0, cur)
             # transform to revlogv1 format
             e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
-                  nodemap[e[4]], nodemap[e[5]], e[6])
+                  nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
             index.append(e2)
             nodemap[e[6]] = n
             n += 1
@@ -516,7 +521,7 @@
         try:
             return self.nodemap[node]
         except KeyError:
-            raise LookupError(_('%s: no node %s') % (self.indexfile, hex(node)))
+            raise LookupError(hex(node), _('%s: no node %s') % (self.indexfile, hex(node)))
     def node(self, rev):
         return self.index[rev][7]
     def linkrev(self, node):
@@ -836,7 +841,8 @@
                 for n in self.nodemap:
                     if n.startswith(bin_id) and hex(n).startswith(id):
                         if node is not None:
-                            raise LookupError(_("Ambiguous identifier"))
+                            raise LookupError(hex(node),
+                                              _("Ambiguous identifier"))
                         node = n
                 if node is not None:
                     return node
@@ -855,7 +861,7 @@
         if n:
             return n
 
-        raise LookupError(_("No match found"))
+        raise LookupError(id, _("No match found"))
 
     def cmp(self, node, text):
         """compare text with a given file revision"""
@@ -1166,13 +1172,13 @@
 
             for p in (p1, p2):
                 if not p in self.nodemap:
-                    raise LookupError(_("unknown parent %s") % short(p))
+                    raise LookupError(hex(p), _("unknown parent %s") % short(p))
 
             if not chain:
                 # retrieve the parent revision of the delta chain
                 chain = p1
                 if not chain in self.nodemap:
-                    raise LookupError(_("unknown base %s") % short(chain[:4]))
+                    raise LookupError(hex(chain), _("unknown base %s") % short(chain[:4]))
 
             # full versions are inserted when the needed deltas become
             # comparable to the uncompressed text or when the previous
--- a/mercurial/util_win32.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/util_win32.py	Sun Dec 02 19:45:38 2007 +0100
@@ -16,6 +16,7 @@
 from i18n import _
 import errno, os, pywintypes, win32con, win32file, win32process
 import cStringIO, winerror
+import osutil
 from win32com.shell import shell,shellcon
 
 class WinError:
@@ -179,6 +180,20 @@
 
 def system_rcpath_win32():
     '''return default os-specific hgrc search path'''
+    try:
+        value = win32api.RegQueryValue(
+                win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
+        rcpath = []
+        for p in value.split(os.pathsep):
+            if p.lower().endswith('mercurial.ini'):
+                rcpath.append(p)
+            elif os.path.isdir(p):
+                for f, kind in osutil.listdir(p):
+                    if f.endswith('.rc'):
+                        rcpath.append(os.path.join(p, f))
+        return rcpath
+    except pywintypes.error:
+        pass
     proc = win32api.GetCurrentProcess()
     try:
         # This will fail on windows < NT
--- a/mercurial/verify.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/mercurial/verify.py	Sun Dec 02 19:45:38 2007 +0100
@@ -62,9 +62,14 @@
         repo.ui.status(_("repository uses revlog format %d\n") %
                        (revlogv1 and 1 or 0))
 
+    havecl = havemf = 1
     seen = {}
     repo.ui.status(_("checking changesets\n"))
-    checksize(repo.changelog, "changelog")
+    if repo.changelog.count() == 0 and repo.manifest.count() > 1:
+        havecl = 0
+        err(0, _("empty or missing 00changelog.i"))
+    else:
+        checksize(repo.changelog, "changelog")
 
     for i in xrange(repo.changelog.count()):
         changesets += 1
@@ -96,14 +101,18 @@
 
     seen = {}
     repo.ui.status(_("checking manifests\n"))
-    checkversion(repo.manifest, "manifest")
-    checksize(repo.manifest, "manifest")
+    if repo.changelog.count() > 0 and repo.manifest.count() == 0:
+        havemf = 0
+        err(0, _("empty or missing 00manifest.i"))
+    else:
+        checkversion(repo.manifest, "manifest")
+        checksize(repo.manifest, "manifest")
 
     for i in xrange(repo.manifest.count()):
         n = repo.manifest.node(i)
         l = repo.manifest.linkrev(n)
 
-        if l < 0 or l >= repo.changelog.count():
+        if l < 0 or (havecl and l >= repo.changelog.count()):
             err(None, _("bad link (%d) at manifest revision %d") % (l, i))
 
         if n in neededmanifests:
@@ -132,38 +141,51 @@
 
     repo.ui.status(_("crosschecking files in changesets and manifests\n"))
 
-    nm = neededmanifests.items()
-    nm.sort()
-    for m, c in nm:
-        err(m, _("changeset refers to unknown manifest %s") % short(c))
-    del neededmanifests, nm
+    if havemf > 0:
+        nm = [(c, m) for m, c in neededmanifests.items()]
+        nm.sort()
+        for c, m in nm:
+            err(c, _("changeset refers to unknown manifest %s") % short(m))
+        del neededmanifests, nm
 
-    for f in filenodes:
-        if f not in filelinkrevs:
-            lrs = [repo.manifest.linkrev(n) for n in filenodes[f]]
-            lrs.sort()
-            err(lrs[0], _("in manifest but not in changeset"), f)
+    if havecl:
+        fl = filenodes.keys()
+        fl.sort()
+        for f in fl:
+            if f not in filelinkrevs:
+                lrs = [repo.manifest.linkrev(n) for n in filenodes[f]]
+                lrs.sort()
+                err(lrs[0], _("in manifest but not in changeset"), f)
+        del fl
 
-    for f in filelinkrevs:
-        if f not in filenodes:
-            lr = filelinkrevs[f][0]
-            err(lr, _("in changeset but not in manifest"), f)
+    if havemf:
+        fl = filelinkrevs.keys()
+        fl.sort()
+        for f in fl:
+            if f not in filenodes:
+                lr = filelinkrevs[f][0]
+                err(lr, _("in changeset but not in manifest"), f)
+        del fl
 
     repo.ui.status(_("checking files\n"))
-    ff = filenodes.keys()
+    ff = dict.fromkeys(filenodes.keys() + filelinkrevs.keys()).keys()
     ff.sort()
     for f in ff:
         if f == "/dev/null":
             continue
         files += 1
         if not f:
-            lr = repo.manifest.linkrev(filenodes[f][0])
-            err(lr, _("file without name in manifest %s") % short(ff[n]))
+            lr = filelinkrevs[f][0]
+            err(lr, _("file without name in manifest"))
             continue
         fl = repo.file(f)
         checkversion(fl, f)
         checksize(fl, f)
 
+        if fl.count() == 0:
+            err(filelinkrevs[f][0], _("empty or missing revlog"), f)
+            continue
+
         seen = {}
         nodes = {nullid: 1}
         for i in xrange(fl.count()):
@@ -171,7 +193,7 @@
             n = fl.node(i)
             flr = fl.linkrev(n)
 
-            if flr not in filelinkrevs.get(f, []):
+            if flr < 0 or (havecl and flr not in filelinkrevs.get(f, [])):
                 if flr < 0 or flr >= repo.changelog.count():
                     err(None, _("rev %d point to nonexistent changeset %d")
                         % (i, flr), f)
@@ -182,14 +204,16 @@
                     warn(_(" (expected %s)") % filelinkrevs[f][0])
                 flr = None # can't be trusted
             else:
-                filelinkrevs[f].remove(flr)
+                if havecl:
+                    filelinkrevs[f].remove(flr)
 
             if n in seen:
                 err(flr, _("duplicate revision %d") % i, f)
-            if n not in filenodes[f]:
-                err(flr, _("%s not in manifests") % (short(n)), f)
-            else:
-                del filenodes[f][n]
+            if f in filenodes:
+                if havemf and n not in filenodes[f]:
+                    err(flr, _("%s not in manifests") % (short(n)), f)
+                else:
+                    del filenodes[f][n]
 
             # verify contents
             try:
@@ -230,11 +254,12 @@
                     (short(n), inst), f)
 
         # cross-check
-        fns = [(repo.manifest.linkrev(filenodes[f][n]), n)
-               for n in filenodes[f]]
-        fns.sort()
-        for lr, node in fns:
-            err(lr, _("%s in manifests not found") % short(node), f)
+        if f in filenodes:
+            fns = [(repo.manifest.linkrev(filenodes[f][n]), n)
+                   for n in filenodes[f]]
+            fns.sort()
+            for lr, node in fns:
+                err(lr, _("%s in manifests not found") % short(node), f)
 
     repo.ui.status(_("%d files, %d changesets, %d total revisions\n") %
                    (files, changesets, revisions))
@@ -247,4 +272,3 @@
             repo.ui.warn(_("(first damaged changeset appears to be %d)\n")
                          % firstbad[0])
         return 1
-
--- a/templates/gitweb/notfound.tmpl	Sun Dec 02 19:39:27 2007 +0100
+++ b/templates/gitweb/notfound.tmpl	Sun Dec 02 19:45:38 2007 +0100
@@ -1,5 +1,5 @@
 {header}
-<title>Mercurial repositories index</title>
+<title>Mercurial repository not found</title>
 </head>
 
 <body>
--- a/templates/map-cmdline.default	Sun Dec 02 19:39:27 2007 +0100
+++ b/templates/map-cmdline.default	Sun Dec 02 19:45:38 2007 +0100
@@ -1,10 +1,13 @@
 changeset = 'changeset:   {rev}:{node|short}\n{branches}{tags}{parents}user:        {author}\ndate:        {date|date}\nsummary:     {desc|firstline}\n\n'
 changeset_quiet = '{rev}:{node|short}\n'
-changeset_verbose = 'changeset:   {rev}:{node|short}\n{branches}{tags}{parents}{manifest}user:        {author}\ndate:        {date|date}\n{files}{file_adds}{file_dels}{file_copies}description:\n{desc|strip}\n\n\n'
-changeset_debug = 'changeset:   {rev}:{node}\n{branches}{tags}{parents}{manifest}user:        {author}\ndate:        {date|date}\n{files}{file_adds}{file_dels}{file_copies}{extras}description:\n{desc|strip}\n\n\n'
+changeset_verbose = 'changeset:   {rev}:{node|short}\n{branches}{tags}{parents}user:        {author}\ndate:        {date|date}\n{files}{file_copies}description:\n{desc|strip}\n\n\n'
+changeset_debug = 'changeset:   {rev}:{node}\n{branches}{tags}{parents}{manifest}user:        {author}\ndate:        {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies}{extras}description:\n{desc|strip}\n\n\n'
 start_files = 'files:      '
 file = ' {file}'
 end_files = '\n'
+start_file_mods = 'files:      '
+file_mod = ' {file_mod}'
+end_file_mods = '\n'
 start_file_adds = 'files+:     '
 file_add = ' {file_add}'
 end_file_adds = '\n'
--- a/templates/notfound.tmpl	Sun Dec 02 19:39:27 2007 +0100
+++ b/templates/notfound.tmpl	Sun Dec 02 19:45:38 2007 +0100
@@ -1,9 +1,9 @@
 #header#
-<title>Mercurial repositories index</title>
+<title>Mercurial repository not found</title>
 </head>
 <body>
 
-<h2>Mercurial Repositories</h2>
+<h2>Mercurial repository not found</h2>
 
 The specified repository "#repo|escape#" is unknown, sorry.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/raw/error.tmpl	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,2 @@
+#header#
+error: #error#
--- a/templates/raw/map	Sun Dec 02 19:39:27 2007 +0100
+++ b/templates/raw/map	Sun Dec 02 19:45:38 2007 +0100
@@ -18,4 +18,6 @@
 manifestdirentry = 'drwxr-xr-x {basename}\n'
 manifestfileentry = '{permissions|permissions} {size} {basename}\n'
 index = index.tmpl
+notfound = notfound.tmpl
+error = error.tmpl
 indexentry = '#url#\n'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/raw/notfound.tmpl	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,2 @@
+#header#
+error: repository #repo# not found
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/static/highlight.css	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,59 @@
+.c { color: #808080 } /* Comment */
+.err { color: #F00000; background-color: #F0A0A0 } /* Error */
+.k { color: #008000; font-weight: bold } /* Keyword */
+.o { color: #303030 } /* Operator */
+.cm { color: #808080 } /* Comment.Multiline */
+.cp { color: #507090 } /* Comment.Preproc */
+.c1 { color: #808080 } /* Comment.Single */
+.cs { color: #cc0000; font-weight: bold } /* Comment.Special */
+.gd { color: #A00000 } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #FF0000 } /* Generic.Error */
+.gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.gi { color: #00A000 } /* Generic.Inserted */
+.go { color: #808080 } /* Generic.Output */
+.gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.gt { color: #0040D0 } /* Generic.Traceback */
+.kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */
+.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #303090; font-weight: bold } /* Keyword.Type */
+.m { color: #6000E0; font-weight: bold } /* Literal.Number */
+.s { background-color: #fff0f0 } /* Literal.String */
+.na { color: #0000C0 } /* Name.Attribute */
+.nb { color: #007020 } /* Name.Builtin */
+.nc { color: #B00060; font-weight: bold } /* Name.Class */
+.no { color: #003060; font-weight: bold } /* Name.Constant */
+.nd { color: #505050; font-weight: bold } /* Name.Decorator */
+.ni { color: #800000; font-weight: bold } /* Name.Entity */
+.ne { color: #F00000; font-weight: bold } /* Name.Exception */
+.nf { color: #0060B0; font-weight: bold } /* Name.Function */
+.nl { color: #907000; font-weight: bold } /* Name.Label */
+.nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.nt { color: #007000 } /* Name.Tag */
+.nv { color: #906030 } /* Name.Variable */
+.ow { color: #000000; font-weight: bold } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */
+.mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */
+.mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */
+.mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */
+.sb { background-color: #fff0f0 } /* Literal.String.Backtick */
+.sc { color: #0040D0 } /* Literal.String.Char */
+.sd { color: #D04020 } /* Literal.String.Doc */
+.s2 { background-color: #fff0f0 } /* Literal.String.Double */
+.se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */
+.sh { background-color: #fff0f0 } /* Literal.String.Heredoc */
+.si { background-color: #e0e0e0 } /* Literal.String.Interpol */
+.sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */
+.sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */
+.s1 { background-color: #fff0f0 } /* Literal.String.Single */
+.ss { color: #A06000 } /* Literal.String.Symbol */
+.bp { color: #007020 } /* Name.Builtin.Pseudo */
+.vc { color: #306090 } /* Name.Variable.Class */
+.vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */
+.vi { color: #3030B0 } /* Name.Variable.Instance */
+.il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */
--- a/tests/get-with-headers.py	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/get-with-headers.py	Sun Dec 02 19:45:38 2007 +0100
@@ -14,3 +14,7 @@
         print "%s: %s" % (h, response.getheader(h))
 print
 sys.stdout.write(response.read())
+
+if 200 <= response.status <= 299:
+    sys.exit(0)
+sys.exit(1)
--- a/tests/test-command-template	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-command-template	Sun Dec 02 19:45:38 2007 +0100
@@ -89,8 +89,8 @@
 cat changelog
 
 echo "# keys work"
-for key in author branches date desc file_adds file_dels files \
-        manifest node parents rev tags; do
+for key in author branches date desc file_adds file_dels file_mods \
+        files manifest node parents rev tags; do
     for mode in '' --verbose --debug; do
         hg log $mode --template "$key$mode: {$key}\n"
     done
--- a/tests/test-command-template.out	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-command-template.out	Sun Dec 02 19:45:38 2007 +0100
@@ -260,22 +260,22 @@
 other 3
 desc--debug: line 1
 line 2
-file_adds: 
-file_adds: 
+file_adds: second
 file_adds: 
-file_adds: 
-file_adds: 
-file_adds: 
+file_adds: d
 file_adds: 
 file_adds: 
+file_adds: c
+file_adds: b
+file_adds: a
+file_adds--verbose: second
 file_adds--verbose: 
-file_adds--verbose: 
+file_adds--verbose: d
 file_adds--verbose: 
 file_adds--verbose: 
-file_adds--verbose: 
-file_adds--verbose: 
-file_adds--verbose: 
-file_adds--verbose: 
+file_adds--verbose: c
+file_adds--verbose: b
+file_adds--verbose: a
 file_adds--debug: second
 file_adds--debug: 
 file_adds--debug: d
@@ -308,6 +308,30 @@
 file_dels--debug: 
 file_dels--debug: 
 file_dels--debug: 
+file_mods: 
+file_mods: 
+file_mods: 
+file_mods: 
+file_mods: c
+file_mods: 
+file_mods: 
+file_mods: 
+file_mods--verbose: 
+file_mods--verbose: 
+file_mods--verbose: 
+file_mods--verbose: 
+file_mods--verbose: c
+file_mods--verbose: 
+file_mods--verbose: 
+file_mods--verbose: 
+file_mods--debug: 
+file_mods--debug: 
+file_mods--debug: 
+file_mods--debug: 
+file_mods--debug: c
+file_mods--debug: 
+file_mods--debug: 
+file_mods--debug: 
 files: second
 files: 
 files: d
@@ -324,30 +348,30 @@
 files--verbose: c
 files--verbose: b
 files--verbose: a
-files--debug: 
+files--debug: second
 files--debug: 
-files--debug: 
+files--debug: d
 files--debug: 
 files--debug: c
-files--debug: 
-files--debug: 
-files--debug: 
-manifest: 
-manifest: 
-manifest: 
-manifest: 
-manifest: 
-manifest: 
-manifest: 
-manifest: 
-manifest--verbose: 
-manifest--verbose: 
-manifest--verbose: 
-manifest--verbose: 
-manifest--verbose: 
-manifest--verbose: 
-manifest--verbose: 
-manifest--verbose: 
+files--debug: c
+files--debug: b
+files--debug: a
+manifest: 7:f2dbc354b94e
+manifest: 6:91015e9dbdd7
+manifest: 5:4dc3def4f9b4
+manifest: 4:90ae8dda64e1
+manifest: 3:cb5a1327723b
+manifest: 2:6e0e82995c35
+manifest: 1:4e8d705b1e53
+manifest: 0:a0c8bcbbb45c
+manifest--verbose: 7:f2dbc354b94e
+manifest--verbose: 6:91015e9dbdd7
+manifest--verbose: 5:4dc3def4f9b4
+manifest--verbose: 4:90ae8dda64e1
+manifest--verbose: 3:cb5a1327723b
+manifest--verbose: 2:6e0e82995c35
+manifest--verbose: 1:4e8d705b1e53
+manifest--verbose: 0:a0c8bcbbb45c
 manifest--debug: 7:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
 manifest--debug: 6:91015e9dbdd76a6791085d12b0a0ec7fcd22ffbf
 manifest--debug: 5:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
--- a/tests/test-convert	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-convert	Sun Dec 02 19:45:38 2007 +0100
@@ -1,7 +1,11 @@
 #!/bin/sh
 
-echo "[extensions]" >> $HGRCPATH
-echo "convert=" >> $HGRCPATH
+cat >> $HGRCPATH <<EOF
+[extensions]
+convert=
+[convert]
+hg.saverev=False
+EOF
 
 hg help convert
 
--- a/tests/test-convert-cvs.out	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-convert-cvs.out	Sun Dec 02 19:45:38 2007 +0100
@@ -44,7 +44,6 @@
 checking in src/a,v
 checking in src/b/c,v
 % convert again
-destination src-hg is a Mercurial repository
 connecting to cvsrepo
 scanning source...
 sorting...
@@ -56,7 +55,6 @@
 c
 c
 % convert again with --filemap
-destination src-filemap is a Mercurial repository
 connecting to cvsrepo
 scanning source...
 sorting...
--- a/tests/test-convert-hg-sink	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-convert-hg-sink	Sun Dec 02 19:45:38 2007 +0100
@@ -1,7 +1,11 @@
 #!/bin/sh
 
-echo "[extensions]" >> $HGRCPATH
-echo "hgext.convert=" >> $HGRCPATH
+cat >> $HGRCPATH <<EOF
+[extensions]
+convert=
+[convert]
+hg.saverev=False
+EOF
 
 hg init orig
 cd orig
--- a/tests/test-convert-hg-sink.out	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-convert-hg-sink.out	Sun Dec 02 19:45:38 2007 +0100
@@ -37,7 +37,6 @@
 a   0         -1 unset             baz
 copy: bar -> baz
 % add a new revision in the original repo
-destination new is a Mercurial repository
 scanning source...
 sorting...
 converting...
--- a/tests/test-convert-hg-source	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-convert-hg-source	Sun Dec 02 19:45:38 2007 +0100
@@ -1,7 +1,11 @@
 #!/bin/sh
 
-echo "[extensions]" >> $HGRCPATH
-echo "hgext.convert=" >> $HGRCPATH
+cat >> $HGRCPATH <<EOF
+[extensions]
+convert=
+[convert]
+hg.saverev=False
+EOF
 
 hg init orig
 cd orig
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-hg-svn	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+"$TESTDIR/hghave" svn svn-bindings || exit 80
+
+fix_path()
+{
+    tr '\\' /
+}
+
+echo "[extensions]" >> $HGRCPATH
+echo "convert = " >> $HGRCPATH
+
+svnpath=`pwd`/svn-repo
+svnadmin create $svnpath
+
+cat > $svnpath/hooks/pre-revprop-change <<'EOF'
+#!/bin/sh
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
+
+echo "Changing prohibited revision property" >&2
+exit 1
+EOF
+chmod +x $svnpath/hooks/pre-revprop-change
+
+svnurl=file://$svnpath
+svn co $svnurl $svnpath-wc
+
+cd $svnpath-wc
+echo a > a
+svn add a
+svn ci -m'added a' a
+
+cd ..
+
+echo % initial roundtrip
+hg convert -s svn -d hg $svnpath-wc $svnpath-hg | grep -v initializing
+hg convert -s hg -d svn $svnpath-hg $svnpath-wc
+
+echo % second roundtrip should do nothing
+hg convert -s svn -d hg $svnpath-wc $svnpath-hg
+hg convert -s hg -d svn $svnpath-hg $svnpath-wc
+
+echo % new hg rev
+
+hg clone $svnpath-hg $svnpath-work
+echo b > $svnpath-work/b
+hg --cwd $svnpath-work add b
+hg --cwd $svnpath-work ci -mb
+
+echo % echo hg to svn
+hg --cwd $svnpath-hg pull -q $svnpath-work
+hg convert -s hg -d svn $svnpath-hg $svnpath-wc
+
+echo % svn back to hg should do nothing
+hg convert -s svn -d hg $svnpath-wc $svnpath-hg
+echo % hg back to svn should do nothing
+hg convert -s hg -d svn $svnpath-hg $svnpath-wc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-hg-svn.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,35 @@
+Checked out revision 0.
+A         a
+Adding         a
+Transmitting file data .
+Committed revision 1.
+% initial roundtrip
+scanning source...
+sorting...
+converting...
+0 added a
+scanning source...
+sorting...
+converting...
+% second roundtrip should do nothing
+scanning source...
+sorting...
+converting...
+scanning source...
+sorting...
+converting...
+% new hg rev
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% echo hg to svn
+scanning source...
+sorting...
+converting...
+0 b
+% svn back to hg should do nothing
+scanning source...
+sorting...
+converting...
+% hg back to svn should do nothing
+scanning source...
+sorting...
+converting...
--- a/tests/test-convert-svn	Sun Dec 02 19:39:27 2007 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-#!/bin/sh
-
-"$TESTDIR/hghave" svn svn-bindings || exit 80
-
-fix_path()
-{
-    tr '\\' /
-}
-
-echo "[extensions]" >> $HGRCPATH
-echo "convert = " >> $HGRCPATH
-
-svnadmin create svn-repo
-
-echo % initial svn import
-mkdir t
-cd t
-echo a > a
-cd ..
-
-svnpath=`pwd | fix_path`
-# SVN wants all paths to start with a slash. Unfortunately,
-# Windows ones don't. Handle that.
-expr $svnpath : "\/" > /dev/null
-if [ $? -ne 0 ]; then
-    svnpath='/'$svnpath
-fi
-
-svnurl=file://$svnpath/svn-repo/trunk
-svn import -m init t $svnurl | fix_path
-
-echo % update svn repository
-svn co $svnurl t2 | fix_path
-cd t2
-echo b >> a
-echo b > b
-svn add b
-svn ci -m changea
-cd ..
-
-echo % convert to hg once
-hg convert $svnurl
-
-echo % update svn repository again
-cd t2
-echo c >> a
-echo c >> b
-svn ci -m changeb
-cd ..
-
-echo % test incremental conversion
-hg convert $svnurl
-
-echo % test filemap
-echo 'include b' > filemap
-hg convert --filemap filemap $svnurl fmap
-echo '[extensions]' >> $HGRCPATH
-echo 'hgext.graphlog =' >> $HGRCPATH
-hg glog -R fmap --template '#rev# #desc|firstline# files: #files#\n'
-
-########################################
-
-echo "# now tests that it works with trunk/branches/tags layout"
-echo
-echo % initial svn import
-mkdir projA
-cd projA
-mkdir trunk
-mkdir branches
-mkdir tags
-cd ..
-
-svnurl=file://$svnpath/svn-repo/projA
-svn import -m "init projA" projA $svnurl | fix_path
-
-
-echo % update svn repository
-svn co $svnurl/trunk A | fix_path
-cd A
-echo hello > letter.txt
-svn add letter.txt
-svn ci -m hello
-
-echo world >> letter.txt
-svn ci -m world
-
-svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
-
-echo 'nice day today!' >> letter.txt
-svn ci -m "nice day"
-cd ..
-
-echo % convert to hg once
-hg convert $svnurl A-hg
-
-echo % update svn repository again
-cd A
-echo "see second letter" >> letter.txt
-echo "nice to meet you" > letter2.txt
-svn add letter2.txt
-svn ci -m "second letter"
-
-svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
-
-echo "blah-blah-blah" >> letter2.txt
-svn ci -m "work in progress"
-cd ..
-
-echo % test incremental conversion
-hg convert $svnurl A-hg
-
-cd A-hg
-hg glog --template '#rev# #desc|firstline# files: #files#\n'
-hg tags -q
-cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-sink	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+"$TESTDIR/hghave" svn svn-bindings || exit 80
+
+echo "[extensions]" >> $HGRCPATH
+echo "convert = " >> $HGRCPATH
+
+hg init a
+
+echo a > a/a
+mkdir -p a/d1/d2
+echo b > a/d1/d2/b
+echo % add
+hg --cwd a ci -d '0 0' -A -m 'add a file'
+
+echo a >> a/a
+echo % modify
+hg --cwd a ci -d '1 0' -m 'modify a file'
+hg --cwd a tip -q
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=2 | sed 's,<date>.*,<date/>,')
+ls a a-hg-wc
+cmp a/a a-hg-wc/a && echo same || echo different
+
+hg --cwd a mv a b
+echo % rename
+hg --cwd a ci -d '2 0' -m 'rename a file'
+hg --cwd a tip -q
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
+ls a a-hg-wc
+
+hg --cwd a cp b c
+echo % copy
+hg --cwd a ci -d '3 0' -m 'copy a file'
+hg --cwd a tip -q
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
+ls a a-hg-wc
+
+hg --cwd a rm b
+echo % remove
+hg --cwd a ci -d '4 0' -m 'remove a file'
+hg --cwd a tip -q
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
+ls a a-hg-wc
+
+chmod +x a/c
+echo % executable
+hg --cwd a ci -d '5 0' -m 'make a file executable'
+hg --cwd a tip -q
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
+test -x a-hg-wc/c && echo executable || echo not executable
+
+echo % branchy history
+
+hg init b
+echo base > b/b
+hg --cwd b ci -d '0 0' -Ambase
+
+echo left-1 >> b/b
+echo left-1 > b/left-1
+hg --cwd b ci -d '1 0' -Amleft-1
+
+echo left-2 >> b/b
+echo left-2 > b/left-2
+hg --cwd b ci -d '2 0' -Amleft-2
+
+hg --cwd b up 0
+
+echo right-1 >> b/b
+echo right-1 > b/right-1
+hg --cwd b ci -d '3 0' -Amright-1
+
+echo right-2 >> b/b
+echo right-2 > b/right-2
+hg --cwd b ci -d '4 0' -Amright-2
+
+hg --cwd b up -C 2
+hg --cwd b merge
+hg --cwd b revert -r 2 b
+hg --cwd b ci -d '5 0' -m 'merge'
+
+hg convert -d svn b
+echo % expect 4 changes
+(cd b-hg-wc; svn up; svn st -v; svn log --xml -v | sed 's,<date>.*,<date/>,')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-sink.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,281 @@
+% add
+adding a
+adding d1/d2/b
+% modify
+1:e0e2b8a9156b
+assuming destination a-hg
+initializing svn repo 'a-hg'
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+1 add a file
+0 modify a file
+At revision 2.
+                2        2 test         .
+                2        2 test         a
+                2        1 test         d1
+                2        1 test         d1/d2
+                2        1 test         d1/d2/b
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="2">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="M">/a</path>
+</paths>
+<msg>modify a file</msg>
+</logentry>
+<logentry
+   revision="1">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="A">/a</path>
+<path
+   action="A">/d1</path>
+<path
+   action="A">/d1/d2</path>
+<path
+   action="A">/d1/d2/b</path>
+</paths>
+<msg>add a file</msg>
+</logentry>
+</log>
+a:
+a
+d1
+
+a-hg-wc:
+a
+d1
+same
+% rename
+2:7009fc4efb34
+assuming destination a-hg
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+0 rename a file
+At revision 3.
+                3        3 test         .
+                3        3 test         b
+                3        1 test         d1
+                3        1 test         d1/d2
+                3        1 test         d1/d2/b
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="3">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="D">/a</path>
+<path
+   copyfrom-path="/a"
+   copyfrom-rev="2"
+   action="A">/b</path>
+</paths>
+<msg>rename a file</msg>
+</logentry>
+</log>
+a:
+b
+d1
+
+a-hg-wc:
+b
+d1
+% copy
+3:56c519973ce6
+assuming destination a-hg
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+0 copy a file
+At revision 4.
+                4        4 test         .
+                4        3 test         b
+                4        4 test         c
+                4        1 test         d1
+                4        1 test         d1/d2
+                4        1 test         d1/d2/b
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="4">
+<author>test</author>
+<date/>
+<paths>
+<path
+   copyfrom-path="/b"
+   copyfrom-rev="3"
+   action="A">/c</path>
+</paths>
+<msg>copy a file</msg>
+</logentry>
+</log>
+a:
+b
+c
+d1
+
+a-hg-wc:
+b
+c
+d1
+% remove
+4:ed4dc9a6f585
+assuming destination a-hg
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+0 remove a file
+At revision 5.
+                5        5 test         .
+                5        4 test         c
+                5        1 test         d1
+                5        1 test         d1/d2
+                5        1 test         d1/d2/b
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="5">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="D">/b</path>
+</paths>
+<msg>remove a file</msg>
+</logentry>
+</log>
+a:
+c
+d1
+
+a-hg-wc:
+c
+d1
+% executable
+5:f205b3636d77
+svn: Path 'b' does not exist
+assuming destination a-hg
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+0 make a file executable
+abort: svn exited with status 1
+At revision 5.
+                5        5 test         .
+ M              5        4 test         c
+                5        1 test         d1
+                5        1 test         d1/d2
+                5        1 test         d1/d2/b
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="5">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="D">/b</path>
+</paths>
+<msg>remove a file</msg>
+</logentry>
+</log>
+executable
+% branchy history
+adding b
+adding left-1
+adding left-2
+1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+adding right-1
+adding right-2
+3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+warning: conflicts during merge.
+merging b
+merging b failed!
+2 files updated, 0 files merged, 0 files removed, 1 files unresolved
+There are unresolved merges, you can redo the full merge using:
+  hg update -C 2
+  hg merge 4
+assuming destination b-hg
+initializing svn repo 'b-hg'
+initializing svn wc 'b-hg-wc'
+scanning source...
+sorting...
+converting...
+5 base
+4 left-1
+3 left-2
+2 right-1
+1 right-2
+0 merge
+% expect 4 changes
+At revision 4.
+                4        4 test         .
+                4        3 test         b
+                4        2 test         left-1
+                4        3 test         left-2
+                4        4 test         right-1
+                4        4 test         right-2
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="4">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="A">/right-1</path>
+<path
+   action="A">/right-2</path>
+</paths>
+<msg>merge</msg>
+</logentry>
+<logentry
+   revision="3">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="M">/b</path>
+<path
+   action="A">/left-2</path>
+</paths>
+<msg>left-2</msg>
+</logentry>
+<logentry
+   revision="2">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="M">/b</path>
+<path
+   action="A">/left-1</path>
+</paths>
+<msg>left-1</msg>
+</logentry>
+<logentry
+   revision="1">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="A">/b</path>
+</paths>
+<msg>base</msg>
+</logentry>
+</log>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-source	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+"$TESTDIR/hghave" svn svn-bindings || exit 80
+
+fix_path()
+{
+    tr '\\' /
+}
+
+echo "[extensions]" >> $HGRCPATH
+echo "convert = " >> $HGRCPATH
+
+svnadmin create svn-repo
+
+echo % initial svn import
+mkdir t
+cd t
+echo a > a
+cd ..
+
+svnpath=`pwd | fix_path`
+# SVN wants all paths to start with a slash. Unfortunately,
+# Windows ones don't. Handle that.
+expr $svnpath : "\/" > /dev/null
+if [ $? -ne 0 ]; then
+    svnpath='/'$svnpath
+fi
+
+svnurl=file://$svnpath/svn-repo/trunk
+svn import -m init t $svnurl | fix_path
+
+echo % update svn repository
+svn co $svnurl t2 | fix_path
+cd t2
+echo b >> a
+echo b > b
+svn add b
+svn ci -m changea
+cd ..
+
+echo % convert to hg once
+hg convert $svnurl
+
+echo % update svn repository again
+cd t2
+echo c >> a
+echo c >> b
+svn ci -m changeb
+cd ..
+
+echo % test incremental conversion
+hg convert $svnurl
+
+echo % test filemap
+echo 'include b' > filemap
+hg convert --filemap filemap $svnurl fmap
+echo '[extensions]' >> $HGRCPATH
+echo 'hgext.graphlog =' >> $HGRCPATH
+hg glog -R fmap --template '#rev# #desc|firstline# files: #files#\n'
+
+########################################
+
+echo "# now tests that it works with trunk/branches/tags layout"
+echo
+echo % initial svn import
+mkdir projA
+cd projA
+mkdir trunk
+mkdir branches
+mkdir tags
+cd ..
+
+svnurl=file://$svnpath/svn-repo/projA
+svn import -m "init projA" projA $svnurl | fix_path
+
+
+echo % update svn repository
+svn co $svnurl/trunk A | fix_path
+cd A
+echo hello > letter.txt
+svn add letter.txt
+svn ci -m hello
+
+echo world >> letter.txt
+svn ci -m world
+
+svn copy -m "tag v0.1" $svnurl/trunk $svnurl/tags/v0.1
+
+echo 'nice day today!' >> letter.txt
+svn ci -m "nice day"
+cd ..
+
+echo % convert to hg once
+hg convert $svnurl A-hg
+
+echo % update svn repository again
+cd A
+echo "see second letter" >> letter.txt
+echo "nice to meet you" > letter2.txt
+svn add letter2.txt
+svn ci -m "second letter"
+
+svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
+
+echo "blah-blah-blah" >> letter2.txt
+svn ci -m "work in progress"
+cd ..
+
+echo % test incremental conversion
+hg convert $svnurl A-hg
+
+cd A-hg
+hg glog --template '#rev# #desc|firstline# files: #files#\n'
+hg tags -q
+cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-source.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,112 @@
+% initial svn import
+Adding         t/a
+
+Committed revision 1.
+% update svn repository
+A    t2/a
+Checked out revision 1.
+A         b
+Sending        a
+Adding         b
+Transmitting file data ..
+Committed revision 2.
+% convert to hg once
+assuming destination trunk-hg
+initializing destination trunk-hg repository
+scanning source...
+sorting...
+converting...
+1 init
+0 changea
+% update svn repository again
+Sending        a
+Sending        b
+Transmitting file data ..
+Committed revision 3.
+% test incremental conversion
+assuming destination trunk-hg
+scanning source...
+sorting...
+converting...
+0 changeb
+% test filemap
+initializing destination fmap repository
+scanning source...
+sorting...
+converting...
+2 init
+1 changea
+0 changeb
+o  1 changeb files: b
+|
+o  0 changea files: b
+
+# now tests that it works with trunk/branches/tags layout
+
+% initial svn import
+Adding         projA/trunk
+Adding         projA/branches
+Adding         projA/tags
+
+Committed revision 4.
+% update svn repository
+Checked out revision 4.
+A         letter.txt
+Adding         letter.txt
+Transmitting file data .
+Committed revision 5.
+Sending        letter.txt
+Transmitting file data .
+Committed revision 6.
+
+Committed revision 7.
+Sending        letter.txt
+Transmitting file data .
+Committed revision 8.
+% convert to hg once
+initializing destination A-hg repository
+scanning source...
+sorting...
+converting...
+3 init projA
+2 hello
+1 world
+0 nice day
+updating tags
+% update svn repository again
+A         letter2.txt
+Sending        letter.txt
+Adding         letter2.txt
+Transmitting file data ..
+Committed revision 9.
+
+Committed revision 10.
+Sending        letter2.txt
+Transmitting file data .
+Committed revision 11.
+% test incremental conversion
+scanning source...
+sorting...
+converting...
+1 second letter
+0 work in progress
+updating tags
+o  7 update tags files: .hgtags
+|
+o  6 work in progress files: letter2.txt
+|
+o  5 second letter files: letter.txt letter2.txt
+|
+o  4 update tags files: .hgtags
+|
+o  3 nice day files: letter.txt
+|
+o  2 world files: letter.txt
+|
+o  1 hello files: letter.txt
+|
+o  0 init projA files:
+
+tip
+v0.2
+v0.1
--- a/tests/test-convert-svn.out	Sun Dec 02 19:39:27 2007 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-% initial svn import
-Adding         t/a
-
-Committed revision 1.
-% update svn repository
-A    t2/a
-Checked out revision 1.
-A         b
-Sending        a
-Adding         b
-Transmitting file data ..
-Committed revision 2.
-% convert to hg once
-assuming destination trunk-hg
-initializing destination trunk-hg repository
-scanning source...
-sorting...
-converting...
-1 init
-0 changea
-% update svn repository again
-Sending        a
-Sending        b
-Transmitting file data ..
-Committed revision 3.
-% test incremental conversion
-assuming destination trunk-hg
-destination trunk-hg is a Mercurial repository
-scanning source...
-sorting...
-converting...
-0 changeb
-% test filemap
-initializing destination fmap repository
-scanning source...
-sorting...
-converting...
-2 init
-1 changea
-0 changeb
-o  1 changeb files: b
-|
-o  0 changea files: b
-
-# now tests that it works with trunk/branches/tags layout
-
-% initial svn import
-Adding         projA/trunk
-Adding         projA/branches
-Adding         projA/tags
-
-Committed revision 4.
-% update svn repository
-Checked out revision 4.
-A         letter.txt
-Adding         letter.txt
-Transmitting file data .
-Committed revision 5.
-Sending        letter.txt
-Transmitting file data .
-Committed revision 6.
-
-Committed revision 7.
-Sending        letter.txt
-Transmitting file data .
-Committed revision 8.
-% convert to hg once
-initializing destination A-hg repository
-scanning source...
-sorting...
-converting...
-3 init projA
-2 hello
-1 world
-0 nice day
-updating tags
-% update svn repository again
-A         letter2.txt
-Sending        letter.txt
-Adding         letter2.txt
-Transmitting file data ..
-Committed revision 9.
-
-Committed revision 10.
-Sending        letter2.txt
-Transmitting file data .
-Committed revision 11.
-% test incremental conversion
-destination A-hg is a Mercurial repository
-scanning source...
-sorting...
-converting...
-1 second letter
-0 work in progress
-updating tags
-o  7 update tags files: .hgtags
-|
-o  6 work in progress files: letter2.txt
-|
-o  5 second letter files: letter.txt letter2.txt
-|
-o  4 update tags files: .hgtags
-|
-o  3 nice day files: letter.txt
-|
-o  2 world files: letter.txt
-|
-o  1 hello files: letter.txt
-|
-o  0 init projA files:
-
-tip
-v0.2
-v0.1
--- a/tests/test-convert.out	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-convert.out	Sun Dec 02 19:45:38 2007 +0100
@@ -11,6 +11,7 @@
 
     Accepted destination formats:
     - Mercurial
+    - Subversion (history on branches is not preserved)
 
     If no revision is given, all revisions will be converted. Otherwise,
     convert will only import up to the named revision (given in a format
@@ -54,6 +55,24 @@
     subdirectory into the root of the repository, use '.' as the path to
     rename to.
 
+    Back end options:
+
+    --config convert.hg.clonebranches=False   (boolean)
+        hg target: XXX not documented
+    --config convert.hg.saverev=True          (boolean)
+        hg source: allow target to preserve source revision ID
+    --config convert.hg.tagsbranch=default    (branch name)
+        hg target: XXX not documented
+    --config convert.hg.usebranchnames=True   (boolean)
+        hg target: preserve branch names
+
+    --config convert.svn.branches=branches    (directory name)
+        svn source: specify the directory containing branches
+    --config convert.svn.tags=tags            (directory name)
+        svn source: specify the directory containing tags
+    --config convert.svn.trunk=trunk          (directory name)
+        svn source: specify the name of the trunk branch
+
 options:
 
  -A --authors      username mapping filename
--- a/tests/test-execute-bit	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-execute-bit	Sun Dec 02 19:45:38 2007 +0100
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+"$TESTDIR/hghave" execbit || exit 80
+
 hg init
 echo a > a
 hg ci -d'0 0' -Am'not executable'
--- a/tests/test-hgweb	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-hgweb	Sun Dec 02 19:45:38 2007 +0100
@@ -1,4 +1,5 @@
 #!/bin/sh
+# Some tests for hgweb. Tests static files, plain files and different 404's.
 
 hg init test
 cd test
@@ -7,7 +8,25 @@
 echo foo > foo
 hg ci -Ambase -d '0 0'
 hg serve -p $HGPORT -d --pid-file=hg.pid
+cat hg.pid >> $DAEMON_PIDS
 echo % manifest
 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/?style=raw')
 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/da?style=raw')
-kill `cat hg.pid`
+
+echo % plain file
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?style=raw'
+
+echo % should give a 404 - static file that does not exist
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/bogus'
+
+echo % should give a 404 - bad revision
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/spam/foo?style=raw'
+
+echo % should give a 400 - bad command
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/foo?cmd=spam&style=raw' | sed 's/400.*/400/'
+
+echo % should give a 404 - file does not exist
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/bork?style=raw'
+
+echo % static file
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/style-gitweb.css'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-no-request-uri	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,77 @@
+#!/bin/sh
+# This tests if hgweb and hgwebdir still work if the REQUEST_URI variable is
+# no longer passed with the request. Instead, SCRIPT_NAME and PATH_INFO
+# should be used from d74fc8dec2b4 onward to route the request.
+
+mkdir repo
+cd repo
+hg init
+echo foo > bar
+hg add bar
+hg commit -m "test" -d "0 0" -u "Testing"
+hg tip
+
+cat > request.py <<EOF
+from mercurial.hgweb import hgweb, hgwebdir
+from StringIO import StringIO
+import os, sys
+
+errors = StringIO()
+input = StringIO()
+
+def startrsp(headers, data):
+	print '---- HEADERS'
+	print headers
+	print '---- DATA'
+	print data
+	return output.write
+
+env = {
+	'wsgi.version': (1, 0),
+	'wsgi.url_scheme': 'http',
+	'wsgi.errors': errors,
+	'wsgi.input': input,
+	'wsgi.multithread': False,
+	'wsgi.multiprocess': False,
+	'wsgi.run_once': False,
+	'REQUEST_METHOD': 'GET',
+	'SCRIPT_NAME': '',
+	'SERVER_NAME': '127.0.0.1',
+	'SERVER_PORT': os.environ['HGPORT'],
+	'SERVER_PROTOCOL': 'HTTP/1.0'
+}
+
+output = StringIO()
+env['PATH_INFO'] = '/'
+env['QUERY_STRING'] = 'style=atom'
+hgweb('.', name = 'repo')(env, startrsp)
+print output.getvalue()
+print '---- ERRORS'
+print errors.getvalue()
+
+output = StringIO()
+env['PATH_INFO'] = '/file/tip/'
+env['QUERY_STRING'] = 'style=raw'
+hgweb('.', name = 'repo')(env, startrsp)
+print output.getvalue()
+print '---- ERRORS'
+print errors.getvalue()
+
+output = StringIO()
+env['PATH_INFO'] = '/'
+env['QUERY_STRING'] = 'style=raw'
+hgwebdir({'repo': '.'})(env, startrsp)
+print output.getvalue()
+print '---- ERRORS'
+print errors.getvalue()
+
+output = StringIO()
+env['PATH_INFO'] = '/repo/file/tip/'
+env['QUERY_STRING'] = 'style=raw'
+hgwebdir({'repo': '.'})(env, startrsp)
+print output.getvalue()
+print '---- ERRORS'
+print errors.getvalue()
+EOF
+
+python request.py | sed "s/http:\/\/127\.0\.0\.1:[0-9]*\//http:\/\/127.0.0.1\//"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-no-request-uri.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,72 @@
+changeset:   0:4cbec7e6f8c4
+tag:         tip
+user:        Testing
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     test
+
+---- HEADERS
+200 Script output follows
+---- DATA
+[('content-type', 'application/atom+xml; charset=ascii')]
+<?xml version="1.0" encoding="ascii"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <!-- Changelog -->
+ <id>http://127.0.0.1/</id>
+ <link rel="self" href="http://127.0.0.1/atom-log"/>
+ <link rel="alternate" href="http://127.0.0.1/"/>
+ <title>repo Changelog</title>
+ <updated>1970-01-01T00:00:00+00:00</updated>
+
+ <entry>
+  <title>test</title>
+  <id>http://www.selenic.com/mercurial/#changeset-4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e</id>
+  <link href="http://127.0.0.1/rev/4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e"/>
+  <author>
+   <name>Testing</name>
+   <email>&#84;&#101;&#115;&#116;&#105;&#110;&#103;</email>
+  </author>
+  <updated>1970-01-01T00:00:00+00:00</updated>
+  <published>1970-01-01T00:00:00+00:00</published>
+  <content type="xhtml">
+   <div xmlns="http://www.w3.org/1999/xhtml">
+    <pre xml:space="preserve">test</pre>
+   </div>
+  </content>
+ </entry>
+
+</feed>
+
+---- ERRORS
+
+---- HEADERS
+200 Script output follows
+---- DATA
+[('content-type', 'text/plain; charset=ascii')]
+
+-rw-r--r-- 4 bar
+
+
+
+---- ERRORS
+
+---- HEADERS
+200 Script output follows
+---- DATA
+[('content-type', 'text/plain; charset=ascii')]
+
+/repo/
+
+
+---- ERRORS
+
+---- HEADERS
+200 Script output follows
+---- DATA
+[('content-type', 'text/plain; charset=ascii')]
+
+-rw-r--r-- 4 bar
+
+
+
+---- ERRORS
+
--- a/tests/test-hgweb.out	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-hgweb.out	Sun Dec 02 19:45:38 2007 +0100
@@ -14,3 +14,123 @@
 -rw-r--r-- 4 foo
 
 
+% plain file
+200 Script output follows
+
+foo
+% should give a 404 - static file that does not exist
+404 Not Found
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<link rel="icon" href="/static/hgicon.png" type="image/png">
+<meta name="robots" content="index, nofollow" />
+<link rel="stylesheet" href="/static/style.css" type="text/css" />
+
+<title>Mercurial Error</title>
+</head>
+<body>
+
+<h2>Mercurial Error</h2>
+
+<p>
+An error occured while processing your request:
+</p>
+<p>
+Not Found
+</p>
+
+
+<div class="logo">
+powered by<br/>
+<a href="http://www.selenic.com/mercurial/">mercurial</a>
+</div>
+
+</body>
+</html>
+
+% should give a 404 - bad revision
+404 Not Found
+
+
+error: revision not found: spam
+% should give a 400 - bad command
+400
+
+
+error: No such method: spam
+% should give a 404 - file does not exist
+404 Not Found
+
+
+error: Path not found: bork/
+% static file
+200 Script output follows
+
+body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
+a { color:#0000cc; }
+a:hover, a:visited, a:active { color:#880000; }
+div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
+div.page_header a:visited { color:#0000cc; }
+div.page_header a:hover { color:#880000; }
+div.page_nav { padding:8px; }
+div.page_nav a:visited { color:#0000cc; }
+div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
+div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
+div.page_footer_text { float:left; color:#555555; font-style:italic; }
+div.page_body { padding:8px; }
+div.title, a.title {
+	display:block; padding:6px 8px;
+	font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
+}
+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;
+	font-size:10px; font-family:sans-serif; font-style:normal;
+	position:relative; float:left; width:136px;
+}
+div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
+a.list { text-decoration:none; color:#000000; }
+a.list:hover { text-decoration:underline; color:#880000; }
+table { padding:8px 4px; }
+th { padding:2px 5px; font-size:12px; text-align:left; }
+tr.light:hover, .parity0:hover { background-color:#edece6; }
+tr.dark, .parity1 { background-color:#f6f6f0; }
+tr.dark:hover, .parity1:hover { background-color:#edece6; }
+td { padding:2px 5px; font-size:12px; vertical-align:top; }
+td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
+div.pre { font-family:monospace; font-size:12px; white-space:pre; }
+div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
+div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
+div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
+.linenr { color:#999999; text-decoration:none }
+a.rss_logo {
+	float:right; padding:3px 0px; width:35px; line-height:10px;
+	border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
+	color:#ffffff; background-color:#ff6600;
+	font-weight:bold; font-family:sans-serif; font-size:10px;
+	text-align:center; text-decoration:none;
+}
+a.rss_logo:hover { background-color:#ee5500; }
+pre { margin: 0; }
+span.logtags span {
+	padding: 0px 4px;
+	font-size: 10px;
+	font-weight: normal;
+	border: 1px solid;
+	background-color: #ffaaff;
+	border-color: #ffccff #ff00ee #ff00ee #ffccff;
+}
+span.logtags span.tagtag {
+	background-color: #ffffaa;
+	border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
+}
+span.logtags span.branchtag {
+	background-color: #aaffaa;
+	border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgwebdir	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,77 @@
+#!/bin/sh
+# Tests some basic hgwebdir functionality. Tests setting up paths and
+# collection, different forms of 404s and the subdirectory support.
+
+mkdir webdir
+cd webdir
+
+hg init a
+echo a > a/a
+hg --cwd a ci -Ama -d'1 0'
+
+hg init b
+echo b > b/b
+hg --cwd b ci -Amb -d'2 0'
+
+hg init c
+echo c > c/c
+hg --cwd c ci -Amc -d'3 0'
+root=`pwd`
+
+cd ..
+
+cat > paths.conf <<EOF
+[paths]
+a=$root/a
+b=$root/b
+EOF
+
+hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
+    -A access-paths.log -E error-paths.log
+cat hg.pid >> $DAEMON_PIDS
+
+echo % should give a 404 - file does not exist
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
+
+echo % should succeed
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
+
+echo % should give a 404 - repo is not published
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
+
+cat > paths.conf <<EOF
+[paths]
+t/a=$root/a
+b=$root/b
+EOF
+
+hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
+    -A access-paths.log -E error-paths.log
+cat hg.pid >> $DAEMON_PIDS
+
+echo % should succeed, slashy names
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a?style=atom' \
+	| sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/?style=atom' \
+	| sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/file/tip/a?style=raw'
+
+cat > collections.conf <<EOF
+[collections]
+$root=$root
+EOF
+
+hg serve -p $HGPORT2 -d --pid-file=hg.pid --webdir-conf collections.conf \
+    -A access-collections.log -E error-collections.log
+cat hg.pid >> $DAEMON_PIDS
+
+echo % should succeed
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/file/tip/a?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/b/file/tip/b?style=raw'
+"$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/c/file/tip/c?style=raw'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgwebdir.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,121 @@
+adding a
+adding b
+adding c
+% should give a 404 - file does not exist
+404 Not Found
+
+
+error: Path not found: bork/
+% should succeed
+200 Script output follows
+
+
+/a/
+/b/
+
+200 Script output follows
+
+a
+200 Script output follows
+
+b
+% should give a 404 - repo is not published
+404 Not Found
+
+
+error: repository c not found
+% should succeed, slashy names
+200 Script output follows
+
+
+/b/
+/t/a/
+
+200 Script output follows
+
+
+/t/a/
+
+200 Script output follows
+
+
+/t/a/
+
+200 Script output follows
+
+<?xml version="1.0" encoding="ascii"?>
+<feed xmlns="http://127.0.0.1/2005/Atom">
+ <!-- Changelog -->
+ <id>http://127.0.0.1/t/a/</id>
+ <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
+ <link rel="alternate" href="http://127.0.0.1/t/a/"/>
+ <title>t/a Changelog</title>
+ <updated>1970-01-01T00:00:01+00:00</updated>
+
+ <entry>
+  <title>a</title>
+  <id>http://127.0.0.1/mercurial/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
+  <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
+  <author>
+   <name>test</name>
+   <email>&#116;&#101;&#115;&#116;</email>
+  </author>
+  <updated>1970-01-01T00:00:01+00:00</updated>
+  <published>1970-01-01T00:00:01+00:00</published>
+  <content type="xhtml">
+   <div xmlns="http://127.0.0.1/1999/xhtml">
+    <pre xml:space="preserve">a</pre>
+   </div>
+  </content>
+ </entry>
+
+</feed>
+200 Script output follows
+
+<?xml version="1.0" encoding="ascii"?>
+<feed xmlns="http://127.0.0.1/2005/Atom">
+ <!-- Changelog -->
+ <id>http://127.0.0.1/t/a/</id>
+ <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
+ <link rel="alternate" href="http://127.0.0.1/t/a/"/>
+ <title>t/a Changelog</title>
+ <updated>1970-01-01T00:00:01+00:00</updated>
+
+ <entry>
+  <title>a</title>
+  <id>http://127.0.0.1/mercurial/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
+  <link href="http://127.0.0.1/t/a/rev/8580ff50825a50c8f716709acdf8de0deddcd6ab"/>
+  <author>
+   <name>test</name>
+   <email>&#116;&#101;&#115;&#116;</email>
+  </author>
+  <updated>1970-01-01T00:00:01+00:00</updated>
+  <published>1970-01-01T00:00:01+00:00</published>
+  <content type="xhtml">
+   <div xmlns="http://127.0.0.1/1999/xhtml">
+    <pre xml:space="preserve">a</pre>
+   </div>
+  </content>
+ </entry>
+
+</feed>
+200 Script output follows
+
+a
+% should succeed
+200 Script output follows
+
+
+/a/
+/b/
+/c/
+
+200 Script output follows
+
+a
+200 Script output follows
+
+b
+200 Script output follows
+
+c
--- a/tests/test-import	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-import	Sun Dec 02 19:45:38 2007 +0100
@@ -57,7 +57,7 @@
 cat > mkmsg.py <<EOF
 import email.Message, sys
 msg = email.Message.Message()
-msg.set_payload('email commit message\n' + open('tip.patch').read())
+msg.set_payload('email commit message\n' + open('tip.patch', 'rb').read())
 msg['Subject'] = 'email patch'
 msg['From'] = 'email patcher'
 sys.stdout.write(msg.as_string())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-merge-types	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+hg init
+echo a > a
+hg ci -Amadd
+
+chmod +x a
+hg ci -mexecutable
+
+hg up 0
+rm a
+ln -s symlink a
+hg ci -msymlink
+
+hg merge
+
+echo % symlink is left parent, executable is right
+
+if [ -L a ]; then
+    echo a is a symlink
+    readlink a
+elif [ -x a ]; then
+    echo a is executable
+fi
+
+hg update -C 1
+hg merge
+
+echo % symlink is right parent, executable is left
+
+if [ -L a ]; then
+    echo a is a symlink
+    readlink a
+elif [ -x a ]; then
+    echo a is executable
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-merge-types.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,1 @@
+### This test is for a known, unfixed bug ###
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-newcgi	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,91 @@
+#!/bin/sh
+# This tests if CGI files from after d0db3462d568 but
+# before d74fc8dec2b4 still work.
+
+hg init test
+
+cat >hgweb.cgi <<HGWEB
+#!/usr/bin/env python
+#
+# An example CGI script to use hgweb, edit as necessary
+
+import cgitb
+cgitb.enable()
+
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb import hgweb
+from mercurial.hgweb import wsgicgi
+from mercurial.hgweb.request import wsgiapplication
+
+def make_web_app():
+	return hgweb("test", "Empty test repository")
+
+wsgicgi.launch(wsgiapplication(make_web_app))
+HGWEB
+chmod 755 hgweb.cgi
+
+cat >hgweb.config <<HGWEBDIRCONF
+[paths]
+test = test
+HGWEBDIRCONF
+
+cat >hgwebdir.cgi <<HGWEBDIR
+#!/usr/bin/env python
+#
+# An example CGI script to export multiple hgweb repos, edit as necessary
+
+import cgitb
+cgitb.enable()
+
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb import hgwebdir
+from mercurial.hgweb import wsgicgi
+from mercurial.hgweb.request import wsgiapplication
+
+def make_web_app():
+	return hgwebdir("hgweb.config")
+
+wsgicgi.launch(wsgiapplication(make_web_app))
+HGWEBDIR
+chmod 755 hgwebdir.cgi
+
+DOCUMENT_ROOT="/var/www/hg"; export DOCUMENT_ROOT
+GATEWAY_INTERFACE="CGI/1.1"; export GATEWAY_INTERFACE
+HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; export HTTP_ACCEPT
+HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"; export HTTP_ACCEPT_CHARSET
+HTTP_ACCEPT_ENCODING="gzip,deflate"; export HTTP_ACCEPT_ENCODING
+HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"; export HTTP_ACCEPT_LANGUAGE
+HTTP_CACHE_CONTROL="max-age=0"; export HTTP_CACHE_CONTROL
+HTTP_CONNECTION="keep-alive"; export HTTP_CONNECTION
+HTTP_HOST="hg.omnifarious.org"; export HTTP_HOST
+HTTP_KEEP_ALIVE="300"; export HTTP_KEEP_ALIVE
+HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"; export HTTP_USER_AGENT
+PATH_INFO="/"; export PATH_INFO
+PATH_TRANSLATED="/var/www/hg/index.html"; export PATH_TRANSLATED
+QUERY_STRING=""; export QUERY_STRING
+REMOTE_ADDR="127.0.0.2"; export REMOTE_ADDR
+REMOTE_PORT="44703"; export REMOTE_PORT
+REQUEST_METHOD="GET"; export REQUEST_METHOD
+REQUEST_URI="/test/"; export REQUEST_URI
+SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"; export SCRIPT_FILENAME
+SCRIPT_NAME="/test"; export SCRIPT_NAME
+SCRIPT_URI="http://hg.omnifarious.org/test/"; export SCRIPT_URI
+SCRIPT_URL="/test/"; export SCRIPT_URL
+SERVER_ADDR="127.0.0.1"; export SERVER_ADDR
+SERVER_ADMIN="eric@localhost"; export SERVER_ADMIN
+SERVER_NAME="hg.omnifarious.org"; export SERVER_NAME
+SERVER_PORT="80"; export SERVER_PORT
+SERVER_PROTOCOL="HTTP/1.1"; export SERVER_PROTOCOL
+SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>\; export SERVER_SIGNATURE
+"
+SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"; export SERVER_SOFTWARE
+python hgweb.cgi >page1 2>&1 ; echo $?
+python hgwebdir.cgi >page2 2>&1 ; echo $?
+PATH_INFO="/test/"
+PATH_TRANSLATED="/var/something/test.cgi"
+REQUEST_URI="/test/test/"
+SCRIPT_URI="http://hg.omnifarious.org/test/test/"
+SCRIPT_URL="/test/test/"
+python hgwebdir.cgi >page3 2>&1 ; echo $?
+fgrep -i error page1 page2 page3 && exit 1
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-newcgi.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,3 @@
+0
+0
+0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-newercgi	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,84 @@
+#!/bin/sh
+# This is a rudimentary test of the CGI files as of d74fc8dec2b4.
+
+hg init test
+
+cat >hgweb.cgi <<HGWEB
+#!/usr/bin/env python
+#
+# An example CGI script to use hgweb, edit as necessary
+
+import cgitb
+cgitb.enable()
+
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb import hgweb
+from mercurial.hgweb import wsgicgi
+
+application = hgweb("test", "Empty test repository")
+wsgicgi.launch(application)
+HGWEB
+chmod 755 hgweb.cgi
+
+cat >hgweb.config <<HGWEBDIRCONF
+[paths]
+test = test
+HGWEBDIRCONF
+
+cat >hgwebdir.cgi <<HGWEBDIR
+#!/usr/bin/env python
+#
+# An example CGI script to export multiple hgweb repos, edit as necessary
+
+import cgitb
+cgitb.enable()
+
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb import hgwebdir
+from mercurial.hgweb import wsgicgi
+
+application = hgwebdir("hgweb.config")
+wsgicgi.launch(application)
+HGWEBDIR
+chmod 755 hgwebdir.cgi
+
+DOCUMENT_ROOT="/var/www/hg"; export DOCUMENT_ROOT
+GATEWAY_INTERFACE="CGI/1.1"; export GATEWAY_INTERFACE
+HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; export HTTP_ACCEPT
+HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"; export HTTP_ACCEPT_CHARSET
+HTTP_ACCEPT_ENCODING="gzip,deflate"; export HTTP_ACCEPT_ENCODING
+HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"; export HTTP_ACCEPT_LANGUAGE
+HTTP_CACHE_CONTROL="max-age=0"; export HTTP_CACHE_CONTROL
+HTTP_CONNECTION="keep-alive"; export HTTP_CONNECTION
+HTTP_HOST="hg.omnifarious.org"; export HTTP_HOST
+HTTP_KEEP_ALIVE="300"; export HTTP_KEEP_ALIVE
+HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"; export HTTP_USER_AGENT
+PATH_INFO="/"; export PATH_INFO
+PATH_TRANSLATED="/var/www/hg/index.html"; export PATH_TRANSLATED
+QUERY_STRING=""; export QUERY_STRING
+REMOTE_ADDR="127.0.0.2"; export REMOTE_ADDR
+REMOTE_PORT="44703"; export REMOTE_PORT
+REQUEST_METHOD="GET"; export REQUEST_METHOD
+REQUEST_URI="/test/"; export REQUEST_URI
+SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"; export SCRIPT_FILENAME
+SCRIPT_NAME="/test"; export SCRIPT_NAME
+SCRIPT_URI="http://hg.omnifarious.org/test/"; export SCRIPT_URI
+SCRIPT_URL="/test/"; export SCRIPT_URL
+SERVER_ADDR="127.0.0.1"; export SERVER_ADDR
+SERVER_ADMIN="eric@localhost"; export SERVER_ADMIN
+SERVER_NAME="hg.omnifarious.org"; export SERVER_NAME
+SERVER_PORT="80"; export SERVER_PORT
+SERVER_PROTOCOL="HTTP/1.1"; export SERVER_PROTOCOL
+SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>\; export SERVER_SIGNATURE
+"
+SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"; export SERVER_SOFTWARE
+python hgweb.cgi >page1 2>&1 ; echo $?
+python hgwebdir.cgi >page2 2>&1 ; echo $?
+PATH_INFO="/test/"
+PATH_TRANSLATED="/var/something/test.cgi"
+REQUEST_URI="/test/test/"
+SCRIPT_URI="http://hg.omnifarious.org/test/test/"
+SCRIPT_URL="/test/test/"
+python hgwebdir.cgi >page3 2>&1 ; echo $?
+fgrep -i error page1 page2 page3 && exit 1
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-newercgi.out	Sun Dec 02 19:45:38 2007 +0100
@@ -0,0 +1,3 @@
+0
+0
+0
--- a/tests/test-non-interactive-wsgi	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-non-interactive-wsgi	Sun Dec 02 19:45:38 2007 +0100
@@ -1,4 +1,6 @@
 #!/bin/sh
+# Tests if hgweb can run without touching sys.stdin, as is required
+# by the WSGI standard and strictly implemented by mod_wsgi.
 
 mkdir repo
 cd repo
@@ -11,7 +13,6 @@
 cat > request.py <<EOF
 from mercurial import dispatch
 from mercurial.hgweb.hgweb_mod import hgweb
-from mercurial.hgweb.request import _wsgirequest
 from mercurial.ui import ui
 from mercurial import hg
 from StringIO import StringIO
@@ -62,7 +63,7 @@
 	'SERVER_PROTOCOL': 'HTTP/1.0'
 }
 
-_wsgirequest(hgweb('.'), env, startrsp)
+hgweb('.')(env, startrsp)
 print '---- ERRORS'
 print errors.getvalue()
 EOF
--- a/tests/test-oldcgi	Sun Dec 02 19:39:27 2007 +0100
+++ b/tests/test-oldcgi	Sun Dec 02 19:45:38 2007 +0100
@@ -1,4 +1,5 @@
 #!/bin/sh
+# This tests if CGI files from before d0db3462d568 still work.
 
 hg init test