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

Compare commits

..

2 Commits

Author SHA1 Message Date
cmpilato
b97d2d49fa 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.4@2451 8cb11bc2-c004-0410-86c3-e597b4017df7
2010-09-08 16:51:21 +00:00
cmpilato
3f43d12ee4 Tag the 1.1.4 final release.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.1.4@2333 8cb11bc2-c004-0410-86c3-e597b4017df7
2010-03-10 21:25:25 +00:00
69 changed files with 1483 additions and 3677 deletions

133
CHANGES
View File

@@ -1,136 +1,3 @@
Version 1.1.20 (released 24-Apr-2013)
* fix tab-to-space handling regression in markup view
* fix regression in root lookup handling (issue #526)
Version 1.1.19 (released 22-Apr-2013)
* improve root lookup performance (issue #523)
* new 'max_filesize_kbytes' config option and handling (issue #524)
* tarball generation improvements:
- preserve Subversion symlinks in generated tarballs (issue #487)
- reduce memory usage of tarball generation logic
- fix double compression of generated tarballs (issue #525)
* file content handling improvements:
- expanded support for encoding detection and transcoding (issue #11)
- fix tab-to-space conversion bugs in markup, annotate, and diff views
- fix handling of trailing whitespace in diff view
* add support for timestamp display in ISO8601 format (issue #46)
Version 1.1.18 (released 28-Feb-2013)
* fix exception raised by BDB-backed SVN repositories (issue #519)
* hide revision-less files when rcsparse is in use
* include branchpoints in branch views using rcsparse (issue #347)
* miscellaneous cvsdb improvements:
- add --port option to make-database (issue #521)
- explicitly name columns in queries (issue #522)
- update MySQL syntax to avoid discontinued "TYPE=" terms
Version 1.1.17 (released 25-Oct-2012)
* fix exception caused by uninitialized variable usage (issue #516)
Version 1.1.16 (released 24-Oct-2012)
* security fix: escape "extra" diff info to avoid XSS attack (issue #515)
* add 'binary_mime_types' configuration option and handling (issue #510)
* fix 'select for diffs' persistence across log pages (issue #512)
* remove lock status and filesize check on directories in remote SVN views
* fix bogus 'Annotation of' page title for non-annotated view (issue #514)
Version 1.1.15 (released 22-Jun-2012)
* security fix: complete authz support for remote SVN views (issue #353)
* security fix: log msg leak in SVN revision view with unreadable copy source
* fix several instances of incorrect information in remote SVN views
* increase performance of some revision metadata lookups in remote SVN views
* fix RSS feed regression introduced in 1.1.14
Version 1.1.14 (released 12-Jun-2012)
* fix annotation of svn files with non-URI-safe paths (issue #504)
* handle file:/// Subversion rootpaths as local roots (issue #446)
* fix bug caused by trying to case-normalize anon usernames (issue #505)
* speed up log handling by reusing tokenization results (issue #506)
* add support for custom revision log markup rules (issue #246)
Version 1.1.13 (released 23-Jan-2012)
* fix svndbadmin failure on deleted paths under Subversion 1.7 (issue #499)
* fix annotation of files in svn roots with non-URI-safe paths
* fix stray annotation warning in markup display of images
* more gracefully handle attempts to display binary content (issue #501)
Version 1.1.12 (released 03-Nov-2011)
* fix path display in patch and certain diff views (issue #485)
* fix broken cvsdb glob searching (issue 486)
* allow svn revision specifiers to have leading r's (issue #441, #448)
* allow environmental override of configuration location (issue #494)
* fix exception HTML-escaping non-string data under WSGI (issue #454)
* add links to root logs from roots view (issue #470)
* use Pygments lexer-guessing functionality (issue #495)
Version 1.1.11 (released 17-May-2011)
* security fix: remove user-reachable override of cvsdb row limit
* fix broken standalone.py -c and -d options handling
* add --help option to standalone.py
* fix stack trace when asked to checkout a directory (issue #478)
* improve memory usage and speed of revision log markup (issue #477)
* fix broken annotation view in CVS keyword-bearing files (issue #479)
* warn users when query results are incomplete (issue #433)
* avoid parsing errors on RCS newphrases in the admin section (issue #483)
* make rlog parsing code more robust in certain error cases (issue #444)
Version 1.1.10 (released 15-Mar-2011)
* fix stack trace in Subversion revision info logic (issue #475, issue #476)
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
* 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) Version 1.1.4 (released 10-Mar-2010)
* security fix: escape user-provided query form input to avoid XSS attack * security fix: escape user-provided query form input to avoid XSS attack

95
INSTALL
View File

@@ -19,7 +19,7 @@ Congratulations on getting this far. :-)
For CVS Support: 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/) (http://www.python.org/)
* RCS, Revision Control System * RCS, Revision Control System
(http://www.cs.purdue.edu/homes/trinkle/RCS/) (http://www.cs.purdue.edu/homes/trinkle/RCS/)
@@ -30,11 +30,11 @@ Congratulations on getting this far. :-)
For Subversion Support: For Subversion Support:
* Python 2.0 or later (sorry, no 3.x support yet) * Python 2.0 or later
(http://www.python.org/) (http://www.python.org/)
* Subversion, Version Control System, 1.3.1 or later * Subversion, Version Control System, 1.3.1 or later
(binary installation and Python bindings) (binary installation and Python bindings)
(http://subversion.apache.org/) (http://subversion.tigris.org/)
Optional: Optional:
@@ -176,24 +176,7 @@ APACHE CONFIGURATION
or /etc/local. Use the vendor documentation or the find utility if or /etc/local. Use the vendor documentation or the find utility if
in doubt. in doubt.
2) Depending on how your Apache configuration is setup by default, you 2) Configure Apache to expose ViewVC to users at the URL of your choice.
might need to explicitly allow high-level access to the ViewVC
install location.
<Directory <VIEWVC_INSTALLATION_DIRECTORY>>
Order allow,deny
Allow from all
</Directory>
For example, if ViewVC is installed in /usr/local/viewvc-1.0 on
your system:
<Directory /usr/local/viewvc-1.0>
Order allow,deny
Allow from all
</Directory>
3) Configure Apache to expose ViewVC to users at the URL of your choice.
ViewVC provides several different ways to do this. Choose one of ViewVC provides several different ways to do this. Choose one of
the following methods: the following methods:
@@ -219,10 +202,6 @@ APACHE CONFIGURATION
<VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi <VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
to the /cgi-bin/ directory configured in your httpd.conf file. to the /cgi-bin/ directory configured in your httpd.conf file.
You can override configuration file location using:
SetEnv VIEWVC_CONF_PATHNAME /etc/viewvc.conf
------------------------------------------ ------------------------------------------
METHOD C: CGI mode in ExecCGI'd directory METHOD C: CGI mode in ExecCGI'd directory
------------------------------------------ ------------------------------------------
@@ -234,9 +213,9 @@ APACHE CONFIGURATION
Options +ExecCGI Options +ExecCGI
AddHandler cgi-script .cgi 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" 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) METHOD D: Using mod_python (if installed)
@@ -249,59 +228,16 @@ APACHE CONFIGURATION
"AllowOverride FileInfo Options" are enabled for the directory "AllowOverride FileInfo Options" are enabled for the directory
you copied the files to. you copied the files to.
You can override configuration file location using:
SetEnv VIEWVC_CONF_PATHNAME /etc/viewvc.conf
Note: If you are using Mod_Python under Apache 1.3 the tarball generation Note: If you are using Mod_Python under Apache 1.3 the tarball generation
feature may not work because it uses multithreading. This works fine feature may not work because it uses multithreading. This works fine
under Apache 2. under Apache 2.
---------------------------------------- 3) Restart Apache.
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 The commands to do this vary. "httpd -k restart" and "apache -k
WSGIScriptAlias /query <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/query.wsgi restart" are two common variants. On RedHat Linux it is done using
the command "/sbin/service httpd restart" and on SuSE Linux it is
You'll probably also need the following directive because of the done with "rcapache restart". Other systems use "apachectl restart".
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
4) [Optional] Add access control. 4) [Optional] Add access control.
@@ -324,14 +260,7 @@ APACHE CONFIGURATION
http://<server_name>/viewvc/~checkout~/<module_name> http://<server_name>/viewvc/~checkout~/<module_name>
http://<server_name>/viewvc/<module_name>.tar.gz?view=tar http://<server_name>/viewvc/<module_name>.tar.gz?view=tar
5) Restart Apache. 5) Optional: Protect your ViewVC instance from server-whacking webcrawlers.
The commands to do this vary. "httpd -k restart" and "apache -k
restart" are two common variants. On RedHat Linux it is done using
the command "/sbin/service httpd restart" and on SuSE Linux it is
done with "rcapache restart". Other systems use "apachectl restart".
6) Optional: Protect your ViewVC instance from server-whacking webcrawlers.
As ViewVC is a web-based application which each page containing various As ViewVC is a web-based application which each page containing various
links to other pages and views, you can expect your server's performance links to other pages and views, you can expect your server's performance

View File

@@ -15,7 +15,7 @@
<blockquote> <blockquote>
<p><strong>Copyright &copy; 1999-2013 The ViewCVS Group. All rights <p><strong>Copyright &copy; 1999-2009 The ViewCVS Group. All rights
reserved.</strong></p> reserved.</strong></p>
<p>By using ViewVC, you agree to the terms and conditions set forth <p>By using ViewVC, you agree to the terms and conditions set forth
@@ -60,10 +60,6 @@
<li>April 10, 2007 &mdash; copyright years updated</li> <li>April 10, 2007 &mdash; copyright years updated</li>
<li>February 22, 2008 &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 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>
<li>January 23, 2012 &mdash; copyright years updated</li>
<li>January 04, 2013 &mdash; copyright years updated</li>
</ul> </ul>
</body> </body>

View File

@@ -3,7 +3,7 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -54,10 +54,7 @@ import query
server = sapi.AspServer(Server, Request, Response, Application) server = sapi.AspServer(Server, Request, Response, Application)
try: try:
cfg = viewvc.load_config(CONF_PATHNAME, server) cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc_base_url = cfg.query.viewvc_base_url query.main(server, cfg, "viewvc.asp")
if viewvc_base_url is None:
viewvc_base_url = "viewvc.asp"
query.main(server, cfg, viewvc_base_url)
finally: finally:
s.close() s.close()

View File

@@ -3,7 +3,7 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -54,7 +54,4 @@ import query
server = sapi.CgiServer() server = sapi.CgiServer()
cfg = viewvc.load_config(CONF_PATHNAME, server) cfg = viewvc.load_config(CONF_PATHNAME, server)
viewvc_base_url = cfg.query.viewvc_base_url query.main(server, cfg, "viewvc.cgi")
if viewvc_base_url is None:
viewvc_base_url = "viewvc.cgi"
query.main(server, cfg, viewvc_base_url)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -40,7 +40,7 @@ CREATE TABLE branches (
branch varchar(64) binary DEFAULT '' NOT NULL, branch varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE branch (branch) UNIQUE branch (branch)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS checkins; DROP TABLE IF EXISTS checkins;
CREATE TABLE checkins ( CREATE TABLE checkins (
@@ -63,7 +63,7 @@ CREATE TABLE checkins (
KEY dirid (dirid), KEY dirid (dirid),
KEY fileid (fileid), KEY fileid (fileid),
KEY branchid (branchid) KEY branchid (branchid)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS descs; DROP TABLE IF EXISTS descs;
CREATE TABLE descs ( CREATE TABLE descs (
@@ -72,7 +72,7 @@ CREATE TABLE descs (
hash bigint(20) DEFAULT '0' NOT NULL, hash bigint(20) DEFAULT '0' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
KEY hash (hash) KEY hash (hash)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS dirs; DROP TABLE IF EXISTS dirs;
CREATE TABLE dirs ( CREATE TABLE dirs (
@@ -80,7 +80,7 @@ CREATE TABLE dirs (
dir varchar(255) binary DEFAULT '' NOT NULL, dir varchar(255) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE dir (dir) UNIQUE dir (dir)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS files; DROP TABLE IF EXISTS files;
CREATE TABLE files ( CREATE TABLE files (
@@ -88,7 +88,7 @@ CREATE TABLE files (
file varchar(255) binary DEFAULT '' NOT NULL, file varchar(255) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE file (file) UNIQUE file (file)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS people; DROP TABLE IF EXISTS people;
CREATE TABLE people ( CREATE TABLE people (
@@ -96,7 +96,7 @@ CREATE TABLE people (
who varchar(128) binary DEFAULT '' NOT NULL, who varchar(128) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE who (who) UNIQUE who (who)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS repositories; DROP TABLE IF EXISTS repositories;
CREATE TABLE repositories ( CREATE TABLE repositories (
@@ -104,7 +104,7 @@ CREATE TABLE repositories (
repository varchar(64) binary DEFAULT '' NOT NULL, repository varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE repository (repository) UNIQUE repository (repository)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS tags;
CREATE TABLE tags ( CREATE TABLE tags (
@@ -118,7 +118,7 @@ CREATE TABLE tags (
KEY dirid (dirid), KEY dirid (dirid),
KEY fileid (fileid), KEY fileid (fileid),
KEY branchid (branchid) KEY branchid (branchid)
) ENGINE=MyISAM; ) TYPE=MyISAM;
""" """
## ------------------------------------------------------------------------ ## ------------------------------------------------------------------------
@@ -132,7 +132,7 @@ CREATE TABLE branches (
branch varchar(64) binary DEFAULT '' NOT NULL, branch varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE branch (branch) UNIQUE branch (branch)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS commits; DROP TABLE IF EXISTS commits;
CREATE TABLE commits ( CREATE TABLE commits (
@@ -156,7 +156,7 @@ CREATE TABLE commits (
KEY fileid (fileid), KEY fileid (fileid),
KEY branchid (branchid), KEY branchid (branchid),
KEY descid (descid) KEY descid (descid)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS descs; DROP TABLE IF EXISTS descs;
CREATE TABLE descs ( CREATE TABLE descs (
@@ -165,7 +165,7 @@ CREATE TABLE descs (
hash bigint(20) DEFAULT '0' NOT NULL, hash bigint(20) DEFAULT '0' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
KEY hash (hash) KEY hash (hash)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS dirs; DROP TABLE IF EXISTS dirs;
CREATE TABLE dirs ( CREATE TABLE dirs (
@@ -173,7 +173,7 @@ CREATE TABLE dirs (
dir varchar(255) binary DEFAULT '' NOT NULL, dir varchar(255) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE dir (dir) UNIQUE dir (dir)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS files; DROP TABLE IF EXISTS files;
CREATE TABLE files ( CREATE TABLE files (
@@ -181,7 +181,7 @@ CREATE TABLE files (
file varchar(255) binary DEFAULT '' NOT NULL, file varchar(255) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE file (file) UNIQUE file (file)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS people; DROP TABLE IF EXISTS people;
CREATE TABLE people ( CREATE TABLE people (
@@ -189,7 +189,7 @@ CREATE TABLE people (
who varchar(128) binary DEFAULT '' NOT NULL, who varchar(128) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE who (who) UNIQUE who (who)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS repositories; DROP TABLE IF EXISTS repositories;
CREATE TABLE repositories ( CREATE TABLE repositories (
@@ -197,7 +197,7 @@ CREATE TABLE repositories (
repository varchar(64) binary DEFAULT '' NOT NULL, repository varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE repository (repository) UNIQUE repository (repository)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS tags; DROP TABLE IF EXISTS tags;
CREATE TABLE tags ( CREATE TABLE tags (
@@ -211,7 +211,7 @@ CREATE TABLE tags (
KEY dirid (dirid), KEY dirid (dirid),
KEY fileid (fileid), KEY fileid (fileid),
KEY branchid (branchid) KEY branchid (branchid)
) ENGINE=MyISAM; ) TYPE=MyISAM;
DROP TABLE IF EXISTS metadata; DROP TABLE IF EXISTS metadata;
CREATE TABLE metadata ( CREATE TABLE metadata (
@@ -219,7 +219,7 @@ CREATE TABLE metadata (
value text, value text,
PRIMARY KEY (name), PRIMARY KEY (name),
UNIQUE name (name) UNIQUE name (name)
) ENGINE=MyISAM; ) TYPE=MyISAM;
INSERT INTO metadata (name, value) VALUES ('version', '1'); INSERT INTO metadata (name, value) VALUES ('version', '1');
""" """
@@ -245,10 +245,6 @@ options.) This script will use the 'mysql' program to create the
database for you. You will then need to set the appropriate database for you. You will then need to set the appropriate
parameters in the [cvsdb] section of your viewvc.conf file. parameters in the [cvsdb] section of your viewvc.conf file.
NOTE: If a hostname or port is supplied at the command line or during
interactive prompting, this script will pass '--protocol=TCP' to
'mysql'.
Options: Options:
--dbname=ARG Use ARG as the ViewVC database name to create. --dbname=ARG Use ARG as the ViewVC database name to create.
@@ -257,8 +253,7 @@ Options:
--help Show this usage message. --help Show this usage message.
--hostname=ARG Use ARG as the hostname for the MySQL connection. --hostname=ARG Use ARG as the hostname for the MySQL connection.
[Default: localhost]
--port=ARG Use ARG as the port for the MySQL connection.
--password=ARG Use ARG as the password for the MySQL connection. --password=ARG Use ARG as the password for the MySQL connection.
@@ -278,11 +273,10 @@ Options:
if __name__ == "__main__": if __name__ == "__main__":
try: try:
# Parse the command-line options, if any. # Parse the command-line options, if any.
dbname = version = hostname = port = username = password = None dbname = version = hostname = username = password = None
opts, args = getopt.getopt(sys.argv[1:], '', [ 'dbname=', opts, args = getopt.getopt(sys.argv[1:], '', [ 'dbname=',
'help', 'help',
'hostname=', 'hostname=',
'port=',
'password=', 'password=',
'username=', 'username=',
'version=', 'version=',
@@ -296,8 +290,6 @@ if __name__ == "__main__":
dbname = value dbname = value
elif name == '--hostname': elif name == '--hostname':
hostname = value hostname = value
elif name == '--port':
port = value
elif name == '--username': elif name == '--username':
username = value username = value
elif name == '--password': elif name == '--password':
@@ -310,9 +302,7 @@ if __name__ == "__main__":
# Prompt for information not provided via command-line options. # Prompt for information not provided via command-line options.
if hostname is None: if hostname is None:
hostname = raw_input("MySQL Hostname (leave blank for default): ") hostname = raw_input("MySQL Hostname [default: localhost]: ") or ""
if port is None:
port = raw_input("MySQL Port (leave blank for default): ")
if username is None: if username is None:
username = raw_input("MySQL User: ") username = raw_input("MySQL User: ")
if password is None: if password is None:
@@ -320,7 +310,7 @@ if __name__ == "__main__":
if dbname is None: if dbname is None:
dbname = raw_input("ViewVC Database Name [default: ViewVC]: ") or "ViewVC" dbname = raw_input("ViewVC Database Name [default: ViewVC]: ") or "ViewVC"
# Create the database. # Create the database
dscript = string.replace(DATABASE_SCRIPT_COMMON, "<dbname>", dbname) dscript = string.replace(DATABASE_SCRIPT_COMMON, "<dbname>", dbname)
if version == "1.0": if version == "1.0":
print BONSAI_COMPAT print BONSAI_COMPAT
@@ -328,22 +318,16 @@ if __name__ == "__main__":
else: else:
dscript = dscript + DATABASE_SCRIPT_VERSION_1 dscript = dscript + DATABASE_SCRIPT_VERSION_1
# Calculate command arguments. host_option = hostname and "--host=%s" % (hostname) or ""
cmd_args = "--user=%s --password=%s" % (username, password)
if hostname or port:
cmd_args = cmd_args + " --protocol=TCP"
if hostname:
cmd_args = cmd_args + " --host=%s" % (hostname)
if port:
cmd_args = cmd_args + " --port=%s" % (port)
if sys.platform == "win32": if sys.platform == "win32":
cmd = "mysql %s" % (cmd_args) cmd = "mysql --user=%s --password=%s %s "\
% (username, password, host_option)
mysql = os.popen(cmd, "w") # popen2.Popen3 is not provided on windows mysql = os.popen(cmd, "w") # popen2.Popen3 is not provided on windows
mysql.write(dscript) mysql.write(dscript)
status = mysql.close() status = mysql.close()
else: else:
cmd = "{ mysql %s ; } 2>&1" % (cmd_args) cmd = "{ mysql --user=%s --password=%s %s ; } 2>&1" \
% (username, password, host_option)
pipes = popen2.Popen3(cmd) pipes = popen2.Popen3(cmd)
pipes.tochild.write(dscript) pipes.tochild.write(dscript)
pipes.tochild.close() pipes.tochild.close()

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # 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): def index(req):
server = sapi.ModPythonServer(req) server = sapi.ModPythonServer(req)
try: try:
viewvc_base_url = cfg.query.viewvc_base_url query.main(server, cfg, "viewvc.py")
if viewvc_base_url is None:
viewvc_base_url = "viewvc.py"
query.main(server, cfg, viewvc_base_url)
finally: finally:
server.close() server.close()

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -41,7 +41,6 @@ import urllib
import rfc822 import rfc822
import socket import socket
import select import select
import base64
import BaseHTTPServer import BaseHTTPServer
if LIBRARY_DIR: if LIBRARY_DIR:
@@ -54,39 +53,21 @@ import viewvc
import compat; compat.for_standalone() import compat; compat.for_standalone()
# The 'crypt' module is only available on Unix platforms. We'll try
# to use 'fcrypt' if it's available (for more information, see
# http://carey.geek.nz/code/python-fcrypt/).
has_crypt = False
try:
import crypt
has_crypt = True
def _check_passwd(user_passwd, real_passwd):
return real_passwd == crypt.crypt(user_passwd, real_passwd[:2])
except ImportError:
try:
import fcrypt
has_crypt = True
def _check_passwd(user_passwd, real_passwd):
return real_passwd == fcrypt.crypt(user_passwd, real_passwd[:2])
except ImportError:
def _check_passwd(user_passwd, real_passwd):
return False
class Options: class Options:
port = 49152 # default TCP/IP port used for the server port = 49152 # default TCP/IP port used for the server
start_gui = 0 # No GUI unless requested.
daemon = 0 # stay in the foreground by default
repositories = {} # use default repositories specified in config repositories = {} # use default repositories specified in config
host = sys.platform == 'mac' and '127.0.0.1' or 'localhost' if sys.platform == 'mac':
host = '127.0.0.1'
else:
host = 'localhost'
script_alias = 'viewvc' script_alias = 'viewvc'
config_file = None config_file = None
htpasswd_file = None
# --- web browser interface: ----------------------------------------------
class StandaloneServer(sapi.CgiServer): class StandaloneServer(sapi.CgiServer):
"""Custom sapi interface that uses a BaseHTTPRequestHandler HANDLER
to generate output."""
def __init__(self, handler): def __init__(self, handler):
sapi.CgiServer.__init__(self, inheritableOut = sys.platform != "win32") sapi.CgiServer.__init__(self, inheritableOut = sys.platform != "win32")
self.handler = handler self.handler = handler
@@ -112,130 +93,69 @@ class StandaloneServer(sapi.CgiServer):
self.handler.end_headers() self.handler.end_headers()
class NotViewVCLocationException(Exception): def serve(host, port, callback=None):
"""The request location was not aimed at ViewVC.""" """start a HTTP server on the given port. call 'callback' when the
pass server is ready to serve"""
class ViewVC_Handler(BaseHTTPServer.BaseHTTPRequestHandler):
class AuthenticationException(Exception):
"""Authentication requirements have not been met."""
pass
class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Custom HTTP request handler for ViewVC."""
def do_GET(self): def do_GET(self):
"""Serve a GET request.""" """Serve a GET request."""
self.handle_request('GET') if not self.path or self.path == "/":
self.redirect()
elif self.is_viewvc():
try:
self.run_viewvc()
except IOError:
# ignore IOError: [Errno 32] Broken pipe
pass
else:
self.send_error(404)
def do_POST(self): def do_POST(self):
"""Serve a POST request.""" """Serve a POST request."""
self.handle_request('POST') if self.is_viewvc():
def handle_request(self, method):
"""Handle a request of type METHOD."""
try:
self.run_viewvc() self.run_viewvc()
except NotViewVCLocationException: else:
# If the request was aimed at the server root, but there's a self.send_error(501, "Can only POST to %s"
# non-empty script_alias, automatically redirect to the % (options.script_alias))
# script_alias. Otherwise, just return a 404 and shrug.
if (not self.path or self.path == "/") and options.script_alias: def is_viewvc(self):
"""Check whether self.path is, or is a child of, the ScriptAlias"""
if self.path == '/' + options.script_alias:
return 1
if self.path[:len(options.script_alias)+2] == \
'/' + options.script_alias + '/':
return 1
if self.path[:len(options.script_alias)+2] == \
'/' + options.script_alias + '?':
return 1
return 0
def redirect(self):
"""redirect the browser to the viewvc URL"""
new_url = self.server.url + options.script_alias + '/' new_url = self.server.url + options.script_alias + '/'
self.send_response(301, "Moved Permanently") self.send_response(301, "Moved (redirection follows)")
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.send_header("Location", new_url) self.send_header("Location", new_url)
self.end_headers() self.end_headers()
self.wfile.write("""<html> self.wfile.write("""<html>
<head> <head>
<meta http-equiv="refresh" content="10; url=%s" /> <meta http-equiv="refresh" content="1; URL=%s">
<title>Moved Temporarily</title>
</head> </head>
<body> <body>
<h1>Redirecting to ViewVC</h1> <h1>Redirection to <a href="%s">ViewVC</a></h1>
<p>You will be automatically redirected to <a href="%s">ViewVC</a>. Wait a second. You will be automatically redirected to <b>ViewVC</b>.
If this doesn't work, please click on the link above.</p> If this doesn't work, please click on the link above.
</body> </body>
</html> </html>
""" % (new_url, new_url)) """ % tuple([new_url]*2))
else:
self.send_error(404)
except IOError: # ignore IOError: [Errno 32] Broken pipe
pass
except AuthenticationException:
self.send_response(401, "Unauthorized")
self.send_header("WWW-Authenticate", 'Basic realm="ViewVC"')
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write("""<html>
<head>
<title>Authentication failed</title>
</head>
<body>
<h1>Authentication failed</h1>
<p>Authentication has failed. Please retry with the correct username
and password.</p>
</body>
</html>""")
def is_viewvc(self):
"""Check whether self.path is, or is a child of, the ScriptAlias"""
if not options.script_alias:
return 1
if self.path == '/' + options.script_alias:
return 1
alias_len = len(options.script_alias)
if self.path[:alias_len+2] == '/' + options.script_alias + '/':
return 1
if self.path[:alias_len+2] == '/' + options.script_alias + '?':
return 1
return 0
def validate_password(self, htpasswd_file, username, password):
"""Compare USERNAME and PASSWORD against HTPASSWD_FILE."""
try:
lines = open(htpasswd_file, 'r').readlines()
for line in lines:
file_user, file_pass = string.split(line.rstrip(), ':', 1)
if username == file_user:
return _check_passwd(password, file_pass)
except:
pass
return False
def run_viewvc(self): def run_viewvc(self):
"""Run ViewVC to field a single request.""" """This is a quick and dirty cut'n'rape from Python's
standard library module CGIHTTPServer."""
### Much of this is adapter from Python's standard library scriptname = '/' + options.script_alias
### module CGIHTTPServer. assert string.find(self.path, scriptname) == 0
# Is this request even aimed at ViewVC? If not, complain.
if not self.is_viewvc():
raise NotViewVCLocationException()
# If htpasswd authentication is enabled, try to authenticate the user.
self.username = None
if options.htpasswd_file:
authn = self.headers.get('authorization')
if not authn:
raise AuthenticationException()
try:
kind, data = string.split(authn, ' ', 1)
if kind == 'Basic':
data = base64.b64decode(data)
username, password = string.split(data, ':', 1)
except:
raise AuthenticationException()
if not self.validate_password(options.htpasswd_file, username, password):
raise AuthenticationException()
self.username = username
# Setup the environment in preparation of executing ViewVC's core code.
env = os.environ
scriptname = options.script_alias and '/' + options.script_alias or ''
viewvc_url = self.server.url[:-1] + scriptname viewvc_url = self.server.url[:-1] + scriptname
rest = self.path[len(scriptname):] rest = self.path[len(scriptname):]
i = string.rfind(rest, '?') i = string.rfind(rest, '?')
@@ -243,7 +163,8 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
rest, query = rest[:i], rest[i+1:] rest, query = rest[:i], rest[i+1:]
else: else:
query = '' query = ''
# sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n")
env = os.environ
# Since we're going to modify the env in the parent, provide empty # Since we're going to modify the env in the parent, provide empty
# values to override previously set values # values to override previously set values
for k in env.keys(): for k in env.keys():
@@ -253,7 +174,6 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
'HTTP_USER_AGENT', 'HTTP_COOKIE'): 'HTTP_USER_AGENT', 'HTTP_COOKIE'):
if env.has_key(k): if env.has_key(k):
env[k] = "" env[k] = ""
# XXX Much of the following could be prepared ahead of time! # XXX Much of the following could be prepared ahead of time!
env['SERVER_SOFTWARE'] = self.version_string() env['SERVER_SOFTWARE'] = self.version_string()
env['SERVER_NAME'] = self.server.server_name env['SERVER_NAME'] = self.server.server_name
@@ -271,8 +191,9 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
if host != self.client_address[0]: if host != self.client_address[0]:
env['REMOTE_HOST'] = host env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0] env['REMOTE_ADDR'] = self.client_address[0]
if self.username: # AUTH_TYPE
env['REMOTE_USER'] = self.username # REMOTE_USER
# REMOTE_IDENT
if self.headers.typeheader is None: if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type env['CONTENT_TYPE'] = self.headers.type
else: else:
@@ -296,9 +217,8 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
etag = self.headers.getheader('if-none-match') etag = self.headers.getheader('if-none-match')
if etag: if etag:
env['HTTP_IF_NONE_MATCH'] = etag env['HTTP_IF_NONE_MATCH'] = etag
# AUTH_TYPE
# REMOTE_IDENT
# XXX Other HTTP_* headers # XXX Other HTTP_* headers
decoded_query = string.replace(query, '+', ' ')
# Preserve state, because we execute script in current process: # Preserve state, because we execute script in current process:
save_argv = sys.argv save_argv = sys.argv
@@ -316,8 +236,7 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# #
# But we no longer use pipe_cmds. So at the very least, the # But we no longer use pipe_cmds. So at the very least, the
# comment is stale. Is the code okay, though? # comment is stale. Is the code okay, though?
if sys.platform != "win32": if sys.platform != "win32": save_realstdout = os.dup(1)
save_realstdout = os.dup(1)
try: try:
try: try:
sys.stdout = self.wfile sys.stdout = self.wfile
@@ -339,14 +258,13 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
else: else:
self.log_error("ViewVC exited ok") self.log_error("ViewVC exited ok")
class ViewVCHTTPServer(BaseHTTPServer.HTTPServer): class ViewVC_Server(BaseHTTPServer.HTTPServer):
"""Customized HTTP server for ViewVC."""
def __init__(self, host, port, callback): def __init__(self, host, port, callback):
self.address = (host, port) self.address = (host, port)
self.url = 'http://%s:%d/' % (host, port) self.url = 'http://%s:%d/' % (host, port)
self.callback = callback self.callback = callback
BaseHTTPServer.HTTPServer.__init__(self, self.address, self.handler) BaseHTTPServer.HTTPServer.__init__(self, self.address,
self.handler)
def serve_until_quit(self): def serve_until_quit(self):
self.quit = 0 self.quit = 0
@@ -362,26 +280,25 @@ class ViewVCHTTPServer(BaseHTTPServer.HTTPServer):
def server_bind(self): def server_bind(self):
# set SO_REUSEADDR (if available on this platform) # set SO_REUSEADDR (if available on this platform)
if hasattr(socket, 'SOL_SOCKET') and hasattr(socket, 'SO_REUSEADDR'): if hasattr(socket, 'SOL_SOCKET') \
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) and hasattr(socket, 'SO_REUSEADDR'):
self.socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
BaseHTTPServer.HTTPServer.server_bind(self) BaseHTTPServer.HTTPServer.server_bind(self)
def serve(host, port, callback=None): ViewVC_Server.handler = ViewVC_Handler
"""Start an HTTP server for HOST on PORT. Call CALLBACK function
when the server is ready to serve."""
ViewVCHTTPServer.handler = ViewVCHTTPRequestHandler
try: try:
# XXX Move this code out of this function. # XXX Move this code out of this function.
# Early loading of configuration here. Used to allow tinkering # Early loading of configuration here. Used to
# with some configuration settings: # allow tinkering with some configuration settings:
handle_config(options.config_file) handle_config(options.config_file)
if options.repositories: if options.repositories:
cfg.general.default_root = "Development" cfg.general.default_root = "Development"
for repo_name in options.repositories.keys(): for repo_name in options.repositories.keys():
repo_path = os.path.normpath(options.repositories[repo_name]) repo_path = os.path.normpath(options.repositories[repo_name])
if os.path.exists(os.path.join(repo_path, "CVSROOT", "config")): if os.path.exists(os.path.join(repo_path, "CVSROOT",
"config")):
cfg.general.cvs_roots[repo_name] = repo_path cfg.general.cvs_roots[repo_name] = repo_path
elif os.path.exists(os.path.join(repo_path, "format")): elif os.path.exists(os.path.join(repo_path, "format")):
cfg.general.svn_roots[repo_name] = repo_path cfg.general.svn_roots[repo_name] = repo_path
@@ -404,8 +321,7 @@ def serve(host, port, callback=None):
try: try:
while 1: while 1:
line = fp.readline() line = fp.readline()
if not line: if not line: break
break
if string.find(line, "Concurrent Versions System (CVSNT)")>=0: if string.find(line, "Concurrent Versions System (CVSNT)")>=0:
cvsnt_works = 1 cvsnt_works = 1
while fp.read(4096): while fp.read(4096):
@@ -418,7 +334,7 @@ def serve(host, port, callback=None):
if not cvsnt_works: if not cvsnt_works:
cfg.utilities.cvsnt = None cfg.utilities.cvsnt = None
ViewVCHTTPServer(host, port, callback).serve_until_quit() ViewVC_Server(host, port, callback).serve_until_quit()
except (KeyboardInterrupt, select.error): except (KeyboardInterrupt, select.error):
pass pass
print 'server stopped' print 'server stopped'
@@ -430,11 +346,10 @@ def handle_config(config_file):
# --- graphical interface: -------------------------------------------------- # --- graphical interface: --------------------------------------------------
def nogui(missing_module): def nogui(missing_module):
sys.stderr.write("""\ sys.stderr.write(
Sorry! Your Python was compiled without the %s module enabled. "Sorry! Your Python was compiled without the %s module"%missing_module+
I'm unable to run the GUI part. Please omit the '-g' and '--gui' options, " enabled.\nI'm unable to run the GUI part. Please omit the '-g'\n"+
or install another Python interpreter. "and '--gui' options or install another Python interpreter.\n")
""" % (missing_module))
raise SystemExit, 1 raise SystemExit, 1
def gui(host, port): def gui(host, port):
@@ -454,13 +369,10 @@ def gui(host, port):
self.title_lbl = Tkinter.Label(self.server_frm, self.title_lbl = Tkinter.Label(self.server_frm,
text='Starting server...\n ') text='Starting server...\n ')
self.open_btn = Tkinter.Button(self.server_frm, self.open_btn = Tkinter.Button(self.server_frm,
text='open browser', text='open browser', command=self.open, state='disabled')
command=self.open,
state='disabled')
self.quit_btn = Tkinter.Button(self.server_frm, self.quit_btn = Tkinter.Button(self.server_frm,
text='quit serving', text='quit serving', command=self.quit, state='disabled')
command=self.quit,
state='disabled')
self.window.title('ViewVC standalone') self.window.title('ViewVC standalone')
self.window.protocol('WM_DELETE_WINDOW', self.quit) self.window.protocol('WM_DELETE_WINDOW', self.quit)
@@ -479,146 +391,109 @@ def gui(host, port):
# cvsgraph toggle: # cvsgraph toggle:
self.cvsgraph_ivar = Tkinter.IntVar() self.cvsgraph_ivar = Tkinter.IntVar()
self.cvsgraph_ivar.set(cfg.options.use_cvsgraph) self.cvsgraph_ivar.set(cfg.options.use_cvsgraph)
self.cvsgraph_toggle = \ self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
Tkinter.Checkbutton(self.options_frm, text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
text="enable cvsgraph (needs binary)",
var=self.cvsgraph_ivar,
command=self.toggle_use_cvsgraph) command=self.toggle_use_cvsgraph)
self.cvsgraph_toggle.pack(side='top', anchor='w') self.cvsgraph_toggle.pack(side='top', anchor='w')
# show_subdir_lastmod toggle: # show_subdir_lastmod toggle:
self.subdirmod_ivar = Tkinter.IntVar() self.subdirmod_ivar = Tkinter.IntVar()
self.subdirmod_ivar.set(cfg.options.show_subdir_lastmod) self.subdirmod_ivar.set(cfg.options.show_subdir_lastmod)
self.subdirmod_toggle = \ self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
Tkinter.Checkbutton(self.options_frm, text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
text="show subdir last mod (dir view)",
var=self.subdirmod_ivar,
command=self.toggle_subdirmod) command=self.toggle_subdirmod)
self.subdirmod_toggle.pack(side='top', anchor='w') self.subdirmod_toggle.pack(side='top', anchor='w')
# use_re_search toggle: # use_re_search toggle:
self.useresearch_ivar = Tkinter.IntVar() self.useresearch_ivar = Tkinter.IntVar()
self.useresearch_ivar.set(cfg.options.use_re_search) self.useresearch_ivar.set(cfg.options.use_re_search)
self.useresearch_toggle = \ self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm,
Tkinter.Checkbutton(self.options_frm, text="allow regular expr search", var=self.useresearch_ivar,
text="allow regular expr search",
var=self.useresearch_ivar,
command=self.toggle_useresearch) command=self.toggle_useresearch)
self.useresearch_toggle.pack(side='top', anchor='w') self.useresearch_toggle.pack(side='top', anchor='w')
# use_localtime toggle: # use_localtime toggle:
self.use_localtime_ivar = Tkinter.IntVar() self.use_localtime_ivar = Tkinter.IntVar()
self.use_localtime_ivar.set(cfg.options.use_localtime) self.use_localtime_ivar.set(cfg.options.use_localtime)
self.use_localtime_toggle = \ self.use_localtime_toggle = Tkinter.Checkbutton(self.options_frm,
Tkinter.Checkbutton(self.options_frm,
text="use localtime (instead of UTC)", text="use localtime (instead of UTC)",
var=self.use_localtime_ivar, var=self.use_localtime_ivar,
command=self.toggle_use_localtime) command=self.toggle_use_localtime)
self.use_localtime_toggle.pack(side='top', anchor='w') self.use_localtime_toggle.pack(side='top', anchor='w')
# log_pagesize integer var: # log_pagesize integer var:
self.log_pagesize_lbl = \ self.log_pagesize_lbl = Tkinter.Label(self.options_frm,
Tkinter.Label(self.options_frm, text='Paging (number of items per log page, 0 disables):')
text='number of items per log page (0 disables):')
self.log_pagesize_lbl.pack(side='top', anchor='w') self.log_pagesize_lbl.pack(side='top', anchor='w')
self.log_pagesize_ivar = Tkinter.IntVar() self.log_pagesize_ivar = Tkinter.IntVar()
self.log_pagesize_ivar.set(cfg.options.log_pagesize) self.log_pagesize_ivar.set(cfg.options.log_pagesize)
self.log_pagesize_entry = \ self.log_pagesize_entry = Tkinter.Entry(self.options_frm,
Tkinter.Entry(self.options_frm, width=10, textvariable=self.log_pagesize_ivar)
width=10,
textvariable=self.log_pagesize_ivar)
self.log_pagesize_entry.bind('<Return>', self.set_log_pagesize) self.log_pagesize_entry.bind('<Return>', self.set_log_pagesize)
self.log_pagesize_entry.pack(side='top', anchor='w') self.log_pagesize_entry.pack(side='top', anchor='w')
# dir_pagesize integer var: # dir_pagesize integer var:
self.dir_pagesize_lbl = \ self.dir_pagesize_lbl = Tkinter.Label(self.options_frm,
Tkinter.Label(self.options_frm, text='Paging (number of items per dir page, 0 disables):')
text='number of items per dir page (0 disables):')
self.dir_pagesize_lbl.pack(side='top', anchor='w') self.dir_pagesize_lbl.pack(side='top', anchor='w')
self.dir_pagesize_ivar = Tkinter.IntVar() self.dir_pagesize_ivar = Tkinter.IntVar()
self.dir_pagesize_ivar.set(cfg.options.dir_pagesize) self.dir_pagesize_ivar.set(cfg.options.dir_pagesize)
self.dir_pagesize_entry = \ self.dir_pagesize_entry = Tkinter.Entry(self.options_frm,
Tkinter.Entry(self.options_frm, width=10, textvariable=self.dir_pagesize_ivar)
width=10,
textvariable=self.dir_pagesize_ivar)
self.dir_pagesize_entry.bind('<Return>', self.set_dir_pagesize) self.dir_pagesize_entry.bind('<Return>', self.set_dir_pagesize)
self.dir_pagesize_entry.pack(side='top', anchor='w') self.dir_pagesize_entry.pack(side='top', anchor='w')
# directory view template: # directory view template:
self.dirtemplate_lbl = \ self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
Tkinter.Label(self.options_frm,
text='Choose HTML Template for the Directory pages:') text='Choose HTML Template for the Directory pages:')
self.dirtemplate_lbl.pack(side='top', anchor='w') self.dirtemplate_lbl.pack(side='top', anchor='w')
self.dirtemplate_svar = Tkinter.StringVar() self.dirtemplate_svar = Tkinter.StringVar()
self.dirtemplate_svar.set(cfg.templates.directory) self.dirtemplate_svar.set(cfg.templates.directory)
self.dirtemplate_entry = \ self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
Tkinter.Entry(self.options_frm, width = 40, textvariable=self.dirtemplate_svar)
width=40,
textvariable=self.dirtemplate_svar)
self.dirtemplate_entry.bind('<Return>', self.set_templates_directory) self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
self.dirtemplate_entry.pack(side='top', anchor='w') self.dirtemplate_entry.pack(side='top', anchor='w')
self.templates_dir = \ self.templates_dir = Tkinter.Radiobutton(self.options_frm,
Tkinter.Radiobutton(self.options_frm, text="directory.ezt", value="templates/directory.ezt",
text="directory.ezt", var=self.dirtemplate_svar, command=self.set_templates_directory)
value="templates/directory.ezt",
var=self.dirtemplate_svar,
command=self.set_templates_directory)
self.templates_dir.pack(side='top', anchor='w') self.templates_dir.pack(side='top', anchor='w')
self.templates_dir_alt = \ self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm,
Tkinter.Radiobutton(self.options_frm, text="dir_alternate.ezt", value="templates/dir_alternate.ezt",
text="dir_alternate.ezt", var=self.dirtemplate_svar, command=self.set_templates_directory)
value="templates/dir_alternate.ezt",
var=self.dirtemplate_svar,
command=self.set_templates_directory)
self.templates_dir_alt.pack(side='top', anchor='w') self.templates_dir_alt.pack(side='top', anchor='w')
# log view template: # log view template:
self.logtemplate_lbl = \ self.logtemplate_lbl = Tkinter.Label(self.options_frm,
Tkinter.Label(self.options_frm,
text='Choose HTML Template for the Log pages:') text='Choose HTML Template for the Log pages:')
self.logtemplate_lbl.pack(side='top', anchor='w') self.logtemplate_lbl.pack(side='top', anchor='w')
self.logtemplate_svar = Tkinter.StringVar() self.logtemplate_svar = Tkinter.StringVar()
self.logtemplate_svar.set(cfg.templates.log) self.logtemplate_svar.set(cfg.templates.log)
self.logtemplate_entry = \ self.logtemplate_entry = Tkinter.Entry(self.options_frm,
Tkinter.Entry(self.options_frm, width = 40, textvariable=self.logtemplate_svar)
width=40,
textvariable=self.logtemplate_svar)
self.logtemplate_entry.bind('<Return>', self.set_templates_log) self.logtemplate_entry.bind('<Return>', self.set_templates_log)
self.logtemplate_entry.pack(side='top', anchor='w') self.logtemplate_entry.pack(side='top', anchor='w')
self.templates_log = \ self.templates_log = Tkinter.Radiobutton(self.options_frm,
Tkinter.Radiobutton(self.options_frm, text="log.ezt", value="templates/log.ezt",
text="log.ezt", var=self.logtemplate_svar, command=self.set_templates_log)
value="templates/log.ezt",
var=self.logtemplate_svar,
command=self.set_templates_log)
self.templates_log.pack(side='top', anchor='w') self.templates_log.pack(side='top', anchor='w')
self.templates_log_table = \ self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
Tkinter.Radiobutton(self.options_frm, text="log_table.ezt", value="templates/log_table.ezt",
text="log_table.ezt", var=self.logtemplate_svar, command=self.set_templates_log)
value="templates/log_table.ezt",
var=self.logtemplate_svar,
command=self.set_templates_log)
self.templates_log_table.pack(side='top', anchor='w') self.templates_log_table.pack(side='top', anchor='w')
# query view template: # query view template:
self.querytemplate_lbl = \ self.querytemplate_lbl = Tkinter.Label(self.options_frm,
Tkinter.Label(self.options_frm,
text='Template for the database query page:') text='Template for the database query page:')
self.querytemplate_lbl.pack(side='top', anchor='w') self.querytemplate_lbl.pack(side='top', anchor='w')
self.querytemplate_svar = Tkinter.StringVar() self.querytemplate_svar = Tkinter.StringVar()
self.querytemplate_svar.set(cfg.templates.query) self.querytemplate_svar.set(cfg.templates.query)
self.querytemplate_entry = \ self.querytemplate_entry = Tkinter.Entry(self.options_frm,
Tkinter.Entry(self.options_frm, width = 40, textvariable=self.querytemplate_svar)
width=40,
textvariable=self.querytemplate_svar)
self.querytemplate_entry.bind('<Return>', self.set_templates_query) self.querytemplate_entry.bind('<Return>', self.set_templates_query)
self.querytemplate_entry.pack(side='top', anchor='w') self.querytemplate_entry.pack(side='top', anchor='w')
self.templates_query = \ self.templates_query = Tkinter.Radiobutton(self.options_frm,
Tkinter.Radiobutton(self.options_frm, text="query.ezt", value="templates/query.ezt",
text="query.ezt", var=self.querytemplate_svar, command=self.set_templates_query)
value="templates/query.ezt",
var=self.querytemplate_svar,
command=self.set_templates_query)
self.templates_query.pack(side='top', anchor='w') self.templates_query.pack(side='top', anchor='w')
# pack and set window manager hints: # pack and set window manager hints:
@@ -636,7 +511,8 @@ def gui(host, port):
import threading import threading
except ImportError: except ImportError:
nogui("thread") nogui("thread")
threading.Thread(target=serve, args=(host, port, self.ready)).start() threading.Thread(target=serve,
args=(host, port, self.ready)).start()
def toggle_use_cvsgraph(self, event=None): def toggle_use_cvsgraph(self, event=None):
cfg.options.use_cvsgraph = self.cvsgraph_ivar.get() cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
@@ -668,7 +544,8 @@ def gui(host, port):
def ready(self, server): def ready(self, server):
"""used as callback parameter to the serve() function""" """used as callback parameter to the serve() function"""
self.server = server self.server = server
self.title_lbl.config(text='ViewVC standalone server at\n' + server.url) self.title_lbl.config(
text='ViewVC standalone server at\n' + server.url)
self.open_btn.config(state='normal') self.open_btn.config(state='normal')
self.quit_btn.config(state='normal') self.quit_btn.config(state='normal')
@@ -688,8 +565,7 @@ def gui(host, port):
except ImportError: pass except ImportError: pass
else: else:
rc = os.system('netscape -remote "openURL(%s)" &' % url) rc = os.system('netscape -remote "openURL(%s)" &' % url)
if rc: if rc: os.system('netscape "%s" &' % url)
os.system('netscape "%s" &' % url)
def quit(self, event=None): def quit(self, event=None):
if self.server: if self.server:
@@ -705,175 +581,102 @@ def gui(host, port):
# --- command-line interface: ---------------------------------------------- # --- command-line interface: ----------------------------------------------
def cli(argv):
def usage():
clean_options = Options()
cmd = os.path.basename(sys.argv[0])
port = clean_options.port
host = clean_options.host
script_alias = clean_options.script_alias
sys.stderr.write("""Usage: %(cmd)s [OPTIONS]
Run a simple, standalone HTTP server configured to serve up ViewVC requests.
Options:
--config-file=FILE (-c) Read configuration options from FILE. If not
specified, ViewVC will look for a configuration
file in its installation tree, falling back to
built-in default values. (Not valid in GUI mode.)
--daemon (-d) Background the server process.
--gui (-g) Pop up a graphical configuration interface.
Requires a valid X11 display connection.
--help Show this usage message and exit.
--host=HOSTNAME (-h) Listen on HOSTNAME. Required for access from a
remote machine. [default: %(host)s]
--htpasswd-file=FILE Authenticate incoming requests, validating against
against FILE, which is an Apache HTTP Server
htpasswd file. (CRYPT only; no DIGEST support.)
--port=PORT (-p) Listen on PORT. [default: %(port)d]
--repository=PATH (-r) Serve the Subversion or CVS repository located
at PATH. This option may be used more than once.
--script-alias=PATH (-s) Use PATH as the virtual script location (similar
to Apache HTTP Server's ScriptAlias directive).
For example, "--script-alias=repo/view" will serve
ViewVC at "http://HOSTNAME:PORT/repo/view".
[default: %(script_alias)s]
""" % locals())
sys.exit(0)
def badusage(errstr):
cmd = os.path.basename(sys.argv[0])
sys.stderr.write("ERROR: %s\n\n"
"Try '%s --help' for detailed usage information.\n"
% (errstr, cmd))
sys.exit(1)
def main(argv):
"""Command-line interface (looks at argv to decide what to do).""" """Command-line interface (looks at argv to decide what to do)."""
import getopt import getopt
short_opts = string.join(['c:',
'd',
'g',
'h:',
'p:',
'r:',
's:',
], '')
long_opts = ['daemon',
'config-file=',
'gui',
'help',
'host=',
'htpasswd-file=',
'port=',
'repository=',
'script-alias=',
]
opt_daemon = 0
opt_gui = 0
opt_host = None
opt_port = None
opt_htpasswd_file = None
opt_config_file = None
opt_script_alias = None
opt_repositories = []
# Parse command-line options.
try:
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
for opt, val in opts:
if opt in ['--help']:
usage()
elif opt in ['-g', '--gui']:
opt_gui = 1
elif opt in ['-r', '--repository']: # may be used more than once
opt_repositories.append(val)
elif opt in ['-d', '--daemon']:
opt_daemon = 1
elif opt in ['-p', '--port']:
opt_port = val
elif opt in ['-h', '--host']:
opt_host = val
elif opt in ['-s', '--script-alias']:
opt_script_alias = val
elif opt in ['-c', '--config-file']:
opt_config_file = val
elif opt in ['--htpasswd-file']:
opt_htpasswd_file = val
except getopt.error, err:
badusage(str(err))
# Validate options that need validating.
class BadUsage(Exception): pass class BadUsage(Exception): pass
try:
if opt_port is not None:
try:
options.port = int(opt_port)
except ValueError:
raise BadUsage("Port '%s' is not a valid port number" % (opt_port))
if not options.port:
raise BadUsage("You must supply a valid port.")
if opt_htpasswd_file is not None:
if not os.path.isfile(opt_htpasswd_file):
raise BadUsage("'%s' does not appear to be a valid htpasswd file."
% (opt_htpasswd_file))
if not has_crypt:
raise BadUsage("Unable to locate suitable `crypt' module for use "
"with --htpasswd-file option. If your Python "
"distribution does not include this module (as is "
"the case on many non-Unix platforms), consider "
"installing the `fcrypt' module instead (see "
"http://carey.geek.nz/code/python-fcrypt/).")
options.htpasswd_file = opt_htpasswd_file
if opt_config_file is not None:
if not os.path.isfile(opt_config_file):
raise BadUsage("'%s' does not appear to be a valid configuration file."
% (opt_config_file))
options.config_file = opt_config_file
if opt_host is not None:
options.host = opt_host
if opt_script_alias is not None:
options.script_alias = string.join(filter(None,
string.split(opt_script_alias,
'/')),
'/')
for repository in opt_repositories:
if not options.repositories.has_key('Development'):
rootname = 'Development'
else:
rootname = 'Repository%d' % (len(options.repositories.keys()) + 1)
options.repositories[rootname] = repository
except BadUsage, err:
badusage(str(err))
# Fork if we're in daemon mode. try:
if opt_daemon: opts, args = getopt.getopt(argv[1:], 'gdc:p:r:h:s:',
['gui', 'daemon', 'config-file=', 'host=',
'port=', 'repository=', 'script-alias='])
for opt, val in opts:
if opt in ('-g', '--gui'):
options.start_gui = 1
elif opt in ('-r', '--repository'):
if options.repositories: # option may be used more than once:
num = len(options.repositories.keys())+1
symbolic_name = "Repository"+str(num)
options.repositories[symbolic_name] = val
else:
options.repositories["Development"] = val
elif opt in ('-d', '--daemon'):
options.daemon = 1
elif opt in ('-p', '--port'):
try:
options.port = int(val)
except ValueError:
raise BadUsage, "Port '%s' is not a valid port number" \
% (val)
elif opt in ('-h', '--host'):
options.host = val
elif opt in ('-s', '--script-alias'):
options.script_alias = \
string.join(filter(None, string.split(val, '/')), '/')
elif opt in ('-c', '--config-file'):
options.config_file = val
if options.start_gui and options.config_file:
raise BadUsage, "--config-file option is not valid in GUI mode."
if not options.start_gui and not options.port:
raise BadUsage, "You must supply a valid port, or run in GUI mode."
if options.daemon:
pid = os.fork() pid = os.fork()
if pid != 0: if pid != 0:
sys.exit() sys.exit()
if options.start_gui:
# Finaly, start the server.
if opt_gui:
gui(options.host, options.port) gui(options.host, options.port)
else: return
elif options.port:
def ready(server): def ready(server):
print 'server ready at %s%s' % (server.url, options.script_alias) print 'server ready at %s%s' % (server.url,
options.script_alias)
serve(options.host, options.port, ready) serve(options.host, options.port, ready)
return
except (getopt.error, BadUsage), err:
cmd = os.path.basename(sys.argv[0])
port = options.port
host = options.host
script_alias = options.script_alias
if str(err):
sys.stderr.write("ERROR: %s\n\n" % (str(err)))
sys.stderr.write("""Usage: %(cmd)s [OPTIONS]
Run a simple, standalone HTTP server configured to serve up ViewVC
requests.
Options:
--config-file=PATH (-c) Use the file at PATH as the ViewVC configuration
file. If not specified, ViewVC will try to use
the configuration file in its installation tree;
otherwise, built-in default values are used.
(Not valid in GUI mode.)
--daemon (-d) Background the server process.
--host=HOST (-h) Start the server listening on HOST. You need
to provide the hostname if you want to
access the standalone server from a remote
machine. [default: %(host)s]
--port=PORT (-p) Start the server on the given PORT.
[default: %(port)d]
--repository=PATH (-r) Serve up the Subversion or CVS repository located
at PATH. This option may be used more than once.
--script-alias=PATH (-s) Specify the ScriptAlias, the artificial path
location that at which ViewVC appears to be
located. For example, if your ScriptAlias is
"cgi-bin/viewvc", then ViewVC will be accessible
at "http://%(host)s:%(port)s/cgi-bin/viewvc".
[default: %(script_alias)s]
--gui (-g) Pop up a graphical interface for serving and
testing ViewVC. NOTE: this requires a valid
X11 display connection.
""" % locals())
if __name__ == '__main__': if __name__ == '__main__':
options = Options() options = Options()
main(sys.argv) cli(sys.argv)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 2004-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 2004-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2004-2007 James Henstridge # Copyright (C) 2004-2007 James Henstridge
# #
# By using this file, you agree to the terms and conditions set forth in # By using this file, you agree to the terms and conditions set forth in
@@ -153,7 +153,7 @@ class SvnRev:
fsroot = self._get_root_for_rev(rev) fsroot = self._get_root_for_rev(rev)
# find changes in the revision # find changes in the revision
editor = svn.repos.ChangeCollector(repo.fs, fsroot) editor = svn.repos.RevisionChangeCollector(repo.fs, rev)
e_ptr, e_baton = svn.delta.make_editor(editor) e_ptr, e_baton = svn.delta.make_editor(editor)
svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton) svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton)
@@ -164,33 +164,21 @@ class SvnRev:
continue continue
# deal with the change types we handle # deal with the change types we handle
action = None
base_root = None base_root = None
base_path = change.base_path
if change.base_path: if change.base_path:
base_root = self._get_root_for_rev(change.base_rev) base_root = self._get_root_for_rev(change.base_rev)
# figure out what kind of change this is, and get a diff if not change.path:
# object for it. note that prior to 1.4 Subversion's
# bindings didn't give us change.action, but that's okay
# because back then deleted paths always had a change.path
# of None.
if hasattr(change, 'action') \
and change.action == svn.repos.CHANGE_ACTION_DELETE:
action = 'remove'
elif not change.path:
action = 'remove' action = 'remove'
elif change.added: elif change.added:
action = 'add' action = 'add'
else: else:
action = 'change' action = 'change'
if action == 'remove': diffobj = svn.fs.FileDiff(base_root and base_root or None,
diffobj = svn.fs.FileDiff(base_root, base_path, None, None) base_root and change.base_path or None,
else: change.path and fsroot or None,
diffobj = svn.fs.FileDiff(base_root, base_path, change.path and change.path or None)
fsroot, change.path)
diff_fp = diffobj.get_pipe() diff_fp = diffobj.get_pipe()
plus, minus = _get_diff_counts(diff_fp) plus, minus = _get_diff_counts(diff_fp)
self.changes.append((path, action, plus, minus)) self.changes.append((path, action, plus, minus))
@@ -254,7 +242,6 @@ def main(command, repository, revs=[], verbose=0, force=0):
cfg = viewvc.load_config(CONF_PATHNAME) cfg = viewvc.load_config(CONF_PATHNAME)
db = cvsdb.ConnectDatabase(cfg) db = cvsdb.ConnectDatabase(cfg)
# Purge what must be purged.
if command in ('rebuild', 'purge'): if command in ('rebuild', 'purge'):
if verbose: if verbose:
print "Purging commit info for repository root `%s'" % repository print "Purging commit info for repository root `%s'" % repository
@@ -265,12 +252,6 @@ def main(command, repository, revs=[], verbose=0, force=0):
sys.stderr.write("ERROR: " + str(e) + "\n") sys.stderr.write("ERROR: " + str(e) + "\n")
sys.exit(1) 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) repo = SvnRepo(repository)
if command == 'rebuild' or (command == 'update' and not revs): if command == 'rebuild' or (command == 'update' and not revs):
for rev in range(repo.rev_max+1): for rev in range(repo.rev_max+1):
@@ -300,7 +281,7 @@ def usage():
located at REPOS-PATH. located at REPOS-PATH.
Usage: 1. %s [-v] rebuild REPOS-PATH Usage: 1. %s [-v] rebuild REPOS-PATH
2. %s [-v] update REPOS-PATH [REV[:REV2]] [--force] 2. %s [-v] update REPOS-PATH [REV:[REV2]] [--force]
3. %s [-v] purge REPOS-PATH 3. %s [-v] purge REPOS-PATH
1. Rebuild the commit database information for the repository located 1. Rebuild the commit database information for the repository located
@@ -349,6 +330,12 @@ if __name__ == '__main__':
sys.stderr.write('ERROR: unknown command %s\n' % command) sys.stderr.write('ERROR: unknown command %s\n' % command)
usage() 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 = [] revs = []
if len(sys.argv) > 3: if len(sys.argv) > 3:
if command == 'rebuild': if command == 'rebuild':
@@ -371,7 +358,6 @@ if __name__ == '__main__':
rev = None rev = None
try: try:
repository = vclib.svn.canonicalize_rootpath(args[2])
repository = cvsdb.CleanRepository(os.path.abspath(repository)) repository = cvsdb.CleanRepository(os.path.abspath(repository))
main(command, repository, revs, verbose, force) main(command, repository, revs, verbose, force)
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@@ -1,54 +0,0 @@
#!/usr/bin/python
# -*-python-*-
#
# Copyright (C) 1999-2013 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-2013 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-2013 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-2013 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

@@ -86,8 +86,8 @@
## cvs_roots: Specifies each of the CVS roots on your system and ## cvs_roots: Specifies each of the CVS roots on your system and
## assigns names to them. Each root should be given by a "name: path" ## assigns names to them. Each root should be given by a "name: path"
## value (where the path is an absolute filesystem path). Multiple roots ## value. Multiple roots should be separated by commas and can be
## should be separated by commas and can be placed on separate lines. ## placed on separate lines.
## ##
## Example: ## Example:
## cvs_roots = cvsroot: /opt/cvs/repos1, ## cvs_roots = cvsroot: /opt/cvs/repos1,
@@ -97,13 +97,8 @@
## svn_roots: Specifies each of the Subversion roots (repositories) on ## svn_roots: Specifies each of the Subversion roots (repositories) on
## your system and assigns names to them. Each root should be given by ## your system and assigns names to them. Each root should be given by
## a "name: path" value (where the path is an absolute filesystem path). ## a "name: path" value. Multiple roots should be separated by commas
## Multiple roots should be separated by commas and can be placed on ## and can be placed on separate lines.
## separate lines.
##
## NOTE: ViewVC offers *experimental* support for displaying remote
## Subversion repositories. Simply use the repository's URL instead
## of a local filesystem path when defining the root.
## ##
## Example: ## Example:
## svn_roots = svnrepos: /opt/svn/, ## svn_roots = svnrepos: /opt/svn/,
@@ -111,44 +106,23 @@
## ##
#svn_roots = #svn_roots =
## root_parents: Specifies a list of directories under which any ## root_parents: Specifies a list of directories in which any number of
## number of repositories may reside. You can specify multiple root ## repositories may reside. Rather than force you to add a new entry
## parents separated by commas or new lines, each of which is of the ## to 'cvs_roots' or 'svn_roots' each time you create a new repository,
## form "path: type" (where the type is either "cvs" or "svn", and ## ViewVC rewards you for organising all your repositories under a few
## the path is an absolute filesystem path). ## 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 ## You can specify multiple parent paths separated by commas or new lines.
## '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.
## ##
## For example, if you have three Subversion repositories located at ## WARNING: these names can, of course, clash with names you have
## /opt/svn/projects, /opt/svn/websites, and /opt/svn/devstuff, you ## defined in your cvs_roots or svn_roots configuration items. If this
## could list them individually in svn_roots like so: ## occurs, you can either rename the offending repository on disk, or
## ## grant new names to the clashing item in cvs_roots or svn_roots.
## svn_roots = projects: /opt/svn/projects, ## Each parent path is processed sequentially, so repositories under
## websites: /opt/svn/websites, ## later parent paths may override earlier ones.
## 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.
## ##
## Example: ## Example:
## root_parents = /opt/svn : svn, ## root_parents = /opt/svn : svn,
@@ -351,64 +325,10 @@
## allowed_views: List the ViewVC views which are enabled. Views not ## allowed_views: List the ViewVC views which are enabled. Views not
## in this comma-delited list will not be served (or, will return an ## in this comma-delited list will not be served (or, will return an
## error on attempted access). ## error on attempted access).
## ## Possible values: "annotate", "co", "diff", "markup", "roots", "tar"
## Valid items for this list include: "annotate", "co", "diff", "markup",
## "roots", "tar".
##
## ----------+---------------------------------------------------------
## VIEW | DESCRIPTION
## ----------+---------------------------------------------------------
## annotate | The 'annotate' view shows the contents of a single
## | revision of a versioned file in exactly the same way as
## | the markup view, but with additional line-by-line
## | change attribution (the revision number, author, etc.
## | the most recent edit to that line of text as of the
## | displayed version).
## ----------+---------------------------------------------------------
## co | The 'co' (aka "checkout" or "download") view isn't
## | really a branded view at all, but allows for direct
## | downloading of the contents of a single revision of a
## | versioned file.
## ----------+---------------------------------------------------------
## diff | The 'diff' view displays line-based differences between
## | two revisions of a versioned file in a variety of
## | different user-selectable formats.
## ----------+---------------------------------------------------------
## markup | The 'markup' view shows the contents of a single
## | revision of a versioned file, with syntax highlighting
## | where possible and enabled. It can also optionally
## | show change log information for that revision of the
## | file.
## ----------+---------------------------------------------------------
## roots | The 'roots' view is a simple listing of the various
## | repositories which ViewVC has been configured to serve
## | to users.
## ----------+---------------------------------------------------------
## tar | The 'tar' view isn't a branded view, but generates
## | a GNU Tar archive file containing a single versioned
## | directory and its contents (recursively).
## ----------+---------------------------------------------------------
## ##
#allowed_views = annotate, diff, markup, roots #allowed_views = annotate, diff, markup, roots
## Comma-delimited list of MIME content types (with support for fnmatch-
## style glob characters) which are considered not-human-readable and for
## which ViewVC will neither generate links to, nor support the direct
## display of, non-checkout views which carry the file's content (the
## 'markup', 'annotate', 'diff', and 'patch' views).
##
## NOTE: Handling of this option is given priority over ViewVC's
## longstanding support for showing web-friendly file formats -- even
## binary ones such as "image/jpeg" and "image/gif" -- in the 'markup'
## view. Thus, if you add "image/*" to this list, 'markup'-view
## display of JPEG, GIF, and PNG images will be disabled.
##
## Example:
## binary_mime_types = application/octet-stream, image/*, application/pdf,
## application/vnd*, application/msword, audio/*
#
#binary_mime_types =
## authorizer: The name of the ViewVC authorizer plugin to use when ## authorizer: The name of the ViewVC authorizer plugin to use when
## authorizing access to repository contents. This value must be the ## authorizing access to repository contents. This value must be the
## name of a Python module addressable as vcauth.MODULENAME (most ## name of a Python module addressable as vcauth.MODULENAME (most
@@ -451,26 +371,6 @@
## ##
#mangle_email_addresses = 0 #mangle_email_addresses = 0
## custom_log_formatting: Specifies mappings of regular expressions to
## substitution format strings used to URL-ize strings found in
## revision log messages. Multiple mappings (specified as
## REGEXP:FORMATSTRING) may be defined, separated by commas.
##
## NOTE: Due to a limitation of the configuration format, commas may
## not be used in the regular expression portion of each mapping.
## Commas "in the raw" may not be used in the format string portion,
## either, but you can probably use the URI-encoded form of the comma
## ("%2C") instead with no ill side-effects. If you must specify a
## colon character in either the regular expression or the format
## string, escape it with a preceding backslash ("\:").
##
## Example:
## custom_log_formatting =
## artf[0-9]+ : http://example.com/tracker?id=\0,
## issue ([0-9]+) : http://example.com/bug?id=\1&opts=full%2csortby=id
##
#custom_log_formatting =
## default_file_view: "log", "co", or "markup" ## default_file_view: "log", "co", or "markup"
## Controls whether the default view for file URLs is a checkout view or ## Controls whether the default view for file URLs is a checkout view or
## a log view. "log" is the default for backwards compatibility with old ## a log view. "log" is the default for backwards compatibility with old
@@ -510,15 +410,6 @@
## ##
#svn_ignore_mimetype = 0 #svn_ignore_mimetype = 0
## max_filesize_kbytes: Limit ViewVC's processing of file contents in
## "markup" and "annotate" views to only those files which are smaller
## than this setting, expressed in kilobytes. Set to 0 to disable
## this safeguard.
##
## NOTE: The "co" and "tar" views are unaffected by this setting.
##
#max_filesize_kbytes = 512
## svn_config_dir: Path of the Subversion runtime configuration ## svn_config_dir: Path of the Subversion runtime configuration
## directory ViewVC should consult for various things, including cached ## directory ViewVC should consult for various things, including cached
## remote authentication credentials. If unset, Subversion will use ## remote authentication credentials. If unset, Subversion will use
@@ -588,7 +479,7 @@
## (Only works well for C source files, otherwise diff's heuristic falls short.) ## (Only works well for C source files, otherwise diff's heuristic falls short.)
## ('-p' option to diff) ## ('-p' option to diff)
## ##
#hr_funout = 1 #hr_funout = 0
## hr_ignore_white: Ignore whitespace (indendation and stuff) for human ## hr_ignore_white: Ignore whitespace (indendation and stuff) for human
## readable diffs. ## readable diffs.
@@ -661,14 +552,6 @@
## ##
#show_subdir_lastmod = 0 #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: Show the most recent log entry in directory listings.
## ##
#show_logs = 1 #show_logs = 1
@@ -686,35 +569,25 @@
## ##
#use_localtime = 0 #use_localtime = 0
## iso8601_dates: Display timestamps using a standard ISO-8601 format.
##
#iso8601_timestamps = 0
## short_log_len: The length (in characters) to which the most recent ## short_log_len: The length (in characters) to which the most recent
## log entry should be truncated when shown in the directory view. ## log entry should be truncated when shown in the directory view.
## ##
#short_log_len = 80 #short_log_len = 80
## enable_syntax_coloration: Should we colorize known file content ## enable_syntax_coloration: Should we colorize known file content
## syntaxes? ## syntaxes? [Requires Pygments Python module]
##
## NOTE: This feature requires the Pygments Python module
## (http://pygments.org) and works only when ViewVC can determine the
## MIME content type of the file whose contents it wishes to colorize.
## Use the 'mime_types_files' configuration option to specify MIME
## type mapping files useful for making that determination.
## ##
#enable_syntax_coloration = 1 #enable_syntax_coloration = 1
## tabsize: The number of spaces into which horizontal tab characters ## tabsize: The number of spaces into which tabstops are converted
## are converted when viewing file contents. Set to 0 to preserve ## when viewing file contents.
## tab characters.
## ##
#tabsize = 8 #tabsize = 8
## detect_encoding: Should we attempt to detect versioned file ## detect_encoding: Should we attempt to detect versioned file
## character encodings? [Requires 'chardet' module, and is currently ## character encodings? [Requires 'chardet' module, and is currently
## used only for the 'markup' and 'annotate' views.] ## used only by the syntax coloration logic -- if enabled -- for the
## 'markup' and 'annotate' views; see 'enable_syntax_coloration'.]
## ##
#detect_encoding = 0 #detect_encoding = 0
@@ -756,26 +629,6 @@
## ##
#log_pagesize = 0 #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 ## limit_changes: Maximum number of changed paths shown per commit in
## the Subversion revision view and in query results. This is not a ## the Subversion revision view and in query results. This is not a
## hard limit (the UI provides options to show all changed paths), but ## hard limit (the UI provides options to show all changed paths), but
@@ -896,14 +749,6 @@
## row_limit: Maximum number of rows returned by a given normal query ## row_limit: Maximum number of rows returned by a given normal query
## to the database. ## to the database.
## ##
## NOTE: This limits the amount of data provided to ViewVC by the
## database. It is from this already-reduced data set that ViewVC
## builds the query response it presents to the user, which may or may
## not include still more limiting via the query form's 'limit'
## parameter. In other words, there is no value which the user can use
## in the query form's 'limit' parameter which will cause more data to
## be returned by the database for ViewVC to process.
##
#row_limit = 1000 #row_limit = 1000
## rss_row_limit: Maximum number of rows returned by a given query to ## rss_row_limit: Maximum number of rows returned by a given query to
@@ -911,9 +756,6 @@
## that RSS readers tend to poll regularly for new data, you might want ## that RSS readers tend to poll regularly for new data, you might want
## to keep this set to a conservative number.) ## to keep this set to a conservative number.)
## ##
## See also the `NOTE' for the 'row_limit' option, which applies here
## as well.
##
#rss_row_limit = 100 #rss_row_limit = 100
## check_database_for_root: Check if the repository is found in the ## check_database_for_root: Check if the repository is found in the
@@ -1155,30 +997,3 @@
#force_username_case = #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

@@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>ViewVC 1.1 Template Authoring Guide</title> <title>ViewVC 1.0 Template Authoring Guide</title>
<style> <style>
body { body {
background-color: rgb(180,193,205); background-color: rgb(180,193,205);
@@ -38,13 +38,13 @@ td {
</head> </head>
<body> <body>
<h1>ViewVC 1.1 Template Authoring Guide</h1> <h1>ViewVC 1.0 Template Authoring Guide</h1>
<div class="h2"> <div class="h2">
<h2 id="introduction">Introduction</h2> <h2 id="introduction">Introduction</h2>
<p>This document represents an (unfinished) attempt at providing <p>This document represents an (unfinished) attempt at providing
instructions for how to customize ViewVC's HTML output via documentation for how to customize ViewVC 1.0-dev's HTML output via
modification of its templates.</p> modification of its templates.</p>
</div> </div>
@@ -802,11 +802,6 @@ td {
<td>String</td> <td>String</td>
<td>This is a URL for the markup view of the left file.</td> <td>This is a URL for the markup view of the left file.</td>
</tr> </tr>
<tr class="varlevel1">
<td class="varname">patch_href</td>
<td>String</td>
<td>URL of the patch view for the file.</td>
</tr>
<tr class="varlevel1"> <tr class="varlevel1">
<td class="varname">raw_diff</td> <td class="varname">raw_diff</td>
<td>String</td> <td>String</td>
@@ -1826,14 +1821,6 @@ td {
<td>Indicates how query results are being sorted. Possible values: <td>Indicates how query results are being sorted. Possible values:
<tt>date</tt>, <tt>author</tt>, and <tt>file</tt>.</td> <tt>date</tt>, <tt>author</tt>, and <tt>file</tt>.</td>
</tr> </tr>
<tr class="varlevel1">
<td class="varname">row_limit_reached</td>
<td>Boolean</td>
<td>Indicates whether the internal database row limit threshold (set
via the <code>cvsdb.row_limit</code>
and <code>cvsdb.rss_row_limit</code> configuration options) was
reached by the query.</td>
</tr>
<tr class="varlevel1"> <tr class="varlevel1">
<td class="varname">show_branch</td> <td class="varname">show_branch</td>
<td>Boolean</td> <td>Boolean</td>
@@ -2157,38 +2144,6 @@ td {
<td>List</td> <td>List</td>
<td>Set of configured viewable repositories.</td> <td>Set of configured viewable repositories.</td>
</tr> </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.log_href</td>
<td>String</td>
<td>URL of log revision view for the top-most (root) directory of
the root (repository).</td>
</tr>
<tr class="varlevel2"> <tr class="varlevel2">
<td class="varname">roots.name</td> <td class="varname">roots.name</td>
<td>String</td> <td>String</td>
@@ -2202,24 +2157,17 @@ td {
configuration can have negative security implications. Use this configuration can have negative security implications. Use this
token at your own risk.</td> token at your own risk.</td>
</tr> </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"> <tr class="varlevel2">
<td class="varname">roots.type</td> <td class="varname">roots.type</td>
<td>String</td> <td>String</td>
<td>Version control type of a configured repository. Valid <td>Version control type of a configured repository. Valid
values: <tt>cvs</tt>, <tt>svn</tt>.</td> values: <tt>cvs</tt>, <tt>svn</tt>.</td>
</tr> </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> </tbody>
</table> </table>

View File

@@ -1192,6 +1192,13 @@ th.caption {
commands to back out changes instead showing a normal query result commands to back out changes instead showing a normal query result
page</td> page</td>
</tr> </tr>
<tr>
<td><code>limit=<var>LIMIT</var></code></td>
<td>optional</td>
<td>maximum number of file-revisions to process during a
query. Default is value of <code>row_limit</code> configuration
option</td>
</tr>
<tr> <tr>
<td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td> <td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td>
<td>optional</td> <td>optional</td>

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

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

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -88,11 +88,6 @@ import fnmatch
# | vhosts | # | vhosts |
# | | # | |
# `-----------' # `-----------'
# ,-----------.
# | |
# | query |
# | |
# `-----------'
# #
# ### TODO: Figure out what this all means for the 'kv' stuff. # ### TODO: Figure out what this all means for the 'kv' stuff.
# #
@@ -105,15 +100,12 @@ class Config:
'cvsdb', 'cvsdb',
'general', 'general',
'options', 'options',
'query',
'templates', 'templates',
'utilities', 'utilities',
) )
_force_multi_value = ( _force_multi_value = (
# Configuration values with multiple, comma-separated values. # Configuration values with multiple, comma-separated values.
'allowed_views', 'allowed_views',
'binary_mime_types',
'custom_log_formatting',
'cvs_roots', 'cvs_roots',
'kv_files', 'kv_files',
'languages', 'languages',
@@ -153,11 +145,11 @@ class Config:
self.conf_path = os.path.isfile(pathname) and pathname or None self.conf_path = os.path.isfile(pathname) and pathname or None
self.base = os.path.dirname(pathname) self.base = os.path.dirname(pathname)
self.parser = ConfigParser.ConfigParser() self.parser = ConfigParser.ConfigParser()
self.parser.optionxform = lambda x: x # don't case-normalize option names.
self.parser.read(self.conf_path or []) self.parser.read(self.conf_path or [])
for section in self.parser.sections(): 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) self._process_section(self.parser, section, section)
if vhost and self.parser.has_section('vhosts'): if vhost and self.parser.has_section('vhosts'):
@@ -180,7 +172,6 @@ class Config:
fname = string.replace(fname, '%lang%', language) fname = string.replace(fname, '%lang%', language)
parser = ConfigParser.ConfigParser() parser = ConfigParser.ConfigParser()
parser.optionxform = lambda x: x # don't case-normalize option names.
parser.read(os.path.join(self.base, fname)) parser.read(os.path.join(self.base, fname))
for section in parser.sections(): for section in parser.sections():
for option in parser.options(section): for option in parser.options(section):
@@ -223,7 +214,7 @@ class Config:
setattr(sc, opt, value) 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 """Return 1 iff SECTION is an allowed section, defined as being
explicitly present in the ALLOWED_SECTIONS list or present in the explicitly present in the ALLOWED_SECTIONS list or present in the
form 'someprefix-*' in that list.""" form 'someprefix-*' in that list."""
@@ -236,7 +227,7 @@ class Config:
return 1 return 1
return 0 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 """Test if SECTION is an allowed override section for sections of
type SECTYPE ('vhosts' or 'root', currently) and type-specifier type SECTYPE ('vhosts' or 'root', currently) and type-specifier
SECSPEC (a rootname or vhostname, currently). If it is, return SECSPEC (a rootname or vhostname, currently). If it is, return
@@ -249,7 +240,7 @@ class Config:
if section[:lcv] != cv: if section[:lcv] != cv:
return None return None
base_section = section[lcv:] base_section = section[lcv:]
if self._is_allowed_section(base_section, if self._is_allowed_section(parser, base_section,
self._allowed_overrides[sectype]): self._allowed_overrides[sectype]):
return base_section return base_section
raise IllegalOverrideSection(sectype, section) raise IllegalOverrideSection(sectype, section)
@@ -262,7 +253,8 @@ class Config:
# Overlay any option sections associated with this vhost name. # Overlay any option sections associated with this vhost name.
for section in parser.sections(): 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: if base_section:
self._process_section(parser, section, base_section) self._process_section(parser, section, base_section)
@@ -288,7 +280,8 @@ class Config:
return return
for section in self.parser.sections(): 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: if base_section:
# We can currently only deal with root overlays happening # We can currently only deal with root overlays happening
# once, so check that we've not yet done any overlaying of # once, so check that we've not yet done any overlaying of
@@ -329,7 +322,7 @@ class Config:
assert(self.root_options_overlayed == 0) assert(self.root_options_overlayed == 0)
if not self.conf_path: if not self.conf_path:
return None, {} return None
# Figure out the authorizer by searching first for a per-root # Figure out the authorizer by searching first for a per-root
# override, then falling back to the base/vhost configuration. # override, then falling back to the base/vhost configuration.
@@ -400,14 +393,11 @@ class Config:
self.options.allowed_views = ['annotate', 'diff', 'markup', 'roots'] self.options.allowed_views = ['annotate', 'diff', 'markup', 'roots']
self.options.authorizer = None self.options.authorizer = None
self.options.mangle_email_addresses = 0 self.options.mangle_email_addresses = 0
self.options.custom_log_formatting = []
self.options.default_file_view = "log" self.options.default_file_view = "log"
self.options.binary_mime_types = []
self.options.http_expiration_time = 600 self.options.http_expiration_time = 600
self.options.generate_etags = 1 self.options.generate_etags = 1
self.options.svn_ignore_mimetype = 0 self.options.svn_ignore_mimetype = 0
self.options.svn_config_dir = None self.options.svn_config_dir = None
self.options.max_filesize_kbytes = 512
self.options.use_rcsparse = 0 self.options.use_rcsparse = 0
self.options.sort_by = 'file' self.options.sort_by = 'file'
self.options.sort_group_dirs = 1 self.options.sort_group_dirs = 1
@@ -425,12 +415,10 @@ class Config:
self.options.template_dir = "templates" self.options.template_dir = "templates"
self.options.docroot = None self.options.docroot = None
self.options.show_subdir_lastmod = 0 self.options.show_subdir_lastmod = 0
self.options.show_roots_lastmod = 0
self.options.show_logs = 1 self.options.show_logs = 1
self.options.show_log_in_markup = 1 self.options.show_log_in_markup = 1
self.options.cross_copies = 1 self.options.cross_copies = 1
self.options.use_localtime = 0 self.options.use_localtime = 0
self.options.iso8601_timestamps = 0
self.options.short_log_len = 80 self.options.short_log_len = 80
self.options.enable_syntax_coloration = 1 self.options.enable_syntax_coloration = 1
self.options.tabsize = 8 self.options.tabsize = 8
@@ -440,7 +428,6 @@ class Config:
self.options.use_re_search = 0 self.options.use_re_search = 0
self.options.dir_pagesize = 0 self.options.dir_pagesize = 0
self.options.log_pagesize = 0 self.options.log_pagesize = 0
self.options.log_pagesextra = 3
self.options.limit_changes = 100 self.options.limit_changes = 100
self.templates.diff = None self.templates.diff = None
@@ -466,8 +453,6 @@ class Config:
self.cvsdb.rss_row_limit = 100 self.cvsdb.rss_row_limit = 100
self.cvsdb.check_database_for_root = 0 self.cvsdb.check_database_for_root = 0
self.query.viewvc_base_url = None
def _startswith(somestr, substr): def _startswith(somestr, substr):
return somestr[:len(substr)] == substr return somestr[:len(substr)] == substr

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -38,12 +38,13 @@ error = "cvsdb error"
## complient database interface ## complient database interface
class CheckinDatabase: class CheckinDatabase:
def __init__(self, host, port, user, passwd, database): def __init__(self, host, port, user, passwd, database, row_limit):
self._host = host self._host = host
self._port = port self._port = port
self._user = user self._user = user
self._passwd = passwd self._passwd = passwd
self._database = database self._database = database
self._row_limit = row_limit
self._version = None self._version = None
## database lookup caches ## database lookup caches
@@ -168,9 +169,6 @@ class CheckinDatabase:
return list return list
def GetCommitsTable(self):
return self._version >= 1 and 'commits' or 'checkins'
def GetTableList(self): def GetTableList(self):
sql = "SHOW TABLES" sql = "SHOW TABLES"
cursor = self.db.cursor() cursor = self.db.cursor()
@@ -311,7 +309,8 @@ class CheckinDatabase:
minus_count = commit.GetMinusCount() or '0' minus_count = commit.GetMinusCount() or '0'
description_id = self.GetDescriptionID(commit.GetDescription()) description_id = self.GetDescriptionID(commit.GetDescription())
sql = "REPLACE INTO %s" % (self.GetCommitsTable()) commits_table = self._version >= 1 and 'commits' or 'checkins'
sql = "REPLACE INTO %s" % (commits_table)
sql = sql + \ sql = sql + \
" (type,ci_when,whoid,repositoryid,dirid,fileid,revision,"\ " (type,ci_when,whoid,repositoryid,dirid,fileid,revision,"\
" stickytag,branchid,addedlines,removedlines,descid)"\ " stickytag,branchid,addedlines,removedlines,descid)"\
@@ -352,17 +351,9 @@ class CheckinDatabase:
match = " LIKE " match = " LIKE "
elif query_entry.match == "glob": elif query_entry.match == "glob":
match = " REGEXP " match = " REGEXP "
# Use fnmatch to translate the glob into a regular # use fnmatch to translate the glob into a regexp
# expression. Sadly, we have to account for the fact
# that in Python 2.6, fnmatch.translate() started
# sticking '\Z(?ms)' at the end of the regular
# expression instead of just '$', and doesn't prepend
# the '^'.
data = fnmatch.translate(data) data = fnmatch.translate(data)
if data[0] != '^': if data[0] != '^': data = '^' + data
data = '^' + data
if data[-7:] == '\Z(?ms)':
data = data[:-7] + '$'
elif query_entry.match == "regex": elif query_entry.match == "regex":
match = " REGEXP " match = " REGEXP "
elif query_entry.match == "notregex": elif query_entry.match == "notregex":
@@ -372,8 +363,8 @@ class CheckinDatabase:
return "(%s)" % (string.join(sqlList, " OR ")) return "(%s)" % (string.join(sqlList, " OR "))
def CreateSQLQueryString(self, query, detect_leftover=0): def CreateSQLQueryString(self, query):
commits_table = self.GetCommitsTable() commits_table = self._version >= 1 and 'commits' or 'checkins'
tableList = [(commits_table, None)] tableList = [(commits_table, None)]
condList = [] condList = []
@@ -453,14 +444,13 @@ class CheckinDatabase:
conditions = string.join(joinConds + condList, " AND ") conditions = string.join(joinConds + condList, " AND ")
conditions = conditions and "WHERE %s" % conditions conditions = conditions and "WHERE %s" % conditions
## apply the query's row limit, if any (so we avoid really ## limit the number of rows requested or we could really slam
## slamming a server with a large database) ## a server with a large database
limit = "" limit = ""
if query.limit: if query.limit:
if detect_leftover:
limit = "LIMIT %s" % (str(query.limit + 1))
else:
limit = "LIMIT %s" % (str(query.limit)) limit = "LIMIT %s" % (str(query.limit))
elif self._row_limit:
limit = "LIMIT %s" % (str(self._row_limit))
sql = "SELECT %s.* FROM %s %s %s %s" \ sql = "SELECT %s.* FROM %s %s %s %s" \
% (commits_table, tables, conditions, order_by, limit) % (commits_table, tables, conditions, order_by, limit)
@@ -468,20 +458,14 @@ class CheckinDatabase:
return sql return sql
def RunQuery(self, query): def RunQuery(self, query):
sql = self.CreateSQLQueryString(query, 1) sql = self.CreateSQLQueryString(query)
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute(sql) cursor.execute(sql)
query.SetExecuted()
row_count = 0
while 1: while 1:
row = cursor.fetchone() row = cursor.fetchone()
if not row: if not row:
break break
row_count = row_count + 1
if query.limit and (row_count > query.limit):
query.SetLimitReached()
break
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID, (dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines, dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
@@ -520,15 +504,13 @@ class CheckinDatabase:
if file_id == None: if file_id == None:
return None return None
sql = "SELECT type, ci_when, whoid, repositoryid, dirid, fileid, " \ commits_table = self._version >= 1 and 'commits' or 'checkins'
"revision, stickytag, branchid, addedlines, removedlines, " \ sql = "SELECT * FROM %s WHERE "\
"descid "\
" FROM %s WHERE "\
" repositoryid=%%s "\ " repositoryid=%%s "\
" AND dirid=%%s"\ " AND dirid=%%s"\
" AND fileid=%%s"\ " AND fileid=%%s"\
" AND revision=%%s"\ " AND revision=%%s"\
% (self.GetCommitsTable()) % (commits_table)
sql_args = (repository_id, dir_id, file_id, commit.GetRevision()) sql_args = (repository_id, dir_id, file_id, commit.GetRevision())
cursor = self.db.cursor() cursor = self.db.cursor()
@@ -545,9 +527,10 @@ class CheckinDatabase:
def sql_delete(self, table, key, value, keep_fkey = None): def sql_delete(self, table, key, value, keep_fkey = None):
sql = "DELETE FROM %s WHERE %s=%%s" % (table, key) sql = "DELETE FROM %s WHERE %s=%%s" % (table, key)
sql_args = (value, ) sql_args = (value, )
commits_table = self._version >= 1 and 'commits' or 'checkins'
if keep_fkey: if keep_fkey:
sql += " AND %s NOT IN (SELECT %s FROM %s WHERE %s = %%s)" \ sql += " AND %s NOT IN (SELECT %s FROM %s WHERE %s = %%s)" \
% (key, keep_fkey, self.GetCommitsTable(), keep_fkey) % (key, keep_fkey, commits_table, keep_fkey)
sql_args = (value, value) sql_args = (value, value)
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute(sql, sql_args) cursor.execute(sql, sql_args)
@@ -573,10 +556,7 @@ class CheckinDatabase:
self.sql_purge('descs', 'id', 'descid', 'commits') self.sql_purge('descs', 'id', 'descid', 'commits')
self.sql_purge('people', 'id', 'whoid', 'commits') self.sql_purge('people', 'id', 'whoid', 'commits')
else: else:
sql = "SELECT type, ci_when, whoid, repositoryid, dirid, " \ sql = "SELECT * FROM checkins WHERE repositoryid=%s"
"fileid, revision, stickytag, branchid, addedlines, " \
"removedlines, descid "\
" FROM checkins WHERE repositoryid=%s"
sql_args = (rep_id, ) sql_args = (rep_id, )
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute(sql, sql_args) cursor.execute(sql, sql_args)
@@ -789,9 +769,8 @@ class QueryEntry:
self.data = data self.data = data
self.match = match self.match = match
## CheckinDatabaseQuery is an object which contains the search ## CheckinDatabaseQueryData is a object which contains the search parameters
## parameters for a query to the Checkin Database and -- after the ## for a query to the CheckinDatabase
## query is executed -- the data returned by the query.
class CheckinDatabaseQuery: class CheckinDatabaseQuery:
def __init__(self): def __init__(self):
## sorting ## sorting
@@ -811,7 +790,6 @@ class CheckinDatabaseQuery:
## limit on number of rows to return ## limit on number of rows to return
self.limit = None self.limit = None
self.limit_reached = 0
## list of commits -- filled in by CVS query ## list of commits -- filled in by CVS query
self.commit_list = [] self.commit_list = []
@@ -820,9 +798,6 @@ class CheckinDatabaseQuery:
## are added ## are added
self.commit_cb = None self.commit_cb = None
## has this query been run?
self.executed = 0
def SetRepository(self, repository, match = "exact"): def SetRepository(self, repository, match = "exact"):
self.repository_list.append(QueryEntry(repository, match)) self.repository_list.append(QueryEntry(repository, match))
@@ -868,20 +843,6 @@ class CheckinDatabaseQuery:
def AddCommit(self, commit): def AddCommit(self, commit):
self.commit_list.append(commit) self.commit_list.append(commit)
def SetExecuted(self):
self.executed = 1
def SetLimitReached(self):
self.limit_reached = 1
def GetLimitReached(self):
assert self.executed
return self.limit_reached
def GetCommitList(self):
assert self.executed
return self.commit_list
## ##
## entrypoints ## entrypoints
@@ -900,7 +861,7 @@ def ConnectDatabase(cfg, readonly=0):
user = cfg.cvsdb.user user = cfg.cvsdb.user
passwd = cfg.cvsdb.passwd passwd = cfg.cvsdb.passwd
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd, db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd,
cfg.cvsdb.database_name) cfg.cvsdb.database_name, cfg.cvsdb.row_limit)
db.Connect() db.Connect()
return db return db

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

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

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -20,7 +20,7 @@ import difflib
import sys import sys
import re import re
import ezt import ezt
import sapi import cgi
def sidebyside(fromlines, tolines, context): def sidebyside(fromlines, tolines, context):
"""Generate side by side diff""" """Generate side by side diff"""
@@ -49,18 +49,18 @@ def _mdiff_split(flag, (line_number, text)):
while True: while True:
m = _re_mdiff.search(text, pos) m = _re_mdiff.search(text, pos)
if not m: if not m:
segments.append(_item(text=sapi.escape(text[pos:]), type=None)) segments.append(_item(text=cgi.escape(text[pos:]), type=None))
break break
if m.start() > pos: 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) == "+": 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) == "-": 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) == "^": 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() pos = m.end()
@@ -166,12 +166,12 @@ def _differ_split(row, guide):
for m in _re_differ.finditer(guide, pos): for m in _re_differ.finditer(guide, pos):
if m.start() > pos: if m.start() > pos:
segments.append(_item(text=sapi.escape(line[pos:m.start()]), type=None)) segments.append(_item(text=cgi.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[m.start():m.end()]),
type="change")) type="change"))
pos = m.end() 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, return _item(gap=ezt.boolean(gap), type=type, segments=segments,
left_number=left_number, right_number=right_number) left_number=left_number, right_number=right_number)

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -217,9 +217,8 @@ def decode_command(cmd):
else: else:
return "exact" return "exact"
def form_to_cvsdb_query(cfg, form_data): def form_to_cvsdb_query(form_data):
query = cvsdb.CreateCheckinQuery() query = cvsdb.CreateCheckinQuery()
query.SetLimit(cfg.cvsdb.row_limit)
if form_data.repository: if form_data.repository:
for cmd, str in listparse_string(form_data.repository): for cmd, str in listparse_string(form_data.repository):
@@ -313,7 +312,11 @@ def is_forbidden(cfg, cvsroot_name, module):
def build_commit(server, cfg, desc, files, cvsroots, viewvc_link): def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
ob = _item(num_files=len(files), files=[]) 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: for commit in files:
repository = commit.GetRepository() repository = commit.GetRepository()
@@ -347,10 +350,9 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
except: except:
raise Exception, str([directory, commit.GetFile()]) raise Exception, str([directory, commit.GetFile()])
## If we couldn't find the cvsroot path configured in the ## if we couldn't find the cvsroot path configured in the
## viewvc.conf file, or we don't have a VIEWVC_LINK, then ## viewvc.conf file, then don't make the link
## don't make the link. if cvsroot_name:
if cvsroot_name and viewvc_link:
flink = '[%s] <a href="%s/%s?root=%s">%s</a>' % ( flink = '[%s] <a href="%s/%s?root=%s">%s</a>' % (
cvsroot_name, viewvc_link, urllib.quote(file), cvsroot_name, viewvc_link, urllib.quote(file),
cvsroot_name, file) cvsroot_name, file)
@@ -378,15 +380,12 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
return ob return ob
def run_query(server, cfg, form_data, viewvc_link): def run_query(server, cfg, form_data, viewvc_link):
query = form_to_cvsdb_query(cfg, form_data) query = form_to_cvsdb_query(form_data)
db = cvsdb.ConnectDatabaseReadOnly(cfg) db = cvsdb.ConnectDatabaseReadOnly(cfg)
db.RunQuery(query) db.RunQuery(query)
commit_list = query.GetCommitList() if not query.commit_list:
if not commit_list: return [ ]
return [ ], 0
row_limit_reached = query.GetLimitReached()
commits = [ ] commits = [ ]
files = [ ] files = [ ]
@@ -397,8 +396,8 @@ def run_query(server, cfg, form_data, viewvc_link):
for key, value in rootitems: for key, value in rootitems:
cvsroots[cvsdb.CleanRepository(value)] = key cvsroots[cvsdb.CleanRepository(value)] = key
current_desc = commit_list[0].GetDescription() current_desc = query.commit_list[0].GetDescription()
for commit in commit_list: for commit in query.commit_list:
desc = commit.GetDescription() desc = commit.GetDescription()
if current_desc == desc: if current_desc == desc:
files.append(commit) files.append(commit)
@@ -421,7 +420,7 @@ def run_query(server, cfg, form_data, viewvc_link):
return len(commit.files) > 0 return len(commit.files) > 0
commits = filter(_only_with_files, commits) commits = filter(_only_with_files, commits)
return commits, row_limit_reached return commits
def main(server, cfg, viewvc_link): def main(server, cfg, viewvc_link):
try: try:
@@ -430,32 +429,29 @@ def main(server, cfg, viewvc_link):
form_data = FormData(form) form_data = FormData(form)
if form_data.valid: if form_data.valid:
commits, row_limit_reached = run_query(server, cfg, commits = run_query(server, cfg, form_data, viewvc_link)
form_data, viewvc_link)
query = None query = None
else: else:
commits = [ ] commits = [ ]
row_limit_reached = 0
query = 'skipped' query = 'skipped'
docroot = cfg.options.docroot
if docroot is None and viewvc_link:
docroot = viewvc_link + '/' + viewvc.docroot_magic_path
data = ezt.TemplateData({ data = ezt.TemplateData({
'cfg' : cfg, 'cfg' : cfg,
'address' : cfg.general.address, 'address' : cfg.general.address,
'vsn' : viewvc.__version__, 'vsn' : viewvc.__version__,
'repository' : server.escape(form_data.repository), 'repository' : server.escape(form_data.repository, 1),
'branch' : server.escape(form_data.branch), 'branch' : server.escape(form_data.branch, 1),
'directory' : server.escape(form_data.directory), 'directory' : server.escape(form_data.directory, 1),
'file' : server.escape(form_data.file), 'file' : server.escape(form_data.file, 1),
'who' : server.escape(form_data.who), 'who' : server.escape(form_data.who, 1),
'docroot' : docroot, 'docroot' : cfg.options.docroot is None \
and viewvc_link + '/' + viewvc.docroot_magic_path \
or cfg.options.docroot,
'sortby' : form_data.sortby, 'sortby' : form_data.sortby,
'date' : form_data.date, 'date' : form_data.date,
'query' : query, 'query' : query,
'row_limit_reached' : ezt.boolean(row_limit_reached),
'commits' : commits, 'commits' : commits,
'num_commits' : len(commits), 'num_commits' : len(commits),
'rss_href' : None, 'rss_href' : None,

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -20,27 +20,13 @@ import string
import os import os
import sys import sys
import re import re
import cgi
# global server object. It will be either a CgiServer, a WsgiServer, # global server object. It will be either a CgiServer or a proxy to
# or a proxy to an AspServer or ModPythonServer object. # an AspServer or ModPythonServer object.
server = None 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: class Server:
def __init__(self): def __init__(self):
self.pageGlobals = {} self.pageGlobals = {}
@@ -48,9 +34,6 @@ class Server:
def self(self): def self(self):
return self return self
def escape(self, s):
return escape(s)
def close(self): def close(self):
pass pass
@@ -146,6 +129,9 @@ class CgiServer(Server):
global server global server
server = self server = self
global cgi
import cgi
def addheader(self, name, value): def addheader(self, name, value):
self.headers.append((name, value)) self.headers.append((name, value))
@@ -174,6 +160,9 @@ class CgiServer(Server):
self.header(status='301 Moved') self.header(status='301 Moved')
sys.stdout.write('This document is located <a href="%s">here</a>.\n' % url) 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): def getenv(self, name, value=None):
ret = os.environ.get(name, value) ret = os.environ.get(name, value)
if self.iis and name == 'PATH_INFO' and ret: if self.iis and name == 'PATH_INFO' and ret:
@@ -198,63 +187,6 @@ class CgiServer(Server):
return sys.stdout 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 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): class AspServer(ThreadedServer):
def __init__(self, Server, Request, Response, Application): def __init__(self, Server, Request, Response, Application):
ThreadedServer.__init__(self) ThreadedServer.__init__(self)
@@ -287,6 +219,9 @@ class AspServer(ThreadedServer):
def redirect(self, url): def redirect(self, url):
self.response.Redirect(url) self.response.Redirect(url)
def escape(self, s, quote = None):
return self.server.HTMLEncode(str(s))
def getenv(self, name, value = None): def getenv(self, name, value = None):
ret = self.request.ServerVariables(name)() ret = self.request.ServerVariables(name)()
if not type(ret) is types.UnicodeType: if not type(ret) is types.UnicodeType:
@@ -348,6 +283,9 @@ class ModPythonServer(ThreadedServer):
self.request = request self.request = request
self.headerSent = 0 self.headerSent = 0
global cgi
import cgi
def addheader(self, name, value): def addheader(self, name, value):
self.request.headers_out.add(name, value) self.request.headers_out.add(name, value)
@@ -370,6 +308,9 @@ class ModPythonServer(ThreadedServer):
self.request.write("You are being redirected to <a href=\"%s\">%s</a>" self.request.write("You are being redirected to <a href=\"%s\">%s</a>"
% (url, url)) % (url, url))
def escape(self, s, quote = None):
return cgi.escape(s, quote)
def getenv(self, name, value = None): def getenv(self, name, value = None):
try: try:
return self.request.subprocess_env[name] return self.request.subprocess_env[name]

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 2006-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -30,14 +30,6 @@ class GenericViewVCAuthorizer:
"""Return 1 iff the associated username is permitted to read ROOTNAME.""" """Return 1 iff the associated username is permitted to read ROOTNAME."""
pass 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): def check_path_access(self, rootname, path_parts, pathtype, rev=None):
"""Return 1 iff the associated username is permitted to read """Return 1 iff the associated username is permitted to read
revision REV of the path PATH_PARTS (of type PATHTYPE) in revision REV of the path PATH_PARTS (of type PATHTYPE) in
@@ -53,8 +45,5 @@ class ViewVCAuthorizer(GenericViewVCAuthorizer):
def check_root_access(self, rootname): def check_root_access(self, rootname):
return 1 return 1
def check_universal_access(self, rootname):
return 1
def check_path_access(self, rootname, path_parts, pathtype, rev=None): def check_path_access(self, rootname, path_parts, pathtype, rev=None):
return 1 return 1

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 2006-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -24,13 +24,6 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
def check_root_access(self, rootname): def check_root_access(self, rootname):
return 1 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): def check_path_access(self, rootname, path_parts, pathtype, rev=None):
# No path? No problem. # No path? No problem.
if not path_parts: if not path_parts:

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 2008-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 2008 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -46,13 +46,6 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
def check_root_access(self, rootname): def check_root_access(self, rootname):
return self._check_root_path_access(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): def check_path_access(self, rootname, path_parts, pathtype, rev=None):
root_path = rootname root_path = rootname
if path_parts: if path_parts:

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 2006-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -34,9 +34,9 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
# See if the admin wants us to do case normalization of usernames. # See if the admin wants us to do case normalization of usernames.
self.force_username_case = params.get('force_username_case') self.force_username_case = params.get('force_username_case')
if self.force_username_case == "upper": if self.force_username_case == "upper":
self.username = username and string.upper(username) or username self.username = username.upper()
elif self.force_username_case == "lower": elif self.force_username_case == "lower":
self.username = username and string.lower(username) or username self.username = username.lower()
elif not self.force_username_case: elif not self.force_username_case:
self.username = username self.username = username
else: else:
@@ -54,10 +54,7 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
# option names. # option names.
cp = ConfigParser() cp = ConfigParser()
cp.optionxform = lambda x: x cp.optionxform = lambda x: x
try:
cp.read(self.authz_file) cp.read(self.authz_file)
except:
raise debug.ViewVCException("Unable to parse configured authzfile file")
# Figure out if there are any aliases for the current username # Figure out if there are any aliases for the current username
aliases = [] aliases = []
@@ -224,36 +221,6 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
paths = self._get_paths_for_root(rootname) paths = self._get_paths_for_root(rootname)
return (paths is not None) and 1 or 0 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): def check_path_access(self, rootname, path_parts, pathtype, rev=None):
# Crawl upward from the path represented by PATH_PARTS toward to # Crawl upward from the path represented by PATH_PARTS toward to
# the root of the repository, looking for an explicitly grant or # the root of the repository, looking for an explicitly grant or

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -76,7 +76,7 @@ class Repository:
""" """
pass pass
def openfile(self, path_parts, rev, options): def openfile(self, path_parts, rev):
"""Open a file object to read file contents at a given path and revision. """Open a file object to read file contents at a given path and revision.
The return value is a 2-tuple of containg the file object and revision The return value is a 2-tuple of containg the file object and revision
@@ -86,8 +86,6 @@ class Repository:
of the repository. e.g. ["subdir1", "subdir2", "filename"] of the repository. e.g. ["subdir1", "subdir2", "filename"]
rev is the revision of the file to check out rev is the revision of the file to check out
options is a dictionary of implementation specific options
""" """
def listdir(self, path_parts, rev, options): def listdir(self, path_parts, rev, options):
@@ -170,29 +168,20 @@ class Repository:
Return value is a python file object Return value is a python file object
""" """
def annotate(self, path_parts, rev, include_text=False): def annotate(self, path_parts, rev):
"""Return a list of Annotation object, sorted by their """Return a list of annotate file content lines and a revision.
"line_number" components, which describe the lines of given
version of a file.
The file path is specified as a list of components, relative to The result is a list of Annotation objects, sorted by their
the root of the repository. e.g. ["subdir1", "subdir2", "filename"] line_number components.
"""
rev is the revision of the item to return information about.
If include_text is true, populate the Annotation objects' "text"
members with the corresponding line of file content; otherwise,
leave that member set to None."""
def revinfo(self, rev): def revinfo(self, rev):
"""Return information about a global revision """Return information about a global revision
rev is the revision of the item to return information about rev is the revision of the item to return information about
Return value is a 5-tuple containing: the date, author, log Return value is a 4-tuple containing the date, author, log
message, a list of ChangedPath items representing paths changed, message, and a list of ChangedPath items representing paths changed
and a dictionary mapping property names to property values for
properties stored on an item.
Raise vclib.UnsupportedFeature if the version control system Raise vclib.UnsupportedFeature if the version control system
doesn't support a global revision concept. doesn't support a global revision concept.
@@ -208,20 +197,6 @@ class Repository:
rev is the revision of the item to return information about rev is the revision of the item to return information about
""" """
def filesize(self, path_parts, rev):
"""Return the size of a versioned file's contents if it can be
obtained without a brute force measurement; -1 otherwise.
NOTE: Callers that require a filesize answer when this function
returns -1 may obtain it by measuring the data returned via
openfile().
The path is specified as a list of components, relative to the root
of the repository. e.g. ["subdir1", "subdir2", "filename"]
rev is the revision of the item to return information about
"""
# ====================================================================== # ======================================================================
class DirEntry: class DirEntry:

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -14,44 +14,25 @@ import os.path
def canonicalize_rootpath(rootpath): def canonicalize_rootpath(rootpath):
assert os.path.isabs(rootpath)
return os.path.normpath(rootpath) return os.path.normpath(rootpath)
def _is_cvsroot(path):
return os.path.exists(os.path.join(path, "CVSROOT", "config"))
def expand_root_parent(parent_path): def expand_root_parent(parent_path):
# Each subdirectory of PARENT_PATH that contains a child # Each subdirectory of PARENT_PATH that contains a child
# "CVSROOT/config" is added the set of returned roots. Or, if the # "CVSROOT/config" is added the set of returned roots. Or, if the
# PARENT_PATH itself contains a child "CVSROOT/config", then all its # PARENT_PATH itself contains a child "CVSROOT/config", then all its
# subdirectories are returned as roots. # subdirectories are returned as roots.
assert os.path.isabs(parent_path)
roots = {} roots = {}
subpaths = os.listdir(parent_path) subpaths = os.listdir(parent_path)
cvsroot = os.path.exists(os.path.join(parent_path, "CVSROOT", "config"))
for rootname in subpaths: for rootname in subpaths:
rootpath = os.path.join(parent_path, rootname) rootpath = os.path.join(parent_path, rootname)
if _is_cvsroot(parent_path) or _is_cvsroot(rootpath): if cvsroot \
or (os.path.exists(os.path.join(rootpath, "CVSROOT", "config"))):
roots[rootname] = canonicalize_rootpath(rootpath) roots[rootname] = canonicalize_rootpath(rootpath)
return roots return roots
def find_root_in_parent(parent_path, rootname):
"""Search PARENT_PATH for a root named ROOTNAME, returning the
canonicalized ROOTPATH of the root if found; return None if no such
root is found."""
# Is PARENT_PATH itself a CVS repository? If so, we allow ROOTNAME
# to be any subdir within it. Otherwise, we expect
# PARENT_PATH/ROOTNAME to be a CVS repository.
assert os.path.isabs(parent_path)
rootpath = os.path.join(parent_path, rootname)
if (_is_cvsroot(parent_path) and os.path.exists(rootpath)) \
or _is_cvsroot(rootpath):
return canonicalize_rootpath(rootpath)
return None
def CVSRepository(name, rootpath, authorizer, utilities, use_rcsparse): def CVSRepository(name, rootpath, authorizer, utilities, use_rcsparse):
rootpath = canonicalize_rootpath(rootpath) rootpath = canonicalize_rootpath(rootpath)
if use_rcsparse: if use_rcsparse:

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -40,11 +40,6 @@ class BaseCVSRepository(vclib.Repository):
if not vclib.check_root_access(self): if not vclib.check_root_access(self):
raise vclib.ReposNotFound(name) 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): def rootname(self):
return self.name return self.name
@@ -150,11 +145,6 @@ class BaseCVSRepository(vclib.Repository):
rcsfile = self.rcsfile(path_parts, 1) rcsfile = self.rcsfile(path_parts, 1)
return os.access(rcsfile, os.X_OK) return os.access(rcsfile, os.X_OK)
def filesize(self, path_parts, rev):
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % (_path_join(path_parts)))
return -1
class BinCVSRepository(BaseCVSRepository): class BinCVSRepository(BaseCVSRepository):
def _get_tip_revision(self, rcs_file, rev=None): def _get_tip_revision(self, rcs_file, rev=None):
@@ -172,14 +162,7 @@ class BinCVSRepository(BaseCVSRepository):
return revs[-1] return revs[-1]
return None return None
def openfile(self, path_parts, rev, options): def openfile(self, path_parts, rev):
"""see vclib.Repository.openfile docstring
Option values recognized by this implementation:
cvs_oldkeywords
boolean. true to use the original keyword substitution values.
"""
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." raise vclib.Error("Path '%s' is not a file."
% (string.join(path_parts, "/"))) % (string.join(path_parts, "/")))
@@ -187,14 +170,12 @@ class BinCVSRepository(BaseCVSRepository):
rev_flag = '-p' rev_flag = '-p'
else: else:
rev_flag = '-p' + rev rev_flag = '-p' + rev
if options.get('cvs_oldkeywords', 0):
kv_flag = '-ko'
else:
kv_flag = '-kkv'
full_name = self.rcsfile(path_parts, root=1, v=0) full_name = self.rcsfile(path_parts, root=1, v=0)
used_rlog = 0 used_rlog = 0
tip_rev = None # used only if we have to fallback to using rlog tip_rev = None # used only if we have to fallback to using rlog
fp = self.rcs_popen('co', (kv_flag, rev_flag, full_name), 'rb')
fp = self.rcs_popen('co', (rev_flag, full_name), 'rb')
try: try:
filename, revision = _parse_co_header(fp) filename, revision = _parse_co_header(fp)
except COMissingRevision: except COMissingRevision:
@@ -337,13 +318,13 @@ class BinCVSRepository(BaseCVSRepository):
args = rcs_args args = rcs_args
return popen.popen(cmd, args, mode, capture_err) return popen.popen(cmd, args, mode, capture_err)
def annotate(self, path_parts, rev=None, include_text=False): def annotate(self, path_parts, rev=None):
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." raise vclib.Error("Path '%s' is not a file."
% (string.join(path_parts, "/"))) % (string.join(path_parts, "/")))
from vclib.ccvs import blame from vclib.ccvs import blame
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev, include_text) source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
return source, source.revision return source, source.revision
def revinfo(self, rev): def revinfo(self, rev):
@@ -1041,16 +1022,16 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
file.errors.append("rlog error: %s" % msg) file.errors.append("rlog error: %s" % msg)
continue continue
tag = None
if view_tag == 'MAIN' or view_tag == 'HEAD': if view_tag == 'MAIN' or view_tag == 'HEAD':
tag = Tag(None, default_branch) tag = Tag(None, default_branch)
elif taginfo.has_key(view_tag): elif taginfo.has_key(view_tag):
tag = Tag(None, taginfo[view_tag]) tag = Tag(None, taginfo[view_tag])
elif view_tag and (eof != _EOF_FILE): elif view_tag:
# the tag wasn't found, so skip this file (unless we already # the tag wasn't found, so skip this file
# know there's nothing left of it to read)
_skip_file(rlog) _skip_file(rlog)
eof = _EOF_FILE eof = 1
else:
tag = None
# we don't care about the specific values -- just the keys and whether # we don't care about the specific values -- just the keys and whether
# the values point to branches or revisions. this the fastest way to # the values point to branches or revisions. this the fastest way to

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org> # Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
# #
# By using this file, you agree to the terms and conditions set forth in # By using this file, you agree to the terms and conditions set forth in
@@ -414,7 +414,7 @@ class CVSParser(rcsparse.Sink):
class BlameSource: class BlameSource:
def __init__(self, rcs_file, opt_rev=None, include_text=False): def __init__(self, rcs_file, opt_rev=None):
# Parse the CVS file # Parse the CVS file
parser = CVSParser() parser = CVSParser()
revision = parser.parse_cvs_file(rcs_file, opt_rev) revision = parser.parse_cvs_file(rcs_file, opt_rev)
@@ -428,7 +428,6 @@ class BlameSource:
self.lines = lines self.lines = lines
self.num_lines = count self.num_lines = count
self.parser = parser self.parser = parser
self.include_text = include_text
# keep track of where we are during an iteration # keep track of where we are during an iteration
self.idx = -1 self.idx = -1
@@ -448,8 +447,6 @@ class BlameSource:
line_number = idx + 1 line_number = idx + 1
author = self.parser.revision_author[rev] author = self.parser.revision_author[rev]
thisline = self.lines[idx] thisline = self.lines[idx]
if not self.include_text:
thisline = None
### TODO: Put a real date in here. ### TODO: Put a real date in here.
item = vclib.Annotation(thisline, line_number, rev, prev_rev, author, None) item = vclib.Annotation(thisline, line_number, rev, prev_rev, author, None)
self.last = item self.last = item

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -127,9 +127,9 @@ class CCVSRepository(BaseCVSRepository):
% (string.join(path_parts2, "/"))) % (string.join(path_parts2, "/")))
temp1 = tempfile.mktemp() temp1 = tempfile.mktemp()
open(temp1, 'wb').write(self.openfile(path_parts1, rev1, {})[0].getvalue()) open(temp1, 'wb').write(self.openfile(path_parts1, rev1)[0].getvalue())
temp2 = tempfile.mktemp() temp2 = tempfile.mktemp()
open(temp2, 'wb').write(self.openfile(path_parts2, rev2, {})[0].getvalue()) open(temp2, 'wb').write(self.openfile(path_parts2, rev2)[0].getvalue())
r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1] r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1] r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
@@ -142,17 +142,17 @@ class CCVSRepository(BaseCVSRepository):
return vclib._diff_fp(temp1, temp2, info1, info2, return vclib._diff_fp(temp1, temp2, info1, info2,
self.utilities.diff or 'diff', diff_args) self.utilities.diff or 'diff', diff_args)
def annotate(self, path_parts, rev=None, include_text=False): def annotate(self, path_parts, rev=None):
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." raise vclib.Error("Path '%s' is not a file."
% (string.join(path_parts, "/"))) % (string.join(path_parts, "/")))
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev, include_text) source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
return source, source.revision return source, source.revision
def revinfo(self, rev): def revinfo(self, rev):
raise vclib.UnsupportedFeature raise vclib.UnsupportedFeature
def openfile(self, path_parts, rev, options): def openfile(self, path_parts, rev=None):
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." raise vclib.Error("Path '%s' is not a file."
% (string.join(path_parts, "/"))) % (string.join(path_parts, "/")))
@@ -200,7 +200,6 @@ class InfoSink(MatchingSink):
self.matching_rev = None self.matching_rev = None
self.perfect_match = 0 self.perfect_match = 0
self.lockinfo = { } self.lockinfo = { }
self.saw_revision = False
def define_tag(self, name, revision): def define_tag(self, name, revision):
MatchingSink.define_tag(self, name, revision) MatchingSink.define_tag(self, name, revision)
@@ -214,17 +213,10 @@ class InfoSink(MatchingSink):
self.entry.absent = 1 self.entry.absent = 1
raise rcsparse.RCSStopParser raise rcsparse.RCSStopParser
def parse_completed(self):
if not self.saw_revision:
#self.entry.errors.append("No revisions exist on %s" % (view_tag or "MAIN"))
self.entry.absent = 1
def set_locker(self, rev, locker): def set_locker(self, rev, locker):
self.lockinfo[rev] = locker self.lockinfo[rev] = locker
def define_revision(self, revision, date, author, state, branches, next): def define_revision(self, revision, date, author, state, branches, next):
self.saw_revision = True
if self.perfect_match: if self.perfect_match:
return return
@@ -232,18 +224,14 @@ class InfoSink(MatchingSink):
rev = Revision(revision, date, author, state == "dead") rev = Revision(revision, date, author, state == "dead")
rev.lockinfo = self.lockinfo.get(revision) rev.lockinfo = self.lockinfo.get(revision)
# perfect match if revision number matches tag number or if # perfect match if revision number matches tag number or if revision is on
# revision is on trunk and tag points to trunk. imperfect match # trunk and tag points to trunk. imperfect match if tag refers to a branch
# if tag refers to a branch and either a) this revision is the # and this revision is the highest revision so far found on that branch
# highest revision so far found on that branch, or b) this
# revision is the branchpoint.
perfect = ((rev.number == tag.number) or perfect = ((rev.number == tag.number) or
(not tag.number and len(rev.number) == 2)) (not tag.number and len(rev.number) == 2))
if perfect or (tag.is_branch and \ if perfect or (tag.is_branch and tag.number == rev.number[:-1] and
((tag.number == rev.number[:-1] and
(not self.matching_rev or (not self.matching_rev or
rev.number > self.matching_rev.number)) or rev.number > self.matching_rev.number)):
(rev.number == tag.number[:-1]))):
self.matching_rev = rev self.matching_rev = rev
self.perfect_match = perfect self.perfect_match = perfect

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -194,7 +194,6 @@ class _Parser:
else: else:
# Chew up "newphrase" # Chew up "newphrase"
# warn("Unexpected RCS token: $token\n") # warn("Unexpected RCS token: $token\n")
while self.ts.get() != ';':
pass pass
else: else:
if f is None: if f is None:

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

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

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -15,50 +15,17 @@
import os import os
import os.path import os.path
import re import re
import urllib
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://') _re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
def _canonicalize_path(path):
import svn.core
try:
return svn.core.svn_path_canonicalize(path)
except AttributeError: # svn_path_canonicalize() appeared in 1.4.0 bindings
# There's so much more that we *could* do here, but if we're
# here at all its because there's a really old Subversion in
# place, and those older Subversion versions cared quite a bit
# less about the specifics of path canonicalization.
if re.search(_re_url, path):
return path.rstrip('/')
else:
return os.path.normpath(path)
def canonicalize_rootpath(rootpath): def canonicalize_rootpath(rootpath):
# Try to canonicalize the rootpath using Subversion semantics. try:
rootpath = _canonicalize_path(rootpath) import svn.core
return svn.core.svn_path_canonicalize(rootpath)
# ViewVC's support for local repositories is more complete and more except:
# performant than its support for remote ones, so if we're on a if re.search(_re_url, rootpath):
# Unix-y system and we have a file:/// URL, convert it to a local return rootpath[-1] == '/' and rootpath[:-1] or rootpath
# path instead. return os.path.normpath(rootpath)
if os.name == 'posix':
rootpath_lower = rootpath.lower()
if rootpath_lower in ['file://localhost',
'file://localhost/',
'file://',
'file:///'
]:
return '/'
if rootpath_lower.startswith('file://localhost/'):
rootpath = os.path.normpath(urllib.unquote(rootpath[16:]))
elif rootpath_lower.startswith('file:///'):
rootpath = os.path.normpath(urllib.unquote(rootpath[7:]))
# Ensure that we have an absolute path (or URL), and return.
if not re.search(_re_url, rootpath):
assert os.path.isabs(rootpath)
return rootpath
def expand_root_parent(parent_path): def expand_root_parent(parent_path):
@@ -68,7 +35,6 @@ def expand_root_parent(parent_path):
else: else:
# Any subdirectories of PARENT_PATH which themselves have a child # Any subdirectories of PARENT_PATH which themselves have a child
# "format" are returned as roots. # "format" are returned as roots.
assert os.path.isabs(parent_path)
subpaths = os.listdir(parent_path) subpaths = os.listdir(parent_path)
for rootname in subpaths: for rootname in subpaths:
rootpath = os.path.join(parent_path, rootname) rootpath = os.path.join(parent_path, rootname)
@@ -77,20 +43,6 @@ def expand_root_parent(parent_path):
return roots return roots
def find_root_in_parent(parent_path, rootname):
"""Search PARENT_PATH for a root named ROOTNAME, returning the
canonicalized ROOTPATH of the root if found; return None if no such
root is found."""
if not re.search(_re_url, parent_path):
assert os.path.isabs(parent_path)
rootpath = os.path.join(parent_path, rootname)
format_path = os.path.join(rootpath, "format")
if os.path.exists(format_path):
return canonicalize_rootpath(rootpath)
return None
def SubversionRepository(name, rootpath, authorizer, utilities, config_dir): def SubversionRepository(name, rootpath, authorizer, utilities, config_dir):
rootpath = canonicalize_rootpath(rootpath) rootpath = canonicalize_rootpath(rootpath)
if re.search(_re_url, rootpath): if re.search(_re_url, rootpath):

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -18,12 +18,10 @@ import os
import string import string
import re import re
import tempfile import tempfile
import popen2
import time import time
import urllib import urllib
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, \ from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception
_compare_paths, _path_parts, _cleanup_path, \
_rev2optrev, _fix_subversion_exception, \
_split_revprops, _canonicalize_path
from svn import core, delta, client, wc, ra from svn import core, delta, client, wc, ra
@@ -54,71 +52,13 @@ def get_directory_props(ra_session, path, rev):
props = ra.svn_ra_get_dir(ra_session, path, rev) props = ra.svn_ra_get_dir(ra_session, path, rev)
return props return props
def client_log(url, start_rev, end_rev, log_limit, include_changes,
cross_copies, cb_func, ctx):
include_changes = include_changes and 1 or 0
cross_copies = cross_copies and 1 or 0
try:
client.svn_client_log4([url], start_rev, start_rev, end_rev,
log_limit, include_changes, not cross_copies,
0, None, cb_func, ctx)
except AttributeError:
# Wrap old svn_log_message_receiver_t interface with a
# svn_log_entry_t one.
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,
include_changes, not cross_copies, cb_convert, ctx)
def setup_client_ctx(config_dir):
# Ensure that the configuration directory exists.
core.svn_config_ensure(config_dir)
# Fetch the configuration (and 'config' bit thereof).
cfg = core.svn_config_get_config(config_dir)
config = cfg.get(core.SVN_CONFIG_CATEGORY_CONFIG)
# Here's the compat-sensitive part: try to use
# svn_cmdline_create_auth_baton(), and fall back to making our own
# if that fails.
try:
auth_baton = core.svn_cmdline_create_auth_baton(1, None, None, config_dir,
1, 1, config, None)
except AttributeError:
auth_baton = core.svn_auth_open([
client.svn_client_get_simple_provider(),
client.svn_client_get_username_provider(),
client.svn_client_get_ssl_server_trust_file_provider(),
client.svn_client_get_ssl_client_cert_file_provider(),
client.svn_client_get_ssl_client_cert_pw_file_provider(),
])
if config_dir is not None:
core.svn_auth_set_parameter(auth_baton,
core.SVN_AUTH_PARAM_CONFIG_DIR,
config_dir)
# Create, setup, and return the client context baton.
ctx = client.svn_client_create_context()
ctx.config = cfg
ctx.auth_baton = auth_baton
return ctx
### END COMPATABILITY CODE ### ### END COMPATABILITY CODE ###
class LogCollector: class LogCollector:
### TODO: Make this thing authz-aware
def __init__(self, path, show_all_logs, lockinfo, access_check_func): def __init__(self, path, show_all_logs, lockinfo):
# This class uses leading slashes for paths internally # This class uses leading slashes for paths internally
if not path: if not path:
self.path = '/' self.path = '/'
@@ -127,16 +67,8 @@ class LogCollector:
self.logs = [] self.logs = []
self.show_all_logs = show_all_logs self.show_all_logs = show_all_logs
self.lockinfo = lockinfo self.lockinfo = lockinfo
self.access_check_func = access_check_func
self.done = False
def add_log(self, log_entry, pool):
if self.done:
return
paths = log_entry.changed_paths
revision = log_entry.revision
msg, author, date, revprops = _split_revprops(log_entry.revprops)
def add_log(self, paths, revision, author, date, message, pool):
# Changed paths have leading slashes # Changed paths have leading slashes
changed_paths = paths.keys() changed_paths = paths.keys()
changed_paths.sort(lambda a, b: _compare_paths(a, b)) changed_paths.sort(lambda a, b: _compare_paths(a, b))
@@ -156,17 +88,13 @@ class LogCollector:
if change.copyfrom_path: if change.copyfrom_path:
this_path = change.copyfrom_path + self.path[len(changed_path):] this_path = change.copyfrom_path + self.path[len(changed_path):]
if self.show_all_logs or this_path: if self.show_all_logs or this_path:
if self.access_check_func is None \ entry = Revision(revision, _datestr_to_date(date), author, message, None,
or self.access_check_func(self.path[1:], revision): self.lockinfo, self.path[1:], None, None)
entry = Revision(revision, date, author, msg, None, self.lockinfo,
self.path[1:], None, None)
self.logs.append(entry) self.logs.append(entry)
else:
self.done = True
if this_path: if this_path:
self.path = this_path self.path = this_path
def cat_to_tempfile(svnrepos, path, rev): def temp_checkout(svnrepos, path, rev):
"""Check out file revision to temporary file""" """Check out file revision to temporary file"""
temp = tempfile.mktemp() temp = tempfile.mktemp()
stream = core.svn_stream_from_aprfile(temp) stream = core.svn_stream_from_aprfile(temp)
@@ -227,8 +155,21 @@ class RemoteSubversionRepository(vclib.Repository):
def open(self): def open(self):
# Setup the client context baton, complete with non-prompting authstuffs. # Setup the client context baton, complete with non-prompting authstuffs.
self.ctx = setup_client_ctx(self.config_dir) # TODO: svn_cmdline_setup_auth_baton() is mo' better (when available)
core.svn_config_ensure(self.config_dir)
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(),
client.svn_client_get_ssl_server_trust_file_provider(),
client.svn_client_get_ssl_client_cert_file_provider(),
client.svn_client_get_ssl_client_cert_pw_file_provider(),
])
self.ctx.config = core.svn_config_get_config(self.config_dir)
if self.config_dir is not None:
core.svn_auth_set_parameter(self.ctx.auth_baton,
core.SVN_AUTH_PARAM_CONFIG_DIR,
self.config_dir)
ra_callbacks = ra.svn_ra_callbacks_t() ra_callbacks = ra.svn_ra_callbacks_t()
ra_callbacks.auth_baton = self.ctx.auth_baton ra_callbacks.auth_baton = self.ctx.auth_baton
self.ra_session = ra.svn_ra_open(self.rootpath, ra_callbacks, None, self.ra_session = ra.svn_ra_open(self.rootpath, ra_callbacks, None,
@@ -237,10 +178,6 @@ class RemoteSubversionRepository(vclib.Repository):
self._dirent_cache = { } self._dirent_cache = { }
self._revinfo_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): def rootname(self):
return self.name return self.name
@@ -274,16 +211,18 @@ class RemoteSubversionRepository(vclib.Repository):
raise vclib.ItemNotFound(path_parts) raise vclib.ItemNotFound(path_parts)
return pathtype return pathtype
def openfile(self, path_parts, rev, options): def openfile(self, path_parts, rev):
path = self._getpath(path_parts) path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path) raise vclib.Error("Path '%s' is not a file." % path)
rev = self._getrev(rev) rev = self._getrev(rev)
url = self._geturl(path) url = self._geturl(path)
tmp_file = tempfile.mktemp()
stream = core.svn_stream_from_aprfile(tmp_file)
### rev here should be the last history revision of the URL ### rev here should be the last history revision of the URL
fp = SelfCleanFP(cat_to_tempfile(self, path, rev)) client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev), self.ctx)
lh_rev, c_rev = self._get_last_history_rev(path_parts, rev) core.svn_stream_close(stream)
return fp, lh_rev return SelfCleanFP(tmp_file), self._get_last_history_rev(path_parts, rev)
def listdir(self, path_parts, rev, options): def listdir(self, path_parts, rev, options):
path = self._getpath(path_parts) path = self._getpath(path_parts)
@@ -298,8 +237,7 @@ class RemoteSubversionRepository(vclib.Repository):
kind = vclib.DIR kind = vclib.DIR
elif entry.kind == core.svn_node_file: elif entry.kind == core.svn_node_file:
kind = vclib.FILE kind = vclib.FILE
else: if vclib.check_path_access(self, path_parts + [name], kind, rev):
kind = None
entries.append(vclib.DirEntry(name, kind)) entries.append(vclib.DirEntry(name, kind))
return entries return entries
@@ -311,13 +249,11 @@ class RemoteSubversionRepository(vclib.Repository):
dirents, locks = self._get_dirents(path, rev) dirents, locks = self._get_dirents(path, rev)
for entry in entries: for entry in entries:
entry_path_parts = path_parts + [entry.name] entry_path_parts = path_parts + [entry.name]
dirent = dirents.get(entry.name, None) if not vclib.check_path_access(self, entry_path_parts, entry.kind, rev):
# dirents is authz-sanitized, so ensure the entry is found therein.
if dirent is None:
continue continue
# Get authz-sanitized revision metadata. dirent = dirents[entry.name]
entry.date, entry.author, entry.log, revprops, changes = \ entry.date, entry.author, entry.log, changes = \
self._revinfo(dirent.created_rev) self.revinfo(dirent.created_rev)
entry.rev = str(dirent.created_rev) entry.rev = str(dirent.created_rev)
entry.size = dirent.size entry.size = dirent.size
entry.lockinfo = None entry.lockinfo = None
@@ -331,51 +267,29 @@ class RemoteSubversionRepository(vclib.Repository):
rev = self._getrev(rev) rev = self._getrev(rev)
url = self._geturl(path) url = self._geturl(path)
# If this is a file, fetch the lock status and size (as of REV) # Use ls3 to fetch the lock status for this item.
# for this item. lockinfo = None
lockinfo = size_in_rev = None basename = path_parts and path_parts[-1] or ""
if path_type == vclib.FILE: dirents, locks = list_directory(url, _rev2optrev(rev),
basename = path_parts[-1]
list_url = self._geturl(self._getpath(path_parts[:-1]))
dirents, locks = list_directory(list_url, _rev2optrev(rev),
_rev2optrev(rev), 0, self.ctx) _rev2optrev(rev), 0, self.ctx)
if locks.has_key(basename): if locks.has_key(basename):
lockinfo = locks[basename].owner lockinfo = locks[basename].owner
if dirents.has_key(basename):
size_in_rev = dirents[basename].size
# Special handling for the 'svn_latest_log' scenario.
### FIXME: Don't like this hack. We should just introduce
### something more direct in the vclib API.
if options.get('svn_latest_log', 0):
dir_lh_rev, dir_c_rev = self._get_last_history_rev(path_parts, rev)
date, author, log, revprops, changes = self._revinfo(dir_lh_rev)
return [vclib.Revision(dir_lh_rev, str(dir_lh_rev), date, author,
None, log, size_in_rev, lockinfo)]
def _access_checker(check_path, check_rev):
return vclib.check_path_access(self, _path_parts(check_path),
path_type, check_rev)
# It's okay if we're told to not show all logs on a file -- all # It's okay if we're told to not show all logs on a file -- all
# the revisions should match correctly anyway. # the revisions should match correctly anyway.
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0), lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0), lockinfo)
lockinfo, _access_checker)
cross_copies = options.get('svn_cross_copies', 0) cross_copies = options.get('svn_cross_copies', 0)
log_limit = 0 log_limit = 0
if limit: if limit:
log_limit = first + limit log_limit = first + limit
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit, 1, client.svn_client_log2([url], _rev2optrev(rev), _rev2optrev(1),
cross_copies, lc.add_log, self.ctx) log_limit, 1, not cross_copies,
lc.add_log, self.ctx)
revs = lc.logs revs = lc.logs
revs.sort() revs.sort()
prev = None prev = None
for rev in revs: for rev in revs:
# Swap out revision info with stuff from the cache (which is
# authz-sanitized).
rev.date, rev.author, rev.log, revprops, changes \
= self._revinfo(rev.number)
rev.prev = prev rev.prev = prev
prev = rev prev = rev
revs.reverse() revs.reverse()
@@ -395,25 +309,13 @@ class RemoteSubversionRepository(vclib.Repository):
_rev2optrev(rev), 0, self.ctx) _rev2optrev(rev), 0, self.ctx)
return pairs and pairs[0][1] or {} return pairs and pairs[0][1] or {}
def annotate(self, path_parts, rev, include_text=False): def annotate(self, path_parts, rev):
path = self._getpath(path_parts) path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path) raise vclib.Error("Path '%s' is not a file." % path)
rev = self._getrev(rev) rev = self._getrev(rev)
url = self._geturl(path) url = self._geturl(path)
# Examine logs for the file to determine the oldest revision we are
# permitted to see.
log_options = {
'svn_cross_copies' : 1,
'svn_show_all_dir_logs' : 1,
}
revs = self.itemlog(path_parts, rev, vclib.SORTBY_REV, 0, 0, log_options)
oldest_rev = revs[-1].number
# Now calculate the annotation data. Note that we'll not
# inherently trust the provided author and date, because authz
# rules might necessitate that we strip that information out.
blame_data = [] blame_data = []
def _blame_cb(line_no, revision, author, date, def _blame_cb(line_no, revision, author, date,
@@ -421,27 +323,21 @@ class RemoteSubversionRepository(vclib.Repository):
prev_rev = None prev_rev = None
if revision > 1: if revision > 1:
prev_rev = revision - 1 prev_rev = revision - 1
# If we have an invalid revision, clear the date and author
# values. Otherwise, if we have authz filtering to do, use the
# revinfo cache to do so.
if revision < 0:
date = author = None
elif self.auth:
date, author, msg, revprops, changes = self._revinfo(revision)
# Strip text if the caller doesn't want it.
if not include_text:
line = None
blame_data.append(vclib.Annotation(line, line_no+1, revision, prev_rev, blame_data.append(vclib.Annotation(line, line_no+1, revision, prev_rev,
author, date)) author, None))
client.svn_client_blame(url, _rev2optrev(1), _rev2optrev(rev),
_blame_cb, self.ctx)
client.blame2(url, _rev2optrev(rev), _rev2optrev(oldest_rev),
_rev2optrev(rev), _blame_cb, self.ctx)
return blame_data, rev return blame_data, rev
def revinfo(self, rev): def revinfo(self, rev):
return self._revinfo(rev, 1) rev = self._getrev(rev)
cached_info = self._revinfo_cache.get(rev)
if not cached_info:
cached_info = self._revinfo_raw(rev)
self._revinfo_cache[rev] = cached_info
return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}): def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
p1 = self._getpath(path_parts1) p1 = self._getpath(path_parts1)
@@ -456,12 +352,12 @@ class RemoteSubversionRepository(vclib.Repository):
args = vclib._diff_args(type, options) args = vclib._diff_args(type, options)
def _date_from_rev(rev): def _date_from_rev(rev):
date, author, msg, revprops, changes = self._revinfo(rev) date, author, msg, changes = self.revinfo(rev)
return date return date
try: try:
temp1 = cat_to_tempfile(self, p1, r1) temp1 = temp_checkout(self, p1, r1)
temp2 = cat_to_tempfile(self, p2, r2) temp2 = temp_checkout(self, p2, r2)
info1 = p1, _date_from_rev(r1), r1 info1 = p1, _date_from_rev(r1), r1
info2 = p2, _date_from_rev(r2), r2 info2 = p2, _date_from_rev(r2), r2
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args) return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
@@ -475,15 +371,6 @@ class RemoteSubversionRepository(vclib.Repository):
props = self.itemprops(path_parts, rev) # does authz-check props = self.itemprops(path_parts, rev) # does authz-check
return props.has_key(core.SVN_PROP_EXECUTABLE) return props.has_key(core.SVN_PROP_EXECUTABLE)
def filesize(self, path_parts, rev):
path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path)
rev = self._getrev(rev)
dirents, locks = self._get_dirents(self._getpath(path_parts[:-1]), rev)
dirent = dirents.get(path_parts[-1], None)
return dirent.size
def _getpath(self, path_parts): def _getpath(self, path_parts):
return string.join(path_parts, '/') return string.join(path_parts, '/')
@@ -491,11 +378,8 @@ class RemoteSubversionRepository(vclib.Repository):
if rev is None or rev == 'HEAD': if rev is None or rev == 'HEAD':
return self.youngest return self.youngest
try: try:
if type(rev) == type(''):
while rev[0] == 'r':
rev = rev[1:]
rev = int(rev) rev = int(rev)
except: except ValueError:
raise vclib.InvalidRevision(rev) raise vclib.InvalidRevision(rev)
if (rev < 0) or (rev > self.youngest): if (rev < 0) or (rev > self.youngest):
raise vclib.InvalidRevision(rev) raise vclib.InvalidRevision(rev)
@@ -504,139 +388,58 @@ class RemoteSubversionRepository(vclib.Repository):
def _geturl(self, path=None): def _geturl(self, path=None):
if not path: if not path:
return self.rootpath return self.rootpath
path = self.rootpath + '/' + urllib.quote(path) return self.rootpath + '/' + urllib.quote(path, "/*~")
return _canonicalize_path(path)
def _get_dirents(self, path, rev): def _get_dirents(self, path, rev):
"""Return a 2-type of dirents and locks, possibly reading/writing """Return a 2-type of dirents and locks, possibly reading/writing
from a local cache of that information. This functions performs from a local cache of that information."""
authz checks, stripping out unreadable dirents."""
dir_url = self._geturl(path) dir_url = self._geturl(path)
path_parts = _path_parts(path)
if path: if path:
key = str(rev) + '/' + path key = str(rev) + '/' + path
else: else:
key = str(rev) key = str(rev)
# Ensure that the cache gets filled...
dirents_locks = self._dirent_cache.get(key) dirents_locks = self._dirent_cache.get(key)
if not dirents_locks: if not dirents_locks:
tmp_dirents, locks = list_directory(dir_url, _rev2optrev(rev), dirents, locks = list_directory(dir_url, _rev2optrev(rev),
_rev2optrev(rev), 0, self.ctx) _rev2optrev(rev), 0, self.ctx)
dirents = {}
for name, dirent in tmp_dirents.items():
dirent_parts = path_parts + [name]
kind = dirent.kind
if (kind == core.svn_node_dir or kind == core.svn_node_file) \
and vclib.check_path_access(self, dirent_parts,
kind == core.svn_node_dir \
and vclib.DIR or vclib.FILE, rev):
lh_rev, c_rev = self._get_last_history_rev(dirent_parts, rev)
dirent.created_rev = lh_rev
dirents[name] = dirent
dirents_locks = [dirents, locks] dirents_locks = [dirents, locks]
self._dirent_cache[key] = dirents_locks self._dirent_cache[key] = dirents_locks
# ...then return the goodies from the cache.
return dirents_locks[0], dirents_locks[1] return dirents_locks[0], dirents_locks[1]
def _get_last_history_rev(self, path_parts, rev): def _get_last_history_rev(self, path_parts, rev):
"""Return the a 2-tuple which contains:
- the last interesting revision equal to or older than REV in
the history of PATH_PARTS.
- the created_rev of of PATH_PARTS as of REV."""
path = self._getpath(path_parts)
url = self._geturl(self._getpath(path_parts)) url = self._geturl(self._getpath(path_parts))
optrev = _rev2optrev(rev) optrev = _rev2optrev(rev)
# Get the last-changed-rev.
revisions = [] revisions = []
def _info_cb(path, info, pool, retval=revisions): def _info_cb(path, info, pool, retval=revisions):
revisions.append(info.last_changed_rev) revisions.append(info.last_changed_rev)
client.svn_client_info(url, optrev, optrev, _info_cb, 0, self.ctx) client.svn_client_info(url, optrev, optrev, _info_cb, 0, self.ctx)
last_changed_rev = revisions[0] return revisions[0]
# Now, this object might not have been directly edited since the def _revinfo_raw(self, rev):
# last-changed-rev, but it might have been the child of a copy. # return 5-tuple (date, author, message, changes)
# To determine this, we'll run a potentially no-op log between optrev = _rev2optrev(rev)
# LAST_CHANGED_REV and REV.
lc = LogCollector(path, 1, None, None)
client_log(url, optrev, _rev2optrev(last_changed_rev), 1, 1, 0,
lc.add_log, self.ctx)
revs = lc.logs
if revs:
revs.sort()
return revs[0].number, last_changed_rev
else:
return last_changed_rev, last_changed_rev
def _revinfo_fetch(self, rev, include_changed_paths=0):
need_changes = include_changed_paths or self.auth
revs = [] revs = []
def _log_cb(log_entry, pool, retval=revs): def _log_cb(changed_paths, revision, author,
# If Subversion happens to call us more than once, we choose not datestr, message, pool, retval=revs):
# to care. date = _datestr_to_date(datestr)
if retval:
return
revision = log_entry.revision
msg, author, date, revprops = _split_revprops(log_entry.revprops)
action_map = { 'D' : vclib.DELETED, action_map = { 'D' : vclib.DELETED,
'A' : vclib.ADDED, 'A' : vclib.ADDED,
'R' : vclib.REPLACED, 'R' : vclib.REPLACED,
'M' : vclib.MODIFIED, 'M' : vclib.MODIFIED,
} }
# Easy out: if we won't use the changed-path info, just return a
# changes-less tuple.
if not need_changes:
return revs.append([date, author, msg, revprops, None])
# Subversion 1.5 and earlier didn't offer the 'changed_paths2'
# hash, and in Subversion 1.6, it's offered but broken.
try:
changed_paths = log_entry.changed_paths2
paths = (changed_paths or {}).keys()
except:
changed_paths = log_entry.changed_paths
paths = (changed_paths or {}).keys() paths = (changed_paths or {}).keys()
paths.sort(lambda a, b: _compare_paths(a, b)) paths.sort(lambda a, b: _compare_paths(a, b))
# If we get this far, our caller needs changed-paths, or we need
# them for authz-related sanitization.
changes = [] changes = []
found_readable = found_unreadable = 0 found_readable = found_unreadable = 0
for path in paths: 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 pathtype = None
if hasattr(change, 'node_kind'): change = changed_paths[path]
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
# 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) 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?"
if change.copyfrom_path and change.copyfrom_rev: if change.copyfrom_path and change.copyfrom_rev:
is_copy = 1 is_copy = 1
base_path = change.copyfrom_path base_path = change.copyfrom_path
@@ -649,61 +452,31 @@ class RemoteSubversionRepository(vclib.Repository):
base_path = path base_path = path
base_rev = revision - 1 base_rev = revision - 1
# Check authz rules (sadly, we have to lie about the path type) ### Check authz rules (we lie about the path type)
parts = _path_parts(path) parts = _path_parts(path)
if vclib.check_path_access(self, parts, vclib.FILE, revision): if vclib.check_path_access(self, parts, vclib.FILE, revision):
if is_copy and base_path and (base_path != path): if is_copy and base_path and (base_path != path):
parts = _path_parts(base_path) parts = _path_parts(base_path)
if not vclib.check_path_access(self, parts, vclib.FILE, base_rev): if vclib.check_path_access(self, parts, vclib.FILE, base_rev):
is_copy = 0 is_copy = 0
base_path = None base_path = None
base_rev = None base_rev = None
found_unreadable = 1
changes.append(SVNChangedPath(path, revision, pathtype, base_path, changes.append(SVNChangedPath(path, revision, pathtype, base_path,
base_rev, action, is_copy, base_rev, action, is_copy, 0, 0))
text_modified, props_modified))
found_readable = 1 found_readable = 1
else: else:
found_unreadable = 1 found_unreadable = 1
# If our caller doesn't want changed-path stuff, and we have
# the info we need to make an authz determination already,
# quit this loop and get on with it.
if (not include_changed_paths) and found_unreadable and found_readable:
break
# Filter unreadable information.
if found_unreadable: if found_unreadable:
msg = None message = None
if not found_readable: if not found_readable:
author = None author = None
date = None date = None
revs.append([date, author, message, changes])
# Drop unrequested changes. client.svn_client_log([self.rootpath], optrev, optrev,
if not include_changed_paths: 1, 0, _log_cb, self.ctx)
changes = None return revs[0][0], revs[0][1], revs[0][2], revs[0][3]
# Add this revision information to the "return" array.
retval.append([date, author, msg, revprops, changes])
optrev = _rev2optrev(rev)
client_log(self.rootpath, optrev, optrev, 1, need_changes, 0,
_log_cb, self.ctx)
return tuple(revs[0])
def _revinfo(self, rev, include_changed_paths=0):
"""Internal-use, cache-friendly revision information harvester."""
# Consult the revinfo cache first. If we don't have cached info,
# or our caller wants changed paths and we don't have those for
# this revision, go do the real work.
rev = self._getrev(rev)
cached_info = self._revinfo_cache.get(rev)
if not cached_info \
or (include_changed_paths and cached_info[4] is None):
cached_info = self._revinfo_fetch(rev, include_changed_paths)
self._revinfo_cache[rev] = cached_info
return cached_info
##--- custom --## ##--- custom --##
@@ -722,16 +495,23 @@ class RemoteSubversionRepository(vclib.Repository):
old_path = results[old_rev] old_path = results[old_rev]
except KeyError: except KeyError:
raise vclib.ItemNotFound(path) raise vclib.ItemNotFound(path)
old_path = _cleanup_path(old_path)
old_path_parts = _path_parts(old_path) return _cleanup_path(old_path)
# Check access (lying about path types)
if not vclib.check_path_access(self, old_path_parts, vclib.FILE, old_rev):
raise vclib.ItemNotFound(path)
return old_path
def created_rev(self, path, rev): def created_rev(self, path, rev):
lh_rev, c_rev = self._get_last_history_rev(_path_parts(path), rev) # NOTE: We can't use svn_client_propget here because the
return lh_rev # interfaces in that layer strip out the properties not meant for
# human consumption (such as svn:entry:committed-rev, which we are
# using here to get the created revision of PATH@REV).
kind = ra.svn_ra_check_path(self.ra_session, path, rev)
if kind == core.svn_node_none:
raise vclib.ItemNotFound(_path_parts(path))
elif kind == core.svn_node_dir:
props = get_directory_props(self.ra_session, path, rev)
elif kind == core.svn_node_file:
fetched_rev, props = ra.svn_ra_get_file(self.ra_session, path, rev, None)
return int(props.get(core.SVN_PROP_ENTRY_COMMITTED_REV,
SVN_INVALID_REVNUM))
def last_rev(self, path, peg_revision, limit_revision=None): def last_rev(self, path, peg_revision, limit_revision=None):
"""Given PATH, known to exist in PEG_REVISION, find the youngest """Given PATH, known to exist in PEG_REVISION, find the youngest
@@ -766,33 +546,3 @@ class RemoteSubversionRepository(vclib.Repository):
else: else:
peg_revision = mid peg_revision = mid
return peg_revision, path return peg_revision, path
def get_symlink_target(self, path_parts, rev):
"""Return the target of the symbolic link versioned at PATH_PARTS
in REV, or None if that object is not a symlink."""
path = self._getpath(path_parts)
path_type = self.itemtype(path_parts, rev) # does auth-check
rev = self._getrev(rev)
url = self._geturl(path)
# Symlinks must be files with the svn:special property set on them
# and with file contents which read "link SOME_PATH".
if path_type != vclib.FILE:
return None
pairs = client.svn_client_proplist2(url, _rev2optrev(rev),
_rev2optrev(rev), 0, self.ctx)
props = pairs and pairs[0][1] or {}
if not props.has_key(core.SVN_PROP_SPECIAL):
return None
pathspec = ''
### FIXME: We're being a touch sloppy here, first by grabbing the
### whole file and then by checking only the first line
### of it.
fp = SelfCleanFP(cat_to_tempfile(self, path, rev))
pathspec = fp.readline()
fp.close()
if pathspec[:5] != 'link ':
return None
return pathspec[5:]

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -22,7 +22,6 @@ import time
import tempfile import tempfile
import popen import popen
import re import re
import urllib
from svn import fs, repos, core, client, delta from svn import fs, repos, core, client, delta
@@ -44,13 +43,6 @@ def _fix_subversion_exception(e):
if not hasattr(e, 'message'): if not hasattr(e, 'message'):
e.message = e[0] e.message = e[0]
### Pre-1.4 Subversion doesn't have svn_path_canonicalize()
def _canonicalize_path(path):
try:
return core.svn_path_canonicalize(path)
except AttributeError:
return path
def _allow_all(root, path, pool): def _allow_all(root, path, pool):
"""Generic authz_read_func that permits access to all paths""" """Generic authz_read_func that permits access to all paths"""
return 1 return 1
@@ -116,37 +108,11 @@ def _rev2optrev(rev):
def _rootpath2url(rootpath, path): def _rootpath2url(rootpath, path):
rootpath = os.path.abspath(rootpath) rootpath = os.path.abspath(rootpath)
drive, rootpath = os.path.splitdrive(rootpath) if rootpath and rootpath[0] != '/':
rootpath = '/' + rootpath
if os.sep != '/': if os.sep != '/':
rootpath = string.replace(rootpath, os.sep, '/') rootpath = string.replace(rootpath, os.sep, '/')
rootpath = urllib.quote(rootpath) return 'file://' + string.join([rootpath, path], "/")
path = urllib.quote(path)
if drive:
url = 'file:///' + drive + rootpath + '/' + path
else:
url = 'file://' + rootpath + '/' + path
return _canonicalize_path(url)
# 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): def _datestr_to_date(datestr):
@@ -217,6 +183,59 @@ class NodeHistory:
def __getitem__(self, idx): def __getitem__(self, idx):
return self.histories[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): def _get_last_history_rev(fsroot, path):
history = fs.node_history(fsroot, path) history = fs.node_history(fsroot, path)
history = fs.history_prev(history, 0) history = fs.history_prev(history, 0)
@@ -295,13 +314,12 @@ class FileContentsPipe:
class BlameSource: class BlameSource:
def __init__(self, local_url, rev, first_rev, include_text, config_dir): def __init__(self, local_url, rev, first_rev, config_dir):
self.idx = -1 self.idx = -1
self.first_rev = first_rev self.first_rev = first_rev
self.blame_data = [] self.blame_data = []
self.include_text = include_text
ctx = client.svn_client_create_context() ctx = client.ctx_t()
core.svn_config_ensure(config_dir) core.svn_config_ensure(config_dir)
ctx.config = core.svn_config_get_config(config_dir) ctx.config = core.svn_config_get_config(config_dir)
ctx.auth_baton = core.svn_auth_open([]) ctx.auth_baton = core.svn_auth_open([])
@@ -320,8 +338,6 @@ class BlameSource:
prev_rev = None prev_rev = None
if rev > self.first_rev: if rev > self.first_rev:
prev_rev = rev - 1 prev_rev = rev - 1
if not self.include_text:
text = None
self.blame_data.append(vclib.Annotation(text, line_no + 1, rev, self.blame_data.append(vclib.Annotation(text, line_no + 1, rev,
prev_rev, author, None)) prev_rev, author, None))
@@ -390,10 +406,6 @@ class LocalSubversionRepository(vclib.Repository):
self._fsroots = {} self._fsroots = {}
self._revinfo_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): def rootname(self):
return self.name return self.name
@@ -409,14 +421,19 @@ class LocalSubversionRepository(vclib.Repository):
def itemtype(self, path_parts, rev): def itemtype(self, path_parts, rev):
rev = self._getrev(rev) rev = self._getrev(rev)
basepath = self._getpath(path_parts) basepath = self._getpath(path_parts)
pathtype = self._gettype(basepath, rev) kind = fs.check_path(self._getroot(rev), basepath)
if pathtype is None: pathtype = None
if kind == core.svn_node_dir:
pathtype = vclib.DIR
elif kind == core.svn_node_file:
pathtype = vclib.FILE
else:
raise vclib.ItemNotFound(path_parts) raise vclib.ItemNotFound(path_parts)
if not vclib.check_path_access(self, path_parts, pathtype, rev): if not vclib.check_path_access(self, path_parts, pathtype, rev):
raise vclib.ItemNotFound(path_parts) raise vclib.ItemNotFound(path_parts)
return pathtype return pathtype
def openfile(self, path_parts, rev, options): def openfile(self, path_parts, rev):
path = self._getpath(path_parts) path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path) raise vclib.Error("Path '%s' is not a file." % path)
@@ -455,7 +472,7 @@ class LocalSubversionRepository(vclib.Repository):
continue continue
path = self._getpath(entry_path_parts) path = self._getpath(entry_path_parts)
entry_rev = _get_last_history_rev(fsroot, path) 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.rev = str(entry_rev)
entry.date = date entry.date = date
entry.author = author entry.author = author
@@ -504,19 +521,20 @@ class LocalSubversionRepository(vclib.Repository):
# 'limit' parameter here as numeric cut-off for the depth of our # 'limit' parameter here as numeric cut-off for the depth of our
# history search. # history search.
if options.get('svn_latest_log', 0): if options.get('svn_latest_log', 0):
revision = self._log_helper(path, rev, lockinfo) revision = _log_helper(self, path, rev, lockinfo)
if revision: if revision:
revision.prev = None revision.prev = None
revs.append(revision) revs.append(revision)
else: 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: if len(history) < first:
history = [] history = []
if limit: if limit:
history = history[first:first+limit] history = history[first:first+limit]
for hist_rev, hist_path in history: 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 revision:
# If we have unreadable copyfrom data, obscure it. # If we have unreadable copyfrom data, obscure it.
if revision.copy_path is not None: if revision.copy_path is not None:
@@ -537,70 +555,43 @@ class LocalSubversionRepository(vclib.Repository):
fsroot = self._getroot(rev) fsroot = self._getroot(rev)
return fs.node_proplist(fsroot, path) return fs.node_proplist(fsroot, path)
def annotate(self, path_parts, rev, include_text=False): def annotate(self, path_parts, rev):
path = self._getpath(path_parts) path = self._getpath(path_parts)
path_type = self.itemtype(path_parts, rev) # does auth-check path_type = self.itemtype(path_parts, rev) # does auth-check
if path_type != vclib.FILE: if path_type != vclib.FILE:
raise vclib.Error("Path '%s' is not a file." % path) raise vclib.Error("Path '%s' is not a file." % path)
rev = self._getrev(rev) rev = self._getrev(rev)
fsroot = self._getroot(rev) fsroot = self._getroot(rev)
history = self._get_history(path, rev, path_type, 0, history = _get_history(self, path, rev, path_type, 0,
{'svn_cross_copies': 1}) {'svn_cross_copies': 1})
youngest_rev, youngest_path = history[0] youngest_rev, youngest_path = history[0]
oldest_rev, oldest_path = history[-1] oldest_rev, oldest_path = history[-1]
source = BlameSource(_rootpath2url(self.rootpath, path), youngest_rev, source = BlameSource(_rootpath2url(self.rootpath, path),
oldest_rev, include_text, self.config_dir) youngest_rev, oldest_rev, self.config_dir)
return source, youngest_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)
def filesize(self, path_parts, rev):
path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path)
fsroot = self._getroot(self._getrev(rev))
return fs.file_length(fsroot, path)
##--- helpers ---##
def _revinfo(self, rev, include_changed_paths=0): def _revinfo(self, rev, include_changed_paths=0):
"""Internal-use, cache-friendly revision information harvester.""" """Internal-use, cache-friendly revision information harvester."""
def _get_changed_paths(fsroot): def _revinfo_helper(rev, include_changed_paths):
"""Return a 3-tuple: found_readable, found_unreadable, 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) editor = repos.ChangeCollector(self.fs_ptr, fsroot)
e_ptr, e_baton = delta.make_editor(editor) e_ptr, e_baton = delta.make_editor(editor)
repos.svn_repos_replay(fsroot, e_ptr, e_baton) repos.svn_repos_replay(fsroot, e_ptr, e_baton)
@@ -653,12 +644,10 @@ class LocalSubversionRepository(vclib.Repository):
if vclib.check_path_access(self, parts, pathtype, rev): if vclib.check_path_access(self, parts, pathtype, rev):
if is_copy and change.base_path and (change.base_path != path): if is_copy and change.base_path and (change.base_path != path):
parts = _path_parts(change.base_path) parts = _path_parts(change.base_path)
if not vclib.check_path_access(self, parts, pathtype, if not vclib.check_path_access(self, parts, pathtype, change.base_rev):
change.base_rev):
is_copy = 0 is_copy = 0
change.base_path = None change.base_path = None
change.base_rev = None change.base_rev = None
found_unreadable = 1
changedpaths[path] = SVNChangedPath(path, rev, pathtype, changedpaths[path] = SVNChangedPath(path, rev, pathtype,
change.base_path, change.base_path,
change.base_rev, action, change.base_rev, action,
@@ -667,116 +656,24 @@ class LocalSubversionRepository(vclib.Repository):
found_readable = 1 found_readable = 1
else: else:
found_unreadable = 1 found_unreadable = 1
return found_readable, found_unreadable, changedpaths.values()
def _get_change_copyinfo(fsroot, path, change): # If our caller doesn't care about changed paths, we must be
# If we know the copyfrom info, return it... # here for authz reasons only. That means the minute we've
if hasattr(change, 'copyfrom_known') and change.copyfrom_known: # found both a readable and an unreadable path, we can bail out.
copyfrom_path = change.copyfrom_path if (not include_changed_paths) and found_readable and found_unreadable:
copyfrom_rev = change.copyfrom_rev return date, author, None, None
# ...otherwise, if this change could be a copy (that is, it
# contains an add action), query the copyfrom info ...
elif (change.change_kind == fs.path_change_add or
change.change_kind == fs.path_change_replace):
copyfrom_rev, copyfrom_path = fs.copied_from(fsroot, path)
# ...else, there's no copyfrom info.
else:
copyfrom_rev = core.SVN_INVALID_REVNUM
copyfrom_path = None
return copyfrom_path, copyfrom_rev
def _simple_auth_check(fsroot): # Okay, we've process all our paths. Let's filter our metadata,
"""Return a 2-tuple: found_readable, found_unreadable.""" # and return the requested data.
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 pathtype is None:
# Figure out the pathtype so we can query the authz subsystem.
if change.change_kind == fs.path_change_delete:
# Deletions are annoying, because they might be underneath
# copies (make their previous location non-trivial).
prev_parts = parts
prev_rev = rev - 1
parent_parts = parts[:-1]
while parent_parts:
parent_path = '/' + self._getpath(parent_parts)
parent_change = changes.get(parent_path)
if not (parent_change and \
(parent_change.change_kind == fs.path_change_add or
parent_change.change_kind == fs.path_change_replace)):
del(parent_parts[-1])
continue
copyfrom_path, copyfrom_rev = \
_get_change_copyinfo(fsroot, parent_path, parent_change)
if copyfrom_path:
prev_rev = copyfrom_rev
prev_parts = _path_parts(copyfrom_path) + \
parts[len(parent_parts):]
break
del(parent_parts[-1])
pathtype = self._gettype(self._getpath(prev_parts), prev_rev)
else:
pathtype = self._gettype(self._getpath(parts), rev)
if vclib.check_path_access(self, parts, pathtype, rev):
found_readable = 1
copyfrom_path, copyfrom_rev = \
_get_change_copyinfo(fsroot, path, change)
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)
# Filter our metadata where necessary, and return the requested data.
if found_unreadable: if found_unreadable:
msg = None msg = None
if not found_readable: if not found_readable:
author = None author = None
date = 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, # 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 # or our caller wants changed paths and we don't have those for
@@ -784,55 +681,45 @@ class LocalSubversionRepository(vclib.Repository):
rev = self._getrev(rev) rev = self._getrev(rev)
cached_info = self._revinfo_cache.get(rev) cached_info = self._revinfo_cache.get(rev)
if not cached_info \ 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) cached_info = _revinfo_helper(rev, include_changed_paths)
self._revinfo_cache[rev] = cached_info 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): def revinfo(self, rev):
rev_root = fs.revision_root(self.fs_ptr, rev) return self._revinfo(rev, 1)
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 _get_history(self, path, rev, path_type, limit=0, options={}): def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
if self.youngest == 0: p1 = self._getpath(path_parts1)
return [] 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)
rev_paths = [] args = vclib._diff_args(type, options)
fsroot = self._getroot(rev)
show_all_logs = options.get('svn_show_all_dir_logs', 0) def _date_from_rev(rev):
if not show_all_logs: date, author, msg, changes = self._revinfo(rev)
# See if the path is a file or directory. return date
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: try:
repos.svn_repos_history(self.fs_ptr, path, history.add_history, temp1 = temp_checkout(self, p1, r1)
1, rev, options.get('svn_cross_copies', 0)) 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: except core.SubversionException, e:
_fix_subversion_exception(e) _fix_subversion_exception(e)
if e.apr_err != _SVN_ERR_CEASE_INVOCATION: if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
raise vclib.InvalidRevision
raise raise
# Now, iterate over those history items, checking for changes of def isexecutable(self, path_parts, rev):
# location, pruning as necessitated by authz rules. props = self.itemprops(path_parts, rev) # does authz-check
for hist_rev, hist_path in history: return props.has_key(core.SVN_PROP_EXECUTABLE)
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 _getpath(self, path_parts): def _getpath(self, path_parts):
return string.join(path_parts, '/') return string.join(path_parts, '/')
@@ -841,11 +728,8 @@ class LocalSubversionRepository(vclib.Repository):
if rev is None or rev == 'HEAD': if rev is None or rev == 'HEAD':
return self.youngest return self.youngest
try: try:
if type(rev) == type(''):
while rev[0] == 'r':
rev = rev[1:]
rev = int(rev) rev = int(rev)
except: except ValueError:
raise vclib.InvalidRevision(rev) raise vclib.InvalidRevision(rev)
if (rev < 0) or (rev > self.youngest): if (rev < 0) or (rev > self.youngest):
raise vclib.InvalidRevision(rev) raise vclib.InvalidRevision(rev)
@@ -858,20 +742,7 @@ class LocalSubversionRepository(vclib.Repository):
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev) r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev)
return r return r
def _gettype(self, path, rev): ##--- custom --##
# Similar to itemtype(), but without the authz check. Returns
# None for missing paths.
try:
kind = fs.check_path(self._getroot(rev), path)
except:
return None
if kind == core.svn_node_dir:
return vclib.DIR
if kind == core.svn_node_file:
return vclib.FILE
return None
##--- custom ---##
def get_youngest_revision(self): def get_youngest_revision(self):
return self.youngest return self.youngest
@@ -949,32 +820,3 @@ class LocalSubversionRepository(vclib.Repository):
return peg_revision, path return peg_revision, path
finally: finally:
pass pass
def get_symlink_target(self, path_parts, rev):
"""Return the target of the symbolic link versioned at PATH_PARTS
in REV, or None if that object is not a symlink."""
path = self._getpath(path_parts)
rev = self._getrev(rev)
path_type = self.itemtype(path_parts, rev) # does auth-check
fsroot = self._getroot(rev)
# Symlinks must be files with the svn:special property set on them
# and with file contents which read "link SOME_PATH".
if path_type != vclib.FILE:
return None
props = fs.node_proplist(fsroot, path)
if not props.has_key(core.SVN_PROP_SPECIAL):
return None
pathspec = ''
### FIXME: We're being a touch sloppy here, only checking the first line
### of the file.
stream = fs.file_contents(fsroot, path)
try:
pathspec, eof = core.svn_stream_readline(stream, '\n')
finally:
core.svn_stream_close(stream)
if pathspec[:5] != 'link ':
return None
return pathspec[5:]

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# -*-python-*- # -*-python-*-
# #
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -27,9 +27,7 @@ numbers, and not literal):
3. Verify that copyright years are correct in both the license-1.html 3. Verify that copyright years are correct in both the license-1.html
file and the source code. file and the source code.
4. Update and commit the 'CHANGES' file, using any available crystal 4. Update and commit the 'CHANGES' file.
balls or other forward-looking devices to take a stab at the
release date.
5. Test, test, test! There is no automatic testsuite available. So 5. Test, test, test! There is no automatic testsuite available. So
just run with permuting different `viewvc.conf' settings... and just run with permuting different `viewvc.conf' settings... and
@@ -40,86 +38,50 @@ numbers, and not literal):
should exactly reflect what you wish to distribute and dub "the should exactly reflect what you wish to distribute and dub "the
release". release".
7. Update your release branch working copy to HEAD. 7. Edit the file 'lib/viewvc.py' and remove the "-dev" suffix from
svn up
8. 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, __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 svn update
current HEAD revision: svn cp -m "Tag the X.Y.Z final release." . \
http://viewvc.tigris.org/svn/viewvc/tags/X.Y.Z
svn pedit svn:externals . 9. Go into an empty directory and run the 'make-release' script:
(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:
tools/make-release viewvc-X.Y.Z tags/X.Y.Z 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 a LICENSE.html file?
- do they have necessary include documentation? - do they have necessary include documentation?
- do they *not* have unnecessary stuff? - do they *not* have unnecessary stuff?
- do they install and work correctly? - 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 and Documents section of the Tigris.org project, and modify the
CHECKSUMS document there accordingly: CHECKSUMS document there accordingly. Also, drop a copy of the
archive files into the root directory of the viewvc.org website
(unversioned).
http://viewvc.tigris.org/servlets/ProjectDocumentList?folderID=6004 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.
Also, drop a copy of the archive files into the root directory of 13. Edit the file 'lib/viewvc.py' again, re-adding the "-dev" suffix
the viewvc.org website (unversioned). and incrementing the patch number assigned to the __version__
variable, and add a new empty block in the branch's CHANGES file,
15. Update the Tigris.org website (^/trunk/www/index.html) to refer to and commit:
the new release files and commit.
svn ci -m "Bump latest advertised release."
16. Back on the release branch, edit the file 'lib/viewvc.py' again,
incrementing the patch number assigned to the __version__
variable. Add a new empty block in the branch's CHANGES file.
Commit your changes:
svn ci -m "Begin a new release cycle." 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 for the just-released one, and a new Milestone for the next patch
(and possibly, minor or major) release. (For the Milestone sort (and possibly, minor or major) release. (For the Milestone sort
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.) key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
http://viewvc.tigris.org/issues/editversions.cgi?component=viewvc&action=add 15. Write an announcement explaining all the cool new features and
http://viewvc.tigris.org/issues/editmilestones.cgi?component=viewvc&action=add 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
18. Send to the announce@ list a message explaining all the cool new (http://www.freshmeat.net).
features.
http://viewvc.tigris.org/ds/viewForumSummary.do?dsForumId=4253
19. Post a new release notification at Freecode.
https://freecode.com/projects/viewvc/releases/new
20. Merge CHANGES for this release into the CHANGES file for newer
release lines and commit.

View File

@@ -94,6 +94,9 @@ form { margin: 0; }
.vc_properties { .vc_properties {
margin: 1em 0; margin: 1em 0;
} }
.vc_properties h2 {
font-size: 115%;
}
/*** File Content Markup Styles ***/ /*** File Content Markup Styles ***/
@@ -269,14 +272,3 @@ table.vc_idiff tbody th {
.vc_query_form { .vc_query_form {
background-color: #e6e6e6; background-color: #e6e6e6;
} }
/*** Warning! ***/
.vc_warning {
border-width: 1px 2px 2px 2px;
border-color: black;
border-style: solid;
background-color: red;
color: white;
padding: 0.5em;
}

View File

@@ -9,11 +9,7 @@
[# ------------------------------------------------------------------------- ] [# ------------------------------------------------------------------------- ]
[# setup page definitions] [# setup page definitions]
[is annotation "annotated"]
[define page_title]Annotation of /[where][end]
[else]
[define page_title]Contents of /[where][end] [define page_title]Contents of /[where][end]
[end]
[define help_href][docroot]/help_rootview.html[end] [define help_href][docroot]/help_rootview.html[end]
[# end] [# end]

View File

@@ -5,7 +5,7 @@
<table> <table>
<tr> <tr>
<td>[if-any cfg.general.address]<address><a href="mailto:[cfg.general.address]">[cfg.general.address]</a></address>[else]&nbsp;[end]</td> <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>
<tr> <tr>
<td>Powered by <a href="http://viewvc.tigris.org/">ViewVC [vsn]</a></td> <td>Powered by <a href="http://viewvc.tigris.org/">ViewVC [vsn]</a></td>

View File

@@ -5,7 +5,7 @@
<head> <head>
<title>[if-any rootname][[][rootname]][else]ViewVC[end] [page_title]</title> <title>[if-any rootname][[][rootname]][else]ViewVC[end] [page_title]</title>
<meta name="generator" content="ViewVC [vsn]" /> <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" /> <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] [if-any rss_href]<link rel="alternate" type="application/rss+xml" title="RSS [[][rootname]][where]" href="[rss_href]" />[end]
</head> </head>

View File

@@ -4,13 +4,13 @@
<!-- ViewVC :: http://www.viewvc.org/ --> <!-- ViewVC :: http://www.viewvc.org/ -->
<head> <head>
<title>Checkin Database Query</title> <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> </head>
<body> <body>
[# setup page definitions] [# setup page definitions]
[define help_href][if-any docroot][docroot]/help_query.html[end][end] [define help_href][docroot]/help_query.html[end]
[# end] [# end]
<p> <p>
@@ -158,15 +158,7 @@
[is query "skipped"] [is query "skipped"]
[else] [else]
<p><strong>[num_commits]</strong> matches found.</p> <p><strong>[num_commits]</strong> matches found.</p>
[if-any row_limit_reached]
<p class="vc_warning">WARNING: These query results have been
artificially limited by an administrative threshold value and do
<em>not</em> represent the entirety of the data set which matches
the query. Consider modifying your query to be more specific</a>,
using your version control tool's query capabilities, or asking
your administrator to raise the database response size
threshold.</p>
[end]
[if-any commits] [if-any commits]
<table cellspacing="0" cellpadding="2"> <table cellspacing="0" cellpadding="2">
<thead> <thead>
@@ -210,21 +202,21 @@
</td> </td>
[# uncommment, if you want a separate Description column: [# 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}"> <td style="vertical-align:top;" rowspan="{commits.num_files}">
{if-any commits.log}{commits.log}{else}&nbsp;{end} {commits.log}
</td> </td>
{end} {end}
(substitute brackets for the braces) (substitute brackets for the braces)
] ]
</tr> </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] [if-index commits.files last]
<tr class="vc_row_[if-index commits even]even[else]odd[end]"> <tr class="vc_row_[if-index commits even]even[else]odd[end]">
<td>&nbsp;</td> <td>&nbsp;</td>
<td colspan="5"><strong>Log:</strong><br /> <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> </tr>
[end] [end]
[# ---] [# ---]

View File

@@ -7,15 +7,6 @@
<p><strong>[english_query]</strong></p> <p><strong>[english_query]</strong></p>
[# <!-- {sql} --> ] [# <!-- {sql} --> ]
[if-any row_limit_reached]
<p class="vc_warning">WARNING: These query results have been
artificially limited by an administrative threshold value and do
<em>not</em> represent the entirety of the data set which matches
the query. Consider <a href="[queryform_href]">modifying your
query to be more specific</a>, using your version control tool's
query capabilities, or asking your administrator to raise the
database response size threshold.</p>
[end]
<p><a href="[queryform_href]">Modify query</a></p> <p><a href="[queryform_href]">Modify query</a></p>
<p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p> <p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p>

View File

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

View File

@@ -9,12 +9,6 @@
<thead> <thead>
<tr> <tr>
<th class="vc_header_sort">Name</th> <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> </tr>
</thead> </thead>
@@ -26,12 +20,6 @@
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" /> <img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
[roots.name]</a> [roots.name]</a>
</td> </td>
[is cfg.options.show_roots_lastmod "1"]
<td style="width:20">&nbsp;[if-any roots.log_href]<a href="[roots.log_href]">[roots.rev]</a>[else][roots.rev][end]</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> </tr>
[end] [end]
</tbody> </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> <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> [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] [if-any commits.rss_url]<link>[commits.rss_url]</link>[end]
<author>[commits.author]</author> <author>[commits.author]</author>
<pubDate>[if-any commits.rss_date][commits.rss_date][else](unknown date)[end]</pubDate> <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] </item>[end]
</channel> </channel>
</rss> </rss>

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
# #
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,5 +1,5 @@
/* /*
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,5 +1,5 @@
/* /*
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved. # Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# #
# By using this file, you agree to the terms and conditions set forth in # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,5 +1,5 @@
/* /*
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,5 +1,5 @@
/* /*
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- Mode: python -*- # -*- Mode: python -*-
# #
# Copyright (C) 1999-2013 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 # 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 # the LICENSE.html file which can be found at the top level of the ViewVC
@@ -49,10 +49,6 @@ CLEAN_MODE = None
FILE_INFO_LIST = [ FILE_INFO_LIST = [
("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0), ("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/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/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/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), ("bin/mod_python/handler.py", "bin/mod_python/handler.py", 0755, 1, 0, 0),
@@ -209,8 +205,6 @@ def install_file(src_path, dst_path, mode, subst_path_vars,
temp = raw_input("Do you want to [O]verwrite, [D]o " temp = raw_input("Do you want to [O]verwrite, [D]o "
"not overwrite, or [V]iew " "not overwrite, or [V]iew "
"differences? ") "differences? ")
if len(temp) == 0:
continue
temp = string.lower(temp[0]) temp = string.lower(temp[0])
if temp == "v" and ext not in BINARY_FILE_EXTS: if temp == "v" and ext not in BINARY_FILE_EXTS:
print """ print """