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