mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
739c51dd7c | ||
![]() |
167a2a041b | ||
![]() |
c4483d0501 | ||
![]() |
d620bc13e7 | ||
![]() |
e3756ae365 | ||
![]() |
77ad38cbdb | ||
![]() |
7feecdd512 | ||
![]() |
a087c06a88 | ||
![]() |
acce6a556d | ||
![]() |
4670019d3a | ||
![]() |
9ce7372e1a | ||
![]() |
e1959ac2e5 | ||
![]() |
cb38ccc929 | ||
![]() |
f2b82132c2 | ||
![]() |
5caf3a4437 | ||
![]() |
06ee4df42d | ||
![]() |
0a6b13145e | ||
![]() |
65d3568c92 | ||
![]() |
de5b147a6f | ||
![]() |
eb6b575701 | ||
![]() |
8d82b6f0d6 | ||
![]() |
5a51470cbc | ||
![]() |
acc0783468 | ||
![]() |
c91325d40a | ||
![]() |
ca1bd67b5d | ||
![]() |
f1e7ef42d6 | ||
![]() |
8b7eae7f14 | ||
![]() |
6f3d9a3a00 | ||
![]() |
1d7307c09b |
29
CHANGES
29
CHANGES
@@ -1,3 +1,31 @@
|
||||
Version 1.1.17 (released 25-Oct-2012)
|
||||
|
||||
* fix exception caused by uninitialized variable usage (issue #516)
|
||||
|
||||
Version 1.1.16 (released 24-Oct-2012)
|
||||
|
||||
* security fix: escape "extra" diff info to avoid XSS attack (issue #515)
|
||||
* add 'binary_mime_types' configuration option and handling (issue #510)
|
||||
* fix 'select for diffs' persistence across log pages (issue #512)
|
||||
* remove lock status and filesize check on directories in remote SVN views
|
||||
* fix bogus 'Annotation of' page title for non-annotated view (issue #514)
|
||||
|
||||
Version 1.1.15 (released 22-Jun-2012)
|
||||
|
||||
* security fix: complete authz support for remote SVN views (issue #353)
|
||||
* security fix: log msg leak in SVN revision view with unreadable copy source
|
||||
* fix several instances of incorrect information in remote SVN views
|
||||
* increase performance of some revision metadata lookups in remote SVN views
|
||||
* fix RSS feed regression introduced in 1.1.14
|
||||
|
||||
Version 1.1.14 (released 12-Jun-2012)
|
||||
|
||||
* fix annotation of svn files with non-URI-safe paths (issue #504)
|
||||
* handle file:/// Subversion rootpaths as local roots (issue #446)
|
||||
* fix bug caused by trying to case-normalize anon usernames (issue #505)
|
||||
* speed up log handling by reusing tokenization results (issue #506)
|
||||
* add support for custom review log markup rules (issue #429)
|
||||
|
||||
Version 1.1.13 (released 23-Jan-2012)
|
||||
|
||||
* fix svndbadmin failure on deleted paths under Subversion 1.7 (issue #499)
|
||||
@@ -38,7 +66,6 @@ Version 1.1.9 (released 18-Feb-2011)
|
||||
* make revision log "extra pages" count configurable
|
||||
* fix Subversion 1.4.x revision log compatibility code regression
|
||||
* display sanitized error when authzfile is malformed
|
||||
* handle file:/// Subversion rootpaths as local roots (issue #446)
|
||||
* restore markup of URLs in file contents (issue #455)
|
||||
* optionally display last-committed metadata in roots view (issue #457)
|
||||
|
||||
|
@@ -391,6 +391,24 @@
|
||||
##
|
||||
#allowed_views = annotate, diff, markup, roots
|
||||
|
||||
## Comma-delimited list of MIME content types (with support for fnmatch-
|
||||
## style glob characters) which are considered not-human-readable and for
|
||||
## which ViewVC will neither generate links to, nor support the direct
|
||||
## display of, non-checkout views which carry the file's content (the
|
||||
## 'markup', 'annotate', 'diff', and 'patch' views).
|
||||
##
|
||||
## NOTE: Handling of this option is given priority over ViewVC's
|
||||
## longstanding support for showing web-friendly file formats -- even
|
||||
## binary ones such as "image/jpeg" and "image/gif" -- in the 'markup'
|
||||
## view. Thus, if you add "image/*" to this list, 'markup'-view
|
||||
## display of JPEG, GIF, and PNG images will be disabled.
|
||||
##
|
||||
## Example:
|
||||
## binary_mime_types = application/octet-stream, image/*, application/pdf,
|
||||
## application/vnd*, application/msword, audio/*
|
||||
#
|
||||
#binary_mime_types =
|
||||
|
||||
## authorizer: The name of the ViewVC authorizer plugin to use when
|
||||
## authorizing access to repository contents. This value must be the
|
||||
## name of a Python module addressable as vcauth.MODULENAME (most
|
||||
@@ -433,6 +451,26 @@
|
||||
##
|
||||
#mangle_email_addresses = 0
|
||||
|
||||
## custom_log_formatting: Specifies mappings of regular expressions to
|
||||
## substitution format strings used to URL-ize strings found in
|
||||
## revision log messages. Multiple mappings (specified as
|
||||
## REGEXP:FORMATSTRING) may be defined, separated by commas.
|
||||
##
|
||||
## NOTE: Due to a limitation of the configuration format, commas may
|
||||
## not be used in the regular expression portion of each mapping.
|
||||
## Commas "in the raw" may not be used in the format string portion,
|
||||
## either, but you can probably use the URI-encoded form of the comma
|
||||
## ("%2C") instead with no ill side-effects. If you must specify a
|
||||
## colon character in either the regular expression or the format
|
||||
## string, escape it with a preceding backslash ("\:").
|
||||
##
|
||||
## Example:
|
||||
## custom_log_formatting =
|
||||
## artf[0-9]+ : http://example.com/tracker?id=\0,
|
||||
## issue ([0-9]+) : http://example.com/bug?id=\1&opts=full%2csortby=id
|
||||
##
|
||||
#custom_log_formatting =
|
||||
|
||||
## default_file_view: "log", "co", or "markup"
|
||||
## Controls whether the default view for file URLs is a checkout view or
|
||||
## a log view. "log" is the default for backwards compatibility with old
|
||||
@@ -541,7 +579,7 @@
|
||||
## (Only works well for C source files, otherwise diff's heuristic falls short.)
|
||||
## ('-p' option to diff)
|
||||
##
|
||||
#hr_funout = 0
|
||||
#hr_funout = 1
|
||||
|
||||
## hr_ignore_white: Ignore whitespace (indendation and stuff) for human
|
||||
## readable diffs.
|
||||
|
@@ -112,6 +112,8 @@ class Config:
|
||||
_force_multi_value = (
|
||||
# Configuration values with multiple, comma-separated values.
|
||||
'allowed_views',
|
||||
'binary_mime_types',
|
||||
'custom_log_formatting',
|
||||
'cvs_roots',
|
||||
'kv_files',
|
||||
'languages',
|
||||
@@ -398,7 +400,9 @@ class Config:
|
||||
self.options.allowed_views = ['annotate', 'diff', 'markup', 'roots']
|
||||
self.options.authorizer = None
|
||||
self.options.mangle_email_addresses = 0
|
||||
self.options.custom_log_formatting = []
|
||||
self.options.default_file_view = "log"
|
||||
self.options.binary_mime_types = []
|
||||
self.options.http_expiration_time = 600
|
||||
self.options.generate_etags = 1
|
||||
self.options.svn_ignore_mimetype = 0
|
||||
|
@@ -34,9 +34,9 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
# See if the admin wants us to do case normalization of usernames.
|
||||
self.force_username_case = params.get('force_username_case')
|
||||
if self.force_username_case == "upper":
|
||||
self.username = username.upper()
|
||||
self.username = username and string.upper(username) or username
|
||||
elif self.force_username_case == "lower":
|
||||
self.username = username.lower()
|
||||
self.username = username and string.lower(username) or username
|
||||
elif not self.force_username_case:
|
||||
self.username = username
|
||||
else:
|
||||
|
@@ -19,27 +19,46 @@ import urllib
|
||||
|
||||
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
def _canonicalize_path(path):
|
||||
import svn.core
|
||||
try:
|
||||
import svn.core
|
||||
return svn.core.svn_path_canonicalize(rootpath)
|
||||
except:
|
||||
if os.name == 'posix':
|
||||
rootpath_lower = rootpath.lower()
|
||||
if rootpath_lower in ['file://localhost',
|
||||
'file://localhost/',
|
||||
'file://',
|
||||
'file:///'
|
||||
]:
|
||||
return '/'
|
||||
if rootpath_lower.startswith('file://localhost/'):
|
||||
return os.path.normpath(urllib.unquote(rootpath[16:]))
|
||||
elif rootpath_lower.startswith('file:///'):
|
||||
return os.path.normpath(urllib.unquote(rootpath[7:]))
|
||||
if re.search(_re_url, rootpath):
|
||||
return rootpath.rstrip('/')
|
||||
return svn.core.svn_path_canonicalize(path)
|
||||
except AttributeError: # svn_path_canonicalize() appeared in 1.4.0 bindings
|
||||
# There's so much more that we *could* do here, but if we're
|
||||
# here at all its because there's a really old Subversion in
|
||||
# place, and those older Subversion versions cared quite a bit
|
||||
# less about the specifics of path canonicalization.
|
||||
if re.search(_re_url, path):
|
||||
return path.rstrip('/')
|
||||
else:
|
||||
return os.path.normpath(path)
|
||||
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
# Try to canonicalize the rootpath using Subversion semantics.
|
||||
rootpath = _canonicalize_path(rootpath)
|
||||
|
||||
# ViewVC's support for local repositories is more complete and more
|
||||
# performant than its support for remote ones, so if we're on a
|
||||
# Unix-y system and we have a file:/// URL, convert it to a local
|
||||
# path instead.
|
||||
if os.name == 'posix':
|
||||
rootpath_lower = rootpath.lower()
|
||||
if rootpath_lower in ['file://localhost',
|
||||
'file://localhost/',
|
||||
'file://',
|
||||
'file:///'
|
||||
]:
|
||||
return '/'
|
||||
if rootpath_lower.startswith('file://localhost/'):
|
||||
rootpath = os.path.normpath(urllib.unquote(rootpath[16:]))
|
||||
elif rootpath_lower.startswith('file:///'):
|
||||
rootpath = os.path.normpath(urllib.unquote(rootpath[7:]))
|
||||
|
||||
# Ensure that we have an absolute path (or URL), and return.
|
||||
if not re.search(_re_url, rootpath):
|
||||
assert os.path.isabs(rootpath)
|
||||
return os.path.normpath(rootpath)
|
||||
return rootpath
|
||||
|
||||
|
||||
def expand_root_parent(parent_path):
|
||||
|
@@ -20,7 +20,10 @@ import re
|
||||
import tempfile
|
||||
import time
|
||||
import urllib
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception, _split_revprops
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, \
|
||||
_compare_paths, _path_parts, _cleanup_path, \
|
||||
_rev2optrev, _fix_subversion_exception, \
|
||||
_split_revprops, _canonicalize_path
|
||||
from svn import core, delta, client, wc, ra
|
||||
|
||||
|
||||
@@ -51,12 +54,14 @@ def get_directory_props(ra_session, path, rev):
|
||||
props = ra.svn_ra_get_dir(ra_session, path, rev)
|
||||
return props
|
||||
|
||||
def client_log(url, start_rev, end_rev, log_limit, cross_copies,
|
||||
cb_func, ctx):
|
||||
def client_log(url, start_rev, end_rev, log_limit, include_changes,
|
||||
cross_copies, cb_func, ctx):
|
||||
include_changes = include_changes and 1 or 0
|
||||
cross_copies = cross_copies and 1 or 0
|
||||
try:
|
||||
client.svn_client_log4([url], start_rev, start_rev, end_rev,
|
||||
log_limit, 1, not cross_copies, 0, None,
|
||||
cb_func, ctx)
|
||||
log_limit, include_changes, not cross_copies,
|
||||
0, None, cb_func, ctx)
|
||||
except AttributeError:
|
||||
# Wrap old svn_log_message_receiver_t interface with a
|
||||
# svn_log_entry_t one.
|
||||
@@ -72,15 +77,14 @@ def client_log(url, start_rev, end_rev, log_limit, cross_copies,
|
||||
}
|
||||
cb_func(log_entry, pool)
|
||||
client.svn_client_log2([url], start_rev, end_rev, log_limit,
|
||||
1, not cross_copies, cb_convert, ctx)
|
||||
include_changes, not cross_copies, cb_convert, ctx)
|
||||
|
||||
### END COMPATABILITY CODE ###
|
||||
|
||||
|
||||
class LogCollector:
|
||||
### TODO: Make this thing authz-aware
|
||||
|
||||
def __init__(self, path, show_all_logs, lockinfo):
|
||||
def __init__(self, path, show_all_logs, lockinfo, access_check_func):
|
||||
# This class uses leading slashes for paths internally
|
||||
if not path:
|
||||
self.path = '/'
|
||||
@@ -89,8 +93,12 @@ class LogCollector:
|
||||
self.logs = []
|
||||
self.show_all_logs = show_all_logs
|
||||
self.lockinfo = lockinfo
|
||||
self.access_check_func = access_check_func
|
||||
self.done = False
|
||||
|
||||
def add_log(self, log_entry, pool):
|
||||
if self.done:
|
||||
return
|
||||
paths = log_entry.changed_paths
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
@@ -114,9 +122,13 @@ class LogCollector:
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path + self.path[len(changed_path):]
|
||||
if self.show_all_logs or this_path:
|
||||
entry = Revision(revision, date, author, msg, None, self.lockinfo,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
if self.access_check_func is None \
|
||||
or self.access_check_func(self.path[1:], revision):
|
||||
entry = Revision(revision, date, author, msg, None, self.lockinfo,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
else:
|
||||
self.done = True
|
||||
if this_path:
|
||||
self.path = this_path
|
||||
|
||||
@@ -252,14 +264,15 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
### rev here should be the last history revision of the URL
|
||||
client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev), self.ctx)
|
||||
core.svn_stream_close(stream)
|
||||
return SelfCleanFP(tmp_file), self._get_last_history_rev(path_parts, rev)
|
||||
lh_rev, c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
return SelfCleanFP(tmp_file), lh_rev
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
path = self._getpath(path_parts)
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a directory." % path)
|
||||
rev = self._getrev(rev)
|
||||
entries = [ ]
|
||||
entries = []
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for name in dirents.keys():
|
||||
entry = dirents[name]
|
||||
@@ -267,8 +280,9 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
kind = vclib.DIR
|
||||
elif entry.kind == core.svn_node_file:
|
||||
kind = vclib.FILE
|
||||
if vclib.check_path_access(self, path_parts + [name], kind, rev):
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
else:
|
||||
kind = None
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
@@ -279,11 +293,13 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for entry in entries:
|
||||
entry_path_parts = path_parts + [entry.name]
|
||||
if not vclib.check_path_access(self, entry_path_parts, entry.kind, rev):
|
||||
dirent = dirents.get(entry.name, None)
|
||||
# dirents is authz-sanitized, so ensure the entry is found therein.
|
||||
if dirent is None:
|
||||
continue
|
||||
dirent = dirents[entry.name]
|
||||
# Get authz-sanitized revision metadata.
|
||||
entry.date, entry.author, entry.log, revprops, changes = \
|
||||
self.revinfo(dirent.created_rev)
|
||||
self._revinfo(dirent.created_rev)
|
||||
entry.rev = str(dirent.created_rev)
|
||||
entry.size = dirent.size
|
||||
entry.lockinfo = None
|
||||
@@ -297,28 +313,51 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
|
||||
# Use ls3 to fetch the lock status for this item.
|
||||
lockinfo = None
|
||||
basename = path_parts and path_parts[-1] or ""
|
||||
dirents, locks = list_directory(url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
if locks.has_key(basename):
|
||||
lockinfo = locks[basename].owner
|
||||
# If this is a file, fetch the lock status and size (as of REV)
|
||||
# for this item.
|
||||
lockinfo = size_in_rev = None
|
||||
if path_type == vclib.FILE:
|
||||
basename = path_parts[-1]
|
||||
list_url = self._geturl(self._getpath(path_parts[:-1]))
|
||||
dirents, locks = list_directory(list_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
if locks.has_key(basename):
|
||||
lockinfo = locks[basename].owner
|
||||
if dirents.has_key(basename):
|
||||
size_in_rev = dirents[basename].size
|
||||
|
||||
# Special handling for the 'svn_latest_log' scenario.
|
||||
### FIXME: Don't like this hack. We should just introduce
|
||||
### something more direct in the vclib API.
|
||||
if options.get('svn_latest_log', 0):
|
||||
dir_lh_rev, dir_c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
date, author, log, revprops, changes = self._revinfo(dir_lh_rev)
|
||||
return [vclib.Revision(dir_lh_rev, str(dir_lh_rev), date, author,
|
||||
None, log, size_in_rev, lockinfo)]
|
||||
|
||||
def _access_checker(check_path, check_rev):
|
||||
return vclib.check_path_access(self, _path_parts(check_path),
|
||||
path_type, check_rev)
|
||||
|
||||
# It's okay if we're told to not show all logs on a file -- all
|
||||
# the revisions should match correctly anyway.
|
||||
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0), lockinfo)
|
||||
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0),
|
||||
lockinfo, _access_checker)
|
||||
|
||||
cross_copies = options.get('svn_cross_copies', 0)
|
||||
log_limit = 0
|
||||
if limit:
|
||||
log_limit = first + limit
|
||||
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit,
|
||||
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit, 1,
|
||||
cross_copies, lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
revs.sort()
|
||||
prev = None
|
||||
for rev in revs:
|
||||
# Swap out revision info with stuff from the cache (which is
|
||||
# authz-sanitized).
|
||||
rev.date, rev.author, rev.log, revprops, changes \
|
||||
= self._revinfo(rev.number)
|
||||
rev.prev = prev
|
||||
prev = rev
|
||||
revs.reverse()
|
||||
@@ -345,6 +384,18 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
|
||||
# Examine logs for the file to determine the oldest revision we are
|
||||
# permitted to see.
|
||||
log_options = {
|
||||
'svn_cross_copies' : 1,
|
||||
'svn_show_all_dir_logs' : 1,
|
||||
}
|
||||
revs = self.itemlog(path_parts, rev, vclib.SORTBY_REV, 0, 0, log_options)
|
||||
oldest_rev = revs[-1].number
|
||||
|
||||
# Now calculate the annotation data. Note that we'll not
|
||||
# inherently trust the provided author and date, because authz
|
||||
# rules might necessitate that we strip that information out.
|
||||
blame_data = []
|
||||
|
||||
def _blame_cb(line_no, revision, author, date,
|
||||
@@ -352,22 +403,27 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
prev_rev = None
|
||||
if revision > 1:
|
||||
prev_rev = revision - 1
|
||||
|
||||
# If we have an invalid revision, clear the date and author
|
||||
# values. Otherwise, if we have authz filtering to do, use the
|
||||
# revinfo cache to do so.
|
||||
if revision < 0:
|
||||
date = author = None
|
||||
elif self.auth:
|
||||
date, author, msg, revprops, changes = self._revinfo(revision)
|
||||
|
||||
# Strip text if the caller doesn't want it.
|
||||
if not include_text:
|
||||
line = None
|
||||
blame_data.append(vclib.Annotation(line, line_no + 1, revision, prev_rev,
|
||||
author, None))
|
||||
author, date))
|
||||
|
||||
client.svn_client_blame(url, _rev2optrev(1), _rev2optrev(rev),
|
||||
_blame_cb, self.ctx)
|
||||
client.blame2(url, _rev2optrev(rev), _rev2optrev(oldest_rev),
|
||||
_rev2optrev(rev), _blame_cb, self.ctx)
|
||||
return blame_data, rev
|
||||
|
||||
def revinfo(self, rev):
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info:
|
||||
cached_info = self._revinfo_raw(rev)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return tuple(cached_info)
|
||||
return self._revinfo(rev, 1)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
@@ -382,7 +438,7 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, revprops, changes = self.revinfo(rev)
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
@@ -421,49 +477,84 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
def _geturl(self, path=None):
|
||||
if not path:
|
||||
return self.rootpath
|
||||
return self.rootpath + '/' + urllib.quote(path, "/*~")
|
||||
path = self.rootpath + '/' + urllib.quote(path)
|
||||
return _canonicalize_path(path)
|
||||
|
||||
def _get_dirents(self, path, rev):
|
||||
"""Return a 2-type of dirents and locks, possibly reading/writing
|
||||
from a local cache of that information."""
|
||||
from a local cache of that information. This functions performs
|
||||
authz checks, stripping out unreadable dirents."""
|
||||
|
||||
dir_url = self._geturl(path)
|
||||
path_parts = _path_parts(path)
|
||||
if path:
|
||||
key = str(rev) + '/' + path
|
||||
else:
|
||||
key = str(rev)
|
||||
|
||||
# Ensure that the cache gets filled...
|
||||
dirents_locks = self._dirent_cache.get(key)
|
||||
if not dirents_locks:
|
||||
dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
tmp_dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
dirents = {}
|
||||
for name, dirent in tmp_dirents.items():
|
||||
dirent_parts = path_parts + [name]
|
||||
kind = dirent.kind
|
||||
if (kind == core.svn_node_dir or kind == core.svn_node_file) \
|
||||
and vclib.check_path_access(self, dirent_parts,
|
||||
kind == core.svn_node_dir \
|
||||
and vclib.DIR or vclib.FILE, rev):
|
||||
lh_rev, c_rev = self._get_last_history_rev(dirent_parts, rev)
|
||||
dirent.created_rev = lh_rev
|
||||
dirents[name] = dirent
|
||||
dirents_locks = [dirents, locks]
|
||||
self._dirent_cache[key] = dirents_locks
|
||||
|
||||
# ...then return the goodies from the cache.
|
||||
return dirents_locks[0], dirents_locks[1]
|
||||
|
||||
def _get_last_history_rev(self, path_parts, rev):
|
||||
"""Return the a 2-tuple which contains:
|
||||
- the last interesting revision equal to or older than REV in
|
||||
the history of PATH_PARTS.
|
||||
- the created_rev of of PATH_PARTS as of REV."""
|
||||
|
||||
path = self._getpath(path_parts)
|
||||
url = self._geturl(self._getpath(path_parts))
|
||||
optrev = _rev2optrev(rev)
|
||||
|
||||
# Get the last-changed-rev.
|
||||
revisions = []
|
||||
def _info_cb(path, info, pool, retval=revisions):
|
||||
revisions.append(info.last_changed_rev)
|
||||
client.svn_client_info(url, optrev, optrev, _info_cb, 0, self.ctx)
|
||||
return revisions[0]
|
||||
|
||||
def _revinfo_raw(self, rev):
|
||||
# return 5-tuple (date, author, msg, revprops, changes)
|
||||
optrev = _rev2optrev(rev)
|
||||
revs = []
|
||||
last_changed_rev = revisions[0]
|
||||
|
||||
# Now, this object might not have been directly edited since the
|
||||
# last-changed-rev, but it might have been the child of a copy.
|
||||
# To determine this, we'll run a potentially no-op log between
|
||||
# LAST_CHANGED_REV and REV.
|
||||
lc = LogCollector(path, 1, None, None)
|
||||
client_log(url, optrev, _rev2optrev(last_changed_rev), 1, 1, 0,
|
||||
lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
if revs:
|
||||
revs.sort()
|
||||
return revs[0].number, last_changed_rev
|
||||
else:
|
||||
return last_changed_rev, last_changed_rev
|
||||
|
||||
def _revinfo_fetch(self, rev, include_changed_paths=0):
|
||||
need_changes = include_changed_paths or self.auth
|
||||
revs = []
|
||||
|
||||
def _log_cb(log_entry, pool, retval=revs):
|
||||
### Subversion 1.5 and earlier didn't offer the 'changed_paths2'
|
||||
### hash, and in Subversion 1.6, it's offered but broken.
|
||||
try:
|
||||
changed_paths = log_entry.changed_paths2
|
||||
paths = (changed_paths or {}).keys()
|
||||
except:
|
||||
changed_paths = log_entry.changed_paths
|
||||
paths = (changed_paths or {}).keys()
|
||||
paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
# If Subversion happens to call us more than once, we choose not
|
||||
# to care.
|
||||
if retval:
|
||||
return
|
||||
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
action_map = { 'D' : vclib.DELETED,
|
||||
@@ -471,22 +562,42 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
'R' : vclib.REPLACED,
|
||||
'M' : vclib.MODIFIED,
|
||||
}
|
||||
|
||||
# Easy out: if we won't use the changed-path info, just return a
|
||||
# changes-less tuple.
|
||||
if not need_changes:
|
||||
return revs.append([date, author, msg, revprops, None])
|
||||
|
||||
# Subversion 1.5 and earlier didn't offer the 'changed_paths2'
|
||||
# hash, and in Subversion 1.6, it's offered but broken.
|
||||
try:
|
||||
changed_paths = log_entry.changed_paths2
|
||||
paths = (changed_paths or {}).keys()
|
||||
except:
|
||||
changed_paths = log_entry.changed_paths
|
||||
paths = (changed_paths or {}).keys()
|
||||
paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
|
||||
# If we get this far, our caller needs changed-paths, or we need
|
||||
# them for authz-related sanitization.
|
||||
changes = []
|
||||
found_readable = found_unreadable = 0
|
||||
for path in paths:
|
||||
change = changed_paths[path]
|
||||
### svn_log_changed_path_t (which we might get instead of the
|
||||
### svn_log_changed_path2_t we'd prefer) doesn't have the
|
||||
### 'node_kind' member.
|
||||
|
||||
# svn_log_changed_path_t (which we might get instead of the
|
||||
# svn_log_changed_path2_t we'd prefer) doesn't have the
|
||||
# 'node_kind' member.
|
||||
pathtype = None
|
||||
if hasattr(change, 'node_kind'):
|
||||
if change.node_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif change.node_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
### svn_log_changed_path2_t only has the 'text_modified' and
|
||||
### 'props_modified' bits in Subversion 1.7 and beyond. And
|
||||
### svn_log_changed_path_t is without.
|
||||
|
||||
# svn_log_changed_path2_t only has the 'text_modified' and
|
||||
# 'props_modified' bits in Subversion 1.7 and beyond. And
|
||||
# svn_log_changed_path_t is without.
|
||||
text_modified = props_modified = 0
|
||||
if hasattr(change, 'text_modified'):
|
||||
if change.text_modified == core.svn_tristate_true:
|
||||
@@ -494,9 +605,10 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
if hasattr(change, 'props_modified'):
|
||||
if change.props_modified == core.svn_tristate_true:
|
||||
props_modified = 1
|
||||
### Wrong, diddily wrong wrong wrong. Can you say,
|
||||
### "Manufacturing data left and right because it hurts to
|
||||
### figure out the right stuff?"
|
||||
|
||||
# Wrong, diddily wrong wrong wrong. Can you say,
|
||||
# "Manufacturing data left and right because it hurts to
|
||||
# figure out the right stuff?"
|
||||
action = action_map.get(change.action, vclib.MODIFIED)
|
||||
if change.copyfrom_path and change.copyfrom_rev:
|
||||
is_copy = 1
|
||||
@@ -510,15 +622,16 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
base_path = path
|
||||
base_rev = revision - 1
|
||||
|
||||
### Check authz rules (we lie about the path type)
|
||||
# Check authz rules (sadly, we have to lie about the path type)
|
||||
parts = _path_parts(path)
|
||||
if vclib.check_path_access(self, parts, vclib.FILE, revision):
|
||||
if is_copy and base_path and (base_path != path):
|
||||
parts = _path_parts(base_path)
|
||||
if vclib.check_path_access(self, parts, vclib.FILE, base_rev):
|
||||
if not vclib.check_path_access(self, parts, vclib.FILE, base_rev):
|
||||
is_copy = 0
|
||||
base_path = None
|
||||
base_rev = None
|
||||
found_unreadable = 1
|
||||
changes.append(SVNChangedPath(path, revision, pathtype, base_path,
|
||||
base_rev, action, is_copy,
|
||||
text_modified, props_modified))
|
||||
@@ -526,16 +639,45 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
else:
|
||||
found_unreadable = 1
|
||||
|
||||
# If our caller doesn't want changed-path stuff, and we have
|
||||
# the info we need to make an authz determination already,
|
||||
# quit this loop and get on with it.
|
||||
if (not include_changed_paths) and found_unreadable and found_readable:
|
||||
break
|
||||
|
||||
# Filter unreadable information.
|
||||
if found_unreadable:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
revs.append([date, author, msg, revprops, changes])
|
||||
|
||||
client_log(self.rootpath, optrev, optrev, 1, 0, _log_cb, self.ctx)
|
||||
# Drop unrequested changes.
|
||||
if not include_changed_paths:
|
||||
changes = None
|
||||
|
||||
# Add this revision information to the "return" array.
|
||||
retval.append([date, author, msg, revprops, changes])
|
||||
|
||||
optrev = _rev2optrev(rev)
|
||||
client_log(self.rootpath, optrev, optrev, 1, need_changes, 0,
|
||||
_log_cb, self.ctx)
|
||||
return tuple(revs[0])
|
||||
|
||||
def _revinfo(self, rev, include_changed_paths=0):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
# Consult the revinfo cache first. If we don't have cached info,
|
||||
# or our caller wants changed paths and we don't have those for
|
||||
# this revision, go do the real work.
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info \
|
||||
or (include_changed_paths and cached_info[4] is None):
|
||||
cached_info = self._revinfo_fetch(rev, include_changed_paths)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return cached_info
|
||||
|
||||
##--- custom --##
|
||||
|
||||
def get_youngest_revision(self):
|
||||
@@ -553,23 +695,16 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(old_path)
|
||||
old_path = _cleanup_path(old_path)
|
||||
old_path_parts = _path_parts(old_path)
|
||||
# Check access (lying about path types)
|
||||
if not vclib.check_path_access(self, old_path_parts, vclib.FILE, old_rev):
|
||||
raise vclib.ItemNotFound(path)
|
||||
return old_path
|
||||
|
||||
def created_rev(self, path, rev):
|
||||
# NOTE: We can't use svn_client_propget here because the
|
||||
# interfaces in that layer strip out the properties not meant for
|
||||
# human consumption (such as svn:entry:committed-rev, which we are
|
||||
# using here to get the created revision of PATH@REV).
|
||||
kind = ra.svn_ra_check_path(self.ra_session, path, rev)
|
||||
if kind == core.svn_node_none:
|
||||
raise vclib.ItemNotFound(_path_parts(path))
|
||||
elif kind == core.svn_node_dir:
|
||||
props = get_directory_props(self.ra_session, path, rev)
|
||||
elif kind == core.svn_node_file:
|
||||
fetched_rev, props = ra.svn_ra_get_file(self.ra_session, path, rev, None)
|
||||
return int(props.get(core.SVN_PROP_ENTRY_COMMITTED_REV,
|
||||
SVN_INVALID_REVNUM))
|
||||
lh_rev, c_rev = self._get_last_history_rev(_path_parts(path), rev)
|
||||
return lh_rev
|
||||
|
||||
def last_rev(self, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
|
@@ -43,7 +43,14 @@ def _fix_subversion_exception(e):
|
||||
e.apr_err = e[1]
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = e[0]
|
||||
|
||||
|
||||
### Pre-1.4 Subversion doesn't have svn_path_canonicalize()
|
||||
def _canonicalize_path(path):
|
||||
try:
|
||||
return core.svn_path_canonicalize(path)
|
||||
except AttributeError:
|
||||
return path
|
||||
|
||||
def _allow_all(root, path, pool):
|
||||
"""Generic authz_read_func that permits access to all paths"""
|
||||
return 1
|
||||
@@ -115,9 +122,10 @@ def _rootpath2url(rootpath, path):
|
||||
rootpath = urllib.quote(rootpath)
|
||||
path = urllib.quote(path)
|
||||
if drive:
|
||||
return 'file:///' + drive + rootpath + '/' + path
|
||||
url = 'file:///' + drive + rootpath + '/' + path
|
||||
else:
|
||||
return 'file://' + rootpath + '/' + path
|
||||
url = 'file://' + rootpath + '/' + path
|
||||
return _canonicalize_path(url)
|
||||
|
||||
|
||||
# Given a dictionary REVPROPS of revision properties, pull special
|
||||
@@ -643,6 +651,7 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
is_copy = 0
|
||||
change.base_path = None
|
||||
change.base_rev = None
|
||||
found_unreadable = 1
|
||||
changedpaths[path] = SVNChangedPath(path, rev, pathtype,
|
||||
change.base_path,
|
||||
change.base_rev, action,
|
||||
|
261
lib/viewvc.py
261
lib/viewvc.py
@@ -14,7 +14,7 @@
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
__version__ = '1.1.13'
|
||||
__version__ = '1.1.17'
|
||||
|
||||
# this comes from our library; measure the startup time
|
||||
import debug
|
||||
@@ -24,6 +24,7 @@ debug.t_start('imports')
|
||||
# standard modules that we know are in the path or builtin
|
||||
import sys
|
||||
import os
|
||||
import fnmatch
|
||||
import gzip
|
||||
import mimetypes
|
||||
import re
|
||||
@@ -1017,6 +1018,15 @@ def default_view(mime_type, cfg):
|
||||
return view_markup
|
||||
return view_checkout
|
||||
|
||||
def is_binary_file_mime_type(mime_type, cfg):
|
||||
"""Return True iff MIME_TYPE is set and matches one of the binary
|
||||
file mime type patterns in CFG."""
|
||||
if mime_type:
|
||||
for pattern in cfg.options.binary_mime_types:
|
||||
if fnmatch.fnmatch(mime_type, pattern):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1):
|
||||
"""Return an object holding common hrefs and a viewability flag used
|
||||
for various views of FILENAME at revision REV whose MIME type is
|
||||
@@ -1077,7 +1087,12 @@ def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1):
|
||||
params={'revision': rev},
|
||||
escape=1)
|
||||
|
||||
prefer_markup = default_view(mime_type, request.cfg) == view_markup
|
||||
is_binary_file = is_binary_file_mime_type(mime_type, request.cfg)
|
||||
if is_binary_file:
|
||||
download_text_href = annotate_href = view_href = None
|
||||
prefer_markup = False
|
||||
else:
|
||||
prefer_markup = default_view(mime_type, request.cfg) == view_markup
|
||||
|
||||
return _item(view_href=view_href,
|
||||
download_href=download_href,
|
||||
@@ -1099,6 +1114,29 @@ _re_rewrite_email = re.compile('([-a-zA-Z0-9_.\+]+)@'
|
||||
# Matches revision references
|
||||
_re_rewrite_svnrevref = re.compile(r'\b(r|rev #?|revision #?)([0-9]+)\b')
|
||||
|
||||
class ViewVCHtmlFormatterTokens:
|
||||
def __init__(self, tokens):
|
||||
self.tokens = tokens
|
||||
|
||||
def get_result(self, maxlen=0):
|
||||
"""Format the tokens per the registered set of formatters, and
|
||||
limited to MAXLEN visible characters (or unlimited if MAXLEN is
|
||||
0). Return a 3-tuple containing the formatted result string, the
|
||||
number of visible characters in the result string, and a boolean
|
||||
flag indicating whether or not S was truncated."""
|
||||
out = ''
|
||||
out_len = 0
|
||||
for token in self.tokens:
|
||||
chunk, chunk_len = token.converter(token.match, token.userdata, maxlen)
|
||||
out = out + chunk
|
||||
out_len = out_len + chunk_len
|
||||
if maxlen:
|
||||
maxlen = maxlen - chunk_len
|
||||
if maxlen <= 0:
|
||||
return out, out_len, 1
|
||||
return out, out_len, 0
|
||||
|
||||
|
||||
class ViewVCHtmlFormatter:
|
||||
"""Format a string as HTML-encoded output with customizable markup
|
||||
rules, for example turning strings that look like URLs into anchor links.
|
||||
@@ -1184,6 +1222,30 @@ class ViewVCHtmlFormatter:
|
||||
sapi.escape(trunc_s)), \
|
||||
len(trunc_s)
|
||||
|
||||
def format_custom_url(self, mobj, userdata, maxlen=0):
|
||||
"""Return a 2-tuple containing:
|
||||
- the text represented by MatchObject MOBJ, formatted as an
|
||||
linkified URL created by substituting match groups 0-9 into
|
||||
USERDATA (which is a format string that uses \N to
|
||||
represent the substitution locations) and with no more than
|
||||
MAXLEN characters in the non-HTML-tag portions. If MAXLEN
|
||||
is 0, there is no maximum.
|
||||
- the number of characters returned.
|
||||
"""
|
||||
format = userdata
|
||||
text = mobj.group(0)
|
||||
url = format
|
||||
for i in range(9):
|
||||
try:
|
||||
repl = mobj.group(i)
|
||||
except:
|
||||
repl = ''
|
||||
url = url.replace('\%d' % (i), repl)
|
||||
trunc_s = maxlen and text[:maxlen] or text
|
||||
return '<a href="%s">%s</a>' % (sapi.escape(url),
|
||||
sapi.escape(trunc_s)), \
|
||||
len(trunc_s)
|
||||
|
||||
def format_text(self, s, unused, maxlen=0):
|
||||
"""Return a 2-tuple containing:
|
||||
- the text S, HTML-escaped, containing no more than MAXLEN
|
||||
@@ -1216,20 +1278,14 @@ class ViewVCHtmlFormatter:
|
||||
"""
|
||||
out = ''
|
||||
out_len = 0
|
||||
for token in self._tokenize_text(s):
|
||||
chunk, chunk_len = token.converter(token.match, token.userdata, maxlen)
|
||||
out = out + chunk
|
||||
out_len = out_len + chunk_len
|
||||
if maxlen:
|
||||
maxlen = maxlen - chunk_len
|
||||
if maxlen <= 0:
|
||||
return out, out_len, 1
|
||||
return out, out_len, 0
|
||||
tokens = self.tokenize_text(s)
|
||||
return tokens.get_result()
|
||||
|
||||
def _entity_encode(self, s):
|
||||
return string.join(map(lambda x: '&#%d;' % (ord(x)), s), '')
|
||||
|
||||
def _tokenize_text(self, s):
|
||||
def tokenize_text(self, s):
|
||||
"""Return a ViewVCHtmlFormatterTokens object containing the tokens
|
||||
created when parsing the string S. Callers can use that object's
|
||||
get_result() function to retrieve HTML-formatted text.
|
||||
"""
|
||||
tokens = []
|
||||
# We could just have a "while s:" here instead of "for line: while
|
||||
# line:", but for really large log messages with heavy
|
||||
@@ -1276,36 +1332,80 @@ class ViewVCHtmlFormatter:
|
||||
converter=self.format_text,
|
||||
userdata=None))
|
||||
line = ''
|
||||
return tokens
|
||||
return ViewVCHtmlFormatterTokens(tokens)
|
||||
|
||||
def _entity_encode(self, s):
|
||||
return string.join(map(lambda x: '&#%d;' % (ord(x)), s), '')
|
||||
|
||||
|
||||
def format_log(request, log, maxlen=0, htmlize=1):
|
||||
if not log:
|
||||
return log
|
||||
class LogFormatter:
|
||||
def __init__(self, request, log):
|
||||
self.request = request
|
||||
self.log = log or ''
|
||||
self.tokens = None
|
||||
self.cache = {} # (maxlen, htmlize) => resulting_log
|
||||
|
||||
cfg = request.cfg
|
||||
if htmlize:
|
||||
lf = ViewVCHtmlFormatter()
|
||||
lf.add_formatter(_re_rewrite_url, lf.format_url)
|
||||
if request.roottype == 'svn':
|
||||
def revision_to_url(rev):
|
||||
return request.get_url(view_func=view_revision,
|
||||
params={'revision': rev},
|
||||
escape=1)
|
||||
lf.add_formatter(_re_rewrite_svnrevref, lf.format_svnrevref,
|
||||
revision_to_url)
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_truncated)
|
||||
elif cfg.options.mangle_email_addresses == 1:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_obfuscated)
|
||||
def get(self, maxlen=0, htmlize=1):
|
||||
cfg = self.request.cfg
|
||||
|
||||
# Prefer the cache.
|
||||
if self.cache.has_key((maxlen, htmlize)):
|
||||
return self.cache[(maxlen, htmlize)]
|
||||
|
||||
# If we are HTML-izing...
|
||||
if htmlize:
|
||||
# ...and we don't yet have ViewVCHtmlFormatter() object tokens...
|
||||
if not self.tokens:
|
||||
# ... then get them.
|
||||
lf = ViewVCHtmlFormatter()
|
||||
|
||||
# Rewrite URLs.
|
||||
lf.add_formatter(_re_rewrite_url, lf.format_url)
|
||||
|
||||
# Rewrite Subversion revision references.
|
||||
if self.request.roottype == 'svn':
|
||||
def revision_to_url(rev):
|
||||
return self.request.get_url(view_func=view_revision,
|
||||
params={'revision': rev},
|
||||
escape=1)
|
||||
lf.add_formatter(_re_rewrite_svnrevref, lf.format_svnrevref,
|
||||
revision_to_url)
|
||||
|
||||
# Rewrite email addresses.
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_truncated)
|
||||
elif cfg.options.mangle_email_addresses == 1:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_obfuscated)
|
||||
else:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email)
|
||||
|
||||
# Add custom rewrite handling per configuration.
|
||||
for rule in cfg.options.custom_log_formatting:
|
||||
rule = rule.replace('\\:', '\x01')
|
||||
regexp, format = map(lambda x: x.strip(), rule.split(':', 1))
|
||||
regexp = regexp.replace('\x01', ':')
|
||||
format = format.replace('\x01', ':')
|
||||
lf.add_formatter(re.compile(regexp), lf.format_custom_url, format)
|
||||
|
||||
# Tokenize the log message.
|
||||
self.tokens = lf.tokenize_text(self.log)
|
||||
|
||||
# Use our formatter to ... you know ... format.
|
||||
log, log_len, truncated = self.tokens.get_result(maxlen)
|
||||
result_log = log + (truncated and '…' or '')
|
||||
|
||||
# But if we're not HTML-izing...
|
||||
else:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email)
|
||||
log, log_len, truncated = lf.get_result(log, maxlen)
|
||||
return log + (truncated and '…' or '')
|
||||
else:
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
log = re.sub(_re_rewrite_email, r'\1@...', log)
|
||||
return maxlen and log[:maxlen] or log
|
||||
# ...then do much more simplistic transformations as necessary.
|
||||
log = self.log
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
log = re.sub(_re_rewrite_email, r'\1@...', log)
|
||||
result_log = maxlen and log[:maxlen] or log
|
||||
|
||||
# In either case, populate the cache and return the results.
|
||||
self.cache[(maxlen, htmlize)] = result_log
|
||||
return result_log
|
||||
|
||||
|
||||
_time_desc = {
|
||||
1 : 'second',
|
||||
@@ -1693,7 +1793,8 @@ def get_itemprops(request, path_parts, rev):
|
||||
propnames.sort()
|
||||
props = []
|
||||
for name in propnames:
|
||||
value = format_log(request, itemprops[name])
|
||||
lf = LogFormatter(request, itemprops[name])
|
||||
value = lf.get(maxlen=0, htmlize=1)
|
||||
undisplayable = ezt.boolean(0)
|
||||
# skip non-utf8 property names
|
||||
try:
|
||||
@@ -1745,6 +1846,11 @@ def markup_or_annotate(request, is_annotate):
|
||||
revision = None
|
||||
mime_type, encoding = calculate_mime_type(request, path, rev)
|
||||
|
||||
# Is this display blocked by 'binary_mime_types' configuration?
|
||||
if is_binary_file_mime_type(mime_type, cfg):
|
||||
raise debug.ViewVCException('Display of binary file content disabled '
|
||||
'by configuration', '403 Forbidden')
|
||||
|
||||
# Is this a viewable image type?
|
||||
if is_viewable_image(mime_type) \
|
||||
and 'co' in cfg.options.allowed_views:
|
||||
@@ -1840,14 +1946,19 @@ def markup_or_annotate(request, is_annotate):
|
||||
}))
|
||||
|
||||
if cfg.options.show_log_in_markup:
|
||||
options = {'svn_latest_log': 1} ### FIXME: No longer needed?
|
||||
options = {
|
||||
'svn_latest_log': 1, ### FIXME: Use of this magical value is uncool.
|
||||
'svn_cross_copies': 1,
|
||||
}
|
||||
revs = request.repos.itemlog(path, revision, vclib.SORTBY_REV,
|
||||
0, 1, options)
|
||||
entry = revs[-1]
|
||||
lf = LogFormatter(request, entry.log)
|
||||
|
||||
data['date'] = make_time_string(entry.date, cfg)
|
||||
data['author'] = entry.author
|
||||
data['changed'] = entry.changed
|
||||
data['log'] = format_log(request, entry.log)
|
||||
data['log'] = lf.get(maxlen=0, htmlize=1)
|
||||
data['size'] = entry.size
|
||||
|
||||
if entry.date is not None:
|
||||
@@ -2083,9 +2194,11 @@ def view_directory(request):
|
||||
row.date = make_time_string(file.date, cfg)
|
||||
row.ago = html_time(request, file.date)
|
||||
if cfg.options.show_logs:
|
||||
row.log = format_log(request, file.log)
|
||||
row.short_log = format_log(request, file.log,
|
||||
maxlen=cfg.options.short_log_len)
|
||||
debug.t_start("dirview_logformat")
|
||||
lf = LogFormatter(request, file.log)
|
||||
row.log = lf.get(maxlen=0, htmlize=1)
|
||||
row.short_log = lf.get(maxlen=cfg.options.short_log_len, htmlize=1)
|
||||
debug.t_end("dirview_logformat")
|
||||
row.lockinfo = file.lockinfo
|
||||
row.anchor = request.server.escape(file.name)
|
||||
row.name = request.server.escape(file.name)
|
||||
@@ -2438,6 +2551,7 @@ def view_log(request):
|
||||
sortby = vclib.SORTBY_DEFAULT
|
||||
|
||||
first = last = 0
|
||||
log_pagestart = None
|
||||
if cfg.options.log_pagesize:
|
||||
log_pagestart = int(request.query_dict.get('log_pagestart', 0))
|
||||
total = cfg.options.log_pagesextra * cfg.options.log_pagesize
|
||||
@@ -2462,7 +2576,6 @@ def view_log(request):
|
||||
entry.ago = None
|
||||
if rev.date is not None:
|
||||
entry.ago = html_time(request, rev.date, 1)
|
||||
entry.log = format_log(request, rev.log or '')
|
||||
entry.size = rev.size
|
||||
entry.lockinfo = rev.lockinfo
|
||||
entry.branch_point = None
|
||||
@@ -2470,6 +2583,9 @@ def view_log(request):
|
||||
entry.orig_path = None
|
||||
entry.copy_path = None
|
||||
|
||||
lf = LogFormatter(request, rev.log or '')
|
||||
entry.log = lf.get(maxlen=0, htmlize=1)
|
||||
|
||||
entry.view_href = None
|
||||
entry.download_href = None
|
||||
entry.download_text_href = None
|
||||
@@ -2559,7 +2675,8 @@ def view_log(request):
|
||||
if selected_rev != entry.rev:
|
||||
entry.sel_for_diff_href = \
|
||||
request.get_url(view_func=view_log,
|
||||
params={'r1': entry.rev},
|
||||
params={'r1': entry.rev,
|
||||
'log_pagestart': log_pagestart},
|
||||
escape=1)
|
||||
if entry.prev is not None:
|
||||
entry.diff_to_prev_href = \
|
||||
@@ -2700,7 +2817,9 @@ def view_log(request):
|
||||
|
||||
if cfg.options.log_pagesize:
|
||||
data['log_paging_action'], data['log_paging_hidden_values'] = \
|
||||
request.get_form(params={'log_pagestart': None})
|
||||
request.get_form(params={'log_pagestart': None,
|
||||
'r1': selected_rev,
|
||||
})
|
||||
data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0))
|
||||
data['entries'] = paging_sws(data, 'entries', data['log_pagestart'],
|
||||
'rev', cfg.options.log_pagesize,
|
||||
@@ -2966,7 +3085,7 @@ class DiffSource:
|
||||
return _item(type='header',
|
||||
line_info_left=match.group(1),
|
||||
line_info_right=match.group(2),
|
||||
line_info_extra=match.group(3))
|
||||
line_info_extra=self._format_text(match.group(3)))
|
||||
|
||||
if line[0] == '\\':
|
||||
# \ No newline at end of file
|
||||
@@ -3181,6 +3300,13 @@ def view_patch(request):
|
||||
query_dict = request.query_dict
|
||||
p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request)
|
||||
|
||||
mime_type1, encoding1 = calculate_mime_type(request, p1, rev1)
|
||||
mime_type2, encoding2 = calculate_mime_type(request, p2, rev2)
|
||||
if is_binary_file_mime_type(mime_type1, cfg) or \
|
||||
is_binary_file_mime_type(mime_type2, cfg):
|
||||
raise debug.ViewVCException('Display of binary file content disabled '
|
||||
'by configuration', '403 Forbidden')
|
||||
|
||||
# In the absence of a format dictation in the CGI params, we'll let
|
||||
# use the configured diff format, allowing 'c' to mean 'c' and
|
||||
# anything else to mean 'u'.
|
||||
@@ -3221,6 +3347,13 @@ def view_diff(request):
|
||||
query_dict = request.query_dict
|
||||
p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request)
|
||||
|
||||
mime_type1, encoding1 = calculate_mime_type(request, p1, rev1)
|
||||
mime_type2, encoding2 = calculate_mime_type(request, p2, rev2)
|
||||
if is_binary_file_mime_type(mime_type1, cfg) or \
|
||||
is_binary_file_mime_type(mime_type2, cfg):
|
||||
raise debug.ViewVCException('Display of binary file content disabled '
|
||||
'by configuration', '403 Forbidden')
|
||||
|
||||
# since templates are in use and subversion allows changes to the dates,
|
||||
# we can't provide a strong etag
|
||||
if check_freshness(request, None, '%s-%s' % (rev1, rev2), weak=1):
|
||||
@@ -3318,7 +3451,8 @@ def view_diff(request):
|
||||
fvi = get_file_view_info(request, path_left, rev1)
|
||||
left = _item(date=make_time_string(log_entry1.date, cfg),
|
||||
author=log_entry1.author,
|
||||
log=format_log(request, log_entry1.log),
|
||||
log=LogFormatter(request,
|
||||
log_entry1.log).get(maxlen=0, htmlize=1),
|
||||
size=log_entry1.size,
|
||||
ago=ago1,
|
||||
path=path_left,
|
||||
@@ -3334,7 +3468,8 @@ def view_diff(request):
|
||||
fvi = get_file_view_info(request, path_right, rev2)
|
||||
right = _item(date=make_time_string(log_entry2.date, cfg),
|
||||
author=log_entry2.author,
|
||||
log=format_log(request, log_entry2.log),
|
||||
log=LogFormatter(request,
|
||||
log_entry2.log).get(maxlen=0, htmlize=1),
|
||||
size=log_entry2.size,
|
||||
ago=ago2,
|
||||
path=path_right,
|
||||
@@ -3582,7 +3717,8 @@ def view_revision(request):
|
||||
propnames.sort()
|
||||
props = []
|
||||
for name in propnames:
|
||||
value = format_log(request, revprops[name])
|
||||
lf = LogFormatter(request, revprops[name])
|
||||
value = lf.get(maxlen=0, htmlize=1)
|
||||
undisplayable = ezt.boolean(0)
|
||||
# skip non-utf8 property names
|
||||
try:
|
||||
@@ -3705,13 +3841,14 @@ def view_revision(request):
|
||||
escape=1)
|
||||
jump_rev_action, jump_rev_hidden_values = \
|
||||
request.get_form(params={'revision': None})
|
||||
|
||||
|
||||
lf = LogFormatter(request, msg)
|
||||
data = common_template_data(request)
|
||||
data.merge(ezt.TemplateData({
|
||||
'rev' : str(rev),
|
||||
'author' : author,
|
||||
'date' : date_str,
|
||||
'log' : format_log(request, msg),
|
||||
'log' : lf.get(maxlen=0, htmlize=1),
|
||||
'properties' : props,
|
||||
'ago' : date is not None and html_time(request, date, 1) or None,
|
||||
'changes' : changes,
|
||||
@@ -4069,9 +4206,10 @@ def build_commit(request, files, max_files, dir_strip, format):
|
||||
commit.log = None
|
||||
commit.short_log = None
|
||||
else:
|
||||
commit.log = format_log(request, desc, 0, format != 'rss')
|
||||
commit.short_log = format_log(request, desc, cfg.options.short_log_len,
|
||||
format != 'rss')
|
||||
lf = LogFormatter(request, desc)
|
||||
htmlize = (format != 'rss')
|
||||
commit.log = lf.get(maxlen=0, htmlize=htmlize)
|
||||
commit.short_log = lf.get(maxlen=cfg.options.short_log_len, htmlize=htmlize)
|
||||
commit.author = request.server.escape(author)
|
||||
commit.rss_date = make_rss_time_string(date, request.cfg)
|
||||
if request.roottype == 'svn':
|
||||
@@ -4365,8 +4503,9 @@ def list_roots(request):
|
||||
date, author, msg, revprops, changes = repos.revinfo(youngest_rev)
|
||||
date_str = make_time_string(date, cfg)
|
||||
ago = html_time(request, date)
|
||||
log = format_log(request, msg)
|
||||
short_log = format_log(request, msg, maxlen=cfg.options.short_log_len)
|
||||
lf = LogFormatter(request, msg)
|
||||
log = lf.get(maxlen=0, htmlize=1)
|
||||
short_log = lf.get(maxlen=cfg.options.short_log_len, htmlize=1)
|
||||
lastmod = _item(ago=ago, author=author, date=date_str, log=log,
|
||||
short_log=short_log, rev=str(youngest_rev))
|
||||
except:
|
||||
|
@@ -92,13 +92,15 @@ numbers, and not literal):
|
||||
Also, drop a copy of the archive files into the root directory of
|
||||
the viewvc.org website (unversioned).
|
||||
|
||||
15. On trunk, update the websites (both the viewvc.org/ and www/ ones)
|
||||
to refer to the new release files, and copy the CHANGES for the
|
||||
new release into trunk's CHANGES file.
|
||||
15. Update the Tigris.org website (^/trunk/www/index.html) to refer to
|
||||
the new release files and commit.
|
||||
|
||||
16. Edit the file 'lib/viewvc.py' again, incrementing the patch number
|
||||
assigned to the __version__ variable. Add a new empty block in
|
||||
the branch's CHANGES file. Commit your changes:
|
||||
svn ci -m "Bump latest advertised release."
|
||||
|
||||
16. Back on the release branch, edit the file 'lib/viewvc.py' again,
|
||||
incrementing the patch number assigned to the __version__
|
||||
variable. Add a new empty block in the branch's CHANGES file.
|
||||
Commit your changes:
|
||||
|
||||
svn ci -m "Begin a new release cycle."
|
||||
|
||||
@@ -111,8 +113,13 @@ numbers, and not literal):
|
||||
http://viewvc.tigris.org/issues/editmilestones.cgi?component=viewvc&action=add
|
||||
|
||||
18. Send to the announce@ list a message explaining all the cool new
|
||||
features, and post similar announcements to other places interested
|
||||
in this sort of stuff, such as Freshmeat (http://www.freshmeat.net).
|
||||
features.
|
||||
|
||||
19. Merge CHANGES for this release into the CHANGES file for newer
|
||||
release lines.
|
||||
http://viewvc.tigris.org/ds/viewForumSummary.do?dsForumId=4253
|
||||
|
||||
19. Post a new release notification at Freecode.
|
||||
|
||||
https://freecode.com/projects/viewvc/releases/new
|
||||
|
||||
20. Merge CHANGES for this release into the CHANGES file for newer
|
||||
release lines and commit.
|
||||
|
@@ -9,7 +9,11 @@
|
||||
[# ------------------------------------------------------------------------- ]
|
||||
|
||||
[# setup page definitions]
|
||||
[define page_title]Contents of /[where][end]
|
||||
[is annotation "annotated"]
|
||||
[define page_title]Annotation of /[where][end]
|
||||
[else]
|
||||
[define page_title]Contents of /[where][end]
|
||||
[end]
|
||||
[define help_href][docroot]/help_rootview.html[end]
|
||||
[# end]
|
||||
|
||||
|
Reference in New Issue
Block a user