changeset 257:ffccf0080e54

Move wrappers for hg commands to their own module.
author Augie Fackler <durin42@gmail.com>
date Fri, 10 Apr 2009 22:38:29 -0500
parents 7932d098cb5f
children 4ab27ddbae51
files __init__.py cmdutil.py hg_delta_editor.py svncommands.py tests/comprehensive/test_stupid_pull.py tests/test_diff.py tests/test_fetch_command.py tests/test_fetch_mappings.py tests/test_fetch_truncated.py tests/test_push_command.py tests/test_util.py tests/test_utility_commands.py utility_commands.py wrappers.py
diffstat 14 files changed, 389 insertions(+), 373 deletions(-) [+]
line wrap: on
line diff
--- a/__init__.py
+++ b/__init__.py
@@ -18,7 +18,6 @@ import traceback
 
 from mercurial import commands
 from mercurial import extensions
-from mercurial import hg
 from mercurial import util as hgutil
 
 from svn import core
@@ -26,6 +25,7 @@ from svn import core
 import svncommands
 import tag_repo
 import util
+import wrappers
 
 def reposetup(ui, repo):
     if not util.is_svn_repo(repo):
@@ -41,26 +41,26 @@ def uisetup(ui):
      * outgoing -> utility_commands.outgoing
      """
     entry = extensions.wrapcommand(commands.table, 'parents',
-                                   utility_commands.parent)
+                                   wrappers.parent)
     entry[1].append(('', 'svn', None, "show parent svn revision instead"))
     entry = extensions.wrapcommand(commands.table, 'outgoing',
-                                   utility_commands.outgoing)
+                                   wrappers.outgoing)
     entry[1].append(('', 'svn', None, "show revisions outgoing to subversion"))
     entry = extensions.wrapcommand(commands.table, 'diff',
-                                   svncommands.diff)
+                                   wrappers.diff)
     entry[1].append(('', 'svn', None,
                      "show svn-style diffs, default against svn parent"))
     entry = extensions.wrapcommand(commands.table, 'push',
-                                   svncommands.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',
-                                   svncommands.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',
-                                   svncommands.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.'),
@@ -68,9 +68,6 @@ def uisetup(ui):
                      ('', 'svn-filemap', '',
                       'remap file to exclude paths or include only certain paths'),
                      ])
-#         (svn_fetch,
-#          ,
-#          'hg svnclone source [dest]'),
 
 
 def svn(ui, repo, subcommand, *args, **opts):
--- a/cmdutil.py
+++ b/cmdutil.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python
 import re
 import os
+import urllib
 
 from mercurial import util as hgutil
 
@@ -278,16 +279,19 @@ def commit_from_rev(ui, repo, rev_ctx, h
         else:
             raise
 
-def filecheck(path):
-    for x in ('locks', 'hooks', 'format', 'db', ):
-        if not os.path.exists(os.path.join(path, x)):
-            return False
     return True
 
+def islocalrepo(url):
+    if not url.startswith('file:///'):
+        return False
+    path = urllib.unquote(url[len('file://'):])
+    while '/' in path:
+        if reduce(lambda x,y: x and y,
+                  map(lambda p: os.path.exists(os.path.join(path, p)),
+                      ('hooks', 'format', 'db', ))):
+            return True
+        path = path.rsplit('/', 1)[0]
+    return False
 
 def issvnurl(url):
-    return url.startswith('svn+') or (
-        url.startswith('file://') and
-        reduce(lambda x,y: x and y,
-               map(lambda p: os.path.exists(os.path.join(url[7:], p)),
-                   ('locks', 'hooks', 'format', 'db', ))))
+    return url.startswith('svn') or islocalrepo(url)
--- a/hg_delta_editor.py
+++ b/hg_delta_editor.py
@@ -710,9 +710,9 @@ class HgChangeReceiver(delta.Editor):
         self.clear_current_info()
 
     def authorforsvnauthor(self, author):
-        if(author in self.authors):
+        if author in self.authors:
             return self.authors[author]
-        return '%s%s' %(author, self.author_host)
+        return '%s%s' % (author, self.author_host)
 
     def svnauthorforauthor(self, author):
         for svnauthor, hgauthor in self.authors.iteritems():
--- a/svncommands.py
+++ b/svncommands.py
@@ -1,160 +1,17 @@
 import os
 import cPickle as pickle
 
-from mercurial import cmdutil as hgcmdutil
-from mercurial import commands
 from mercurial import hg
 from mercurial import node
-from mercurial import patch
 from mercurial import util as hgutil
 
-from svn import core
-from svn import delta
 
 import hg_delta_editor
 import svnwrap
-import stupid as stupidmod
-import cmdutil
 import util
 import utility_commands
 
 
-def clone(orig, ui, source, dest=None, **opts):
-    '''clone Subversion repository to a local Mercurial repository.
-
-    If no destination directory name is specified, it defaults to the
-    basename of the source plus "-hg".
-
-    You can specify multiple paths for the location of tags using comma
-    separated values.
-    '''
-    svnurl = ui.expandpath(source)
-    if not cmdutil.issvnurl(svnurl):
-        orig(ui, repo, source=source, dest=dest, *args, **opts)
-
-    if not dest:
-        dest = hg.defaultdest(source) + '-hg'
-        ui.status("Assuming destination %s\n" % dest)
-
-    if os.path.exists(dest):
-        raise hgutil.Abort("destination '%s' already exists" % dest)
-    url = util.normalize_url(svnurl)
-    res = -1
-    try:
-        try:
-            res = pull(None, ui, None, True, opts.pop('svn_stupid', False),
-                       source=url, create_new_dest=dest, **opts)
-        except core.SubversionException, e:
-            if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED:
-                raise hgutil.Abort('It appears svn does not trust the ssl cert for this site.\n'
-                         'Please try running svn ls on that url first.')
-            raise
-    finally:
-        if os.path.exists(dest):
-            repo = hg.repository(ui, dest)
-            fp = repo.opener("hgrc", "w", text=True)
-            fp.write("[paths]\n")
-            # percent needs to be escaped for ConfigParser
-            fp.write("default = %(url)s\nsvn = %(url)s\n" % {'url': svnurl.replace('%', '%%')})
-            fp.close()
-            if res is None or res == 0:
-                commands.update(ui, repo, repo['tip'].node())
-
-    return res
-
-
-def pull(orig, ui, repo, svn=None, svn_stupid=False, source="default", create_new_dest=False,
-         *args, **opts):
-    """pull new revisions from Subversion
-    """
-    url = ui.expandpath(source)
-    if not (cmdutil.issvnurl(url) or svn or create_new_dest):
-        orig(ui, repo, source=source, *args, **opts)
-    svn_url = url
-    svn_url = util.normalize_url(svn_url)
-    old_encoding = util.swap_out_encoding()
-    # TODO implement skipto support
-    skipto_rev = 0
-    have_replay = not svn_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'
-                  ' work until you upgrade to 1.5.0 or newer. Falling back to'
-                  ' a slower method that may be buggier. Please upgrade, or'
-                  ' contribute a patch to use the ctypes bindings instead'
-                  ' of SWIG.\n')
-        have_replay = False
-    initializing_repo = False
-    user = opts.get('username', hgutil.getuser())
-    passwd = opts.get('password', '')
-    svn = svnwrap.SubversionRepo(svn_url, user, passwd)
-    author_host = "@%s" % svn.uuid
-    # TODO these should be configurable again, but I'm torn on how.
-    # Maybe this should be configured in .hg/hgrc for each repo? Seems vaguely reasonable.
-    tag_locations = ['tags', ]
-    authors = None
-    filemap = None
-    if repo:
-        hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo)
-    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)
-    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
-
-    if initializing_repo and start > 0:
-        raise hgutil.Abort('Revision skipping at repository initialization '
-                           'remains unimplemented.')
-
-    # start converting revisions
-    for r in svn.revisions(start=start):
-        valid = True
-        hg_editor.update_branch_tag_map_for_rev(r)
-        for p in r.paths:
-            if hg_editor._is_path_valid(p):
-                valid = True
-                break
-        if valid:
-            # got a 502? Try more than once!
-            tries = 0
-            converted = False
-            while not converted:
-                try:
-                    util.describe_revision(ui, r)
-                    if have_replay:
-                        try:
-                            cmdutil.replay_convert_rev(hg_editor, svn, r)
-                        except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover
-                            ui.status('%s\n' % e.message)
-                            stupidmod.print_your_svn_is_old_message(ui)
-                            have_replay = False
-                            stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r)
-                    else:
-                        stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r)
-                    converted = True
-                except core.SubversionException, e: #pragma: no cover
-                    if (e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED
-                        and '502' in str(e)
-                        and tries < 3):
-                        tries += 1
-                        ui.status('Got a 502, retrying (%s)\n' % tries)
-                    else:
-                        raise hgutil.Abort(*e.args)
-    util.swap_out_encoding(old_encoding)
-
-
 def incoming(ui, svn_url, hg_repo_path, skipto_rev=0, stupid=None,
              tag_locations='tags', authors=None, filemap=None, **opts):
     """show incoming revisions from Subversion
