changeset 339:13998e698d3e

Merge with crew.
author Augie Fackler <durin42@gmail.com>
date Tue, 26 May 2009 22:22:00 -0500
parents 47c0110046dc (diff) 4f4db3d2fdbb (current diff)
children 88ba55ad58c0
files hgsubversion/hg_delta_editor.py hgsubversion/svnexternals.py hgsubversion/svnwrap/svn_swig_wrapper.py hgsubversion/svnwrap/tests/test_svnwrap.py
diffstat 33 files changed, 559 insertions(+), 457 deletions(-) [+]
line wrap: on
line diff
rename from __init__.py
rename to hgsubversion/__init__.py
--- a/__init__.py
+++ b/hgsubversion/__init__.py
@@ -1,16 +1,24 @@
 '''integration with Subversion repositories
 
-This extension allows Mercurial to act as a Subversion client, for
-fast incremental, bidirectional updates.
+hgsubversion is an extension for Mercurial that allows it to act as a Subversion
+client, offering fast, incremental and bidirectional synchronisation.
 
-It is *not* ready yet for production use. You should only be using
-this if you're ready to hack on it, and go diving into the internals
-of Mercurial and/or Subversion.
+Please note that hgsubversion should not be considered stable software. It is
+not feature complete, and neither guarantees of functionality nor future
+compatability can be offered. It is, however, quite useful for the cases where
+it works, and a good platform for further improvements.
 
-Before using hgsubversion, it is *strongly* encouraged to run the
+Before using hgsubversion, we *strongly* encourage running the
 automated tests. See `README' in the hgsubversion directory for
 details.
+
+The operation of hgsubversion can be customised with the following variables:
+
+<list not written yet>
+
 '''
+# TODO: The docstring should be slightly more helpful, and at least mention all
+#       configuration settings we support
 
 import os
 import sys
@@ -18,21 +26,42 @@ import traceback
 
 from mercurial import commands
 from mercurial import extensions
+from mercurial import hg
 from mercurial import util as hgutil
 
 from svn import core
 
 import svncommands
-import tag_repo
+import svnrepo
 import util
 import wrappers
 import svnexternals
 
-def reposetup(ui, repo):
-    if not util.is_svn_repo(repo):
-        return
+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))
 
-    repo.__class__ = tag_repo.generate_repo_class(ui, repo)
+    return orig(ui, repo, *args, **opts)
 
 def uisetup(ui):
     """Do our UI setup.
@@ -51,25 +80,20 @@ 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"))
-    entry = extensions.wrapcommand(commands.table, 'pull',
-                                   wrappers.pull)
-    entry[1].append(('', 'svn', None, "pull from subversion"))
-    entry[1].append(('', 'svn-stupid', None, "use stupid replay during pull from svn"))
-
-    entry = extensions.wrapcommand(commands.table, 'clone',
-                                   wrappers.clone)
-    entry[1].extend([#('', 'skipto-rev', '0', 'skip commits before this revision.'),
-                     ('', 'svn-stupid', False, 'be stupid and use diffy replay.'),
-                     ('', 'svn-tag-locations', 'tags', 'Relative path to Subversion tags.'),
-                     ('', 'svn-authors', '', 'username mapping filename'),
-                     ('', 'svn-filemap', '',
-                      'remap file to exclude paths or include only certain paths'),
-                     ('', 'svn-no-branchnames', False, "don't record branch names in hg"),
-                     ])
+
+    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 = 'hgsubversion'
+
+    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')
@@ -116,7 +140,12 @@ def svn(ui, repo, subcommand, *args, **o
         else:
             raise
 
+def reposetup(ui, repo):
+    if repo.local():
+       svnrepo.generate_repo_class(ui, repo)
 
+for scheme in schemes:
+    hg.schemes[scheme] = svnrepo
 
 cmdtable = {
     "svn":
rename from cmdutil.py
rename to hgsubversion/cmdutil.py
rename from hg_delta_editor.py
rename to hgsubversion/hg_delta_editor.py
--- a/hg_delta_editor.py
+++ b/hgsubversion/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,21 +87,22 @@ class HgChangeReceiver(delta.Editor):
         if not ui_:
             ui_ = ui.ui()
         self.ui = ui_
-        if repo:
-            self.repo = repo
-            self.path = os.path.normpath(os.path.join(self.repo.path, '..'))
-        elif path:
-            self.path = path
-            self.__setup_repo(path)
-        else: #pragma: no cover
-            raise TypeError("Expected either path or repo argument")
+        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)
@@ -144,20 +145,31 @@ class HgChangeReceiver(delta.Editor):
             date = self.lastdate
         return date
 
-    def __setup_repo(self, repo_path):
+    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
         the Subversion metadata.
         """
