Mercurial > hgsubversion
diff cmdutil.py @ 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 |
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