@@ -204,125 +61,6 @@ def incoming(ui, svn_url, hg_repo_path, 
                                   str(r.__getattribute__(attr)).strip(), ))
 
 
-def push(orig, ui, repo, dest=None, **opts):
-    """push revisions starting at a specified head back to Subversion.
-    """
-    svnurl = ui.expandpath(dest or 'default-push', dest or 'default')
-    if not cmdutil.issvnurl(svnurl):
-        orig(ui, repo, dest=dest, *args, **opts)
-    old_encoding = util.swap_out_encoding()
-    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
-    assert svnurl == hge.url
-    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
-                                 hge.revmap.iterkeys()))
-    user = opts.get('username', hgutil.getuser())
-    passwd = opts.get('password', '')
-
-    # Strategy:
-    # 1. Find all outgoing commits from this head
-    if len(repo.parents()) != 1:
-        ui.status('Cowardly refusing to push branch merge')
-        return 1
-    workingrev = repo.parents()[0]
-    outgoing = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, workingrev.node())
-    if not (outgoing and len(outgoing)):
-        ui.status('No revisions to push.')
-        return 0
-    while outgoing:
-        oldest = outgoing.pop(-1)
-        old_ctx = repo[oldest]
-        if len(old_ctx.parents()) != 1:
-            ui.status('Found a branch merge, this needs discussion and '
-                      'implementation.')
-            return 1
-        base_n = old_ctx.parents()[0].node()
-        old_children = repo[base_n].children()
-        svnbranch = repo[base_n].branch()
-        oldtip = base_n
-        samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch
-                              and c.node() in svn_commit_hashes]
-        while samebranchchildren:
-            oldtip = samebranchchildren[0].node()
-            samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch
-                                  and c.node() in svn_commit_hashes]
-        # 2. Commit oldest revision that needs to be pushed
-        base_revision = svn_commit_hashes[base_n][0]
-        try:
-            cmdutil.commit_from_rev(ui, repo, old_ctx, hge, svnurl,
-                                    base_revision, user, passwd)
-        except cmdutil.NoFilesException:
-            ui.warn("Could not push revision %s because it had no changes in svn.\n" %
-                     old_ctx)
-            return 1
-        # 3. Fetch revisions from svn
-        r = pull(None, ui, repo, True, stupid=opts.get('svn_stupid', False),
-                 username=user, password=passwd)
-        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()]
-        assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement
-        replacement = replacement[0]
-        # 5. Rebase all children of the currently-pushing rev to the new branch
-        heads = repo.heads(old_ctx.node())
-        for needs_transplant in heads:
-            def extrafn(ctx, extra):
-                if ctx.node() == oldest:
-                    return
-                extra['branch'] = ctx.branch()
-            utility_commands.rebase(ui, repo, extrafn=extrafn,
-                                    sourcerev=needs_transplant, **opts)
-            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)))
-                if rebasesrc in outgoing:
-                    while rebasesrc in outgoing:
-                        rebsrcindex = outgoing.index(rebasesrc)
-                        outgoing = (outgoing[0:rebsrcindex] +
-                                    [child.node(), ] + outgoing[rebsrcindex+1:])
-                        children = [c for c in child.children() if c.branch() == child.branch()]
-                        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)
-        svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys()))
-    util.swap_out_encoding(old_encoding)
-    return 0
-
-
-def diff(orig, ui, repo, *args, **opts):
-    """show a diff of the most recent revision against its parent from svn
-    """
-    if not opts.get('svn', False) or opts.get('change', None):
-        return orig(ui, repo, *args, **opts)
-    svn_commit_hashes = {}
-    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
-    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
-                                 hge.revmap.iterkeys()))
-    if not opts.get('rev', None):
-        parent = repo.parents()[0]
-        o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes,
-                                      parent.node())
-        if o_r:
-            parent = repo[o_r[-1]].parents()[0]
-        opts['rev'] = ['%s:.' % node.hex(parent.node()), ]
-    node1, node2 = hgcmdutil.revpair(repo, opts['rev'])
-    baserev, _junk = svn_commit_hashes.get(node1, (-1, 'junk', ))
-    newrev, _junk = svn_commit_hashes.get(node2, (-1, 'junk', ))
-    it = patch.diff(repo, node1, node2,
-                    opts=patch.diffopts(ui, opts={'git': True,
-                                                  'show_function': False,
-                                                  'ignore_all_space': False,
-                                                  'ignore_space_change': False,
-                                                  'ignore_blank_lines': False,
-                                                  'unified': True,
-                                                  'text': False,
-                                                  }))
-    ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev))
-
-
 def rebuildmeta(ui, repo, hg_repo_path, args, **opts):
     """rebuild hgsubversion metadata using values stored in revisions
     """
