You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by gj...@apache.org on 2012/10/16 22:06:19 UTC

svn commit: r1398968 [25/28] - in /incubator/bloodhound/trunk/trac: ./ contrib/ doc/ doc/api/ doc/utils/ sample-plugins/ sample-plugins/permissions/ sample-plugins/workflow/ trac/ trac/admin/ trac/admin/templates/ trac/admin/tests/ trac/db/ trac/db/tes...

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_fs.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_fs.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_fs.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_fs.py Tue Oct 16 20:06:09 2012
@@ -1,8 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2005-2011 Edgewall Software
-# Copyright (C) 2005 Christopher Lenz <cm...@gmx.de>
-# Copyright (C) 2005-2007 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2012 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -12,1076 +10,10 @@
 # This software consists of voluntary contributions made by many
 # individuals. For the exact contribution history, see the revision
 # history and logs, available at http://trac.edgewall.org/log/.
-#
-# Author: Christopher Lenz <cm...@gmx.de>
-#         Christian Boos <cb...@neuf.fr>
-
-"""
-
-Note about Unicode
-------------------
-
-The Subversion bindings are not unicode-aware and they expect to
-receive UTF-8 encoded `string` parameters,
-
-On the other hand, all paths manipulated by Trac are `unicode`
-objects.
-
-Therefore:
-
- * before being handed out to SVN, the Trac paths have to be encoded
-   to UTF-8, using `_to_svn()`
-
- * before being handed out to Trac, a SVN path has to be decoded from
-   UTF-8, using `_from_svn()`
-
-Whenever a value has to be stored as utf8, we explicitly mark the
-variable name with "_utf8", in order to avoid any possible confusion.
-
-Warning: 
-  `SubversionNode.get_content()` returns an object from which one can
-  read a stream of bytes. NO guarantees can be given about what that
-  stream of bytes represents. It might be some text, encoded in some
-  way or another.  SVN properties *might* give some hints about the
-  content, but they actually only reflect the beliefs of whomever set
-  those properties...
-"""
-
-import os.path
-import weakref
-import posixpath
-
-from trac.config import ListOption
-from trac.core import *
-from trac.env import ISystemInfoProvider
-from trac.versioncontrol import Changeset, Node, Repository, \
-                                IRepositoryConnector, \
-                                NoSuchChangeset, NoSuchNode
-from trac.versioncontrol.cache import CachedRepository
-from trac.util import embedded_numbers
-from trac.util.text import exception_to_unicode, to_unicode
-from trac.util.translation import _
-from trac.util.datefmt import from_utimestamp
-
-
-application_pool = None
-
-
-def _import_svn():
-    global fs, repos, core, delta, _kindmap
-    from svn import fs, repos, core, delta
-    _kindmap = {core.svn_node_dir: Node.DIRECTORY,
-                core.svn_node_file: Node.FILE}
-    # Protect svn.core methods from GC
-    Pool.apr_pool_clear = staticmethod(core.apr_pool_clear)
-    Pool.apr_pool_destroy = staticmethod(core.apr_pool_destroy)
-
-def _to_svn(pool, *args):
-    """Expect a pool and a list of `unicode` path components.
-    
-    Returns an UTF-8 encoded string suitable for the Subversion python 
-    bindings (the returned path never starts with a leading "/")
-    """
-    return core.svn_path_canonicalize('/'.join(args).lstrip('/')
-                                                    .encode('utf-8'),
-                                      pool)
-
-def _from_svn(path):
-    """Expect an UTF-8 encoded string and transform it to an `unicode` object
-
-    But Subversion repositories built from conversion utilities can have
-    non-UTF-8 byte strings, so we have to convert using `to_unicode`.
-    """
-    return path and to_unicode(path, 'utf-8')
-    
-# The following 3 helpers deal with unicode paths
-
-def _normalize_path(path):
-    """Remove leading "/", except for the root."""
-    return path and path.strip('/') or '/'
-
-def _path_within_scope(scope, fullpath):
-    """Remove the leading scope from repository paths.
-
-    Return `None` if the path is not is scope.
-    """
-    if fullpath is not None:
-        fullpath = fullpath.lstrip('/')
-        if scope == '/':
-            return _normalize_path(fullpath)
-        scope = scope.strip('/')
-        if (fullpath + '/').startswith(scope + '/'):
-            return fullpath[len(scope) + 1:] or '/'
-
-def _is_path_within_scope(scope, fullpath):
-    """Check whether the given `fullpath` is within the given `scope`"""
-    if scope == '/':
-        return fullpath is not None
-    fullpath = fullpath.lstrip('/') if fullpath else ''
-    scope = scope.strip('/')
-    return (fullpath + '/').startswith(scope + '/')
-
-# svn_opt_revision_t helpers
-
-def _svn_rev(num):
-    value = core.svn_opt_revision_value_t()
-    value.number = num
-    revision = core.svn_opt_revision_t()
-    revision.kind = core.svn_opt_revision_number
-    revision.value = value
-    return revision
-
-def _svn_head():
-    revision = core.svn_opt_revision_t()
-    revision.kind = core.svn_opt_revision_head
-    return revision
-
-# apr_pool_t helpers
-
-def _mark_weakpool_invalid(weakpool):
-    if weakpool():
-        weakpool()._mark_invalid()
-
-
-class Pool(object):
-    """A Pythonic memory pool object"""
-
-    def __init__(self, parent_pool=None):
-        """Create a new memory pool"""
-
-        global application_pool
-        self._parent_pool = parent_pool or application_pool
-
-        # Create pool
-        if self._parent_pool:
-            self._pool = core.svn_pool_create(self._parent_pool())
-        else:
-            # If we are an application-level pool,
-            # then initialize APR and set this pool
-            # to be the application-level pool
-            core.apr_initialize()
-            application_pool = self
-
-            self._pool = core.svn_pool_create(None)
-        self._mark_valid()
-
-    def __call__(self):
-        return self._pool
-
-    def valid(self):
-        """Check whether this memory pool and its parents
-        are still valid"""
-        return hasattr(self,"_is_valid")
-
-    def assert_valid(self):
-        """Assert that this memory_pool is still valid."""
-        assert self.valid()
-
-    def clear(self):
-        """Clear embedded memory pool. Invalidate all subpools."""
-        self.apr_pool_clear(self._pool)
-        self._mark_valid()
-
-    def destroy(self):
-        """Destroy embedded memory pool. If you do not destroy
-        the memory pool manually, Python will destroy it
-        automatically."""
-
-        global application_pool
-
-        self.assert_valid()
-
-        # Destroy pool
-        self.apr_pool_destroy(self._pool)
-
-        # Clear application pool and terminate APR if necessary
-        if not self._parent_pool:
-            application_pool = None
-
-        self._mark_invalid()
-
-    def __del__(self):
-        """Automatically destroy memory pools, if necessary"""
-        if self.valid():
-            self.destroy()
-
-    def _mark_valid(self):
-        """Mark pool as valid"""
-        if self._parent_pool:
-            # Refer to self using a weakreference so that we don't
-            # create a reference cycle
-            weakself = weakref.ref(self)
-
-            # Set up callbacks to mark pool as invalid when parents
-            # are destroyed
-            self._weakref = weakref.ref(self._parent_pool._is_valid,
-                                        lambda x: \
-                                        _mark_weakpool_invalid(weakself))
-
-        # mark pool as valid
-        self._is_valid = lambda: 1
-
-    def _mark_invalid(self):
-        """Mark pool as invalid"""
-        if self.valid():
-            # Mark invalid
-            del self._is_valid
-
-            # Free up memory
-            del self._parent_pool
-            if hasattr(self, "_weakref"):
-                del self._weakref
-
-
-class SvnCachedRepository(CachedRepository):
-    """Subversion-specific cached repository, zero-pads revision numbers
-    in the cache tables.
-    """
-    has_linear_changesets = True
-
-    def db_rev(self, rev):
-        return '%010d' % rev
-
-    def rev_db(self, rev):
-        return int(rev or 0)
-
-
-class SubversionConnector(Component):
-
-    implements(ISystemInfoProvider, IRepositoryConnector)
-
-    branches = ListOption('svn', 'branches', 'trunk, branches/*', doc=
-        """Comma separated list of paths categorized as branches.
-        If a path ends with '*', then all the directory entries found below 
-        that path will be included. 
-        Example: `/trunk, /branches/*, /projectAlpha/trunk, /sandbox/*`
-        """)
-
-    tags = ListOption('svn', 'tags', 'tags/*', doc=
-        """Comma separated list of paths categorized as tags.
-        
-        If a path ends with '*', then all the directory entries found below
-        that path will be included.
-        Example: `/tags/*, /projectAlpha/tags/A-1.0, /projectAlpha/tags/A-v1.1`
-        """)
-
-    error = None
-
-    def __init__(self):
-        self._version = None
-        try:
-            _import_svn()
-            self.log.debug('Subversion bindings imported')
-        except ImportError, e:
-            self.error = e
-            self.log.info('Failed to load Subversion bindings', exc_info=True)
-        else:
-            version = (core.SVN_VER_MAJOR, core.SVN_VER_MINOR,
-                       core.SVN_VER_MICRO)
-            self._version = '%d.%d.%d' % version + core.SVN_VER_TAG
-            if version[0] < 1:
-                self.error = _("Subversion >= 1.0 required, found %(version)s",
-                               version=self._version)
-            Pool()
-
-    # ISystemInfoProvider methods
-    
-    def get_system_info(self):
-        if self._version is not None:
-            yield 'Subversion', self._version
-        
-    # IRepositoryConnector methods
-    
-    def get_supported_types(self):
-        prio = 1
-        if self.error:
-            prio = -1
-        yield ("direct-svnfs", prio * 4)
-        yield ("svnfs", prio * 4)
-        yield ("svn", prio * 2)
-
-    def get_repository(self, type, dir, params):
-        """Return a `SubversionRepository`.
-
-        The repository is wrapped in a `CachedRepository`, unless `type` is
-        'direct-svnfs'.
-        """
-        params.update(tags=self.tags, branches=self.branches)
-        repos = SubversionRepository(dir, params, self.log)
-        if type != 'direct-svnfs':
-            repos = SvnCachedRepository(self.env, repos, self.log)
-        return repos
-
-
-class SubversionRepository(Repository):
-    """Repository implementation based on the svn.fs API."""
-
-    has_linear_changesets = True
-
-    def __init__(self, path, params, log):
-        self.log = log
-        self.pool = Pool()
-        
-        # Remove any trailing slash or else subversion might abort
-        if isinstance(path, unicode):
-            path_utf8 = path.encode('utf-8')
-        else: # note that this should usually not happen (unicode arg expected)
-            path_utf8 = to_unicode(path).encode('utf-8')
-
-        path_utf8 = os.path.normpath(path_utf8).replace('\\', '/')
-        self.path = path_utf8.decode('utf-8')
-        
-        root_path_utf8 = repos.svn_repos_find_root_path(path_utf8, self.pool())
-        if root_path_utf8 is None:
-            raise TracError(_("%(path)s does not appear to be a Subversion "
-                              "repository.", path=to_unicode(path_utf8)))
-
-        try:
-            self.repos = repos.svn_repos_open(root_path_utf8, self.pool())
-        except core.SubversionException, e:
-            raise TracError(_("Couldn't open Subversion repository %(path)s: "
-                              "%(svn_error)s", path=to_unicode(path_utf8),
-                              svn_error=exception_to_unicode(e)))
-        self.fs_ptr = repos.svn_repos_fs(self.repos)
-        
-        self.uuid = fs.get_uuid(self.fs_ptr, self.pool())
-        self.base = 'svn:%s:%s' % (self.uuid, _from_svn(root_path_utf8))
-        name = 'svn:%s:%s' % (self.uuid, self.path)
-
-        Repository.__init__(self, name, params, log)
-
-        # if root_path_utf8 is shorter than the path_utf8, the difference is
-        # this scope (which always starts with a '/')
-        if root_path_utf8 != path_utf8:
-            self.scope = path_utf8[len(root_path_utf8):].decode('utf-8')
-            if not self.scope[-1] == '/':
-                self.scope += '/'
-        else:
-            self.scope = '/'
-        assert self.scope[0] == '/'
-        # we keep root_path_utf8 for  RA 
-        ra_prefix = 'file:///' if os.name == 'nt' else 'file://'
-        self.ra_url_utf8 = ra_prefix + root_path_utf8
-        self.clear()
-
-    def clear(self, youngest_rev=None):
-        """Reset notion of `youngest` and `oldest`"""
-        self.youngest = None
-        if youngest_rev is not None:
-            self.youngest = self.normalize_rev(youngest_rev)
-        self.oldest = None
-
-    def __del__(self):
-        self.close()
-
-    def has_node(self, path, rev=None, pool=None):
-        """Check if `path` exists at `rev` (or latest if unspecified)"""
-        if not pool:
-            pool = self.pool
-        rev = self.normalize_rev(rev)
-        rev_root = fs.revision_root(self.fs_ptr, rev, pool())
-        node_type = fs.check_path(rev_root, _to_svn(pool(), self.scope, path),
-                                  pool())
-        return node_type in _kindmap
-
-    def normalize_path(self, path):
-        """Take any path specification and produce a path suitable for 
-        the rest of the API
-        """
-        return _normalize_path(path)
-
-    def normalize_rev(self, rev):
-        """Take any revision specification and produce a revision suitable
-        for the rest of the API
-        """
-        if rev is None or isinstance(rev, basestring) and \
-               rev.lower() in ('', 'head', 'latest', 'youngest'):
-            return self.youngest_rev
-        else:
-            try:
-                rev = int(rev)
-                if rev <= self.youngest_rev:
-                    return rev
-            except (ValueError, TypeError):
-                pass
-            raise NoSuchChangeset(rev)
-
-    def close(self):
-        """Dispose of low-level resources associated to this repository."""
-        if self.pool:
-            self.pool.destroy()
-        self.repos = self.fs_ptr = self.pool = None
-
-    def get_base(self):
-        """Retrieve the base path corresponding to the Subversion
-        repository itself. 
-
-        This is the same as the `.path` property minus the
-        intra-repository scope, if one was specified.
-        """
-        return self.base
-        
-    def _get_tags_or_branches(self, paths):
-        """Retrieve known branches or tags."""
-        for path in self.params.get(paths, []):
-            if path.endswith('*'):
-                folder = posixpath.dirname(path)
-                try:
-                    entries = [n for n in self.get_node(folder).get_entries()]
-                    for node in sorted(entries, key=lambda n: 
-                                       embedded_numbers(n.path.lower())):
-                        if node.kind == Node.DIRECTORY:
-                            yield node
-                except Exception: # no right (TODO: use a specific Exception)
-                    pass
-            else:
-                try:
-                    yield self.get_node(path)
-                except Exception: # no right
-                    pass
-
-    def get_quickjump_entries(self, rev):
-        """Retrieve known branches, as (name, id) pairs.
-        
-        Purposedly ignores `rev` and always takes the last revision.
-        """
-        for n in self._get_tags_or_branches('branches'):
-            yield 'branches', n.path, n.path, None
-        for n in self._get_tags_or_branches('tags'):
-            yield 'tags', n.path, n.created_path, n.created_rev
-
-    def get_path_url(self, path, rev):
-        """Retrieve the "native" URL from which this repository is reachable
-        from Subversion clients.
-        """
-        url = self.params.get('url', '').rstrip('/')
-        if url:
-            if not path or path == '/':
-                return url
-            return url + '/' + path.lstrip('/')
-    
-    def get_changeset(self, rev):
-        """Produce a `SubversionChangeset` from given revision
-        specification"""
-        rev = self.normalize_rev(rev)
-        return SubversionChangeset(self, rev, self.scope, self.pool)
-
-    def get_changeset_uid(self, rev):
-        """Build a value identifying the `rev` in this repository."""
-        return (self.uuid, rev)
-
-    def get_node(self, path, rev=None):
-        """Produce a `SubversionNode` from given path and optionally revision
-        specifications. No revision given means use the latest.
-        """
-        path = path or ''
-        if path and path[-1] == '/':
-            path = path[:-1]
-        rev = self.normalize_rev(rev) or self.youngest_rev
-        return SubversionNode(path, rev, self, self.pool)
-
-    def _get_node_revs(self, path, last=None, first=None):
-        """Return the revisions affecting `path` between `first` and `last` 
-        revs. If `first` is not given, it goes down to the revision in which
-        the branch was created.
-        """
-        node = self.get_node(path, last)
-        revs = []
-        for (p, r, chg) in node.get_history():
-            if p != path or (first and r < first):
-                break
-            revs.append(r)
-        return revs
-
-    def _history(self, path, start, end, pool):
-        """`path` is a unicode path in the scope.
-
-        Generator yielding `(path, rev)` pairs, where `path` is an `unicode`
-        object. Must start with `(path, created rev)`.
-
-        (wraps ``fs.node_history``)
-        """
-        path_utf8 = _to_svn(pool(), self.scope, path)
-        if start < end:
-            start, end = end, start
-        if (start, end) == (1, 0): # only happens for empty repos
-            return
-        root = fs.revision_root(self.fs_ptr, start, pool())
-        # fs.node_history leaks when path doesn't exist (#6588)
-        if fs.check_path(root, path_utf8, pool()) == core.svn_node_none:
-            return
-        tmp1 = Pool(pool)
-        tmp2 = Pool(pool)
-        history_ptr = fs.node_history(root, path_utf8, tmp1())
-        cross_copies = 1
-        while history_ptr:
-            history_ptr = fs.history_prev(history_ptr, cross_copies, tmp2())
-            tmp1.clear()
-            tmp1, tmp2 = tmp2, tmp1
-            if history_ptr:
-                path_utf8, rev = fs.history_location(history_ptr, tmp2())
-                tmp2.clear()
-                if rev < end:
-                    break
-                path = _from_svn(path_utf8)
-                yield path, rev
-        del tmp1
-        del tmp2
-    
-    def _previous_rev(self, rev, path='', pool=None):
-        if rev > 1: # don't use oldest here, as it's too expensive
-            for _, prev in self._history(path, 1, rev-1, pool or self.pool):
-                return prev
-        return None
-    
-
-    def get_oldest_rev(self):
-        """Gives an approximation of the oldest revision."""
-        if self.oldest is None:
-            self.oldest = 1
-            # trying to figure out the oldest rev for scoped repository
-            # is too expensive and uncovers a big memory leak (#5213)
-            # if self.scope != '/':
-            #    self.oldest = self.next_rev(0, find_initial_rev=True)
-        return self.oldest
-
-    def get_youngest_rev(self):
-        """Retrieve the latest revision in the repository.
-
-        (wraps ``fs.youngest_rev``)
-        """
-        if not self.youngest:
-            self.youngest = fs.youngest_rev(self.fs_ptr, self.pool())
-            if self.scope != '/':
-                for path, rev in self._history('', 1, self.youngest, self.pool):
-                    self.youngest = rev
-                    break
-        return self.youngest
-
-    def previous_rev(self, rev, path=''):
-        """Return revision immediately preceeding `rev`, eventually below 
-        given `path` or globally.
-        """
-        # FIXME optimize for non-scoped
-        rev = self.normalize_rev(rev)
-        return self._previous_rev(rev, path)
-
-    def next_rev(self, rev, path='', find_initial_rev=False):
-        """Return revision immediately following `rev`, eventually below 
-        given `path` or globally.
-        """
-        rev = self.normalize_rev(rev)
-        next = rev + 1
-        youngest = self.youngest_rev
-        subpool = Pool(self.pool)
-        while next <= youngest:
-            subpool.clear()            
-            for _, next in self._history(path, rev+1, next, subpool):
-                return next
-            else:
-                if not find_initial_rev and \
-                         not self.has_node(path, next, subpool):
-                    return next # a 'delete' event is also interesting...
-            next += 1
-        return None
-
-    def rev_older_than(self, rev1, rev2):
-        """Check relative order between two revision specifications."""
-        return self.normalize_rev(rev1) < self.normalize_rev(rev2)
-
-    def get_path_history(self, path, rev=None, limit=None):
-        """Retrieve creation and deletion events that happened on
-        given `path`.
-        """
-        path = self.normalize_path(path)
-        rev = self.normalize_rev(rev)
-        expect_deletion = False
-        subpool = Pool(self.pool)
-        numrevs = 0
-        while rev and (not limit or numrevs < limit):
-            subpool.clear()
-            if self.has_node(path, rev, subpool):
-                if expect_deletion:
-                    # it was missing, now it's there again:
-                    #  rev+1 must be a delete
-                    numrevs += 1
-                    yield path, rev+1, Changeset.DELETE
-                newer = None # 'newer' is the previously seen history tuple
-                older = None # 'older' is the currently examined history tuple
-                for p, r in self._history(path, 1, rev, subpool):
-                    older = (_path_within_scope(self.scope, p), r,
-                             Changeset.ADD)
-                    rev = self._previous_rev(r, pool=subpool)
-                    if newer:
-                        numrevs += 1
-                        if older[0] == path:
-                            # still on the path: 'newer' was an edit
-                            yield newer[0], newer[1], Changeset.EDIT
-                        else:
-                            # the path changed: 'newer' was a copy
-                            rev = self._previous_rev(newer[1], pool=subpool)
-                            # restart before the copy op
-                            yield newer[0], newer[1], Changeset.COPY
-                            older = (older[0], older[1], 'unknown')
-                            break
-                    newer = older
-                if older:
-                    # either a real ADD or the source of a COPY
-                    numrevs += 1
-                    yield older
-            else:
-                expect_deletion = True
-                rev = self._previous_rev(rev, pool=subpool)
-
-    def get_changes(self, old_path, old_rev, new_path, new_rev,
-                    ignore_ancestry=0):
-        """Determine differences between two arbitrary pairs of paths
-        and revisions.
-
-        (wraps ``repos.svn_repos_dir_delta``)
-        """
-        old_node = new_node = None
-        old_rev = self.normalize_rev(old_rev)
-        new_rev = self.normalize_rev(new_rev)
-        if self.has_node(old_path, old_rev):
-            old_node = self.get_node(old_path, old_rev)
-        else:
-            raise NoSuchNode(old_path, old_rev, 'The Base for Diff is invalid')
-        if self.has_node(new_path, new_rev):
-            new_node = self.get_node(new_path, new_rev)
-        else:
-            raise NoSuchNode(new_path, new_rev,
-                             'The Target for Diff is invalid')
-        if new_node.kind != old_node.kind:
-            raise TracError(_('Diff mismatch: Base is a %(oldnode)s '
-                              '(%(oldpath)s in revision %(oldrev)s) and '
-                              'Target is a %(newnode)s (%(newpath)s in '
-                              'revision %(newrev)s).', oldnode=old_node.kind,
-                              oldpath=old_path, oldrev=old_rev,
-                              newnode=new_node.kind, newpath=new_path,
-                              newrev=new_rev))
-        subpool = Pool(self.pool)
-        if new_node.isdir:
-            editor = DiffChangeEditor()
-            e_ptr, e_baton = delta.make_editor(editor, subpool())
-            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool())
-            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool())
-            def authz_cb(root, path, pool):
-                return 1
-            text_deltas = 0 # as this is anyway re-done in Diff.py...
-            entry_props = 0 # "... typically used only for working copy updates"
-            repos.svn_repos_dir_delta(old_root,
-                                      _to_svn(subpool(), self.scope, old_path),
-                                      '', new_root,
-                                      _to_svn(subpool(), self.scope, new_path),
-                                      e_ptr, e_baton, authz_cb,
-                                      text_deltas,
-                                      1, # directory
-                                      entry_props,
-                                      ignore_ancestry,
-                                      subpool())
-            for path, kind, change in editor.deltas:
-                path = _from_svn(path)
-                old_node = new_node = None
-                if change != Changeset.ADD:
-                    old_node = self.get_node(posixpath.join(old_path, path),
-                                             old_rev)
-                if change != Changeset.DELETE:
-                    new_node = self.get_node(posixpath.join(new_path, path),
-                                             new_rev)
-                else:
-                    kind = _kindmap[fs.check_path(old_root,
-                                                  _to_svn(subpool(),
-                                                          self.scope,
-                                                          old_node.path),
-                                                  subpool())]
-                yield  (old_node, new_node, kind, change)
-        else:
-            old_root = fs.revision_root(self.fs_ptr, old_rev, subpool())
-            new_root = fs.revision_root(self.fs_ptr, new_rev, subpool())
-            if fs.contents_changed(old_root,
-                                   _to_svn(subpool(), self.scope, old_path),
-                                   new_root,
-                                   _to_svn(subpool(), self.scope, new_path),
-                                   subpool()):
-                yield (old_node, new_node, Node.FILE, Changeset.EDIT)
-
-
-class SubversionNode(Node):
-
-    def __init__(self, path, rev, repos, pool=None, parent_root=None):
-        self.fs_ptr = repos.fs_ptr
-        self.scope = repos.scope
-        self.pool = Pool(pool)
-        pool = self.pool()
-        self._scoped_path_utf8 = _to_svn(pool, self.scope, path)
-
-        if parent_root:
-            self.root = parent_root
-        else:
-            self.root = fs.revision_root(self.fs_ptr, rev, pool)
-        node_type = fs.check_path(self.root, self._scoped_path_utf8, pool)
-        if not node_type in _kindmap:
-            raise NoSuchNode(path, rev)
-        cp_utf8 = fs.node_created_path(self.root, self._scoped_path_utf8, pool)
-        cp = _from_svn(cp_utf8)
-        cr = fs.node_created_rev(self.root, self._scoped_path_utf8, pool)
-        # Note: `cp` differs from `path` if the last change was a copy,
-        #        In that case, `path` doesn't even exist at `cr`.
-        #        The only guarantees are:
-        #          * this node exists at (path,rev)
-        #          * the node existed at (created_path,created_rev)
-        # Also, `cp` might well be out of the scope of the repository,
-        # in this case, we _don't_ use the ''create'' information.
-        if _is_path_within_scope(self.scope, cp):
-            self.created_rev = cr
-            self.created_path = _path_within_scope(self.scope, cp)
-        else:
-            self.created_rev, self.created_path = rev, path
-        # TODO: check node id
-        Node.__init__(self, repos, path, rev, _kindmap[node_type])
-
-    def get_content(self):
-        """Retrieve raw content as a "read()"able object."""
-        if self.isdir:
-            return None
-        s = core.Stream(fs.file_contents(self.root, self._scoped_path_utf8,
-                                         self.pool()))
-        # The stream object needs to reference the pool to make sure the pool
-        # is not destroyed before the former.
-        s._pool = self.pool
-        return s
-
-    def get_entries(self):
-        """Yield `SubversionNode` corresponding to entries in this directory.
-
-        (wraps ``fs.dir_entries``)
-        """
-        if self.isfile:
-            return
-        pool = Pool(self.pool)
-        entries = fs.dir_entries(self.root, self._scoped_path_utf8, pool())
-        for item in entries.keys():
-            path = posixpath.join(self.path, _from_svn(item))
-            yield SubversionNode(path, self.rev, self.repos, self.pool,
-                                 self.root)
-
-    def get_history(self, limit=None):
-        """Yield change events that happened on this path"""
-        newer = None # 'newer' is the previously seen history tuple
-        older = None # 'older' is the currently examined history tuple
-        pool = Pool(self.pool)
-        numrevs = 0
-        for path, rev in self.repos._history(self.path, 1, self.rev, pool):
-            path = _path_within_scope(self.scope, path)
-            if rev > 0 and path:
-                older = (path, rev, Changeset.ADD)
-                if newer:
-                    if newer[0] == older[0]: # stay on same path
-                        change = Changeset.EDIT
-                    else:
-                        change = Changeset.COPY
-                    newer = (newer[0], newer[1], change)
-                    numrevs += 1
-                    yield newer
-                newer = older
-            if limit and numrevs >= limit:
-                break
-        if newer and (not limit or numrevs < limit):
-            yield newer
-
-    def get_annotations(self):
-        """Return a list the last changed revision for each line.
-        (wraps ``client.blame2``)
-        """
-        annotations = []
-        if self.isfile:
-            def blame_receiver(line_no, revision, author, date, line, pool):
-                annotations.append(revision)
-            try:
-                rev = _svn_rev(self.rev)
-                start = _svn_rev(0)
-                file_url_utf8 = posixpath.join(self.repos.ra_url_utf8,
-                                               self._scoped_path_utf8)
-                self.repos.log.info('opening ra_local session to %r',
-                                    file_url_utf8)
-                from svn import client
-                client.blame2(file_url_utf8, rev, start, rev, blame_receiver,
-                              client.create_context(), self.pool())
-            except (core.SubversionException, AttributeError), e:
-                # svn thinks file is a binary or blame not supported
-                raise TracError(_('svn blame failed on %(path)s: %(error)s',
-                                  path=self.path, error=to_unicode(e)))
-        return annotations
-
-#    def get_previous(self):
-#        # FIXME: redo it with fs.node_history
-
-    def get_properties(self):
-        """Return `dict` of node properties at current revision.
-
-        (wraps ``fs.node_proplist``)
-        """
-        props = fs.node_proplist(self.root, self._scoped_path_utf8, self.pool())
-        for name, value in props.items():
-            # Note that property values can be arbitrary binary values
-            # so we can't assume they are UTF-8 strings...
-            props[_from_svn(name)] = to_unicode(value)
-        return props
-
-    def get_content_length(self):
-        """Retrieve byte size of a file.
-
-        Return `None` for a folder. (wraps ``fs.file_length``)
-        """
-        if self.isdir:
-            return None
-        return fs.file_length(self.root, self._scoped_path_utf8, self.pool())
-
-    def get_content_type(self):
-        """Retrieve mime-type property of a file.
-
-        Return `None` for a folder. (wraps ``fs.revision_prop``)
-        """
-        if self.isdir:
-            return None
-        return self._get_prop(core.SVN_PROP_MIME_TYPE)
-
-    def get_last_modified(self):
-        """Retrieve timestamp of last modification, in micro-seconds.
-
-        (wraps ``fs.revision_prop``)
-        """
-        _date = fs.revision_prop(self.fs_ptr, self.created_rev,
-                                 core.SVN_PROP_REVISION_DATE, self.pool())
-        if not _date:
-            return None
-        return from_utimestamp(core.svn_time_from_cstring(_date, self.pool()))
-
-    def _get_prop(self, name):
-        return fs.node_prop(self.root, self._scoped_path_utf8, name,
-                            self.pool())
-
-    def get_branch_origin(self):
-        """Return the revision in which the node's path was created.
-
-        (wraps ``fs.revision_root_revision(fs.closest_copy)``)
-        """
-        root_and_path = fs.closest_copy(self.root, self._scoped_path_utf8)
-        if root_and_path:
-            return fs.revision_root_revision(root_and_path[0])
-
-    def get_copy_ancestry(self):
-        """Retrieve the list of `(path,rev)` copy ancestors of this node.
-        Most recent ancestor first. Each ancestor `(path, rev)` corresponds 
-        to the path and revision of the source at the time the copy or move
-        operation was performed.
-        """
-        ancestors = []
-        previous = (self._scoped_path_utf8, self.rev, self.root)
-        while previous:
-            (previous_path, previous_rev, previous_root) = previous
-            previous = None
-            root_path = fs.closest_copy(previous_root, previous_path)
-            if root_path:
-                (root, path) = root_path
-                path = path.lstrip('/')
-                rev = fs.revision_root_revision(root)
-                relpath = None
-                if path != previous_path:
-                    # `previous_path` is a subfolder of `path` and didn't
-                    # change since `path` was copied
-                    relpath = previous_path[len(path):].strip('/')
-                copied_from = fs.copied_from(root, path)
-                if copied_from:
-                    (rev, path) = copied_from
-                    path = path.lstrip('/')
-                    root = fs.revision_root(self.fs_ptr, rev, self.pool())
-                    if relpath:
-                        path += '/' + relpath
-                    ui_path = _path_within_scope(self.scope, _from_svn(path))
-                    if ui_path:
-                        ancestors.append((ui_path, rev))
-                    previous = (path, rev, root)
-        return ancestors
-
-
-class SubversionChangeset(Changeset):
-
-    def __init__(self, repos, rev, scope, pool=None):
-        self.rev = rev
-        self.scope = scope
-        self.fs_ptr = repos.fs_ptr
-        self.pool = Pool(pool)
-        try:
-            message = self._get_prop(core.SVN_PROP_REVISION_LOG)
-        except core.SubversionException:
-            raise NoSuchChangeset(rev)
-        author = self._get_prop(core.SVN_PROP_REVISION_AUTHOR)
-        # we _hope_ it's UTF-8, but can't be 100% sure (#4321)
-        message = message and to_unicode(message, 'utf-8')
-        author = author and to_unicode(author, 'utf-8')
-        _date = self._get_prop(core.SVN_PROP_REVISION_DATE)
-        if _date:
-            ts = core.svn_time_from_cstring(_date, self.pool())
-            date = from_utimestamp(ts)
-        else:
-            date = None
-        Changeset.__init__(self, repos, rev, message, author, date)
-
-    def get_properties(self):
-        """Retrieve `dict` of Subversion properties for this revision
-        (revprops)
-        """
-        props = fs.revision_proplist(self.fs_ptr, self.rev, self.pool())
-        properties = {}
-        for k, v in props.iteritems():
-            if k not in (core.SVN_PROP_REVISION_LOG,
-                         core.SVN_PROP_REVISION_AUTHOR,
-                         core.SVN_PROP_REVISION_DATE):
-                properties[k] = to_unicode(v)
-                # Note: the above `to_unicode` has a small probability
-                # to mess-up binary properties, like icons.
-        return properties
-
-    def get_changes(self):
-        """Retrieve file changes for a given revision.
-
-        (wraps ``repos.svn_repos_replay``)
-        """
-        pool = Pool(self.pool)
-        tmp = Pool(pool)
-        root = fs.revision_root(self.fs_ptr, self.rev, pool())
-        editor = repos.RevisionChangeCollector(self.fs_ptr, self.rev, pool())
-        e_ptr, e_baton = delta.make_editor(editor, pool())
-        repos.svn_repos_replay(root, e_ptr, e_baton, pool())
-
-        idx = 0
-        copies, deletions = {}, {}
-        changes = []
-        revroots = {}
-        for path_utf8, change in editor.changes.items():
-            new_path = _from_svn(path_utf8)
-
-            # Filtering on `path`
-            if not _is_path_within_scope(self.scope, new_path):
-                continue
-
-            path_utf8 = change.path
-            base_path_utf8 = change.base_path
-            path = _from_svn(path_utf8)
-            base_path = _from_svn(base_path_utf8)
-            base_rev = change.base_rev
-            change_action = getattr(change, 'action', None)
-
-            # Ensure `base_path` is within the scope
-            if not _is_path_within_scope(self.scope, base_path):
-                base_path, base_rev = None, -1
-
-            # Determine the action
-            if not path and not new_path and self.scope == '/':
-                action = Changeset.EDIT # root property change
-            elif not path or (change_action is not None
-                              and change_action == repos.CHANGE_ACTION_DELETE):
-                if new_path:            # deletion
-                    action = Changeset.DELETE
-                    deletions[new_path.lstrip('/')] = idx
-                else:                   # deletion outside of scope, ignore
-                    continue
-            elif change.added or not base_path: # add or copy
-                action = Changeset.ADD
-                if base_path and base_rev:
-                    action = Changeset.COPY
-                    copies[base_path.lstrip('/')] = idx
-            else:
-                action = Changeset.EDIT
-                # identify the most interesting base_path/base_rev
-                # in terms of last changed information (see r2562)
-                if revroots.has_key(base_rev):
-                    b_root = revroots[base_rev]
-                else:
-                    b_root = fs.revision_root(self.fs_ptr, base_rev, pool())
-                    revroots[base_rev] = b_root
-                tmp.clear()
-                cbase_path_utf8 = fs.node_created_path(b_root, base_path_utf8,
-                                                       tmp())
-                cbase_path = _from_svn(cbase_path_utf8)
-                cbase_rev = fs.node_created_rev(b_root, base_path_utf8, tmp()) 
-                # give up if the created path is outside the scope
-                if _is_path_within_scope(self.scope, cbase_path):
-                    base_path, base_rev = cbase_path, cbase_rev
-
-            kind = _kindmap[change.item_kind]
-            path = _path_within_scope(self.scope, new_path or base_path)
-            base_path = _path_within_scope(self.scope, base_path)
-            changes.append([path, kind, action, base_path, base_rev])
-            idx += 1
-
-        moves = []
-        # a MOVE is a COPY whose `base_path` corresponds to a `new_path`
-        # which has been deleted
-        for k, v in copies.items():
-            if k in deletions:
-                changes[v][2] = Changeset.MOVE
-                moves.append(deletions[k])
-        offset = 0
-        moves.sort()
-        for i in moves:
-            del changes[i - offset]
-            offset += 1
-
-        changes.sort()
-        for change in changes:
-            yield tuple(change)
-
-    def _get_prop(self, name):
-        return fs.revision_prop(self.fs_ptr, self.rev, name, self.pool())
-
-
-#
-# Delta editor for diffs between arbitrary nodes
-#
-# Note 1: the 'copyfrom_path' and 'copyfrom_rev' information is not used
-#         because 'repos.svn_repos_dir_delta' *doesn't* provide it.
-#
-# Note 2: the 'dir_baton' is the path of the parent directory
-#
-
-
-def DiffChangeEditor():
-
-    class DiffChangeEditor(delta.Editor): 
-
-        def __init__(self):
-            self.deltas = []
-    
-        # -- svn.delta.Editor callbacks
-
-        def open_root(self, base_revision, dir_pool):
-            return ('/', Changeset.EDIT)
-
-        def add_directory(self, path, dir_baton, copyfrom_path, copyfrom_rev,
-                          dir_pool):
-            self.deltas.append((path, Node.DIRECTORY, Changeset.ADD))
-            return (path, Changeset.ADD)
-
-        def open_directory(self, path, dir_baton, base_revision, dir_pool):
-            return (path, dir_baton[1])
-
-        def change_dir_prop(self, dir_baton, name, value, pool):
-            path, change = dir_baton
-            if change != Changeset.ADD:
-                self.deltas.append((path, Node.DIRECTORY, change))
-
-        def delete_entry(self, path, revision, dir_baton, pool):
-            self.deltas.append((path, None, Changeset.DELETE))
-
-        def add_file(self, path, dir_baton, copyfrom_path, copyfrom_revision,
-                     dir_pool):
-            self.deltas.append((path, Node.FILE, Changeset.ADD))
-
-        def open_file(self, path, dir_baton, dummy_rev, file_pool):
-            self.deltas.append((path, Node.FILE, Changeset.EDIT))
 
