Modified: bloodhound/vendor/trac/current/tracopt/perm/authz_policy.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/perm/authz_policy.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/perm/authz_policy.py (original) +++ bloodhound/vendor/trac/current/tracopt/perm/authz_policy.py Fri Nov 14 11:06:23 2014 @@ -14,7 +14,7 @@ # # Author: Alec Thomas <a...@swapoff.org> -from fnmatch import fnmatch +from fnmatch import fnmatchcase from itertools import groupby import os @@ -23,7 +23,6 @@ from trac.config import ConfigurationErr from trac.perm import PermissionSystem, IPermissionPolicy from trac.util import lazy from trac.util.text import to_unicode -from trac.util.translation import _ ConfigObj = None try: @@ -196,14 +195,11 @@ class AuthzPolicy(Component): self.log.error("Error parsing authz permission policy file: %s", to_unicode(e)) raise ConfigurationError() - if not self.authz: - self.log.error("The authz file is empty.") - raise ConfigurationError() groups = {} for group, users in self.authz.get('groups', {}).iteritems(): if isinstance(users, basestring): users = [users] - groups[group] = users + groups[group] = map(to_unicode, users) self.groups_by_user = {} @@ -220,28 +216,29 @@ class AuthzPolicy(Component): self.authz_mtime = os.path.getmtime(self.get_authz_file) def normalise_resource(self, resource): + def to_descriptor(resource): + id = resource.id + return '%s:%s@%s' % (resource.realm or '*', + id if id is not None else '*', + resource.version or '*') + def flatten(resource): if not resource: return ['*:*@*'] - if not (resource.realm or resource.id): - return ['%s:%s@%s' % (resource.realm or '*', - resource.id or '*', - resource.version or '*')] + descriptor = to_descriptor(resource) + if not resource.realm and resource.id is None: + return [descriptor] # XXX Due to the mixed functionality in resource we can end up with # ticket, ticket:1, ticket:1@10. This code naively collapses all # subsets of the parent resource into one. eg. ticket:1@10 parent = resource.parent - while parent and (resource.realm == parent.realm or - (resource.realm == parent.realm and - resource.id == parent.id)): + while parent and resource.realm == parent.realm: parent = parent.parent if parent: - parent = flatten(parent) + return flatten(parent) + [descriptor] else: - parent = [] - return parent + ['%s:%s@%s' % (resource.realm or '*', - resource.id or '*', - resource.version or '*')] + return [descriptor] + return '/'.join(flatten(resource)) def authz_permissions(self, resource_key, username): @@ -252,14 +249,15 @@ class AuthzPolicy(Component): else: valid_users = ['*', 'anonymous'] for resource_section in [a for a in self.authz.sections - if a != 'groups']: - resource_glob = resource_section + if a != 'groups']: + resource_glob = to_unicode(resource_section) if '@' not in resource_glob: resource_glob += '@*' - if fnmatch(resource_key, resource_glob): + if fnmatchcase(resource_key, resource_glob): section = self.authz[resource_section] for who, permissions in section.iteritems(): + who = to_unicode(who) if who in valid_users or \ who in self.groups_by_user.get(username, []): self.log.debug('%s matched section %s for user %s',
Modified: bloodhound/vendor/trac/current/tracopt/perm/config_perm_provider.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/perm/config_perm_provider.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/perm/config_perm_provider.py (original) +++ bloodhound/vendor/trac/current/tracopt/perm/config_perm_provider.py Fri Nov 14 11:06:23 2014 @@ -34,17 +34,21 @@ class ExtraPermissionsProvider(Component and a comma-separated list of permissions. For example: {{{ [extra-permissions] - extra_admin = extra_view, extra_modify, extra_delete + EXTRA_ADMIN = EXTRA_VIEW, EXTRA_MODIFY, EXTRA_DELETE }}} This entry will define three new permissions `EXTRA_VIEW`, `EXTRA_MODIFY` and `EXTRA_DELETE`, as well as a meta-permissions `EXTRA_ADMIN` that grants all three permissions. + The permissions are created in upper-case characters regardless of + the casing of the definitions in `trac.ini`. For example, the + definition `extra_view` would create the permission `EXTRA_VIEW`. + If you don't want a meta-permission, start the meta-name with an underscore (`_`): {{{ [extra-permissions] - _perms = extra_view, extra_modify + _perms = EXTRA_VIEW, EXTRA_MODIFY }}} """) Modified: bloodhound/vendor/trac/current/tracopt/perm/tests/authz_policy.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/perm/tests/authz_policy.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/perm/tests/authz_policy.py (original) +++ bloodhound/vendor/trac/current/tracopt/perm/tests/authz_policy.py Fri Nov 14 11:06:23 2014 @@ -19,11 +19,13 @@ try: except ImportError: ConfigObj = None -from trac.tests import compat +import trac.tests.compat from trac.config import ConfigurationError +from trac.perm import PermissionCache from trac.resource import Resource -from trac.test import EnvironmentStub +from trac.test import EnvironmentStub, Mock from trac.util import create_file +from trac.versioncontrol.api import Repository from tracopt.perm.authz_policy import AuthzPolicy @@ -47,8 +49,33 @@ administrators = éat änon = @administrators = WIKI_VIEW * = + +# Tickets +[ticket:43] +änon = TICKET_VIEW +@administrators = +* = + +[ticket:*] +änon = +@administrators = TICKET_VIEW +* = + +# Default repository +[repository:@*] +änon = +@administrators = BROWSER_VIEW, FILE_VIEW +* = + +# Non-default repository +[repository:bláh@*] +änon = BROWSER_VIEW, FILE_VIEW +@administrators = BROWSER_VIEW, FILE_VIEW +* = """) - self.env = EnvironmentStub(enable=[AuthzPolicy], path=tmpdir) + self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir) + self.env.config.set('trac', 'permission_policies', + 'AuthzPolicy, DefaultPermissionPolicy') self.env.config.set('authz_policy', 'authz_file', self.authz_file) self.authz_policy = AuthzPolicy(self.env) @@ -59,26 +86,96 @@ administrators = éat def check_permission(self, action, user, resource, perm): return self.authz_policy.check_permission(action, user, resource, perm) + def get_repository(self, reponame): + params = {'id': 1, 'name': reponame} + return Mock(Repository, 'mock', params, self.env.log) + + def get_perm(self, username, *args): + perm = PermissionCache(self.env, username) + if args: + return perm(*args) + return perm + def test_unicode_username(self): resource = Resource('wiki', 'WikiStart') + + perm = self.get_perm('anonymous') self.assertEqual( False, - self.check_permission('WIKI_VIEW', 'anonymous', resource, None)) + self.check_permission('WIKI_VIEW', 'anonymous', resource, perm)) + self.assertNotIn('WIKI_VIEW', perm) + self.assertNotIn('WIKI_VIEW', perm(resource)) + + perm = self.get_perm(u'änon') self.assertEqual( True, - self.check_permission('WIKI_VIEW', u'änon', resource, None)) + self.check_permission('WIKI_VIEW', u'änon', resource, perm)) + self.assertNotIn('WIKI_VIEW', perm) + self.assertIn('WIKI_VIEW', perm(resource)) def test_unicode_resource_name(self): resource = Resource('wiki', u'résumé') + + perm = self.get_perm('anonymous') self.assertEqual( False, - self.check_permission('WIKI_VIEW', 'anonymous', resource, None)) + self.check_permission('WIKI_VIEW', 'anonymous', resource, perm)) + self.assertNotIn('WIKI_VIEW', perm) + self.assertNotIn('WIKI_VIEW', perm(resource)) + + perm = self.get_perm(u'änon') self.assertEqual( False, - self.check_permission('WIKI_VIEW', u'änon', resource, None)) + self.check_permission('WIKI_VIEW', u'änon', resource, perm)) + self.assertNotIn('WIKI_VIEW', perm) + self.assertNotIn('WIKI_VIEW', perm(resource)) + + perm = self.get_perm(u'éat') self.assertEqual( True, - self.check_permission('WIKI_VIEW', u'éat', resource, None)) + self.check_permission('WIKI_VIEW', u'éat', resource, perm)) + self.assertNotIn('WIKI_VIEW', perm) + self.assertIn('WIKI_VIEW', perm(resource)) + + def test_resource_without_id(self): + perm = self.get_perm('anonymous') + self.assertNotIn('TICKET_VIEW', perm) + self.assertNotIn('TICKET_VIEW', perm('ticket')) + self.assertNotIn('TICKET_VIEW', perm('ticket', 42)) + self.assertNotIn('TICKET_VIEW', perm('ticket', 43)) + + perm = self.get_perm(u'änon') + self.assertNotIn('TICKET_VIEW', perm) + self.assertNotIn('TICKET_VIEW', perm('ticket')) + self.assertNotIn('TICKET_VIEW', perm('ticket', 42)) + self.assertIn('TICKET_VIEW', perm('ticket', 43)) + + perm = self.get_perm(u'éat') + self.assertNotIn('TICKET_VIEW', perm) + self.assertIn('TICKET_VIEW', perm('ticket')) + self.assertIn('TICKET_VIEW', perm('ticket', 42)) + self.assertNotIn('TICKET_VIEW', perm('ticket', 43)) + + def test_default_repository(self): + repos = self.get_repository('') + self.assertEqual(False, repos.is_viewable(self.get_perm('anonymous'))) + self.assertEqual(False, repos.is_viewable(self.get_perm(u'änon'))) + self.assertEqual(True, repos.is_viewable(self.get_perm(u'éat'))) + + def test_non_default_repository(self): + repos = self.get_repository(u'bláh') + self.assertEqual(False, repos.is_viewable(self.get_perm('anonymous'))) + self.assertEqual(True, repos.is_viewable(self.get_perm(u'änon'))) + self.assertEqual(True, repos.is_viewable(self.get_perm(u'éat'))) + + def test_case_sensitive_resource(self): + resource = Resource('WIKI', 'wikistart') + self.assertEqual( + None, + self.check_permission('WIKI_VIEW', 'anonymous', resource, None)) + self.assertEqual( + None, + self.check_permission('WIKI_VIEW', u'änon', resource, None)) def test_get_authz_file(self): """get_authz_file should resolve a relative path and lazily compute. @@ -109,10 +206,21 @@ administrators = éat self.assertRaises(ConfigurationError, getattr, self.authz_policy, 'get_authz_file') - def test_parse_authz_empty_raises(self): - """ConfigurationError should be raised if the file is empty.""" - create_file(self.authz_file, "") - self.assertRaises(ConfigurationError, self.authz_policy.parse_authz) + def test_parse_authz_empty(self): + """Allow the file to be empty.""" + create_file(self.authz_file, '') + self.authz_policy.parse_authz() + self.assertFalse(self.authz_policy.authz) + + def test_parse_authz_no_settings(self): + """Allow the file to have no settings.""" + create_file(self.authz_file, """\ +# [wiki:WikiStart] +# änon = WIKI_VIEW +# * = +""") + self.authz_policy.parse_authz() + self.assertFalse(self.authz_policy.authz) def test_parse_authz_malformed_raises(self): """ConfigurationError should be raised if the file is malformed.""" Modified: bloodhound/vendor/trac/current/tracopt/ticket/commit_updater.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/ticket/commit_updater.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/ticket/commit_updater.py (original) +++ bloodhound/vendor/trac/current/tracopt/ticket/commit_updater.py Fri Nov 14 11:06:23 2014 @@ -50,7 +50,7 @@ from trac.ticket import Ticket from trac.ticket.notification import TicketNotifyEmail from trac.util.datefmt import utc from trac.util.text import exception_to_unicode -from trac.util.translation import cleandoc_ +from trac.util.translation import _, cleandoc_ from trac.versioncontrol import IRepositoryChangeListener, RepositoryManager from trac.versioncontrol.web_ui.changeset import ChangesetModule from trac.wiki.formatter import format_to_html @@ -209,7 +209,8 @@ In [changeset:"%s"]: def _update_tickets(self, tickets, changeset, comment, date): """Update the tickets with the given comment.""" - perm = PermissionCache(self.env, changeset.author) + authname = self._authname(changeset) + perm = PermissionCache(self.env, authname) for tkt_id, cmds in tickets.iteritems(): try: self.log.debug("Updating ticket #%d", tkt_id) @@ -221,7 +222,7 @@ In [changeset:"%s"]: if cmd(ticket, changeset, ticket_perm) is not False: save = True if save: - ticket.save_changes(changeset.author, comment, date) + ticket.save_changes(authname, comment, date) if save: self._notify(ticket, date) except Exception, e: @@ -251,23 +252,29 @@ In [changeset:"%s"]: functions[cmd] = func return functions + def _authname(self, changeset): + return changeset.author.lower() \ + if self.env.config.getbool('trac', 'ignore_auth_case') \ + else changeset.author + # Command-specific behavior # The ticket isn't updated if all extracted commands return False. def cmd_close(self, ticket, changeset, perm): + authname = self._authname(changeset) if self.check_perms and not 'TICKET_MODIFY' in perm: self.log.info("%s doesn't have TICKET_MODIFY permission for #%d", - changeset.author, ticket.id) + authname, ticket.id) return False ticket['status'] = 'closed' ticket['resolution'] = 'fixed' if not ticket['owner']: - ticket['owner'] = changeset.author + ticket['owner'] = authname def cmd_refs(self, ticket, changeset, perm): if self.check_perms and not 'TICKET_APPEND' in perm: self.log.info("%s doesn't have TICKET_APPEND permission for #%d", - changeset.author, ticket.id) + self._authname(changeset), ticket.id) return False @@ -303,8 +310,8 @@ class CommitTicketReferenceMacro(WikiMac ticket_re = CommitTicketUpdater.ticket_re if not any(int(tkt_id) == int(formatter.context.resource.id) for tkt_id in ticket_re.findall(message)): - return tag.p("(The changeset message doesn't reference this " - "ticket)", class_='hint') + return tag.p(_("(The changeset message doesn't reference this " + "ticket)"), class_='hint') if ChangesetModule(self.env).wiki_format_messages: return tag.div(format_to_html(self.env, formatter.context.child('changeset', rev, parent=resource), Modified: bloodhound/vendor/trac/current/tracopt/ticket/templates/ticket_delete.html URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/ticket/templates/ticket_delete.html?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/ticket/templates/ticket_delete.html (original) +++ bloodhound/vendor/trac/current/tracopt/ticket/templates/ticket_delete.html Fri Nov 14 11:06:23 2014 @@ -1,3 +1,13 @@ +<!--! Copyright (C) 2010-2014 Edgewall Software + + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://trac.edgewall.com/license.html. + + 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/. +--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/git/PyGIT.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/git/PyGIT.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/git/PyGIT.py (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/git/PyGIT.py Fri Nov 14 11:06:23 2014 @@ -225,13 +225,19 @@ class StorageFactory(object): self.__repo)) return self.__inst + @classmethod + def _clean(cls): + """For testing purpose only""" + with StorageFactory.__dict_lock: + cls.__dict.clear() + cls.__dict_nonweak.clear() + class Storage(object): """High-level wrapper around GitCore with in-memory caching""" __SREV_MIN = 4 # minimum short-rev length - class RevCache(tuple): """RevCache(youngest_rev, oldest_rev, rev_dict, tag_set, srev_dict, branch_dict) @@ -476,7 +482,7 @@ class Storage(object): for k, v in self._get_branches()] head_revs = set(v for _, v in new_branches) - rev = ord_rev = 0 + rev = ord_rev = None for ord_rev, revs in enumerate( self.repo.rev_list('--parents', '--topo-order', @@ -969,10 +975,12 @@ class Storage(object): return [rev.strip() for rev in tmp.splitlines()] def history_timerange(self, start, stop): + # retrieve start <= committer-time < stop, + # see CachedRepository.get_changesets() return [ rev.strip() for rev in \ - self.repo.rev_list('--reverse', + self.repo.rev_list('--date-order', '--max-age=%d' % start, - '--min-age=%d' % stop, + '--min-age=%d' % (stop - 1), '--all').splitlines() ] def rev_is_anchestor_of(self, rev1, rev2): Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/git/git_fs.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/git/git_fs.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/git/git_fs.py (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/git/git_fs.py Fri Nov 14 11:06:23 2014 @@ -15,10 +15,12 @@ from __future__ import with_statement from datetime import datetime +import itertools import os import sys from genshi.builder import tag +from genshi.core import Markup from trac.cache import cached from trac.config import BoolOption, IntOption, PathOption, Option @@ -26,10 +28,12 @@ from trac.core import * from trac.util import TracError, shorten_line from trac.util.datefmt import FixedOffset, to_timestamp, format_datetime from trac.util.text import to_unicode, exception_to_unicode +from trac.util.translation import _ from trac.versioncontrol.api import Changeset, Node, Repository, \ IRepositoryConnector, NoSuchChangeset, \ NoSuchNode, IRepositoryProvider -from trac.versioncontrol.cache import CachedRepository, CachedChangeset +from trac.versioncontrol.cache import CACHE_YOUNGEST_REV, CachedRepository, \ + CachedChangeset from trac.versioncontrol.web_ui import IPropertyRenderer from trac.web.chrome import Chrome from trac.wiki import IWikiSyntaxProvider @@ -38,10 +42,7 @@ from tracopt.versioncontrol.git import P class GitCachedRepository(CachedRepository): - """Git-specific cached repository. - - Passes through {display,short,normalize}_rev - """ + """Git-specific cached repository.""" def display_rev(self, rev): return self.short_rev(rev) @@ -51,15 +52,113 @@ class GitCachedRepository(CachedReposito def normalize_rev(self, rev): if not rev: - return self.repos.get_youngest_rev() + return self.get_youngest_rev() normrev = self.repos.git.verifyrev(rev) if normrev is None: raise NoSuchChangeset(rev) return normrev + def get_youngest_rev(self): + # return None if repository is empty + return CachedRepository.get_youngest_rev(self) or None + + def child_revs(self, rev): + return self.repos.child_revs(rev) + + def get_changesets(self, start, stop): + for key, csets in itertools.groupby( + CachedRepository.get_changesets(self, start, stop), + key=lambda cset: cset.date): + csets = list(csets) + if len(csets) == 1: + yield csets[0] + continue + rev_csets = dict((cset.rev, cset) for cset in csets) + while rev_csets: + revs = [rev for rev in rev_csets + if not any(r in rev_csets + for r in self.repos.child_revs(rev))] + for rev in sorted(revs): + yield rev_csets.pop(rev) + def get_changeset(self, rev): return GitCachedChangeset(self, self.normalize_rev(rev), self.env) + def sync(self, feedback=None, clean=False): + if clean: + self.remove_cache() + + metadata = self.metadata + self.save_metadata(metadata) + meta_youngest = metadata.get(CACHE_YOUNGEST_REV) + repos = self.repos + + def is_synced(rev): + for count, in self.env.db_query(""" + SELECT COUNT(*) FROM revision WHERE repos=%s AND rev=%s + """, (self.id, rev)): + return count > 0 + return False + + def traverse(rev, seen, revs=None): + if revs is None: + revs = [] + while True: + if rev in seen: + return revs + seen.add(rev) + if is_synced(rev): + return revs + revs.append(rev) + parent_revs = repos.parent_revs(rev) + if not parent_revs: + return revs + if len(parent_revs) == 1: + rev = parent_revs[0] + continue + idx = len(revs) + traverse(parent_revs.pop(), seen, revs) + for parent in parent_revs: + revs[idx:idx] = traverse(parent, seen) + + while True: + repos.sync() + repos_youngest = repos.youngest_rev + updated = False + seen = set() + + for rev in repos.git.all_revs(): + if repos.child_revs(rev): + continue + revs = traverse(rev, seen) # topology ordered + while revs: + # sync revision from older revision to newer revision + rev = revs.pop() + self.log.info("Trying to sync revision [%s]", rev) + cset = repos.get_changeset(rev) + with self.env.db_transaction as db: + try: + self._insert_changeset(db, rev, cset) + updated = True + except self.env.db_exc.IntegrityError, e: + self.log.info('Revision %s already cached: %r', + rev, e) + db.rollback() + continue + if feedback: + feedback(rev) + + if updated: + continue # sync again + + if meta_youngest != repos_youngest: + with self.env.db_transaction as db: + db(""" + UPDATE repository SET value=%s WHERE id=%s AND name=%s + """, (repos_youngest, self.id, CACHE_YOUNGEST_REV)) + del self.metadata + return + class GitCachedChangeset(CachedChangeset): """Git-specific cached changeset. @@ -321,9 +420,9 @@ class CsetPropertyRenderer(Component): parent_links = intersperse(', ', \ ((sha_link(rev), ' (', - tag.a('diff', - title="Diff against this parent (show the " \ - "changes merged from the other parents)", + tag.a(_("diff"), + title=_("Diff against this parent (show the " + "changes merged from the other parents)"), href=context.href.changeset(current_sha, reponame, old=rev)), ')') @@ -331,15 +430,16 @@ class CsetPropertyRenderer(Component): return tag(list(parent_links), tag.br(), - tag.span(tag("Note: this is a ", - tag.strong("merge"), " changeset, " - "the changes displayed below " - "correspond to the merge itself."), + tag.span(Markup(_("Note: this is a <strong>merge" + "</strong> changeset, the " + "changes displayed below " + "correspond to the merge " + "itself.")), class_='hint'), tag.br(), - tag.span(tag("Use the ", tag.tt("(diff)"), - " links above to see all the changes " - "relative to each parent."), + tag.span(Markup(_("Use the <tt>(diff)</tt> links " + "above to see all the changes " + "relative to each parent.")), class_='hint')) # simple non-merge commit @@ -379,35 +479,37 @@ class GitRepository(Repository): self.use_committer_id = use_committer_id try: - self.git = PyGIT.StorageFactory(path, log, not persistent_cache, - git_bin=git_bin, - git_fs_encoding=git_fs_encoding) \ - .getInstance() + factory = PyGIT.StorageFactory(path, log, not persistent_cache, + git_bin=git_bin, + git_fs_encoding=git_fs_encoding) + self._git = factory.getInstance() except PyGIT.GitError, e: log.error(exception_to_unicode(e)) raise TracError("%s does not appear to be a Git " "repository." % path) - Repository.__init__(self, 'git:'+path, self.params, log) - self._rev_cache_id = str(self.id) + Repository.__init__(self, 'git:' + path, self.params, log) + self._cached_git_id = str(self.id) def close(self): - self.git = None + self._git = None - @cached('_rev_cache_id') - def _rev_cache(self): - self.git.invalidate_rev_cache() - - def _check_rev_cache(self): + @property + def git(self): if self.persistent_cache: - self._rev_cache + return self._cached_git + else: + return self._git + + @cached('_cached_git_id') + def _cached_git(self): + self._git.invalidate_rev_cache() + return self._git def get_youngest_rev(self): - self._check_rev_cache() return self.git.youngest_rev() def get_oldest_rev(self): - self._check_rev_cache() return self.git.oldest_rev() def normalize_path(self, path): @@ -432,7 +534,6 @@ class GitRepository(Repository): return GitNode(self, path, rev, self.log, None, historian) def get_quickjump_entries(self, rev): - self._check_rev_cache() for bname, bsha in self.git.get_branches(): yield 'branches', bname, '/', bsha for t in self.git.get_tags(): @@ -513,7 +614,7 @@ class GitRepository(Repository): revs = set(self.git.all_revs()) if self.persistent_cache: - del self._rev_cache # invalidate persistent cache + del self._cached_git # invalidate persistent cache if not self.git.sync(): return None # nothing expected to change @@ -532,11 +633,16 @@ class GitNode(Node): self.fs_sha = None # points to either tree or blobs self.fs_perm = None self.fs_size = None - rev = rev and str(rev) or 'HEAD' + if rev: + rev = repos.normalize_rev(to_unicode(rev)) + else: + rev = repos.youngest_rev kind = Node.DIRECTORY p = path.strip('/') - if p: # ie. not the root-tree + if p: # ie. not the root-tree + if not rev: + raise NoSuchNode(path, rev) if not ls_tree_info: ls_tree_info = repos.git.ls_tree(rev, p) or None if ls_tree_info: @@ -595,6 +701,8 @@ class GitNode(Node): self.repos.git.blame(self.rev,self.__git_path())] def get_entries(self): + if not self.rev: # if empty repository + return if not self.isdir: return @@ -620,6 +728,8 @@ class GitNode(Node): return self.fs_size def get_history(self, limit=None): + if not self.rev: # if empty repository + return # TODO: find a way to follow renames/copies for is_last, rev in _last_iterable(self.repos.git.history(self.rev, self.__git_path(), limit)): Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/PyGIT.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/PyGIT.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/PyGIT.py (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/git/tests/PyGIT.py Fri Nov 14 11:06:23 2014 @@ -16,17 +16,23 @@ from __future__ import with_statement import os import tempfile import unittest +from datetime import datetime from subprocess import Popen, PIPE +import trac.tests.compat from trac.test import locate, EnvironmentStub -from trac.tests import compat from trac.tests.compat import rmtree from trac.util import create_file from trac.util.compat import close_fds -from trac.versioncontrol.api import Changeset, DbRepositoryProvider +from trac.versioncontrol.api import Changeset, DbRepositoryProvider, \ + RepositoryManager from tracopt.versioncontrol.git.git_fs import GitConnector from tracopt.versioncontrol.git.PyGIT import GitCore, GitError, Storage, \ StorageFactory, parse_commit +from tracopt.versioncontrol.git.tests.git_fs import GitCommandMixin + + +git_bin = None class GitTestCase(unittest.TestCase): @@ -150,37 +156,28 @@ signature automatically. Yay. The bran prettier. I'll tell Ted to use nicer tag names for future cases.""", msg) -class NormalTestCase(unittest.TestCase): +class NormalTestCase(unittest.TestCase, GitCommandMixin): def setUp(self): self.env = EnvironmentStub() self.repos_path = tempfile.mkdtemp(prefix='trac-gitrepos-') - self.git_bin = locate('git') # create git repository and master branch - self._git('init', self.repos_path) + self._git('init') self._git('config', 'core.quotepath', 'true') # ticket:11198 self._git('config', 'user.name', "Joe") self._git('config', 'user.email', "j...@example.com") create_file(os.path.join(self.repos_path, '.gitignore')) self._git('add', '.gitignore') - self._git('commit', '-a', '-m', 'test', - '--date', 'Tue Jan 1 18:04:56 2013 +0900') + self._git_commit('-a', '-m', 'test', + date=datetime(2013, 1, 1, 9, 4, 56)) def tearDown(self): + RepositoryManager(self.env).reload_repositories() + StorageFactory._clean() self.env.reset_db() if os.path.isdir(self.repos_path): rmtree(self.repos_path) - def _git(self, *args): - args = [self.git_bin] + list(args) - proc = Popen(args, stdout=PIPE, stderr=PIPE, close_fds=close_fds, - cwd=self.repos_path) - stdout, stderr = proc.communicate() - self.assertEqual(0, proc.returncode, - 'git exits with %r, stdout %r, stderr %r' % (proc.returncode, - stdout, stderr)) - return proc - def _factory(self, weak, path=None): if path is None: path = os.path.join(self.repos_path, '.git') @@ -189,7 +186,7 @@ class NormalTestCase(unittest.TestCase): def _storage(self, path=None): if path is None: path = os.path.join(self.repos_path, '.git') - return Storage(path, self.env.log, self.git_bin, 'utf-8') + return Storage(path, self.env.log, git_bin, 'utf-8') def test_control_files_detection(self): # Exception not raised when path points to ctrl file dir @@ -209,8 +206,8 @@ class NormalTestCase(unittest.TestCase): create_file(os.path.join(self.repos_path, 'ticket11598.txt')) self._git('add', 'ticket11598.txt') - self._git('commit', '-m', message, - '--date', 'Thu May 9 20:05:21 2013 +0900') + self._git_commit('-m', message, + date=datetime(2013, 5, 9, 11, 5, 21)) storage = self._storage() branches = sorted(storage.get_branches()) @@ -229,8 +226,8 @@ class NormalTestCase(unittest.TestCase): create_file(os.path.join(self.repos_path, 'ticket11215.txt')) self._git('add', 'ticket11215.txt') - self._git('commit', '-m', 'ticket11215', - '--date', 'Fri Jun 28 03:26:02 2013 +0900') + self._git_commit('-m', 'ticket11215', + date=datetime(2013, 6, 27, 18, 26, 2)) repos.sync() rev = repos.youngest_rev @@ -250,8 +247,8 @@ class NormalTestCase(unittest.TestCase): repos = self.env.get_repository('gitrepos') parent_rev = repos.youngest_rev - self._git('commit', '-m', 'ticket:11328', '--allow-empty', - '--date', 'Tue Oct 15 18:46:27 2013 +0900') + self._git_commit('-m', 'ticket:11328', '--allow-empty', + date=datetime(2013, 10, 15, 9, 46, 27)) repos.sync() rev = repos.youngest_rev @@ -271,8 +268,8 @@ class NormalTestCase(unittest.TestCase): self._git('checkout', 'master') create_file(os.path.join(self.repos_path, 'newfile.txt')) self._git('add', 'newfile.txt') - self._git('commit', '-m', 'added newfile.txt to master', - '--date', 'Mon Dec 23 15:52:23 2013 +0900') + self._git_commit('-m', 'added newfile.txt to master', + date=datetime(2013, 12, 23, 6, 52, 23)) storage = self._storage() storage.sync() @@ -290,48 +287,37 @@ class NormalTestCase(unittest.TestCase): create_file(os.path.join(self.repos_path, 'newfile.txt')) self._git('add', 'newfile.txt') - self._git('commit', '-m', 'test_turn_off_persistent_cache', - '--date', 'Wed, 29 Jan 2014 22:13:25 +0900') + self._git_commit('-m', 'test_turn_off_persistent_cache', + date=datetime(2014, 1, 29, 13, 13, 25)) # persistent_cache is disabled rev = self._factory(True).getInstance().youngest_rev() self.assertNotEqual(rev, parent_rev) -class UnicodeNameTestCase(unittest.TestCase): +class UnicodeNameTestCase(unittest.TestCase, GitCommandMixin): def setUp(self): self.env = EnvironmentStub() self.repos_path = tempfile.mkdtemp(prefix='trac-gitrepos-') - self.git_bin = locate('git') # create git repository and master branch - self._git('init', self.repos_path) + self._git('init') self._git('config', 'core.quotepath', 'true') # ticket:11198 self._git('config', 'user.name', "Joé") # passing utf-8 bytes self._git('config', 'user.email', "j...@example.com") create_file(os.path.join(self.repos_path, '.gitignore')) self._git('add', '.gitignore') - self._git('commit', '-a', '-m', 'test', - '--date', 'Tue Jan 1 18:04:57 2013 +0900') + self._git_commit('-a', '-m', 'test', + date=datetime(2013, 1, 1, 9, 4, 57)) def tearDown(self): self.env.reset_db() if os.path.isdir(self.repos_path): rmtree(self.repos_path) - def _git(self, *args): - args = [self.git_bin] + list(args) - proc = Popen(args, stdout=PIPE, stderr=PIPE, close_fds=close_fds, - cwd=self.repos_path) - stdout, stderr = proc.communicate() - self.assertEqual(0, proc.returncode, - 'git exits with %r, stdout %r, stderr %r' % (proc.returncode, - stdout, stderr)) - return proc - def _storage(self): path = os.path.join(self.repos_path, '.git') - return Storage(path, self.env.log, self.git_bin, 'utf-8') + return Storage(path, self.env.log, git_bin, 'utf-8') def test_unicode_verifyrev(self): storage = self._storage() @@ -341,8 +327,7 @@ class UnicodeNameTestCase(unittest.TestC def test_unicode_filename(self): create_file(os.path.join(self.repos_path, 'tickét.txt')) self._git('add', 'tickét.txt') - self._git('commit', '-m', 'unicode-filename', - '--date', 'Sun Feb 3 18:30 2013 +0100') + self._git_commit('-m', 'unicode-filename', date='1359912600 +0100') storage = self._storage() filenames = sorted(fname for mode, type, sha, size, fname in storage.ls_tree('HEAD')) @@ -386,8 +371,8 @@ class UnicodeNameTestCase(unittest.TestC path_utf8 = path.encode('utf-8') create_file(os.path.join(self.repos_path, path_utf8)) self._git('add', path_utf8) - self._git('commit', '-m', 'ticket:11180 and ticket:11198', - '--date', 'Fri Aug 30 00:48:57 2013 +0900') + self._git_commit('-m', 'ticket:11180 and ticket:11198', + date=datetime(2013, 4, 30, 13, 48, 57)) storage = self._storage() rev = storage.head() @@ -407,8 +392,8 @@ class UnicodeNameTestCase(unittest.TestC path_utf8 = path.encode('utf-8') create_file(os.path.join(self.repos_path, path_utf8)) self._git('add', path_utf8) - self._git('commit', '-m', 'ticket:11180 and ticket:11198', - '--date', 'Fri Aug 30 00:48:57 2013 +0900') + self._git_commit('-m', 'ticket:11180 and ticket:11198', + date=datetime(2013, 4, 30, 17, 48, 57)) def validate(path, quotepath): self._git('config', 'core.quotepath', quotepath) @@ -575,9 +560,10 @@ class UnicodeNameTestCase(unittest.TestC def suite(): + global git_bin suite = unittest.TestSuite() - git = locate("git") - if git: + git_bin = locate('git') + if git_bin: suite.addTest(unittest.makeSuite(GitTestCase)) suite.addTest(unittest.makeSuite(TestParseCommit)) suite.addTest(unittest.makeSuite(NormalTestCase)) Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_fs.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_fs.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_fs.py (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_fs.py Fri Nov 14 11:06:23 2014 @@ -53,6 +53,7 @@ import os.path import re import weakref import posixpath +from urllib import quote from trac.config import ListOption, ChoiceOption from trac.core import * @@ -385,7 +386,8 @@ class SubversionRepository(Repository): assert self.scope[0] == '/' # we keep root_path_utf8 for RA ra_prefix = 'file:///' if os.name == 'nt' else 'file://' - self.ra_url_utf8 = _svn_uri_canonicalize(ra_prefix + root_path_utf8) + self.ra_url_utf8 = _svn_uri_canonicalize(ra_prefix + + quote(root_path_utf8)) self.clear() def clear(self, youngest_rev=None): @@ -499,7 +501,7 @@ class SubversionRepository(Repository): specifications. No revision given means use the latest. """ path = path or '' - if path and path[-1] == '/': + if path and path != '/' and path[-1] == '/': path = path[:-1] rev = self.normalize_rev(rev) or self.youngest_rev return SubversionNode(path, rev, self, self.pool) @@ -517,6 +519,18 @@ class SubversionRepository(Repository): revs.append(r) return revs + def _get_changed_revs(self, node_infos): + path_revs = {} + for node, first in node_infos: + path = node.path + revs = [] + for p, r, chg in node.get_history(): + if p != path or r < first: + break + revs.append(r) + path_revs[path] = revs + return path_revs + def _history(self, path, start, end, pool): """`path` is a unicode path in the scope. @@ -664,14 +678,6 @@ class SubversionRepository(Repository): (wraps ``repos.svn_repos_dir_delta``) """ - def key(value): - return value[1].path if value[1] is not None else value[0].path - return iter(sorted(self._get_changes(old_path, old_rev, new_path, - new_rev, ignore_ancestry), - key=key)) - - def _get_changes(self, old_path, old_rev, new_path, new_rev, - ignore_ancestry): old_node = new_node = None old_rev = self.normalize_rev(old_rev) new_rev = self.normalize_rev(new_rev) @@ -712,8 +718,12 @@ class SubversionRepository(Repository): entry_props, ignore_ancestry, subpool()) - for path, kind, change in editor.deltas: - path = _from_svn(path) + # sort deltas by path before creating `SubversionNode`s to reduce + # memory usage (#10978) + deltas = sorted(((_from_svn(path), kind, change) + for path, kind, change in editor.deltas), + key=lambda entry: entry[0]) + for path, kind, change in deltas: old_node = new_node = None if change != Changeset.ADD: old_node = self.get_node(posixpath.join(old_path, path), @@ -837,7 +847,7 @@ class SubversionNode(Node): rev = _svn_rev(self.rev) start = _svn_rev(0) file_url_utf8 = posixpath.join(self.repos.ra_url_utf8, - self._scoped_path_utf8) + quote(self._scoped_path_utf8)) # svn_client_blame2() requires a canonical uri since # Subversion 1.7 (#11167) file_url_utf8 = _svn_uri_canonicalize(file_url_utf8) Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_prop.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_prop.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_prop.py (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/svn_prop.py Fri Nov 14 11:06:23 2014 @@ -154,7 +154,7 @@ class SubversionPropertyRenderer(Compone def _render_needslock(self, context): return tag.img(src=context.href.chrome('common/lock-locked.png'), - alt="needs lock", title="needs lock") + alt=_("needs lock"), title=_("needs lock")) def _render_mergeinfo(self, name, mode, context, props): rows = [] @@ -197,6 +197,7 @@ class SubversionMergePropertyRenderer(Co if path not in branch_starts: branch_starts[path] = rev + 1 rows = [] + eligible_infos = [] if name.startswith('svnmerge-'): sources = props[name].split() else: @@ -232,9 +233,9 @@ class SubversionMergePropertyRenderer(Co if blocked: eligible -= set(Ranges(blocked)) if eligible: - nrevs = repos._get_node_revs(spath, max(eligible), - min(eligible)) - eligible &= set(nrevs) + node = repos.get_node(spath, max(eligible)) + eligible_infos.append((spath, node, eligible, row)) + continue eligible = to_ranges(eligible) row.append(_get_revs_link(_('eligible'), context, spath, eligible)) @@ -246,6 +247,22 @@ class SubversionMergePropertyRenderer(Co rows.append((deleted, spath, [tag.td('/' + spath), tag.td(revs, colspan=revs_cols)])) + + # fetch eligible revisions for each path at a time + changed_revs = {} + changed_nodes = [(node, min(eligible)) + for spath, node, eligible, row in eligible_infos] + if changed_nodes: + changed_revs = repos._get_changed_revs(changed_nodes) + for spath, node, eligible, row in eligible_infos: + if spath in changed_revs: + eligible &= set(changed_revs[spath]) + else: + eligible.clear() + row.append(_get_revs_link(_("eligible"), context, spath, + to_ranges(eligible))) + rows.append((False, spath, [tag.td(each) for each in row])) + if not rows: return None rows.sort() @@ -346,33 +363,52 @@ class SubversionMergePropertyDiffRendere 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 = [] + + sources = [] + changed_revs = {} + changed_nodes = [] 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: + new_spath = spath not in old_sources + if new_spath: old_revs = old_revs_ni = set() - status = _(' (added)') + else: + old_revs, old_revs_ni = old_sources.pop(spath) added = new_revs - old_revs removed = old_revs - new_revs + # unless new revisions differ from old revisions + if not added and not removed: + continue added_ni = new_revs_ni - old_revs_ni removed_ni = old_revs_ni - new_revs_ni + revs = sorted(added | removed | added_ni | removed_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 + node = repos.get_node(spath, revs[-1]) + changed_nodes.append((node, revs[0])) except NoSuchNode: pass + sources.append((spath, new_spath, added, removed, added_ni, + removed_ni)) + if changed_nodes: + changed_revs = repos._get_changed_revs(changed_nodes) + + 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_spath, added, removed, added_ni, removed_ni in sources: + if spath in changed_revs: + revs = set(changed_revs[spath]) + added &= revs + removed &= revs if added or removed: + added_ni &= revs + removed_ni &= revs + if new_spath: + status = _(" (added)") + else: + status = None modified_sources.append(( spath, [_get_source_link(spath, new_context), status], added and tag(added_label, revs_link(added, new_context)), Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svn_fs.py URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svn_fs.py?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svn_fs.py (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svn_fs.py Fri Nov 14 11:06:23 2014 @@ -17,8 +17,6 @@ from datetime import datetime import new import os.path -import stat -import shutil import tempfile import unittest @@ -30,26 +28,37 @@ try: except ImportError: has_svn = False -from trac.test import EnvironmentStub, TestSetup -from trac.tests import compat +from genshi.core import Stream + +import trac.tests.compat +from trac.test import EnvironmentStub, Mock, MockPerm, TestSetup from trac.core import TracError +from trac.mimeview.api import Context from trac.resource import Resource, resource_exists from trac.util.concurrency import get_thread_id from trac.util.datefmt import utc -from trac.versioncontrol import DbRepositoryProvider, Changeset, Node, \ - NoSuchChangeset -from tracopt.versioncontrol.svn import svn_fs +from trac.versioncontrol.api import DbRepositoryProvider, Changeset, Node, \ + NoSuchChangeset, RepositoryManager +from trac.versioncontrol import svn_fs, svn_prop +from trac.web.href import Href -REPOS_PATH = os.path.join(tempfile.gettempdir(), 'trac-svnrepos') +REPOS_PATH = None REPOS_NAME = 'repo' URL = 'svn://test' -HEAD = 28 +HEAD = 29 TETE = 26 NATIVE_EOL = '\r\n' if os.name == 'nt' else '\n' +def _create_context(): + req = Mock(base_path='', chrome={}, args={}, session={}, + abs_href=Href('/'), href=Href('/'), locale=None, + perm=MockPerm(), authname='anonymous', tz=utc) + return Context.from_request(req) + + class SubversionRepositoryTestSetup(TestSetup): def setUp(self): @@ -61,8 +70,6 @@ class SubversionRepositoryTestSetup(Test pool = core.svn_pool_create(None) dumpstream = None try: - if os.path.exists(REPOS_PATH): - print 'trouble ahead with db/rep-cache.db... see #8278' r = repos.svn_repos_create(REPOS_PATH, '', '', None, None, pool) if hasattr(repos, 'svn_repos_load_fs2'): repos.svn_repos_load_fs2(r, dumpfile, StringIO(), @@ -147,6 +154,14 @@ class NormalTests(object): self.assertTrue(self.repos.has_node(u'/tête/dir1')) def test_get_node(self): + node = self.repos.get_node(u'/') + self.assertEqual(u'', node.name) + self.assertEqual(u'/', node.path) + self.assertEqual(Node.DIRECTORY, node.kind) + self.assertEqual(HEAD, node.rev) + self.assertEqual(HEAD, node.created_rev) + self.assertEqual(datetime(2014, 4, 14, 16, 49, 44, 990695, utc), + node.last_modified) node = self.repos.get_node(u'/tête') self.assertEqual(u'tête', node.name) self.assertEqual(u'/tête', node.path) @@ -390,6 +405,10 @@ En r\xe9sum\xe9 ... \xe7a marche. if os.name != 'nt': del test_get_annotations_lower_drive_letter + def test_get_annotations_with_urlencoded_percent_sign(self): + node = self.repos.get_node(u'/branches/t10386/READ%25ME.txt') + self.assertEqual([14], node.get_annotations()) + # Revision Log / node history def test_get_node_history(self): @@ -709,6 +728,112 @@ En r\xe9sum\xe9 ... \xe7a marche. if os.name != 'posix': del test_canonical_repos_path + def test_merge_prop_renderer_without_deleted_branches(self): + context = _create_context() + context = context(self.repos.get_node('branches/v1x', HEAD).resource) + renderer = svn_prop.SubversionMergePropertyRenderer(self.env) + props = {'svn:mergeinfo': u"""\ +/tête:1-20,23-26 +/branches/v3:22 +/branches/v2:16 +"""} + result = Stream(renderer.render_property('svn:mergeinfo', 'browser', + context, props)) + + node = unicode(result.select('//tr[1]//td[1]')) + self.assertIn(' href="/browser/repo/branches/v2?rev=%d"' % HEAD, node) + self.assertIn('>/branches/v2</a>', node) + node = unicode(result.select('//tr[1]//td[2]')) + self.assertIn(' title="16"', node) + self.assertIn('>merged</a>', node) + node = unicode(result.select('//tr[1]//td[3]')) + self.assertIn(' title="No revisions"', node) + self.assertIn('>eligible</span>', node) + + node = unicode(result.select('//tr[3]//td[1]')) + self.assertIn(' href="/browser/repo/%s?rev=%d"' % ('t%C3%AAte', HEAD), + node) + self.assertIn(u'>/tête</a>', node) + node = unicode(result.select('//tr[3]//td[2]')) + self.assertIn(' title="1-20, 23-26"', node) + self.assertIn(' href="/log/repo/t%C3%AAte?revs=1-20%2C23-26"', node) + self.assertIn('>merged</a>', node) + node = unicode(result.select('//tr[3]//td[3]')) + self.assertIn(' title="21"', node) + self.assertIn(' href="/changeset/21/repo/t%C3%AAte"', node) + self.assertIn('>eligible</a>', node) + + self.assertNotIn('(toggle deleted branches)', unicode(result)) + + def test_merge_prop_renderer_with_deleted_branches(self): + context = _create_context() + context = context(self.repos.get_node('branches/v1x', HEAD).resource) + renderer = svn_prop.SubversionMergePropertyRenderer(self.env) + props = {'svn:mergeinfo': u"""\ +/tête:19 +/branches/v3:22 +/branches/deleted:1,3-5,22 +"""} + result = Stream(renderer.render_property('svn:mergeinfo', 'browser', + context, props)) + + node = unicode(result.select('//tr[1]//td[1]')) + self.assertIn(' href="/browser/repo/branches/v3?rev=%d"' % HEAD, node) + self.assertIn('>/branches/v3</a>', node) + node = unicode(result.select('//tr[1]//td[2]')) + self.assertIn(' title="22"', node) + self.assertIn('>merged</a>', node) + node = unicode(result.select('//tr[1]//td[3]')) + self.assertIn(' title="No revisions"', node) + self.assertIn('>eligible</span>', node) + + node = unicode(result.select('//tr[2]//td[1]')) + self.assertIn(' href="/browser/repo/%s?rev=%d"' % ('t%C3%AAte', HEAD), + node) + self.assertIn(u'>/tête</a>', node) + node = unicode(result.select('//tr[2]//td[2]')) + self.assertIn(' title="19"', node) + self.assertIn(' href="/changeset/19/repo/t%C3%AAte"', node) + self.assertIn('>merged</a>', node) + node = unicode(result.select('//tr[2]//td[3]')) + self.assertIn(' title="13-14, 17-18, 20-21, 23-26"', node) + self.assertIn(' href="/log/repo/t%C3%AAte?revs=' + '13-14%2C17-18%2C20-21%2C23-26"', node) + self.assertIn('>eligible</a>', node) + + self.assertIn('(toggle deleted branches)', unicode(result)) + self.assertIn('<td>/branches/deleted</td>', + unicode(result.select('//tr[3]//td[1]'))) + self.assertIn(u'<td colspan="2">1,\u200b3-5,\u200b22</td>', + unicode(result.select('//tr[3]//td[2]'))) + + def test_merge_prop_diff_renderer_added(self): + context = _create_context() + old_context = context(self.repos.get_node(u'tête', 20).resource) + old_props = {'svn:mergeinfo': u"""\ +/branches/v2:1,8-9,12-15 +/branches/v1x:12 +/branches/deleted:1,3-5,22 +"""} + new_context = context(self.repos.get_node(u'tête', 21).resource) + new_props = {'svn:mergeinfo': u"""\ +/branches/v2:1,8-9,12-16 +/branches/v1x:12 +/branches/deleted:1,3-5,22 +"""} + options = {} + renderer = svn_prop.SubversionMergePropertyDiffRenderer(self.env) + result = Stream(renderer.render_property_diff( + 'svn:mergeinfo', old_context, old_props, new_context, + new_props, options)) + + node = unicode(result.select('//tr[1]//td[1]')) + self.assertIn(' href="/browser/repo/branches/v2?rev=21"', node) + self.assertIn('>/branches/v2</a>', node) + node = unicode(result.select('//tr[1]//td[2]')) + self.assertIn(' title="16"', node) + self.assertIn(' href="/changeset/16/repo/branches/v2"', node) + class ScopedTests(object): @@ -1024,6 +1149,10 @@ class SubversionRepositoryTestCase(unitt def tearDown(self): + self.repos.close() + self.repos = None + # clear cached repositories to avoid TypeError on termination (#11505) + RepositoryManager(self.env).reload_repositories() self.env.reset_db() # needed to avoid issue with 'WindowsError: The process cannot access # the file ... being used by another process: ...\rep-cache.db' @@ -1046,11 +1175,16 @@ class SvnCachedRepositoryTestCase(unitte self.env.reset_db() self.repos.close() self.repos = None + # clear cached repositories to avoid TypeError on termination (#11505) + RepositoryManager(self.env).reload_repositories() def suite(): + global REPOS_PATH suite = unittest.TestSuite() if has_svn: + REPOS_PATH = tempfile.mkdtemp(prefix='trac-svnrepos-') + os.rmdir(REPOS_PATH) tests = [(NormalTests, ''), (ScopedTests, u'/tête'), (RecentPathScopedTests, u'/tête/dir1'), @@ -1071,14 +1205,14 @@ def suite(): (SubversionRepositoryTestCase, test), {'path': REPOS_PATH + scope}) suite.addTest(unittest.makeSuite( - tc, 'test', suiteClass=SubversionRepositoryTestSetup)) + tc, suiteClass=SubversionRepositoryTestSetup)) tc = new.classobj('SvnCachedRepository' + test.__name__, (SvnCachedRepositoryTestCase, test), {'path': REPOS_PATH + scope}) for skip in skipped.get(tc.__name__, []): setattr(tc, skip, lambda self: None) # no skip, so we cheat... suite.addTest(unittest.makeSuite( - tc, 'test', suiteClass=SubversionRepositoryTestSetup)) + tc, suiteClass=SubversionRepositoryTestSetup)) else: print("SKIP: tracopt/versioncontrol/svn/tests/svn_fs.py (no svn " "bindings)") Modified: bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svnrepos.dump URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svnrepos.dump?rev=1639602&r1=1639601&r2=1639602&view=diff ============================================================================== --- bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svnrepos.dump (original) +++ bloodhound/vendor/trac/current/tracopt/versioncontrol/svn/tests/svnrepos.dump Fri Nov 14 11:06:23 2014 @@ -1000,3 +1000,41 @@ A test. # $Rev$ is not substituted with no svn:keywords. +Revision-number: 29 +Prop-content-length: 134 +Content-length: 134 + +K 10 +svn:author +V 5 +jomae +K 8 +svn:date +V 27 +2014-04-14T16:49:44.990695Z +K 7 +svn:log +V 34 +blame with urlencoded percent sign +PROPS-END + +Node-path: branches/t10386 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 28 +Node-copyfrom-path: tête + + +Node-path: branches/t10386/READ%25ME.txt +Node-kind: file +Node-action: add +Node-copyfrom-rev: 28 +Node-copyfrom-path: tête/README3.txt +Text-copy-source-md5: 211b820b566541dd49a1283d6476d89f +Text-copy-source-sha1: 7e9f46800519d6ae305f44cc07bd7568be3c9d5c + + +Node-path: branches/t10386/README3.txt +Node-action: delete + +