# HG changeset patch # User Dirkjan Ochtman # Date 1245136340 -7200 # Node ID e8f13bd20467a0ee8280011863fab7abc843f5fa # Parent 45ce07a4807f27c8155353915dfee230761f3852 pushmod: split push functions out into separate module diff --git a/hgsubversion/cmdutil.py b/hgsubversion/cmdutil.py --- 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 diff --git a/hgsubversion/cmdutil.py b/hgsubversion/pushmod.py 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) diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py --- 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