changeset 438:e8f13bd20467

pushmod: split push functions out into separate module
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Tue, 16 Jun 2009 09:12:20 +0200
parents 45ce07a4807f
children d8f84daf744d
files hgsubversion/cmdutil.py hgsubversion/pushmod.py hgsubversion/wrappers.py
diffstat 3 files changed, 6 insertions(+), 272 deletions(-) [+]
line wrap: on
line diff
--- a/hgsubversion/cmdutil.py
+++ b/hgsubversion/cmdutil.py
@@ -3,13 +3,7 @@ import re
 import os
 import urllib
 
-from mercurial import util as hgutil
-
-from svn import core
-
 import util
-import svnwrap
-import svnexternals
 
 
 b_re = re.compile(r'^\+\+\+ b\/([^\n]*)', re.MULTILINE)
@@ -20,10 +14,6 @@ newfile_devnull_re = re.compile(r'^--- /
                                 re.MULTILINE)
 
 
-class NoFilesException(Exception):
-    """Exception raised when you try and commit without files.
-    """
-
 def formatrev(rev):
     if rev == -1:
         return '\t(working copy)'
@@ -53,202 +43,6 @@ def parentrev(ui, repo, meta, hashes):
     return workingctx
 
 
-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, meta, 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 in ('.hgsvnexternals',
-                    '.hgtags',
-                    ):
-            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)).rstrip('/')
-
-    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)
-
-    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
-
-    return True
-
 def islocalrepo(url):
     if not url.startswith('file:///'):
         return False
copy from hgsubversion/cmdutil.py
copy to hgsubversion/pushmod.py
--- a/hgsubversion/cmdutil.py
+++ b/hgsubversion/pushmod.py
@@ -1,57 +1,17 @@
 #!/usr/bin/python
-import re
-import os
-import urllib
 
 from mercurial import util as hgutil
 
 from svn import core
 
-import util
 import svnwrap
 import svnexternals
 
 
-b_re = re.compile(r'^\+\+\+ b\/([^\n]*)', re.MULTILINE)
-a_re = re.compile(r'^--- a\/([^\n]*)', re.MULTILINE)
-devnull_re = re.compile(r'^([-+]{3}) /dev/null', re.MULTILINE)
-header_re = re.compile(r'^diff --git .* b\/(.*)', re.MULTILINE)
-newfile_devnull_re = re.compile(r'^--- /dev/null\n\+\+\+ b/([^\n]*)',
-                                re.MULTILINE)
-
-
 class NoFilesException(Exception):
     """Exception raised when you try and commit without files.
     """
 
-def formatrev(rev):
-    if rev == -1:
-        return '\t(working copy)'
-    return '\t(revision %d)' % rev
-
-
-def filterdiff(diff, oldrev, newrev):
-    diff = newfile_devnull_re.sub(r'--- \1\t(revision 0)' '\n'
-                                  r'+++ \1\t(working copy)',
-                                  diff)
-    oldrev = formatrev(oldrev)
-    newrev = formatrev(newrev)
-    diff = a_re.sub(r'--- \1'+ oldrev, diff)
-    diff = b_re.sub(r'+++ \1' + newrev, diff)
-    diff = devnull_re.sub(r'\1 /dev/null\t(working copy)', diff)
-    diff = header_re.sub(r'Index: \1' + '\n' + ('=' * 67), diff)
-    return diff
-
-
-def parentrev(ui, repo, meta, hashes):
-    """Find the svn parent revision of the repo's dirstate.
-    """
-    workingctx = repo.parents()[0]
-    outrev = util.outgoing_revisions(repo, hashes, workingctx.node())
-    if outrev:
-        workingctx = repo[outrev[-1]].parents()[0]
-    return workingctx
-
 
 def _isdir(svn, branchpath, svndir):
     try:
@@ -132,8 +92,8 @@ def _externals(ctx):
     return ext
 
 
-def commit_from_rev(ui, repo, rev_ctx, meta, svn_url, base_revision,
-                    username, password):
+def commit(ui, repo, rev_ctx, meta, svn_url, base_revision,
+           username, password):
     """Build and send a commit from Mercurial to Subversion.
     """
     file_data = {}
@@ -248,24 +208,3 @@ def commit_from_rev(ui, repo, rev_ctx, m
             raise
 
     return True
-
-def islocalrepo(url):
-    if not url.startswith('file:///'):
-        return False
-    if '#' in url.split('/')[-1]: # strip off #anchor
-        url = url[:url.rfind('#')]
-    path = url[len('file://'):]
-    path = urllib.url2pathname(path).replace(os.sep, '/')
-    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):
-    for scheme in ('svn', 'http', 'https', 'svn+ssh'):
-        if url.startswith(scheme + '://'):
-            return True
-    return islocalrepo(url)
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -13,6 +13,7 @@ from svn import delta
 import cmdutil
 import svnmeta
 import replay
+import pushmod
 import stupid as stupidmod
 import svnwrap
 import util
@@ -155,9 +156,9 @@ def push(repo, dest, force, revs):
         # 2. Commit oldest revision that needs to be pushed
         base_revision = hashes[base_n][0]
         try:
-            cmdutil.commit_from_rev(ui, repo, old_ctx, meta, svnurl,
-                                    base_revision, user, passwd)
-        except cmdutil.NoFilesException:
+            pushmod.commit(ui, repo, old_ctx, meta, svnurl,
+                           base_revision, user, passwd)
+        except pushmod.NoFilesException:
             ui.warn("Could not push revision %s because it had no changes in svn.\n" %
                      old_ctx)
             return 1