-        if os.path.isdir(repo_path) and len(os.listdir(repo_path)):
-            self.repo = hg.repository(self.ui, repo_path)
-            assert os.path.isfile(self.revmap_file)
-            assert os.path.isfile(self.svn_url_file)
-            assert os.path.isfile(self.uuid_file)
+        if isinstance(arg, basestring):
+            self.repo = hg.repository(self.ui, arg,
+                                      create=(not os.path.exists(arg)))
+            self.path = os.path.normpath(os.path.join(arg, '..'))
+        elif arg:
+            self.repo = arg
+            self.path = os.path.normpath(os.path.join(self.repo.path, '..'))
+        else: #pragma: no cover
+            raise TypeError("editor requires either a path or a repository "
+                            "specified")
+
+        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:
-            self.repo = hg.repository(self.ui, repo_path, create=True)
-            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()
@@ -638,7 +650,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,
@@ -660,9 +672,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()):
@@ -684,16 +694,18 @@ class HgChangeReceiver(delta.Editor):
                 is_link = self.current_files_symlink.get(current_file, 'l' in flags)
                 if current_file in self.current_files:
                     data = self.current_files[current_file]
-                    if is_link:
-                        assert data.startswith('link ')
+                    if is_link and data.startswith('link '):
                         data = data[len('link '):]
+                    elif is_link:
+                        raise ValueError('file erronously marked as a link: '
+                                         '%s (%r)' % (current_file, flags))
                 else:
                     data = parent_ctx.filectx(path).data()
                 return context.memfilectx(path=path,
                                           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,
@@ -719,10 +731,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),
@@ -785,13 +795,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')
@@ -805,10 +833,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)
rename from maps.py
rename to hgsubversion/maps.py
rename from stupid.py
rename to hgsubversion/stupid.py
--- a/stupid.py
+++ b/hgsubversion/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,
rename from svncommands.py
rename to hgsubversion/svncommands.py
--- a/svncommands.py
+++ b/hgsubversion/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()
rename from svnexternals.py
rename to hgsubversion/svnexternals.py
--- a/svnexternals.py
+++ b/hgsubversion/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')
@@ -246,16 +248,17 @@ class externalsupdater:
 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