-    return DiffChangeEditor() 
+from trac.util import import_namespace
+import_namespace(globals(), 'tracopt.versioncontrol.svn.svn_fs')
 
+# This module is a stub provided for backward compatibility. The svn_fs
+# module has been moved to tracopt.versioncontrol.svn. Please update your
+# code to use the new location.

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_prop.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_prop.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_prop.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/svn_prop.py Tue Oct 16 20:06:09 2012
@@ -1,8 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2005-2009 Edgewall Software
-# Copyright (C) 2005 Christopher Lenz <cm...@gmx.de>
-# Copyright (C) 2005-2007 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2012 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -12,391 +10,10 @@
 # This software consists of voluntary contributions made by many
 # individuals. For the exact contribution history, see the revision
 # history and logs, available at http://trac.edgewall.org/log/.
-#
-# Author: Christopher Lenz <cm...@gmx.de>
-#         Christian Boos <cb...@neuf.fr>
-
-import posixpath
-
-from genshi.builder import tag
-
-from trac.config import ConfigSection
-from trac.core import *
-from trac.versioncontrol.api import NoSuchNode, RepositoryManager
-from trac.versioncontrol.svn_fs import _path_within_scope
-from trac.versioncontrol.web_ui.browser import IPropertyRenderer
-from trac.versioncontrol.web_ui.changeset import IPropertyDiffRenderer
-from trac.util import Ranges, to_ranges
-from trac.util.translation import _, tag_
-
-
-class SubversionPropertyRenderer(Component):
-
-    implements(IPropertyRenderer)
-
-    svn_externals_section = ConfigSection('svn:externals',
-        """The TracBrowser for Subversion can interpret the `svn:externals`
-        property of folders. By default, it only turns the URLs into links as
-        Trac can't browse remote repositories.
-
-        However, if you have another Trac instance (or an other repository
-        browser like [http://www.viewvc.org/ ViewVC]) configured to browse the
-        target repository, then you can instruct Trac which other repository
-        browser to use for which external URL. This mapping is done in the
-        `[svn:externals]` section of the TracIni.
-        
-        Example:
-        {{{
-        [svn:externals]
-        1 = svn://server/repos1                       http://trac/proj1/browser/$path?rev=$rev
-        2 = svn://server/repos2                       http://trac/proj2/browser/$path?rev=$rev
-        3 = http://theirserver.org/svn/eng-soft       http://ourserver/viewvc/svn/$path/?pathrev=25914
-        4 = svn://anotherserver.com/tools_repository  http://ourserver/tracs/tools/browser/$path?rev=$rev
-        }}}
-        With the above, the
-        `svn://anotherserver.com/tools_repository/tags/1.1/tools` external will
-        be mapped to `http://ourserver/tracs/tools/browser/tags/1.1/tools?rev=`
-        (and `rev` will be set to the appropriate revision number if the
-        external additionally specifies a revision, see the
-        [http://svnbook.red-bean.com/en/1.4/svn.advanced.externals.html SVN Book on externals]
-        for more details).
-        
-        Note that the number used as a key in the above section is purely used
-        as a place holder, as the URLs themselves can't be used as a key due to
-        various limitations in the configuration file parser.
-        
-        Finally, the relative URLs introduced in
-        [http://subversion.apache.org/docs/release-notes/1.5.html#externals Subversion 1.5]
-        are not yet supported.
-
-        (''since 0.11'')""")
-
-    def __init__(self):
-        self._externals_map = {}
-
-    # IPropertyRenderer methods
-
-    def match_property(self, name, mode):
-        if name in ('svn:externals', 'svn:needs-lock'):
-            return 4
-        return 2 if name in ('svn:mergeinfo', 'svnmerge-blocked',
-                             'svnmerge-integrated') else 0
-    
-    def render_property(self, name, mode, context, props):
-        if name == 'svn:externals':
-            return self._render_externals(props[name])
-        elif name == 'svn:needs-lock':
-            return self._render_needslock(context)
-        elif name == 'svn:mergeinfo' or name.startswith('svnmerge-'):
-            return self._render_mergeinfo(name, mode, context, props)
-
-    def _render_externals(self, prop):
-        if not self._externals_map:
-            for dummykey, value in self.svn_externals_section.options():
-                value = value.split()
-                if len(value) != 2:
-                    self.log.warn("svn:externals entry %s doesn't contain "
-                            "a space-separated key value pair, skipping.", 
-                            dummykey)
-                    continue
-                key, value = value
-                self._externals_map[key] = value.replace('%', '%%') \
-                                           .replace('$path', '%(path)s') \
-                                           .replace('$rev', '%(rev)s')
-        externals = []
-        for external in prop.splitlines():
-            elements = external.split()
-            if not elements:
-                continue
-            localpath, rev, url = elements[0], '', elements[-1]
-            if localpath.startswith('#'):
-                externals.append((external, None, None, None, None))
-                continue
-            if len(elements) == 3:
-                rev = elements[1]
-                rev = rev.replace('-r', '')
-            # retrieve a matching entry in the externals map
-            prefix = []
-            base_url = url
-            while base_url:
-                if base_url in self._externals_map or base_url == u'/':
-                    break
-                base_url, pref = posixpath.split(base_url)
-                prefix.append(pref)
-            href = self._externals_map.get(base_url)
-            revstr = ' at revision ' + rev if rev else ''
-            if not href and (url.startswith('http://') or 
-                             url.startswith('https://')):
-                href = url.replace('%', '%%')
-            if href:
-                remotepath = ''
-                if prefix:
-                    remotepath = posixpath.join(*reversed(prefix))
-                externals.append((localpath, revstr, base_url, remotepath,
-                                  href % {'path': remotepath, 'rev': rev}))
-            else:
-                externals.append((localpath, revstr, url, None, None))
-        externals_data = []
-        for localpath, rev, url, remotepath, href in externals:
-            label = localpath
-            if url is None:
-                title = ''
-            elif href:
-                if url:
-                    url = ' in ' + url
-                label += rev + url
-                title = ''.join((remotepath, rev, url))
-            else:
-                title = _('No svn:externals configured in trac.ini')
-            externals_data.append((label, href, title))
-        return tag.ul([tag.li(tag.a(label, href=href, title=title))
-                       for label, href, title in externals_data])
-
-    def _render_needslock(self, context):
-        return tag.img(src=context.href.chrome('common/lock-locked.png'),
-                       alt="needs lock", title="needs lock")
-
-    def _render_mergeinfo(self, name, mode, context, props):
-        rows = []
-        for row in props[name].splitlines():
-            try:
-                (path, revs) = row.rsplit(':', 1)
-                rows.append([tag.td(path),
-                             tag.td(revs.replace(',', u',\u200b'))])
-            except ValueError:
-                rows.append(tag.td(row, colspan=2))
-        return tag.table(tag.tbody([tag.tr(row) for row in rows]),
-                         class_='props')
-
-
-class SubversionMergePropertyRenderer(Component):
-    implements(IPropertyRenderer)
-
-    # IPropertyRenderer methods
-
-    def match_property(self, name, mode):
-        return 4 if name in ('svn:mergeinfo', 'svnmerge-blocked',
-                             'svnmerge-integrated') else 0
-    
-    def render_property(self, name, mode, context, props):
-        """Parse svn:mergeinfo and svnmerge-* properties, converting branch
-        names to links and providing links to the revision log for merged
-        and eligible revisions.
-        """
-        has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo')
-        revs_label = _('blocked') if name.endswith('blocked') else _('merged')
-        revs_cols = 2 if has_eligible else None
-        reponame = context.resource.parent.id
-        target_path = context.resource.id
-        repos = RepositoryManager(self.env).get_repository(reponame)
-        target_rev = context.resource.version
-        if has_eligible:
-            node = repos.get_node(target_path, target_rev)
-            branch_starts = {}
-            for path, rev in node.get_copy_ancestry(): 
-                if path not in branch_starts:
-                    branch_starts[path] = rev + 1
-        rows = []
-        if name.startswith('svnmerge-'):
-            sources = props[name].split()
-        else:
-            sources = props[name].splitlines()
-        for line in sources:
-            path, revs = line.split(':', 1)
-            spath = _path_within_scope(repos.scope, path)
-            if spath is None:
-                continue
-            revs = revs.strip()
-            inheritable, non_inheritable = _partition_inheritable(revs)
-            revs = ','.join(inheritable)
-            deleted = False
-            try:
-                node = repos.get_node(spath, target_rev)
-                resource = context.resource.parent.child('source', spath)
-                if 'LOG_VIEW' in context.perm(resource):
-                    row = [_get_source_link(spath, context),
-                           _get_revs_link(revs_label, context, spath, revs)]
-                    if non_inheritable:
-                        non_inheritable = ','.join(non_inheritable)
-                        row.append(_get_revs_link(_('non-inheritable'), context,
-                                                  spath, non_inheritable,
-                                                  _('merged on the directory '
-                                                    'itself but not below')))
-                    if has_eligible:
-                        first_rev = branch_starts.get(spath)
-                        if not first_rev:
-                            first_rev = node.get_branch_origin()
-                        eligible = set(xrange(first_rev or 1, target_rev + 1))
-                        eligible -= set(Ranges(revs))
-                        blocked = _get_blocked_revs(props, name, spath)
-                        if blocked:
-                            eligible -= set(Ranges(blocked))
-                        if eligible:
-                            nrevs = repos._get_node_revs(spath, max(eligible),
-                                                         min(eligible))
-                            eligible &= set(nrevs)
-                        eligible = to_ranges(eligible)
-                        row.append(_get_revs_link(_('eligible'), context,
-                                                  spath, eligible))
-                    rows.append((False, spath, [tag.td(each) for each in row]))
-                    continue
-            except NoSuchNode:
-                deleted = True
-            revs = revs.replace(',', u',\u200b')
-            rows.append((deleted, spath,
-                         [tag.td('/' + spath),
-                          tag.td(revs, colspan=revs_cols)]))
-        if not rows:
-            return None
-        rows.sort()
-        has_deleted = rows[-1][0] if rows else None
-        return tag(has_deleted and tag.a(_('(toggle deleted branches)'),
-                                         class_='trac-toggledeleted',
-                                         href='#'),
-                   tag.table(tag.tbody(
-                       [tag.tr(row, class_='trac-deleted' if deleted else None)
-                        for deleted, spath, row in rows]), class_='props'))
-
-
-def _partition_inheritable(revs):
-    """Non-inheritable revision ranges are marked with a trailing '*'."""
-    inheritable, non_inheritable = [], []           
-    for r in revs.split(','):
-        if r and r[-1] == '*':
-            non_inheritable.append(r[:-1])
-        else:
-            inheritable.append(r)
-    return inheritable, non_inheritable
-
-def _get_blocked_revs(props, name, path):
-    """Return the revisions blocked from merging for the given property
-    name and path.
-    """
-    if name == 'svnmerge-integrated':
-        prop = props.get('svnmerge-blocked', '')
-    else:
-        return ""
-    for line in prop.splitlines():
-        try:
-            p, revs = line.split(':', 1)
-            if p.strip('/') == path:
-                return revs
-        except Exception:
-            pass
-    return ""
-
-def _get_source_link(spath, context):
-    """Return a link to a merge source."""
-    reponame = context.resource.parent.id
-    return tag.a('/' + spath, title=_('View merge source'),
-                 href=context.href.browser(reponame or None, spath,
-                                           rev=context.resource.version))
-
-def _get_revs_link(label, context, spath, revs, title=None):
-    """Return a link to the revision log when more than one revision is
-    given, to the revision itself for a single revision, or a `<span>`
-    with "no revision" for none.
-    """
-    reponame = context.resource.parent.id
-    if not revs:
-        return tag.span(label, title=_('No revisions'))
-    elif ',' in revs or '-' in revs:
-        revs_href = context.href.log(reponame or None, spath, revs=revs)
-    else:
-        revs_href = context.href.changeset(revs, reponame or None, spath)
-    revs = revs.replace(',', ', ')
-    if title:
-        title = _("%(title)s: %(revs)s", title=title, revs=revs)
-    else:
-        title = revs
-    return tag.a(label, title=title, href=revs_href)
-
-
-class SubversionMergePropertyDiffRenderer(Component):
-    implements(IPropertyDiffRenderer)
-
-    # IPropertyDiffRenderer methods
 
