# HG changeset patch # User Dan Villiom Podlaski Christiansen # Date 1241722253 -7200 # Node ID 33736e2e25f0670282a52bc406f8dafaec5e9be7 # Parent 1ba8ed29148ef89a89cfcd4164eef3278db8e894 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. diff --git a/__init__.py b/__init__.py --- 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": diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- 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) diff --git a/tag_repo.py b/tag_repo.py --- 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)) diff --git a/tests/test_fetch_branches.py b/tests/test_fetch_branches.py --- 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): diff --git a/tests/test_util.py b/tests/test_util.py --- 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): diff --git a/util.py b/util.py --- 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('/') diff --git a/wrappers.py b/wrappers.py --- 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)