diff options
Diffstat (limited to 'hgsubversion/svnexternals.py')
-rw-r--r-- | hgsubversion/svnexternals.py | 111 |
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() |