--- a/tests/comprehensive/test_stupid_pull.py
+++ b/tests/comprehensive/test_stupid_pull.py
@@ -6,7 +6,7 @@ from mercurial import hg
 from mercurial import ui
 
 from tests import test_util
-import svncommands
+import wrappers
 
 
 def _do_case(self, name):
@@ -18,8 +18,8 @@ def _do_case(self, name):
     checkout_path = self.repo_path
     if subdir:
         checkout_path += '/' + subdir
-    svncommands.pull(ui.ui(), svn_url=test_util.fileurl(checkout_path),
-                     hg_repo_path=wc2_path, stupid=True)
+    wrappers.clone(None, ui.ui(), source=test_util.fileurl(checkout_path),
+                     dest=wc2_path, stupid=True, noupdate=True)
     self.repo2 = hg.repository(ui.ui(), wc2_path)
     self.assertEqual(self.repo.branchtags(), self.repo2.branchtags())
     self.assertEqual(pickle.load(open(os.path.join(self.wc_path, '.hg', 'svn', 'tag_info'))),
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -2,7 +2,7 @@ import unittest
 
 from mercurial import ui
 
-import svncommands
+import wrappers
 
 import test_util
 
@@ -32,7 +32,7 @@ class DiffTests(test_util.TestBase):
                             ('alpha', 'alpha', 'alpha\n\nadded line\n'),
                             ])
         u = ui.ui()
