mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
173 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
71b8440308 | ||
![]() |
370c49ad75 | ||
![]() |
40e938f123 | ||
![]() |
57ab699677 | ||
![]() |
c4ec79be78 | ||
![]() |
6f8c5a64b4 | ||
![]() |
f3335bd10c | ||
![]() |
f842941cd1 | ||
![]() |
d4eedc49b9 | ||
![]() |
24b213e14c | ||
![]() |
82c0b25d80 | ||
![]() |
8fdb3c6a98 | ||
![]() |
ea15def7ad | ||
![]() |
d15ff63726 | ||
![]() |
ac2566c7ab | ||
![]() |
41fdf2b86d | ||
![]() |
a878d93992 | ||
![]() |
dae4abebaf | ||
![]() |
dba173ca38 | ||
![]() |
5e18bf0aa4 | ||
![]() |
41809c22bf | ||
![]() |
850f4dd0eb | ||
![]() |
7eace69bfb | ||
![]() |
907d9eddd2 | ||
![]() |
02eefc07a5 | ||
![]() |
ddd96672b0 | ||
![]() |
0fc67216d5 | ||
![]() |
167a2a041b | ||
![]() |
c4483d0501 | ||
![]() |
d620bc13e7 | ||
![]() |
e3756ae365 | ||
![]() |
77ad38cbdb | ||
![]() |
7feecdd512 | ||
![]() |
a087c06a88 | ||
![]() |
acce6a556d | ||
![]() |
4670019d3a | ||
![]() |
9ce7372e1a | ||
![]() |
e1959ac2e5 | ||
![]() |
cb38ccc929 | ||
![]() |
f2b82132c2 | ||
![]() |
5caf3a4437 | ||
![]() |
06ee4df42d | ||
![]() |
0a6b13145e | ||
![]() |
65d3568c92 | ||
![]() |
de5b147a6f | ||
![]() |
eb6b575701 | ||
![]() |
8d82b6f0d6 | ||
![]() |
5a51470cbc | ||
![]() |
acc0783468 | ||
![]() |
c91325d40a | ||
![]() |
ca1bd67b5d | ||
![]() |
f1e7ef42d6 | ||
![]() |
8b7eae7f14 | ||
![]() |
6f3d9a3a00 | ||
![]() |
1d7307c09b | ||
![]() |
22d1e72c66 | ||
![]() |
0e7d4061ed | ||
![]() |
56dd2dcf28 | ||
![]() |
5932f24a68 | ||
![]() |
161421a20f | ||
![]() |
2a297c5361 | ||
![]() |
f43117b10f | ||
![]() |
a025237f7e | ||
![]() |
228db2fadb | ||
![]() |
0cd26cc79f | ||
![]() |
b808d5d1e8 | ||
![]() |
f843c054b7 | ||
![]() |
cf06a971e8 | ||
![]() |
bc8f3bdd4f | ||
![]() |
a6fcab67b0 | ||
![]() |
4069208316 | ||
![]() |
cfaa30b40f | ||
![]() |
97c5a82b7b | ||
![]() |
21bd391d62 | ||
![]() |
9d1476ef1d | ||
![]() |
151fcd05e6 | ||
![]() |
22400ddcfb | ||
![]() |
ae55530edc | ||
![]() |
112700a12c | ||
![]() |
cecffecf39 | ||
![]() |
bdac8697fe | ||
![]() |
3d9404b67d | ||
![]() |
7c50f55153 | ||
![]() |
368e4dc360 | ||
![]() |
964d8bb5f9 | ||
![]() |
846e8e46c5 | ||
![]() |
75c719bcde | ||
![]() |
fc8793bf15 | ||
![]() |
65935c40fd | ||
![]() |
a30d0b44cc | ||
![]() |
fded8462d2 | ||
![]() |
b1095ac763 | ||
![]() |
e54399a169 | ||
![]() |
4cc0db75be | ||
![]() |
74a9cbb2a0 | ||
![]() |
390e337a8e | ||
![]() |
ecdac77d5f | ||
![]() |
79158c2ee7 | ||
![]() |
96fdfbdbcf | ||
![]() |
361538da21 | ||
![]() |
6250d4134b | ||
![]() |
8589949521 | ||
![]() |
f12e262fa5 | ||
![]() |
f379070697 | ||
![]() |
d47fc0ff3b | ||
![]() |
f2b6f0ba86 | ||
![]() |
ddbe150be1 | ||
![]() |
6b5ed7c857 | ||
![]() |
391f7d8237 | ||
![]() |
141cf5ff10 | ||
![]() |
bfe148eb45 | ||
![]() |
6fb28f2198 | ||
![]() |
02cc53d34b | ||
![]() |
5928918da4 | ||
![]() |
2e9f84427c | ||
![]() |
df599031a0 | ||
![]() |
6311c93298 | ||
![]() |
b982cccbcd | ||
![]() |
33b8224714 | ||
![]() |
2f05f570b1 | ||
![]() |
d737657e1d | ||
![]() |
3d5294635e | ||
![]() |
75c3fc2346 | ||
![]() |
80bce159af | ||
![]() |
7ff9b84ee1 | ||
![]() |
b6acd3c114 | ||
![]() |
fa5d40caa3 | ||
![]() |
a9cbd4c6a6 | ||
![]() |
882cdaa46b | ||
![]() |
58e64cb28d | ||
![]() |
779e67653b | ||
![]() |
0381a772b7 | ||
![]() |
048b2c8033 | ||
![]() |
e354ab302c | ||
![]() |
5f931c6cf1 | ||
![]() |
576837351a | ||
![]() |
6084ccf877 | ||
![]() |
514da91629 | ||
![]() |
d2c8bb9878 | ||
![]() |
fe274e9b9c | ||
![]() |
6603a67a91 | ||
![]() |
b50de1c92e | ||
![]() |
dfd8dc82f6 | ||
![]() |
1a942d1062 | ||
![]() |
289a78eb99 | ||
![]() |
2897763020 | ||
![]() |
47886423e5 | ||
![]() |
0890821839 | ||
![]() |
2903389d3e | ||
![]() |
5bdea369e8 | ||
![]() |
3699e8e0c7 | ||
![]() |
55c0b30bc0 | ||
![]() |
6fd321529a | ||
![]() |
531877db4a | ||
![]() |
51df003d8e | ||
![]() |
7d05859d7e | ||
![]() |
2e48cd2ba1 | ||
![]() |
267b61f347 | ||
![]() |
7f53d5cc3c | ||
![]() |
af591ed9f7 | ||
![]() |
17492a3856 | ||
![]() |
e990ff9f2f | ||
![]() |
d832930c8d | ||
![]() |
8a0c66c7cf | ||
![]() |
292c2d7ce0 | ||
![]() |
16937af30f | ||
![]() |
d067d49afa | ||
![]() |
132a01a88f | ||
![]() |
4db5103ff1 | ||
![]() |
41943f67c5 | ||
![]() |
1f37623526 | ||
![]() |
7e92babad8 | ||
![]() |
97e45101d9 |
124
CHANGES
124
CHANGES
@@ -1,3 +1,127 @@
|
||||
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
|
||||
|
95
INSTALL
95
INSTALL
@@ -19,7 +19,7 @@ Congratulations on getting this far. :-)
|
||||
|
||||
For CVS Support:
|
||||
|
||||
* Python 1.5.2 or later
|
||||
* Python 1.5.2 or later (sorry, no 3.x support yet)
|
||||
(http://www.python.org/)
|
||||
* RCS, Revision Control System
|
||||
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
|
||||
@@ -30,11 +30,11 @@ Congratulations on getting this far. :-)
|
||||
|
||||
For Subversion Support:
|
||||
|
||||
* Python 2.0 or later
|
||||
* Python 2.0 or later (sorry, no 3.x support yet)
|
||||
(http://www.python.org/)
|
||||
* Subversion, Version Control System, 1.3.1 or later
|
||||
(binary installation and Python bindings)
|
||||
(http://subversion.tigris.org/)
|
||||
(http://subversion.apache.org/)
|
||||
|
||||
Optional:
|
||||
|
||||
@@ -176,7 +176,24 @@ APACHE CONFIGURATION
|
||||
or /etc/local. Use the vendor documentation or the find utility if
|
||||
in doubt.
|
||||
|
||||
2) Configure Apache to expose ViewVC to users at the URL of your choice.
|
||||
2) Depending on how your Apache configuration is setup by default, you
|
||||
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
|
||||
the following methods:
|
||||
@@ -202,6 +219,10 @@ APACHE CONFIGURATION
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
|
||||
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
|
||||
------------------------------------------
|
||||
@@ -213,9 +234,9 @@ APACHE CONFIGURATION
|
||||
Options +ExecCGI
|
||||
AddHandler cgi-script .cgi
|
||||
|
||||
(Note: For this to work mod_cgi has to be loaded. And for the .htaccess file
|
||||
Note: For this to work mod_cgi has to be loaded. And for the .htaccess file
|
||||
to be effective, "AllowOverride All" or "AllowOverride Options FileInfo"
|
||||
need to have been specified for the directory.)
|
||||
needs to have been specified for the directory.
|
||||
|
||||
------------------------------------------
|
||||
METHOD D: Using mod_python (if installed)
|
||||
@@ -228,16 +249,59 @@ APACHE CONFIGURATION
|
||||
"AllowOverride FileInfo Options" are enabled for the directory
|
||||
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
|
||||
feature may not work because it uses multithreading. This works fine
|
||||
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:
|
||||
|
||||
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".
|
||||
WSGIScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/viewvc.wsgi
|
||||
WSGIScriptAlias /query <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/query.wsgi
|
||||
|
||||
You'll probably also need the following directive because of the
|
||||
not-quite-sanctioned way that ViewVC manipulates Python objects.
|
||||
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
|
||||
Note: WSGI support in ViewVC is at this time quite rudimentary,
|
||||
bordering on downright experimental. Your mileage may vary.
|
||||
|
||||
-----------------------------------------
|
||||
METHOD F: Using mod_fcgid (if installed)
|
||||
-----------------------------------------
|
||||
|
||||
This uses ViewVC's WSGI support (from above), but supports using FastCGI,
|
||||
and is a somewhat hybrid approach of several of the above methods.
|
||||
|
||||
Especially if fcgi is already being used for other purposes, e.g. PHP,
|
||||
also using fcgi can prevent the need for including additional modules
|
||||
(e.g. mod_python or mod_wsgi) within Apache, which may help lessen Apache's
|
||||
memory usage and/or help improve performance.
|
||||
|
||||
This depends on mod_fcgid:
|
||||
|
||||
http://httpd.apache.org/mod_fcgid/
|
||||
|
||||
as well as the fcgi server from Python's flup package:
|
||||
|
||||
http://pypi.python.org/pypi/flup
|
||||
http://trac.saddi.com/flup
|
||||
|
||||
The following are some example httpd.conf fragments you can use to
|
||||
support this configuration:
|
||||
|
||||
ScriptAlias /viewvc /usr/local/viewvc/bin/wsgi/viewvc.fcgi
|
||||
ScriptAlias /query /usr/local/viewvc/bin/wsgi/query.fcgi
|
||||
|
||||
4) [Optional] Add access control.
|
||||
|
||||
@@ -260,7 +324,14 @@ APACHE CONFIGURATION
|
||||
http://<server_name>/viewvc/~checkout~/<module_name>
|
||||
http://<server_name>/viewvc/<module_name>.tar.gz?view=tar
|
||||
|
||||
5) Optional: Protect your ViewVC instance from server-whacking webcrawlers.
|
||||
5) Restart Apache.
|
||||
|
||||
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
|
||||
links to other pages and views, you can expect your server's performance
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
<blockquote>
|
||||
|
||||
<p><strong>Copyright © 1999-2010 The ViewCVS Group. All rights
|
||||
<p><strong>Copyright © 1999-2013 The ViewCVS Group. All rights
|
||||
reserved.</strong></p>
|
||||
|
||||
<p>By using ViewVC, you agree to the terms and conditions set forth
|
||||
@@ -61,6 +61,9 @@
|
||||
<li>February 22, 2008 — copyright years updated</li>
|
||||
<li>March 18, 2009 — copyright years updated</li>
|
||||
<li>March 29, 2010 — copyright years updated</li>
|
||||
<li>February 18, 2011 — copyright years updated</li>
|
||||
<li>January 23, 2012 — copyright years updated</li>
|
||||
<li>January 04, 2013 — copyright years updated</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -54,7 +54,10 @@ import query
|
||||
server = sapi.AspServer(Server, Request, Response, Application)
|
||||
try:
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
query.main(server, cfg, "viewvc.asp")
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.asp"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -54,4 +54,7 @@ import query
|
||||
|
||||
server = sapi.CgiServer()
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
query.main(server, cfg, "viewvc.cgi")
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.cgi"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -40,7 +40,7 @@ CREATE TABLE branches (
|
||||
branch varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE branch (branch)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS checkins;
|
||||
CREATE TABLE checkins (
|
||||
@@ -63,7 +63,7 @@ CREATE TABLE checkins (
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS descs;
|
||||
CREATE TABLE descs (
|
||||
@@ -72,7 +72,7 @@ CREATE TABLE descs (
|
||||
hash bigint(20) DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY hash (hash)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS dirs;
|
||||
CREATE TABLE dirs (
|
||||
@@ -80,7 +80,7 @@ CREATE TABLE dirs (
|
||||
dir varchar(255) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE dir (dir)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS files;
|
||||
CREATE TABLE files (
|
||||
@@ -88,7 +88,7 @@ CREATE TABLE files (
|
||||
file varchar(255) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE file (file)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS people;
|
||||
CREATE TABLE people (
|
||||
@@ -96,7 +96,7 @@ CREATE TABLE people (
|
||||
who varchar(128) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE who (who)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS repositories;
|
||||
CREATE TABLE repositories (
|
||||
@@ -104,7 +104,7 @@ CREATE TABLE repositories (
|
||||
repository varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE repository (repository)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS tags;
|
||||
CREATE TABLE tags (
|
||||
@@ -118,7 +118,7 @@ CREATE TABLE tags (
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
"""
|
||||
|
||||
## ------------------------------------------------------------------------
|
||||
@@ -132,7 +132,7 @@ CREATE TABLE branches (
|
||||
branch varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE branch (branch)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS commits;
|
||||
CREATE TABLE commits (
|
||||
@@ -156,7 +156,7 @@ CREATE TABLE commits (
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid),
|
||||
KEY descid (descid)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS descs;
|
||||
CREATE TABLE descs (
|
||||
@@ -165,7 +165,7 @@ CREATE TABLE descs (
|
||||
hash bigint(20) DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY hash (hash)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS dirs;
|
||||
CREATE TABLE dirs (
|
||||
@@ -173,7 +173,7 @@ CREATE TABLE dirs (
|
||||
dir varchar(255) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE dir (dir)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS files;
|
||||
CREATE TABLE files (
|
||||
@@ -181,7 +181,7 @@ CREATE TABLE files (
|
||||
file varchar(255) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE file (file)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS people;
|
||||
CREATE TABLE people (
|
||||
@@ -189,7 +189,7 @@ CREATE TABLE people (
|
||||
who varchar(128) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE who (who)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS repositories;
|
||||
CREATE TABLE repositories (
|
||||
@@ -197,7 +197,7 @@ CREATE TABLE repositories (
|
||||
repository varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE repository (repository)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS tags;
|
||||
CREATE TABLE tags (
|
||||
@@ -211,7 +211,7 @@ CREATE TABLE tags (
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS metadata;
|
||||
CREATE TABLE metadata (
|
||||
@@ -219,7 +219,7 @@ CREATE TABLE metadata (
|
||||
value text,
|
||||
PRIMARY KEY (name),
|
||||
UNIQUE name (name)
|
||||
) TYPE=MyISAM;
|
||||
) ENGINE=MyISAM;
|
||||
INSERT INTO metadata (name, value) VALUES ('version', '1');
|
||||
"""
|
||||
|
||||
@@ -245,6 +245,10 @@ options.) This script will use the 'mysql' program to create the
|
||||
database for you. You will then need to set the appropriate
|
||||
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:
|
||||
|
||||
--dbname=ARG Use ARG as the ViewVC database name to create.
|
||||
@@ -253,7 +257,8 @@ Options:
|
||||
--help Show this usage message.
|
||||
|
||||
--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.
|
||||
|
||||
@@ -273,10 +278,11 @@ Options:
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# Parse the command-line options, if any.
|
||||
dbname = version = hostname = username = password = None
|
||||
dbname = version = hostname = port = username = password = None
|
||||
opts, args = getopt.getopt(sys.argv[1:], '', [ 'dbname=',
|
||||
'help',
|
||||
'hostname=',
|
||||
'port=',
|
||||
'password=',
|
||||
'username=',
|
||||
'version=',
|
||||
@@ -290,6 +296,8 @@ if __name__ == "__main__":
|
||||
dbname = value
|
||||
elif name == '--hostname':
|
||||
hostname = value
|
||||
elif name == '--port':
|
||||
port = value
|
||||
elif name == '--username':
|
||||
username = value
|
||||
elif name == '--password':
|
||||
@@ -302,7 +310,9 @@ if __name__ == "__main__":
|
||||
|
||||
# Prompt for information not provided via command-line options.
|
||||
if hostname is None:
|
||||
hostname = raw_input("MySQL Hostname [default: localhost]: ") or ""
|
||||
hostname = raw_input("MySQL Hostname (leave blank for default): ")
|
||||
if port is None:
|
||||
port = raw_input("MySQL Port (leave blank for default): ")
|
||||
if username is None:
|
||||
username = raw_input("MySQL User: ")
|
||||
if password is None:
|
||||
@@ -310,7 +320,7 @@ if __name__ == "__main__":
|
||||
if dbname is None:
|
||||
dbname = raw_input("ViewVC Database Name [default: ViewVC]: ") or "ViewVC"
|
||||
|
||||
# Create the database
|
||||
# Create the database.
|
||||
dscript = string.replace(DATABASE_SCRIPT_COMMON, "<dbname>", dbname)
|
||||
if version == "1.0":
|
||||
print BONSAI_COMPAT
|
||||
@@ -318,16 +328,22 @@ if __name__ == "__main__":
|
||||
else:
|
||||
dscript = dscript + DATABASE_SCRIPT_VERSION_1
|
||||
|
||||
host_option = hostname and "--host=%s" % (hostname) or ""
|
||||
# Calculate command arguments.
|
||||
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":
|
||||
cmd = "mysql --user=%s --password=%s %s "\
|
||||
% (username, password, host_option)
|
||||
cmd = "mysql %s" % (cmd_args)
|
||||
mysql = os.popen(cmd, "w") # popen2.Popen3 is not provided on windows
|
||||
mysql.write(dscript)
|
||||
status = mysql.close()
|
||||
else:
|
||||
cmd = "{ mysql --user=%s --password=%s %s ; } 2>&1" \
|
||||
% (username, password, host_option)
|
||||
cmd = "{ mysql %s ; } 2>&1" % (cmd_args)
|
||||
pipes = popen2.Popen3(cmd)
|
||||
pipes.tochild.write(dscript)
|
||||
pipes.tochild.close()
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -65,7 +65,10 @@ cfg = viewvc.load_config(CONF_PATHNAME)
|
||||
def index(req):
|
||||
server = sapi.ModPythonServer(req)
|
||||
try:
|
||||
query.main(server, cfg, "viewvc.py")
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.py"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
finally:
|
||||
server.close()
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -41,6 +41,7 @@ import urllib
|
||||
import rfc822
|
||||
import socket
|
||||
import select
|
||||
import base64
|
||||
import BaseHTTPServer
|
||||
|
||||
if LIBRARY_DIR:
|
||||
@@ -53,21 +54,39 @@ import viewvc
|
||||
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:
|
||||
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
|
||||
if sys.platform == 'mac':
|
||||
host = '127.0.0.1'
|
||||
else:
|
||||
host = 'localhost'
|
||||
host = sys.platform == 'mac' and '127.0.0.1' or 'localhost'
|
||||
script_alias = 'viewvc'
|
||||
config_file = None
|
||||
htpasswd_file = None
|
||||
|
||||
# --- web browser interface: ----------------------------------------------
|
||||
|
||||
class StandaloneServer(sapi.CgiServer):
|
||||
"""Custom sapi interface that uses a BaseHTTPRequestHandler HANDLER
|
||||
to generate output."""
|
||||
|
||||
def __init__(self, handler):
|
||||
sapi.CgiServer.__init__(self, inheritableOut = sys.platform != "win32")
|
||||
self.handler = handler
|
||||
@@ -93,69 +112,130 @@ class StandaloneServer(sapi.CgiServer):
|
||||
self.handler.end_headers()
|
||||
|
||||
|
||||
def serve(host, port, callback=None):
|
||||
"""start a HTTP server on the given port. call 'callback' when the
|
||||
server is ready to serve"""
|
||||
class NotViewVCLocationException(Exception):
|
||||
"""The request location was not aimed at ViewVC."""
|
||||
pass
|
||||
|
||||
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):
|
||||
"""Serve a GET request."""
|
||||
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)
|
||||
self.handle_request('GET')
|
||||
|
||||
def do_POST(self):
|
||||
"""Serve a POST request."""
|
||||
if self.is_viewvc():
|
||||
self.handle_request('POST')
|
||||
|
||||
def handle_request(self, method):
|
||||
"""Handle a request of type METHOD."""
|
||||
try:
|
||||
self.run_viewvc()
|
||||
else:
|
||||
self.send_error(501, "Can only POST to %s"
|
||||
% (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"""
|
||||
except NotViewVCLocationException:
|
||||
# If the request was aimed at the server root, but there's a
|
||||
# non-empty script_alias, automatically redirect to the
|
||||
# script_alias. Otherwise, just return a 404 and shrug.
|
||||
if (not self.path or self.path == "/") and options.script_alias:
|
||||
new_url = self.server.url + options.script_alias + '/'
|
||||
self.send_response(301, "Moved (redirection follows)")
|
||||
self.send_response(301, "Moved Permanently")
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header("Location", new_url)
|
||||
self.end_headers()
|
||||
self.wfile.write("""<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="1; URL=%s">
|
||||
<meta http-equiv="refresh" content="10; url=%s" />
|
||||
<title>Moved Temporarily</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Redirection to <a href="%s">ViewVC</a></h1>
|
||||
Wait a second. You will be automatically redirected to <b>ViewVC</b>.
|
||||
If this doesn't work, please click on the link above.
|
||||
<h1>Redirecting to ViewVC</h1>
|
||||
<p>You will be automatically redirected to <a href="%s">ViewVC</a>.
|
||||
If this doesn't work, please click on the link above.</p>
|
||||
</body>
|
||||
</html>
|
||||
""" % tuple([new_url]*2))
|
||||
""" % (new_url, new_url))
|
||||
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):
|
||||
"""This is a quick and dirty cut'n'rape from Python's
|
||||
standard library module CGIHTTPServer."""
|
||||
scriptname = '/' + options.script_alias
|
||||
assert string.find(self.path, scriptname) == 0
|
||||
"""Run ViewVC to field a single request."""
|
||||
|
||||
### Much of this is adapter from Python's standard library
|
||||
### module CGIHTTPServer.
|
||||
|
||||
# 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
|
||||
rest = self.path[len(scriptname):]
|
||||
i = string.rfind(rest, '?')
|
||||
@@ -163,8 +243,7 @@ If this doesn't work, please click on the link above.
|
||||
rest, query = rest[:i], rest[i+1:]
|
||||
else:
|
||||
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
|
||||
# values to override previously set values
|
||||
for k in env.keys():
|
||||
@@ -174,6 +253,7 @@ If this doesn't work, please click on the link above.
|
||||
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
|
||||
if env.has_key(k):
|
||||
env[k] = ""
|
||||
|
||||
# XXX Much of the following could be prepared ahead of time!
|
||||
env['SERVER_SOFTWARE'] = self.version_string()
|
||||
env['SERVER_NAME'] = self.server.server_name
|
||||
@@ -191,9 +271,8 @@ If this doesn't work, please click on the link above.
|
||||
if host != self.client_address[0]:
|
||||
env['REMOTE_HOST'] = host
|
||||
env['REMOTE_ADDR'] = self.client_address[0]
|
||||
# AUTH_TYPE
|
||||
# REMOTE_USER
|
||||
# REMOTE_IDENT
|
||||
if self.username:
|
||||
env['REMOTE_USER'] = self.username
|
||||
if self.headers.typeheader is None:
|
||||
env['CONTENT_TYPE'] = self.headers.type
|
||||
else:
|
||||
@@ -217,8 +296,9 @@ If this doesn't work, please click on the link above.
|
||||
etag = self.headers.getheader('if-none-match')
|
||||
if etag:
|
||||
env['HTTP_IF_NONE_MATCH'] = etag
|
||||
# AUTH_TYPE
|
||||
# REMOTE_IDENT
|
||||
# XXX Other HTTP_* headers
|
||||
decoded_query = string.replace(query, '+', ' ')
|
||||
|
||||
# Preserve state, because we execute script in current process:
|
||||
save_argv = sys.argv
|
||||
@@ -236,7 +316,8 @@ If this doesn't work, please click on the link above.
|
||||
#
|
||||
# But we no longer use pipe_cmds. So at the very least, the
|
||||
# comment is stale. Is the code okay, though?
|
||||
if sys.platform != "win32": save_realstdout = os.dup(1)
|
||||
if sys.platform != "win32":
|
||||
save_realstdout = os.dup(1)
|
||||
try:
|
||||
try:
|
||||
sys.stdout = self.wfile
|
||||
@@ -258,13 +339,14 @@ If this doesn't work, please click on the link above.
|
||||
else:
|
||||
self.log_error("ViewVC exited ok")
|
||||
|
||||
class ViewVC_Server(BaseHTTPServer.HTTPServer):
|
||||
class ViewVCHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
"""Customized HTTP server for ViewVC."""
|
||||
|
||||
def __init__(self, host, port, callback):
|
||||
self.address = (host, port)
|
||||
self.url = 'http://%s:%d/' % (host, port)
|
||||
self.callback = callback
|
||||
BaseHTTPServer.HTTPServer.__init__(self, self.address,
|
||||
self.handler)
|
||||
BaseHTTPServer.HTTPServer.__init__(self, self.address, self.handler)
|
||||
|
||||
def serve_until_quit(self):
|
||||
self.quit = 0
|
||||
@@ -280,25 +362,26 @@ If this doesn't work, please click on the link above.
|
||||
|
||||
def server_bind(self):
|
||||
# set SO_REUSEADDR (if available on this platform)
|
||||
if hasattr(socket, 'SOL_SOCKET') \
|
||||
and hasattr(socket, 'SO_REUSEADDR'):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR, 1)
|
||||
if hasattr(socket, 'SOL_SOCKET') and hasattr(socket, 'SO_REUSEADDR'):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
BaseHTTPServer.HTTPServer.server_bind(self)
|
||||
|
||||
ViewVC_Server.handler = ViewVC_Handler
|
||||
def serve(host, port, callback=None):
|
||||
"""Start an HTTP server for HOST on PORT. Call CALLBACK function
|
||||
when the server is ready to serve."""
|
||||
|
||||
ViewVCHTTPServer.handler = ViewVCHTTPRequestHandler
|
||||
|
||||
try:
|
||||
# XXX Move this code out of this function.
|
||||
# Early loading of configuration here. Used to
|
||||
# allow tinkering with some configuration settings:
|
||||
# Early loading of configuration here. Used to allow tinkering
|
||||
# with some configuration settings:
|
||||
handle_config(options.config_file)
|
||||
if options.repositories:
|
||||
cfg.general.default_root = "Development"
|
||||
for repo_name in options.repositories.keys():
|
||||
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
|
||||
elif os.path.exists(os.path.join(repo_path, "format")):
|
||||
cfg.general.svn_roots[repo_name] = repo_path
|
||||
@@ -321,7 +404,8 @@ If this doesn't work, please click on the link above.
|
||||
try:
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line: break
|
||||
if not line:
|
||||
break
|
||||
if string.find(line, "Concurrent Versions System (CVSNT)") >= 0:
|
||||
cvsnt_works = 1
|
||||
while fp.read(4096):
|
||||
@@ -334,7 +418,7 @@ If this doesn't work, please click on the link above.
|
||||
if not cvsnt_works:
|
||||
cfg.utilities.cvsnt = None
|
||||
|
||||
ViewVC_Server(host, port, callback).serve_until_quit()
|
||||
ViewVCHTTPServer(host, port, callback).serve_until_quit()
|
||||
except (KeyboardInterrupt, select.error):
|
||||
pass
|
||||
print 'server stopped'
|
||||
@@ -346,10 +430,11 @@ def handle_config(config_file):
|
||||
# --- graphical interface: --------------------------------------------------
|
||||
|
||||
def nogui(missing_module):
|
||||
sys.stderr.write(
|
||||
"Sorry! Your Python was compiled without the %s module"%missing_module+
|
||||
" enabled.\nI'm unable to run the GUI part. Please omit the '-g'\n"+
|
||||
"and '--gui' options or install another Python interpreter.\n")
|
||||
sys.stderr.write("""\
|
||||
Sorry! Your Python was compiled without the %s module enabled.
|
||||
I'm unable to run the GUI part. Please omit the '-g' and '--gui' options,
|
||||
or install another Python interpreter.
|
||||
""" % (missing_module))
|
||||
raise SystemExit, 1
|
||||
|
||||
def gui(host, port):
|
||||
@@ -369,10 +454,13 @@ def gui(host, port):
|
||||
self.title_lbl = Tkinter.Label(self.server_frm,
|
||||
text='Starting server...\n ')
|
||||
self.open_btn = Tkinter.Button(self.server_frm,
|
||||
text='open browser', command=self.open, state='disabled')
|
||||
text='open browser',
|
||||
command=self.open,
|
||||
state='disabled')
|
||||
self.quit_btn = Tkinter.Button(self.server_frm,
|
||||
text='quit serving', command=self.quit, state='disabled')
|
||||
|
||||
text='quit serving',
|
||||
command=self.quit,
|
||||
state='disabled')
|
||||
|
||||
self.window.title('ViewVC standalone')
|
||||
self.window.protocol('WM_DELETE_WINDOW', self.quit)
|
||||
@@ -391,109 +479,146 @@ def gui(host, port):
|
||||
# cvsgraph toggle:
|
||||
self.cvsgraph_ivar = Tkinter.IntVar()
|
||||
self.cvsgraph_ivar.set(cfg.options.use_cvsgraph)
|
||||
self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
|
||||
self.cvsgraph_toggle = \
|
||||
Tkinter.Checkbutton(self.options_frm,
|
||||
text="enable cvsgraph (needs binary)",
|
||||
var=self.cvsgraph_ivar,
|
||||
command=self.toggle_use_cvsgraph)
|
||||
self.cvsgraph_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# show_subdir_lastmod toggle:
|
||||
self.subdirmod_ivar = Tkinter.IntVar()
|
||||
self.subdirmod_ivar.set(cfg.options.show_subdir_lastmod)
|
||||
self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
|
||||
self.subdirmod_toggle = \
|
||||
Tkinter.Checkbutton(self.options_frm,
|
||||
text="show subdir last mod (dir view)",
|
||||
var=self.subdirmod_ivar,
|
||||
command=self.toggle_subdirmod)
|
||||
self.subdirmod_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# use_re_search toggle:
|
||||
self.useresearch_ivar = Tkinter.IntVar()
|
||||
self.useresearch_ivar.set(cfg.options.use_re_search)
|
||||
self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
text="allow regular expr search", var=self.useresearch_ivar,
|
||||
self.useresearch_toggle = \
|
||||
Tkinter.Checkbutton(self.options_frm,
|
||||
text="allow regular expr search",
|
||||
var=self.useresearch_ivar,
|
||||
command=self.toggle_useresearch)
|
||||
self.useresearch_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# use_localtime toggle:
|
||||
self.use_localtime_ivar = Tkinter.IntVar()
|
||||
self.use_localtime_ivar.set(cfg.options.use_localtime)
|
||||
self.use_localtime_toggle = Tkinter.Checkbutton(self.options_frm,
|
||||
self.use_localtime_toggle = \
|
||||
Tkinter.Checkbutton(self.options_frm,
|
||||
text="use localtime (instead of UTC)",
|
||||
var=self.use_localtime_ivar,
|
||||
command=self.toggle_use_localtime)
|
||||
self.use_localtime_toggle.pack(side='top', anchor='w')
|
||||
|
||||
# log_pagesize integer var:
|
||||
self.log_pagesize_lbl = Tkinter.Label(self.options_frm,
|
||||
text='Paging (number of items per log page, 0 disables):')
|
||||
self.log_pagesize_lbl = \
|
||||
Tkinter.Label(self.options_frm,
|
||||
text='number of items per log page (0 disables):')
|
||||
self.log_pagesize_lbl.pack(side='top', anchor='w')
|
||||
self.log_pagesize_ivar = Tkinter.IntVar()
|
||||
self.log_pagesize_ivar.set(cfg.options.log_pagesize)
|
||||
self.log_pagesize_entry = Tkinter.Entry(self.options_frm,
|
||||
width=10, textvariable=self.log_pagesize_ivar)
|
||||
self.log_pagesize_entry = \
|
||||
Tkinter.Entry(self.options_frm,
|
||||
width=10,
|
||||
textvariable=self.log_pagesize_ivar)
|
||||
self.log_pagesize_entry.bind('<Return>', self.set_log_pagesize)
|
||||
self.log_pagesize_entry.pack(side='top', anchor='w')
|
||||
|
||||
# dir_pagesize integer var:
|
||||
self.dir_pagesize_lbl = Tkinter.Label(self.options_frm,
|
||||
text='Paging (number of items per dir page, 0 disables):')
|
||||
self.dir_pagesize_lbl = \
|
||||
Tkinter.Label(self.options_frm,
|
||||
text='number of items per dir page (0 disables):')
|
||||
self.dir_pagesize_lbl.pack(side='top', anchor='w')
|
||||
self.dir_pagesize_ivar = Tkinter.IntVar()
|
||||
self.dir_pagesize_ivar.set(cfg.options.dir_pagesize)
|
||||
self.dir_pagesize_entry = Tkinter.Entry(self.options_frm,
|
||||
width=10, textvariable=self.dir_pagesize_ivar)
|
||||
self.dir_pagesize_entry = \
|
||||
Tkinter.Entry(self.options_frm,
|
||||
width=10,
|
||||
textvariable=self.dir_pagesize_ivar)
|
||||
self.dir_pagesize_entry.bind('<Return>', self.set_dir_pagesize)
|
||||
self.dir_pagesize_entry.pack(side='top', anchor='w')
|
||||
|
||||
# directory view template:
|
||||
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
self.dirtemplate_lbl = \
|
||||
Tkinter.Label(self.options_frm,
|
||||
text='Choose HTML Template for the Directory pages:')
|
||||
self.dirtemplate_lbl.pack(side='top', anchor='w')
|
||||
self.dirtemplate_svar = Tkinter.StringVar()
|
||||
self.dirtemplate_svar.set(cfg.templates.directory)
|
||||
self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
|
||||
width = 40, textvariable=self.dirtemplate_svar)
|
||||
self.dirtemplate_entry = \
|
||||
Tkinter.Entry(self.options_frm,
|
||||
width=40,
|
||||
textvariable=self.dirtemplate_svar)
|
||||
self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
|
||||
self.dirtemplate_entry.pack(side='top', anchor='w')
|
||||
self.templates_dir = Tkinter.Radiobutton(self.options_frm,
|
||||
text="directory.ezt", value="templates/directory.ezt",
|
||||
var=self.dirtemplate_svar, command=self.set_templates_directory)
|
||||
self.templates_dir = \
|
||||
Tkinter.Radiobutton(self.options_frm,
|
||||
text="directory.ezt",
|
||||
value="templates/directory.ezt",
|
||||
var=self.dirtemplate_svar,
|
||||
command=self.set_templates_directory)
|
||||
self.templates_dir.pack(side='top', anchor='w')
|
||||
self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm,
|
||||
text="dir_alternate.ezt", value="templates/dir_alternate.ezt",
|
||||
var=self.dirtemplate_svar, command=self.set_templates_directory)
|
||||
self.templates_dir_alt = \
|
||||
Tkinter.Radiobutton(self.options_frm,
|
||||
text="dir_alternate.ezt",
|
||||
value="templates/dir_alternate.ezt",
|
||||
var=self.dirtemplate_svar,
|
||||
command=self.set_templates_directory)
|
||||
self.templates_dir_alt.pack(side='top', anchor='w')
|
||||
|
||||
# log view template:
|
||||
self.logtemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
self.logtemplate_lbl = \
|
||||
Tkinter.Label(self.options_frm,
|
||||
text='Choose HTML Template for the Log pages:')
|
||||
self.logtemplate_lbl.pack(side='top', anchor='w')
|
||||
self.logtemplate_svar = Tkinter.StringVar()
|
||||
self.logtemplate_svar.set(cfg.templates.log)
|
||||
self.logtemplate_entry = Tkinter.Entry(self.options_frm,
|
||||
width = 40, textvariable=self.logtemplate_svar)
|
||||
self.logtemplate_entry = \
|
||||
Tkinter.Entry(self.options_frm,
|
||||
width=40,
|
||||
textvariable=self.logtemplate_svar)
|
||||
self.logtemplate_entry.bind('<Return>', self.set_templates_log)
|
||||
self.logtemplate_entry.pack(side='top', anchor='w')
|
||||
self.templates_log = Tkinter.Radiobutton(self.options_frm,
|
||||
text="log.ezt", value="templates/log.ezt",
|
||||
var=self.logtemplate_svar, command=self.set_templates_log)
|
||||
self.templates_log = \
|
||||
Tkinter.Radiobutton(self.options_frm,
|
||||
text="log.ezt",
|
||||
value="templates/log.ezt",
|
||||
var=self.logtemplate_svar,
|
||||
command=self.set_templates_log)
|
||||
self.templates_log.pack(side='top', anchor='w')
|
||||
self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
|
||||
text="log_table.ezt", value="templates/log_table.ezt",
|
||||
var=self.logtemplate_svar, command=self.set_templates_log)
|
||||
self.templates_log_table = \
|
||||
Tkinter.Radiobutton(self.options_frm,
|
||||
text="log_table.ezt",
|
||||
value="templates/log_table.ezt",
|
||||
var=self.logtemplate_svar,
|
||||
command=self.set_templates_log)
|
||||
self.templates_log_table.pack(side='top', anchor='w')
|
||||
|
||||
# query view template:
|
||||
self.querytemplate_lbl = Tkinter.Label(self.options_frm,
|
||||
self.querytemplate_lbl = \
|
||||
Tkinter.Label(self.options_frm,
|
||||
text='Template for the database query page:')
|
||||
self.querytemplate_lbl.pack(side='top', anchor='w')
|
||||
self.querytemplate_svar = Tkinter.StringVar()
|
||||
self.querytemplate_svar.set(cfg.templates.query)
|
||||
self.querytemplate_entry = Tkinter.Entry(self.options_frm,
|
||||
width = 40, textvariable=self.querytemplate_svar)
|
||||
self.querytemplate_entry = \
|
||||
Tkinter.Entry(self.options_frm,
|
||||
width=40,
|
||||
textvariable=self.querytemplate_svar)
|
||||
self.querytemplate_entry.bind('<Return>', self.set_templates_query)
|
||||
self.querytemplate_entry.pack(side='top', anchor='w')
|
||||
self.templates_query = Tkinter.Radiobutton(self.options_frm,
|
||||
text="query.ezt", value="templates/query.ezt",
|
||||
var=self.querytemplate_svar, command=self.set_templates_query)
|
||||
self.templates_query = \
|
||||
Tkinter.Radiobutton(self.options_frm,
|
||||
text="query.ezt",
|
||||
value="templates/query.ezt",
|
||||
var=self.querytemplate_svar,
|
||||
command=self.set_templates_query)
|
||||
self.templates_query.pack(side='top', anchor='w')
|
||||
|
||||
# pack and set window manager hints:
|
||||
@@ -511,8 +636,7 @@ def gui(host, port):
|
||||
import threading
|
||||
except ImportError:
|
||||
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):
|
||||
cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
|
||||
@@ -544,8 +668,7 @@ def gui(host, port):
|
||||
def ready(self, server):
|
||||
"""used as callback parameter to the serve() function"""
|
||||
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.quit_btn.config(state='normal')
|
||||
|
||||
@@ -565,7 +688,8 @@ def gui(host, port):
|
||||
except ImportError: pass
|
||||
else:
|
||||
rc = os.system('netscape -remote "openURL(%s)" &' % url)
|
||||
if rc: os.system('netscape "%s" &' % url)
|
||||
if rc:
|
||||
os.system('netscape "%s" &' % url)
|
||||
|
||||
def quit(self, event=None):
|
||||
if self.server:
|
||||
@@ -581,102 +705,175 @@ def gui(host, port):
|
||||
|
||||
# --- command-line interface: ----------------------------------------------
|
||||
|
||||
def cli(argv):
|
||||
"""Command-line interface (looks at argv to decide what to do)."""
|
||||
import getopt
|
||||
class BadUsage(Exception): pass
|
||||
|
||||
try:
|
||||
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()
|
||||
if pid != 0:
|
||||
sys.exit()
|
||||
if options.start_gui:
|
||||
gui(options.host, options.port)
|
||||
return
|
||||
elif options.port:
|
||||
def ready(server):
|
||||
print 'server ready at %s%s' % (server.url,
|
||||
options.script_alias)
|
||||
serve(options.host, options.port, ready)
|
||||
return
|
||||
except (getopt.error, BadUsage), err:
|
||||
def usage():
|
||||
clean_options = Options()
|
||||
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)))
|
||||
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.
|
||||
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.)
|
||||
--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.
|
||||
|
||||
--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]
|
||||
--gui (-g) Pop up a graphical configuration interface.
|
||||
Requires a valid X11 display connection.
|
||||
|
||||
--port=PORT (-p) Start the server on the given PORT.
|
||||
[default: %(port)d]
|
||||
--help Show this usage message and exit.
|
||||
|
||||
--repository=PATH (-r) Serve up the Subversion or CVS repository located
|
||||
--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) 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".
|
||||
--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]
|
||||
|
||||
--gui (-g) Pop up a graphical interface for serving and
|
||||
testing ViewVC. NOTE: this requires a valid
|
||||
X11 display connection.
|
||||
""" % 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)."""
|
||||
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
|
||||
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.
|
||||
if opt_daemon:
|
||||
pid = os.fork()
|
||||
if pid != 0:
|
||||
sys.exit()
|
||||
|
||||
# Finaly, start the server.
|
||||
if opt_gui:
|
||||
gui(options.host, options.port)
|
||||
else:
|
||||
def ready(server):
|
||||
print 'server ready at %s%s' % (server.url, options.script_alias)
|
||||
serve(options.host, options.port, ready)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
options = Options()
|
||||
cli(sys.argv)
|
||||
main(sys.argv)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2004-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2004-2013 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2004-2007 James Henstridge
|
||||
#
|
||||
# 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)
|
||||
|
||||
# find changes in the revision
|
||||
editor = svn.repos.RevisionChangeCollector(repo.fs, rev)
|
||||
editor = svn.repos.ChangeCollector(repo.fs, fsroot)
|
||||
e_ptr, e_baton = svn.delta.make_editor(editor)
|
||||
svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
|
||||
@@ -164,21 +164,33 @@ class SvnRev:
|
||||
continue
|
||||
|
||||
# deal with the change types we handle
|
||||
action = None
|
||||
base_root = None
|
||||
base_path = change.base_path
|
||||
if change.base_path:
|
||||
base_root = self._get_root_for_rev(change.base_rev)
|
||||
|
||||
if not change.path:
|
||||
# figure out what kind of change this is, and get a diff
|
||||
# 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'
|
||||
elif change.added:
|
||||
action = 'add'
|
||||
else:
|
||||
action = 'change'
|
||||
|
||||
diffobj = svn.fs.FileDiff(base_root and base_root or None,
|
||||
base_root and change.base_path or None,
|
||||
change.path and fsroot or None,
|
||||
change.path and change.path or None)
|
||||
if action == 'remove':
|
||||
diffobj = svn.fs.FileDiff(base_root, base_path, None, None)
|
||||
else:
|
||||
diffobj = svn.fs.FileDiff(base_root, base_path,
|
||||
fsroot, change.path)
|
||||
|
||||
diff_fp = diffobj.get_pipe()
|
||||
plus, minus = _get_diff_counts(diff_fp)
|
||||
self.changes.append((path, action, plus, minus))
|
||||
@@ -242,6 +254,7 @@ def main(command, repository, revs=[], verbose=0, force=0):
|
||||
cfg = viewvc.load_config(CONF_PATHNAME)
|
||||
db = cvsdb.ConnectDatabase(cfg)
|
||||
|
||||
# Purge what must be purged.
|
||||
if command in ('rebuild', 'purge'):
|
||||
if verbose:
|
||||
print "Purging commit info for repository root `%s'" % repository
|
||||
@@ -252,6 +265,12 @@ def main(command, repository, revs=[], verbose=0, force=0):
|
||||
sys.stderr.write("ERROR: " + str(e) + "\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Record what must be recorded.
|
||||
if command in ('rebuild', 'update'):
|
||||
if not os.path.exists(repository):
|
||||
sys.stderr.write('ERROR: could not find repository %s\n'
|
||||
% (repository))
|
||||
sys.exit(1)
|
||||
repo = SvnRepo(repository)
|
||||
if command == 'rebuild' or (command == 'update' and not revs):
|
||||
for rev in range(repo.rev_max+1):
|
||||
@@ -281,7 +300,7 @@ def usage():
|
||||
located at 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
|
||||
|
||||
1. Rebuild the commit database information for the repository located
|
||||
@@ -330,12 +349,6 @@ if __name__ == '__main__':
|
||||
sys.stderr.write('ERROR: unknown command %s\n' % command)
|
||||
usage()
|
||||
|
||||
repository = args[2]
|
||||
if not os.path.exists(repository):
|
||||
sys.stderr.write('ERROR: could not find repository %s\n' % args[2])
|
||||
usage()
|
||||
repository = vclib.svn.canonicalize_rootpath(repository)
|
||||
|
||||
revs = []
|
||||
if len(sys.argv) > 3:
|
||||
if command == 'rebuild':
|
||||
@@ -358,6 +371,7 @@ if __name__ == '__main__':
|
||||
rev = None
|
||||
|
||||
try:
|
||||
repository = vclib.svn.canonicalize_rootpath(args[2])
|
||||
repository = cvsdb.CleanRepository(os.path.abspath(repository))
|
||||
main(command, repository, revs, verbose, force)
|
||||
except KeyboardInterrupt:
|
||||
|
54
bin/wsgi/query.fcgi
Normal file
54
bin/wsgi/query.fcgi
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/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()
|
45
bin/wsgi/query.wsgi
Normal file
45
bin/wsgi/query.wsgi
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*-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 []
|
50
bin/wsgi/viewvc.fcgi
Normal file
50
bin/wsgi/viewvc.fcgi
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/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()
|
41
bin/wsgi/viewvc.wsgi
Normal file
41
bin/wsgi/viewvc.wsgi
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*-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 []
|
@@ -86,8 +86,8 @@
|
||||
|
||||
## 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"
|
||||
## value. Multiple roots should be separated by commas and can be
|
||||
## placed on separate lines.
|
||||
## value (where the path is an absolute filesystem path). Multiple roots
|
||||
## should be separated by commas and can be placed on separate lines.
|
||||
##
|
||||
## Example:
|
||||
## cvs_roots = cvsroot: /opt/cvs/repos1,
|
||||
@@ -97,8 +97,13 @@
|
||||
|
||||
## svn_roots: Specifies each of the Subversion roots (repositories) on
|
||||
## your system and assigns names to them. Each root should be given by
|
||||
## a "name: path" value. Multiple roots should be separated by commas
|
||||
## and can be placed on separate lines.
|
||||
## a "name: path" value (where the path is an absolute filesystem path).
|
||||
## Multiple roots should be separated by commas and can be placed on
|
||||
## 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:
|
||||
## svn_roots = svnrepos: /opt/svn/,
|
||||
@@ -106,23 +111,44 @@
|
||||
##
|
||||
#svn_roots =
|
||||
|
||||
## root_parents: Specifies a list of directories in which any number of
|
||||
## repositories may reside. Rather than force you to add a new entry
|
||||
## to 'cvs_roots' or 'svn_roots' each time you create a new repository,
|
||||
## ViewVC rewards you for organising all your repositories under a few
|
||||
## parent directories by allowing you to simply specifiy just those
|
||||
## parent directories. ViewVC will then notice each repository in that
|
||||
## directory as a new root whose name is the subdirectory of the parent
|
||||
## path in which that repository lives.
|
||||
## root_parents: Specifies a list of directories under which any
|
||||
## number of repositories may reside. You can specify multiple root
|
||||
## parents separated by commas or new lines, each of which is of the
|
||||
## form "path: type" (where the type is either "cvs" or "svn", and
|
||||
## the path is an absolute filesystem path).
|
||||
##
|
||||
## You can specify multiple parent paths separated by commas or new lines.
|
||||
## Rather than force you to add a new entry to 'cvs_roots' or
|
||||
## 'svn_roots' each time you create a new repository, ViewVC rewards
|
||||
## you for organizing all your repositories under a few parent
|
||||
## directories by allowing you to simply tell it about those parent
|
||||
## directories. ViewVC will then notice each repository of the
|
||||
## specified type in that directory as a root whose name is the
|
||||
## subdirectory in which that repository lives.
|
||||
##
|
||||
## WARNING: these names can, of course, clash with names you have
|
||||
## defined in your cvs_roots or svn_roots configuration items. If this
|
||||
## occurs, you can either rename the offending repository on disk, or
|
||||
## grant new names to the clashing item in cvs_roots or svn_roots.
|
||||
## Each parent path is processed sequentially, so repositories under
|
||||
## later parent paths may override earlier ones.
|
||||
## For example, if you have three Subversion repositories located at
|
||||
## /opt/svn/projects, /opt/svn/websites, and /opt/svn/devstuff, you
|
||||
## could list them individually in svn_roots like so:
|
||||
##
|
||||
## svn_roots = projects: /opt/svn/projects,
|
||||
## websites: /opt/svn/websites,
|
||||
## devstuff: /opt/svn/devstuff
|
||||
##
|
||||
## or you could instead use the root_parents configuration option:
|
||||
##
|
||||
## root_parents = /opt/svn: svn
|
||||
##
|
||||
## The benefit of this latter approach is that, as you add new
|
||||
## repositories to your /opt/svn directory, they automatically become
|
||||
## available for display in ViewVC without additional configuration.
|
||||
##
|
||||
## WARNING: the root names derived for repositories configured via the
|
||||
## root_parents option can, of course, clash with names you have
|
||||
## defined in your cvs_roots or svn_roots configuration items. If
|
||||
## this occurs, you can either rename the offending repository on
|
||||
## disk, or grant new names to the clashing item in cvs_roots or
|
||||
## svn_roots. Each parent path is processed sequentially, so the
|
||||
## names of repositories under later parent paths may override earlier
|
||||
## ones.
|
||||
##
|
||||
## Example:
|
||||
## root_parents = /opt/svn: svn,
|
||||
@@ -325,10 +351,64 @@
|
||||
## allowed_views: List the ViewVC views which are enabled. Views not
|
||||
## in this comma-delited list will not be served (or, will return an
|
||||
## 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
|
||||
|
||||
## Comma-delimited list of MIME content types (with support for fnmatch-
|
||||
## style glob characters) which are considered not-human-readable and for
|
||||
## which ViewVC will neither generate links to, nor support the direct
|
||||
## display of, non-checkout views which carry the file's content (the
|
||||
## 'markup', 'annotate', 'diff', and 'patch' views).
|
||||
##
|
||||
## NOTE: Handling of this option is given priority over ViewVC's
|
||||
## longstanding support for showing web-friendly file formats -- even
|
||||
## binary ones such as "image/jpeg" and "image/gif" -- in the 'markup'
|
||||
## view. Thus, if you add "image/*" to this list, 'markup'-view
|
||||
## display of JPEG, GIF, and PNG images will be disabled.
|
||||
##
|
||||
## Example:
|
||||
## binary_mime_types = application/octet-stream, image/*, application/pdf,
|
||||
## application/vnd*, application/msword, audio/*
|
||||
#
|
||||
#binary_mime_types =
|
||||
|
||||
## authorizer: The name of the ViewVC authorizer plugin to use when
|
||||
## authorizing access to repository contents. This value must be the
|
||||
## name of a Python module addressable as vcauth.MODULENAME (most
|
||||
@@ -371,6 +451,26 @@
|
||||
##
|
||||
#mangle_email_addresses = 0
|
||||
|
||||
## custom_log_formatting: Specifies mappings of regular expressions to
|
||||
## substitution format strings used to URL-ize strings found in
|
||||
## revision log messages. Multiple mappings (specified as
|
||||
## REGEXP:FORMATSTRING) may be defined, separated by commas.
|
||||
##
|
||||
## NOTE: Due to a limitation of the configuration format, commas may
|
||||
## not be used in the regular expression portion of each mapping.
|
||||
## Commas "in the raw" may not be used in the format string portion,
|
||||
## either, but you can probably use the URI-encoded form of the comma
|
||||
## ("%2C") instead with no ill side-effects. If you must specify a
|
||||
## colon character in either the regular expression or the format
|
||||
## string, escape it with a preceding backslash ("\:").
|
||||
##
|
||||
## Example:
|
||||
## custom_log_formatting =
|
||||
## artf[0-9]+ : http://example.com/tracker?id=\0,
|
||||
## issue ([0-9]+) : http://example.com/bug?id=\1&opts=full%2csortby=id
|
||||
##
|
||||
#custom_log_formatting =
|
||||
|
||||
## default_file_view: "log", "co", or "markup"
|
||||
## Controls whether the default view for file URLs is a checkout view or
|
||||
## a log view. "log" is the default for backwards compatibility with old
|
||||
@@ -410,6 +510,15 @@
|
||||
##
|
||||
#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
|
||||
## directory ViewVC should consult for various things, including cached
|
||||
## remote authentication credentials. If unset, Subversion will use
|
||||
@@ -479,7 +588,7 @@
|
||||
## (Only works well for C source files, otherwise diff's heuristic falls short.)
|
||||
## ('-p' option to diff)
|
||||
##
|
||||
#hr_funout = 0
|
||||
#hr_funout = 1
|
||||
|
||||
## hr_ignore_white: Ignore whitespace (indendation and stuff) for human
|
||||
## readable diffs.
|
||||
@@ -552,6 +661,14 @@
|
||||
##
|
||||
#show_subdir_lastmod = 0
|
||||
|
||||
## show_roots_lastmod: In the root listing view, show the most recent
|
||||
## modifications made to the root. (Subversion roots only.)
|
||||
##
|
||||
## NOTE: Enabling this feature will significantly reduce the
|
||||
## performance of the root listing view.
|
||||
##
|
||||
#show_roots_lastmod = 0
|
||||
|
||||
## show_logs: Show the most recent log entry in directory listings.
|
||||
##
|
||||
#show_logs = 1
|
||||
@@ -569,25 +686,35 @@
|
||||
##
|
||||
#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
|
||||
## log entry should be truncated when shown in the directory view.
|
||||
##
|
||||
#short_log_len = 80
|
||||
|
||||
## enable_syntax_coloration: Should we colorize known file content
|
||||
## syntaxes? [Requires Pygments Python module]
|
||||
## syntaxes?
|
||||
##
|
||||
## 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
|
||||
|
||||
## tabsize: The number of spaces into which tabstops are converted
|
||||
## when viewing file contents.
|
||||
## tabsize: The number of spaces into which horizontal tab characters
|
||||
## are converted when viewing file contents. Set to 0 to preserve
|
||||
## tab characters.
|
||||
##
|
||||
#tabsize = 8
|
||||
|
||||
## detect_encoding: Should we attempt to detect versioned file
|
||||
## character encodings? [Requires 'chardet' module, and is currently
|
||||
## used only by the syntax coloration logic -- if enabled -- for the
|
||||
## 'markup' and 'annotate' views; see 'enable_syntax_coloration'.]
|
||||
## used only for the 'markup' and 'annotate' views.]
|
||||
##
|
||||
#detect_encoding = 0
|
||||
|
||||
@@ -629,6 +756,26 @@
|
||||
##
|
||||
#log_pagesize = 0
|
||||
|
||||
## log_pagesextra: Maximum number of extra pages (based on
|
||||
## log_pagesize) of revision log data to fetch and present to the user
|
||||
## as additional options for display. Revision log information
|
||||
## "beyond" this window is still accessible, but must be navigated to
|
||||
## in multiple steps.
|
||||
##
|
||||
## Example:
|
||||
## log_pagesize = 100
|
||||
## log_pagesextra = 3
|
||||
##
|
||||
## For a versioned file with 1000 revisions, the above settings would
|
||||
## present to the user the first 100 of those 1000 revisions, with
|
||||
## links to three additional pages (the 200-299th revisions, 300-399th
|
||||
## revisions, and 400-499th revisions) plus a link to the 500th
|
||||
## revision. Following these links slides the display "window",
|
||||
## showing the requested set of revisions plus links to three
|
||||
## additional pages beyond those, and so on.
|
||||
##
|
||||
#log_pagesextra = 3
|
||||
|
||||
## limit_changes: Maximum number of changed paths shown per commit in
|
||||
## the Subversion revision view and in query results. This is not a
|
||||
## hard limit (the UI provides options to show all changed paths), but
|
||||
@@ -749,6 +896,14 @@
|
||||
## row_limit: Maximum number of rows returned by a given normal query
|
||||
## 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
|
||||
|
||||
## rss_row_limit: Maximum number of rows returned by a given query to
|
||||
@@ -756,6 +911,9 @@
|
||||
## that RSS readers tend to poll regularly for new data, you might want
|
||||
## 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
|
||||
|
||||
## check_database_for_root: Check if the repository is found in the
|
||||
@@ -997,3 +1155,30 @@
|
||||
#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 =
|
||||
|
||||
##---------------------------------------------------------------------------
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>ViewVC 1.0 Template Authoring Guide</title>
|
||||
<title>ViewVC 1.1 Template Authoring Guide</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: rgb(180,193,205);
|
||||
@@ -38,13 +38,13 @@ td {
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>ViewVC 1.0 Template Authoring Guide</h1>
|
||||
<h1>ViewVC 1.1 Template Authoring Guide</h1>
|
||||
|
||||
<div class="h2">
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
<p>This document represents an (unfinished) attempt at providing
|
||||
documentation for how to customize ViewVC 1.0-dev's HTML output via
|
||||
instructions for how to customize ViewVC's HTML output via
|
||||
modification of its templates.</p>
|
||||
|
||||
</div>
|
||||
@@ -802,6 +802,11 @@ td {
|
||||
<td>String</td>
|
||||
<td>This is a URL for the markup view of the left file.</td>
|
||||
</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">
|
||||
<td class="varname">raw_diff</td>
|
||||
<td>String</td>
|
||||
@@ -1821,6 +1826,14 @@ td {
|
||||
<td>Indicates how query results are being sorted. Possible values:
|
||||
<tt>date</tt>, <tt>author</tt>, and <tt>file</tt>.</td>
|
||||
</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">
|
||||
<td class="varname">show_branch</td>
|
||||
<td>Boolean</td>
|
||||
@@ -2144,6 +2157,38 @@ td {
|
||||
<td>List</td>
|
||||
<td>Set of configured viewable repositories.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.ago</td>
|
||||
<td>String</td>
|
||||
<td>Textual description of the time since <var>roots.date</var>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.author</td>
|
||||
<td>String</td>
|
||||
<td>Username of the last modifier of the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">root.date</td>
|
||||
<td>String</td>
|
||||
<td>Date (in UTC if not otherwise configured) of the last
|
||||
modification of the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.href</td>
|
||||
<td>String</td>
|
||||
<td>URL of root directory view for a configured repository.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.log</td>
|
||||
<td>String</td>
|
||||
<td>Log message of last modification to the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.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">
|
||||
<td class="varname">roots.name</td>
|
||||
<td>String</td>
|
||||
@@ -2157,17 +2202,24 @@ td {
|
||||
configuration can have negative security implications. Use this
|
||||
token at your own risk.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.rev</td>
|
||||
<td>String</td>
|
||||
<td>Youngest revision of the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.short_log</td>
|
||||
<td>String</td>
|
||||
<td>Log message of last modification to the root, truncated to
|
||||
contain no more than the number of characters specified by
|
||||
the <code>short_log_len</code> configuration option.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.type</td>
|
||||
<td>String</td>
|
||||
<td>Version control type of a configured repository. Valid
|
||||
values: <tt>cvs</tt>, <tt>svn</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.href</td>
|
||||
<td>String</td>
|
||||
<td>URL of root directory view for a configured repository.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@@ -1192,13 +1192,6 @@ th.caption {
|
||||
commands to back out changes instead showing a normal query result
|
||||
page</td>
|
||||
</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>
|
||||
<td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td>
|
||||
<td>optional</td>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
@@ -74,7 +74,8 @@ class HTMLBlameSource:
|
||||
self.path_parts = path_parts
|
||||
self.diff_url = diff_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):
|
||||
item = self.annotation.__getitem__(idx)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -88,6 +88,11 @@ import fnmatch
|
||||
# | vhosts |
|
||||
# | |
|
||||
# `-----------'
|
||||
# ,-----------.
|
||||
# | |
|
||||
# | query |
|
||||
# | |
|
||||
# `-----------'
|
||||
#
|
||||
# ### TODO: Figure out what this all means for the 'kv' stuff.
|
||||
#
|
||||
@@ -100,12 +105,15 @@ class Config:
|
||||
'cvsdb',
|
||||
'general',
|
||||
'options',
|
||||
'query',
|
||||
'templates',
|
||||
'utilities',
|
||||
)
|
||||
_force_multi_value = (
|
||||
# Configuration values with multiple, comma-separated values.
|
||||
'allowed_views',
|
||||
'binary_mime_types',
|
||||
'custom_log_formatting',
|
||||
'cvs_roots',
|
||||
'kv_files',
|
||||
'languages',
|
||||
@@ -145,11 +153,11 @@ class Config:
|
||||
self.conf_path = os.path.isfile(pathname) and pathname or None
|
||||
self.base = os.path.dirname(pathname)
|
||||
self.parser = ConfigParser.ConfigParser()
|
||||
self.parser.optionxform = lambda x: x # don't case-normalize option names.
|
||||
self.parser.read(self.conf_path or [])
|
||||
|
||||
for section in self.parser.sections():
|
||||
if self._is_allowed_section(self.parser, section,
|
||||
self._base_sections):
|
||||
if self._is_allowed_section(section, self._base_sections):
|
||||
self._process_section(self.parser, section, section)
|
||||
|
||||
if vhost and self.parser.has_section('vhosts'):
|
||||
@@ -172,6 +180,7 @@ class Config:
|
||||
fname = string.replace(fname, '%lang%', language)
|
||||
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.optionxform = lambda x: x # don't case-normalize option names.
|
||||
parser.read(os.path.join(self.base, fname))
|
||||
for section in parser.sections():
|
||||
for option in parser.options(section):
|
||||
@@ -214,7 +223,7 @@ class Config:
|
||||
|
||||
setattr(sc, opt, value)
|
||||
|
||||
def _is_allowed_section(self, parser, section, allowed_sections):
|
||||
def _is_allowed_section(self, section, allowed_sections):
|
||||
"""Return 1 iff SECTION is an allowed section, defined as being
|
||||
explicitly present in the ALLOWED_SECTIONS list or present in the
|
||||
form 'someprefix-*' in that list."""
|
||||
@@ -227,7 +236,7 @@ class Config:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def _is_allowed_override(self, parser, sectype, secspec, section):
|
||||
def _is_allowed_override(self, sectype, secspec, section):
|
||||
"""Test if SECTION is an allowed override section for sections of
|
||||
type SECTYPE ('vhosts' or 'root', currently) and type-specifier
|
||||
SECSPEC (a rootname or vhostname, currently). If it is, return
|
||||
@@ -240,7 +249,7 @@ class Config:
|
||||
if section[:lcv] != cv:
|
||||
return None
|
||||
base_section = section[lcv:]
|
||||
if self._is_allowed_section(parser, base_section,
|
||||
if self._is_allowed_section(base_section,
|
||||
self._allowed_overrides[sectype]):
|
||||
return base_section
|
||||
raise IllegalOverrideSection(sectype, section)
|
||||
@@ -253,8 +262,7 @@ class Config:
|
||||
|
||||
# Overlay any option sections associated with this vhost name.
|
||||
for section in parser.sections():
|
||||
base_section = self._is_allowed_override(parser, 'vhost',
|
||||
canon_vhost, section)
|
||||
base_section = self._is_allowed_override('vhost', canon_vhost, section)
|
||||
if base_section:
|
||||
self._process_section(parser, section, base_section)
|
||||
|
||||
@@ -280,8 +288,7 @@ class Config:
|
||||
return
|
||||
|
||||
for section in self.parser.sections():
|
||||
base_section = self._is_allowed_override(self.parser, 'root',
|
||||
rootname, section)
|
||||
base_section = self._is_allowed_override('root', rootname, section)
|
||||
if base_section:
|
||||
# We can currently only deal with root overlays happening
|
||||
# once, so check that we've not yet done any overlaying of
|
||||
@@ -322,7 +329,7 @@ class Config:
|
||||
assert(self.root_options_overlayed == 0)
|
||||
|
||||
if not self.conf_path:
|
||||
return None
|
||||
return None, {}
|
||||
|
||||
# Figure out the authorizer by searching first for a per-root
|
||||
# override, then falling back to the base/vhost configuration.
|
||||
@@ -393,11 +400,14 @@ class Config:
|
||||
self.options.allowed_views = ['annotate', 'diff', 'markup', 'roots']
|
||||
self.options.authorizer = None
|
||||
self.options.mangle_email_addresses = 0
|
||||
self.options.custom_log_formatting = []
|
||||
self.options.default_file_view = "log"
|
||||
self.options.binary_mime_types = []
|
||||
self.options.http_expiration_time = 600
|
||||
self.options.generate_etags = 1
|
||||
self.options.svn_ignore_mimetype = 0
|
||||
self.options.svn_config_dir = None
|
||||
self.options.max_filesize_kbytes = 512
|
||||
self.options.use_rcsparse = 0
|
||||
self.options.sort_by = 'file'
|
||||
self.options.sort_group_dirs = 1
|
||||
@@ -415,10 +425,12 @@ class Config:
|
||||
self.options.template_dir = "templates"
|
||||
self.options.docroot = None
|
||||
self.options.show_subdir_lastmod = 0
|
||||
self.options.show_roots_lastmod = 0
|
||||
self.options.show_logs = 1
|
||||
self.options.show_log_in_markup = 1
|
||||
self.options.cross_copies = 1
|
||||
self.options.use_localtime = 0
|
||||
self.options.iso8601_timestamps = 0
|
||||
self.options.short_log_len = 80
|
||||
self.options.enable_syntax_coloration = 1
|
||||
self.options.tabsize = 8
|
||||
@@ -428,6 +440,7 @@ class Config:
|
||||
self.options.use_re_search = 0
|
||||
self.options.dir_pagesize = 0
|
||||
self.options.log_pagesize = 0
|
||||
self.options.log_pagesextra = 3
|
||||
self.options.limit_changes = 100
|
||||
|
||||
self.templates.diff = None
|
||||
@@ -453,6 +466,8 @@ class Config:
|
||||
self.cvsdb.rss_row_limit = 100
|
||||
self.cvsdb.check_database_for_root = 0
|
||||
|
||||
self.query.viewvc_base_url = None
|
||||
|
||||
def _startswith(somestr, substr):
|
||||
return somestr[:len(substr)] == substr
|
||||
|
||||
|
85
lib/cvsdb.py
85
lib/cvsdb.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -38,13 +38,12 @@ error = "cvsdb error"
|
||||
## complient database interface
|
||||
|
||||
class CheckinDatabase:
|
||||
def __init__(self, host, port, user, passwd, database, row_limit):
|
||||
def __init__(self, host, port, user, passwd, database):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._user = user
|
||||
self._passwd = passwd
|
||||
self._database = database
|
||||
self._row_limit = row_limit
|
||||
self._version = None
|
||||
|
||||
## database lookup caches
|
||||
@@ -169,6 +168,9 @@ class CheckinDatabase:
|
||||
|
||||
return list
|
||||
|
||||
def GetCommitsTable(self):
|
||||
return self._version >= 1 and 'commits' or 'checkins'
|
||||
|
||||
def GetTableList(self):
|
||||
sql = "SHOW TABLES"
|
||||
cursor = self.db.cursor()
|
||||
@@ -309,8 +311,7 @@ class CheckinDatabase:
|
||||
minus_count = commit.GetMinusCount() or '0'
|
||||
description_id = self.GetDescriptionID(commit.GetDescription())
|
||||
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
sql = "REPLACE INTO %s" % (commits_table)
|
||||
sql = "REPLACE INTO %s" % (self.GetCommitsTable())
|
||||
sql = sql + \
|
||||
" (type,ci_when,whoid,repositoryid,dirid,fileid,revision,"\
|
||||
" stickytag,branchid,addedlines,removedlines,descid)"\
|
||||
@@ -351,9 +352,17 @@ class CheckinDatabase:
|
||||
match = " LIKE "
|
||||
elif query_entry.match == "glob":
|
||||
match = " REGEXP "
|
||||
# use fnmatch to translate the glob into a regexp
|
||||
# Use fnmatch to translate the glob into a regular
|
||||
# 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)
|
||||
if data[0] != '^': data = '^' + data
|
||||
if data[0] != '^':
|
||||
data = '^' + data
|
||||
if data[-7:] == '\Z(?ms)':
|
||||
data = data[:-7] + '$'
|
||||
elif query_entry.match == "regex":
|
||||
match = " REGEXP "
|
||||
elif query_entry.match == "notregex":
|
||||
@@ -363,8 +372,8 @@ class CheckinDatabase:
|
||||
|
||||
return "(%s)" % (string.join(sqlList, " OR "))
|
||||
|
||||
def CreateSQLQueryString(self, query):
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
def CreateSQLQueryString(self, query, detect_leftover=0):
|
||||
commits_table = self.GetCommitsTable()
|
||||
tableList = [(commits_table, None)]
|
||||
condList = []
|
||||
|
||||
@@ -444,13 +453,14 @@ class CheckinDatabase:
|
||||
conditions = string.join(joinConds + condList, " AND ")
|
||||
conditions = conditions and "WHERE %s" % conditions
|
||||
|
||||
## limit the number of rows requested or we could really slam
|
||||
## a server with a large database
|
||||
## apply the query's row limit, if any (so we avoid really
|
||||
## slamming a server with a large database)
|
||||
limit = ""
|
||||
if query.limit:
|
||||
if detect_leftover:
|
||||
limit = "LIMIT %s" % (str(query.limit + 1))
|
||||
else:
|
||||
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" \
|
||||
% (commits_table, tables, conditions, order_by, limit)
|
||||
@@ -458,14 +468,20 @@ class CheckinDatabase:
|
||||
return sql
|
||||
|
||||
def RunQuery(self, query):
|
||||
sql = self.CreateSQLQueryString(query)
|
||||
sql = self.CreateSQLQueryString(query, 1)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql)
|
||||
query.SetExecuted()
|
||||
row_count = 0
|
||||
|
||||
while 1:
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
break
|
||||
row_count = row_count + 1
|
||||
if query.limit and (row_count > query.limit):
|
||||
query.SetLimitReached()
|
||||
break
|
||||
|
||||
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
|
||||
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
|
||||
@@ -504,13 +520,15 @@ class CheckinDatabase:
|
||||
if file_id == None:
|
||||
return None
|
||||
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
sql = "SELECT * FROM %s WHERE "\
|
||||
sql = "SELECT type, ci_when, whoid, repositoryid, dirid, fileid, " \
|
||||
"revision, stickytag, branchid, addedlines, removedlines, " \
|
||||
"descid "\
|
||||
" FROM %s WHERE "\
|
||||
" repositoryid=%%s "\
|
||||
" AND dirid=%%s"\
|
||||
" AND fileid=%%s"\
|
||||
" AND revision=%%s"\
|
||||
% (commits_table)
|
||||
% (self.GetCommitsTable())
|
||||
sql_args = (repository_id, dir_id, file_id, commit.GetRevision())
|
||||
|
||||
cursor = self.db.cursor()
|
||||
@@ -527,10 +545,9 @@ class CheckinDatabase:
|
||||
def sql_delete(self, table, key, value, keep_fkey = None):
|
||||
sql = "DELETE FROM %s WHERE %s=%%s" % (table, key)
|
||||
sql_args = (value, )
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
if keep_fkey:
|
||||
sql += " AND %s NOT IN (SELECT %s FROM %s WHERE %s = %%s)" \
|
||||
% (key, keep_fkey, commits_table, keep_fkey)
|
||||
% (key, keep_fkey, self.GetCommitsTable(), keep_fkey)
|
||||
sql_args = (value, value)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
@@ -556,7 +573,10 @@ class CheckinDatabase:
|
||||
self.sql_purge('descs', 'id', 'descid', 'commits')
|
||||
self.sql_purge('people', 'id', 'whoid', 'commits')
|
||||
else:
|
||||
sql = "SELECT * FROM checkins WHERE repositoryid=%s"
|
||||
sql = "SELECT type, ci_when, whoid, repositoryid, dirid, " \
|
||||
"fileid, revision, stickytag, branchid, addedlines, " \
|
||||
"removedlines, descid "\
|
||||
" FROM checkins WHERE repositoryid=%s"
|
||||
sql_args = (rep_id, )
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
@@ -769,8 +789,9 @@ class QueryEntry:
|
||||
self.data = data
|
||||
self.match = match
|
||||
|
||||
## CheckinDatabaseQueryData is a object which contains the search parameters
|
||||
## for a query to the CheckinDatabase
|
||||
## CheckinDatabaseQuery is an object which contains the search
|
||||
## parameters for a query to the Checkin Database and -- after the
|
||||
## query is executed -- the data returned by the query.
|
||||
class CheckinDatabaseQuery:
|
||||
def __init__(self):
|
||||
## sorting
|
||||
@@ -790,6 +811,7 @@ class CheckinDatabaseQuery:
|
||||
|
||||
## limit on number of rows to return
|
||||
self.limit = None
|
||||
self.limit_reached = 0
|
||||
|
||||
## list of commits -- filled in by CVS query
|
||||
self.commit_list = []
|
||||
@@ -798,6 +820,9 @@ class CheckinDatabaseQuery:
|
||||
## are added
|
||||
self.commit_cb = None
|
||||
|
||||
## has this query been run?
|
||||
self.executed = 0
|
||||
|
||||
def SetRepository(self, repository, match = "exact"):
|
||||
self.repository_list.append(QueryEntry(repository, match))
|
||||
|
||||
@@ -843,6 +868,20 @@ class CheckinDatabaseQuery:
|
||||
def AddCommit(self, 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
|
||||
@@ -861,7 +900,7 @@ def ConnectDatabase(cfg, readonly=0):
|
||||
user = cfg.cvsdb.user
|
||||
passwd = cfg.cvsdb.passwd
|
||||
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd,
|
||||
cfg.cvsdb.database_name, cfg.cvsdb.row_limit)
|
||||
cfg.cvsdb.database_name)
|
||||
db.Connect()
|
||||
return db
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
75
lib/ezt.py
75
lib/ezt.py
@@ -347,7 +347,7 @@ class Template:
|
||||
for_names = [ ]
|
||||
|
||||
if base_format:
|
||||
program.append((self._cmd_format, _printers[base_format]))
|
||||
program.append((self._cmd_format, _formatters[base_format]))
|
||||
|
||||
for i in range(len(parts)):
|
||||
piece = parts[i]
|
||||
@@ -405,13 +405,13 @@ class Template:
|
||||
elif cmd == 'format':
|
||||
if args[1][0]:
|
||||
# argument is a variable reference
|
||||
printer = args[1]
|
||||
formatter = args[1]
|
||||
else:
|
||||
# argument is a string constant referring to built-in printer
|
||||
printer = _printers.get(args[1][1])
|
||||
if not printer:
|
||||
# argument is a string constant referring to built-in formatter
|
||||
formatter = _formatters.get(args[1][1])
|
||||
if not formatter:
|
||||
raise UnknownFormatConstantError(str(args[1:]))
|
||||
program.append((self._cmd_format, printer))
|
||||
program.append((self._cmd_format, formatter))
|
||||
|
||||
# remember the cmd, current pos, args, and a section placeholder
|
||||
stack.append([cmd, len(program), args[1:], None])
|
||||
@@ -465,13 +465,13 @@ class Template:
|
||||
except TypeError:
|
||||
raise Exception("Unprintable value type for '%s'" % (str(valrefs[0][0])))
|
||||
|
||||
def _cmd_format(self, printer, ctx):
|
||||
if type(printer) is TupleType:
|
||||
printer = _get_value(printer, ctx)
|
||||
ctx.printers.append(printer)
|
||||
def _cmd_format(self, formatter, ctx):
|
||||
if type(formatter) is TupleType:
|
||||
formatter = _get_value(formatter, ctx)
|
||||
ctx.formatters.append(formatter)
|
||||
|
||||
def _cmd_end_format(self, valref, ctx):
|
||||
ctx.printers.pop()
|
||||
ctx.formatters.pop()
|
||||
|
||||
def _cmd_include(self, (valref, reader), ctx):
|
||||
fname = _get_value(valref, ctx)
|
||||
@@ -637,14 +637,23 @@ def _get_value((refname, start, rest), ctx):
|
||||
# string or a sequence
|
||||
return ob
|
||||
|
||||
def _print_formatted(formatters, ctx, chunk):
|
||||
# print chunk to ctx.fp after running it sequentially through formatters
|
||||
for formatter in formatters:
|
||||
chunk = formatter(chunk)
|
||||
ctx.fp.write(chunk)
|
||||
|
||||
def _write_value(value, args, ctx):
|
||||
# value is a callback function, generates its own output
|
||||
if callable(value):
|
||||
apply(value, [ctx] + list(args))
|
||||
return
|
||||
|
||||
# pop printer in case it recursively calls _write_value
|
||||
printer = ctx.printers.pop()
|
||||
# squirrel away formatters in case one of them recursively calls
|
||||
# _write_value() -- we'll use them (in reverse order) to format our
|
||||
# output.
|
||||
formatters = ctx.formatters[:]
|
||||
formatters.reverse()
|
||||
|
||||
try:
|
||||
# if the value has a 'read' attribute, then it is a stream: copy it
|
||||
@@ -653,7 +662,7 @@ def _write_value(value, args, ctx):
|
||||
chunk = value.read(16384)
|
||||
if not chunk:
|
||||
break
|
||||
printer(ctx, chunk)
|
||||
_print_formatted(formatters, ctx, chunk)
|
||||
|
||||
# value is a substitution pattern
|
||||
elif args:
|
||||
@@ -666,14 +675,16 @@ def _write_value(value, args, ctx):
|
||||
piece = args[idx]
|
||||
else:
|
||||
piece = '<undef>'
|
||||
printer(ctx, piece)
|
||||
_print_formatted(formatters, ctx, piece)
|
||||
|
||||
# plain old value, write to output
|
||||
else:
|
||||
printer(ctx, value)
|
||||
_print_formatted(formatters, ctx, value)
|
||||
|
||||
finally:
|
||||
ctx.printers.append(printer)
|
||||
# restore our formatters
|
||||
formatters.reverse()
|
||||
ctx.formatters = formatters
|
||||
|
||||
|
||||
class TemplateData:
|
||||
@@ -715,7 +726,7 @@ class Context:
|
||||
"""A container for the execution context"""
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
self.printers = []
|
||||
self.formatters = []
|
||||
def write(self, value, args=()):
|
||||
_write_value(value, args, self)
|
||||
|
||||
@@ -828,20 +839,26 @@ class BaseUnavailableError(EZTException):
|
||||
class UnknownFormatConstantError(EZTException):
|
||||
"""The format specifier is an unknown value."""
|
||||
|
||||
def _raw_printer(ctx, s):
|
||||
ctx.fp.write(s)
|
||||
def _raw_formatter(s):
|
||||
return s
|
||||
|
||||
def _html_printer(ctx, s):
|
||||
ctx.fp.write(cgi.escape(s))
|
||||
def _html_formatter(s):
|
||||
return cgi.escape(s)
|
||||
|
||||
def _uri_printer(ctx, s):
|
||||
ctx.fp.write(urllib.quote(s))
|
||||
def _xml_formatter(s):
|
||||
s = s.replace('&', '&')
|
||||
s = s.replace('<', '<')
|
||||
s = s.replace('>', '>')
|
||||
return s
|
||||
|
||||
_printers = {
|
||||
FORMAT_RAW : _raw_printer,
|
||||
FORMAT_HTML : _html_printer,
|
||||
FORMAT_XML : _html_printer,
|
||||
FORMAT_URI : _uri_printer,
|
||||
def _uri_formatter(s):
|
||||
return urllib.quote(s)
|
||||
|
||||
_formatters = {
|
||||
FORMAT_RAW : _raw_formatter,
|
||||
FORMAT_HTML : _html_formatter,
|
||||
FORMAT_XML : _xml_formatter,
|
||||
FORMAT_URI : _uri_formatter,
|
||||
}
|
||||
|
||||
# --- standard test environment ---
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
48
lib/query.py
48
lib/query.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -217,8 +217,9 @@ def decode_command(cmd):
|
||||
else:
|
||||
return "exact"
|
||||
|
||||
def form_to_cvsdb_query(form_data):
|
||||
def form_to_cvsdb_query(cfg, form_data):
|
||||
query = cvsdb.CreateCheckinQuery()
|
||||
query.SetLimit(cfg.cvsdb.row_limit)
|
||||
|
||||
if form_data.repository:
|
||||
for cmd, str in listparse_string(form_data.repository):
|
||||
@@ -312,11 +313,7 @@ def is_forbidden(cfg, cvsroot_name, module):
|
||||
|
||||
def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
ob = _item(num_files=len(files), files=[])
|
||||
|
||||
if desc:
|
||||
ob.log = string.replace(server.escape(desc), '\n', '<br />')
|
||||
else:
|
||||
ob.log = ' '
|
||||
ob.log = desc and string.replace(server.escape(desc), '\n', '<br />') or ''
|
||||
|
||||
for commit in files:
|
||||
repository = commit.GetRepository()
|
||||
@@ -350,9 +347,10 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
except:
|
||||
raise Exception, str([directory, commit.GetFile()])
|
||||
|
||||
## if we couldn't find the cvsroot path configured in the
|
||||
## viewvc.conf file, then don't make the link
|
||||
if cvsroot_name:
|
||||
## If we couldn't find the cvsroot path configured in the
|
||||
## viewvc.conf file, or we don't have a VIEWVC_LINK, then
|
||||
## don't make the link.
|
||||
if cvsroot_name and viewvc_link:
|
||||
flink = '[%s] <a href="%s/%s?root=%s">%s</a>' % (
|
||||
cvsroot_name, viewvc_link, urllib.quote(file),
|
||||
cvsroot_name, file)
|
||||
@@ -380,12 +378,15 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
return ob
|
||||
|
||||
def run_query(server, cfg, form_data, viewvc_link):
|
||||
query = form_to_cvsdb_query(form_data)
|
||||
query = form_to_cvsdb_query(cfg, form_data)
|
||||
db = cvsdb.ConnectDatabaseReadOnly(cfg)
|
||||
db.RunQuery(query)
|
||||
|
||||
if not query.commit_list:
|
||||
return [ ]
|
||||
commit_list = query.GetCommitList()
|
||||
if not commit_list:
|
||||
return [ ], 0
|
||||
|
||||
row_limit_reached = query.GetLimitReached()
|
||||
|
||||
commits = [ ]
|
||||
files = [ ]
|
||||
@@ -396,8 +397,8 @@ def run_query(server, cfg, form_data, viewvc_link):
|
||||
for key, value in rootitems:
|
||||
cvsroots[cvsdb.CleanRepository(value)] = key
|
||||
|
||||
current_desc = query.commit_list[0].GetDescription()
|
||||
for commit in query.commit_list:
|
||||
current_desc = commit_list[0].GetDescription()
|
||||
for commit in commit_list:
|
||||
desc = commit.GetDescription()
|
||||
if current_desc == desc:
|
||||
files.append(commit)
|
||||
@@ -420,7 +421,7 @@ def run_query(server, cfg, form_data, viewvc_link):
|
||||
return len(commit.files) > 0
|
||||
commits = filter(_only_with_files, commits)
|
||||
|
||||
return commits
|
||||
return commits, row_limit_reached
|
||||
|
||||
def main(server, cfg, viewvc_link):
|
||||
try:
|
||||
@@ -429,12 +430,18 @@ def main(server, cfg, viewvc_link):
|
||||
form_data = FormData(form)
|
||||
|
||||
if form_data.valid:
|
||||
commits = run_query(server, cfg, form_data, viewvc_link)
|
||||
commits, row_limit_reached = run_query(server, cfg,
|
||||
form_data, viewvc_link)
|
||||
query = None
|
||||
else:
|
||||
commits = [ ]
|
||||
row_limit_reached = 0
|
||||
query = 'skipped'
|
||||
|
||||
docroot = cfg.options.docroot
|
||||
if docroot is None and viewvc_link:
|
||||
docroot = viewvc_link + '/' + viewvc.docroot_magic_path
|
||||
|
||||
data = ezt.TemplateData({
|
||||
'cfg' : cfg,
|
||||
'address' : cfg.general.address,
|
||||
@@ -444,14 +451,11 @@ def main(server, cfg, viewvc_link):
|
||||
'directory' : server.escape(form_data.directory),
|
||||
'file' : server.escape(form_data.file),
|
||||
'who' : server.escape(form_data.who),
|
||||
'docroot' : cfg.options.docroot is None \
|
||||
and viewvc_link + '/' + viewvc.docroot_magic_path \
|
||||
or cfg.options.docroot,
|
||||
|
||||
'docroot' : docroot,
|
||||
'sortby' : form_data.sortby,
|
||||
'date' : form_data.date,
|
||||
|
||||
'query' : query,
|
||||
'row_limit_reached' : ezt.boolean(row_limit_reached),
|
||||
'commits' : commits,
|
||||
'num_commits' : len(commits),
|
||||
'rss_href' : None,
|
||||
|
64
lib/sapi.py
64
lib/sapi.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -23,8 +23,8 @@ import re
|
||||
import cgi
|
||||
|
||||
|
||||
# global server object. It will be either a CgiServer or a proxy to
|
||||
# an AspServer or ModPythonServer object.
|
||||
# global server object. It will be either a CgiServer, a WsgiServer,
|
||||
# or a proxy to an AspServer or ModPythonServer object.
|
||||
server = None
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ server = None
|
||||
# that character as-is, and sometimes needs to embed escaped values
|
||||
# into HTML attributes.
|
||||
def escape(s):
|
||||
s = str(s)
|
||||
s = string.replace(s, '&', '&')
|
||||
s = string.replace(s, '>', '>')
|
||||
s = string.replace(s, '<', '<')
|
||||
@@ -197,6 +198,63 @@ class CgiServer(Server):
|
||||
return sys.stdout
|
||||
|
||||
|
||||
class WsgiServer(Server):
|
||||
def __init__(self, environ, start_response):
|
||||
Server.__init__(self)
|
||||
|
||||
self._environ = environ
|
||||
self._start_response = start_response;
|
||||
self._headers = []
|
||||
self._wsgi_write = None
|
||||
self.headerSent = False
|
||||
|
||||
global server
|
||||
server = self
|
||||
|
||||
global cgi
|
||||
import cgi
|
||||
|
||||
def addheader(self, name, value):
|
||||
self._headers.append((name, value))
|
||||
|
||||
def header(self, content_type='text/html; charset=UTF-8', status=None):
|
||||
if not status:
|
||||
status = "200 OK"
|
||||
if not self.headerSent:
|
||||
self.headerSent = True
|
||||
self._headers.insert(0, ("Content-Type", content_type),)
|
||||
self._wsgi_write = self._start_response("%s" % status, self._headers)
|
||||
|
||||
def redirect(self, url):
|
||||
"""Redirect client to url. This discards any data that has been queued
|
||||
to be sent to the user. But there should never by any anyway.
|
||||
"""
|
||||
self.addheader('Location', url)
|
||||
self.header(status='301 Moved')
|
||||
self._wsgi_write('This document is located <a href="%s">here</a>.' % url)
|
||||
|
||||
def getenv(self, name, value=None):
|
||||
return self._environ.get(name, value)
|
||||
|
||||
def params(self):
|
||||
return cgi.parse(environ=self._environ, fp=self._environ["wsgi.input"])
|
||||
|
||||
def FieldStorage(self, fp=None, headers=None, outerboundary="",
|
||||
environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
||||
return cgi.FieldStorage(self._environ["wsgi.input"], headers,
|
||||
outerboundary, self._environ, keep_blank_values,
|
||||
strict_parsing)
|
||||
|
||||
def write(self, s):
|
||||
self._wsgi_write(s)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def file(self):
|
||||
return File(self)
|
||||
|
||||
|
||||
class AspServer(ThreadedServer):
|
||||
def __init__(self, Server, Request, Response, Application):
|
||||
ThreadedServer.__init__(self)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-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
|
||||
@@ -30,6 +30,14 @@ class GenericViewVCAuthorizer:
|
||||
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
|
||||
pass
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
"""Return 1 if the associated username is permitted to read every
|
||||
path in the repository at every revision, 0 if the associated
|
||||
username is prohibited from reading any path in the repository, or
|
||||
None if no such determination can be made (perhaps because the
|
||||
cost of making it is too great)."""
|
||||
pass
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
"""Return 1 iff the associated username is permitted to read
|
||||
revision REV of the path PATH_PARTS (of type PATHTYPE) in
|
||||
@@ -45,5 +53,8 @@ class ViewVCAuthorizer(GenericViewVCAuthorizer):
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
return 1
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-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
|
||||
@@ -24,6 +24,13 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
# If there aren't any forbidden paths, we can grant universal read
|
||||
# access. Otherwise, we make no claim.
|
||||
if not self.forbidden:
|
||||
return 1
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
# No path? No problem.
|
||||
if not path_parts:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2008-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
|
||||
@@ -46,6 +46,13 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
def check_root_access(self, rootname):
|
||||
return self._check_root_path_access(rootname)
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
# If there aren't any forbidden regexps, we can grant universal
|
||||
# read access. Otherwise, we make no claim.
|
||||
if not self.forbidden:
|
||||
return 1
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
root_path = rootname
|
||||
if path_parts:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-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
|
||||
@@ -34,9 +34,9 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
# See if the admin wants us to do case normalization of usernames.
|
||||
self.force_username_case = params.get('force_username_case')
|
||||
if self.force_username_case == "upper":
|
||||
self.username = username.upper()
|
||||
self.username = username and string.upper(username) or username
|
||||
elif self.force_username_case == "lower":
|
||||
self.username = username.lower()
|
||||
self.username = username and string.lower(username) or username
|
||||
elif not self.force_username_case:
|
||||
self.username = username
|
||||
else:
|
||||
@@ -54,7 +54,10 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
# option names.
|
||||
cp = ConfigParser()
|
||||
cp.optionxform = lambda x: x
|
||||
try:
|
||||
cp.read(self.authz_file)
|
||||
except:
|
||||
raise debug.ViewVCException("Unable to parse configured authzfile file")
|
||||
|
||||
# Figure out if there are any aliases for the current username
|
||||
aliases = []
|
||||
@@ -221,6 +224,36 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
paths = self._get_paths_for_root(rootname)
|
||||
return (paths is not None) and 1 or 0
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
paths = self._get_paths_for_root(rootname)
|
||||
if not paths: # None or empty.
|
||||
return 0
|
||||
|
||||
# Search the access determinations. If there's a mix, we can't
|
||||
# claim a universal access determination.
|
||||
found_allow = 0
|
||||
found_deny = 0
|
||||
for access in paths.values():
|
||||
if access:
|
||||
found_allow = 1
|
||||
else:
|
||||
found_deny = 1
|
||||
if found_allow and found_deny:
|
||||
return None
|
||||
|
||||
# We didn't find both allowances and denials, so we must have
|
||||
# found one or the other. Denials only is a universal denial.
|
||||
if found_deny:
|
||||
return 0
|
||||
|
||||
# ... but allowances only is only a universal allowance if read
|
||||
# access is granted to the root directory.
|
||||
if found_allow and paths.has_key('/'):
|
||||
return 1
|
||||
|
||||
# Anything else is indeterminable.
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
# Crawl upward from the path represented by PATH_PARTS toward to
|
||||
# the root of the repository, looking for an explicitly grant or
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -76,7 +76,7 @@ class Repository:
|
||||
"""
|
||||
pass
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
def openfile(self, path_parts, rev, options):
|
||||
"""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
|
||||
@@ -86,6 +86,8 @@ class Repository:
|
||||
of the repository. e.g. ["subdir1", "subdir2", "filename"]
|
||||
|
||||
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):
|
||||
@@ -168,20 +170,29 @@ class Repository:
|
||||
Return value is a python file object
|
||||
"""
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
"""Return a list of annotate file content lines and a revision.
|
||||
def annotate(self, path_parts, rev, include_text=False):
|
||||
"""Return a list of Annotation object, sorted by their
|
||||
"line_number" components, which describe the lines of given
|
||||
version of a file.
|
||||
|
||||
The result is a list of Annotation objects, sorted by their
|
||||
line_number components.
|
||||
"""
|
||||
The file 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.
|
||||
|
||||
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):
|
||||
"""Return information about a global revision
|
||||
|
||||
rev is the revision of the item to return information about
|
||||
|
||||
Return value is a 4-tuple containing the date, author, log
|
||||
message, and a list of ChangedPath items representing paths changed
|
||||
Return value is a 5-tuple containing: the date, author, log
|
||||
message, a list of ChangedPath items representing paths changed,
|
||||
and a dictionary mapping property names to property values for
|
||||
properties stored on an item.
|
||||
|
||||
Raise vclib.UnsupportedFeature if the version control system
|
||||
doesn't support a global revision concept.
|
||||
@@ -197,6 +208,20 @@ class Repository:
|
||||
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:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -14,6 +14,7 @@ import os.path
|
||||
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
assert os.path.isabs(rootpath)
|
||||
return os.path.normpath(rootpath)
|
||||
|
||||
|
||||
@@ -22,6 +23,7 @@ def expand_root_parent(parent_path):
|
||||
# "CVSROOT/config" is added the set of returned roots. Or, if the
|
||||
# PARENT_PATH itself contains a child "CVSROOT/config", then all its
|
||||
# subdirectories are returned as roots.
|
||||
assert os.path.isabs(parent_path)
|
||||
roots = {}
|
||||
subpaths = os.listdir(parent_path)
|
||||
cvsroot = os.path.exists(os.path.join(parent_path, "CVSROOT", "config"))
|
||||
@@ -33,6 +35,23 @@ def expand_root_parent(parent_path):
|
||||
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."""
|
||||
|
||||
assert os.path.isabs(parent_path)
|
||||
# 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.
|
||||
rootpath = os.path.join(parent_path, rootname)
|
||||
if os.path.exists(os.path.join(parent_path, "CVSROOT", "config")):
|
||||
return canonicalize_rootpath(rootpath)
|
||||
if os.path.exists(os.path.join(rootpath, "CVSROOT", "config")):
|
||||
return canonicalize_rootpath(rootpath)
|
||||
return None
|
||||
|
||||
|
||||
def CVSRepository(name, rootpath, authorizer, utilities, use_rcsparse):
|
||||
rootpath = canonicalize_rootpath(rootpath)
|
||||
if use_rcsparse:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -40,6 +40,11 @@ class BaseCVSRepository(vclib.Repository):
|
||||
if not vclib.check_root_access(self):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
def open(self):
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
@@ -145,6 +150,11 @@ class BaseCVSRepository(vclib.Repository):
|
||||
rcsfile = self.rcsfile(path_parts, 1)
|
||||
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):
|
||||
def _get_tip_revision(self, rcs_file, rev=None):
|
||||
@@ -162,7 +172,14 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
return revs[-1]
|
||||
return None
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
def openfile(self, path_parts, rev, options):
|
||||
"""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
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
@@ -170,12 +187,14 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
rev_flag = '-p'
|
||||
else:
|
||||
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)
|
||||
|
||||
used_rlog = 0
|
||||
tip_rev = None # used only if we have to fallback to using rlog
|
||||
|
||||
fp = self.rcs_popen('co', (rev_flag, full_name), 'rb')
|
||||
fp = self.rcs_popen('co', (kv_flag, rev_flag, full_name), 'rb')
|
||||
try:
|
||||
filename, revision = _parse_co_header(fp)
|
||||
except COMissingRevision:
|
||||
@@ -318,13 +337,13 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
args = rcs_args
|
||||
return popen.popen(cmd, args, mode, capture_err)
|
||||
|
||||
def annotate(self, path_parts, rev=None):
|
||||
def annotate(self, path_parts, rev=None, include_text=False):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
|
||||
from vclib.ccvs import blame
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev, include_text)
|
||||
return source, source.revision
|
||||
|
||||
def revinfo(self, rev):
|
||||
@@ -1022,16 +1041,16 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
|
||||
file.errors.append("rlog error: %s" % msg)
|
||||
continue
|
||||
|
||||
tag = None
|
||||
if view_tag == 'MAIN' or view_tag == 'HEAD':
|
||||
tag = Tag(None, default_branch)
|
||||
elif taginfo.has_key(view_tag):
|
||||
tag = Tag(None, taginfo[view_tag])
|
||||
elif view_tag:
|
||||
# the tag wasn't found, so skip this file
|
||||
elif view_tag and (eof != _EOF_FILE):
|
||||
# the tag wasn't found, so skip this file (unless we already
|
||||
# know there's nothing left of it to read)
|
||||
_skip_file(rlog)
|
||||
eof = 1
|
||||
else:
|
||||
tag = None
|
||||
eof = _EOF_FILE
|
||||
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
@@ -414,7 +414,7 @@ class CVSParser(rcsparse.Sink):
|
||||
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, rcs_file, opt_rev=None):
|
||||
def __init__(self, rcs_file, opt_rev=None, include_text=False):
|
||||
# Parse the CVS file
|
||||
parser = CVSParser()
|
||||
revision = parser.parse_cvs_file(rcs_file, opt_rev)
|
||||
@@ -428,6 +428,7 @@ class BlameSource:
|
||||
self.lines = lines
|
||||
self.num_lines = count
|
||||
self.parser = parser
|
||||
self.include_text = include_text
|
||||
|
||||
# keep track of where we are during an iteration
|
||||
self.idx = -1
|
||||
@@ -447,6 +448,8 @@ class BlameSource:
|
||||
line_number = idx + 1
|
||||
author = self.parser.revision_author[rev]
|
||||
thisline = self.lines[idx]
|
||||
if not self.include_text:
|
||||
thisline = None
|
||||
### TODO: Put a real date in here.
|
||||
item = vclib.Annotation(thisline, line_number, rev, prev_rev, author, None)
|
||||
self.last = item
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -127,9 +127,9 @@ class CCVSRepository(BaseCVSRepository):
|
||||
% (string.join(path_parts2, "/")))
|
||||
|
||||
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()
|
||||
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]
|
||||
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,
|
||||
self.utilities.diff or 'diff', diff_args)
|
||||
|
||||
def annotate(self, path_parts, rev=None):
|
||||
def annotate(self, path_parts, rev=None, include_text=False):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev, include_text)
|
||||
return source, source.revision
|
||||
|
||||
def revinfo(self, rev):
|
||||
raise vclib.UnsupportedFeature
|
||||
|
||||
def openfile(self, path_parts, rev=None):
|
||||
def openfile(self, path_parts, rev, options):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
@@ -200,6 +200,7 @@ class InfoSink(MatchingSink):
|
||||
self.matching_rev = None
|
||||
self.perfect_match = 0
|
||||
self.lockinfo = { }
|
||||
self.saw_revision = False
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
MatchingSink.define_tag(self, name, revision)
|
||||
@@ -213,10 +214,17 @@ class InfoSink(MatchingSink):
|
||||
self.entry.absent = 1
|
||||
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):
|
||||
self.lockinfo[rev] = locker
|
||||
|
||||
def define_revision(self, revision, date, author, state, branches, next):
|
||||
self.saw_revision = True
|
||||
|
||||
if self.perfect_match:
|
||||
return
|
||||
|
||||
@@ -224,14 +232,18 @@ class InfoSink(MatchingSink):
|
||||
rev = Revision(revision, date, author, state == "dead")
|
||||
rev.lockinfo = self.lockinfo.get(revision)
|
||||
|
||||
# perfect match if revision number matches tag number or if revision is on
|
||||
# trunk and tag points to trunk. imperfect match if tag refers to a branch
|
||||
# and this revision is the highest revision so far found on that branch
|
||||
# perfect match if revision number matches tag number or if
|
||||
# revision is on trunk and tag points to trunk. imperfect match
|
||||
# if tag refers to a branch and either a) this revision is the
|
||||
# highest revision so far found on that branch, or b) this
|
||||
# revision is the branchpoint.
|
||||
perfect = ((rev.number == tag.number) or
|
||||
(not tag.number and len(rev.number) == 2))
|
||||
if perfect or (tag.is_branch and tag.number == rev.number[:-1] and
|
||||
if perfect or (tag.is_branch and \
|
||||
((tag.number == rev.number[:-1] and
|
||||
(not self.matching_rev or
|
||||
rev.number > self.matching_rev.number)):
|
||||
rev.number > self.matching_rev.number)) or
|
||||
(rev.number == tag.number[:-1]))):
|
||||
self.matching_rev = rev
|
||||
self.perfect_match = perfect
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -194,6 +194,7 @@ class _Parser:
|
||||
else:
|
||||
# Chew up "newphrase"
|
||||
# warn("Unexpected RCS token: $token\n")
|
||||
while self.ts.get() != ';':
|
||||
pass
|
||||
else:
|
||||
if f is None:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -19,7 +19,11 @@ import string
|
||||
import common
|
||||
|
||||
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.
|
||||
# grab a good-sized chunk, but not too large to overwhelm memory.
|
||||
@@ -44,15 +48,17 @@ class _TokenStream:
|
||||
# out more complex solutions.
|
||||
|
||||
buf = self.buf
|
||||
lbuf = len(buf)
|
||||
idx = self.idx
|
||||
|
||||
while 1:
|
||||
if idx == len(buf):
|
||||
if idx == lbuf:
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '':
|
||||
# signal EOF by returning None as the token
|
||||
del self.buf # so we fail if get() is called again
|
||||
return None
|
||||
lbuf = len(buf)
|
||||
idx = 0
|
||||
|
||||
if buf[idx] not in string.whitespace:
|
||||
@@ -60,7 +66,7 @@ class _TokenStream:
|
||||
|
||||
idx = idx + 1
|
||||
|
||||
if buf[idx] == ';' or buf[idx] == ':':
|
||||
if buf[idx] in ';:':
|
||||
self.buf = buf
|
||||
self.idx = idx + 1
|
||||
return buf[idx]
|
||||
@@ -70,17 +76,18 @@ class _TokenStream:
|
||||
token = ''
|
||||
while 1:
|
||||
# find token characters in the current buffer
|
||||
while end < len(buf) and buf[end] not in self.token_term:
|
||||
while end < lbuf and buf[end] not in self.token_term:
|
||||
end = end + 1
|
||||
token = token + buf[idx:end]
|
||||
|
||||
if end < len(buf):
|
||||
if end < lbuf:
|
||||
# we stopped before the end, so we have a full token
|
||||
idx = end
|
||||
break
|
||||
|
||||
# we stopped at the end of the buffer, so we may have a partial token
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
lbuf = len(buf)
|
||||
idx = end = 0
|
||||
|
||||
self.buf = buf
|
||||
@@ -94,22 +101,24 @@ class _TokenStream:
|
||||
chunks = [ ]
|
||||
|
||||
while 1:
|
||||
if idx == len(buf):
|
||||
if idx == lbuf:
|
||||
idx = 0
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '':
|
||||
raise RuntimeError, 'EOF'
|
||||
lbuf = len(buf)
|
||||
i = string.find(buf, '@', idx)
|
||||
if i == -1:
|
||||
chunks.append(buf[idx:])
|
||||
idx = len(buf)
|
||||
idx = lbuf
|
||||
continue
|
||||
if i == len(buf) - 1:
|
||||
if i == lbuf - 1:
|
||||
chunks.append(buf[idx:i])
|
||||
idx = 0
|
||||
buf = '@' + self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '@':
|
||||
raise RuntimeError, 'EOF'
|
||||
lbuf = len(buf)
|
||||
continue
|
||||
if buf[i + 1] == '@':
|
||||
chunks.append(buf[idx:i+1])
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -15,17 +15,50 @@
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import urllib
|
||||
|
||||
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
try:
|
||||
def _canonicalize_path(path):
|
||||
import svn.core
|
||||
return svn.core.svn_path_canonicalize(rootpath)
|
||||
except:
|
||||
if re.search(_re_url, rootpath):
|
||||
return rootpath[-1] == '/' and rootpath[:-1] or rootpath
|
||||
return os.path.normpath(rootpath)
|
||||
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):
|
||||
# Try to canonicalize the rootpath using Subversion semantics.
|
||||
rootpath = _canonicalize_path(rootpath)
|
||||
|
||||
# ViewVC's support for local repositories is more complete and more
|
||||
# performant than its support for remote ones, so if we're on a
|
||||
# Unix-y system and we have a file:/// URL, convert it to a local
|
||||
# path instead.
|
||||
if os.name == 'posix':
|
||||
rootpath_lower = rootpath.lower()
|
||||
if rootpath_lower in ['file://localhost',
|
||||
'file://localhost/',
|
||||
'file://',
|
||||
'file:///'
|
||||
]:
|
||||
return '/'
|
||||
if rootpath_lower.startswith('file://localhost/'):
|
||||
rootpath = os.path.normpath(urllib.unquote(rootpath[16:]))
|
||||
elif rootpath_lower.startswith('file:///'):
|
||||
rootpath = os.path.normpath(urllib.unquote(rootpath[7:]))
|
||||
|
||||
# Ensure that we have an absolute path (or URL), and return.
|
||||
if not re.search(_re_url, rootpath):
|
||||
assert os.path.isabs(rootpath)
|
||||
return rootpath
|
||||
|
||||
|
||||
def expand_root_parent(parent_path):
|
||||
@@ -35,6 +68,7 @@ def expand_root_parent(parent_path):
|
||||
else:
|
||||
# Any subdirectories of PARENT_PATH which themselves have a child
|
||||
# "format" are returned as roots.
|
||||
assert os.path.isabs(parent_path)
|
||||
subpaths = os.listdir(parent_path)
|
||||
for rootname in subpaths:
|
||||
rootpath = os.path.join(parent_path, rootname)
|
||||
@@ -43,6 +77,20 @@ def expand_root_parent(parent_path):
|
||||
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):
|
||||
rootpath = canonicalize_rootpath(rootpath)
|
||||
if re.search(_re_url, rootpath):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -18,10 +18,12 @@ import os
|
||||
import string
|
||||
import re
|
||||
import tempfile
|
||||
import popen2
|
||||
import time
|
||||
import urllib
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, \
|
||||
_compare_paths, _path_parts, _cleanup_path, \
|
||||
_rev2optrev, _fix_subversion_exception, \
|
||||
_split_revprops, _canonicalize_path
|
||||
from svn import core, delta, client, wc, ra
|
||||
|
||||
|
||||
@@ -52,13 +54,71 @@ def get_directory_props(ra_session, path, rev):
|
||||
props = ra.svn_ra_get_dir(ra_session, path, rev)
|
||||
return props
|
||||
|
||||
def client_log(url, start_rev, end_rev, log_limit, 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 ###
|
||||
|
||||
|
||||
class LogCollector:
|
||||
### TODO: Make this thing authz-aware
|
||||
|
||||
def __init__(self, path, show_all_logs, lockinfo):
|
||||
def __init__(self, path, show_all_logs, lockinfo, access_check_func):
|
||||
# This class uses leading slashes for paths internally
|
||||
if not path:
|
||||
self.path = '/'
|
||||
@@ -67,8 +127,16 @@ class LogCollector:
|
||||
self.logs = []
|
||||
self.show_all_logs = show_all_logs
|
||||
self.lockinfo = lockinfo
|
||||
self.access_check_func = access_check_func
|
||||
self.done = False
|
||||
|
||||
def add_log(self, log_entry, pool):
|
||||
if self.done:
|
||||
return
|
||||
paths = log_entry.changed_paths
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
|
||||
def add_log(self, paths, revision, author, date, message, pool):
|
||||
# Changed paths have leading slashes
|
||||
changed_paths = paths.keys()
|
||||
changed_paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
@@ -88,13 +156,17 @@ class LogCollector:
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path + self.path[len(changed_path):]
|
||||
if self.show_all_logs or this_path:
|
||||
entry = Revision(revision, _datestr_to_date(date), author, message, None,
|
||||
self.lockinfo, self.path[1:], None, None)
|
||||
if self.access_check_func is None \
|
||||
or self.access_check_func(self.path[1:], revision):
|
||||
entry = Revision(revision, date, author, msg, None, self.lockinfo,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
else:
|
||||
self.done = True
|
||||
if this_path:
|
||||
self.path = this_path
|
||||
|
||||
def temp_checkout(svnrepos, path, rev):
|
||||
def cat_to_tempfile(svnrepos, path, rev):
|
||||
"""Check out file revision to temporary file"""
|
||||
temp = tempfile.mktemp()
|
||||
stream = core.svn_stream_from_aprfile(temp)
|
||||
@@ -155,21 +227,8 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
|
||||
def open(self):
|
||||
# Setup the client context baton, complete with non-prompting authstuffs.
|
||||
# TODO: svn_cmdline_setup_auth_baton() is mo' better (when available)
|
||||
core.svn_config_ensure(self.config_dir)
|
||||
self.ctx = client.svn_client_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)
|
||||
self.ctx = setup_client_ctx(self.config_dir)
|
||||
|
||||
ra_callbacks = ra.svn_ra_callbacks_t()
|
||||
ra_callbacks.auth_baton = self.ctx.auth_baton
|
||||
self.ra_session = ra.svn_ra_open(self.rootpath, ra_callbacks, None,
|
||||
@@ -178,6 +237,10 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
self._dirent_cache = { }
|
||||
self._revinfo_cache = { }
|
||||
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
@@ -211,18 +274,16 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
return pathtype
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
def openfile(self, path_parts, rev, options):
|
||||
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)
|
||||
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
|
||||
client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev), self.ctx)
|
||||
core.svn_stream_close(stream)
|
||||
return SelfCleanFP(tmp_file), self._get_last_history_rev(path_parts, rev)
|
||||
fp = SelfCleanFP(cat_to_tempfile(self, path, rev))
|
||||
lh_rev, c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
return fp, lh_rev
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
path = self._getpath(path_parts)
|
||||
@@ -237,7 +298,8 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
kind = vclib.DIR
|
||||
elif entry.kind == core.svn_node_file:
|
||||
kind = vclib.FILE
|
||||
if vclib.check_path_access(self, path_parts + [name], kind, rev):
|
||||
else:
|
||||
kind = None
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
return entries
|
||||
|
||||
@@ -249,11 +311,13 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for entry in entries:
|
||||
entry_path_parts = path_parts + [entry.name]
|
||||
if not vclib.check_path_access(self, entry_path_parts, entry.kind, rev):
|
||||
dirent = dirents.get(entry.name, None)
|
||||
# dirents is authz-sanitized, so ensure the entry is found therein.
|
||||
if dirent is None:
|
||||
continue
|
||||
dirent = dirents[entry.name]
|
||||
entry.date, entry.author, entry.log, changes = \
|
||||
self.revinfo(dirent.created_rev)
|
||||
# Get authz-sanitized revision metadata.
|
||||
entry.date, entry.author, entry.log, revprops, changes = \
|
||||
self._revinfo(dirent.created_rev)
|
||||
entry.rev = str(dirent.created_rev)
|
||||
entry.size = dirent.size
|
||||
entry.lockinfo = None
|
||||
@@ -267,29 +331,51 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
|
||||
# Use ls3 to fetch the lock status for this item.
|
||||
lockinfo = None
|
||||
basename = path_parts and path_parts[-1] or ""
|
||||
dirents, locks = list_directory(url, _rev2optrev(rev),
|
||||
# If this is a file, fetch the lock status and size (as of REV)
|
||||
# for this item.
|
||||
lockinfo = size_in_rev = None
|
||||
if path_type == vclib.FILE:
|
||||
basename = path_parts[-1]
|
||||
list_url = self._geturl(self._getpath(path_parts[:-1]))
|
||||
dirents, locks = list_directory(list_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
if locks.has_key(basename):
|
||||
lockinfo = locks[basename].owner
|
||||
if dirents.has_key(basename):
|
||||
size_in_rev = dirents[basename].size
|
||||
|
||||
# Special handling for the 'svn_latest_log' scenario.
|
||||
### FIXME: Don't like this hack. We should just introduce
|
||||
### something more direct in the vclib API.
|
||||
if options.get('svn_latest_log', 0):
|
||||
dir_lh_rev, dir_c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
date, author, log, revprops, changes = self._revinfo(dir_lh_rev)
|
||||
return [vclib.Revision(dir_lh_rev, str(dir_lh_rev), date, author,
|
||||
None, log, size_in_rev, lockinfo)]
|
||||
|
||||
def _access_checker(check_path, check_rev):
|
||||
return vclib.check_path_access(self, _path_parts(check_path),
|
||||
path_type, check_rev)
|
||||
|
||||
# It's okay if we're told to not show all logs on a file -- all
|
||||
# the revisions should match correctly anyway.
|
||||
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0), lockinfo)
|
||||
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0),
|
||||
lockinfo, _access_checker)
|
||||
|
||||
cross_copies = options.get('svn_cross_copies', 0)
|
||||
log_limit = 0
|
||||
if limit:
|
||||
log_limit = first + limit
|
||||
client.svn_client_log2([url], _rev2optrev(rev), _rev2optrev(1),
|
||||
log_limit, 1, not cross_copies,
|
||||
lc.add_log, self.ctx)
|
||||
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit, 1,
|
||||
cross_copies, lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
revs.sort()
|
||||
prev = None
|
||||
for rev in revs:
|
||||
# Swap out revision info with stuff from the cache (which is
|
||||
# authz-sanitized).
|
||||
rev.date, rev.author, rev.log, revprops, changes \
|
||||
= self._revinfo(rev.number)
|
||||
rev.prev = prev
|
||||
prev = rev
|
||||
revs.reverse()
|
||||
@@ -309,13 +395,25 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
return pairs and pairs[0][1] or {}
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
def annotate(self, path_parts, rev, include_text=False):
|
||||
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)
|
||||
url = self._geturl(path)
|
||||
|
||||
# Examine logs for the file to determine the oldest revision we are
|
||||
# permitted to see.
|
||||
log_options = {
|
||||
'svn_cross_copies' : 1,
|
||||
'svn_show_all_dir_logs' : 1,
|
||||
}
|
||||
revs = self.itemlog(path_parts, rev, vclib.SORTBY_REV, 0, 0, log_options)
|
||||
oldest_rev = revs[-1].number
|
||||
|
||||
# Now calculate the annotation data. Note that we'll not
|
||||
# inherently trust the provided author and date, because authz
|
||||
# rules might necessitate that we strip that information out.
|
||||
blame_data = []
|
||||
|
||||
def _blame_cb(line_no, revision, author, date,
|
||||
@@ -323,21 +421,27 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
prev_rev = None
|
||||
if revision > 1:
|
||||
prev_rev = revision - 1
|
||||
|
||||
# If we have an invalid revision, clear the date and author
|
||||
# values. Otherwise, if we have authz filtering to do, use the
|
||||
# revinfo cache to do so.
|
||||
if revision < 0:
|
||||
date = author = None
|
||||
elif self.auth:
|
||||
date, author, msg, revprops, changes = self._revinfo(revision)
|
||||
|
||||
# Strip text if the caller doesn't want it.
|
||||
if not include_text:
|
||||
line = None
|
||||
blame_data.append(vclib.Annotation(line, line_no + 1, revision, prev_rev,
|
||||
author, None))
|
||||
|
||||
client.svn_client_blame(url, _rev2optrev(1), _rev2optrev(rev),
|
||||
_blame_cb, self.ctx)
|
||||
author, date))
|
||||
|
||||
client.blame2(url, _rev2optrev(rev), _rev2optrev(oldest_rev),
|
||||
_rev2optrev(rev), _blame_cb, self.ctx)
|
||||
return blame_data, rev
|
||||
|
||||
def revinfo(self, rev):
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info:
|
||||
cached_info = self._revinfo_raw(rev)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
|
||||
return self._revinfo(rev, 1)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
@@ -352,12 +456,12 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, changes = self.revinfo(rev)
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
temp1 = cat_to_tempfile(self, p1, r1)
|
||||
temp2 = cat_to_tempfile(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)
|
||||
@@ -371,6 +475,15 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
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)
|
||||
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):
|
||||
return string.join(path_parts, '/')
|
||||
|
||||
@@ -378,8 +491,11 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
if rev is None or rev == 'HEAD':
|
||||
return self.youngest
|
||||
try:
|
||||
if type(rev) == type(''):
|
||||
while rev[0] == 'r':
|
||||
rev = rev[1:]
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
except:
|
||||
raise vclib.InvalidRevision(rev)
|
||||
if (rev < 0) or (rev > self.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
@@ -388,58 +504,139 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
def _geturl(self, path=None):
|
||||
if not path:
|
||||
return self.rootpath
|
||||
return self.rootpath + '/' + urllib.quote(path, "/*~")
|
||||
path = self.rootpath + '/' + urllib.quote(path)
|
||||
return _canonicalize_path(path)
|
||||
|
||||
def _get_dirents(self, path, rev):
|
||||
"""Return a 2-type of dirents and locks, possibly reading/writing
|
||||
from a local cache of that information."""
|
||||
from a local cache of that information. This functions performs
|
||||
authz checks, stripping out unreadable dirents."""
|
||||
|
||||
dir_url = self._geturl(path)
|
||||
path_parts = _path_parts(path)
|
||||
if path:
|
||||
key = str(rev) + '/' + path
|
||||
else:
|
||||
key = str(rev)
|
||||
|
||||
# Ensure that the cache gets filled...
|
||||
dirents_locks = self._dirent_cache.get(key)
|
||||
if not dirents_locks:
|
||||
dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
tmp_dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
dirents = {}
|
||||
for name, dirent in tmp_dirents.items():
|
||||
dirent_parts = path_parts + [name]
|
||||
kind = dirent.kind
|
||||
if (kind == core.svn_node_dir or kind == core.svn_node_file) \
|
||||
and vclib.check_path_access(self, dirent_parts,
|
||||
kind == core.svn_node_dir \
|
||||
and vclib.DIR or vclib.FILE, rev):
|
||||
lh_rev, c_rev = self._get_last_history_rev(dirent_parts, rev)
|
||||
dirent.created_rev = lh_rev
|
||||
dirents[name] = dirent
|
||||
dirents_locks = [dirents, locks]
|
||||
self._dirent_cache[key] = dirents_locks
|
||||
|
||||
# ...then return the goodies from the cache.
|
||||
return dirents_locks[0], dirents_locks[1]
|
||||
|
||||
def _get_last_history_rev(self, path_parts, rev):
|
||||
"""Return the a 2-tuple which contains:
|
||||
- the last interesting revision equal to or older than REV in
|
||||
the history of PATH_PARTS.
|
||||
- the created_rev of of PATH_PARTS as of REV."""
|
||||
|
||||
path = self._getpath(path_parts)
|
||||
url = self._geturl(self._getpath(path_parts))
|
||||
optrev = _rev2optrev(rev)
|
||||
|
||||
# Get the last-changed-rev.
|
||||
revisions = []
|
||||
def _info_cb(path, info, pool, retval=revisions):
|
||||
revisions.append(info.last_changed_rev)
|
||||
client.svn_client_info(url, optrev, optrev, _info_cb, 0, self.ctx)
|
||||
return revisions[0]
|
||||
last_changed_rev = revisions[0]
|
||||
|
||||
def _revinfo_raw(self, rev):
|
||||
# return 5-tuple (date, author, message, changes)
|
||||
optrev = _rev2optrev(rev)
|
||||
# Now, this object might not have been directly edited since the
|
||||
# last-changed-rev, but it might have been the child of a copy.
|
||||
# To determine this, we'll run a potentially no-op log between
|
||||
# LAST_CHANGED_REV and REV.
|
||||
lc = LogCollector(path, 1, None, None)
|
||||
client_log(url, optrev, _rev2optrev(last_changed_rev), 1, 1, 0,
|
||||
lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
if revs:
|
||||
revs.sort()
|
||||
return revs[0].number, last_changed_rev
|
||||
else:
|
||||
return last_changed_rev, last_changed_rev
|
||||
|
||||
def _revinfo_fetch(self, rev, include_changed_paths=0):
|
||||
need_changes = include_changed_paths or self.auth
|
||||
revs = []
|
||||
|
||||
def _log_cb(changed_paths, revision, author,
|
||||
datestr, message, pool, retval=revs):
|
||||
date = _datestr_to_date(datestr)
|
||||
def _log_cb(log_entry, pool, retval=revs):
|
||||
# If Subversion happens to call us more than once, we choose not
|
||||
# to care.
|
||||
if retval:
|
||||
return
|
||||
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
action_map = { 'D' : vclib.DELETED,
|
||||
'A' : vclib.ADDED,
|
||||
'R' : vclib.REPLACED,
|
||||
'M' : vclib.MODIFIED,
|
||||
}
|
||||
|
||||
# Easy out: if we won't use the changed-path info, just return a
|
||||
# changes-less tuple.
|
||||
if not need_changes:
|
||||
return revs.append([date, author, msg, revprops, None])
|
||||
|
||||
# Subversion 1.5 and earlier didn't offer the 'changed_paths2'
|
||||
# hash, and in Subversion 1.6, it's offered but broken.
|
||||
try:
|
||||
changed_paths = log_entry.changed_paths2
|
||||
paths = (changed_paths or {}).keys()
|
||||
except:
|
||||
changed_paths = log_entry.changed_paths
|
||||
paths = (changed_paths or {}).keys()
|
||||
paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
|
||||
# If we get this far, our caller needs changed-paths, or we need
|
||||
# them for authz-related sanitization.
|
||||
changes = []
|
||||
found_readable = found_unreadable = 0
|
||||
for path in paths:
|
||||
pathtype = None
|
||||
change = changed_paths[path]
|
||||
|
||||
# svn_log_changed_path_t (which we might get instead of the
|
||||
# svn_log_changed_path2_t we'd prefer) doesn't have the
|
||||
# 'node_kind' member.
|
||||
pathtype = None
|
||||
if hasattr(change, 'node_kind'):
|
||||
if change.node_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif change.node_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
|
||||
# svn_log_changed_path2_t only has the 'text_modified' and
|
||||
# 'props_modified' bits in Subversion 1.7 and beyond. And
|
||||
# svn_log_changed_path_t is without.
|
||||
text_modified = props_modified = 0
|
||||
if hasattr(change, 'text_modified'):
|
||||
if change.text_modified == core.svn_tristate_true:
|
||||
text_modified = 1
|
||||
if hasattr(change, 'props_modified'):
|
||||
if change.props_modified == core.svn_tristate_true:
|
||||
props_modified = 1
|
||||
|
||||
# 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)
|
||||
### 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:
|
||||
is_copy = 1
|
||||
base_path = change.copyfrom_path
|
||||
@@ -452,31 +649,61 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
base_path = path
|
||||
base_rev = revision - 1
|
||||
|
||||
### Check authz rules (we lie about the path type)
|
||||
# Check authz rules (sadly, we have to lie about the path type)
|
||||
parts = _path_parts(path)
|
||||
if vclib.check_path_access(self, parts, vclib.FILE, revision):
|
||||
if is_copy and base_path and (base_path != path):
|
||||
parts = _path_parts(base_path)
|
||||
if vclib.check_path_access(self, parts, vclib.FILE, base_rev):
|
||||
if not vclib.check_path_access(self, parts, vclib.FILE, base_rev):
|
||||
is_copy = 0
|
||||
base_path = None
|
||||
base_rev = None
|
||||
found_unreadable = 1
|
||||
changes.append(SVNChangedPath(path, revision, pathtype, base_path,
|
||||
base_rev, action, is_copy, 0, 0))
|
||||
base_rev, action, is_copy,
|
||||
text_modified, props_modified))
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
|
||||
# If our caller doesn't want changed-path stuff, and we have
|
||||
# the info we need to make an authz determination already,
|
||||
# quit this loop and get on with it.
|
||||
if (not include_changed_paths) and found_unreadable and found_readable:
|
||||
break
|
||||
|
||||
# Filter unreadable information.
|
||||
if found_unreadable:
|
||||
message = None
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
revs.append([date, author, message, changes])
|
||||
|
||||
client.svn_client_log([self.rootpath], optrev, optrev,
|
||||
1, 0, _log_cb, self.ctx)
|
||||
return revs[0][0], revs[0][1], revs[0][2], revs[0][3]
|
||||
# Drop unrequested changes.
|
||||
if not include_changed_paths:
|
||||
changes = None
|
||||
|
||||
# Add this revision information to the "return" array.
|
||||
retval.append([date, author, msg, revprops, changes])
|
||||
|
||||
optrev = _rev2optrev(rev)
|
||||
client_log(self.rootpath, optrev, optrev, 1, need_changes, 0,
|
||||
_log_cb, self.ctx)
|
||||
return tuple(revs[0])
|
||||
|
||||
def _revinfo(self, rev, include_changed_paths=0):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
# Consult the revinfo cache first. If we don't have cached info,
|
||||
# or our caller wants changed paths and we don't have those for
|
||||
# this revision, go do the real work.
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info \
|
||||
or (include_changed_paths and cached_info[4] is None):
|
||||
cached_info = self._revinfo_fetch(rev, include_changed_paths)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return cached_info
|
||||
|
||||
##--- custom --##
|
||||
|
||||
@@ -495,23 +722,16 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(old_path)
|
||||
old_path = _cleanup_path(old_path)
|
||||
old_path_parts = _path_parts(old_path)
|
||||
# Check access (lying about path types)
|
||||
if not vclib.check_path_access(self, old_path_parts, vclib.FILE, old_rev):
|
||||
raise vclib.ItemNotFound(path)
|
||||
return old_path
|
||||
|
||||
def created_rev(self, path, rev):
|
||||
# NOTE: We can't use svn_client_propget here because the
|
||||
# interfaces in that layer strip out the properties not meant for
|
||||
# human consumption (such as svn:entry:committed-rev, which we are
|
||||
# using here to get the created revision of PATH@REV).
|
||||
kind = ra.svn_ra_check_path(self.ra_session, path, rev)
|
||||
if kind == core.svn_node_none:
|
||||
raise vclib.ItemNotFound(_path_parts(path))
|
||||
elif kind == core.svn_node_dir:
|
||||
props = get_directory_props(self.ra_session, path, rev)
|
||||
elif kind == core.svn_node_file:
|
||||
fetched_rev, props = ra.svn_ra_get_file(self.ra_session, path, rev, None)
|
||||
return int(props.get(core.SVN_PROP_ENTRY_COMMITTED_REV,
|
||||
SVN_INVALID_REVNUM))
|
||||
lh_rev, c_rev = self._get_last_history_rev(_path_parts(path), rev)
|
||||
return lh_rev
|
||||
|
||||
def last_rev(self, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
@@ -546,3 +766,33 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
else:
|
||||
peg_revision = mid
|
||||
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:]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -22,6 +22,7 @@ import time
|
||||
import tempfile
|
||||
import popen
|
||||
import re
|
||||
import urllib
|
||||
from svn import fs, repos, core, client, delta
|
||||
|
||||
|
||||
@@ -43,6 +44,13 @@ def _fix_subversion_exception(e):
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = e[0]
|
||||
|
||||
### Pre-1.4 Subversion doesn't have svn_path_canonicalize()
|
||||
def _canonicalize_path(path):
|
||||
try:
|
||||
return core.svn_path_canonicalize(path)
|
||||
except AttributeError:
|
||||
return path
|
||||
|
||||
def _allow_all(root, path, pool):
|
||||
"""Generic authz_read_func that permits access to all paths"""
|
||||
return 1
|
||||
@@ -108,11 +116,37 @@ def _rev2optrev(rev):
|
||||
|
||||
def _rootpath2url(rootpath, path):
|
||||
rootpath = os.path.abspath(rootpath)
|
||||
if rootpath and rootpath[0] != '/':
|
||||
rootpath = '/' + rootpath
|
||||
drive, rootpath = os.path.splitdrive(rootpath)
|
||||
if os.sep != '/':
|
||||
rootpath = string.replace(rootpath, os.sep, '/')
|
||||
return 'file://' + string.join([rootpath, path], "/")
|
||||
rootpath = urllib.quote(rootpath)
|
||||
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):
|
||||
@@ -183,59 +217,6 @@ class NodeHistory:
|
||||
def __getitem__(self, idx):
|
||||
return self.histories[idx]
|
||||
|
||||
|
||||
def _get_history(svnrepos, path, rev, path_type, limit=0, options={}):
|
||||
if svnrepos.youngest == 0:
|
||||
return []
|
||||
|
||||
rev_paths = []
|
||||
fsroot = svnrepos._getroot(rev)
|
||||
show_all_logs = options.get('svn_show_all_dir_logs', 0)
|
||||
if not show_all_logs:
|
||||
# See if the path is a file or directory.
|
||||
kind = fs.check_path(fsroot, path)
|
||||
if kind is core.svn_node_file:
|
||||
show_all_logs = 1
|
||||
|
||||
# Instantiate a NodeHistory collector object, and use it to collect
|
||||
# history items for PATH@REV.
|
||||
history = NodeHistory(svnrepos.fs_ptr, show_all_logs, limit)
|
||||
try:
|
||||
repos.svn_repos_history(svnrepos.fs_ptr, path, history.add_history,
|
||||
1, rev, options.get('svn_cross_copies', 0))
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
|
||||
raise
|
||||
|
||||
# Now, iterate over those history items, checking for changes of
|
||||
# location, pruning as necessitated by authz rules.
|
||||
for hist_rev, hist_path in history:
|
||||
path_parts = _path_parts(hist_path)
|
||||
if not vclib.check_path_access(svnrepos, path_parts, path_type, hist_rev):
|
||||
break
|
||||
rev_paths.append([hist_rev, hist_path])
|
||||
return rev_paths
|
||||
|
||||
|
||||
def _log_helper(svnrepos, path, rev, lockinfo):
|
||||
rev_root = fs.revision_root(svnrepos.fs_ptr, rev)
|
||||
|
||||
# Was this path@rev the target of a copy?
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
|
||||
|
||||
# Assemble our LogEntry
|
||||
date, author, msg, changes = svnrepos._revinfo(rev)
|
||||
if fs.is_file(rev_root, path):
|
||||
size = fs.file_length(rev_root, path)
|
||||
else:
|
||||
size = None
|
||||
entry = Revision(rev, date, author, msg, size, lockinfo, path,
|
||||
copyfrom_path and _cleanup_path(copyfrom_path),
|
||||
copyfrom_rev)
|
||||
return entry
|
||||
|
||||
|
||||
def _get_last_history_rev(fsroot, path):
|
||||
history = fs.node_history(fsroot, path)
|
||||
history = fs.history_prev(history, 0)
|
||||
@@ -314,12 +295,13 @@ class FileContentsPipe:
|
||||
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, local_url, rev, first_rev, config_dir):
|
||||
def __init__(self, local_url, rev, first_rev, include_text, config_dir):
|
||||
self.idx = -1
|
||||
self.first_rev = first_rev
|
||||
self.blame_data = []
|
||||
self.include_text = include_text
|
||||
|
||||
ctx = client.ctx_t()
|
||||
ctx = client.svn_client_create_context()
|
||||
core.svn_config_ensure(config_dir)
|
||||
ctx.config = core.svn_config_get_config(config_dir)
|
||||
ctx.auth_baton = core.svn_auth_open([])
|
||||
@@ -338,6 +320,8 @@ class BlameSource:
|
||||
prev_rev = None
|
||||
if rev > self.first_rev:
|
||||
prev_rev = rev - 1
|
||||
if not self.include_text:
|
||||
text = None
|
||||
self.blame_data.append(vclib.Annotation(text, line_no + 1, rev,
|
||||
prev_rev, author, None))
|
||||
|
||||
@@ -406,6 +390,10 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
self._fsroots = {}
|
||||
self._revinfo_cache = {}
|
||||
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
@@ -421,19 +409,14 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
def itemtype(self, path_parts, rev):
|
||||
rev = self._getrev(rev)
|
||||
basepath = self._getpath(path_parts)
|
||||
kind = fs.check_path(self._getroot(rev), basepath)
|
||||
pathtype = None
|
||||
if kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
else:
|
||||
pathtype = self._gettype(basepath, rev)
|
||||
if pathtype is None:
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
if not vclib.check_path_access(self, path_parts, pathtype, rev):
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
return pathtype
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
def openfile(self, path_parts, rev, options):
|
||||
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)
|
||||
@@ -472,7 +455,7 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
continue
|
||||
path = self._getpath(entry_path_parts)
|
||||
entry_rev = _get_last_history_rev(fsroot, path)
|
||||
date, author, msg, changes = self._revinfo(entry_rev)
|
||||
date, author, msg, revprops, changes = self._revinfo(entry_rev)
|
||||
entry.rev = str(entry_rev)
|
||||
entry.date = date
|
||||
entry.author = author
|
||||
@@ -521,20 +504,19 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
# 'limit' parameter here as numeric cut-off for the depth of our
|
||||
# history search.
|
||||
if options.get('svn_latest_log', 0):
|
||||
revision = _log_helper(self, path, rev, lockinfo)
|
||||
revision = self._log_helper(path, rev, lockinfo)
|
||||
if revision:
|
||||
revision.prev = None
|
||||
revs.append(revision)
|
||||
else:
|
||||
history = _get_history(self, path, rev, path_type,
|
||||
first + limit, options)
|
||||
history = self._get_history(path, rev, path_type, first + limit, options)
|
||||
if len(history) < first:
|
||||
history = []
|
||||
if limit:
|
||||
history = history[first:first+limit]
|
||||
|
||||
for hist_rev, hist_path in history:
|
||||
revision = _log_helper(self, hist_path, hist_rev, lockinfo)
|
||||
revision = self._log_helper(hist_path, hist_rev, lockinfo)
|
||||
if revision:
|
||||
# If we have unreadable copyfrom data, obscure it.
|
||||
if revision.copy_path is not None:
|
||||
@@ -555,43 +537,70 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
fsroot = self._getroot(rev)
|
||||
return fs.node_proplist(fsroot, path)
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
def annotate(self, path_parts, rev, include_text=False):
|
||||
path = self._getpath(path_parts)
|
||||
path_type = self.itemtype(path_parts, rev) # does auth-check
|
||||
if path_type != vclib.FILE:
|
||||
raise vclib.Error("Path '%s' is not a file." % path)
|
||||
rev = self._getrev(rev)
|
||||
fsroot = self._getroot(rev)
|
||||
history = _get_history(self, path, rev, path_type, 0,
|
||||
history = self._get_history(path, rev, path_type, 0,
|
||||
{'svn_cross_copies': 1})
|
||||
youngest_rev, youngest_path = history[0]
|
||||
oldest_rev, oldest_path = history[-1]
|
||||
source = BlameSource(_rootpath2url(self.rootpath, path),
|
||||
youngest_rev, oldest_rev, self.config_dir)
|
||||
source = BlameSource(_rootpath2url(self.rootpath, path), youngest_rev,
|
||||
oldest_rev, include_text, self.config_dir)
|
||||
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):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
def _revinfo_helper(rev, include_changed_paths):
|
||||
# Get the revision property info. (Would use
|
||||
# editor.get_root_props(), but something is broken there...)
|
||||
revprops = fs.revision_proplist(self.fs_ptr, rev)
|
||||
msg = revprops.get(core.SVN_PROP_REVISION_LOG)
|
||||
author = revprops.get(core.SVN_PROP_REVISION_AUTHOR)
|
||||
datestr = revprops.get(core.SVN_PROP_REVISION_DATE)
|
||||
date = _datestr_to_date(datestr)
|
||||
|
||||
# Optimization: If our caller doesn't care about the changed
|
||||
# paths, and we don't need them to do authz determinations, let's
|
||||
# get outta here.
|
||||
if self.auth is None and not include_changed_paths:
|
||||
return date, author, msg, None
|
||||
|
||||
# If we get here, then we either need the changed paths because we
|
||||
# were asked for them, or we need them to do authorization checks.
|
||||
# Either way, we need 'em, so let's get 'em.
|
||||
fsroot = self._getroot(rev)
|
||||
def _get_changed_paths(fsroot):
|
||||
"""Return a 3-tuple: found_readable, found_unreadable, changed_paths."""
|
||||
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
||||
e_ptr, e_baton = delta.make_editor(editor)
|
||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
@@ -644,10 +653,12 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
if vclib.check_path_access(self, parts, pathtype, rev):
|
||||
if is_copy and change.base_path and (change.base_path != path):
|
||||
parts = _path_parts(change.base_path)
|
||||
if not vclib.check_path_access(self, parts, pathtype, change.base_rev):
|
||||
if not vclib.check_path_access(self, parts, pathtype,
|
||||
change.base_rev):
|
||||
is_copy = 0
|
||||
change.base_path = None
|
||||
change.base_rev = None
|
||||
found_unreadable = 1
|
||||
changedpaths[path] = SVNChangedPath(path, rev, pathtype,
|
||||
change.base_path,
|
||||
change.base_rev, action,
|
||||
@@ -656,24 +667,116 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
return found_readable, found_unreadable, changedpaths.values()
|
||||
|
||||
# If our caller doesn't care about changed paths, we must be
|
||||
# here for authz reasons only. That means the minute we've
|
||||
# found both a readable and an unreadable path, we can bail out.
|
||||
if (not include_changed_paths) and found_readable and found_unreadable:
|
||||
return date, author, None, None
|
||||
def _get_change_copyinfo(fsroot, path, change):
|
||||
# If we know the copyfrom info, return it...
|
||||
if hasattr(change, 'copyfrom_known') and change.copyfrom_known:
|
||||
copyfrom_path = change.copyfrom_path
|
||||
copyfrom_rev = change.copyfrom_rev
|
||||
# ...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
|
||||
|
||||
# Okay, we've process all our paths. Let's filter our metadata,
|
||||
# and return the requested data.
|
||||
def _simple_auth_check(fsroot):
|
||||
"""Return a 2-tuple: found_readable, found_unreadable."""
|
||||
found_unreadable = found_readable = 0
|
||||
if hasattr(fs, 'paths_changed2'):
|
||||
changes = fs.paths_changed2(fsroot)
|
||||
else:
|
||||
changes = fs.paths_changed(fsroot)
|
||||
paths = changes.keys()
|
||||
for path in paths:
|
||||
change = changes[path]
|
||||
pathtype = None
|
||||
if hasattr(change, 'node_kind'):
|
||||
if change.node_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
elif change.node_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
parts = _path_parts(path)
|
||||
if 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:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
if include_changed_paths:
|
||||
return date, author, msg, changedpaths.values()
|
||||
else:
|
||||
return date, author, msg, None
|
||||
return date, author, msg, revprops, changedpaths
|
||||
|
||||
# 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
|
||||
@@ -681,45 +784,55 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info \
|
||||
or (include_changed_paths and cached_info[3] is None):
|
||||
or (include_changed_paths and cached_info[4] is None):
|
||||
cached_info = _revinfo_helper(rev, include_changed_paths)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
|
||||
return tuple(cached_info)
|
||||
|
||||
def revinfo(self, rev):
|
||||
return self._revinfo(rev, 1)
|
||||
def _log_helper(self, path, rev, lockinfo):
|
||||
rev_root = fs.revision_root(self.fs_ptr, rev)
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
if fs.is_file(rev_root, path):
|
||||
size = fs.file_length(rev_root, path)
|
||||
else:
|
||||
size = None
|
||||
return Revision(rev, date, author, msg, size, lockinfo, path,
|
||||
copyfrom_path and _cleanup_path(copyfrom_path),
|
||||
copyfrom_rev)
|
||||
|
||||
def 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)
|
||||
def _get_history(self, path, rev, path_type, limit=0, options={}):
|
||||
if self.youngest == 0:
|
||||
return []
|
||||
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, changes = self._revinfo(rev)
|
||||
return date
|
||||
rev_paths = []
|
||||
fsroot = self._getroot(rev)
|
||||
show_all_logs = options.get('svn_show_all_dir_logs', 0)
|
||||
if not show_all_logs:
|
||||
# See if the path is a file or directory.
|
||||
kind = fs.check_path(fsroot, path)
|
||||
if kind is core.svn_node_file:
|
||||
show_all_logs = 1
|
||||
|
||||
# Instantiate a NodeHistory collector object, and use it to collect
|
||||
# history items for PATH@REV.
|
||||
history = NodeHistory(self.fs_ptr, show_all_logs, limit)
|
||||
try:
|
||||
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)
|
||||
repos.svn_repos_history(self.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 == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.InvalidRevision
|
||||
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
|
||||
raise
|
||||
|
||||
def isexecutable(self, path_parts, rev):
|
||||
props = self.itemprops(path_parts, rev) # does authz-check
|
||||
return props.has_key(core.SVN_PROP_EXECUTABLE)
|
||||
# Now, iterate over those history items, checking for changes of
|
||||
# location, pruning as necessitated by authz rules.
|
||||
for hist_rev, hist_path in history:
|
||||
path_parts = _path_parts(hist_path)
|
||||
if not vclib.check_path_access(self, path_parts, path_type, hist_rev):
|
||||
break
|
||||
rev_paths.append([hist_rev, hist_path])
|
||||
return rev_paths
|
||||
|
||||
def _getpath(self, path_parts):
|
||||
return string.join(path_parts, '/')
|
||||
@@ -728,8 +841,11 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
if rev is None or rev == 'HEAD':
|
||||
return self.youngest
|
||||
try:
|
||||
if type(rev) == type(''):
|
||||
while rev[0] == 'r':
|
||||
rev = rev[1:]
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
except:
|
||||
raise vclib.InvalidRevision(rev)
|
||||
if (rev < 0) or (rev > self.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
@@ -742,7 +858,20 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev)
|
||||
return r
|
||||
|
||||
##--- custom --##
|
||||
def _gettype(self, path, rev):
|
||||
# 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):
|
||||
return self.youngest
|
||||
@@ -820,3 +949,32 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
return peg_revision, path
|
||||
finally:
|
||||
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:]
|
||||
|
||||
|
946
lib/viewvc.py
946
lib/viewvc.py
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -27,7 +27,9 @@ numbers, and not literal):
|
||||
3. Verify that copyright years are correct in both the license-1.html
|
||||
file and the source code.
|
||||
|
||||
4. Update and commit the 'CHANGES' file.
|
||||
4. Update and commit the 'CHANGES' file, using any available crystal
|
||||
balls or other forward-looking devices to take a stab at the
|
||||
release date.
|
||||
|
||||
5. Test, test, test! There is no automatic testsuite available. So
|
||||
just run with permuting different `viewvc.conf' settings... and
|
||||
@@ -38,50 +40,86 @@ numbers, and not literal):
|
||||
should exactly reflect what you wish to distribute and dub "the
|
||||
release".
|
||||
|
||||
7. Edit the file 'lib/viewvc.py' and remove the "-dev" suffix from
|
||||
7. Update your release branch working copy to HEAD.
|
||||
|
||||
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,
|
||||
Y, and Z are positive integers. Do NOT commit this change.
|
||||
Y, and Z are positive integers.
|
||||
|
||||
8. Update your working copy to HEAD, and tag the release:
|
||||
*** Do NOT commit this change. ***
|
||||
|
||||
svn update
|
||||
svn cp -m "Tag the X.Y.Z final release." . \
|
||||
http://viewvc.tigris.org/svn/viewvc/tags/X.Y.Z
|
||||
9. "Peg" the contributed templates externals definition to the
|
||||
current HEAD revision:
|
||||
|
||||
9. Go into an empty directory and run the 'make-release' script:
|
||||
svn pedit svn:externals .
|
||||
|
||||
(squeeze "-rBASE_REV", where BASE_REV is the current HEAD revision
|
||||
number, between 'templates-contrib' and the target URL).
|
||||
|
||||
*** Do NOT commit this change. ***
|
||||
|
||||
10. Tag the release:
|
||||
|
||||
svn cp -m "Tag the X.Y.Z final release." . ^/tags/X.Y.Z
|
||||
|
||||
This will create a copy of the release branch, plus your local
|
||||
modifications to the svn:externals property and lib/viewvc.py
|
||||
file, to the tag location.
|
||||
|
||||
11. Revert the changes in your working copy.
|
||||
|
||||
svn revert -R .
|
||||
|
||||
12. Go into an empty directory and run the 'make-release' script:
|
||||
|
||||
tools/make-release viewvc-X.Y.Z tags/X.Y.Z
|
||||
|
||||
10. Verify the archive files:
|
||||
13. Verify the archive files:
|
||||
|
||||
- do they have a LICENSE.html file?
|
||||
- do they have necessary include documentation?
|
||||
- do they *not* have unnecessary stuff?
|
||||
- do they install and work correctly?
|
||||
|
||||
11. Upload the created archive files (tar.gz and zip) into the Files
|
||||
14. Upload the created archive files (tar.gz and zip) into the Files
|
||||
and Documents section of the Tigris.org project, and modify the
|
||||
CHECKSUMS document there accordingly. Also, drop a copy of the
|
||||
archive files into the root directory of the viewvc.org website
|
||||
(unversioned).
|
||||
CHECKSUMS document there accordingly:
|
||||
|
||||
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.
|
||||
http://viewvc.tigris.org/servlets/ProjectDocumentList?folderID=6004
|
||||
|
||||
13. Edit the file 'lib/viewvc.py' again, re-adding the "-dev" suffix
|
||||
and incrementing the patch number assigned to the __version__
|
||||
variable, and add a new empty block in the branch's CHANGES file,
|
||||
and commit:
|
||||
Also, drop a copy of the archive files into the root directory of
|
||||
the viewvc.org website (unversioned).
|
||||
|
||||
15. Update the Tigris.org website (^/trunk/www/index.html) to refer to
|
||||
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."
|
||||
|
||||
14. Edit the Issue Tracker configuration options, adding a new Version
|
||||
17. Edit the Issue Tracker configuration options, adding a new Version
|
||||
for the just-released one, and a new Milestone for the next patch
|
||||
(and possibly, minor or major) release. (For the Milestone sort
|
||||
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
|
||||
|
||||
15. Write an announcement explaining all the cool new features and
|
||||
post it to the announce@ list, to the project's News area, and to
|
||||
other places interested in this sort of stuff, such as Freshmeat
|
||||
(http://www.freshmeat.net).
|
||||
http://viewvc.tigris.org/issues/editversions.cgi?component=viewvc&action=add
|
||||
http://viewvc.tigris.org/issues/editmilestones.cgi?component=viewvc&action=add
|
||||
|
||||
18. Send to the announce@ list a message explaining all the cool new
|
||||
features.
|
||||
|
||||
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.
|
||||
|
@@ -94,9 +94,6 @@ form { margin: 0; }
|
||||
.vc_properties {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.vc_properties h2 {
|
||||
font-size: 115%;
|
||||
}
|
||||
|
||||
|
||||
/*** File Content Markup Styles ***/
|
||||
@@ -272,3 +269,14 @@ table.vc_idiff tbody th {
|
||||
.vc_query_form {
|
||||
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;
|
||||
}
|
||||
|
@@ -9,7 +9,11 @@
|
||||
[# ------------------------------------------------------------------------- ]
|
||||
|
||||
[# setup page definitions]
|
||||
[is annotation "annotated"]
|
||||
[define page_title]Annotation of /[where][end]
|
||||
[else]
|
||||
[define page_title]Contents of /[where][end]
|
||||
[end]
|
||||
[define help_href][docroot]/help_rootview.html[end]
|
||||
[# end]
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>[if-any cfg.general.address]<address><a href="mailto:[cfg.general.address]">[cfg.general.address]</a></address>[else] [end]</td>
|
||||
<td style="text-align: right;"><strong><a href="[help_href]">ViewVC Help</a></strong></td>
|
||||
<td style="text-align: right;">[if-any help_href]<strong><a href="[help_href]">ViewVC Help</a></strong>[else] [end]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Powered by <a href="http://viewvc.tigris.org/">ViewVC [vsn]</a></td>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<title>[if-any rootname][[][rootname]][else]ViewVC[end] [page_title]</title>
|
||||
<meta name="generator" content="ViewVC [vsn]" />
|
||||
<link rel="shortcut icon" href="[docroot]/images/favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="[docroot]/images/favicon.ico" />
|
||||
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
|
||||
[if-any rss_href]<link rel="alternate" type="application/rss+xml" title="RSS [[][rootname]][where]" href="[rss_href]" />[end]
|
||||
</head>
|
||||
|
@@ -4,13 +4,13 @@
|
||||
<!-- ViewVC :: http://www.viewvc.org/ -->
|
||||
<head>
|
||||
<title>Checkin Database Query</title>
|
||||
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
|
||||
[if-any docroot]<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />[end]
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
[# setup page definitions]
|
||||
[define help_href][docroot]/help_query.html[end]
|
||||
[define help_href][if-any docroot][docroot]/help_query.html[end][end]
|
||||
[# end]
|
||||
|
||||
<p>
|
||||
@@ -158,7 +158,15 @@
|
||||
[is query "skipped"]
|
||||
[else]
|
||||
<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]
|
||||
<table cellspacing="0" cellpadding="2">
|
||||
<thead>
|
||||
@@ -202,21 +210,21 @@
|
||||
</td>
|
||||
|
||||
[# uncommment, if you want a separate Description column:
|
||||
{if-index commits.files first{
|
||||
{if-index commits.files first}
|
||||
<td style="vertical-align:top;" rowspan="{commits.num_files}">
|
||||
{commits.log}
|
||||
{if-any commits.log}{commits.log}{else} {end}
|
||||
</td>
|
||||
{end}
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
</tr>
|
||||
[# and also take the following out in the "Description column"-case:]
|
||||
[# and also take the following out in the "Description column" case:]
|
||||
[if-index commits.files last]
|
||||
<tr class="vc_row_[if-index commits even]even[else]odd[end]">
|
||||
<td> </td>
|
||||
<td colspan="5"><strong>Log:</strong><br />
|
||||
<pre class="vc_log">[commits.log]</pre></td>
|
||||
<pre class="vc_log">[if-any commits.log][commits.log][else] [end]</pre></td>
|
||||
</tr>
|
||||
[end]
|
||||
[# ---]
|
||||
|
@@ -7,6 +7,15 @@
|
||||
|
||||
<p><strong>[english_query]</strong></p>
|
||||
[# <!-- {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="[backout_href]">Show commands which could be used to back out these changes</a></p>
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
[include "include/header.ezt" "revision"]
|
||||
|
||||
<hr />
|
||||
|
||||
<form method="get" action="[jump_rev_action]">
|
||||
<table cellspacing="1" cellpadding="2" style="width: auto;">
|
||||
<tr align="left">
|
||||
@@ -42,7 +43,7 @@
|
||||
|
||||
<hr />
|
||||
|
||||
<p><strong>Changed paths:</strong></p>
|
||||
<h2>Changed paths</h2>
|
||||
|
||||
<table cellspacing="1" cellpadding="2">
|
||||
<thead>
|
||||
@@ -77,4 +78,5 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[include "include/props.ezt"]
|
||||
[include "include/footer.ezt"]
|
||||
|
@@ -9,6 +9,12 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="vc_header_sort">Name</th>
|
||||
[is cfg.options.show_roots_lastmod "1"]
|
||||
<th class="vc_header">Revision</th>
|
||||
<th class="vc_header">Age</th>
|
||||
<th class="vc_header">Author</th>
|
||||
<th class="vc_header">Log</th>
|
||||
[end]
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -20,6 +26,12 @@
|
||||
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
|
||||
[roots.name]</a>
|
||||
</td>
|
||||
[is cfg.options.show_roots_lastmod "1"]
|
||||
<td style="width:20"> [if-any roots.log_href]<a href="[roots.log_href]">[roots.rev]</a>[else][roots.rev][end]</td>
|
||||
<td style="width:20"> [roots.ago]</td>
|
||||
<td style="width:20"> [roots.author]</td>
|
||||
<td style="width:20"> [roots.short_log]</td>
|
||||
[end]
|
||||
</tr>
|
||||
[end]
|
||||
</tbody>
|
||||
|
@@ -7,11 +7,11 @@
|
||||
<description>[is roottype "svn"]Subversion[else]CVS[end] commits to the[if-any where] [where] directory of the[end] [rootname] repository</description>
|
||||
|
||||
[for commits]<item>
|
||||
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [commits.short_log]</title>
|
||||
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [format "xml"][commits.short_log][end]</title>
|
||||
[if-any commits.rss_url]<link>[commits.rss_url]</link>[end]
|
||||
<author>[commits.author]</author>
|
||||
<pubDate>[if-any commits.rss_date][commits.rss_date][else](unknown date)[end]</pubDate>
|
||||
<description><pre>[format "xml"][commits.log][end]</pre></description>
|
||||
<description><pre>[format "xml"][format "html"][commits.log][end][end]</pre></description>
|
||||
</item>[end]
|
||||
</channel>
|
||||
</rss>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# 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
|
||||
@@ -49,6 +49,10 @@ CLEAN_MODE = None
|
||||
FILE_INFO_LIST = [
|
||||
("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0),
|
||||
("bin/cgi/query.cgi", "bin/cgi/query.cgi", 0755, 1, 0, 0),
|
||||
("bin/wsgi/viewvc.wsgi", "bin/wsgi/viewvc.wsgi", 0755, 1, 0, 0),
|
||||
("bin/wsgi/viewvc.fcgi", "bin/wsgi/viewvc.fcgi", 0755, 1, 0, 0),
|
||||
("bin/wsgi/query.wsgi", "bin/wsgi/query.wsgi", 0755, 1, 0, 0),
|
||||
("bin/wsgi/query.fcgi", "bin/wsgi/query.fcgi", 0755, 1, 0, 0),
|
||||
("bin/mod_python/viewvc.py", "bin/mod_python/viewvc.py", 0755, 1, 0, 0),
|
||||
("bin/mod_python/query.py", "bin/mod_python/query.py", 0755, 1, 0, 0),
|
||||
("bin/mod_python/handler.py", "bin/mod_python/handler.py", 0755, 1, 0, 0),
|
||||
@@ -205,6 +209,8 @@ def install_file(src_path, dst_path, mode, subst_path_vars,
|
||||
temp = raw_input("Do you want to [O]verwrite, [D]o "
|
||||
"not overwrite, or [V]iew "
|
||||
"differences? ")
|
||||
if len(temp) == 0:
|
||||
continue
|
||||
temp = string.lower(temp[0])
|
||||
if temp == "v" and ext not in BINARY_FILE_EXTS:
|
||||
print """
|
||||
|
Reference in New Issue
Block a user