"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "lib/viewvc.py" between
viewvc-1.1.28.tar.gz and viewvc-1.2.1.tar.gz

About: ViewVC is a browser interface for CVS and Subversion version control repositories.

viewvc.py  (viewvc-1.1.28):viewvc.py  (viewvc-1.2.1)
skipping to change at line 17 skipping to change at line 17
# distribution or at http://viewvc.org/license-1.html. # distribution or at http://viewvc.org/license-1.html.
# #
# For more information, visit http://viewvc.org/ # For more information, visit http://viewvc.org/
# #
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# #
# viewvc: View CVS/SVN repositories via a web browser # viewvc: View CVS/SVN repositories via a web browser
# #
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
__version__ = '1.1.28' __version__ = '1.2.1'
# this comes from our library; measure the startup time # this comes from our library; measure the startup time
import debug import debug
debug.t_start('startup') debug.t_start('startup')
debug.t_start('imports') debug.t_start('imports')
# standard modules that we know are in the path or builtin # standard modules that we know are in the path or builtin
import sys import sys
import os import os
import calendar
import copy
import fnmatch import fnmatch
import gzip import gzip
import mimetypes import mimetypes
import re import re
import rfc822 import rfc822
import stat import stat
import string
import struct import struct
import tempfile import tempfile
import time import time
import types import types
import urllib import urllib
# These modules come from our library (the stub has set up the path) # These modules come from our library (the stub has set up the path)
from common import _item, _RCSDIFF_NO_CHANGES, _RCSDIFF_IS_BINARY, _RCSDIFF_ERRO R, TemplateData
import accept import accept
import compat
import config import config
import ezt import ezt
import popen import popen
import sapi import sapi
import vcauth import vcauth
import vclib import vclib
import vclib.ccvs import vclib.ccvs
import vclib.svn import vclib.svn
try: try:
skipping to change at line 91 skipping to change at line 92
'sortdir', 'sortdir',
'logsort', 'logsort',
'diff_format', 'diff_format',
'search', 'search',
'limit_changes', 'limit_changes',
] ]
# for reading/writing between a couple descriptors # for reading/writing between a couple descriptors
CHUNK_SIZE = 8192 CHUNK_SIZE = 8192
# for rcsdiff processing of header
_RCSDIFF_IS_BINARY = 'binary-diff'
_RCSDIFF_ERROR = 'error'
# special characters that don't need to be URL encoded # special characters that don't need to be URL encoded
_URL_SAFE_CHARS = "/*~" _URL_SAFE_CHARS = "/*~"
class Request: class Request:
def __init__(self, server, cfg): def __init__(self, server, cfg):
self.server = server self.server = server
self.cfg = cfg self.cfg = cfg
self.script_name = _normalize_path(server.getenv('SCRIPT_NAME', '')) self.script_name = _normalize_path(server.getenv('SCRIPT_NAME', ''))
self.browser = server.getenv('HTTP_USER_AGENT', 'unknown') self.browser = server.getenv('HTTP_USER_AGENT', 'unknown')
skipping to change at line 124 skipping to change at line 121
self.kv = cfg.load_kv_files(self.language) self.kv = cfg.load_kv_files(self.language)
# check for an authenticated username # check for an authenticated username
self.username = server.getenv('REMOTE_USER') self.username = server.getenv('REMOTE_USER')
# if we allow compressed output, see if the client does too # if we allow compressed output, see if the client does too
self.gzip_compress_level = 0 self.gzip_compress_level = 0
if cfg.options.allow_compress: if cfg.options.allow_compress:
http_accept_encoding = os.environ.get("HTTP_ACCEPT_ENCODING", "") http_accept_encoding = os.environ.get("HTTP_ACCEPT_ENCODING", "")
if "gzip" in filter(None, if "gzip" in filter(None,
map(lambda x: string.strip(x), map(lambda x: x.strip(),
string.split(http_accept_encoding, ","))): http_accept_encoding.split(','))):
self.gzip_compress_level = 9 # make this configurable? self.gzip_compress_level = 9 # make this configurable?
def run_viewvc(self): def run_viewvc(self):
cfg = self.cfg cfg = self.cfg
# This function first parses the query string and sets the following # This function first parses the query string and sets the following
# variables. Then it executes the request. # variables. Then it executes the request.
self.view_func = None # function to call to process the request self.view_func = None # function to call to process the request
self.repos = None # object representing current repository self.repos = None # object representing current repository
skipping to change at line 219 skipping to change at line 216
# Figure out root name # Figure out root name
self.rootname = self.query_dict.get('root') self.rootname = self.query_dict.get('root')
if self.rootname == view_roots_magic: if self.rootname == view_roots_magic:
del self.query_dict['root'] del self.query_dict['root']
self.rootname = "" self.rootname = ""
needs_redirect = 1 needs_redirect = 1
elif self.rootname is None: elif self.rootname is None:
if cfg.options.root_as_url_component: if cfg.options.root_as_url_component:
if path_parts: if path_parts:
self.rootname = path_parts.pop(0) roottype, rootpath, self.rootname, new_path_parts = \
locate_root_from_path(cfg, path_parts)
if roottype is None:
# Perhaps the root name is candidate for renaming...
# Take care of old-new roots mapping
for old_root, new_root in cfg.general.renamed_roots.iteritems():
pp = _path_parts(old_root)
if _path_starts_with(path_parts, pp):
path_parts = path_parts[len(pp):]
self.rootname = new_root
needs_redirect = 1
if self.rootname is None:
# Not found; interpret whole path as root, to show as error
self.rootname = _path_join(path_parts)
path_parts = []
else:
path_parts = new_path_parts
else: else:
self.rootname = "" self.rootname = ""
elif self.view_func != view_roots: elif self.view_func != view_roots:
self.rootname = cfg.general.default_root self.rootname = cfg.general.default_root
elif cfg.options.root_as_url_component: elif cfg.options.root_as_url_component:
needs_redirect = 1 needs_redirect = 1
# Take care of old-new roots mapping # Take care of old-new roots mapping
for old_root, new_root in cfg.general.renamed_roots.items(): for old_root, new_root in cfg.general.renamed_roots.iteritems():
if self.rootname == old_root: if self.rootname == old_root:
self.rootname = new_root self.rootname = new_root
needs_redirect = 1 needs_redirect = 1
self.where = _path_join(path_parts) self.where = _path_join(path_parts)
self.path_parts = path_parts self.path_parts = path_parts
if self.rootname: if self.rootname:
roottype, rootpath = locate_root(cfg, self.rootname) roottype, rootpath = locate_root(cfg, self.rootname)
if roottype: if roottype:
skipping to change at line 366 skipping to change at line 379
if self.view_func is None: if self.view_func is None:
# view parameter is not set, try looking at pathtype and the # view parameter is not set, try looking at pathtype and the
# other parameters # other parameters
if not self.rootname: if not self.rootname:
self.view_func = view_roots self.view_func = view_roots
elif self.pathtype == vclib.DIR: elif self.pathtype == vclib.DIR:
# ViewCVS 0.9.2 used to put ?tarball=1 at the end of tarball urls # ViewCVS 0.9.2 used to put ?tarball=1 at the end of tarball urls
if self.query_dict.has_key('tarball'): if self.query_dict.has_key('tarball'):
self.view_func = download_tarball self.view_func = download_tarball
elif self.query_dict.has_key('r1') and self.query_dict.has_key('r2'):
self.view_func = view_diff
else: else:
self.view_func = view_directory self.view_func = view_directory
elif self.pathtype == vclib.FILE: elif self.pathtype == vclib.FILE:
if self.query_dict.has_key('r1') and self.query_dict.has_key('r2'): if self.query_dict.has_key('r1') and self.query_dict.has_key('r2'):
self.view_func = view_diff self.view_func = view_diff
elif self.query_dict.has_key('annotate'): elif self.query_dict.has_key('annotate'):
self.view_func = view_annotate self.view_func = view_annotate
elif self.query_dict.has_key('graph'): elif self.query_dict.has_key('graph'):
if not self.query_dict.has_key('makeimage'): if not self.query_dict.has_key('makeimage'):
self.view_func = view_cvsgraph self.view_func = view_cvsgraph
skipping to change at line 422 skipping to change at line 437
self.view_func(self) self.view_func(self)
debug.t_end('view-func') debug.t_end('view-func')
def get_url(self, escape=0, partial=0, prefix=0, **args): def get_url(self, escape=0, partial=0, prefix=0, **args):
"""Constructs a link to another ViewVC page just like the get_link """Constructs a link to another ViewVC page just like the get_link
function except that it returns a single URL instead of a URL function except that it returns a single URL instead of a URL
split into components. If PREFIX is set, include the protocol and split into components. If PREFIX is set, include the protocol and
server name portions of the URL.""" server name portions of the URL."""
url, params = apply(self.get_link, (), args) url, params = apply(self.get_link, (), args)
qs = compat.urlencode(params) qs = urllib.urlencode(params)
if qs: if qs:
result = urllib.quote(url, _URL_SAFE_CHARS) + '?' + qs result = urllib.quote(url, _URL_SAFE_CHARS) + '?' + qs
else: else:
result = urllib.quote(url, _URL_SAFE_CHARS) result = urllib.quote(url, _URL_SAFE_CHARS)
if partial: if partial:
result = result + (qs and '&' or '?') result = result + (qs and '&' or '?')
if escape: if escape:
result = self.server.escape(result) result = self.server.escape(result)
if prefix: if prefix:
skipping to change at line 598 skipping to change at line 613
for name, value in params.items(): for name, value in params.items():
if value is None: if value is None:
del params[name] del params[name]
return url, params return url, params
def _path_parts(path): def _path_parts(path):
"""Split up a repository path into a list of path components""" """Split up a repository path into a list of path components"""
# clean it up. this removes duplicate '/' characters and any that may # clean it up. this removes duplicate '/' characters and any that may
# exist at the front or end of the path. # exist at the front or end of the path.
return filter(None, string.split(path, '/')) return filter(None, path.split('/'))
def _normalize_path(path): def _normalize_path(path):
"""Collapse leading slashes in the script name """Collapse leading slashes in the script name
You only get multiple slashes in the script name when users accidentally You only get multiple slashes in the script name when users accidentally
type urls like http://abc.com//viewvc.cgi/, but we correct for it type urls like http://abc.com//viewvc.cgi/, but we correct for it
because we output the script name in links and web browsers because we output the script name in links and web browsers
interpret //viewvc.cgi/ as http://viewvc.cgi/ interpret //viewvc.cgi/ as http://viewvc.cgi/
""" """
skipping to change at line 709 skipping to change at line 724
'annotate' : _re_validate_revnum, 'annotate' : _re_validate_revnum,
'graph' : _re_validate_revnum, 'graph' : _re_validate_revnum,
'makeimage' : _re_validate_boolint, 'makeimage' : _re_validate_boolint,
'r1' : _re_validate_revnum, 'r1' : _re_validate_revnum,
'tr1' : _re_validate_revnum, 'tr1' : _re_validate_revnum,
'r2' : _re_validate_revnum, 'r2' : _re_validate_revnum,
'tr2' : _re_validate_revnum, 'tr2' : _re_validate_revnum,
'revision' : _re_validate_revnum, 'revision' : _re_validate_revnum,
'content-type' : _validate_mimetype, 'content-type' : _validate_mimetype,
# for cvsgraph
'gflip' : _re_validate_boolint,
'gbbox' : _re_validate_boolint,
'gshow' : _re_validate_alpha,
'gleft' : _re_validate_boolint,
'gmaxtag' : _re_validate_number,
# for query # for query
'file_match' : _re_validate_alpha, 'file_match' : _re_validate_alpha,
'branch_match' : _re_validate_alpha, 'branch_match' : _re_validate_alpha,
'who_match' : _re_validate_alpha, 'who_match' : _re_validate_alpha,
'comment_match' : _re_validate_alpha, 'comment_match' : _re_validate_alpha,
'dir' : None, 'dir' : None,
'file' : None, 'file' : None,
'branch' : None, 'branch' : None,
'who' : None, 'who' : None,
'comment' : None, 'comment' : None,
skipping to change at line 732 skipping to change at line 754
'mindate' : _re_validate_datetime, 'mindate' : _re_validate_datetime,
'maxdate' : _re_validate_datetime, 'maxdate' : _re_validate_datetime,
'format' : _re_validate_alpha, 'format' : _re_validate_alpha,
# for redirect_pathrev # for redirect_pathrev
'orig_path' : None, 'orig_path' : None,
'orig_pathtype' : None, 'orig_pathtype' : None,
'orig_pathrev' : None, 'orig_pathrev' : None,
'orig_view' : None, 'orig_view' : None,
# deprecated # deprecated - these are no longer used, but kept around so that
# bookmarked URLs still "work" (for some definition thereof) after a
# ViewVC upgrade.
'parent' : _re_validate_boolint, 'parent' : _re_validate_boolint,
'rev' : _re_validate_revnum, 'rev' : _re_validate_revnum,
'tarball' : _re_validate_boolint, 'tarball' : _re_validate_boolint,
'hidecvsroot' : _re_validate_boolint, 'hidecvsroot' : _re_validate_boolint,
'limit' : _re_validate_number,
} }
def _path_join(path_parts): def _path_join(path_parts):
return string.join(path_parts, '/') return '/'.join(path_parts)
def _path_starts_with(path_parts, first_path_parts):
if not path_parts:
return False
if len(path_parts) < len(first_path_parts):
return False
return path_parts[0:len(first_path_parts)] == first_path_parts
def _strip_suffix(suffix, path_parts, rev, pathtype, repos, view_func): def _strip_suffix(suffix, path_parts, rev, pathtype, repos, view_func):
"""strip the suffix from a repository path if the resulting path """strip the suffix from a repository path if the resulting path
is of the specified type, otherwise return None""" is of the specified type, otherwise return None"""
if not path_parts: if not path_parts:
return None return None
l = len(suffix) l = len(suffix)
if path_parts[-1][-l:] == suffix: if path_parts[-1][-l:] == suffix:
path_parts = path_parts[:] path_parts = path_parts[:]
if len(path_parts[-1]) == l: if len(path_parts[-1]) == l:
skipping to change at line 882 skipping to change at line 914
# if not available, then the document isn't fresh. # if not available, then the document isn't fresh.
if etag is not None: if etag is not None:
isfresh = (request_etag == etag) isfresh = (request_etag == etag)
elif mtime is not None: elif mtime is not None:
isfresh = (request_mtime >= mtime) isfresh = (request_mtime >= mtime)
else: else:
isfresh = 0 isfresh = 0
# require revalidation after the configured amount of time # require revalidation after the configured amount of time
if cfg and cfg.options.http_expiration_time >= 0: if cfg and cfg.options.http_expiration_time >= 0:
expiration = compat.formatdate(time.time() + expiration = rfc822.formatdate(time.time() +
cfg.options.http_expiration_time) cfg.options.http_expiration_time)
request.server.addheader('Expires', expiration) request.server.addheader('Expires', expiration)
request.server.addheader('Cache-Control', request.server.addheader('Cache-Control',
'max-age=%d' % cfg.options.http_expiration_time) 'max-age=%d' % cfg.options.http_expiration_time)
if isfresh: if isfresh:
request.server.header(status='304 Not Modified') request.server.header(status='304 Not Modified')
else: else:
if etag is not None: if etag is not None:
request.server.addheader('ETag', etag) request.server.addheader('ETag', etag)
if mtime is not None: if mtime is not None:
request.server.addheader('Last-Modified', compat.formatdate(mtime)) request.server.addheader('Last-Modified', rfc822.formatdate(mtime))
return isfresh return isfresh
def get_view_template(cfg, view_name, language="en"): def get_view_template(cfg, view_name, language="en"):
# See if the configuration specifies a template for this view. If # See if the configuration specifies a template for this view. If
# not, use the default template path for this view. # not, use the default template path for this view.
tname = vars(cfg.templates).get(view_name) or view_name + ".ezt" tname = vars(cfg.templates).get(view_name) or view_name + ".ezt"
# Template paths are relative to the configurated template_dir (if # Template paths are relative to the configurated template_dir (if
# any, "templates" otherwise), so build the template path as such. # any, "templates" otherwise), so build the template path as such.
tname = os.path.join(cfg.options.template_dir or "templates", tname) tname = os.path.join(cfg.options.template_dir or "templates", tname)
# Allow per-language template selection. # Allow per-language template selection.
tname = string.replace(tname, '%lang%', language) tname = tname.replace('%lang%', language)
# Finally, construct the whole template path. # Finally, construct the whole template path.
tname = cfg.path(tname) tname = cfg.path(tname)
debug.t_start('ezt-parse') debug.t_start('ezt-parse')
template = ezt.Template(tname) template = ezt.Template(tname)
debug.t_end('ezt-parse') debug.t_end('ezt-parse')
return template return template
skipping to change at line 1005 skipping to change at line 1037
item.href = request.get_url(view_func=view_log, item.href = request.get_url(view_func=view_log,
where=_path_join(path_parts), where=_path_join(path_parts),
pathtype=vclib.FILE, pathtype=vclib.FILE,
params={}, escape=1) params={}, escape=1)
items.append(item) items.append(item)
return items return items
def prep_tags(request, tags): def prep_tags(request, tags):
url, params = request.get_link(params={'pathrev': None}) url, params = request.get_link(params={'pathrev': None})
params = compat.urlencode(params) params = urllib.urlencode(params)
if params: if params:
url = urllib.quote(url, _URL_SAFE_CHARS) + '?' + params + '&pathrev=' url = urllib.quote(url, _URL_SAFE_CHARS) + '?' + params + '&pathrev='
else: else:
url = urllib.quote(url, _URL_SAFE_CHARS) + '?pathrev=' url = urllib.quote(url, _URL_SAFE_CHARS) + '?pathrev='
url = request.server.escape(url) url = request.server.escape(url)
links = [ ] links = [ ]
for tag in tags: for tag in tags:
links.append(_item(name=tag.name, href=url+tag.name)) links.append(_item(name=tag.name, href=url+tag.name))
links.sort(lambda a, b: cmp(a.name, b.name)) links.sort(lambda a, b: cmp(a.name, b.name))
skipping to change at line 1056 skipping to change at line 1088
def is_binary_file_mime_type(mime_type, cfg): def is_binary_file_mime_type(mime_type, cfg):
"""Return True iff MIME_TYPE is set and matches one of the binary """Return True iff MIME_TYPE is set and matches one of the binary
file mime type patterns in CFG.""" file mime type patterns in CFG."""
if mime_type: if mime_type:
for pattern in cfg.options.binary_mime_types: for pattern in cfg.options.binary_mime_types:
if fnmatch.fnmatch(mime_type, pattern): if fnmatch.fnmatch(mime_type, pattern):
return True return True
return False return False
def is_dir_ignored_file(file_name, cfg):
"""Return True if FILE_NAME is set and matches one of the file names
or extensions to be ignored in directory listing per CFG."""
if file_name:
for pattern in cfg.options.dir_ignored_files:
if fnmatch.fnmatch(file_name, pattern):
return True
return False
def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1): 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 """Return an object holding common hrefs and a viewability flag used
for various views of FILENAME at revision REV whose MIME type is for various views of FILENAME at revision REV whose MIME type is
MIME_TYPE. MIME_TYPE.
The object's members include: The object's members include:
view_href view_href
download_href download_href
download_text_href download_text_href
annotate_href annotate_href
skipping to change at line 1319 skipping to change at line 1360
def tokenize_text(self, s): def tokenize_text(self, s):
"""Return a ViewVCHtmlFormatterTokens object containing the tokens """Return a ViewVCHtmlFormatterTokens object containing the tokens
created when parsing the string S. Callers can use that object's created when parsing the string S. Callers can use that object's
get_result() function to retrieve HTML-formatted text. get_result() function to retrieve HTML-formatted text.
""" """
tokens = [] tokens = []
# We could just have a "while s:" here instead of "for line: while # We could just have a "while s:" here instead of "for line: while
# line:", but for really large log messages with heavy # line:", but for really large log messages with heavy
# tokenization, the cost in both performance and memory # tokenization, the cost in both performance and memory
# consumption of the approach taken was atrocious. # consumption of the approach taken was atrocious.
for line in string.split(string.replace(s, '\r\n', '\n'), '\n'): for line in s.replace('\r\n', '\n').split('\n'):
line = line + '\n' line = line + '\n'
while line: while line:
best_match = best_conv = best_userdata = None best_match = best_conv = best_userdata = None
for test in self._formatters: for test in self._formatters:
match = test[0].search(line) match = test[0].search(line)
# If we find and match and (a) its our first one, or (b) it # If we find and match and (a) its our first one, or (b) it
# matches text earlier than our previous best match, or (c) it # matches text earlier than our previous best match, or (c) it
# matches text at the same location as our previous best match # matches text at the same location as our previous best match
# but extends to cover more text than that match, then this is # but extends to cover more text than that match, then this is
# our new best match. # our new best match.
skipping to change at line 1363 skipping to change at line 1404
line = line[end:] line = line[end:]
else: else:
# Otherwise, just add the rest of the string. # Otherwise, just add the rest of the string.
tokens.append(_item(match=line, tokens.append(_item(match=line,
converter=self.format_text, converter=self.format_text,
userdata=None)) userdata=None))
line = '' line = ''
return ViewVCHtmlFormatterTokens(tokens) return ViewVCHtmlFormatterTokens(tokens)
def _entity_encode(self, s): def _entity_encode(self, s):
return string.join(map(lambda x: '&#%d;' % (ord(x)), s), '') return ''.join(map(lambda x: '&#%d;' % (ord(x)), s))
class LogFormatter: class LogFormatter:
def __init__(self, request, log): def __init__(self, request, log):
self.request = request self.request = request
self.log = log or '' self.log = log or ''
self.tokens = None self.tokens = None
self.cache = {} # (maxlen, htmlize) => resulting_log self.cache = {} # (maxlen, htmlize) => resulting_log
def get(self, maxlen=0, htmlize=1): def get(self, maxlen=0, htmlize=1):
cfg = self.request.cfg cfg = self.request.cfg
skipping to change at line 1492 skipping to change at line 1533
if extended and i > 1: if extended and i > 1:
secs = secs % value secs = secs % value
value = breaks[i - 2] value = breaks[i - 2]
ext = get_time_text(request, value, secs / value) ext = get_time_text(request, value, secs / value)
if ext: if ext:
### this is not i18n compatible. pass on it for now ### this is not i18n compatible. pass on it for now
s = s + ', ' + ext s = s + ', ' + ext
return s return s
def common_template_data(request, revision=None, mime_type=None): def common_template_data(request, revision=None, mime_type=None):
"""Return a ezt.TemplateData instance with data dictionary items """Return a TemplateData instance with data dictionary items
common to most ViewVC views.""" common to most ViewVC views."""
cfg = request.cfg cfg = request.cfg
# Initialize data dictionary members (sorted alphanumerically) # Initialize data dictionary members (sorted alphanumerically)
data = ezt.TemplateData({ data = TemplateData({
'annotate_href' : None, 'annotate_href' : None,
'cfg' : cfg, 'cfg' : cfg,
'docroot' : cfg.options.docroot is None \ 'docroot' : cfg.options.docroot is None \
and request.script_name + '/' + docroot_magic_path \ and request.script_name + '/' + docroot_magic_path \
or cfg.options.docroot, or cfg.options.docroot,
'download_href' : None, 'download_href' : None,
'download_text_href' : None, 'download_text_href' : None,
'graph_href': None, 'graph_href': None,
'home_href': request.script_name or '/', 'home_href': request.script_name or '/',
'kv' : request.kv, 'kv' : request.kv,
skipping to change at line 1670 skipping to change at line 1711
'(://[-a-zA-Z0-9%.~:_/]+)' '(://[-a-zA-Z0-9%.~:_/]+)'
'((\?|\&amp;amp;|\&amp;|\&)' '((\?|\&amp;amp;|\&amp;|\&)'
'([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+) *' '([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+) *'
'(#([-a-zA-Z0-9%.~:_]+)?)?)') '(#([-a-zA-Z0-9%.~:_]+)?)?)')
def markup_escaped_urls(s): def markup_escaped_urls(s):
# Return a copy of S with all URL references -- which are expected # Return a copy of S with all URL references -- which are expected
# to be already HTML-escaped -- wrapped in <a href=""></a>. # to be already HTML-escaped -- wrapped in <a href=""></a>.
def _url_repl(match_obj): def _url_repl(match_obj):
url = match_obj.group(0) url = match_obj.group(0)
unescaped_url = string.replace(url, "&amp;amp;", "&amp;") unescaped_url = url.replace("&amp;amp;", "&amp;")
return "<a href=\"%s\">%s</a>" % (unescaped_url, url) return "<a href=\"%s\">%s</a>" % (unescaped_url, url)
return re.sub(_re_rewrite_escaped_url, _url_repl, s) return re.sub(_re_rewrite_escaped_url, _url_repl, s)
def detect_encoding(text_block): def detect_encoding(text_block):
"""Return the encoding used by TEXT_BLOCK as detected by the chardet """Return the encoding used by TEXT_BLOCK as detected by the chardet
Python module. (Currently, this is used only when syntax Python module. (Currently, this is used only when syntax
highlighting is not enabled/available; otherwise, Pygments does this highlighting is not enabled/available; otherwise, Pygments does this
work for us.)""" work for us.)"""
# Does the TEXT_BLOCK start with a BOM? # Does the TEXT_BLOCK start with a BOM?
for bom, encoding in [('\xef\xbb\xbf', 'utf-8'), for bom, encoding in [('\xef\xbb\xbf', 'utf-8'),
('\xff\xfe', 'utf-16'), ('\xff\xfe', 'utf-16'),
('\xfe\xff', 'utf-16be'), ('\xfe\xff', 'utf-16be'),
('\xff\xfe\0\0', 'utf-32'), ('\xff\xfe\0\0', 'utf-32'),
('\0\0\xfe\xff', 'utf-32be'), ('\0\0\xfe\xff', 'utf-32be'),
]: ]:
if text_block[:len(bom)] == bom: if text_block.startswith(bom):
return encoding return encoding
# If no recognized BOM, see if chardet can help us. # If no recognized BOM, see if chardet can help us.
try: try:
import chardet import chardet
# If chardet can confidently claimed a match, we'll use its # If chardet can confidently claimed a match, we'll use its
# findings. (And if that match is 'ascii' -- which is a subset of # findings. (And if that match is 'ascii' -- which is a subset of
# utf-8 -- we'll just call it 'utf-8' and score a zero transform.) # utf-8 -- we'll just call it 'utf-8' and score a zero transform.)
resp = chardet.detect(text_block) resp = chardet.detect(text_block)
skipping to change at line 1721 skipping to change at line 1762
ENCODING to UTF-8.""" ENCODING to UTF-8."""
if not encoding or encoding == 'utf-8': if not encoding or encoding == 'utf-8':
return text return text
try: try:
return unicode(text, encoding, 'replace').encode('utf-8', 'replace') return unicode(text, encoding, 'replace').encode('utf-8', 'replace')
except: except:
pass pass
return text return text
def markup_stream(request, cfg, blame_data, file_lines, filename, def markup_file_contents(request, cfg, file_lines, filename,
mime_type, encoding, colorize): mime_type, encoding, colorize):
"""Return the contents of a versioned file as a list of
vclib.Annotation objects, each representing one line of the file's
contents. Use BLAME_DATA as the annotation information for the file
if provided. Use FILE_LINES as the lines of file content text
themselves. MIME_TYPE is the MIME content type of the file;
ENCODING is its character encoding. If COLORIZE is true, attempt to
apply syntax coloration to the file contents, and use the
HTML-marked-up results as the text in the return vclib.Annotation
objects."""
# Nothing to mark up? So be it. # Nothing to mark up? So be it.
if not file_lines: if not file_lines:
return [] return []
# Determine if we should (and can) use Pygments to highlight our # Determine if we should (and can) use Pygments to highlight our
# output. Reasons not to include a) being told not to by the # output. Reasons not to include a) being told not to by the
# configuration, b) not being able to import the Pygments modules, # configuration, b) not being able to import the Pygments modules,
# and c) Pygments not having a lexer for our file's format. # and c) Pygments not having a lexer for our file's format.
pygments_lexer = None pygments_lexer = None
if colorize: if colorize:
skipping to change at line 1790 skipping to change at line 1821
# file, try to guess the lexer based on the file's content. # file, try to guess the lexer based on the file's content.
if not pygments_lexer and is_text(mime_type) and file_lines: if not pygments_lexer and is_text(mime_type) and file_lines:
try: try:
pygments_lexer = guess_lexer(file_lines[0], pygments_lexer = guess_lexer(file_lines[0],
encoding=encoding, encoding=encoding,
tabsize=cfg.options.tabsize, tabsize=cfg.options.tabsize,
stripnl=False) stripnl=False)
except ClassNotFound: except ClassNotFound:
pygments_lexer = None pygments_lexer = None
# If we aren't highlighting, just return an amalgamation of the # If we aren't highlighting, just return FILE_LINES, corrected for
# BLAME_DATA (if any) and the FILE_LINES. # encoding (if possible).
if not pygments_lexer: if not pygments_lexer:
# If allowed by configuration, try to detect the source encoding # If allowed by configuration, try to detect the source encoding
# for this file. We'll assemble a block of data from the file # for this file. We'll assemble a block of data from the file
# contents to do so... 1024 bytes should be enough. # contents to do so... 1024 bytes should be enough.
if not encoding and cfg.options.detect_encoding: if not encoding and cfg.options.detect_encoding:
block_size = 0 block_size = 0
text_block = '' text_block = ''
for i in range(len(file_lines)): for i in range(len(file_lines)):
text_block = text_block + file_lines[i] text_block = text_block + file_lines[i]
if len(text_block) >= 1024: if len(text_block) >= 1024:
break break
encoding = detect_encoding(text_block) encoding = detect_encoding(text_block)
# Built output data comprised of marked-up and possibly-transcoded # Built output data comprised of marked-up and possibly-transcoded
# source text lines wrapped in (possibly dummy) vclib.Annotation # source text lines wrapped in (possibly dummy) vclib.Annotation
# objects. # objects.
lines = [] file_lines = transcode_text(''.join(file_lines), encoding)
file_lines = transcode_text(string.join(file_lines, ''), encoding)
if file_lines[-1] == '\n': if file_lines[-1] == '\n':
file_lines = file_lines[:-1] file_lines = file_lines[:-1]
file_lines = string.split(file_lines, '\n') file_lines = file_lines.split('\n')
for i in range(len(file_lines)): for i in range(len(file_lines)):
line = file_lines[i] line = file_lines[i]
if cfg.options.tabsize > 0: if cfg.options.tabsize > 0:
line = string.expandtabs(line, cfg.options.tabsize) line = line.expandtabs(cfg.options.tabsize)
line = markup_escaped_urls(sapi.escape(line)) file_lines[i] = markup_escaped_urls(sapi.escape(line))
if blame_data: return file_lines
blame_item = blame_data[i]
blame_item.text = line
else:
blame_item = vclib.Annotation(line, i + 1, None, None, None, None)
blame_item.diff_href = None
lines.append(blame_item)
return lines
# If we get here, we're highlighting something. # If we get here, we're highlighting something.
class PygmentsSink: class PygmentsSink:
def __init__(self, blame_data): def __init__(self):
if blame_data: self.colorized_file_lines = []
self.has_blame_data = 1
self.blame_data = blame_data
else:
self.has_blame_data = 0
self.blame_data = []
self.line_no = 0
def write(self, buf): def write(self, buf):
### FIXME: Don't bank on write() being called once per line ### FIXME: Don't bank on write() being called once per line
buf = markup_escaped_urls(string.rstrip(buf, '\n\r')) self.colorized_file_lines.append(markup_escaped_urls(buf.rstrip('\n\r')))
if self.has_blame_data:
self.blame_data[self.line_no].text = buf
else:
item = vclib.Annotation(buf, self.line_no + 1,
None, None, None, None)
item.diff_href = None
self.blame_data.append(item)
self.line_no = self.line_no + 1
ps = PygmentsSink(blame_data) ps = PygmentsSink()
highlight(string.join(file_lines, ''), pygments_lexer, highlight(''.join(file_lines), pygments_lexer,
HtmlFormatter(nowrap=True, HtmlFormatter(nowrap=True,
classprefix="pygments-", classprefix="pygments-",
encoding='utf-8'), ps) encoding='utf-8'), ps)
return ps.blame_data return ps.colorized_file_lines
def empty_blame_item(line, line_no):
blame_item = vclib.Annotation(line, line_no, None, None, None, None)
blame_item.diff_href = None
return blame_item
def merge_blame_data(file_lines, blame_data):
errorful = 0
if blame_data and (len(file_lines) != len(blame_data)):
errorful = 1
blame_data = None
if not blame_data:
new_blame_data = []
for i in range(len(file_lines)):
line = file_lines[i]
if blame_data:
blame_data[i].text = line
else:
new_blame_data.append(empty_blame_item(line, i + 1))
return blame_data or new_blame_data, errorful
def make_time_string(date, cfg): def make_time_string(date, cfg):
"""Returns formatted date string in either local time or UTC. """Returns formatted date string in either local time or UTC.
The passed in 'date' variable is seconds since epoch. The passed in 'date' variable is seconds since epoch.
""" """
if date is None: if date is None:
return None return None
if cfg.options.use_localtime: if cfg.options.use_localtime:
tm = time.localtime(date) tm = time.localtime(date)
else: else:
tm = time.gmtime(date) tm = time.gmtime(date)
if cfg.options.iso8601_timestamps: if cfg.options.iso8601_timestamps:
if cfg.options.use_localtime: if cfg.options.use_localtime:
if tm[8] and time.daylight: if tm[8] and time.daylight:
tz = -time.altzone tz = -time.altzone
else: else:
tz = -time.timezone tz = -time.timezone
if tz < 0: if tz < 0:
tz = '-%02d:%02d' % (-tz / 3600, (-tz % 3600) / 60) tz = '-%02d:%02d' % (-tz // 3600, (-tz % 3600) // 60)
else: else:
tz = '+%02d:%02d' % (tz / 3600, (tz % 3600) / 60) tz = '+%02d:%02d' % (tz // 3600, (tz % 3600) // 60)
else: else:
tz = 'Z' tz = 'Z'
return time.strftime('%Y-%m-%dT%H:%M:%S', tm) + tz return time.strftime('%Y-%m-%dT%H:%M:%S', tm) + tz
else: else:
return time.asctime(tm) + ' ' + \ return time.asctime(tm) + ' ' + \
(cfg.options.use_localtime and time.tzname[tm[8]] or 'UTC') (cfg.options.use_localtime and time.tzname[tm[8]] or 'UTC')
def make_rss_time_string(date, cfg): def make_rss_time_string(date, cfg):
"""Returns formatted date string in UTC, formatted for RSS. """Returns formatted date string in UTC, formatted for RSS.
The passed in 'date' variable is seconds since epoch. The passed in 'date' variable is seconds since epoch.
""" """
if date is None: if date is None:
return None return None
return time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime(date)) + ' UTC' return time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime(date)) + ' UTC'
def make_comma_sep_list_string(items): def make_comma_sep_list_string(items):
return string.join(map(lambda x: x.name, items), ', ') return ', '.join(map(lambda x: x.name, items))
def is_undisplayable(val):
try:
unicode(val)
return 0
except:
return 1
def get_itemprops(request, path_parts, rev): def get_itemprops(request, path_parts, rev):
itemprops = request.repos.itemprops(path_parts, rev) itemprops = request.repos.itemprops(path_parts, rev)
propnames = itemprops.keys() propnames = itemprops.keys()
propnames.sort() propnames.sort()
props = [] props = []
for name in propnames: for name in propnames:
lf = LogFormatter(request, itemprops[name])
value = lf.get(maxlen=0, htmlize=1)
undisplayable = ezt.boolean(0)
# skip non-utf8 property names # skip non-utf8 property names
try: if is_undisplayable(name):
unicode(name, 'utf8')
except:
continue continue
# note non-utf8 property values lf = LogFormatter(request, itemprops[name])
try: value = lf.get(maxlen=0, htmlize=1)
unicode(value, 'utf8') undisplayable = is_undisplayable(value)
except: if undisplayable:
value = None value = None
undisplayable = ezt.boolean(1) props.append(_item(name=name, value=value,
props.append(_item(name=name, value=value, undisplayable=undisplayable)) undisplayable=ezt.boolean(undisplayable)))
return props return props
def parse_mime_type(mime_type): def parse_mime_type(mime_type):
mime_parts = map(lambda x: x.strip(), string.split(mime_type, ';')) mime_parts = map(lambda x: x.strip(), mime_type.split(';'))
type_subtype = mime_parts[0].lower() type_subtype = mime_parts[0].lower()
parameters = {} parameters = {}
for part in mime_parts[1:]: for part in mime_parts[1:]:
name, value = string.split(part, '=', 1) name, value = part.split('=', 1)
parameters[name] = value parameters[name] = value
return type_subtype, parameters return type_subtype, parameters
def calculate_mime_type(request, path_parts, rev): def calculate_mime_type(request, path_parts, rev):
"""Return a 2-tuple carrying the MIME content type and character """Return a 2-tuple carrying the MIME content type and character
encoding for the file represented by PATH_PARTS in REV. Use REQUEST encoding for the file represented by PATH_PARTS in REV. Use REQUEST
for repository access as necessary.""" for repository access as necessary."""
if not path_parts: if not path_parts:
return None, None return None, None
mime_type = encoding = None mime_type = encoding = None
skipping to change at line 2041 skipping to change at line 2073
line = fp.readline() line = fp.readline()
if not line: if not line:
break break
filesize = filesize + len(line) filesize = filesize + len(line)
assert_viewable_filesize(cfg, filesize) assert_viewable_filesize(cfg, filesize)
file_lines.append(line) file_lines.append(line)
else: else:
file_lines = fp.readlines() file_lines = fp.readlines()
fp.close() fp.close()
# Do we have a differing number of file content lines and # Try to colorize the file contents.
# annotation items? That's no good. Call it an error and don't
# bother attempting the annotation display.
if blame_data and (len(file_lines) != len(blame_data)):
annotation = 'error'
blame_data = None
# Try to markup the file contents/annotation. If we get an error
# and we were colorizing the stream, try once more without the
# colorization enabled.
colorize = cfg.options.enable_syntax_coloration colorize = cfg.options.enable_syntax_coloration
try: try:
lines = markup_stream(request, cfg, blame_data, file_lines, lines = markup_file_contents(request, cfg, file_lines, path[-1],
path[-1], mime_type, encoding, colorize) mime_type, encoding, colorize)
except: except:
if colorize: if colorize:
lines = markup_stream(request, cfg, blame_data, file_lines, lines = markup_file_contents(request, cfg, file_lines, path[-1],
path[-1], mime_type, encoding, False) mime_type, encoding, False)
else: else:
raise debug.ViewVCException('Error displaying file contents', raise debug.ViewVCException('Error displaying file contents',
'500 Internal Server Error') '500 Internal Server Error')
# Now, try to match up the annotation data (if any) with the file
# lines.
lines, errorful = merge_blame_data(lines, blame_data)
if errorful:
annotation = 'error'
data = common_template_data(request, revision, mime_type) data = common_template_data(request, revision, mime_type)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'mime_type' : mime_type, 'mime_type' : mime_type,
'log' : None, 'log' : None,
'date' : None, 'date' : None,
'ago' : None, 'ago' : None,
'author' : None, 'author' : None,
'branches' : None, 'branches' : None,
'tags' : None, 'tags' : None,
'branch_points' : None, 'branch_points' : None,
'changed' : None, 'changed' : None,
'size' : None, 'size' : None,
skipping to change at line 2147 skipping to change at line 2176
def view_annotate(request): def view_annotate(request):
if 'annotate' not in request.cfg.options.allowed_views: if 'annotate' not in request.cfg.options.allowed_views:
raise debug.ViewVCException('Annotation view is disabled', raise debug.ViewVCException('Annotation view is disabled',
'403 Forbidden') '403 Forbidden')
if request.pathtype != vclib.FILE: if request.pathtype != vclib.FILE:
raise debug.ViewVCException('Unsupported feature: annotate view on ' raise debug.ViewVCException('Unsupported feature: annotate view on '
'directory', '400 Bad Request') 'directory', '400 Bad Request')
markup_or_annotate(request, 1) markup_or_annotate(request, 1)
def revcmp(rev1, rev2): def revcmp(rev1, rev2):
rev1 = map(int, string.split(rev1, '.')) rev1 = map(int, rev1.split('.'))
rev2 = map(int, string.split(rev2, '.')) rev2 = map(int, rev2.split('.'))
return cmp(rev1, rev2) return cmp(rev1, rev2)
def sort_file_data(file_data, roottype, sortdir, sortby, group_dirs): def sort_file_data(file_data, roottype, sortdir, sortby, group_dirs):
# convert sortdir into a sign bit # convert sortdir into a sign bit
s = sortdir == "down" and -1 or 1 s = sortdir == "down" and -1 or 1
# in cvs, revision numbers can't be compared meaningfully between # in cvs, revision numbers can't be compared meaningfully between
# files, so try to do the right thing and compare dates instead # files, so try to do the right thing and compare dates instead
if roottype == "cvs" and sortby == "rev": if roottype == "cvs" and sortby == "rev":
sortby = "date" sortby = "date"
skipping to change at line 2204 skipping to change at line 2233
elif file2.rev is not None: elif file2.rev is not None:
return 1 return 1
# sort by file name # sort by file name
return s * cmp(file1.name, file2.name) return s * cmp(file1.name, file2.name)
file_data.sort(file_sort_cmp) file_data.sort(file_sort_cmp)
def icmp(x, y): def icmp(x, y):
"""case insensitive comparison""" """case insensitive comparison"""
return cmp(string.lower(x), string.lower(y)) return cmp(x.lower(), y.lower())
def view_roots(request): def view_roots(request):
if 'roots' not in request.cfg.options.allowed_views: if 'roots' not in request.cfg.options.allowed_views:
raise debug.ViewVCException('Root listing view is disabled', raise debug.ViewVCException('Root listing view is disabled',
'403 Forbidden') '403 Forbidden')
# add in the roots for the selection # add in the roots for the selection
roots = [] roots = []
expand_root_parents(request.cfg) expand_root_parents(request.cfg)
allroots = list_roots(request) allroots = list_roots(request)
skipping to change at line 2242 skipping to change at line 2271
author=lastmod and lastmod.author or None, author=lastmod and lastmod.author or None,
ago=lastmod and lastmod.ago or None, ago=lastmod and lastmod.ago or None,
date=lastmod and lastmod.date or None, date=lastmod and lastmod.date or None,
log=lastmod and lastmod.log or None, log=lastmod and lastmod.log or None,
short_log=lastmod and lastmod.short_log or None, short_log=lastmod and lastmod.short_log or None,
rev=lastmod and lastmod.rev or None, rev=lastmod and lastmod.rev or None,
href=href, href=href,
log_href=log_href)) log_href=log_href))
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'roots' : roots, 'roots' : roots,
'roots_shown' : len(roots), 'roots_shown' : len(roots),
})) }))
generate_page(request, "roots", data) generate_page(request, "roots", data)
def view_directory(request): def view_directory(request):
cfg = request.cfg cfg = request.cfg
# For Subversion repositories, the revision acts as a weak validator for # For Subversion repositories, the revision acts as a weak validator for
# the directory listing (to take into account template changes or # the directory listing (to take into account template changes or
skipping to change at line 2270 skipping to change at line 2299
if check_freshness(request, None, str(tree_rev), weak=1): if check_freshness(request, None, str(tree_rev), weak=1):
return return
# List current directory # List current directory
options = {} options = {}
if request.roottype == 'cvs': if request.roottype == 'cvs':
hideattic = int(request.query_dict.get('hideattic', hideattic = int(request.query_dict.get('hideattic',
cfg.options.hide_attic)) cfg.options.hide_attic))
options["cvs_subdirs"] = (cfg.options.show_subdir_lastmod and options["cvs_subdirs"] = (cfg.options.show_subdir_lastmod and
cfg.options.show_logs) cfg.options.show_logs)
debug.t_start("listdir")
file_data = request.repos.listdir(request.path_parts, request.pathrev, file_data = request.repos.listdir(request.path_parts, request.pathrev,
options) options)
debug.t_end("listdir")
# sort with directories first, and using the "sortby" criteria # sort with directories first, and using the "sortby" criteria
sortby = request.query_dict.get('sortby', cfg.options.sort_by) or 'file' sortby = request.query_dict.get('sortby', cfg.options.sort_by) or 'file'
sortdir = request.query_dict.get('sortdir', 'up') sortdir = request.query_dict.get('sortdir', 'up')
# when paging and sorting by filename, we can greatly improve # when paging and sorting by filename, we can greatly improve
# performance by "cheating" -- first, we sort (we already have the # performance by "cheating" -- first, we sort (we already have the
# names), then we just fetch dirlogs for the needed entries. # names), then we just fetch dirlogs for the needed entries.
# however, when sorting by other properties or not paging, we've no # however, when sorting by other properties or not paging, we've no
# choice but to fetch dirlogs for everything. # choice but to fetch dirlogs for everything.
skipping to change at line 2323 skipping to change at line 2354
# loop through entries creating rows and changing these values # loop through entries creating rows and changing these values
rows = [ ] rows = [ ]
dirs_displayed = files_displayed = 0 dirs_displayed = files_displayed = 0
num_dead = 0 num_dead = 0
# set some values to be used inside loop # set some values to be used inside loop
where = request.where where = request.where
where_prefix = where and where + '/' where_prefix = where and where + '/'
debug.t_start("row-building")
for file in file_data: for file in file_data:
if is_dir_ignored_file(file.name, cfg):
continue
row = _item(author=None, log=None, short_log=None, state=None, size=None, row = _item(author=None, log=None, short_log=None, state=None, size=None,
log_file=None, log_rev=None, graph_href=None, mime_type=None, log_file=None, log_rev=None, graph_href=None, mime_type=None,
date=None, ago=None, view_href=None, log_href=None, date=None, ago=None, view_href=None, log_href=None,
revision_href=None, annotate_href=None, download_href=None, revision_href=None, annotate_href=None, download_href=None,
download_text_href=None, prefer_markup=ezt.boolean(0), download_text_href=None, prefer_markup=ezt.boolean(0),
is_viewable_image=ezt.boolean(0), is_binary=ezt.boolean(0)) is_viewable_image=ezt.boolean(0), is_binary=ezt.boolean(0))
if request.roottype == 'cvs' and file.absent: if request.roottype == 'cvs' and file.absent:
continue continue
if cfg.options.hide_errorful_entries and file.errors: if cfg.options.hide_errorful_entries and file.errors:
continue continue
skipping to change at line 2428 skipping to change at line 2462
params={}, params={},
escape=1) escape=1)
if cfg.options.use_cvsgraph and request.roottype == 'cvs': if cfg.options.use_cvsgraph and request.roottype == 'cvs':
row.graph_href = request.get_url(view_func=view_cvsgraph, row.graph_href = request.get_url(view_func=view_cvsgraph,
where=file_where, where=file_where,
pathtype=vclib.FILE, pathtype=vclib.FILE,
params={}, params={},
escape=1) escape=1)
rows.append(row) rows.append(row)
debug.t_end("row-building")
# Prepare the data that will be passed to the template, based on the # Prepare the data that will be passed to the template, based on the
# common template data. # common template data.
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'entries' : rows, 'entries' : rows,
'sortby' : sortby, 'sortby' : sortby,
'sortdir' : sortdir, 'sortdir' : sortdir,
'search_re' : request.server.escape(search_re), 'search_re' : request.server.escape(search_re),
'dir_pagestart' : None, 'dir_pagestart' : None,
'sortby_file_href' : request.get_url(params={'sortby': 'file', 'sortby_file_href' : request.get_url(params={'sortby': 'file',
'sortdir': None}, 'sortdir': None},
escape=1), escape=1),
'sortby_rev_href' : request.get_url(params={'sortby': 'rev', 'sortby_rev_href' : request.get_url(params={'sortby': 'rev',
'sortdir': None}, 'sortdir': None},
skipping to change at line 2503 skipping to change at line 2538
escape=1) escape=1)
# CVS doesn't support sorting by rev # CVS doesn't support sorting by rev
if request.roottype == "cvs": if request.roottype == "cvs":
data['sortby_rev_href'] = None data['sortby_rev_href'] = None
# set cvs-specific fields # set cvs-specific fields
if request.roottype == 'cvs': if request.roottype == 'cvs':
plain_tags = options['cvs_tags'] plain_tags = options['cvs_tags']
plain_tags.sort(icmp) plain_tags.sort(icmp)
plain_tags.reverse() plain_tags.reverse()
data['plain_tags']= plain_tags data['plain_tags'] = []
for plain_tag in plain_tags:
data['plain_tags'].append(_item(name=plain_tag, revision=None))
branch_tags = options['cvs_branches'] branch_tags = options['cvs_branches']
branch_tags.sort(icmp) branch_tags.sort(icmp)
branch_tags.reverse() branch_tags.reverse()
data['branch_tags']= branch_tags data['branch_tags'] = []
for branch_tag in branch_tags:
data['branch_tags'].append(_item(name=branch_tag, revision=None))
data['attic_showing'] = ezt.boolean(not hideattic) data['attic_showing'] = ezt.boolean(not hideattic)
data['show_attic_href'] = request.get_url(params={'hideattic': 0}, data['show_attic_href'] = request.get_url(params={'hideattic': 0},
escape=1) escape=1)
data['hide_attic_href'] = request.get_url(params={'hideattic': 1}, data['hide_attic_href'] = request.get_url(params={'hideattic': 1},
escape=1) escape=1)
# set svn-specific fields # set svn-specific fields
elif request.roottype == 'svn': elif request.roottype == 'svn':
data['tree_rev'] = tree_rev data['tree_rev'] = tree_rev
skipping to change at line 2878 skipping to change at line 2917
entries.append(entry) entries.append(entry)
diff_select_action, diff_select_hidden_values = \ diff_select_action, diff_select_hidden_values = \
request.get_form(view_func=view_diff, request.get_form(view_func=view_diff,
params={'r1': None, 'r2': None, 'tr1': None, params={'r1': None, 'r2': None, 'tr1': None,
'tr2': None, 'diff_format': None}) 'tr2': None, 'diff_format': None})
logsort_action, logsort_hidden_values = \ logsort_action, logsort_hidden_values = \
request.get_form(params={'logsort': None}) request.get_form(params={'logsort': None})
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'default_branch' : None, 'default_branch' : None,
'mime_type' : mime_type, 'mime_type' : mime_type,
'rev_selected' : selected_rev, 'rev_selected' : selected_rev,
'diff_format' : diff_format, 'diff_format' : diff_format,
'logsort' : logsort, 'logsort' : logsort,
'human_readable' : ezt.boolean(diff_format in ('f', 'h', 'l')), 'human_readable' : ezt.boolean(diff_format in ('f', 'h', 'l')),
'log_pagestart' : None, 'log_pagestart' : None,
'log_paging_action' : None, 'log_paging_action' : None,
'log_paging_hidden_values' : [], 'log_paging_hidden_values' : [],
'entries': entries, 'entries': entries,
skipping to change at line 2958 skipping to change at line 2997
if main: if main:
# Default branch may have multiple names so we list them # Default branch may have multiple names so we list them
branches = [] branches = []
for branch in main.aliases: for branch in main.aliases:
# Don't list MAIN # Don't list MAIN
if branch is not main: if branch is not main:
branches.append(branch) branches.append(branch)
data['default_branch'] = prep_tags(request, branches) data['default_branch'] = prep_tags(request, branches)
for tag, rev in tagitems: for tag, rev in tagitems:
rev_str = None
if rev.number:
rev_str = '.'.join(map(str, rev.number))
if rev.co_rev: if rev.co_rev:
data['tags'].append(_item(rev=rev.co_rev.string, name=tag)) data['tags'].append(_item(rev=rev.co_rev.string, name=tag))
if rev.is_branch: if rev.is_branch:
data['branch_tags'].append(tag) data['branch_tags'].append(_item(name=tag, revision=rev_str))
else: else:
data['plain_tags'].append(tag) data['plain_tags'].append(_item(name=tag, revision=rev_str))
if cfg.options.log_pagesize: if cfg.options.log_pagesize:
data['log_paging_action'], data['log_paging_hidden_values'] = \ 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, 'r1': selected_rev,
}) })
data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0)) data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0))
data['entries'] = paging_sws(data, 'entries', data['log_pagestart'], data['entries'] = paging_sws(data, 'entries', data['log_pagestart'],
'rev', cfg.options.log_pagesize, 'rev', cfg.options.log_pagesize,
cfg.options.log_pagesextra, first) cfg.options.log_pagesextra, first)
skipping to change at line 3001 skipping to change at line 3044
# The revision number acts as a strong validator. # The revision number acts as a strong validator.
if not check_freshness(request, None, revision): if not check_freshness(request, None, revision):
mime_type, encoding = calculate_mime_type(request, path, rev) mime_type, encoding = calculate_mime_type(request, path, rev)
mime_type = request.query_dict.get('content-type') \ mime_type = request.query_dict.get('content-type') \
or mime_type \ or mime_type \
or 'text/plain' or 'text/plain'
server_fp = get_writeready_server_file(request, mime_type, encoding) server_fp = get_writeready_server_file(request, mime_type, encoding)
copy_stream(fp, server_fp) copy_stream(fp, server_fp)
fp.close() fp.close()
def cvsgraph_make_reqopt(request, cfgname, queryparam, optvalue):
# Return a cvsgraph custom option substring bit OPTVALUE based on
# CFGNAME's presence in the allowed list of user-configurable
# options and QUERYPARAM's presence and boolean interpretation in
# the actual request; otherwise, return the empty string for options
# that either aren't overridden or aren't allowed to be overridden.
if (cfgname in request.cfg.options.allowed_cvsgraph_useropts) \
and (int(request.query_dict.get(queryparam, 0))):
return optvalue
return ''
def cvsgraph_normalize_gshow(request):
# Return the effective value of the 'gshow' query parameter, noting
# that a missing parameter is the same as gshow=all, and treating a
# bogus parameter value as the same as gshow=all, too.
gshow = request.query_dict.get('gshow', 'all')
if gshow not in ('all', 'inittagged', 'tagged'):
gshow = 'all'
return gshow
def cvsgraph_extraopts(request):
# Build a set of -O options for controlling cvsgraph's behavior,
# based on what the user has requested and filtered against what the
# user is allowed to request.
cfg = request.cfg
ep = '-O'
# Simple mappings of boolean flags
ep = ep + cvsgraph_make_reqopt(request, 'invert', 'gflip',
';upside_down=true')
ep = ep + cvsgraph_make_reqopt(request, 'branchbox', 'gbbox',
';branch_dupbox=true')
ep = ep + cvsgraph_make_reqopt(request, 'rotate', 'gleft',
';left_right=true')
# Stripping is a little more complex.
if ('show' in request.cfg.options.allowed_cvsgraph_useropts):
gshow = cvsgraph_normalize_gshow(request)
if gshow == 'inittagged':
ep = ep + ';strip_untagged=true'
elif gshow == 'tagged':
ep = ep + ';strip_untagged=true;strip_first_rev=true'
# And tag limitation has a user-supplied value to mess with.
if ('limittags' in request.cfg.options.allowed_cvsgraph_useropts) \
and request.query_dict.has_key('gmaxtag'):
ep = ep + ';rev_maxtags=' + request.query_dict['gmaxtag']
return ep + ';'
def view_cvsgraph_image(request): def view_cvsgraph_image(request):
"output the image rendered by cvsgraph" "output the image rendered by cvsgraph"
# this function is derived from cgi/cvsgraphmkimg.cgi # this function is derived from cgi/cvsgraphmkimg.cgi
cfg = request.cfg cfg = request.cfg
if not cfg.options.use_cvsgraph: if not cfg.options.use_cvsgraph:
raise debug.ViewVCException('Graph view is disabled', '403 Forbidden') raise debug.ViewVCException('Graph view is disabled', '403 Forbidden')
# If cvsgraph can't find its supporting libraries, uncomment and set # If cvsgraph can't find its supporting libraries, uncomment and set
# accordingly. Do the same in view_cvsgraph(). # accordingly. Do the same in view_cvsgraph().
#os.environ['LD_LIBRARY_PATH'] = '/usr/lib:/usr/local/lib:/path/to/cvsgraph' #os.environ['LD_LIBRARY_PATH'] = '/usr/lib:/usr/local/lib:/path/to/cvsgraph'
rcsfile = request.repos.rcsfile(request.path_parts) rcsfile = request.repos.rcsfile(request.path_parts)
fp = popen.popen(cfg.utilities.cvsgraph or 'cvsgraph', fp = popen.popen(cfg.utilities.cvsgraph or 'cvsgraph',
("-c", cfg.path(cfg.options.cvsgraph_conf), ("-c", cfg.path(cfg.options.cvsgraph_conf),
"-r", request.repos.rootpath, "-r", request.repos.rootpath,
cvsgraph_extraopts(request),
rcsfile), 'rb', 0) rcsfile), 'rb', 0)
copy_stream(fp, get_writeready_server_file(request, 'image/png')) copy_stream(fp, get_writeready_server_file(request, 'image/png'))
fp.close() fp.close()
def view_cvsgraph(request): def view_cvsgraph(request):
"output a page containing an image rendered by cvsgraph" "output a page containing an image rendered by cvsgraph"
cfg = request.cfg cfg = request.cfg
skipping to change at line 3060 skipping to change at line 3157
params={'revision': None}, params={'revision': None},
escape=1, partial=1), escape=1, partial=1),
"-5", request.get_url(view_func=view_diff, "-5", request.get_url(view_func=view_diff,
params={'r1': None, 'r2': None}, params={'r1': None, 'r2': None},
escape=1, partial=1), escape=1, partial=1),
"-6", request.get_url(view_func=view_directory, "-6", request.get_url(view_func=view_directory,
where=up_where, where=up_where,
pathtype=vclib.DIR, pathtype=vclib.DIR,
params={'pathrev': None}, params={'pathrev': None},
escape=1, partial=1), escape=1, partial=1),
cvsgraph_extraopts(request),
rcsfile), 'rb', 0) rcsfile), 'rb', 0)
graph_action, graph_hidden_values = \
request.get_form(view_func=view_cvsgraph, params={})
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'imagemap' : fp, 'imagemap' : fp,
'imagesrc' : imagesrc, 'imagesrc' : imagesrc,
'graph_action' : graph_action,
'graph_hidden_values' : graph_hidden_values,
'opt_gflip' : ezt.boolean('invert' in cfg.options.allowed_cvsgraph_useropts)
,
'opt_gbbox' : ezt.boolean('branchbox' in cfg.options.allowed_cvsgraph_userop
ts),
'opt_gshow' : ezt.boolean('show' in cfg.options.allowed_cvsgraph_useropts),
'opt_gleft' : ezt.boolean('rotate' in cfg.options.allowed_cvsgraph_useropts)
,
'opt_gmaxtag' : ezt.boolean('limittags' in cfg.options.allowed_cvsgraph_user
opts),
'gflip' : ezt.boolean(int(request.query_dict.get('gflip', 0))),
'gbbox' : ezt.boolean(int(request.query_dict.get('gbbox', 0))),
'gleft' : ezt.boolean(int(request.query_dict.get('gleft', 0))),
'gmaxtag' : request.query_dict.get('gmaxtag', 0),
'gshow' : cvsgraph_normalize_gshow(request),
})) }))
generate_page(request, "graph", data) generate_page(request, "graph", data)
def search_file(repos, path_parts, rev, search_re): def search_file(repos, path_parts, rev, search_re):
"""Return 1 iff the contents of the file at PATH_PARTS in REPOS as """Return 1 iff the contents of the file at PATH_PARTS in REPOS as
of revision REV matches regular expression SEARCH_RE.""" of revision REV matches regular expression SEARCH_RE."""
# Read in each line of a checked-out file, and then use re.search to # Read in each line of a checked-out file, and then use re.search to
# search line. # search line.
fp = repos.openfile(path_parts, rev, {})[0] fp = repos.openfile(path_parts, rev, {})[0]
skipping to change at line 3135 skipping to change at line 3248
else: # assume HTML: else: # assume HTML:
mime_type = None mime_type = None
copy_stream(fp, get_writeready_server_file(request, mime_type, copy_stream(fp, get_writeready_server_file(request, mime_type,
content_length=content_length)) content_length=content_length))
fp.close() fp.close()
def rcsdiff_date_reformat(date_str, cfg): def rcsdiff_date_reformat(date_str, cfg):
if date_str is None: if date_str is None:
return None return None
try: try:
date = compat.cvs_strptime(date_str) date = vclib.ccvs.cvs_strptime(date_str)
except ValueError: except ValueError:
return date_str return date_str
return make_time_string(compat.timegm(date), cfg) return make_time_string(calendar.timegm(date), cfg)
_re_extract_rev = re.compile(r'^[-+*]{3} [^\t]+\t([^\t]+)\t((\d+\.)*\d+)$') _re_extract_rev = re.compile(r'^[-+*]{3} [^\t]+\t([^\t]+)\t((\d+\.)*\d+)$')
_re_extract_info = re.compile(r'@@ \-([0-9]+).*\+([0-9]+).*@@(.*)') _re_extract_info = re.compile(r'@@ \-([0-9]+).*\+([0-9]+).*@@(.*)')
class DiffSource: class DiffSource:
def __init__(self, fp, cfg): def __init__(self, fp, cfg):
self.fp = fp self.fp = fp
self.cfg = cfg self.cfg = cfg
self.save_line = None self.save_line = None
self.line_number = None self.line_number = None
skipping to change at line 3177 skipping to change at line 3290
# doesn't return a row immediately because it is accumulating changes. # doesn't return a row immediately because it is accumulating changes.
# when it is out of data, _get_row will raise IndexError. # when it is out of data, _get_row will raise IndexError.
while 1: while 1:
item = self._get_row() item = self._get_row()
if item: if item:
self.idx = idx self.idx = idx
self.last = item self.last = item
return item return item
def _format_text(self, text): def _format_text(self, text):
text = string.rstrip(text, '\r\n') text = text.rstrip('\r\n')
if self.cfg.options.tabsize > 0: if self.cfg.options.tabsize > 0:
text = string.expandtabs(text, self.cfg.options.tabsize) text = text.expandtabs(self.cfg.options.tabsize)
hr_breakable = self.cfg.options.hr_breakable hr_breakable = self.cfg.options.hr_breakable
# in the code below, "\x01" will be our stand-in for "&". We don't want # in the code below, "\x01" will be our stand-in for "&". We don't want
# to insert "&" because it would get escaped by sapi.escape(). Similarly, # to insert "&" because it would get escaped by sapi.escape(). Similarly,
# we use "\x02" as a stand-in for "<br>" # we use "\x02" as a stand-in for "<br>"
if hr_breakable > 1 and len(text) > hr_breakable: if hr_breakable > 1 and len(text) > hr_breakable:
text = re.sub('(' + ('.' * hr_breakable) + ')', '\\1\x02', text) text = re.sub('(' + ('.' * hr_breakable) + ')', '\\1\x02', text)
if hr_breakable: if hr_breakable:
# make every other space "breakable" # make every other space "breakable"
text = string.replace(text, ' ', ' \x01nbsp;') text = text.replace(' ', ' \x01nbsp;')
else: else:
text = string.replace(text, ' ', '\x01nbsp;') text = text.replace(' ', '\x01nbsp;')
text = sapi.escape(text) text = sapi.escape(text)
text = string.replace(text, '\x01', '&') text = text.replace('\x01', '&')
text = string.replace(text, '\x02', text = text.replace('\x02', '<span style="color:red">\</span><br />')
'<span style="color:red">\</span><br />')
return text return text
def _get_row(self): def _get_row(self):
if self.state[:5] == 'flush': if self.state[:5] == 'flush':
item = self._flush_row() item = self._flush_row()
if item: if item:
return item return item
self.state = 'dump' self.state = 'dump'
if self.save_line: if self.save_line:
line = self.save_line line = self.save_line
self.save_line = None self.save_line = None
else: else:
line = self.fp.readline() line = self.fp.readline()
if not line: if not line:
if self.state == 'no-changes': if self.state == 'no-changes':
self.state = 'done' self.state = 'done'
return _item(type='no-changes') return _item(type=_RCSDIFF_NO_CHANGES)
# see if there are lines to flush # see if there are lines to flush
if self.left_col or self.right_col: if self.left_col or self.right_col:
# move into the flushing state # move into the flushing state
self.state = 'flush-' + self.state self.state = 'flush-' + self.state
return None return None
# nothing more to return # nothing more to return
raise IndexError raise IndexError
skipping to change at line 3241 skipping to change at line 3353
match = _re_extract_info.match(line) match = _re_extract_info.match(line)
self.line_number = int(match.group(2)) - 1 self.line_number = int(match.group(2)) - 1
self.prev_line_number = int(match.group(1)) - 1 self.prev_line_number = int(match.group(1)) - 1
return _item(type='header', return _item(type='header',
line_info_left=match.group(1), line_info_left=match.group(1),
line_info_right=match.group(2), line_info_right=match.group(2),
line_info_extra=self._format_text(match.group(3))) line_info_extra=self._format_text(match.group(3)))
if line[0] == '\\': if line[0] == '\\':
# \ No newline at end of file # \ No newline at end of file
# Just skip. This code used to move to flush state, but that resulted in
# move into the flushing state. note: it doesn't matter if we really # changes being displayed as removals-and-readditions.
# have data to flush or not; that will be figured out later
self.state = 'flush-' + self.state
return None return None
diff_code = line[0] diff_code = line[0]
output = self._format_text(line[1:]) output = self._format_text(line[1:])
if diff_code == '+': if diff_code == '+':
if self.state == 'dump': if self.state == 'dump':
self.line_number = self.line_number + 1 self.line_number = self.line_number + 1
return _item(type='add', right=output, line_number=self.line_number) return _item(type='add', right=output, line_number=self.line_number)
skipping to change at line 3323 skipping to change at line 3433
elif diff_type == vclib.CONTEXT: elif diff_type == vclib.CONTEXT:
f1 = '*** ' f1 = '*** '
f2 = '--- ' f2 = '--- '
else: else:
f1 = f2 = None f1 = f2 = None
# If we're parsing headers, then parse and tweak the diff headers, # If we're parsing headers, then parse and tweak the diff headers,
# collecting them in an array until we've read and handled them all. # collecting them in an array until we've read and handled them all.
if f1 and f2: if f1 and f2:
parsing = 1 parsing = 1
flag = _RCSDIFF_NO_CHANGES
len_f1 = len(f1) len_f1 = len(f1)
len_f2 = len(f2) len_f2 = len(f2)
while parsing: while parsing:
line = fp.readline() line = fp.readline()
if not line: if not line:
break break
# Saw at least one line in the stream
flag = None
if line[:len(f1)] == f1: if line[:len(f1)] == f1:
match = _re_extract_rev.match(line) match = _re_extract_rev.match(line)
if match: if match:
date1 = match.group(1) date1 = match.group(1)
log_rev1 = match.group(2) log_rev1 = match.group(2)
line = '%s%s\t%s\t%s%s\n' % (f1, path1, date1, log_rev1, line = '%s%s\t%s\t%s%s\n' % (f1, path1, date1, log_rev1,
sym1 and ' ' + sym1 or '') sym1 and ' ' + sym1 or '')
elif line[:len(f2)] == f2: elif line[:len(f2)] == f2:
match = _re_extract_rev.match(line) match = _re_extract_rev.match(line)
if match: if match:
date2 = match.group(1) date2 = match.group(1)
log_rev2 = match.group(2) log_rev2 = match.group(2)
line = '%s%s\t%s\t%s%s\n' % (f2, path2, date2, log_rev2, line = '%s%s\t%s\t%s%s\n' % (f2, path2, date2, log_rev2,
sym2 and ' ' + sym2 or '') sym2 and ' ' + sym2 or '')
parsing = 0 parsing = 0
elif line[:3] == 'Bin': elif line[:3] == 'Bin':
flag = _RCSDIFF_IS_BINARY flag = _RCSDIFF_IS_BINARY
parsing = 0 parsing = 0
elif (string.find(line, 'not found') != -1 or elif (line.find('not found') != -1 or
string.find(line, 'illegal option') != -1): line.find('illegal option') != -1):
flag = _RCSDIFF_ERROR flag = _RCSDIFF_ERROR
parsing = 0 parsing = 0
header_lines.append(line) header_lines.append(line)
if (log_rev1 and log_rev1 != rev1): if (log_rev1 and log_rev1 != rev1):
raise debug.ViewVCException('rcsdiff found revision %s, but expected ' raise debug.ViewVCException('rcsdiff found revision %s, but expected '
'revision %s' % (log_rev1, rev1), 'revision %s' % (log_rev1, rev1),
'500 Internal Server Error') '500 Internal Server Error')
if (log_rev2 and log_rev2 != rev2): if (log_rev2 and log_rev2 != rev2):
raise debug.ViewVCException('rcsdiff found revision %s, but expected ' raise debug.ViewVCException('rcsdiff found revision %s, but expected '
'revision %s' % (log_rev2, rev2), 'revision %s' % (log_rev2, rev2),
'500 Internal Server Error') '500 Internal Server Error')
return date1, date2, flag, string.join(header_lines, '') return date1, date2, flag, ''.join(header_lines)
def _get_diff_path_parts(request, query_key, rev, base_rev): def _get_diff_path_parts(request, query_key, rev, base_rev):
repos = request.repos repos = request.repos
if request.query_dict.has_key(query_key): if request.query_dict.has_key(query_key):
parts = _path_parts(request.query_dict[query_key]) parts = _path_parts(request.query_dict[query_key])
elif request.roottype == 'svn': elif request.roottype == 'svn':
try: try:
parts = _path_parts(repos.get_location(request.where, parts = _path_parts(repos.get_location(request.where,
repos._getrev(base_rev), repos._getrev(base_rev),
repos._getrev(rev))) repos._getrev(rev)))
skipping to change at line 3398 skipping to change at line 3512
rev2 = r2 = query_dict['r2'] rev2 = r2 = query_dict['r2']
sym1 = sym2 = None sym1 = sym2 = None
# hack on the diff revisions # hack on the diff revisions
if r1 == 'text': if r1 == 'text':
rev1 = query_dict.get('tr1', None) rev1 = query_dict.get('tr1', None)
if not rev1: if not rev1:
raise debug.ViewVCException('Missing revision from the diff ' raise debug.ViewVCException('Missing revision from the diff '
'form text field', '400 Bad Request') 'form text field', '400 Bad Request')
else: else:
idx = string.find(r1, ':') idx = r1.find(':')
if idx == -1: if idx == -1:
rev1 = r1 rev1 = r1
else: else:
rev1 = r1[:idx] rev1 = r1[:idx]
sym1 = r1[idx+1:] sym1 = r1[idx+1:]
if r2 == 'text': if r2 == 'text':
rev2 = query_dict.get('tr2', None) rev2 = query_dict.get('tr2', None)
if not rev2: if not rev2:
raise debug.ViewVCException('Missing revision from the diff ' raise debug.ViewVCException('Missing revision from the diff '
'form text field', '400 Bad Request') 'form text field', '400 Bad Request')
sym2 = '' sym2 = ''
else: else:
idx = string.find(r2, ':') idx = r2.find(':')
if idx == -1: if idx == -1:
rev2 = r2 rev2 = r2
else: else:
rev2 = r2[:idx] rev2 = r2[:idx]
sym2 = r2[idx+1:] sym2 = r2[idx+1:]
if request.roottype == 'svn': if request.roottype == 'svn':
try: try:
rev1 = str(request.repos._getrev(rev1)) rev1 = str(request.repos._getrev(rev1))
rev2 = str(request.repos._getrev(rev2)) rev2 = str(request.repos._getrev(rev2))
skipping to change at line 3469 skipping to change at line 3583
format = query_dict.get('diff_format', format = query_dict.get('diff_format',
cfg.options.diff_format == 'c' and 'c' or 'u') cfg.options.diff_format == 'c' and 'c' or 'u')
if format == 'c': if format == 'c':
diff_type = vclib.CONTEXT diff_type = vclib.CONTEXT
elif format == 'u': elif format == 'u':
diff_type = vclib.UNIFIED diff_type = vclib.UNIFIED
else: else:
raise debug.ViewVCException('Diff format %s not understood' raise debug.ViewVCException('Diff format %s not understood'
% format, '400 Bad Request') % format, '400 Bad Request')
# Set some diff options. (Are there other options folks might want?
# Maybe not. For a patch, perhaps the precise change is ideal.)
diff_options = {}
diff_options['funout'] = cfg.options.hr_funout
try: try:
fp = request.repos.rawdiff(p1, rev1, p2, rev2, diff_type) fp = request.repos.rawdiff(p1, rev1, p2, rev2, diff_type, diff_options)
except vclib.InvalidRevision: except vclib.InvalidRevision:
raise debug.ViewVCException('Invalid path(s) or revision(s) passed ' raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request') 'to diff', '400 Bad Request')
path_left = _path_join(p1) path_left = _path_join(p1)
path_right = _path_join(p2) path_right = _path_join(p2)
date1, date2, flag, headers = diff_parse_headers(fp, diff_type, date1, date2, flag, headers = diff_parse_headers(fp, diff_type,
path_left, path_right, path_left, path_right,
rev1, rev2, sym1, sym2) rev1, rev2, sym1, sym2)
server_fp = get_writeready_server_file(request, 'text/plain') server_fp = get_writeready_server_file(request, 'text/plain')
server_fp.write(headers) server_fp.write(headers)
copy_stream(fp, server_fp) copy_stream(fp, server_fp)
fp.close() fp.close()
def diff_side_item(request, path_comp, rev, sym):
'''Prepare information about left/right side of the diff. Prepare two flavors,
for content and for property diffs.'''
# TODO: Is the slice necessary, or is limit enough?
options = {'svn_show_all_dir_logs': 1}
log_entry = request.repos.itemlog(path_comp, rev, vclib.SORTBY_REV,
0, 1, options)[-1]
ago = log_entry.date is not None \
and html_time(request, log_entry.date, 1) or None
path_joined = _path_join(path_comp)
lf = LogFormatter(request, log_entry.log)
# Item for property diff: no hrefs, there's no view
# to download/annotate property
i_prop = _item(log_entry=log_entry,
date=make_time_string(log_entry.date, request.cfg),
author=log_entry.author,
log = lf.get(maxlen=0, htmlize=1),
size=log_entry.size,
ago=ago,
path=path_joined,
path_comp=path_comp,
rev=rev,
tag=sym,
view_href=None,
download_href=None,
download_text_href=None,
annotate_href=None,
revision_href=None,
prefer_markup=ezt.boolean(0))
# Content diff item is based on property diff, with URIs added
fvi = get_file_view_info(request, path_joined, rev)
i_content = copy.copy(i_prop)
i_content.view_href = fvi.view_href
i_content.download_href = fvi.download_href
i_content.download_text_href = fvi.download_text_href
i_content.annotate_href = fvi.annotate_href
i_content.revision_href = fvi.revision_href
i_content.prefer_markup = fvi.prefer_markup
# Property diff item has properties hash, naturally. Content item doesn't.
i_content.properties = None
i_prop.properties = request.repos.itemprops(path_comp, rev)
return i_content, i_prop
class DiffDescription:
def __init__(self, request):
cfg = request.cfg
query_dict = request.query_dict
self.diff_format = query_dict.get('diff_format', cfg.options.diff_format)
self.human_readable = 0
self.hide_legend = 0
self.line_differ = None
self.fp_differ = None
self.request = request
self.context = -1
self.changes = []
if self.diff_format == 'c':
self.diff_type = vclib.CONTEXT
self.hide_legend = 1
elif self.diff_format == 's':
self.diff_type = vclib.SIDE_BY_SIDE
self.hide_legend = 1
elif self.diff_format == 'l':
self.diff_type = vclib.UNIFIED
self.context = 15
self.human_readable = 1
elif self.diff_format == 'f':
self.diff_type = vclib.UNIFIED
self.context = None
self.human_readable = 1
elif self.diff_format == 'h':
self.diff_type = vclib.UNIFIED
self.human_readable = 1
elif self.diff_format == 'u':
self.diff_type = vclib.UNIFIED
self.hide_legend = 1
else:
raise debug.ViewVCException('Diff format %s not understood'
% self.diff_format, '400 Bad Request')
# Determine whether idiff is avaialble and whether it could be used.
# idiff only supports side-by-side (conditionally) and unified formats,
# and is only used if intra-line diffs are requested.
if (cfg.options.hr_intraline and idiff
and ((self.human_readable and idiff.sidebyside)
or (not self.human_readable and self.diff_type == vclib.UNIFIED))):
# Override hiding legend for unified format. It is not marked 'human
# readable', and it is displayed differently depending on whether
# hr_intraline is disabled (displayed as raw diff) or enabled
# (displayed as colored). What a royal mess... Issue #301 should
# at some time address it; at that time, human_readable and hide_legend
# controls should both be merged into one, 'is_colored' or something.
self.hide_legend = 0
if self.human_readable:
self.line_differ = self._line_idiff_sidebyside
self.diff_block_format = 'sidebyside-2'
else:
self.line_differ = self._line_idiff_unified
self.diff_block_format = 'unified'
else:
if self.human_readable:
self.diff_block_format = 'sidebyside-1'
self.fp_differ = self._fp_vclib_hr
else:
self.diff_block_format = 'raw'
self.fp_differ = self._fp_vclib_raw
def anchor(self, anchor_name):
self.changes.append(_item(diff_block_format='anchor', anchor=anchor_name))
def get_content_diff(self, left, right):
cfg = self.request.cfg
diff_options = {}
if self.context != -1:
diff_options['context'] = self.context
if self.human_readable or self.diff_format == 'u':
diff_options['funout'] = cfg.options.hr_funout
if self.human_readable:
diff_options['ignore_white'] = cfg.options.hr_ignore_white
diff_options['ignore_keyword_subst'] = \
cfg.options.hr_ignore_keyword_subst
self._get_diff(left, right, self._content_lines, self._content_fp,
diff_options, None)
def get_prop_diff(self, left, right):
diff_options = {}
if self.context != -1:
diff_options['context'] = self.context
if self.human_readable:
cfg = self.request.cfg
diff_options['ignore_white'] = cfg.options.hr_ignore_white
for name in self._uniq(left.properties.keys() + right.properties.keys()):
# Skip non-utf8 property names
if is_undisplayable(name):
continue
val_left = left.properties.get(name, '')
val_right = right.properties.get(name, '')
# Skip non-changed properties
if val_left == val_right:
continue
# Check for binary properties
if is_undisplayable(val_left) or is_undisplayable(val_right):
self.changes.append(_item(left=left,
right=right,
diff_block_format=self.diff_block_format,
changes=[ _item(type=_RCSDIFF_IS_BINARY) ],
propname=name))
continue
self._get_diff(left, right, self._prop_lines, self._prop_fp,
diff_options, name)
def _get_diff(self, left, right, get_lines, get_fp, diff_options, propname):
if self.fp_differ is not None:
fp = get_fp(left, right, propname, diff_options)
changes = self.fp_differ(left, right, fp, propname)
else:
lines_left = get_lines(left, propname)
lines_right = get_lines(right, propname)
changes = self.line_differ(lines_left, lines_right, diff_options)
self.changes.append(_item(left=left,
right=right,
changes=changes,
diff_block_format=self.diff_block_format,
propname=propname))
def _line_idiff_sidebyside(self, lines_left, lines_right, diff_options):
return idiff.sidebyside(lines_left, lines_right,
diff_options.get("context", 5))
def _line_idiff_unified(self, lines_left, lines_right, diff_options):
return idiff.unified(lines_left, lines_right,
diff_options.get("context", 2))
def _fp_vclib_hr(self, left, right, fp, propname):
date1, date2, flag, headers = \
diff_parse_headers(fp, self.diff_type,
self._property_path(left, propname),
self._property_path(right, propname),
left.rev, right.rev, left.tag, right.tag)
if flag is not None:
return [ _item(type=flag) ]
else:
return DiffSource(fp, self.request.cfg)
def _fp_vclib_raw(self, left, right, fp, propname):
date1, date2, flag, headers = \
diff_parse_headers(fp, self.diff_type,
self._property_path(left, propname),
self._property_path(right, propname),
left.rev, right.rev, left.tag, right.tag)
if flag is not None:
return _item(type=flag)
else:
return _item(type='raw', raw=MarkupPipeWrapper(fp,
self.request.server.escape(headers), None, 1))
def _content_lines(self, side, propname):
f = self.request.repos.openfile(side.path_comp, side.rev, {})[0]
try:
lines = f.readlines()
finally:
f.close()
return lines
def _content_fp(self, left, right, propname, diff_options):
return self.request.repos.rawdiff(left.path_comp, left.rev,
right.path_comp, right.rev,
self.diff_type, diff_options)
def _prop_lines(self, side, propname):
val = side.properties.get(propname, '')
return val.splitlines()
def _prop_fp(self, left, right, propname, diff_options):
fn_left = self._temp_file(left.properties.get(propname))
fn_right = self._temp_file(right.properties.get(propname))
diff_args = vclib._diff_args(self.diff_type, diff_options)
info_left = self._property_path(left, propname), \
left.log_entry.date, left.rev
info_right = self._property_path(right, propname), \
right.log_entry.date, right.rev
return vclib._diff_fp(fn_left, fn_right, info_left, info_right,
self.request.cfg.utilities.diff or 'diff', diff_args)
def _temp_file(self, val):
'''Create a temporary file with content from val'''
fd, fn = tempfile.mkstemp()
fp = os.fdopen(fd, "wb")
if val:
fp.write(val)
fp.close()
return fn
def _uniq(self, lst):
'''Determine unique set of list elements'''
h = {}
for e in lst:
h[e] = 1
return sorted(h.keys())
def _property_path(self, side, propname):
'''Return path to be displayed in raw diff - possibly augmented with
property name'''
if propname is None:
return side.path
else:
return "%s:property(%s)" % (side.path, propname)
def view_diff(request): def view_diff(request):
if 'diff' not in request.cfg.options.allowed_views: if 'diff' not in request.cfg.options.allowed_views:
raise debug.ViewVCException('Diff generation is disabled', raise debug.ViewVCException('Diff generation is disabled',
'403 Forbidden') '403 Forbidden')
cfg = request.cfg cfg = request.cfg
query_dict = request.query_dict
p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request) p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request)
mime_type1, encoding1 = calculate_mime_type(request, p1, rev1) mime_type1, encoding1 = calculate_mime_type(request, p1, rev1)
mime_type2, encoding2 = calculate_mime_type(request, p2, rev2) mime_type2, encoding2 = calculate_mime_type(request, p2, rev2)
if is_binary_file_mime_type(mime_type1, cfg) or \ if is_binary_file_mime_type(mime_type1, cfg) or \
is_binary_file_mime_type(mime_type2, cfg): is_binary_file_mime_type(mime_type2, cfg):
raise debug.ViewVCException('Display of binary file content disabled ' raise debug.ViewVCException('Display of binary file content disabled '
'by configuration', '403 Forbidden') 'by configuration', '403 Forbidden')
# since templates are in use and subversion allows changes to the dates, # since templates are in use and subversion allows changes to the dates,
# we can't provide a strong etag # we can't provide a strong etag
if check_freshness(request, None, '%s-%s' % (rev1, rev2), weak=1): if check_freshness(request, None, '%s-%s' % (rev1, rev2), weak=1):
return return
# TODO: Is the slice necessary, or is limit enough? left_side_content, left_side_prop = diff_side_item(request, p1, rev1, sym1)
log_entry1 = request.repos.itemlog(p1, rev1, vclib.SORTBY_REV, 0, 1, {})[-1] right_side_content, right_side_prop = diff_side_item(request, p2, rev2, sym2)
log_entry2 = request.repos.itemlog(p2, rev2, vclib.SORTBY_REV, 0, 1, {})[-1]
ago1 = log_entry1.date is not None \
and html_time(request, log_entry1.date, 1) or None
ago2 = log_entry2.date is not None \
and html_time(request, log_entry2.date, 2) or None
diff_type = None desc = DiffDescription(request)
diff_options = {}
human_readable = 0
format = query_dict.get('diff_format', cfg.options.diff_format)
if format == 'c':
diff_type = vclib.CONTEXT
elif format == 's':
diff_type = vclib.SIDE_BY_SIDE
elif format == 'l':
diff_type = vclib.UNIFIED
diff_options['context'] = 15
human_readable = 1
elif format == 'f':
diff_type = vclib.UNIFIED
diff_options['context'] = None
human_readable = 1
elif format == 'h':
diff_type = vclib.UNIFIED
human_readable = 1
elif format == 'u':
diff_type = vclib.UNIFIED
else:
raise debug.ViewVCException('Diff format %s not understood'
% format, '400 Bad Request')
if human_readable or format == 'u':
diff_options['funout'] = cfg.options.hr_funout
if human_readable:
diff_options['ignore_white'] = cfg.options.hr_ignore_white
diff_options['ignore_keyword_subst'] = cfg.options.hr_ignore_keyword_subst
try: try:
fp = sidebyside = unified = None if request.pathtype == vclib.FILE:
if (cfg.options.hr_intraline and idiff # Get file content diff
and ((human_readable and idiff.sidebyside) desc.anchor("content")
or (not human_readable and diff_type == vclib.UNIFIED))): desc.get_content_diff(left_side_content, right_side_content)
f1 = request.repos.openfile(p1, rev1, {})[0]
try: # Get property list and diff each property
lines_left = f1.readlines() desc.anchor("properties")
finally: desc.get_prop_diff(left_side_prop, right_side_prop)
f1.close()
f2 = request.repos.openfile(p2, rev2, {})[0]
try:
lines_right = f2.readlines()
finally:
f2.close()
if human_readable:
sidebyside = idiff.sidebyside(lines_left, lines_right,
diff_options.get("context", 5))
else:
unified = idiff.unified(lines_left, lines_right,
diff_options.get("context", 2))
else:
fp = request.repos.rawdiff(p1, rev1, p2, rev2, diff_type, diff_options)
except vclib.InvalidRevision: except vclib.InvalidRevision:
raise debug.ViewVCException('Invalid path(s) or revision(s) passed ' raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request') 'to diff', '400 Bad Request')
path_left = _path_join(p1)
path_right = _path_join(p2)
date1 = date2 = raw_diff_fp = None
changes = []
if fp:
date1, date2, flag, headers = diff_parse_headers(fp, diff_type,
path_left, path_right,
rev1, rev2, sym1, sym2)
if human_readable:
if flag is not None:
changes = [ _item(type=flag) ]
else:
changes = DiffSource(fp, cfg)
else:
raw_diff_fp = MarkupPipeWrapper(fp, request.server.escape(headers), None,
1)
no_format_params = request.query_dict.copy() no_format_params = request.query_dict.copy()
no_format_params['diff_format'] = None no_format_params['diff_format'] = None
diff_format_action, diff_format_hidden_values = \ diff_format_action, diff_format_hidden_values = \
request.get_form(params=no_format_params) request.get_form(params=no_format_params)
fvi = get_file_view_info(request, path_left, rev1)
left = _item(date=make_time_string(log_entry1.date, cfg),
author=log_entry1.author,
log=LogFormatter(request,
log_entry1.log).get(maxlen=0, htmlize=1),
size=log_entry1.size,
ago=ago1,
path=path_left,
rev=rev1,
tag=sym1,
view_href=fvi.view_href,
download_href=fvi.download_href,
download_text_href=fvi.download_text_href,
annotate_href=fvi.annotate_href,
revision_href=fvi.revision_href,
prefer_markup=fvi.prefer_markup)
fvi = get_file_view_info(request, path_right, rev2)
right = _item(date=make_time_string(log_entry2.date, cfg),
author=log_entry2.author,
log=LogFormatter(request,
log_entry2.log).get(maxlen=0, htmlize=1),
size=log_entry2.size,
ago=ago2,
path=path_right,
rev=rev2,
tag=sym2,
view_href=fvi.view_href,
download_href=fvi.download_href,
download_text_href=fvi.download_text_href,
annotate_href=fvi.annotate_href,
revision_href=fvi.revision_href,
prefer_markup=fvi.prefer_markup)
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'left' : left, 'diffs' : desc.changes,
'right' : right, 'diff_format' : desc.diff_format,
'raw_diff' : raw_diff_fp, 'hide_legend' : ezt.boolean(desc.hide_legend),
'changes' : changes,
'sidebyside': sidebyside,
'unified': unified,
'diff_format' : request.query_dict.get('diff_format',
cfg.options.diff_format),
'patch_href' : request.get_url(view_func=view_patch, 'patch_href' : request.get_url(view_func=view_patch,
params=no_format_params, params=no_format_params,
escape=1), escape=1),
'diff_format_action' : diff_format_action, 'diff_format_action' : diff_format_action,
'diff_format_hidden_values' : diff_format_hidden_values, 'diff_format_hidden_values' : diff_format_hidden_values,
})) }))
generate_page(request, "diff", data) generate_page(request, "diff", data)
def generate_tarball_header(out, name, size=0, mode=None, mtime=0, def generate_tarball_header(out, name, size=0, mode=None, mtime=0,
uid=0, gid=0, typeflag=None, linkname='', uid=0, gid=0, typeflag=None, linkname='',
skipping to change at line 3729 skipping to change at line 3996
entries = request.repos.listdir(rep_path, request.pathrev, {}) entries = request.repos.listdir(rep_path, request.pathrev, {})
request.repos.dirlogs(rep_path, request.pathrev, entries, {}) request.repos.dirlogs(rep_path, request.pathrev, entries, {})
entries.sort(lambda a, b: cmp(a.name, b.name)) entries.sort(lambda a, b: cmp(a.name, b.name))
# figure out corresponding path in tar file. everything gets put underneath # figure out corresponding path in tar file. everything gets put underneath
# a single top level directory named after the repository directory being # a single top level directory named after the repository directory being
# tarred # tarred
if request.path_parts: if request.path_parts:
tar_dir = request.path_parts[-1] + '/' tar_dir = request.path_parts[-1] + '/'
else: else:
tar_dir = request.rootname + '/' # Don't handle context as a directory in the tar ball.
root_path_parts = _path_parts(request.rootname)
tar_dir = root_path_parts[-1] + '/'
if reldir: if reldir:
tar_dir = tar_dir + _path_join(reldir) + '/' tar_dir = tar_dir + _path_join(reldir) + '/'
cvs = request.roottype == 'cvs' cvs = request.roottype == 'cvs'
# If our caller doesn't dictate a datestamp to use for the current # If our caller doesn't dictate a datestamp to use for the current
# directory, its datestamps will be the youngest of the datestamps # directory, its datestamps will be the youngest of the datestamps
# of versioned items in that subdirectory. We'll be ignoring dead # of versioned items in that subdirectory. We'll be ignoring dead
# or busted items and, in CVS, subdirs. # or busted items and, in CVS, subdirs.
if dir_mtime is None: if dir_mtime is None:
skipping to change at line 3908 skipping to change at line 4177
# Fetch the revision information. # Fetch the revision information.
date, author, msg, revprops, changes = request.repos.revinfo(rev) date, author, msg, revprops, changes = request.repos.revinfo(rev)
date_str = make_time_string(date, cfg) date_str = make_time_string(date, cfg)
# Fix up the revprops list (rather like get_itemprops()). # Fix up the revprops list (rather like get_itemprops()).
propnames = revprops.keys() propnames = revprops.keys()
propnames.sort() propnames.sort()
props = [] props = []
for name in propnames: for name in propnames:
lf = LogFormatter(request, revprops[name])
value = lf.get(maxlen=0, htmlize=1)
undisplayable = ezt.boolean(0)
# skip non-utf8 property names # skip non-utf8 property names
try: if is_undisplayable(name):
unicode(name, 'utf8')
except:
continue continue
lf = LogFormatter(request, revprops[name])
value = lf.get(maxlen=0, htmlize=1)
# note non-utf8 property values # note non-utf8 property values
try: undisplayable = is_undisplayable(value)
unicode(value, 'utf8') if undisplayable:
except:
value = None value = None
undisplayable = ezt.boolean(1) props.append(_item(name=name, value=value,
props.append(_item(name=name, value=value, undisplayable=undisplayable)) undisplayable=ezt.boolean(undisplayable)))
# Sort the changes list by path. # Sort the changes list by path.
def changes_sort_by_path(a, b): def changes_sort_by_path(a, b):
return cmp(a.path_parts, b.path_parts) return cmp(a.path_parts, b.path_parts)
changes.sort(changes_sort_by_path) changes.sort(changes_sort_by_path)
# Handle limit_changes parameter # Handle limit_changes parameter
cfg_limit_changes = cfg.options.limit_changes cfg_limit_changes = cfg.options.limit_changes
limit_changes = int(query_dict.get('limit_changes', cfg_limit_changes)) limit_changes = int(query_dict.get('limit_changes', cfg_limit_changes))
more_changes = None more_changes = None
skipping to change at line 3989 skipping to change at line 4254
where=link_where, where=link_where,
pathtype=change.pathtype, pathtype=change.pathtype,
params={'pathrev' : link_rev}, params={'pathrev' : link_rev},
escape=1) escape=1)
change.log_href = request.get_url(view_func=view_log, change.log_href = request.get_url(view_func=view_log,
where=link_where, where=link_where,
pathtype=change.pathtype, pathtype=change.pathtype,
params={'pathrev' : link_rev}, params={'pathrev' : link_rev},
escape=1) escape=1)
if change.pathtype is vclib.FILE and change.text_changed: if (change.pathtype is vclib.FILE and change.text_changed) \
or change.props_changed:
change.diff_href = request.get_url(view_func=view_diff, change.diff_href = request.get_url(view_func=view_diff,
where=path, where=path,
pathtype=change.pathtype, pathtype=change.pathtype,
params={'pathrev' : str(rev), params={'pathrev' : str(rev),
'r1' : str(rev), 'r1' : str(rev),
'r2' : str(change.base_rev), 'r2' : str(change.base_rev),
}, },
escape=1) escape=1)
# use same variable names as the log template # use same variable names as the log template
skipping to change at line 4034 skipping to change at line 4300
next_rev_href = request.get_url(view_func=view_revision, next_rev_href = request.get_url(view_func=view_revision,
where=None, where=None,
pathtype=None, pathtype=None,
params={'revision': str(rev + 1)}, params={'revision': str(rev + 1)},
escape=1) escape=1)
jump_rev_action, jump_rev_hidden_values = \ jump_rev_action, jump_rev_hidden_values = \
request.get_form(params={'revision': None}) request.get_form(params={'revision': None})
lf = LogFormatter(request, msg) lf = LogFormatter(request, msg)
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'rev' : str(rev), 'rev' : str(rev),
'author' : author, 'author' : author,
'date' : date_str, 'date' : date_str,
'log' : lf.get(maxlen=0, htmlize=1), 'log' : lf.get(maxlen=0, htmlize=1),
'properties' : props, 'properties' : props,
'ago' : date is not None and html_time(request, date, 1) or None, 'ago' : date is not None and html_time(request, date, 1) or None,
'changes' : changes, 'changes' : changes,
'prev_href' : prev_rev_href, 'prev_href' : prev_rev_href,
'next_href' : next_rev_href, 'next_href' : next_rev_href,
'num_changes' : num_changes, 'num_changes' : num_changes,
skipping to change at line 4130 skipping to change at line 4396
query_action, query_hidden_values = \ query_action, query_hidden_values = \
request.get_form(view_func=view_query, params={'limit_changes': None}) request.get_form(view_func=view_query, params={'limit_changes': None})
limit_changes = \ limit_changes = \
int(request.query_dict.get('limit_changes', int(request.query_dict.get('limit_changes',
request.cfg.options.limit_changes)) request.cfg.options.limit_changes))
def escaped_query_dict_get(itemname, itemdefault=''): def escaped_query_dict_get(itemname, itemdefault=''):
return request.server.escape(request.query_dict.get(itemname, itemdefault)) return request.server.escape(request.query_dict.get(itemname, itemdefault))
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'branch' : escaped_query_dict_get('branch', ''), 'branch' : escaped_query_dict_get('branch', ''),
'branch_match' : escaped_query_dict_get('branch_match', 'exact'), 'branch_match' : escaped_query_dict_get('branch_match', 'exact'),
'dir' : escaped_query_dict_get('dir', ''), 'dir' : escaped_query_dict_get('dir', ''),
'file' : escaped_query_dict_get('file', ''), 'file' : escaped_query_dict_get('file', ''),
'file_match' : escaped_query_dict_get('file_match', 'exact'), 'file_match' : escaped_query_dict_get('file_match', 'exact'),
'who' : escaped_query_dict_get('who', ''), 'who' : escaped_query_dict_get('who', ''),
'who_match' : escaped_query_dict_get('who_match', 'exact'), 'who_match' : escaped_query_dict_get('who_match', 'exact'),
'comment' : escaped_query_dict_get('comment', ''), 'comment' : escaped_query_dict_get('comment', ''),
'comment_match' : escaped_query_dict_get('comment_match', 'exact'), 'comment_match' : escaped_query_dict_get('comment_match', 'exact'),
'querysort' : escaped_query_dict_get('querysort', 'date'), 'querysort' : escaped_query_dict_get('querysort', 'date'),
skipping to change at line 4179 skipping to change at line 4445
minute = int(minute) minute = int(minute)
else: else:
minute = 0 minute = 0
second = match.group(6) second = match.group(6)
if second is not None: if second is not None:
second = int(second) second = int(second)
else: else:
second = 0 second = 0
# return a "seconds since epoch" value assuming date given in UTC # return a "seconds since epoch" value assuming date given in UTC
tm = (year, month, day, hour, minute, second, 0, 0, 0) tm = (year, month, day, hour, minute, second, 0, 0, 0)
return compat.timegm(tm) return calendar.timegm(tm)
else: else:
return None return None
def english_query(request): def english_query(request):
"""Generate a sentance describing the query.""" """Generate a sentance describing the query."""
cfg = request.cfg cfg = request.cfg
ret = [ 'Checkins ' ] ret = [ 'Checkins ' ]
dir = request.query_dict.get('dir', '') dir = request.query_dict.get('dir', '')
if dir: if dir:
ret.append('to ') ret.append('to ')
skipping to change at line 4236 skipping to change at line 4502
if mindate and maxdate: if mindate and maxdate:
w1, w2 = 'between', 'and' w1, w2 = 'between', 'and'
else: else:
w1, w2 = 'since', 'before' w1, w2 = 'since', 'before'
if mindate: if mindate:
mindate = make_time_string(parse_date(mindate), cfg) mindate = make_time_string(parse_date(mindate), cfg)
ret.append('%s <em>%s</em> ' % (w1, mindate)) ret.append('%s <em>%s</em> ' % (w1, mindate))
if maxdate: if maxdate:
maxdate = make_time_string(parse_date(maxdate), cfg) maxdate = make_time_string(parse_date(maxdate), cfg)
ret.append('%s <em>%s</em> ' % (w2, maxdate)) ret.append('%s <em>%s</em> ' % (w2, maxdate))
return string.join(ret, '') return ''.join(ret)
def prev_rev(rev): def prev_rev(rev):
"""Returns a string representing the previous revision of the argument.""" """Returns a string representing the previous revision of the argument."""
r = string.split(rev, '.') r = rev.split('.')
# decrement final revision component # decrement final revision component
r[-1] = str(int(r[-1]) - 1) r[-1] = str(int(r[-1]) - 1)
# prune if we pass the beginning of the branch # prune if we pass the beginning of the branch
if len(r) > 2 and r[-1] == '0': if len(r) > 2 and r[-1] == '0':
r = r[:-2] r = r[:-2]
return string.join(r, '.') return '.'.join(r)
def build_commit(request, files, max_files, dir_strip, format): def build_commit(request, files, max_files, dir_strip, format):
"""Return a commit object build from the information in FILES, or """Return a commit object build from the information in FILES, or
None if no allowed files are present in the set. DIR_STRIP is the None if no allowed files are present in the set. DIR_STRIP is the
path prefix to remove from the commit object's set of files. If path prefix to remove from the commit object's set of files. If
MAX_FILES is non-zero, it is used to limit the number of files MAX_FILES is non-zero, it is used to limit the number of files
returned in the commit object. FORMAT is the requested output returned in the commit object. FORMAT is the requested output
format of the query request.""" format of the query request."""
cfg = request.cfg cfg = request.cfg
skipping to change at line 4504 skipping to change at line 4770
# create the database query from the form data # create the database query from the form data
query = cvsdb.CreateCheckinQuery() query = cvsdb.CreateCheckinQuery()
query.SetRepository(repos_root) query.SetRepository(repos_root)
# treat "HEAD" specially ... # treat "HEAD" specially ...
if branch_match == 'exact' and branch == 'HEAD': if branch_match == 'exact' and branch == 'HEAD':
query.SetBranch('') query.SetBranch('')
elif branch: elif branch:
query.SetBranch(branch, branch_match) query.SetBranch(branch, branch_match)
if dir: if dir:
for subdir in string.split(dir, ','): for subdir in dir.split(','):
path = (_path_join(repos_dir + request.path_parts path = (_path_join(repos_dir + request.path_parts
+ _path_parts(string.strip(subdir)))) + _path_parts(subdir.strip())))
query.SetDirectory(path, 'exact') query.SetDirectory(path, 'exact')
query.SetDirectory('%s/%%' % cvsdb.EscapeLike(path), 'like') query.SetDirectory('%s/%%' % cvsdb.EscapeLike(path), 'like')
else: else:
where = _path_join(repos_dir + request.path_parts) where = _path_join(repos_dir + request.path_parts)
if where: # if we are in a subdirectory ... if where: # if we are in a subdirectory ...
query.SetDirectory(where, 'exact') query.SetDirectory(where, 'exact')
query.SetDirectory('%s/%%' % cvsdb.EscapeLike(where), 'like') query.SetDirectory('%s/%%' % cvsdb.EscapeLike(where), 'like')
if file: if file:
query.SetFile(file, file_match) query.SetFile(file, file_match)
if who: if who:
skipping to change at line 4629 skipping to change at line 4895
# if we got any results, use the newest commit as the modification time # if we got any results, use the newest commit as the modification time
if mod_time >= 0: if mod_time >= 0:
if check_freshness(request, mod_time): if check_freshness(request, mod_time):
return return
if format == 'backout': if format == 'backout':
query_backout(request, commits) query_backout(request, commits)
return return
data = common_template_data(request) data = common_template_data(request)
data.merge(ezt.TemplateData({ data.merge(TemplateData({
'sql': request.server.escape(db.CreateSQLQueryString(query)), 'sql': request.server.escape(db.CreateSQLQueryString(query)),
'english_query': english_query(request), 'english_query': english_query(request),
'queryform_href': request.get_url(view_func=view_queryform, escape=1), 'queryform_href': request.get_url(view_func=view_queryform, escape=1),
'backout_href': backout_href, 'backout_href': backout_href,
'plus_count': plus_count, 'plus_count': plus_count,
'minus_count': minus_count, 'minus_count': minus_count,
'show_branch': show_branch, 'show_branch': show_branch,
'querysort': querysort, 'querysort': querysort,
'commits': commits, 'commits': commits,
'row_limit_reached' : ezt.boolean(row_limit_reached), 'row_limit_reached' : ezt.boolean(row_limit_reached),
skipping to change at line 4716 skipping to change at line 4982
auth = setup_authorizer(cfg, request.username, root) auth = setup_authorizer(cfg, request.username, root)
try: try:
vclib.ccvs.CVSRepository(root, cfg.general.cvs_roots[root], auth, vclib.ccvs.CVSRepository(root, cfg.general.cvs_roots[root], auth,
cfg.utilities, cfg.options.use_rcsparse) cfg.utilities, cfg.options.use_rcsparse)
except vclib.ReposNotFound: except vclib.ReposNotFound:
continue continue
allroots[root] = [cfg.general.cvs_roots[root], 'cvs', None] allroots[root] = [cfg.general.cvs_roots[root], 'cvs', None]
return allroots return allroots
def _parse_root_parent(pp):
"""Parse a single root parent "directory [= context] : repo_type" string
and return as tuple."""
pos = pp.rfind(':')
if pos > 0:
repo_type = pp[pos+1:].strip()
pp = pp[:pos].strip()
else:
repo_type = None
pos = pp.rfind('=')
if pos > 0:
context = _path_parts(pp[pos+1:].strip())
pp = pp[:pos].strip()
else:
context = None
path = os.path.normpath(pp)
return path,context,repo_type
def expand_root_parents(cfg): def expand_root_parents(cfg):
"""Expand the configured root parents into individual roots.""" """Expand the configured root parents into individual roots."""
# Each item in root_parents is a "directory : repo_type" string. # Each item in root_parents is a "directory [= context ] : repo_type" string.
for pp in cfg.general.root_parents: for pp in cfg.general.root_parents:
pos = string.rfind(pp, ':') path,context,repo_type = _parse_root_parent(pp)
if pos < 0:
raise debug.ViewVCException(
'The path "%s" in "root_parents" does not include a '
'repository type. Expected "cvs" or "svn".' % (pp))
repo_type = string.strip(pp[pos+1:])
pp = os.path.normpath(string.strip(pp[:pos]))
if repo_type == 'cvs': if repo_type == 'cvs':
roots = vclib.ccvs.expand_root_parent(pp) roots = vclib.ccvs.expand_root_parent(path)
if cfg.options.hide_cvsroot and roots.has_key('CVSROOT'): if cfg.options.hide_cvsroot and roots.has_key('CVSROOT'):
del roots['CVSROOT'] del roots['CVSROOT']
cfg.general.cvs_roots.update(roots) if context:
fullroots = {}
for root, rootpath in roots.iteritems():
fullroots[_path_join(context + [root])] = rootpath
cfg.general.cvs_roots.update(fullroots)
else:
cfg.general.cvs_roots.update(roots)
elif repo_type == 'svn': elif repo_type == 'svn':
roots = vclib.svn.expand_root_parent(pp) roots = vclib.svn.expand_root_parent(path)
cfg.general.svn_roots.update(roots) if context:
fullroots = {}
for root, rootpath in roots.iteritems():
fullroots[_path_join(context + [root])] = rootpath
cfg.general.svn_roots.update(fullroots)
else:
cfg.general.svn_roots.update(roots)
elif repo_type == None:
raise debug.ViewVCException(
'The path "%s" in "root_parents" does not include a '
'repository type. Expected "cvs" or "svn".' % (pp))
else: else:
raise debug.ViewVCException( raise debug.ViewVCException(
'The path "%s" in "root_parents" has an unrecognized ' 'The path "%s" in "root_parents" has an unrecognized '
'repository type ("%s"). Expected "cvs" or "svn".' 'repository type ("%s"). Expected "cvs" or "svn".'
% (pp, repo_type)) % (pp, repo_type))
def find_root_in_parents(cfg, rootname, roottype): def find_root_in_parents(cfg, path_parts, roottype):
"""Return the rootpath for configured ROOTNAME of ROOTTYPE.""" """Return the rootpath for configured ROOTNAME of ROOTTYPE."""
# Easy out: caller wants rootname "CVSROOT", and we're hiding those. # Easy out: caller wants rootname "CVSROOT", and we're hiding those.
if rootname == 'CVSROOT' and cfg.options.hide_cvsroot: if path_parts[-1] == 'CVSROOT' and cfg.options.hide_cvsroot:
return None return None
for pp in cfg.general.root_parents: for pp in cfg.general.root_parents:
pos = string.rfind(pp, ':') path,context,repo_type = _parse_root_parent(pp)
if pos < 0:
continue
repo_type = string.strip(pp[pos+1:])
if repo_type != roottype: if repo_type != roottype:
continue continue
pp = os.path.normpath(string.strip(pp[:pos])) if context != None:
if not _path_starts_with(path_parts, context):
continue
rootidx = len(context)
else:
rootidx = 0
if len(path_parts) <= rootidx:
continue
rootname = path_parts[rootidx]
fullroot = _path_join(path_parts[0:rootidx+1])
remain = path_parts[rootidx+1:]
rootpath = None rootpath = None
if roottype == 'cvs': if roottype == 'cvs':
rootpath = vclib.ccvs.find_root_in_parent(pp, rootname) rootpath = vclib.ccvs.find_root_in_parent(path, rootname)
elif roottype == 'svn': elif roottype == 'svn':
rootpath = vclib.svn.find_root_in_parent(pp, rootname) rootpath = vclib.svn.find_root_in_parent(path, rootname)
if rootpath is not None: if rootpath is not None:
return rootpath return fullroot, rootpath, remain
return None return None, None, None
def locate_root_from_path(cfg, path_parts):
"""Return a 4-tuple ROOTTYPE, ROOTPATH, ROOTNAME, REMAIN for path_parts."""
for rootname, rootpath in cfg.general.cvs_roots.iteritems():
pp = _path_parts(rootname)
if _path_starts_with(path_parts, pp):
return 'cvs', rootpath, rootname, path_parts[len(pp):]
for rootname, rootpath in cfg.general.svn_roots.iteritems():
pp = _path_parts(rootname)
if _path_starts_with(path_parts, pp):
return 'svn', rootpath, rootname, path_parts[len(pp):]
rootname, path_in_parent, remain = \
find_root_in_parents(cfg, path_parts, 'cvs')
if path_in_parent:
cfg.general.cvs_roots[rootname] = path_in_parent
return 'cvs', path_in_parent, rootname, remain
rootname, path_in_parent, remain = \
find_root_in_parents(cfg, path_parts, 'svn')
if path_in_parent:
cfg.general.svn_roots[rootname] = path_in_parent
return 'svn', path_in_parent, rootname, remain
return None, None, None, None
def locate_root(cfg, rootname): def locate_root(cfg, rootname):
"""Return a 2-tuple ROOTTYPE, ROOTPATH for configured ROOTNAME.""" """Return a 2-tuple ROOTTYPE, ROOTPATH for configured ROOTNAME."""
# First try a direct match
if cfg.general.cvs_roots.has_key(rootname): if cfg.general.cvs_roots.has_key(rootname):
return 'cvs', cfg.general.cvs_roots[rootname] return 'cvs', cfg.general.cvs_roots[rootname]
path_in_parent = find_root_in_parents(cfg, rootname, 'cvs')
if path_in_parent:
cfg.general.cvs_roots[rootname] = path_in_parent
return 'cvs', path_in_parent
if cfg.general.svn_roots.has_key(rootname): if cfg.general.svn_roots.has_key(rootname):
return 'svn', cfg.general.svn_roots[rootname] return 'svn', cfg.general.svn_roots[rootname]
path_in_parent = find_root_in_parents(cfg, rootname, 'svn')
if path_in_parent: path_parts = _path_parts(rootname)
cfg.general.svn_roots[rootname] = path_in_parent roottype, rootpath, rootname_dupl, remain = \
return 'svn', path_in_parent locate_root_from_path(cfg, path_parts)
return None, None if roottype != None:
if rootname_dupl != rootname:
raise debug.ViewVCException(
'Found root name "%s" doesn\'t match "%s"' \
% (rootname_dupl, rootname),
'500 Internal Server Error')
if len(remain) > 0:
raise debug.ViewVCException(
'Have remaining path "%s"' \
% (remain),
'500 Internal Server Error')
return roottype, rootpath
def load_config(pathname=None, server=None): def load_config(pathname=None, server=None):
"""Load the ViewVC configuration file. SERVER is the server object """Load the ViewVC configuration file. SERVER is the server object
that will be using this configuration. Consult the environment for that will be using this configuration. Consult the environment for
the variable VIEWVC_CONF_PATHNAME and VIEWCVS_CONF_PATHNAME (its the variable VIEWVC_CONF_PATHNAME and VIEWCVS_CONF_PATHNAME (its
legacy name) and, if set, use its value as the path of the legacy name) and, if set, use its value as the path of the
configuration file; otherwise, use PATHNAME (if provided). Failing configuration file; otherwise, use PATHNAME (if provided). Failing
all else, use a hardcoded default configuration path.""" all else, use a hardcoded default configuration path."""
debug.t_start('load-config') debug.t_start('load-config')
skipping to change at line 4869 skipping to change at line 5205
request.run_viewvc() request.run_viewvc()
except SystemExit, e: except SystemExit, e:
return return
except: except:
view_error(server, cfg) view_error(server, cfg)
finally: finally:
debug.t_end('main') debug.t_end('main')
debug.t_dump(server.file()) debug.t_dump(server.file())
debug.DumpChildren(server) debug.DumpChildren(server)
class _item:
def __init__(self, **kw):
vars(self).update(kw)
 End of changes. 131 change blocks. 
313 lines changed or deleted 652 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)