rename from tag_repo.py
rename to hgsubversion/svnrepo.py
--- a/tag_repo.py
+++ b/hgsubversion/svnrepo.py
@@ -1,23 +1,112 @@
+"""
+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
 
 import hg_delta_editor
+import util
+import wrappers
 
+def generate_repo_class(ui, repo):
+    """ This function generates the local repository wrapper. """
 
-def tags_from_tag_info(repo):
-    hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo)
-    for tag, source in hg_editor.tags.iteritems():
-        source_ha = hg_editor.get_parent_revision(source[1]+1, source[0])
-        yield 'tag/%s'%tag, node.hex(source_ha)
+    def localsvn(fn):
+        """
+        Filter for instance methods which only apply to local Subversion
+        repositories.
+        """
+        if util.is_svn_repo(repo):
+            return fn
+        else:
+            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 = getattr(repo.__class__, fn.__name__)
+        def wrapper(self, *args, **opts):
+            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
 
-def generate_repo_class(ui, repo):
+    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)
 
-    class svntagrepo(repo.__class__):
+        @remotesvn
+        def pull(self, remote, heads=None, force=False):
+            try:
+                lock = self.wlock()
+                wrappers.pull(self, source=remote.svnurl, rev=heads, force=force)
+            except KeyboardInterrupt:
+                pass
+            finally:
+                lock.release()
+
+        @localsvn
         def tags(self):
-            tags = dict((k, node.bin(v))
-                        for k,v in tags_from_tag_info(self))
-            hg_tags = super(svntagrepo, self).tags()
-            tags.update(hg_tags)
+            tags = super(svnlocalrepo, self).tags()
+            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] = target
             return tags
 
-    return svntagrepo
+    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', 'subversion'])
+
+    @property
+    def svnurl(self):
+        return util.normalize_url(self.path)
+
+    def url(self):
+        return self.path
+
+    def lookup(self, key):
+        return key
+
+    def cancopy(self):
+        return False
+
+    def heads(self, *args, **opts):
+        """
+        Whenever this function is hit, we abort. The traceback is useful for
+        figuring out where to intercept the functionality.
+        """
+        raise hgutil.Abort('command unavailable for Subversion repositories')
+
+def instance(ui, url, create):
+    if create:
+        raise hgutil.Abort('cannot create new remote Subversion repository')
+
+    return svnremoterepo(ui, url)
rename from svnwrap/__init__.py
rename to hgsubversion/svnwrap/__init__.py
rename from svnwrap/svn_ctypes_wrapper.py
rename to hgsubversion/svnwrap/svn_ctypes_wrapper.py
rename from svnwrap/svn_swig_wrapper.py
rename to hgsubversion/svnwrap/svn_swig_wrapper.py
rename from svnwrap/tests/__init__.py
rename to hgsubversion/svnwrap/tests/__init__.py
rename from svnwrap/tests/fixtures/project_root_at_repo_root.svndump
rename to hgsubversion/svnwrap/tests/fixtures/project_root_at_repo_root.svndump
rename from svnwrap/tests/fixtures/project_root_not_repo_root.svndump
rename to hgsubversion/svnwrap/tests/fixtures/project_root_not_repo_root.svndump
rename from svnwrap/tests/test_svnwrap.py
rename to hgsubversion/svnwrap/tests/test_svnwrap.py
rename from util.py
rename to hgsubversion/util.py
--- a/util.py
+++ b/hgsubversion/util.py
@@ -24,7 +24,7 @@ def version(ui):
 
 
 def normalize_url(svnurl):
-    if svnurl.startswith('svn+http'):
+    if svnurl.startswith('svn+') and not svnurl.startswith('svn+ssh'):
         svnurl = svnurl[4:]
     url, revs, checkout = hg.parseurl(svnurl)
     url = url.rstrip('/')
rename from utility_commands.py
rename to hgsubversion/utility_commands.py
--- a/utility_commands.py
+++ b/hgsubversion/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,
rename from wrappers.py
rename to hgsubversion/wrappers.py
--- a/wrappers.py
+++ b/hgsubversion/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 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
--- a/setup.py
+++ b/setup.py
@@ -23,8 +23,7 @@ setup(
     long_description = open(os.path.join(os.path.dirname(__file__),
                                          'README')).read(),
     keywords = 'mercurial',
-    packages = ['hgext.hgsubversion', 'hgext.hgsubversion.svnwrap'],
-    package_dir = {'hgext.hgsubversion': ''},
+    packages = ['hgsubversion', 'hgsubversion.svnwrap'],
     platforms = 'any',
     classifiers = [
         'License :: OSI Approved :: GNU General Public License (GPL)',
--- a/tests/run.py
+++ b/tests/run.py
@@ -2,7 +2,7 @@ import os
 import sys
 import unittest
 
-sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
 import test_binaryfiles
 import test_diff
@@ -54,5 +54,8 @@ if __name__ == '__main__':
         kwargs['descriptions'] = 3
         kwargs['verbosity'] = 2
 
+    # silence output when running outside nose
+    sys.stdout = os.tmpfile()
+
     runner = unittest.TextTestRunner(**kwargs)
     runner.run(suite())
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -2,7 +2,7 @@ import unittest
 
 from mercurial import ui
 
-import wrappers
+from hgsubversion import wrappers
 
 import test_util
 
@@ -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_externals.py
+++ b/tests/test_externals.py
@@ -2,7 +2,7 @@ import os, unittest
 
 from mercurial import commands
 
-import svnexternals
+from hgsubversion import svnexternals
 import test_util
 
 class TestFetchExternals(test_util.TestBase):
@@ -47,7 +47,7 @@ class TestFetchExternals(test_util.TestB
             ('http://svn.example.com/skin-maker@21 third-party/skins/toolkit',
              ('third-party/skins/toolkit', None, 'http://svn.example.com/skin-maker', '21')),
             ]
-        
+
         for line, expected in samples:
             self.assertEqual(expected, svnexternals.parsedefinition(line))
 
@@ -106,7 +106,7 @@ class TestFetchExternals(test_util.TestB
             svnexternals.updateexternals(ui, [rev], repo)
             for d in deps:
                 p = os.path.join(repo.root, d)
-                self.assertTrue(os.path.isdir(p), 
+                self.assertTrue(os.path.isdir(p),
                                 'missing: %s@%r' % (d, rev))
             for d in nodeps:
                 p = os.path.join(repo.root, d)
@@ -118,10 +118,10 @@ class TestFetchExternals(test_util.TestB
         commands.update(ui, repo)
         checkdeps(['deps/project1'], [], repo, 0)
         checkdeps(['deps/project1', 'deps/project2'], [], repo, 1)
-        checkdeps(['subdir/deps/project1', 'subdir2/deps/project1', 
-                   'deps/project2'], 
+        checkdeps(['subdir/deps/project1', 'subdir2/deps/project1',
+                   'deps/project2'],
                   ['deps/project1'], repo, 2)
-        checkdeps(['subdir/deps/project1', 'deps/project2'], 
+        checkdeps(['subdir/deps/project1', 'deps/project2'],
                   ['subdir2/deps/project1'], repo, 3)
         checkdeps(['subdir/deps/project1'], ['deps/project2'], repo, 4)
 
--- a/tests/test_fetch_branches.py
+++ b/tests/test_fetch_branches.py
@@ -5,7 +5,6 @@ from mercurial import node
 from mercurial import ui
 
 import test_util
-import wrappers
 
 
 class TestFetchBranches(test_util.TestBase):
@@ -18,7 +17,7 @@ class TestFetchBranches(test_util.TestBa
     def _load_fixture_and_fetch_with_anchor(self, fixture_name, anchor):
         test_util.load_svndump_fixture(self.repo_path, fixture_name)
         source = '%s#%s' % (test_util.fileurl(self.repo_path), anchor)
-        wrappers.clone(None, ui.ui(), source=source, dest=self.wc_path)
+        repo = hg.clone(ui.ui(), source=source, dest=self.wc_path)
         return hg.repository(ui.ui(), self.wc_path)
 
     def test_unrelatedbranch(self, stupid=False):
@@ -75,10 +74,10 @@ class TestFetchBranches(test_util.TestBa
                                             stupid, noupdate=False)
         self.assertEqual(repo[None].branch(), 'default')
         self.assertTrue('tip' not in repo[None].tags())
-    
+
     def test_branch_tip_update_to_default_stupid(self):
         self.test_branch_tip_update_to_default(True)
-    
+
     def test_branch_tip_update_to_branch_anchor(self):
         repo = self._load_fixture_and_fetch_with_anchor(
             'unorderedbranch.svndump', 'branch')
--- a/tests/test_fetch_command_regexes.py
+++ b/tests/test_fetch_command_regexes.py
@@ -1,6 +1,8 @@
-import stupid
 import unittest
 
+from hgsubversion import stupid
+
+
 two_empties = """Index: __init__.py
 ===================================================================
 Index: bar/__init__.py
