changeset 342:76c833526fbc

Use fallbacks in the wrappers for Subversion support, instead of prefixes. The change only applies to the ambiguous URL schemes: file, http and https. The others - svn+ssh and svn - behave the same as previously. For http and https, the wrapping is implemented in 'svnrepo.py': Only when the attempt to create a httprepo instance fails, will the URL be considered for Subversion URL. For file, the ambiguity is treated much like the Mercurial core distinguishes bundle and directories. In this case, a directory that looks like a Subversion repository will *not* be considered for a Mercurial clone. Tthe command lines are more similar to before this refactor. The only option added to push & pull is --stupid; others are only added to clone. Any of these options specified to clone will be added to the generated '.hgrc'. Also, the -r/--rev option now works for clone & push.
author Dan Villiom Podlaski Christiansen <danchr@gmail.com>
date Wed, 20 May 2009 18:38:01 +0200
parents cfbd0e563af9
children 49e6895ce041
files hgsubversion/__init__.py hgsubversion/cmdutil.py hgsubversion/hg_delta_editor.py hgsubversion/svncommands.py hgsubversion/svnrepo.py hgsubversion/svnwrap/svn_swig_wrapper.py hgsubversion/util.py hgsubversion/wrappers.py tests/test_pull.py tests/test_util.py
diffstat 10 files changed, 106 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/hgsubversion/__init__.py
+++ b/hgsubversion/__init__.py
@@ -28,17 +28,17 @@ from mercurial import commands
 from mercurial import extensions
 from mercurial import hg
 from mercurial import util as hgutil
+from mercurial import cmdutil as hgcmdutil
 
 from svn import core
 
 import svncommands
+import cmdutil
 import svnrepo
 import util
 import wrappers
 import svnexternals
 
-schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+file')
-
 optionmap = {
     'tagpaths': ('hgsubversion', 'tagpaths'),
     'authors': ('hgsubversion', 'authormap'),
@@ -49,14 +49,25 @@ optionmap = {
     'usebranchnames': ('hgsubversion', 'usebranchnames'),
 }
 
+svnopts = (('', 'stupid', '', 'use slower, but more compatible, protocol for '
+            'Subversion'),)
+
+svncloneopts = (('T', 'tagpaths', [], 'list of path s to search for tags '
+                 'in Subversion repositories'),
+                ('A', 'authors', '', 'path to file mapping Subversion '
+                 'usernames to Mercurial authors'),
+                ('', 'filemap', '', 'path to file containing rules for '
+                 'remapping Subversion repository paths'),)
+
+
+
 def wrapper(orig, ui, repo, *args, **opts):
     """
-    Some of the options listed below only apply to Subversion 
-    %(action)s. See 'hg help %(extension)s' for more information on 
-    them as well as other ways of customising the conversion process.
+    Subversion %(target)s can be used for %(command)s. See 'hg help
+    %(extension)s' for more on the conversion process.
     """
     for opt, (section, name) in optionmap.iteritems():
-        if opt in opts:
+        if opt in opts and opts[opt]:
             if isinstance(repo, str):
                 ui.setconfig(section, name, opts.pop(opt))
             else:
@@ -64,6 +75,35 @@ def wrapper(orig, ui, repo, *args, **opt
 
     return orig(ui, repo, *args, **opts)
 
+def clonewrapper(orig, ui, source, dest=None, **opts):
+    """
+    Some of the options listed below only apply to Subversion
+    %(target)s. See 'hg help %(extension)s' for more information on
+    them as well as other ways of customising the conversion process.
+    """
+
+    for opt, (section, name) in optionmap.iteritems():
+        if opt in opts and opts[opt]:
+            ui.setconfig(section, name, opts.pop(opt))
+
+    # this must be kept in sync with mercurial/commands.py
+    srcrepo, dstrepo = hg.clone(hgcmdutil.remoteui(ui, opts), source, dest,
+                                pull=opts.get('pull'),
+                                stream=opts.get('uncompressed'),
+                                rev=opts.get('rev'),
+                                update=not opts.get('noupdate'))
+
+    if dstrepo.local() and srcrepo.capable('subversion'):
+        fd = dstrepo.opener("hgrc", "a", text=True)
+        for section in set(s for s, v in optionmap.itervalues()):
+            if ui.has_section(section):
+                fd.write('\n[%s]\n' % section)
+
+            for key, value in ui.configitems(section):
+                if not isinstance(value, str):
+                    value = ', '.join(value)
+                fd.write('%s = %s\n' % (key, value))
+
 def uisetup(ui):
     """Do our UI setup.
 
@@ -82,21 +122,19 @@ def uisetup(ui):
     entry[1].append(('', 'svn', None,
                      "show svn-style diffs, default against svn parent"))
 
-    newflags = (('A', 'authors', '', 'path to file mapping Subversion '
-                 'usernames to Mercurial authors.'),
-                ('', 'filemap', '', 'path to file containing rules for '
-                 'mapping Subversion repository paths.'),
-                ('T', 'tagpaths', ['tags'], 'list of paths to search for tags '
-                 'in Subversion repositories.'))
-
-    for command, action in [('clone', 'sources'), ('pull', 'sources'),
-                            ('push', 'destinations')]:
-        doc = wrapper.__doc__.strip() % { 'extension': 'hgsubversion',
-                                          'action': action }
+    for command, target, isclone in [('clone', 'sources', True),
+                                     ('pull', 'sources', False),
+                                     ('push', 'destinations', False)]:
+        doc = wrapper.__doc__.strip() % { 'command': command,
+                                          'Command': command.capitalize(),
+                                          'extension': 'hgsubversion',
+                                          'target': target }
         fn = getattr(commands, command)
         fn.__doc__ = fn.__doc__.rstrip() + '\n\n    ' + doc
-        entry = extensions.wrapcommand(commands.table, command, wrapper)
-        entry[1].extend(newflags)
+        entry = extensions.wrapcommand(commands.table, command,
+                                       (wrapper, clonewrapper)[isclone])
+        entry[1].extend(svnopts)
+        if isclone: entry[1].extend(svncloneopts)
 
     try:
         rebase = extensions.find('rebase')
@@ -147,8 +185,16 @@ def reposetup(ui, repo):
     if repo.local():
        svnrepo.generate_repo_class(ui, repo)
 
-for scheme in schemes:
-    hg.schemes[scheme] = svnrepo
+_origschemes = hg.schemes.copy()
+def _lookup(url):
+    if cmdutil.islocalrepo(url):
+        return svnrepo
+    else:
+        return _origschemes['file'](url)
+
+# install scheme handlers
+hg.schemes.update({ 'file': _lookup, 'http': svnrepo, 'https': svnrepo,
+                    'svn': svnrepo, 'svn+ssh': svnrepo })
 
 cmdtable = {
     "svn":
@@ -165,3 +211,6 @@ cmdtable = {
          svncommands._helpgen(),
          ),
 }
+
+# only these methods are public
+__all__ = ('cmdtable', 'reposetup', 'uisetup')
--- a/hgsubversion/cmdutil.py
+++ b/hgsubversion/cmdutil.py
@@ -297,4 +297,7 @@ def islocalrepo(url):
     return False
 
 def issvnurl(url):
-    return url.startswith('svn') or islocalrepo(url)
+    for scheme in ('svn', 'http', 'https', 'svn+ssh'):
+        if url.startswith(scheme + '://'):
+            return True
+    return islocalrepo(url)
--- a/hgsubversion/hg_delta_editor.py
+++ b/hgsubversion/hg_delta_editor.py
@@ -96,7 +96,7 @@ class HgChangeReceiver(delta.Editor):
         if not filemap:
             filemap = self.ui.config('hgsubversion', 'filemap')
         if not tag_locations:
-            tag_locations = self.ui.config('hgsubversion', 'tagpaths', ['tags'])
+            tag_locations = self.ui.configlist('hgsubversion', 'tagpaths', ['tags'])
         self.usebranchnames = self.ui.configbool('hgsubversion',
                                                   'usebranchnames', True)
 
--- a/hgsubversion/svncommands.py
+++ b/hgsubversion/svncommands.py
@@ -62,8 +62,6 @@ def rebuildmeta(ui, repo, hg_repo_path, 
         url = repo.ui.expandpath(dest or 'default-push', dest or 'default')
     else:
         url = args[0]
-    if not (url.startswith('svn+') or url.startswith('svn:')):
-        raise hgutil.Abort('No valid Subversion URI found; please specify one.')
     uuid = None
     url = util.normalize_url(url.rstrip('/'))
     user, passwd = util.getuserpass(opts)
--- a/hgsubversion/svnrepo.py
+++ b/hgsubversion/svnrepo.py
@@ -14,8 +14,10 @@ subclass: pull() is called on the instan
   are used to distinguish and filter these operations from others.
 """
 
+from mercurial import error
 from mercurial import node
 from mercurial import util as hgutil
+from mercurial import httprepo
 import mercurial.repo
 
 import hg_delta_editor
@@ -42,7 +44,8 @@ def generate_repo_class(ui, repo):
         """
         original = getattr(repo.__class__, fn.__name__)
         def wrapper(self, *args, **opts):
-            if 'subversion' in getattr(args[0], 'capabilities', []):
+            capable = getattr(args[0], 'capable', lambda x: False)
+            if capable('subversion'):
                 return fn(self, *args, **opts)
             else:
                 return original(self, *args, **opts)
@@ -53,16 +56,14 @@ def generate_repo_class(ui, repo):
     class svnlocalrepo(repo.__class__):
         @remotesvn
         def push(self, remote, force=False, revs=None):
-            # TODO: pass on revs
             wrappers.push(self, dest=remote.svnurl, force=force, revs=None)
 
         @remotesvn
         def pull(self, remote, heads=None, force=False):
+            lock = self.wlock()
             try:
-                lock = self.wlock()
-                wrappers.pull(self, source=remote.svnurl, rev=heads, force=force)
-            except KeyboardInterrupt:
-                pass
+                wrappers.pull(self, source=remote.svnurl,
+                              rev=heads, force=force)
             finally:
                 lock.release()
 
@@ -90,7 +91,7 @@ class svnremoterepo(mercurial.repo.repos
         return util.normalize_url(self.path)
 
     def url(self):
-        return self.path
+        return self.path.rstrip('/')
 
     def lookup(self, key):
         return key
@@ -106,6 +107,13 @@ class svnremoterepo(mercurial.repo.repos
         raise hgutil.Abort('command unavailable for Subversion repositories')
 
 def instance(ui, url, create):
+    if url.startswith('http://') or url.startswith('https://'):
+        try:
+            # may yield a bogus 'real URL...' message
+            return httprepo.instance(ui, url, create)
+        except error.RepoError:
+            pass
+
     if create:
         raise hgutil.Abort('cannot create new remote Subversion repository')
 
--- a/hgsubversion/svnwrap/svn_swig_wrapper.py
+++ b/hgsubversion/svnwrap/svn_swig_wrapper.py
@@ -358,7 +358,7 @@ class SubversionRepo(object):
         The reason this is lazy is so that you can use the same repo object
         to perform RA calls to get deltas.
         """
-        return self.fetch_history_at_paths([''], start=start,
+        return self.fetch_history_at_paths([''], start=start, stop=stop,
                                            chunk_size=chunk_size)
 
     def fetch_history_at_paths(self, paths, start=None, stop=None,
--- a/hgsubversion/util.py
+++ b/hgsubversion/util.py
@@ -24,8 +24,6 @@ def version(ui):
 
 
 def normalize_url(svnurl):
-    if svnurl.startswith('svn+') and not svnurl.startswith('svn+ssh'):
-        svnurl = svnurl[4:]
     url, revs, checkout = hg.parseurl(svnurl)
     url = url.rstrip('/')
     if checkout:
--- a/hgsubversion/wrappers.py
+++ b/hgsubversion/wrappers.py
@@ -202,12 +202,18 @@ def pull(repo, source="default", rev=Non
     url = repo.ui.expandpath(source)
     svn_url = util.normalize_url(url)
 
-    # Split off #rev; TODO: implement --rev/#rev support limiting the pulled/cloned revisions
+    # Split off #rev
     svn_url, revs, checkout = hg.parseurl(svn_url, rev)
     old_encoding = util.swap_out_encoding()
 
     # TODO implement skipto support
     skipto_rev = 0
+    try:
+        stopat_rev = int(checkout or 0)
+    except ValueError:
+        raise hgutil.Abort('unrecognised Subversion revision; '
+                           'only numbers work.')
+
     have_replay = not repo.ui.configbool('hgsubversion', 'stupid')
     if have_replay and not callable(
         delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover
@@ -235,8 +241,9 @@ def pull(repo, source="default", rev=Non
 
     revisions = 0
 
-    # start converting revisions
-    for r in svn.revisions(start=start):
+    try:
+      # start converting revisions
+      for r in svn.revisions(start=start, stop=stopat_rev):
         valid = True
         hg_editor.update_branch_tag_map_for_rev(r)
         for p in r.paths:
@@ -270,6 +277,9 @@ def pull(repo, source="default", rev=Non
                     else:
                         raise hgutil.Abort(*e.args)
             revisions += 1
+    except KeyboardInterrupt:
+        pass
+
     util.swap_out_encoding(old_encoding)
 
     if revisions == 0:
--- a/tests/test_pull.py
+++ b/tests/test_pull.py
@@ -21,7 +21,7 @@ class TestPull(test_util.TestBase):
         if self.svn_wc is None:
             self.svn_wc = os.path.join(self.tmpdir, 'testsvn_wc')
             subprocess.call([
-                'svn', 'co', '-q', test_util.fileurl(self.repo_path)[4:], 
+                'svn', 'co', '-q', test_util.fileurl(self.repo_path), 
                 self.svn_wc
             ],
             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -31,7 +31,7 @@ def fileurl(path):
     path = urllib.pathname2url(path)
     if drive:
         drive = '/' + drive
-    url = 'svn+file://%s%s' % (drive, path)
+    url = 'file://%s%s' % (drive, path)
     return url
 
 def load_svndump_fixture(path, fixture_name):