# HG changeset patch # User Augie Fackler # Date 1239407238 18000 # Node ID 7932d098cb5f61641718b7ff2e28fbc7e3479c69 # Parent 246aaefb1cc00b5d77dfec1d3c8eed13ebe5e1f8 Refactor commands to wrap their hg equivalent adding a --svn flag where sane. diff --git a/__init__.py b/__init__.py --- a/__init__.py +++ b/__init__.py @@ -17,6 +17,7 @@ import sys import traceback from mercurial import commands +from mercurial import extensions from mercurial import hg from mercurial import util as hgutil @@ -32,6 +33,45 @@ def reposetup(ui, repo): repo.__class__ = tag_repo.generate_repo_class(ui, repo) +def uisetup(ui): + """Do our UI setup. + + Does the following wrappings: + * parent -> utility_commands.parent + * outgoing -> utility_commands.outgoing + """ + entry = extensions.wrapcommand(commands.table, 'parents', + utility_commands.parent) + entry[1].append(('', 'svn', None, "show parent svn revision instead")) + entry = extensions.wrapcommand(commands.table, 'outgoing', + utility_commands.outgoing) + entry[1].append(('', 'svn', None, "show revisions outgoing to subversion")) + entry = extensions.wrapcommand(commands.table, 'diff', + svncommands.diff) + entry[1].append(('', 'svn', None, + "show svn-style diffs, default against svn parent")) + entry = extensions.wrapcommand(commands.table, 'push', + svncommands.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) + 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) + 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.'), + ('', 'svn-authors', '', 'username mapping filename'), + ('', '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): '''see detailed help for list of subcommands''' @@ -70,33 +110,7 @@ def svn(ui, repo, subcommand, *args, **o raise -def svn_fetch(ui, svn_url, hg_repo_path=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. - ''' - if not hg_repo_path: - hg_repo_path = hg.defaultdest(svn_url) + "-hg" - ui.status("Assuming destination %s\n" % hg_repo_path) - should_update = not os.path.exists(hg_repo_path) - svn_url = util.normalize_url(svn_url) - try: - res = svncommands.pull(ui, svn_url, hg_repo_path, **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 - if (res is None or res == 0) and should_update: - repo = hg.repository(ui, hg_repo_path) - commands.update(ui, repo, repo['tip'].node()) - return res - -commands.norepo += " svnclone" cmdtable = { "svn": (svn, @@ -111,16 +125,4 @@ cmdtable = { ], svncommands._helpgen(), ), - "svnclone": - (svn_fetch, - [('S', 'skipto-rev', '0', 'skip commits before this revision.'), - ('', 'stupid', False, 'be stupid and use diffy replay.'), - ('T', 'tag-locations', 'tags', 'Relative path to Subversion tags.'), - ('A', 'authors', '', 'username mapping filename'), - ('', 'filemap', '', - 'remap file to exclude paths or include only certain paths'), - ('', 'username', '', 'username for authentication'), - ('', 'password', '', 'password for authentication'), - ], - 'hg svnclone source [dest]'), } diff --git a/cmdutil.py b/cmdutil.py --- a/cmdutil.py +++ b/cmdutil.py @@ -1,5 +1,6 @@ #!/usr/bin/python import re +import os from mercurial import util as hgutil @@ -22,15 +23,21 @@ 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, base_revision): + +def filterdiff(diff, oldrev, newrev): diff = newfile_devnull_re.sub(r'--- \1\t(revision 0)' '\n' r'+++ \1\t(working copy)', diff) - diff = a_re.sub(r'--- \1'+ ('\t(revision %d)' % base_revision), diff) - diff = b_re.sub(r'+++ \1' + '\t(working copy)', diff) - diff = devnull_re.sub(r'\1 /dev/null' '\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 @@ -270,3 +277,17 @@ def commit_from_rev(ui, repo, rev_ctx, h raise hgutil.Abort('Base text was out of date, maybe rebase?') 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 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', )))) diff --git a/svncommands.py b/svncommands.py --- a/svncommands.py +++ b/svncommands.py @@ -1,6 +1,8 @@ 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 @@ -17,14 +19,63 @@ import util import utility_commands -def pull(ui, svn_url, hg_repo_path, skipto_rev=0, stupid=None, - tag_locations='tags', authors=None, filemap=None, **opts): +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() - skipto_rev=int(skipto_rev) - have_replay = not stupid + # 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' @@ -38,14 +89,21 @@ def pull(ui, svn_url, hg_repo_path, skip passwd = opts.get('password', '') svn = svnwrap.SubversionRepo(svn_url, user, passwd) author_host = "@%s" % svn.uuid - tag_locations = tag_locations.split(',') - hg_editor = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui, - subdir=svn.subdir, - author_host=author_host, - tag_locations=tag_locations, - authors=authors, - filemap=filemap) + # 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 @@ -146,12 +204,15 @@ def incoming(ui, svn_url, hg_repo_path, str(r.__getattribute__(attr)).strip(), )) -def push(ui, repo, hg_repo_path, svn_url, stupid=False, **opts): +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(hg_repo_path, - ui_=ui) + 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()) @@ -187,14 +248,14 @@ def push(ui, repo, hg_repo_path, svn_url # 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, svn_url, + 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(ui, svn_url, hg_repo_path, stupid=stupid, + 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 @@ -225,25 +286,32 @@ def push(ui, repo, hg_repo_path, svn_url if children: child = children[0] rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, ui_=ui) + 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(ui, repo, hg_repo_path, **opts): +def diff(orig, ui, repo, *args, **opts): """show a diff of the most recent revision against its parent from svn """ - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui) + 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())) - 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] - base_rev, _junk = svn_commit_hashes[parent.node()] - it = patch.diff(repo, parent.node(), None, + 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, @@ -252,7 +320,7 @@ def diff(ui, repo, hg_repo_path, **opts) 'unified': True, 'text': False, })) - ui.write(cmdutil.filterdiff(''.join(it), base_rev)) + ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev)) def rebuildmeta(ui, repo, hg_repo_path, args, **opts): @@ -420,13 +488,9 @@ def update(ui, args, repo, clean=False, nourl = ['rebuildmeta'] + utility_commands.nourl table = { - 'pull': pull, - 'push': push, - 'dcommit': push, 'update': update, 'help': help, 'rebuildmeta': rebuildmeta, - 'diff': diff, 'incoming': incoming, } diff --git a/tests/test_diff.py b/tests/test_diff.py --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -32,7 +32,8 @@ class DiffTests(test_util.TestBase): ('alpha', 'alpha', 'alpha\n\nadded line\n'), ]) u = ui.ui() - svncommands.diff(u, self.repo, self.wc_path) + svncommands.diff(lambda x,y,z: None, + u, self.repo, svn=True) self.assertEqual(u.stream.getvalue(), expected_diff_output) 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 @@ -65,18 +65,34 @@ class UtilityTests(test_util.TestBase): {'branch': 'localbranch', }) new = self.repo.commitctx(ctx) hg.update(self.repo, new) - utility_commands.parent(u, self.repo, self.wc_path) - self.assert_(node.hex(self.repo['the_branch'].node())[:8] in - u.stream.getvalue()) - self.assert_('the_branch' in u.stream.getvalue()) - self.assert_('r5' in u.stream.getvalue()) + utility_commands.parent(lambda x, y: None, u, self.repo, svn=True) + self.assertEqual(u.stream.getvalue(), + 'changeset: 3:4e256962fc5d\n' + 'branch: the_branch\n' + 'user: durin@df2126f7-00ab-4d49-b42c-7e981dde0bcf\n' + 'date: Wed Oct 08 01:39:05 2008 +0000\n' + 'summary: add delta on the branch\n\n') + hg.update(self.repo, 'default') + # Make sure styles work u = ui.ui() - utility_commands.parent(u, self.repo, self.wc_path) - self.assert_(node.hex(self.repo['default'].node())[:8] in - u.stream.getvalue()) - self.assert_('trunk' in u.stream.getvalue()) - self.assert_('r6' in u.stream.getvalue()) + utility_commands.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') + self.assertEqual(u.stream.getvalue(), '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n') + + u = ui.ui() + utility_commands.parent(lambda x, y: None, u, self.repo, svn=True) + self.assertEqual(u.stream.getvalue(), + 'changeset: 4:1083037b18d8\n' + 'parent: 1:c95251e0dd04\n' + 'user: durin@df2126f7-00ab-4d49-b42c-7e981dde0bcf\n' + 'date: Wed Oct 08 01:39:29 2008 +0000\n' + 'summary: Add gamma on trunk.\n\n') def test_outgoing_output(self): self._load_fixture_and_fetch('two_heads.svndump') @@ -98,14 +114,21 @@ class UtilityTests(test_util.TestBase): {'branch': 'localbranch', }) new = self.repo.commitctx(ctx) hg.update(self.repo, new) - utility_commands.outgoing(u, self.repo, self.wc_path) + utility_commands.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.assert_('testy' in u.stream.getvalue()) + self.assertEqual(u.stream.getvalue(), ('changeset: 5:6de15430fa20\n' + 'branch: localbranch\n' + 'tag: tip\n' + 'parent: 3:4e256962fc5d\n' + 'user: testy\n' + 'date: Sun Dec 21 16:32:00 2008 -0500\n' + 'summary: automated test\n' + '\n')) hg.update(self.repo, 'default') u = ui.ui() - utility_commands.outgoing(u, self.repo, self.wc_path) - self.assertEqual(u.stream.getvalue(), 'No outgoing changes found.\n') + utility_commands.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): self._load_fixture_and_fetch('two_revs.svndump') @@ -185,7 +208,7 @@ class UtilityTests(test_util.TestBase): args=[test_util.fileurl(self.repo_path)], authors=author_path) self.assertEqual(open(author_path).read(), 'Augie=\nevil=\n') - + def suite(): all = [unittest.TestLoader().loadTestsFromTestCase(UtilityTests), diff --git a/util.py b/util.py --- a/util.py +++ b/util.py @@ -15,8 +15,10 @@ def version(ui): return node.hex(ver)[:12] -def normalize_url(svn_url): - return svn_url.rstrip('/') +def normalize_url(svnurl): + if svnurl.startswith('svn+http'): + svnurl = svnurl[4:] + return svnurl.rstrip('/') def wipe_all_files(hg_wc_path): diff --git a/utility_commands.py b/utility_commands.py --- a/utility_commands.py +++ b/utility_commands.py @@ -102,20 +102,19 @@ Last Changed Date: %(date)s\n''' % }) -def parent(ui, repo, hg_repo_path, **opts): +def parent(orig, ui, repo, *args, **opts): """show Mercurial & Subversion parents of the working dir or revision """ - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui) + 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: - r, br = svn_commit_hashes[ha.node()] - ui.status('Working copy parent revision is %s: r%s on %s\n' % - (ha, r, br or 'trunk')) - else: - ui.status('Working copy seems to have no parent svn revision.\n') + 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 @@ -167,16 +166,20 @@ def rebase(ui, repo, extrafn=None, sourc extrafn=extrafn) -def outgoing(ui, repo, hg_repo_path, **opts): +def outgoing(orig, ui, repo, dest=None, *args, **opts): """show changesets not found in the Subversion repository """ - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui) + 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()) + 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 outgoing changes found.\n') + ui.status('no changes found\n') return 0 displayer = hgcmdutil.show_changeset(ui, repo, opts, buffered=False) for node in reversed(o_r): @@ -214,8 +217,6 @@ table = { 'url': url, 'genignore': genignore, 'info': info, - 'parent': parent, - 'outgoing': outgoing, 'listauthors': listauthors, 'version': version, 'rebase': rebase,