--- 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,84 +1,22 @@
+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
 
 
-class PushOverSvnserveTests(test_util.TestBase):
-    def setUp(self):
-        test_util.TestBase.setUp(self)
-        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
-        time.sleep(2)
-        wrappers.clone(None, ui.ui(), source='svn://localhost/',
-                       dest=self.wc_path, noupdate=True)
-
-    def tearDown(self):
-        os.system('kill -9 %d' % self.svnserve_pid)
-        test_util.TestBase.tearDown(self)
-
-    def test_push_to_default(self, commit=True):
-        repo = self.repo
-        old_tip = repo['tip'].node()
-        expected_parent = repo['default'].node()
-        def file_callback(repo, memctx, path):
-            if path == 'adding_file':
-                return context.memfilectx(path=path,
-                                          data='foo',
-                                          islink=False,
-                                          isexec=False,
-                                          copied=False)
-            raise IOError()
-        ctx = context.memctx(repo,
-                             (repo['default'].node(), node.nullid),
-                             'automated test',
-                             ['adding_file'],
-                             file_callback,
-                             'an_author',
-                             '2008-10-07 20:59:48 -0500',
-                             {'branch': 'default',})
-        new_hash = repo.commitctx(ctx)
-        if not commit:
-            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)
-        tip = self.repo['tip']
-        self.assertNotEqual(oldauthor, tip.user())
-        self.assertNotEqual(tip.node(), old_tip)
-        self.assertEqual(tip.parents()[0].node(), expected_parent)
-        self.assertEqual(tip['adding_file'].data(), 'foo')
-        self.assertEqual(tip.branch(), 'default')
-
-
 class PushTests(test_util.TestBase):
     def setUp(self):
         test_util.TestBase.setUp(self)
@@ -111,6 +49,58 @@ class PushTests(test_util.TestBase):
         tip = self.repo['tip']
         self.assertEqual(tip.node(), old_tip)
 
