changeset 242:06130689a2c8

Move push into svncommands.
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Wed, 08 Apr 2009 17:53:48 +0200
parents 4950b18cf949
children 2027f851d60c
files cmdutil.py push_cmd.py svncommand.py svncommands.py tests/test_push_command.py tests/test_util.py
diffstat 6 files changed, 312 insertions(+), 313 deletions(-) [+]
line wrap: on
line diff
--- a/cmdutil.py
+++ b/cmdutil.py
@@ -1,4 +1,21 @@
 
+from mercurial import util as hgutil
+
+from svn import core
+
+import svnwrap
+import svnexternals
+
+
+class BaseException(Exception):
+    pass
+
+
+class NoFilesException(BaseException):
+    """Exception raised when you try and commit without files.
+    """
+
+
 def replay_convert_rev(hg_editor, svn, r):
     hg_editor.set_current_rev(r)
     svn.get_replay(r.revnum, hg_editor)
@@ -30,3 +47,196 @@ def replay_convert_rev(hg_editor, svn, r
         hg_editor.missing_plaintexts = set()
         hg_editor.ui.note('\n')
     hg_editor.commit_current_delta()
+
+
+def _isdir(svn, branchpath, svndir):
+    try:
+        svn.list_dir('%s/%s' % (branchpath, svndir))
+        return True
+    except core.SubversionException:
+        return False
+
+
+def _getdirchanges(svn, branchpath, parentctx, ctx, changedfiles, extchanges):
+    """Compute directories to add or delete when moving from parentctx
+    to ctx, assuming only 'changedfiles' files changed, and 'extchanges'
+    external references changed (as returned by svnexternals.diff()).
+
+    Return (added, deleted) where 'added' is the list of all added
+    directories and 'deleted' the list of deleted directories.
+    Intermediate directories are included: if a/b/c is new and requires
+    the addition of a/b and a, those will be listed too. Intermediate
+    deleted directories are also listed, but item order of undefined
+    in either list.
+    """
+    def finddirs(path, includeself=False):
+        if includeself:
+            yield path
+        pos = path.rfind('/')
+        while pos != -1:
+            yield path[:pos]
+            pos = path.rfind('/', 0, pos)
+
+    def getctxdirs(ctx, keptdirs, extdirs):
+        dirs = {}
+        for f in ctx.manifest():
+            for d in finddirs(f):
+                if d in dirs:
+                    break
+                if d in keptdirs:
+                    dirs[d] = 1
+        for extdir in extdirs:
+            for d in finddirs(extdir, True):
+                dirs[d] = 1
+        return dirs
+
+    deleted, added = [], []
+    changeddirs = {}
+    for f in changedfiles:
+        if f in parentctx and f in ctx:
+            # Updated files cannot cause directories to be created
+            # or removed.
+            continue
+        for d in finddirs(f):
+            changeddirs[d] = 1
+    for e in extchanges:
+        if not e[1] or not e[2]:
+            for d in finddirs(e[0], True):
+                changeddirs[d] = 1
+    if not changeddirs:
+        return added, deleted
+    olddirs = getctxdirs(parentctx, changeddirs,
+                         [e[0] for e in extchanges if e[1]])
+    newdirs = getctxdirs(ctx, changeddirs,
+                         [e[0] for e in extchanges if e[2]])
+
+    for d in newdirs:
+        if d not in olddirs and not _isdir(svn, branchpath, d):
+            added.append(d)
+
+    for d in olddirs:
+        if d not in newdirs and _isdir(svn, branchpath, d):
+            deleted.append(d)
+
+    return added, deleted
+
+
+def _externals(ctx):
+    ext = svnexternals.externalsfile()
+    if '.hgsvnexternals' in ctx:
+        ext.read(ctx['.hgsvnexternals'].data())
+    return ext
+
+
+def commit_from_rev(ui, repo, rev_ctx, hg_editor, svn_url, base_revision
+                    username, password):
+    """Build and send a commit from Mercurial to Subversion.
+    """
+    file_data = {}
+    svn = svnwrap.SubversionRepo(svn_url, username, password)
+    parent = rev_ctx.parents()[0]
+    parent_branch = rev_ctx.parents()[0].branch()
+    branch_path = 'trunk'
+
+    if parent_branch and parent_branch != 'default':
+        branch_path = 'branches/%s' % parent_branch
+
+    extchanges = list(svnexternals.diff(_externals(parent),
+                                        _externals(rev_ctx)))
+    addeddirs, deleteddirs = _getdirchanges(svn, branch_path, parent, rev_ctx,
+                                            rev_ctx.files(), extchanges)
+    deleteddirs = set(deleteddirs)
+
+    props = {}
+    copies = {}
+    for file in rev_ctx.files():
+        if file == '.hgsvnexternals':
+            continue
+        new_data = base_data = ''
+        action = ''
+        if file in rev_ctx:
+            fctx = rev_ctx.filectx(file)
+            new_data = fctx.data()
+
+            if 'x' in fctx.flags():
+                props.setdefault(file, {})['svn:executable'] = '*'
+            if 'l' in fctx.flags():
+                props.setdefault(file, {})['svn:special'] = '*'
+
+            if file not in parent:
+                renamed = fctx.renamed()
+                if renamed:
+                    # TODO current model (and perhaps svn model) does not support
+                    # this kind of renames: a -> b, b -> c
+                    copies[file] = renamed[0]
+                    base_data = parent[renamed[0]].data()
+
+                action = 'add'
+                dirname = '/'.join(file.split('/')[:-1] + [''])
+            else:
+                base_data = parent.filectx(file).data()
+                if ('x' in parent.filectx(file).flags()
+                    and 'x' not in rev_ctx.filectx(file).flags()):
+                    props.setdefault(file, {})['svn:executable'] = None
+                if ('l' in parent.filectx(file).flags()
+                    and 'l' not in rev_ctx.filectx(file).flags()):
+                    props.setdefault(file, {})['svn:special'] = None
+                action = 'modify'
+        else:
+            pos = file.rfind('/')
+            if pos >= 0:
+                if file[:pos] in deleteddirs:
+                    # This file will be removed when its directory is removed
+                    continue
+            action = 'delete'
+        file_data[file] = base_data, new_data, action
+
+    def svnpath(p):
+        return '%s/%s' % (branch_path, p)
+
+    changeddirs = []
+    for d, v1, v2 in extchanges:
+        props.setdefault(svnpath(d), {})['svn:externals'] = v2
+        if d not in deleteddirs and d not in addeddirs:
+            changeddirs.append(svnpath(d))
+
+    # Now we are done with files, we can prune deleted directories
+    # against themselves: ignore a/b if a/ is already removed
+    deleteddirs2 = list(deleteddirs)
+    deleteddirs2.sort(reverse=True)
+    for d in deleteddirs2:
+        pos = d.rfind('/')
+        if pos >= 0 and d[:pos] in deleteddirs:
+            deleteddirs.remove(d[:pos])
+
+    newcopies = {}
+    for source, dest in copies.iteritems():
+        newcopies[svnpath(source)] = (svnpath(dest), base_revision)
+
+    new_target_files = [svnpath(f) for f in file_data]
+    for tf, ntf in zip(file_data, new_target_files):
+        if tf in file_data:
+            file_data[ntf] = file_data[tf]
+            if tf in props:
+                props[ntf] = props[tf]
+                del props[tf]
+            if hgutil.binary(file_data[ntf][1]):
+                props.setdefault(ntf, {}).update(props.get(ntf, {}))
+                props.setdefault(ntf, {})['svn:mime-type'] = 'application/octet-stream'
+            del file_data[tf]
+
+    addeddirs = [svnpath(d) for d in addeddirs]
+    deleteddirs = [svnpath(d) for d in deleteddirs]
+    new_target_files += addeddirs + deleteddirs + changeddirs
+    if not new_target_files:
+        raise NoFilesException()
+    try:
+        svn.commit(new_target_files, rev_ctx.description(), file_data,
+                   base_revision, set(addeddirs), set(deleteddirs),
+                   props, newcopies)
+    except core.SubversionException, e:
+        if hasattr(e, 'apr_err') and (e.apr_err == core.SVN_ERR_FS_TXN_OUT_OF_DATE
+                                      or e.apr_err == core.SVN_ERR_FS_CONFLICT):
+            raise hgutil.Abort('Base text was out of date, maybe rebase?')
+        else:
+            raise
deleted file mode 100644
--- a/push_cmd.py
+++ /dev/null
@@ -1,299 +0,0 @@
-from mercurial import util as merc_util
-from mercurial import hg
-from mercurial import node
-from svn import core
-
-import util
-import hg_delta_editor
-import svnexternals
-import svnwrap
-import svncommands
-import utility_commands
-
-
-class BaseException(Exception):
-    pass
-
-
-class NoFilesException(BaseException):
-    """Exception raised when you try and commit without files.
-    """
-
-
-def push_revisions_to_subversion(ui, repo, hg_repo_path, svn_url,
-                                 stupid=False, **opts):
-    """push revisions starting at a specified head back to Subversion.
-    """
-    old_encoding = util.swap_out_encoding()
-    hge = hg_delta_editor.HgChangeReceiver(hg_repo_path,
-                                           ui_=ui)
-    svn_commit_hashes = dict(zip(hge.revmap.itervalues(),
-                                 hge.revmap.iterkeys()))
-    user = opts.get('username', merc_util.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:
-            commit_from_rev(ui, repo, old_ctx, hge, svn_url, base_revision,
-                            user, passwd)
-        except 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 = svncommands.pull(ui, svn_url, hg_repo_path, stupid=stupid,
-                             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_commits(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(hg_repo_path, ui_=ui)
-        svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys()))
-    util.swap_out_encoding(old_encoding)
-    return 0
-push_revisions_to_subversion = util.register_subcommand('push')(push_revisions_to_subversion)
-# for git expats
-push_revisions_to_subversion = util.register_subcommand('dcommit')(push_revisions_to_subversion)
-
-def _isdir(svn, branchpath, svndir):
-    try:
-        svn.list_dir('%s/%s' % (branchpath, svndir))
-        return True
-    except core.SubversionException:
-        return False
-
-def _getdirchanges(svn, branchpath, parentctx, ctx, changedfiles, extchanges):
-    """Compute directories to add or delete when moving from parentctx
-    to ctx, assuming only 'changedfiles' files changed, and 'extchanges'
-    external references changed (as returned by svnexternals.diff()).
-
-    Return (added, deleted) where 'added' is the list of all added
-    directories and 'deleted' the list of deleted directories.
-    Intermediate directories are included: if a/b/c is new and requires
-    the addition of a/b and a, those will be listed too. Intermediate
-    deleted directories are also listed, but item order of undefined
-    in either list.
-    """
-    def finddirs(path, includeself=False):
-        if includeself:
-            yield path
-        pos = path.rfind('/')
-        while pos != -1:
-            yield path[:pos]
-            pos = path.rfind('/', 0, pos)
-
-    def getctxdirs(ctx, keptdirs, extdirs):
-        dirs = {}
-        for f in ctx.manifest():
-            for d in finddirs(f):
-                if d in dirs:
-                    break
-                if d in keptdirs:
-                    dirs[d] = 1
-        for extdir in extdirs:
-            for d in finddirs(extdir, True):
-                dirs[d] = 1
-        return dirs
-
-    deleted, added = [], []
-    changeddirs = {}
-    for f in changedfiles:
-        if f in parentctx and f in ctx:
-            # Updated files cannot cause directories to be created
-            # or removed.
-            continue
-        for d in finddirs(f):
-            changeddirs[d] = 1
-    for e in extchanges:
-        if not e[1] or not e[2]:
-            for d in finddirs(e[0], True):
-                changeddirs[d] = 1
-    if not changeddirs:
-        return added, deleted
-    olddirs = getctxdirs(parentctx, changeddirs,
-                         [e[0] for e in extchanges if e[1]])
-    newdirs = getctxdirs(ctx, changeddirs,
-                         [e[0] for e in extchanges if e[2]])
-
-    for d in newdirs:
-        if d not in olddirs and not _isdir(svn, branchpath, d):
-            added.append(d)
-
-    for d in olddirs:
-        if d not in newdirs and _isdir(svn, branchpath, d):
-            deleted.append(d)
-
-    return added, deleted
-
-def _externals(ctx):
-    ext = svnexternals.externalsfile()
-    if '.hgsvnexternals' in ctx:
-        ext.read(ctx['.hgsvnexternals'].data())
-    return ext
-
-def commit_from_rev(ui, repo, rev_ctx, hg_editor, svn_url, base_revision,
-                    username, password):
-    """Build and send a commit from Mercurial to Subversion.
-    """
-    file_data = {}
-    svn = svnwrap.SubversionRepo(svn_url, username, password)
-    parent = rev_ctx.parents()[0]
-    parent_branch = rev_ctx.parents()[0].branch()
-    branch_path = 'trunk'
-
-    if parent_branch and parent_branch != 'default':
-        branch_path = 'branches/%s' % parent_branch
-
-    extchanges = list(svnexternals.diff(_externals(parent),
-                                        _externals(rev_ctx)))
-    addeddirs, deleteddirs = _getdirchanges(svn, branch_path, parent, rev_ctx,
-                                            rev_ctx.files(), extchanges)
-    deleteddirs = set(deleteddirs)
-
-    props = {}
-    copies = {}
-    for file in rev_ctx.files():
-        if file == '.hgsvnexternals':
-            continue
-        new_data = base_data = ''
-        action = ''
-        if file in rev_ctx:
-            fctx = rev_ctx.filectx(file)
-            new_data = fctx.data()
-
-            if 'x' in fctx.flags():
-                props.setdefault(file, {})['svn:executable'] = '*'
-            if 'l' in fctx.flags():
-                props.setdefault(file, {})['svn:special'] = '*'
-
-            if file not in parent:
-                renamed = fctx.renamed()
-                if renamed:
-                    # TODO current model (and perhaps svn model) does not support
-                    # this kind of renames: a -> b, b -> c
-                    copies[file] = renamed[0]
-                    base_data = parent[renamed[0]].data()
-
-                action = 'add'
-                dirname = '/'.join(file.split('/')[:-1] + [''])
-            else:
-                base_data = parent.filectx(file).data()
-                if ('x' in parent.filectx(file).flags()
-                    and 'x' not in rev_ctx.filectx(file).flags()):
-                    props.setdefault(file, {})['svn:executable'] = None
-                if ('l' in parent.filectx(file).flags()
-                    and 'l' not in rev_ctx.filectx(file).flags()):
-                    props.setdefault(file, {})['svn:special'] = None
-                action = 'modify'
-        else:
-            pos = file.rfind('/')
-            if pos >= 0:
-                if file[:pos] in deleteddirs:
-                    # This file will be removed when its directory is removed
-                    continue
-            action = 'delete'
-        file_data[file] = base_data, new_data, action
-
-    def svnpath(p):
-        return '%s/%s' % (branch_path, p)
-
-    changeddirs = []
-    for d, v1, v2 in extchanges:
-        props.setdefault(svnpath(d), {})['svn:externals'] = v2
-        if d not in deleteddirs and d not in addeddirs:
-            changeddirs.append(svnpath(d))
-
-    # Now we are done with files, we can prune deleted directories
-    # against themselves: ignore a/b if a/ is already removed
-    deleteddirs2 = list(deleteddirs)
-    deleteddirs2.sort(reverse=True)
-    for d in deleteddirs2:
-        pos = d.rfind('/')
-        if pos >= 0 and d[:pos] in deleteddirs:
-            deleteddirs.remove(d[:pos])
-
-    newcopies = {}
-    for source, dest in copies.iteritems():
-        newcopies[svnpath(source)] = (svnpath(dest), base_revision)
-
-    new_target_files = [svnpath(f) for f in file_data]
-    for tf, ntf in zip(file_data, new_target_files):
-        if tf in file_data:
-            file_data[ntf] = file_data[tf]
-            if tf in props:
-                props[ntf] = props[tf]
-                del props[tf]
-            if merc_util.binary(file_data[ntf][1]):
-                props.setdefault(ntf, {}).update(props.get(ntf, {}))
-                props.setdefault(ntf, {})['svn:mime-type'] = 'application/octet-stream'
-            del file_data[tf]
-
-    addeddirs = [svnpath(d) for d in addeddirs]
-    deleteddirs = [svnpath(d) for d in deleteddirs]
-    new_target_files += addeddirs + deleteddirs + changeddirs
-    if not new_target_files:
-        raise NoFilesException()
-    try:
-        svn.commit(new_target_files, rev_ctx.description(), file_data,
-                   base_revision, set(addeddirs), set(deleteddirs),
-                   props, newcopies)
-    except core.SubversionException, e:
-        if hasattr(e, 'apr_err') and (e.apr_err == core.SVN_ERR_FS_TXN_OUT_OF_DATE
-                                      or e.apr_err == core.SVN_ERR_FS_CONFLICT):
-            raise merc_util.Abort('Base text was out of date, maybe rebase?')
-        else:
-            raise
--- a/svncommand.py
+++ b/svncommand.py
@@ -12,11 +12,10 @@ from util import register_subcommand, sv
 # dirty trick to force demandimport to run my decorator anyway.
 from svncommands import pull
 from utility_commands import print_wc_url
-from push_cmd import commit_from_rev
 from diff_cmd import diff_command
 from rebuildmeta import rebuildmeta
 # shut up, pyflakes, we must import those
-__x = [print_wc_url, pull, commit_from_rev, diff_command, rebuildmeta]
+__x = [print_wc_url, pull, diff_command, rebuildmeta]
 
 
 def svncmd(ui, repo, subcommand, *args, **opts):
--- a/svncommands.py
+++ b/svncommands.py
@@ -1,6 +1,9 @@
 import os
 
+from mercurial import hg
+from mercurial import node
 from mercurial import util as hgutil
+
 from svn import core
 from svn import delta
 
@@ -9,6 +12,7 @@ import svnwrap
 import stupid as stupidmod
 import cmdutil
 import util
+import utility_commands
 
 
 def pull(ui, svn_url, hg_repo_path, skipto_rev=0, stupid=None,
@@ -91,3 +95,93 @@ def pull(ui, svn_url, hg_repo_path, skip
     util.swap_out_encoding(old_encoding)
 
 pull = util.register_subcommand('pull')(pull)
+
+
+def push(ui, repo, hg_repo_path, svn_url, stupid=False, **opts):
+    """push revisions starting at a specified head back to Subversion.
+    """
+    old_encoding = util.swap_out_encoding()
+    hge = hg_delta_editor.HgChangeReceiver(hg_repo_path,
+                                           ui_=ui)
+    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, svn_url,
+                                    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(ui, svn_url, hg_repo_path, stupid=stupid,
+                 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_commits(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(hg_repo_path, ui_=ui)
+        svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys()))
+    util.swap_out_encoding(old_encoding)
+    return 0
+push = util.register_subcommand('push')(push)
+# for git expats
+dcommit = util.register_subcommand('dcommit')(push)
--- a/tests/test_push_command.py
+++ b/tests/test_push_command.py
@@ -10,7 +10,6 @@ from mercurial import ui
 from mercurial import revlog
 
 import svncommands
-import push_cmd
 import test_util
 import time
 
@@ -69,9 +68,8 @@ class PushOverSvnserveTests(test_util.Te
         if not commit:
             return # some tests use this test as an extended setup.
         hg.update(repo, repo['tip'].node())
-        push_cmd.push_revisions_to_subversion(ui.ui(), repo=self.repo,
-                                              hg_repo_path=self.wc_path,
-                                              svn_url='svn://localhost/')
+        svncommands.push(ui.ui(), repo=self.repo, hg_repo_path=self.wc_path,
+                         svn_url='svn://localhost/')
         tip = self.repo['tip']
         self.assertNotEqual(tip.node(), old_tip)
         self.assertEqual(tip.parents()[0].node(), expected_parent)
@@ -172,10 +170,9 @@ class PushTests(test_util.TestBase):
         newhash = self.repo.commitctx(ctx)
         repo = self.repo
         hg.update(repo, newhash)
-        push_cmd.push_revisions_to_subversion(ui.ui(),
-                                              repo=repo,
-                                              svn_url=test_util.fileurl(self.repo_path),
-                                              hg_repo_path=self.wc_path)
+        svncommands.push(ui.ui(), repo=repo,
+                         svn_url=test_util.fileurl(self.repo_path),
+                         hg_repo_path=self.wc_path)
         self.assertEqual(self.repo['tip'].parents()[0].parents()[0].node(), oldtiphash)
         self.assertEqual(self.repo['tip'].files(), ['delta', ])
         self.assertEqual(self.repo['tip'].manifest().keys(),
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -14,7 +14,6 @@ from mercurial import node
 from mercurial import ui
 
 import svncommands
-import push_cmd
 
 # Fixtures that need to be pulled at a subdirectory of the repo path
 subdir = {'truncatedhistory.svndump': '/project2',
@@ -122,9 +121,8 @@ class TestBase(unittest.TestCase):
 
     def pushrevisions(self, stupid=False):
         before = len(self.repo)
-        push_cmd.push_revisions_to_subversion(
-            ui.ui(), repo=self.repo, hg_repo_path=self.wc_path,
-            svn_url=fileurl(self.repo_path), stupid=stupid)
+        svncommands.push(ui.ui(), repo=self.repo, hg_repo_path=self.wc_path,
+                         svn_url=fileurl(self.repo_path), stupid=stupid)
         after = len(self.repo)
         self.assertEqual(0, after - before)