# HG changeset patch # User Augie Fackler # Date 1239421109 18000 # Node ID ffccf0080e547c08601bb7abfa5879a83ce74db5 # Parent 7932d098cb5f61641718b7ff2e28fbc7e3479c69 Move wrappers for hg commands to their own module. diff --git a/__init__.py b/__init__.py --- a/__init__.py +++ b/__init__.py @@ -18,7 +18,6 @@ import traceback from mercurial import commands from mercurial import extensions -from mercurial import hg from mercurial import util as hgutil from svn import core @@ -26,6 +25,7 @@ from svn import core import svncommands import tag_repo import util +import wrappers def reposetup(ui, repo): if not util.is_svn_repo(repo): @@ -41,26 +41,26 @@ def uisetup(ui): * outgoing -> utility_commands.outgoing """ entry = extensions.wrapcommand(commands.table, 'parents', - utility_commands.parent) + wrappers.parent) entry[1].append(('', 'svn', None, "show parent svn revision instead")) entry = extensions.wrapcommand(commands.table, 'outgoing', - utility_commands.outgoing) + wrappers.outgoing) entry[1].append(('', 'svn', None, "show revisions outgoing to subversion")) entry = extensions.wrapcommand(commands.table, 'diff', - svncommands.diff) + wrappers.diff) entry[1].append(('', 'svn', None, "show svn-style diffs, default against svn parent")) entry = extensions.wrapcommand(commands.table, 'push', - svncommands.push) + wrappers.push) entry[1].append(('', 'svn', None, "push to subversion")) entry[1].append(('', 'svn-stupid', None, "use stupid replay during push to svn")) entry = extensions.wrapcommand(commands.table, 'pull', - svncommands.pull) + wrappers.pull) entry[1].append(('', 'svn', None, "pull from subversion")) entry[1].append(('', 'svn-stupid', None, "use stupid replay during pull from svn")) entry = extensions.wrapcommand(commands.table, 'clone', - svncommands.clone) + wrappers.clone) entry[1].extend([#('', 'skipto-rev', '0', 'skip commits before this revision.'), ('', 'svn-stupid', False, 'be stupid and use diffy replay.'), ('', 'svn-tag-locations', 'tags', 'Relative path to Subversion tags.'), @@ -68,9 +68,6 @@ def uisetup(ui): ('', 'svn-filemap', '', 'remap file to exclude paths or include only certain paths'), ]) -# (svn_fetch, -# , -# 'hg svnclone source [dest]'), def svn(ui, repo, subcommand, *args, **opts): diff --git a/cmdutil.py b/cmdutil.py --- a/cmdutil.py +++ b/cmdutil.py @@ -1,6 +1,7 @@ #!/usr/bin/python import re import os +import urllib from mercurial import util as hgutil @@ -278,16 +279,19 @@ def commit_from_rev(ui, repo, rev_ctx, h else: raise -def filecheck(path): - for x in ('locks', 'hooks', 'format', 'db', ): - if not os.path.exists(os.path.join(path, x)): - return False return True +def islocalrepo(url): + if not url.startswith('file:///'): + return False + path = urllib.unquote(url[len('file://'):]) + 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): - return url.startswith('svn+') or ( - url.startswith('file://') and - reduce(lambda x,y: x and y, - map(lambda p: os.path.exists(os.path.join(url[7:], p)), - ('locks', 'hooks', 'format', 'db', )))) + return url.startswith('svn') or islocalrepo(url) diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- a/hg_delta_editor.py +++ b/hg_delta_editor.py @@ -710,9 +710,9 @@ class HgChangeReceiver(delta.Editor): self.clear_current_info() def authorforsvnauthor(self, author): - if(author in self.authors): + if author in self.authors: return self.authors[author] - return '%s%s' %(author, self.author_host) + return '%s%s' % (author, self.author_host) def svnauthorforauthor(self, author): for svnauthor, hgauthor in self.authors.iteritems(): diff --git a/svncommands.py b/svncommands.py --- a/svncommands.py +++ b/svncommands.py @@ -1,160 +1,17 @@ import os import cPickle as pickle -from mercurial import cmdutil as hgcmdutil -from mercurial import commands from mercurial import hg from mercurial import node -from mercurial import patch from mercurial import util as hgutil -from svn import core -from svn import delta import hg_delta_editor import svnwrap -import stupid as stupidmod -import cmdutil import util import utility_commands -def clone(orig, ui, source, dest=None, **opts): - '''clone Subversion repository to a local Mercurial repository. - - If no destination directory name is specified, it defaults to the - basename of the source plus "-hg". - - You can specify multiple paths for the location of tags using comma - separated values. - ''' - svnurl = ui.expandpath(source) - if not cmdutil.issvnurl(svnurl): - orig(ui, repo, source=source, dest=dest, *args, **opts) - - if not dest: - dest = hg.defaultdest(source) + '-hg' - ui.status("Assuming destination %s\n" % dest) - - if os.path.exists(dest): - raise hgutil.Abort("destination '%s' already exists" % dest) - url = util.normalize_url(svnurl) - res = -1 - try: - try: - res = pull(None, ui, None, True, opts.pop('svn_stupid', False), - source=url, create_new_dest=dest, **opts) - except core.SubversionException, e: - if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED: - raise hgutil.Abort('It appears svn does not trust the ssl cert for this site.\n' - 'Please try running svn ls on that url first.') - raise - finally: - if os.path.exists(dest): - repo = hg.repository(ui, dest) - fp = repo.opener("hgrc", "w", text=True) - fp.write("[paths]\n") - # percent needs to be escaped for ConfigParser - fp.write("default = %(url)s\nsvn = %(url)s\n" % {'url': svnurl.replace('%', '%%')}) - fp.close() - if res is None or res == 0: - commands.update(ui, repo, repo['tip'].node()) - - return res - - -def pull(orig, ui, repo, svn=None, svn_stupid=False, source="default", create_new_dest=False, - *args, **opts): - """pull new revisions from Subversion - """ - url = ui.expandpath(source) - if not (cmdutil.issvnurl(url) or svn or create_new_dest): - orig(ui, repo, source=source, *args, **opts) - svn_url = url - svn_url = util.normalize_url(svn_url) - old_encoding = util.swap_out_encoding() - # TODO implement skipto support - skipto_rev = 0 - have_replay = not svn_stupid - if have_replay and not callable( - delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover - ui.status('You are using old Subversion SWIG bindings. Replay will not' - ' work until you upgrade to 1.5.0 or newer. Falling back to' - ' a slower method that may be buggier. Please upgrade, or' - ' contribute a patch to use the ctypes bindings instead' - ' of SWIG.\n') - have_replay = False - initializing_repo = False - user = opts.get('username', hgutil.getuser()) - passwd = opts.get('password', '') - svn = svnwrap.SubversionRepo(svn_url, user, passwd) - author_host = "@%s" % svn.uuid - # TODO these should be configurable again, but I'm torn on how. - # Maybe this should be configured in .hg/hgrc for each repo? Seems vaguely reasonable. - tag_locations = ['tags', ] - authors = None - filemap = None - if repo: - hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo) - else: - hg_editor = hg_delta_editor.HgChangeReceiver(ui_=ui, - path=create_new_dest, - subdir=svn.subdir, - author_host=author_host, - tag_locations=tag_locations, - authors=authors, - filemap=filemap) - if os.path.exists(hg_editor.uuid_file): - uuid = open(hg_editor.uuid_file).read() - assert uuid == svn.uuid - start = hg_editor.last_known_revision() - else: - open(hg_editor.uuid_file, 'w').write(svn.uuid) - open(hg_editor.svn_url_file, 'w').write(svn_url) - initializing_repo = True - start = skipto_rev - - if initializing_repo and start > 0: - raise hgutil.Abort('Revision skipping at repository initialization ' - 'remains unimplemented.') - - # start converting revisions - for r in svn.revisions(start=start): - valid = True - hg_editor.update_branch_tag_map_for_rev(r) - for p in r.paths: - if hg_editor._is_path_valid(p): - valid = True - break - if valid: - # got a 502? Try more than once! - tries = 0 - converted = False - while not converted: - try: - util.describe_revision(ui, r) - if have_replay: - try: - cmdutil.replay_convert_rev(hg_editor, svn, r) - except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover - ui.status('%s\n' % e.message) - stupidmod.print_your_svn_is_old_message(ui) - have_replay = False - stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) - else: - stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) - converted = True - except core.SubversionException, e: #pragma: no cover - if (e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED - and '502' in str(e) - and tries < 3): - tries += 1 - ui.status('Got a 502, retrying (%s)\n' % tries) - else: - raise hgutil.Abort(*e.args) - util.swap_out_encoding(old_encoding) - - def incoming(ui, svn_url, hg_repo_path, skipto_rev=0, stupid=None, tag_locations='tags', authors=None, filemap=None, **opts): """show incoming revisions from Subversion @@ -204,125 +61,6 @@ def incoming(ui, svn_url, hg_repo_path, str(r.__getattribute__(attr)).strip(), )) -def push(orig, ui, repo, dest=None, **opts): - """push revisions starting at a specified head back to Subversion. - """ - svnurl = ui.expandpath(dest or 'default-push', dest or 'default') - if not cmdutil.issvnurl(svnurl): - orig(ui, repo, dest=dest, *args, **opts) - old_encoding = util.swap_out_encoding() - hge = hg_delta_editor.HgChangeReceiver(repo=repo) - assert svnurl == hge.url - 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, svnurl, - 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(None, ui, repo, True, stupid=opts.get('svn_stupid', False), - 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(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(hge.path, ui_=ui) - svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) - util.swap_out_encoding(old_encoding) - return 0 - - -def diff(orig, ui, repo, *args, **opts): - """show a diff of the most recent revision against its parent from svn - """ - if not opts.get('svn', False) or opts.get('change', None): - return orig(ui, repo, *args, **opts) - svn_commit_hashes = {} - hge = hg_delta_editor.HgChangeReceiver(repo=repo) - svn_commit_hashes = dict(zip(hge.revmap.itervalues(), - hge.revmap.iterkeys())) - if not opts.get('rev', None): - parent = repo.parents()[0] - o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, - parent.node()) - if o_r: - parent = repo[o_r[-1]].parents()[0] - opts['rev'] = ['%s:.' % node.hex(parent.node()), ] - node1, node2 = hgcmdutil.revpair(repo, opts['rev']) - baserev, _junk = svn_commit_hashes.get(node1, (-1, 'junk', )) - newrev, _junk = svn_commit_hashes.get(node2, (-1, 'junk', )) - it = patch.diff(repo, node1, node2, - opts=patch.diffopts(ui, opts={'git': True, - 'show_function': False, - 'ignore_all_space': False, - 'ignore_space_change': False, - 'ignore_blank_lines': False, - 'unified': True, - 'text': False, - })) - ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev)) - - def rebuildmeta(ui, repo, hg_repo_path, args, **opts): """rebuild hgsubversion metadata using values stored in revisions """ diff --git a/tests/comprehensive/test_stupid_pull.py b/tests/comprehensive/test_stupid_pull.py --- a/tests/comprehensive/test_stupid_pull.py +++ b/tests/comprehensive/test_stupid_pull.py @@ -6,7 +6,7 @@ from mercurial import hg from mercurial import ui from tests import test_util -import svncommands +import wrappers def _do_case(self, name): @@ -18,8 +18,8 @@ def _do_case(self, name): checkout_path = self.repo_path if subdir: checkout_path += '/' + subdir - svncommands.pull(ui.ui(), svn_url=test_util.fileurl(checkout_path), - hg_repo_path=wc2_path, stupid=True) + wrappers.clone(None, ui.ui(), source=test_util.fileurl(checkout_path), + dest=wc2_path, stupid=True, noupdate=True) self.repo2 = hg.repository(ui.ui(), wc2_path) self.assertEqual(self.repo.branchtags(), self.repo2.branchtags()) self.assertEqual(pickle.load(open(os.path.join(self.wc_path, '.hg', 'svn', 'tag_info'))), diff --git a/tests/test_diff.py b/tests/test_diff.py --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -2,7 +2,7 @@ import unittest from mercurial import ui -import svncommands +import wrappers import test_util @@ -32,7 +32,7 @@ class DiffTests(test_util.TestBase): ('alpha', 'alpha', 'alpha\n\nadded line\n'), ]) u = ui.ui() - svncommands.diff(lambda x,y,z: None, + wrappers.diff(lambda x,y,z: None, u, self.repo, svn=True) self.assertEqual(u.stream.getvalue(), expected_diff_output) diff --git a/tests/test_fetch_command.py b/tests/test_fetch_command.py --- a/tests/test_fetch_command.py +++ b/tests/test_fetch_command.py @@ -145,9 +145,6 @@ class TestBasicRepoLayout(test_util.Test def test_fetch_when_trunk_has_no_files(self, stupid=False): repo = self._load_fixture_and_fetch('file_not_in_trunk_root.svndump', stupid=stupid) - print repo['tip'].branch() - print repo['tip'] - print repo['tip'].files() self.assertEqual(repo['tip'].branch(), 'default') def test_fetch_when_trunk_has_no_files_stupid(self): diff --git a/tests/test_fetch_mappings.py b/tests/test_fetch_mappings.py --- a/tests/test_fetch_mappings.py +++ b/tests/test_fetch_mappings.py @@ -7,7 +7,7 @@ from mercurial import ui from mercurial import node import test_util -import svncommands +import wrappers class MapTests(test_util.TestBase): @property @@ -23,9 +23,8 @@ class MapTests(test_util.TestBase): authormap = open(self.authors, 'w') authormap.write("Augie=Augie Fackler \n") authormap.close() - svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path), - hg_repo_path=self.wc_path, stupid=stupid, - authors=self.authors) + wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), + dest=self.wc_path, stupid=stupid, svn_authors=self.authors) self.assertEqual(self.repo[0].user(), 'Augie Fackler ') self.assertEqual(self.repo['tip'].user(), @@ -39,9 +38,9 @@ class MapTests(test_util.TestBase): authormap = open(self.authors, 'w') authormap.write("evil=Testy ") authormap.close() - svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path), - hg_repo_path=self.wc_path, stupid=stupid, - authors=self.authors) + wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), + dest=self.wc_path, stupid=stupid, + svn_authors=self.authors) self.assertEqual(self.repo[0].user(), 'Augie@5b65bade-98f3-4993-a01f-b7a6710da339') self.assertEqual(self.repo['tip'].user(), @@ -55,9 +54,9 @@ class MapTests(test_util.TestBase): filemap = open(self.filemap, 'w') filemap.write("include alpha\n") filemap.close() - svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path), - hg_repo_path=self.wc_path, stupid=stupid, - filemap=self.filemap) + wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), + dest=self.wc_path, stupid=stupid, + svn_filemap=self.filemap) self.assertEqual(node.hex(self.repo[0].node()), '88e2c7492d83e4bf30fbb2dcbf6aa24d60ac688d') self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155') @@ -69,9 +68,9 @@ class MapTests(test_util.TestBase): filemap = open(self.filemap, 'w') filemap.write("exclude alpha\n") filemap.close() - svncommands.pull(ui.ui(), svn_url=test_util.fileurl(self.repo_path), - hg_repo_path=self.wc_path, stupid=stupid, - filemap=self.filemap) + wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), + dest=self.wc_path, stupid=stupid, + svn_filemap=self.filemap) self.assertEqual(node.hex(self.repo[0].node()), '2c48f3525926ab6c8b8424bcf5eb34b149b61841') self.assertEqual(node.hex(self.repo['default'].node()), 'b37a3c0297b71f989064d9b545b5a478bbed7cc1') diff --git a/tests/test_fetch_truncated.py b/tests/test_fetch_truncated.py --- a/tests/test_fetch_truncated.py +++ b/tests/test_fetch_truncated.py @@ -3,7 +3,7 @@ import unittest from mercurial import hg from mercurial import ui -import svncommands +import wrappers import test_util class TestFetchTruncatedHistory(test_util.TestBase): @@ -11,8 +11,9 @@ class TestFetchTruncatedHistory(test_uti # Test repository does not follow the usual layout test_util.load_svndump_fixture(self.repo_path, 'truncatedhistory.svndump') svn_url = test_util.fileurl(self.repo_path + '/project2') - svncommands.pull(ui.ui(), svn_url=svn_url, - hg_repo_path=self.wc_path, stupid=stupid) + wrappers.clone(None, ui.ui(), source=svn_url, + dest=self.wc_path, stupid=stupid, + noupdate=True) repo = hg.repository(ui.ui(), self.wc_path) # We are converting /project2/trunk coming from: diff --git a/tests/test_push_command.py b/tests/test_push_command.py --- a/tests/test_push_command.py +++ b/tests/test_push_command.py @@ -9,7 +9,7 @@ from mercurial import node from mercurial import ui from mercurial import revlog -import svncommands +import wrappers import test_util import time @@ -37,8 +37,8 @@ class PushOverSvnserveTests(test_util.Te args = ['svnserve', '-d', '--foreground', '-r', self.repo_path] self.svnserve_pid = subprocess.Popen(args).pid time.sleep(2) - svncommands.pull(ui.ui(), svn_url='svn://localhost/', - hg_repo_path=self.wc_path) + wrappers.clone(None, ui.ui(), source='svn://localhost/', + dest=self.wc_path, noupdate=True) def tearDown(self): os.system('kill -9 %d' % self.svnserve_pid) @@ -68,9 +68,10 @@ class PushOverSvnserveTests(test_util.Te if not commit: return # some tests use this test as an extended setup. hg.update(repo, repo['tip'].node()) - svncommands.push(ui.ui(), repo=self.repo, hg_repo_path=self.wc_path, - svn_url='svn://localhost/') + oldauthor = repo['tip'].user() + wrappers.push(None, ui.ui(), repo=self.repo) tip = self.repo['tip'] + self.assertNotEqual(oldauthor, tip.user()) self.assertNotEqual(tip.node(), old_tip) self.assertEqual(tip.parents()[0].node(), expected_parent) self.assertEqual(tip['adding_file'].data(), 'foo') @@ -170,9 +171,7 @@ class PushTests(test_util.TestBase): newhash = self.repo.commitctx(ctx) repo = self.repo hg.update(repo, newhash) - svncommands.push(ui.ui(), repo=repo, - svn_url=test_util.fileurl(self.repo_path), - hg_repo_path=self.wc_path) + wrappers.push(None, ui.ui(), repo=repo) 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(), @@ -285,11 +284,11 @@ class PushTests(test_util.TestBase): '2008-10-29 21:26:00 -0500', {'branch': 'default', }) new_hash = repo.commitctx(ctx) - hg.update(repo, repo['tip'].node()) + hg.clean(repo, repo['tip'].node()) self.pushrevisions() tip = self.repo['tip'] self.assertNotEqual(tip.node(), new_hash) - self.assert_('@' in tip.user()) + self.assert_('@' in self.repo['tip'].user()) self.assertEqual(tip['gamma'].flags(), 'x') self.assertEqual(tip['gamma'].data(), 'foo') self.assertEqual([x for x in tip.manifest().keys() if 'x' not in diff --git a/tests/test_util.py b/tests/test_util.py --- a/tests/test_util.py +++ b/tests/test_util.py @@ -13,7 +13,7 @@ from mercurial import hg from mercurial import node from mercurial import ui -import svncommands +import wrappers # Fixtures that need to be pulled at a subdirectory of the repo path subdir = {'truncatedhistory.svndump': '/project2', @@ -48,8 +48,8 @@ def load_fixture_and_fetch(fixture_name, load_svndump_fixture(repo_path, fixture_name) if subdir: repo_path += '/' + subdir - svncommands.pull(ui.ui(), svn_url=fileurl(repo_path), - hg_repo_path=wc_path, stupid=stupid) + wrappers.clone(None, ui.ui(), source=fileurl(repo_path), + dest=wc_path, stupid=stupid, noupdate=True) repo = hg.repository(ui.ui(), wc_path) return repo @@ -121,8 +121,7 @@ class TestBase(unittest.TestCase): def pushrevisions(self, stupid=False): before = len(self.repo) - svncommands.push(ui.ui(), repo=self.repo, hg_repo_path=self.wc_path, - svn_url=fileurl(self.repo_path), stupid=stupid) + wrappers.push(None, ui.ui(), repo=self.repo, stupid=stupid) after = len(self.repo) self.assertEqual(0, after - before) diff --git a/tests/test_utility_commands.py b/tests/test_utility_commands.py --- a/tests/test_utility_commands.py +++ b/tests/test_utility_commands.py @@ -8,8 +8,8 @@ from mercurial import context from mercurial import node import utility_commands -import svncommands import test_util +import wrappers expected_info_output = '''URL: %(repourl)s/%(branch)s Repository Root: %(repourl)s @@ -65,7 +65,7 @@ class UtilityTests(test_util.TestBase): {'branch': 'localbranch', }) new = self.repo.commitctx(ctx) hg.update(self.repo, new) - utility_commands.parent(lambda x, y: None, u, self.repo, svn=True) + wrappers.parent(lambda x, y: None, u, self.repo, svn=True) self.assertEqual(u.stream.getvalue(), 'changeset: 3:4e256962fc5d\n' 'branch: the_branch\n' @@ -76,17 +76,17 @@ class UtilityTests(test_util.TestBase): hg.update(self.repo, 'default') # Make sure styles work u = ui.ui() - utility_commands.parent(lambda x, y: None, u, self.repo, svn=True, style='compact') + wrappers.parent(lambda x, y: None, u, self.repo, svn=True, style='compact') self.assertEqual(u.stream.getvalue(), '4:1 1083037b18d8 2008-10-08 01:39 +0000 durin\n' ' Add gamma on trunk.\n\n') # custom templates too u = ui.ui() - utility_commands.parent(lambda x, y: None, u, self.repo, svn=True, template='{node}\n') + wrappers.parent(lambda x, y: None, u, self.repo, svn=True, template='{node}\n') self.assertEqual(u.stream.getvalue(), '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n') u = ui.ui() - utility_commands.parent(lambda x, y: None, u, self.repo, svn=True) + wrappers.parent(lambda x, y: None, u, self.repo, svn=True) self.assertEqual(u.stream.getvalue(), 'changeset: 4:1083037b18d8\n' 'parent: 1:c95251e0dd04\n' @@ -114,7 +114,7 @@ class UtilityTests(test_util.TestBase): {'branch': 'localbranch', }) new = self.repo.commitctx(ctx) hg.update(self.repo, new) - utility_commands.outgoing(lambda x,y,z: None, u, self.repo, svn=True) + wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True) self.assert_(node.hex(self.repo['localbranch'].node())[:8] in u.stream.getvalue()) self.assertEqual(u.stream.getvalue(), ('changeset: 5:6de15430fa20\n' @@ -127,7 +127,7 @@ class UtilityTests(test_util.TestBase): '\n')) hg.update(self.repo, 'default') u = ui.ui() - utility_commands.outgoing(lambda x,y,z: None, u, self.repo, svn=True) + wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True) self.assertEqual(u.stream.getvalue(), 'no changes found\n') def test_url_output(self): @@ -168,9 +168,9 @@ class UtilityTests(test_util.TestBase): """Verify url gets normalized on initial clone. """ test_util.load_svndump_fixture(self.repo_path, 'two_revs.svndump') - svncommands.pull(ui.ui(), - svn_url=test_util.fileurl(self.repo_path) + '/', - hg_repo_path=self.wc_path, stupid=False) + wrappers.clone(None, ui.ui(), + source=test_util.fileurl(self.repo_path) + '/', + dest=self.wc_path, stupid=False) hg.update(self.repo, 'tip') u = ui.ui() utility_commands.url(u, self.repo, self.wc_path) @@ -181,9 +181,9 @@ class UtilityTests(test_util.TestBase): """Verify url gets normalized on initial clone. """ test_util.load_svndump_fixture(self.repo_path, 'ignores.svndump') - svncommands.pull(ui.ui(), - svn_url=test_util.fileurl(self.repo_path) + '/', - hg_repo_path=self.wc_path, stupid=False) + wrappers.clone(None, ui.ui(), + source=test_util.fileurl(self.repo_path) + '/', + dest=self.wc_path, stupid=False) hg.update(self.repo, 'tip') u = ui.ui() utility_commands.genignore(u, self.repo, self.wc_path) diff --git a/utility_commands.py b/utility_commands.py --- a/utility_commands.py +++ b/utility_commands.py @@ -1,7 +1,5 @@ import os -import mercurial -from mercurial import cmdutil as hgcmdutil from mercurial import node from mercurial import util as hgutil from hgext import rebase as hgrebase @@ -102,22 +100,6 @@ Last Changed Date: %(date)s\n''' % }) -def parent(orig, ui, repo, *args, **opts): - """show Mercurial & Subversion parents of the working dir or revision - """ - if not opts.get('svn', False): - return orig(ui, repo, *args, **opts) - hge = hg_delta_editor.HgChangeReceiver(repo=repo) - svn_commit_hashes = dict(zip(hge.revmap.itervalues(), - hge.revmap.iterkeys())) - ha = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes) - if ha.node() == node.nullid: - raise mutil.Abort('No parent svn revision!') - displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) - displayer.show(ha) - return 0 - - def rebase(ui, repo, extrafn=None, sourcerev=None, **opts): """rebase current unpushed revisions onto the Subversion head @@ -166,26 +148,6 @@ def rebase(ui, repo, extrafn=None, sourc extrafn=extrafn) -def outgoing(orig, ui, repo, dest=None, *args, **opts): - """show changesets not found in the Subversion repository - """ - svnurl = ui.expandpath(dest or 'default-push', dest or 'default') - if not (cmdutil.issvnurl(svnurl) or opts.get('svn', False)): - return orig(ui, repo, dest, *args, **opts) - - hge = hg_delta_editor.HgChangeReceiver(repo=repo) - svn_commit_hashes = dict(zip(hge.revmap.itervalues(), - hge.revmap.iterkeys())) - o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, - repo.parents()[0].node()) - if not (o_r and len(o_r)): - ui.status('no changes found\n') - return 0 - displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) - for node in reversed(o_r): - displayer.show(repo[node]) - - def listauthors(ui, args, authors=None, **opts): """list all authors in a Subversion repository """ diff --git a/wrappers.py b/wrappers.py new file mode 100644 --- /dev/null +++ b/wrappers.py @@ -0,0 +1,320 @@ +import os + +from mercurial import cmdutil as hgcmdutil +from mercurial import commands +from mercurial import patch +from mercurial import hg +from mercurial import util as hgutil +from mercurial import node + +from svn import core +from svn import delta + +import cmdutil +import hg_delta_editor +import stupid as stupidmod +import svnwrap +import util +import utility_commands + +def parent(orig, ui, repo, *args, **opts): + """show Mercurial & Subversion parents of the working dir or revision + """ + if not opts.get('svn', False): + return orig(ui, repo, *args, **opts) + hge = hg_delta_editor.HgChangeReceiver(repo=repo) + svn_commit_hashes = dict(zip(hge.revmap.itervalues(), + hge.revmap.iterkeys())) + ha = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes) + if ha.node() == node.nullid: + raise hgutil.Abort('No parent svn revision!') + displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) + displayer.show(ha) + return 0 + + +def outgoing(orig, ui, repo, dest=None, *args, **opts): + """show changesets not found in the Subversion repository + """ + svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') + if not (cmdutil.issvnurl(svnurl) or opts.get('svn', False)): + return orig(ui, repo, dest, *args, **opts) + + hge = hg_delta_editor.HgChangeReceiver(repo=repo) + svn_commit_hashes = dict(zip(hge.revmap.itervalues(), + hge.revmap.iterkeys())) + o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, + repo.parents()[0].node()) + if not (o_r and len(o_r)): + ui.status('no changes found\n') + return 0 + displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) + for node in reversed(o_r): + displayer.show(repo[node]) + + + + +def diff(orig, ui, repo, *args, **opts): + """show a diff of the most recent revision against its parent from svn + """ + if not opts.get('svn', False) or opts.get('change', None): + return orig(ui, repo, *args, **opts) + svn_commit_hashes = {} + hge = hg_delta_editor.HgChangeReceiver(repo=repo) + svn_commit_hashes = dict(zip(hge.revmap.itervalues(), + hge.revmap.iterkeys())) + if not opts.get('rev', None): + parent = repo.parents()[0] + o_r = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, + parent.node()) + if o_r: + parent = repo[o_r[-1]].parents()[0] + opts['rev'] = ['%s:.' % node.hex(parent.node()), ] + node1, node2 = hgcmdutil.revpair(repo, opts['rev']) + baserev, _junk = svn_commit_hashes.get(node1, (-1, 'junk', )) + newrev, _junk = svn_commit_hashes.get(node2, (-1, 'junk', )) + it = patch.diff(repo, node1, node2, + opts=patch.diffopts(ui, opts={'git': True, + 'show_function': False, + 'ignore_all_space': False, + 'ignore_space_change': False, + 'ignore_blank_lines': False, + 'unified': True, + 'text': False, + })) + ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev)) + + + + +def push(orig, ui, repo, dest=None, *args, **opts): + """push revisions starting at a specified head back to Subversion. + """ + svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') + if not cmdutil.issvnurl(svnurl): + orig(ui, repo, dest=dest, *args, **opts) + old_encoding = util.swap_out_encoding() + hge = hg_delta_editor.HgChangeReceiver(repo=repo) + svnurl = util.normalize_url(svnurl) + if svnurl != hge.url: + raise hgutil.Abort('wrong subversion url!') + 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, svnurl, + 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(None, ui, repo, True, stupid=opts.get('svn_stupid', False), + 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(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(hge.path, ui_=ui) + svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) + util.swap_out_encoding(old_encoding) + return 0 + + + +def clone(orig, ui, source, dest=None, *args, **opts): + '''clone Subversion repository to a local Mercurial repository. + + If no destination directory name is specified, it defaults to the + basename of the source plus "-hg". + + You can specify multiple paths for the location of tags using comma + separated values. + ''' + svnurl = ui.expandpath(source) + if not cmdutil.issvnurl(svnurl): + orig(ui, source=source, dest=dest, *args, **opts) + + if not dest: + dest = hg.defaultdest(source) + '-hg' + ui.status("Assuming destination %s\n" % dest) + + if os.path.exists(dest): + raise hgutil.Abort("destination '%s' already exists" % dest) + url = util.normalize_url(svnurl) + res = -1 + try: + try: + res = pull(None, ui, None, True, opts.pop('svn_stupid', False), + source=url, create_new_dest=dest, **opts) + except core.SubversionException, e: + if e.apr_err == core.SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED: + raise hgutil.Abort('It appears svn does not trust the ssl cert for this site.\n' + 'Please try running svn ls on that url first.') + raise + finally: + if os.path.exists(dest): + repo = hg.repository(ui, dest) + fp = repo.opener("hgrc", "w", text=True) + fp.write("[paths]\n") + # percent needs to be escaped for ConfigParser + fp.write("default = %(url)s\nsvn = %(url)s\n" % {'url': svnurl.replace('%', '%%')}) + fp.close() + if (res is None or res == 0) and not opts.get('noupdate', False): + commands.update(ui, repo, repo['tip'].node()) + + return res + + +def pull(orig, ui, repo, svn=None, svn_stupid=False, source="default", create_new_dest=False, + *args, **opts): + """pull new revisions from Subversion + """ + url = ((repo and repo.ui) or ui).expandpath(source) + if not (cmdutil.issvnurl(url) or svn or create_new_dest): + orig(ui, repo, source=source, *args, **opts) + svn_url = url + svn_url = util.normalize_url(svn_url) + old_encoding = util.swap_out_encoding() + # TODO implement skipto support + skipto_rev = 0 + have_replay = not svn_stupid + if have_replay and not callable( + delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover + ui.status('You are using old Subversion SWIG bindings. Replay will not' + ' work until you upgrade to 1.5.0 or newer. Falling back to' + ' a slower method that may be buggier. Please upgrade, or' + ' contribute a patch to use the ctypes bindings instead' + ' of SWIG.\n') + have_replay = False + initializing_repo = False + user = opts.get('username', hgutil.getuser()) + passwd = opts.get('password', '') + svn = svnwrap.SubversionRepo(svn_url, user, passwd) + author_host = "@%s" % svn.uuid + # TODO these should be configurable again, but I'm torn on how. + # Maybe this should be configured in .hg/hgrc for each repo? Seems vaguely reasonable. + tag_locations = ['tags', ] + authors = opts.pop('svn_authors', None) + filemap = opts.pop('svn_filemap', None) + if repo: + hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo, + subdir=svn.subdir, + author_host=author_host, + tag_locations=tag_locations, + authors=authors, + filemap=filemap) + else: + hg_editor = hg_delta_editor.HgChangeReceiver(ui_=ui, + path=create_new_dest, + subdir=svn.subdir, + author_host=author_host, + tag_locations=tag_locations, + authors=authors, + filemap=filemap) + if os.path.exists(hg_editor.uuid_file): + uuid = open(hg_editor.uuid_file).read() + assert uuid == svn.uuid + start = hg_editor.last_known_revision() + else: + open(hg_editor.uuid_file, 'w').write(svn.uuid) + open(hg_editor.svn_url_file, 'w').write(svn_url) + initializing_repo = True + start = skipto_rev + + if initializing_repo and start > 0: + raise hgutil.Abort('Revision skipping at repository initialization ' + 'remains unimplemented.') + + # start converting revisions + for r in svn.revisions(start=start): + valid = True + hg_editor.update_branch_tag_map_for_rev(r) + for p in r.paths: + if hg_editor._is_path_valid(p): + valid = True + break + if valid: + # got a 502? Try more than once! + tries = 0 + converted = False + while not converted: + try: + util.describe_revision(ui, r) + if have_replay: + try: + cmdutil.replay_convert_rev(hg_editor, svn, r) + except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover + ui.status('%s\n' % e.message) + stupidmod.print_your_svn_is_old_message(ui) + have_replay = False + stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) + else: + stupidmod.svn_server_pull_rev(ui, svn, hg_editor, r) + converted = True + except core.SubversionException, e: #pragma: no cover + if (e.apr_err == core.SVN_ERR_RA_DAV_REQUEST_FAILED + and '502' in str(e) + and tries < 3): + tries += 1 + ui.status('Got a 502, retrying (%s)\n' % tries) + else: + raise hgutil.Abort(*e.args) + util.swap_out_encoding(old_encoding)