-    def match_property_diff(self, name):
-        return 4 if name in ('svn:mergeinfo', 'svnmerge-blocked',
-                             'svnmerge-integrated') else 0
+from trac.util import import_namespace
+import_namespace(globals(), 'tracopt.versioncontrol.svn.svn_prop')
 
-    def render_property_diff(self, name, old_context, old_props,
-                             new_context, new_props, options):
-        # Build 5 columns table showing modifications on merge sources
-        # || source || added || removed || added (ni) || removed (ni) ||
-        # || source || removed                                        ||
-        rm = RepositoryManager(self.env)
-        repos = rm.get_repository(old_context.resource.parent.id)
-        def parse_sources(props):
-            sources = {}
-            for line in props[name].splitlines():
-                path, revs = line.split(':', 1)
-                spath = _path_within_scope(repos.scope, path)
-                if spath is not None:
-                    inheritable, non_inheritable = _partition_inheritable(revs)
-                    sources[spath] = (set(Ranges(inheritable)),
-                                      set(Ranges(non_inheritable)))
-            return sources
-        old_sources = parse_sources(old_props)
-        new_sources = parse_sources(new_props)
-        # Go through new sources, detect modified ones or added ones
-        blocked = name.endswith('blocked')
-        added_label = [_("merged: "), _("blocked: ")][blocked]
-        removed_label = [_("reverse-merged: "), _("un-blocked: ")][blocked]
-        added_ni_label = _("marked as non-inheritable: ")
-        removed_ni_label = _("unmarked as non-inheritable: ")
-        def revs_link(revs, context):
-            if revs:
-                revs = to_ranges(revs)
-                return _get_revs_link(revs.replace(',', u',\u200b'),
-                                      context, spath, revs)
-        modified_sources = []
-        for spath, (new_revs, new_revs_ni) in new_sources.iteritems():
-            if spath in old_sources:
-                (old_revs, old_revs_ni), status = old_sources.pop(spath), None
-            else:
-                old_revs = old_revs_ni = set()
-                status = _(' (added)')
-            added = new_revs - old_revs
-            removed = old_revs - new_revs
-            added_ni = new_revs_ni - old_revs_ni
-            removed_ni = old_revs_ni - new_revs_ni
-            try:
-                all_revs = set(repos._get_node_revs(spath))
-                # TODO: also pass first_rev here, for getting smaller a set
-                #       (this is an optmization fix, result is already correct)
-                added &= all_revs
-                removed &= all_revs
-                added_ni &= all_revs
-                removed_ni &= all_revs
-            except NoSuchNode:
-                pass
-            if added or removed:
-                modified_sources.append((
-                    spath, [_get_source_link(spath, new_context), status],
-                    added and tag(added_label, revs_link(added, new_context)),
-                    removed and tag(removed_label,
-                                    revs_link(removed, old_context)),
-                    added_ni and tag(added_ni_label, 
-                                     revs_link(added_ni, new_context)),
-                    removed_ni and tag(removed_ni_label,
-                                       revs_link(removed_ni, old_context))
-                    ))
-        # Go through remaining old sources, those were deleted
-        removed_sources = []
-        for spath, old_revs in old_sources.iteritems():
-            removed_sources.append((spath,
-                                    _get_source_link(spath, old_context)))
-        if modified_sources or removed_sources:
-            modified_sources.sort()
-            removed_sources.sort()
-            changes = tag.table(tag.tbody(
-                [tag.tr(tag.td(c) for c in cols[1:]) 
-                 for cols in modified_sources],
-                [tag.tr(tag.td(src), tag.td(_('removed'), colspan=4))
-                 for spath, src in removed_sources]), class_='props')
-        else:
-            changes = tag.em(_(' (with no actual effect on merging)'))
-        return tag.li(tag_('Property %(prop)s changed', prop=tag.strong(name)),
-                      changes)
+# This module is a stub provided for backward compatibility. The svn_prop
+# module has been moved to tracopt.versioncontrol.svn. Please update your
+# code to use the new location.

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/admin_repositories.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/admin_repositories.html?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/admin_repositories.html (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/admin_repositories.html Tue Oct 16 20:06:09 2012
@@ -10,12 +10,12 @@
     <title>Repositories</title>
   </head>
 
-  <body>
+  <body py:with="sorted_repos = sorted(repositories.iteritems(), key=lambda item: item[0].lower())">
     <h2>Manage Repositories</h2>
 
-    <py:def function="type_field(editable, selected=None)">
+    <py:def function="type_field(editable, multiline=False, selected=None)">
       <div class="field">
-        <label>Type:<br/>
+        <label>Type:<br py:if="multiline"/>
           <select size="1" id="trac-type" name="type" disabled="${not editable or None}">
             <option py:for="type in types" value="$type" selected="${type == selected or None}">${type or _('(default)')}</option>
             <option py:if="selected and selected not in types" selected="selected">$selected</option>
@@ -25,11 +25,11 @@
       </div>
     </py:def>
 
-    <py:def function="alias_field(editable, selected=None)">
+    <py:def function="alias_field(editable, multiline=False, selected=None)">
       <div class="field">
-        <label>Repository:<br/>
+        <label>Repository:<br py:if="multiline"/>
           <select size="1" id="trac-repository" name="alias" disabled="${not editable or None}">
-            <option py:for="(reponame, info) in sorted(repositories.iteritems())" py:if="not info.alias"
+            <option py:for="(reponame, info) in sorted_repos" py:if="not info.alias"
                     value="$info.name" selected="${info.name == selected or None}">${info.name or _('(default)')}</option>
             <option py:if="selected is not None and selected not in repositories" selected="selected">$selected</option>
           </select>
@@ -51,10 +51,10 @@
           </div>
           <py:choose>
             <py:when test="'alias' in info">
-              ${alias_field(info.editable, info.alias)}
+              ${alias_field(info.editable, True, info.alias)}
             </py:when>
             <py:otherwise>
-              ${type_field(info.editable, info.type)}
+              ${type_field(info.editable, True, info.type)}
               <div class="field">
                 <label>Directory:<br/><input type="text" name="dir" size="48" value="$info.dir" readonly="$readonly"/></label>
               </div>
@@ -69,7 +69,7 @@
             </label>
           </div>
           <div class="field">
-            <fieldset class="iefix">
+            <fieldset>
               <label for="description" i18n:msg="">
                 Description (you may use <a tabindex="42" href="${href.wiki('WikiFormatting')}">WikiFormatting</a> here):
               </label>
@@ -92,11 +92,11 @@ $info.description</textarea>
           <fieldset>
             <legend>Add Repository:</legend>
             <div class="field">
-              <label>Name:<br/><input type="text" name="name"/></label>
+              <label>Name: <input type="text" name="name"/></label>
             </div>
             ${type_field(True)}
             <div class="field">
-              <label>Directory:<br/><input type="text" name="dir"/></label>
+              <label>Directory: <input type="text" name="dir"/></label>
             </div>
             <div class="buttons">
               <input type="submit" name="add_repos" value="${_('Add')}"/>
@@ -109,7 +109,7 @@ $info.description</textarea>
           <fieldset>
             <legend>Add Alias:</legend>
             <div class="field">
-              <label>Name:<br/><input type="text" name="name"/></label>
+              <label>Name: <input type="text" name="name"/></label>
             </div>
             ${alias_field(True)}
             <div class="buttons">
@@ -126,7 +126,7 @@ $info.description</textarea>
               </tr>
             </thead>
             <tbody>
-              <tr py:for="(reponame, info) in sorted(repositories.iteritems())">
+              <tr py:for="(reponame, info) in sorted_repos">
                 <td class="sel"><input py:if="info.editable" type="checkbox" name="sel" value="$info.name"/></td>
                 <td class="name">
                   <a href="${panel_href(info.name or '(default)')}">${info.name or _('(default)')}</a>

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/browser.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/browser.html?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/browser.html (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/templates/browser.html Tue Oct 16 20:06:09 2012
@@ -207,11 +207,6 @@
         <xi:include href="preview_file.html" py:with="preview = file.preview"/>
       </div>
 
-      <div id="help" i18n:msg="">
-        <strong>Note:</strong> See <a href="${href.wiki('TracBrowser')}">TracBrowser</a>
-        for help on using the repository browser.
-      </div>
-
       <div id="anydiff">
         <form action="${href.diff()}" method="get">
           <div class="buttons">
@@ -224,6 +219,11 @@
         </form>
       </div>
 
+      <div id="help" i18n:msg="">
+        <strong>Note:</strong> See <a href="${href.wiki('TracBrowser')}">TracBrowser</a>
+        for help on using the repository browser.
+      </div>
+
     </div>
   </body>
 </html>

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/__init__.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/__init__.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/__init__.py Tue Oct 16 20:06:09 2012
@@ -1,6 +1,6 @@
 import unittest
 
-from trac.versioncontrol.tests import cache, diff, svn_authz, svn_fs, api
+from trac.versioncontrol.tests import cache, diff, svn_authz, api
 from trac.versioncontrol.tests.functional import functionalSuite
 
 def suite():
@@ -9,7 +9,6 @@ def suite():
     suite.addTest(cache.suite())
     suite.addTest(diff.suite())
     suite.addTest(svn_authz.suite())
-    suite.addTest(svn_fs.suite())
     suite.addTest(api.suite())
     return suite
 

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/cache.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/cache.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/cache.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/tests/cache.py Tue Oct 16 20:06:09 2012
@@ -219,6 +219,69 @@ class CacheTestCase(unittest.TestCase):
         self.assertEquals((to_utimestamp(t1), 'joe', '**empty**'), rows[0])
         self.assertEquals((to_utimestamp(t2), 'joe', 'Import'), rows[1])
 
+    def test_sync_changeset_if_not_exists(self):
+        t = [
+            datetime(2001, 1, 1, 1, 1, 1, 0, utc), # r0
+            datetime(2002, 1, 1, 1, 1, 1, 0, utc), # r1
+            datetime(2003, 1, 1, 1, 1, 1, 0, utc), # r2
+            datetime(2004, 1, 1, 1, 1, 1, 0, utc), # r3
+        ]
+        self.preset_cache(
+            (('0', to_utimestamp(t[0]), 'joe', '**empty**'), []),
+            (('1', to_utimestamp(t[1]), 'joe', 'Import'),
+             [('trunk', 'D', 'A', None, None),
+              ('trunk/README', 'F', 'A', None, None)]),
+            # not exists r2
+            (('3', to_utimestamp(t[3]), 'joe', 'Add COPYING'),
+             [('trunk/COPYING', 'F', 'A', None, None)]),
+            )
+        repos = self.get_repos(get_changeset=lambda x: changesets[int(x)],
+                               youngest_rev=3)
+        changes = [
+            None,                                                       # r0
+            [('trunk', Node.DIRECTORY, Changeset.ADD, None, None),      # r1
+             ('trunk/README', Node.FILE, Changeset.ADD, None, None)],
+            [('branches', Node.DIRECTORY, Changeset.ADD, None, None),   # r2
+             ('tags', Node.DIRECTORY, Changeset.ADD, None, None)],
+            [('trunk/COPYING', Node.FILE, Changeset.ADD, None, None)],  # r3
+        ]
+        changesets = [
+            Mock(Changeset, repos, 0, '**empty**', 'joe', t[0],
+                 get_changes=lambda: []),
+            Mock(Changeset, repos, 1, 'Initial Import', 'joe', t[1],
+                 get_changes=lambda: iter(changes[1])),
+            Mock(Changeset, repos, 2, 'Created directories', 'john', t[2],
+                 get_changes=lambda: iter(changes[2])),
+            Mock(Changeset, repos, 3, 'Add COPYING', 'joe', t[3],
+                 get_changes=lambda: iter(changes[3])),
+            ]
+        cache = CachedRepository(self.env, repos, self.log)
+        self.assertRaises(NoSuchChangeset, cache.get_changeset, 2)
+        cache.sync()
+        self.assertRaises(NoSuchChangeset, cache.get_changeset, 2)
+
+        self.assertEqual(None, cache.sync_changeset(2))
+        cset = cache.get_changeset(2)
+        self.assertEqual('john', cset.author)
+        self.assertEqual('Created directories', cset.message)
+        self.assertEqual(t[2], cset.date)
+        cset_changes = cset.get_changes()
+        self.assertEqual(('branches', Node.DIRECTORY, Changeset.ADD, None,
+                          None),
+                         cset_changes.next())
+        self.assertEqual(('tags', Node.DIRECTORY, Changeset.ADD, None, None),
+                         cset_changes.next())
+        self.assertRaises(StopIteration, cset_changes.next)
+
+        rows = self.env.db_query(
+                "SELECT time,author,message FROM revision ORDER BY rev")
+        self.assertEquals(4, len(rows))
+        self.assertEquals((to_utimestamp(t[0]), 'joe', '**empty**'), rows[0])
+        self.assertEquals((to_utimestamp(t[1]), 'joe', 'Import'), rows[1])
+        self.assertEquals((to_utimestamp(t[2]), 'john', 'Created directories'),
+                          rows[2])
+        self.assertEquals((to_utimestamp(t[3]), 'joe', 'Add COPYING'), rows[3])
+
     def test_get_changes(self):
         t1 = datetime(2001, 1, 1, 1, 1, 1, 0, utc)
         t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc)

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/browser.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/browser.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/browser.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/browser.py Tue Oct 16 20:06:09 2012
@@ -2,7 +2,7 @@
 #
 # Copyright (C) 2003-2010 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jo...@edgewall.com>
-# Copyright (C) 2005-2007 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2007 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/changeset.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/changeset.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/changeset.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/changeset.py Tue Oct 16 20:06:09 2012
@@ -3,7 +3,7 @@
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jo...@edgewall.com>
 # Copyright (C) 2004-2006 Christopher Lenz <cm...@gmx.de>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -16,7 +16,7 @@
 #
 # Author: Jonas Borgström <jo...@edgewall.com>
 #         Christopher Lenz <cm...@gmx.de>
-#         Christian Boos <cb...@neuf.fr>
+#         Christian Boos <cb...@edgewall.org>
 
 from __future__ import with_statement
 
@@ -681,7 +681,7 @@ class ChangesetModule(Component):
         req.send_response(200)
         req.send_header('Content-Type', 'text/x-patch;charset=utf-8')
         req.send_header('Content-Disposition',
-                        content_disposition('inline', filename + '.diff'))
+                        content_disposition('attachment', filename + '.diff'))
         buf = StringIO()
         mimeview = Mimeview(self.env)
 
