Return-Path: X-Original-To: apmail-incubator-bloodhound-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-bloodhound-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 4C69DDEB3 for ; Tue, 16 Oct 2012 15:57:20 +0000 (UTC) Received: (qmail 78446 invoked by uid 500); 16 Oct 2012 15:57:20 -0000 Delivered-To: apmail-incubator-bloodhound-commits-archive@incubator.apache.org Received: (qmail 78421 invoked by uid 500); 16 Oct 2012 15:57:20 -0000 Mailing-List: contact bloodhound-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: bloodhound-dev@incubator.apache.org Delivered-To: mailing list bloodhound-commits@incubator.apache.org Received: (qmail 78413 invoked by uid 99); 16 Oct 2012 15:57:20 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 16 Oct 2012 15:57:20 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 16 Oct 2012 15:57:16 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 559DD2388C8B; Tue, 16 Oct 2012 15:55:27 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1398858 [28/33] - in /incubator/bloodhound/vendor/trac/current: ./ contrib/ doc/ doc/api/ doc/utils/ sample-plugins/ sample-plugins/permissions/ sample-plugins/workflow/ trac/ trac/admin/ trac/admin/templates/ trac/admin/tests/ trac/db/ tr... Date: Tue, 16 Oct 2012 15:55:11 -0000 To: bloodhound-commits@incubator.apache.org From: gjm@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20121016155527.559DD2388C8B@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Modified: incubator/bloodhound/vendor/trac/current/trac/util/tests/text.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/util/tests/text.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/util/tests/text.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/util/tests/text.py Tue Oct 16 15:55:00 2012 @@ -4,11 +4,12 @@ import unittest from StringIO import StringIO from trac.util.text import empty, expandtabs, fix_eol, javascript_quote, \ - normalize_whitespace, to_unicode, \ + to_js_string, normalize_whitespace, to_unicode, \ text_width, print_table, unicode_quote, \ unicode_quote_plus, unicode_unquote, \ unicode_urlencode, wrap, quote_query_string, \ - unicode_to_base64, unicode_from_base64 + unicode_to_base64, unicode_from_base64, stripws, \ + levenshtein_distance class ToUnicodeTestCase(unittest.TestCase): @@ -65,6 +66,19 @@ class JavascriptQuoteTestCase(unittest.T self.assertEqual(r'\u0026\u003c\u003e', javascript_quote('&<>')) + +class ToJsStringTestCase(unittest.TestCase): + def test_(self): + self.assertEqual(r'"Quote \" in text"', + to_js_string('Quote " in text')) + self.assertEqual(r'''"\\\"\b\f\n\r\t'"''', + to_js_string('\\"\b\f\n\r\t\'')) + self.assertEqual(r'"\u0002\u001e"', + to_js_string('\x02\x1e')) + self.assertEqual(r'"\u0026\u003c\u003e"', + to_js_string('&<>')) + + class UnicodeQuoteTestCase(unittest.TestCase): def test_unicode_quote(self): self.assertEqual(u'the%20%C3%9C%20thing', @@ -289,12 +303,38 @@ class UnicodeBase64TestCase(unittest.Tes self.assertEqual(text, unicode_from_base64(text_base64_no_strip)) +class StripwsTestCase(unittest.TestCase): + def test_stripws(self): + self.assertEquals(u'stripws', + stripws(u' \u200b\t\u3000stripws \u200b\t\u2008')) + self.assertEquals(u'stripws \u3000\t', + stripws(u'\u200b\t\u2008 stripws \u3000\t', + trailing=False)) + self.assertEquals(u' \t\u3000stripws', + stripws(u' \t\u3000stripws \u200b\t\u2008', + leading=False)) + self.assertEquals(u' \t\u3000stripws \u200b\t\u2008', + stripws(u' \t\u3000stripws \u200b\t\u2008', + leading=False, trailing=False)) + + + +class LevenshteinDistanceTestCase(unittest.TestCase): + def test_distance(self): + self.assertEqual(5, levenshtein_distance('kitten', 'sitting')) + self.assertEqual(1, levenshtein_distance('wii', 'wiki')) + self.assertEqual(2, levenshtein_distance('comfig', 'config')) + self.assertEqual(5, levenshtein_distance('update', 'upgrade')) + self.assertEqual(0, levenshtein_distance('milestone', 'milestone')) + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ToUnicodeTestCase, 'test')) suite.addTest(unittest.makeSuite(ExpandtabsTestCase, 'test')) suite.addTest(unittest.makeSuite(UnicodeQuoteTestCase, 'test')) suite.addTest(unittest.makeSuite(JavascriptQuoteTestCase, 'test')) + suite.addTest(unittest.makeSuite(ToJsStringTestCase, 'test')) suite.addTest(unittest.makeSuite(QuoteQueryStringTestCase, 'test')) suite.addTest(unittest.makeSuite(WhitespaceTestCase, 'test')) suite.addTest(unittest.makeSuite(TextWidthTestCase, 'test')) @@ -302,6 +342,8 @@ def suite(): suite.addTest(unittest.makeSuite(WrapTestCase, 'test')) suite.addTest(unittest.makeSuite(FixEolTestCase, 'test')) suite.addTest(unittest.makeSuite(UnicodeBase64TestCase, 'test')) + suite.addTest(unittest.makeSuite(StripwsTestCase, 'test')) + suite.addTest(unittest.makeSuite(LevenshteinDistanceTestCase, 'test')) return suite if __name__ == '__main__': Modified: incubator/bloodhound/vendor/trac/current/trac/util/text.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/util/text.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/util/text.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/util/text.py Tue Oct 16 15:55:00 2012 @@ -3,7 +3,7 @@ # Copyright (C) 2003-2009 Edgewall Software # Copyright (C) 2003-2004 Jonas Borgström # Copyright (C) 2006 Matthew Good -# Copyright (C) 2005-2006 Christian Boos +# Copyright (C) 2005-2006 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -16,7 +16,7 @@ # # Author: Jonas Borgström # Matthew Good -# Christian Boos +# Christian Boos import __builtin__ import locale @@ -95,15 +95,36 @@ def path_to_unicode(path): return unicode(path) +_ws_leading_re = re.compile(ur'\A[\s\u200b]+', re.UNICODE) +_ws_trailing_re = re.compile(ur'[\s\u200b]+\Z', re.UNICODE) + +def stripws(text, leading=True, trailing=True): + """Strips unicode white-spaces and ZWSPs from ``text``. + + :param leading: strips leading spaces from ``text`` unless ``leading`` is + `False`. + :param trailing: strips trailing spaces from ``text`` unless ``trailing`` + is `False`. + """ + if leading: + text = _ws_leading_re.sub('', text) + if trailing: + text = _ws_trailing_re.sub('', text) + return text + + _js_quote = {'\\': '\\\\', '"': '\\"', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', "'": "\\'"} for i in range(0x20) + [ord(c) for c in '&<>']: _js_quote.setdefault(chr(i), '\\u%04x' % i) _js_quote_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t\'&<>]') +_js_string_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t&<>]') def javascript_quote(text): - """Quote strings for inclusion in javascript""" + """Quote strings for inclusion in single or double quote delimited + Javascript strings + """ if not text: return '' def replace(match): @@ -111,6 +132,17 @@ def javascript_quote(text): return _js_quote_re.sub(replace, text) +def to_js_string(text): + """Embed the given string in a double quote delimited Javascript string + (conform to the JSON spec) + """ + if not text: + return '' + def replace(match): + return _js_quote[match.group(0)] + return '"%s"' % _js_string_re.sub(replace, text) + + def unicode_quote(value, safe='/'): """A unicode aware version of `urllib.quote` @@ -541,7 +573,7 @@ def normalize_whitespace(text, to_space= def unquote_label(txt): """Remove (one level of) enclosing single or double quotes. - .. versionadded :: 0.13 + .. versionadded :: 1.0 """ return txt[1:-1] if txt and txt[0] in "'\"" and txt[0] == txt[-1] else txt @@ -626,3 +658,22 @@ def unicode_to_base64(text, strip_newlin def unicode_from_base64(text): """Safe conversion of ``text`` to unicode based on utf-8 bytes.""" return text.decode('base64').decode('utf-8') + + +def levenshtein_distance(lhs, rhs): + """Return the Levenshtein distance between two strings.""" + if len(lhs) > len(rhs): + rhs, lhs = lhs, rhs + if not lhs: + return len(rhs) + + prev = range(len(rhs) + 1) + for lidx, lch in enumerate(lhs): + curr = [lidx + 1] + for ridx, rch in enumerate(rhs): + cost = (lch != rch) * 2 + curr.append(min(prev[ridx + 1] + 1, # deletion + curr[ridx] + 1, # insertion + prev[ridx] + cost)) # substitution + prev = curr + return prev[-1] Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/api.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/api.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/api.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/api.py Tue Oct 16 15:55:00 2012 @@ -663,7 +663,11 @@ class RepositoryManager(Component): try: changeset = repos.get_changeset(rev) except NoSuchChangeset: - continue + try: + repos.sync_changeset(rev) + changeset = repos.get_changeset(rev) + except NoSuchChangeset: + continue self.log.debug("Event %s on %s for revision %s", event, repos.reponame or '(default)', rev) for listener in self.change_listeners: @@ -1139,7 +1143,7 @@ class Changeset(object): def get_tags(self): """Yield tags associated with this changeset. - .. versionadded :: 0.13 + .. versionadded :: 1.0 """ return [] Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/cache.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/cache.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/cache.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/cache.py Tue Oct 16 15:55:00 2012 @@ -30,6 +30,12 @@ _actionmap = {'A': Changeset.ADD, 'C': C 'D': Changeset.DELETE, 'E': Changeset.EDIT, 'M': Changeset.MOVE} +def _invert_dict(d): + return dict(zip(d.values(), d.keys())) + +_inverted_kindmap = _invert_dict(_kindmap) +_inverted_actionmap = _invert_dict(_actionmap) + CACHE_REPOSITORY_DIR = 'repository_dir' CACHE_YOUNGEST_REV = 'youngest_rev' @@ -89,10 +95,13 @@ class CachedRepository(Repository): """, (self.id, srev)): old_cset = Changeset(self.repos, cset.rev, message, author, from_utimestamp(time)) - db("""UPDATE revision SET time=%s, author=%s, message=%s - WHERE repos=%s AND rev=%s - """, (to_utimestamp(cset.date), cset.author, cset.message, - self.id, srev)) + if old_cset: + db("""UPDATE revision SET time=%s, author=%s, message=%s + WHERE repos=%s AND rev=%s + """, (to_utimestamp(cset.date), cset.author, + cset.message, self.id, srev)) + else: + self._insert_changeset(db, rev, cset) return old_cset @cached('_metadata_id') @@ -215,69 +224,69 @@ class CachedRepository(Repository): self.repos.clear(youngest_rev=youngest) return - # 1. prepare for resyncing - # (there still might be a race condition at this point) - - kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) - actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) - + # prepare for resyncing (there might still be a race + # condition at this point) while next_youngest is not None: srev = self.db_rev(next_youngest) - + with self.env.db_transaction as db: - - # 1.1 Attempt to resync the 'revision' table self.log.info("Trying to sync revision [%s]", next_youngest) cset = self.repos.get_changeset(next_youngest) try: - db("""INSERT INTO revision - (repos, rev, time, author, message) - VALUES (%s, %s, %s, %s, %s) - """, (self.id, srev, to_utimestamp(cset.date), - cset.author, cset.message)) - except Exception, e: # *another* 1.1. resync attempt won + # steps 1. and 2. + self._insert_changeset(db, next_youngest, cset) + except Exception, e: # *another* 1.1. resync attempt won self.log.warning('Revision %s already cached: %r', next_youngest, e) - # also potentially in progress, so keep ''previous'' - # notion of 'youngest' + # the other resync attempts is also + # potentially still in progress, so for our + # process/thread, keep ''previous'' notion of + # 'youngest' self.repos.clear(youngest_rev=youngest) # FIXME: This aborts a containing transaction db.rollback() return - - # 1.2. now *only* one process was able to get there - # (i.e. there *shouldn't* be any race condition here) - - for path, kind, action, bpath, brev in cset.get_changes(): - self.log.debug("Caching node change in [%s]: %r", - next_youngest, - (path, kind, action, bpath, brev)) - kind = kindmap[kind] - action = actionmap[action] - db("""INSERT INTO node_change - (repos, rev, path, node_type, change_type, - base_path, base_rev) - VALUES (%s, %s, %s, %s, %s, %s, %s) - """, (self.id, srev, path, kind, action, bpath, - brev)) - - # 1.3. update 'youngest_rev' metadata - # (minimize possibility of failures at point 0.) - db("""UPDATE repository SET value=%s - WHERE id=%s AND name=%s - """, (str(next_youngest), - self.id, CACHE_YOUNGEST_REV)) + + # 3. update 'youngest_rev' metadata (minimize + # possibility of failures at point 0.) + db(""" + UPDATE repository SET value=%s WHERE id=%s AND name=%s + """, (str(next_youngest), self.id, CACHE_YOUNGEST_REV)) del self.metadata - # 1.4. iterate (1.1 should always succeed now) + # 4. iterate (1. should always succeed now) youngest = next_youngest next_youngest = self.repos.next_rev(next_youngest) - # 1.5. provide some feedback + # 5. provide some feedback if feedback: feedback(youngest) + def _insert_changeset(self, db, rev, cset): + srev = self.db_rev(rev) + # 1. Attempt to resync the 'revision' table. In case of + # concurrent syncs, only such insert into the `revision` table + # will succeed, the others will fail and raise an exception. + db(""" + INSERT INTO revision (repos,rev,time,author,message) + VALUES (%s,%s,%s,%s,%s) + """, (self.id, srev, to_utimestamp(cset.date), + cset.author, cset.message)) + # 2. now *only* one process was able to get there (i.e. there + # *shouldn't* be any race condition here) + for path, kind, action, bpath, brev in cset.get_changes(): + self.log.debug("Caching node change in [%s]: %r", rev, + (path, kind, action, bpath, brev)) + kind = _inverted_kindmap[kind] + action = _inverted_actionmap[action] + db(""" + INSERT INTO node_change + (repos,rev,path,node_type,change_type,base_path, + base_rev) + VALUES (%s,%s,%s,%s,%s,%s,%s) + """, (self.id, srev, path, kind, action, bpath, brev)) + def get_node(self, path, rev=None): return self.repos.get_node(path, self.normalize_rev(rev)) Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/diff.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/diff.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/diff.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/diff.py Tue Oct 16 15:55:00 2012 @@ -177,7 +177,7 @@ def filter_ignorable_lines(hunks, fromli def hdf_diff(*args, **kwargs): - """:deprecated: use `diff_blocks` (will be removed in 0.14)""" + """:deprecated: use `diff_blocks` (will be removed in 1.1.1)""" return diff_blocks(*args, **kwargs) def diff_blocks(fromlines, tolines, context=None, tabwidth=8, @@ -309,7 +309,7 @@ def get_diff_options(req): pref = int(req.session.get('diff_' + name, default)) arg = int(name in req.args) if 'update' in req.args and arg != pref: - req.session['diff_' + name] = arg + req.session.set('diff_' + name, arg, default) else: arg = pref return arg @@ -317,7 +317,7 @@ def get_diff_options(req): pref = req.session.get('diff_style', 'inline') style = req.args.get('style', pref) if 'update' in req.args and style != pref: - req.session['diff_style'] = style + req.session.set('diff_style', style, 'inline') data['style'] = style pref = int(req.session.get('diff_contextlines', 2)) @@ -326,7 +326,7 @@ def get_diff_options(req): except ValueError: context = -1 if 'update' in req.args and context != pref: - req.session['diff_contextlines'] = context + req.session.set('diff_contextlines', context, 2) options_data['contextlines'] = context arg = int(req.args.get('contextall', 0)) Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_fs.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_fs.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_fs.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_fs.py Tue Oct 16 15:55:00 2012 @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2005-2011 Edgewall Software -# Copyright (C) 2005 Christopher Lenz -# Copyright (C) 2005-2007 Christian Boos +# Copyright (C) 2012 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -12,1076 +10,10 @@ # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. -# -# Author: Christopher Lenz -# Christian Boos - -""" - -Note about Unicode ------------------- - -The Subversion bindings are not unicode-aware and they expect to -receive UTF-8 encoded `string` parameters, - -On the other hand, all paths manipulated by Trac are `unicode` -objects. - -Therefore: - - * before being handed out to SVN, the Trac paths have to be encoded - to UTF-8, using `_to_svn()` - - * before being handed out to Trac, a SVN path has to be decoded from - UTF-8, using `_from_svn()` - -Whenever a value has to be stored as utf8, we explicitly mark the -variable name with "_utf8", in order to avoid any possible confusion. - -Warning: - `SubversionNode.get_content()` returns an object from which one can - read a stream of bytes. NO guarantees can be given about what that - stream of bytes represents. It might be some text, encoded in some - way or another. SVN properties *might* give some hints about the - content, but they actually only reflect the beliefs of whomever set - those properties... -""" - -import os.path -import weakref -import posixpath - -from trac.config import ListOption -from trac.core import * -from trac.env import ISystemInfoProvider -from trac.versioncontrol import Changeset, Node, Repository, \ - IRepositoryConnector, \ - NoSuchChangeset, NoSuchNode -from trac.versioncontrol.cache import CachedRepository -from trac.util import embedded_numbers -from trac.util.text import exception_to_unicode, to_unicode -from trac.util.translation import _ -from trac.util.datefmt import from_utimestamp - - -application_pool = None - - -def _import_svn(): - global fs, repos, core, delta, _kindmap - from svn import fs, repos, core, delta - _kindmap = {core.svn_node_dir: Node.DIRECTORY, - core.svn_node_file: Node.FILE} - # Protect svn.core methods from GC - Pool.apr_pool_clear = staticmethod(core.apr_pool_clear) - Pool.apr_pool_destroy = staticmethod(core.apr_pool_destroy) - -def _to_svn(pool, *args): - """Expect a pool and a list of `unicode` path components. - - Returns an UTF-8 encoded string suitable for the Subversion python - bindings (the returned path never starts with a leading "/") - """ - return core.svn_path_canonicalize('/'.join(args).lstrip('/') - .encode('utf-8'), - pool) - -def _from_svn(path): - """Expect an UTF-8 encoded string and transform it to an `unicode` object - - But Subversion repositories built from conversion utilities can have - non-UTF-8 byte strings, so we have to convert using `to_unicode`. - """ - return path and to_unicode(path, 'utf-8') - -# The following 3 helpers deal with unicode paths - -def _normalize_path(path): - """Remove leading "/", except for the root.""" - return path and path.strip('/') or '/' - -def _path_within_scope(scope, fullpath): - """Remove the leading scope from repository paths. - - Return `None` if the path is not is scope. - """ - if fullpath is not None: - fullpath = fullpath.lstrip('/') - if scope == '/': - return _normalize_path(fullpath) - scope = scope.strip('/') - if (fullpath + '/').startswith(scope + '/'): - return fullpath[len(scope) + 1:] or '/' - -def _is_path_within_scope(scope, fullpath): - """Check whether the given `fullpath` is within the given `scope`""" - if scope == '/': - return fullpath is not None - fullpath = fullpath.lstrip('/') if fullpath else '' - scope = scope.strip('/') - return (fullpath + '/').startswith(scope + '/') - -# svn_opt_revision_t helpers - -def _svn_rev(num): - value = core.svn_opt_revision_value_t() - value.number = num - revision = core.svn_opt_revision_t() - revision.kind = core.svn_opt_revision_number - revision.value = value - return revision - -def _svn_head(): - revision = core.svn_opt_revision_t() - revision.kind = core.svn_opt_revision_head - return revision - -# apr_pool_t helpers - -def _mark_weakpool_invalid(weakpool): - if weakpool(): - weakpool()._mark_invalid() - - -class Pool(object): - """A Pythonic memory pool object""" - - def __init__(self, parent_pool=None): - """Create a new memory pool""" - - global application_pool - self._parent_pool = parent_pool or application_pool - - # Create pool - if self._parent_pool: - self._pool = core.svn_pool_create(self._parent_pool()) - else: - # If we are an application-level pool, - # then initialize APR and set this pool - # to be the application-level pool - core.apr_initialize() - application_pool = self - - self._pool = core.svn_pool_create(None) - self._mark_valid() - - def __call__(self): - return self._pool - - def valid(self): - """Check whether this memory pool and its parents - are still valid""" - return hasattr(self,"_is_valid") - - def assert_valid(self): - """Assert that this memory_pool is still valid.""" - assert self.valid() - - def clear(self): - """Clear embedded memory pool. Invalidate all subpools.""" - self.apr_pool_clear(self._pool) - self._mark_valid() - - def destroy(self): - """Destroy embedded memory pool. If you do not destroy - the memory pool manually, Python will destroy it - automatically.""" - - global application_pool - - self.assert_valid() - - # Destroy pool - self.apr_pool_destroy(self._pool) - - # Clear application pool and terminate APR if necessary - if not self._parent_pool: - application_pool = None - - self._mark_invalid() - - def __del__(self): - """Automatically destroy memory pools, if necessary""" - if self.valid(): - self.destroy() - - def _mark_valid(self): - """Mark pool as valid""" - if self._parent_pool: - # Refer to self using a weakreference so that we don't - # create a reference cycle - weakself = weakref.ref(self) - - # Set up callbacks to mark pool as invalid when parents - # are destroyed - self._weakref = weakref.ref(self._parent_pool._is_valid, - lambda x: \ - _mark_weakpool_invalid(weakself)) - - # mark pool as valid - self._is_valid = lambda: 1 - - def _mark_invalid(self): - """Mark pool as invalid""" - if self.valid(): - # Mark invalid - del self._is_valid - - # Free up memory - del self._parent_pool - if hasattr(self, "_weakref"): - del self._weakref - - -class SvnCachedRepository(CachedRepository): - """Subversion-specific cached repository, zero-pads revision numbers - in the cache tables. - """ - has_linear_changesets = True - - def db_rev(self, rev): - return '%010d' % rev - - def rev_db(self, rev): - return int(rev or 0) - - -class SubversionConnector(Component): - - implements(ISystemInfoProvider, IRepositoryConnector) - - branches = ListOption('svn', 'branches', 'trunk, branches/*', doc= - """Comma separated list of paths categorized as branches. - If a path ends with '*', then all the directory entries found below - that path will be included. - Example: `/trunk, /branches/*, /projectAlpha/trunk, /sandbox/*` - """) - - tags = ListOption('svn', 'tags', 'tags/*', doc= - """Comma separated list of paths categorized as tags. - - If a path ends with '*', then all the directory entries found below - that path will be included. - Example: `/tags/*, /projectAlpha/tags/A-1.0, /projectAlpha/tags/A-v1.1` - """) - - error = None - - def __init__(self): - self._version = None - try: - _import_svn() - self.log.debug('Subversion bindings imported') - except ImportError, e: - self.error = e - self.log.info('Failed to load Subversion bindings', exc_info=True) - else: - version = (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, - core.SVN_VER_MICRO) - self._version = '%d.%d.%d' % version + core.SVN_VER_TAG - if version[0] < 1: - self.error = _("Subversion >= 1.0 required, found %(version)s", - version=self._version) - Pool() - - # ISystemInfoProvider methods - - def get_system_info(self): - if self._version is not None: - yield 'Subversion', self._version - - # IRepositoryConnector methods - - def get_supported_types(self): - prio = 1 - if self.error: - prio = -1 - yield ("direct-svnfs", prio * 4) - yield ("svnfs", prio * 4) - yield ("svn", prio * 2) - - def get_repository(self, type, dir, params): - """Return a `SubversionRepository`. - - The repository is wrapped in a `CachedRepository`, unless `type` is - 'direct-svnfs'. - """ - params.update(tags=self.tags, branches=self.branches) - repos = SubversionRepository(dir, params, self.log) - if type != 'direct-svnfs': - repos = SvnCachedRepository(self.env, repos, self.log) - return repos - - -class SubversionRepository(Repository): - """Repository implementation based on the svn.fs API.""" - - has_linear_changesets = True - - def __init__(self, path, params, log): - self.log = log - self.pool = Pool() - - # Remove any trailing slash or else subversion might abort - if isinstance(path, unicode): - path_utf8 = path.encode('utf-8') - else: # note that this should usually not happen (unicode arg expected) - path_utf8 = to_unicode(path).encode('utf-8') - - path_utf8 = os.path.normpath(path_utf8).replace('\\', '/') - self.path = path_utf8.decode('utf-8') - - root_path_utf8 = repos.svn_repos_find_root_path(path_utf8, self.pool()) - if root_path_utf8 is None: - raise TracError(_("%(path)s does not appear to be a Subversion " - "repository.", path=to_unicode(path_utf8))) - - try: - self.repos = repos.svn_repos_open(root_path_utf8, self.pool()) - except core.SubversionException, e: - raise TracError(_("Couldn't open Subversion repository %(path)s: " - "%(svn_error)s", path=to_unicode(path_utf8), - svn_error=exception_to_unicode(e))) - self.fs_ptr = repos.svn_repos_fs(self.repos) - - self.uuid = fs.get_uuid(self.fs_ptr, self.pool()) - self.base = 'svn:%s:%s' % (self.uuid, _from_svn(root_path_utf8)) - name = 'svn:%s:%s' % (self.uuid, self.path) - - Repository.__init__(self, name, params, log) - - # if root_path_utf8 is shorter than the path_utf8, the difference is - # this scope (which always starts with a '/') - if root_path_utf8 != path_utf8: - self.scope = path_utf8[len(root_path_utf8):].decode('utf-8') - if not self.scope[-1] == '/': - self.scope += '/' - else: - self.scope = '/' - assert self.scope[0] == '/' - # we keep root_path_utf8 for RA - ra_prefix = 'file:///' if os.name == 'nt' else 'file://' - self.ra_url_utf8 = ra_prefix + root_path_utf8 - self.clear() - - def clear(self, youngest_rev=None): - """Reset notion of `youngest` and `oldest`""" - self.youngest = None - if youngest_rev is not None: - self.youngest = self.normalize_rev(youngest_rev) - self.oldest = None - - def __del__(self): - self.close() - - def has_node(self, path, rev=None, pool=None): - """Check if `path` exists at `rev` (or latest if unspecified)""" - if not pool: - pool = self.pool - rev = self.normalize_rev(rev) - rev_root = fs.revision_root(self.fs_ptr, rev, pool()) - node_type = fs.check_path(rev_root, _to_svn(pool(), self.scope, path), - pool()) - return node_type in _kindmap - - def normalize_path(self, path): - """Take any path specification and produce a path suitable for - the rest of the API - """ - return _normalize_path(path) - - def normalize_rev(self, rev): - """Take any revision specification and produce a revision suitable - for the rest of the API - """ - if rev is None or isinstance(rev, basestring) and \ - rev.lower() in ('', 'head', 'latest', 'youngest'): - return self.youngest_rev - else: - try: - rev = int(rev) - if rev <= self.youngest_rev: - return rev - except (ValueError, TypeError): - pass - raise NoSuchChangeset(rev) - - def close(self): - """Dispose of low-level resources associated to this repository.""" - if self.pool: - self.pool.destroy() - self.repos = self.fs_ptr = self.pool = None - - def get_base(self): - """Retrieve the base path corresponding to the Subversion - repository itself. - - This is the same as the `.path` property minus the - intra-repository scope, if one was specified. - """ - return self.base - - def _get_tags_or_branches(self, paths): - """Retrieve known branches or tags.""" - for path in self.params.get(paths, []): - if path.endswith('*'): - folder = posixpath.dirname(path) - try: - entries = [n for n in self.get_node(folder).get_entries()] - for node in sorted(entries, key=lambda n: - embedded_numbers(n.path.lower())): - if node.kind == Node.DIRECTORY: - yield node - except Exception: # no right (TODO: use a specific Exception) - pass - else: - try: - yield self.get_node(path) - except Exception: # no right - pass - - def get_quickjump_entries(self, rev): - """Retrieve known branches, as (name, id) pairs. - - Purposedly ignores `rev` and always takes the last revision. - """ - for n in self._get_tags_or_branches('branches'): - yield 'branches', n.path, n.path, None - for n in self._get_tags_or_branches('tags'): - yield 'tags', n.path, n.created_path, n.created_rev - - def get_path_url(self, path, rev): - """Retrieve the "native" URL from which this repository is reachable - from Subversion clients. - """ - url = self.params.get('url', '').rstrip('/') - if url: - if not path or path == '/': - return url - return url + '/' + path.lstrip('/') - - def get_changeset(self, rev): - """Produce a `SubversionChangeset` from given revision - specification""" - rev = self.normalize_rev(rev) - return SubversionChangeset(self, rev, self.scope, self.pool) - - def get_changeset_uid(self, rev): - """Build a value identifying the `rev` in this repository.""" - return (self.uuid, rev) - - def get_node(self, path, rev=None): - """Produce a `SubversionNode` from given path and optionally revision - specifications. No revision given means use the latest. - """ - path = path or '' - if path and path[-1] == '/': - path = path[:-1] - rev = self.normalize_rev(rev) or self.youngest_rev - return SubversionNode(path, rev, self, self.pool) - - def _get_node_revs(self, path, last=None, first=None): - """Return the revisions affecting `path` between `first` and `last` - revs. If `first` is not given, it goes down to the revision in which - the branch was created. - """ - node = self.get_node(path, last) - revs = [] - for (p, r, chg) in node.get_history(): - if p != path or (first and r < first): - break - revs.append(r) - return revs - - def _history(self, path, start, end, pool): - """`path` is a unicode path in the scope. - - Generator yielding `(path, rev)` pairs, where `path` is an `unicode` - object. Must start with `(path, created rev)`. - - (wraps ``fs.node_history``) - """ - path_utf8 = _to_svn(pool(), self.scope, path) - if start < end: - start, end = end, start - if (start, end) == (1, 0): # only happens for empty repos - return - root = fs.revision_root(self.fs_ptr, start, pool()) - # fs.node_history leaks when path doesn't exist (#6588) - if fs.check_path(root, path_utf8, pool()) == core.svn_node_none: - return - tmp1 = Pool(pool) - tmp2 = Pool(pool) - history_ptr = fs.node_history(root, path_utf8, tmp1()) - cross_copies = 1 - while history_ptr: - history_ptr = fs.history_prev(history_ptr, cross_copies, tmp2()) - tmp1.clear() - tmp1, tmp2 = tmp2, tmp1 - if history_ptr: - path_utf8, rev = fs.history_location(history_ptr, tmp2()) - tmp2.clear() - if rev < end: - break - path = _from_svn(path_utf8) - yield path, rev - del tmp1 - del tmp2 - - def _previous_rev(self, rev, path='', pool=None): - if rev > 1: # don't use oldest here, as it's too expensive - for _, prev in self._history(path, 1, rev-1, pool or self.pool): - return prev - return None - - - def get_oldest_rev(self): - """Gives an approximation of the oldest revision.""" - if self.oldest is None: - self.oldest = 1 - # trying to figure out the oldest rev for scoped repository - # is too expensive and uncovers a big memory leak (#5213) - # if self.scope != '/': - # self.oldest = self.next_rev(0, find_initial_rev=True) - return self.oldest - - def get_youngest_rev(self): - """Retrieve the latest revision in the repository. - - (wraps ``fs.youngest_rev``) - """ - if not self.youngest: - self.youngest = fs.youngest_rev(self.fs_ptr, self.pool()) - if self.scope != '/': - for path, rev in self._history('', 1, self.youngest, self.pool): - self.youngest = rev - break - return self.youngest - - def previous_rev(self, rev, path=''): - """Return revision immediately preceeding `rev`, eventually below - given `path` or globally. - """ - # FIXME optimize for non-scoped - rev = self.normalize_rev(rev) - return self._previous_rev(rev, path) - - def next_rev(self, rev, path='', find_initial_rev=False): - """Return revision immediately following `rev`, eventually below - given `path` or globally. - """ - rev = self.normalize_rev(rev) - next = rev + 1 - youngest = self.youngest_rev - subpool = Pool(self.pool) - while next <= youngest: - subpool.clear() - for _, next in self._history(path, rev+1, next, subpool): - return next - else: - if not find_initial_rev and \ - not self.has_node(path, next, subpool): - return next # a 'delete' event is also interesting... - next += 1 - return None - - def rev_older_than(self, rev1, rev2): - """Check relative order between two revision specifications.""" - return self.normalize_rev(rev1) < self.normalize_rev(rev2) - - def get_path_history(self, path, rev=None, limit=None): - """Retrieve creation and deletion events that happened on - given `path`. - """ - path = self.normalize_path(path) - rev = self.normalize_rev(rev) - expect_deletion = False - subpool = Pool(self.pool) - numrevs = 0 - while rev and (not limit or numrevs < limit): - subpool.clear() - if self.has_node(path, rev, subpool): - if expect_deletion: - # it was missing, now it's there again: - # rev+1 must be a delete - numrevs += 1 - yield path, rev+1, Changeset.DELETE - newer = None # 'newer' is the previously seen history tuple - older = None # 'older' is the currently examined history tuple - for p, r in self._history(path, 1, rev, subpool): - older = (_path_within_scope(self.scope, p), r, - Changeset.ADD) - rev = self._previous_rev(r, pool=subpool) - if newer: - numrevs += 1 - if older[0] == path: - # still on the path: 'newer' was an edit - yield newer[0], newer[1], Changeset.EDIT - else: - # the path changed: 'newer' was a copy - rev = self._previous_rev(newer[1], pool=subpool) - # restart before the copy op - yield newer[0], newer[1], Changeset.COPY - older = (older[0], older[1], 'unknown') - break - newer = older - if older: - # either a real ADD or the source of a COPY - numrevs += 1 - yield older - else: - expect_deletion = True - rev = self._previous_rev(rev, pool=subpool) - - def get_changes(self, old_path, old_rev, new_path, new_rev, - ignore_ancestry=0): - """Determine differences between two arbitrary pairs of paths - and revisions. - - (wraps ``repos.svn_repos_dir_delta``) - """ - old_node = new_node = None - old_rev = self.normalize_rev(old_rev) - new_rev = self.normalize_rev(new_rev) - if self.has_node(old_path, old_rev): - old_node = self.get_node(old_path, old_rev) - else: - raise NoSuchNode(old_path, old_rev, 'The Base for Diff is invalid') - if self.has_node(new_path, new_rev): - new_node = self.get_node(new_path, new_rev) - else: - raise NoSuchNode(new_path, new_rev, - 'The Target for Diff is invalid') - if new_node.kind != old_node.kind: - raise TracError(_('Diff mismatch: Base is a %(oldnode)s ' - '(%(oldpath)s in revision %(oldrev)s) and ' - 'Target is a %(newnode)s (%(newpath)s in ' - 'revision %(newrev)s).', oldnode=old_node.kind, - oldpath=old_path, oldrev=old_rev, - newnode=new_node.kind, newpath=new_path, - newrev=new_rev)) - subpool = Pool(self.pool) - if new_node.isdir: - editor = DiffChangeEditor() - e_ptr, e_baton = delta.make_editor(editor, subpool()) - old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) - new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) - def authz_cb(root, path, pool): - return 1 - text_deltas = 0 # as this is anyway re-done in Diff.py... - entry_props = 0 # "... typically used only for working copy updates" - repos.svn_repos_dir_delta(old_root, - _to_svn(subpool(), self.scope, old_path), - '', new_root, - _to_svn(subpool(), self.scope, new_path), - e_ptr, e_baton, authz_cb, - text_deltas, - 1, # directory - entry_props, - ignore_ancestry, - subpool()) - for path, kind, change in editor.deltas: - path = _from_svn(path) - old_node = new_node = None - if change != Changeset.ADD: - old_node = self.get_node(posixpath.join(old_path, path), - old_rev) - if change != Changeset.DELETE: - new_node = self.get_node(posixpath.join(new_path, path), - new_rev) - else: - kind = _kindmap[fs.check_path(old_root, - _to_svn(subpool(), - self.scope, - old_node.path), - subpool())] - yield (old_node, new_node, kind, change) - else: - old_root = fs.revision_root(self.fs_ptr, old_rev, subpool()) - new_root = fs.revision_root(self.fs_ptr, new_rev, subpool()) - if fs.contents_changed(old_root, - _to_svn(subpool(), self.scope, old_path), - new_root, - _to_svn(subpool(), self.scope, new_path), - subpool()): - yield (old_node, new_node, Node.FILE, Changeset.EDIT) - - -class SubversionNode(Node): - - def __init__(self, path, rev, repos, pool=None, parent_root=None): - self.fs_ptr = repos.fs_ptr - self.scope = repos.scope - self.pool = Pool(pool) - pool = self.pool() - self._scoped_path_utf8 = _to_svn(pool, self.scope, path) - - if parent_root: - self.root = parent_root - else: - self.root = fs.revision_root(self.fs_ptr, rev, pool) - node_type = fs.check_path(self.root, self._scoped_path_utf8, pool) - if not node_type in _kindmap: - raise NoSuchNode(path, rev) - cp_utf8 = fs.node_created_path(self.root, self._scoped_path_utf8, pool) - cp = _from_svn(cp_utf8) - cr = fs.node_created_rev(self.root, self._scoped_path_utf8, pool) - # Note: `cp` differs from `path` if the last change was a copy, - # In that case, `path` doesn't even exist at `cr`. - # The only guarantees are: - # * this node exists at (path,rev) - # * the node existed at (created_path,created_rev) - # Also, `cp` might well be out of the scope of the repository, - # in this case, we _don't_ use the ''create'' information. - if _is_path_within_scope(self.scope, cp): - self.created_rev = cr - self.created_path = _path_within_scope(self.scope, cp) - else: - self.created_rev, self.created_path = rev, path - # TODO: check node id - Node.__init__(self, repos, path, rev, _kindmap[node_type]) - - def get_content(self): - """Retrieve raw content as a "read()"able object.""" - if self.isdir: - return None - s = core.Stream(fs.file_contents(self.root, self._scoped_path_utf8, - self.pool())) - # The stream object needs to reference the pool to make sure the pool - # is not destroyed before the former. - s._pool = self.pool - return s - - def get_entries(self): - """Yield `SubversionNode` corresponding to entries in this directory. - - (wraps ``fs.dir_entries``) - """ - if self.isfile: - return - pool = Pool(self.pool) - entries = fs.dir_entries(self.root, self._scoped_path_utf8, pool()) - for item in entries.keys(): - path = posixpath.join(self.path, _from_svn(item)) - yield SubversionNode(path, self.rev, self.repos, self.pool, - self.root) - - def get_history(self, limit=None): - """Yield change events that happened on this path""" - newer = None # 'newer' is the previously seen history tuple - older = None # 'older' is the currently examined history tuple - pool = Pool(self.pool) - numrevs = 0 - for path, rev in self.repos._history(self.path, 1, self.rev, pool): - path = _path_within_scope(self.scope, path) - if rev > 0 and path: - older = (path, rev, Changeset.ADD) - if newer: - if newer[0] == older[0]: # stay on same path - change = Changeset.EDIT - else: - change = Changeset.COPY - newer = (newer[0], newer[1], change) - numrevs += 1 - yield newer - newer = older - if limit and numrevs >= limit: - break - if newer and (not limit or numrevs < limit): - yield newer - - def get_annotations(self): - """Return a list the last changed revision for each line. - (wraps ``client.blame2``) - """ - annotations = [] - if self.isfile: - def blame_receiver(line_no, revision, author, date, line, pool): - annotations.append(revision) - try: - rev = _svn_rev(self.rev) - start = _svn_rev(0) - file_url_utf8 = posixpath.join(self.repos.ra_url_utf8, - self._scoped_path_utf8) - self.repos.log.info('opening ra_local session to %r', - file_url_utf8) - from svn import client - client.blame2(file_url_utf8, rev, start, rev, blame_receiver, - client.create_context(), self.pool()) - except (core.SubversionException, AttributeError), e: - # svn thinks file is a binary or blame not supported - raise TracError(_('svn blame failed on %(path)s: %(error)s', - path=self.path, error=to_unicode(e))) - return annotations - -# def get_previous(self): -# # FIXME: redo it with fs.node_history - - def get_properties(self): - """Return `dict` of node properties at current revision. - - (wraps ``fs.node_proplist``) - """ - props = fs.node_proplist(self.root, self._scoped_path_utf8, self.pool()) - for name, value in props.items(): - # Note that property values can be arbitrary binary values - # so we can't assume they are UTF-8 strings... - props[_from_svn(name)] = to_unicode(value) - return props - - def get_content_length(self): - """Retrieve byte size of a file. - - Return `None` for a folder. (wraps ``fs.file_length``) - """ - if self.isdir: - return None - return fs.file_length(self.root, self._scoped_path_utf8, self.pool()) - - def get_content_type(self): - """Retrieve mime-type property of a file. - - Return `None` for a folder. (wraps ``fs.revision_prop``) - """ - if self.isdir: - return None - return self._get_prop(core.SVN_PROP_MIME_TYPE) - - def get_last_modified(self): - """Retrieve timestamp of last modification, in micro-seconds. - - (wraps ``fs.revision_prop``) - """ - _date = fs.revision_prop(self.fs_ptr, self.created_rev, - core.SVN_PROP_REVISION_DATE, self.pool()) - if not _date: - return None - return from_utimestamp(core.svn_time_from_cstring(_date, self.pool())) - - def _get_prop(self, name): - return fs.node_prop(self.root, self._scoped_path_utf8, name, - self.pool()) - - def get_branch_origin(self): - """Return the revision in which the node's path was created. - - (wraps ``fs.revision_root_revision(fs.closest_copy)``) - """ - root_and_path = fs.closest_copy(self.root, self._scoped_path_utf8) - if root_and_path: - return fs.revision_root_revision(root_and_path[0]) - - def get_copy_ancestry(self): - """Retrieve the list of `(path,rev)` copy ancestors of this node. - Most recent ancestor first. Each ancestor `(path, rev)` corresponds - to the path and revision of the source at the time the copy or move - operation was performed. - """ - ancestors = [] - previous = (self._scoped_path_utf8, self.rev, self.root) - while previous: - (previous_path, previous_rev, previous_root) = previous - previous = None - root_path = fs.closest_copy(previous_root, previous_path) - if root_path: - (root, path) = root_path - path = path.lstrip('/') - rev = fs.revision_root_revision(root) - relpath = None - if path != previous_path: - # `previous_path` is a subfolder of `path` and didn't - # change since `path` was copied - relpath = previous_path[len(path):].strip('/') - copied_from = fs.copied_from(root, path) - if copied_from: - (rev, path) = copied_from - path = path.lstrip('/') - root = fs.revision_root(self.fs_ptr, rev, self.pool()) - if relpath: - path += '/' + relpath - ui_path = _path_within_scope(self.scope, _from_svn(path)) - if ui_path: - ancestors.append((ui_path, rev)) - previous = (path, rev, root) - return ancestors - - -class SubversionChangeset(Changeset): - - def __init__(self, repos, rev, scope, pool=None): - self.rev = rev - self.scope = scope - self.fs_ptr = repos.fs_ptr - self.pool = Pool(pool) - try: - message = self._get_prop(core.SVN_PROP_REVISION_LOG) - except core.SubversionException: - raise NoSuchChangeset(rev) - author = self._get_prop(core.SVN_PROP_REVISION_AUTHOR) - # we _hope_ it's UTF-8, but can't be 100% sure (#4321) - message = message and to_unicode(message, 'utf-8') - author = author and to_unicode(author, 'utf-8') - _date = self._get_prop(core.SVN_PROP_REVISION_DATE) - if _date: - ts = core.svn_time_from_cstring(_date, self.pool()) - date = from_utimestamp(ts) - else: - date = None - Changeset.__init__(self, repos, rev, message, author, date) - - def get_properties(self): - """Retrieve `dict` of Subversion properties for this revision - (revprops) - """ - props = fs.revision_proplist(self.fs_ptr, self.rev, self.pool()) - properties = {} - for k, v in props.iteritems(): - if k not in (core.SVN_PROP_REVISION_LOG, - core.SVN_PROP_REVISION_AUTHOR, - core.SVN_PROP_REVISION_DATE): - properties[k] = to_unicode(v) - # Note: the above `to_unicode` has a small probability - # to mess-up binary properties, like icons. - return properties - - def get_changes(self): - """Retrieve file changes for a given revision. - - (wraps ``repos.svn_repos_replay``) - """ - pool = Pool(self.pool) - tmp = Pool(pool) - root = fs.revision_root(self.fs_ptr, self.rev, pool()) - editor = repos.RevisionChangeCollector(self.fs_ptr, self.rev, pool()) - e_ptr, e_baton = delta.make_editor(editor, pool()) - repos.svn_repos_replay(root, e_ptr, e_baton, pool()) - - idx = 0 - copies, deletions = {}, {} - changes = [] - revroots = {} - for path_utf8, change in editor.changes.items(): - new_path = _from_svn(path_utf8) - - # Filtering on `path` - if not _is_path_within_scope(self.scope, new_path): - continue - - path_utf8 = change.path - base_path_utf8 = change.base_path - path = _from_svn(path_utf8) - base_path = _from_svn(base_path_utf8) - base_rev = change.base_rev - change_action = getattr(change, 'action', None) - - # Ensure `base_path` is within the scope - if not _is_path_within_scope(self.scope, base_path): - base_path, base_rev = None, -1 - - # Determine the action - if not path and not new_path and self.scope == '/': - action = Changeset.EDIT # root property change - elif not path or (change_action is not None - and change_action == repos.CHANGE_ACTION_DELETE): - if new_path: # deletion - action = Changeset.DELETE - deletions[new_path.lstrip('/')] = idx - else: # deletion outside of scope, ignore - continue - elif change.added or not base_path: # add or copy - action = Changeset.ADD - if base_path and base_rev: - action = Changeset.COPY - copies[base_path.lstrip('/')] = idx - else: - action = Changeset.EDIT - # identify the most interesting base_path/base_rev - # in terms of last changed information (see r2562) - if revroots.has_key(base_rev): - b_root = revroots[base_rev] - else: - b_root = fs.revision_root(self.fs_ptr, base_rev, pool()) - revroots[base_rev] = b_root - tmp.clear() - cbase_path_utf8 = fs.node_created_path(b_root, base_path_utf8, - tmp()) - cbase_path = _from_svn(cbase_path_utf8) - cbase_rev = fs.node_created_rev(b_root, base_path_utf8, tmp()) - # give up if the created path is outside the scope - if _is_path_within_scope(self.scope, cbase_path): - base_path, base_rev = cbase_path, cbase_rev - - kind = _kindmap[change.item_kind] - path = _path_within_scope(self.scope, new_path or base_path) - base_path = _path_within_scope(self.scope, base_path) - changes.append([path, kind, action, base_path, base_rev]) - idx += 1 - - moves = [] - # a MOVE is a COPY whose `base_path` corresponds to a `new_path` - # which has been deleted - for k, v in copies.items(): - if k in deletions: - changes[v][2] = Changeset.MOVE - moves.append(deletions[k]) - offset = 0 - moves.sort() - for i in moves: - del changes[i - offset] - offset += 1 - - changes.sort() - for change in changes: - yield tuple(change) - - def _get_prop(self, name): - return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool()) - - -# -# Delta editor for diffs between arbitrary nodes -# -# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used -# because 'repos.svn_repos_dir_delta' *doesn't* provide it. -# -# Note 2: the 'dir_baton' is the path of the parent directory -# - - -def DiffChangeEditor(): - - class DiffChangeEditor(delta.Editor): - - def __init__(self): - self.deltas = [] - - # -- svn.delta.Editor callbacks - - def open_root(self, base_revision, dir_pool): - return ('/', Changeset.EDIT) - - def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev, - dir_pool): - self.deltas.append((path, Node.DIRECTORY, Changeset.ADD)) - return (path, Changeset.ADD) - - def open_directory(self, path, dir_baton, base_revision, dir_pool): - return (path, dir_baton[1]) - - def change_dir_prop(self, dir_baton, name, value, pool): - path, change = dir_baton - if change != Changeset.ADD: - self.deltas.append((path, Node.DIRECTORY, change)) - - def delete_entry(self, path, revision, dir_baton, pool): - self.deltas.append((path, None, Changeset.DELETE)) - - def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision, - dir_pool): - self.deltas.append((path, Node.FILE, Changeset.ADD)) - - def open_file(self, path, dir_baton, dummy_rev, file_pool): - self.deltas.append((path, Node.FILE, Changeset.EDIT)) - return DiffChangeEditor() +from trac.util import import_namespace +import_namespace(globals(), 'tracopt.versioncontrol.svn.svn_fs') +# This module is a stub provided for backward compatibility. The svn_fs +# module has been moved to tracopt.versioncontrol.svn. Please update your +# code to use the new location. Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_prop.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_prop.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_prop.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/svn_prop.py Tue Oct 16 15:55:00 2012 @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2005-2009 Edgewall Software -# Copyright (C) 2005 Christopher Lenz -# Copyright (C) 2005-2007 Christian Boos +# Copyright (C) 2012 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -12,391 +10,10 @@ # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. -# -# Author: Christopher Lenz -# Christian Boos - -import posixpath - -from genshi.builder import tag - -from trac.config import ConfigSection -from trac.core import * -from trac.versioncontrol.api import NoSuchNode, RepositoryManager -from trac.versioncontrol.svn_fs import _path_within_scope -from trac.versioncontrol.web_ui.browser import IPropertyRenderer -from trac.versioncontrol.web_ui.changeset import IPropertyDiffRenderer -from trac.util import Ranges, to_ranges -from trac.util.translation import _, tag_ - - -class SubversionPropertyRenderer(Component): - - implements(IPropertyRenderer) - - svn_externals_section = ConfigSection('svn:externals', - """The TracBrowser for Subversion can interpret the `svn:externals` - property of folders. By default, it only turns the URLs into links as - Trac can't browse remote repositories. - - However, if you have another Trac instance (or an other repository - browser like [http://www.viewvc.org/ ViewVC]) configured to browse the - target repository, then you can instruct Trac which other repository - browser to use for which external URL. This mapping is done in the - `[svn:externals]` section of the TracIni. - - Example: - {{{ - [svn:externals] - 1 = svn://server/repos1 http://trac/proj1/browser/$path?rev=$rev - 2 = svn://server/repos2 http://trac/proj2/browser/$path?rev=$rev - 3 = http://theirserver.org/svn/eng-soft http://ourserver/viewvc/svn/$path/?pathrev=25914 - 4 = svn://anotherserver.com/tools_repository http://ourserver/tracs/tools/browser/$path?rev=$rev - }}} - With the above, the - `svn://anotherserver.com/tools_repository/tags/1.1/tools` external will - be mapped to `http://ourserver/tracs/tools/browser/tags/1.1/tools?rev=` - (and `rev` will be set to the appropriate revision number if the - external additionally specifies a revision, see the - [http://svnbook.red-bean.com/en/1.4/svn.advanced.externals.html SVN Book on externals] - for more details). - - Note that the number used as a key in the above section is purely used - as a place holder, as the URLs themselves can't be used as a key due to - various limitations in the configuration file parser. - - Finally, the relative URLs introduced in - [http://subversion.apache.org/docs/release-notes/1.5.html#externals Subversion 1.5] - are not yet supported. - - (''since 0.11'')""") - - def __init__(self): - self._externals_map = {} - - # IPropertyRenderer methods - - def match_property(self, name, mode): - if name in ('svn:externals', 'svn:needs-lock'): - return 4 - return 2 if name in ('svn:mergeinfo', 'svnmerge-blocked', - 'svnmerge-integrated') else 0 - - def render_property(self, name, mode, context, props): - if name == 'svn:externals': - return self._render_externals(props[name]) - elif name == 'svn:needs-lock': - return self._render_needslock(context) - elif name == 'svn:mergeinfo' or name.startswith('svnmerge-'): - return self._render_mergeinfo(name, mode, context, props) - - def _render_externals(self, prop): - if not self._externals_map: - for dummykey, value in self.svn_externals_section.options(): - value = value.split() - if len(value) != 2: - self.log.warn("svn:externals entry %s doesn't contain " - "a space-separated key value pair, skipping.", - dummykey) - continue - key, value = value - self._externals_map[key] = value.replace('%', '%%') \ - .replace('$path', '%(path)s') \ - .replace('$rev', '%(rev)s') - externals = [] - for external in prop.splitlines(): - elements = external.split() - if not elements: - continue - localpath, rev, url = elements[0], '', elements[-1] - if localpath.startswith('#'): - externals.append((external, None, None, None, None)) - continue - if len(elements) == 3: - rev = elements[1] - rev = rev.replace('-r', '') - # retrieve a matching entry in the externals map - prefix = [] - base_url = url - while base_url: - if base_url in self._externals_map or base_url == u'/': - break - base_url, pref = posixpath.split(base_url) - prefix.append(pref) - href = self._externals_map.get(base_url) - revstr = ' at revision ' + rev if rev else '' - if not href and (url.startswith('http://') or - url.startswith('https://')): - href = url.replace('%', '%%') - if href: - remotepath = '' - if prefix: - remotepath = posixpath.join(*reversed(prefix)) - externals.append((localpath, revstr, base_url, remotepath, - href % {'path': remotepath, 'rev': rev})) - else: - externals.append((localpath, revstr, url, None, None)) - externals_data = [] - for localpath, rev, url, remotepath, href in externals: - label = localpath - if url is None: - title = '' - elif href: - if url: - url = ' in ' + url - label += rev + url - title = ''.join((remotepath, rev, url)) - else: - title = _('No svn:externals configured in trac.ini') - externals_data.append((label, href, title)) - return tag.ul([tag.li(tag.a(label, href=href, title=title)) - for label, href, title in externals_data]) - - def _render_needslock(self, context): - return tag.img(src=context.href.chrome('common/lock-locked.png'), - alt="needs lock", title="needs lock") - - def _render_mergeinfo(self, name, mode, context, props): - rows = [] - for row in props[name].splitlines(): - try: - (path, revs) = row.rsplit(':', 1) - rows.append([tag.td(path), - tag.td(revs.replace(',', u',\u200b'))]) - except ValueError: - rows.append(tag.td(row, colspan=2)) - return tag.table(tag.tbody([tag.tr(row) for row in rows]), - class_='props') - - -class SubversionMergePropertyRenderer(Component): - implements(IPropertyRenderer) - - # IPropertyRenderer methods - - def match_property(self, name, mode): - return 4 if name in ('svn:mergeinfo', 'svnmerge-blocked', - 'svnmerge-integrated') else 0 - - def render_property(self, name, mode, context, props): - """Parse svn:mergeinfo and svnmerge-* properties, converting branch - names to links and providing links to the revision log for merged - and eligible revisions. - """ - has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo') - revs_label = _('blocked') if name.endswith('blocked') else _('merged') - revs_cols = 2 if has_eligible else None - reponame = context.resource.parent.id - target_path = context.resource.id - repos = RepositoryManager(self.env).get_repository(reponame) - target_rev = context.resource.version - if has_eligible: - node = repos.get_node(target_path, target_rev) - branch_starts = {} - for path, rev in node.get_copy_ancestry(): - if path not in branch_starts: - branch_starts[path] = rev + 1 - rows = [] - if name.startswith('svnmerge-'): - sources = props[name].split() - else: - sources = props[name].splitlines() - for line in sources: - path, revs = line.split(':', 1) - spath = _path_within_scope(repos.scope, path) - if spath is None: - continue - revs = revs.strip() - inheritable, non_inheritable = _partition_inheritable(revs) - revs = ','.join(inheritable) - deleted = False - try: - node = repos.get_node(spath, target_rev) - resource = context.resource.parent.child('source', spath) - if 'LOG_VIEW' in context.perm(resource): - row = [_get_source_link(spath, context), - _get_revs_link(revs_label, context, spath, revs)] - if non_inheritable: - non_inheritable = ','.join(non_inheritable) - row.append(_get_revs_link(_('non-inheritable'), context, - spath, non_inheritable, - _('merged on the directory ' - 'itself but not below'))) - if has_eligible: - first_rev = branch_starts.get(spath) - if not first_rev: - first_rev = node.get_branch_origin() - eligible = set(xrange(first_rev or 1, target_rev + 1)) - eligible -= set(Ranges(revs)) - blocked = _get_blocked_revs(props, name, spath) - if blocked: - eligible -= set(Ranges(blocked)) - if eligible: - nrevs = repos._get_node_revs(spath, max(eligible), - min(eligible)) - eligible &= set(nrevs) - eligible = to_ranges(eligible) - row.append(_get_revs_link(_('eligible'), context, - spath, eligible)) - rows.append((False, spath, [tag.td(each) for each in row])) - continue - except NoSuchNode: - deleted = True - revs = revs.replace(',', u',\u200b') - rows.append((deleted, spath, - [tag.td('/' + spath), - tag.td(revs, colspan=revs_cols)])) - if not rows: - return None - rows.sort() - has_deleted = rows[-1][0] if rows else None - return tag(has_deleted and tag.a(_('(toggle deleted branches)'), - class_='trac-toggledeleted', - href='#'), - tag.table(tag.tbody( - [tag.tr(row, class_='trac-deleted' if deleted else None) - for deleted, spath, row in rows]), class_='props')) - - -def _partition_inheritable(revs): - """Non-inheritable revision ranges are marked with a trailing '*'.""" - inheritable, non_inheritable = [], [] - for r in revs.split(','): - if r and r[-1] == '*': - non_inheritable.append(r[:-1]) - else: - inheritable.append(r) - return inheritable, non_inheritable - -def _get_blocked_revs(props, name, path): - """Return the revisions blocked from merging for the given property - name and path. - """ - if name == 'svnmerge-integrated': - prop = props.get('svnmerge-blocked', '') - else: - return "" - for line in prop.splitlines(): - try: - p, revs = line.split(':', 1) - if p.strip('/') == path: - return revs - except Exception: - pass - return "" - -def _get_source_link(spath, context): - """Return a link to a merge source.""" - reponame = context.resource.parent.id - return tag.a('/' + spath, title=_('View merge source'), - href=context.href.browser(reponame or None, spath, - rev=context.resource.version)) - -def _get_revs_link(label, context, spath, revs, title=None): - """Return a link to the revision log when more than one revision is - given, to the revision itself for a single revision, or a `` - with "no revision" for none. - """ - reponame = context.resource.parent.id - if not revs: - return tag.span(label, title=_('No revisions')) - elif ',' in revs or '-' in revs: - revs_href = context.href.log(reponame or None, spath, revs=revs) - else: - revs_href = context.href.changeset(revs, reponame or None, spath) - revs = revs.replace(',', ', ') - if title: - title = _("%(title)s: %(revs)s", title=title, revs=revs) - else: - title = revs - return tag.a(label, title=title, href=revs_href) - - -class SubversionMergePropertyDiffRenderer(Component): - implements(IPropertyDiffRenderer) - - # IPropertyDiffRenderer methods - def match_property_diff(self, name): - return 4 if name in ('svn:mergeinfo', 'svnmerge-blocked', - 'svnmerge-integrated') else 0 +from trac.util import import_namespace +import_namespace(globals(), 'tracopt.versioncontrol.svn.svn_prop') - def render_property_diff(self, name, old_context, old_props, - new_context, new_props, options): - # Build 5 columns table showing modifications on merge sources - # || source || added || removed || added (ni) || removed (ni) || - # || source || removed || - rm = RepositoryManager(self.env) - repos = rm.get_repository(old_context.resource.parent.id) - def parse_sources(props): - sources = {} - for line in props[name].splitlines(): - path, revs = line.split(':', 1) - spath = _path_within_scope(repos.scope, path) - if spath is not None: - inheritable, non_inheritable = _partition_inheritable(revs) - sources[spath] = (set(Ranges(inheritable)), - set(Ranges(non_inheritable))) - return sources - old_sources = parse_sources(old_props) - new_sources = parse_sources(new_props) - # Go through new sources, detect modified ones or added ones - blocked = name.endswith('blocked') - added_label = [_("merged: "), _("blocked: ")][blocked] - removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked] - added_ni_label = _("marked as non-inheritable: ") - removed_ni_label = _("unmarked as non-inheritable: ") - def revs_link(revs, context): - if revs: - revs = to_ranges(revs) - return _get_revs_link(revs.replace(',', u',\u200b'), - context, spath, revs) - modified_sources = [] - for spath, (new_revs, new_revs_ni) in new_sources.iteritems(): - if spath in old_sources: - (old_revs, old_revs_ni), status = old_sources.pop(spath), None - else: - old_revs = old_revs_ni = set() - status = _(' (added)') - added = new_revs - old_revs - removed = old_revs - new_revs - added_ni = new_revs_ni - old_revs_ni - removed_ni = old_revs_ni - new_revs_ni - try: - all_revs = set(repos._get_node_revs(spath)) - # TODO: also pass first_rev here, for getting smaller a set - # (this is an optmization fix, result is already correct) - added &= all_revs - removed &= all_revs - added_ni &= all_revs - removed_ni &= all_revs - except NoSuchNode: - pass - if added or removed: - modified_sources.append(( - spath, [_get_source_link(spath, new_context), status], - added and tag(added_label, revs_link(added, new_context)), - removed and tag(removed_label, - revs_link(removed, old_context)), - added_ni and tag(added_ni_label, - revs_link(added_ni, new_context)), - removed_ni and tag(removed_ni_label, - revs_link(removed_ni, old_context)) - )) - # Go through remaining old sources, those were deleted - removed_sources = [] - for spath, old_revs in old_sources.iteritems(): - removed_sources.append((spath, - _get_source_link(spath, old_context))) - if modified_sources or removed_sources: - modified_sources.sort() - removed_sources.sort() - changes = tag.table(tag.tbody( - [tag.tr(tag.td(c) for c in cols[1:]) - for cols in modified_sources], - [tag.tr(tag.td(src), tag.td(_('removed'), colspan=4)) - for spath, src in removed_sources]), class_='props') - else: - changes = tag.em(_(' (with no actual effect on merging)')) - return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)), - changes) +# This module is a stub provided for backward compatibility. The svn_prop +# module has been moved to tracopt.versioncontrol.svn. Please update your +# code to use the new location. Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/admin_repositories.html URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/admin_repositories.html?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/admin_repositories.html (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/admin_repositories.html Tue Oct 16 15:55:00 2012 @@ -10,12 +10,12 @@ Repositories - +

Manage Repositories

- +
-
- ${alias_field(info.editable, info.alias)} + ${alias_field(info.editable, True, info.alias)} - ${type_field(info.editable, info.type)} + ${type_field(info.editable, True, info.type)}
@@ -69,7 +69,7 @@
-
+
@@ -92,11 +92,11 @@ $info.description
Add Repository:
- +
${type_field(True)}
- +
@@ -109,7 +109,7 @@ $info.description
Add Alias:
- +
${alias_field(True)}
@@ -126,7 +126,7 @@ $info.description - + ${info.name or _('(default)')} Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/browser.html URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/browser.html?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/browser.html (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/templates/browser.html Tue Oct 16 15:55:00 2012 @@ -207,11 +207,6 @@
-
- Note: See TracBrowser - for help on using the repository browser. -
-
@@ -224,6 +219,11 @@
+
+ Note: See TracBrowser + for help on using the repository browser. +
+
Modified: incubator/bloodhound/vendor/trac/current/trac/versioncontrol/tests/__init__.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/vendor/trac/current/trac/versioncontrol/tests/__init__.py?rev=1398858&r1=1398857&r2=1398858&view=diff ============================================================================== --- incubator/bloodhound/vendor/trac/current/trac/versioncontrol/tests/__init__.py (original) +++ incubator/bloodhound/vendor/trac/current/trac/versioncontrol/tests/__init__.py Tue Oct 16 15:55:00 2012 @@ -1,6 +1,6 @@ import unittest -from trac.versioncontrol.tests import cache, diff, svn_authz, svn_fs, api +from trac.versioncontrol.tests import cache, diff, svn_authz, api from trac.versioncontrol.tests.functional import functionalSuite def suite(): @@ -9,7 +9,6 @@ def suite(): suite.addTest(cache.suite()) suite.addTest(diff.suite()) suite.addTest(svn_authz.suite()) - suite.addTest(svn_fs.suite()) suite.addTest(api.suite()) return suite