# HG changeset patch # User Patrick Mezard # Date 1290718521 -3600 # Node ID 6463b34bbcb68ff67fcc4498a930affdb06118cb # Parent c31a1f92e1c654dc66c254c50d6c1e85bd253800 svnexternals: support subrepos based externals checkout diff --git a/hgsubversion/__init__.py b/hgsubversion/__init__.py --- a/hgsubversion/__init__.py +++ b/hgsubversion/__init__.py @@ -51,8 +51,8 @@ try: from mercurial import subrepo # require svnsubrepo and hg >= 1.7.1 subrepo.svnsubrepo - hgutil.checklink -except ImportError: + hgutil.checknlink +except (ImportError, AttributeError), e: subrepo = None import svncommands diff --git a/hgsubversion/svnexternals.py b/hgsubversion/svnexternals.py --- a/hgsubversion/svnexternals.py +++ b/hgsubversion/svnexternals.py @@ -8,8 +8,8 @@ try: from mercurial import subrepo # require svnsubrepo and hg >= 1.7.1 subrepo.svnsubrepo - hgutil.checklink -except ImportError: + hgutil.checknlink +except (ImportError, AttributeError), e: subrepo = None import util @@ -88,8 +88,8 @@ def diff(ext1, ext2): class BadDefinition(Exception): pass -re_defold = re.compile(r'^\s*(.*?)\s+(?:-r\s*(\d+)\s+)?([a-zA-Z]+://.*)\s*$') -re_defnew = re.compile(r'^\s*(?:-r\s*(\d+)\s+)?((?:[a-zA-Z]+://|\^/).*)\s+(\S+)\s*$') +re_defold = re.compile(r'^\s*(.*?)\s+(?:-r\s*(\d+|\{REV\})\s+)?([a-zA-Z]+://.*)\s*$') +re_defnew = re.compile(r'^\s*(?:-r\s*(\d+|\{REV\})\s+)?((?:[a-zA-Z]+://|\^/).*)\s+(\S+)\s*$') re_scheme = re.compile(r'^[a-zA-Z]+://') def parsedefinition(line): @@ -118,6 +118,19 @@ def parsedefinition(line): norevline = line return (path, rev, source, pegrev, norevline) +class RelativeSourceError(Exception): + pass + +def resolvesource(ui, svnroot, source): + if re_scheme.search(source): + return source + if source.startswith('^/'): + if svnroot is None: + raise RelativeSourceError() + return svnroot + source[1:] + ui.warn(_('ignoring unsupported non-fully qualified external: %r' % source)) + return None + def parsedefinitions(ui, repo, svnroot, exts): """Return (targetdir, revision, source) tuples. Fail if nested targetdirs are detected. source is an svn project URL. @@ -130,12 +143,8 @@ def parsedefinitions(ui, repo, svnroot, except BadDefinition: ui.warn(_('ignoring invalid external definition: %r' % line)) continue - if re_scheme.search(source): - pass - elif source.startswith('^/'): - source = svnroot + source[1:] - else: - ui.warn(_('ignoring unsupported non-fully qualified external: %r' % source)) + source = resolvesource(ui, svnroot, source) + if source is None: continue wpath = hgutil.pconvert(os.path.join(base, path)) wpath = hgutil.canonpath(repo.root, '', wpath) @@ -373,4 +382,30 @@ def parse(ui, ctx): if subrepo: class svnsubrepo(subrepo.svnsubrepo): def __init__(self, ctx, path, state): + state = (state[0].split(':', 1)[1], state[1]) super(svnsubrepo, self).__init__(ctx, path, state) + + def get(self, state): + # Resolve source first + line = state[0].split(':', 1)[1] + source, pegrev = parsedefinition(line)[2:4] + try: + # Getting the root SVN repository URL is expensive. + # Assume the externals is absolute. + source = resolvesource(self._ui, None, source) + except RelativeSourceError: + svnurl = self._ctx._repo.ui.expandpath('default') + svnroot = getsvninfo(util.normalize_url(svnurl))[1] + source = resolvesource(self._ui, svnroot, source) + if pegrev is not None: + source = source + '@' + pegrev + return super(svnsubrepo, self).get((source, state[1])) + + def dirty(self): + # You cannot compare anything with HEAD. Just accept it + # can be anything. + wcrev = self._wcrev() + if (wcrev == 'HEAD' or self._state[1] == 'HEAD' or + wcrev == self._state[1]) and not self._wcchanged()[0]: + return False + return True diff --git a/tests/test_externals.py b/tests/test_externals.py --- a/tests/test_externals.py +++ b/tests/test_externals.py @@ -1,8 +1,17 @@ import test_util -import os, unittest +import os, unittest, sys from mercurial import commands +from mercurial import util as hgutil +try: + from mercurial import subrepo + # require svnsubrepo and hg >= 1.7.1 + subrepo.svnsubrepo + hgutil.checknlink +except (ImportError, AttributeError), e: + print >>sys.stderr, 'test_externals: skipping .hgsub tests' + subrepo = None from hgsubversion import svnexternals @@ -205,6 +214,33 @@ 2 deps/project2 def test_hgsub_stupid(self): self.test_hgsub(True) + def test_updatehgsub(self): + def checkdeps(ui, repo, rev, deps, nodeps): + commands.update(ui, repo, node=str(rev)) + for d in deps: + p = os.path.join(repo.root, d) + self.assertTrue(os.path.isdir(p), + 'missing: %s@%r' % (d, repo[None].rev())) + for d in nodeps: + p = os.path.join(repo.root, d) + self.assertTrue(not os.path.isdir(p), + 'unexpected: %s@%r' % (d, repo[None].rev())) + + if subrepo is None: + return + + ui = self.ui() + repo = self._load_fixture_and_fetch('externals.svndump', + stupid=0, externals='subrepos') + checkdeps(ui, repo, 0, ['deps/project1'], []) + checkdeps(ui, repo, 1, ['deps/project1', 'deps/project2'], []) + checkdeps(ui, repo, 2, ['subdir/deps/project1', 'subdir2/deps/project1', + 'deps/project2'], + ['deps/project1']) + checkdeps(ui, repo, 3, ['subdir/deps/project1', 'deps/project2'], + ['subdir2/deps/project1']) + checkdeps(ui, repo, 4, ['subdir/deps/project1'], ['deps/project2']) + class TestPushExternals(test_util.TestBase): def setUp(self): test_util.TestBase.setUp(self)