mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
253 Commits
custis-r22
...
1.1.17
Author | SHA1 | Date | |
---|---|---|---|
![]() |
739c51dd7c | ||
![]() |
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 | ||
![]() |
62d5a3b649 | ||
![]() |
c96c585b39 | ||
![]() |
136063e5a7 | ||
![]() |
f59db1597d | ||
![]() |
c6c3f55c2c | ||
![]() |
8063d46a53 | ||
![]() |
fbc7737465 | ||
![]() |
23f3f3ad55 | ||
![]() |
0efc53a373 | ||
![]() |
0cfe027753 | ||
![]() |
df3b75cdb0 | ||
![]() |
24dd18bf0f | ||
![]() |
bbf27f4afd | ||
![]() |
d4ac97de3c | ||
![]() |
3039b1f43b | ||
![]() |
13597f89cd | ||
![]() |
870ae9aecc | ||
![]() |
c94b0709ec | ||
![]() |
5632e63bcb | ||
![]() |
ae7b1103f6 | ||
![]() |
2882178be7 | ||
![]() |
bfc59256ca | ||
![]() |
3abe695c3c | ||
![]() |
624dfdf0f8 | ||
![]() |
28bf22f279 | ||
![]() |
830a48f88c | ||
![]() |
a23543d763 | ||
![]() |
586a183d5c | ||
![]() |
7ae02a5887 | ||
![]() |
4020fd2e5f | ||
![]() |
af871d59ce | ||
![]() |
b8d36c8c14 | ||
![]() |
4b5721a3d7 | ||
![]() |
10235b8fdf | ||
![]() |
45332f577a | ||
![]() |
148fd7b462 | ||
![]() |
6f66313b51 | ||
![]() |
cf32af4816 | ||
![]() |
df3addacdb | ||
![]() |
a1428600b7 | ||
![]() |
4a4a3b1d61 | ||
![]() |
8c65f1b2c5 | ||
![]() |
55046f2261 | ||
![]() |
b6cedd7c1a | ||
![]() |
771d6736fb | ||
![]() |
455157e413 | ||
![]() |
c875582cfe | ||
![]() |
c3d896d5a8 | ||
![]() |
236069b068 | ||
![]() |
6de96c4fc6 | ||
![]() |
e45c2fcf6e | ||
![]() |
8c620c8c1a | ||
![]() |
c6d6dc4bdf | ||
![]() |
355fac5da1 | ||
![]() |
77d0c3dd06 | ||
![]() |
d95cb540f5 | ||
![]() |
98b757de23 | ||
![]() |
efb811d20c | ||
![]() |
a57e6b7054 | ||
![]() |
fe52bbb079 | ||
![]() |
76d6b541c3 | ||
![]() |
269a9ca864 | ||
![]() |
a5aafe3172 | ||
![]() |
d30bd89c42 | ||
![]() |
d76ce85625 | ||
![]() |
b2b247f417 | ||
![]() |
3ab2ec665b | ||
![]() |
2806f0e9a2 | ||
![]() |
8b426f6fb3 | ||
![]() |
19ee8b32df | ||
![]() |
a5389019fa | ||
![]() |
7f272fbe04 | ||
![]() |
89edf0ac04 | ||
![]() |
71fbec8871 | ||
![]() |
504b1e1a8a | ||
![]() |
cf9381e0ee | ||
![]() |
3907172131 | ||
![]() |
c34752ce92 | ||
![]() |
40cc23f12a | ||
![]() |
9b7fea99d4 | ||
![]() |
85949a6464 | ||
![]() |
d5291a6b98 | ||
![]() |
9e41d171c9 | ||
![]() |
da54e91dea | ||
![]() |
41b2ae2c7a | ||
![]() |
1815220abe | ||
![]() |
a080d8eef4 | ||
![]() |
8c4af42d56 | ||
![]() |
7f1d0952fc | ||
![]() |
222aa4ec1f | ||
![]() |
c03bfd4b89 | ||
![]() |
6fa8cdf117 | ||
![]() |
c4c777ffe6 | ||
![]() |
3680903ed4 | ||
![]() |
6fe61a11bb | ||
![]() |
437975652f | ||
![]() |
7ff75bec7b | ||
![]() |
ea03f20f46 | ||
![]() |
a5df515d79 | ||
![]() |
fabe8e8a66 | ||
![]() |
f01fcc4ec4 | ||
![]() |
05e99f4c7b | ||
![]() |
d8aff4e58f | ||
![]() |
e8d3d6ad89 | ||
![]() |
1317088e3c | ||
![]() |
fe81ee5969 |
169
CHANGES
169
CHANGES
@@ -1,4 +1,148 @@
|
||||
Version 1.1.0 (released ??-???-????)
|
||||
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 review log markup rules (issue #429)
|
||||
|
||||
Version 1.1.13 (released 23-Jan-2012)
|
||||
|
||||
* fix svndbadmin failure on deleted paths under Subversion 1.7 (issue #499)
|
||||
* fix annotation of files in svn roots with non-URI-safe paths
|
||||
* fix stray annotation warning in markup display of images
|
||||
* more gracefully handle attempts to display binary content (issue #501)
|
||||
|
||||
Version 1.1.12 (released 03-Nov-2011)
|
||||
|
||||
* fix path display in patch and certain diff views (issue #485)
|
||||
* fix broken cvsdb glob searching (issue 486)
|
||||
* allow svn revision specifiers to have leading r's (issue #441, #448)
|
||||
* allow environmental override of configuration location (issue #494)
|
||||
* fix exception HTML-escaping non-string data under WSGI (issue #454)
|
||||
* add links to root logs from roots view (issue #470)
|
||||
* use Pygments lexer-guessing functionality (issue #495)
|
||||
|
||||
Version 1.1.11 (released 17-May-2011)
|
||||
|
||||
* security fix: remove user-reachable override of cvsdb row limit
|
||||
* fix broken standalone.py -c and -d options handling
|
||||
* add --help option to standalone.py
|
||||
* fix stack trace when asked to checkout a directory (issue #478)
|
||||
* improve memory usage and speed of revision log markup (issue #477)
|
||||
* fix broken annotation view in CVS keyword-bearing files (issue #479)
|
||||
* warn users when query results are incomplete (issue #433)
|
||||
* avoid parsing errors on RCS newphrases in the admin section (issue #483)
|
||||
* make rlog parsing code more robust in certain error cases (issue #444)
|
||||
|
||||
Version 1.1.10 (released 15-Mar-2011)
|
||||
|
||||
* fix stack trace in Subversion revision info logic (issue #475, issue #476)
|
||||
|
||||
Version 1.1.9 (released 18-Feb-2011)
|
||||
|
||||
* vcauth universal access determinations (issue #425)
|
||||
* rework svn revision info cache for performance
|
||||
* make revision log "extra pages" count configurable
|
||||
* fix Subversion 1.4.x revision log compatibility code regression
|
||||
* display sanitized error when authzfile is malformed
|
||||
* restore markup of URLs in file contents (issue #455)
|
||||
* optionally display last-committed metadata in roots view (issue #457)
|
||||
|
||||
Version 1.1.8 (released 02-Dec-2010)
|
||||
|
||||
* fix slowness triggered by allow_compress=1 configuration (issue #467)
|
||||
* allow use of 'fcrypt' for Windows standalone.py authn support (issue #471)
|
||||
* yield more useful error on directory markup/annotate request (issue #472)
|
||||
|
||||
Version 1.1.7 (released 09-Sep-2010)
|
||||
|
||||
* display Subversion revision properties in the revision view (issue #453)
|
||||
* fix exception in 'standalone.py -r REPOS' when run without a config file
|
||||
* fix standalone.py server root deployments (--script-alias='')
|
||||
* add Basic authentication support to standalone.py (Unix only) (issue #49)
|
||||
* fix obscure "unexpected NULL parent pool" Subversion bindings error
|
||||
* enable path info / link display in remote Subversion root revision view
|
||||
* fix vhost name case handling inconsistency (issue #466)
|
||||
* use svn:mime-type property charset param as encoding hint
|
||||
* markup Subversion revision references in log messages (issue #313)
|
||||
* add rudimentary support for FastCGI-based deployments (issue #464)
|
||||
* fix query script WSGI deployment
|
||||
* add configuration to fix query script cross-linking to ViewVC
|
||||
|
||||
Version 1.1.6 (released 02-Jun-2010)
|
||||
|
||||
* add rudimentary support for WSGI-based deployments (issue #397)
|
||||
* fix exception caused by trying to HTML-escape non-string data (issue #454)
|
||||
* fix incorrect RSS feed Content-Type header (issue #449)
|
||||
* fix RSS <title> encoding problem (issue #451)
|
||||
* allow 'svndbadmin purge' to work on missing repositories (issue #452)
|
||||
|
||||
Version 1.1.5 (released 29-Mar-2010)
|
||||
|
||||
* security fix: escape user-provided search_re input to avoid XSS attack
|
||||
|
||||
Version 1.1.4 (released 10-Mar-2010)
|
||||
|
||||
* security fix: escape user-provided query form input to avoid XSS attack
|
||||
* fix standalone.py failure (when per-root options aren't used) (issue #445)
|
||||
* fix annotate failure caused by ignored svn_config_dir (issue #447)
|
||||
|
||||
Version 1.1.3 (released 22-Dec-2009)
|
||||
|
||||
* security fix: add root listing support of per-root authz config
|
||||
* security fix: query.py requires 'forbidden' authorizer (or none) in config
|
||||
* fix URL-ification of truncated log messages (issue #3)
|
||||
* fix regexp input validation (issue #426, #427, #440)
|
||||
* add support for configurable tab-to-spaces conversion
|
||||
* fix not-a-sequence error in diff view
|
||||
* allow viewvc-install to work when templates-contrib is absent
|
||||
* minor template improvements/corrections
|
||||
* expose revision metadata in diff view (issue #431)
|
||||
* markup file/directory item property URLs and email addresses (issue #434)
|
||||
* make ViewVC cross copies in Subversion history by default
|
||||
* fix bug that caused standalone.py failure under Python 1.5.2 (issue #442)
|
||||
* fix support for per-vhost overrides of authorizer parameters (issue #411)
|
||||
* fix root name identification in query.py interface
|
||||
|
||||
Version 1.1.2 (released 11-Aug-2009)
|
||||
|
||||
* security fix: validate the 'view' parameter to avoid XSS attack
|
||||
* security fix: avoid printing illegal parameter names and values
|
||||
* add optional support for character encoding detection (issue #400)
|
||||
* fix username case handling in svnauthz module (issue #419)
|
||||
* fix cvsdbadmin/svnadmin rebuild error on missing repos (issue #420)
|
||||
* don't drop leading blank lines from colorized file contents (issue #422)
|
||||
* add file.ezt template logic for optionally hiding binary file contents
|
||||
|
||||
Version 1.1.1 (released 03-Jun-2009)
|
||||
|
||||
* fix broken query form (missing required template variables) (issue #416)
|
||||
* fix bug in cvsdb which caused rebuild operations to lose data (issue #417)
|
||||
* fix cvsdb purge/rebuild repos lookup to error on missing repos
|
||||
* fix misleading file contents view page title
|
||||
|
||||
Version 1.1.0 (released 13-May-2009)
|
||||
|
||||
* add support for full content diffs (issue #153)
|
||||
* make many more data dictionary items available to all views
|
||||
@@ -12,9 +156,7 @@ Version 1.1.0 (released ??-???-????)
|
||||
* add support for query by log message (issues #22, #121)
|
||||
* fix bug parsing 'svn blame' output with too-long author names (issue #221)
|
||||
* fix default standalone.py port to be within private IANA range (issue #234)
|
||||
* add support for integration with GNU source-highlight (issue #285)
|
||||
* add unified configury of allowed views
|
||||
* add support for disabling the checkout view (now the default state)
|
||||
* add unified configury of allowed views; checkout view disabled by default
|
||||
* add support for ranges of revisions to svndbadmin (issue #224)
|
||||
* make the query handling more forgiving of malformatted subdirs (issue #244)
|
||||
* add support for per-root configuration overrides (issue #371)
|
||||
@@ -36,7 +178,24 @@ Version 1.1.0 (released ??-???-????)
|
||||
* show RSS/query links only for roots found in commits database (issue #357)
|
||||
* recognize Subversion svn:mime-type property values (issue #364)
|
||||
* hide CVS files when viewing tags/branches on which they don't exist
|
||||
* add support for hiding errorful entries from the directory view (issue #105)
|
||||
* allow hiding of errorful entries from the directory view (issue #105)
|
||||
* fix directory view sorting UI
|
||||
* tolerate malformed Accept-Language headers (issue #396)
|
||||
* allow MIME type mapping overrides in ViewVC configuration (issue #401)
|
||||
* fix exception in rev-sorted remote Subversion directory views (issue #409)
|
||||
* allow setting of page sizes for log and dir views individually (issue #402)
|
||||
|
||||
Version 1.0.9 (released 11-Aug-2009)
|
||||
|
||||
* security fix: validate the 'view' parameter to avoid XSS attack
|
||||
* security fix: avoid printing illegal parameter names and values
|
||||
|
||||
Version 1.0.8 (released 05-May-2009)
|
||||
|
||||
* fix directory view sorting UI
|
||||
* tolerate malformed Accept-Language headers (issue #396)
|
||||
* fix directory log views in revision-less Subversion repositories
|
||||
* fix exception in rev-sorted remote Subversion directory views (issue #409)
|
||||
|
||||
Version 1.0.7 (released 14-Oct-2008)
|
||||
|
||||
|
160
INSTALL
160
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:
|
||||
|
||||
@@ -139,7 +139,7 @@ installation instructions.
|
||||
default_root
|
||||
root_as_url_component
|
||||
rcs_dir
|
||||
mime_types_file
|
||||
mime_types_files
|
||||
|
||||
There are some other options that are usually nice to change. See
|
||||
viewvc.conf for more information. ViewVC provides a working,
|
||||
@@ -168,72 +168,145 @@ checkin database working are below.
|
||||
APACHE CONFIGURATION
|
||||
--------------------
|
||||
|
||||
1) Find out where the web server configuration file is kept. Typical
|
||||
locations are /etc/httpd/httpd.conf, /etc/httpd/conf/httpd.conf,
|
||||
and /etc/apache/httpd.conf. Depending on how apache was installed,
|
||||
you may also look under /usr/local/etc or /etc/local. Use the vendor
|
||||
documentation or the find utility if in doubt.
|
||||
1) Locate your Apache configuration file(s).
|
||||
|
||||
Either METHOD A:
|
||||
2) The ScriptAlias directive is very useful for pointing
|
||||
Typical locations are /etc/httpd/httpd.conf,
|
||||
/etc/httpd/conf/httpd.conf, and /etc/apache/httpd.conf. Depending
|
||||
on how Apache was installed, you may also look under /usr/local/etc
|
||||
or /etc/local. Use the vendor documentation or the find utility if
|
||||
in doubt.
|
||||
|
||||
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:
|
||||
|
||||
-----------------------------------
|
||||
METHOD A: CGI mode via ScriptAlias
|
||||
-----------------------------------
|
||||
The ScriptAlias directive is very useful for pointing
|
||||
directly to the viewvc.cgi script. Simply insert a line containing
|
||||
|
||||
ScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/viewvc.cgi
|
||||
ScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/viewvc.cgi
|
||||
|
||||
into your httpd.conf file. Choose the location in httpd.conf where
|
||||
also the other ScriptAlias lines reside. Some examples:
|
||||
|
||||
ScriptAlias /viewvc /usr/local/viewvc-1.0/bin/cgi/viewvc.cgi
|
||||
ScriptAlias /query /usr/local/viewvc-1.0/bin/cgi/query.cgi
|
||||
ScriptAlias /viewvc /usr/local/viewvc-1.0/bin/cgi/viewvc.cgi
|
||||
ScriptAlias /query /usr/local/viewvc-1.0/bin/cgi/query.cgi
|
||||
|
||||
continue with step 3).
|
||||
|
||||
or alternatively METHOD B:
|
||||
2) Copy the CGI scripts from
|
||||
----------------------------------------
|
||||
METHOD B: CGI mode in cgi-bin directory
|
||||
----------------------------------------
|
||||
Copy the CGI scripts from
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
|
||||
to the /cgi-bin/ directory configured in your httpd.conf file.
|
||||
|
||||
continue with step 3).
|
||||
You can override configuration file location using:
|
||||
|
||||
SetEnv VIEWVC_CONF_PATHNAME /etc/viewvc.conf
|
||||
|
||||
and then there's METHOD C:
|
||||
2) Copy the CGI scripts from
|
||||
------------------------------------------
|
||||
METHOD C: CGI mode in ExecCGI'd directory
|
||||
------------------------------------------
|
||||
Copy the CGI scripts from
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
|
||||
to the directory of your choosing in the Document Root adding the following
|
||||
apache directives for the directory in httpd.conf or an .htaccess file:
|
||||
Apache directives for the directory in httpd.conf or an .htaccess file:
|
||||
|
||||
Options +ExecCGI
|
||||
AddHandler cgi-script .cgi
|
||||
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.
|
||||
|
||||
continue with step 3).
|
||||
|
||||
or if you've got Mod_Python installed you can use METHOD D:
|
||||
2) Copy the Python scripts and .htaccess file from
|
||||
------------------------------------------
|
||||
METHOD D: Using mod_python (if installed)
|
||||
------------------------------------------
|
||||
Copy the Python scripts and .htaccess file from
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/mod_python/
|
||||
to a directory being served by apache.
|
||||
to a directory being served by Apache.
|
||||
|
||||
In httpd.conf, make sure that "AllowOverride All" or at least
|
||||
"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.
|
||||
|
||||
continue with step 3).
|
||||
----------------------------------------
|
||||
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:
|
||||
|
||||
3) 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"
|
||||
WSGIScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/viewvc.wsgi
|
||||
WSGIScriptAlias /query <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/query.wsgi
|
||||
|
||||
4) Optional: Add access control.
|
||||
You'll probably also need the following directive because of the
|
||||
not-quite-sanctioned way that ViewVC manipulates Python objects.
|
||||
|
||||
In your httpd.conf you can control access to certain modules by adding
|
||||
directives like this:
|
||||
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.
|
||||
|
||||
In your httpd.conf you can control access to certain modules by
|
||||
adding directives like this:
|
||||
|
||||
<Location "<url to viewvc.cgi>/<modname_you_wish_to_access_ctl>">
|
||||
AllowOverride None
|
||||
@@ -251,7 +324,14 @@ or if you've got Mod_Python installed you can use METHOD D:
|
||||
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-2008 The ViewCVS Group. All rights
|
||||
<p><strong>Copyright © 1999-2012 The ViewCVS Group. All rights
|
||||
reserved.</strong></p>
|
||||
|
||||
<p>By using ViewVC, you agree to the terms and conditions set forth
|
||||
@@ -59,6 +59,10 @@
|
||||
<li>March 17, 2006 — software renamed from "ViewCVS"</li>
|
||||
<li>April 10, 2007 — copyright years updated</li>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
1
README
1
README
@@ -4,4 +4,3 @@ Please read the file INSTALL for more information.
|
||||
|
||||
And see windows/README for more information on running ViewVC on
|
||||
Microsoft Windows.
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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-2012 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-2012 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-2012 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,216 +0,0 @@
|
||||
#!/bin/sh
|
||||
# ViewVC installation script for CustIS
|
||||
|
||||
if [ -f custis-install-config ]; then
|
||||
. custis-install-config
|
||||
else
|
||||
cat >custis-install-config <<EOF
|
||||
CVS_ASYNC=
|
||||
SVN_ASYNC=
|
||||
CVS_USER=www-data
|
||||
CVS_GROUP=cvs.users
|
||||
CVSROOTS=
|
||||
SVNROOT=
|
||||
SVN_FORBIDDENRE=
|
||||
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_SOCKET=/var/run/mysqld/mysqld.sock
|
||||
DB=
|
||||
DB_USER=
|
||||
DB_PASSWD=
|
||||
|
||||
VIEWVC_DIR=
|
||||
VIEWVC_URI=/
|
||||
VIEWVC_URI_SLASH=/
|
||||
VIEWVC_STATIC_URI=/static
|
||||
http_proxy=
|
||||
no_proxy=
|
||||
EOF
|
||||
echo Empty 'custis-install-config' initialized, please edit it before installation.
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! "$DB" -o ! "$VIEWVC_DIR" ]; then
|
||||
echo Please set up 'custis-install-config' before installation.
|
||||
exit
|
||||
fi
|
||||
|
||||
################################################################################
|
||||
|
||||
export http_proxy
|
||||
export no_proxy
|
||||
|
||||
DEPS="python libapache2-mod-python rcs diff cvsnt subversion subversion-tools python-setuptools python-subversion python-mysqldb cvsgraph"
|
||||
|
||||
echo "*** Installing dependency packages: $DEPS"
|
||||
apt-get install $DEPS
|
||||
|
||||
echo "*** Installing Pygments for Python"
|
||||
wget http://pypi.python.org/packages/source/P/Pygments/Pygments-0.11.1.tar.gz
|
||||
tar -zxf Pygments-0.11.1.tar.gz
|
||||
cd Pygments-0.11.1
|
||||
python setup.py install
|
||||
cd ..
|
||||
|
||||
echo "*** Installing ViewVC into $VIEWVC_DIR"
|
||||
if [ ! -e $VIEWVC_DIR/viewvc.conf ]; then
|
||||
../viewvc-install <<EOF
|
||||
$VIEWVC_DIR
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "*** Writing ViewVC configuration into $VIEWVC_DIR/viewvc.conf"
|
||||
for CVSROOT in $CVSROOTS; do
|
||||
if [ "$vccvsroots" ]; then
|
||||
vccvsroots="$vccvsroots, "
|
||||
fi
|
||||
vcrootname=`basename $CVSROOT`
|
||||
vccvsroots="$vccvsroots$vcrootname: $CVSROOT"
|
||||
done;
|
||||
cat >"$VIEWVC_DIR/viewvc.conf" <<EOF
|
||||
[general]
|
||||
cvs_roots = $vccvsroots
|
||||
root_parents = $SVNROOT : svn
|
||||
cvsnt_exe_path = /usr/bin/cvsnt
|
||||
mime_types_file = /etc/mime.types
|
||||
address = Admin address: stas [gav-gav] custis [ru]
|
||||
kv_files =
|
||||
languages = en-us, ru-ru
|
||||
|
||||
[utilities]
|
||||
rcs_dir = /usr/bin
|
||||
cvsnt = /usr/bin/cvsnt
|
||||
svn = /usr/bin/svn
|
||||
diff = /usr/bin/diff
|
||||
cvsgraph = /usr/bin/cvsgraph
|
||||
|
||||
[options]
|
||||
allowed_views = markup, annotate, roots
|
||||
authorizer = forbiddenre
|
||||
checkout_magic = 0
|
||||
cross_copies = 1
|
||||
cvsgraph_conf = $VIEWVC_DIR/cvsgraph.conf
|
||||
default_file_view = log
|
||||
diff_format = h
|
||||
docroot = $VIEWVC_STATIC_URI
|
||||
enable_syntax_coloration = 1
|
||||
generate_etags = 1
|
||||
hide_attic = 1
|
||||
hide_cvsroot = 1
|
||||
hide_errorful_entries = 0
|
||||
hr_breakable = 1
|
||||
hr_funout = 0
|
||||
hr_ignore_keyword_subst = 1
|
||||
hr_ignore_white = 1
|
||||
hr_intraline = 0
|
||||
http_expiration_time = 600
|
||||
limit_changes = 100
|
||||
log_sort = date
|
||||
mangle_email_addresses = 1
|
||||
root_as_url_component = 1
|
||||
short_log_len = 80
|
||||
show_log_in_markup = 1
|
||||
show_logs = 1
|
||||
show_subdir_lastmod = 0
|
||||
sort_by = file
|
||||
sort_group_dirs = 1
|
||||
svn_config_dir =
|
||||
template_dir = templates
|
||||
use_cvsgraph = 1
|
||||
use_localtime = 1
|
||||
use_pagesize = 0
|
||||
use_rcsparse = 0
|
||||
use_re_search = 0
|
||||
|
||||
[templates]
|
||||
|
||||
[cvsdb]
|
||||
enabled = 1
|
||||
host = $DB_HOST
|
||||
socket = $DB_SOCKET
|
||||
database_name = $DB
|
||||
user = $DB_USER
|
||||
passwd = $DB_PASSWD
|
||||
readonly_user = $DB_USER
|
||||
readonly_passwd = $DB_PASSWD
|
||||
|
||||
[vhosts]
|
||||
|
||||
[authz-forbidden]
|
||||
forbidden =
|
||||
|
||||
[authz-forbiddenre]
|
||||
forbiddenre = $SVN_FORBIDDENRE
|
||||
|
||||
[authz-svnauthz]
|
||||
authzfile =
|
||||
EOF
|
||||
|
||||
echo "*** Initializing database: $DB using $DB_USER@$DB_HOST"
|
||||
$VIEWVC_DIR/bin/make-database <<EOF
|
||||
$DB_HOST
|
||||
$DB_USER
|
||||
$DB_PASSWD
|
||||
$DB
|
||||
EOF
|
||||
|
||||
echo "*** Configuring Apache"
|
||||
cat >/etc/apache2/sites-available/viewvc <<EOF
|
||||
<VirtualHost *:80>
|
||||
ServerName viewvc.office.custis.ru
|
||||
ServerAlias viewvc
|
||||
ServerAdmin sysadmins@custis.ru
|
||||
ServerSignature Off
|
||||
ErrorLog /var/log/apache2/viewvc-error.log
|
||||
TransferLog /var/log/apache2/viewvc-access.log
|
||||
LogLevel warn
|
||||
# ViewVC at $VIEWVC_URI installed at $VIEWVC_DIR
|
||||
DocumentRoot $VIEWVC_DIR/bin/mod_python
|
||||
Alias $VIEWVC_STATIC_URI $VIEWVC_DIR/templates/docroot
|
||||
Alias $VIEWVC_URI $VIEWVC_DIR/bin/mod_python/
|
||||
<Location ~ ^${VIEWVC_URI_SLASH}*(\?.*)?$>
|
||||
Options -Indexes
|
||||
RewriteEngine On
|
||||
RewriteRule .* ${VIEWVC_URI_SLASH}viewvc.py%{REQUEST_URI} [R,L]
|
||||
</Location>
|
||||
<Directory $VIEWVC_DIR/bin/mod_python>
|
||||
Options +ExecCGI
|
||||
AddHandler python-program .py
|
||||
PythonHandler handler
|
||||
PythonDebug Off
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
EOF
|
||||
a2enmod python
|
||||
a2enmod mod_python
|
||||
a2enmod rewrite
|
||||
a2ensite viewvc
|
||||
|
||||
echo "*** Restarting Apache"
|
||||
apache2ctl stop
|
||||
sleep 1
|
||||
apache2ctl start
|
||||
|
||||
echo "*** Building commit database for CVS"
|
||||
for CVSROOT in $CVSROOTS; do
|
||||
$VIEWVC_DIR/bin/cvsdbadmin rebuild $CVSROOT
|
||||
done;
|
||||
|
||||
echo "*** Building commit database for Subversion repositories"
|
||||
for i in `ls $SVNROOT`; do
|
||||
if [ -d "$SVNROOT/$i" ]; then
|
||||
$VIEWVC_DIR/bin/svndbadmin -v rebuild "$SVNROOT/$i"
|
||||
fi
|
||||
done;
|
||||
|
||||
# setup hooks for CVS
|
||||
./setup-cvs-hooks "$CVSROOTS" "$VIEWVC_DIR" "$CVS_USER" "$CVS_GROUP" "$CVS_ASYNC"
|
||||
|
||||
# setup hooks for Subversion
|
||||
./setup-svn-hooks "$SVNROOT" "$VIEWVC_DIR" "$SVN_ASYNC"
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -44,23 +44,17 @@ import string
|
||||
import cvsdb
|
||||
import viewvc
|
||||
import vclib.ccvs
|
||||
from stat import *
|
||||
|
||||
|
||||
def UpdateFile(db, repository, path, update, latest_checkin, quiet_level):
|
||||
|
||||
def UpdateFile(db, repository, path, update, quiet_level):
|
||||
try:
|
||||
if update:
|
||||
mtime = os.stat(repository.rcsfile(path, 1))[ST_MTIME]
|
||||
if mtime < latest_checkin:
|
||||
return
|
||||
commit_list = cvsdb.GetUnrecordedCommitList(repository, path, db)
|
||||
else:
|
||||
commit_list = cvsdb.GetCommitListFromRCSFile(repository, path)
|
||||
except cvsdb.error, e:
|
||||
print '[ERROR] %s' % (e)
|
||||
return
|
||||
except vclib.ItemNotFound, e:
|
||||
return
|
||||
|
||||
file = string.join(path, "/")
|
||||
printing = 0
|
||||
@@ -83,8 +77,7 @@ def UpdateFile(db, repository, path, update, latest_checkin, quiet_level):
|
||||
print
|
||||
|
||||
|
||||
def RecurseUpdate(db, repository, directory, update, latest_checkin,
|
||||
quiet_level):
|
||||
def RecurseUpdate(db, repository, directory, update, quiet_level):
|
||||
for entry in repository.listdir(directory, None, {}):
|
||||
path = directory + [entry.name]
|
||||
|
||||
@@ -92,13 +85,11 @@ def RecurseUpdate(db, repository, directory, update, latest_checkin,
|
||||
continue
|
||||
|
||||
if entry.kind is vclib.DIR:
|
||||
RecurseUpdate(db, repository, path, update, latest_checkin,
|
||||
quiet_level)
|
||||
RecurseUpdate(db, repository, path, update, quiet_level)
|
||||
continue
|
||||
|
||||
if entry.kind is vclib.FILE:
|
||||
UpdateFile(db, repository, path, update, latest_checkin,
|
||||
quiet_level)
|
||||
UpdateFile(db, repository, path, update, quiet_level)
|
||||
|
||||
def RootPath(path, quiet_level):
|
||||
"""Break os path into cvs root path and other parts"""
|
||||
@@ -196,11 +187,8 @@ if __name__ == '__main__':
|
||||
if command in ('rebuild', 'update'):
|
||||
repository = vclib.ccvs.CVSRepository(None, rootpath, None,
|
||||
cfg.utilities, 0)
|
||||
latest_checkin = db.GetLatestCheckinTime(repository)
|
||||
if latest_checkin is None:
|
||||
command = 'rebuild'
|
||||
RecurseUpdate(db, repository, path_parts,
|
||||
command == 'update', latest_checkin, quiet_level)
|
||||
command == 'update', quiet_level)
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
print '** break **'
|
||||
|
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
my ($type, $path, $branch, $user, $perm);
|
||||
|
||||
my $R = ''; # -R for recursive
|
||||
my $lvl = {
|
||||
r => 1,
|
||||
t => 2,
|
||||
w => 3,
|
||||
c => 4,
|
||||
a => 4,
|
||||
p => 5,
|
||||
};
|
||||
|
||||
while(<>)
|
||||
{
|
||||
chomp;
|
||||
next if /^\s*#/so;
|
||||
($type, $path, $branch, $user, $perm) = split /:/, $_;
|
||||
($user, $perm) = split /!/, $user;
|
||||
next unless $perm;
|
||||
$perm = [ sort { $lvl->{$b} cmp $lvl->{$a} } split //, $perm ];
|
||||
$perm = $perm->[0];
|
||||
if ($perm eq 't')
|
||||
{
|
||||
$perm = 'read,tag,nowrite,nocreate,nocontrol';
|
||||
}
|
||||
elsif ($perm eq 'r')
|
||||
{
|
||||
$perm = 'read,notag,nowrite,nocreate,nocontrol';
|
||||
}
|
||||
elsif ($perm eq 'w')
|
||||
{
|
||||
$perm = 'read,tag,write,nocreate,nocontrol';
|
||||
}
|
||||
elsif ($perm eq 'c' || $perm eq 'a')
|
||||
{
|
||||
$perm = 'read,tag,write,create,nocontrol';
|
||||
}
|
||||
elsif ($perm eq 'p')
|
||||
{
|
||||
$perm = 'read,tag,write,create,control';
|
||||
}
|
||||
print "cvs rchacl$R -a $perm";
|
||||
print " -u '$user'" if $user ne 'ALL';
|
||||
print " -r '$branch'" if $branch ne 'ALL';
|
||||
if ($path ne 'ALL')
|
||||
{
|
||||
print " '$path'";
|
||||
}
|
||||
else
|
||||
{
|
||||
print ' `ls $CVSROOT | grep -v '."'#cvs'`";
|
||||
}
|
||||
print "\n";
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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-2012 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
|
||||
@@ -62,8 +62,7 @@ CREATE TABLE checkins (
|
||||
KEY repositoryid_2 (repositoryid),
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid),
|
||||
KEY descid (descid)
|
||||
KEY branchid (branchid)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS descs;
|
||||
@@ -72,8 +71,7 @@ CREATE TABLE descs (
|
||||
description text,
|
||||
hash bigint(20) DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY hash (hash),
|
||||
FULLTEXT KEY description (description)
|
||||
KEY hash (hash)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS dirs;
|
||||
|
@@ -1,3 +1,3 @@
|
||||
AddHandler python-program .py
|
||||
PythonHandler handler
|
||||
PythonDebug Off
|
||||
PythonDebug On
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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-2012 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-2012 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,57 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
CVSROOTS=$1
|
||||
VIEWVC_DIR=$2
|
||||
CVS_USER=$3
|
||||
CVS_GROUP=$4
|
||||
CVS_ASYNC=$5
|
||||
|
||||
if [ ! "$CVSROOTS" -o ! "$VIEWVC_DIR" -o ! "$CVS_USER" -o ! "$CVS_GROUP" ]; then
|
||||
echo "USAGE: $0 <CVS_ROOTS> <VIEWVC_DIR> <CVS_USER> <CVS_GROUP> [CVS_ASYNC]"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "*** Setting up commit hooks for CVS repositories (job=$CVS_ASYNC)"
|
||||
chgrp $CVS_GROUP $VIEWVC_DIR
|
||||
chmod 775 $VIEWVC_DIR
|
||||
chmod 1777 .
|
||||
for CVSROOT in $CVSROOTS; do
|
||||
vcrootname=`basename $CVSROOT`
|
||||
if [ "$CVS_ASYNC" ]; then
|
||||
VHOOK="touch $VIEWVC_DIR/.cvs-updated-$vcrootname"
|
||||
else
|
||||
VHOOK="$VIEWVC_DIR/bin/cvsdbadmin update $CVSROOT >/dev/null"
|
||||
fi;
|
||||
su $CVS_USER -s /bin/sh <<EOSH
|
||||
CVSROOT=$CVSROOT cvs co CVSROOT
|
||||
if [ $? -eq 0 ]; then
|
||||
cd CVSROOT
|
||||
mv loginfo loginfo.bak
|
||||
grep -v '/.cvs-updated' <loginfo.bak | grep -v '/bin/cvsdbadmin update' >loginfo
|
||||
cat >>loginfo <<EOF
|
||||
ALL $VHOOK
|
||||
EOF
|
||||
cvs ci -m 'CVS commit hook for ViewVC' loginfo
|
||||
cd ..
|
||||
rm -Rf CVSROOT
|
||||
fi
|
||||
EOSH
|
||||
if [ "$CVS_ASYNC" ]; then
|
||||
cat >$VIEWVC_DIR/cvshook-$vcrootname <<EOF
|
||||
#!/bin/sh
|
||||
if [ -e "$VIEWVC_DIR/.cvs-updated-$vcrootname" ]; then
|
||||
rm "$VIEWVC_DIR/.cvs-updated-$vcrootname"
|
||||
$VIEWVC_DIR/bin/cvsdbadmin update $CVSROOT >/dev/null
|
||||
fi
|
||||
EOF
|
||||
chmod 755 $VIEWVC_DIR/cvshook-$vcrootname
|
||||
cat >/etc/cron.d/viewvc-cvs-$vcrootname <<EOF
|
||||
*/5 * * * * root $VIEWVC_DIR/cvshook-$vcrootname
|
||||
EOF
|
||||
fi
|
||||
done;
|
||||
|
||||
if [ "$CVS_ASYNC" ]; then
|
||||
echo "*** Reloading cron daemon"
|
||||
/etc/init.d/cron reload
|
||||
fi
|
@@ -1,36 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
SVNROOT=$1
|
||||
VIEWVC_DIR=$2
|
||||
SVN_ASYNC=$3
|
||||
|
||||
if [ ! "$SVNROOT" -o ! "$VIEWVC_DIR" ]; then
|
||||
echo "USAGE: $0 <SVNROOT> <VIEWVC_DIR>"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "*** Setting up commit hooks for Subversion"
|
||||
for i in `ls $SVNROOT`; do
|
||||
if [ -d "$SVNROOT/$i" ]; then
|
||||
if [ "$SVN_ASYNC" ]; then
|
||||
cat >"$SVNROOT/$i/hooks/post-commit.tmp" <<EOF
|
||||
#!/bin/sh
|
||||
echo "\$1 \$2" >> $VIEWVC_DIR/.svn-updated
|
||||
EOF
|
||||
else
|
||||
cat >"$SVNROOT/$i/hooks/post-commit.tmp" <<EOF
|
||||
#!/bin/sh
|
||||
$VIEWVC_DIR/bin/svndbadmin update \$1 \$2
|
||||
EOF
|
||||
fi
|
||||
chmod 755 "$SVNROOT/$i/hooks/post-commit.tmp"
|
||||
mv "$SVNROOT/$i/hooks/post-commit.tmp" "$SVNROOT/$i/hooks/post-commit"
|
||||
fi
|
||||
done;
|
||||
|
||||
if [ "$SVN_ASYNC" ]; then
|
||||
cat >/etc/cron.d/viewvc-svn <<EOF
|
||||
*/10 * * * * root $VIEWVC_DIR/bin/svnupdate-async.sh "$VIEWVC_DIR"
|
||||
EOF
|
||||
/etc/init.d/cron reload
|
||||
fi
|
1297
bin/standalone.py
1297
bin/standalone.py
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2004-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2004-2012 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,43 +153,46 @@ 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)
|
||||
|
||||
self.changes = []
|
||||
changes_hash = {}
|
||||
for path, change in editor.changes.items():
|
||||
# skip non-file changes
|
||||
if change.item_kind != svn.core.svn_node_file:
|
||||
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)
|
||||
|
||||
# CustIS Bug 50473: a workaround for svnlib behaviour in file movements (FILE1 -> FILE2 + FILE1 -> null)
|
||||
if change.base_path:
|
||||
if not change.path and changes_hash.get(change.base_path, '') != '':
|
||||
minus = 0
|
||||
elif change.path:
|
||||
changes_hash[change.base_path] = change.path
|
||||
|
||||
self.changes.append((path, action, plus, minus))
|
||||
|
||||
def _get_root_for_rev(self, rev):
|
||||
@@ -251,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
|
||||
@@ -261,18 +265,24 @@ def main(command, repository, revs=[], verbose=0, force=0):
|
||||
sys.stderr.write("ERROR: " + str(e) + "\n")
|
||||
sys.exit(1)
|
||||
|
||||
repo = SvnRepo(repository)
|
||||
if command == 'rebuild' or (command == 'update' and not revs):
|
||||
for rev in range(repo.rev_max+1):
|
||||
handle_revision(db, command, repo, rev, verbose)
|
||||
elif command == 'update':
|
||||
if revs[0] is None:
|
||||
revs[0] = repo.rev_max
|
||||
if revs[1] is None:
|
||||
revs[1] = repo.rev_max
|
||||
revs.sort()
|
||||
for rev in range(revs[0], revs[1]+1):
|
||||
handle_revision(db, command, repo, rev, verbose, force)
|
||||
# Record what must be recorded.
|
||||
if command in ('rebuild', 'update'):
|
||||
if not os.path.exists(repository):
|
||||
sys.stderr.write('ERROR: could not find repository %s\n'
|
||||
% (repository))
|
||||
sys.exit(1)
|
||||
repo = SvnRepo(repository)
|
||||
if command == 'rebuild' or (command == 'update' and not revs):
|
||||
for rev in range(repo.rev_max+1):
|
||||
handle_revision(db, command, repo, rev, verbose)
|
||||
elif command == 'update':
|
||||
if revs[0] is None:
|
||||
revs[0] = repo.rev_max
|
||||
if revs[1] is None:
|
||||
revs[1] = repo.rev_max
|
||||
revs.sort()
|
||||
for rev in range(revs[0], revs[1]+1):
|
||||
handle_revision(db, command, repo, rev, verbose, force)
|
||||
|
||||
def _rev2int(r):
|
||||
if r == 'HEAD':
|
||||
@@ -290,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
|
||||
@@ -339,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':
|
||||
@@ -367,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:
|
||||
|
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
# Скрипт для обновления SVN репозиториев svndbadmin-ом
|
||||
# Берёт номера ревизий и имена репозиториев из перечисленных файлов или STDIN,
|
||||
# группирует их по номерам и выводит список команд, необходимых для обновления
|
||||
|
||||
use strict;
|
||||
|
||||
# первый аргумент - путь к svndbadmin
|
||||
my $svndbadmin = shift @ARGV
|
||||
|| die "USAGE: $0 <path_to_svndbadmin> FILES...";
|
||||
|
||||
# считываем названия репозиториев и номера ревизий из файла
|
||||
my $tou = {};
|
||||
my ($repos, $rev);
|
||||
while (<>)
|
||||
{
|
||||
s/^\s+//so;
|
||||
s/\s+$//so;
|
||||
($repos, $rev) = split /\s+/, $_;
|
||||
$tou->{$repos}->{$rev} = 1;
|
||||
}
|
||||
|
||||
# превращаем номера ревизий в диапазоны ревизий
|
||||
my ($i, $j, $r, $nr);
|
||||
foreach $repos (keys %$tou)
|
||||
{
|
||||
$rev = [ sort keys %{$tou->{$repos}} ];
|
||||
$nr = [];
|
||||
$j = 0;
|
||||
for $i (1..@$rev)
|
||||
{
|
||||
if ($i > $#$rev || $rev->[$i]-$rev->[$j] > $i-$j)
|
||||
{
|
||||
$r = $rev->[$j];
|
||||
$r .= ":".$rev->[$i-1] if $i-$j > 1;
|
||||
push @$nr, $r;
|
||||
$j = $i;
|
||||
}
|
||||
}
|
||||
$tou->{$repos} = $nr;
|
||||
}
|
||||
|
||||
# выводим список команд для выполнения
|
||||
foreach $repos (keys %$tou)
|
||||
{
|
||||
foreach (@{$tou->{$repos}})
|
||||
{
|
||||
print "$svndbadmin update $repos $_\n";
|
||||
}
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
VIEWVC_DIR=$1
|
||||
|
||||
test -f "$VIEWVC_DIR/.svn-updating" -o ! -f "$VIEWVC_DIR/.svn-updated" && exit 0
|
||||
mv "$VIEWVC_DIR/.svn-updated" "$VIEWVC_DIR/.svn-updating"
|
||||
"$VIEWVC_DIR/bin/svnupdate-async" "$VIEWVC_DIR/bin/svndbadmin" "$VIEWVC_DIR/.svn-updating" | sh >/dev/null
|
||||
rm "$VIEWVC_DIR/.svn-updating"
|
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-2012 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-2012 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-2012 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-2012 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 []
|
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -161,12 +161,6 @@ td {
|
||||
resource. Valid only when <var>pathtype</var> is <tt>file</tt>
|
||||
or (for Subversion roots) <tt>dir</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">log_rev_href</td>
|
||||
<td>String</td>
|
||||
<td>Revision number of the file-revision currently being viewed, or
|
||||
None.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">nav_path</td>
|
||||
<td>List</td>
|
||||
@@ -321,7 +315,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">pathrev_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the revision/tag selection form.</td>
|
||||
<td>Hidden field name/value pairs for the revision/tag selection form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">pathrev_clear_action</td>
|
||||
@@ -331,7 +325,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">pathrev_clear_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the path revision clear button.</td>
|
||||
<td>Hidden field name/value pairs for the path revision clear button.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -450,10 +444,10 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">annotation</td>
|
||||
<td>String</td>
|
||||
<td>If set, indicates that annotations were requested. Valid values
|
||||
are "annotated" (annotation was successful), "binary" (file contents
|
||||
are not line-based and human-readable), and "error" (something went
|
||||
wrong during annotation).</td>
|
||||
<td>Valid values are "none" (no annotations were attempted),
|
||||
"annotated" (annotation was successful), "binary" (file contents
|
||||
are not line-based and human-readable), and "error" (something
|
||||
went wrong during annotation).</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">author</td>
|
||||
@@ -725,19 +719,29 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">diff_format_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the diff format selection form.</td>
|
||||
<td>Hidden field name/value pairs for the diff format selection form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">left</td>
|
||||
<td>Container</td>
|
||||
<td>Container object for grouping information about the left file.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.ago</td>
|
||||
<td>String</td>
|
||||
<td>Text description of the time elapsed since <var>left.date</date>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.annotate_href</td>
|
||||
<td>String</td>
|
||||
<td>URL of the ViewVC annotation view for the left file.
|
||||
Valid only when <var>entries.pathtype</var> is <tt>file</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.author</td>
|
||||
<td>String</td>
|
||||
<td>Author of the revision of the left file.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.date</td>
|
||||
<td>String</td>
|
||||
@@ -755,6 +759,11 @@ td {
|
||||
<td>URL to download the HEAD revision of the left file as
|
||||
<tt>text/plain</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.log</td>
|
||||
<td>String</td>
|
||||
<td>Log message of the left file revision.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.path</td>
|
||||
<td>String</td>
|
||||
@@ -778,6 +787,11 @@ td {
|
||||
current revision. Valid only when <var>roottype</var> is
|
||||
<tt>svn</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.size</td>
|
||||
<td>String</td>
|
||||
<td>Size of the left file revision, in bytes. Subversion only.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">left.tag</td>
|
||||
<td>String</td>
|
||||
@@ -788,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>
|
||||
@@ -799,12 +818,22 @@ td {
|
||||
<td>Container</td>
|
||||
<td>Container object for grouping information about the right file.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.author</td>
|
||||
<td>String</td>
|
||||
<td>Author of the revision of the right file.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.annotate_href</td>
|
||||
<td>String</td>
|
||||
<td>URL of the ViewVC annotation view for the right file.
|
||||
Valid only when <var>entries.pathtype</var> is <tt>file</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.author</td>
|
||||
<td>String</td>
|
||||
<td>Author of the revision of the right file.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.date</td>
|
||||
<td>String</td>
|
||||
@@ -822,6 +851,11 @@ td {
|
||||
<td>URL to download the HEAD revision of the right file as
|
||||
<tt>text/plain</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.log</td>
|
||||
<td>String</td>
|
||||
<td>Log message of the right file revision.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.path</td>
|
||||
<td>String</td>
|
||||
@@ -845,6 +879,11 @@ td {
|
||||
current revision. Valid only when <var>roottype</var> is
|
||||
<tt>svn</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.size</td>
|
||||
<td>String</td>
|
||||
<td>Size of the right file revision, in bytes. Subversion only.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">right.tag</td>
|
||||
<td>String</td>
|
||||
@@ -905,7 +944,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">dir_paging_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the page selection form.</td>
|
||||
<td>Hidden field name/value pairs for the page selection form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">entries</td>
|
||||
@@ -1087,23 +1126,16 @@ td {
|
||||
<td>String</td>
|
||||
<td>Current search expression, if any.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">search_re_form</td>
|
||||
<td>Boolean</td>
|
||||
<td>Indicates whether or not to display the regular expression search
|
||||
form. Value depends on the whether searching is enabled in the
|
||||
configuration and whether or not the current directory is
|
||||
empty.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">search_re_action</td>
|
||||
<td>String</td>
|
||||
<td>Form action URL for the regular expression search form.</td>
|
||||
<td>Form action URL for the regular expression search form,
|
||||
if searching is available.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">search_re_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the regular expression search form.</td>
|
||||
<td>Hidden field name/value pairs for the regular expression search form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">show_attic_href</td>
|
||||
@@ -1249,7 +1281,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">diff_select_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the diff selection form.</td>
|
||||
<td>Hidden field name/value pairs for the diff selection form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">entries</td>
|
||||
@@ -1530,7 +1562,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">log_paging_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the page selection form.</td>
|
||||
<td>Hidden field name/value pairs for the page selection form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">logsort</td>
|
||||
@@ -1546,7 +1578,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">logsort_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for the log sort drop down box</td>
|
||||
<td>Hidden field name/value pairs for the log sort drop down box</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">mime_type</td>
|
||||
@@ -1794,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>
|
||||
@@ -1910,7 +1950,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">query_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for query form.</td>
|
||||
<td>Hidden field name/value pairs for query form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">querysort</td>
|
||||
@@ -2050,7 +2090,7 @@ td {
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">jump_rev_hidden_values</td>
|
||||
<td>List</td>
|
||||
<td>Hidden value name/value pairs for revision jump form.</td>
|
||||
<td>Hidden field name/value pairs for revision jump form.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">limit_changes</td>
|
||||
@@ -2073,6 +2113,11 @@ td {
|
||||
<td>String</td>
|
||||
<td>URL for the current view but with <tt>limit_changes</tt> disabled.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">num_changes</td>
|
||||
<td>String</td>
|
||||
<td>Number of paths changed in this revision.</td>
|
||||
</tr>
|
||||
<tr class="varlevel1">
|
||||
<td class="varname">next_href</td>
|
||||
<td>String</td>
|
||||
@@ -2112,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>
|
||||
@@ -2125,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>
|
||||
|
||||
|
@@ -137,9 +137,8 @@ td {
|
||||
configuration's "general" section.</li>
|
||||
|
||||
<li>Finally, ensure that that the new <code>authorizer</code>
|
||||
option is set to either "forbidden" (which is the default) or
|
||||
"forbiddenre", depending on which of those you were using in
|
||||
ViewVC 1.0.x.</li>
|
||||
option is set to either "forbidden" or "forbiddenre", depending
|
||||
on which of those you were using in ViewVC 1.0.x.</li>
|
||||
|
||||
</ol>
|
||||
|
||||
@@ -215,10 +214,9 @@ td {
|
||||
<li>options/py2html_path</li>
|
||||
<li>options/use_enscript</li>
|
||||
<li>options/use_highlight</li>
|
||||
<li>options/use_pagesize</li>
|
||||
<li>options/use_php</li>
|
||||
<li>options/use_py2html</li>
|
||||
<li>options/use_pygments</li>
|
||||
<li>options/use_source_highlight</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
@@ -226,16 +224,23 @@ td {
|
||||
<div class="h3">
|
||||
<h3>Checkin Database</h3>
|
||||
|
||||
<p>In ViewVC 1.1, the <code>svndbadmin</code> program's "rebuild"
|
||||
subcommand has had its purpose become more defined. It no longer
|
||||
accepts a revision argument, and therefore can now only be used to
|
||||
completely rebuild the entirety of the checkin database information
|
||||
for a Subversion repository (instead of being able to only update
|
||||
the information related to single Subversion revision). For
|
||||
per-revision updating, use <code>svndbadmin update</code> and
|
||||
<p>ViewVC 1.1 introduces to the <code>cvsdbadmin</code>
|
||||
and <code>svndbadmin</code> tools a new "purge" operation, which
|
||||
allows you to remove all the information related to a given root
|
||||
from your checkins database (without disturbing the information
|
||||
associated with other roots). Likewise, the "rebuild" command in
|
||||
those tools now implies a "purge" followed by an update.</p>
|
||||
|
||||
<p>As a related change, the <code>svndbadmin</code> program's
|
||||
"rebuild" subcommand has had its purpose become more defined. It
|
||||
no longer accepts a revision argument, and therefore can now only
|
||||
be used to completely rebuild the entirety of the checkin database
|
||||
information for a Subversion repository (instead of being able to
|
||||
only update the information related to single Subversion revision).
|
||||
For per-revision updating, use <code>svndbadmin update</code> and
|
||||
provide a revision (or revision range). And to get the previous
|
||||
rebuild-a-revision effect, pass the new <code>--force</code>
|
||||
option to <code>svndbadmin update</code>.</p>
|
||||
rebuild-a-revision effect, pass the new <code>--force</code> option
|
||||
to <code>svndbadmin update</code>.</p>
|
||||
|
||||
<p>In other words, where you once did this:</p>
|
||||
|
||||
@@ -249,6 +254,19 @@ td {
|
||||
</pre>
|
||||
</blockquote>
|
||||
|
||||
<p>To enhance the performance of the new "purge" operation, ViewVC 1.1
|
||||
introduces some slight changes to the checkin database schema. If
|
||||
you use the <code>make-database</code> tool to (re)create your
|
||||
checkins database, it will by default employ the new database
|
||||
schema. This should cause the database to be virtually unusable by
|
||||
previous versions of ViewVC, and that's by design. If, however,
|
||||
you need to (re)create your checkins database and you require
|
||||
compatibility with previous versions of ViewVC or ViewCVS, simply
|
||||
pass the "--version=1.0" option to the <code>make-database</code>
|
||||
script. Note that your "purge" and "rebuild" operations could be
|
||||
abysmally slow, though, as that version of the database schema is
|
||||
not optimized for those operations.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="h3">
|
||||
@@ -296,6 +314,22 @@ td {
|
||||
<li>options/allow_tar</li>
|
||||
</ul>
|
||||
|
||||
<p>ViewVC now honors the "svn:mime-type" property stored on
|
||||
Subversion-versioned files as the primary source of MIME type
|
||||
determination (before falling back to name-based MIME mappings and
|
||||
such). However, this can negatively affect the viewability of
|
||||
certain files — especially images — whose
|
||||
"svn:mime-type" properties are set incorrectly, such as will happen
|
||||
if Subversion itself merely determines that the file isn't
|
||||
human-readable and uses the "application/octet-stream" MIME type to
|
||||
record this determination. To make ViewVC <em>not</em> honor the
|
||||
"svn:mime-type" property value, set the <code>svn_ignore_mimetype</code>
|
||||
configuration option.</p>
|
||||
|
||||
<p>Speaking of MIME types, the option <code>mime_types_file</code> is
|
||||
now <code>mime_types_files</code>, as it now carries multiple paths
|
||||
to MIME mappings files, ordered by preference.</p>
|
||||
|
||||
<p>The <code>use_rcsparse</code> option was moved from the "general"
|
||||
section to the "options" section.</p>
|
||||
|
||||
@@ -314,7 +348,7 @@ td {
|
||||
all = viewvc.*
|
||||
|
||||
[all-options]
|
||||
allow_tar = 1
|
||||
allowed_views = annotate, diff, markup, tar
|
||||
</pre>
|
||||
</blockquote>
|
||||
|
||||
@@ -324,7 +358,7 @@ allow_tar = 1
|
||||
all = viewvc.*
|
||||
|
||||
[vhost-all/options]
|
||||
allow_tar = 1
|
||||
allowed_views = annotate, diff, markup, tar
|
||||
</pre>
|
||||
</blockquote>
|
||||
|
||||
@@ -631,6 +665,11 @@ allow_tar = 1
|
||||
<td>revision.ezt</td>
|
||||
<td>now is an iterable list of objects with .name and .value attributes</td>
|
||||
</tr>
|
||||
<tr class="added">
|
||||
<td class="varname">num_changes</td>
|
||||
<td>revision.ezt</td>
|
||||
<td>added</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -659,7 +698,7 @@ allow_tar = 1
|
||||
<h3>Checkin Database</h3>
|
||||
|
||||
<p>ViewVC 1.0 reads and writes commit times in the MySQL database in
|
||||
UTC time rather than local time. This can cause times displayed on
|
||||
UTC time rather than local time. This can cause times displayed on
|
||||
the query page to be a few hours off if an old database is being
|
||||
used with a new version of ViewVC. The best way to fix this is to
|
||||
rebuild the database with the new version of cvsdbadmin, but it
|
||||
@@ -702,7 +741,8 @@ allow_tar = 1
|
||||
<li>options/root_as_url_component</li>
|
||||
<li>options/default_file_view</li>
|
||||
<li>options/sort_group_dirs</li>
|
||||
<li>options/use_pagesize</li>
|
||||
<li>options/dir_pagesize</li>
|
||||
<li>options/log_pagesize</li>
|
||||
<li>options/limit_changes</li>
|
||||
<li>options/use_localtime</li>
|
||||
<li>options/cross_copies</li>
|
||||
|
@@ -996,7 +996,7 @@ th.caption {
|
||||
<td>file query string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_match=FILE_MATCH</code></td>
|
||||
<td><code>file_match=<var>FILE_MATCH</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"exact" "like" "glob" "regex" or "notregex" determining type
|
||||
of file match</td>
|
||||
@@ -1007,7 +1007,7 @@ th.caption {
|
||||
<td>author query string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>who_match=WHO_MATCH</code></td>
|
||||
<td><code>who_match=<var>WHO_MATCH</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"exact" "like" "glob" "regex" or "notregex" determining type
|
||||
of author match</td>
|
||||
@@ -1024,36 +1024,36 @@ th.caption {
|
||||
of log message match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>querysort=SORT</code></td>
|
||||
<td><code>querysort=<var>SORT</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"date" "author" or "file" determining order of query results</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>date=DATE</code></td>
|
||||
<td><code>date=<var>DATE</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"hours" "day" "week" "month" "all" or "explicit" to filter
|
||||
query results by date</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>hours=HOURS</code></td>
|
||||
<td><code>hours=<var>HOURS</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>number of hours back to include results from when
|
||||
<code><var>DATE</var></code> is "hours"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mindate=MINDATE</code></td>
|
||||
<td><code>mindate=<var>MINDATE</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>earliest date to include results from when
|
||||
<code><var>DATE</var></code> is "explicit"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>maxdate=MAXDATE</code></td>
|
||||
<td><code>maxdate=<var>MAXDATE</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>latest date to include results from when
|
||||
<code><var>DATE</var></code> is "explicit"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit_changes=LIMIT_CHANGES</code></td>
|
||||
<td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>maximum number of files to list per commit in query
|
||||
results. Default is value of <code>limit_changes</code>
|
||||
@@ -1113,7 +1113,7 @@ th.caption {
|
||||
<td>branch query string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>branch_match=BRANCH_MATCH</code></td>
|
||||
<td><code>branch_match=<var>BRANCH_MATCH</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"exact" "like" "glob" "regex" or "notregex" determining type
|
||||
of branch match</td>
|
||||
@@ -1129,7 +1129,7 @@ th.caption {
|
||||
<td>file query string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_match=FILE_MATCH</code></td>
|
||||
<td><code>file_match=<var>FILE_MATCH</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"exact" "like" "glob" "regex" or "notregex" determining type
|
||||
of file match</td>
|
||||
@@ -1140,7 +1140,7 @@ th.caption {
|
||||
<td>author query string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>who_match=WHO_MATCH</code></td>
|
||||
<td><code>who_match=<var>WHO_MATCH</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"exact" "like" "glob" "regex" or "notregex" determining type
|
||||
of author match</td>
|
||||
@@ -1157,50 +1157,43 @@ th.caption {
|
||||
of log message match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>querysort=SORT</code></td>
|
||||
<td><code>querysort=<var>SORT</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"date" "author" or "file" determining order of query results</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>date=DATE</code></td>
|
||||
<td><code>date=<var>DATE</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"hours" "day" "week" "month" "all" or "explicit" to filter
|
||||
query results by date</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>hours=HOURS</code></td>
|
||||
<td><code>hours=<var>HOURS</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>number of hours back to include results from when
|
||||
<code><var>DATE</var></code> is "hours"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mindate=MINDATE</code></td>
|
||||
<td><code>mindate=<var>MINDATE</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>earliest date to include results from when
|
||||
<code><var>DATE</var></code> is "explicit"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>maxdate=MAXDATE</code></td>
|
||||
<td><code>maxdate=<var>MAXDATE</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>latest date to include results from when
|
||||
<code><var>DATE</var></code> is "explicit"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>format=FORMAT</code></td>
|
||||
<td><code>format=<var>FORMAT</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>"rss" or "backout" values to generate an rss feed or list of
|
||||
commands to back out changes instead showing a normal query result
|
||||
page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit=LIMIT</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=LIMIT_CHANGES</code></td>
|
||||
<td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>maximum number of files to list per commit in query
|
||||
results. Default is value of <code>limit_changes</code>
|
||||
@@ -1254,7 +1247,7 @@ th.caption {
|
||||
<td><a href="#revision-param"><code>revision</code> parameter</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit_changes=LIMIT_CHANGES</code></td>
|
||||
<td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td>
|
||||
<td>optional</td>
|
||||
<td>maximum number of files to list per commit. Default is value
|
||||
of <code>limit_changes</code> configuration option</td>
|
||||
|
54
elemx/Makefile
Normal file
54
elemx/Makefile
Normal file
@@ -0,0 +1,54 @@
|
||||
TARGETS = python/elx-python java/elx-java
|
||||
|
||||
all : $(TARGETS)
|
||||
|
||||
CFLAGS = -g -Wpointer-arith -Wwrite-strings -Wshadow -Wall
|
||||
CPPFLAGS = -Ipython -Ijava -I.
|
||||
|
||||
# the scanner depends on tokens generated from python.y
|
||||
python/scanner.c : python/python.c
|
||||
|
||||
# the keywords also need the tokens in python.h
|
||||
python/py_keywords.c : python/python.c
|
||||
|
||||
# we need the scanner tokens in py_scan.h and keywords in py_keywords.h
|
||||
python/elx-python.o : python/elx-python.c python/py_keywords.c
|
||||
|
||||
# we need java.[ch] generated first to get the tokens in java.h
|
||||
java/j_scan.c : java/java.c java/j_keywords.c
|
||||
|
||||
# the keywords also need the tokens in java.h
|
||||
java/j_keywords.c : java/java.c
|
||||
|
||||
# we need the scanner tokens in j_scan.h and keywords in j_keywords.h
|
||||
java/elx-java.o : java/elx-java.c java/j_keywords.c java/java.c
|
||||
|
||||
|
||||
python/elx-python : python/elx-python.o python/scanner.o python/python.o \
|
||||
python/py_keywords.o elx-common.o
|
||||
$(CC) -o $@ $^
|
||||
|
||||
java/elx-java : java/elx-java.o java/j_scan.o java/java.o java/j_keywords.o \
|
||||
elx-common.o
|
||||
$(CC) -o $@ $^
|
||||
|
||||
clean :
|
||||
rm -f *.o
|
||||
rm -f python/*.o python/python.[ch] python/py_keywords.[ch]
|
||||
rm -f java/*.o java/java.[ch] java/j_keywords.[ch] java/j_scan.[ch]
|
||||
rm -f python/*.output java/*.output
|
||||
rm -f $(TARGETS)
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .y .shilka .o
|
||||
|
||||
%.c : %.y
|
||||
@d="`echo $@ | sed 's/\.c//'`" ; \
|
||||
echo msta -d -enum -v -o $$d $< ; \
|
||||
msta -d -enum -v -o $$d $<
|
||||
|
||||
%.c : %.shilka
|
||||
@d="`echo $@ | sed 's,/[^/]*$$,,'`" ; \
|
||||
f="`echo $< | sed 's,.*/,,'`" ; \
|
||||
echo shilka -length -no-definitions -interface $$f ; \
|
||||
(cd $$d && shilka -length -no-definitions -interface $$f)
|
110
elemx/elx-common.c
Normal file
110
elemx/elx-common.c
Normal file
@@ -0,0 +1,110 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "elx.h"
|
||||
|
||||
#define ELX_ELEMS_EXT ".elx"
|
||||
#define ELX_SYMBOLS_EXT ".els"
|
||||
|
||||
|
||||
static void usage(const char *progname)
|
||||
{
|
||||
fprintf(stderr, "USAGE: %s FILENAME\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static const char * build_one(const char *base, int len, const char *suffix)
|
||||
{
|
||||
int slen = strlen(suffix);
|
||||
char *fn;
|
||||
|
||||
fn = malloc(len + slen + 1);
|
||||
memcpy(fn, base, len);
|
||||
memcpy(fn + len, suffix, slen);
|
||||
fn[len + slen] = '\0';
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
elx_context_t *elx_process_args(int argc, const char **argv)
|
||||
{
|
||||
elx_context_t *ec;
|
||||
const char *input_fn;
|
||||
const char *p;
|
||||
int len;
|
||||
|
||||
/* ### in the future, we can expand this for more options */
|
||||
|
||||
if (argc != 2)
|
||||
{
|
||||
usage(argv[0]);
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
input_fn = argv[1];
|
||||
|
||||
p = strrchr(input_fn, '.');
|
||||
if (p == NULL)
|
||||
len = strlen(input_fn);
|
||||
else
|
||||
len = p - argv[1];
|
||||
|
||||
ec = malloc(sizeof(*ec));
|
||||
ec->input_fn = input_fn;
|
||||
ec->elx_fn = build_one(input_fn, len, ELX_ELEMS_EXT);
|
||||
ec->sym_fn = build_one(input_fn, len, ELX_SYMBOLS_EXT);
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
void elx_open_files(elx_context_t *ec)
|
||||
{
|
||||
const char *fn;
|
||||
const char *op;
|
||||
|
||||
if ((ec->input_fp = fopen(ec->input_fn, "r")) == NULL)
|
||||
{
|
||||
fn = ec->input_fn;
|
||||
op = "reading";
|
||||
goto error;
|
||||
}
|
||||
if ((ec->elx_fp = fopen(ec->elx_fn, "w")) == NULL)
|
||||
{
|
||||
fn = ec->elx_fn;
|
||||
op = "writing";
|
||||
goto error;
|
||||
}
|
||||
if ((ec->sym_fp = fopen(ec->sym_fn, "w")) == NULL)
|
||||
{
|
||||
fn = ec->sym_fn;
|
||||
op = "writing";
|
||||
goto error;
|
||||
}
|
||||
return;
|
||||
|
||||
error:
|
||||
fprintf(stderr, "ERROR: file \"%s\" could not be opened for %s.\n %s\n",
|
||||
fn, op, strerror(errno));
|
||||
exit(2);
|
||||
}
|
||||
|
||||
void elx_close_files(elx_context_t *ec)
|
||||
{
|
||||
fclose(ec->input_fp);
|
||||
fclose(ec->elx_fp);
|
||||
fclose(ec->sym_fp);
|
||||
}
|
||||
|
||||
void elx_issue_token(elx_context_t *ec,
|
||||
char which, int start, int len,
|
||||
const char *symbol)
|
||||
{
|
||||
fprintf(ec->elx_fp, "%c %d %d\n", which, start, len);
|
||||
|
||||
if (ELX_DEFINES_SYM(which))
|
||||
{
|
||||
fprintf(ec->sym_fp, "%s %d %s\n", symbol, start, ec->input_fn);
|
||||
}
|
||||
}
|
54
elemx/elx.h
Normal file
54
elemx/elx.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef ELX_H
|
||||
#define ELX_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
#define ELX_COMMENT 'C' /* a comment */
|
||||
#define ELX_STRING 'S' /* a string constant */
|
||||
#define ELX_KEYWORD 'K' /* a language keyword */
|
||||
#define ELX_GLOBAL_FDEF 'F' /* function defn in global (visible) scope */
|
||||
#define ELX_LOCAL_FDEF 'L' /* function defn in local (hidden) scope */
|
||||
#define ELX_METHOD_DEF 'M' /* method definition */
|
||||
#define ELX_FUNC_REF 'R' /* function reference / call */
|
||||
|
||||
#define ELX_DEFINES_SYM(c) ((c) == ELX_GLOBAL_FDEF || (c) == ELX_LOCAL_FDEF \
|
||||
|| (c) == ELX_METHOD_DEF)
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* input filename */
|
||||
const char *input_fn;
|
||||
|
||||
/* output filenames: element extractions, and symbols */
|
||||
const char *elx_fn;
|
||||
const char *sym_fn;
|
||||
|
||||
/* file pointers for each of the input/output files */
|
||||
FILE *input_fp;
|
||||
FILE *elx_fp;
|
||||
FILE *sym_fp;
|
||||
|
||||
} elx_context_t;
|
||||
|
||||
elx_context_t *elx_process_args(int argc, const char **argv);
|
||||
|
||||
void elx_open_files(elx_context_t *ec);
|
||||
void elx_close_files(elx_context_t *ec);
|
||||
|
||||
|
||||
void elx_issue_token(elx_context_t *ec,
|
||||
char which, int start, int len,
|
||||
const char *symbol);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* ELX_H */
|
121
elemx/elx_html.py
Normal file
121
elemx/elx_html.py
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# generate HTML given an input file and an element file
|
||||
#
|
||||
|
||||
import re
|
||||
import string
|
||||
import cgi
|
||||
import struct
|
||||
|
||||
_re_elem = re.compile('([a-zA-Z]) ([0-9]+) ([0-9]+)\n')
|
||||
|
||||
CHUNK_SIZE = 98304 # 4096*24
|
||||
|
||||
|
||||
class ElemParser:
|
||||
"Parse an elements file, extracting the token type, start, and length."
|
||||
|
||||
def __init__(self, efile):
|
||||
self.efile = efile
|
||||
|
||||
def get(self):
|
||||
line = self.efile.readline()
|
||||
if not line:
|
||||
return None, None, None
|
||||
t, s, e = string.split(line)
|
||||
return t, int(s)-1, int(e)
|
||||
|
||||
def unused_get(self):
|
||||
record = self.efile.read(9)
|
||||
if not record:
|
||||
return None, None, None
|
||||
return struct.unpack('>cii', record)
|
||||
|
||||
|
||||
class Writer:
|
||||
"Generate output, including copying from another input."
|
||||
|
||||
def __init__(self, ifile, ofile):
|
||||
self.ifile = ifile
|
||||
self.ofile = ofile
|
||||
|
||||
self.buf = ifile.read(CHUNK_SIZE)
|
||||
self.offset = 0
|
||||
|
||||
def write(self, data):
|
||||
self.ofile.write(data)
|
||||
|
||||
def copy(self, pos, amt):
|
||||
"Copy 'amt' bytes from position 'pos' of input to output."
|
||||
idx = pos - self.offset
|
||||
self.ofile.write(cgi.escape(buffer(self.buf, idx, amt)))
|
||||
amt = amt - (len(self.buf) - idx)
|
||||
while amt > 0:
|
||||
self._more()
|
||||
self.ofile.write(cgi.escape(buffer(self.buf, 0, amt)))
|
||||
amt = amt - len(self.buf)
|
||||
|
||||
def flush(self, pos):
|
||||
"Flush the rest of the input to the output."
|
||||
idx = pos - self.offset
|
||||
self.ofile.write(cgi.escape(buffer(self.buf, idx)))
|
||||
while 1:
|
||||
buf = self.ifile.read(CHUNK_SIZE)
|
||||
if not buf:
|
||||
break
|
||||
self.ofile.write(cgi.escape(buf))
|
||||
|
||||
def _more(self):
|
||||
self.offset = self.offset + len(self.buf)
|
||||
self.buf = self.ifile.read(CHUNK_SIZE)
|
||||
|
||||
|
||||
def generate(input, elems, output, genpage=0):
|
||||
ep = ElemParser(elems)
|
||||
w = Writer(input, output)
|
||||
cur = 0
|
||||
if genpage:
|
||||
w.write('''\
|
||||
<html><head><title>ELX Output Page</title>
|
||||
<style type="text/css">
|
||||
.elx_C { color: firebrick; font-style: italic; }
|
||||
.elx_S { color: #bc8f8f; font-weight: bold; }
|
||||
.elx_K { color: purple; font-weight: bold }
|
||||
.elx_F { color: blue; font-weight: bold; }
|
||||
.elx_L { color: blue; font-weight: bold; }
|
||||
.elx_M { color: blue; font-weight: bold; }
|
||||
.elx_R { color: blue; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
''')
|
||||
w.write('<pre>')
|
||||
while 1:
|
||||
type, start, length = ep.get()
|
||||
if type is None:
|
||||
break
|
||||
if cur < start:
|
||||
# print out some plain text up to 'cur'
|
||||
w.copy(cur, start - cur)
|
||||
|
||||
# wrap a bit o' formatting here
|
||||
w.write('<span class="elx_%s">' % type)
|
||||
|
||||
# copy over the token
|
||||
w.copy(start, length)
|
||||
|
||||
# and close up the formatting
|
||||
w.write('</span>')
|
||||
|
||||
cur = start + length
|
||||
|
||||
# all done.
|
||||
w.flush(cur)
|
||||
w.write('</pre>')
|
||||
if genpage:
|
||||
w.write('</body></html>\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
generate(open(sys.argv[1]), open(sys.argv[2]), sys.stdout, 1)
|
26
elemx/elx_page.sh
Executable file
26
elemx/elx_page.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test "$#" != 2; then
|
||||
echo "USAGE: $0 SOURCE-FILE ELX-FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
<html><head><title>ELX Output Page</title>
|
||||
<style type="text/css">
|
||||
.elx_C { color: firebrick; font-style: italic; }
|
||||
.elx_S { color: #bc8f8f; font-weight: bold; }
|
||||
.elx_K { color: purple; font-weight: bold }
|
||||
.elx_F { color: blue; font-weight: bold; }
|
||||
.elx_L { color: blue; font-weight: bold; }
|
||||
.elx_M { color: blue; font-weight: bold; }
|
||||
.elx_R { color: blue; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
EOF
|
||||
|
||||
dirname="`dirname $0`"
|
||||
python2 $dirname/elx_html.py $1 $2
|
||||
|
||||
echo "</body></html>"
|
148
elemx/java/elx-java.c
Normal file
148
elemx/java/elx-java.c
Normal file
@@ -0,0 +1,148 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "java.h"
|
||||
#include "j_keywords.h"
|
||||
#include "elx.h"
|
||||
|
||||
/* from j_scan.c */
|
||||
extern int yylex(void);
|
||||
extern void yylex_start(int *error_flag);
|
||||
extern void yylex_finish(void);
|
||||
extern const char *get_identifier(void);
|
||||
|
||||
static const char *fname;
|
||||
static int saw_error = 0;
|
||||
|
||||
static int lineno = 1;
|
||||
static int hpos = 1;
|
||||
static int fpos = 0;
|
||||
|
||||
static int token_start = 0;
|
||||
static int start_lineno;
|
||||
static int start_hpos;
|
||||
|
||||
static elx_context_t *ectx;
|
||||
|
||||
|
||||
//#define DEBUG_SCANNER
|
||||
|
||||
/* if we're debugging, then the scanner looks for this var */
|
||||
int yysdebug = 0;
|
||||
|
||||
/* and the parser looks for this */
|
||||
int yydebug = 1;
|
||||
|
||||
|
||||
void yyserror(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "%s:%d:%d: lex error: %s\n",
|
||||
fname, start_lineno, start_hpos, msg);
|
||||
saw_error = 1;
|
||||
}
|
||||
|
||||
void yyerror(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "%s:%d:%d: parse error: %s\n",
|
||||
fname, start_lineno, start_hpos, msg);
|
||||
saw_error = 1;
|
||||
}
|
||||
|
||||
int yyslex(void)
|
||||
{
|
||||
int c = fgetc(ectx->input_fp);
|
||||
|
||||
if (c == EOF)
|
||||
return -1; /* tell lexer we're done */
|
||||
|
||||
++fpos;
|
||||
if (c == '\n')
|
||||
{
|
||||
hpos = 1;
|
||||
++lineno;
|
||||
}
|
||||
else
|
||||
++hpos;
|
||||
|
||||
// printf("char: '%c'\n", c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void issue_token(char which)
|
||||
{
|
||||
const char *ident = NULL;
|
||||
|
||||
if (ELX_DEFINES_SYM(which))
|
||||
ident = get_identifier();
|
||||
else
|
||||
ident = NULL;
|
||||
|
||||
elx_issue_token(ectx, which, token_start, fpos - token_start + 1, ident);
|
||||
}
|
||||
|
||||
void mark_token_start(void)
|
||||
{
|
||||
token_start = fpos;
|
||||
start_lineno = lineno;
|
||||
start_hpos = hpos;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SCANNER
|
||||
|
||||
void gen_scan_tokens(void)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
int v = yylex();
|
||||
|
||||
if (v == TK_IDENTIFIER)
|
||||
printf("%d-%d: %d '%s'\n",
|
||||
token_start, fpos-1, v, get_identifier());
|
||||
else
|
||||
printf("%d-%d: %d\n", token_start, fpos-1, v);
|
||||
|
||||
/* end of parse? */
|
||||
if (v <= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#else /* DEBUG_SCANNER */
|
||||
|
||||
static void gen_elx_tokens(void)
|
||||
{
|
||||
/* ### what to do with the result? should have seen/set saw_error */
|
||||
(void) yyparse();
|
||||
}
|
||||
|
||||
#endif /* DEBUG_SCANNER */
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
int errcode;
|
||||
|
||||
ectx = elx_process_args(argc, argv);
|
||||
|
||||
yylex_start(&errcode);
|
||||
if (errcode)
|
||||
{
|
||||
fprintf(stderr, "error: yylex_start: %d\n", errcode);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
elx_open_files(ectx);
|
||||
|
||||
#ifdef DEBUG_SCANNER
|
||||
gen_scan_tokens();
|
||||
#else
|
||||
gen_elx_tokens();
|
||||
#endif
|
||||
|
||||
yylex_finish();
|
||||
elx_close_files(ectx);
|
||||
|
||||
if (saw_error)
|
||||
return EXIT_FAILURE;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
65
elemx/java/j_keywords.shilka
Normal file
65
elemx/java/j_keywords.shilka
Normal file
@@ -0,0 +1,65 @@
|
||||
%local {
|
||||
/* get the KR_* values */
|
||||
#include "java.h"
|
||||
}
|
||||
|
||||
%%
|
||||
abstract
|
||||
boolean
|
||||
break
|
||||
byte
|
||||
/* byvalue */
|
||||
case
|
||||
/* cast */
|
||||
catch
|
||||
char
|
||||
class
|
||||
/* const */
|
||||
continue
|
||||
default
|
||||
do
|
||||
double
|
||||
else
|
||||
extends
|
||||
false
|
||||
final
|
||||
finally
|
||||
float
|
||||
for
|
||||
/* future */
|
||||
/* generic */
|
||||
/* goto */
|
||||
if
|
||||
implements
|
||||
import
|
||||
/* inner */
|
||||
instanceof
|
||||
int
|
||||
interface
|
||||
long
|
||||
native
|
||||
new
|
||||
null
|
||||
/* operator */
|
||||
/* outer */
|
||||
package
|
||||
private
|
||||
protected
|
||||
public
|
||||
/* rest */
|
||||
return
|
||||
short
|
||||
static
|
||||
super
|
||||
switch
|
||||
synchronized
|
||||
this
|
||||
throw
|
||||
throws
|
||||
transient
|
||||
true
|
||||
try
|
||||
/* var */
|
||||
void
|
||||
volatile
|
||||
while
|
135
elemx/java/j_scan.y
Normal file
135
elemx/java/j_scan.y
Normal file
@@ -0,0 +1,135 @@
|
||||
%start token
|
||||
%scanner
|
||||
|
||||
%local {
|
||||
#include "elx.h"
|
||||
|
||||
/* from elx-java.c */
|
||||
void yyserror(const char *msg);
|
||||
int yyslex(void);
|
||||
|
||||
/* for the TK_ symbols, generated from java.y */
|
||||
#include "java.h"
|
||||
|
||||
/* for keyword recognition */
|
||||
#include "j_keywords.h"
|
||||
|
||||
extern void issue_token(char which);
|
||||
extern void mark_token_start(void);
|
||||
|
||||
#define MAX_IDENT 200
|
||||
static int idlen;
|
||||
static char identifier[MAX_IDENT+1];
|
||||
#define INIT_IDENT(c) (identifier[0] = (c), idlen = 1)
|
||||
#define ADD_IDENT(c) if (idlen == MAX_IDENT) return E_IDENT_TOO_LONG; \
|
||||
else identifier[idlen++] = (c)
|
||||
|
||||
/* ### is there a better place? */
|
||||
#define E_IDENT_TOO_LONG (-100)
|
||||
|
||||
static int lookup(void);
|
||||
}
|
||||
|
||||
|
||||
%%
|
||||
|
||||
token : pure_ws* { mark_token_start(); } slash_op
|
||||
|
||||
slash_op : "/=" { return TK_OPERATOR; }
|
||||
| comment token
|
||||
| '/' { return TK_OPERATOR; }
|
||||
| one_token
|
||||
|
|
||||
;
|
||||
|
||||
one_token : t_identifier { return lookup(); }
|
||||
| t_literal { return TK_LITERAL; }
|
||||
| t_operator { return TK_OPERATOR; }
|
||||
| t_chars { return yysprev_char; }
|
||||
| t_inc_dec { return TK_INC_DEC; }
|
||||
| t_bracket
|
||||
;
|
||||
|
||||
t_identifier : alpha { INIT_IDENT(yysprev_char); }
|
||||
( alphanum { ADD_IDENT(yysprev_char); } )*
|
||||
|
||||
alpha : 'a' - 'z' | 'A' - 'Z' | '_' | '$'
|
||||
alphanum : alpha | digit
|
||||
|
||||
digit : '0' - '9'
|
||||
hexdigit : digit | 'a' - 'f' | 'A' - 'F'
|
||||
octal : '0' - '7'
|
||||
|
||||
t_literal : number | string | char_constant
|
||||
|
||||
number : ('1' - '9') digit* decimal_suffix
|
||||
| '.' digit+ [exponent] [float_suffix]
|
||||
| '0' (('x' | 'X') hexdigit+ | octal+) decimal_suffix
|
||||
;
|
||||
decimal_suffix : ('.' digit* [exponent] [float_suffix])
|
||||
| 'l' | 'L'
|
||||
| /* nothing */
|
||||
;
|
||||
exponent : ('e' | 'E') ['+' | '-'] digit+
|
||||
float_suffix : 'f' | 'F' | 'd' | 'D'
|
||||
|
||||
string : '"' string_char* '"' { issue_token(ELX_STRING); }
|
||||
string_char : '\1' -> '"' | '"' <-> '\\' | '\\' <- '\377' | '\\' '\1' - '\377'
|
||||
|
||||
char_constant : '\'' one_char '\''
|
||||
one_char : '\1' -> '\'' | '\'' <-> '\\' | '\\' <- '\377' | '\\' '\1' - '\377'
|
||||
|
||||
comment : ( "//" line_comment_char* '\n'
|
||||
| "/*" (block_comment_char | '*' block_non_term_char)* "*/"
|
||||
) { issue_token(ELX_COMMENT); }
|
||||
;
|
||||
line_comment_char : '\1' -> '\n' | '\n' <- '\377'
|
||||
block_comment_char : '\1' -> '*' | '*' <- '\377'
|
||||
block_non_term_char : '\1' -> '/' | '/' <- '\377'
|
||||
|
||||
t_operator : "<<" | ">>" | ">>>"
|
||||
| ">=" | "<=" | "==" | "!=" | "&&" | "||"
|
||||
| "*=" | "%=" | "+=" | "-=" | "<<=" | ">>="
|
||||
| ">>>=" | "&=" | "^=" | "|="
|
||||
| '<' | '>' | '%' | '^' | '&' | '|'
|
||||
;
|
||||
t_inc_dec : "++" | "--"
|
||||
|
||||
/* note: could not use ws* ; the '[' form would only reduce on $end
|
||||
rather than "any" character. that meant we could not recognize '['
|
||||
within the program text. separating out the cases Does The Right
|
||||
Thing */
|
||||
t_bracket : '[' { return '['; }
|
||||
| '[' ']' { return TK_DIM; }
|
||||
| '[' ws+ ']' { return TK_DIM; }
|
||||
;
|
||||
|
||||
t_chars : ',' | ';' | '.' | '{' | '}' | '=' | '(' | ')' | ':'
|
||||
| ']' | '!' | '~' | '+' | '-' | '*' | '?'
|
||||
;
|
||||
|
||||
ws : pure_ws | comment
|
||||
|
||||
pure_ws : ' ' | '\t' | '\n' | '\f'
|
||||
|
||||
%%
|
||||
|
||||
static int lookup(void)
|
||||
{
|
||||
int kw = KR_find_keyword(identifier, idlen);
|
||||
|
||||
if (kw == KR__not_found)
|
||||
{
|
||||
/* terminate so user can grab an identifier string */
|
||||
identifier[idlen] = '\0';
|
||||
return TK_IDENTIFIER;
|
||||
}
|
||||
|
||||
issue_token(ELX_KEYWORD);
|
||||
return kw;
|
||||
}
|
||||
|
||||
const char *get_identifier(void)
|
||||
{
|
||||
return identifier;
|
||||
}
|
458
elemx/java/java.y
Normal file
458
elemx/java/java.y
Normal file
@@ -0,0 +1,458 @@
|
||||
%token KR_abstract
|
||||
%token KR_boolean KR_break KR_byte /* KR_byvalue */
|
||||
%token KR_case /* KR_cast */ KR_catch KR_char KR_class /* KR_const */ KR_continue
|
||||
%token KR_default KR_do KR_double
|
||||
%token KR_else KR_extends
|
||||
%token KR_false KR_final KR_finally KR_float KR_for /* KR_future */
|
||||
/* %token KR_generic KR_goto */
|
||||
%token KR_if KR_implements KR_import /* KR_inner */ KR_instanceof KR_int KR_interface
|
||||
%token KR_long
|
||||
%token KR_native KR_new KR_null
|
||||
/* %token KR_operator KR_outer */
|
||||
%token KR_package KR_private KR_protected KR_public
|
||||
%token /* KR_rest */ KR_return
|
||||
%token KR_short KR_static KR_super KR_switch KR_synchronized
|
||||
%token KR_this KR_throw KR_throws KR_transient KR_true KR_try
|
||||
%token /* KR_var */ KR_void KR_volatile
|
||||
%token KR_while
|
||||
|
||||
%token TK_OP_ASSIGN TK_OPERATOR TK_IDENTIFIER TK_LITERAL
|
||||
%token TK_DIM TK_INC_DEC
|
||||
|
||||
%start CompilationUnit
|
||||
|
||||
/* the standard if/then/else conflict */
|
||||
/* %expect 1 */
|
||||
|
||||
%{
|
||||
#include "elx.h"
|
||||
|
||||
void yyerror(const char *msg);
|
||||
int yylex(void);
|
||||
|
||||
/* ### should come from an elx-python.h or something */
|
||||
void issue_token(char which);
|
||||
%}
|
||||
|
||||
%export {
|
||||
/* the main parsing function */
|
||||
int yyparse(void);
|
||||
|
||||
/* need to define the 'not found' in addition to the regular keywords */
|
||||
#define KR__not_found 0
|
||||
}
|
||||
|
||||
%%
|
||||
|
||||
TypeSpecifier
|
||||
: TypeName
|
||||
| TypeNameDims
|
||||
;
|
||||
|
||||
TypeNameDims
|
||||
: TypeName TK_DIM+
|
||||
;
|
||||
|
||||
TypeNameDot
|
||||
: NamePeriod
|
||||
| PrimitiveType '.'
|
||||
;
|
||||
|
||||
TypeName
|
||||
: PrimitiveType
|
||||
| NamePeriod TK_IDENTIFIER
|
||||
| TK_IDENTIFIER
|
||||
;
|
||||
|
||||
NamePeriod
|
||||
: TK_IDENTIFIER '.'
|
||||
| NamePeriod TK_IDENTIFIER '.'
|
||||
;
|
||||
|
||||
TypeNameList
|
||||
: TypeName / ','
|
||||
;
|
||||
|
||||
PrimitiveType
|
||||
: KR_boolean
|
||||
| KR_byte
|
||||
| KR_char
|
||||
| KR_double
|
||||
| KR_float
|
||||
| KR_int
|
||||
| KR_long
|
||||
| KR_short
|
||||
| KR_void
|
||||
;
|
||||
|
||||
CompilationUnit
|
||||
: PackageStatement [ImportStatements] [TypeDeclarations]
|
||||
| ImportStatements [TypeDeclarations]
|
||||
| TypeDeclarations
|
||||
;
|
||||
|
||||
PackageStatement
|
||||
: KR_package (TK_IDENTIFIER / '.') ';'
|
||||
;
|
||||
|
||||
TypeDeclarations
|
||||
: TypeDeclaration+
|
||||
;
|
||||
|
||||
TypeDeclaration
|
||||
: ClassDeclaration
|
||||
| InterfaceDeclaration
|
||||
;
|
||||
|
||||
ImportStatements
|
||||
: ImportStatement+
|
||||
;
|
||||
|
||||
ImportStatement
|
||||
: KR_import TK_IDENTIFIER ('.' TK_IDENTIFIER)* [".*"] ';'
|
||||
;
|
||||
/*
|
||||
QualifiedName
|
||||
: TK_IDENTIFIER / '.'
|
||||
;
|
||||
*/
|
||||
ClassDeclaration
|
||||
: [Modifiers] KR_class TK_IDENTIFIER [Super] [Interfaces] ClassBody
|
||||
;
|
||||
|
||||
Modifiers
|
||||
: Modifier+
|
||||
;
|
||||
|
||||
Modifier
|
||||
: KR_abstract
|
||||
| KR_final
|
||||
| KR_public
|
||||
| KR_protected
|
||||
| KR_private
|
||||
| KR_static
|
||||
| KR_transient
|
||||
| KR_volatile
|
||||
| KR_native
|
||||
| KR_synchronized
|
||||
;
|
||||
|
||||
Super
|
||||
: KR_extends TypeNameList
|
||||
;
|
||||
|
||||
Interfaces
|
||||
: KR_implements TypeNameList
|
||||
;
|
||||
|
||||
ClassBody
|
||||
: '{' FieldDeclaration* '}'
|
||||
;
|
||||
|
||||
FieldDeclaration
|
||||
: FieldVariableDeclaration
|
||||
| MethodDeclaration
|
||||
| ConstructorDeclaration
|
||||
| StaticInitializer
|
||||
;
|
||||
|
||||
FieldVariableDeclaration
|
||||
: [Modifiers] TypeSpecifier VariableDeclarators ';'
|
||||
;
|
||||
|
||||
VariableDeclarators
|
||||
: VariableDeclarator / ','
|
||||
;
|
||||
|
||||
VariableDeclarator
|
||||
: DeclaratorName ['=' VariableInitializer]
|
||||
;
|
||||
|
||||
VariableInitializer
|
||||
: Expression
|
||||
| '{' [ArrayInitializers] '}'
|
||||
;
|
||||
|
||||
ArrayInitializers
|
||||
: VariableInitializer ( ',' [VariableInitializer] )*
|
||||
;
|
||||
|
||||
MethodDeclaration
|
||||
: [Modifiers] TypeSpecifier MethodDeclarator [Throws] MethodBody
|
||||
;
|
||||
|
||||
MethodDeclarator
|
||||
: DeclaratorName '(' [ParameterList] ')' TK_DIM*
|
||||
;
|
||||
|
||||
ParameterList
|
||||
: Parameter / ','
|
||||
;
|
||||
|
||||
Parameter
|
||||
: TypeSpecifier DeclaratorName
|
||||
;
|
||||
|
||||
DeclaratorName
|
||||
: TK_IDENTIFIER TK_DIM*
|
||||
;
|
||||
|
||||
Throws
|
||||
: KR_throws TypeNameList
|
||||
;
|
||||
|
||||
MethodBody
|
||||
: Block
|
||||
| ';'
|
||||
;
|
||||
|
||||
ConstructorDeclaration
|
||||
: [Modifiers] ConstructorDeclarator [Throws] Block
|
||||
;
|
||||
|
||||
ConstructorDeclarator
|
||||
: TypeName '(' [ParameterList] ')'
|
||||
;
|
||||
|
||||
StaticInitializer
|
||||
: KR_static Block
|
||||
;
|
||||
|
||||
InterfaceDeclaration
|
||||
: [Modifiers] KR_interface TK_IDENTIFIER [ExtendsInterfaces] InterfaceBody
|
||||
;
|
||||
|
||||
ExtendsInterfaces
|
||||
: KR_extends TypeNameList
|
||||
;
|
||||
|
||||
InterfaceBody
|
||||
: '{' FieldDeclaration+ '}'
|
||||
;
|
||||
|
||||
Block
|
||||
: '{' LocalVariableDeclarationOrStatement* '}'
|
||||
;
|
||||
|
||||
LocalVariableDeclarationOrStatement
|
||||
: LocalVariableDeclarationStatement
|
||||
| Statement
|
||||
;
|
||||
|
||||
LocalVariableDeclarationStatement
|
||||
: TypeSpecifier VariableDeclarators ';'
|
||||
;
|
||||
|
||||
Statement
|
||||
: EmptyStatement
|
||||
| LabeledStatement
|
||||
| ExpressionStatement ';'
|
||||
| SelectionStatement
|
||||
| IterationStatement
|
||||
| JumpStatement
|
||||
| GuardingStatement
|
||||
| Block
|
||||
;
|
||||
|
||||
EmptyStatement
|
||||
: ';'
|
||||
;
|
||||
|
||||
LabeledStatement
|
||||
: TK_IDENTIFIER ':' LocalVariableDeclarationOrStatement
|
||||
| KR_case ConstantExpression ':' LocalVariableDeclarationOrStatement
|
||||
| KR_default ':' LocalVariableDeclarationOrStatement
|
||||
;
|
||||
|
||||
ExpressionStatement
|
||||
: Expression
|
||||
;
|
||||
|
||||
SelectionStatement
|
||||
: KR_if '(' Expression ')' Statement [KR_else Statement]
|
||||
| KR_switch '(' Expression ')' Block
|
||||
;
|
||||
|
||||
IterationStatement
|
||||
: KR_while '(' Expression ')' Statement
|
||||
| KR_do Statement KR_while '(' Expression ')' ';'
|
||||
| KR_for '(' ForInit ForExpr [ForIncr] ')' Statement
|
||||
;
|
||||
|
||||
ForInit
|
||||
: ExpressionStatements ';'
|
||||
| LocalVariableDeclarationStatement
|
||||
| ';'
|
||||
;
|
||||
|
||||
ForExpr
|
||||
: [Expression] ';'
|
||||
;
|
||||
|
||||
ForIncr
|
||||
: ExpressionStatements
|
||||
;
|
||||
|
||||
ExpressionStatements
|
||||
: ExpressionStatement / ','
|
||||
;
|
||||
|
||||
JumpStatement
|
||||
: KR_break [TK_IDENTIFIER] ';'
|
||||
| KR_continue [TK_IDENTIFIER] ';'
|
||||
| KR_return [Expression] ';'
|
||||
| KR_throw Expression ';'
|
||||
;
|
||||
|
||||
GuardingStatement
|
||||
: KR_synchronized '(' Expression ')' Statement
|
||||
| KR_try Block Finally
|
||||
| KR_try Block Catches
|
||||
| KR_try Block Catches Finally
|
||||
;
|
||||
|
||||
Catches
|
||||
: Catch+
|
||||
;
|
||||
|
||||
Catch
|
||||
: KR_catch '(' TypeSpecifier [TK_IDENTIFIER] ')' Block
|
||||
;
|
||||
|
||||
Finally
|
||||
: KR_finally Block
|
||||
;
|
||||
|
||||
ArgumentList
|
||||
: Expression / ','
|
||||
;
|
||||
|
||||
PrimaryExpression
|
||||
: TK_LITERAL
|
||||
| KR_true | KR_false
|
||||
| KR_this
|
||||
| KR_null
|
||||
| KR_super
|
||||
| '(' Expression ')'
|
||||
;
|
||||
|
||||
PostfixExpression
|
||||
: PrimaryExpression Trailers
|
||||
|
||||
| TypeName AltTrailers
|
||||
| TypeNameDot FollowsPeriod Trailers
|
||||
| TypeNameDot DimAllocation TypeTrailers
|
||||
| TypeNameDims TypeTrailers
|
||||
|
||||
| KR_new TypeName AltTrailers
|
||||
| KR_new TypeNameDims TypeTrailers
|
||||
;
|
||||
|
||||
DimAllocation
|
||||
: KR_new TypeNameDims
|
||||
;
|
||||
|
||||
PostfixDims
|
||||
: TK_DIM+ '.' KR_class
|
||||
;
|
||||
|
||||
FollowsPeriod
|
||||
: KR_this
|
||||
| KR_class
|
||||
| KR_super
|
||||
| KR_new TypeName NoPeriodsTrailer
|
||||
;
|
||||
|
||||
NoPeriodsTrailer
|
||||
: '[' Expression ']'
|
||||
| '(' [ArgumentList] ')'
|
||||
;
|
||||
|
||||
NoDimTrailer
|
||||
: NoPeriodsTrailer
|
||||
| '.' (FollowsPeriod | TK_IDENTIFIER)
|
||||
;
|
||||
|
||||
AnyTrailer
|
||||
: NoDimTrailer
|
||||
| PostfixDims
|
||||
;
|
||||
|
||||
Trailers
|
||||
: (AnyTrailer | DimAllocation NoDimTrailer)* [DimAllocation]
|
||||
;
|
||||
|
||||
AltTrailers
|
||||
: NoPeriodsTrailer Trailers
|
||||
|
|
||||
;
|
||||
|
||||
TypeTrailers
|
||||
: NoDimTrailer Trailers
|
||||
|
|
||||
;
|
||||
|
||||
CastablePrefixExpression
|
||||
: PostfixExpression [TK_INC_DEC]
|
||||
| LogicalUnaryOperator CastExpression
|
||||
;
|
||||
|
||||
LogicalUnaryOperator
|
||||
: '~'
|
||||
| '!'
|
||||
;
|
||||
|
||||
UnaryOperator
|
||||
: '+'
|
||||
| '-'
|
||||
| TK_INC_DEC
|
||||
;
|
||||
|
||||
/* note: we don't actually have grammar for a cast. we just rely on:
|
||||
(expr) (argument)
|
||||
as our parse match */
|
||||
CastExpression
|
||||
: /* '(' PrimitiveType ')' CastExpression
|
||||
| '(' NamePeriod TK_IDENTIFIER TK_DIM TK_DIM* ')' CastablePrefixExpression
|
||||
| '(' NamePeriod TK_IDENTIFIER ')' CastablePrefixExpression
|
||||
| '(' TK_IDENTIFIER TK_DIM TK_DIM* ')' CastablePrefixExpression
|
||||
| '(' TK_IDENTIFIER ')' CastablePrefixExpression
|
||||
| */ UnaryOperator CastExpression
|
||||
| CastablePrefixExpression
|
||||
;
|
||||
|
||||
BinaryExpression
|
||||
: CastExpression
|
||||
| BinaryExpression TK_BINARY CastExpression
|
||||
| BinaryExpression KR_instanceof TypeSpecifier
|
||||
;
|
||||
|
||||
ConditionalExpression
|
||||
: BinaryExpression
|
||||
| BinaryExpression '?' Expression ':' ConditionalExpression
|
||||
;
|
||||
|
||||
AssignmentExpression
|
||||
: ConditionalExpression [AssignmentOperator AssignmentExpression]
|
||||
;
|
||||
|
||||
AssignmentOperator
|
||||
: '='
|
||||
| TK_OP_ASSIGN
|
||||
;
|
||||
|
||||
Expression
|
||||
: AssignmentExpression
|
||||
;
|
||||
|
||||
ConstantExpression
|
||||
: ConditionalExpression
|
||||
;
|
||||
|
||||
/*
|
||||
TK_OPERATOR : OP_LOR | OP_LAND
|
||||
| OP_EQ | OP_NE | OP_LE | OP_GE
|
||||
| OP_SHL | OP_SHR | OP_SHRR
|
||||
;
|
||||
*/
|
||||
TK_BINARY : TK_OPERATOR | '+' | '-' | '*'
|
150
elemx/python/elx-python.c
Normal file
150
elemx/python/elx-python.c
Normal file
@@ -0,0 +1,150 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "scanner.h"
|
||||
#include "python.h"
|
||||
#include "py_keywords.h"
|
||||
#include "elx.h"
|
||||
|
||||
extern int yylex(void);
|
||||
|
||||
static const char *fname;
|
||||
static int saw_error = 0;
|
||||
static void *scan_ctx;
|
||||
static elx_context_t *ectx;
|
||||
|
||||
void yyerror(const char *msg)
|
||||
{
|
||||
int sl, sc, el, ec;
|
||||
|
||||
scanner_token_linecol(scan_ctx, &sl, &sc, &el, &ec);
|
||||
fprintf(stderr, "%s:%d:%d: parse error: %s\n", fname, sl, sc, msg);
|
||||
saw_error = 1;
|
||||
}
|
||||
|
||||
int reader(void *user_ctx)
|
||||
{
|
||||
FILE *inf = user_ctx;
|
||||
int c = fgetc(inf);
|
||||
|
||||
if (c == EOF)
|
||||
return SCANNER_EOF;
|
||||
|
||||
// printf("char: '%c'\n", c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void issue_token(char which)
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
const char *ident = NULL;
|
||||
|
||||
scanner_token_range(scan_ctx, &start, &end);
|
||||
|
||||
if (ELX_DEFINES_SYM(which))
|
||||
{
|
||||
int length;
|
||||
|
||||
scanner_identifier(scan_ctx, &ident, &length);
|
||||
}
|
||||
|
||||
elx_issue_token(ectx, which, start, end - start + 1, ident);
|
||||
}
|
||||
|
||||
int yylex(void)
|
||||
{
|
||||
int v;
|
||||
|
||||
do {
|
||||
v = scanner_get_token(scan_ctx);
|
||||
|
||||
if (v == TK_COMMENT)
|
||||
{
|
||||
issue_token(ELX_COMMENT);
|
||||
}
|
||||
} while (v == TK_COMMENT);
|
||||
|
||||
/* is this identifier a keyword? */
|
||||
if (v == TK_IDENTIFIER)
|
||||
{
|
||||
const char *ident;
|
||||
int length;
|
||||
int kw;
|
||||
|
||||
scanner_identifier(scan_ctx, &ident, &length);
|
||||
#if 0
|
||||
printf("id=%s\n", ident);
|
||||
#endif
|
||||
|
||||
kw = KR_find_keyword(ident, length);
|
||||
if (kw != KR__not_found)
|
||||
{
|
||||
v = kw;
|
||||
issue_token(ELX_KEYWORD);
|
||||
}
|
||||
}
|
||||
else if (v == TK_STRING)
|
||||
{
|
||||
issue_token(ELX_STRING);
|
||||
}
|
||||
|
||||
// printf("token=%d\n", v);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SCANNER
|
||||
|
||||
void gen_scan_tokens(void)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
int v = scanner_get_token(scan_ctx);
|
||||
int sl, sc, el, ec;
|
||||
|
||||
scanner_token_linecol(scan_ctx, &sl, &sc, &el, &ec);
|
||||
if (v == TK_NEWLINE)
|
||||
printf("%d,%d: NEWLINE\n", sl, sc);
|
||||
else if (v == TK_INDENT)
|
||||
printf("%d,%d: INDENT\n", el, ec);
|
||||
else if (v == TK_DEDENT)
|
||||
printf("%d,%d: DEDENT\n", el, ec);
|
||||
else
|
||||
printf("%d,%d-%d,%d: %d\n", sl, sc, el, ec, v);
|
||||
|
||||
/* end of parse? */
|
||||
if (v <= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DEBUG_SCANNER */
|
||||
|
||||
static void gen_elx_tokens(void)
|
||||
{
|
||||
/* ### what to do with the result? should have seen/set saw_error */
|
||||
(void) yyparse();
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
ectx = elx_process_args(argc, argv);
|
||||
elx_open_files(ectx);
|
||||
|
||||
scan_ctx = scanner_begin(reader, ectx->input_fp);
|
||||
|
||||
#ifdef DEBUG_SCANNER
|
||||
gen_scan_tokens();
|
||||
#else
|
||||
gen_elx_tokens();
|
||||
#endif
|
||||
|
||||
scanner_end(scan_ctx);
|
||||
elx_close_files(ectx);
|
||||
|
||||
if (saw_error)
|
||||
return EXIT_FAILURE;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
36
elemx/python/py_keywords.shilka
Normal file
36
elemx/python/py_keywords.shilka
Normal file
@@ -0,0 +1,36 @@
|
||||
%local {
|
||||
/* get the KR_* values */
|
||||
#include "python.h"
|
||||
}
|
||||
|
||||
%%
|
||||
and
|
||||
/* as */
|
||||
assert
|
||||
break
|
||||
class
|
||||
continue
|
||||
def
|
||||
del
|
||||
elif
|
||||
else
|
||||
except
|
||||
exec
|
||||
finally
|
||||
for
|
||||
from
|
||||
global
|
||||
if
|
||||
import
|
||||
in
|
||||
is
|
||||
lambda
|
||||
not
|
||||
or
|
||||
pass
|
||||
print
|
||||
raise
|
||||
return
|
||||
try
|
||||
while
|
||||
yield
|
135
elemx/python/python.y
Normal file
135
elemx/python/python.y
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
%token TK_COMMENT TK_IDENTIFIER TK_NUMBER
|
||||
%token TK_OPERATOR TK_STRING
|
||||
%token TK_INDENT TK_DEDENT TK_NEWLINE
|
||||
|
||||
%token KR_and KR_assert KR_break KR_class KR_continue KR_def
|
||||
%token KR_del KR_elif KR_else KR_except KR_exec KR_finally
|
||||
%token KR_for KR_from KR_global KR_if KR_import KR_in KR_is
|
||||
%token KR_lambda KR_not KR_or KR_pass KR_print KR_raise
|
||||
%token KR_return KR_try KR_while KR_yield
|
||||
|
||||
%start file_input
|
||||
|
||||
%{
|
||||
#include "elx.h"
|
||||
|
||||
void yyerror(const char *msg);
|
||||
int yylex(void);
|
||||
|
||||
/* ### should come from an elx-python.h or something */
|
||||
void issue_token(char which);
|
||||
%}
|
||||
|
||||
%export {
|
||||
/* the main parsing function */
|
||||
int yyparse(void);
|
||||
|
||||
/* need to define the 'not found' in addition to the regular keywords */
|
||||
#define KR__not_found 0
|
||||
}
|
||||
|
||||
%%
|
||||
|
||||
file_input: (TK_NEWLINE | stmt)*
|
||||
|
||||
NAME: TK_IDENTIFIER
|
||||
|
||||
funcdef: KR_def NAME { issue_token(ELX_LOCAL_FDEF); } parameters ':' suite
|
||||
parameters: '(' [varargslist] ')'
|
||||
varargslist: paramdef (',' paramdef)* [',' [varargsdef]]
|
||||
| varargsdef
|
||||
;
|
||||
/* the TK_OPERATOR represents '*' or '**' */
|
||||
varargsdef: TK_OPERATOR NAME [',' TK_OPERATOR NAME]
|
||||
paramdef: fpdef [TK_OPERATOR test]
|
||||
fpdef: NAME | '(' fplist ')'
|
||||
fplist: fpdef (',' fpdef)* [',']
|
||||
|
||||
stmt: simple_stmt | compound_stmt
|
||||
simple_stmt: small_stmt (';' small_stmt)* [';'] TK_NEWLINE
|
||||
small_stmt: expr_stmt | print_stmt | raise_stmt
|
||||
| import_stmt | global_stmt | exec_stmt | assert_stmt
|
||||
| KR_del exprlist
|
||||
| KR_pass
|
||||
| KR_break
|
||||
| KR_continue
|
||||
| KR_return [testlist]
|
||||
| KR_yield testlist
|
||||
;
|
||||
|
||||
/* expr_stmt is normally assignment, which we get thru TK_OPERATOR in 'expr' */
|
||||
expr_stmt: testlist
|
||||
|
||||
/* a print normally allows '>> test'; since that is a TK_OPERATOR, we
|
||||
get it as part of 'factor'. this rule also allows for a trailing
|
||||
comma in '>> test,' which the normal print doesn't */
|
||||
print_stmt: KR_print [test (',' test)* [',']]
|
||||
|
||||
raise_stmt: KR_raise [test [',' test [',' test]]]
|
||||
|
||||
/* the TK_OPERATOR represents '*' */
|
||||
import_stmt: KR_import dotted_as_name (',' dotted_as_name)*
|
||||
| KR_from dotted_name KR_import (TK_OPERATOR | import_as_name (',' import_as_name)*)
|
||||
import_as_name: NAME [NAME NAME]
|
||||
dotted_as_name: dotted_name [NAME NAME]
|
||||
dotted_name: NAME ('.' NAME)*
|
||||
global_stmt: KR_global NAME (',' NAME)*
|
||||
exec_stmt: KR_exec expr [KR_in test [',' test]]
|
||||
assert_stmt: KR_assert test [',' test]
|
||||
|
||||
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef
|
||||
if_stmt: KR_if test ':' suite (KR_elif test ':' suite)* [KR_else ':' suite]
|
||||
while_stmt: KR_while test ':' suite [KR_else ':' suite]
|
||||
for_stmt: KR_for exprlist KR_in testlist ':' suite [KR_else ':' suite]
|
||||
try_stmt: KR_try ':' suite (except_clause ':' suite)+
|
||||
[KR_else ':' suite] | KR_try ':' suite KR_finally ':' suite
|
||||
/* NB compile.c makes sure that the default except clause is last */
|
||||
except_clause: KR_except [test [',' test]]
|
||||
suite: simple_stmt | TK_NEWLINE TK_INDENT stmt+ TK_DEDENT
|
||||
|
||||
test: test_factor (test_op test_factor | KR_is [KR_not] factor)*
|
||||
[TK_OPERATOR lambdef] | lambdef
|
||||
test_op: bin_op | KR_in
|
||||
test_factor: KR_not* factor
|
||||
|
||||
expr: factor (expr_op factor)*
|
||||
expr_op: bin_op | KR_is [KR_not]
|
||||
|
||||
factor: TK_OPERATOR* atom trailer*
|
||||
|
||||
bin_op: TK_OPERATOR | KR_or | KR_and | KR_not KR_in
|
||||
|
||||
atom: '(' [testlist] ')' | '[' [listmaker] ']' | '{' [dictmaker] '}'
|
||||
| '`' testlist_no_trailing '`' | TK_IDENTIFIER | TK_NUMBER | TK_STRING+
|
||||
listmaker: test ( list_for | (',' test)* [','] )
|
||||
lambdef: KR_lambda [varargslist] ':' test
|
||||
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
|
||||
subscriptlist: subscript (',' subscript)* [',']
|
||||
subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop]
|
||||
sliceop: ':' [test]
|
||||
exprlist: expr (',' expr)* [',']
|
||||
testlist: test (',' test)* [',']
|
||||
testlist_no_trailing: test (',' test)*
|
||||
testlist_safe: test [(',' test)+ [',']] /* doesn't match: test, */
|
||||
dictmaker: test ':' test (',' test ':' test)* [',']
|
||||
|
||||
classdef: KR_class NAME ['(' testlist ')'] ':' suite
|
||||
|
||||
/* arguments are normally 'keyword = test; since '=' is TK_OPERATOR, we
|
||||
match keyword arguments as part of 'test' (in 'expr').
|
||||
|
||||
the vararg portion is normally '* test' or '** test'; since '*' and
|
||||
'**' are TK_OPERATOR, we match varargs as part of 'test' (in
|
||||
'factor')
|
||||
|
||||
thus, all argument forms are simply 'test'
|
||||
|
||||
varargs does not normally allow a trailing comma, but we can
|
||||
simplify things and allow a match
|
||||
*/
|
||||
arglist: test (',' test)* [',']
|
||||
|
||||
list_iter: list_for | list_if
|
||||
list_for: KR_for exprlist KR_in testlist_safe [list_iter]
|
||||
list_if: KR_if test [list_iter]
|
523
elemx/python/scanner.c
Normal file
523
elemx/python/scanner.c
Normal file
@@ -0,0 +1,523 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "python.h" /* get the TK_ values */
|
||||
#include "scanner.h"
|
||||
|
||||
#define SCANNER_EMPTY (SCANNER_EOF - 1) /* -2 */
|
||||
#define SCANNER_TABSIZE 8
|
||||
#define SCANNER_MAXINDENT 100
|
||||
#define SCANNER_MAXIDLEN 200
|
||||
|
||||
typedef struct
|
||||
{
|
||||
get_char_t getfunc;
|
||||
void *user_ctx;
|
||||
|
||||
char saved;
|
||||
int was_newline; /* was previous character a newline? */
|
||||
|
||||
int start; /* start position of last token returned */
|
||||
int start_col;
|
||||
int start_line;
|
||||
|
||||
int fpos; /* file position */
|
||||
int lineno; /* file line number */
|
||||
int line_pos; /* file position of current line's first char */
|
||||
|
||||
int nesting_level;
|
||||
|
||||
int indent; /* which indent */
|
||||
int indents[SCANNER_MAXINDENT]; /* the set of indents */
|
||||
|
||||
int dedent_count; /* how many DEDENTs to issue */
|
||||
|
||||
int skip_newline; /* skip the newline after a blank_line + comment */
|
||||
|
||||
int idlen;
|
||||
char identifier[SCANNER_MAXIDLEN]; /* accumulated identifier */
|
||||
|
||||
} scanner_ctx;
|
||||
|
||||
|
||||
static int next_char(scanner_ctx *ctx)
|
||||
{
|
||||
int c;
|
||||
|
||||
++ctx->fpos;
|
||||
|
||||
if (ctx->saved == SCANNER_EMPTY)
|
||||
{
|
||||
return (*ctx->getfunc)(ctx->user_ctx);
|
||||
}
|
||||
|
||||
c = ctx->saved;
|
||||
ctx->saved = SCANNER_EMPTY;
|
||||
return c;
|
||||
}
|
||||
|
||||
static void backup_char(scanner_ctx *ctx, int c)
|
||||
{
|
||||
assert(ctx->saved == SCANNER_EMPTY);
|
||||
ctx->saved = c;
|
||||
ctx->was_newline = 0; /* we may have put it back */
|
||||
--ctx->fpos;
|
||||
}
|
||||
|
||||
/* called to note that we've moved on to another line */
|
||||
static void on_next_line(scanner_ctx *ctx)
|
||||
{
|
||||
ctx->line_pos = ctx->fpos;
|
||||
++ctx->lineno;
|
||||
}
|
||||
|
||||
void *scanner_begin(get_char_t getfunc, void *user_ctx)
|
||||
{
|
||||
scanner_ctx *ctx = malloc(sizeof(*ctx));
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->getfunc = getfunc;
|
||||
ctx->user_ctx = user_ctx;
|
||||
ctx->saved = SCANNER_EMPTY;
|
||||
ctx->lineno = 1;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int scanner_get_token(void *opaque_ctx)
|
||||
{
|
||||
scanner_ctx *ctx = opaque_ctx;
|
||||
int c;
|
||||
int c2;
|
||||
int blank_line;
|
||||
|
||||
if (ctx->dedent_count)
|
||||
{
|
||||
--ctx->dedent_count;
|
||||
return TK_DEDENT;
|
||||
}
|
||||
|
||||
nextline:
|
||||
blank_line = 0;
|
||||
/* if we're at the start of the line, then get the indentation level */
|
||||
if (ctx->fpos == ctx->line_pos)
|
||||
{
|
||||
int col = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (c == ' ')
|
||||
++col;
|
||||
else if (c == '\t')
|
||||
col = (col / SCANNER_TABSIZE + 1) * SCANNER_TABSIZE;
|
||||
else if (c == '\f') /* ^L / formfeed */
|
||||
col = 0;
|
||||
else
|
||||
break;
|
||||
}
|
||||
backup_char(ctx, c);
|
||||
|
||||
if (c == '#' || c == '\n')
|
||||
{
|
||||
/* this is a "blank" line and doesn't count towards indentation,
|
||||
and it doesn't produce NEWLINE tokens */
|
||||
blank_line = 1;
|
||||
}
|
||||
|
||||
/* if it isn't blank, and we aren't inside nesting expressions, then
|
||||
we need to handle INDENT/DEDENT */
|
||||
if (!blank_line && ctx->nesting_level == 0)
|
||||
{
|
||||
int last_indent = ctx->indents[ctx->indent];
|
||||
|
||||
if (col == last_indent)
|
||||
{
|
||||
/* no change */
|
||||
}
|
||||
else if (col > last_indent)
|
||||
{
|
||||
if (ctx->indent == SCANNER_MAXINDENT - 1)
|
||||
{
|
||||
/* oops. too deep. */
|
||||
return E_TOO_MANY_INDENTS;
|
||||
}
|
||||
ctx->indents[++ctx->indent] = col;
|
||||
return TK_INDENT;
|
||||
}
|
||||
else /* col < last_indent */
|
||||
{
|
||||
/* find the previous indentation that matches this one */
|
||||
while (ctx->indent > 0
|
||||
&& col < ctx->indents[ctx->indent])
|
||||
{
|
||||
++ctx->dedent_count;
|
||||
--ctx->indent;
|
||||
}
|
||||
if (col != ctx->indents[ctx->indent])
|
||||
{
|
||||
/* oops. dedent doesn't match any indent. */
|
||||
return E_DEDENT_MISMATCH;
|
||||
}
|
||||
|
||||
/* deliver one dedent now */
|
||||
--ctx->dedent_count;
|
||||
return TK_DEDENT;
|
||||
}
|
||||
} /* !blank_line ... */
|
||||
} /* start of line */
|
||||
|
||||
/* start here if we see a line continuation */
|
||||
read_more:
|
||||
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (c == ' ' || c == '\t' || c == '\f');
|
||||
|
||||
/* here is where the token starts */
|
||||
ctx->start = ctx->fpos;
|
||||
ctx->start_line = ctx->lineno;
|
||||
ctx->start_col = ctx->fpos - ctx->line_pos;
|
||||
|
||||
/* comment? */
|
||||
if (c == '#')
|
||||
{
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (c != SCANNER_EOF && c != '\n');
|
||||
|
||||
/* if we are suppressing newlines because this is a blank line, then
|
||||
leave a marker to skip the newline, next time through. */
|
||||
if (blank_line && c == '\n')
|
||||
ctx->skip_newline = 1;
|
||||
|
||||
/* put back whatever we sucked up */
|
||||
backup_char(ctx, c);
|
||||
|
||||
return TK_COMMENT;
|
||||
}
|
||||
|
||||
/* Look for an identifier */
|
||||
if (isalpha(c) || c == '_')
|
||||
{
|
||||
ctx->idlen = 0;
|
||||
|
||||
/* is this actually a string? */
|
||||
if (c == 'r' || c == 'R')
|
||||
{
|
||||
ctx->identifier[ctx->idlen++] = c;
|
||||
c = next_char(ctx);
|
||||
if (c == '"' || c == '\'')
|
||||
goto parse_string;
|
||||
}
|
||||
else if (c == 'u' || c == 'U')
|
||||
{
|
||||
ctx->identifier[ctx->idlen++] = c;
|
||||
c = next_char(ctx);
|
||||
if (c == 'r' || c == 'R')
|
||||
{
|
||||
ctx->identifier[ctx->idlen++] = c;
|
||||
c = next_char(ctx);
|
||||
}
|
||||
if (c == '"' || c == '\'')
|
||||
goto parse_string;
|
||||
}
|
||||
|
||||
while (isalnum(c) || c == '_') {
|
||||
/* store the character if there is room for it, and room left
|
||||
for a null-terminator. */
|
||||
if (ctx->idlen < SCANNER_MAXIDLEN-1)
|
||||
ctx->identifier[ctx->idlen++] = c;
|
||||
c = next_char(ctx);
|
||||
}
|
||||
backup_char(ctx, c);
|
||||
|
||||
/* ### check for a keyword */
|
||||
return TK_IDENTIFIER;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
on_next_line(ctx);
|
||||
|
||||
/* don't report NEWLINE tokens for blank lines or nested exprs */
|
||||
if (blank_line || ctx->nesting_level > 0 || ctx->skip_newline)
|
||||
{
|
||||
ctx->skip_newline = 0;
|
||||
goto nextline;
|
||||
}
|
||||
|
||||
return TK_NEWLINE;
|
||||
}
|
||||
|
||||
if (c == '.')
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (isdigit(c))
|
||||
goto parse_fraction;
|
||||
backup_char(ctx, c);
|
||||
return '.';
|
||||
}
|
||||
|
||||
if (isdigit(c))
|
||||
{
|
||||
if (c == '0')
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (c == 'x' || c == 'X')
|
||||
{
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (isxdigit(c));
|
||||
goto skip_fp;
|
||||
}
|
||||
else if (isdigit(c))
|
||||
{
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (isdigit(c));
|
||||
}
|
||||
if (c == '.')
|
||||
goto parse_fraction;
|
||||
if (c == 'e' || c == 'E')
|
||||
goto parse_exponent;
|
||||
if (c == 'j' || c == 'J')
|
||||
goto parse_imaginary;
|
||||
skip_fp:
|
||||
/* this point: parsed an octal, decimal, or hexadecimal */
|
||||
|
||||
if (c == 'l' || c == 'L')
|
||||
{
|
||||
/* we consumed just enough. stop and return a NUMBER */
|
||||
return TK_NUMBER;
|
||||
}
|
||||
|
||||
/* consumed too much. backup and return a NUMBER */
|
||||
backup_char(ctx, c);
|
||||
return TK_NUMBER;
|
||||
}
|
||||
|
||||
/* decimal number */
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (isdigit(c));
|
||||
|
||||
if (c == 'l' || c == 'L')
|
||||
{
|
||||
/* we consumed just enogh. stop and return a NUMBER */
|
||||
return TK_NUMBER;
|
||||
}
|
||||
|
||||
if (c == '.')
|
||||
{
|
||||
parse_fraction:
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (isdigit(c));
|
||||
}
|
||||
|
||||
if (c == 'e' || c == 'E')
|
||||
{
|
||||
parse_exponent:
|
||||
c = next_char(ctx);
|
||||
if (c == '+' || c == '-')
|
||||
c = next_char(ctx);
|
||||
if (!isdigit(c))
|
||||
{
|
||||
backup_char(ctx, c);
|
||||
return E_BAD_NUMBER;
|
||||
}
|
||||
do {
|
||||
c = next_char(ctx);
|
||||
} while (isdigit(c));
|
||||
}
|
||||
|
||||
if (c == 'j' || c == 'J')
|
||||
{
|
||||
parse_imaginary:
|
||||
c = next_char(ctx);
|
||||
}
|
||||
|
||||
/* one too far. backup and return a NUMBER */
|
||||
backup_char(ctx, c);
|
||||
return TK_NUMBER;
|
||||
|
||||
} /* isdigit */
|
||||
|
||||
parse_string:
|
||||
if (c == '\'' || c == '"')
|
||||
{
|
||||
int second_quote_pos = ctx->fpos + 1;
|
||||
int which_quote = c;
|
||||
int is_triple = 0;
|
||||
int quote_count = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (c == '\n')
|
||||
{
|
||||
on_next_line(ctx);
|
||||
|
||||
if (!is_triple)
|
||||
return E_UNTERM_STRING;
|
||||
quote_count = 0;
|
||||
}
|
||||
else if (c == SCANNER_EOF)
|
||||
{
|
||||
return E_UNTERM_STRING;
|
||||
}
|
||||
else if (c == which_quote)
|
||||
{
|
||||
++quote_count;
|
||||
if (ctx->fpos == second_quote_pos)
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (c == which_quote)
|
||||
{
|
||||
is_triple = 1;
|
||||
quote_count = 0;
|
||||
continue;
|
||||
}
|
||||
/* we just read one past the empty string. back up. */
|
||||
backup_char(ctx, c);
|
||||
}
|
||||
|
||||
/* this quote may have terminated the string */
|
||||
if (!is_triple || quote_count == 3)
|
||||
return TK_STRING;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (c == SCANNER_EOF)
|
||||
return E_UNTERM_STRING;
|
||||
if (c == '\n')
|
||||
on_next_line(ctx);
|
||||
quote_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
quote_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/* line continuation */
|
||||
if (c == '\\')
|
||||
{
|
||||
c = next_char(ctx);
|
||||
if (c != '\n')
|
||||
return E_BAD_CONTINUATION;
|
||||
|
||||
on_next_line(ctx);
|
||||
goto read_more;
|
||||
}
|
||||
|
||||
/* look for operators */
|
||||
|
||||
/* the nesting operators */
|
||||
if (c == '(' || c == '[' || c == '{')
|
||||
{
|
||||
++ctx->nesting_level;
|
||||
return c;
|
||||
}
|
||||
if (c == ')' || c == ']' || c == '}')
|
||||
{
|
||||
--ctx->nesting_level;
|
||||
return c;
|
||||
}
|
||||
|
||||
/* look for up-to-3-char ops */
|
||||
if (c == '<' || c == '>' || c == '*' || c == '/')
|
||||
{
|
||||
c2 = next_char(ctx);
|
||||
if (c == c2)
|
||||
{
|
||||
c2 = next_char(ctx);
|
||||
if (c2 != '=')
|
||||
{
|
||||
/* oops. one too far. */
|
||||
backup_char(ctx, c2);
|
||||
}
|
||||
return TK_OPERATOR;
|
||||
}
|
||||
|
||||
if (c == '<' && c2 == '>')
|
||||
return TK_OPERATOR;
|
||||
|
||||
if (c2 != '=')
|
||||
{
|
||||
/* one char too far. */
|
||||
backup_char(ctx, c2);
|
||||
}
|
||||
return TK_OPERATOR;
|
||||
}
|
||||
|
||||
/* look for 2-char ops */
|
||||
if (c == '=' || c == '!' || c == '+' || c == '-'
|
||||
|| c == '|' || c == '%' || c == '&' || c == '^')
|
||||
{
|
||||
c2 = next_char(ctx);
|
||||
if (c2 == '=')
|
||||
return TK_OPERATOR;
|
||||
|
||||
/* oops. too far. */
|
||||
backup_char(ctx, c2);
|
||||
return TK_OPERATOR;
|
||||
}
|
||||
|
||||
/* ### should all of these return 'c' ? */
|
||||
if (c == ':' || c == ',' || c == ';' || c == '`')
|
||||
return c;
|
||||
|
||||
/* as a unary operator, this must be a TK_OPERATOR */
|
||||
if (c == '~')
|
||||
return TK_OPERATOR;
|
||||
|
||||
/* if we have an EOF, then just return it */
|
||||
if (c == SCANNER_EOF)
|
||||
return SCANNER_EOF;
|
||||
|
||||
/* unknown input */
|
||||
return E_UNKNOWN_TOKEN;
|
||||
}
|
||||
|
||||
void scanner_identifier(void *opaque_ctx, const char **ident, int *len)
|
||||
{
|
||||
scanner_ctx *ctx = opaque_ctx;
|
||||
|
||||
ctx->identifier[ctx->idlen] = '\0';
|
||||
*ident = ctx->identifier;
|
||||
*len = ctx->idlen;
|
||||
}
|
||||
|
||||
void scanner_token_range(void *opaque_ctx, int *start, int *end)
|
||||
{
|
||||
scanner_ctx *ctx = opaque_ctx;
|
||||
|
||||
*start = ctx->start;
|
||||
*end = ctx->fpos;
|
||||
}
|
||||
|
||||
void scanner_token_linecol(void *opaque_ctx,
|
||||
int *sline, int *scol, int *eline, int *ecol)
|
||||
{
|
||||
scanner_ctx *ctx = opaque_ctx;
|
||||
|
||||
*sline = ctx->start_line;
|
||||
*scol = ctx->start_col;
|
||||
|
||||
*eline = ctx->lineno;
|
||||
*ecol = ctx->fpos - ctx->line_pos;
|
||||
}
|
||||
|
||||
void scanner_end(void *ctx)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
42
elemx/python/scanner.h
Normal file
42
elemx/python/scanner.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef SCANNER_H
|
||||
#define SCANNER_H
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
/* constants and errors returned by the scanner */
|
||||
enum
|
||||
{
|
||||
SCANNER_EOF = -1, /* returned by get_char_t and
|
||||
scanner_get_token to symbolize EOF */
|
||||
|
||||
E_TOO_MANY_INDENTS = -100, /* too many indents */
|
||||
E_DEDENT_MISMATCH, /* no matching indent */
|
||||
E_BAD_CONTINUATION, /* character occurred after \ */
|
||||
E_BAD_NUMBER, /* parse error in a number */
|
||||
E_UNKNOWN_TOKEN, /* dunno what we found */
|
||||
E_UNTERM_STRING /* unterminated string constant */
|
||||
};
|
||||
|
||||
typedef int (*get_char_t)(void *user_ctx);
|
||||
|
||||
void *scanner_begin(get_char_t getfunc, void *user_ctx);
|
||||
|
||||
int scanner_get_token(void *ctx);
|
||||
|
||||
void scanner_identifier(void *ctx, const char **ident, int *len);
|
||||
void scanner_token_range(void *ctx, int *start, int *end);
|
||||
void scanner_token_linecol(void *ctx,
|
||||
int *sline, int *scol, int *eline, int *ecol);
|
||||
|
||||
void scanner_end(void *ctx);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* SCANNER_H */
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
10
lib/blame.py
10
lib/blame.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
@@ -32,9 +32,8 @@ import os
|
||||
import re
|
||||
import time
|
||||
import math
|
||||
import cgi
|
||||
import vclib
|
||||
|
||||
import sapi
|
||||
|
||||
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
||||
|
||||
@@ -75,14 +74,15 @@ 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)
|
||||
diff_url = None
|
||||
if item.prev_rev:
|
||||
diff_url = '%sr1=%s&r2=%s' % (self.diff_url, item.prev_rev, item.rev)
|
||||
thisline = link_includes(cgi.escape(item.text), self.repos,
|
||||
thisline = link_includes(sapi.escape(item.text), self.repos,
|
||||
self.path_parts, self.include_url)
|
||||
return _item(text=thisline, line_number=item.line_number,
|
||||
rev=item.rev, prev_rev=item.prev_rev,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
338
lib/config.py
338
lib/config.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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,82 +19,155 @@ import os
|
||||
import string
|
||||
import ConfigParser
|
||||
import fnmatch
|
||||
import vclib
|
||||
import vclib.ccvs
|
||||
import vclib.svn
|
||||
import cvsdb
|
||||
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
# CONFIGURATION
|
||||
# -------------
|
||||
#
|
||||
# There are three forms of configuration:
|
||||
#
|
||||
# 1) edit the viewvc.conf created by the viewvc-install(er)
|
||||
# 2) as (1), but delete all unchanged entries from viewvc.conf
|
||||
# 3) do not use viewvc.conf and just edit the defaults in this file
|
||||
# 1. edit the viewvc.conf created by the viewvc-install(er)
|
||||
# 2. as (1), but delete all unchanged entries from viewvc.conf
|
||||
# 3. do not use viewvc.conf and just edit the defaults in this file
|
||||
#
|
||||
# Most users will want to use (1), but there are slight speed advantages
|
||||
# to the other two options. Note that viewvc.conf values are a bit easier
|
||||
# to work with since it is raw text, rather than python literal values.
|
||||
#
|
||||
#
|
||||
# A WORD ABOUT OPTION LAYERING/OVERRIDES
|
||||
# --------------------------------------
|
||||
#
|
||||
# ViewVC has three "layers" of configuration options:
|
||||
#
|
||||
# 1. base configuration options - very basic configuration bits
|
||||
# found in sections like 'general', 'options', etc.
|
||||
# 2. vhost overrides - these options overlay/override the base
|
||||
# configuration on a per-vhost basis.
|
||||
# 3. root overrides - these options overlay/override the base
|
||||
# configuration and vhost overrides on a per-root basis.
|
||||
#
|
||||
# Here's a diagram of the valid overlays/overrides:
|
||||
#
|
||||
# PER-ROOT PER-VHOST BASE
|
||||
#
|
||||
# ,-----------. ,-----------.
|
||||
# | vhost-*/ | | |
|
||||
# | general | --> | general |
|
||||
# | | | |
|
||||
# `-----------' `-----------'
|
||||
# ,-----------. ,-----------. ,-----------.
|
||||
# | root-*/ | | vhost-*/ | | |
|
||||
# | options | --> | options | --> | options |
|
||||
# | | | | | |
|
||||
# `-----------' `-----------' `-----------'
|
||||
# ,-----------. ,-----------. ,-----------.
|
||||
# | root-*/ | | vhost-*/ | | |
|
||||
# | templates | --> | templates | --> | templates |
|
||||
# | | | | | |
|
||||
# `-----------' `-----------' `-----------'
|
||||
# ,-----------. ,-----------. ,-----------.
|
||||
# | root-*/ | | vhost-*/ | | |
|
||||
# | utilities | --> | utilities | --> | utilities |
|
||||
# | | | | | |
|
||||
# `-----------' `-----------' `-----------'
|
||||
# ,-----------. ,-----------.
|
||||
# | vhost-*/ | | |
|
||||
# | cvsdb | --> | cvsdb |
|
||||
# | | | |
|
||||
# `-----------' `-----------'
|
||||
# ,-----------. ,-----------. ,-----------.
|
||||
# | root-*/ | | vhost-*/ | | |
|
||||
# | authz-* | --> | authz-* | --> | authz-* |
|
||||
# | | | | | |
|
||||
# `-----------' `-----------' `-----------'
|
||||
# ,-----------.
|
||||
# | |
|
||||
# | vhosts |
|
||||
# | |
|
||||
# `-----------'
|
||||
# ,-----------.
|
||||
# | |
|
||||
# | query |
|
||||
# | |
|
||||
# `-----------'
|
||||
#
|
||||
# ### TODO: Figure out what this all means for the 'kv' stuff.
|
||||
#
|
||||
#########################################################################
|
||||
|
||||
class Config:
|
||||
_sections = ('general', 'utilities', 'options', 'cvsdb', 'templates')
|
||||
_force_multi_value = ('cvs_roots', 'svn_roots', 'languages', 'kv_files',
|
||||
'root_parents', 'allowed_views', 'mime_types_files')
|
||||
_base_sections = (
|
||||
# Base configuration sections.
|
||||
'authz-*',
|
||||
'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',
|
||||
'mime_types_files',
|
||||
'root_parents',
|
||||
'svn_roots',
|
||||
)
|
||||
_allowed_overrides = {
|
||||
# Mapping of override types to allowed overridable sections.
|
||||
'vhost' : ('authz-*',
|
||||
'cvsdb',
|
||||
'general',
|
||||
'options',
|
||||
'templates',
|
||||
'utilities',
|
||||
),
|
||||
'root' : ('authz-*',
|
||||
'options',
|
||||
'templates',
|
||||
'utilities',
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
for section in self._sections:
|
||||
self.root_options_overlayed = 0
|
||||
for section in self._base_sections:
|
||||
if section[-1] == '*':
|
||||
continue
|
||||
setattr(self, section, _sub_config())
|
||||
|
||||
def load_config(self, pathname, vhost=None, rootname=None):
|
||||
def load_config(self, pathname, vhost=None):
|
||||
"""Load the configuration file at PATHNAME, applying configuration
|
||||
settings there as overrides to the built-in default values. If
|
||||
VHOST is provided, also process the configuration overrides
|
||||
specific to that virtual host."""
|
||||
|
||||
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._sections:
|
||||
if self.parser.has_section(section):
|
||||
|
||||
for section in self.parser.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'):
|
||||
self._process_vhost(self.parser, vhost)
|
||||
|
||||
if rootname:
|
||||
self._process_root_options(self.parser, rootname)
|
||||
self.expand_root_parents()
|
||||
cvsdb.setencs(self.options.encodings.split(':'))
|
||||
|
||||
def expand_root_parents(self):
|
||||
"""Expand the configured root parents into individual roots."""
|
||||
|
||||
# Each item in root_parents is a "directory : repo_type" string.
|
||||
for pp in self.general.root_parents:
|
||||
pos = string.rfind(pp, ':')
|
||||
if pos < 0:
|
||||
raise debug.ViewVCException(
|
||||
"The path '%s' in 'root_parents' does not include a "
|
||||
"repository type." % (pp))
|
||||
|
||||
repo_type = string.strip(pp[pos+1:])
|
||||
pp = os.path.normpath(string.strip(pp[:pos]))
|
||||
|
||||
if repo_type == 'cvs':
|
||||
roots = vclib.ccvs.expand_root_parent(pp)
|
||||
if self.options.hide_cvsroot and roots.has_key('CVSROOT'):
|
||||
del roots['CVSROOT']
|
||||
self.general.cvs_roots.update(roots)
|
||||
elif repo_type == 'svn':
|
||||
roots = vclib.svn.expand_root_parent(pp)
|
||||
self.general.svn_roots.update(roots)
|
||||
else:
|
||||
raise debug.ViewVCException(
|
||||
"The path '%s' in 'root_parents' has an unrecognized "
|
||||
"repository type." % (pp))
|
||||
|
||||
def load_kv_files(self, language):
|
||||
"""Process the key/value (kv) files specified in the
|
||||
configuration, merging their values into the configuration as
|
||||
dotted heirarchical items."""
|
||||
|
||||
kv = _sub_config()
|
||||
|
||||
for fname in self.general.kv_files:
|
||||
@@ -107,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):
|
||||
@@ -124,10 +198,12 @@ class Config:
|
||||
return kv
|
||||
|
||||
def path(self, path):
|
||||
"""Return path relative to the config file directory"""
|
||||
"""Return PATH relative to the config file directory."""
|
||||
return os.path.join(self.base, path)
|
||||
|
||||
def _process_section(self, parser, section, subcfg_name):
|
||||
if not hasattr(self, subcfg_name):
|
||||
setattr(self, subcfg_name, _sub_config())
|
||||
sc = getattr(self, subcfg_name)
|
||||
|
||||
for opt in parser.options(section):
|
||||
@@ -140,25 +216,54 @@ class Config:
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
### FIXME: This feels like unnecessary depth of knowledge for a
|
||||
### semi-generic configuration object.
|
||||
if opt == 'cvs_roots' or opt == 'svn_roots':
|
||||
value = _parse_roots(opt, value)
|
||||
|
||||
setattr(sc, opt, value)
|
||||
|
||||
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."""
|
||||
|
||||
for allowed_section in allowed_sections:
|
||||
if allowed_section[-1] == '*':
|
||||
if _startswith(section, allowed_section[:-1]):
|
||||
return 1
|
||||
elif allowed_section == section:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
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
|
||||
the overridden base section name. If it's not an override section
|
||||
at all, return None. And if it's an override section but not an
|
||||
allowed one, raise IllegalOverrideSection."""
|
||||
|
||||
cv = '%s-%s/' % (sectype, secspec)
|
||||
lcv = len(cv)
|
||||
if section[:lcv] != cv:
|
||||
return None
|
||||
base_section = section[lcv:]
|
||||
if self._is_allowed_section(base_section,
|
||||
self._allowed_overrides[sectype]):
|
||||
return base_section
|
||||
raise IllegalOverrideSection(sectype, section)
|
||||
|
||||
def _process_vhost(self, parser, vhost):
|
||||
# find a vhost name for this vhost, if any (if not, we've nothing to do)
|
||||
# Find a vhost name for this VHOST, if any (else, we've nothing to do).
|
||||
canon_vhost = self._find_canon_vhost(parser, vhost)
|
||||
if not canon_vhost:
|
||||
return
|
||||
|
||||
# overlay any option sections associated with this vhost name
|
||||
cv = 'vhost-%s/' % (canon_vhost)
|
||||
lcv = len(cv)
|
||||
# Overlay any option sections associated with this vhost name.
|
||||
for section in parser.sections():
|
||||
if section[:lcv] == cv:
|
||||
base_section = section[lcv:]
|
||||
if base_section not in self._sections:
|
||||
raise IllegalOverrideSection('vhost', section)
|
||||
base_section = self._is_allowed_override('vhost', canon_vhost, section)
|
||||
if base_section:
|
||||
self._process_section(parser, section, base_section)
|
||||
|
||||
def _find_canon_vhost(self, parser, vhost):
|
||||
@@ -173,26 +278,29 @@ class Config:
|
||||
|
||||
return None
|
||||
|
||||
def _process_root_options(self, parser, rootname):
|
||||
rn = 'root-%s/' % (rootname)
|
||||
lrn = len(rn)
|
||||
for section in parser.sections():
|
||||
if section[:lrn] == rn:
|
||||
base_section = section[lrn:]
|
||||
if base_section in self._sections:
|
||||
if base_section == 'general':
|
||||
raise IllegalOverrideSection('root', section)
|
||||
self._process_section(parser, section, base_section)
|
||||
elif _startswith(base_section, 'authz-'):
|
||||
pass
|
||||
else:
|
||||
raise IllegalOverrideSection('root', section)
|
||||
|
||||
def overlay_root_options(self, rootname):
|
||||
"Overly per-root options atop the existing option set."
|
||||
"""Overlay per-root options for ROOTNAME atop the existing option
|
||||
set. This is a destructive change to the configuration."""
|
||||
|
||||
did_overlay = 0
|
||||
|
||||
if not self.conf_path:
|
||||
return
|
||||
self._process_root_options(self.parser, rootname)
|
||||
|
||||
for section in self.parser.sections():
|
||||
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
|
||||
# per-root options.
|
||||
assert(self.root_options_overlayed == 0)
|
||||
self._process_section(self.parser, section, base_section)
|
||||
did_overlay = 1
|
||||
|
||||
# If we actually did any overlaying, remember this fact so we
|
||||
# don't do it again later.
|
||||
if did_overlay:
|
||||
self.root_options_overlayed = 1
|
||||
|
||||
def _get_parser_items(self, parser, section):
|
||||
"""Basically implement ConfigParser.items() for pre-Python-2.3 versions."""
|
||||
@@ -203,24 +311,67 @@ class Config:
|
||||
for option in parser.options(section):
|
||||
d[option] = parser.get(section, option)
|
||||
return d.items()
|
||||
|
||||
def get_authorizer_params(self, authorizer, rootname=None):
|
||||
if not self.conf_path:
|
||||
return {}
|
||||
|
||||
def get_authorizer_and_params_hack(self, rootname):
|
||||
"""Return a 2-tuple containing the name and parameters of the
|
||||
authorizer configured for use with ROOTNAME.
|
||||
|
||||
### FIXME: This whole thing is a hack caused by our not being able
|
||||
### to non-destructively overlay root options when trying to do
|
||||
### something like a root listing (which might need to get
|
||||
### different authorizer bits for each and every root in the list).
|
||||
### Until we have a good way to do that, we expose this function,
|
||||
### which assumes that base and per-vhost configuration has been
|
||||
### absorbed into this object and that per-root options have *not*
|
||||
### been overlayed. See issue #371."""
|
||||
|
||||
# We assume that per-root options have *not* been overlayed.
|
||||
assert(self.root_options_overlayed == 0)
|
||||
|
||||
if not self.conf_path:
|
||||
return None, {}
|
||||
|
||||
# Figure out the authorizer by searching first for a per-root
|
||||
# override, then falling back to the base/vhost configuration.
|
||||
authorizer = None
|
||||
root_options_section = 'root-%s/options' % (rootname)
|
||||
if self.parser.has_section(root_options_section) \
|
||||
and self.parser.has_option(root_options_section, 'authorizer'):
|
||||
authorizer = self.parser.get(root_options_section, 'authorizer')
|
||||
if not authorizer:
|
||||
authorizer = self.options.authorizer
|
||||
|
||||
# No authorizer? Get outta here.
|
||||
if not authorizer:
|
||||
return None, {}
|
||||
|
||||
# Dig up the parameters for the authorizer, starting with the
|
||||
# base/vhost items, then overlaying any root-specific ones we find.
|
||||
params = {}
|
||||
authz_section = 'authz-%s' % (authorizer)
|
||||
if hasattr(self, authz_section):
|
||||
sub_config = getattr(self, authz_section)
|
||||
for attr in dir(sub_config):
|
||||
params[attr] = getattr(sub_config, attr)
|
||||
root_authz_section = 'root-%s/authz-%s' % (rootname, authorizer)
|
||||
for section in self.parser.sections():
|
||||
if section == authz_section:
|
||||
if section == root_authz_section:
|
||||
for key, value in self._get_parser_items(self.parser, section):
|
||||
params[key] = value
|
||||
if rootname:
|
||||
root_authz_section = 'root-%s/authz-%s' % (rootname, authorizer)
|
||||
for section in self.parser.sections():
|
||||
if section == root_authz_section:
|
||||
for key, value in self._get_parser_items(self.parser, section):
|
||||
params[key] = value
|
||||
params['__config'] = self
|
||||
return authorizer, params
|
||||
|
||||
def get_authorizer_params(self, authorizer=None):
|
||||
"""Return a dictionary of parameter names and values which belong
|
||||
to the configured authorizer (or AUTHORIZER, if provided)."""
|
||||
params = {}
|
||||
if authorizer is None:
|
||||
authorizer = self.options.authorizer
|
||||
if authorizer:
|
||||
authz_section = 'authz-%s' % (self.options.authorizer)
|
||||
if hasattr(self, authz_section):
|
||||
sub_config = getattr(self, authz_section)
|
||||
for attr in dir(sub_config):
|
||||
params[attr] = getattr(sub_config, attr)
|
||||
return params
|
||||
|
||||
def set_defaults(self):
|
||||
@@ -249,7 +400,9 @@ 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
|
||||
@@ -271,23 +424,22 @@ 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 = 0
|
||||
self.options.cross_copies = 1
|
||||
self.options.use_localtime = 0
|
||||
self.options.short_log_len = 80
|
||||
self.options.enable_syntax_coloration = 1
|
||||
self.options.tabsize = 8
|
||||
self.options.detect_encoding = 0
|
||||
self.options.use_cvsgraph = 0
|
||||
self.options.cvsgraph_conf = "cvsgraph.conf"
|
||||
self.options.allowed_cvsgraph_useropts = []
|
||||
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.options.cvs_ondisk_charset = 'cp1251'
|
||||
self.options.binary_mime_re = '^(?!text/|.*\Wxml)'
|
||||
self.options.encodings = 'utf-8:cp1251:iso-8859-1'
|
||||
|
||||
self.templates.diff = None
|
||||
self.templates.directory = None
|
||||
@@ -303,7 +455,6 @@ class Config:
|
||||
self.cvsdb.enabled = 0
|
||||
self.cvsdb.host = ''
|
||||
self.cvsdb.port = 3306
|
||||
self.cvsdb.socket = ''
|
||||
self.cvsdb.database_name = ''
|
||||
self.cvsdb.user = ''
|
||||
self.cvsdb.passwd = ''
|
||||
@@ -312,8 +463,9 @@ class Config:
|
||||
self.cvsdb.row_limit = 1000
|
||||
self.cvsdb.rss_row_limit = 100
|
||||
self.cvsdb.check_database_for_root = 0
|
||||
self.cvsdb.fulltext_min_relevance = 0.2
|
||||
|
||||
self.query.viewvc_base_url = None
|
||||
|
||||
def _startswith(somestr, substr):
|
||||
return somestr[:len(substr)] == substr
|
||||
|
||||
|
252
lib/cvsdb.py
252
lib/cvsdb.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -37,32 +37,14 @@ error = "cvsdb error"
|
||||
## defined to actually be complete; it should run well off of any DBI 2.0
|
||||
## complient database interface
|
||||
|
||||
encs = [ "utf-8", "cp1251", "iso-8859-1" ]
|
||||
|
||||
def utf8string(value):
|
||||
for e in encs:
|
||||
try:
|
||||
value = value.decode(e)
|
||||
break
|
||||
except: pass
|
||||
return value.encode("utf-8")
|
||||
|
||||
def setencs(e):
|
||||
global encs
|
||||
encs = e
|
||||
|
||||
class CheckinDatabase:
|
||||
def __init__(self, host, port, socket, user, passwd, database, row_limit, min_relevance, authorizer = None):
|
||||
def __init__(self, host, port, user, passwd, database):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._socket = socket
|
||||
self._user = user
|
||||
self._passwd = passwd
|
||||
self._database = database
|
||||
self._row_limit = row_limit
|
||||
self._version = None
|
||||
self._min_relevance = min_relevance
|
||||
self.authorizer = authorizer
|
||||
|
||||
## database lookup caches
|
||||
self._get_cache = {}
|
||||
@@ -71,7 +53,7 @@ class CheckinDatabase:
|
||||
|
||||
def Connect(self):
|
||||
self.db = dbi.connect(
|
||||
self._host, self._port, self._socket, self._user, self._passwd, self._database)
|
||||
self._host, self._port, self._user, self._passwd, self._database)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute("SET AUTOCOMMIT=1")
|
||||
table_list = self.GetTableList()
|
||||
@@ -84,16 +66,14 @@ class CheckinDatabase:
|
||||
else:
|
||||
self._version = 0
|
||||
if self._version > CURRENT_SCHEMA_VERSION:
|
||||
raise DatabaseVersionError("Database version %d is newer than the "
|
||||
"last version supported by this "
|
||||
"software." % (self._version))
|
||||
raise DatabaseVersionError("Database version %d is newer than the "
|
||||
"last version supported by this "
|
||||
"software." % (self._version))
|
||||
|
||||
def sql_get_id(self, table, column, value, auto_set):
|
||||
value = utf8string(value)
|
||||
|
||||
sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
|
||||
sql_args = (value, )
|
||||
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
@@ -103,7 +83,7 @@ class CheckinDatabase:
|
||||
return None
|
||||
else:
|
||||
return str(int(id))
|
||||
|
||||
|
||||
## insert the new identifier
|
||||
sql = "INSERT INTO %s(%s) VALUES(%%s)" % (table, column)
|
||||
sql_args = (value, )
|
||||
@@ -188,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()
|
||||
@@ -254,17 +237,13 @@ class CheckinDatabase:
|
||||
def GetRepository(self, id):
|
||||
return self.get("repositories", "repository", id)
|
||||
|
||||
def GetRepositoryList(self):
|
||||
return self.get_list("repositories", repository)
|
||||
|
||||
def SQLGetDescriptionID(self, description, auto_set = 1):
|
||||
description = utf8string(description)
|
||||
## lame string hash, blame Netscape -JMP
|
||||
hash = len(description)
|
||||
|
||||
sql = "SELECT id FROM descs WHERE hash=%s AND description=%s"
|
||||
sql_args = (hash, description)
|
||||
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
@@ -278,7 +257,7 @@ class CheckinDatabase:
|
||||
sql = "INSERT INTO descs (hash,description) values (%s,%s)"
|
||||
sql_args = (hash, description)
|
||||
cursor.execute(sql, sql_args)
|
||||
|
||||
|
||||
return self.GetDescriptionID(description, 0)
|
||||
|
||||
def GetDescriptionID(self, description, auto_set = 1):
|
||||
@@ -314,26 +293,6 @@ class CheckinDatabase:
|
||||
def GetAuthorList(self):
|
||||
return self.get_list("people", 1)
|
||||
|
||||
def GetLatestCheckinTime(self, repository):
|
||||
repository_id = self.GetRepositoryID(repository.rootpath, 0)
|
||||
if repository_id is None:
|
||||
return None
|
||||
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
sql = "SELECT ci_when FROM %s WHERE "\
|
||||
"repositoryid = %%s ORDER BY ci_when DESC LIMIT 1" % (commits_table)
|
||||
sql_args = (repository_id)
|
||||
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
ci_when = None
|
||||
try:
|
||||
ci_when = cursor.fetchone()[0]
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
return dbi.TicksFromDateTime(ci_when)
|
||||
|
||||
def AddCommitList(self, commit_list):
|
||||
for commit in commit_list:
|
||||
self.AddCommit(commit)
|
||||
@@ -352,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)"\
|
||||
@@ -394,91 +352,91 @@ 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":
|
||||
match = " NOT REGEXP "
|
||||
|
||||
sqlList.append("%s%s%s" % (field, match, self.db.literal(data)))
|
||||
|
||||
return "(%s)" % (string.join(sqlList, " OR "))
|
||||
|
||||
def CreateSQLQueryString(self, query):
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
fields = [
|
||||
commits_table+".*",
|
||||
"repositories.repository AS repository_name",
|
||||
"dirs.dir AS dir_name",
|
||||
"files.file AS file_name"]
|
||||
tableList = [
|
||||
(commits_table, None),
|
||||
("repositories","(%s.repositoryid=repositories.id)" % (commits_table)),
|
||||
("dirs", "(%s.dirid=dirs.id)" % (commits_table)),
|
||||
("files", "(%s.fileid=files.id)" % (commits_table))]
|
||||
def CreateSQLQueryString(self, query, detect_leftover=0):
|
||||
commits_table = self.GetCommitsTable()
|
||||
tableList = [(commits_table, None)]
|
||||
condList = []
|
||||
|
||||
if len(query.text_query):
|
||||
tableList.append(("descs", "(descs.id=%s.descid)" % (commits_table)))
|
||||
temp = "MATCH (descs.description) AGAINST (%s" % (self.db.literal(query.text_query))
|
||||
condList.append("%s IN BOOLEAN MODE) > %s" % (temp, self._min_relevance))
|
||||
fields.append("%s) AS relevance" % temp)
|
||||
else:
|
||||
fields.append("'' AS relevance")
|
||||
|
||||
if len(query.repository_list):
|
||||
tableList.append(("repositories",
|
||||
"(%s.repositoryid=repositories.id)"
|
||||
% (commits_table)))
|
||||
temp = self.SQLQueryListString("repositories.repository",
|
||||
query.repository_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.branch_list):
|
||||
tableList.append(("branches", "(%s.branchid=branches.id)" % (commits_table)))
|
||||
tableList.append(("branches",
|
||||
"(%s.branchid=branches.id)" % (commits_table)))
|
||||
temp = self.SQLQueryListString("branches.branch",
|
||||
query.branch_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.directory_list):
|
||||
tableList.append(("dirs",
|
||||
"(%s.dirid=dirs.id)" % (commits_table)))
|
||||
temp = self.SQLQueryListString("dirs.dir", query.directory_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.file_list):
|
||||
tableList.append(("files", "(%s.fileid=files.id)" % (commits_table)))
|
||||
tableList.append(("files",
|
||||
"(%s.fileid=files.id)" % (commits_table)))
|
||||
temp = self.SQLQueryListString("files.file", query.file_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.revision_list):
|
||||
condList.append("(%s.revision IN (" % (commits_table) + ','.join(map(lambda s: self.db.literal(s), query.revision_list)) + "))")
|
||||
|
||||
if len(query.author_list):
|
||||
tableList.append(("people", "(%s.whoid=people.id)" % (commits_table)))
|
||||
tableList.append(("people",
|
||||
"(%s.whoid=people.id)" % (commits_table)))
|
||||
temp = self.SQLQueryListString("people.who", query.author_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.comment_list):
|
||||
tableList.append(("descs", "(%s.descid=descs.id)" % (commits_table)))
|
||||
tableList.append(("descs",
|
||||
"(%s.descid=descs.id)" % (commits_table)))
|
||||
temp = self.SQLQueryListString("descs.description",
|
||||
query.comment_list)
|
||||
condList.append(temp)
|
||||
|
||||
if query.from_date:
|
||||
temp = "(%s.ci_when>=\"%s\")" % (commits_table, str(query.from_date))
|
||||
temp = "(%s.ci_when>=\"%s\")" \
|
||||
% (commits_table, str(query.from_date))
|
||||
condList.append(temp)
|
||||
|
||||
if query.to_date:
|
||||
temp = "(%s.ci_when<=\"%s\")" % (commits_table, str(query.to_date))
|
||||
temp = "(%s.ci_when<=\"%s\")" \
|
||||
% (commits_table, str(query.to_date))
|
||||
condList.append(temp)
|
||||
|
||||
if query.sort == "date":
|
||||
order_by = "ORDER BY %s.ci_when DESC,descid" % (commits_table)
|
||||
elif query.sort == "author":
|
||||
tableList.append(("people", "(%s.whoid=people.id)" % (commits_table)))
|
||||
tableList.append(("people",
|
||||
"(%s.whoid=people.id)" % (commits_table)))
|
||||
order_by = "ORDER BY people.who,descid"
|
||||
elif query.sort == "file":
|
||||
tableList.append(("files", "(%s.fileid=files.id)" % (commits_table)))
|
||||
tableList.append(("files",
|
||||
"(%s.fileid=files.id)" % (commits_table)))
|
||||
order_by = "ORDER BY files.file,descid"
|
||||
elif query.sort == "relevance" and len(query.text_query):
|
||||
order_by = "ORDER BY relevance DESC,%s.ci_when DESC,descid" % (commits_table)
|
||||
|
||||
## exclude duplicates from the table list, and split out join
|
||||
## conditions from table names. In future, the join conditions
|
||||
@@ -491,59 +449,51 @@ class CheckinDatabase:
|
||||
tables.append(table)
|
||||
if cond is not None: joinConds.append(cond)
|
||||
|
||||
fields = string.join(fields, ",")
|
||||
tables = string.join(tables, ",")
|
||||
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:
|
||||
limit = "LIMIT %s" % (str(query.limit))
|
||||
elif self._row_limit:
|
||||
limit = "LIMIT %s" % (str(self._row_limit))
|
||||
if detect_leftover:
|
||||
limit = "LIMIT %s" % (str(query.limit + 1))
|
||||
else:
|
||||
limit = "LIMIT %s" % (str(query.limit))
|
||||
|
||||
sql = "SELECT %s FROM %s %s %s %s" % (
|
||||
fields, tables, conditions, order_by, limit)
|
||||
sql = "SELECT %s.* FROM %s %s %s %s" \
|
||||
% (commits_table, tables, conditions, order_by, limit)
|
||||
|
||||
return sql
|
||||
|
||||
def check_commit_access(self, repos, dir, file, rev):
|
||||
if self.authorizer:
|
||||
rootname = repos.split('/')
|
||||
rootname = rootname.pop()
|
||||
path_parts = dir.split('/')
|
||||
path_parts.append(file)
|
||||
return self.authorizer.check_path_access(rootname, path_parts, vclib.FILE, rev)
|
||||
return True
|
||||
|
||||
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,
|
||||
dbRemovedLines, dbDescID, dbRepositoryName, dbDirName,
|
||||
dbFileName, dbRelevance) = row
|
||||
|
||||
if not self.check_commit_access(dbRepositoryName, dbDirName, dbFileName, dbRevision):
|
||||
continue
|
||||
dbRemovedLines, dbDescID) = row
|
||||
|
||||
commit = LazyCommit(self)
|
||||
if dbType == 'Add':
|
||||
commit.SetTypeAdd()
|
||||
commit.SetTypeAdd()
|
||||
elif dbType == 'Remove':
|
||||
commit.SetTypeRemove()
|
||||
commit.SetTypeRemove()
|
||||
else:
|
||||
commit.SetTypeChange()
|
||||
|
||||
commit.SetTypeChange()
|
||||
commit.SetTime(dbi.TicksFromDateTime(dbCI_When))
|
||||
commit.SetFileID(dbFileID)
|
||||
commit.SetDirectoryID(dbDirID)
|
||||
@@ -554,7 +504,6 @@ class CheckinDatabase:
|
||||
commit.SetPlusCount(dbAddedLines)
|
||||
commit.SetMinusCount(dbRemovedLines)
|
||||
commit.SetDescriptionID(dbDescID)
|
||||
commit.SetRelevance(dbRelevance)
|
||||
|
||||
query.AddCommit(commit)
|
||||
|
||||
@@ -571,13 +520,12 @@ class CheckinDatabase:
|
||||
if file_id == None:
|
||||
return None
|
||||
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
sql = "SELECT * 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()
|
||||
@@ -594,14 +542,13 @@ 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)
|
||||
|
||||
|
||||
def sql_purge(self, table, key, fkey, ftable):
|
||||
sql = "DELETE FROM %s WHERE %s NOT IN (SELECT %s FROM %s)" \
|
||||
% (table, key, fkey, ftable)
|
||||
@@ -680,7 +627,6 @@ class Commit:
|
||||
self.__pluscount = ''
|
||||
self.__minuscount = ''
|
||||
self.__description = ''
|
||||
self.__relevance = ''
|
||||
self.__gmt_time = 0.0
|
||||
self.__type = Commit.CHANGE
|
||||
|
||||
@@ -749,12 +695,6 @@ class Commit:
|
||||
def GetDescription(self):
|
||||
return self.__description
|
||||
|
||||
def SetRelevance(self, relevance):
|
||||
self.__relevance = relevance
|
||||
|
||||
def GetRelevance(self):
|
||||
return self.__relevance
|
||||
|
||||
def SetTypeChange(self):
|
||||
self.__type = Commit.CHANGE
|
||||
|
||||
@@ -843,8 +783,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
|
||||
@@ -855,10 +796,8 @@ class CheckinDatabaseQuery:
|
||||
self.branch_list = []
|
||||
self.directory_list = []
|
||||
self.file_list = []
|
||||
self.revision_list = []
|
||||
self.author_list = []
|
||||
self.comment_list = []
|
||||
self.text_query = ""
|
||||
|
||||
## date range in DBI 2.0 timedate objects
|
||||
self.from_date = None
|
||||
@@ -866,7 +805,8 @@ 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 = []
|
||||
|
||||
@@ -874,8 +814,8 @@ class CheckinDatabaseQuery:
|
||||
## are added
|
||||
self.commit_cb = None
|
||||
|
||||
def SetTextQuery(self, query):
|
||||
self.text_query = query
|
||||
## has this query been run?
|
||||
self.executed = 0
|
||||
|
||||
def SetRepository(self, repository, match = "exact"):
|
||||
self.repository_list.append(QueryEntry(repository, match))
|
||||
@@ -889,15 +829,10 @@ class CheckinDatabaseQuery:
|
||||
def SetFile(self, file, match = "exact"):
|
||||
self.file_list.append(QueryEntry(file, match))
|
||||
|
||||
def SetRevision(self, revision):
|
||||
r = re.compile('\s*[,;]+\s*')
|
||||
for i in r.split(revision):
|
||||
self.revision_list.append(i)
|
||||
|
||||
def SetAuthor(self, author, match = "exact"):
|
||||
self.author_list.append(QueryEntry(author, match))
|
||||
|
||||
def SetComment(self, comment, match = "fulltext"):
|
||||
def SetComment(self, comment, match = "exact"):
|
||||
self.comment_list.append(QueryEntry(comment, match))
|
||||
|
||||
def SetSortMethod(self, sort):
|
||||
@@ -927,6 +862,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
|
||||
@@ -937,21 +886,20 @@ def CreateCommit():
|
||||
def CreateCheckinQuery():
|
||||
return CheckinDatabaseQuery()
|
||||
|
||||
def ConnectDatabase(cfg, authorizer=None, readonly=0):
|
||||
def ConnectDatabase(cfg, readonly=0):
|
||||
if readonly:
|
||||
user = cfg.cvsdb.readonly_user
|
||||
passwd = cfg.cvsdb.readonly_passwd
|
||||
else:
|
||||
user = cfg.cvsdb.user
|
||||
passwd = cfg.cvsdb.passwd
|
||||
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, cfg.cvsdb.socket, user, passwd,
|
||||
cfg.cvsdb.database_name, cfg.cvsdb.row_limit, cfg.cvsdb.fulltext_min_relevance,
|
||||
authorizer)
|
||||
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd,
|
||||
cfg.cvsdb.database_name)
|
||||
db.Connect()
|
||||
return db
|
||||
|
||||
def ConnectDatabaseReadOnly(cfg, authorizer):
|
||||
return ConnectDatabase(cfg, authorizer, 1)
|
||||
def ConnectDatabaseReadOnly(cfg):
|
||||
return ConnectDatabase(cfg, 1)
|
||||
|
||||
def GetCommitListFromRCSFile(repository, path_parts, revision=None):
|
||||
commit_list = []
|
||||
|
14
lib/dbi.py
14
lib/dbi.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -59,13 +59,5 @@ def TicksFromDateTime(datetime):
|
||||
else:
|
||||
return time.mktime(t[:8] + (-1,))
|
||||
|
||||
def connect(host, port, socket, user, passwd, db, charset = 'utf8'):
|
||||
return MySQLdb.connect(
|
||||
host = host,
|
||||
port = port,
|
||||
unix_socket = socket,
|
||||
user = user,
|
||||
passwd = passwd,
|
||||
db = db,
|
||||
charset = charset,
|
||||
use_unicode = charset == 'utf8')
|
||||
def connect(host, port, user, passwd, db):
|
||||
return MySQLdb.connect(host=host, port=port, user=user, passwd=passwd, db=db)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
83
lib/ezt.py
83
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,26 +839,26 @@ class BaseUnavailableError(EZTException):
|
||||
class UnknownFormatConstantError(EZTException):
|
||||
"""The format specifier is an unknown value."""
|
||||
|
||||
def _raw_printer(ctx, s):
|
||||
try: s = s.encode('utf-8')
|
||||
except: pass
|
||||
ctx.fp.write(s)
|
||||
def _raw_formatter(s):
|
||||
return s
|
||||
|
||||
def _html_printer(ctx, s):
|
||||
try: s = s.encode('utf-8')
|
||||
except: pass
|
||||
ctx.fp.write(cgi.escape(s))
|
||||
def _html_formatter(s):
|
||||
return cgi.escape(s)
|
||||
|
||||
def _uri_printer(ctx, s):
|
||||
try: s = s.encode('utf-8')
|
||||
except: pass
|
||||
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 ---
|
||||
@@ -865,7 +876,7 @@ def test_parse():
|
||||
['', '["a \\"b[foo]" c.d f]', None, '']
|
||||
|
||||
def _test(argv):
|
||||
import doctest, ezt
|
||||
import doctest, ezt
|
||||
verbose = "-v" in argv
|
||||
return doctest.testmod(ezt, verbose=verbose)
|
||||
|
||||
|
20
lib/idiff.py
20
lib/idiff.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -20,7 +20,7 @@ import difflib
|
||||
import sys
|
||||
import re
|
||||
import ezt
|
||||
import cgi
|
||||
import sapi
|
||||
|
||||
def sidebyside(fromlines, tolines, context):
|
||||
"""Generate side by side diff"""
|
||||
@@ -49,18 +49,18 @@ def _mdiff_split(flag, (line_number, text)):
|
||||
while True:
|
||||
m = _re_mdiff.search(text, pos)
|
||||
if not m:
|
||||
segments.append(_item(text=cgi.escape(text[pos:]), type=None))
|
||||
segments.append(_item(text=sapi.escape(text[pos:]), type=None))
|
||||
break
|
||||
|
||||
if m.start() > pos:
|
||||
segments.append(_item(text=cgi.escape(text[pos:m.start()]), type=None))
|
||||
segments.append(_item(text=sapi.escape(text[pos:m.start()]), type=None))
|
||||
|
||||
if m.group(1) == "+":
|
||||
segments.append(_item(text=cgi.escape(m.group(2)), type="add"))
|
||||
segments.append(_item(text=sapi.escape(m.group(2)), type="add"))
|
||||
elif m.group(1) == "-":
|
||||
segments.append(_item(text=cgi.escape(m.group(2)), type="remove"))
|
||||
segments.append(_item(text=sapi.escape(m.group(2)), type="remove"))
|
||||
elif m.group(1) == "^":
|
||||
segments.append(_item(text=cgi.escape(m.group(2)), type="change"))
|
||||
segments.append(_item(text=sapi.escape(m.group(2)), type="change"))
|
||||
|
||||
pos = m.end()
|
||||
|
||||
@@ -166,12 +166,12 @@ def _differ_split(row, guide):
|
||||
|
||||
for m in _re_differ.finditer(guide, pos):
|
||||
if m.start() > pos:
|
||||
segments.append(_item(text=cgi.escape(line[pos:m.start()]), type=None))
|
||||
segments.append(_item(text=cgi.escape(line[m.start():m.end()]),
|
||||
segments.append(_item(text=sapi.escape(line[pos:m.start()]), type=None))
|
||||
segments.append(_item(text=sapi.escape(line[m.start():m.end()]),
|
||||
type="change"))
|
||||
pos = m.end()
|
||||
|
||||
segments.append(_item(text=cgi.escape(line[pos:]), type=None))
|
||||
segments.append(_item(text=sapi.escape(line[pos:]), type=None))
|
||||
|
||||
return _item(gap=ezt.boolean(gap), type=type, segments=segments,
|
||||
left_number=left_number, right_number=right_number)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
112
lib/query.py
112
lib/query.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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,22 +40,12 @@ class FormData:
|
||||
self.file = ""
|
||||
self.who = ""
|
||||
self.sortby = ""
|
||||
self.textquery = ""
|
||||
self.date = ""
|
||||
self.hours = 0
|
||||
|
||||
self.decode_thyself(form)
|
||||
|
||||
def decode_thyself(self, form):
|
||||
try:
|
||||
self.textquery = string.strip(form["textquery"].value)
|
||||
except KeyError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.valid = 1
|
||||
|
||||
try:
|
||||
self.repository = string.strip(form["repository"].value)
|
||||
except KeyError:
|
||||
@@ -227,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):
|
||||
@@ -255,15 +246,10 @@ def form_to_cvsdb_query(form_data):
|
||||
cmd = decode_command(cmd)
|
||||
query.SetAuthor(str, cmd)
|
||||
|
||||
if form_data.textquery:
|
||||
query.SetTextQuery(form_data.textquery)
|
||||
|
||||
if form_data.sortby == "author":
|
||||
query.SetSortMethod("author")
|
||||
elif form_data.sortby == "file":
|
||||
query.SetSortMethod("file")
|
||||
elif form_data.sortby == "relevance" and form_data.textquery:
|
||||
query.SetSortMethod("relevance")
|
||||
else:
|
||||
query.SetSortMethod("date")
|
||||
|
||||
@@ -290,8 +276,30 @@ def prev_rev(rev):
|
||||
return string.join(r, '.')
|
||||
|
||||
def is_forbidden(cfg, cvsroot_name, module):
|
||||
auth_params = cfg.get_authorizer_params('forbidden', cvsroot_name)
|
||||
forbidden = auth_params.get('forbidden', '')
|
||||
'''Return 1 if MODULE in CVSROOT_NAME is forbidden; return 0 otherwise.'''
|
||||
|
||||
# CVSROOT_NAME might be None here if the data comes from an
|
||||
# unconfigured root. This interfaces doesn't care that the root
|
||||
# isn't configured, but if that's the case, it will consult only
|
||||
# the base and per-vhost configuration for authorizer and
|
||||
# authorizer parameters.
|
||||
if cvsroot_name:
|
||||
authorizer, params = cfg.get_authorizer_and_params_hack(cvsroot_name)
|
||||
else:
|
||||
authorizer = cfg.options.authorizer
|
||||
params = cfg.get_authorizer_params()
|
||||
|
||||
# If CVSROOT_NAME isn't configured to use an authorizer, nothing
|
||||
# is forbidden. If it's configured to use something other than
|
||||
# the 'forbidden' authorizer, complain. Otherwise, check for
|
||||
# forbiddenness per the PARAMS as expected.
|
||||
if not authorizer:
|
||||
return 0
|
||||
if authorizer != 'forbidden':
|
||||
raise Exception("The 'forbidden' authorizer is the only one supported "
|
||||
"by this interface. The '%s' root is configured to "
|
||||
"use a different one." % (cvsroot_name))
|
||||
forbidden = params.get('forbidden', '')
|
||||
forbidden = map(string.strip, filter(None, string.split(forbidden, ',')))
|
||||
default = 0
|
||||
for pat in forbidden:
|
||||
@@ -304,12 +312,8 @@ def is_forbidden(cfg, cvsroot_name, module):
|
||||
return default
|
||||
|
||||
def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
ob = _item(num_files=len(files), files=[], plus=0, minus=0)
|
||||
|
||||
if desc:
|
||||
ob.log = string.replace(server.escape(desc), '\n', '<br />')
|
||||
else:
|
||||
ob.log = ' '
|
||||
ob = _item(num_files=len(files), files=[])
|
||||
ob.log = desc and string.replace(server.escape(desc), '\n', '<br />') or ''
|
||||
|
||||
for commit in files:
|
||||
repository = commit.GetRepository()
|
||||
@@ -343,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)
|
||||
@@ -359,9 +364,6 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
flink = '[%s] %s' % (repository, file)
|
||||
dlink = None
|
||||
|
||||
ob.relevance = commit.GetRelevance()
|
||||
ob.plus += int(commit.GetPlusCount())
|
||||
ob.minus += int(commit.GetMinusCount())
|
||||
ob.files.append(_item(date=ctime,
|
||||
author=commit.GetAuthor(),
|
||||
link=flink,
|
||||
@@ -375,23 +377,28 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
|
||||
return ob
|
||||
|
||||
def run_query(server, cfg, db, form_data, viewvc_link):
|
||||
query = form_to_cvsdb_query(form_data)
|
||||
def run_query(server, cfg, form_data, viewvc_link):
|
||||
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 = [ ]
|
||||
|
||||
cvsroots = {}
|
||||
viewvc.expand_root_parents(cfg)
|
||||
rootitems = cfg.general.svn_roots.items() + cfg.general.cvs_roots.items()
|
||||
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)
|
||||
@@ -414,7 +421,7 @@ def run_query(server, cfg, db, 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:
|
||||
@@ -422,37 +429,36 @@ def main(server, cfg, viewvc_link):
|
||||
form = server.FieldStorage()
|
||||
form_data = FormData(form)
|
||||
|
||||
db = cvsdb.ConnectDatabaseReadOnly(cfg, None)
|
||||
if form_data.valid:
|
||||
commits = run_query(server, cfg, db, 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,
|
||||
'vsn' : viewvc.__version__,
|
||||
|
||||
'textquery' : server.escape(form_data.textquery, 1),
|
||||
'repository' : server.escape(form_data.repository, 1),
|
||||
'branch' : server.escape(form_data.branch, 1),
|
||||
'directory' : server.escape(form_data.directory, 1),
|
||||
'file' : server.escape(form_data.file, 1),
|
||||
'who' : server.escape(form_data.who, 1),
|
||||
'docroot' : cfg.options.docroot is None \
|
||||
and viewvc_link + '/' + viewvc.docroot_magic_path \
|
||||
or cfg.options.docroot,
|
||||
|
||||
'repository' : server.escape(form_data.repository),
|
||||
'branch' : server.escape(form_data.branch),
|
||||
'directory' : server.escape(form_data.directory),
|
||||
'file' : server.escape(form_data.file),
|
||||
'who' : server.escape(form_data.who),
|
||||
'docroot' : docroot,
|
||||
'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,
|
||||
'repositories' : db.GetRepositoryList(),
|
||||
'hours' : form_data.hours and form_data.hours or 2,
|
||||
})
|
||||
|
||||
|
97
lib/sapi.py
97
lib/sapi.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -20,13 +20,27 @@ import string
|
||||
import os
|
||||
import sys
|
||||
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
|
||||
|
||||
|
||||
# Simple HTML string escaping. Note that we always escape the
|
||||
# double-quote character -- ViewVC shouldn't ever need to preserve
|
||||
# that character as-is, and sometimes needs to embed escaped values
|
||||
# into HTML attributes.
|
||||
def escape(s):
|
||||
s = str(s)
|
||||
s = string.replace(s, '&', '&')
|
||||
s = string.replace(s, '>', '>')
|
||||
s = string.replace(s, '<', '<')
|
||||
s = string.replace(s, '"', """)
|
||||
return s
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self):
|
||||
self.pageGlobals = {}
|
||||
@@ -34,6 +48,9 @@ class Server:
|
||||
def self(self):
|
||||
return self
|
||||
|
||||
def escape(self, s):
|
||||
return escape(s)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
@@ -129,9 +146,6 @@ class CgiServer(Server):
|
||||
global server
|
||||
server = self
|
||||
|
||||
global cgi
|
||||
import cgi
|
||||
|
||||
def addheader(self, name, value):
|
||||
self.headers.append((name, value))
|
||||
|
||||
@@ -160,9 +174,6 @@ class CgiServer(Server):
|
||||
self.header(status='301 Moved')
|
||||
sys.stdout.write('This document is located <a href="%s">here</a>.\n' % url)
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return cgi.escape(s, quote)
|
||||
|
||||
def getenv(self, name, value=None):
|
||||
ret = os.environ.get(name, value)
|
||||
if self.iis and name == 'PATH_INFO' and ret:
|
||||
@@ -175,7 +186,7 @@ class CgiServer(Server):
|
||||
def FieldStorage(fp=None, headers=None, outerboundary="",
|
||||
environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
||||
return cgi.FieldStorage(fp, headers, outerboundary, environ,
|
||||
keep_blank_values, strict_parsing)
|
||||
keep_blank_values, strict_parsing)
|
||||
|
||||
def write(self, s):
|
||||
sys.stdout.write(s)
|
||||
@@ -187,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)
|
||||
@@ -219,9 +287,6 @@ class AspServer(ThreadedServer):
|
||||
def redirect(self, url):
|
||||
self.response.Redirect(url)
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return self.server.HTMLEncode(str(s))
|
||||
|
||||
def getenv(self, name, value = None):
|
||||
ret = self.request.ServerVariables(name)()
|
||||
if not type(ret) is types.UnicodeType:
|
||||
@@ -283,9 +348,6 @@ class ModPythonServer(ThreadedServer):
|
||||
self.request = request
|
||||
self.headerSent = 0
|
||||
|
||||
global cgi
|
||||
import cgi
|
||||
|
||||
def addheader(self, name, value):
|
||||
self.request.headers_out.add(name, value)
|
||||
|
||||
@@ -308,9 +370,6 @@ class ModPythonServer(ThreadedServer):
|
||||
self.request.write("You are being redirected to <a href=\"%s\">%s</a>"
|
||||
% (url, url))
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return cgi.escape(s, quote)
|
||||
|
||||
def getenv(self, name, value = None):
|
||||
try:
|
||||
return self.request.subprocess_env[name]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2012 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
|
||||
@@ -29,7 +29,15 @@ class GenericViewVCAuthorizer:
|
||||
def check_root_access(self, rootname):
|
||||
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
|
||||
pass
|
||||
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
"""Return 1 if the associated username is permitted to read every
|
||||
path in the repository at every revision, 0 if the associated
|
||||
username is prohibited from reading any path in the repository, or
|
||||
None if no such determination can be made (perhaps because the
|
||||
cost of making it is too great)."""
|
||||
pass
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
"""Return 1 iff the associated username is permitted to read
|
||||
revision REV of the path PATH_PARTS (of type PATHTYPE) in
|
||||
@@ -44,6 +52,9 @@ class ViewVCAuthorizer(GenericViewVCAuthorizer):
|
||||
"""The uber-permissive authorizer."""
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
return 1
|
||||
|
@@ -1,100 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2009 Vitaliy Filippov.
|
||||
#
|
||||
# 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/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import vcauth
|
||||
import vclib
|
||||
import string
|
||||
from xml.dom.ext.reader import Sax2
|
||||
from xml import xpath
|
||||
|
||||
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
"""An authorizer making use of CVSnt access control lists (which are in form
|
||||
of XML files in CVS/ subdirectories in the repository)."""
|
||||
|
||||
def __init__(self, username, params={}):
|
||||
self.username = username
|
||||
self.params = params
|
||||
self.cfg = params['__config']
|
||||
self.default = params.get('default', 0)
|
||||
self.cached = {}
|
||||
self.xmlcache = {}
|
||||
|
||||
def checkr(self, element, paths):
|
||||
r = None
|
||||
for p in paths:
|
||||
nodes = xpath.Evaluate(p, element)
|
||||
if nodes and len(nodes):
|
||||
for c in nodes:
|
||||
if c.nodeName == 'read' and r is None:
|
||||
r = True
|
||||
if c.attributes and len(c.attributes):
|
||||
for a in c.attributes:
|
||||
if a.nodeName == 'deny':
|
||||
r = not a.value
|
||||
if r is not None:
|
||||
break
|
||||
return r
|
||||
|
||||
def check(self, rootname, path_parts, filename):
|
||||
d = self.cfg.general.cvs_roots.get(rootname,None)
|
||||
if not d:
|
||||
return self.default
|
||||
i = len(path_parts)
|
||||
r = None
|
||||
while i >= 0:
|
||||
try:
|
||||
xml = d
|
||||
if len(path_parts):
|
||||
xml = xml + '/' + string.join(path_parts, '/')
|
||||
xml = xml + '/CVS/fileattr.xml'
|
||||
if self.cached.get(xml, None) is not None:
|
||||
return self.cached.get(xml, None)
|
||||
doc = self.xmlcache.get(xml, None)
|
||||
if doc is None:
|
||||
fp = open(xml, 'rb')
|
||||
doc = Sax2.Reader().fromStream(fp)
|
||||
fp.close()
|
||||
self.xmlcache[xml] = doc
|
||||
if filename:
|
||||
r = self.checkr(doc.documentElement, [
|
||||
'/fileattr/file[@name=\'%s\']/acl[@user=\'%s\' and not(@branch)]/read' % (filename, self.username),
|
||||
'/fileattr/file[@name=\'%s\']/acl[not(@user) and not(@branch)]/read' % filename,
|
||||
'/fileattr/directory/acl[@user=\'%s\' and not(@branch)]/read' % self.username,
|
||||
'/fileattr/directory/acl[not(@user) and not(@branch)]/read'
|
||||
] )
|
||||
else:
|
||||
r = self.checkr(doc.documentElement, [
|
||||
'/fileattr/directory/acl[@user=\'%s\' and not(@branch)]/read' % self.username,
|
||||
'/fileattr/directory/acl[not(@user) and not(@branch)]/read'
|
||||
] )
|
||||
if r is not None:
|
||||
self.cached[xml] = r
|
||||
return r
|
||||
raise Exception(None)
|
||||
except:
|
||||
if len(path_parts) > 0:
|
||||
path_parts = path_parts[:-1]
|
||||
filename = ''
|
||||
i = i-1
|
||||
return self.default
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
return self.check(rootname, [], '')
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
if not path_parts:
|
||||
return 1
|
||||
if pathtype == vclib.DIR:
|
||||
return self.check(rootname, path_parts, '')
|
||||
f = path_parts[-1]
|
||||
path_parts = path_parts[:-1]
|
||||
return self.check(rootname, path_parts, f)
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2012 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,7 +23,14 @@ 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-2012 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,57 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2009 Vitaliy Filippov.
|
||||
#
|
||||
# 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/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import vcauth
|
||||
import vclib
|
||||
import string
|
||||
import grp
|
||||
import re
|
||||
|
||||
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
"""A simple authorizer checking system group membership for every
|
||||
repository and every user."""
|
||||
|
||||
def __init__(self, username, params={}):
|
||||
self.username = username
|
||||
self.params = params
|
||||
self.cfg = params['__config']
|
||||
self.cached = {}
|
||||
self.grp = {}
|
||||
self.byroot = {}
|
||||
self.fmt = map(lambda l: l.strip(), params.get('group_name_format', 'svn.%s|svn.%s.ro').split('|'))
|
||||
byr = params.get('by_root', '')
|
||||
for i in byr.split(','):
|
||||
if i.find(':') < 0:
|
||||
continue
|
||||
(root, auth) = i.split(':', 2)
|
||||
self.byroot[root.strip()] = map(lambda l: l.strip(), auth.split('|'))
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
r = self.cached.get(rootname, None)
|
||||
if r is not None:
|
||||
return r
|
||||
try:
|
||||
grent = self.grp.get(rootname, None)
|
||||
if grent is None:
|
||||
grent = map(lambda grn: grp.getgrnam(grn.replace('%s', re.sub('[^\w\.\-]+', '', rootname))), self.byroot.get(rootname, self.fmt))
|
||||
self.grp[rootname] = grent
|
||||
for i in grent:
|
||||
if i.gr_mem and len(i.gr_mem) and self.username in i.gr_mem:
|
||||
r = 1
|
||||
break
|
||||
except:
|
||||
r = 0
|
||||
self.cached[rootname] = r
|
||||
return r
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
return self.check_root_access(rootname)
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2012 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
|
||||
cp.read(self.authz_file)
|
||||
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,69 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2009 Vitaliy Filippov.
|
||||
#
|
||||
# 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/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import vcauth
|
||||
import vclib
|
||||
import string
|
||||
|
||||
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
"""A 'union' authorizer: it makes possible to use one authorizer for
|
||||
one root and other authorizer for other roots."""
|
||||
|
||||
def __init__(self, username, params={}):
|
||||
self.username = username
|
||||
self.params = params
|
||||
self.cfg = params['__config']
|
||||
self.default = params.get('default', '')
|
||||
self.byroot = {}
|
||||
self.authz = {}
|
||||
union = params.get('union', '')
|
||||
for i in union.split(','):
|
||||
if i.find(':') < 0:
|
||||
continue
|
||||
(root, auth) = i.split(':', 2)
|
||||
self.byroot[root.strip()] = auth.strip()
|
||||
|
||||
def create_authz(self, rootname):
|
||||
aname = self.byroot.get(rootname, '') or self.default
|
||||
if not aname:
|
||||
return None
|
||||
if self.authz.get(aname, None):
|
||||
return self.authz[aname]
|
||||
import imp
|
||||
fp = None
|
||||
try:
|
||||
try:
|
||||
fp, path, desc = imp.find_module(aname, vcauth.__path__)
|
||||
my_auth = imp.load_module('viewvc', fp, path, desc)
|
||||
except ImportError:
|
||||
raise debug.ViewVCException(
|
||||
'Invalid authorizer (%s) specified for root "%s"' \
|
||||
% (self.cfg.options.authorizer, rootname),
|
||||
'500 Internal Server Error')
|
||||
finally:
|
||||
if fp:
|
||||
fp.close()
|
||||
params = self.cfg.get_authorizer_params(aname, rootname)
|
||||
self.authz[aname] = my_auth.ViewVCAuthorizer(self.username, params)
|
||||
return self.authz[aname]
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
a = self.create_authz(rootname)
|
||||
if a:
|
||||
return a.check_root_access(rootname)
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
a = self.create_authz(rootname)
|
||||
if a:
|
||||
return a.check_path_access(rootname, path_parts, pathtype, rev)
|
||||
return None
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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.
|
||||
@@ -328,7 +339,7 @@ def _diff_args(type, options):
|
||||
if type == CONTEXT:
|
||||
if options.has_key('context'):
|
||||
if options['context'] is None:
|
||||
args.append('--context=%i' % 0x01ffffff)
|
||||
args.append('--context=-1')
|
||||
else:
|
||||
args.append('--context=%i' % options['context'])
|
||||
else:
|
||||
@@ -336,7 +347,7 @@ def _diff_args(type, options):
|
||||
elif type == UNIFIED:
|
||||
if options.has_key('context'):
|
||||
if options['context'] is None:
|
||||
args.append('--unified=%i' % 0x01ffffff)
|
||||
args.append('--unified=-1')
|
||||
else:
|
||||
args.append('--unified=%i' % options['context'])
|
||||
else:
|
||||
@@ -381,13 +392,13 @@ class _diff_fp:
|
||||
self.fp = None
|
||||
finally:
|
||||
try:
|
||||
if self.temp1 and self.temp1 != '/dev/null':
|
||||
if self.temp1:
|
||||
os.remove(self.temp1)
|
||||
self.temp1 = None
|
||||
self.temp1 = None
|
||||
finally:
|
||||
if self.temp2 and self.temp2 != '/dev/null':
|
||||
if self.temp2:
|
||||
os.remove(self.temp2)
|
||||
self.temp2 = None
|
||||
self.temp2 = None
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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"))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -21,7 +21,6 @@ import stat
|
||||
import string
|
||||
import re
|
||||
import time
|
||||
import cvsdb
|
||||
|
||||
# ViewVC libs
|
||||
import compat
|
||||
@@ -41,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
|
||||
|
||||
@@ -163,7 +167,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, "/")))
|
||||
@@ -171,12 +182,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:
|
||||
@@ -319,13 +332,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):
|
||||
@@ -338,19 +351,13 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
|
||||
ignore_keyword_subst - boolean, ignore keyword substitution
|
||||
"""
|
||||
if not path_parts1:
|
||||
path_parts1 = path_parts2
|
||||
rev1 = '1.0'
|
||||
if not path_parts2:
|
||||
path_parts2 = path_parts1
|
||||
rev2 = '1.0'
|
||||
if self.itemtype(path_parts1, rev1) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts1, "/")))
|
||||
if self.itemtype(path_parts2, rev2) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts2, "/")))
|
||||
|
||||
|
||||
args = vclib._diff_args(type, options)
|
||||
if options.get('ignore_keyword_subst', 0):
|
||||
args.append('-kk')
|
||||
@@ -358,7 +365,8 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
rcsfile = self.rcsfile(path_parts1, 1)
|
||||
if path_parts1 != path_parts2:
|
||||
raise NotImplementedError, "cannot diff across paths in cvs"
|
||||
args.extend(['-N', '-r' + rev1, '-r' + rev2, rcsfile])
|
||||
args.extend(['-r' + rev1, '-r' + rev2, rcsfile])
|
||||
|
||||
fp = self.rcs_popen('rcsdiff', args, 'rt')
|
||||
|
||||
# Eat up the non-GNU-diff-y headers.
|
||||
@@ -367,6 +375,7 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
if not line or line[0:5] == 'diff ':
|
||||
break
|
||||
return fp
|
||||
|
||||
|
||||
class CVSDirEntry(vclib.DirEntry):
|
||||
def __init__(self, name, kind, errors, in_attic, absent=0):
|
||||
@@ -838,8 +847,6 @@ def _parse_log_entry(fp):
|
||||
raise ValueError, 'invalid year'
|
||||
date = compat.timegm(tm)
|
||||
|
||||
log = cvsdb.utf8string(log)
|
||||
|
||||
return Revision(rev, date,
|
||||
# author, state, lines changed
|
||||
match.group(2), match.group(3) == "dead", match.group(5),
|
||||
@@ -1029,16 +1036,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-2012 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
@@ -32,7 +32,6 @@ import time
|
||||
import math
|
||||
import rcsparse
|
||||
import vclib
|
||||
import cvsdb
|
||||
|
||||
class CVSParser(rcsparse.Sink):
|
||||
# Precompiled regular expressions
|
||||
@@ -415,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)
|
||||
@@ -429,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,7 +447,9 @@ class BlameSource:
|
||||
prev_rev = self.parser.prev_revision.get(rev)
|
||||
line_number = idx + 1
|
||||
author = self.parser.revision_author[rev]
|
||||
thisline = cvsdb.utf8string(self.lines[idx])
|
||||
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-2012 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,6 @@ import tempfile
|
||||
import vclib
|
||||
import rcsparse
|
||||
import blame
|
||||
import cvsdb
|
||||
|
||||
### The functionality shared with bincvs should probably be moved to a
|
||||
### separate module
|
||||
@@ -67,7 +66,6 @@ class CCVSRepository(BaseCVSRepository):
|
||||
entry.path = path
|
||||
try:
|
||||
rcsparse.parse(open(path, 'rb'), InfoSink(entry, rev, alltags))
|
||||
entry.log = cvsdb.utf8string(entry.log)
|
||||
except IOError, e:
|
||||
entry.errors.append("rcsparse error: %s" % e)
|
||||
except RuntimeError, e:
|
||||
@@ -121,49 +119,40 @@ class CCVSRepository(BaseCVSRepository):
|
||||
return filtered_revs
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
if path_parts1 and self.itemtype(path_parts1, rev1) != vclib.FILE: # does auth-check
|
||||
if self.itemtype(path_parts1, rev1) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts1, "/")))
|
||||
if path_parts2 and self.itemtype(path_parts2, rev2) != vclib.FILE: # does auth-check
|
||||
if self.itemtype(path_parts2, rev2) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts2, "/")))
|
||||
if not path_parts1 and not path_parts2:
|
||||
raise vclib.Error("Nothing to diff.")
|
||||
|
||||
temp1 = tempfile.mktemp()
|
||||
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())
|
||||
|
||||
if path_parts1:
|
||||
temp1 = tempfile.mktemp()
|
||||
open(temp1, 'wb').write(self.openfile(path_parts1, rev1)[0].getvalue())
|
||||
r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
|
||||
info1 = (self.rcsfile(path_parts1, root=1, v=0), r1.date, r1.string)
|
||||
else:
|
||||
temp1 = '/dev/null'
|
||||
info1 = ('/dev/null', '', '')
|
||||
r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
|
||||
r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
|
||||
|
||||
if path_parts2:
|
||||
temp2 = tempfile.mktemp()
|
||||
open(temp2, 'wb').write(self.openfile(path_parts2, rev2)[0].getvalue())
|
||||
r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
|
||||
info2 = (self.rcsfile(path_parts2, root=1, v=0), r2.date, r2.string)
|
||||
else:
|
||||
temp2 = '/dev/null'
|
||||
info2 = ('/dev/null', '', '')
|
||||
info1 = (self.rcsfile(path_parts1, root=1, v=0), r1.date, r1.string)
|
||||
info2 = (self.rcsfile(path_parts2, root=1, v=0), r2.date, r2.string)
|
||||
|
||||
diff_args = vclib._diff_args(type, options)
|
||||
|
||||
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, "/")))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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-2012 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,7 +194,8 @@ class _Parser:
|
||||
else:
|
||||
# Chew up "newphrase"
|
||||
# warn("Unexpected RCS token: $token\n")
|
||||
pass
|
||||
while self.ts.get() != ';':
|
||||
pass
|
||||
else:
|
||||
if f is None:
|
||||
self.ts.unget(token)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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-2012 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-2012 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-2012 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):
|
||||
def _canonicalize_path(path):
|
||||
import svn.core
|
||||
try:
|
||||
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)
|
||||
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)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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,37 @@ 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)
|
||||
|
||||
### 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 +93,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,9 +122,13 @@ 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)
|
||||
self.logs.append(entry)
|
||||
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
|
||||
|
||||
@@ -157,7 +195,7 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
# Setup the client context baton, complete with non-prompting authstuffs.
|
||||
# TODO: svn_cmdline_setup_auth_baton() is mo' better (when available)
|
||||
core.svn_config_ensure(self.config_dir)
|
||||
self.ctx = client.svn_client_ctx_t()
|
||||
self.ctx = client.svn_client_create_context()
|
||||
self.ctx.auth_baton = core.svn_auth_open([
|
||||
client.svn_client_get_simple_provider(),
|
||||
client.svn_client_get_username_provider(),
|
||||
@@ -177,6 +215,10 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
self.youngest = ra.svn_ra_get_latest_revnum(self.ra_session)
|
||||
self._dirent_cache = { }
|
||||
self._revinfo_cache = { }
|
||||
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
@@ -211,7 +253,7 @@ 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)
|
||||
@@ -222,14 +264,15 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
### 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)
|
||||
lh_rev, c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
return SelfCleanFP(tmp_file), lh_rev
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
path = self._getpath(path_parts)
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a directory." % path)
|
||||
rev = self._getrev(rev)
|
||||
entries = [ ]
|
||||
entries = []
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for name in dirents.keys():
|
||||
entry = dirents[name]
|
||||
@@ -237,8 +280,9 @@ 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):
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
else:
|
||||
kind = None
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
@@ -249,11 +293,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 +313,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),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
if locks.has_key(basename):
|
||||
lockinfo = locks[basename].owner
|
||||
# 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 +377,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,65 +403,56 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
prev_rev = None
|
||||
if revision > 1:
|
||||
prev_rev = revision - 1
|
||||
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)
|
||||
|
||||
# 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, 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={}):
|
||||
|
||||
if path_parts1 is not None:
|
||||
p1 = self._getpath(path_parts1)
|
||||
r1 = self._getrev(rev1)
|
||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
||||
raise vclib.ItemNotFound(path_parts1)
|
||||
else:
|
||||
p1 = None
|
||||
|
||||
if path_parts2 is not None:
|
||||
if not p1:
|
||||
raise vclib.ItemNotFound(parh_parts2)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r2 = self._getrev(rev2)
|
||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
else:
|
||||
p2 = None
|
||||
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, 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)
|
||||
info1 = p1, _date_from_rev(r1), r1
|
||||
info2 = p2, _date_from_rev(r2), r2
|
||||
if p1:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
else:
|
||||
temp1 = '/dev/null'
|
||||
if p2:
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
else:
|
||||
temp2 = '/dev/null'
|
||||
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:
|
||||
if e.apr_err == vclib.svn.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)
|
||||
@@ -393,8 +464,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)
|
||||
@@ -403,58 +477,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),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
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]
|
||||
|
||||
def _revinfo_raw(self, rev):
|
||||
# return 5-tuple (date, author, message, changes)
|
||||
optrev = _rev2optrev(rev)
|
||||
revs = []
|
||||
last_changed_rev = revisions[0]
|
||||
|
||||
def _log_cb(changed_paths, revision, author,
|
||||
datestr, message, pool, retval=revs):
|
||||
date = _datestr_to_date(datestr)
|
||||
# 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(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,
|
||||
}
|
||||
paths = (changed_paths or {}).keys()
|
||||
|
||||
# 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
|
||||
@@ -467,31 +622,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 --##
|
||||
|
||||
@@ -510,23 +695,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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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,15 +15,14 @@
|
||||
import vclib
|
||||
import os
|
||||
import os.path
|
||||
import stat
|
||||
import string
|
||||
import cStringIO
|
||||
import signal
|
||||
import shutil
|
||||
import time
|
||||
import tempfile
|
||||
import popen
|
||||
import re
|
||||
import urllib
|
||||
from svn import fs, repos, core, client, delta
|
||||
|
||||
|
||||
@@ -31,13 +30,27 @@ from svn import fs, repos, core, client, delta
|
||||
if (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_PATCH) < (1, 3, 1):
|
||||
raise Exception, "Version requirement not met (needs 1.3.1 or better)"
|
||||
|
||||
|
||||
### Pre-1.5 Subversion doesn't have SVN_ERR_CEASE_INVOCATION
|
||||
try:
|
||||
_SVN_ERR_CEASE_INVOCATION = core.SVN_ERR_CEASE_INVOCATION
|
||||
except:
|
||||
_SVN_ERR_CEASE_INVOCATION = core.SVN_ERR_CANCELLED
|
||||
|
||||
### Pre-1.5 SubversionException's might not have the .msg and .apr_err members
|
||||
def _fix_subversion_exception(e):
|
||||
if not hasattr(e, 'apr_err'):
|
||||
e.apr_err = e[1]
|
||||
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
|
||||
@@ -103,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):
|
||||
@@ -173,64 +212,11 @@ class NodeHistory:
|
||||
return
|
||||
self.histories.append([revision, _cleanup_path(path)])
|
||||
if self.limit and len(self.histories) == self.limit:
|
||||
raise core.SubversionException("", core.SVN_ERR_CEASE_INVOCATION)
|
||||
raise core.SubversionException("", _SVN_ERR_CEASE_INVOCATION)
|
||||
|
||||
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 != core.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)
|
||||
@@ -309,14 +295,15 @@ class FileContentsPipe:
|
||||
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, local_url, rev, first_rev):
|
||||
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()
|
||||
core.svn_config_ensure(None)
|
||||
ctx.config = core.svn_config_get_config(None)
|
||||
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([])
|
||||
try:
|
||||
### TODO: Is this use of FIRST_REV always what we want? Should we
|
||||
@@ -333,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))
|
||||
|
||||
@@ -371,7 +360,7 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
self.auth = authorizer
|
||||
self.svn_client_path = utilities.svn or 'svn'
|
||||
self.diff_cmd = utilities.diff or 'diff'
|
||||
self.config_dir = config_dir
|
||||
self.config_dir = config_dir or None
|
||||
|
||||
# See if this repository is even viewable, authz-wise.
|
||||
if not vclib.check_root_access(self):
|
||||
@@ -401,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
|
||||
|
||||
@@ -416,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)
|
||||
@@ -467,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
|
||||
@@ -516,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:
|
||||
@@ -550,153 +537,45 @@ 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,
|
||||
{'svn_cross_copies': 1})
|
||||
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)
|
||||
source = BlameSource(_rootpath2url(self.rootpath, path), youngest_rev,
|
||||
oldest_rev, include_text, self.config_dir)
|
||||
return source, youngest_rev
|
||||
|
||||
def _revinfo_raw(self, rev):
|
||||
fsroot = self._getroot(rev)
|
||||
|
||||
# Get the changes for the revision
|
||||
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
||||
e_ptr, e_baton = delta.make_editor(editor)
|
||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
changes = editor.get_changes()
|
||||
changedpaths = {}
|
||||
|
||||
# Now 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)
|
||||
|
||||
# Copy the Subversion changes into a new hash, converting them into
|
||||
# ChangedPath objects.
|
||||
found_readable = found_unreadable = 0
|
||||
for path in changes.keys():
|
||||
change = changes[path]
|
||||
if change.path:
|
||||
change.path = _cleanup_path(change.path)
|
||||
if change.base_path:
|
||||
change.base_path = _cleanup_path(change.base_path)
|
||||
is_copy = 0
|
||||
if not hasattr(change, 'action'): # new to subversion 1.4.0
|
||||
action = vclib.MODIFIED
|
||||
if not change.path:
|
||||
action = vclib.DELETED
|
||||
elif change.added:
|
||||
action = vclib.ADDED
|
||||
replace_check_path = path
|
||||
if change.base_path and change.base_rev:
|
||||
replace_check_path = change.base_path
|
||||
if changedpaths.has_key(replace_check_path) \
|
||||
and changedpaths[replace_check_path].action == vclib.DELETED:
|
||||
action = vclib.REPLACED
|
||||
else:
|
||||
if change.action == repos.CHANGE_ACTION_ADD:
|
||||
action = vclib.ADDED
|
||||
elif change.action == repos.CHANGE_ACTION_DELETE:
|
||||
action = vclib.DELETED
|
||||
elif change.action == repos.CHANGE_ACTION_REPLACE:
|
||||
action = vclib.REPLACED
|
||||
else:
|
||||
action = vclib.MODIFIED
|
||||
if (action == vclib.ADDED or action == vclib.REPLACED) \
|
||||
and change.base_path \
|
||||
and change.base_rev:
|
||||
is_copy = 1
|
||||
if change.item_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif change.item_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
else:
|
||||
pathtype = None
|
||||
|
||||
parts = _path_parts(path)
|
||||
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):
|
||||
is_copy = 0
|
||||
change.base_path = None
|
||||
change.base_rev = None
|
||||
changedpaths[path] = SVNChangedPath(path, rev, pathtype,
|
||||
change.base_path,
|
||||
change.base_rev, action,
|
||||
is_copy, change.text_changed,
|
||||
change.prop_changes)
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
|
||||
# Return our tuple, auth-filtered: date, author, msg, changes
|
||||
if found_unreadable:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
datestr = None
|
||||
|
||||
date = _datestr_to_date(datestr)
|
||||
return date, author, msg, changedpaths.values()
|
||||
|
||||
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={}):
|
||||
if path_parts1:
|
||||
p1 = self._getpath(path_parts1)
|
||||
r1 = self._getrev(rev1)
|
||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
||||
raise vclib.ItemNotFound(path_parts1)
|
||||
else:
|
||||
p1 = None
|
||||
|
||||
if path_parts2:
|
||||
p2 = self._getpath(path_parts2)
|
||||
r2 = self._getrev(rev2)
|
||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
else:
|
||||
if not p1:
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
p2 = None
|
||||
|
||||
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, changes = self.revinfo(rev)
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
if p1:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
info1 = p1, _date_from_rev(r1), r1
|
||||
else:
|
||||
temp1 = '/dev/null'
|
||||
info1 = '/dev/null', _date_from_rev(rev1), rev1
|
||||
if p2:
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
info2 = p2, _date_from_rev(r2), r2
|
||||
else:
|
||||
temp2 = '/dev/null'
|
||||
info2 = '/dev/null', _date_from_rev(rev2), rev2
|
||||
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)
|
||||
@@ -707,6 +586,238 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
def isexecutable(self, path_parts, rev):
|
||||
props = self.itemprops(path_parts, rev) # does authz-check
|
||||
return props.has_key(core.SVN_PROP_EXECUTABLE)
|
||||
|
||||
##--- helpers ---##
|
||||
|
||||
def _revinfo(self, rev, include_changed_paths=0):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
def _get_changed_paths(fsroot):
|
||||
"""Return a 3-tuple: found_readable, found_unreadable, changed_paths."""
|
||||
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
||||
e_ptr, e_baton = delta.make_editor(editor)
|
||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
changedpaths = {}
|
||||
changes = editor.get_changes()
|
||||
|
||||
# Copy the Subversion changes into a new hash, checking
|
||||
# authorization and converting them into ChangedPath objects.
|
||||
found_readable = found_unreadable = 0
|
||||
for path in changes.keys():
|
||||
change = changes[path]
|
||||
if change.path:
|
||||
change.path = _cleanup_path(change.path)
|
||||
if change.base_path:
|
||||
change.base_path = _cleanup_path(change.base_path)
|
||||
is_copy = 0
|
||||
if not hasattr(change, 'action'): # new to subversion 1.4.0
|
||||
action = vclib.MODIFIED
|
||||
if not change.path:
|
||||
action = vclib.DELETED
|
||||
elif change.added:
|
||||
action = vclib.ADDED
|
||||
replace_check_path = path
|
||||
if change.base_path and change.base_rev:
|
||||
replace_check_path = change.base_path
|
||||
if changedpaths.has_key(replace_check_path) \
|
||||
and changedpaths[replace_check_path].action == vclib.DELETED:
|
||||
action = vclib.REPLACED
|
||||
else:
|
||||
if change.action == repos.CHANGE_ACTION_ADD:
|
||||
action = vclib.ADDED
|
||||
elif change.action == repos.CHANGE_ACTION_DELETE:
|
||||
action = vclib.DELETED
|
||||
elif change.action == repos.CHANGE_ACTION_REPLACE:
|
||||
action = vclib.REPLACED
|
||||
else:
|
||||
action = vclib.MODIFIED
|
||||
if (action == vclib.ADDED or action == vclib.REPLACED) \
|
||||
and change.base_path \
|
||||
and change.base_rev:
|
||||
is_copy = 1
|
||||
if change.item_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif change.item_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
else:
|
||||
pathtype = None
|
||||
|
||||
parts = _path_parts(path)
|
||||
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):
|
||||
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,
|
||||
is_copy, change.text_changed,
|
||||
change.prop_changes)
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
return found_readable, found_unreadable, changedpaths.values()
|
||||
|
||||
def _get_change_copyinfo(fsroot, path, change):
|
||||
if hasattr(change, 'copyfrom_known') and change.copyfrom_known:
|
||||
copyfrom_path = change.copyfrom_path
|
||||
copyfrom_rev = change.copyfrom_rev
|
||||
else:
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(fsroot, path)
|
||||
return copyfrom_path, copyfrom_rev
|
||||
|
||||
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
|
||||
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
|
||||
# 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 = _revinfo_helper(rev, include_changed_paths)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return tuple(cached_info)
|
||||
|
||||
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 _get_history(self, path, rev, path_type, limit=0, options={}):
|
||||
if self.youngest == 0:
|
||||
return []
|
||||
|
||||
rev_paths = []
|
||||
fsroot = self._getroot(rev)
|
||||
show_all_logs = options.get('svn_show_all_dir_logs', 0)
|
||||
if not show_all_logs:
|
||||
# See if the path is a file or directory.
|
||||
kind = fs.check_path(fsroot, path)
|
||||
if kind is core.svn_node_file:
|
||||
show_all_logs = 1
|
||||
|
||||
# Instantiate a NodeHistory collector object, and use it to collect
|
||||
# history items for PATH@REV.
|
||||
history = NodeHistory(self.fs_ptr, show_all_logs, limit)
|
||||
try:
|
||||
repos.svn_repos_history(self.fs_ptr, path, history.add_history,
|
||||
1, rev, options.get('svn_cross_copies', 0))
|
||||
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(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, '/')
|
||||
@@ -715,8 +826,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)
|
||||
@@ -729,7 +843,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
|
||||
|
1810
lib/viewvc.py
1810
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-2012 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
|
||||
|
53
notes/TODO
Normal file
53
notes/TODO
Normal file
@@ -0,0 +1,53 @@
|
||||
PREFACE
|
||||
-------
|
||||
This file will go away soon after release 0.8. Please use the SourceForge
|
||||
tracker to resubmit any of the items listed below, if you think, it is
|
||||
still an issue:
|
||||
http://sourceforge.net/tracker/?group_id=18760
|
||||
Before reporting please check, whether someone else has already done this.
|
||||
Working patches increase the chance to be included into the next release.
|
||||
-- PeFu / October 2001
|
||||
|
||||
TODO ITEMS
|
||||
----------
|
||||
*) add Tamminen Eero's comments on how to make Linux directly execute
|
||||
the Python script. From email on Feb 19.
|
||||
[ add other examples, such as my /bin/sh hack or the teeny CGI stub
|
||||
importing the bulk hack ]
|
||||
|
||||
*) insert rcs_path into PATH before calling "rcsdiff". rcsdiff might
|
||||
use "co" and needs to find it on the path.
|
||||
|
||||
*) show the "locked" flag (attach it to the LogEntry objects).
|
||||
Idea from Russell Gordon <russell@hoopscotch.dhs.org>
|
||||
|
||||
*) committing with a specific revision number:
|
||||
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
|
||||
|
||||
*) add capability similar to cvs2cl.pl:
|
||||
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
|
||||
suggestion from Chris Meyer <cmeyer@gatan.com>.
|
||||
|
||||
*) add a tree view of the directory structure (and files?)
|
||||
|
||||
*) include a ConfigParser.py to help older Python installations
|
||||
|
||||
*) add a check for the rcs programs/paths to viewvc-install. clarify the
|
||||
dependency on RCS in the docs.
|
||||
|
||||
*) have a "check" mode that verifies binaries are available on rcs_path
|
||||
|
||||
-> alternately (probably?): use rcsparse rather than external tools
|
||||
|
||||
KNOWN BUGS
|
||||
----------
|
||||
*) time.timezone seems to not be available on some 1.5.2 installs.
|
||||
I was unable to verify this. On RedHat and SuSE Linux this bug
|
||||
is non existant.
|
||||
|
||||
*) With old repositories containing many branches, tags or thousands
|
||||
or revisions, the cvsgraph feature becomes unusable (see INSTALL).
|
||||
ViewVC can't do much about this, but it might be possible to
|
||||
investigate the number of branches, tags and revision in advance
|
||||
and disable the cvsgraph links, if the numbers exceed a certain
|
||||
treshold.
|
82
notes/authz-dev-TODO
Normal file
82
notes/authz-dev-TODO
Normal file
@@ -0,0 +1,82 @@
|
||||
Here lie TODO items for the pluggable authz system:
|
||||
|
||||
* Subversion uses path privelege to determine visibility of revision
|
||||
metadata. That logic is pretty Subversion-specific, so it feels like it
|
||||
belongs outside the vcauth library as just a helper function in viewvc.py
|
||||
or something. The algorithm is something like this (culled from the
|
||||
CollabNet implementation, and not expected to work as edited):
|
||||
|
||||
# Subversion revision access levels
|
||||
REVISION_ACCESS_NONE = 0
|
||||
REVISION_ACCESS_PARTIAL = 1
|
||||
REVISION_ACCESS_FULL = 2
|
||||
|
||||
def check_svn_revision_access(request, rev):
|
||||
# Check our revision access cache first.
|
||||
if request.rev_access_cache.has_key(rev):
|
||||
return request.rev_access_cache[rev]
|
||||
|
||||
# Check our cached answer to the question "Does the user have
|
||||
# an all-access or a not-at-all-access pass?"
|
||||
if request.full_access is not None:
|
||||
return request.full_access \
|
||||
and REVISION_ACCESS_FULL or REVISION_ACCESS_NONE
|
||||
|
||||
# Get a list of paths changed in REV.
|
||||
### FIXME: There outta be a vclib-complaint way to do this,
|
||||
### as this won't work for vclib.svn_ra.
|
||||
import svn.fs
|
||||
rev_root = svn.fs.revision_root(self.repos.fs_ptr, rev)
|
||||
changes = svn.fs.paths_changed(rev_root)
|
||||
|
||||
# Loop over the list of changed paths, asking the access question
|
||||
# for each one. We'll track whether we've found any readable paths
|
||||
# as well as any un-readable (non-authorized) paths, and quit
|
||||
# checking as soon as we know our revision access level.
|
||||
found_readable = 0
|
||||
found_unreadable = 0
|
||||
for path in changes.keys():
|
||||
parts = _path_parts(path)
|
||||
kind = request.repos.itemtype(parts, rev)
|
||||
if kind == vclib.DIR:
|
||||
access = request.auth.check_dir_access(parts, rev)
|
||||
elif:
|
||||
access = request.auth.check_file_access(parts, rev)
|
||||
if access:
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
# Optimization: if we've found at least one readable, and one
|
||||
# unreadable, we needn't ask about any more paths.
|
||||
if found_readable and found_unreadable:
|
||||
break
|
||||
|
||||
# If there are paths but we can't read any of them, no access is
|
||||
# granted.
|
||||
if len(changes) and not found_readable:
|
||||
request.rev_access_cache[rev] = REVISION_ACCESS_NONE
|
||||
# If we found at least one unreadable path, partial access is
|
||||
# granted.
|
||||
elif found_unreadable:
|
||||
request.rev_access_cache[rev] = REVISION_ACCESS_PARTIAL
|
||||
# Finally, if there were no paths at all, or none of the existing
|
||||
# ones were unreadable, grant full access.
|
||||
else:
|
||||
request.rev_access_cache[rev] = REVISION_ACCESS_FULL
|
||||
return request.rev_access_cache[rev]
|
||||
|
||||
The problems are: where does one hang the revision access cache
|
||||
so that it doesn't survive a given request? On the request, as
|
||||
shown in the edited code above?
|
||||
|
||||
Can we actually get a good interface into the vcauth layer for
|
||||
asking the all-access / no-access question? Obviously each vcauth
|
||||
provider can cache that value for itself and use it as it deems
|
||||
necessary, but ideally the revision access level question will
|
||||
want to know if said auth provider was able to answer the question
|
||||
and, if so, what the answer was.
|
||||
|
||||
Another off-the-wall idea -- let's just teach Subversion to do
|
||||
this calculation as part of a libsvn_repos API.
|
||||
|
||||
|
BIN
notes/logo/viewvc-logo.odg
Normal file
BIN
notes/logo/viewvc-logo.odg
Normal file
Binary file not shown.
BIN
notes/logo/viewvc-logo.pdf
Normal file
BIN
notes/logo/viewvc-logo.pdf
Normal file
Binary file not shown.
2
notes/logo/viewvc-logo.svg
Normal file
2
notes/logo/viewvc-logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
125
notes/releases.txt
Normal file
125
notes/releases.txt
Normal file
@@ -0,0 +1,125 @@
|
||||
RELEASE MANAGEMENT
|
||||
|
||||
ViewVC rolls releases from release branches associate with each minor
|
||||
version of the software. For example, the 1.1.0 is rolled from the
|
||||
1.1.x branch. The same is true for the 1.1.1, 1.1.2, ... releases.
|
||||
|
||||
There is a script, `tools/make-release', which creates a release
|
||||
directory and the various archive files that we distribute. All other
|
||||
steps required to get a ViewVC release out of the door require manual
|
||||
execution (currently by C. Michael Pilato). Those steps are as
|
||||
follows:
|
||||
|
||||
Checkout a working copy of the release branch for the release you
|
||||
intend to roll, and in that working copy, perform the following steps
|
||||
(X, Y, and Z below represent integral major, minor, and patch version
|
||||
numbers, and not literal):
|
||||
|
||||
1. Review any open bug reports:
|
||||
|
||||
http://viewvc.tigris.org/servlets/ProjectIssues
|
||||
|
||||
2. Add a new subsection to the file 'docs/upgrading.html' describing
|
||||
all user visible changes for users of previous releases of ViewVC.
|
||||
Commit any modifications. NOTE: This step should not be necessary
|
||||
for patch releases.
|
||||
|
||||
3. Verify that copyright years are correct in both the license-1.html
|
||||
file and the source code.
|
||||
|
||||
4. Update and commit the 'CHANGES' file, using any available crystal
|
||||
balls or other forward-looking devices to take a stab at the
|
||||
release date.
|
||||
|
||||
5. Test, test, test! There is no automatic testsuite available. So
|
||||
just run with permuting different `viewvc.conf' settings... and
|
||||
pray. Fix what needs fixin', keeping the CHANGES file in sync
|
||||
with the branch.
|
||||
|
||||
6. At this point, the source code committed to the release branch
|
||||
should exactly reflect what you wish to distribute and dub "the
|
||||
release".
|
||||
|
||||
7. Update your release branch working copy to HEAD.
|
||||
|
||||
svn up
|
||||
|
||||
8. Edit the file 'lib/viewvc.py' and remove the "-dev" suffix from
|
||||
__version__. The remainder should be of the form "X.Y.Z", where X,
|
||||
Y, and Z are positive integers.
|
||||
|
||||
*** Do NOT commit this change. ***
|
||||
|
||||
9. "Peg" the contributed templates externals definition to the
|
||||
current HEAD revision:
|
||||
|
||||
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
|
||||
|
||||
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?
|
||||
|
||||
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:
|
||||
|
||||
http://viewvc.tigris.org/servlets/ProjectDocumentList?folderID=6004
|
||||
|
||||
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."
|
||||
|
||||
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.)
|
||||
|
||||
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.
|
78
notes/vclib-enhancements.txt
Normal file
78
notes/vclib-enhancements.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
The following is an email from a developer who was integrating bzr
|
||||
into ViewVC in which he shares some thoughts on how to further
|
||||
abstract the version control system interactions into first-class APIs
|
||||
in the vclib module.
|
||||
|
||||
Subject: Re: [ViewCVS-dev] difflib module
|
||||
Date: Wed, 1 Jun 2005 16:59:10 -0800
|
||||
From: "Johan Rydberg" <jrydberg@gnu.org>
|
||||
To: "Michael Pilato" <cmpilato@collab.net>
|
||||
Cc: <viewcvs-dev@lyra.org>
|
||||
|
||||
"C. Michael Pilato" <cmpilato@collab.net> writes:
|
||||
|
||||
>> I've tried to minimize the changes to the viewcvs.py, but of course
|
||||
>> there are a few places where some things has to be altered.
|
||||
>
|
||||
> Well, if along the way, you have ideas about how to further abstract
|
||||
> stuff into the vclib/ modules, please post.
|
||||
|
||||
I came up with a few as off now;
|
||||
|
||||
* Generalize revision counting; svn starts from 0, bzr starts from 1.
|
||||
Can be done by a constant; request.repos.MIN_REVNO. For CVS I'm not
|
||||
sure exactly what should be done. Right now this is only used in
|
||||
view_revision_svn, so it is not a problem in the short term.
|
||||
|
||||
* Generalize view_diff;
|
||||
|
||||
* Have a repo-method diff(file1, rev1, file2, rev2, args) that returns
|
||||
(date1, date2, fp). Means human_readbale_diff and raw_diff does not
|
||||
have to parse dates. Good for VCS that does not have the date in
|
||||
the diff. [### DONE ###]
|
||||
|
||||
* I'm not sure you should require GNU diff. Some VCS may use own
|
||||
diff mechanisms (bzr uses difflib, _or_ GNU diff when needed.
|
||||
Monotone uses its own, IIRC.)
|
||||
|
||||
* Generalize view_revision ;
|
||||
|
||||
* Have a method, revision_info(), which returns (date, author, msg,
|
||||
changes) much like vclib.svn.get_revision_info. The CVS version
|
||||
can raise a ViewCVSException. [### DONE ###]
|
||||
|
||||
* Establish a convention for renamed/copied files; current should
|
||||
work good enough (change.base_path, change.base_rev) but action
|
||||
string must be same for both svn and others.
|
||||
|
||||
* request.repos.rev (or .revision) should give the current revision
|
||||
number of the repo. No need for this (from view_directory):
|
||||
|
||||
if request.roottype == 'svn':
|
||||
revision = str(vclib.svn.created_rev(request.repos, request.where))
|
||||
|
||||
If svn needs the full name of the repo, why not give it when the
|
||||
repo is created?
|
||||
|
||||
* request.repos.youngest vs vclib.svn.get_youngest_revision(REPO)
|
||||
|
||||
* More object oriented;
|
||||
|
||||
* The subversion backend is not really object oriented. viewcfg.py uses
|
||||
a lot function from vclib.svn, which could instead be methods of the
|
||||
Repository class. Example:
|
||||
|
||||
diffobj = vclib.svn.do_diff(request.repos, p1, int(rev1),
|
||||
p2, int(rev2), args)
|
||||
|
||||
This should be a method of the repository;
|
||||
|
||||
diffobj = request.repos.do_diff(p1, rev1, ...)
|
||||
|
||||
I have identified the following functions;
|
||||
|
||||
- vclib.svn.created_rev
|
||||
- vclib.svn.get_youngest_revision
|
||||
- vclib.svn.date_from_rev
|
||||
- vclib.svn.do_diff
|
||||
- vclib.svn.get_revision_info
|
@@ -1,5 +0,0 @@
|
||||
This directory contains ViewVC template sets contributed by their
|
||||
respective authors and expected to work against ViewVC 1.0.x. They
|
||||
are not necessarily supported by the ViewVC development community, and
|
||||
do not necessarily carry the same license or copyright as ViewVC
|
||||
itself.
|
@@ -1,7 +0,0 @@
|
||||
Template Set: newvc
|
||||
Author(s): C. Michael Pilato <cmpilato@red-bean.com>
|
||||
Compatibility: ViewVC 1.1
|
||||
|
||||
The "newvc" template set uses top navigation tabs to flip between
|
||||
various views of a file or directory, and aims for a clean-yet-modern
|
||||
look and feel.
|
@@ -1,128 +0,0 @@
|
||||
[# Setup page definitions]
|
||||
[define page_title]Diff of:[end]
|
||||
[define help_href][docroot]/help_rootview.html[end]
|
||||
[# end]
|
||||
[include "include/header.ezt" "diff"]
|
||||
|
||||
<form method="get" action="[diff_format_action]" style="display: inline;">
|
||||
<div>
|
||||
[for diff_format_hidden_values]<input type="hidden" name="[diff_format_hidden_values.name]" value="[diff_format_hidden_values.value]"/>[end]
|
||||
<select name="diff_format" onchange="submit()">
|
||||
<option value="h" [is diff_format "h"]selected="selected"[end]>Colored Diff</option>
|
||||
<option value="l" [is diff_format "l"]selected="selected"[end]>Long Colored Diff</option>
|
||||
<option value="f" [is diff_format "f"]selected="selected"[end]>Full Colored Diff</option>
|
||||
<option value="u" [is diff_format "u"]selected="selected"[end]>Unidiff</option>
|
||||
<option value="c" [is diff_format "c"]selected="selected"[end]>Context Diff</option>
|
||||
<option value="s" [is diff_format "s"]selected="selected"[end]>Side by Side</option>
|
||||
</select>
|
||||
<input type="submit" value="Show" />
|
||||
(<a href="[patch_href]">Generate patch</a>)
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="vc_main_body">
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
[if-any raw_diff]
|
||||
<pre>[raw_diff]</pre>
|
||||
[else]
|
||||
|
||||
[define change_right][end]
|
||||
[define last_change_type][end]
|
||||
|
||||
[# these should live in stylesheets]
|
||||
|
||||
<table cellpadding="0" cellspacing="0" style="width: 100%;">
|
||||
[for changes]
|
||||
[is changes.type "change"][else]
|
||||
[if-any change_right][change_right][define change_right][end][end]
|
||||
[end]
|
||||
[is changes.type "header"]
|
||||
<tr>
|
||||
<th class="vc_header" style="width:6%;"><strong>#</strong></th>
|
||||
<th colspan="2" class="vc_header">
|
||||
<strong>Line [changes.line_info_left]</strong> |
|
||||
<strong>Line [changes.line_info_right]</strong>
|
||||
</th>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "add"]
|
||||
<tr>
|
||||
<td id="l[changes.line_number]">[if-any right.annotate_href]<a href="[right.annotate_href]#l[changes.line_number]">[changes.line_number]</a>[else][changes.line_number][end]</td>
|
||||
<td class="vc_diff_plusminus"><strong style="color: green;">+</strong></td>
|
||||
<td class="vc_diff_add">[changes.right]</td>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "remove"]
|
||||
<tr>
|
||||
<td style="text-decoration: line-through">[changes.line_number]</td>
|
||||
<td class="vc_diff_plusminus"><strong style="color: red;">–</strong></td>
|
||||
<td class="vc_diff_remove">[changes.left]</td>
|
||||
</tr>
|
||||
[else]
|
||||
[is changes.type "change"]
|
||||
[if-any changes.have_left]
|
||||
<tr>
|
||||
<td style="text-decoration: line-through">[changes.line_number]</td>
|
||||
<td class="vc_diff_plusminus"><strong style="color: yellow;"><</strong></td>
|
||||
<td class="vc_diff_changes1">[changes.left]</td>
|
||||
</tr>
|
||||
[end]
|
||||
[define change_right][change_right]
|
||||
[if-any changes.have_right]
|
||||
<tr>
|
||||
<td id="l[changes.line_number]">[if-any right.annotate_href]<a href="[right.annotate_href]#l[changes.line_number]">[changes.line_number]</a>[else][changes.line_number][end]</td>
|
||||
<td class="vc_diff_plusminus"><strong style="color: yellow;">></strong></td>
|
||||
<td class="vc_diff_changes2">[changes.right]</td>
|
||||
</tr>[end]
|
||||
[end]
|
||||
[else]
|
||||
[is changes.type "no-changes"]
|
||||
<tr><td colspan="3" style="vc_diff_nochange"><strong>- No changes -</strong></td></tr>
|
||||
[else]
|
||||
[is changes.type "binary-diff"]
|
||||
<tr><td colspan="3" class="vc_diff_binary"><strong>- Binary file revisions differ -</strong></td></tr>
|
||||
[else]
|
||||
[is changes.type "error"]
|
||||
<tr><td colspan="3" class="vc_diff_error"><strong>- ViewVC depends on rcsdiff and GNU diff
|
||||
to create this page. ViewVC cannot find GNU diff. Even if you
|
||||
have GNU diff installed, the rcsdiff program must be configured
|
||||
and compiled with the GNU diff location. -</strong></td></tr>
|
||||
[else][# a line of context]
|
||||
<tr>
|
||||
<td id="l[changes.line_number]">[if-any right.annotate_href]<a href="[right.annotate_href]#l[changes.line_number]">[changes.line_number]</a>[else][changes.line_number][end]</td>
|
||||
<td class="vc_diff_plusminus"> </td>
|
||||
<td style="font-family: monospace; white-space: pre;">[changes.right]</td>
|
||||
</tr>
|
||||
[end][end][end][end][end][end][end]
|
||||
[define last_change_type][changes.type][end]
|
||||
[end]
|
||||
[if-any change_right][change_right][end]
|
||||
</table>
|
||||
|
||||
<h3>Diff Legend</h3>
|
||||
<table class="auto" cellspacing="0">
|
||||
<tr>
|
||||
<td class="vc_diff_plusminus"><strong style="color: red;">–</strong></td>
|
||||
<td class="vc_diff_remove">Removed lines</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="vc_diff_plusminus"><strong style="color: green;">+</strong></td>
|
||||
<td class="vc_diff_add">Added lines</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="vc_diff_plusminus"><strong style="color: yellow;"><</strong></td>
|
||||
<td class="vc_diff_changes1">Changed lines</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="vc_diff_plusminus"><strong style="color: yellow;">></strong></td>
|
||||
<td class="vc_diff_changes2">Changed lines</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
[end]
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
</div>
|
||||
|
||||
[include "include/footer.ezt"]
|
@@ -1,139 +0,0 @@
|
||||
[# setup page definitions]
|
||||
[define page_title]Index of:[end]
|
||||
[define help_href][docroot]/help_[if-any where]dir[else]root[end]view.html[end]
|
||||
[# end]
|
||||
[include "include/header.ezt" "directory"]
|
||||
|
||||
[if-any where][else]
|
||||
<!-- you may insert repository access instructions here -->
|
||||
[end]
|
||||
|
||||
<table class="auto">
|
||||
[is cfg.options.use_pagesize "0"][else][is picklist_len "1"][else]
|
||||
<tr>
|
||||
<td>Jump to page:</td>
|
||||
<td><form method="get" action="[dir_paging_action]">
|
||||
[for dir_paging_hidden_values]<input type="hidden" name="[dir_paging_hidden_values.name]" value="[dir_paging_hidden_values.value]"/>[end]
|
||||
<select name="dir_pagestart" onchange="submit()">
|
||||
[for picklist]
|
||||
<option [is picklist.count dir_pagestart]selected[end] value="[picklist.count]">Page [picklist.page]: [picklist.start] to [picklist.end]</option>
|
||||
[end]
|
||||
</select>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
[end][end]
|
||||
</table>
|
||||
|
||||
<div id="vc_main_body">
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<div id="vc_togglables">
|
||||
[is roottype "svn"]
|
||||
[if-any rev]r<a href="[revision_href]">[rev]</a>[end]
|
||||
[else]
|
||||
[is num_dead "0"]
|
||||
[else]
|
||||
[if-any attic_showing]
|
||||
<a href="[hide_attic_href]">Hide
|
||||
[else]
|
||||
<a href="[show_attic_href]">Show
|
||||
[end]
|
||||
dead files</a>
|
||||
[end]
|
||||
[end]
|
||||
</div>
|
||||
|
||||
<table cellspacing="2" class="fixed" id="dirlist">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 200px" class="vc_header[is sortby "file"]_sort[end]">
|
||||
<a href="[sortby_file_href]#dirlist">File
|
||||
[is sortby "file"]
|
||||
<img class="vc_sortarrow" alt="[is sortdir "down"](rev)[end]"
|
||||
width="13" height="13"
|
||||
src="[docroot]/images/[is sortdir "up"]up[else]down[end].png" />
|
||||
[end]
|
||||
</a>
|
||||
</th>
|
||||
<th class="vc_header[is sortby "rev"]_sort[end]">
|
||||
<a href="[sortby_rev_href]#dirlist">Last Change
|
||||
[is sortby "rev"]
|
||||
<img class="vc_sortarrow" alt="[is sortdir "down"](rev)[end]"
|
||||
width="13" height="13"
|
||||
src="[docroot]/images/[is sortdir "up"]up[else]down[end].png" />
|
||||
[end]
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
[if-any up_href]
|
||||
<tr class="vc_row_odd">
|
||||
<td colspan="2">
|
||||
<a href="[up_href]">
|
||||
<img src="[docroot]/images/back_small.png" alt="" class="vc_icon"
|
||||
/> ../</a>
|
||||
</td>
|
||||
</tr>
|
||||
[end]
|
||||
[for entries]
|
||||
[define click_href][is entries.pathtype "dir"][entries.view_href][else][if-any entries.prefer_markup][entries.view_href][else][entries.download_href][end][end][end]
|
||||
|
||||
<tr class="vc_row_[if-index entries even]even[else]odd[end]">
|
||||
<td style="width: 200px" onclick="jumpTo('[click_href]')">
|
||||
<a name="[entries.anchor]" href="[click_href]" title="[is entries.pathtype "dir"]View Directory Contents[else][if-any entries.prefer_markup]View[else]Download[end] File Contents[end]">
|
||||
<img src="[docroot]/images/[is entries.pathtype "dir"]dir[else][is entries.state "dead"]broken[else]text[end][end].png" alt="" class="vc_icon" />
|
||||
[entries.name][is entries.pathtype "dir"]/[end]</a>
|
||||
[is entries.state "dead"](dead)[end]
|
||||
</td>
|
||||
|
||||
<td [if-any entries.log_href]onclick="jumpTo('[entries.log_href]')"[end]>
|
||||
[if-any entries.rev]
|
||||
<strong>[if-any entries.log_href]<a href="[entries.log_href]" title="Revision [entries.rev]">[entries.rev]</a>[else][entries.rev][end]</strong>
|
||||
([entries.ago] ago)
|
||||
by <em>[entries.author]</em>:
|
||||
[entries.log]
|
||||
[is entries.pathtype "dir"][is roottype "cvs"]
|
||||
<em>(from [entries.log_file]/[entries.log_rev])</em>
|
||||
[end][end]
|
||||
[end]
|
||||
</td>
|
||||
</tr>
|
||||
[end]
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<div id="vc_view_summary">
|
||||
[if-any search_re_form]
|
||||
<form class="inline" method="get" action="[search_re_action]">
|
||||
<div class="inline">
|
||||
[for search_re_hidden_values]<input type="hidden" name="[search_re_hidden_values.name]" value="[search_re_hidden_values.value]"/>[end]
|
||||
<input type="text" name="search" value="[search_re]" />
|
||||
<input type="submit" value="Search Files" />
|
||||
</div>
|
||||
</form>
|
||||
[if-any search_re]
|
||||
<form class="inline" method="get" action="[search_re_action]">
|
||||
<div class="inline">
|
||||
[for search_re_hidden_values]<input type="hidden" name="[search_re_hidden_values.name]" value="[search_re_hidden_values.value]"/>[end]
|
||||
<input type="submit" value="Show all files" />
|
||||
</div>
|
||||
</form>
|
||||
[end]
|
||||
|
||||
[end]
|
||||
[include "include/pathrev_form.ezt"]
|
||||
|
||||
[files_shown] file[is files_shown "1"][else]s[end] shown
|
||||
</div>
|
||||
|
||||
[include "include/props.ezt"]
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
</div>
|
||||
|
||||
[include "include/footer.ezt"]
|
@@ -1,8 +0,0 @@
|
||||
/************************************/
|
||||
/*** ViewVC Help CSS Stylesheet ***/
|
||||
/************************************/
|
||||
body { margin: 0.5em; }
|
||||
img { border: none; }
|
||||
table { width: 100%; }
|
||||
td { vertical-align: top; }
|
||||
col.menu { width:12em; }
|
@@ -1,126 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>ViewVC Help: Directory View</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h1>ViewVC Help: Directory View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br />
|
||||
<strong>Directory View</strong><br />
|
||||
<a href="help_log.html">Log View</a><br />
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewvc.org/index.html">Home</a><br />
|
||||
<a href="http://viewvc.org/upgrading.html">Upgrading</a><br />
|
||||
<a href="http://viewvc.org/contributing.html">Contributing</a><br />
|
||||
<a href="http://viewvc.org/license-1.html">License</a><br />
|
||||
</td><td colspan="2">
|
||||
|
||||
<p>The directory listing view should be a familiar sight to any
|
||||
computer user. It shows the path of the current directory being viewed
|
||||
at the top of the page. Below that is a table summarizing the
|
||||
directory contents, and then comes actual contents, a sortable list of
|
||||
all files and subdirectories inside the current directory.</p>
|
||||
|
||||
<p><a name="summary"></a>The summary table is made up of some or all
|
||||
of the following rows:</p>
|
||||
<ul>
|
||||
<li><a name="summary-files-shown"><strong>Files Shown</strong></a>
|
||||
- Number of files shown in the directory listing. This might be less
|
||||
than the actual number of files in the directory if a
|
||||
<a href="#option-search">regular expression search</a> is in place,
|
||||
hiding files which don't meet the search criteria. In CVS directory
|
||||
listings, this row will also have a link to toggle display of
|
||||
<a href="help_rootview.html#dead-files">dead files</a>, if any are
|
||||
present.</li>
|
||||
|
||||
<li><a name="summary-revision"><strong>Directory
|
||||
Revision</strong></a> - For Subversion directories only.
|
||||
Shown as "# of #" where the first number is the most recent
|
||||
repository revision where the directory (or a path underneath it)
|
||||
was modified. The second number is just the latest repository
|
||||
revision. Both numbers are links to
|
||||
<a href="help_rootview.html#view-rev">revision views</a></li>
|
||||
|
||||
<li><a name="summary-sticky-revision-tag"><strong>Sticky
|
||||
Revision/Tag</strong></a> - shows the current
|
||||
<a href="help_rootview.html#sticky-revision-tag">sticky revision or
|
||||
tag</a> and contains form fields to set or clear it.</li>
|
||||
|
||||
<li><a name="summary-search"><strong>Current Search</strong></a> -
|
||||
If a <a href="#option-search">regular expression search</a> is in place,
|
||||
shows the search string.</li>
|
||||
|
||||
<li><a name="summary-query"><strong>Query</strong></a> - Provides
|
||||
a link to a <a href="help_rootview.html#view-query">query form</a>
|
||||
for the directory</li>
|
||||
</ul>
|
||||
|
||||
<p><a name="list"></a>The actual directory list is a table with
|
||||
filenames and directory names in one column and information about the
|
||||
most recent revisions where each file or directory was modified in the
|
||||
other columns. Column headers can be clicked to sort the directory
|
||||
entries in order by a column, and clicked again to reverse the sort
|
||||
order.</p>
|
||||
|
||||
<p>
|
||||
<!-- If using directory.ezt template -->
|
||||
File names are links to <a href="help_log.html">log views</a>
|
||||
showing a list of revisions where a file was modified. Revision
|
||||
numbers are links to either
|
||||
<a href="help_rootview.html#view-markup">view</a>
|
||||
or <a href="help_rootview.html#view-checkout">download</a> a file
|
||||
(depending on its file type). The links are reversed for directories.
|
||||
Directory revision numbers are links to <a href="help_log.html">log
|
||||
views</a>, while directory names are links showing the contents of those
|
||||
directories.
|
||||
|
||||
<!-- If using dir_alt.ezt template -->
|
||||
<!--
|
||||
File and directory names are links to retrieve their contents.
|
||||
File links may be either
|
||||
<a href="help_rootview.html#view-markup">view</a>
|
||||
or <a href="help_rootview.html#view-download">download</a> links
|
||||
depending on the file type. Directory links go to directory
|
||||
listings. Revision numbers are links to <a href="help_log.html">log
|
||||
views</a> showing lists of revisions where a file or directory was
|
||||
modified.
|
||||
-->
|
||||
|
||||
Also, in CVS repositories with the <a
|
||||
href="help_rootview.html#view-graph">graph view</a> enabled, there
|
||||
will be small icons next to file names which are links to revision
|
||||
graphs.</p>
|
||||
|
||||
<p>Depending on how ViewVC is configured, there may be more options
|
||||
at the bottom of directory pages:</p>
|
||||
|
||||
<ul>
|
||||
<li><a name="option-search"><strong>Regular expression
|
||||
search</strong></a> - If enabled, will show a form field accepting
|
||||
a search string (a
|
||||
<a href="http://doc.python.org/lib/re-syntax.html">python regular
|
||||
expression</a>). Once submitted, only files that have at least
|
||||
one occurance of the expression will show up in directory listings.
|
||||
</li>
|
||||
<li><a name="option-tarball"><strong>Tarball download</strong></a> -
|
||||
If enabled, will show a link to download a gzipped tar archive of
|
||||
the directory contents.</li>
|
||||
</ul>
|
||||
|
||||
</td></tr></table>
|
||||
<hr />
|
||||
<address><a href="mailto:users@viewvc.tigris.org">ViewVC Users Mailinglist</a></address>
|
||||
</body>
|
||||
</html>
|
@@ -1,71 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>ViewVC Help: Log View</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h1>ViewVC Help: Log View</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>
|
||||
<h3>Help</h3>
|
||||
<a href="help_rootview.html">General</a><br />
|
||||
<a href="help_dirview.html">Directory View</a><br />
|
||||
<strong>Log View</strong><br />
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewvc.org/index.html">Home</a><br />
|
||||
<a href="http://viewvc.org/upgrading.html">Upgrading</a><br />
|
||||
<a href="http://viewvc.org/contributing.html">Contributing</a><br />
|
||||
<a href="http://viewvc.org/license-1.html">License</a><br />
|
||||
</td><td colspan="2">
|
||||
<p>
|
||||
The log view displays the revision history of the selected source
|
||||
file or directory. For each revision the following information is
|
||||
displayed:
|
||||
|
||||
<ul>
|
||||
<li>The revision number. In Subversion repositories, this is a
|
||||
link to the <a href="help_rootview.html#view-rev">revision
|
||||
view</a></li>
|
||||
<li>For files, links to
|
||||
<a href="help_rootview.html#view-markup">view</a>,
|
||||
<a href="help_rootview.html#view-checkout">download</a>, and
|
||||
<a href="help_rootview.html#view-annotate">annotate</a> the
|
||||
revision. For directories, a link to
|
||||
<a href="help_dirview.html">list directory contents</a></li>
|
||||
<li>A link to select the revision for diffs (see below)</li>
|
||||
<li>The date and age of the change</li>
|
||||
<li>The author of the modification</li>
|
||||
<li>The CVS branch (usually <em>MAIN</em>, if not on a branch)</li>
|
||||
<li>Possibly a list of CVS tags bound to the revision (if any)</li>
|
||||
<li>The size of the change measured in added and removed lines of
|
||||
code. (CVS only)</li>
|
||||
<li>The size of the file in bytes at the time of the revision
|
||||
(Subversion only)</li>
|
||||
<li>Links to view diffs to the previous revision or possibly to
|
||||
an arbitrary selected revision (if any, see above)</li>
|
||||
<li>If the revision is the result of a copy, the path and revision
|
||||
copied from</li>
|
||||
<li>If the revision precedes a copy or rename, the path at the
|
||||
time of the revision</li>
|
||||
<li>And last but not least, the commit log message which should tell
|
||||
about the reason for the change.</li>
|
||||
</ul>
|
||||
<p>
|
||||
At the bottom of the page you will find a form which allows
|
||||
to request diffs between arbitrary revisions.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
<hr />
|
||||
<address><a href="mailto:users@viewvc.tigris.org">ViewVC Users Mailinglist</a></address>
|
||||
</body>
|
||||
</html>
|
@@ -1,66 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>ViewVC Help: Query The Commit Database</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h1>ViewVC Help: Query The Commit Database</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>
|
||||
<h3>Other Help:</h3>
|
||||
<a href="help_rootview.html">General</a><br />
|
||||
<a href="help_dirview.html">Directory View</a><br />
|
||||
<a href="help_log.html">Classic Log View</a><br />
|
||||
<a href="help_logtable.html">Alternative Log View</a><br />
|
||||
<strong>Query Database</strong>
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewvc.org/index.html">Home</a><br />
|
||||
<a href="http://viewvc.org/upgrading.html">Upgrading</a><br />
|
||||
<a href="http://viewvc.org/contributing.html">Contributing</a><br />
|
||||
<a href="http://viewvc.org/license-1.html">License</a><br />
|
||||
</td><td colspan="2">
|
||||
|
||||
<p>
|
||||
Select your parameters for querying the CVS commit database in the
|
||||
form at the top of the page. You
|
||||
can search for multiple matches by typing a comma-seperated list
|
||||
into the text fields. Regular expressions, and wildcards are also
|
||||
supported. Blank text input fields are treated as wildcards.
|
||||
</p>
|
||||
<p>
|
||||
Any of the text entry fields can take a comma-seperated list of
|
||||
search arguments. For example, to search for all commits from
|
||||
authors <em>jpaint</em> and <em>gstein</em>, just type: <code>jpaint,
|
||||
gstein</code> in the <em>Author</em> input box. If you are searching
|
||||
for items containing spaces or quotes, you will need to quote your
|
||||
request. For example, the same search above with quotes is:
|
||||
<code>"jpaint", "gstein"</code>.
|
||||
</p>
|
||||
<p>
|
||||
Wildcard and regular expression searches are entered in a similar
|
||||
way to the quoted requests. You must quote any wildcard or
|
||||
regular expression request, and a command character preceeds the
|
||||
first quote. The command character <code>l</code>(lowercase L) is for wildcard
|
||||
searches, and the wildcard character is a percent (<code>%</code>). The
|
||||
command character for regular expressions is <code>r</code>, and is
|
||||
passed directly to MySQL, so you'll need to refer to the MySQL
|
||||
manual for the exact regex syntax. It is very similar to Perl. A
|
||||
wildard search for all files with a <em>.py</em> extention is:
|
||||
<code>l"%.py"</code> in the <em>File</em> input box. The same search done
|
||||
with a regular expression is: <code>r".*\.py"</code>.
|
||||
</p>
|
||||
<p>
|
||||
All search types can be mixed, as long as they are seperated by
|
||||
commas.
|
||||
</p>
|
||||
</td></tr></table>
|
||||
</body></html>
|
@@ -1,166 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>ViewVC Help: General</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h1>ViewVC Help: General</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td>
|
||||
<h3>Help</h3>
|
||||
<strong>General</strong><br />
|
||||
<a href="help_dirview.html">Directory View</a><br />
|
||||
<a href="help_log.html">Log View</a><br />
|
||||
|
||||
<h3>Internet</h3>
|
||||
<a href="http://viewvc.org/index.html">Home</a><br />
|
||||
<a href="http://viewvc.org/upgrading.html">Upgrading</a><br />
|
||||
<a href="http://viewvc.org/contributing.html">Contributing</a><br />
|
||||
<a href="http://viewvc.org/license-1.html">License</a><br />
|
||||
</td><td colspan="2">
|
||||
|
||||
<p><em>ViewVC</em> is a WWW interface for CVS and Subversion
|
||||
repositories. It allows you to browse the files and directories in a
|
||||
repository while showing you metadata from the repository history: log
|
||||
messages, modification dates, author names, revision numbers, copy
|
||||
history, and so on. It provides several different views of repository
|
||||
data to help you find the information you are looking for:</p>
|
||||
|
||||
<ul>
|
||||
<li><a name="view-dir" href="help_dirview.html"><strong>Directory
|
||||
View</strong></a> - Shows a list of files and subdirectories in a
|
||||
directory of the repository, along with metadata like author names and
|
||||
log entries.</li>
|
||||
|
||||
<li><a name="view-log" href="help_log.html"><strong>Log
|
||||
View</strong></a> - Shows a revision by revision list of all the
|
||||
changes that have made to a file or directory in the repository, with
|
||||
metadata and links to views of each revision.</li>
|
||||
|
||||
<li><a name="view-markup"><strong>File Contents View (Markup
|
||||
View)</strong></a> - Shows the contents of a file at a particular
|
||||
revision, with revision information at the top of the page. File
|
||||
revisions which are GIF, PNG, or JPEG images are displayed inline on
|
||||
the page. Other file types are displayed as marked up text. The markup
|
||||
may be limited to turning URLs and email addresses into links, or
|
||||
configured to show colorized source code.</li>
|
||||
|
||||
<li><a name="view-checkout"><strong>File Download (Checkout
|
||||
View)</strong></a> - Retrieves the unaltered contents of a file
|
||||
revision. Browsers may try to display the file, or just save it to
|
||||
disk.</li>
|
||||
|
||||
<li><a name="view-annotate"><strong>File Annotate View</strong></a> -
|
||||
Shows the contents of a file revision and breaks it down line by line,
|
||||
showing the revision number where each one was last modified, along
|
||||
with links and other information. <em>This view is disabled in some
|
||||
ViewVC configurations</em></li>
|
||||
|
||||
<li><a name="view-diff"><strong>File Diff View</strong></a> - Shows
|
||||
the changes made between two revisions of a file</li>
|
||||
|
||||
<li><a name="view-tarball"><strong>Directory Tarball View</strong> -
|
||||
Retrieves a gzipped tar archive containing the contents of a
|
||||
directory.<em>This view is disabled in the default ViewVC
|
||||
configuration.</em></li>
|
||||
|
||||
<li><a name="view-query"><strong>Directory Query View</strong></a> -
|
||||
Shows information about changes made to all subdirectories and files
|
||||
under a parent directory, sorted and filtered by criteria you specify.
|
||||
<em>This view is disabled in the default ViewVC configuration.</em>
|
||||
</li>
|
||||
|
||||
<li><a name="view-rev"><strong>Revision View</strong> - Shows
|
||||
information about a revision including log message, author, and a list
|
||||
of changed paths. <em>For Subversion repositories only.</em></li>
|
||||
|
||||
<li><a name="view-graph"><strong>Graph View</strong></a> - Shows a
|
||||
graphical representation of a file's revisions and branches complete
|
||||
with tag and author names and links to markup and diff pages.
|
||||
<em>For CVS repositories only, and disabled in the default
|
||||
configuration.</em></li>
|
||||
</ul>
|
||||
|
||||
<h3><a name="multiple-repositories">Multiple Repositories</a></h3>
|
||||
|
||||
<p>A single installation of ViewVC is often used to provide access to
|
||||
more than one repository. In these installations, ViewVC shows a
|
||||
<em>Project Root</em> drop down box in the top right corner of every
|
||||
generated page to allow for quick access to any repository.</p>
|
||||
|
||||
<h3><a name="sticky-revision-tag">Sticky Revision and Tag</a></h3>
|
||||
|
||||
<p>By default, ViewVC will show the files and directories and revisions
|
||||
that currently exist in the repository. But it's also possible to browse
|
||||
the contents of a repository at a point in its past history by choosing
|
||||
a "sticky tag" (in CVS) or a "sticky revision" (in Subversion) from the
|
||||
forms at the top of directory and log pages. They're called sticky
|
||||
because once they're chosen, they stick around when you navigate to
|
||||
other pages, until you reset them. When they're set, directory and log
|
||||
pages only show revisions preceding the specified point in history. In
|
||||
CVS, when a tag refers to a branch or a revision on a branch, only
|
||||
revisions from the branch history are shown, including branch points and
|
||||
their preceding revisions.</p>
|
||||
|
||||
<h3><a name="dead-files">Dead Files</a></h3>
|
||||
|
||||
<p>In CVS directory listings, ViewVC can optionally display dead files.
|
||||
Dead files are files which used to be in a directory but are currently
|
||||
deleted, or files which just don't exist in the currently selected
|
||||
<a href="#sticky-revision-tag">sticky tag</a>. Dead files cannot be
|
||||
shown in Subversion repositories. The only way to see a deleted file in
|
||||
a Subversion directory is to navigate to a sticky revision where the
|
||||
file previously existed.</p>
|
||||
|
||||
<h3><a name="artificial-tags">Artificial Tags</a></h3>
|
||||
|
||||
<p>In CVS Repositories, ViewVC adds artificial tags <em>HEAD</em> and
|
||||
<em>MAIN</em> to tag listings and accepts them in place of revision
|
||||
numbers and real tag names in all URLs. <em>MAIN</em> acts like a branch
|
||||
tag pointing at the default branch, while <em>HEAD</em> acts like a
|
||||
revision tag pointing to the latest revision on the default branch. The
|
||||
default branch is usually just the trunk, but may be set to other
|
||||
branches inside individual repository files. CVS will always check out
|
||||
revisions from a file's default branch when no other branch is specified
|
||||
on the command line.</p>
|
||||
|
||||
<h3><a name="more-information">More Information</a></h3>
|
||||
|
||||
<p>More information about <em>ViewVC</em> is available from
|
||||
<a href="http://viewvc.org/">viewvc.org</a>.
|
||||
See the links below for guides to CVS and Subversion</p>
|
||||
|
||||
<h4>Documentation about CVS</h4>
|
||||
<blockquote>
|
||||
<p>
|
||||
<a href="http://cvsbook.red-bean.com/"><em>Open Source
|
||||
Development with CVS</em></a><br />
|
||||
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
||||
User's Guide</a><br />
|
||||
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br />
|
||||
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet another CVS tutorial (a little old, but nice)</a><br />
|
||||
<a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but very useful FAQ about CVS</a>
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<h4>Documentation about Subversion</h3>
|
||||
<blockquote>
|
||||
<p>
|
||||
<a href="http://svnbook.red-bean.com/"><em>Version Control with
|
||||
Subversion</em></a><br />
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
</td></tr></table>
|
||||
<hr />
|
||||
<address><a href="mailto:users@viewvc.tigris.org">ViewVC Users Mailinglist</a></address>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 337 B |
Binary file not shown.
Before Width: | Height: | Size: 205 B |
Binary file not shown.
Before Width: | Height: | Size: 247 B |
Binary file not shown.
Before Width: | Height: | Size: 601 B |
Binary file not shown.
Before Width: | Height: | Size: 228 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user