-        svncommands.diff(lambda x,y,z: None,
+        wrappers.diff(lambda x,y,z: None,
                          u, self.repo, svn=True)
         self.assertEqual(u.stream.getvalue(), expected_diff_output)
 
--- a/tests/test_fetch_command.py
+++ b/tests/test_fetch_command.py
@@ -145,9 +145,6 @@ class TestBasicRepoLayout(test_util.Test
 
     def test_fetch_when_trunk_has_no_files(self, stupid=False):
         repo = self._load_fixture_and_fetch('file_not_in_trunk_root.svndump', stupid=stupid)
-        print repo['tip'].branch()
-        print repo['tip']
-        print repo['tip'].files()
         self.assertEqual(repo['tip'].branch(), 'default')
 
     def test_fetch_when_trunk_has_no_files_stupid(self):
--- a/tests/test_fetch_mappings.py
+++ b/tests/test_fetch_mappings.py
@@ -7,7 +7,7 @@ from mercurial import ui
 from mercurial import node
 
 import test_util
-import svncommands
+import wrappers
 
 class MapTests(test_util.TestBase):
     @property
@@ -23,9 +23,8 @@ class MapTests(test_util.TestBase):
         authormap = open(self.authors, 'w')
         authormap.write("Augie=Augie Fackler <durin42@gmail.com>\n")
         authormap.close()
-        svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path),
-                         hg_repo_path=self.wc_path, stupid=stupid,
-                         authors=self.authors)
+        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
+                         dest=self.wc_path, stupid=stupid, svn_authors=self.authors)
         self.assertEqual(self.repo[0].user(),
                          'Augie Fackler <durin42@gmail.com>')
         self.assertEqual(self.repo['tip'].user(),
@@ -39,9 +38,9 @@ class MapTests(test_util.TestBase):
         authormap = open(self.authors, 'w')
         authormap.write("evil=Testy <test@test>")
         authormap.close()
-        svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path),
-                         hg_repo_path=self.wc_path, stupid=stupid,
-                         authors=self.authors)
+        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
+                         dest=self.wc_path, stupid=stupid,
+                         svn_authors=self.authors)
         self.assertEqual(self.repo[0].user(),
                          'Augie@5b65bade-98f3-4993-a01f-b7a6710da339')
         self.assertEqual(self.repo['tip'].user(),
@@ -55,9 +54,9 @@ class MapTests(test_util.TestBase):
         filemap = open(self.filemap, 'w')
         filemap.write("include alpha\n")
         filemap.close()
-        svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path),
-                         hg_repo_path=self.wc_path, stupid=stupid,
-                         filemap=self.filemap)
+        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
+                         dest=self.wc_path, stupid=stupid,
+                         svn_filemap=self.filemap)
         self.assertEqual(node.hex(self.repo[0].node()), '88e2c7492d83e4bf30fbb2dcbf6aa24d60ac688d')
         self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155')
 
@@ -69,9 +68,9 @@ class MapTests(test_util.TestBase):
         filemap = open(self.filemap, 'w')
         filemap.write("exclude alpha\n")
         filemap.close()
-        svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path),
-                         hg_repo_path=self.wc_path, stupid=stupid,
-                         filemap=self.filemap)
+        wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path),
+                         dest=self.wc_path, stupid=stupid,
+                         svn_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
@@ -3,7 +3,7 @@ import unittest
 from mercurial import hg
 from mercurial import ui
 
-import svncommands
+import wrappers
 import test_util
 
 class TestFetchTruncatedHistory(test_util.TestBase):
