changeset 326:33736e2e25f0

alternate approach for supporting svn schemes for repository paths We now intercept the operations in the local repo class, and handle the relevant operation ourselves. This frees us from wrapping all relevant commands and replicating their functionality. The implementation is incomplete; only one test has been modified to use the standard Mercurial API with the changed URLs. Once changed, those tests will likely reveal bugs or missing features in the new wrappers. Also, new wrappers will be needed for handling conversion flags such as -A/--authormap.
author Dan Villiom Podlaski Christiansen <danchr@gmail.com>
date Thu, 07 May 2009 20:50:53 +0200
parents 1ba8ed29148e
children 98740f66a70c
files __init__.py hg_delta_editor.py tag_repo.py tests/test_fetch_branches.py tests/test_util.py util.py wrappers.py
diffstat 7 files changed, 113 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/__init__.py
+++ b/__init__.py
@@ -18,6 +18,7 @@ import traceback
 
 from mercurial import commands
 from mercurial import extensions
+from mercurial import hg
 from mercurial import util as hgutil
 
 from svn import core
@@ -28,12 +29,6 @@ import util
 import wrappers
 import svnexternals
 
-def reposetup(ui, repo):
-    if not util.is_svn_repo(repo):
-        return
-
-    repo.__class__ = tag_repo.generate_repo_class(ui, repo)
-
 def uisetup(ui):
     """Do our UI setup.
 
@@ -55,20 +50,6 @@ def uisetup(ui):
                                    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',
-                                   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',
-                                   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.'),
-                     ('', 'svn-authors', '', 'username mapping filename'),
-                     ('', 'svn-filemap', '',
-                      'remap file to exclude paths or include only certain paths'),
-                     ])
 
     try:
         rebase = extensions.find('rebase')
@@ -115,7 +96,12 @@ def svn(ui, repo, subcommand, *args, **o
         else:
             raise
 
+def reposetup(ui, repo):
+    if repo.local():
+       tag_repo.generate_repo_class(ui, repo)
 
+for scheme in ('svn', 'svn+ssh', 'svn+http', 'svn+file'):
+    hg.schemes[scheme] = tag_repo
 
 cmdtable = {
     "svn":
--- a/hg_delta_editor.py
+++ b/hg_delta_editor.py
@@ -89,12 +89,11 @@ class HgChangeReceiver(delta.Editor):
         self.ui = ui_
         if repo:
             self.repo = repo
+            self.__setup_repo(repo)
             self.path = os.path.normpath(os.path.join(self.repo.path, '..'))
         elif path:
             self.path = path
             self.__setup_repo(path)
-        else: #pragma: no cover
-            raise TypeError("Expected either path or repo argument")
 
         self.subdir = subdir
         if self.subdir and self.subdir[0] == '/':
@@ -144,19 +143,27 @@ class HgChangeReceiver(delta.Editor):
             date = self.lastdate
         return date
 
-    def __setup_repo(self, repo_path):
+    def __setup_repo(self, arg):
         """Verify the repo is going to work out for us.
 
         This method will fail an assertion if the repo exists but doesn't have
         the Subversion metadata.
         """
-        if os.path.isdir(repo_path) and len(os.listdir(repo_path)):
-            self.repo = hg.repository(self.ui, repo_path)
+        if isinstance(arg, basestring):
+            self.path = arg
+            self.repo = hg.repository(self.ui, self.path, create=True)
+        elif arg:
+            self.repo = arg
+            self.path = os.path.normpath(os.path.join(self.repo.path, '..'))
+        else: #pragma: no cover
+            raise TypeError("editor requires either a path or a repository "
+                            "specified")
+
+        if os.path.isdir(self.meta_data_dir) and os.listdir(self.meta_data_dir):
             assert os.path.isfile(self.revmap_file)
             assert os.path.isfile(self.svn_url_file)
             assert os.path.isfile(self.uuid_file)
         else:
-            self.repo = hg.repository(self.ui, repo_path, create=True)
             os.makedirs(os.path.dirname(self.uuid_file))
             f = open(self.revmap_file, 'w')
             f.write('%s\n' % util.REVMAP_FILE_VERSION)
--- a/tag_repo.py
+++ b/tag_repo.py
@@ -1,23 +1,100 @@
 from mercurial import node
+from mercurial import util as hgutil
+import mercurial.repo
 
 import hg_delta_editor
+import util
+import wrappers
 
+def generate_repo_class(ui, repo):
+    def localsvn(fn):
+        '''
+        Filter for instance methods which only apply to local Subversion
+        repositories.
+        '''
+        if util.is_svn_repo(repo):
+            return fn
+        else:
+            original = repo.__getattribute__(fn.__name__)
+            return original
 
-def tags_from_tag_info(repo):
-    hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo)
-    for tag, source in hg_editor.tags.iteritems():
-        source_ha = hg_editor.get_parent_revision(source[1]+1, source[0])
-        yield 'tag/%s'%tag, node.hex(source_ha)
-
+    def remotesvn(fn):
+        '''
+        Filter for instance methods which require the first argument
+        to be a remote Subversion repository instance.
+        '''
+        original = repo.__getattribute__(fn.__name__)
+        def wrapper(self, *args, **opts):
+            print args
+            if not isinstance(args[0], svnremoterepo):
+                return original(*args, **opts)
+            else:
+                return fn(self, *args, **opts)
+        wrapper.__name__ = fn.__name__ + '_wrapper'
+        wrapper.__doc__ = fn.__doc__
+        return wrapper
 
-def generate_repo_class(ui, repo):
+    class svnlocalrepo(repo.__class__):
+        @remotesvn
+        def pull(self, remote, heads=None, force=False):
+            try:
+                lock = self.wlock()
+                wrappers.pull(None, self.ui, self, source=remote.path,
+                              svn=True, rev=heads, force=force)
+            except KeyboardInterrupt:
+                pass
+            finally:
+                lock.release()
 
-    class svntagrepo(repo.__class__):
+        @localsvn
         def tags(self):
-            tags = dict((k, node.bin(v))
-                        for k,v in tags_from_tag_info(self))
-            hg_tags = super(svntagrepo, self).tags()
-            tags.update(hg_tags)
+            tags = super(svnlocalrepo, self).tags()
+            hg_editor = hg_delta_editor.HgChangeReceiver(repo=self)
+            for tag, source in hg_editor.tags.iteritems():
+                target = hg_editor.get_parent_revision(source[1]+1, source[0])
+                tags['tag/%s' % tag] = node.hex(target)
+            # TODO: should we even generate these tags?
+            if not hasattr(self, '_nofaketags'):
+                for (revnum, branch), node_hash in hg_editor.revmap.iteritems():
+                    tags['%s@r%d' % (branch or 'trunk', revnum)] = node_hash
             return tags
 
-    return svntagrepo
+        @localsvn
+        def tagslist(self):
+            try:
+                self._nofaketags = True
+                return super(svnlocalrepo, self).tagslist()
+            finally:
+                del self._nofaketags
+
+    repo.__class__ = svnlocalrepo
+
+class svnremoterepo(mercurial.repo.repository):
+    def __init__(self, ui, path):
+        self.ui = ui
+        self.path = path
+        self.capabilities = set(['lookup'])
+
+    def url(self):
+        return self.path
+
+    def lookup(self, key):
+        return key
+
+    def cancopy(self):
+        return False
+
+    def heads(self, *args, **opts):
+        """
+        Whenever this function is hit, we abort. The traceback is useful for
+        figuring out where to intercept the functionality.
+        """
+        raise hgutil.Abort('command unavailable for Subversion repositories')
+
+def instance(ui, url, create):
+    if create:
+        raise hgutil.Abort('cannot create new remote Subversion repository')
+
+    if url.startswith('svn+') and not url.startswith('svn+ssh:'):
+        url = url[4:]
+    return svnremoterepo(ui, util.normalize_url(url))
--- a/tests/test_fetch_branches.py
+++ b/tests/test_fetch_branches.py
@@ -5,7 +5,6 @@ from mercurial import node
 from mercurial import ui
 
 import test_util
-import wrappers
 
 
 class TestFetchBranches(test_util.TestBase):
@@ -18,7 +17,7 @@ class TestFetchBranches(test_util.TestBa
     def _load_fixture_and_fetch_with_anchor(self, fixture_name, anchor):
         test_util.load_svndump_fixture(self.repo_path, fixture_name)
         source = '%s#%s' % (test_util.fileurl(self.repo_path), anchor)
-        wrappers.clone(None, ui.ui(), source=source, dest=self.wc_path)
+        repo = hg.clone(ui.ui(), source=source, dest=self.wc_path)
         return hg.repository(ui.ui(), self.wc_path)
 
     def test_unrelatedbranch(self, stupid=False):
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -30,7 +30,7 @@ def fileurl(path):
     path = urllib.pathname2url(path)
     if drive:
         drive = '/' + drive
-    url = 'file://%s%s' % (drive, path)
+    url = 'svn+file://%s%s' % (drive, path)
     return url
 
 def load_svndump_fixture(path, fixture_name):
--- a/util.py
+++ b/util.py
@@ -24,7 +24,7 @@ def version(ui):
 
 
 def normalize_url(svnurl):
-    if svnurl.startswith('svn+http'):
+    if svnurl.startswith('svn+') and not svnurl.startswith('svn+ssh'):
         svnurl = svnurl[4:]
     url, revs, checkout = hg.parseurl(svnurl)
     url = url.rstrip('/')
--- a/wrappers.py
+++ b/wrappers.py
@@ -243,7 +243,7 @@ def pull(orig, ui, repo, source="default
     svn_stupid = opts.pop('svn_stupid', False)
     create_new_dest = opts.pop('create_new_dest', False)
     url = ((repo and repo.ui) or ui).expandpath(source)
-    if not (cmdutil.issvnurl(url) or svn or create_new_dest):
+    if orig and not (cmdutil.issvnurl(url) or svn or create_new_dest):
         return orig(ui, repo, source=source, *args, **opts)
     svn_url = url
     svn_url = util.normalize_url(svn_url)