changeset 763:6463b34bbcb6

svnexternals: support subrepos based externals checkout
author Patrick Mezard <pmezard@gmail.com>
date Thu, 25 Nov 2010 21:55:21 +0100
parents c31a1f92e1c6
children bc5c176b63eb
files hgsubversion/__init__.py hgsubversion/svnexternals.py tests/test_externals.py
diffstat 3 files changed, 84 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
--- 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)