@@ -758,7 +758,7 @@ class ChangesetModule(Component):
         req.send_response(200)
         req.send_header('Content-Type', 'application/zip')
         req.send_header('Content-Disposition',
-                        content_disposition('inline', filename + '.zip'))
+                        content_disposition('attachment', filename + '.zip'))
 
         from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED as compression
 
@@ -990,6 +990,7 @@ class ChangesetModule(Component):
                                tag.strong(self._get_location(files) or '/')),
                         markup, class_="changes")
                 elif show_files:
+                    unique_files = set()
                     for c, r, repos_for_c in changesets:
                         for chg in c.get_changes():
                             resource = c.resource.parent.child('source',
@@ -998,8 +999,9 @@ class ChangesetModule(Component):
                                 continue
                             if show_files > 0 and len(files) > show_files:
                                 break
-                            files.append(tag.li(tag.div(class_=chg[2]),
-                                                chg[0] or '/'))
+                            unique_files.add((chg[0], chg[2]))
+                    files = [tag.li(tag.div(class_=mod), path or '/')
+                             for path, mod in sorted(unique_files)]
                     if show_files > 0 and len(files) > show_files:
                         files = files[:show_files] + [tag.li(u'\u2026')]
                     markup = tag(tag.ul(files, class_="changes"), markup)
@@ -1041,7 +1043,7 @@ class ChangesetModule(Component):
 
     # IWikiSyntaxProvider methods
 
-    CHANGESET_ID = r"(?:\d+|[a-fA-F\d]{8,})" # only "long enough" hexa ids
+    CHANGESET_ID = r"(?:[0-9]+|[a-fA-F0-9]{8,})" # only "long enough" hexa ids
 
     def get_wiki_syntax(self):
         yield (
@@ -1051,7 +1053,7 @@ class ChangesetModule(Component):
             # + optional query and fragment
             r"%s(?:/[^\]]*)?(?:\?[^\]]*)?(?:#[^\]]*)?\]|" % self.CHANGESET_ID +
             # r... form: allow r1 but not r1:2 (handled by the log syntax)
-            r"(?:\b|!)r\d+\b(?!:\d)(?:/[a-zA-Z0-9_/+-]+)?",
+            r"(?:\b|!)r[0-9]+\b(?!:[0-9])(?:/[a-zA-Z0-9_/+-]+)?",
             lambda x, y, z:
             self._format_changeset_link(x, 'changeset',
                                         y[1:] if y[0] == 'r' else y[1:-1],

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/log.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/log.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/log.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/log.py Tue Oct 16 20:06:09 2012
@@ -2,7 +2,7 @@
 #
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jo...@edgewall.com>
-# Copyright (C) 2005-2006 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2006 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -14,7 +14,7 @@
 # history and logs, available at http://trac.edgewall.org/log/.
 #
 # Author: Jonas Borgström <jo...@edgewall.com>
-#         Christian Boos <cb...@neuf.fr>
+#         Christian Boos <cb...@edgewall.org>
 
 import re
 
@@ -51,7 +51,7 @@ class LogModule(Component):
     graph_colors = ListOption('revisionlog', 'graph_colors',
         ['#cc0', '#0c0', '#0cc', '#00c', '#c0c', '#c00'],
         doc="""Comma-separated list of colors to use for the TracRevisionLog
-        graph display. (''since 0.13'')""")
+        graph display. (''since 1.0'')""")
 
     # INavigationContributor methods
 

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py Tue Oct 16 20:06:09 2012
@@ -99,7 +99,13 @@ changeset:1#file0
 [1], r1
 </p>
 ------------------------------
-[1], r1
+============================== unicode digits
+[₁₂₃], r₁₂₃, [₀A₁B₂C₃D]
+------------------------------
+<p>
+[₁₂₃], r₁₂₃, [₀A₁B₂C₃D]
+</p>
+------------------------------
 ============================== Link resolver counter examples
 Change:[10] There should be a link to changeset [10]
 
@@ -237,6 +243,19 @@ rfc:4180 should not be a log link
 <a class="ext-link" href="http://trac.edgewall.org/intertrac/log%3A/trunk%403317%3A3318" title="log:/trunk@3317:3318 in Trac\'s Trac"><span class="icon"></span>[trac 3317:3318/trunk]</a>
 </p>
 ------------------------------
+============================== Log range with unicode digits
+r₁₂:₂₀,₂₅,₃₀-₃₅
+[₁₂:₂₀,₂₅,₃₀-₃₅]
+[T₃₃₁₇:₃₃₁₈]
+[trac ₃₃₁₇:₃₃₁₈]
+------------------------------
+<p>
+r₁₂:₂₀,₂₅,₃₀-₃₅
+[₁₂:₂₀,₂₅,₃₀-₃₅]
+[T₃₃₁₇:₃₃₁₈]
+[trac ₃₃₁₇:₃₃₁₈]
+</p>
+------------------------------
 """
 
 

Modified: incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/util.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/util.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/util.py (original)
+++ incubator/bloodhound/trunk/trac/trac/versioncontrol/web_ui/util.py Tue Oct 16 20:06:09 2012
@@ -2,7 +2,7 @@
 #
 # Copyright (C) 2003-2009 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jo...@edgewall.com>
-# Copyright (C) 2005-2007 Christian Boos <cb...@neuf.fr>
+# Copyright (C) 2005-2007 Christian Boos <cb...@edgewall.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -14,7 +14,7 @@
 # history and logs, available at http://trac.edgewall.org/log/.
 #
 # Author: Jonas Borgström <jo...@edgewall.com>
-#         Christian Boos <cb...@neuf.fr>
+#         Christian Boos <cb...@edgewall.org>
 
 from itertools import izip
 

Modified: incubator/bloodhound/trunk/trac/trac/web/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/trac/web/api.py?rev=1398968&r1=1398967&r2=1398968&view=diff
==============================================================================
--- incubator/bloodhound/trunk/trac/trac/web/api.py (original)
+++ incubator/bloodhound/trunk/trac/trac/web/api.py Tue Oct 16 20:06:09 2012
@@ -62,7 +62,7 @@ class IRequestHandler(Interface):
         Note that if template processing should not occur, this method can
         simply send the response itself and not return anything.
 
-        :Since 0.13: Clearsilver templates are no longer supported.
+        :Since 1.0: Clearsilver templates are no longer supported.
         """
 
 
@@ -98,7 +98,7 @@ class IRequestFilter(Interface):
            at ClearSilver templates and the newer ones targeted at Genshi
            templates.
 
-        :Since 0.13: Clearsilver templates are no longer supported.
+        :Since 1.0: Clearsilver templates are no longer supported.
         """
 
 
@@ -150,7 +150,7 @@ class HTTPException(Exception):
         new_class.reason = reason
         return new_class
 
-
+_HTTPException_subclass_names = []
 for code in [code for code in HTTP_STATUS if code >= 400]:
     exc_name = HTTP_STATUS[code].replace(' ', '').replace('-', '')
     # 2.5 compatibility hack:
@@ -158,9 +158,10 @@ for code in [code for code in HTTP_STATU
         exc_name = 'InternalError'
     if exc_name.lower().startswith('http'):
         exc_name = exc_name[4:]
-    exc_name = 'HTTP' + exc_name        
+    exc_name = 'HTTP' + exc_name
     setattr(sys.modules[__name__], exc_name,
             HTTPException.subclass(exc_name, code))
+    _HTTPException_subclass_names.append(exc_name)
 del code, exc_name
 
 
@@ -562,9 +563,12 @@ class Request(object):
         self.send_header('Content-Type', mimetype)
         self.send_header('Content-Length', stat.st_size)
         self.send_header('Last-Modified', last_modified)
+        use_xsendfile = getattr(self, 'use_xsendfile', False)
+        if use_xsendfile:
+            self.send_header('X-Sendfile', os.path.abspath(path))
         self.end_headers()
 
-        if self.method != 'HEAD':
+        if not use_xsendfile and self.method != 'HEAD':
             fileobj = file(path, 'rb')
             file_wrapper = self.environ.get('wsgi.file_wrapper', _FileWrapper)
             self._response = file_wrapper(fileobj, 4096)
@@ -703,3 +707,5 @@ class Request(object):
         cookies = to_unicode(self.outcookie.output(header='')).encode('utf-8')
         for cookie in cookies.splitlines():
             self._outheaders.append(('Set-Cookie', cookie.strip()))
+
+__no_apidoc__ = _HTTPException_subclass_names