Mercurial > hgsubversion
view push_cmd.py @ 73:9c1b53abefcb
fetch_command: support svn copy detection in stupid mode
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Wed, 05 Nov 2008 13:37:08 +0100 |
parents | 49b7cbe4c8e3 |
children | 6c9b7cf1c5aa |
line wrap: on
line source
from mercurial import util as merc_util from mercurial import hg from svn import core import util import hg_delta_editor import svnwrap import fetch_command import utility_commands @util.register_subcommand('push') @util.register_subcommand('dcommit') # for git expats def push_revisions_to_subversion(ui, repo, hg_repo_path, svn_url, **opts): """Push revisions starting at a specified head back to Subversion. """ oldencoding = merc_util._encoding merc_util._encoding = 'UTF-8' hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, ui_=ui) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) # Strategy: # 1. Find all outgoing commits from this head outgoing = utility_commands.outgoing_revisions(ui, repo, hge, svn_commit_hashes) if not (outgoing and len(outgoing)): ui.status('No revisions to push.') return 0 if len(repo.parents()) != 1: ui.status('Cowardly refusing to push branch merge') return 1 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() # 2. Commit oldest revision that needs to be pushed base_revision = svn_commit_hashes[old_ctx.parents()[0].node()][0] commit_from_rev(ui, repo, old_ctx, hge, svn_url, base_revision) # 3. Fetch revisions from svn r = fetch_command.fetch_revisions(ui, svn_url, hg_repo_path) assert not r or r == 0 # 4. Find the new head of the target branch repo = hg.repository(ui, hge.path) base_c = repo[base_n] replacement = [c for c in base_c.children() if c not in old_children and c.branch() == old_ctx.branch()] assert len(replacement) == 1 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: hg.clean(repo, needs_transplant) utility_commands.rebase_commits(ui, repo, hg_repo_path, **opts) repo = hg.repository(ui, hge.path) if needs_transplant in outgoing: hg.clean(repo, repo['tip'].node()) hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, ui_=ui) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) outgoing = utility_commands.outgoing_revisions(ui, repo, hge, svn_commit_hashes) merc_util._encoding = oldencoding return 0 def _findmissing(dirname, svn, branch_path): """Find missing directories in svn. dirname *must* end in a / """ assert dirname[-1] == '/' missing = [] keep_checking = True # check and see if the dir exists svn-side. path = dirname while keep_checking: try: assert svn.list_dir('%s/%s' % (branch_path, path)) keep_checking = False except core.SubversionException, e: # dir must not exist missing.append(path[:-1]) path = '/'.join(path.split('/')[:-2] + ['']) return missing def commit_from_rev(ui, repo, rev_ctx, hg_editor, svn_url, base_revision): """Build and send a commit from Mercurial to Subversion. """ file_data = {} svn = svnwrap.SubversionRepo(svn_url, username=merc_util.getuser()) 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 added_dirs = [] props = {} copies = {} for file in rev_ctx.files(): 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] + ['']) # check for new directories if not list(parent.walk(util.PrefixMatch(dirname))): added_dirs += _findmissing(dirname, svn, branch_path) 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: base_data = parent.filectx(file).data() action = 'delete' file_data[file] = base_data, new_data, action # TODO check for directory deletes here def svnpath(p): return '%s/%s' % (branch_path, p) newcopies = {} for source, dest in copies.iteritems(): newcopies[svnpath(source)] = (svnpath(dest), base_revision) new_target_files = [svnpath(f) for f in rev_ctx.files()] for tf, ntf in zip(rev_ctx.files(), 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] added_dirs = ['%s/%s' % (branch_path, f) for f in added_dirs] added_dirs = set(added_dirs) new_target_files += added_dirs try: svn.commit(new_target_files, rev_ctx.description(), file_data, base_revision, set(added_dirs), props, newcopies) except core.SubversionException, e: if hasattr(e, 'apr_err') and e.apr_err == 160028: raise merc_util.Abort('Base text was out of date, maybe rebase?') else: raise