summaryrefslogtreecommitdiff
path: root/hgsubversion/svnexternals.py
diff options
context:
space:
mode:
Diffstat (limited to 'hgsubversion/svnexternals.py')
-rw-r--r--hgsubversion/svnexternals.py111
1 files changed, 95 insertions, 16 deletions
diff --git a/hgsubversion/svnexternals.py b/hgsubversion/svnexternals.py
index f738aa2..fc32e12 100644
--- a/hgsubversion/svnexternals.py
+++ b/hgsubversion/svnexternals.py
@@ -88,7 +88,7 @@ class BadDefinition(Exception):
pass
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_defnew = re.compile(r'^\s*(?:-r\s*(\d+|\{REV\})\s+)?((?:[a-zA-Z+]+://|\^/)\S*)\s+(\S+)\s*$')
re_scheme = re.compile(r'^[a-zA-Z+]+://')
def parsedefinition(line):
@@ -120,13 +120,84 @@ def parsedefinition(line):
class RelativeSourceError(Exception):
pass
+def resolvedots(url):
+ """
+ Fix references that include .. entries.
+ Scans a URL for .. type entries and resolves them but will not allow any
+ number of ..s to take us out of domain so http://.. will raise an exception.
+
+ Tests, (Don't know how to construct a round trip for this so doctest):
+ >>> # Relative URL within servers svn area
+ >>> resolvedots(
+ ... "http://some.svn.server/svn/some_repo/../other_repo")
+ 'http://some.svn.server/svn/other_repo'
+ >>> # Complex One
+ >>> resolvedots(
+ ... "http://some.svn.server/svn/repo/../other/repo/../../other_repo")
+ 'http://some.svn.server/svn/other_repo'
+ >>> # Another Complex One
+ >>> resolvedots(
+ ... "http://some.svn.server/svn/repo/dir/subdir/../../../other_repo/dir")
+ 'http://some.svn.server/svn/other_repo/dir'
+ >>> # Last Complex One - SVN Allows this & seen it used even if it is BAD!
+ >>> resolvedots(
+ ... "http://svn.server/svn/my_repo/dir/subdir/../../other_dir")
+ 'http://svn.server/svn/my_repo/other_dir'
+ >>> # Outside the SVN Area might be OK
+ >>> resolvedots(
+ ... "http://svn.server/svn/some_repo/../../other_svn_repo")
+ 'http://svn.server/other_svn_repo'
+ >>> # Complex One
+ >>> resolvedots(
+ ... "http://some.svn.server/svn/repo/../other/repo/../../other_repo")
+ 'http://some.svn.server/svn/other_repo'
+ >>> # On another server is not a relative URL should give an exception
+ >>> resolvedots(
+ ... "http://some.svn.server/svn/some_repo/../../../other_server")
+ Traceback (most recent call last):
+ ...
+ RelativeSourceError: Relative URL cannot be to another server
+ """
+ orig = url.split('/')
+ fixed = []
+ for item in orig:
+ if item != '..':
+ fixed.append(item)
+ elif len(fixed) > 3: # Don't allow things to go out of domain
+ fixed.pop()
+ else:
+ raise RelativeSourceError(
+ 'Relative URL cannot be to another server')
+ return '/'.join(fixed)
+
+
+
def resolvesource(ui, svnroot, source):
+ """ Resolve the source as either matching the scheme re or by resolving
+ relative URLs which start with ^ and my include relative .. references.
+
+ >>> root = 'http://some.svn.server/svn/some_repo'
+ >>> resolvesource(None, root, 'http://other.svn.server')
+ 'http://other.svn.server'
+ >>> resolvesource(None, root, 'ssh://other.svn.server')
+ 'ssh://other.svn.server'
+ >>> resolvesource(None, root, '^/other_repo')
+ 'http://some.svn.server/svn/some_repo/other_repo'
+ >>> resolvesource(None, root, '^/sub_repo')
+ 'http://some.svn.server/svn/some_repo/sub_repo'
+ >>> resolvesource(None, root, '^/../other_repo')
+ 'http://some.svn.server/svn/other_repo'
+ >>> resolvesource(None, root, '^/../../../server/other_repo')
+ Traceback (most recent call last):
+ ...
+ RelativeSourceError: Relative URL cannot be to another server
+ """
if re_scheme.search(source):
return source
if source.startswith('^/'):
if svnroot is None:
raise RelativeSourceError()
- return svnroot + source[1:]
+ return resolvedots(svnroot + source[1:])
ui.warn(_('ignoring unsupported non-fully qualified external: %r\n'
% source))
return None
@@ -218,7 +289,7 @@ class externalsupdater:
self.ui = ui
def update(self, wpath, rev, source, pegrev):
- path = self.repo.wjoin(wpath)
+ path = self.repo.wvfs.join(wpath)
revspec = []
if rev:
revspec = ['-r', rev]
@@ -232,7 +303,7 @@ class externalsupdater:
if source == exturl:
if extrev != rev:
self.ui.status(_('updating external on %s@%s\n') %
- (wpath, rev or 'HEAD'))
+ (wpath, rev or pegrev or 'HEAD'))
cwd = os.path.join(self.repo.root, path)
self.svn(['update'] + revspec, cwd)
return
@@ -245,11 +316,12 @@ class externalsupdater:
pegrev = rev
if pegrev:
source = '%s@%s' % (source, pegrev)
- self.ui.status(_('fetching external %s@%s\n') % (wpath, rev or 'HEAD'))
+ self.ui.status(_('fetching external %s@%s\n') %
+ (wpath, rev or pegrev or 'HEAD'))
self.svn(['co'] + revspec + [source, dest], cwd)
def delete(self, wpath):
- path = self.repo.wjoin(wpath)
+ path = self.repo.wvfs.join(wpath)
if os.path.isdir(path):
self.ui.status(_('removing external %s\n') % wpath)
@@ -268,12 +340,12 @@ class externalsupdater:
def svn(self, args, cwd):
args = ['svn'] + args
- self.ui.debug(_('updating externals: %r, cwd=%s\n') % (args, cwd))
+ self.ui.note(_('updating externals: %r, cwd=%s\n') % (args, cwd))
shell = os.name == 'nt'
p = subprocess.Popen(args, cwd=cwd, shell=shell,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in p.stdout:
- self.ui.note(line)
+ self.ui.debug(line)
p.wait()
if p.returncode != 0:
raise hgutil.Abort("subprocess '%s' failed" % ' '.join(args))
@@ -296,7 +368,7 @@ def updateexternals(ui, args, repo, **opts):
# Retrieve current externals status
try:
- oldext = file(repo.join('svn/externals'), 'rb').read()
+ oldext = file(repo.vfs.join('svn/externals'), 'rb').read()
except IOError:
oldext = ''
newext = ''
@@ -314,7 +386,7 @@ def updateexternals(ui, args, repo, **opts):
else:
raise hgutil.Abort(_('unknown update actions: %r') % action)
- file(repo.join('svn/externals'), 'wb').write(newext)
+ file(repo.vfs.join('svn/externals'), 'wb').write(newext)
def getchanges(ui, repo, parentctx, exts):
"""Take a parent changectx and the new externals definitions as an
@@ -421,18 +493,21 @@ class svnsubrepo(subrepo.svnsubrepo):
state = (source, state[1])
return super(svnsubrepo, self).get(state, *args, **kwargs)
- def dirty(self, ignoreupdate=False):
+ def dirty(self, ignoreupdate=False, missing=False):
# You cannot compare anything with HEAD. Just accept it
# can be anything.
- if hasattr(self, '_wcrevs'):
+ if hgutil.safehasattr(self, '_wcrevs'):
wcrevs = self._wcrevs()
else:
wcrev = self._wcrev()
wcrevs = (wcrev, wcrev)
- if (('HEAD' in wcrevs or self._state[1] == 'HEAD' or
- self._state[1] in wcrevs or ignoreupdate)
- and not self._wcchanged()[0]):
- return False
+ shouldcheck = ('HEAD' in wcrevs or self._state[1] == 'HEAD' or
+ self._state[1] in wcrevs or ignoreupdate or missing)
+ if shouldcheck:
+ changes, extchanges, wcmissing = self._wcchanged()
+ changed = changes or (missing and wcmissing)
+ if not changed:
+ return False
return True
def commit(self, text, user, date):
@@ -447,3 +522,7 @@ class svnsubrepo(subrepo.svnsubrepo):
if self._state[1] == 'HEAD':
return 'HEAD'
return super(svnsubrepo, self).basestate()
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()