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
+
+


Reply via email to