summaryrefslogtreecommitdiff
path: root/sandbox/axk/viewcvs/viewcvs.py
diff options
context:
space:
mode:
authorwiemann <wiemann@929543f6-e4f2-0310-98a6-ba3bd3dd1d04>2006-01-09 20:44:25 +0000
committerwiemann <wiemann@929543f6-e4f2-0310-98a6-ba3bd3dd1d04>2006-01-09 20:44:25 +0000
commitd77fdfef70e08114f57cbef5d91707df8717ea9f (patch)
tree49444e3486c0c333cb7b33dfa721296c08ee4ece /sandbox/axk/viewcvs/viewcvs.py
parent53cd16ca6ca5f638cbe5956988e88f9339e355cf (diff)
parent3993c4097756e9885bcfbd07cb1cc1e4e95e50e4 (diff)
downloaddocutils-0.4.tar.gz
Release 0.4: tagging released revisiondocutils-0.4
git-svn-id: http://svn.code.sf.net/p/docutils/code/tags/docutils-0.4@4268 929543f6-e4f2-0310-98a6-ba3bd3dd1d04
Diffstat (limited to 'sandbox/axk/viewcvs/viewcvs.py')
-rw-r--r--sandbox/axk/viewcvs/viewcvs.py2833
1 files changed, 0 insertions, 2833 deletions
diff --git a/sandbox/axk/viewcvs/viewcvs.py b/sandbox/axk/viewcvs/viewcvs.py
deleted file mode 100644
index 23e1cbd84..000000000
--- a/sandbox/axk/viewcvs/viewcvs.py
+++ /dev/null
@@ -1,2833 +0,0 @@
-# -*-python-*-
-#
-# Copyright (C) 1999-2002 The ViewCVS Group. All Rights Reserved.
-#
-# By using this file, you agree to the terms and conditions set forth in
-# the LICENSE.html file which can be found at the top level of the ViewCVS
-# distribution or at http://viewcvs.sourceforge.net/license-1.html.
-#
-# Contact information:
-# Greg Stein, PO Box 760, Palo Alto, CA, 94302
-# gstein@lyra.org, http://viewcvs.sourceforge.net/
-#
-# -----------------------------------------------------------------------
-#
-# viewcvs: View CVS repositories via a web browser
-#
-# -----------------------------------------------------------------------
-#
-# This software is based on "cvsweb" by Henner Zeller (which is, in turn,
-# derived from software by Bill Fenner, with additional modifications by
-# Henrik Nordstrom and Ken Coar). The cvsweb distribution can be found
-# on Zeller's site:
-# http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/
-#
-# -----------------------------------------------------------------------
-#
-
-__version__ = '1.0-dev'
-
-#########################################################################
-#
-# INSTALL-TIME CONFIGURATION
-#
-# These values will be set during the installation process. During
-# development, they will remain None.
-#
-
-CONF_PATHNAME = None
-
-#########################################################################
-
-# this comes from our library; measure the startup time
-import debug
-debug.t_start('startup')
-debug.t_start('imports')
-
-# standard modules that we know are in the path or builtin
-import sys
-import os
-import sapi
-import cgi
-import string
-import urllib
-import mimetypes
-import time
-import re
-import stat
-import struct
-import types
-
-# these modules come from our library (the stub has set up the path)
-import compat
-import config
-import popen
-import ezt
-import accept
-import vclib
-from vclib import bincvs
-
-debug.t_end('imports')
-
-#########################################################################
-
-checkout_magic_path = '*checkout*'
-# According to RFC 1738 the '~' character is unsafe in URLs.
-# But for compatibility with URLs bookmarked with older releases of ViewCVS:
-oldstyle_checkout_magic_path = '~checkout~'
-docroot_magic_path = '*docroot*'
-viewcvs_mime_type = 'text/vnd.viewcvs-markup'
-alt_mime_type = 'text/x-cvsweb-markup'
-
-# put here the variables we need in order to hold our state - they will be
-# added (with their current value) to any link/query string you construct
-_sticky_vars = (
- 'root',
- 'hideattic',
- 'sortby',
- 'sortdir',
- 'logsort',
- 'diff_format',
- 'only_with_tag',
- 'search',
- 'dir_pagestart',
- 'log_pagestart',
- )
-
-# for reading/writing between a couple descriptors
-CHUNK_SIZE = 8192
-
-# for rcsdiff processing of header
-_RCSDIFF_IS_BINARY = 'binary'
-_RCSDIFF_ERROR = 'error'
-
-# global configuration:
-cfg = None # see below
-
-# special characters that don't need to be URL encoded
-_URL_SAFE_CHARS = "/*~"
-
-if CONF_PATHNAME:
- # installed
- g_install_dir = os.path.dirname(CONF_PATHNAME)
-else:
- # development directories
- g_install_dir = os.path.join(os.pardir, os.pardir) # typically, "../.."
-
-
-class Request:
- def __init__(self, server):
- self.server = server
- self.script_name = server.getenv('SCRIPT_NAME', '')
- self.browser = server.getenv('HTTP_USER_AGENT', 'unknown')
-
- # in lynx, it it very annoying to have two links per file, so
- # disable the link at the icon in this case:
- self.no_file_links = string.find(self.browser, 'Lynx') != -1
-
- # newer browsers accept gzip content encoding and state this in a
- # header (netscape did always but didn't state it) It has been
- # reported that these braindamaged MS-Internet Explorers claim
- # that they accept gzip .. but don't in fact and display garbage
- # then :-/
- self.may_compress = (
- ( string.find(server.getenv('HTTP_ACCEPT_ENCODING', ''), 'gzip') != -1
- or string.find(self.browser, 'Mozilla/3') != -1)
- and string.find(self.browser, 'MSIE') == -1
- )
-
- # process the Accept-Language: header
- hal = server.getenv('HTTP_ACCEPT_LANGUAGE','')
- self.lang_selector = accept.language(hal)
- self.language = self.lang_selector.select_from(cfg.general.languages)
-
- # load the key/value files, given the selected language
- self.kv = cfg.load_kv_files(self.language)
-
- def run_viewcvs(self):
-
- # global needed because "import vclib.svn" causes the
- # interpreter to make vclib a local variable
- global vclib
-
- # This function first parses the query string and sets the following
- # variables. Then it executes the request.
- self.view_func = None # function to call to process the request
- self.repos = None # object representing current repository
- self.rootname = None # name of current root (as used in viewcvs.conf)
- self.roottype = None # current root type ('svn' or 'cvs')
- self.rootpath = None # physical path to current root
- self.pathtype = None # type of path, either vclib.FILE or vclib.DIR
- self.where = None # path to file or directory in current root
- self.query_dict = None # validated and cleaned up query options
- self.path_parts = None # for convenience, equals where.split('/')
-
- # Process PATH_INFO component of query string
- path_info = self.server.getenv('PATH_INFO', '')
-
- # clean it up. this removes duplicate '/' characters and any that may
- # exist at the front or end of the path.
- path_parts = filter(None, string.split(path_info, '/'))
-
- if path_parts:
- # handle magic path prefixes
- if path_parts[0] == docroot_magic_path:
- # if this is just a simple hunk of doc, then serve it up
- self.where = string.join(path_parts[1:], '/')
- return view_doc(self)
- elif path_parts[0] in (checkout_magic_path, oldstyle_checkout_magic_path):
- path_parts.pop(0)
- self.view_func = view_checkout
-
- # see if we are treating the first path component (after any
- # magic) as the repository root. if there are parts, and the
- # first component is a named root, use it as such. else, we'll be
- # falling back to the default root a little later.
- if cfg.options.root_as_url_component and path_parts \
- and list_roots(cfg).has_key(path_parts[0]):
- self.rootname = path_parts.pop(0)
-
- # if this is a forbidden path, stop now
- if path_parts and cfg.is_forbidden(path_parts[0]):
- raise debug.ViewCVSException('Access to "%s" is forbidden.'
- % path_parts[0], '403 Forbidden')
-
- self.where = string.join(path_parts, '/')
- self.path_parts = path_parts
-
- # Done with PATH_INFO, now parse the query params
- self.query_dict = {}
-
- for name, values in self.server.params().items():
- # patch up old queries that use 'cvsroot' to look like they used 'root'
- if name == 'cvsroot':
- name = 'root'
-
- # validate the parameter
- _validate_param(name, values[0])
-
- # if we're here, then the parameter is okay
- self.query_dict[name] = values[0]
-
- # Special handling for root parameter
- root_param = self.query_dict.get('root', None)
- if root_param:
- self.rootname = root_param
-
- # in root_as_url_component mode, if we see a root in the query
- # data, we'll redirect to the new url schema. it may fail, but
- # at least we tried.
- if cfg.options.root_as_url_component:
- del self.query_dict['root']
- self.server.redirect(self.get_url())
-
- elif self.rootname is None:
- self.rootname = cfg.general.default_root
-
- # Create the repository object
- if cfg.general.cvs_roots.has_key(self.rootname):
- self.rootpath = cfg.general.cvs_roots[self.rootname]
- try:
- self.repos = bincvs.BinCVSRepository(self.rootname, self.rootpath,
- cfg.general)
- self.roottype = 'cvs'
- except vclib.ReposNotFound:
- raise debug.ViewCVSException(
- '%s not found!\nThe wrong path for this repository was '
- 'configured, or the server on which the CVS tree lives may be '
- 'down. Please try again in a few minutes.'
- % self.server.escape(self.rootname))
- # required so that spawned rcs programs correctly expand $CVSHeader$
- os.environ['CVSROOT'] = self.rootpath
- elif cfg.general.svn_roots.has_key(self.rootname):
- self.rootpath = cfg.general.svn_roots[self.rootname]
- try:
- if re.match(_re_rewrite_url, self.rootpath):
- # If the rootpath is a URL, we'll use the svn_ra module, but
- # lie about its name.
- import vclib.svn_ra
- vclib.svn = vclib.svn_ra
- else:
- import vclib.svn
- rev = None
- if self.query_dict.has_key('rev') \
- and self.query_dict['rev'] != 'HEAD':
- rev = int(self.query_dict['rev'])
- self.repos = vclib.svn.SubversionRepository(self.rootname,
- self.rootpath, rev)
- self.repos.cross_copies = cfg.options.cross_copies
- self.roottype = 'svn'
- except vclib.ReposNotFound:
- raise debug.ViewCVSException(
- '%s not found!\nThe wrong path for this repository was '
- 'configured, or the server on which the Subversion tree lives may'
- 'be down. Please try again in a few minutes.'
- % self.server.escape(self.rootname))
- except vclib.InvalidRevision, ex:
- raise debug.ViewCVSException(str(ex))
- else:
- raise debug.ViewCVSException(
- 'The root "%s" is unknown. If you believe the value is '
- 'correct, then please double-check your configuration.'
- % self.server.escape(self.rootname), "404 Repository not found")
-
- # Make sure path exists
- self.pathtype = _repos_pathtype(self.repos, self.path_parts)
-
- if self.pathtype is None:
- # path doesn't exist, try stripping known fake suffixes
- result = _strip_suffix('.diff', self.where, self.path_parts, \
- vclib.FILE, self.repos, view_diff) or \
- _strip_suffix('.tar.gz', self.where, self.path_parts, \
- vclib.DIR, self.repos, download_tarball) or \
- _strip_suffix('root.tar.gz', self.where, self.path_parts, \
- vclib.DIR, self.repos, download_tarball)
- if result:
- self.where, self.path_parts, self.pathtype, self.view_func = result
- else:
- raise debug.ViewcvsException('%s: unknown location'
- % self.where, '404 Not Found')
-
- # Try to figure out what to do based on view parameter
- self.view_func = _views.get(self.query_dict.get('view', None),
- self.view_func)
-
- if self.view_func is None:
- # view parameter is not set, try looking at pathtype and the
- # other parameters
- if self.pathtype == vclib.DIR:
- self.view_func = view_directory
- elif self.pathtype == vclib.FILE:
- if self.query_dict.has_key('rev'):
- if self.query_dict.get('content-type', None) in (viewcvs_mime_type,
- alt_mime_type):
- self.view_func = view_markup
- else:
- self.view_func = view_checkout
- elif self.query_dict.has_key('annotate'):
- self.view_func = view_annotate
- elif self.query_dict.has_key('r1') and self.query_dict.has_key('r2'):
- self.view_func = view_diff
- elif self.query_dict.has_key('tarball'):
- self.view_func = download_tarball
- elif self.query_dict.has_key('graph'):
- if not self.query_dict.has_key('makeimage'):
- self.view_func = view_cvsgraph
- else:
- self.view_func = view_cvsgraph_image
- else:
- self.view_func = view_log
-
- # Finally done parsing query string, set some extra variables
- # and call view_func
- self.full_name = self.rootpath + (self.where and '/' + self.where)
- if self.pathtype == vclib.FILE:
- self.setup_mime_type_info()
-
- # startup is done now.
- debug.t_end('startup')
-
- self.view_func(self)
-
- def get_url(self, **args):
- """Constructs a link to another ViewCVS page just like the get_link
- function except that it returns a single URL instead of a URL
- split into components"""
-
- url, params = apply(self.get_link, (), args)
- qs = compat.urlencode(params)
- if qs:
- return urllib.quote(url, _URL_SAFE_CHARS) + '?' + qs
- else:
- return urllib.quote(url, _URL_SAFE_CHARS)
-
- def get_link(self, view_func = None, rootname = None, where = None,
- params = None, pathtype = None):
- """Constructs a link pointing to another ViewCVS page. All arguments
- correspond to members of the Request object. If they are set to
- None they take values from the current page. Return value is a base
- URL and a dictionary of parameters"""
-
- if view_func is None:
- view_func = self.view_func
-
- if rootname is None:
- rootname = self.rootname
-
- if params is None:
- params = self.query_dict.copy()
-
- # must specify both where and pathtype or neither
- assert (where is None) == (pathtype is None)
-
- if where is None:
- where = self.where
- pathtype = self.pathtype
-
- last_link = view_func is view_checkout or view_func is download_tarball
-
- # The logic used to construct the URL is an inverse of the
- # logic used to interpret URLs in Request.run_viewcvs
-
- url = self.script_name
-
- # add checkout magic if possible
- if view_func is view_checkout and cfg.options.checkout_magic:
- url = url + '/' + checkout_magic_path
- view_func = None
-
- # add root name
- if cfg.options.root_as_url_component:
- url = url + '/' + rootname
- elif not (params.has_key('root') and params['root'] is None):
- if rootname != cfg.general.default_root:
- params['root'] = rootname
- else:
- params['root'] = None
-
- # if we are asking for the revision info view, we don't need any
- # path information
- if view_func == view_revision:
- where = None
- pathtype = vclib.DIR
-
- # add path
- if where:
- url = url + '/' + where
-
- # add suffix for tarball
- if view_func is download_tarball:
- if not where: url = url + '/root'
- url = url + '.tar.gz'
-
- # add trailing slash for a directory
- elif pathtype == vclib.DIR:
- url = url + '/'
-
- # no need to explicitly specify log view for a file
- if view_func is view_log and pathtype == vclib.FILE:
- view_func = None
-
- # no need to explicitly specify directory view for a directory
- if view_func is view_directory and pathtype == vclib.DIR:
- view_func = None
-
- # no need to explicitly specify annotate view when
- # there's an annotate parameter
- if view_func is view_annotate and params.has_key('annotate'):
- view_func = None
-
- # no need to explicitly specify diff view when
- # there's r1 and r2 parameters
- if view_func is view_diff and params.has_key('r1') \
- and params.has_key('r2'):
- view_func = None
-
- # no need to explicitly specify checkout view when
- # there's a rev parameter
- if view_func is view_checkout and params.has_key('rev'):
- view_func = None
-
- view_code = _view_codes.get(view_func)
- if view_code and not (params.has_key('view') and params['view'] is None):
- params['view'] = view_code
-
- return url, self.get_options(params, not last_link)
-
- def get_options(self, params = {}, sticky_vars=1):
- """Combine params with current sticky values"""
- ret = { }
- if sticky_vars:
- for name in _sticky_vars:
- value = self.query_dict.get(name)
- if value is not None and not params.has_key(name):
- ret[name] = self.query_dict[name]
- for name, val in params.items():
- if val is not None:
- ret[name] = val
- return ret
-
- def setup_mime_type_info(self):
- if cfg.general.mime_types_file:
- mimetypes.init([cfg.general.mime_types_file])
- self.mime_type, self.encoding = mimetypes.guess_type(self.where)
- if not self.mime_type:
- self.mime_type = 'text/plain'
- self.default_viewable = cfg.options.allow_markup and \
- (is_viewable_image(self.mime_type)
- or is_text(self.mime_type))
-
-def _validate_param(name, value):
- """Validate whether the given value is acceptable for the param name.
-
- If the value is not allowed, then an error response is generated, and
- this function throws an exception. Otherwise, it simply returns None.
- """
-
- try:
- validator = _legal_params[name]
- except KeyError:
- raise debug.ViewcvsException(
- 'An illegal parameter name ("%s") was passed.' % cgi.escape(name),
- '400 Bad Request')
-
- if validator is None:
- return
-
- # is the validator a regex?
- if hasattr(validator, 'match'):
- if not validator.match(value):
- raise debug.ViewcvsException(
- 'An illegal value ("%s") was passed as a parameter.' %
- cgi.escape(value), '400 Bad Request')
- return
-
- # the validator must be a function
- validator(value)
-
-def _validate_regex(value):
- # hmm. there isn't anything that we can do here.
-
- ### we need to watch the flow of these parameters through the system
- ### to ensure they don't hit the page unescaped. otherwise, these
- ### parameters could constitute a CSS attack.
- pass
-
-# obvious things here. note that we don't need uppercase for alpha.
-_re_validate_alpha = re.compile('^[a-z]+$')
-_re_validate_number = re.compile('^[0-9]+$')
-
-# when comparing two revs, we sometimes construct REV:SYMBOL, so ':' is needed
-_re_validate_revnum = re.compile('^[-_.a-zA-Z0-9:]*$')
-
-# it appears that RFC 2045 also says these chars are legal: !#$%&'*+^{|}~`
-# but woah... I'll just leave them out for now
-_re_validate_mimetype = re.compile('^[-_.a-zA-Z0-9/]+$')
-
-# the legal query parameters and their validation functions
-_legal_params = {
- 'root' : None,
- 'view' : None,
- 'search' : _validate_regex,
- 'p1' : None,
- 'p2' : None,
-
- 'hideattic' : _re_validate_number,
- 'sortby' : _re_validate_alpha,
- 'sortdir' : _re_validate_alpha,
- 'logsort' : _re_validate_alpha,
- 'diff_format' : _re_validate_alpha,
- 'only_with_tag' : _re_validate_revnum,
- 'dir_pagestart' : _re_validate_number,
- 'log_pagestart' : _re_validate_number,
- 'hidecvsroot' : _re_validate_number,
- 'annotate' : _re_validate_revnum,
- 'graph' : _re_validate_revnum,
- 'makeimage' : _re_validate_number,
- 'tarball' : _re_validate_number,
- 'r1' : _re_validate_revnum,
- 'tr1' : _re_validate_revnum,
- 'r2' : _re_validate_revnum,
- 'tr2' : _re_validate_revnum,
- 'rev' : _re_validate_revnum,
- 'content-type' : _re_validate_mimetype,
- }
-
-# regex used to move from a file to a directory
-_re_up_path = re.compile('(^|/)[^/]+$')
-_re_up_attic_path = re.compile('(^|/)(Attic/)?[^/]+$')
-def get_up_path(request, path, hideattic=0):
- if request.roottype == 'svn' or hideattic:
- return re.sub(_re_up_path, '', path)
- else:
- return re.sub(_re_up_attic_path, '', path)
-
-def _strip_suffix(suffix, where, path_parts, pathtype, repos, view_func):
- """strip the suffix from a repository path if the resulting path
- is of the specified type, otherwise return None"""
- l = len(suffix)
- if where[-l:] == suffix:
- path_parts = path_parts[:]
- path_parts[-1] = path_parts[-1][:-l]
- t = _repos_pathtype(repos, path_parts)
- if pathtype == t:
- return where[:-l], path_parts, t, view_func
- return None
-
-def _repos_pathtype(repos, path_parts):
- """return the type of a repository path, or None if the path
- does not exist"""
- type = None
- try:
- type = repos.itemtype(path_parts)
- except vclib.ItemNotFound:
- pass
- return type
-
-def generate_page(request, tname, data):
- # allow per-language template selection
- if request:
- tname = string.replace(tname, '%lang%', request.language)
- else:
- tname = string.replace(tname, '%lang%', 'en')
-
- debug.t_start('ezt-parse')
- template = ezt.Template(os.path.join(g_install_dir, tname))
- debug.t_end('ezt-parse')
-
- template.generate(sys.stdout, data)
-
-def html_footer(request):
- data = common_template_data(request)
-
- # generate the footer
- generate_page(request, cfg.templates.footer, data)
-
-def clickable_path(request, leaf_is_link, drop_leaf):
- where = ''
- s = '<a href="%s#dirlist">[%s]</a>' % (_dir_url(request, where),
- request.repos.name)
-
- for part in request.path_parts[:-1]:
- if where: where = where + '/'
- where = where + part
- s = s + ' / <a href="%s#dirlist">%s</a>' % (_dir_url(request, where), part)
-
- if not drop_leaf and request.path_parts:
- if leaf_is_link:
- s = s + ' / %s' % (request.path_parts[-1])
- else:
- if request.pathtype == vclib.DIR:
- url = request.get_url(view_func=view_directory, params={}) + '#dirlist'
- else:
- url = request.get_url(view_func=view_log, params={})
- s = s + ' / <a href="%s">%s</a>' % (url, request.path_parts[-1])
-
- return s
-
-def _dir_url(request, where):
- """convenient wrapper for get_url used by clickable_path()"""
- return request.get_url(view_func=view_directory, where=where,
- pathtype=vclib.DIR, params={})
-
-
-def prep_tags(request, tags):
- url, params = request.get_link(params={'only_with_tag': None})
- params = compat.urlencode(params)
- if params:
- url = urllib.quote(url, _URL_SAFE_CHARS) + '?' + params + '&only_with_tag='
- else:
- url = urllib.quote(url, _URL_SAFE_CHARS) + '?only_with_tag='
-
- links = [ ]
- for tag in tags:
- links.append(_item(name=tag, href=url+tag))
- return links
-
-def is_viewable_image(mime_type):
- return mime_type in ('image/gif', 'image/jpeg', 'image/png')
-
-def is_text(mime_type):
- return mime_type[:5] == 'text/'
-
-_re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)(://[-a-zA-Z0-9%.~:_/]+)([?&]([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*(#([-a-zA-Z0-9%.~:_]+)?)?)')
-_re_rewrite_email = re.compile('([-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})')
-def htmlify(html):
- html = cgi.escape(html)
- html = re.sub(_re_rewrite_url, r'<a href="\1">\1</a>', html)
- html = re.sub(_re_rewrite_email, r'<a href="mailto:\1">\1</a>', html)
- return html
-
-def format_log(log):
- s = htmlify(log[:cfg.options.short_log_len])
- if len(log) > cfg.options.short_log_len:
- s = s + '...'
- return s
-
-_time_desc = {
- 1 : 'second',
- 60 : 'minute',
- 3600 : 'hour',
- 86400 : 'day',
- 604800 : 'week',
- 2628000 : 'month',
- 31536000 : 'year',
- }
-
-def get_time_text(request, interval, num):
- "Get some time text, possibly internationalized."
- ### some languages have even harder pluralization rules. we'll have to
- ### deal with those on demand
- if num == 0:
- return ''
- text = _time_desc[interval]
- if num == 1:
- attr = text + '_singular'
- fmt = '%d ' + text
- else:
- attr = text + '_plural'
- fmt = '%d ' + text + 's'
- try:
- fmt = getattr(request.kv.i18n.time, attr)
- except AttributeError:
- pass
- return fmt % num
-
-def little_time(request):
- try:
- return request.kv.i18n.time.little_time
- except AttributeError:
- return 'very little time'
-
-def html_time(request, secs, extended=0):
- secs = long(time.time()) - secs
- if secs < 2:
- return little_time(request)
- breaks = _time_desc.keys()
- breaks.sort()
- i = 0
- while i < len(breaks):
- if secs < 2 * breaks[i]:
- break
- i = i + 1
- value = breaks[i - 1]
- s = get_time_text(request, value, secs / value)
-
- if extended and i > 1:
- secs = secs % value
- value = breaks[i - 2]
- ext = get_time_text(request, value, secs / value)
- if ext:
- ### this is not i18n compatible. pass on it for now
- s = s + ', ' + ext
- return s
-
-def common_template_data(request):
- data = {
- 'cfg' : cfg,
- 'vsn' : __version__,
- 'kv' : request.kv,
- 'icons' : cfg.options.icons,
- 'docroot' : cfg.options.docroot is None \
- and request.script_name + '/' + docroot_magic_path \
- or cfg.options.docroot,
- }
- return data
-
-def nav_header_data(request, rev):
- path, filename = os.path.split(request.where)
- if request.roottype == 'cvs' and path[-6:] == '/Attic':
- path = path[:-6]
-
- data = common_template_data(request)
- data.update({
- 'nav_path' : clickable_path(request, 1, 0),
- 'path' : path,
- 'filename' : filename,
- 'file_url' : request.get_url(view_func=view_log, params={}),
- 'rev' : rev
- })
- return data
-
-def copy_stream(fp):
- while 1:
- chunk = fp.read(CHUNK_SIZE)
- if not chunk:
- break
- sys.stdout.write(chunk)
-
-def read_stream(fp):
- string = ''
- while 1:
- chunk = fp.read(CHUNK_SIZE)
- if not chunk:
- break
- else:
- string = string + chunk
- return string
-
-def markup_stream_default(fp):
- print '<pre>'
- while 1:
- ### technically, the htmlify() could fail if something falls across
- ### the chunk boundary. TFB.
- chunk = fp.read(CHUNK_SIZE)
- if not chunk:
- break
- sys.stdout.write(htmlify(chunk))
- print '</pre>'
-
-def markup_stream_python(fp):
- ### convert this code to use the recipe at:
- ### http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298
- ### note that the cookbook states all the code is licensed according to
- ### the Python license.
- try:
- # see if Marc-Andre Lemburg's py2html stuff is around
- # http://starship.python.net/crew/lemburg/SoftwareDescriptions.html#py2html.py
- ### maybe restrict the import to *only* this directory?
- sys.path.insert(0, cfg.options.py2html_path)
- import py2html
- import PyFontify
- except ImportError:
- # fall back to the default streamer
- markup_stream_default(fp)
- else:
- ### it doesn't escape stuff quite right, nor does it munge URLs and
- ### mailtos as well as we do.
- html = cgi.escape(fp.read())
- pp = py2html.PrettyPrint(PyFontify.fontify, "rawhtml", "color")
- html = pp.fontify(html)
- html = re.sub(_re_rewrite_url, r'<a href="\1">\1</a>', html)
- html = re.sub(_re_rewrite_email, r'<a href="mailto:\1">\1</a>', html)
- sys.stdout.write(html)
-
-def markup_stream_php(fp):
- sys.stdout.flush()
-
- os.putenv("SERVER_SOFTWARE", "")
- os.putenv("SERVER_NAME", "")
- os.putenv("GATEWAY_INTERFACE", "")
- os.putenv("REQUEST_METHOD", "")
- php = popen.pipe_cmds([["php","-q"]])
-
- php.write("<?\n$file = '';\n")
-
- while 1:
- chunk = fp.read(CHUNK_SIZE)
- if not chunk:
- if fp.eof() is None:
- time.sleep(1)
- continue
- break
- php.write("$file .= '")
- php.write(string.replace(string.replace(chunk, "\\", "\\\\"),"'","\\'"))
- php.write("';\n")
-
- php.write("\n\nhighlight_string($file);\n?>")
- php.close()
-
-def markup_stream_enscript(lang, fp):
- sys.stdout.flush()
- # I've tried to pass option '-C' to enscript to generate line numbers
- # Unfortunately this option doesn'nt work with HTML output in enscript
- # version 1.6.2.
- enscript = popen.pipe_cmds([(os.path.normpath(os.path.join(cfg.options.enscript_path,'enscript')),
- '--color', '--language=html',
- '--pretty-print=' + lang, '-o',
- '-', '-'),
- ('sed', '-n', '/^<PRE>$/,/<\\/PRE>$/p')])
-
- try:
- while 1:
- chunk = fp.read(CHUNK_SIZE)
- if not chunk:
- if fp.eof() is None:
- time.sleep(1)
- continue
- break
- enscript.write(chunk)
- except IOError:
- print "<h3>Failure during use of an external program:</h3>"
- print "The command line was:"
- print "<pre>"
- print os.path.normpath(os.path.join(cfg.options.enscript_path,'enscript')
- ) + " --color --language=html --pretty-print="+lang+" -o - -"
- print "</pre>"
- print "Please look at the error log of your webserver for more info."
- raise
-
- enscript.close()
- if sys.platform != "win32":
- os.wait()
-
-def markup_stream_rst(fp):
- import locale
- try:
- locale.setlocale(locale.LC_ALL, '')
- except:
- pass
-
- try:
- import docutils.readers.standalone
-
- class ViewCVSReader(docutils.readers.standalone.Reader):
-
- def __init__(self, fp):
- docutils.readers.Reader.__init__(self, parser=None, parser_name='restructuredtext')
- self.fp = fp
-
- def read(self, source, parser, settings):
- self.source = source
- if not self.parser:
- self.parser = parser
- self.settings = settings
- #self.input = self.source.read()
- # read all at once
- self.input = read_stream(self.fp)
- self.parse()
- return self.document
-
- from docutils.core import publish_file
- publish_file(reader=ViewCVSReader(fp), writer_name='html')
- except:
- raise
-
-markup_streamers = {
-# '.py' : markup_stream_python,
-# '.php' : markup_stream_php,
-# '.inc' : markup_stream_php,
- '.txt' : markup_stream_rst
- }
-
-### this sucks... we have to duplicate the extensions defined by enscript
-enscript_extensions = {
- '.C' : 'cpp',
- '.EPS' : 'postscript',
- '.DEF' : 'modula_2', # requires a patch for enscript 1.6.2, see INSTALL
- '.F' : 'fortran',
- '.H' : 'cpp',
- '.MOD' : 'modula_2', # requires a patch for enscript 1.6.2, see INSTALL
- '.PS' : 'postscript',
- '.S' : 'asm',
- '.SH' : 'sh',
- '.ada' : 'ada',
- '.adb' : 'ada',
- '.ads' : 'ada',
- '.awk' : 'awk',
- '.c' : 'c',
- '.c++' : 'cpp',
- '.cc' : 'cpp',
- '.cpp' : 'cpp',
- '.csh' : 'csh',
- '.cxx' : 'cpp',
- '.diff' : 'diffu',
- '.dpr' : 'delphi',
- '.el' : 'elisp',
- '.eps' : 'postscript',
- '.f' : 'fortran',
- '.for': 'fortran',
- '.gs' : 'haskell',
- '.h' : 'c',
- '.hpp' : 'cpp',
- '.hs' : 'haskell',
- '.htm' : 'html',
- '.html' : 'html',
- '.idl' : 'idl',
- '.java' : 'java',
- '.js' : 'javascript',
- '.lgs' : 'haskell',
- '.lhs' : 'haskell',
- '.m' : 'objc',
- '.m4' : 'm4',
- '.man' : 'nroff',
- '.nr' : 'nroff',
- '.p' : 'pascal',
- # classic setting:
- # '.pas' : 'pascal',
- # most people using pascal today are using the Delphi system originally
- # brought to us as Turbo-Pascal during the eighties of the last century:
- '.pas' : 'delphi',
- # ---
- '.patch' : 'diffu',
- # For Oracle sql packages. The '.pkg' extension might be used for other
- # file types, adjust here if necessary.
- '.pkg' : 'sql',
- '.pl' : 'perl',
- '.pm' : 'perl',
- '.pp' : 'pascal',
- '.ps' : 'postscript',
- '.s' : 'asm',
- '.scheme' : 'scheme',
- '.scm' : 'scheme',
- '.scr' : 'synopsys',
- '.sh' : 'sh',
- '.shtml' : 'html',
- '.sql' : 'sql',
- '.st' : 'states',
- '.syn' : 'synopsys',
- '.synth' : 'synopsys',
- '.tcl' : 'tcl',
- '.tex' : 'tex',
- '.texi' : 'tex',
- '.texinfo' : 'tex',
- '.v' : 'verilog',
- '.vba' : 'vba',
- '.vh' : 'verilog',
- '.vhd' : 'vhdl',
- '.vhdl' : 'vhdl',
-
- ### use enscript or py2html?
- '.py' : 'python',
- }
-enscript_filenames = {
- '.emacs' : 'elisp',
- 'GNUmakefile' : 'makefile',
- 'Makefile' : 'makefile',
- 'makefile' : 'makefile',
- 'ChangeLog' : 'changelog',
- }
-
-
-def make_time_string(date):
- """Returns formatted date string in either local time or UTC.
-
- The passed in 'date' variable is seconds since epoch.
-
- """
- if (cfg.options.use_localtime):
- localtime = time.localtime(date)
- return time.asctime(localtime) + ' ' + time.tzname[localtime[8]]
- else:
- return time.asctime(time.gmtime(date)) + ' UTC'
-
-def view_auto(request):
- if request.default_viewable:
- view_markup(request)
- else:
- view_checkout(request)
-
-def view_markup(request):
- full_name = request.full_name
- where = request.where
- query_dict = request.query_dict
- rev = request.query_dict.get('rev')
-
- fp, revision = request.repos.openfile(request.path_parts, rev)
-
- data = nav_header_data(request, revision)
- data.update({
- 'nav_file' : clickable_path(request, 1, 0),
- 'href' : request.get_url(view_func=view_checkout, params={}),
- 'text_href' : request.get_url(view_func=view_checkout,
- params={'content-type': 'text/plain'}),
- 'mime_type' : request.mime_type,
- 'log' : None,
- })
-
- if cfg.options.show_log_in_markup:
- if request.roottype == 'cvs':
- show_revs, rev_map, rev_order, taginfo, rev2tag, \
- cur_branch, branch_points, branch_names = read_log(full_name)
- entry = rev_map[revision]
-
- idx = string.rfind(revision, '.')
- branch = revision[:idx]
-
- entry.date_str = make_time_string(entry.date)
-
- data.update({
- 'roottype' : 'cvs',
- 'date_str' : entry.date_str,
- 'ago' : html_time(request, entry.date, 1),
- 'author' : entry.author,
- 'branches' : None,
- 'tags' : None,
- 'branch_points' : None,
- 'changed' : entry.changed,
- 'log' : htmlify(entry.log),
- 'size' : None,
- 'state' : entry.state,
- 'vendor_branch' : ezt.boolean(_re_is_vendor_branch.match(revision)),
- })
-
- if rev2tag.has_key(branch):
- data['branches'] = string.join(rev2tag[branch], ', ')
- if rev2tag.has_key(revision):
- data['tags'] = string.join(rev2tag[revision], ', ')
- if branch_points.has_key(revision):
- data['branch_points'] = string.join(branch_points[revision], ', ')
-
- prev_rev = string.split(revision, '.')
- while 1:
- if prev_rev[-1] == '0': # .0 can be caused by 'commit -r X.Y.Z.0'
- prev_rev = prev_rev[:-2] # X.Y.Z.0 becomes X.Y.Z
- else:
- prev_rev[-1] = str(int(prev_rev[-1]) - 1)
- prev = string.join(prev_rev, '.')
- if rev_map.has_key(prev) or prev == '':
- break
- data['prev'] = prev
- elif request.roottype == 'svn':
- alltags, logs = vclib.svn.fetch_log(request.repos, where)
- this_rev = int(revision)
- entry = logs[this_rev]
-
- data.update({
- 'roottype' : 'svn',
- 'date_str' : make_time_string(entry.date),
- 'ago' : html_time(request, entry.date, 1),
- 'author' : entry.author,
- 'branches' : None,
- 'tags' : None,
- 'branch_points' : None,
- 'changed' : entry.changed,
- 'log' : htmlify(entry.log),
- 'state' : entry.state,
- 'size' : entry.size,
- 'vendor_branch' : ezt.boolean(0),
- })
-
- revs = logs.keys()
- revs.sort()
- rev_idx = revs.index(this_rev)
- if rev_idx > 0:
- data['prev'] = str(revs[rev_idx - 1])
- else:
- data['prev'] = None
-
- data['tag'] = query_dict.get('only_with_tag')
-
- request.server.header()
- generate_page(request, cfg.templates.markup, data)
-
- if is_viewable_image(request.mime_type):
- url = request.get_url(view_func=view_checkout, params={})
- print '<img src="%s"><br>' % url
- while fp.read(8192):
- pass
- else:
- basename, ext = os.path.splitext(data['filename'])
- streamer = markup_streamers.get(ext)
- if streamer:
- streamer(fp)
- elif not cfg.options.use_enscript:
- markup_stream_default(fp)
- else:
- lang = enscript_extensions.get(ext)
- if not lang:
- lang = enscript_filenames.get(basename)
- if lang and lang not in cfg.options.disable_enscript_lang:
- markup_stream_enscript(lang, fp)
- else:
- markup_stream_default(fp)
- status = fp.close()
- if status:
- raise 'pipe error status: %d' % status
- html_footer(request)
-
-def revcmp(rev1, rev2):
- rev1 = map(int, string.split(rev1, '.'))
- rev2 = map(int, string.split(rev2, '.'))
- return cmp(rev1, rev2)
-
-def prepare_hidden_values(params):
- """returns variables from params encoded as a invisible HTML snippet.
- """
- hidden_values = []
- for name, value in params.items():
- hidden_values.append('<input type="hidden" name="%s" value="%s" />' %
- (name, value))
- return string.join(hidden_values, '')
-
-def sort_file_data(file_data, sortdir, sortby):
- def file_sort_cmp(file1, file2, sortby=sortby):
- if file1.kind == vclib.DIR: # is_directory
- if file2.kind == vclib.DIR:
- # both are directories. sort on name.
- return cmp(file1.name, file2.name)
- # file1 is a directory, it sorts first.
- return -1
- if file2.kind == vclib.DIR:
- # file2 is a directory, it sorts first.
- return 1
-
- # we should have data on these. if not, then it is because we requested
- # a specific tag and that tag is not present on the file.
- info1 = file1.rev or bincvs._FILE_HAD_ERROR
- info2 = file2.rev or bincvs._FILE_HAD_ERROR
- if info1 != bincvs._FILE_HAD_ERROR and info2 != bincvs._FILE_HAD_ERROR:
- # both are files, sort according to sortby
- if sortby == 'rev':
- return revcmp(file1.rev, file2.rev)
- elif sortby == 'date':
- return cmp(file2.date, file1.date) # latest date is first
- elif sortby == 'log':
- return cmp(file1.log, file2.log)
- elif sortby == 'author':
- return cmp(file1.author, file2.author)
- else:
- # sort by file name
- return cmp(file1.name, file2.name)
-
- # at this point only one of file1 or file2 are _FILE_HAD_ERROR.
- if info1 != bincvs._FILE_HAD_ERROR:
- return -1
-
- return 1
-
- file_data.sort(file_sort_cmp)
-
- if sortdir == "down":
- file_data.reverse()
-
-def view_directory(request):
- # if we have a directory and the request didn't end in "/", then redirect
- # so that it does.
- if request.server.getenv('PATH_INFO', '')[-1:] != '/':
- request.server.redirect(request.get_url())
-
- sortby = request.query_dict.get('sortby', cfg.options.sort_by) or 'file'
- sortdir = request.query_dict.get('sortdir', 'up')
-
- # prepare the data that will be passed to the template
- data = common_template_data(request)
- data.update({
- 'roottype' : request.roottype,
- 'where' : request.where,
- 'current_root' : request.repos.name,
- 'sortby' : sortby,
- 'sortdir' : sortdir,
- 'no_match' : None,
- 'unreadable' : None,
- 'tarball_href' : None,
- 'search_re' : None,
- 'dir_pagestart' : None,
- 'have_logs' : 'yes',
- 'sortby_file_href' : request.get_url(params={'sortby': 'file'}),
- 'sortby_rev_href' : request.get_url(params={'sortby': 'rev'}),
- 'sortby_date_href' : request.get_url(params={'sortby': 'date'}),
- 'sortby_author_href' : request.get_url(params={'sortby': 'author'}),
- 'sortby_log_href' : request.get_url(params={'sortby': 'log'}),
- 'sortdir_down_href' : request.get_url(params={'sortdir': 'down'}),
- 'sortdir_up_href' : request.get_url(params={'sortdir': 'up'}),
-
- ### in the future, it might be nice to break this path up into
- ### a list of elements, allowing the template to display it in
- ### a variety of schemes.
- 'nav_path' : clickable_path(request, 0, 0),
- })
-
- if not request.where:
- url, params = request.get_link(params={'root': None})
- data['change_root_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['change_root_hidden_values'] = prepare_hidden_values(params)
-
- # add in the roots for the selection
- allroots = list_roots(cfg)
- if len(allroots) < 2:
- roots = [ ]
- else:
- roots = allroots.keys()
- roots.sort(lambda n1, n2: cmp(string.lower(n1), string.lower(n2)))
- data['roots'] = roots
-
- if cfg.options.use_pagesize:
- url, params = request.get_link(params={'dir_pagestart': None})
- data['dir_paging_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['dir_paging_hidden_values'] = prepare_hidden_values(params)
-
- if cfg.options.allow_tar:
- data['tarball_href'] = request.get_url(view_func=download_tarball,
- params={})
-
- if request.roottype == 'svn':
- view_directory_svn(request, data, sortby, sortdir)
- else:
- view_directory_cvs(request, data, sortby, sortdir)
-
- if cfg.options.use_pagesize:
- data['dir_pagestart'] = int(query_dict.get('dir_pagestart',0))
- data['rows'] = paging(data, 'rows', data['dir_pagestart'], 'name')
-
- request.server.header()
- generate_page(request, cfg.templates.directory, data)
-
-def view_directory_cvs(request, data, sortby, sortdir):
- where = request.where
- query_dict = request.query_dict
-
- view_tag = query_dict.get('only_with_tag')
- hideattic = int(query_dict.get('hideattic', cfg.options.hide_attic))
-
- search_re = query_dict.get('search', '')
-
- # Search current directory
- file_data = request.repos.listdir(request.path_parts,
- not hideattic or view_tag)
-
- if cfg.options.use_re_search and search_re:
- file_data = search_files(request.repos, request.path_parts,
- file_data, search_re)
-
- get_dirs = cfg.options.show_subdir_lastmod and cfg.options.show_logs
-
- bincvs.get_logs(request.repos, request.path_parts,
- file_data, view_tag, get_dirs)
-
- has_tags = view_tag or request.repos.branch_tags or request.repos.plain_tags
-
- # prepare the data that will be passed to the template
- data.update({
- 'view_tag' : view_tag,
- 'attic_showing' : ezt.boolean(not hideattic),
- 'show_attic_href' : request.get_url(params={'hideattic': 0}),
- 'hide_attic_href' : request.get_url(params={'hideattic': 1}),
- 'has_tags' : ezt.boolean(has_tags),
- ### one day, if EZT has "or" capability, we can lose this
- 'selection_form' : ezt.boolean(has_tags or cfg.options.use_re_search),
- 'branch_tags': request.repos.branch_tags,
- 'plain_tags': request.repos.plain_tags,
- })
-
- if search_re:
- data['search_re'] = htmlify(search_re)
-
- # sort with directories first, and using the "sortby" criteria
- sort_file_data(file_data, sortdir, sortby)
-
- num_files = 0
- num_displayed = 0
- unreadable = 0
- have_logs = 0
- rows = data['rows'] = [ ]
-
- where_prefix = where and where + '/'
-
- ### display a row for ".." ?
- for file in file_data:
- row = _item(href=None, graph_href=None,
- author=None, log=None, log_file=None, log_rev=None,
- show_log=None, state=None)
-
- if file.rev == bincvs._FILE_HAD_ERROR:
- row.cvs = 'error'
- unreadable = 1
- elif file.rev is None:
- row.cvs = 'none'
- else:
- row.cvs = 'data'
- row.rev = file.rev
- row.author = file.author
- row.state = file.state
- row.time = html_time(request, file.date)
- if cfg.options.show_logs and file.log:
- row.show_log = 'yes'
- row.log = format_log(file.log)
- have_logs = 1
-
- row.anchor = file.name
- row.name = file.name
-
- row.type = (file.kind == vclib.FILE and 'file') or \
- (file.kind == vclib.DIR and 'dir')
-
- if file.verboten or not row.type:
- row.type = 'unreadable'
- unreadable = 1
-
- if file.kind == vclib.DIR:
- if not hideattic and file.name == 'Attic':
- continue
- if where == '' and ((file.name == 'CVSROOT' and cfg.options.hide_cvsroot)
- or cfg.is_forbidden(file.name)):
- continue
- if file.name == 'CVS': # CVS directory in repository is for fileattr.
- continue
-
- row.href = request.get_url(view_func=view_directory,
- where=where_prefix+file.name,
- pathtype=vclib.DIR,
- params={})
-
- if row.cvs == 'data':
- if cfg.options.use_cvsgraph:
- row.graph_href = '&nbsp;'
- if cfg.options.show_logs:
- row.log_file = file.newest_file
- row.log_rev = file.rev
-
- elif file.kind == vclib.FILE:
- num_files = num_files + 1
-
- if file.rev is None and not file.verboten:
- continue
- elif hideattic and view_tag and file.state == 'dead':
- continue
- num_displayed = num_displayed + 1
-
- file_where = where_prefix + (file.in_attic and 'Attic/' or '') + file.name
-
- row.href = request.get_url(view_func=view_log,
- where=file_where,
- pathtype=vclib.FILE,
- params={})
-
- row.rev_href = request.get_url(view_func=view_auto,
- where=file_where,
- pathtype=vclib.FILE,
- params={'rev': str(file.rev)})
-
- if cfg.options.use_cvsgraph:
- row.graph_href = request.get_url(view_func=view_cvsgraph,
- where=file_where,
- pathtype=vclib.FILE,
- params={})
-
- rows.append(row)
-
- data.update({
- 'num_files' : num_files,
- 'files_shown' : num_displayed,
- 'no_match' : num_files and not num_displayed and 'yes' or '',
- 'unreadable' : unreadable and 'yes' or '',
-
- # have_logs will be 0 if we only have dirs and !show_subdir_lastmod.
- # in that case, we don't need the extra columns
- 'have_logs' : have_logs and 'yes' or '',
- })
-
- if data['selection_form']:
- url, params = request.get_link(params={'only_with_tag': None,
- 'search': None})
- data['search_tag_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['search_tag_hidden_values'] = prepare_hidden_values(params)
-
-
-def view_directory_svn(request, data, sortby, sortdir):
- query_dict = request.query_dict
- where = request.where
-
- file_data = request.repos.listdir(request.path_parts)
- vclib.svn.get_logs(request.repos, where, file_data)
-
- data.update({
- 'view_tag' : None,
- 'tree_rev' : str(request.repos.rev),
- 'has_tags' : ezt.boolean(0),
- 'selection_form' : ezt.boolean(0)
- })
-
- if request.query_dict.has_key('rev'):
- data['jump_rev'] = request.query_dict['rev']
- else:
- data['jump_rev'] = str(request.repos.rev)
-
- url, params = request.get_link(params={'rev': None})
- data['jump_rev_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['jump_rev_hidden_values'] = prepare_hidden_values(params)
-
- # sort with directories first, and using the "sortby" criteria
- sort_file_data(file_data, sortdir, sortby)
-
- num_files = 0
- num_displayed = 0
- unreadable = 0
- rows = data['rows'] = [ ]
-
- where_prefix = where and where + '/'
- dir_params = {'rev': query_dict.get('rev')}
-
- for file in file_data:
- row = _item(href=None, graph_href=None,
- author=None, log=None, log_file=None, log_rev=None,
- show_log=None, state=None)
-
- row.rev = file.rev
- row.author = file.author or "&nbsp;"
- row.state = ''
- row.time = html_time(request, file.date)
- row.anchor = file.name
-
- if file.kind == vclib.DIR:
- row.type = 'dir'
- row.name = file.name
- row.cvs = 'none' # What the heck is this?
- row.href = request.get_url(view_func=view_directory,
- where=where_prefix + file.name,
- pathtype=vclib.DIR,
- params=dir_params)
- else:
- row.type = 'file'
- row.name = file.name
- row.cvs = 'data' # What the heck is this?
-
- row.href = request.get_url(view_func=view_log,
- where=where_prefix + file.name,
- pathtype=vclib.FILE,
- params={})
-
- row.rev_href = request.get_url(view_func=view_auto,
- where=where_prefix + file.name,
- pathtype=vclib.FILE,
- params={'rev': str(row.rev)})
-
- num_files = num_files + 1
- num_displayed = num_displayed + 1
- if cfg.options.show_logs:
- row.show_log = 'yes'
- row.log = format_log(file.log or "")
-
- rows.append(row)
-
- ### we need to fix the template w.r.t num_files. it usually is not a
- ### correct (original) count of the files available for selecting
- data['num_files'] = num_files
-
- # the number actually displayed
- data['files_shown'] = num_displayed
-
-def paging(data, key, pagestart, local_name):
- # Implement paging
- # Create the picklist
- picklist = data['picklist'] = []
- for i in range(0, len(data[key]), cfg.options.use_pagesize):
- pick = _item(start=None, end=None, count=None)
- pick.start = getattr(data[key][i], local_name)
- pick.count = i
- pick.page = (i / cfg.options.use_pagesize) + 1
- try:
- pick.end = getattr(data[key][i+cfg.options.use_pagesize-1], local_name)
- except IndexError:
- pick.end = getattr(data[key][-1], local_name)
- picklist.append(pick)
- data['picklist_len'] = len(picklist)
- # Need to fix
- # pagestart can be greater than the length of data[key] if you
- # select a tag or search while on a page other than the first.
- # Should reset to the first page, this test won't do that every
- # time that it is needed.
- # Problem might go away if we don't hide non-matching files when
- # selecting for tags or searching.
- if pagestart > len(data[key]):
- pagestart = 0
- pageend = pagestart + cfg.options.use_pagesize
- # Slice
- return data[key][pagestart:pageend]
-
-def logsort_date_cmp(rev1, rev2):
- # sort on date; secondary on revision number
- return -cmp(rev1.date, rev2.date) or -revcmp(rev1.rev, rev2.rev)
-
-def logsort_rev_cmp(rev1, rev2):
- # sort highest revision first
- return -revcmp(rev1.rev, rev2.rev)
-
-def find_first_rev(taginfo, revs):
- "Find first revision that matches a normal tag or is on a branch tag"
- for rev in revs:
- if taginfo.matches_rev(rev) or taginfo.holds_rev(rev):
- return rev
- return None
-
-def read_log(full_name, which_rev=None, view_tag=None, logsort='cvs'):
- head, cur_branch, taginfo, revs = bincvs.fetch_log(cfg.general,
- full_name, which_rev)
-
- rev_order = map(lambda entry: entry.rev, revs)
- rev_order.sort(revcmp)
- rev_order.reverse()
-
- # HEAD is an artificial tag which is simply the highest tag number on the
- # main branch, unless there is a branch tag in the RCS file in which case
- # it's the highest revision on that branch. Find it by looking through
- # rev_order; it is the first commit listed on the appropriate branch.
- # This is not neccesary the same revision as marked as head in the RCS file.
- ### Why are we defining our own HEAD instead of just using the revision
- ### marked as head? What's wrong with: taginfo['HEAD'] = bincvs.TagInfo(head)
- taginfo['MAIN'] = bincvs.TagInfo(cur_branch)
- taginfo['HEAD'] = find_first_rev(taginfo['MAIN'], rev_order)
-
- # map revision numbers to tag names
- rev2tag = { }
-
- # names of symbols at each branch point
- branch_points = { }
-
- branch_names = [ ]
-
- # Now that we know all of the revision numbers, we can associate
- # absolute revision numbers with all of the symbolic names, and
- # pass them to the form so that the same association doesn't have
- # to be built then.
-
- items = taginfo.items()
- items.sort()
- items.reverse()
- for name, tag in items:
- if not isinstance(tag, bincvs.TagInfo):
- taginfo[name] = tag = bincvs.TagInfo(tag)
-
- number = tag.number()
-
- if tag.is_branch():
- branch_names.append(name)
-
- if number == cur_branch:
- default_branch = name
-
- if not tag.is_trunk():
- rev = tag.branches_at()
- if branch_points.has_key(rev):
- branch_points[rev].append(name)
- else:
- branch_points[rev] = [ name ]
-
- # revision number you'd get if you checked out this branch
- tag.co_rev = find_first_rev(tag, rev_order)
- else:
- tag.co_rev = number
-
- if rev2tag.has_key(number):
- rev2tag[number].append(name)
- else:
- rev2tag[number] = [ name ]
-
- if view_tag:
- tag = taginfo.get(view_tag)
- if not tag:
- raise debug.ViewcvsException('Tag %s not defined.' % view_tag,
- '404 Tag Not Found')
-
- show_revs = [ ]
- for entry in revs:
- rev = entry.rev
- if tag.matches_rev(rev) or tag.branches_at()==rev or tag.holds_rev(rev):
- show_revs.append(entry)
- else:
- show_revs = revs
-
- if logsort == 'date':
- show_revs.sort(logsort_date_cmp)
- elif logsort == 'rev':
- show_revs.sort(logsort_rev_cmp)
- else:
- # no sorting
- pass
-
- # build a map of revision number to entry information
- rev_map = { }
- for entry in revs:
- rev_map[entry.rev] = entry
-
- ### some of this return stuff doesn't make a lot of sense...
- return show_revs, rev_map, rev_order, taginfo, rev2tag, \
- default_branch, branch_points, branch_names
-
-_re_is_vendor_branch = re.compile(r'^1\.1\.1\.\d+$')
-
-def augment_entry(entry, request, rev_map, rev2tag, branch_points,
- rev_order, extended, name_printed):
- "Augment the entry with additional, computed data from the log output."
-
- query_dict = request.query_dict
-
- rev = entry.rev
- idx = string.rfind(rev, '.')
- branch = rev[:idx]
-
- entry.vendor_branch = ezt.boolean(_re_is_vendor_branch.match(rev))
-
- entry.date_str = make_time_string(entry.date)
-
- entry.ago = html_time(request, entry.date, 1)
-
- entry.branches = prep_tags(request, rev2tag.get(branch, [ ]))
- entry.tags = prep_tags(request, rev2tag.get(rev, [ ]))
- entry.branch_points = prep_tags(request, branch_points.get(rev, [ ]))
-
- prev_rev = string.split(rev, '.')
- while 1:
- if prev_rev[-1] == '0': # .0 can be caused by 'commit -r X.Y.Z.0'
- prev_rev = prev_rev[:-2] # X.Y.Z.0 becomes X.Y.Z
- else:
- prev_rev[-1] = str(int(prev_rev[-1]) - 1)
- prev = string.join(prev_rev, '.')
- if rev_map.has_key(prev) or prev == '':
- break
- entry.prev = prev
-
- ### maybe just overwrite entry.log?
- entry.html_log = htmlify(entry.log)
-
- if extended:
- entry.tag_names = rev2tag.get(rev, [ ])
- if rev2tag.has_key(branch) and not name_printed.has_key(branch):
- entry.branch_names = rev2tag.get(branch)
- name_printed[branch] = 1
- else:
- entry.branch_names = [ ]
-
- entry.href = request.get_url(view_func=view_checkout, params={'rev': rev})
- entry.view_href = request.get_url(view_func=view_markup,
- params={'rev': rev})
- entry.text_href = request.get_url(view_func=view_checkout,
- params={'content-type': 'text/plain',
- 'rev': rev})
-
- entry.annotate_href = request.get_url(view_func=view_annotate,
- params={'annotate': rev})
-
- # figure out some target revisions for performing diffs
- entry.branch_point = None
- entry.next_main = None
-
- idx = string.rfind(branch, '.')
- if idx != -1:
- branch_point = branch[:idx]
-
- if not entry.vendor_branch \
- and branch_point != rev and branch_point != prev:
- entry.branch_point = branch_point
-
- # if it's on a branch (and not a vendor branch), then diff against the
- # next revision of the higher branch (e.g. change is committed and
- # brought over to -stable)
- if string.count(rev, '.') > 1 and not entry.vendor_branch:
- # locate this rev in the ordered list of revisions
- i = rev_order.index(rev)
-
- # create a rev that can be compared component-wise
- c_rev = string.split(rev, '.')
-
- while i:
- next = rev_order[i - 1]
- c_work = string.split(next, '.')
- if len(c_work) < len(c_rev):
- # found something not on the branch
- entry.next_main = next
- break
-
- # this is a higher version on the same branch; the lower one (rev)
- # shouldn't have a diff against the "next main branch"
- if c_work[:-1] == c_rev[:len(c_work) - 1]:
- break
-
- i = i - 1
-
- # the template could do all these comparisons itself, but let's help
- # it out.
- r1 = query_dict.get('r1')
- if r1 and r1 != rev and r1 != prev and r1 != entry.branch_point \
- and r1 != entry.next_main:
- entry.to_selected = 'yes'
- else:
- entry.to_selected = None
-
-def view_log(request):
- diff_format = request.query_dict.get('diff_format', cfg.options.diff_format)
- logsort = request.query_dict.get('logsort', cfg.options.log_sort)
-
- data = common_template_data(request)
- data.update({
- 'roottype' : request.roottype,
- 'current_root' : request.repos.name,
- 'where' : request.where,
- 'nav_path' : clickable_path(request, 1, 0),
- 'branch' : None,
- 'mime_type' : request.mime_type,
- 'rev_selected' : request.query_dict.get('r1'),
- 'path_selected' : request.query_dict.get('p1'),
- 'diff_format' : diff_format,
- 'logsort' : logsort,
- 'viewable' : ezt.boolean(request.default_viewable),
- 'is_text' : ezt.boolean(is_text(request.mime_type)),
- 'human_readable' : ezt.boolean(diff_format in ('h', 'l')),
- 'log_pagestart' : None,
- 'graph_href' : None,
- })
-
- url, params = request.get_link(view_func=view_diff,
- params={'r1': None, 'r2': None,
- 'diff_format': None})
- params = compat.urlencode(params)
- data['diff_url'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['diff_params'] = params and '&' + params
-
- if cfg.options.use_pagesize:
- url, params = request.get_link(params={'log_pagestart': None})
- data['log_paging_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['log_paging_hidden_values'] = prepare_hidden_values(params)
-
- url, params = request.get_link(params={'r1': None, 'r2': None, 'tr1': None,
- 'tr2': None, 'diff_format': None})
- data['diff_select_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['diff_select_hidden_values'] = prepare_hidden_values(params)
-
- url, params = request.get_link(params={'logsort': None})
- data['logsort_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['logsort_hidden_values'] = prepare_hidden_values(params)
-
- if request.roottype == 'svn':
- view_log_svn(request, data, logsort)
- else:
- view_log_cvs(request, data, logsort)
-
-def view_log_svn(request, data, logsort):
- query_dict = request.query_dict
-
- alltags, logs = vclib.svn.fetch_log(request.repos, request.where)
- up_where, filename = os.path.split(request.where)
-
- entries = []
- prev_rev = None
- prev_path = None
- show_revs = logs.keys()
- show_revs.sort()
- for rev in show_revs:
- entry = logs[rev]
- entry.prev = prev_rev
- entry.prev_path = prev_path
- entry.href = request.get_url(view_func=view_checkout, where=entry.filename,
- pathtype=vclib.FILE, params={'rev': rev})
- entry.view_href = request.get_url(view_func=view_markup,
- where=entry.filename,
- pathtype=vclib.FILE,
- params={'rev': rev})
- entry.text_href = request.get_url(view_func=view_checkout,
- where=entry.filename,
- pathtype=vclib.FILE,
- params={'content-type': 'text/plain',
- 'rev': rev})
- entry.revision_href = request.get_url(view_func=view_revision,
- where=None,
- pathtype=None,
- params={'rev': rev})
-
- if entry.copy_path:
- entry.copy_href = request.get_url(view_func=view_log,
- where=entry.copy_path,
- pathtype=vclib.FILE, params={})
-
- entry.tags = [ ]
- entry.branches = [ ]
- entry.branch_point = None
- entry.branch_points = [ ]
- entry.next_main = None
- entry.to_selected = None
- entry.vendor_branch = None
- entry.ago = html_time(request, entry.date, 1)
- entry.date_str = make_time_string(entry.date)
- entry.tag_names = [ ]
- entry.branch_names = [ ]
- if not entry.log:
- entry.log = ""
- entry.html_log = htmlify(entry.log)
-
- # the template could do all these comparisons itself, but let's help
- # it out.
- r1 = query_dict.get('r1')
- if r1 and r1 != str(rev) and r1 != str(prev_rev):
- entry.to_selected = 'yes'
- else:
- entry.to_selected = None
-
- entries.append(entry)
- prev_rev = rev
- prev_path = entry.filename
- show_revs.reverse()
- entries.reverse()
-
- data.update({
- 'back_url' : request.get_url(view_func=view_directory, pathtype=vclib.DIR,
- where=up_where, params={}),
- 'filename' : filename,
- 'view_tag' : None,
- 'entries' : entries,
- 'tags' : [ ],
- 'branch_names' : [ ],
- })
-
- if len(show_revs):
- data['tr1'] = show_revs[-1]
- data['tr2'] = show_revs[0]
- else:
- data['tr1'] = None
- data['tr2'] = None
-
- if cfg.options.use_pagesize:
- data['log_pagestart'] = int(query_dict.get('log_pagestart',0))
- data['entries'] = paging(data, 'entries', data['log_pagestart'], 'rev')
-
- request.server.header()
- generate_page(request, cfg.templates.log, data)
-
-def view_log_cvs(request, data, logsort):
- full_name = request.full_name
- where = request.where
- query_dict = request.query_dict
-
- view_tag = query_dict.get('only_with_tag')
-
- show_revs, rev_map, rev_order, taginfo, rev2tag, \
- cur_branch, branch_points, branch_names = \
- read_log(full_name, None, view_tag, logsort)
-
- up_where = get_up_path(request, where, int(query_dict.get('hideattic',
- cfg.options.hide_attic)))
-
- filename = os.path.basename(where)
-
- data.update({
- 'back_url' : request.get_url(view_func=view_directory, pathtype=vclib.DIR,
- where=up_where, params={}),
- 'filename' : filename,
- 'view_tag' : view_tag,
- 'entries' : show_revs, ### rename the show_rev local to entries?
-
- })
-
- if cfg.options.use_cvsgraph:
- data['graph_href'] = request.get_url(view_func=view_cvsgraph, params={})
-
- if cur_branch:
- data['branch'] = cur_branch
-
- ### I don't like this URL construction stuff. the value
- ### for head_abs_href vs head_href is a bit bogus: why decide to
- ### include/exclude the mime type from the URL? should just always be
- ### the same, right?
- if request.default_viewable:
- data['head_href'] = request.get_url(view_func=view_markup, params={})
- data['head_abs_href'] = request.get_url(view_func=view_checkout,
- params={})
- else:
- data['head_href'] = request.get_url(view_func=view_checkout, params={})
-
- name_printed = { }
- for entry in show_revs:
- # augment the entry with (extended=1) info.
- augment_entry(entry, request, rev_map, rev2tag, branch_points,
- rev_order, 1, name_printed)
-
- tagitems = taginfo.items()
- tagitems.sort()
- tagitems.reverse()
-
- data['tags'] = tags = [ ]
- for tag, rev in tagitems:
- if rev.co_rev:
- tags.append(_item(rev=rev.co_rev, name=tag))
-
- if query_dict.has_key('r1'):
- diff_rev = query_dict['r1']
- else:
- diff_rev = show_revs[-1].rev
- data['tr1'] = diff_rev
-
- if query_dict.has_key('r2'):
- diff_rev = query_dict['r2']
- else:
- diff_rev = show_revs[0].rev
- data['tr2'] = diff_rev
-
- branch_names.sort()
- branch_names.reverse()
- data['branch_names'] = branch_names
-
- if branch_names:
- url, params = request.get_link(params={'only_with_tag': None})
- data['branch_select_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['branch_select_hidden_values'] = prepare_hidden_values(params)
-
- if cfg.options.use_pagesize:
- data['log_pagestart'] = int(query_dict.get('log_pagestart',0))
- data['entries'] = paging(data, 'entries', data['log_pagestart'], 'rev')
-
- request.server.header()
- generate_page(request, cfg.templates.log, data)
-
-def view_checkout(request):
- rev = request.query_dict.get('rev')
- fp = request.repos.openfile(request.path_parts, rev)[0]
- mime_type = request.query_dict.get('content-type', request.mime_type)
- request.server.header(mime_type)
- copy_stream(fp)
-
-def view_annotate(request):
- if not cfg.options.allow_annotate:
- raise "annotate no allows"
-
- rev = request.query_dict.get('annotate')
- data = nav_header_data(request, rev)
-
- request.server.header()
- generate_page(request, cfg.templates.annotate, data)
-
- ### be nice to hook this into the template...
- import blame
- blame.make_html(request.repos.rootpath, request.where + ',v', rev,
- compat.urlencode(request.get_options()))
-
- html_footer(request)
-
-
-def view_cvsgraph_image(request):
- "output the image rendered by cvsgraph"
- # this function is derived from cgi/cvsgraphmkimg.cgi
-
- if not cfg.options.use_cvsgraph:
- raise "cvsgraph no allows"
-
- request.server.header('image/png')
- fp = popen.popen(os.path.normpath(os.path.join(cfg.options.cvsgraph_path,'cvsgraph')),
- ("-c", cfg.options.cvsgraph_conf,
- "-r", request.repos.rootpath,
- request.where + ',v'), 'rb', 0)
- copy_stream(fp)
- fp.close()
-
-def view_cvsgraph(request):
- "output a page containing an image rendered by cvsgraph"
- # this function is derived from cgi/cvsgraphwrapper.cgi
-
- if not cfg.options.use_cvsgraph:
- raise "cvsgraph no allows"
-
- where = request.where
-
- pathname, filename = os.path.split(where)
- if pathname[-6:] == '/Attic':
- pathname = pathname[:-6]
-
- data = nav_header_data(request, None)
-
- # Required only if cvsgraph needs to find it's supporting libraries.
- # Uncomment and set accordingly if required.
- #os.environ['LD_LIBRARY_PATH'] = '/usr/lib:/usr/local/lib'
-
- query = compat.urlencode(request.get_options({}))
- amp_query = query and '&' + query
- qmark_query = query and '?' + query
-
- imagesrc = request.get_url(view_func=view_cvsgraph_image)
-
- # Create an image map
- fp = popen.popen(os.path.join(cfg.options.cvsgraph_path, 'cvsgraph'),
- ("-i",
- "-c", cfg.options.cvsgraph_conf,
- "-r", request.repos.rootpath,
- "-6", amp_query,
- "-7", qmark_query,
- request.where + ',v'), 'rb', 0)
-
- data.update({
- 'imagemap' : fp,
- 'imagesrc' : imagesrc,
- })
-
- request.server.header()
- generate_page(request, cfg.templates.graph, data)
-
-def search_files(repos, path_parts, files, search_re):
- """ Search files in a directory for a regular expression.
-
- Does a check-out of each file in the directory. Only checks for
- the first match.
- """
-
- # Pass in search regular expression. We check out
- # each file and look for the regular expression. We then return the data
- # for all files that match the regex.
-
- # Compile to make sure we do this as fast as possible.
- searchstr = re.compile(search_re)
-
- # Will become list of files that have at least one match.
- # new_file_list also includes directories.
- new_file_list = [ ]
-
- # Loop on every file (and directory)
- for file in files:
- # Is this a directory? If so, append name to new_file_list
- # and move to next file.
- if file.kind != vclib.FILE:
- new_file_list.append(file)
- continue
-
- # Only files at this point
-
- # figure out where we are and its mime type
- mime_type, encoding = mimetypes.guess_type(file.name)
- if not mime_type:
- mime_type = 'text/plain'
-
- # Shouldn't search binary files, or should we?
- # Should allow all text mime types to pass.
- if mime_type[:4] != 'text':
- continue
-
- # Only text files at this point
-
- # process_checkout will checkout the head version out of the repository
- # Assign contents of checked out file to fp.
- fp = repos.openfile(path_parts + [file.name])[0]
-
- # Read in each line, use re.search to search line.
- # If successful, add file to new_file_list and break.
- while 1:
- line = fp.readline()
- if not line:
- break
- if searchstr.search(line):
- new_file_list.append(file)
- # close down the pipe (and wait for the child to terminate)
- fp.close()
- break
-
- return new_file_list
-
-
-def view_doc(request):
- """Serve ViewCVS help pages locally.
-
- Using this avoids the need for modifying the setup of the web server.
- """
- help_page = request.where
- if CONF_PATHNAME:
- doc_directory = os.path.join(g_install_dir, "doc")
- else:
- # aid testing from CVS working copy:
- doc_directory = os.path.join(g_install_dir, "website")
- try:
- fp = open(os.path.join(doc_directory, help_page), "rb")
- except IOError, v:
- raise debug.ViewcvsException('help file "%s" not available\n(%s)'
- % (help_page, str(v)), '404 Not Found')
- if help_page[-3:] == 'png':
- request.server.header('image/png')
- elif help_page[-3:] == 'jpg':
- request.server.header('image/jpeg')
- elif help_page[-3:] == 'gif':
- request.server.header('image/gif')
- else: # assume HTML:
- request.server.header()
- copy_stream(fp)
- fp.close()
-
-def rcsdiff_date_reformat(date_str):
- try:
- date = compat.cvs_strptime(date_str)
- except ValueError:
- return date_str
- return make_time_string(compat.timegm(date))
-
-_re_extract_rev = re.compile(r'^[-+]+ [^\t]+\t([^\t]+)\t((\d+\.)*\d+)$')
-_re_extract_info = re.compile(r'@@ \-([0-9]+).*\+([0-9]+).*@@(.*)')
-def human_readable_diff(request, fp, rev1, rev2, sym1, sym2):
- # do this now, in case we need to print an error
- request.server.header()
-
- query_dict = request.query_dict
-
- where = request.where
-
- data = nav_header_data(request, rev2)
-
- log_rev1 = log_rev2 = None
- date1 = date2 = ''
- rcsdiff_eflag = 0
- while 1:
- line = fp.readline()
- if not line:
- break
-
- # Use regex matching to extract the data and to ensure that we are
- # extracting it from a properly formatted line. There are rcsdiff
- # programs out there that don't supply the correct format; we'll be
- # flexible in case we run into one of those.
- if line[:4] == '--- ':
- match = _re_extract_rev.match(line)
- if match:
- date1 = match.group(1)
- log_rev1 = match.group(2)
- elif line[:4] == '+++ ':
- match = _re_extract_rev.match(line)
- if match:
- date2 = match.group(1)
- log_rev2 = match.group(2)
- break
-
- # Didn't want to put this here, but had to. The DiffSource class
- # picks up fp after this loop has processed the header. Previously
- # error messages and the 'Binary rev ? and ? differ' where thrown out
- # and DiffSource then showed no differences.
- # Need to process the entire header before DiffSource is used.
- if line[:3] == 'Bin':
- rcsdiff_eflag = _RCSDIFF_IS_BINARY
- break
-
- if (string.find(line, 'not found') != -1 or
- string.find(line, 'illegal option') != -1):
- rcsdiff_eflag = _RCSDIFF_ERROR
- break
-
- if (log_rev1 and log_rev1 != rev1) or (log_rev2 and log_rev2 != rev2):
- ### it would be nice to have an error.ezt for things like this
- print '<strong>ERROR:</strong> rcsdiff did not return the correct'
- print 'version number in its output.'
- print '(got "%s" / "%s", expected "%s" / "%s")' % \
- (log_rev1, log_rev2, rev1, rev2)
- print '<p>Aborting operation.'
- sys.exit(0)
-
- # Process any special lines in the header, or continue to
- # get the differences from DiffSource.
- if rcsdiff_eflag == _RCSDIFF_IS_BINARY:
- rcs_diff = [ (_item(type='binary-diff')) ]
- elif rcsdiff_eflag == _RCSDIFF_ERROR:
- rcs_diff = [ (_item(type='error')) ]
- else:
- rcs_diff = DiffSource(fp)
-
- data.update({
- 'where' : where,
- 'rev1' : rev1,
- 'rev2' : rev2,
- 'tag1' : sym1,
- 'tag2' : sym2,
- 'date1' : ', ' + rcsdiff_date_reformat(date1),
- 'date2' : ', ' + rcsdiff_date_reformat(date2),
- 'changes' : rcs_diff,
- 'diff_format' : query_dict.get('diff_format', cfg.options.diff_format),
- })
-
- params = request.query_dict.copy()
- params['diff_format'] = None
-
- url, params = request.get_link(params=params)
- data['diff_format_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['diff_format_hidden_values'] = prepare_hidden_values(params)
-
- generate_page(request, cfg.templates.diff, data)
-
-def spaced_html_text(text):
- text = string.expandtabs(string.rstrip(text))
-
- # in the code below, "\x01" will be our stand-in for "&". We don't want
- # to insert "&" because it would get escaped by htmlify(). Similarly,
- # we use "\x02" as a stand-in for "<br>"
-
- if cfg.options.hr_breakable > 1 and len(text) > cfg.options.hr_breakable:
- text = re.sub('(' + ('.' * cfg.options.hr_breakable) + ')',
- '\\1\x02',
- text)
- if cfg.options.hr_breakable:
- # make every other space "breakable"
- text = string.replace(text, ' ', ' \x01nbsp;')
- else:
- text = string.replace(text, ' ', '\x01nbsp;')
- text = htmlify(text)
- text = string.replace(text, '\x01', '&')
- text = string.replace(text, '\x02', '<font color=red>\</font><br>')
- return text
-
-class DiffSource:
- def __init__(self, fp):
- self.fp = fp
- self.save_line = None
-
- # keep track of where we are during an iteration
- self.idx = -1
- self.last = None
-
- # these will be set once we start reading
- self.left = None
- self.right = None
- self.state = 'no-changes'
- self.left_col = [ ]
- self.right_col = [ ]
-
- def __getitem__(self, idx):
- if idx == self.idx:
- return self.last
- if idx != self.idx + 1:
- raise DiffSequencingError()
-
- # keep calling _get_row until it gives us something. sometimes, it
- # doesn't return a row immediately because it is accumulating changes
- # when it is out of data, _get_row will raise IndexError
- while 1:
- item = self._get_row()
- if item:
- self.idx = idx
- self.last = item
- return item
-
- def _get_row(self):
- if self.state[:5] == 'flush':
- item = self._flush_row()
- if item:
- return item
- self.state = 'dump'
-
- if self.save_line:
- line = self.save_line
- self.save_line = None
- else:
- line = self.fp.readline()
-
- if not line:
- if self.state == 'no-changes':
- self.state = 'done'
- return _item(type='no-changes')
-
- # see if there are lines to flush
- if self.left_col or self.right_col:
- # move into the flushing state
- self.state = 'flush-' + self.state
- return None
-
- # nothing more to return
- raise IndexError
-
- if line[:2] == '@@':
- self.state = 'dump'
- self.left_col = [ ]
- self.right_col = [ ]
-
- match = _re_extract_info.match(line)
- return _item(type='header', line1=match.group(1), line2=match.group(2),
- extra=match.group(3))
-
- if line[0] == '\\':
- # \ No newline at end of file
-
- # move into the flushing state. note: it doesn't matter if we really
- # have data to flush or not; that will be figured out later
- self.state = 'flush-' + self.state
- return None
-
- diff_code = line[0]
- output = spaced_html_text(line[1:])
-
- if diff_code == '+':
- if self.state == 'dump':
- return _item(type='add', right=output)
-
- self.state = 'pre-change-add'
- self.right_col.append(output)
- return None
-
- if diff_code == '-':
- self.state = 'pre-change-remove'
- self.left_col.append(output)
- return None
-
- if self.left_col or self.right_col:
- # save the line for processing again later
- self.save_line = line
-
- # move into the flushing state
- self.state = 'flush-' + self.state
- return None
-
- return _item(type='context', left=output, right=output)
-
- def _flush_row(self):
- if not self.left_col and not self.right_col:
- # nothing more to flush
- return None
-
- if self.state == 'flush-pre-change-remove':
- return _item(type='remove', left=self.left_col.pop(0))
-
- # state == flush-pre-change-add
- item = _item(type='change', have_left=None, have_right=None)
- if self.left_col:
- item.have_left = 'yes'
- item.left = self.left_col.pop(0)
- if self.right_col:
- item.have_right = 'yes'
- item.right = self.right_col.pop(0)
- return item
-
-class DiffSequencingError(Exception):
- pass
-
-def view_diff(request):
- query_dict = request.query_dict
-
- rev1 = r1 = query_dict['r1']
- rev2 = r2 = query_dict['r2']
- p1 = query_dict.get('p1', request.where)
- p2 = query_dict.get('p2', request.where)
- sym1 = sym2 = None
-
- if r1 == 'text':
- rev1 = query_dict.get('tr1', None)
- if not rev1:
- raise debug.ViewcvsException('Missing revision from the diff '
- 'form text field', '400 Bad Request')
- else:
- idx = string.find(r1, ':')
- if idx == -1:
- rev1 = r1
- else:
- rev1 = r1[:idx]
- sym1 = r1[idx+1:]
-
- if r2 == 'text':
- rev2 = query_dict.get('tr2', None)
- if not rev2:
- raise debug.ViewcvsException('Missing revision from the diff '
- 'form text field', '400 Bad Request')
- sym2 = ''
- else:
- idx = string.find(r2, ':')
- if idx == -1:
- rev2 = r2
- else:
- rev2 = r2[:idx]
- sym2 = r2[idx+1:]
-
- try:
- if revcmp(rev1, rev2) > 0:
- rev1, rev2 = rev2, rev1
- sym1, sym2 = sym2, sym1
- p1, p2 = p2, p1
- except ValueError:
- raise debug.ViewcvsException('Invalid revision(s) passed to diff',
- '400 Bad Request')
-
- human_readable = 0
- unified = 0
- args = [ ]
-
- ### Note: these options only really work out where rcsdiff (used by
- ### CVS) and regular diff (used by SVN) overlap. If for some reason
- ### our use of the options for these starts to deviate too much,
- ### this code may a re-org to just do different things for different
- ### VC types.
-
- format = query_dict.get('diff_format', cfg.options.diff_format)
- if format == 'c':
- args.append('-c')
- elif format == 's':
- args.append('--side-by-side')
- args.append('--width=164')
- elif format == 'l':
- args.append('--unified=15')
- human_readable = 1
- unified = 1
- elif format == 'h':
- args.append('-u')
- human_readable = 1
- unified = 1
- elif format == 'u':
- args.append('-u')
- unified = 1
- else:
- raise debug.ViewcvsException('Diff format %s not understood'
- % format, '400 Bad Request')
-
- if human_readable:
- if cfg.options.hr_funout:
- args.append('-p')
- if cfg.options.hr_ignore_white:
- args.append('-w')
- if cfg.options.hr_ignore_keyword_subst and request.roottype == 'cvs':
- # -kk isn't a regular diff option. it exists only for rcsdiff
- # (as in "-ksubst") ,so 'svn' roottypes can't use it.
- args.append('-kk')
-
- file1 = None
- file2 = None
- if request.roottype == 'cvs':
- args[len(args):] = ['-r' + rev1, '-r' + rev2, request.full_name]
- fp = bincvs.rcs_popen(cfg.general, 'rcsdiff', args, 'rt')
- else:
- try:
- date1 = vclib.svn.date_from_rev(request.repos, int(rev1))
- date2 = vclib.svn.date_from_rev(request.repos, int(rev2))
- except vclib.InvalidRevision:
- raise debug.ViewcvsException('Invalid revision(s) passed to diff',
- '400 Bad Request')
-
- date1 = time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date1))
- date2 = time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date2))
- args.append("-L")
- args.append(p1 + "\t" + date1 + "\t" + rev1)
- args.append("-L")
- args.append(p2 + "\t" + date2 + "\t" + rev2)
-
- # Need to keep a reference to the FileDiff object around long
- # enough to use. It destroys its underlying temporary files when
- # the class is destroyed.
- diffobj = vclib.svn.do_diff(request.repos, p1, int(rev1),
- p2, int(rev2), args)
-
- fp = diffobj.get_pipe()
-
- if human_readable:
- human_readable_diff(request, fp, rev1, rev2, sym1, sym2)
- return
-
- request.server.header('text/plain')
-
- rootpath = request.repos.rootpath
- if unified:
- f1 = '--- ' + rootpath
- f2 = '+++ ' + rootpath
- else:
- f1 = '*** ' + rootpath
- f2 = '--- ' + rootpath
-
- while 1:
- line = fp.readline()
- if not line:
- break
-
- if line[:len(f1)] == f1:
- line = string.replace(line, rootpath + '/', '')
- if sym1:
- line = line[:-1] + ' %s\n' % sym1
- elif line[:len(f2)] == f2:
- line = string.replace(line, rootpath + '/', '')
- if sym2:
- line = line[:-1] + ' %s\n' % sym2
-
- print line[:-1]
-
-
-def generate_tarball_header(out, name, size=0, mode=None, mtime=0, uid=0, gid=0, typefrag=None, linkname='', uname='viewcvs', gname='viewcvs', devmajor=1, devminor=0, prefix=None, magic='ustar', version='', chksum=None):
- if not mode:
- if name[-1:] == '/':
- mode = 0755
- else:
- mode = 0644
-
- if not typefrag:
- if name[-1:] == '/':
- typefrag = '5' # directory
- else:
- typefrag = '0' # regular file
-
- if not prefix:
- prefix = ''
-
- block1 = struct.pack('100s 8s 8s 8s 12s 12s',
- name,
- '%07o' % mode,
- '%07o' % uid,
- '%07o' % gid,
- '%011o' % size,
- '%011o' % mtime)
-
- block2 = struct.pack('c 100s 6s 2s 32s 32s 8s 8s 155s',
- typefrag,
- linkname,
- magic,
- version,
- uname,
- gname,
- '%07o' % devmajor,
- '%07o' % devminor,
- prefix)
-
- if not chksum:
- dummy_chksum = ' '
- block = block1 + dummy_chksum + block2
- chksum = 0
- for i in range(len(block)):
- chksum = chksum + ord(block[i])
-
- block = block1 + struct.pack('8s', '%07o' % chksum) + block2
- block = block + '\0' * (512 - len(block))
-
- out.write(block)
-
-def generate_tarball_cvs(out, request, tar_top, rep_top, reldir, tag, stack=[]):
- if (rep_top == '' and 0 < len(reldir) and
- ((reldir[0] == 'CVSROOT' and cfg.options.hide_cvsroot)
- or cfg.is_forbidden(reldir[0]))):
- return
-
- rep_path = rep_top + reldir
- tar_dir = string.join(tar_top + reldir, '/') + '/'
-
- entries = request.repos.listdir(rep_path, tag)
-
- subdirs = [ ]
- for file in entries:
- if not file.verboten and file.kind == vclib.DIR:
- subdirs.append(file.name)
-
- stack.append(tar_dir)
-
- bincvs.get_logs(request.repos, rep_path, entries, tag)
-
- entries.sort(lambda a, b: cmp(a.name, b.name))
-
- for file in entries:
- if file.rev is None or file.state == 'dead':
- continue
-
- for dir in stack:
- generate_tarball_header(out, dir)
- del stack[0:]
-
- info = os.stat(file.path)
- mode = (info[stat.ST_MODE] & 0555) | 0200
-
- rev_flag = '-p' + file.rev
- fp = bincvs.rcs_popen(cfg.general, 'co', (rev_flag, file.path), 'rb', 0)
- contents = fp.read()
- status = fp.close()
-
- generate_tarball_header(out, tar_dir + file.name,
- len(contents), mode, file.date)
- out.write(contents)
- out.write('\0' * (511 - ((len(contents) + 511) % 512)))
-
- subdirs.sort()
- for subdir in subdirs:
- if subdir != 'Attic':
- generate_tarball_cvs(out, request, tar_top, rep_top,
- reldir + [subdir], tag, stack)
-
- if len(stack):
- del stack[-1:]
-
-def generate_tarball_svn(out, request, tar_top, rep_top, reldir, tag, stack=[]):
- rep_dir = string.join(rep_top + reldir, '/')
- tar_dir = string.join(tar_top + reldir, '/') + '/'
-
- entries = request.repos.listdir(rep_top + reldir)
-
- subdirs = []
- for entry in entries:
- if entry.kind == vclib.DIR:
- subdirs.append(entry.name)
-
- vclib.svn.get_logs(request.repos, rep_dir, entries)
-
- stack.append(tar_dir)
-
- for file in entries:
- if file.kind != vclib.FILE:
- continue
-
- for dir in stack:
- generate_tarball_header(out, dir)
- del stack[0:]
-
- mode = 0644
-
- fp = request.repos.openfile(rep_top + reldir + [file.name])[0]
-
- contents = ""
- while 1:
- chunk = fp.read(CHUNK_SIZE)
- if not chunk:
- break
- contents = contents + chunk
-
- status = fp.close()
-
- generate_tarball_header(out, tar_dir + file.name,
- len(contents), mode, file.date)
- out.write(contents)
- out.write('\0' * (511 - ((len(contents) + 511) % 512)))
-
- for subdir in subdirs:
- generate_tarball_svn(out, request, tar_top, rep_top,
- reldir + [subdir], tag, stack)
-
- if len(stack):
- del stack[-1:]
-
-def download_tarball(request):
- if not cfg.options.allow_tar:
- raise "tarball no allows"
-
- query_dict = request.query_dict
- rep_top = tar_top = request.path_parts
- tag = query_dict.get('only_with_tag')
-
- ### look for GZIP binary
-
- request.server.header('application/octet-stream')
- sys.stdout.flush()
- fp = popen.pipe_cmds([('gzip', '-c', '-n')])
-
- # Switch based on the repository root type.
- if request.roottype == 'cvs':
- generate_tarball_cvs(fp, request, tar_top, rep_top, [], tag)
- elif request.roottype == 'svn':
- generate_tarball_svn(fp, request, tar_top, rep_top, [], tag)
-
- fp.write('\0' * 1024)
- fp.close()
-
-def view_revision(request):
- data = common_template_data(request)
- data.update({
- 'roottype' : request.roottype,
- })
-
- if request.roottype == "cvs":
- raise ViewcvsException("Revision view not supported for CVS repositories "
- "at this time.", "400 Bad Request")
- else:
- view_revision_svn(request, data)
-
-def view_revision_svn(request, data):
- query_dict = request.query_dict
- date, author, msg, changes = vclib.svn.get_revision_info(request.repos)
- date_str = make_time_string(date)
- rev = request.repos.rev
-
- # add the hrefs, types, and prev info
- for change in changes:
- change.view_href = change.diff_href = change.type = None
- change.prev_path = change.prev_rev = None
- if change.pathtype is vclib.FILE:
- change.type = 'file'
- change.view_href = request.get_url(view_func=view_markup,
- where=change.filename,
- pathtype=change.pathtype,
- params={'rev' : str(rev)})
- if change.action == "copied" or change.action == "modified":
- change.prev_path = change.base_path
- change.prev_rev = change.base_rev
- change.diff_href = request.get_url(view_func=view_diff,
- where=change.filename,
- pathtype=change.pathtype,
- params={})
- elif change.pathtype is vclib.DIR:
- change.type = 'dir'
- change.view_href = request.get_url(view_func=view_directory,
- where=change.filename,
- pathtype=change.pathtype,
- params={'rev' : str(rev)})
-
- prev_rev_href = next_rev_href = None
- if rev > 0:
- prev_rev_href = request.get_url(view_func=view_revision,
- where=None,
- pathtype=None,
- params={'rev': str(rev - 1)})
- if rev < request.repos.youngest:
- next_rev_href = request.get_url(view_func=view_revision,
- where=None,
- pathtype=None,
- params={'rev': str(rev + 1)})
- if msg:
- msg = htmlify(msg)
- data.update({
- 'rev' : str(rev),
- 'author' : author,
- 'date_str' : date_str,
- 'log' : msg,
- 'ago' : html_time(request, date, 1),
- 'changes' : changes,
- 'jump_rev' : str(rev),
- 'prev_href' : prev_rev_href,
- 'next_href' : next_rev_href,
- })
-
- url, params = request.get_link(view_func=view_revision,
- where=None,
- pathtype=None,
- params={'rev': None})
- data['jump_rev_action'] = urllib.quote(url, _URL_SAFE_CHARS)
- data['jump_rev_hidden_values'] = prepare_hidden_values(params)
-
- request.server.header()
- generate_page(request, cfg.templates.revision, data)
-
-
-_views = {
- 'annotate': view_annotate,
- 'auto': view_auto,
- 'co': view_checkout,
- 'diff': view_diff,
- 'dir': view_directory,
- 'graph': view_cvsgraph,
- 'graphimg': view_cvsgraph_image,
- 'log': view_log,
- 'markup': view_markup,
- 'rev': view_revision,
- 'tar': download_tarball,
-}
-
-_view_codes = {}
-for code, view in _views.items():
- _view_codes[view] = code
-
-def list_roots(cfg):
- allroots = { }
- allroots.update(cfg.general.cvs_roots)
- allroots.update(cfg.general.svn_roots)
- return allroots
-
-def handle_config():
- debug.t_start('load-config')
- global cfg
- if cfg is None:
- cfg = config.Config()
- cfg.set_defaults()
-
- # load in configuration information from the config file
- pathname = CONF_PATHNAME or os.path.join(g_install_dir, 'viewcvs.conf')
- if sapi.server:
- cfg.load_config(pathname, sapi.server.getenv('HTTP_HOST'))
- else:
- cfg.load_config(pathname, None)
-
- # special handling for svn_parent_path. any subdirectories
- # present in the directory specified as the svn_parent_path that
- # have a child file named "format" will be treated as svn_roots.
- if cfg.general.svn_parent_path is not None:
- pp = cfg.general.svn_parent_path
- try:
- subpaths = os.listdir(pp)
- except OSError:
- raise debug.ViewcvsException(
- "The setting for 'svn_parent_path' does not refer to "
- "a valid directory.")
-
- for subpath in subpaths:
- if os.path.exists(os.path.join(pp, subpath)) \
- and os.path.exists(os.path.join(pp, subpath, "format")):
- cfg.general.svn_roots[subpath] = os.path.join(pp, subpath)
-
- debug.t_end('load-config')
-
-
-def view_error(server):
- exc_dict = debug.GetExceptionData()
- status = exc_dict['status']
- handled = 0
-
- # use the configured error template if possible
- try:
- if cfg:
- server.header(status=status)
- generate_page(None, cfg.templates.error, exc_dict)
- handled = 1
- except:
- # get new exception data, more important than the first
- exc_dict = debug.GetExceptionData()
-
- # but fallback to the old exception printer if no configuration is
- # available, or if something went wrong
- if not handled:
- debug.PrintException(server, exc_dict)
-
-def main(server):
- try:
- debug.t_start('main')
- try:
- # handle the configuration stuff
- handle_config()
-
- # build a Request object, which contains info about the HTTP request
- request = Request(server)
- request.run_viewcvs()
- except SystemExit, e:
- return
- except:
- view_error(server)
-
- finally:
- debug.t_end('main')
- debug.dump()
- debug.DumpChildren(server)
-
-
-class _item:
- def __init__(self, **kw):
- vars(self).update(kw)
-