changeset 5551:dccc127bfc07

Merge with crew-stable
author Patrick Mezard <pmezard@gmail.com>
date Sun, 25 Nov 2007 12:42:06 +0100
parents db6633f11d59 (diff) 1fb38ef1f113 (current diff)
children edfbbb225653
files tests/test-import
diffstat 29 files changed, 1337 insertions(+), 373 deletions(-) [+]
line wrap: on
line diff
--- a/CONTRIBUTORS	Sun Nov 25 12:17:30 2007 +0100
+++ b/CONTRIBUTORS	Sun Nov 25 12:42:06 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 Nov 25 12:17:30 2007 +0100
+++ b/contrib/bash_completion	Sun Nov 25 12:42:06 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 Nov 25 12:17:30 2007 +0100
+++ b/contrib/hgk	Sun Nov 25 12:42:06 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/hg	Sun Nov 25 12:17:30 2007 +0100
+++ b/hg	Sun Nov 25 12:42:06 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 Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/__init__.py	Sun Nov 25 12:42:06 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,13 @@
                         self.dest.copyfile(copyf, f)
 
         newnode = self.dest.putcommit(filenames, parents, commit)
-        self.mapentry(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 +263,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 +274,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 +288,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
--- a/hgext/convert/common.py	Sun Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/common.py	Sun Nov 25 12:42:06 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'
@@ -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):
@@ -184,3 +188,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 Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/cvs.py	Sun Nov 25 12:42:06 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 Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/darcs.py	Sun Nov 25 12:42:06 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 Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/filemap.py	Sun Nov 25 12:42:06 2007 +0100
@@ -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:
--- a/hgext/convert/git.py	Sun Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/git.py	Sun Nov 25 12:42:06 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/subversion.py	Sun Nov 25 12:17:30 2007 +0100
+++ b/hgext/convert/subversion.py	Sun Nov 25 12:42:06 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,9 @@
         self.head = self.revid(self.last_changed)
         self._changescache = None
 
-    def setrevmap(self, revmap, order):
+    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:
@@ -664,3 +669,213 @@
         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)
+
+    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 putcommit(self, files, parents, commit):
+        for parent in parents:
+            try:
+                return 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 rev
+        finally:
+            os.unlink(messagefile)
+
+    def puttags(self, tags):
+        self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
--- a/hgext/gpg.py	Sun Nov 25 12:17:30 2007 +0100
+++ b/hgext/gpg.py	Sun Nov 25 12:42:06 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 Nov 25 12:42:06 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/mercurial/cmdutil.py	Sun Nov 25 12:17:30 2007 +0100
+++ b/mercurial/cmdutil.py	Sun Nov 25 12:42:06 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/templates/map-cmdline.default	Sun Nov 25 12:17:30 2007 +0100
+++ b/templates/map-cmdline.default	Sun Nov 25 12:42:06 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'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/static/highlight.css	Sun Nov 25 12:42:06 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/test-command-template	Sun Nov 25 12:17:30 2007 +0100
+++ b/tests/test-command-template	Sun Nov 25 12:42:06 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 Nov 25 12:17:30 2007 +0100
+++ b/tests/test-command-template.out	Sun Nov 25 12:42:06 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-svn	Sun Nov 25 12:17:30 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 Nov 25 12:42:06 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 Nov 25 12:42:06 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 Nov 25 12:42:06 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 Nov 25 12:42:06 2007 +0100
@@ -0,0 +1,114 @@
+% 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-svn.out	Sun Nov 25 12:17:30 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 Nov 25 12:17:30 2007 +0100
+++ b/tests/test-convert.out	Sun Nov 25 12:42:06 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
--- a/tests/test-execute-bit	Sun Nov 25 12:17:30 2007 +0100
+++ b/tests/test-execute-bit	Sun Nov 25 12:42:06 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-import	Sun Nov 25 12:17:30 2007 +0100
+++ b/tests/test-import	Sun Nov 25 12:42:06 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 Nov 25 12:42:06 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 Nov 25 12:42:06 2007 +0100
@@ -0,0 +1,1 @@
+### This test is for a known, unfixed bug ###