changeset 256:7932d098cb5f

Refactor commands to wrap their hg equivalent adding a --svn flag where sane.
author Augie Fackler <durin42@gmail.com>
date Fri, 10 Apr 2009 18:47:18 -0500
parents 246aaefb1cc0
children ffccf0080e54
files __init__.py cmdutil.py svncommands.py tests/test_diff.py tests/test_utility_commands.py util.py utility_commands.py
diffstat 7 files changed, 223 insertions(+), 109 deletions(-) [+]
line wrap: on
line diff
--- 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]'),
 }
--- 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', ))))
--- 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,
 }
 
--- 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)
 
 
--- 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),
--- 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):
--- 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,