@@ -11,8 +11,9 @@ class TestFetchTruncatedHistory(test_uti
         # 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')
-        svncommands.pull(ui.ui(), svn_url=svn_url,
-                         hg_repo_path=self.wc_path, stupid=stupid)
+        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:
--- a/tests/test_push_command.py
+++ b/tests/test_push_command.py
@@ -9,7 +9,7 @@ from mercurial import node
 from mercurial import ui
 from mercurial import revlog
 
-import svncommands
+import wrappers
 import test_util
 import time
 
@@ -37,8 +37,8 @@ class PushOverSvnserveTests(test_util.Te
         args = ['svnserve', '-d', '--foreground', '-r', self.repo_path]
         self.svnserve_pid = subprocess.Popen(args).pid
         time.sleep(2)
-        svncommands.pull(ui.ui(), svn_url='svn://localhost/',
-                         hg_repo_path=self.wc_path)
+        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)
@@ -68,9 +68,10 @@ class PushOverSvnserveTests(test_util.Te
         if not commit:
             return # some tests use this test as an extended setup.
         hg.update(repo, repo['tip'].node())
-        svncommands.push(ui.ui(), repo=self.repo, hg_repo_path=self.wc_path,
-                         svn_url='svn://localhost/')
+        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')
@@ -170,9 +171,7 @@ class PushTests(test_util.TestBase):
         newhash = self.repo.commitctx(ctx)
         repo = self.repo
         hg.update(repo, newhash)
-        svncommands.push(ui.ui(), repo=repo,
-                         svn_url=test_util.fileurl(self.repo_path),
-                         hg_repo_path=self.wc_path)
+        wrappers.push(None, ui.ui(), repo=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(),
@@ -285,11 +284,11 @@ class PushTests(test_util.TestBase):
                              '2008-10-29 21:26:00 -0500',
                              {'branch': 'default', })
         new_hash = repo.commitctx(ctx)
-        hg.update(repo, repo['tip'].node())
+        hg.clean(repo, repo['tip'].node())
         self.pushrevisions()
         tip = self.repo['tip']
         self.assertNotEqual(tip.node(), new_hash)
-        self.assert_('@' in tip.user())
+        self.assert_('@' in self.repo['tip'].user())
         self.assertEqual(tip['gamma'].flags(), 'x')
         self.assertEqual(tip['gamma'].data(), 'foo')
         self.assertEqual([x for x in tip.manifest().keys() if 'x' not in
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -13,7 +13,7 @@ from mercurial import hg
 from mercurial import node
 from mercurial import ui
 
-import svncommands
+import wrappers
 
 # Fixtures that need to be pulled at a subdirectory of the repo path
 subdir = {'truncatedhistory.svndump': '/project2',
@@ -48,8 +48,8 @@ def load_fixture_and_fetch(fixture_name,
     load_svndump_fixture(repo_path, fixture_name)
     if subdir:
         repo_path += '/' + subdir
-    svncommands.pull(ui.ui(), svn_url=fileurl(repo_path),
-                     hg_repo_path=wc_path, stupid=stupid)
+    wrappers.clone(None, ui.ui(), source=fileurl(repo_path),
+                     dest=wc_path, stupid=stupid, noupdate=True)
     repo = hg.repository(ui.ui(), wc_path)
     return repo
 
@@ -121,8 +121,7 @@ class TestBase(unittest.TestCase):
 
     def pushrevisions(self, stupid=False):
         before = len(self.repo)
-        svncommands.push(ui.ui(), repo=self.repo, hg_repo_path=self.wc_path,
-                         svn_url=fileurl(self.repo_path), stupid=stupid)
+        wrappers.push(None, ui.ui(), repo=self.repo, stupid=stupid)
         after = len(self.repo)
         self.assertEqual(0, after - before)
 
--- a/tests/test_utility_commands.py
+++ b/tests/test_utility_commands.py
@@ -8,8 +8,8 @@ from mercurial import context
 from mercurial import node
 
 import utility_commands
-import svncommands
 import test_util
+import wrappers
 
 expected_info_output = '''URL: %(repourl)s/%(branch)s
 Repository Root: %(repourl)s
@@ -65,7 +65,7 @@ class UtilityTests(test_util.TestBase):
                              {'branch': 'localbranch', })
         new = self.repo.commitctx(ctx)
         hg.update(self.repo, new)
-        utility_commands.parent(lambda x, y: None, u, self.repo, svn=True)
+        wrappers.parent(lambda x, y: None, u, self.repo, svn=True)
         self.assertEqual(u.stream.getvalue(),
                          'changeset:   3:4e256962fc5d\n'
                          'branch:      the_branch\n'
@@ -76,17 +76,17 @@ class UtilityTests(test_util.TestBase):
         hg.update(self.repo, 'default')
         # Make sure styles work
         u = ui.ui()
-        utility_commands.parent(lambda x, y: None, u, self.repo, svn=True, style='compact')
+        wrappers.parent(lambda x, y: None, u, self.repo, svn=True, style='compact')
         self.assertEqual(u.stream.getvalue(),
                          '4:1   1083037b18d8   2008-10-08 01:39 +0000   durin\n'
                          '  Add gamma on trunk.\n\n')
         # custom templates too
         u = ui.ui()
-        utility_commands.parent(lambda x, y: None, u, self.repo, svn=True, template='{node}\n')
+        wrappers.parent(lambda x, y: None, u, self.repo, svn=True, template='{node}\n')
         self.assertEqual(u.stream.getvalue(), '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n')
 
         u = ui.ui()
-        utility_commands.parent(lambda x, y: None, u, self.repo, svn=True)
+        wrappers.parent(lambda x, y: None, u, self.repo, svn=True)
         self.assertEqual(u.stream.getvalue(),
                          'changeset:   4:1083037b18d8\n'
                          'parent:      1:c95251e0dd04\n'
@@ -114,7 +114,7 @@ class UtilityTests(test_util.TestBase):
                              {'branch': 'localbranch', })
         new = self.repo.commitctx(ctx)
         hg.update(self.repo, new)
-        utility_commands.outgoing(lambda x,y,z: None, u, self.repo, svn=True)
+        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'
@@ -127,7 +127,7 @@ class UtilityTests(test_util.TestBase):
                                                '\n'))
         hg.update(self.repo, 'default')
         u = ui.ui()
-        utility_commands.outgoing(lambda x,y,z: None, u, self.repo, svn=True)
+        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):
@@ -168,9 +168,9 @@ class UtilityTests(test_util.TestBase):
         """Verify url gets normalized on initial clone.
         """
         test_util.load_svndump_fixture(self.repo_path, 'two_revs.svndump')
-        svncommands.pull(ui.ui(),
-                         svn_url=test_util.fileurl(self.repo_path) + '/',
-                         hg_repo_path=self.wc_path, stupid=False)
+        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)
@@ -181,9 +181,9 @@ class UtilityTests(test_util.TestBase):
         """Verify url gets normalized on initial clone.
         """
         test_util.load_svndump_fixture(self.repo_path, 'ignores.svndump')
-        svncommands.pull(ui.ui(),
-                         svn_url=test_util.fileurl(self.repo_path) + '/',
-                         hg_repo_path=self.wc_path, stupid=False)
+        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.genignore(u, self.repo, self.wc_path)
--- a/utility_commands.py
+++ b/utility_commands.py
@@ -1,7 +1,5 @@
 import os
 
-import mercurial
-from mercurial import cmdutil as hgcmdutil
 from mercurial import node
 from mercurial import util as hgutil
 from hgext import rebase as hgrebase
@@ -102,22 +100,6 @@ Last Changed Date: %(date)s\n''' %
               })
 
 
-def parent(orig, ui, repo, *args, **opts):
-    """show Mercurial & Subversion parents of the working dir or revision
-    """
-    if not opts.get('svn', False):
-        return orig(ui, repo, *args, **opts)
-    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
-    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
-                                 hge.revmap.iterkeys()))
-    ha = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes)
-    if ha.node() == node.nullid:
-        raise mutil.Abort('No parent svn revision!')
-    displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False)
-    displayer.show(ha)
-    return 0
-
-
 def rebase(ui, repo, extrafn=None, sourcerev=None, **opts):
     """rebase current unpushed revisions onto the Subversion head
 
@@ -166,26 +148,6 @@ def rebase(ui, repo, extrafn=None, sourc
                          extrafn=extrafn)
 
 
-def outgoing(orig, ui, repo, dest=None, *args, **opts):
-    """show changesets not found in the Subversion repository
-    """
-    svnurl = ui.expandpath(dest or 'default-push', dest or 'default')
-    if not (cmdutil.issvnurl(svnurl) or opts.get('svn', False)):
-        return orig(ui, repo, dest, *args, **opts)
-
-    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
-    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
-                                 hge.revmap.iterkeys()))
-    o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes,
-                                  repo.parents()[0].node())
-    if not (o_r and len(o_r)):
-        ui.status('no changes found\n')
-        return 0
-    displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False)
-    for node in reversed(o_r):
-        displayer.show(repo[node])
-
-
 def listauthors(ui, args, authors=None, **opts):
     """list all authors in a Subversion repository
     """
new file mode 100644
--- /dev/null
+++ b/wrappers.py
@@ -0,0 +1,320 @@
+import os
+
+from mercurial import cmdutil as hgcmdutil
+from mercurial import commands
+from mercurial import patch
+from mercurial import hg
+from mercurial import util as hgutil
+from mercurial import node
+
+from svn import core
+from svn import delta
+
+import cmdutil
+import hg_delta_editor
+import stupid as stupidmod
+import svnwrap
+import util
+import utility_commands
+
+def parent(orig, ui, repo, *args, **opts):
+    """show Mercurial & Subversion parents of the working dir or revision
+    """
+    if not opts.get('svn', False):
+        return orig(ui, repo, *args, **opts)
+    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
+    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
+                                 hge.revmap.iterkeys()))
+    ha = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes)
+    if ha.node() == node.nullid:
+        raise hgutil.Abort('No parent svn revision!')
+    displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False)
+    displayer.show(ha)
+    return 0
+
+
+def outgoing(orig, ui, repo, dest=None, *args, **opts):
+    """show changesets not found in the Subversion repository
+    """
+    svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default')
+    if not (cmdutil.issvnurl(svnurl) or opts.get('svn', False)):
+        return orig(ui, repo, dest, *args, **opts)
+
+    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
+    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
+                                 hge.revmap.iterkeys()))
+    o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes,
+                                  repo.parents()[0].node())
+    if not (o_r and len(o_r)):
+        ui.status('no changes found\n')
+        return 0
+    displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False)
+    for node in reversed(o_r):
+        displayer.show(repo[node])
+
+
+
+
+def diff(orig, ui, repo, *args, **opts):
+    """show a diff of the most recent revision against its parent from svn
+    """
+    if not opts.get('svn', False) or opts.get('change', None):
+        return orig(ui, repo, *args, **opts)
+    svn_commit_hashes = {}
+    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
+    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
+                                 hge.revmap.iterkeys()))
+    if not opts.get('rev', None):
+        parent = repo.parents()[0]
+        o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes,
+                                      parent.node())
+        if o_r:
+            parent = repo[o_r[-1]].parents()[0]
+        opts['rev'] = ['%s:.' % node.hex(parent.node()), ]
+    node1, node2 = hgcmdutil.revpair(repo, opts['rev'])
+    baserev, _junk = svn_commit_hashes.get(node1, (-1, 'junk', ))
+    newrev, _junk = svn_commit_hashes.get(node2, (-1, 'junk', ))
+    it = patch.diff(repo, node1, node2,
+                    opts=patch.diffopts(ui, opts={'git': True,
+                                                  'show_function': False,
+                                                  'ignore_all_space': False,
+                                                  'ignore_space_change': False,
+                                                  'ignore_blank_lines': False,
+                                                  'unified': True,
+                                                  'text': False,
+                                                  }))
+    ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev))
+
+
+
+
+def push(orig, ui, repo, dest=None, *args, **opts):
+    """push revisions starting at a specified head back to Subversion.
+    """
+    svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default')
+    if not cmdutil.issvnurl(svnurl):
+        orig(ui, repo, dest=dest, *args, **opts)
+    old_encoding = util.swap_out_encoding()
+    hge = hg_delta_editor.HgChangeReceiver(repo=repo)
+    svnurl = util.normalize_url(svnurl)
+    if svnurl != hge.url:
+        raise hgutil.Abort('wrong subversion url!')
+    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
+                                 hge.revmap.iterkeys()))
+    user = opts.get('username', hgutil.getuser())
+    passwd = opts.get('password', '')
+
+    # Strategy:
+    # 1. Find all outgoing commits from this head
+    if len(repo.parents()) != 1:
+        ui.status('Cowardly refusing to push branch merge')
+        return 1
+    workingrev = repo.parents()[0]
+    outgoing = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, workingrev.node())
+    if not (outgoing and len(outgoing)):
+        ui.status('No revisions to push.')
+        return 0
+    while outgoing:
+        oldest = outgoing.pop(-1)
+        old_ctx = repo[oldest]
+        if len(old_ctx.parents()) != 1:
+            ui.status('Found a branch merge, this needs discussion and '
+                      'implementation.')
+            return 1
+        base_n = old_ctx.parents()[0].node()
+        old_children = repo[base_n].children()
+        svnbranch = repo[base_n].branch()
+        oldtip = base_n
+        samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch
+                              and c.node() in svn_commit_hashes]
+        while samebranchchildren:
+            oldtip = samebranchchildren[0].node()
+            samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch
+                                  and c.node() in svn_commit_hashes]
+        # 2. Commit oldest revision that needs to be pushed
+        base_revision = svn_commit_hashes[base_n][0]
+        try:
+            cmdutil.commit_from_rev(ui, repo, old_ctx, hge, svnurl,
+                                    base_revision, user, passwd)
+        except cmdutil.NoFilesException:
+            ui.warn("Could not push revision %s because it had no changes in svn.\n" %
+                     old_ctx)
+            return 1
+        # 3. Fetch revisions from svn
+        r = pull(None, ui, repo, True, stupid=opts.get('svn_stupid', False),
+                 username=user, password=passwd)
+        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()]
+        assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement
+        replacement = replacement[0]
+        # 5. Rebase all children of the currently-pushing rev to the new branch
+        heads = repo.heads(old_ctx.node())
+        for needs_transplant in heads:
+            def extrafn(ctx, extra):
+                if ctx.node() == oldest:
+                    return
+                extra['branch'] = ctx.branch()
+            utility_commands.rebase(ui, repo, extrafn=extrafn,
+                                    sourcerev=needs_transplant, **opts)
+            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)))
+                if rebasesrc in outgoing:
+                    while rebasesrc in outgoing:
+                        rebsrcindex = outgoing.index(rebasesrc)
+                        outgoing = (outgoing[0:rebsrcindex] +
+                                    [child.node(), ] + outgoing[rebsrcindex+1:])
+                        children = [c for c in child.children() if c.branch() == child.branch()]
+                        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)
+        svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys()))
+    util.swap_out_encoding(old_encoding)
+    return 0
+
+
+
+def clone(orig, ui, source, dest=None, *args, **opts):
+    '''clone Subversion repository to a local Mercurial repository.
+
+    If no destination directory name is specified, it defaults to the
+    basename of the source plus "-hg".
+
+    You can specify multiple paths for the location of tags using comma
+    separated values.
+    '''
+    svnurl = ui.expandpath(source)
+    if not cmdutil.issvnurl(svnurl):
+        orig(ui, source=source, dest=dest, *args, **opts)
+
+    if not dest:
+        dest = hg.defaultdest(source) + '-hg'
+        ui.status("Assuming destination %s\n" % dest)
+
+    if os.path.exists(dest):
+        raise hgutil.Abort("destination '%s' already exists" % dest)
+    url = util.normalize_url(svnurl)
+    res = -1
+    try:
+        try:
+            res = pull(None, ui, None, True, opts.pop('svn_stupid', False),
+                       source=url, create_new_dest=dest, **opts)
+        except core.SubversionException, e:
+            if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED:
+                raise hgutil.Abort('It appears svn does not trust the ssl cert for this site.\n'
+                         'Please try running svn ls on that url first.')
+            raise
+    finally:
+        if os.path.exists(dest):
+            repo = hg.repository(ui, dest)
+            fp = repo.opener("hgrc", "w", text=True)
+            fp.write("[paths]\n")
+            # percent needs to be escaped for ConfigParser
+            fp.write("default = %(url)s\nsvn = %(url)s\n" % {'url': svnurl.replace('%', '%%')})
+            fp.close()
+            if (res is None or res == 0) and not opts.get('noupdate', False):
+                commands.update(ui, repo, repo['tip'].node())
+
+    return res
+
+
+def pull(orig, ui, repo, svn=None, svn_stupid=False, source="default", create_new_dest=False,
+         *args, **opts):
+    """pull new revisions from Subversion
+    """
+    url = ((repo and repo.ui) or ui).expandpath(source)
+    if not (cmdutil.issvnurl(url) or svn or create_new_dest):
+        orig(ui, repo, source=source, *args, **opts)
+    svn_url = url
+    svn_url = util.normalize_url(svn_url)
+    old_encoding = util.swap_out_encoding()
+    # TODO implement skipto support
+    skipto_rev = 0
+    have_replay = not svn_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'
+                  ' work until you upgrade to 1.5.0 or newer. Falling back to'
+                  ' a slower method that may be buggier. Please upgrade, or'
+                  ' contribute a patch to use the ctypes bindings instead'
+                  ' of SWIG.\n')
+        have_replay = False
+    initializing_repo = False
+    user = opts.get('username', hgutil.getuser())
+    passwd = opts.get('password', '')
+    svn = svnwrap.SubversionRepo(svn_url, user, passwd)
+    author_host = "@%s" % svn.uuid
+    # TODO these should be configurable again, but I'm torn on how.
+    # Maybe this should be configured in .hg/hgrc for each repo? Seems vaguely reasonable.
+    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)
+    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
+
+    if initializing_repo and start > 0:
+        raise hgutil.Abort('Revision skipping at repository initialization '
+                           'remains unimplemented.')
+
+    # start converting revisions
+    for r in svn.revisions(start=start):
+        valid = True
+        hg_editor.update_branch_tag_map_for_rev(r)
+        for p in r.paths:
+            if hg_editor._is_path_valid(p):
+                valid = True
+                break
+        if valid:
+            # got a 502? Try more than once!
+            tries = 0
+            converted = False
+            while not converted:
+                try:
+                    util.describe_revision(ui, r)
+                    if have_replay:
+                        try:
+                            cmdutil.replay_convert_rev(hg_editor, svn, r)
+                        except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover
+                            ui.status('%s\n' % e.message)
+                            stupidmod.print_your_svn_is_old_message(ui)
+                            have_replay = False
+                            stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r)
+                    else:
+                        stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r)
+                    converted = True
+                except core.SubversionException, e: #pragma: no cover
+                    if (e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED
+                        and '502' in str(e)
+                        and tries < 3):
+                        tries += 1
+                        ui.status('Got a 502, retrying (%s)\n' % tries)
+                    else:
+                        raise hgutil.Abort(*e.args)
+    util.swap_out_encoding(old_encoding)