1
0
mirror of https://github.com/vitalif/viewvc-4intranet synced 2019-04-16 04:14:59 +03:00

Compare commits

..

2 Commits
1.1.9 ... 1.1.3

Author SHA1 Message Date
cmpilato
79e6d32043 Retroactively "peg" the svn:externals property on these tags which
pulls in the 'templates-contrib' area.

git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.1.3@2451 8cb11bc2-c004-0410-86c3-e597b4017df7
2010-09-08 16:51:21 +00:00
cmpilato
a6c01c2f5b Tag the 1.1.3 final release.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.1.3@2311 8cb11bc2-c004-0410-86c3-e597b4017df7
2009-12-22 19:54:14 +00:00
41 changed files with 1002 additions and 2050 deletions

50
CHANGES
View File

@@ -1,53 +1,3 @@
Version 1.1.9 (released 18-Feb-2011)
* vcauth universal access determinations (issue #425)
* rework svn revision info cache for performance
* make revision log "extra pages" count configurable
* fix Subversion 1.4.x revision log compatibility code regression
* display sanitized error when authzfile is malformed
* handle file:/// Subversion rootpaths as local roots (issue #446)
* restore markup of URLs in file contents (issue #455)
* optionally display last-committed metadata in roots view (issue #457)
Version 1.1.8 (released 02-Dec-2010)
* fix slowness triggered by allow_compress=1 configuration (issue #467)
* allow use of 'fcrypt' for Windows standalone.py authn support (issue #471)
* yield more useful error on directory markup/annotate request (issue #472)
Version 1.1.7 (released 09-Sep-2010)
* display Subversion revision properties in the revision view (issue #453)
* fix exception in 'standalone.py -r REPOS' when run without a config file
* fix standalone.py server root deployments (--script-alias='')
* add Basic authentication support to standalone.py (Unix only) (issue #49)
* fix obscure "unexpected NULL parent pool" Subversion bindings error
* enable path info / link display in remote Subversion root revision view
* fix vhost name case handling inconsistency (issue #466)
* use svn:mime-type property charset param as encoding hint
* markup Subversion revision references in log messages (issue #313)
* add rudimentary support for FastCGI-based deployments (issue #464)
* fix query script WSGI deployment
* add configuration to fix query script cross-linking to ViewVC
Version 1.1.6 (released 02-Jun-2010)
* add rudimentary support for WSGI-based deployments (issue #397)
* fix exception caused by trying to HTML-escape non-string data (issue #454)
* fix incorrect RSS feed Content-Type header (issue #449)
* fix RSS <title> encoding problem (issue #451)
* allow 'svndbadmin purge' to work on missing repositories (issue #452)
Version 1.1.5 (released 29-Mar-2010)
* security fix: escape user-provided search_re input to avoid XSS attack
Version 1.1.4 (released 10-Mar-2010)
* security fix: escape user-provided query form input to avoid XSS attack
* fix standalone.py failure (when per-root options aren't used) (issue #445)
* fix annotate failure caused by ignored svn_config_dir (issue #447)
Version 1.1.3 (released 22-Dec-2009)
* security fix: add root listing support of per-root authz config

54
INSTALL
View File

@@ -19,7 +19,7 @@ Congratulations on getting this far. :-)
For CVS Support:
* Python 1.5.2 or later (sorry, no 3.x support yet)
* Python 1.5.2 or later
(http://www.python.org/)
* RCS, Revision Control System
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
@@ -30,7 +30,7 @@ Congratulations on getting this far. :-)
For Subversion Support:
* Python 2.0 or later (sorry, no 3.x support yet)
* Python 2.0 or later
(http://www.python.org/)
* Subversion, Version Control System, 1.3.1 or later
(binary installation and Python bindings)
@@ -213,9 +213,9 @@ APACHE CONFIGURATION
Options +ExecCGI
AddHandler cgi-script .cgi
Note: For this to work mod_cgi has to be loaded. And for the .htaccess file
(Note: For this to work mod_cgi has to be loaded. And for the .htaccess file
to be effective, "AllowOverride All" or "AllowOverride Options FileInfo"
needs to have been specified for the directory.
need to have been specified for the directory.)
------------------------------------------
METHOD D: Using mod_python (if installed)
@@ -232,52 +232,6 @@ APACHE CONFIGURATION
feature may not work because it uses multithreading. This works fine
under Apache 2.
----------------------------------------
METHOD E: Using mod_wsgi (if installed)
----------------------------------------
Copy the Python scripts file from
<VIEWVC_INSTALLATION_DIRECTORY>/bin/mod_python/
to the directory of your choosing. Modify httpd.conf with the
following directives:
WSGIScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/viewvc.wsgi
WSGIScriptAlias /query <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/query.wsgi
You'll probably also need the following directive because of the
not-quite-sanctioned way that ViewVC manipulates Python objects.
WSGIApplicationGroup %{GLOBAL}
Note: WSGI support in ViewVC is at this time quite rudimentary,
bordering on downright experimental. Your mileage may vary.
-----------------------------------------
METHOD F: Using mod_fcgid (if installed)
-----------------------------------------
This uses ViewVC's WSGI support (from above), but supports using FastCGI,
and is a somewhat hybrid approach of several of the above methods.
Especially if fcgi is already being used for other purposes, e.g. PHP,
also using fcgi can prevent the need for including additional modules
(e.g. mod_python or mod_wsgi) within Apache, which may help lessen Apache's
memory usage and/or help improve performance.
This depends on mod_fcgid:
http://httpd.apache.org/mod_fcgid/
as well as the fcgi server from Python's flup package:
http://pypi.python.org/pypi/flup
http://trac.saddi.com/flup
The following are some example httpd.conf fragments you can use to
support this configuration:
ScriptAlias /viewvc /usr/local/viewvc/bin/wsgi/viewvc.fcgi
ScriptAlias /query /usr/local/viewvc/bin/wsgi/query.fcgi
3) Restart Apache.
The commands to do this vary. "httpd -k restart" and "apache -k

View File

@@ -15,7 +15,7 @@
<blockquote>
<p><strong>Copyright &copy; 1999-2011 The ViewCVS Group. All rights
<p><strong>Copyright &copy; 1999-2009 The ViewCVS Group. All rights
reserved.</strong></p>
<p>By using ViewVC, you agree to the terms and conditions set forth
@@ -60,8 +60,6 @@
<li>April 10, 2007 &mdash; copyright years updated</li>
<li>February 22, 2008 &mdash; copyright years updated</li>
<li>March 18, 2009 &mdash; copyright years updated</li>
<li>March 29, 2010 &mdash; copyright years updated</li>
<li>February 18, 2011 &mdash; copyright years updated</li>
</ul>
</body>

View File

@@ -54,10 +54,7 @@ import query
server = sapi.AspServer(Server, Request, Response, Application)
try:
cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc_base_url = cfg.query.viewvc_base_url
if viewvc_base_url is None:
viewvc_base_url = "viewvc.asp"
query.main(server, cfg, viewvc_base_url)
query.main(server, cfg, "viewvc.asp")
finally:
s.close()

View File

@@ -54,7 +54,4 @@ import query
server = sapi.CgiServer()
cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc_base_url = cfg.query.viewvc_base_url
if viewvc_base_url is None:
viewvc_base_url = "viewvc.cgi"
query.main(server, cfg, viewvc_base_url)
query.main(server, cfg, "viewvc.cgi")

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2008 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 ViewVC
@@ -65,10 +65,7 @@ cfg = viewvc.load_config(CONF_PATHNAME)
def index(req):
server = sapi.ModPythonServer(req)
try:
viewvc_base_url = cfg.query.viewvc_base_url
if viewvc_base_url is None:
viewvc_base_url = "viewvc.py"
query.main(server, cfg, viewvc_base_url)
query.main(server, cfg, "viewvc.py")
finally:
server.close()

File diff suppressed because it is too large Load Diff

View File

@@ -242,7 +242,6 @@ def main(command, repository, revs=[], verbose=0, force=0):
cfg = viewvc.load_config(CONF_PATHNAME)
db = cvsdb.ConnectDatabase(cfg)
# Purge what must be purged.
if command in ('rebuild', 'purge'):
if verbose:
print "Purging commit info for repository root `%s'" % repository
@@ -253,24 +252,18 @@ def main(command, repository, revs=[], verbose=0, force=0):
sys.stderr.write("ERROR: " + str(e) + "\n")
sys.exit(1)
# Record what must be recorded.
if command in ('rebuild', 'update'):
if not os.path.exists(repository):
sys.stderr.write('ERROR: could not find repository %s\n'
% (repository))
sys.exit(1)
repo = SvnRepo(repository)
if command == 'rebuild' or (command == 'update' and not revs):
for rev in range(repo.rev_max+1):
handle_revision(db, command, repo, rev, verbose)
elif command == 'update':
if revs[0] is None:
revs[0] = repo.rev_max
if revs[1] is None:
revs[1] = repo.rev_max
revs.sort()
for rev in range(revs[0], revs[1]+1):
handle_revision(db, command, repo, rev, verbose, force)
repo = SvnRepo(repository)
if command == 'rebuild' or (command == 'update' and not revs):
for rev in range(repo.rev_max+1):
handle_revision(db, command, repo, rev, verbose)
elif command == 'update':
if revs[0] is None:
revs[0] = repo.rev_max
if revs[1] is None:
revs[1] = repo.rev_max
revs.sort()
for rev in range(revs[0], revs[1]+1):
handle_revision(db, command, repo, rev, verbose, force)
def _rev2int(r):
if r == 'HEAD':
@@ -337,6 +330,12 @@ if __name__ == '__main__':
sys.stderr.write('ERROR: unknown command %s\n' % command)
usage()
repository = args[2]
if not os.path.exists(repository):
sys.stderr.write('ERROR: could not find repository %s\n' % args[2])
usage()
repository = vclib.svn.canonicalize_rootpath(repository)
revs = []
if len(sys.argv) > 3:
if command == 'rebuild':
@@ -359,7 +358,6 @@ if __name__ == '__main__':
rev = None
try:
repository = vclib.svn.canonicalize_rootpath(args[2])
repository = cvsdb.CleanRepository(os.path.abspath(repository))
main(command, repository, revs, verbose, force)
except KeyboardInterrupt:

View File

@@ -1,54 +0,0 @@
#!/usr/bin/python
# -*-python-*-
#
# Copyright (C) 1999-2010 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 ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
#
# viewvc: View CVS/SVN repositories via a web browser
#
# -----------------------------------------------------------------------
#
# This is a fcgi entry point for the query ViewVC app. It's appropriate
# for use with mod_fcgid and flup. It defines a single application function
# that is a valid fcgi entry point.
#
# mod_fcgid: http://httpd.apache.org/mod_fcgid/
# flup:
# http://pypi.python.org/pypi/flup
# http://trac.saddi.com/flup
#
# -----------------------------------------------------------------------
import sys, os
LIBRARY_DIR = None
CONF_PATHNAME = None
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
"../../../lib")))
import sapi
import viewvc
import query
from flup.server import fcgi
def application(environ, start_response):
server = sapi.WsgiServer(environ, start_response)
cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc_base_url = cfg.query.viewvc_base_url
if viewvc_base_url is None:
viewvc_base_url = "viewvc.fcgi"
query.main(server, cfg, viewvc_base_url)
return []
fcgi.WSGIServer(application).run()

View File

@@ -1,45 +0,0 @@
# -*-python-*-
#
# Copyright (C) 1999-2010 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 ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
#
# viewvc: View CVS/SVN repositories via a web browser
#
# -----------------------------------------------------------------------
#
# This is a wsgi entry point for the query ViewVC app. It's appropriate
# for use with mod_wsgi. It defines a single application function that
# is a valid wsgi entry point.
#
# -----------------------------------------------------------------------
import sys, os
LIBRARY_DIR = None
CONF_PATHNAME = None
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
"../../../lib")))
import sapi
import viewvc
import query
def application(environ, start_response):
server = sapi.WsgiServer(environ, start_response)
cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc_base_url = cfg.query.viewvc_base_url
if viewvc_base_url is None:
viewvc_base_url = "viewvc.wsgi"
query.main(server, cfg, viewvc_base_url)
return []

View File

@@ -1,50 +0,0 @@
#!/usr/bin/python
# -*-python-*-
#
# Copyright (C) 1999-2010 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 ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
#
# viewvc: View CVS/SVN repositories via a web browser
#
# -----------------------------------------------------------------------
#
# This is a fcgi entry point for the main ViewVC app. It's appropriate
# for use with mod_fcgid and flup. It defines a single application function
# that is a valid fcgi entry point.
#
# mod_fcgid: http://httpd.apache.org/mod_fcgid/
# flup:
# http://pypi.python.org/pypi/flup
# http://trac.saddi.com/flup
#
# -----------------------------------------------------------------------
import sys, os
LIBRARY_DIR = None
CONF_PATHNAME = None
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
"../../../lib")))
import sapi
import viewvc
from flup.server import fcgi
def application(environ, start_response):
server = sapi.WsgiServer(environ, start_response)
cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc.main(server, cfg)
return []
fcgi.WSGIServer(application).run()

View File

@@ -1,41 +0,0 @@
# -*-python-*-
#
# Copyright (C) 1999-2009 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 ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
#
# viewvc: View CVS/SVN repositories via a web browser
#
# -----------------------------------------------------------------------
#
# This is a wsgi entry point for the main ViewVC app. It's appropriate
# for use with mod_wsgi. It defines a single application function that
# is a valid wsgi entry point.
#
# -----------------------------------------------------------------------
import sys, os
LIBRARY_DIR = None
CONF_PATHNAME = None
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
"../../../lib")))
import sapi
import viewvc
def application(environ, start_response):
server = sapi.WsgiServer(environ, start_response)
cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc.main(server, cfg)
return []

View File

@@ -106,47 +106,27 @@
##
#svn_roots =
## root_parents: Specifies a list of directories under which any
## number of repositories may reside. You can specify multiple root
## parents separated by commas or new lines, each of which is of the
## form "path: type" (where type is either "cvs" or "svn").
## root_parents: Specifies a list of directories in which any number of
## repositories may reside. Rather than force you to add a new entry
## to 'cvs_roots' or 'svn_roots' each time you create a new repository,
## ViewVC rewards you for organising all your repositories under a few
## parent directories by allowing you to simply specifiy just those
## parent directories. ViewVC will then notice each repository in that
## directory as a new root whose name is the subdirectory of the parent
## path in which that repository lives.
##
## Rather than force you to add a new entry to 'cvs_roots' or
## 'svn_roots' each time you create a new repository, ViewVC rewards
## you for organizing all your repositories under a few parent
## directories by allowing you to simply tell it about those parent
## directories. ViewVC will then notice each repository of the
## specified type in that directory as a root whose name is the
## subdirectory in which that repository lives.
## You can specify multiple parent paths separated by commas or new lines.
##
## For example, if you have three Subversion repositories located at
## /opt/svn/projects, /opt/svn/websites, and /opt/svn/devstuff, you
## could list them individually in svn_roots like so:
##
## svn_roots = projects: /opt/svn/projects,
## websites: /opt/svn/websites,
## devstuff: /opt/svn/devstuff
##
## or you could instead use the root_parents configuration option:
##
## root_parents = /opt/svn: svn
##
## The benefit of this latter approach is that, as you add new
## repositories to your /opt/svn directory, they automatically become
## available for display in ViewVC without additional configuration.
##
## WARNING: the root names derived for repositories configured via the
## root_parents option can, of course, clash with names you have
## defined in your cvs_roots or svn_roots configuration items. If
## this occurs, you can either rename the offending repository on
## disk, or grant new names to the clashing item in cvs_roots or
## svn_roots. Each parent path is processed sequentially, so the
## names of repositories under later parent paths may override earlier
## ones.
## WARNING: these names can, of course, clash with names you have
## defined in your cvs_roots or svn_roots configuration items. If this
## occurs, you can either rename the offending repository on disk, or
## grant new names to the clashing item in cvs_roots or svn_roots.
## Each parent path is processed sequentially, so repositories under
## later parent paths may override earlier ones.
##
## Example:
## root_parents = /opt/svn: svn,
## /opt/cvs: cvs
## root_parents = /opt/svn : svn,
## /opt/cvs : cvs
##
#root_parents =
@@ -572,14 +552,6 @@
##
#show_subdir_lastmod = 0
## show_roots_lastmod: In the root listing view, show the most recent
## modifications made to the root. (Subversion roots only.)
##
## NOTE: Enabling this feature will significantly reduce the
## performance of the root listing view.
##
#show_roots_lastmod = 0
## show_logs: Show the most recent log entry in directory listings.
##
#show_logs = 1
@@ -657,26 +629,6 @@
##
#log_pagesize = 0
## log_pagesextra: Maximum number of extra pages (based on
## log_pagesize) of revision log data to fetch and present to the user
## as additional options for display. Revision log information
## "beyond" this window is still accessible, but must be navigated to
## in multiple steps.
##
## Example:
## log_pagesize = 100
## log_pagesextra = 3
##
## For a versioned file with 1000 revisions, the above settings would
## present to the user the first 100 of those 1000 revisions, with
## links to three additional pages (the 200-299th revisions, 300-399th
## revisions, and 400-499th revisions) plus a link to the 500th
## revision. Following these links slides the display "window",
## showing the requested set of revisions plus links to three
## additional pages beyond those, and so on.
##
#log_pagesextra = 3
## limit_changes: Maximum number of changed paths shown per commit in
## the Subversion revision view and in query results. This is not a
## hard limit (the UI provides options to show all changed paths), but
@@ -906,10 +858,6 @@
## utilities
## authz-*
##
## WARNING: Do not use per-root overrides if your ViewVC instance is
## served via the standalone.py server option! Doing so could cause
## ViewVC to be unable to function properly (or at all).
##
## Here is an example showing how to enable Subversion authz-based
## authorization for only the single root named "svnroot":
##
@@ -924,15 +872,8 @@
[authz-forbidden]
## The "forbidden" authorizer forbids access to repository modules,
## defined to be top-level subdirectories in a repository.
##
## NOTE: The options in this section apply only when the 'authorizer'
## option (in the [options] section) is set to 'forbidden'.
## forbidden: A comma-delimited list of patterns which match modules
## that ViewVC should hide from users.
##
## You can use a simple list of modules, or something more complex:
## defined to be top-level subdirectories in a repository. You can use
## a simple list of modules, or something more complex:
##
## *) The "!" can be used before a module to explicitly state that it
## is NOT forbidden. Whenever this form is seen, then all modules will
@@ -987,12 +928,6 @@
## regular expressions. Directory paths will be terminated by a forward
## slash.
##
## NOTE: The options in this section apply only when the 'authorizer'
## option (in the [options] section) is set to 'forbiddenre'.
## forbiddenre: A comma-delimited list of regular expressions which
## match paths that ViewVC should hide from users.
##
## Like the "forbidden" authorizer...
##
## *) The "!" can be used before a module to explicitly state that it
@@ -1027,9 +962,6 @@
## The "svnauthz" authorizer uses a Subversion authz configuration file
## to determine access to repository paths.
##
## NOTE: The options in this section apply only when the 'authorizer'
## option (in the [options] section) is set to 'svnauthz'.
## authzfile: Specifies the location of the authorization rules file
## (using an absolute path).
@@ -1045,30 +977,3 @@
#force_username_case =
##---------------------------------------------------------------------------
[query]
## The configuration items in this section are used exclusively by the
## 'query' script, a separate script from ViewVC itself that ships
## with ViewVC and allows for queries into the ViewVC commits
## database. If you aren't using this separate script (which was made
## largely irrelevant by the introduction of an integrated "query"
## view in ViewVC itself, or aren't using the ViewVC commits database
## functionality at all, you can ignore these configurations items
## altogether.
## viewvc_base_url: Base URL at which ViewVC may be accessed on this
## server. The default value for this option is determined at
## run-time by the various front-ends to the query script.
##
## Examples:
## viewvc_base_url = /viewvc.py
## viewvc_base_url = /viewvc.wsgi
## viewvc_base_url = /cgi-bin/viewvc
## viewvc_base_url = viewvc
##
## To disable cross-linking between the query script and ViewVC,
## uncomment this option and leave its value empty.
##
#viewvc_base_url =
##---------------------------------------------------------------------------

View File

@@ -2144,32 +2144,6 @@ td {
<td>List</td>
<td>Set of configured viewable repositories.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.ago</td>
<td>String</td>
<td>Textual description of the time since <var>roots.date</var>.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.author</td>
<td>String</td>
<td>Username of the last modifier of the root.</td>
</tr>
<tr class="varlevel2">
<td class="varname">root.date</td>
<td>String</td>
<td>Date (in UTC if not otherwise configured) of the last
modification of the root.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.href</td>
<td>String</td>
<td>URL of root directory view for a configured repository.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.log</td>
<td>String</td>
<td>Log message of last modification to the root.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.name</td>
<td>String</td>
@@ -2183,24 +2157,17 @@ td {
configuration can have negative security implications. Use this
token at your own risk.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.rev</td>
<td>String</td>
<td>Youngest revision of the root.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.short_log</td>
<td>String</td>
<td>Log message of last modification to the root, truncated to
contain no more than the number of characters specified by
the <code>short_log_len</code> configuration option.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.type</td>
<td>String</td>
<td>Version control type of a configured repository. Valid
values: <tt>cvs</tt>, <tt>svn</tt>.</td>
</tr>
<tr class="varlevel2">
<td class="varname">roots.href</td>
<td>String</td>
<td>URL of root directory view for a configured repository.</td>
</tr>
</tbody>
</table>

View File

@@ -348,7 +348,7 @@ td {
all = viewvc.*
[all-options]
allowed_views = annotate, diff, markup, tar
allow_tar = 1
</pre>
</blockquote>
@@ -358,7 +358,7 @@ allowed_views = annotate, diff, markup, tar
all = viewvc.*
[vhost-all/options]
allowed_views = annotate, diff, markup, tar
allow_tar = 1
</pre>
</blockquote>

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*-python-*-
#
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
#
# By using this file, you agree to the terms and conditions set forth in
@@ -32,8 +32,9 @@ import os
import re
import time
import math
import cgi
import vclib
import sapi
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
@@ -81,7 +82,7 @@ class HTMLBlameSource:
diff_url = None
if item.prev_rev:
diff_url = '%sr1=%s&amp;r2=%s' % (self.diff_url, item.prev_rev, item.rev)
thisline = link_includes(sapi.escape(item.text), self.repos,
thisline = link_includes(cgi.escape(item.text), self.repos,
self.path_parts, self.include_url)
return _item(text=thisline, line_number=item.line_number,
rev=item.rev, prev_rev=item.prev_rev,

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2009 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 ViewVC
@@ -88,11 +88,6 @@ import fnmatch
# | vhosts |
# | |
# `-----------'
# ,-----------.
# | |
# | query |
# | |
# `-----------'
#
# ### TODO: Figure out what this all means for the 'kv' stuff.
#
@@ -105,7 +100,6 @@ class Config:
'cvsdb',
'general',
'options',
'query',
'templates',
'utilities',
)
@@ -151,11 +145,11 @@ class Config:
self.conf_path = os.path.isfile(pathname) and pathname or None
self.base = os.path.dirname(pathname)
self.parser = ConfigParser.ConfigParser()
self.parser.optionxform = lambda x: x # don't case-normalize option names.
self.parser.read(self.conf_path or [])
for section in self.parser.sections():
if self._is_allowed_section(section, self._base_sections):
if self._is_allowed_section(self.parser, section,
self._base_sections):
self._process_section(self.parser, section, section)
if vhost and self.parser.has_section('vhosts'):
@@ -178,7 +172,6 @@ class Config:
fname = string.replace(fname, '%lang%', language)
parser = ConfigParser.ConfigParser()
parser.optionxform = lambda x: x # don't case-normalize option names.
parser.read(os.path.join(self.base, fname))
for section in parser.sections():
for option in parser.options(section):
@@ -221,7 +214,7 @@ class Config:
setattr(sc, opt, value)
def _is_allowed_section(self, section, allowed_sections):
def _is_allowed_section(self, parser, section, allowed_sections):
"""Return 1 iff SECTION is an allowed section, defined as being
explicitly present in the ALLOWED_SECTIONS list or present in the
form 'someprefix-*' in that list."""
@@ -234,7 +227,7 @@ class Config:
return 1
return 0
def _is_allowed_override(self, sectype, secspec, section):
def _is_allowed_override(self, parser, sectype, secspec, section):
"""Test if SECTION is an allowed override section for sections of
type SECTYPE ('vhosts' or 'root', currently) and type-specifier
SECSPEC (a rootname or vhostname, currently). If it is, return
@@ -247,7 +240,7 @@ class Config:
if section[:lcv] != cv:
return None
base_section = section[lcv:]
if self._is_allowed_section(base_section,
if self._is_allowed_section(parser, base_section,
self._allowed_overrides[sectype]):
return base_section
raise IllegalOverrideSection(sectype, section)
@@ -260,7 +253,8 @@ class Config:
# Overlay any option sections associated with this vhost name.
for section in parser.sections():
base_section = self._is_allowed_override('vhost', canon_vhost, section)
base_section = self._is_allowed_override(parser, 'vhost',
canon_vhost, section)
if base_section:
self._process_section(parser, section, base_section)
@@ -280,25 +274,18 @@ class Config:
"""Overlay per-root options for ROOTNAME atop the existing option
set. This is a destructive change to the configuration."""
did_overlay = 0
# We can only deal with this happening once!
assert(self.root_options_overlayed == 0)
self.root_options_overlayed = 1
if not self.conf_path:
return
for section in self.parser.sections():
base_section = self._is_allowed_override('root', rootname, section)
base_section = self._is_allowed_override(self.parser, 'root',
rootname, section)
if base_section:
# We can currently only deal with root overlays happening
# once, so check that we've not yet done any overlaying of
# per-root options.
assert(self.root_options_overlayed == 0)
self._process_section(self.parser, section, base_section)
did_overlay = 1
# If we actually did any overlaying, remember this fact so we
# don't do it again later.
if did_overlay:
self.root_options_overlayed = 1
def _get_parser_items(self, parser, section):
"""Basically implement ConfigParser.items() for pre-Python-2.3 versions."""
@@ -327,7 +314,7 @@ class Config:
assert(self.root_options_overlayed == 0)
if not self.conf_path:
return None, {}
return None
# Figure out the authorizer by searching first for a per-root
# override, then falling back to the base/vhost configuration.
@@ -420,7 +407,6 @@ class Config:
self.options.template_dir = "templates"
self.options.docroot = None
self.options.show_subdir_lastmod = 0
self.options.show_roots_lastmod = 0
self.options.show_logs = 1
self.options.show_log_in_markup = 1
self.options.cross_copies = 1
@@ -434,7 +420,6 @@ class Config:
self.options.use_re_search = 0
self.options.dir_pagesize = 0
self.options.log_pagesize = 0
self.options.log_pagesextra = 3
self.options.limit_changes = 100
self.templates.diff = None
@@ -460,8 +445,6 @@ class Config:
self.cvsdb.rss_row_limit = 100
self.cvsdb.check_database_for_root = 0
self.query.viewvc_base_url = None
def _startswith(somestr, substr):
return somestr[:len(substr)] == substr

View File

@@ -347,7 +347,7 @@ class Template:
for_names = [ ]
if base_format:
program.append((self._cmd_format, _formatters[base_format]))
program.append((self._cmd_format, _printers[base_format]))
for i in range(len(parts)):
piece = parts[i]
@@ -405,13 +405,13 @@ class Template:
elif cmd == 'format':
if args[1][0]:
# argument is a variable reference
formatter = args[1]
printer = args[1]
else:
# argument is a string constant referring to built-in formatter
formatter = _formatters.get(args[1][1])
if not formatter:
# argument is a string constant referring to built-in printer
printer = _printers.get(args[1][1])
if not printer:
raise UnknownFormatConstantError(str(args[1:]))
program.append((self._cmd_format, formatter))
program.append((self._cmd_format, printer))
# remember the cmd, current pos, args, and a section placeholder
stack.append([cmd, len(program), args[1:], None])
@@ -465,13 +465,13 @@ class Template:
except TypeError:
raise Exception("Unprintable value type for '%s'" % (str(valrefs[0][0])))
def _cmd_format(self, formatter, ctx):
if type(formatter) is TupleType:
formatter = _get_value(formatter, ctx)
ctx.formatters.append(formatter)
def _cmd_format(self, printer, ctx):
if type(printer) is TupleType:
printer = _get_value(printer, ctx)
ctx.printers.append(printer)
def _cmd_end_format(self, valref, ctx):
ctx.formatters.pop()
ctx.printers.pop()
def _cmd_include(self, (valref, reader), ctx):
fname = _get_value(valref, ctx)
@@ -637,23 +637,14 @@ def _get_value((refname, start, rest), ctx):
# string or a sequence
return ob
def _print_formatted(formatters, ctx, chunk):
# print chunk to ctx.fp after running it sequentially through formatters
for formatter in formatters:
chunk = formatter(chunk)
ctx.fp.write(chunk)
def _write_value(value, args, ctx):
# value is a callback function, generates its own output
if callable(value):
apply(value, [ctx] + list(args))
return
# squirrel away formatters in case one of them recursively calls
# _write_value() -- we'll use them (in reverse order) to format our
# output.
formatters = ctx.formatters[:]
formatters.reverse()
# pop printer in case it recursively calls _write_value
printer = ctx.printers.pop()
try:
# if the value has a 'read' attribute, then it is a stream: copy it
@@ -662,7 +653,7 @@ def _write_value(value, args, ctx):
chunk = value.read(16384)
if not chunk:
break
_print_formatted(formatters, ctx, chunk)
printer(ctx, chunk)
# value is a substitution pattern
elif args:
@@ -675,16 +666,14 @@ def _write_value(value, args, ctx):
piece = args[idx]
else:
piece = '<undef>'
_print_formatted(formatters, ctx, piece)
printer(ctx, piece)
# plain old value, write to output
else:
_print_formatted(formatters, ctx, value)
printer(ctx, value)
finally:
# restore our formatters
formatters.reverse()
ctx.formatters = formatters
ctx.printers.append(printer)
class TemplateData:
@@ -726,7 +715,7 @@ class Context:
"""A container for the execution context"""
def __init__(self, fp):
self.fp = fp
self.formatters = []
self.printers = []
def write(self, value, args=()):
_write_value(value, args, self)
@@ -839,26 +828,20 @@ class BaseUnavailableError(EZTException):
class UnknownFormatConstantError(EZTException):
"""The format specifier is an unknown value."""
def _raw_formatter(s):
return s
def _raw_printer(ctx, s):
ctx.fp.write(s)
def _html_printer(ctx, s):
ctx.fp.write(cgi.escape(s))
def _html_formatter(s):
return cgi.escape(s)
def _uri_printer(ctx, s):
ctx.fp.write(urllib.quote(s))
def _xml_formatter(s):
s = s.replace('&', '&#x26;')
s = s.replace('<', '&#x3C;')
s = s.replace('>', '&#x3E;')
return s
def _uri_formatter(s):
return urllib.quote(s)
_formatters = {
FORMAT_RAW : _raw_formatter,
FORMAT_HTML : _html_formatter,
FORMAT_XML : _xml_formatter,
FORMAT_URI : _uri_formatter,
_printers = {
FORMAT_RAW : _raw_printer,
FORMAT_HTML : _html_printer,
FORMAT_XML : _html_printer,
FORMAT_URI : _uri_printer,
}
# --- standard test environment ---

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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 ViewVC
@@ -20,7 +20,7 @@ import difflib
import sys
import re
import ezt
import sapi
import cgi
def sidebyside(fromlines, tolines, context):
"""Generate side by side diff"""
@@ -49,18 +49,18 @@ def _mdiff_split(flag, (line_number, text)):
while True:
m = _re_mdiff.search(text, pos)
if not m:
segments.append(_item(text=sapi.escape(text[pos:]), type=None))
segments.append(_item(text=cgi.escape(text[pos:]), type=None))
break
if m.start() > pos:
segments.append(_item(text=sapi.escape(text[pos:m.start()]), type=None))
segments.append(_item(text=cgi.escape(text[pos:m.start()]), type=None))
if m.group(1) == "+":
segments.append(_item(text=sapi.escape(m.group(2)), type="add"))
segments.append(_item(text=cgi.escape(m.group(2)), type="add"))
elif m.group(1) == "-":
segments.append(_item(text=sapi.escape(m.group(2)), type="remove"))
segments.append(_item(text=cgi.escape(m.group(2)), type="remove"))
elif m.group(1) == "^":
segments.append(_item(text=sapi.escape(m.group(2)), type="change"))
segments.append(_item(text=cgi.escape(m.group(2)), type="change"))
pos = m.end()
@@ -166,12 +166,12 @@ def _differ_split(row, guide):
for m in _re_differ.finditer(guide, pos):
if m.start() > pos:
segments.append(_item(text=sapi.escape(line[pos:m.start()]), type=None))
segments.append(_item(text=sapi.escape(line[m.start():m.end()]),
segments.append(_item(text=cgi.escape(line[pos:m.start()]), type=None))
segments.append(_item(text=cgi.escape(line[m.start():m.end()]),
type="change"))
pos = m.end()
segments.append(_item(text=sapi.escape(line[pos:]), type=None))
segments.append(_item(text=cgi.escape(line[pos:]), type=None))
return _item(gap=ezt.boolean(gap), type=type, segments=segments,
left_number=left_number, right_number=right_number)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*-python-*-
#
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2009 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 ViewVC
@@ -312,7 +312,11 @@ def is_forbidden(cfg, cvsroot_name, module):
def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
ob = _item(num_files=len(files), files=[])
ob.log = desc and string.replace(server.escape(desc), '\n', '<br />') or ''
if desc:
ob.log = string.replace(server.escape(desc), '\n', '<br />')
else:
ob.log = '&nbsp;'
for commit in files:
repository = commit.GetRepository()
@@ -346,10 +350,9 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
except:
raise Exception, str([directory, commit.GetFile()])
## If we couldn't find the cvsroot path configured in the
## viewvc.conf file, or we don't have a VIEWVC_LINK, then
## don't make the link.
if cvsroot_name and viewvc_link:
## if we couldn't find the cvsroot path configured in the
## viewvc.conf file, then don't make the link
if cvsroot_name:
flink = '[%s] <a href="%s/%s?root=%s">%s</a>' % (
cvsroot_name, viewvc_link, urllib.quote(file),
cvsroot_name, file)
@@ -432,22 +435,22 @@ def main(server, cfg, viewvc_link):
commits = [ ]
query = 'skipped'
docroot = cfg.options.docroot
if docroot is None and viewvc_link:
docroot = viewvc_link + '/' + viewvc.docroot_magic_path
data = ezt.TemplateData({
'cfg' : cfg,
'address' : cfg.general.address,
'vsn' : viewvc.__version__,
'repository' : server.escape(form_data.repository),
'branch' : server.escape(form_data.branch),
'directory' : server.escape(form_data.directory),
'file' : server.escape(form_data.file),
'who' : server.escape(form_data.who),
'docroot' : docroot,
'repository' : server.escape(form_data.repository, 1),
'branch' : server.escape(form_data.branch, 1),
'directory' : server.escape(form_data.directory, 1),
'file' : server.escape(form_data.file, 1),
'who' : server.escape(form_data.who, 1),
'docroot' : cfg.options.docroot is None \
and viewvc_link + '/' + viewvc.docroot_magic_path \
or cfg.options.docroot,
'sortby' : form_data.sortby,
'date' : form_data.date,
'query' : query,
'commits' : commits,
'num_commits' : len(commits),

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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 ViewVC
@@ -20,27 +20,13 @@ import string
import os
import sys
import re
import cgi
# global server object. It will be either a CgiServer, a WsgiServer,
# or a proxy to an AspServer or ModPythonServer object.
# global server object. It will be either a CgiServer or a proxy to
# an AspServer or ModPythonServer object.
server = None
# Simple HTML string escaping. Note that we always escape the
# double-quote character -- ViewVC shouldn't ever need to preserve
# that character as-is, and sometimes needs to embed escaped values
# into HTML attributes.
def escape(s):
s = str(s)
s = string.replace(s, '&', '&amp;')
s = string.replace(s, '>', '&gt;')
s = string.replace(s, '<', '&lt;')
s = string.replace(s, '"', "&quot;")
return s
class Server:
def __init__(self):
self.pageGlobals = {}
@@ -48,9 +34,6 @@ class Server:
def self(self):
return self
def escape(self, s):
return escape(s)
def close(self):
pass
@@ -146,6 +129,9 @@ class CgiServer(Server):
global server
server = self
global cgi
import cgi
def addheader(self, name, value):
self.headers.append((name, value))
@@ -174,6 +160,9 @@ class CgiServer(Server):
self.header(status='301 Moved')
sys.stdout.write('This document is located <a href="%s">here</a>.\n' % url)
def escape(self, s, quote = None):
return cgi.escape(s, quote)
def getenv(self, name, value=None):
ret = os.environ.get(name, value)
if self.iis and name == 'PATH_INFO' and ret:
@@ -186,7 +175,7 @@ class CgiServer(Server):
def FieldStorage(fp=None, headers=None, outerboundary="",
environ=os.environ, keep_blank_values=0, strict_parsing=0):
return cgi.FieldStorage(fp, headers, outerboundary, environ,
keep_blank_values, strict_parsing)
keep_blank_values, strict_parsing)
def write(self, s):
sys.stdout.write(s)
@@ -198,66 +187,6 @@ class CgiServer(Server):
return sys.stdout
class WsgiServer(Server):
def __init__(self, environ, start_response):
Server.__init__(self)
self._environ = environ
self._start_response = start_response;
self._headers = []
self._wsgi_write = None
self.headerSent = False
global server
server = self
global cgi
import cgi
def addheader(self, name, value):
self._headers.append((name, value))
def header(self, content_type='text/html; charset=UTF-8', status=None):
if not status:
status = "200 OK"
if not self.headerSent:
self.headerSent = True
self._headers.insert(0, ("Content-Type", content_type),)
self._wsgi_write = self._start_response("%s" % status, self._headers)
def redirect(self, url):
"""Redirect client to url. This discards any data that has been queued
to be sent to the user. But there should never by any anyway.
"""
self.addheader('Location', url)
self.header(status='301 Moved')
self._wsgi_write('This document is located <a href="%s">here</a>.' % url)
def escape(self, s, quote = None):
return cgi.escape(s, quote)
def getenv(self, name, value=None):
return self._environ.get(name, value)
def params(self):
return cgi.parse(environ=self._environ, fp=self._environ["wsgi.input"])
def FieldStorage(self, fp=None, headers=None, outerboundary="",
environ=os.environ, keep_blank_values=0, strict_parsing=0):
return cgi.FieldStorage(self._environ["wsgi.input"], headers,
outerboundary, self._environ, keep_blank_values,
strict_parsing)
def write(self, s):
self._wsgi_write(s)
def flush(self):
pass
def file(self):
return File(self)
class AspServer(ThreadedServer):
def __init__(self, Server, Request, Response, Application):
ThreadedServer.__init__(self)
@@ -290,6 +219,9 @@ class AspServer(ThreadedServer):
def redirect(self, url):
self.response.Redirect(url)
def escape(self, s, quote = None):
return self.server.HTMLEncode(str(s))
def getenv(self, name, value = None):
ret = self.request.ServerVariables(name)()
if not type(ret) is types.UnicodeType:
@@ -351,6 +283,9 @@ class ModPythonServer(ThreadedServer):
self.request = request
self.headerSent = 0
global cgi
import cgi
def addheader(self, name, value):
self.request.headers_out.add(name, value)
@@ -373,6 +308,9 @@ class ModPythonServer(ThreadedServer):
self.request.write("You are being redirected to <a href=\"%s\">%s</a>"
% (url, url))
def escape(self, s, quote = None):
return cgi.escape(s, quote)
def getenv(self, name, value = None):
try:
return self.request.subprocess_env[name]

View File

@@ -29,15 +29,7 @@ class GenericViewVCAuthorizer:
def check_root_access(self, rootname):
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
pass
def check_universal_access(self, rootname):
"""Return 1 if the associated username is permitted to read every
path in the repository at every revision, 0 if the associated
username is prohibited from reading any path in the repository, or
None if no such determination can be made (perhaps because the
cost of making it is too great)."""
pass
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
"""Return 1 iff the associated username is permitted to read
revision REV of the path PATH_PARTS (of type PATHTYPE) in
@@ -52,9 +44,6 @@ class ViewVCAuthorizer(GenericViewVCAuthorizer):
"""The uber-permissive authorizer."""
def check_root_access(self, rootname):
return 1
def check_universal_access(self, rootname):
return 1
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
return 1

View File

@@ -23,14 +23,7 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
def check_root_access(self, rootname):
return 1
def check_universal_access(self, rootname):
# If there aren't any forbidden paths, we can grant universal read
# access. Otherwise, we make no claim.
if not self.forbidden:
return 1
return None
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
# No path? No problem.
if not path_parts:

View File

@@ -46,13 +46,6 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
def check_root_access(self, rootname):
return self._check_root_path_access(rootname)
def check_universal_access(self, rootname):
# If there aren't any forbidden regexps, we can grant universal
# read access. Otherwise, we make no claim.
if not self.forbidden:
return 1
return None
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
root_path = rootname
if path_parts:

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 2006-2011 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2006-2008 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 ViewVC
@@ -54,10 +54,7 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
# option names.
cp = ConfigParser()
cp.optionxform = lambda x: x
try:
cp.read(self.authz_file)
except:
raise debug.ViewVCException("Unable to parse configured authzfile file")
cp.read(self.authz_file)
# Figure out if there are any aliases for the current username
aliases = []
@@ -224,36 +221,6 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
paths = self._get_paths_for_root(rootname)
return (paths is not None) and 1 or 0
def check_universal_access(self, rootname):
paths = self._get_paths_for_root(rootname)
if not paths: # None or empty.
return 0
# Search the access determinations. If there's a mix, we can't
# claim a universal access determination.
found_allow = 0
found_deny = 0
for access in paths.values():
if access:
found_allow = 1
else:
found_deny = 1
if found_allow and found_deny:
return None
# We didn't find both allowances and denials, so we must have
# found one or the other. Denials only is a universal denial.
if found_deny:
return 0
# ... but allowances only is only a universal allowance if read
# access is granted to the root directory.
if found_allow and paths.has_key('/'):
return 1
# Anything else is indeterminable.
return None
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
# Crawl upward from the path represented by PATH_PARTS toward to
# the root of the repository, looking for an explicitly grant or

View File

@@ -180,10 +180,8 @@ class Repository:
rev is the revision of the item to return information about
Return value is a 5-tuple containing: the date, author, log
message, a list of ChangedPath items representing paths changed,
and a dictionary mapping property names to property values for
properties stored on an item.
Return value is a 4-tuple containing the date, author, log
message, and a list of ChangedPath items representing paths changed
Raise vclib.UnsupportedFeature if the version control system
doesn't support a global revision concept.

View File

@@ -40,11 +40,6 @@ class BaseCVSRepository(vclib.Repository):
if not vclib.check_root_access(self):
raise vclib.ReposNotFound(name)
def open(self):
# See if a universal read access determination can be made.
if self.auth and self.auth.check_universal_access(self.name) == 1:
self.auth = None
def rootname(self):
return self.name

View File

@@ -19,11 +19,7 @@ import string
import common
class _TokenStream:
token_term = string.whitespace + ";:"
try:
token_term = frozenset(token_term)
except NameError:
pass
token_term = string.whitespace + ';:'
# the algorithm is about the same speed for any CHUNK_SIZE chosen.
# grab a good-sized chunk, but not too large to overwhelm memory.
@@ -48,17 +44,15 @@ class _TokenStream:
# out more complex solutions.
buf = self.buf
lbuf = len(buf)
idx = self.idx
while 1:
if idx == lbuf:
if idx == len(buf):
buf = self.rcsfile.read(self.CHUNK_SIZE)
if buf == '':
# signal EOF by returning None as the token
del self.buf # so we fail if get() is called again
return None
lbuf = len(buf)
idx = 0
if buf[idx] not in string.whitespace:
@@ -66,7 +60,7 @@ class _TokenStream:
idx = idx + 1
if buf[idx] in ';:':
if buf[idx] == ';' or buf[idx] == ':':
self.buf = buf
self.idx = idx + 1
return buf[idx]
@@ -76,18 +70,17 @@ class _TokenStream:
token = ''
while 1:
# find token characters in the current buffer
while end < lbuf and buf[end] not in self.token_term:
while end < len(buf) and buf[end] not in self.token_term:
end = end + 1
token = token + buf[idx:end]
if end < lbuf:
if end < len(buf):
# we stopped before the end, so we have a full token
idx = end
break
# we stopped at the end of the buffer, so we may have a partial token
buf = self.rcsfile.read(self.CHUNK_SIZE)
lbuf = len(buf)
idx = end = 0
self.buf = buf
@@ -101,24 +94,22 @@ class _TokenStream:
chunks = [ ]
while 1:
if idx == lbuf:
if idx == len(buf):
idx = 0
buf = self.rcsfile.read(self.CHUNK_SIZE)
if buf == '':
raise RuntimeError, 'EOF'
lbuf = len(buf)
i = string.find(buf, '@', idx)
if i == -1:
chunks.append(buf[idx:])
idx = lbuf
idx = len(buf)
continue
if i == lbuf - 1:
if i == len(buf) - 1:
chunks.append(buf[idx:i])
idx = 0
buf = '@' + self.rcsfile.read(self.CHUNK_SIZE)
if buf == '@':
raise RuntimeError, 'EOF'
lbuf = len(buf)
continue
if buf[i + 1] == '@':
chunks.append(buf[idx:i+1])

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2008 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 ViewVC
@@ -15,7 +15,6 @@
import os
import os.path
import re
import urllib
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
@@ -24,20 +23,8 @@ def canonicalize_rootpath(rootpath):
import svn.core
return svn.core.svn_path_canonicalize(rootpath)
except:
if os.name == 'posix':
rootpath_lower = rootpath.lower()
if rootpath_lower in ['file://localhost',
'file://localhost/',
'file://',
'file:///'
]:
return '/'
if rootpath_lower.startswith('file://localhost/'):
return os.path.normpath(urllib.unquote(rootpath[16:]))
elif rootpath_lower.startswith('file:///'):
return os.path.normpath(urllib.unquote(rootpath[7:]))
if re.search(_re_url, rootpath):
return rootpath.rstrip('/')
return rootpath[-1] == '/' and rootpath[:-1] or rootpath
return os.path.normpath(rootpath)

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2009 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 ViewVC
@@ -18,9 +18,10 @@ import os
import string
import re
import tempfile
import popen2
import time
import urllib
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception, _split_revprops
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception
from svn import core, delta, client, wc, ra
@@ -51,29 +52,6 @@ def get_directory_props(ra_session, path, rev):
props = ra.svn_ra_get_dir(ra_session, path, rev)
return props
def client_log(url, start_rev, end_rev, log_limit, cross_copies,
cb_func, ctx):
try:
client.svn_client_log4([url], start_rev, start_rev, end_rev,
log_limit, 1, not cross_copies, 0, None,
cb_func, ctx)
except AttributeError:
# Wrap old svn_log_message_receiver_t interface with a
# svn_log_entry_t one.
def cb_convert(paths, revision, author, date, message, pool):
class svn_log_entry_t:
pass
log_entry = svn_log_entry_t()
log_entry.changed_paths = paths
log_entry.revision = revision
log_entry.revprops = { core.SVN_PROP_REVISION_LOG : message,
core.SVN_PROP_REVISION_AUTHOR : author,
core.SVN_PROP_REVISION_DATE : date,
}
cb_func(log_entry, pool)
client.svn_client_log2([url], start_rev, end_rev, log_limit,
1, not cross_copies, cb_convert, ctx)
### END COMPATABILITY CODE ###
@@ -90,11 +68,7 @@ class LogCollector:
self.show_all_logs = show_all_logs
self.lockinfo = lockinfo
def add_log(self, log_entry, pool):
paths = log_entry.changed_paths
revision = log_entry.revision
msg, author, date, revprops = _split_revprops(log_entry.revprops)
def add_log(self, paths, revision, author, date, message, pool):
# Changed paths have leading slashes
changed_paths = paths.keys()
changed_paths.sort(lambda a, b: _compare_paths(a, b))
@@ -114,8 +88,8 @@ class LogCollector:
if change.copyfrom_path:
this_path = change.copyfrom_path + self.path[len(changed_path):]
if self.show_all_logs or this_path:
entry = Revision(revision, date, author, msg, None, self.lockinfo,
self.path[1:], None, None)
entry = Revision(revision, _datestr_to_date(date), author, message, None,
self.lockinfo, self.path[1:], None, None)
self.logs.append(entry)
if this_path:
self.path = this_path
@@ -183,7 +157,7 @@ class RemoteSubversionRepository(vclib.Repository):
# Setup the client context baton, complete with non-prompting authstuffs.
# TODO: svn_cmdline_setup_auth_baton() is mo' better (when available)
core.svn_config_ensure(self.config_dir)
self.ctx = client.svn_client_create_context()
self.ctx = client.svn_client_ctx_t()
self.ctx.auth_baton = core.svn_auth_open([
client.svn_client_get_simple_provider(),
client.svn_client_get_username_provider(),
@@ -203,10 +177,6 @@ class RemoteSubversionRepository(vclib.Repository):
self.youngest = ra.svn_ra_get_latest_revnum(self.ra_session)
self._dirent_cache = { }
self._revinfo_cache = { }
# See if a universal read access determination can be made.
if self.auth and self.auth.check_universal_access(self.name) == 1:
self.auth = None
def rootname(self):
return self.name
@@ -282,7 +252,7 @@ class RemoteSubversionRepository(vclib.Repository):
if not vclib.check_path_access(self, entry_path_parts, entry.kind, rev):
continue
dirent = dirents[entry.name]
entry.date, entry.author, entry.log, revprops, changes = \
entry.date, entry.author, entry.log, changes = \
self.revinfo(dirent.created_rev)
entry.rev = str(dirent.created_rev)
entry.size = dirent.size
@@ -313,8 +283,9 @@ class RemoteSubversionRepository(vclib.Repository):
log_limit = 0
if limit:
log_limit = first + limit
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit,
cross_copies, lc.add_log, self.ctx)
client.svn_client_log2([url], _rev2optrev(rev), _rev2optrev(1),
log_limit, 1, not cross_copies,
lc.add_log, self.ctx)
revs = lc.logs
revs.sort()
prev = None
@@ -366,7 +337,7 @@ class RemoteSubversionRepository(vclib.Repository):
if not cached_info:
cached_info = self._revinfo_raw(rev)
self._revinfo_cache[rev] = cached_info
return tuple(cached_info)
return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
p1 = self._getpath(path_parts1)
@@ -381,7 +352,7 @@ class RemoteSubversionRepository(vclib.Repository):
args = vclib._diff_args(type, options)
def _date_from_rev(rev):
date, author, msg, revprops, changes = self.revinfo(rev)
date, author, msg, changes = self.revinfo(rev)
return date
try:
@@ -446,54 +417,29 @@ class RemoteSubversionRepository(vclib.Repository):
return revisions[0]
def _revinfo_raw(self, rev):
# return 5-tuple (date, author, msg, revprops, changes)
# return 5-tuple (date, author, message, changes)
optrev = _rev2optrev(rev)
revs = []
def _log_cb(log_entry, pool, retval=revs):
### Subversion 1.5 and earlier didn't offer the 'changed_paths2'
### hash, and in Subversion 1.6, it's offered but broken.
try:
changed_paths = log_entry.changed_paths2
paths = (changed_paths or {}).keys()
except:
changed_paths = log_entry.changed_paths
paths = (changed_paths or {}).keys()
paths.sort(lambda a, b: _compare_paths(a, b))
revision = log_entry.revision
msg, author, date, revprops = _split_revprops(log_entry.revprops)
def _log_cb(changed_paths, revision, author,
datestr, message, pool, retval=revs):
date = _datestr_to_date(datestr)
action_map = { 'D' : vclib.DELETED,
'A' : vclib.ADDED,
'R' : vclib.REPLACED,
'M' : vclib.MODIFIED,
}
paths = (changed_paths or {}).keys()
paths.sort(lambda a, b: _compare_paths(a, b))
changes = []
found_readable = found_unreadable = 0
for path in paths:
change = changed_paths[path]
### svn_log_changed_path_t (which we might get instead of the
### svn_log_changed_path2_t we'd prefer) doesn't have the
### 'node_kind' member.
pathtype = None
if hasattr(change, 'node_kind'):
if change.node_kind == core.svn_node_dir:
pathtype = vclib.DIR
elif change.node_kind == core.svn_node_file:
pathtype = vclib.FILE
### svn_log_changed_path2_t only has the 'text_modified' and
### 'props_modified' bits in Subversion 1.7 and beyond. And
### svn_log_changed_path_t is without.
text_modified = props_modified = 0
if hasattr(change, 'text_modified'):
if change.text_modified == core.svn_tristate_true:
text_modified = 1
if hasattr(change, 'props_modified'):
if change.props_modified == core.svn_tristate_true:
props_modified = 1
change = changed_paths[path]
action = action_map.get(change.action, vclib.MODIFIED)
### Wrong, diddily wrong wrong wrong. Can you say,
### "Manufacturing data left and right because it hurts to
### figure out the right stuff?"
action = action_map.get(change.action, vclib.MODIFIED)
if change.copyfrom_path and change.copyfrom_rev:
is_copy = 1
base_path = change.copyfrom_path
@@ -516,21 +462,21 @@ class RemoteSubversionRepository(vclib.Repository):
base_path = None
base_rev = None
changes.append(SVNChangedPath(path, revision, pathtype, base_path,
base_rev, action, is_copy,
text_modified, props_modified))
base_rev, action, is_copy, 0, 0))
found_readable = 1
else:
found_unreadable = 1
if found_unreadable:
msg = None
message = None
if not found_readable:
author = None
date = None
revs.append([date, author, msg, revprops, changes])
revs.append([date, author, message, changes])
client_log(self.rootpath, optrev, optrev, 1, 0, _log_cb, self.ctx)
return tuple(revs[0])
client.svn_client_log([self.rootpath], optrev, optrev,
1, 0, _log_cb, self.ctx)
return revs[0][0], revs[0][1], revs[0][2], revs[0][3]
##--- custom --##

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2009 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 ViewVC
@@ -115,27 +115,6 @@ def _rootpath2url(rootpath, path):
return 'file://' + string.join([rootpath, path], "/")
# Given a dictionary REVPROPS of revision properties, pull special
# ones out of them and return a 4-tuple containing the log message,
# the author, the date (converted from the date string property), and
# a dictionary of any/all other revprops.
def _split_revprops(revprops):
if not revprops:
return None, None, None, {}
special_props = []
for prop in core.SVN_PROP_REVISION_LOG, \
core.SVN_PROP_REVISION_AUTHOR, \
core.SVN_PROP_REVISION_DATE:
if revprops.has_key(prop):
special_props.append(revprops[prop])
del(revprops[prop])
else:
special_props.append(None)
msg, author, datestr = tuple(special_props)
date = _datestr_to_date(datestr)
return msg, author, date, revprops
def _datestr_to_date(datestr):
try:
return core.svn_time_from_cstring(datestr) / 1000000
@@ -204,6 +183,59 @@ class NodeHistory:
def __getitem__(self, idx):
return self.histories[idx]
def _get_history(svnrepos, path, rev, path_type, limit=0, options={}):
if svnrepos.youngest == 0:
return []
rev_paths = []
fsroot = svnrepos._getroot(rev)
show_all_logs = options.get('svn_show_all_dir_logs', 0)
if not show_all_logs:
# See if the path is a file or directory.
kind = fs.check_path(fsroot, path)
if kind is core.svn_node_file:
show_all_logs = 1
# Instantiate a NodeHistory collector object, and use it to collect
# history items for PATH@REV.
history = NodeHistory(svnrepos.fs_ptr, show_all_logs, limit)
try:
repos.svn_repos_history(svnrepos.fs_ptr, path, history.add_history,
1, rev, options.get('svn_cross_copies', 0))
except core.SubversionException, e:
_fix_subversion_exception(e)
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
raise
# Now, iterate over those history items, checking for changes of
# location, pruning as necessitated by authz rules.
for hist_rev, hist_path in history:
path_parts = _path_parts(hist_path)
if not vclib.check_path_access(svnrepos, path_parts, path_type, hist_rev):
break
rev_paths.append([hist_rev, hist_path])
return rev_paths
def _log_helper(svnrepos, path, rev, lockinfo):
rev_root = fs.revision_root(svnrepos.fs_ptr, rev)
# Was this path@rev the target of a copy?
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
# Assemble our LogEntry
date, author, msg, changes = svnrepos._revinfo(rev)
if fs.is_file(rev_root, path):
size = fs.file_length(rev_root, path)
else:
size = None
entry = Revision(rev, date, author, msg, size, lockinfo, path,
copyfrom_path and _cleanup_path(copyfrom_path),
copyfrom_rev)
return entry
def _get_last_history_rev(fsroot, path):
history = fs.node_history(fsroot, path)
history = fs.history_prev(history, 0)
@@ -282,14 +314,14 @@ class FileContentsPipe:
class BlameSource:
def __init__(self, local_url, rev, first_rev, config_dir):
def __init__(self, local_url, rev, first_rev):
self.idx = -1
self.first_rev = first_rev
self.blame_data = []
ctx = client.svn_client_create_context()
core.svn_config_ensure(config_dir)
ctx.config = core.svn_config_get_config(config_dir)
ctx = client.ctx_t()
core.svn_config_ensure(None)
ctx.config = core.svn_config_get_config(None)
ctx.auth_baton = core.svn_auth_open([])
try:
### TODO: Is this use of FIRST_REV always what we want? Should we
@@ -344,7 +376,7 @@ class LocalSubversionRepository(vclib.Repository):
self.auth = authorizer
self.svn_client_path = utilities.svn or 'svn'
self.diff_cmd = utilities.diff or 'diff'
self.config_dir = config_dir or None
self.config_dir = config_dir
# See if this repository is even viewable, authz-wise.
if not vclib.check_root_access(self):
@@ -374,10 +406,6 @@ class LocalSubversionRepository(vclib.Repository):
self._fsroots = {}
self._revinfo_cache = {}
# See if a universal read access determination can be made.
if self.auth and self.auth.check_universal_access(self.name) == 1:
self.auth = None
def rootname(self):
return self.name
@@ -444,7 +472,7 @@ class LocalSubversionRepository(vclib.Repository):
continue
path = self._getpath(entry_path_parts)
entry_rev = _get_last_history_rev(fsroot, path)
date, author, msg, revprops, changes = self._revinfo(entry_rev)
date, author, msg, changes = self._revinfo(entry_rev)
entry.rev = str(entry_rev)
entry.date = date
entry.author = author
@@ -493,19 +521,20 @@ class LocalSubversionRepository(vclib.Repository):
# 'limit' parameter here as numeric cut-off for the depth of our
# history search.
if options.get('svn_latest_log', 0):
revision = self._log_helper(path, rev, lockinfo)
revision = _log_helper(self, path, rev, lockinfo)
if revision:
revision.prev = None
revs.append(revision)
else:
history = self._get_history(path, rev, path_type, first + limit, options)
history = _get_history(self, path, rev, path_type,
first + limit, options)
if len(history) < first:
history = []
if limit:
history = history[first:first+limit]
for hist_rev, hist_path in history:
revision = self._log_helper(hist_path, hist_rev, lockinfo)
revision = _log_helper(self, hist_path, hist_rev, lockinfo)
if revision:
# If we have unreadable copyfrom data, obscure it.
if revision.copy_path is not None:
@@ -533,56 +562,36 @@ class LocalSubversionRepository(vclib.Repository):
raise vclib.Error("Path '%s' is not a file." % path)
rev = self._getrev(rev)
fsroot = self._getroot(rev)
history = self._get_history(path, rev, path_type, 0,
{'svn_cross_copies': 1})
history = _get_history(self, path, rev, path_type, 0,
{'svn_cross_copies': 1})
youngest_rev, youngest_path = history[0]
oldest_rev, oldest_path = history[-1]
source = BlameSource(_rootpath2url(self.rootpath, path),
youngest_rev, oldest_rev, self.config_dir)
youngest_rev, oldest_rev)
return source, youngest_rev
def revinfo(self, rev):
return self._revinfo(rev, 1)
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
p1 = self._getpath(path_parts1)
p2 = self._getpath(path_parts2)
r1 = self._getrev(rev1)
r2 = self._getrev(rev2)
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
raise vclib.ItemNotFound(path_parts1)
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
raise vclib.ItemNotFound(path_parts2)
args = vclib._diff_args(type, options)
def _date_from_rev(rev):
date, author, msg, revprops, changes = self._revinfo(rev)
return date
try:
temp1 = temp_checkout(self, p1, r1)
temp2 = temp_checkout(self, p2, r2)
info1 = p1, _date_from_rev(r1), r1
info2 = p2, _date_from_rev(r2), r2
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
except core.SubversionException, e:
_fix_subversion_exception(e)
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
raise vclib.InvalidRevision
raise
def isexecutable(self, path_parts, rev):
props = self.itemprops(path_parts, rev) # does authz-check
return props.has_key(core.SVN_PROP_EXECUTABLE)
##--- helpers ---##
def _revinfo(self, rev, include_changed_paths=0):
"""Internal-use, cache-friendly revision information harvester."""
def _get_changed_paths(fsroot):
"""Return a 3-tuple: found_readable, found_unreadable, changed_paths."""
def _revinfo_helper(rev, include_changed_paths):
# Get the revision property info. (Would use
# editor.get_root_props(), but something is broken there...)
revprops = fs.revision_proplist(self.fs_ptr, rev)
msg = revprops.get(core.SVN_PROP_REVISION_LOG)
author = revprops.get(core.SVN_PROP_REVISION_AUTHOR)
datestr = revprops.get(core.SVN_PROP_REVISION_DATE)
date = _datestr_to_date(datestr)
# Optimization: If our caller doesn't care about the changed
# paths, and we don't need them to do authz determinations, let's
# get outta here.
if self.auth is None and not include_changed_paths:
return date, author, msg, None
# If we get here, then we either need the changed paths because we
# were asked for them, or we need them to do authorization checks.
# Either way, we need 'em, so let's get 'em.
fsroot = self._getroot(rev)
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
e_ptr, e_baton = delta.make_editor(editor)
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
@@ -635,8 +644,7 @@ class LocalSubversionRepository(vclib.Repository):
if vclib.check_path_access(self, parts, pathtype, rev):
if is_copy and change.base_path and (change.base_path != path):
parts = _path_parts(change.base_path)
if not vclib.check_path_access(self, parts, pathtype,
change.base_rev):
if not vclib.check_path_access(self, parts, pathtype, change.base_rev):
is_copy = 0
change.base_path = None
change.base_rev = None
@@ -648,76 +656,24 @@ class LocalSubversionRepository(vclib.Repository):
found_readable = 1
else:
found_unreadable = 1
return found_readable, found_unreadable, changedpaths.values()
def _simple_auth_check(fsroot):
"""Return a 2-tuple: found_readable, found_unreadable."""
found_unreadable = found_readable = 0
if hasattr(fs, 'paths_changed2'):
changes = fs.paths_changed2(fsroot)
else:
changes = fs.paths_changed(fsroot)
paths = changes.keys()
for path in paths:
change = changes[path]
pathtype = None
if hasattr(change, 'node_kind'):
if change.node_kind == core.svn_node_file:
pathtype = vclib.FILE
elif change.node_kind == core.svn_node_dir:
pathtype = vclib.DIR
parts = _path_parts(path)
if vclib.check_path_access(self, parts, pathtype, rev):
found_readable = 1
if hasattr(change, 'copyfrom_path'):
copyfrom_path = change.copyfrom_path
copyfrom_rev = change.copyfrom_rev
else:
copyfrom_rev, copyfrom_path = fs.copied_from(fsroot, path)
if copyfrom_path and copyfrom_path != path:
parts = _path_parts(copyfrom_path)
if not vclib.check_path_access(self, parts, pathtype,
copyfrom_rev):
found_unreadable = 1
else:
found_unreadable = 1
if found_readable and found_unreadable:
break
return found_readable, found_unreadable
def _revinfo_helper(rev, include_changed_paths):
# Get the revision property info. (Would use
# editor.get_root_props(), but something is broken there...)
revprops = fs.revision_proplist(self.fs_ptr, rev)
msg, author, date, revprops = _split_revprops(revprops)
# Optimization: If our caller doesn't care about the changed
# paths, and we don't need them to do authz determinations, let's
# get outta here.
if self.auth is None and not include_changed_paths:
return date, author, msg, revprops, None
# If we get here, then we either need the changed paths because we
# were asked for them, or we need them to do authorization checks.
#
# If we only need them for authorization checks, though, we
# won't bother generating fully populated ChangedPath items (the
# cost is too great).
fsroot = self._getroot(rev)
if include_changed_paths:
found_readable, found_unreadable, changedpaths = \
_get_changed_paths(fsroot)
else:
changedpaths = None
found_readable, found_unreadable = _simple_auth_check(fsroot)
# If our caller doesn't care about changed paths, we must be
# here for authz reasons only. That means the minute we've
# found both a readable and an unreadable path, we can bail out.
if (not include_changed_paths) and found_readable and found_unreadable:
return date, author, None, None
# Filter our metadata where necessary, and return the requested data.
# Okay, we've process all our paths. Let's filter our metadata,
# and return the requested data.
if found_unreadable:
msg = None
if not found_readable:
author = None
date = None
return date, author, msg, revprops, changedpaths
if include_changed_paths:
return date, author, msg, changedpaths.values()
else:
return date, author, msg, None
# Consult the revinfo cache first. If we don't have cached info,
# or our caller wants changed paths and we don't have those for
@@ -725,55 +681,45 @@ class LocalSubversionRepository(vclib.Repository):
rev = self._getrev(rev)
cached_info = self._revinfo_cache.get(rev)
if not cached_info \
or (include_changed_paths and cached_info[4] is None):
or (include_changed_paths and cached_info[3] is None):
cached_info = _revinfo_helper(rev, include_changed_paths)
self._revinfo_cache[rev] = cached_info
return tuple(cached_info)
return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
def _log_helper(self, path, rev, lockinfo):
rev_root = fs.revision_root(self.fs_ptr, rev)
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
date, author, msg, revprops, changes = self._revinfo(rev)
if fs.is_file(rev_root, path):
size = fs.file_length(rev_root, path)
else:
size = None
return Revision(rev, date, author, msg, size, lockinfo, path,
copyfrom_path and _cleanup_path(copyfrom_path),
copyfrom_rev)
def revinfo(self, rev):
return self._revinfo(rev, 1)
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
p1 = self._getpath(path_parts1)
p2 = self._getpath(path_parts2)
r1 = self._getrev(rev1)
r2 = self._getrev(rev2)
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
raise vclib.ItemNotFound(path_parts1)
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
raise vclib.ItemNotFound(path_parts2)
args = vclib._diff_args(type, options)
def _get_history(self, path, rev, path_type, limit=0, options={}):
if self.youngest == 0:
return []
def _date_from_rev(rev):
date, author, msg, changes = self._revinfo(rev)
return date
rev_paths = []
fsroot = self._getroot(rev)
show_all_logs = options.get('svn_show_all_dir_logs', 0)
if not show_all_logs:
# See if the path is a file or directory.
kind = fs.check_path(fsroot, path)
if kind is core.svn_node_file:
show_all_logs = 1
# Instantiate a NodeHistory collector object, and use it to collect
# history items for PATH@REV.
history = NodeHistory(self.fs_ptr, show_all_logs, limit)
try:
repos.svn_repos_history(self.fs_ptr, path, history.add_history,
1, rev, options.get('svn_cross_copies', 0))
temp1 = temp_checkout(self, p1, r1)
temp2 = temp_checkout(self, p2, r2)
info1 = p1, _date_from_rev(r1), r1
info2 = p2, _date_from_rev(r2), r2
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
except core.SubversionException, e:
_fix_subversion_exception(e)
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
raise
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
raise vclib.InvalidRevision
raise
# Now, iterate over those history items, checking for changes of
# location, pruning as necessitated by authz rules.
for hist_rev, hist_path in history:
path_parts = _path_parts(hist_path)
if not vclib.check_path_access(self, path_parts, path_type, hist_rev):
break
rev_paths.append([hist_rev, hist_path])
return rev_paths
def isexecutable(self, path_parts, rev):
props = self.itemprops(path_parts, rev) # does authz-check
return props.has_key(core.SVN_PROP_EXECUTABLE)
def _getpath(self, path_parts):
return string.join(path_parts, '/')
@@ -796,7 +742,7 @@ class LocalSubversionRepository(vclib.Repository):
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev)
return r
##--- custom ---##
##--- custom --##
def get_youngest_revision(self):
return self.youngest

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2009 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 ViewVC
@@ -14,7 +14,7 @@
#
# -----------------------------------------------------------------------
__version__ = '1.1.9'
__version__ = '1.1.3'
# this comes from our library; measure the startup time
import debug
@@ -24,6 +24,7 @@ debug.t_start('imports')
# standard modules that we know are in the path or builtin
import sys
import os
import cgi
import gzip
import mimetypes
import re
@@ -79,6 +80,10 @@ _sticky_vars = [
'limit_changes',
]
# number of extra pages of information on either side of the current
# page to fetch (see dir_pagesize/log_pagesize configuration option)
EXTRA_PAGES = 3
# for reading/writing between a couple descriptors
CHUNK_SIZE = 8192
@@ -269,8 +274,7 @@ class Request:
self.roottype = 'cvs'
else:
raise debug.ViewVCException(
'The root "%s" has an unknown type ("%s"). Expected "cvs" or "svn".'
% (self.rootname, type),
'The root "%s" has an unknown type (%s).' % (self.rootname, type),
"500 Internal Server Error")
# If this is using an old-style 'rev' parameter, redirect to new hotness.
@@ -428,8 +432,7 @@ class Request:
action = self.server.escape(urllib.quote(url, _URL_SAFE_CHARS))
hidden_values = []
for name, value in params.items():
hidden_values.append(_item(name=self.server.escape(name),
value=self.server.escape(value)))
hidden_values.append(_item(name=name, value=value))
return action, hidden_values
def get_link(self, view_func=None, where=None, pathtype=None, params=None):
@@ -893,41 +896,26 @@ def get_view_template(cfg, view_name, language="en"):
return template
def get_writeready_server_file(request, content_type=None, encoding=None,
content_length=None):
def get_writeready_server_file(request, content_type=None):
"""Return a file handle to a response body stream, after outputting
any queued special headers (on REQUEST.server) and (optionally) a
'Content-Type' header whose value is CONTENT_TYPE and character set
is ENCODING.
If CONTENT_LENGTH is provided and compression is not in use, also
generate a 'Content-Length' header for this response.
After this function is called, it is too late to add new headers to
the response."""
'Content-Type' header whose value is CONTENT_TYPE. After this is
called, it is too late to add new headers to the response."""
if request.gzip_compress_level:
request.server.addheader('Content-Encoding', 'gzip')
elif content_length is not None:
request.server.addheader('Content-Length', content_length)
if content_type and encoding:
request.server.header("%s; charset=%s" % (content_type, encoding))
elif content_type:
if content_type:
request.server.header(content_type)
else:
request.server.header()
if request.gzip_compress_level:
fp = gzip.GzipFile('', 'wb', request.gzip_compress_level,
request.server.file())
else:
fp = request.server.file()
return fp
def generate_page(request, view_name, data, content_type=None):
server_fp = get_writeready_server_file(request, content_type)
server_fp = get_writeready_server_file(request)
template = get_view_template(request.cfg, view_name, request.language)
template.generate(server_fp, data)
@@ -1087,6 +1075,9 @@ def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1):
revision_href=revision_href,
prefer_markup=ezt.boolean(prefer_markup))
def htmlify(html):
return html and cgi.escape(html) or html
# Matches URLs
_re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
@@ -1097,10 +1088,8 @@ _re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
_re_rewrite_email = re.compile('([-a-zA-Z0-9_.\+]+)@'
'(([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})')
# Matches revision references
_re_rewrite_svnrevref = re.compile(r'\b(r|rev #?|revision #?)([0-9]+)\b')
class ViewVCHtmlFormatter:
class HtmlFormatter:
"""Format a string as HTML-encoded output with customizable markup
rules, for example turning strings that look like URLs into anchor links.
@@ -1121,8 +1110,8 @@ class ViewVCHtmlFormatter:
"""
s = mobj.group(0)
trunc_s = maxlen and s[:maxlen] or s
return '<a href="%s">%s</a>' % (sapi.escape(s),
sapi.escape(trunc_s)), \
return '<a href="%s">%s</a>' % (cgi.escape(s),
cgi.escape(trunc_s)), \
len(trunc_s)
def format_email(self, mobj, userdata, maxlen=0):
@@ -1166,25 +1155,6 @@ class ViewVCHtmlFormatter:
trunc_s = mobj.group(1)[:maxlen]
return self._entity_encode(trunc_s), len(trunc_s)
def format_svnrevref(self, mobj, userdata, maxlen=0):
"""Return a 2-tuple containing:
- the text represented by MatchObject MOBJ, formatted as an
linkified URL to a ViewVC Subversion revision view, with no
more than MAXLEN characters in the non-HTML-tag portions.
If MAXLEN is 0, there is no maximum.
- the number of characters returned.
USERDATA is a function that accepts a revision reference
and returns a URL to that revision.
"""
s = mobj.group(0)
revref = mobj.group(2)
trunc_s = maxlen and s[:maxlen] or s
revref_url = userdata(revref)
return '<a href="%s">%s</a>' % (sapi.escape(revref_url),
sapi.escape(trunc_s)), \
len(trunc_s)
def format_text(self, s, unused, maxlen=0):
"""Return a 2-tuple containing:
- the text S, HTML-escaped, containing no more than MAXLEN
@@ -1192,7 +1162,7 @@ class ViewVCHtmlFormatter:
- the number of characters returned.
"""
trunc_s = maxlen and s[:maxlen] or s
return sapi.escape(trunc_s), len(trunc_s)
return cgi.escape(trunc_s), len(trunc_s)
def add_formatter(self, regexp, conv, userdata=None):
"""Register a formatter which finds instances of strings matching
@@ -1273,22 +1243,13 @@ class ViewVCHtmlFormatter:
s = ''
return tokens
def format_log(request, log, maxlen=0, htmlize=1):
def format_log(log, cfg, maxlen=0, htmlize=1):
if not log:
return log
cfg = request.cfg
if htmlize:
lf = ViewVCHtmlFormatter()
lf = HtmlFormatter()
lf.add_formatter(_re_rewrite_url, lf.format_url)
if request.roottype == 'svn':
def revision_to_url(rev):
return request.get_url(view_func=view_revision,
params={'revision': rev},
escape=1)
lf.add_formatter(_re_rewrite_svnrevref, lf.format_svnrevref,
revision_to_url)
if cfg.options.mangle_email_addresses == 2:
lf.add_formatter(_re_rewrite_email, lf.format_email_truncated)
elif cfg.options.mangle_email_addresses == 1:
@@ -1506,7 +1467,7 @@ def copy_stream(src, dst, htmlize=0):
if not chunk:
break
if htmlize:
chunk = sapi.escape(chunk)
chunk = htmlify(chunk)
dst.write(chunk)
class MarkupPipeWrapper:
@@ -1527,23 +1488,7 @@ class MarkupPipeWrapper:
if self.posttext:
ctx.fp.write(self.posttext)
_re_rewrite_escaped_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
'(://[-a-zA-Z0-9%.~:_/]+)'
'((\?|\&amp;amp;|\&amp;|\&)'
'([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*'
'(#([-a-zA-Z0-9%.~:_]+)?)?)')
def markup_escaped_urls(s):
# Return a copy of S with all URL references -- which are expected
# to be already HTML-escaped -- wrapped in <a href=""></a>.
def _url_repl(match_obj):
url = match_obj.group(0)
unescaped_url = string.replace(url, "&amp;amp;", "&amp;")
return "<a href=\"%s\">%s</a>" % (unescaped_url, url)
return re.sub(_re_rewrite_escaped_url, _url_repl, s)
def markup_stream_pygments(request, cfg, blame_data, fp, filename,
mime_type, encoding):
def markup_stream_pygments(request, cfg, blame_data, fp, filename, mime_type):
# Determine if we should use Pygments to highlight our output.
# Reasons not to include a) being told not to by the configuration,
# b) not being able to import the Pygments modules, and c) Pygments
@@ -1551,7 +1496,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
blame_source = []
if blame_data:
for i in blame_data:
i.text = sapi.escape(i.text)
i.text = cgi.escape(i.text)
i.diff_href = None
if i.prev_rev:
i.diff_href = request.get_url(view_func=view_diff,
@@ -1569,14 +1514,13 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
get_lexer_by_name, \
get_lexer_for_mimetype, \
get_lexer_for_filename
if not encoding:
encoding = 'guess'
if cfg.options.detect_encoding:
try:
import chardet
encoding = 'chardet'
except (SyntaxError, ImportError):
pass
encoding = 'guess'
if cfg.options.detect_encoding:
try:
import chardet
encoding = 'chardet'
except (SyntaxError, ImportError):
pass
try:
lexer = get_lexer_for_mimetype(mime_type,
encoding=encoding,
@@ -1605,7 +1549,6 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
def __getitem__(self, idx):
item = self.blame_source.__getitem__(idx)
item.text = string.expandtabs(item.text, self.tabsize)
item.text = markup_escaped_urls(item.text)
return item
return BlameSourceTabsizeWrapper(blame_source, cfg.options.tabsize)
else:
@@ -1616,8 +1559,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
if not line:
break
line_no = line_no + 1
line = sapi.escape(string.expandtabs(line, cfg.options.tabsize))
line = markup_escaped_urls(line)
line = cgi.escape(string.expandtabs(line, cfg.options.tabsize))
item = vclib.Annotation(line, line_no, None, None, None, None)
item.diff_href = None
lines.append(item)
@@ -1635,7 +1577,6 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
self.line_no = 0
def write(self, buf):
### FIXME: Don't bank on write() being called once per line
buf = markup_escaped_urls(buf)
if self.has_blame_data:
self.blame_data[self.line_no].text = buf
else:
@@ -1683,8 +1624,9 @@ def get_itemprops(request, path_parts, rev):
propnames = itemprops.keys()
propnames.sort()
props = []
has_binary_props = 0
for name in propnames:
value = format_log(request, itemprops[name])
value = format_log(itemprops[name], request.cfg)
undisplayable = ezt.boolean(0)
# skip non-utf8 property names
try:
@@ -1700,33 +1642,20 @@ def get_itemprops(request, path_parts, rev):
props.append(_item(name=name, value=value, undisplayable=undisplayable))
return props
def parse_mime_type(mime_type):
mime_parts = map(lambda x: x.strip(), string.split(mime_type, ';'))
type_subtype = mime_parts[0].lower()
parameters = {}
for part in mime_parts[1:]:
name, value = string.split(part, '=', 1)
parameters[name] = value
return type_subtype, parameters
def calculate_mime_type(request, path_parts, rev):
"""Return a 2-tuple carrying the MIME content type and character
encoding for the file represented by PATH_PARTS in REV. Use REQUEST
for repository access as necessary."""
mime_type = None
if not path_parts:
return None, None
mime_type = encoding = None
return None
if request.roottype == 'svn' \
and (not request.cfg.options.svn_ignore_mimetype):
try:
itemprops = request.repos.itemprops(path_parts, rev)
mime_type = itemprops.get('svn:mime-type')
if mime_type:
mime_type, parameters = parse_mime_type(mime_type)
return mime_type, parameters.get('charset')
return mime_type
except:
pass
return guess_mime(path_parts[-1]), None
return guess_mime(path_parts[-1])
def markup_or_annotate(request, is_annotate):
cfg = request.cfg
@@ -1734,7 +1663,7 @@ def markup_or_annotate(request, is_annotate):
lines = fp = image_src_href = None
annotation = 'none'
revision = None
mime_type, encoding = calculate_mime_type(request, path, rev)
mime_type = calculate_mime_type(request, path, rev)
# Is this a viewable image type?
if is_viewable_image(mime_type) \
@@ -1767,7 +1696,7 @@ def markup_or_annotate(request, is_annotate):
fp.close()
return
lines = markup_stream_pygments(request, cfg, blame_source, fp,
path[-1], mime_type, encoding)
path[-1], mime_type)
fp.close()
data = common_template_data(request, revision)
@@ -1801,7 +1730,7 @@ def markup_or_annotate(request, is_annotate):
data['date'] = make_time_string(entry.date, cfg)
data['author'] = entry.author
data['changed'] = entry.changed
data['log'] = format_log(request, entry.log)
data['log'] = format_log(entry.log, cfg)
data['size'] = entry.size
if entry.date is not None:
@@ -1834,18 +1763,12 @@ def view_markup(request):
if 'markup' not in request.cfg.options.allowed_views:
raise debug.ViewVCException('Markup view is disabled',
'403 Forbidden')
if request.pathtype != vclib.FILE:
raise debug.ViewVCException('Unsupported feature: markup view on '
'directory', '400 Bad Request')
markup_or_annotate(request, 0)
def view_annotate(request):
if 'annotate' not in request.cfg.options.allowed_views:
raise debug.ViewVCException('Annotation view is disabled',
'403 Forbidden')
if request.pathtype != vclib.FILE:
raise debug.ViewVCException('Unsupported feature: annotate view on '
'directory', '400 Bad Request')
markup_or_annotate(request, 1)
def revcmp(rev1, rev2):
@@ -1920,16 +1843,9 @@ def view_roots(request):
href = request.get_url(view_func=view_directory,
where='', pathtype=vclib.DIR,
params={'root': rootname}, escape=1)
lastmod = allroots[rootname][2]
roots.append(_item(name=request.server.escape(rootname),
type=allroots[rootname][1],
path=allroots[rootname][0],
author=lastmod and lastmod.author or None,
ago=lastmod and lastmod.ago or None,
date=lastmod and lastmod.date or None,
log=lastmod and lastmod.log or None,
short_log=lastmod and lastmod.short_log or None,
rev=lastmod and lastmod.rev or None,
href=href))
data = common_template_data(request)
@@ -2030,8 +1946,8 @@ def view_directory(request):
row.date = make_time_string(file.date, cfg)
row.ago = html_time(request, file.date)
if cfg.options.show_logs:
row.log = format_log(request, file.log)
row.short_log = format_log(request, file.log,
row.log = format_log(file.log, cfg)
row.short_log = format_log(file.log, cfg,
maxlen=cfg.options.short_log_len)
row.lockinfo = file.lockinfo
row.anchor = request.server.escape(file.name)
@@ -2088,9 +2004,8 @@ def view_directory(request):
if request.roottype == 'svn':
row.size = file.size
row.mime_type, encoding = calculate_mime_type(request,
_path_parts(file_where),
file.rev)
row.mime_type = calculate_mime_type(request, _path_parts(file_where),
file.rev)
fvi = get_file_view_info(request, file_where, file.rev, row.mime_type)
row.view_href = fvi.view_href
row.download_href = fvi.download_href
@@ -2119,7 +2034,7 @@ def view_directory(request):
'entries' : rows,
'sortby' : sortby,
'sortdir' : sortdir,
'search_re' : request.server.escape(search_re),
'search_re' : htmlify(search_re),
'dir_pagestart' : None,
'sortby_file_href' : request.get_url(params={'sortby': 'file',
'sortdir': None},
@@ -2254,11 +2169,10 @@ def paging(data, key, pagestart, local_name, pagesize):
# Slice
return data[key][pagestart:pageend]
def paging_sws(data, key, pagestart, local_name, pagesize,
extra_pages, offset):
def paging_sws(data, key, pagestart, local_name, pagesize, offset):
"""Implement sliding window-style paging."""
# Create the picklist
last_requested = pagestart + (extra_pages * pagesize)
last_requested = pagestart + (EXTRA_PAGES * pagesize)
picklist = data['picklist'] = []
has_more = ezt.boolean(0)
for i in range(0, len(data[key]), pagesize):
@@ -2362,11 +2276,9 @@ def view_log(request):
if request.roottype == 'cvs':
raise debug.ViewVCException('Unsupported feature: log view on CVS '
'directory', '400 Bad Request')
mime_type = encoding = None
mime_type = None
else:
mime_type, encoding = calculate_mime_type(request,
request.path_parts,
request.pathrev)
mime_type = calculate_mime_type(request, request.path_parts, request.pathrev)
options = {}
options['svn_show_all_dir_logs'] = 1 ### someday make this optional?
@@ -2387,9 +2299,9 @@ def view_log(request):
first = last = 0
if cfg.options.log_pagesize:
log_pagestart = int(request.query_dict.get('log_pagestart', 0))
total = cfg.options.log_pagesextra * cfg.options.log_pagesize
first = log_pagestart - min(log_pagestart, total)
last = log_pagestart + (total + cfg.options.log_pagesize) + 1
first = log_pagestart - min(log_pagestart,
(EXTRA_PAGES * cfg.options.log_pagesize))
last = log_pagestart + ((EXTRA_PAGES + 1) * cfg.options.log_pagesize) + 1
show_revs = request.repos.itemlog(request.path_parts, request.pathrev,
sortby, first, last - first, options)
@@ -2409,7 +2321,7 @@ def view_log(request):
entry.ago = None
if rev.date is not None:
entry.ago = html_time(request, rev.date, 1)
entry.log = format_log(request, rev.log or '')
entry.log = format_log(rev.log or '', cfg)
entry.size = rev.size
entry.lockinfo = rev.lockinfo
entry.branch_point = None
@@ -2650,8 +2562,7 @@ def view_log(request):
request.get_form(params={'log_pagestart': None})
data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0))
data['entries'] = paging_sws(data, 'entries', data['log_pagestart'],
'rev', cfg.options.log_pagesize,
cfg.options.log_pagesextra, first)
'rev', cfg.options.log_pagesize, first)
generate_page(request, "log", data)
@@ -2668,11 +2579,10 @@ def view_checkout(request):
# The revision number acts as a strong validator.
if not check_freshness(request, None, revision):
mime_type, encoding = calculate_mime_type(request, path, rev)
mime_type = request.query_dict.get('content-type') \
or mime_type \
or calculate_mime_type(request, path, rev) \
or 'text/plain'
server_fp = get_writeready_server_file(request, mime_type, encoding)
server_fp = get_writeready_server_file(request, mime_type)
copy_stream(fp, server_fp)
fp.close()
@@ -2792,6 +2702,7 @@ def view_doc(request):
raise debug.ViewVCException('Static file "%s" not available (%s)'
% (document, str(v)), '404 Not Found')
request.server.addheader('Content-Length', content_length)
if document[-3:] == 'png':
mime_type = 'image/png'
elif document[-3:] == 'jpg':
@@ -2802,8 +2713,7 @@ def view_doc(request):
mime_type = 'text/css'
else: # assume HTML:
mime_type = None
copy_stream(fp, get_writeready_server_file(request, mime_type,
content_length=content_length))
copy_stream(fp, get_writeready_server_file(request, mime_type))
fp.close()
def rcsdiff_date_reformat(date_str, cfg):
@@ -2856,7 +2766,7 @@ class DiffSource:
hr_breakable = self.cfg.options.hr_breakable
# 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 htmlify(). Similarly,
# we use "\x02" as a stand-in for "<br>"
if hr_breakable > 1 and len(text) > hr_breakable:
@@ -2866,7 +2776,7 @@ class DiffSource:
text = string.replace(text, ' ', ' \x01nbsp;')
else:
text = string.replace(text, ' ', '\x01nbsp;')
text = sapi.escape(text)
text = htmlify(text)
text = string.replace(text, '\x01', '&')
text = string.replace(text, '\x02',
'<span style="color:red">\</span><br />')
@@ -3247,7 +3157,7 @@ def view_diff(request):
else:
changes = DiffSource(fp, cfg)
else:
raw_diff_fp = MarkupPipeWrapper(fp, request.server.escape(headers), None, 1)
raw_diff_fp = MarkupPipeWrapper(fp, htmlify(headers), None, 1)
no_format_params = request.query_dict.copy()
no_format_params['diff_format'] = None
@@ -3257,7 +3167,7 @@ def view_diff(request):
fvi = get_file_view_info(request, path_left, rev1)
left = _item(date=make_time_string(log_entry1.date, cfg),
author=log_entry1.author,
log=format_log(request, log_entry1.log),
log=format_log(log_entry1.log, cfg),
size=log_entry1.size,
ago=ago1,
path=path_left,
@@ -3273,7 +3183,7 @@ def view_diff(request):
fvi = get_file_view_info(request, path_right, rev2)
right = _item(date=make_time_string(log_entry2.date, cfg),
author=log_entry2.author,
log=format_log(request, log_entry2.log),
log=format_log(log_entry2.log, cfg),
size=log_entry2.size,
ago=ago2,
path=path_right,
@@ -3494,7 +3404,7 @@ def download_tarball(request):
def view_revision(request):
if request.roottype != "svn":
if request.roottype == "cvs":
raise debug.ViewVCException("Revision view not supported for CVS "
"repositories at this time.",
"400 Bad Request")
@@ -3513,29 +3423,9 @@ def view_revision(request):
return
# Fetch the revision information.
date, author, msg, revprops, changes = request.repos.revinfo(rev)
date, author, msg, changes = request.repos.revinfo(rev)
date_str = make_time_string(date, cfg)
# Fix up the revprops list (rather like get_itemprops()).
propnames = revprops.keys()
propnames.sort()
props = []
for name in propnames:
value = format_log(request, revprops[name])
undisplayable = ezt.boolean(0)
# skip non-utf8 property names
try:
unicode(name, 'utf8')
except:
continue
# note non-utf8 property values
try:
unicode(value, 'utf8')
except:
value = None
undisplayable = ezt.boolean(1)
props.append(_item(name=name, value=value, undisplayable=undisplayable))
# Sort the changes list by path.
def changes_sort_by_path(a, b):
return cmp(a.path_parts, b.path_parts)
@@ -3650,8 +3540,7 @@ def view_revision(request):
'rev' : str(rev),
'author' : author,
'date' : date_str,
'log' : format_log(request, msg),
'properties' : props,
'log' : format_log(msg, cfg),
'ago' : date is not None and html_time(request, date, 1) or None,
'changes' : changes,
'prev_href' : prev_rev_href,
@@ -3664,11 +3553,6 @@ def view_revision(request):
'first_changes_href': first_changes_href,
'jump_rev_action' : jump_rev_action,
'jump_rev_hidden_values' : jump_rev_hidden_values,
'revision_href' : request.get_url(view_func=view_revision,
where=None,
pathtype=None,
params={'revision': str(rev)},
escape=1),
}))
if rev == youngest_rev:
request.server.addheader("Cache-control", "no-store")
@@ -3737,26 +3621,23 @@ def view_queryform(request):
limit_changes = \
int(request.query_dict.get('limit_changes',
request.cfg.options.limit_changes))
def escaped_query_dict_get(itemname, itemdefault=''):
return request.server.escape(request.query_dict.get(itemname, itemdefault))
data = common_template_data(request)
data.merge(ezt.TemplateData({
'branch' : escaped_query_dict_get('branch', ''),
'branch_match' : escaped_query_dict_get('branch_match', 'exact'),
'dir' : escaped_query_dict_get('dir', ''),
'file' : escaped_query_dict_get('file', ''),
'file_match' : escaped_query_dict_get('file_match', 'exact'),
'who' : escaped_query_dict_get('who', ''),
'who_match' : escaped_query_dict_get('who_match', 'exact'),
'comment' : escaped_query_dict_get('comment', ''),
'comment_match' : escaped_query_dict_get('comment_match', 'exact'),
'querysort' : escaped_query_dict_get('querysort', 'date'),
'date' : escaped_query_dict_get('date', 'hours'),
'hours' : escaped_query_dict_get('hours', '2'),
'mindate' : escaped_query_dict_get('mindate', ''),
'maxdate' : escaped_query_dict_get('maxdate', ''),
'branch' : request.query_dict.get('branch', ''),
'branch_match' : request.query_dict.get('branch_match', 'exact'),
'dir' : request.query_dict.get('dir', ''),
'file' : request.query_dict.get('file', ''),
'file_match' : request.query_dict.get('file_match', 'exact'),
'who' : request.query_dict.get('who', ''),
'who_match' : request.query_dict.get('who_match', 'exact'),
'comment' : request.query_dict.get('comment', ''),
'comment_match' : request.query_dict.get('comment_match', 'exact'),
'querysort' : request.query_dict.get('querysort', 'date'),
'date' : request.query_dict.get('date', 'hours'),
'hours' : request.query_dict.get('hours', '2'),
'mindate' : request.query_dict.get('mindate', ''),
'maxdate' : request.query_dict.get('maxdate', ''),
'query_action' : query_action,
'query_hidden_values' : query_hidden_values,
'limit_changes' : limit_changes,
@@ -3820,7 +3701,7 @@ def english_query(request):
ret.append('on all branches ')
comment = request.query_dict.get('comment', '')
if comment:
ret.append('with comment <i>%s</i> ' % request.server.escape(comment))
ret.append('with comment <i>%s</i> ' % htmlify(comment))
if who:
ret.append('by <em>%s</em> ' % request.server.escape(who))
date = request.query_dict.get('date', 'hours')
@@ -3959,7 +3840,7 @@ def build_commit(request, files, max_files, dir_strip, format):
diff_href = request.get_url(view_func=view_diff,
where=where, pathtype=vclib.FILE,
params=diff_href_params, escape=1)
mime_type, encoding = calculate_mime_type(request, path_parts, exam_rev)
mime_type = calculate_mime_type(request, path_parts, exam_rev)
prefer_markup = ezt.boolean(default_view(mime_type, cfg) == view_markup)
# Update plus/minus line change count.
@@ -4008,8 +3889,8 @@ def build_commit(request, files, max_files, dir_strip, format):
commit.log = None
commit.short_log = None
else:
commit.log = format_log(request, desc, 0, format != 'rss')
commit.short_log = format_log(request, desc, cfg.options.short_log_len,
commit.log = format_log(desc, cfg, 0, format != 'rss')
commit.short_log = format_log(desc, cfg, cfg.options.short_log_len,
format != 'rss')
commit.author = request.server.escape(author)
commit.rss_date = make_rss_time_string(date, request.cfg)
@@ -4027,29 +3908,27 @@ def build_commit(request, files, max_files, dir_strip, format):
return commit
def query_backout(request, commits):
server_fp = get_writeready_server_file(request, 'text/plain')
if not commits:
server_fp.write("""\
# No changes were selected by the query.
# There is nothing to back out.
""")
request.server.header('text/plain')
if commits:
print '# This page can be saved as a shell script and executed.'
print '# It should be run at the top of your work area. It will update'
print '# your working copy to back out the changes selected by the'
print '# query.'
print
else:
print '# No changes were selected by the query.'
print '# There is nothing to back out.'
return
server_fp.write("""\
# This page can be saved as a shell script and executed.
# It should be run at the top of your work area. It will update
# your working copy to back out the changes selected by the
# query.
""")
for commit in commits:
for fileinfo in commit.files:
if request.roottype == 'cvs':
server_fp.write('cvs update -j %s -j %s %s/%s\n'
% (fileinfo.rev, prev_rev(fileinfo.rev),
fileinfo.dir, fileinfo.file))
print 'cvs update -j %s -j %s %s/%s' \
% (fileinfo.rev, prev_rev(fileinfo.rev),
fileinfo.dir, fileinfo.file)
elif request.roottype == 'svn':
server_fp.write('svn merge -r %s:%s %s/%s\n'
% (fileinfo.rev, prev_rev(fileinfo.rev),
fileinfo.dir, fileinfo.file))
print 'svn merge -r %s:%s %s/%s' \
% (fileinfo.rev, prev_rev(fileinfo.rev),
fileinfo.dir, fileinfo.file)
def view_query(request):
if not is_query_supported(request):
@@ -4290,26 +4169,11 @@ def list_roots(request):
for root in cfg.general.svn_roots.keys():
auth = setup_authorizer(cfg, request.username, root)
try:
repos = vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root],
auth, cfg.utilities,
cfg.options.svn_config_dir)
lastmod = None
if cfg.options.show_roots_lastmod:
try:
repos.open()
youngest_rev = repos.youngest
date, author, msg, revprops, changes = repos.revinfo(youngest_rev)
date_str = make_time_string(date, cfg)
ago = html_time(request, date)
log = format_log(request, msg)
short_log = format_log(request, msg, maxlen=cfg.options.short_log_len)
lastmod = _item(ago=ago, author=author, date=date_str, log=log,
short_log=short_log, rev=str(youngest_rev))
except:
lastmod = None
vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root], auth,
cfg.utilities, cfg.options.svn_config_dir)
except vclib.ReposNotFound:
continue
allroots[root] = [cfg.general.svn_roots[root], 'svn', lastmod]
allroots[root] = [cfg.general.svn_roots[root], 'svn']
# Add the viewable CVS roots
for root in cfg.general.cvs_roots.keys():
@@ -4319,7 +4183,7 @@ def list_roots(request):
cfg.utilities, cfg.options.use_rcsparse)
except vclib.ReposNotFound:
continue
allroots[root] = [cfg.general.cvs_roots[root], 'cvs', None]
allroots[root] = [cfg.general.cvs_roots[root], 'cvs']
return allroots
@@ -4331,8 +4195,8 @@ def expand_root_parents(cfg):
pos = string.rfind(pp, ':')
if pos < 0:
raise debug.ViewVCException(
'The path "%s" in "root_parents" does not include a '
'repository type. Expected "cvs" or "svn".' % (pp))
"The path '%s' in 'root_parents' does not include a "
"repository type." % (pp))
repo_type = string.strip(pp[pos+1:])
pp = os.path.normpath(string.strip(pp[:pos]))
@@ -4347,9 +4211,8 @@ def expand_root_parents(cfg):
cfg.general.svn_roots.update(roots)
else:
raise debug.ViewVCException(
'The path "%s" in "root_parents" has an unrecognized '
'repository type ("%s"). Expected "cvs" or "svn".'
% (pp, repo_type))
"The path '%s' in 'root_parents' has an unrecognized "
"repository type." % (pp))
def find_root_in_parents(cfg, rootname, roottype):
"""Return the rootpath for configured ROOTNAME of ROOTTYPE."""

View File

@@ -27,9 +27,7 @@ numbers, and not literal):
3. Verify that copyright years are correct in both the license-1.html
file and the source code.
4. Update and commit the 'CHANGES' file, using any available crystal
balls or other forward-looking devices to take a stab at the
release date.
4. Update and commit the 'CHANGES' file.
5. Test, test, test! There is no automatic testsuite available. So
just run with permuting different `viewvc.conf' settings... and
@@ -40,70 +38,50 @@ numbers, and not literal):
should exactly reflect what you wish to distribute and dub "the
release".
7. Update your release branch working copy to HEAD.
svn up
8. Edit the file 'lib/viewvc.py' and remove the "-dev" suffix from
7. Edit the file 'lib/viewvc.py' and remove the "-dev" suffix from
__version__. The remainder should be of the form "X.Y.Z", where X,
Y, and Z are positive integers.
Y, and Z are positive integers. Do NOT commit this change.
*** Do NOT commit this change. ***
8. Update your working copy to HEAD, and tag the release:
9. "Peg" the contributed templates externals definition to the
current HEAD revision:
svn update
svn cp -m "Tag the X.Y.Z final release." . \
http://viewvc.tigris.org/svn/viewvc/tags/X.Y.Z
svn pedit svn:externals .
(squeeze "-rBASE_REV", where BASE_REV is the current HEAD revision
number, between 'templates-contrib' and the target URL).
*** Do NOT commit this change. ***
10. Tag the release:
svn cp -m "Tag the X.Y.Z final release." . ^/tags/X.Y.Z
This will create a copy of the release branch, plus your local
modifications to the svn:externals property and lib/viewvc.py
file, to the tag location.
11. Revert the changes in your working copy.
svn revert -R .
12. Go into an empty directory and run the 'make-release' script:
9. Go into an empty directory and run the 'make-release' script:
tools/make-release viewvc-X.Y.Z tags/X.Y.Z
13. Verify the archive files:
10. Verify the archive files:
- do they have a LICENSE.html file?
- do they have necessary include documentation?
- do they *not* have unnecessary stuff?
- do they install and work correctly?
14. Upload the created archive files (tar.gz and zip) into the Files
11. Upload the created archive files (tar.gz and zip) into the Files
and Documents section of the Tigris.org project, and modify the
CHECKSUMS document there accordingly. Also, drop a copy of the
archive files into the root directory of the viewvc.org website
(unversioned).
15. On trunk, update the websites (both the viewvc.org/ and www/ ones)
12. On trunk, update the websites (both the viewvc.org/ and www/ ones)
to refer to the new release files, and copy the CHANGES for the
new release into trunk's CHANGES file.
16. Edit the file 'lib/viewvc.py' again, incrementing the patch number
assigned to the __version__ variable. Add a new empty block in
the branch's CHANGES file. Commit your changes:
13. Edit the file 'lib/viewvc.py' again, re-adding the "-dev" suffix
and incrementing the patch number assigned to the __version__
variable, and add a new empty block in the branch's CHANGES file,
and commit:
svn ci -m "Begin a new release cycle."
17. Edit the Issue Tracker configuration options, adding a new Version
14. Edit the Issue Tracker configuration options, adding a new Version
for the just-released one, and a new Milestone for the next patch
(and possibly, minor or major) release. (For the Milestone sort
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
18. Send to the announce@ list a message explaining all the cool new
features, and post similar announcements to other places interested
in this sort of stuff, such as Freshmeat (http://www.freshmeat.net).
15. Write an announcement explaining all the cool new features and
post it to the announce@ list, to the project's News area, and to
other places interested in this sort of stuff, such as Freshmeat
(http://www.freshmeat.net).

View File

@@ -94,6 +94,9 @@ form { margin: 0; }
.vc_properties {
margin: 1em 0;
}
.vc_properties h2 {
font-size: 115%;
}
/*** File Content Markup Styles ***/

View File

@@ -5,7 +5,7 @@
<table>
<tr>
<td>[if-any cfg.general.address]<address><a href="mailto:[cfg.general.address]">[cfg.general.address]</a></address>[else]&nbsp;[end]</td>
<td style="text-align: right;">[if-any help_href]<strong><a href="[help_href]">ViewVC Help</a></strong>[else]&nbsp;[end]</td>
<td style="text-align: right;"><strong><a href="[help_href]">ViewVC Help</a></strong></td>
</tr>
<tr>
<td>Powered by <a href="http://viewvc.tigris.org/">ViewVC [vsn]</a></td>

View File

@@ -5,7 +5,7 @@
<head>
<title>[if-any rootname][[][rootname]][else]ViewVC[end] [page_title]</title>
<meta name="generator" content="ViewVC [vsn]" />
<link rel="shortcut icon" href="[docroot]/images/favicon.ico" />
<link rel="shortcut icon" href="[docroot]/images/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
[if-any rss_href]<link rel="alternate" type="application/rss+xml" title="RSS [[][rootname]][where]" href="[rss_href]" />[end]
</head>

View File

@@ -4,13 +4,13 @@
<!-- ViewVC :: http://www.viewvc.org/ -->
<head>
<title>Checkin Database Query</title>
[if-any docroot]<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />[end]
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
</head>
<body>
[# setup page definitions]
[define help_href][if-any docroot][docroot]/help_query.html[end][end]
[define help_href][docroot]/help_query.html[end]
[# end]
<p>
@@ -202,21 +202,21 @@
</td>
[# uncommment, if you want a separate Description column:
{if-index commits.files first}
{if-index commits.files first{
<td style="vertical-align:top;" rowspan="{commits.num_files}">
{if-any commits.log}{commits.log}{else}&nbsp;{end}
{commits.log}
</td>
{end}
(substitute brackets for the braces)
]
</tr>
[# and also take the following out in the "Description column" case:]
[# and also take the following out in the "Description column"-case:]
[if-index commits.files last]
<tr class="vc_row_[if-index commits even]even[else]odd[end]">
<td>&nbsp;</td>
<td colspan="5"><strong>Log:</strong><br />
<pre class="vc_log">[if-any commits.log][commits.log][else]&nbsp;[end]</pre></td>
<pre class="vc_log">[commits.log]</pre></td>
</tr>
[end]
[# ---]

View File

@@ -6,7 +6,6 @@
[include "include/header.ezt" "revision"]
<hr />
<form method="get" action="[jump_rev_action]">
<table cellspacing="1" cellpadding="2" style="width: auto;">
<tr align="left">
@@ -43,7 +42,7 @@
<hr />
<h2>Changed paths</h2>
<p><strong>Changed paths:</strong></p>
<table cellspacing="1" cellpadding="2">
<thead>
@@ -78,5 +77,4 @@
</tbody>
</table>
[include "include/props.ezt"]
[include "include/footer.ezt"]

View File

@@ -9,12 +9,6 @@
<thead>
<tr>
<th class="vc_header_sort">Name</th>
[is cfg.options.show_roots_lastmod "1"]
<th class="vc_header">Revision</th>
<th class="vc_header">Age</th>
<th class="vc_header">Author</th>
<th class="vc_header">Log</th>
[end]
</tr>
</thead>
@@ -26,12 +20,6 @@
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
[roots.name]</a>
</td>
[is cfg.options.show_roots_lastmod "1"]
<td style="width:20">&nbsp;[roots.rev]</td>
<td style="width:20">&nbsp;[roots.ago]</td>
<td style="width:20">&nbsp;[roots.author]</td>
<td style="width:20">&nbsp;[roots.short_log]</td>
[end]
</tr>
[end]
</tbody>

View File

@@ -7,11 +7,11 @@
<description>[is roottype "svn"]Subversion[else]CVS[end] commits to the[if-any where] [where] directory of the[end] [rootname] repository</description>
[for commits]<item>
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [format "xml"][commits.short_log][end]</title>
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [commits.short_log]</title>
[if-any commits.rss_url]<link>[commits.rss_url]</link>[end]
<author>[commits.author]</author>
<pubDate>[if-any commits.rss_date][commits.rss_date][else](unknown date)[end]</pubDate>
<description>&#x3C;pre&#x3E;[format "xml"][format "html"][commits.log][end][end]&#x3C;/pre&#x3E;</description>
<description>&lt;pre&gt;[format "xml"][commits.log][end]&lt;/pre&gt;</description>
</item>[end]
</channel>
</rss>

View File

@@ -49,10 +49,6 @@ CLEAN_MODE = None
FILE_INFO_LIST = [
("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0),
("bin/cgi/query.cgi", "bin/cgi/query.cgi", 0755, 1, 0, 0),
("bin/wsgi/viewvc.wsgi", "bin/wsgi/viewvc.wsgi", 0755, 1, 0, 0),
("bin/wsgi/viewvc.fcgi", "bin/wsgi/viewvc.fcgi", 0755, 1, 0, 0),
("bin/wsgi/query.wsgi", "bin/wsgi/query.wsgi", 0755, 1, 0, 0),
("bin/wsgi/query.fcgi", "bin/wsgi/query.fcgi", 0755, 1, 0, 0),
("bin/mod_python/viewvc.py", "bin/mod_python/viewvc.py", 0755, 1, 0, 0),
("bin/mod_python/query.py", "bin/mod_python/query.py", 0755, 1, 0, 0),
("bin/mod_python/handler.py", "bin/mod_python/handler.py", 0755, 1, 0, 0),