changeset 331:75f082b5897e

Switch to using url scheme wrappers instead of duplicating each command we wrap. The 'hg svn url' command has been killed; the replacement is '.hg/hgrc'. More stuff related to its disappearance has been stripped, including two tests. HgChangeReceiver now takes a UUID argument, which it uses to ensure that remote repositories remain unchanged. This is a temporary solution, and I'm not entirely satisfied with how it's done either. Access to the UUID file has been isolated in a HgChangeReceiver property. Some more tests have been updated to use ui.pushbuffer()/popbuffer(), and to pass through the Mercurial API. Moved the arguments to wrappers.pull() to the UI configuration. Also, remove HgChangeReceiver.opts in favour of a 'usebranchnames' instance & configuration variable. The name is taken from the ConvertExtension.
author Dan Villiom Podlaski Christiansen <danchr@gmail.com>
date Fri, 15 May 2009 19:18:43 +0200
parents 5f8f2fd4fd54
children 56d877e6ccbb
files __init__.py hg_delta_editor.py stupid.py svncommands.py svnexternals.py svnrepo.py tests/test_diff.py tests/test_fetch_mappings.py tests/test_fetch_truncated.py tests/test_pull.py tests/test_push_command.py tests/test_rebuildmeta.py tests/test_tags.py tests/test_util.py tests/test_utility_commands.py utility_commands.py wrappers.py
diffstat 17 files changed, 402 insertions(+), 359 deletions(-) [+]
line wrap: on
line diff
--- a/__init__.py
+++ b/__init__.py
@@ -11,6 +11,8 @@ Before using hgsubversion, it is *strong
 automated tests. See `README' in the hgsubversion directory for
 details.
 '''
+# TODO: The docstring should be slightly more helpful, and at least mention all
+#       configuration settings we support
 
 import os
 import sys
@@ -29,6 +31,32 @@ import util
 import wrappers
 import svnexternals
 
+schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+file')
+
+optionmap = {
+    'tagpaths': ('hgsubversion', 'tagpaths'),
+    'authors': ('hgsubversion', 'authormap'),
+    'filemap': ('hgsubversion', 'filemap'),
+    'stupid': ('hgsubversion', 'stupid'),
+    'defaulthost': ('hgsubversion', 'defaulthost'),
+    'defaultauthors': ('hgsubversion', 'defaultauthors'),
+    'usebranchnames': ('hgsubversion', 'usebranchnames'),
+}
+
+def wrapper(orig, ui, repo, *args, **opts):
+    """
+    Subversion repositories are also supported for this command. See
+    `hg help %(extension)s` for details.
+    """
+    for opt, (section, name) in optionmap.iteritems():
+        if opt in opts:
+            if isinstance(repo, str):
+                ui.setconfig(section, name, opts.pop(opt))
+            else:
+                repo.ui.setconfig(section, name, opts.pop(opt))
+
+    return orig(ui, repo, *args, **opts)
+
 def uisetup(ui):
     """Do our UI setup.
 
@@ -46,11 +74,21 @@ def uisetup(ui):
                                    wrappers.diff)
     entry[1].append(('', 'svn', None,
                      "show svn-style diffs, default against svn parent"))
-    entry = extensions.wrapcommand(commands.table, 'push',
-                                   wrappers.push)
-    entry[1].append(('', 'svn', None, "push to subversion"))
-    entry[1].append(('', 'svn-stupid', None, "use stupid replay during push to svn"))
 
+    newflags = (('A', 'authors', '', 'path to file containing username '
+                 'mappings for Subversion sources'),
+                ('', 'filemap', '', 'path to file containing rules for file '
+                 'name mapping used for sources)'),
+                ('T', 'tagpaths', ['tags'], 'list of paths to search for tags '
+                 'in Subversion repositories.'))
+    extname = __package__.split('_')[-1]
+
+    for command in ['clone']:
+        doc = wrapper.__doc__.strip() % { 'extension': extname }
+        getattr(commands, command).__doc__ += doc
+        entry = extensions.wrapcommand(commands.table, command, wrapper)
+        entry[1].extend(newflags)
+        
     try:
         rebase = extensions.find('rebase')
         if rebase:
@@ -100,7 +138,7 @@ def reposetup(ui, repo):
     if repo.local():
        svnrepo.generate_repo_class(ui, repo)
 
-for scheme in ('svn', 'svn+ssh', 'svn+http', 'svn+file'):
+for scheme in schemes:
     hg.schemes[scheme] = svnrepo
 
 cmdtable = {
--- a/hg_delta_editor.py
+++ b/hg_delta_editor.py
@@ -11,6 +11,7 @@ from mercurial import ui
 from mercurial import util as hgutil
 from mercurial import revlog
 from mercurial import node
+from mercurial import error
 from svn import delta
 from svn import core
 
@@ -74,9 +75,8 @@ class HgChangeReceiver(delta.Editor):
 
     def __init__(self, path=None, repo=None, ui_=None,
                  subdir='', author_host='',
-                 tag_locations=['tags'],
-                 authors=None,
-                 filemap=None):
+                 tag_locations=[],
+                 authors=None, filemap=None, uuid=None):
         """path is the path to the target hg repo.
 
         subdir is the subdirectory of the edits *on the svn server*.
@@ -87,14 +87,22 @@ class HgChangeReceiver(delta.Editor):
         if not ui_:
             ui_ = ui.ui()
         self.ui = ui_
-        self.__setup_repo(repo or path)
+        self.__setup_repo(repo or path, uuid)
+
+        if not author_host:
+            author_host = self.ui.config('hgsubversion', 'defaulthost', uuid)
+        if not authors:
+            authors = self.ui.config('hgsubversion', 'authormap')
+        if not filemap:
+            filemap = self.ui.config('hgsubversion', 'filemap')
+        if not tag_locations:
+            tag_locations = self.ui.config('hgsubversion', 'tagpaths', ['tags'])
+        self.usebranchnames = self.ui.configbool('hgsubversion',
+                                                  'usebranchnames', True)
 
         self.subdir = subdir
         if self.subdir and self.subdir[0] == '/':
             self.subdir = self.subdir[1:]
-        self.revmap = {}
-        if os.path.exists(self.revmap_file):
-            self.revmap = util.parse_revmap(self.revmap_file)
         self.branches = {}
         if os.path.exists(self.branch_info_file):
             f = open(self.branch_info_file)
@@ -137,7 +145,7 @@ class HgChangeReceiver(delta.Editor):
             date = self.lastdate
         return date
 
-    def __setup_repo(self, arg):
+    def __setup_repo(self, arg, uuid):
         """Verify the repo is going to work out for us.
 
         This method will fail an assertion if the repo exists but doesn't have
@@ -154,12 +162,14 @@ class HgChangeReceiver(delta.Editor):
             raise TypeError("editor requires either a path or a repository "
                             "specified")
 
-        if os.path.isdir(self.meta_data_dir) and os.listdir(self.meta_data_dir):
-            assert os.path.isfile(self.revmap_file)
-            assert os.path.isfile(self.svn_url_file)
-            assert os.path.isfile(self.uuid_file)
+        if not os.path.isdir(self.meta_data_dir):
+            os.makedirs(self.meta_data_dir)
+        self._set_uuid(uuid)
+
+        if os.path.isfile(self.revmap_file):
+            self.revmap = util.parse_revmap(self.revmap_file)
         else:
-            os.makedirs(os.path.dirname(self.uuid_file))
+            self.revmap = {}
             f = open(self.revmap_file, 'w')
             f.write('%s\n' % util.REVMAP_FILE_VERSION)
             f.flush()
@@ -636,7 +646,7 @@ class HgChangeReceiver(delta.Editor):
                 raise IOError
             files = parentctx.manifest().keys()
             extra = {}
-            if not self.opts.get('svn_no_branchnames', False):
+            if self.usebranchnames:
                 extra['branch'] = 'closed-branches'
             current_ctx = context.memctx(self.repo,
                                          parents,
@@ -658,9 +668,7 @@ class HgChangeReceiver(delta.Editor):
                        revlog.nullid)
             if parents[0] in closed_revs and branch in self.branches_to_delete:
                 continue
-            extra = util.build_extra(rev.revnum, branch,
-                                     open(self.uuid_file).read(),
-                                     self.subdir)
+            extra = util.build_extra(rev.revnum, branch, self.uuid, self.subdir)
             if branch is not None:
                 if (branch not in self.branches
                     and branch not in self.repo.branchtags()):
@@ -691,7 +699,7 @@ class HgChangeReceiver(delta.Editor):
                                           data=data,
                                           islink=is_link, isexec=is_exec,
                                           copied=copied)
-            if self.opts.get('svn_no_branchnames', False):
+            if not self.usebranchnames:
                 extra.pop('branch', None)
             current_ctx = context.memctx(self.repo,
                                          parents,
@@ -717,10 +725,8 @@ class HgChangeReceiver(delta.Editor):
             if self.commit_branches_empty[branch]: #pragma: no cover
                raise hgutil.Abort('Empty commit to an open branch attempted. '
                                   'Please report this issue.')
-            extra = util.build_extra(rev.revnum, branch,
-                                     open(self.uuid_file).read(),
-                                     self.subdir)
-            if self.opts.get('svn_no_branchnames', False):
+            extra = util.build_extra(rev.revnum, branch, self.uuid, self.subdir)
+            if not self.usebranchnames:
                 extra.pop('branch', None)
             current_ctx = context.memctx(self.repo,
                                          (ha, node.nullid),
@@ -783,13 +789,31 @@ class HgChangeReceiver(delta.Editor):
         return self.meta_file_named('rev_map')
     revmap_file = property(revmap_file)
 
-    def svn_url_file(self):
-        return self.meta_file_named('url')
-    svn_url_file = property(svn_url_file)
+    def _get_uuid(self):
+        return open(self.meta_file_named('uuid')).read()
+
+    def _set_uuid(self, uuid):
+        if not uuid:
+            return self._get_uuid()
+        elif os.path.isfile(self.meta_file_named('uuid')):
+            stored_uuid = self._get_uuid()
+            assert stored_uuid
+            if uuid != stored_uuid:
+                raise hgutil.Abort('unable to operate on unrelated repository')
+            else:
+                return stored_uuid
+        else:
+            if uuid:
+                f = open(self.meta_file_named('uuid'), 'w')
+                f.write(uuid)
+                f.flush()
+                f.close()
+                return self._get_uuid()
+            else:
+                raise hgutil.Abort('unable to operate on unrelated repository')
 
-    def uuid_file(self):
-        return self.meta_file_named('uuid')
-    uuid_file = property(uuid_file)
+    uuid = property(_get_uuid, _set_uuid, None,
+                    'Error-checked UUID of source Subversion repository.')
 
     def branch_info_file(self):
         return self.meta_file_named('branch_info')
@@ -803,10 +827,6 @@ class HgChangeReceiver(delta.Editor):
         return self.meta_file_named('tag_locations')
     tag_locations_file = property(tag_locations_file)
 
-    def url(self):
-        return open(self.svn_url_file).read()
-    url = property(url)
-
     def authors_file(self):
         return self.meta_file_named('authors')
     authors_file = property(authors_file)
--- a/stupid.py
+++ b/stupid.py
@@ -527,7 +527,7 @@ def svn_server_pull_rev(ui, svn, hg_edit
                                          date,
                                          extra)
             branch = extra.get('branch', None)
-            if hg_editor.opts.get('svn_no_branchnames', False):
+            if not hg_editor.usebranchnames:
                 extra.pop('branch', None)
             ha = hg_editor.repo.commitctx(current_ctx)
             if not branch in hg_editor.branches:
@@ -561,7 +561,7 @@ def svn_server_pull_rev(ui, svn, hg_edit
             closed = hg_editor.repo['closed-branches'].node()
         parents = (parent, closed)
         extra = {}
-        if not hg_editor.opts.get('svn_no_branchnames', False):
+        if hg_editor.usebranchnames:
                 extra['branch'] = 'closed-branches'
         current_ctx = context.memctx(hg_editor.repo,
                                      parents,
--- a/svncommands.py
+++ b/svncommands.py
@@ -30,16 +30,10 @@ def incoming(ui, svn_url, hg_repo_path, 
                                                  author_host=author_host,
                                                  tag_locations=tag_locations,
                                                  authors=authors,
-                                                 filemap=filemap)
-    if os.path.exists(hg_editor.uuid_file):
-        uuid = open(hg_editor.uuid_file).read()
-        assert uuid == svn.uuid
-        start = hg_editor.last_known_revision()
-    else:
-        open(hg_editor.uuid_file, 'w').write(svn.uuid)
-        open(hg_editor.svn_url_file, 'w').write(svn_url)
-        initializing_repo = True
-        start = skipto_rev
+                                                 filemap=filemap,
+                                                 uuid=svn.uuid)
+    start = max(hg_editor.last_known_revision(), skipto_rev)
+    initializing_repo = (hg_editor.last_known_revision() <= 0)
 
     if initializing_repo and start > 0:
         raise hgutil.Abort('Revision skipping at repository initialization '
@@ -65,9 +59,13 @@ def rebuildmeta(ui, repo, hg_repo_path, 
     """rebuild hgsubversion metadata using values stored in revisions
     """
     if len(args) != 1:
-        raise hgutil.Abort('You must pass the svn URI used to create this repo.')
+        url = repo.ui.expandpath(dest or 'default-push', dest or 'default')
+    else:
+        url = args[0]
+    if not (url.startswith('svn+') or url.startswith('svn:')):
+        raise hgutil.Abort('No valid Subversion URI found; please specify one.')
     uuid = None
-    url = args[0].rstrip('/')
+    url = util.normalize_url(url.rstrip('/'))
     user, passwd = util.getuserpass(opts)
     svn = svnwrap.SubversionRepo(url, user, passwd)
     subdir = svn.subdir
@@ -95,9 +93,6 @@ def rebuildmeta(ui, repo, hg_repo_path, 
             if uuid is None:
                 uuid = convinfo[4:40]
                 assert uuid == svn.uuid, 'UUIDs did not match!'
-                urlfile = open(os.path.join(svnmetadir, 'url'), 'w')
-                urlfile.write(url)
-                urlfile.close()
                 uuidfile = open(os.path.join(svnmetadir, 'uuid'), 'w')
                 uuidfile.write(uuid)
                 uuidfile.close()
--- a/svnexternals.py
+++ b/svnexternals.py
@@ -3,6 +3,7 @@ import cStringIO
 import os, re, shutil, stat, subprocess
 from mercurial import util as hgutil
 from mercurial.i18n import _
+from hgsubversion import util
 
 class externalsfile(dict):
     """Map svn directories to lists of externals entries.
@@ -160,7 +161,8 @@ def getsvninfo(svnurl):
     # Yes, this is ugly, but good enough for now
     args = ['svn', 'info', '--xml', svnurl]
     shell = os.name == 'nt'
-    p = subprocess.Popen(args, stdout=subprocess.PIPE, shell=shell)
+    p = subprocess.Popen(args, shell=shell,
+                         stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     stdout = p.communicate()[0]
     if p.returncode:
         raise hgutil.Abort(_('cannot get information about %s')
@@ -240,21 +242,23 @@ class externalsupdater:
         args = ['svn'] + args
         self.ui.debug(_('updating externals: %r, cwd=%s\n') % (args, cwd))
         shell = os.name == 'nt'
-        subprocess.check_call(args, cwd=cwd, shell=shell)
+        subprocess.check_call(args, cwd=cwd, shell=shell,
+                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
 def updateexternals(ui, args, repo, **opts):
     """update repository externals
     """
-    if len(args) > 1:
+    if len(args) > 2:
         raise hgutil.Abort(_('updateexternals expects at most one changeset'))
     node = None
+    if len(args) == 2:
+        svnurl = util.normalize_url(repo.ui.expandpath(args[0]))
+        args = args[1:]
+    else:
+        svnurl = util.normalize_url(repo.ui.expandpath('default'))
     if args:
         node = args[0]
 
-    try:
-        svnurl = file(repo.join('svn/url'), 'rb').read()
-    except:
-        raise hgutil.Abort(_('failed to retrieve original svn URL'))
     svnroot = getsvninfo(svnurl)[1]
 
     # Retrieve current externals status
--- a/svnrepo.py
+++ b/svnrepo.py
@@ -1,3 +1,19 @@
+"""
+repository class-based interface for hgsubversion
+
+  Copyright (C) 2009, Dan Villiom Podlaski Christiansen <danchr@gmail.com>
+  See parent package for licensing.
+
+Internally, Mercurial assumes that every single repository is a localrepository
+subclass: pull() is called on the instance pull *to*, but not the one pulled
+*from*. To work around this, we create two classes:
+
+- svnremoterepo for Subversion repositories, but it doesn't really do anything.
+- svnlocalrepo for local repositories which handles both operations on itself --
+  the local, hgsubversion-enabled clone -- and the remote repository. Decorators
+  are used to distinguish and filter these operations from others.
+"""
+
 from mercurial import node
 from mercurial import util as hgutil
 import mercurial.repo
@@ -7,39 +23,44 @@ import util
 import wrappers
 
 def generate_repo_class(ui, repo):
+    """ This function generates the local repository wrapper. """
+
     def localsvn(fn):
-        '''
+        """
         Filter for instance methods which only apply to local Subversion
         repositories.
-        '''
+        """
         if util.is_svn_repo(repo):
             return fn
         else:
-            original = repo.__getattribute__(fn.__name__)
-            return original
+            return getattr(repo, fn.__name__)
 
     def remotesvn(fn):
-        '''
+        """
         Filter for instance methods which require the first argument
         to be a remote Subversion repository instance.
-        '''
-        original = repo.__getattribute__(fn.__name__)
+        """
+        original = getattr(repo.__class__, fn.__name__)
         def wrapper(self, *args, **opts):
-            if not isinstance(args[0], svnremoterepo):
-                return original(*args, **opts)
-            else:
+            if 'subversion' in getattr(args[0], 'capabilities', []):
                 return fn(self, *args, **opts)
+            else:
+                return original(self, *args, **opts)
         wrapper.__name__ = fn.__name__ + '_wrapper'
         wrapper.__doc__ = fn.__doc__
         return wrapper
 
     class svnlocalrepo(repo.__class__):
+        @remotesvn
+        def push(self, remote, force=False, revs=None):
+            # TODO: pass on revs
+            wrappers.push(self, dest=remote.svnurl, force=force, revs=None)
+
         @remotesvn
         def pull(self, remote, heads=None, force=False):
             try:
                 lock = self.wlock()
-                wrappers.pull(None, self.ui, self, source=remote.path,
-                              svn=True, rev=heads, force=force)
+                wrappers.pull(self, source=remote.svnurl, rev=heads, force=force)
             except KeyboardInterrupt:
                 pass
             finally:
@@ -51,16 +72,22 @@ def generate_repo_class(ui, repo):
             hg_editor = hg_delta_editor.HgChangeReceiver(repo=self)
             for tag, source in hg_editor.tags.iteritems():
                 target = hg_editor.get_parent_revision(source[1]+1, source[0])
-                tags['tag/%s' % tag] = node.hex(target)
+                tags['tag/%s' % tag] = target
             return tags
 
     repo.__class__ = svnlocalrepo
 
 class svnremoterepo(mercurial.repo.repository):
+    """ the dumb wrapper for actual Subversion repositories """
+
     def __init__(self, ui, path):
         self.ui = ui
         self.path = path
-        self.capabilities = set(['lookup'])
+        self.capabilities = set(['lookup', 'subversion'])
+
+    @property
+    def svnurl(self):
+        return util.normalize_url(self.path)
 
     def url(self):
         return self.path
@@ -82,6 +109,4 @@ def instance(ui, url, create):
     if create:
         raise hgutil.Abort('cannot create new remote Subversion repository')
 
-    if url.startswith('svn+') and not url.startswith('svn+ssh:'):
-        url = url[4:]
-    return svnremoterepo(ui, util.normalize_url(url))
+    return svnremoterepo(ui, url)
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -32,9 +32,9 @@ class DiffTests(test_util.TestBase):
                             ('alpha', 'alpha', 'alpha\n\nadded line\n'),
                             ])
         u = ui.ui()
-        wrappers.diff(lambda x,y,z: None,
-                         u, self.repo, svn=True)
-        self.assertEqual(u.stream.getvalue(), expected_diff_output)
+        u.pushbuffer()
+        wrappers.diff(lambda x,y,z: None, u, self.repo, svn=True)
+        self.assertEqual(u.popbuffer(), expected_diff_output)
 
 
 def suite():
--- a/tests/test_fetch_mappings.py
+++ b/tests/test_fetch_mappings.py
@@ -3,11 +3,11 @@
 import os
 import unittest
 
+from mercurial import commands
 from mercurial import ui
 from mercurial import node
 
 import test_util
-import wrappers
 
 class MapTests(test_util.TestBase):
     @property
@@ -23,8 +23,11 @@ class MapTests(test_util.TestBase):
         authormap = open(self.authors, 'w')
         authormap.write("Augie=Augie Fackler <durin42@gmail.com>\n")
         authormap.close()
-        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
-                         dest=self.wc_path, stupid=stupid, svn_authors=self.authors)
+        _ui = ui.ui()
+        _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        _ui.setconfig('hgsubversion', 'authormap', self.authors)
+        commands.clone(_ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, authors=self.authors)
         self.assertEqual(self.repo[0].user(),
                          'Augie Fackler <durin42@gmail.com>')
         self.assertEqual(self.repo['tip'].user(),
@@ -38,9 +41,11 @@ class MapTests(test_util.TestBase):
         authormap = open(self.authors, 'w')
         authormap.write("evil=Testy <test@test>")
         authormap.close()
-        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
-                         dest=self.wc_path, stupid=stupid,
-                         svn_authors=self.authors)
+        _ui = ui.ui()
+        _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        _ui.setconfig('hgsubversion', 'authormap', self.authors)
+        commands.clone(_ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, authors=self.authors)
         self.assertEqual(self.repo[0].user(),
                          'Augie@5b65bade-98f3-4993-a01f-b7a6710da339')
         self.assertEqual(self.repo['tip'].user(),
@@ -54,9 +59,11 @@ class MapTests(test_util.TestBase):
         filemap = open(self.filemap, 'w')
         filemap.write("include alpha\n")
         filemap.close()
-        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
-                         dest=self.wc_path, stupid=stupid,
-                         svn_filemap=self.filemap)
+        _ui = ui.ui()
+        _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        _ui.setconfig('hgsubversion', 'filemap', self.filemap)
+        commands.clone(_ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, filemap=self.filemap)
         self.assertEqual(node.hex(self.repo[0].node()), '88e2c7492d83e4bf30fbb2dcbf6aa24d60ac688d')
         self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155')
 
@@ -68,9 +75,11 @@ class MapTests(test_util.TestBase):
         filemap = open(self.filemap, 'w')
         filemap.write("exclude alpha\n")
         filemap.close()
-        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
-                         dest=self.wc_path, stupid=stupid,
-                         svn_filemap=self.filemap)
+        _ui = ui.ui()
+        _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        _ui.setconfig('hgsubversion', 'filemap', self.filemap)
+        commands.clone(_ui, test_util.fileurl(self.repo_path),
+                       self.wc_path, filemap=self.filemap)
         self.assertEqual(node.hex(self.repo[0].node()), '2c48f3525926ab6c8b8424bcf5eb34b149b61841')
         self.assertEqual(node.hex(self.repo['default'].node()), 'b37a3c0297b71f989064d9b545b5a478bbed7cc1')
 
--- a/tests/test_fetch_truncated.py
+++ b/tests/test_fetch_truncated.py
@@ -1,39 +1,39 @@
-import unittest
-
-from mercurial import hg
-from mercurial import ui
-
-import wrappers
-import test_util
-
-class TestFetchTruncatedHistory(test_util.TestBase):
-    def test_truncated_history(self, stupid=False):
-        # Test repository does not follow the usual layout
-        test_util.load_svndump_fixture(self.repo_path, 'truncatedhistory.svndump')
-        svn_url = test_util.fileurl(self.repo_path + '/project2')
-        wrappers.clone(None, ui.ui(), source=svn_url,
-                       dest=self.wc_path, stupid=stupid,
-                       noupdate=True)
-        repo = hg.repository(ui.ui(), self.wc_path)
-
-        # We are converting /project2/trunk coming from:
-        #
-        # Changed paths:
-        #     D /project1
-        #     A /project2/trunk (from /project1:2)
-        #
-        # Here a full fetch should be performed since we are starting
-        # the conversion on an already filled branch.
-        tip = repo['tip']
-        files = tip.manifest().keys()
-        files.sort()
-        self.assertEqual(files, ['a', 'b'])
-        self.assertEqual(repo['tip']['a'].data(), 'a\n')
-
-    def test_truncated_history_stupid(self):
-        self.test_truncated_history(True)
-
-def suite():
-    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory),
-          ]
-    return unittest.TestSuite(all)
+import unittest
+
+from mercurial import commands
+from mercurial import hg
+from mercurial import ui
+
+import test_util
+
+class TestFetchTruncatedHistory(test_util.TestBase):
+    def test_truncated_history(self, stupid=False):
+        # Test repository does not follow the usual layout
+        test_util.load_svndump_fixture(self.repo_path, 'truncatedhistory.svndump')
+        svn_url = test_util.fileurl(self.repo_path + '/project2')
+        _ui = ui.ui()
+        _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        commands.clone(_ui, svn_url, self.wc_path, noupdate=True)
+        repo = hg.repository(_ui, self.wc_path)
+
+        # We are converting /project2/trunk coming from:
+        #
+        # Changed paths:
+        #     D /project1
+        #     A /project2/trunk (from /project1:2)
+        #
+        # Here a full fetch should be performed since we are starting
+        # the conversion on an already filled branch.
+        tip = repo['tip']
+        files = tip.manifest().keys()
+        files.sort()
+        self.assertEqual(files, ['a', 'b'])
+        self.assertEqual(repo['tip']['a'].data(), 'a\n')
+
+    def test_truncated_history_stupid(self):
+        self.test_truncated_history(True)
+
+def suite():
+    all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory),
+          ]
+    return unittest.TestSuite(all)
--- a/tests/test_pull.py
+++ b/tests/test_pull.py
@@ -3,9 +3,8 @@ import test_util
 import os.path
 import subprocess
 from mercurial import ui
-
-import wrappers
-
+from mercurial import util as hgutil
+from mercurial import commands
 
 class TestPull(test_util.TestBase):
     def setUp(self):
@@ -22,30 +21,34 @@ class TestPull(test_util.TestBase):
         if self.svn_wc is None:
             self.svn_wc = os.path.join(self.tmpdir, 'testsvn_wc')
             subprocess.call([
-                'svn', 'co', '-q', test_util.fileurl(self.repo_path), 
+                'svn', 'co', '-q', test_util.fileurl(self.repo_path)[4:], 
                 self.svn_wc
-            ])
+            ],
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
         for filename, contents in changes.iteritems():
             # filenames are / separated
             filename = filename.replace('/', os.path.sep)
             filename = os.path.join(self.svn_wc, filename)
             open(filename, 'w').write(contents)
-            subprocess.call(['svn', 'add', '-q', filename]) # may be redundant
+            # may be redundant
+            subprocess.call(['svn', 'add', '-q', filename],
+                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
         subprocess.call([
-            'svn', 'commit', '-q', self.svn_wc, '-m', 'test changes'])
+            'svn', 'commit', '-q', self.svn_wc, '-m', 'test changes'],
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
     def test_nochanges(self):
-        repo = self._load_fixture_and_fetch('single_rev.svndump')
-        state = repo.parents()
-        wrappers.pull(None, ui.ui(), repo)
-        self.assertEqual(state, repo.parents())
+        self._load_fixture_and_fetch('single_rev.svndump')
+        state = self.repo.parents()
+        commands.pull(self.repo.ui, self.repo)
+        self.assertEqual(state, self.repo.parents())
 
     def test_onerevision_noupdate(self):
         repo = self._load_fixture_and_fetch('single_rev.svndump')
         state = repo.parents()
         self._add_svn_rev({'trunk/alpha': 'Changed'})
-        wrappers.pull(None, ui.ui(), repo)
+        commands.pull(self.repo.ui, repo)
         self.assertEqual(state, repo.parents())
         self.assertTrue('tip' not in repo[None].tags())
     
@@ -53,7 +56,7 @@ class TestPull(test_util.TestBase):
         repo = self._load_fixture_and_fetch('single_rev.svndump')
         state = repo.parents()
         self._add_svn_rev({'trunk/alpha': 'Changed'})
-        wrappers.pull(None, ui.ui(), repo, update=True)
+        commands.pull(self.repo.ui, repo, update=True)
         self.failIfEqual(state, repo.parents())
         self.assertTrue('tip' in repo[None].tags())
 
@@ -62,7 +65,8 @@ class TestPull(test_util.TestBase):
         self.commitchanges((('alpha', 'alpha', 'Changed another way'),))
         state = repo.parents()
         self._add_svn_rev({'trunk/alpha': 'Changed one way'})
-        wrappers.pull(None, ui.ui(), repo, update=True)
+        self.assertRaises(hgutil.Abort, commands.pull,
+                          self.repo.ui, repo, update=True)
         self.assertEqual(state, repo.parents())
         self.assertTrue('tip' not in repo[None].tags())
         self.assertEqual(len(repo.heads()), 2)
--- a/tests/test_push_command.py
+++ b/tests/test_push_command.py
@@ -1,16 +1,18 @@
+import atexit
 import os
+import random
 import socket
 import subprocess
 import unittest
 
 from mercurial import context
+from mercurial import commands
 from mercurial import hg
 from mercurial import node
 from mercurial import ui
 from mercurial import revlog
 from mercurial import util as hgutil
 
-import wrappers
 import test_util
 import time
 
@@ -21,28 +23,21 @@ class PushOverSvnserveTests(test_util.Te
         test_util.load_svndump_fixture(self.repo_path, 'simple_branch.svndump')
         open(os.path.join(self.repo_path, 'conf', 'svnserve.conf'),
              'w').write('[general]\nanon-access=write\n[sasl]\n')
-        # Paranoia: we try and connect to localhost on 3689 before we start
-        # svnserve. If it is running, we force the test to fail early.
-        user_has_own_svnserve = False
-        try:
-            s = socket.socket()
-            s.settimeout(0.3)
-            s.connect(('localhost', 3690))
-            s.close()
-            user_has_own_svnserve = True
-        except:
-            pass
-        if user_has_own_svnserve:
-            assert False, ('You appear to be running your own svnserve!'
-                           ' You can probably ignore this test failure.')
-        args = ['svnserve', '-d', '--foreground', '-r', self.repo_path]
-        self.svnserve_pid = subprocess.Popen(args).pid
+        self.port = random.randint(socket.IPPORT_USERRESERVED, 65535)
+        self.host = 'localhost'
+        args = ['svnserve', '--daemon', '--foreground',
+                '--listen-port=%d' % self.port,
+                '--listen-host=%s' % self.host,
+                '--root=%s' % self.repo_path]
+        svnserve = subprocess.Popen(args, stdout=subprocess.PIPE,
+                                    stderr=subprocess.STDOUT)
+        self.svnserve_pid = svnserve.pid
         time.sleep(2)
-        wrappers.clone(None, ui.ui(), source='svn://localhost/',
-                       dest=self.wc_path, noupdate=True)
+        commands.clone(ui.ui(), 'svn://%s:%d/' % (self.host, self.port),
+                       self.wc_path, noupdate=True)
 
     def tearDown(self):
-        os.system('kill -9 %d' % self.svnserve_pid)
+        os.kill(self.svnserve_pid, 9)
         test_util.TestBase.tearDown(self)
 
     def test_push_to_default(self, commit=True):
@@ -70,7 +65,7 @@ class PushOverSvnserveTests(test_util.Te
             return # some tests use this test as an extended setup.
         hg.update(repo, repo['tip'].node())
         oldauthor = repo['tip'].user()
-        wrappers.push(None, ui.ui(), repo=self.repo)
+        commands.push(repo.ui, repo)
         tip = self.repo['tip']
         self.assertNotEqual(oldauthor, tip.user())
         self.assertNotEqual(tip.node(), old_tip)
@@ -172,7 +167,7 @@ class PushTests(test_util.TestBase):
         newhash = self.repo.commitctx(ctx)
         repo = self.repo
         hg.update(repo, newhash)
-        wrappers.push(None, ui.ui(), repo=repo)
+        commands.push(repo.ui, repo)
         self.assertEqual(self.repo['tip'].parents()[0].parents()[0].node(), oldtiphash)
         self.assertEqual(self.repo['tip'].files(), ['delta', ])
         self.assertEqual(self.repo['tip'].manifest().keys(),
--- a/tests/test_rebuildmeta.py
+++ b/tests/test_rebuildmeta.py
@@ -22,10 +22,18 @@ def _do_case(self, name, stupid):
                             os.path.dirname(dest.path),
                             args=[test_util.fileurl(self.repo_path +
                                                     subdir), ])
+    self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')),
+                    'no .hg/svn directory in the source!')
+    self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')),
+                    'no .hg/svn directory in the destination!')
     dest = hg.repository(u, os.path.dirname(dest.path))
-    for tf in ('rev_map', 'uuid', 'url'):
-        self.assertEqual(open(os.path.join(src.path, 'svn', tf)).read(),
-                         open(os.path.join(dest.path, 'svn', tf)).read())
+    for tf in ('rev_map', 'uuid'):
+        stf = os.path.join(src.path, 'svn', tf)
+        self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf)
+        dtf = os.path.join(dest.path, 'svn', tf)
+        self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf)
+        self.assertEqual(open(stf).read(),
+                         open(dtf).read())
     self.assertEqual(pickle.load(open(os.path.join(src.path, 'svn',
                                                    'tag_info'))),
                      pickle.load(open(os.path.join(dest.path, 'svn',
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -6,20 +6,15 @@ from mercurial import ui
 
 import test_util
 
-import tag_repo
+import svnrepo
 
 class TestTags(test_util.TestBase):
     def _load_fixture_and_fetch(self, fixture_name, stupid=False):
         return test_util.load_fixture_and_fetch(fixture_name, self.repo_path,
                                                 self.wc_path, stupid=stupid)
 
-    def getrepo(self):
-        ui_ = ui.ui()
-        repo = hg.repository(ui_, self.wc_path)
-        repo.__class__ = tag_repo.generate_repo_class(ui_, repo)
-        return repo
-
     def _test_tag_revision_info(self, repo):
+        print repo.tags()
         self.assertEqual(node.hex(repo[0].node()),
                          '434ed487136c1b47c1e8f952edb4dc5a8e6328df')
         self.assertEqual(node.hex(repo['tip'].node()),
@@ -30,7 +25,7 @@ class TestTags(test_util.TestBase):
         repo = self._load_fixture_and_fetch('basic_tag_tests.svndump',
                                             stupid=stupid)
         self._test_tag_revision_info(repo)
-        repo = self.getrepo()
+        repo = self.repo
         self.assertEqual(repo['tip'].node(), repo['tag/tag_r3'].node())
         self.assertEqual(repo['tip'].node(), repo['tag/copied_tag'].node())
 
@@ -41,7 +36,7 @@ class TestTags(test_util.TestBase):
         repo = self._load_fixture_and_fetch('remove_tag_test.svndump',
                                             stupid=stupid)
         self._test_tag_revision_info(repo)
-        repo = self.getrepo()
+        repo = self.repo
         self.assertEqual(repo['tip'].node(), repo['tag/tag_r3'].node())
         self.assert_('tag/copied_tag' not in repo.tags())
 
@@ -52,7 +47,7 @@ class TestTags(test_util.TestBase):
         repo = self._load_fixture_and_fetch('rename_tag_test.svndump',
                                             stupid=stupid)
         self._test_tag_revision_info(repo)
-        repo = self.getrepo()
+        repo = self.repo
         self.assertEqual(repo['tip'].node(), repo['tag/tag_r3'].node())
         self.assertEqual(repo['tip'].node(), repo['tag/other_tag_r3'].node())
         self.assert_('tag/copied_tag' not in repo.tags())
@@ -63,7 +58,7 @@ class TestTags(test_util.TestBase):
     def test_branch_from_tag(self, stupid=False):
         repo = self._load_fixture_and_fetch('branch_from_tag.svndump',
                                             stupid=stupid)
-        repo = self.getrepo()
+        repo = self.repo
         self.assertEqual(repo['tip'].node(), repo['branch_from_tag'].node())
         self.assertEqual(repo[1].node(), repo['tag/tag_r3'].node())
         self.assertEqual(repo['branch_from_tag'].parents()[0].node(),
@@ -75,7 +70,7 @@ class TestTags(test_util.TestBase):
     def test_tag_by_renaming_branch(self, stupid=False):
         repo = self._load_fixture_and_fetch('tag_by_rename_branch.svndump',
                                             stupid=stupid)
-        repo = self.getrepo()
+        repo = self.repo
         self.assertEqual(repo['tip'], repo['closed-branches'])
         self.assertEqual(node.hex(repo['tip'].node()),
                          '2f0a3abe2004c0fa01f5f6074a8b5441e9c80c2a')
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -9,12 +9,11 @@ import unittest
 import urllib
 
 from mercurial import context
+from mercurial import commands
 from mercurial import hg
 from mercurial import node
 from mercurial import ui
 
-import wrappers
-
 # Fixtures that need to be pulled at a subdirectory of the repo path
 subdir = {'truncatedhistory.svndump': '/project2',
           'fetch_missing_files_subdir.svndump': '/foo',
@@ -37,22 +36,25 @@ def load_svndump_fixture(path, fixture_n
     '''Loads an svnadmin dump into a fresh repo at path, which should not
     already exist.
     '''
-    subprocess.call(['svnadmin', 'create', path,])
-    proc = subprocess.Popen(['svnadmin', 'load', path,], stdin=subprocess.PIPE,
-                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    if os.path.exists(path): rmtree(path)
+    subprocess.call(['svnadmin', 'create', path,],
+                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     inp = open(os.path.join(FIXTURES, fixture_name))
-    proc.stdin.write(inp.read())
-    proc.stdin.flush()
+    proc = subprocess.Popen(['svnadmin', 'load', path,], stdin=inp,
+                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     proc.communicate()
 
 def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False, subdir='', noupdate=True):
     load_svndump_fixture(repo_path, fixture_name)
     if subdir:
         repo_path += '/' + subdir
-    wrappers.clone(None, ui.ui(), source=fileurl(repo_path),
-                     dest=wc_path, stupid=stupid, noupdate=noupdate)
-    repo = hg.repository(ui.ui(), wc_path)
-    return repo
+    
+    _ui = ui.ui()
+    _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+    commands.clone(_ui, fileurl(repo_path), wc_path, noupdate=noupdate)
+    _ui = ui.ui()
+    _ui.setconfig('hgsubversion', 'stupid', str(stupid))
+    return hg.repository(_ui, wc_path)
 
 def rmtree(path):
     # Read-only files cannot be removed under Windows
@@ -69,30 +71,6 @@ def rmtree(path):
                 os.chmod(f, s.st_mode | stat.S_IWRITE)
     shutil.rmtree(path)
 
-
-class MockUI(object):
-    real_ui = ui.ui
-    _isatty = False
-    def __init__(self, src=None):
-        self.stream = StringIO.StringIO()
-        self.inner_ui = self.real_ui(src)
-
-    def status(self, *args):
-        self.stream.write(''.join(args))
-
-    def warn(self, *args):
-        self.stream.write(*args)
-
-    def write(self, *args):
-        self.stream.write(*args)
-
-    def copy(self):
-        return self.__class__(self.inner_ui)
-
-    def __getattr__(self, attr):
-        return getattr(self.inner_ui, attr)
-
-
 class TestBase(unittest.TestCase):
     def setUp(self):
         self.oldwd = os.getcwd()
@@ -105,13 +83,19 @@ class TestBase(unittest.TestCase):
 
         self.repo_path = '%s/testrepo' % self.tmpdir
         self.wc_path = '%s/testrepo_wc' % self.tmpdir
-        self._real_ui = ui.ui
-        ui.ui = MockUI
+
+        # Previously, we had a MockUI class that wrapped ui, and giving access
+        # to the stream. The ui.pushbuffer() and ui.popbuffer() can be used
+        # instead. Using the regular UI class, with all stderr redirected to
+        # stdout ensures that the test setup is much more similar to usage
+        # setups.
+        self._ui_write_err = ui.ui.write_err
+        ui.ui.write_err = ui.ui.write
 
     def tearDown(self):
         rmtree(self.tmpdir)
         os.chdir(self.oldwd)
-        ui.ui = self._real_ui
+        ui.ui.write_err = self._ui_write_err
 
     def _load_fixture_and_fetch(self, fixture_name, subdir='', stupid=False):
         return load_fixture_and_fetch(fixture_name, self.repo_path,
@@ -125,7 +109,8 @@ class TestBase(unittest.TestCase):
 
     def pushrevisions(self, stupid=False):
         before = len(self.repo)
-        wrappers.push(None, ui.ui(), repo=self.repo, stupid=stupid)
+        self.repo.ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        commands.push(self.repo.ui, self.repo)
         after = len(self.repo)
         self.assertEqual(0, after - before)
 
@@ -135,7 +120,7 @@ class TestBase(unittest.TestCase):
         args = ['svn', 'ls', '-r', rev, '-R', path]
         p = subprocess.Popen(args,
                              stdout=subprocess.PIPE,
-                             stderr=subprocess.PIPE)
+                             stderr=subprocess.STDOUT)
         stdout, stderr = p.communicate()
         if p.returncode:
             raise Exception('svn ls failed on %s: %r' % (path, stderr))
--- a/tests/test_utility_commands.py
+++ b/tests/test_utility_commands.py
@@ -8,6 +8,7 @@ from mercurial import revlog
 from mercurial import context
 from mercurial import node
 
+import util
 import utility_commands
 import test_util
 import wrappers
@@ -23,32 +24,40 @@ Last Changed Date: %(date)s
 '''
 
 class UtilityTests(test_util.TestBase):
+    @property
+    def repourl(self):
+        return util.normalize_url(test_util.fileurl(self.repo_path))
+
     def test_info_output(self):
         self._load_fixture_and_fetch('two_heads.svndump')
         hg.update(self.repo, 'the_branch')
         u = ui.ui()
+        u.pushbuffer()
         utility_commands.info(u, self.repo, self.wc_path)
+        actual = u.popbuffer()
         expected = (expected_info_output %
                     {'date': '2008-10-08 01:39:05 +0000 (Wed, 08 Oct 2008)',
-                     'repourl': test_util.fileurl(self.repo_path),
+                     'repourl': self.repourl,
                      'branch': 'branches/the_branch',
                      'rev': 5,
                      })
-        self.assertEqual(u.stream.getvalue(), expected)
+        self.assertEqual(actual, expected)
         hg.update(self.repo, 'default')
-        u = ui.ui()
+        u.pushbuffer()
         utility_commands.info(u, self.repo, self.wc_path)
+        actual = u.popbuffer()
         expected = (expected_info_output %
                     {'date': '2008-10-08 01:39:29 +0000 (Wed, 08 Oct 2008)',
-                     'repourl': test_util.fileurl(self.repo_path),
+                     'repourl': self.repourl,
                      'branch': 'trunk',
                      'rev': 6,
                      })
-        self.assertEqual(u.stream.getvalue(), expected)
+        self.assertEqual(actual, expected)
 
     def test_parent_output(self):
         self._load_fixture_and_fetch('two_heads.svndump')
         u = ui.ui()
+        u.pushbuffer()
         parents = (self.repo['the_branch'].node(), revlog.nullid, )
         def filectxfn(repo, memctx, path):
             return context.memfilectx(path=path,
@@ -67,7 +76,8 @@ class UtilityTests(test_util.TestBase):
         new = self.repo.commitctx(ctx)
         hg.update(self.repo, new)
         wrappers.parent(lambda x, y: None, u, self.repo, svn=True)
-        self.assertEqual(u.stream.getvalue(),
+        actual = u.popbuffer()
+        self.assertEqual(actual,
                          'changeset:   3:4e256962fc5d\n'
                          'branch:      the_branch\n'
                          'user:        durin@df2126f7-00ab-4d49-b42c-7e981dde0bcf\n'
@@ -76,19 +86,22 @@ class UtilityTests(test_util.TestBase):
 
         hg.update(self.repo, 'default')
         # Make sure styles work
-        u = ui.ui()
+        u.pushbuffer()
         wrappers.parent(lambda x, y: None, u, self.repo, svn=True, style='compact')
-        self.assertEqual(u.stream.getvalue(),
+        actual = u.popbuffer()
+        self.assertEqual(actual,
                          '4:1   1083037b18d8   2008-10-08 01:39 +0000   durin\n'
                          '  Add gamma on trunk.\n\n')
         # custom templates too
-        u = ui.ui()
+        u.pushbuffer()
         wrappers.parent(lambda x, y: None, u, self.repo, svn=True, template='{node}\n')
-        self.assertEqual(u.stream.getvalue(), '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n')
+        actual = u.popbuffer()
+        self.assertEqual(actual, '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n')
 
-        u = ui.ui()
+        u.pushbuffer()
         wrappers.parent(lambda x, y: None, u, self.repo, svn=True)
-        self.assertEqual(u.stream.getvalue(),
+        actual = u.popbuffer()
+        self.assertEqual(actual,
                          'changeset:   4:1083037b18d8\n'
                          'parent:      1:c95251e0dd04\n'
                          'user:        durin@df2126f7-00ab-4d49-b42c-7e981dde0bcf\n'
@@ -98,6 +111,7 @@ class UtilityTests(test_util.TestBase):
     def test_outgoing_output(self):
         self._load_fixture_and_fetch('two_heads.svndump')
         u = ui.ui()
+        u.pushbuffer()
         parents = (self.repo['the_branch'].node(), revlog.nullid, )
         def filectxfn(repo, memctx, path):
             return context.memfilectx(path=path,
@@ -116,9 +130,9 @@ class UtilityTests(test_util.TestBase):
         new = self.repo.commitctx(ctx)
         hg.update(self.repo, new)
         wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True)
-        self.assert_(node.hex(self.repo['localbranch'].node())[:8] in
-                     u.stream.getvalue())
-        self.assertEqual(u.stream.getvalue(), ('changeset:   5:6de15430fa20\n'
+        actual = u.popbuffer()
+        self.assert_(node.hex(self.repo['localbranch'].node())[:8] in actual)
+        self.assertEqual(actual, ('changeset:   5:6de15430fa20\n'
                                                'branch:      localbranch\n'
                                                'tag:         tip\n'
                                                'parent:      3:4e256962fc5d\n'
@@ -127,17 +141,10 @@ class UtilityTests(test_util.TestBase):
                                                'summary:     automated test\n'
                                                '\n'))
         hg.update(self.repo, 'default')
-        u = ui.ui()
+        u.pushbuffer()
         wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True)
-        self.assertEqual(u.stream.getvalue(), 'no changes found\n')
-
-    def test_url_output(self):
-        self._load_fixture_and_fetch('two_revs.svndump')
-        hg.update(self.repo, 'tip')
-        u = ui.ui()
-        utility_commands.url(u, self.repo, self.wc_path)
-        expected = test_util.fileurl(self.repo_path) + '\n'
-        self.assertEqual(u.stream.getvalue(), expected)
+        actual = u.popbuffer()
+        self.assertEqual(actual, 'no changes found\n')
 
     def test_rebase(self):
         self._load_fixture_and_fetch('two_revs.svndump')
@@ -165,28 +172,12 @@ class UtilityTests(test_util.TestBase):
         self.assertEqual(self.repo['tip'].parents()[0].parents()[0], self.repo[0])
         self.assertNotEqual(beforerebasehash, self.repo['tip'].node())
 
-    def test_url_is_normalized(self):
-        """Verify url gets normalized on initial clone.
-        """
-        test_util.load_svndump_fixture(self.repo_path, 'two_revs.svndump')
-        wrappers.clone(None, ui.ui(),
-                       source=test_util.fileurl(self.repo_path) + '/',
-                       dest=self.wc_path, stupid=False)
-        hg.update(self.repo, 'tip')
-        u = ui.ui()
-        utility_commands.url(u, self.repo, self.wc_path)
-        expected = test_util.fileurl(self.repo_path) + '\n'
-        self.assertEqual(u.stream.getvalue(), expected)
-
     def test_genignore(self):
-        """Verify url gets normalized on initial clone.
-        """
-        test_util.load_svndump_fixture(self.repo_path, 'ignores.svndump')
-        wrappers.clone(None, ui.ui(),
-                       source=test_util.fileurl(self.repo_path) + '/',
-                       dest=self.wc_path, stupid=False)
-        hg.update(self.repo, 'tip')
+        """ Test generation of .hgignore file. """
+        test_util.load_fixture_and_fetch('ignores.svndump', self.repo_path, 
+                                         self.wc_path, noupdate=False)
         u = ui.ui()
+        u.pushbuffer()
         utility_commands.genignore(u, self.repo, self.wc_path)
         self.assertEqual(open(os.path.join(self.wc_path, '.hgignore')).read(),
                          '.hgignore\nsyntax:glob\nblah\notherblah\nbaz/magic\n')
@@ -195,10 +186,12 @@ class UtilityTests(test_util.TestBase):
         test_util.load_svndump_fixture(self.repo_path,
                                        'replace_trunk_with_branch.svndump')
         u = ui.ui()
+        u.pushbuffer()
         utility_commands.listauthors(u,
                                      args=[test_util.fileurl(self.repo_path)],
                                      authors=None)
-        self.assertEqual(u.stream.getvalue(), 'Augie\nevil\n')
+        actual = u.popbuffer()
+        self.assertEqual(actual, 'Augie\nevil\n')
 
 
     def test_list_authors_map(self):
--- a/utility_commands.py
+++ b/utility_commands.py
@@ -7,14 +7,6 @@ import cmdutil
 import util
 import hg_delta_editor
 
-def url(ui, repo, hg_repo_path, **opts):
-    """show the location (URL) of the Subversion repository
-    """
-    hge = hg_delta_editor.HgChangeReceiver(hg_repo_path,
-                                           ui_=ui)
-    ui.status(hge.url, '\n')
-
-
 def genignore(ui, repo, hg_repo_path, force=False, **opts):
     """generate .hgignore from svn:ignore properties.
     """
@@ -23,8 +15,11 @@ def genignore(ui, repo, hg_repo_path, fo
         raise hgutil.Abort('not overwriting existing .hgignore, try --force?')
     ignorefile = open(ignpath, 'w')
     ignorefile.write('.hgignore\nsyntax:glob\n')
-    hge = hg_delta_editor.HgChangeReceiver(hg_repo_path,
-                                           ui_=ui)
+    url = util.normalize_url(repo.ui.config('paths', 'default'))
+    user, passwd = util.getuserpass(opts)
+    svn = svnwrap.SubversionRepo(url, user, passwd)
+    hge = hg_delta_editor.HgChangeReceiver(path=hg_repo_path, repo=repo,
+                                           ui_=ui, uuid=svn.uuid)
     svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
                                  hge.revmap.iterkeys()))
     parent = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes)
@@ -33,11 +28,8 @@ def genignore(ui, repo, hg_repo_path, fo
         branchpath = 'trunk'
     else:
         branchpath = 'branches/%s' % br
-    url = hge.url
     if url[-1] == '/':
         url = url[:-1]
-    user, passwd = util.getuserpass(opts)
-    svn = svnwrap.SubversionRepo(url, user, passwd)
     dirs = [''] + [d[0] for d in svn.list_files(branchpath, r) if d[1] == 'd']
     for dir in dirs:
         props = svn.list_props('%s/%s/' % (branchpath,dir), r)
@@ -53,8 +45,11 @@ def genignore(ui, repo, hg_repo_path, fo
 def info(ui, repo, hg_repo_path, **opts):
     """show Subversion details similar to `svn info'
     """
-    hge = hg_delta_editor.HgChangeReceiver(hg_repo_path,
-                                           ui_=ui)
+    url = util.normalize_url(repo.ui.config('paths', 'default'))
+    user, passwd = util.getuserpass(opts)
+    svn = svnwrap.SubversionRepo(url, user, passwd)
+    hge = hg_delta_editor.HgChangeReceiver(path=hg_repo_path, repo=repo,
+                                           ui_=ui, uuid=svn.uuid)
     svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
                                  hge.revmap.iterkeys()))
     parent = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes)
@@ -71,7 +66,7 @@ def info(ui, repo, hg_repo_path, **opts)
         subdir = subdir.replace('branches/../', '')
     else:
         branchpath = '/branches/%s' % br
-    url = hge.url
+    url = util.normalize_url(repo.ui.config('paths', 'default'))
     if url[-1] == '/':
         url = url[:-1]
     url = '%s%s' % (url, branchpath)
@@ -87,7 +82,7 @@ Last Changed Author: %(author)s
 Last Changed Rev: %(revision)s
 Last Changed Date: %(date)s\n''' %
               {'reporoot': reporoot,
-               'uuid': open(hge.uuid_file).read(),
+               'uuid': hge.uuid,
                'url': url,
                'author': author,
                'revision': r,
@@ -125,7 +120,6 @@ def version(ui, **opts):
 
 nourl = ['version', 'listauthors']
 table = {
-    'url': url,
     'genignore': genignore,
     'info': info,
     'listauthors': listauthors,
--- a/wrappers.py
+++ b/wrappers.py
@@ -87,24 +87,36 @@ def diff(orig, ui, repo, *args, **opts):
                                                   }))
     ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev))
 
-
-def push(orig, ui, repo, dest=None, *args, **opts):
+def push(repo, dest="default", force=False, revs=None):
     """push revisions starting at a specified head back to Subversion.
     """
-    opts.pop('svn', None) # unused in this case
-    svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default')
-    if not cmdutil.issvnurl(svnurl):
-        return orig(ui, repo, dest=dest, *args, **opts)
+    assert not revs, 'designated revisions for push remains unimplemented.'
+    print dest
+    ui = repo.ui
+    svnurl = util.normalize_url(repo.ui.expandpath(dest))
     old_encoding = util.swap_out_encoding()
-    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
-    svnurl = util.normalize_url(svnurl)
     # split of #rev; TODO: implement --rev/#rev support
-    svnurl, revs, checkout = hg.parseurl(svnurl, opts.get('rev'))
-    if svnurl != hge.url:
-        raise hgutil.Abort('wrong subversion url!')
-    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
-                                 hge.revmap.iterkeys()))
-    user, passwd = util.getuserpass(opts)
+    svnurl, revs, checkout = hg.parseurl(svnurl, revs)
+    # TODO: do credentials specified in the URL still work?
+    user = repo.ui.config('hgsubversion', 'username')
+    passwd = repo.ui.config('hgsubversion', 'password')
+    svn = svnwrap.SubversionRepo(svnurl, user, passwd)
+    hge = hg_delta_editor.HgChangeReceiver(repo=repo, uuid=svn.uuid)
+
+    # Check if we are up-to-date with the Subversion repository.
+    if hge.last_known_revision() != svn.last_changed_rev:
+        # Based on localrepository.push() in localrepo.py:1559. 
+        # TODO: Ideally, we would behave exactly like other repositories:
+        #  - push everything by default
+        #  - handle additional heads in the same way
+        #  - allow pushing single revisions, branches, tags or heads using
+        #    the -r/--rev flag.
+        if force:
+            ui.warn("note: unsynced remote changes!\n")
+        else:
+            ui.warn("abort: unsynced remote changes!\n")
+            return None, 0
+
     # Strategy:
     # 1. Find all outgoing commits from this head
     if len(repo.parents()) != 1:
@@ -112,6 +124,8 @@ def push(orig, ui, repo, dest=None, *arg
         return 1
     workingrev = repo.parents()[0]
     ui.status('searching for changes\n')
+    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
+                                 hge.revmap.iterkeys()))
     outgoing = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, workingrev.node())
     if not (outgoing and len(outgoing)):
         ui.status('no changes found\n')
@@ -143,12 +157,10 @@ def push(orig, ui, repo, dest=None, *arg
                      old_ctx)
             return 1
         # 3. Fetch revisions from svn
-        # TODO this probably should pass in the source explicitly
-        r = pull(None, ui, repo, svn=True, stupid=opts.get('svn_stupid', False),
-                 username=user, password=passwd)
+        # TODO: this probably should pass in the source explicitly - rev too?
+        r = pull(repo, source=dest, force=force)
         assert not r or r == 0
         # 4. Find the new head of the target branch
-        repo = hg.repository(ui, hge.path)
         oldtipctx = repo[oldtip]
         replacement = [c for c in oldtipctx.children() if c not in old_children
                        and c.branch() == oldtipctx.branch()]
@@ -161,8 +173,9 @@ def push(orig, ui, repo, dest=None, *arg
                 if ctx.node() == oldest:
                     return
                 extra['branch'] = ctx.branch()
+            # TODO: can we avoid calling our own rebase wrapper here?
             rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn,
-                   svnsourcerev=needs_transplant, **opts)
+                   svnsourcerev=needs_transplant)
             repo = hg.repository(ui, hge.path)
             for child in repo[replacement.node()].children():
                 rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid)))
@@ -175,7 +188,8 @@ def push(orig, ui, repo, dest=None, *arg
                         if children:
                             child = children[0]
                         rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid)))
-        hge = hg_delta_editor.HgChangeReceiver(hge.path, ui_=ui)
+        # TODO: stop constantly creating the HgChangeReceiver instances.
+        hge = hg_delta_editor.HgChangeReceiver(hge.repo, ui_=ui, uuid=svn.uuid)
         svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys()))
     util.swap_out_encoding(old_encoding)
     return 0
@@ -234,25 +248,20 @@ def clone(orig, ui, source, dest=None, *
     return res
 
 
-def pull(orig, ui, repo, source="default", *args, **opts):
+def pull(repo, source="default", rev=None, force=False):
     """pull new revisions from Subversion
 
     Also takes svn, svn_stupid, and create_new_dest kwargs.
     """
-    svn = opts.pop('svn', None)
-    svn_stupid = opts.pop('svn_stupid', False)
-    create_new_dest = opts.pop('create_new_dest', False)
-    url = ((repo and repo.ui) or ui).expandpath(source)
-    if orig and not (cmdutil.issvnurl(url) or svn or create_new_dest):
-        return orig(ui, repo, source=source, *args, **opts)
-    svn_url = url
-    svn_url = util.normalize_url(svn_url)
+    url = repo.ui.expandpath(source)
+    svn_url = util.normalize_url(url)
+
     # Split off #rev; TODO: implement --rev/#rev support limiting the pulled/cloned revisions
-    svn_url, revs, checkout = hg.parseurl(svn_url, opts.get('rev'))
+    svn_url, revs, checkout = hg.parseurl(svn_url, rev)
     old_encoding = util.swap_out_encoding()
     # TODO implement skipto support
     skipto_rev = 0
-    have_replay = not svn_stupid
+    have_replay = not repo.ui.configbool('hgsubversion', 'stupid')
     if have_replay and not callable(
         delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover
         ui.status('You are using old Subversion SWIG bindings. Replay will not'
@@ -261,46 +270,23 @@ def pull(orig, ui, repo, source="default
                   ' contribute a patch to use the ctypes bindings instead'
                   ' of SWIG.\n')
         have_replay = False
-    initializing_repo = False
-    user, passwd = util.getuserpass(opts)
+
+    # FIXME: enable this
+    user = repo.ui.config('hgsubversion', 'username')
+    passwd = repo.ui.config('hgsubversion', 'password')
     svn = svnwrap.SubversionRepo(svn_url, user, passwd)
-    author_host = ui.config('hgsubversion', 'defaulthost', svn.uuid)
-    tag_locations = ['tags', ]
-    authors = opts.pop('svn_authors', None)
-    filemap = opts.pop('svn_filemap', None)
-    if repo:
-        hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo,
-                                                     subdir=svn.subdir,
-                                                     author_host=author_host,
-                                                     tag_locations=tag_locations,
-                                                     authors=authors,
-                                                     filemap=filemap)
-    else:
-        hg_editor = hg_delta_editor.HgChangeReceiver(ui_=ui,
-                                                     path=create_new_dest,
-                                                     subdir=svn.subdir,
-                                                     author_host=author_host,
-                                                     tag_locations=tag_locations,
-                                                     authors=authors,
-                                                     filemap=filemap)
-    hg_editor.opts = opts
-    if os.path.exists(hg_editor.uuid_file):
-        uuid = open(hg_editor.uuid_file).read()
-        assert uuid == svn.uuid
-        start = hg_editor.last_known_revision()
-    else:
-        open(hg_editor.uuid_file, 'w').write(svn.uuid)
-        open(hg_editor.svn_url_file, 'w').write(svn_url)
-        initializing_repo = True
-        start = skipto_rev
+    hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo, subdir=svn.subdir,
+                                                 uuid=svn.uuid)
+
+    start = max(hg_editor.last_known_revision(), skipto_rev)
+    initializing_repo = (hg_editor.last_known_revision() <= 0)
+    ui = repo.ui
 
     if initializing_repo and start > 0:
         raise hgutil.Abort('Revision skipping at repository initialization '
                            'remains unimplemented.')
 
     revisions = 0
-    if not initializing_repo:
-        oldheads = len(repo.changelog.heads())
 
     # start converting revisions
     for r in svn.revisions(start=start):
@@ -344,14 +330,6 @@ def pull(orig, ui, repo, source="default
         return
     else:
         ui.status("added %d svn revisions\n" % revisions)
-    if not initializing_repo:
-        newheads = len(repo.changelog.heads())
-        # postincoming needs to know if heads were added or removed
-        # calculation based on mercurial.localrepo.addchangegroup
-        # 0 means no changes, 1 no new heads, > 1 new heads, < 0 heads removed
-        modheads = newheads - oldheads + (newheads < oldheads and -1 or 1)
-        commands.postincoming(ui, repo, modheads, opts.get('update'), checkout)
-
 
 def rebase(orig, ui, repo, **opts):
     """rebase current unpushed revisions onto the Subversion head