+    def test_push_over_svnserve(self, commit=True):
+        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')
+        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]
+
+        self.svnserve_pid = subprocess.Popen(args).pid
+        try:
+            time.sleep(2)
+            import shutil
+            shutil.rmtree(self.wc_path)
+            commands.clone(ui.ui(), 'svn://%s:%d/' % (self.host, self.port),
+                           self.wc_path, noupdate=True)
+
+            repo = self.repo
+            old_tip = repo['tip'].node()
+            expected_parent = repo['default'].node()
+            def file_callback(repo, memctx, path):
+                if path == 'adding_file':
+                    return context.memfilectx(path=path,
+                                              data='foo',
+                                              islink=False,
+                                              isexec=False,
+                                              copied=False)
+                raise IOError()
+            ctx = context.memctx(repo,
+                                 (repo['default'].node(), node.nullid),
+                                 'automated test',
+                                 ['adding_file'],
+                                 file_callback,
+                                 'an_author',
+                                 '2008-10-07 20:59:48 -0500',
+                                 {'branch': 'default',})
+            new_hash = repo.commitctx(ctx)
+            if not commit:
+                return # some tests use this test as an extended setup.
+            hg.update(repo, repo['tip'].node())
+            oldauthor = repo['tip'].user()
+            commands.push(repo.ui, repo)
+            tip = self.repo['tip']
+            self.assertNotEqual(oldauthor, tip.user())
+            self.assertNotEqual(tip.node(), old_tip)
+            self.assertEqual(tip.parents()[0].node(), expected_parent)
+            self.assertEqual(tip['adding_file'].data(), 'foo')
+            self.assertEqual(tip.branch(), 'default')
+        finally:
+            os.kill(self.svnserve_pid, 9)
 
     def test_push_to_default(self, commit=True):
         repo = self.repo
@@ -172,7 +162,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(),
@@ -424,7 +414,7 @@ class PushTests(test_util.TestBase):
 
 
 def suite():
-    test_classes = [PushTests, PushOverSvnserveTests]
+    test_classes = [PushTests, ]
     tests = []
     # This is the quickest hack I could come up with to load all the tests from
     # both classes. Would love a patch that simplifies this without adding
--- a/tests/test_rebuildmeta.py
+++ b/tests/test_rebuildmeta.py
@@ -5,8 +5,8 @@ import unittest
 from mercurial import hg
 from mercurial import ui
 
-import svncommands
-import hg_delta_editor
+from hgsubversion import svncommands
+from hgsubversion import hg_delta_editor
 
 import test_util
 
@@ -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
+from hgsubversion 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_urls.py
+++ b/tests/test_urls.py
@@ -1,6 +1,6 @@
 import test_util
 import unittest
-from svnwrap.svn_swig_wrapper import parse_url
+from hgsubversion.svnwrap.svn_swig_wrapper import parse_url
 
 class TestSubversionUrls(test_util.TestBase):
     def test_standard_url(self):
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -9,11 +9,12 @@ 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
+from hgsubversion import util
 
 # Fixtures that need to be pulled at a subdirectory of the repo path
 subdir = {'truncatedhistory.svndump': '/project2',
@@ -30,29 +31,32 @@ def fileurl(path):
     path = urllib.pathname2url(path)
     if drive:
         drive = '/' + drive
-    url = 'file://%s%s' % (drive, path)
+    url = 'svn+file://%s%s' % (drive, path)
     return url
 
 def load_svndump_fixture(path, fixture_name):
     '''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 +73,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()
@@ -101,17 +81,24 @@ class TestBase(unittest.TestCase):
         self.hgrc = os.path.join(self.tmpdir, '.hgrc')
         os.environ['HGRCPATH'] = self.hgrc
         rc = open(self.hgrc, 'w')
-        rc.write('[extensions]\nhgsubversion=')
+        for l in '[extensions]', 'hgsubversion=':
+            print >> rc, l
 
         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.patch = (ui.ui.write_err, ui.ui.write)
+        setattr(ui.ui, self.patch[0].func_name, self.patch[1])
 
     def tearDown(self):
         rmtree(self.tmpdir)
         os.chdir(self.oldwd)
-        ui.ui = self._real_ui
+        setattr(ui.ui, self.patch[0].func_name, self.patch[0])
 
     def _load_fixture_and_fetch(self, fixture_name, subdir='', stupid=False):
         return load_fixture_and_fetch(fixture_name, self.repo_path,
@@ -125,17 +112,18 @@ 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)
 
     def svnls(self, path, rev='HEAD'):
         path = self.repo_path + '/' + path
-        path = fileurl(path)
+        path = util.normalize_url(fileurl(path))
         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,9 +8,10 @@ from mercurial import revlog
 from mercurial import context
 from mercurial import node
 
-import utility_commands
+from hgsubversion import util
+from hgsubversion import utility_commands
 import test_util
-import wrappers
+from hgsubversion import wrappers
 
 expected_info_output = '''URL: %(repourl)s/%(branch)s
 Repository Root: %(repourl)s
@@ -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):