mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8b0bcade3b | ||
![]() |
3f09d2e039 | ||
![]() |
ef501bd217 | ||
![]() |
e0342ff46e | ||
![]() |
d3210a1fec | ||
![]() |
2ba682c141 | ||
![]() |
6f67d8874a | ||
![]() |
88ef87c6af | ||
![]() |
dae7542569 | ||
![]() |
ea28160e45 | ||
![]() |
384d0dc1a6 | ||
![]() |
95927627b2 | ||
![]() |
d95ceab8a3 | ||
![]() |
1aff68d647 | ||
![]() |
4a3e55dac7 | ||
![]() |
171fadee27 | ||
![]() |
bef839ab4e | ||
![]() |
6647de80c3 | ||
![]() |
99e1a51e20 | ||
![]() |
c25dd8c213 | ||
![]() |
6cc6bde99f | ||
![]() |
d4c351e01f | ||
![]() |
7a5a7cfb69 | ||
![]() |
38b429220e | ||
![]() |
f42e157e88 | ||
![]() |
4312ecd3fa | ||
![]() |
504ca48e0f | ||
![]() |
0c0ec82ca2 | ||
![]() |
58a237b14c | ||
![]() |
24d1a691dc | ||
![]() |
51011abec8 | ||
![]() |
0bbe7f5751 | ||
![]() |
1df169ab24 | ||
![]() |
98e7612420 | ||
![]() |
70b0076d88 | ||
![]() |
e23b88d389 | ||
![]() |
336ee6b95d | ||
![]() |
642130cf93 | ||
![]() |
1e3c57f2e0 | ||
![]() |
9693f2b981 | ||
![]() |
69ab22922f | ||
![]() |
d422c3abc9 | ||
![]() |
ded0015f5c | ||
![]() |
6232555f79 | ||
![]() |
81fa4ce6fd | ||
![]() |
98378e7560 | ||
![]() |
8da2897df2 | ||
![]() |
45bca25fbc | ||
![]() |
a54b3bcbbe | ||
![]() |
2612b3bd0a | ||
![]() |
588f19cb0b | ||
![]() |
6d540deba2 | ||
![]() |
9f612d3b29 |
159
CHANGES
159
CHANGES
@@ -1,161 +1,3 @@
|
||||
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
|
||||
* various rcsparse and tparse module fixes
|
||||
* add daemon mode to standalone.py (issue #235)
|
||||
* rework helper application configuration options (issues #229, #62)
|
||||
* teach standalone.py to recognize Subversion repositories via -r option
|
||||
* now interpret relative paths in "viewvc.conf" as relative to that file
|
||||
* add 'purge' subcommand to cvsdbadmin and svndbadmin (issue #271)
|
||||
* fix orphaned data bug in cvsdbadmin/svndbadmin rebuild (issue #271)
|
||||
* 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 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)
|
||||
* add support for optional email address mangling (issue #290)
|
||||
* extensible path-based authorization subsystem (issue #268), supporting:
|
||||
- Subversion authz files (new)
|
||||
- regexp-based path hiding (for compat with 1.0.x)
|
||||
- file glob top-level directory hiding (for compat with 1.0.x)
|
||||
* allow default file view to be "markup" (issue #305)
|
||||
* add support for displaying file/directory properties (issue #39)
|
||||
* pagination improvements
|
||||
* add gzip output encoding support for template-driven pages
|
||||
* fix cache control bugs (issue #259)
|
||||
* add RSS feed URL generation for file history
|
||||
* add support for remote creation of ViewVC checkins database
|
||||
* add integration with Pygments for syntax highlighting
|
||||
* preserve executability of Subversion files in tarballs (issue #233)
|
||||
* add ability to set Subversion runtime config dir (issue #351, issue #339)
|
||||
* 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
|
||||
* 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)
|
||||
|
||||
* fix regression in the 'as text' download view (issue #373)
|
||||
|
||||
Version 1.0.6 (released 16-Sep-2008)
|
||||
|
||||
* security fix: ignore arbitrary user-provided MIME types (issue #354)
|
||||
* fix bug in regexp search filter when used with sticky tag (issue #346)
|
||||
* fix bug in handling of certain 'co' output (issue #348)
|
||||
* fix regexp search filter template bug
|
||||
* fix annotate code syntax error
|
||||
* fix mod_python import cycle (issue #369)
|
||||
|
||||
Version 1.0.5 (released 28-Feb-2008)
|
||||
|
||||
* security fix: omit commits of all-forbidden files from query results
|
||||
* security fix: disallow direct URL navigation to hidden CVSROOT folder
|
||||
* security fix: strip forbidden paths from revision view
|
||||
* security fix: don't traverse log history thru forbidden locations
|
||||
* security fix: honor forbiddenness via diff view path parameters
|
||||
* new 'forbiddenre' regexp-based path authorization feature
|
||||
* fix root name conflict resolution inconsistencies (issue #287)
|
||||
* fix an oversight in the CVS 1.12.9 loginfo-handler support
|
||||
* fix RSS feed content type to be more specific (issue #306)
|
||||
* fix entity escaping problems in RSS feed data (issue #238)
|
||||
* fix bug in tarball generation for remote Subversion repositories
|
||||
* fix query interface file-count-limiting logic
|
||||
* fix query results plus/minus count to ignore forbidden files
|
||||
* fix blame error caused by 'svn' unable to create runtime config dir
|
||||
|
||||
Version 1.0.4 (released 10-Apr-2007)
|
||||
|
||||
* fix some markup bugs in query views (issue #266)
|
||||
@@ -329,6 +171,7 @@ Version 0.9 (released 23-Dec-2001)
|
||||
* create dir_alternate.ezt for the flipped rev/name links
|
||||
* various UI tweaks for the directory pages
|
||||
|
||||
|
||||
Version 0.8 (released 10-Dec-2001)
|
||||
|
||||
* add EZT templating mechanism for generating output pages
|
||||
|
@@ -20,7 +20,6 @@ directly.
|
||||
jamesh James Henstridge <???>
|
||||
maxb Max Bowsher <maxb1@ukf.net>
|
||||
eh Erik Hülsmann <e.huelsmann@gmx.net>
|
||||
mhagger Michael Haggerty <mhagger@alum.mit.edu>
|
||||
|
||||
## Local Variables:
|
||||
## coding:utf-8
|
||||
|
226
INSTALL
226
INSTALL
@@ -32,7 +32,7 @@ Congratulations on getting this far. :-)
|
||||
|
||||
* Python 2.0 or later
|
||||
(http://www.python.org/)
|
||||
* Subversion, Version Control System, 1.3.1 or later
|
||||
* Subversion, Version Control System, 1.2.0 or later
|
||||
(binary installation and Python bindings)
|
||||
(http://subversion.tigris.org/)
|
||||
|
||||
@@ -43,8 +43,11 @@ Congratulations on getting this far. :-)
|
||||
* MySQL 3.22 and MySQLdb 0.9.0 or later to create a commit database
|
||||
(http://www.mysql.com/)
|
||||
(http://sourceforge.net/projects/mysql-python)
|
||||
* Pygments 0.9 or later, syntax highlighting engine
|
||||
(http://pygments.org)
|
||||
* Enscript, code colorizer
|
||||
(http://www.codento.com/people/mtr/genscript/)
|
||||
* Highlight, code colorizer, 2.2.10 or later required, 2.4.5 or
|
||||
later recommended for reliable line numbering
|
||||
(http://www.andre-simon.de/)
|
||||
* CvsGraph 1.5.0 or later, graphical CVS revision tree generator
|
||||
(http://www.akhphd.au.dk/~bertho/cvsgraph/)
|
||||
|
||||
@@ -55,7 +58,7 @@ Congratulations on getting this far. :-)
|
||||
|
||||
$ bin/standalone.py -r /PATH/TO/REPOSITORY
|
||||
|
||||
This will start a tiny ViewVC server at http://localhost:49152/viewvc/,
|
||||
This will start a tiny ViewVC server at http://localhost:7467/viewvc/,
|
||||
to which you can connect with your browser.
|
||||
|
||||
Standard operation:
|
||||
@@ -91,9 +94,15 @@ Visitors viewing those versioned controlled documents get the
|
||||
malicious code, too, which might not be what the original author
|
||||
intended.
|
||||
|
||||
For this reason, ViewVC's "checkout" view is disabled by default. If
|
||||
you wish to enable it, simply add "co" to the list of views enabled in
|
||||
the allowed_views configuration option.
|
||||
If you wish to disable ViewVC's "checkout" view which implements this
|
||||
feature, you can do so by editing lib/viewvc.py, and modifying the
|
||||
function view_checkout() like so, adding the lines indicated:
|
||||
|
||||
def view_checkout(request):
|
||||
>> raise debug.ViewVCException('Checkout view is disabled',
|
||||
>> '403 Forbidden')
|
||||
path, rev = _orig_path(request)
|
||||
fp, revision = request.repos.openfile(path, rev)
|
||||
|
||||
|
||||
INSTALLING VIEWVC
|
||||
@@ -138,8 +147,8 @@ installation instructions.
|
||||
root_parents (for CVS or Subversion)
|
||||
default_root
|
||||
root_as_url_component
|
||||
rcs_dir
|
||||
mime_types_files
|
||||
rcs_path
|
||||
mime_types_file
|
||||
|
||||
There are some other options that are usually nice to change. See
|
||||
viewvc.conf for more information. ViewVC provides a working,
|
||||
@@ -168,23 +177,14 @@ checkin database working are below.
|
||||
APACHE CONFIGURATION
|
||||
--------------------
|
||||
|
||||
1) Locate your Apache configuration file(s).
|
||||
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.
|
||||
|
||||
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) 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
|
||||
Either METHOD A:
|
||||
2) 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
|
||||
@@ -195,100 +195,54 @@ APACHE CONFIGURATION
|
||||
ScriptAlias /viewvc /usr/local/viewvc-1.0/bin/cgi/viewvc.cgi
|
||||
ScriptAlias /query /usr/local/viewvc-1.0/bin/cgi/query.cgi
|
||||
|
||||
----------------------------------------
|
||||
METHOD B: CGI mode in cgi-bin directory
|
||||
----------------------------------------
|
||||
Copy the CGI scripts from
|
||||
continue with step 3).
|
||||
|
||||
or alternatively METHOD B:
|
||||
2) 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).
|
||||
|
||||
------------------------------------------
|
||||
METHOD C: CGI mode in ExecCGI'd directory
|
||||
------------------------------------------
|
||||
Copy the CGI scripts from
|
||||
and then there's METHOD C:
|
||||
2) 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
|
||||
|
||||
Note: For this to work mod_cgi has to be loaded. And for the .htaccess file
|
||||
(Note: For this to work mod_cgi has to be loaded. And for the .htaccess file
|
||||
to be effective, "AllowOverride All" or "AllowOverride Options FileInfo"
|
||||
needs to have been specified for the directory.
|
||||
need to have been specified for the directory.)
|
||||
|
||||
------------------------------------------
|
||||
METHOD D: Using mod_python (if installed)
|
||||
------------------------------------------
|
||||
Copy the Python scripts and .htaccess file from
|
||||
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
|
||||
<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.
|
||||
|
||||
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.
|
||||
and enscript colorizing features may not work because they use
|
||||
multithreading. They do work fine with Apache 2.
|
||||
|
||||
----------------------------------------
|
||||
METHOD E: Using mod_wsgi (if installed)
|
||||
----------------------------------------
|
||||
Copy the Python scripts file from
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/mod_python/
|
||||
to the directory of your choosing. Modify httpd.conf with the
|
||||
following directives:
|
||||
continue with step 3).
|
||||
|
||||
WSGIScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/viewvc.wsgi
|
||||
WSGIScriptAlias /query <VIEWVC_INSTALLATION_DIRECTORY>/bin/wsgi/query.wsgi
|
||||
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"
|
||||
|
||||
You'll probably also need the following directive because of the
|
||||
not-quite-sanctioned way that ViewVC manipulates Python objects.
|
||||
4) Optional: Add access control.
|
||||
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
|
||||
Note: WSGI support in ViewVC is at this time quite rudimentary,
|
||||
bordering on downright experimental. Your mileage may vary.
|
||||
|
||||
-----------------------------------------
|
||||
METHOD F: Using mod_fcgid (if installed)
|
||||
-----------------------------------------
|
||||
|
||||
This uses ViewVC's WSGI support (from above), but supports using FastCGI,
|
||||
and is a somewhat hybrid approach of several of the above methods.
|
||||
|
||||
Especially if fcgi is already being used for other purposes, e.g. PHP,
|
||||
also using fcgi can prevent the need for including additional modules
|
||||
(e.g. mod_python or mod_wsgi) within Apache, which may help lessen Apache's
|
||||
memory usage and/or help improve performance.
|
||||
|
||||
This depends on mod_fcgid:
|
||||
|
||||
http://httpd.apache.org/mod_fcgid/
|
||||
|
||||
as well as the fcgi server from Python's flup package:
|
||||
|
||||
http://pypi.python.org/pypi/flup
|
||||
http://trac.saddi.com/flup
|
||||
|
||||
The following are some example httpd.conf fragments you can use to
|
||||
support this configuration:
|
||||
|
||||
ScriptAlias /viewvc /usr/local/viewvc/bin/wsgi/viewvc.fcgi
|
||||
ScriptAlias /query /usr/local/viewvc/bin/wsgi/query.fcgi
|
||||
|
||||
3) Restart Apache.
|
||||
|
||||
The commands to do this vary. "httpd -k restart" and "apache -k
|
||||
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".
|
||||
|
||||
4) [Optional] Add access control.
|
||||
|
||||
In your httpd.conf you can control access to certain modules by
|
||||
adding directives like this:
|
||||
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
|
||||
@@ -306,16 +260,6 @@ APACHE CONFIGURATION
|
||||
http://<server_name>/viewvc/~checkout~/<module_name>
|
||||
http://<server_name>/viewvc/<module_name>.tar.gz?view=tar
|
||||
|
||||
5) Optional: Protect your ViewVC instance from server-whacking webcrawlers.
|
||||
|
||||
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
|
||||
to suffer if a webcrawler finds your ViewVC instance and begins
|
||||
traversing those links. We highly recommend that you add your ViewVC
|
||||
location to a site-wide robots.txt file. Visit the Wikipedia page
|
||||
for Robots.txt (http://en.wikipedia.org/wiki/Robots.txt) for more
|
||||
information.
|
||||
|
||||
|
||||
UPGRADING VIEWVC
|
||||
-----------------
|
||||
@@ -407,37 +351,27 @@ there are some additional steps required to get the database working.
|
||||
ALL <VIEWVC_INSTALLATION_DIRECTORY>/bin/loginfo-handler %{sVv} cvsnt
|
||||
|
||||
To publish Subversion commits into the database:
|
||||
|
||||
|
||||
To build a database of all the commits in the Subversion
|
||||
repository /home/svn, invoke: "./svndbadmin rebuild /home/svn".
|
||||
If you want to update the checkin database, invoke:
|
||||
"./svndbadmin update /home/svn".
|
||||
|
||||
|
||||
To get real time updates, you will need to add a post-commit
|
||||
hook (for the repository example above, the script should go in
|
||||
/home/svn/hooks/post-commit). The script should look something
|
||||
like this:
|
||||
|
||||
#!/bin/sh
|
||||
REPOS="$1"
|
||||
REV="$2"
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/svndbadmin update \
|
||||
"$REPOS" "$REV"
|
||||
|
||||
|
||||
#!/bin/sh
|
||||
REPOS="$1"
|
||||
REV="$2"
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/svndbadmin rebuild "$REPOS" "$REV"
|
||||
|
||||
If you allow revision property changes in your repository,
|
||||
create a post-revprop-change hook script which uses the same
|
||||
'svndbadmin update' command as the post-commit script, except
|
||||
with the addition of the --force option:
|
||||
|
||||
#!/bin/sh
|
||||
REPOS="$1"
|
||||
REV="$2"
|
||||
<VIEWVC_INSTALLATION_DIRECTORY>/bin/svndbadmin update --force \
|
||||
"$REPOS" "$REV"
|
||||
|
||||
This will make sure that the checkin database stays consistent
|
||||
when you change the svn:log, svn:author or svn:date revision
|
||||
properties.
|
||||
create a post-revprop-change hook script containing the same
|
||||
commands as the post-commit one. This will make sure that the
|
||||
checkin database stays consistent when you change the svn:log,
|
||||
svn:author or svn:date revision properties.
|
||||
|
||||
You should be ready to go. Click one of the "Query revision history"
|
||||
links in ViewVC directory listings and give it a try.
|
||||
@@ -446,11 +380,20 @@ links in ViewVC directory listings and give it a try.
|
||||
ENABLING SYNTAX COLORATION
|
||||
--------------------------
|
||||
|
||||
ViewVC uses Pygments (http://pygments.org) for syntax coloration. You
|
||||
need only install a suitable version of that module, and if ViewVC
|
||||
finds it in your Python module path, it will use it (unless you
|
||||
specifically disable the feature by setting use_pygments = 0 in your
|
||||
viewvc.conf file).
|
||||
Enscript and Highlight are two programs that can colorize source code
|
||||
for a lot of languages. ViewVC can be configured to use either one.
|
||||
|
||||
1) Install Enscript or Highlight using your system's package manager
|
||||
or downloading from the project home pages.
|
||||
|
||||
2) Set either the 'use_enscript' or 'use_highlight' options in
|
||||
viewvc.conf to 1.
|
||||
|
||||
3) You may also need to set 'enscript_path' or 'highlight_path' option
|
||||
if the executables are not located on the system PATH.
|
||||
|
||||
That's it! Now when you view the contents of recognized filetypes in
|
||||
ViewVC, you should see colorized syntax.
|
||||
|
||||
|
||||
CVSGRAPH CONFIGURATION
|
||||
@@ -488,12 +431,9 @@ SUBVERSION INTEGRATION
|
||||
Unlike the CVS integration, which simply wraps the RCS and CVS utility
|
||||
programs, the Subversion integration requires additional Python
|
||||
libraries. To use ViewVC with Subversion, make sure you have both
|
||||
Subversion itself and the Subversion Python bindings installed. These
|
||||
can be obtained through typical package distribution mechanisms, or
|
||||
may be build from source. (See the files 'INSTALL' and
|
||||
'subversion/bindings/swig/INSTALL' in the Subversion source tree for
|
||||
more details on how to build and install Subversion and its Python
|
||||
bindings.)
|
||||
Subversion itself and the Subversion Python bindings installed. See
|
||||
Subversion's installation notes for more details on how to build and
|
||||
install these items.
|
||||
|
||||
Generally speaking, you'll know when your installation of Subversion's
|
||||
bindings has been successful if you can import the 'svn.core' module
|
||||
@@ -507,7 +447,7 @@ Python binding you have:
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> from svn.core import *
|
||||
>>> "%s.%s.%s" % (SVN_VER_MAJOR, SVN_VER_MINOR, SVN_VER_PATCH)
|
||||
'1.3.1'
|
||||
'1.2.0'
|
||||
>>>
|
||||
|
||||
Note that by default, Subversion installs its bindings in a location
|
||||
@@ -515,7 +455,7 @@ that is not in Python's default module search path (for example, on
|
||||
Linux systems the default is usually /usr/local/lib/svn-python). You
|
||||
need to remedy this, either by adding this path to Python's module
|
||||
search path, or by relocating the bindings to some place in that
|
||||
search path.
|
||||
search path.
|
||||
|
||||
For example, you might want to create .pth file in your Python
|
||||
installation directory's site-packages area which tells Python where
|
||||
@@ -551,7 +491,7 @@ error: you can't see any files)
|
||||
CVS-Repository. The CGI-script generally runs as the same user
|
||||
that the web server does, often user 'nobody' or 'httpd'.
|
||||
|
||||
* Does ViewVC find your RCS utilities? (edit rcs_dir)
|
||||
* Does ViewVC find your RCS utilities? (edit rcs_path)
|
||||
|
||||
If something else happens or you can't get it to work:
|
||||
|
||||
|
@@ -54,10 +54,7 @@ import query
|
||||
server = sapi.AspServer(Server, Request, Response, Application)
|
||||
try:
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.asp"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
query.main(server, cfg, "viewvc.asp")
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
@@ -54,7 +54,4 @@ import query
|
||||
|
||||
server = sapi.CgiServer()
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.cgi"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
query.main(server, cfg, "viewvc.cgi")
|
||||
|
127
bin/cvsdbadmin
127
bin/cvsdbadmin
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -43,10 +43,10 @@ import os
|
||||
import string
|
||||
import cvsdb
|
||||
import viewvc
|
||||
import vclib.ccvs
|
||||
import vclib.bincvs
|
||||
|
||||
|
||||
def UpdateFile(db, repository, path, update, quiet_level):
|
||||
def UpdateFile(db, repository, path, update):
|
||||
try:
|
||||
if update:
|
||||
commit_list = cvsdb.GetUnrecordedCommitList(repository, path, db)
|
||||
@@ -57,27 +57,20 @@ def UpdateFile(db, repository, path, update, quiet_level):
|
||||
return
|
||||
|
||||
file = string.join(path, "/")
|
||||
printing = 0
|
||||
if update:
|
||||
if quiet_level < 1 or (quiet_level < 2 and len(commit_list)):
|
||||
printing = 1
|
||||
print '[%s [%d new commits]]' % (file, len(commit_list)),
|
||||
print '[%s [%d new commits]]' % (file, len(commit_list)),
|
||||
else:
|
||||
if quiet_level < 2:
|
||||
printing = 1
|
||||
print '[%s [%d commits]]' % (file, len(commit_list)),
|
||||
print '[%s [%d commits]]' % (file, len(commit_list)),
|
||||
|
||||
## add the commits into the database
|
||||
for commit in commit_list:
|
||||
db.AddCommit(commit)
|
||||
if printing:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
if printing:
|
||||
print
|
||||
print
|
||||
|
||||
|
||||
def RecurseUpdate(db, repository, directory, update, quiet_level):
|
||||
def RecurseUpdate(db, repository, directory, update):
|
||||
for entry in repository.listdir(directory, None, {}):
|
||||
path = directory + [entry.name]
|
||||
|
||||
@@ -85,13 +78,13 @@ def RecurseUpdate(db, repository, directory, update, quiet_level):
|
||||
continue
|
||||
|
||||
if entry.kind is vclib.DIR:
|
||||
RecurseUpdate(db, repository, path, update, quiet_level)
|
||||
RecurseUpdate(db, repository, path, update)
|
||||
continue
|
||||
|
||||
if entry.kind is vclib.FILE:
|
||||
UpdateFile(db, repository, path, update, quiet_level)
|
||||
UpdateFile(db, repository, path, update)
|
||||
|
||||
def RootPath(path, quiet_level):
|
||||
def RootPath(path):
|
||||
"""Break os path into cvs root path and other parts"""
|
||||
root = os.path.abspath(path)
|
||||
path_parts = []
|
||||
@@ -100,16 +93,14 @@ def RootPath(path, quiet_level):
|
||||
while 1:
|
||||
if os.path.exists(os.path.join(p, 'CVSROOT')):
|
||||
root = p
|
||||
if quiet_level < 2:
|
||||
print "Using repository root `%s'" % root
|
||||
print "Using repository root `%s'" % root
|
||||
break
|
||||
|
||||
p, pdir = os.path.split(p)
|
||||
if not pdir:
|
||||
del path_parts[:]
|
||||
if quiet_level < 1:
|
||||
print "Using repository root `%s'" % root
|
||||
print "Warning: CVSROOT directory not found."
|
||||
print "Using repository root `%s'" % root
|
||||
print "Warning: CVSROOT directory not found."
|
||||
break
|
||||
|
||||
path_parts.append(pdir)
|
||||
@@ -119,76 +110,46 @@ def RootPath(path, quiet_level):
|
||||
return root, path_parts
|
||||
|
||||
def usage():
|
||||
cmd = os.path.basename(sys.argv[0])
|
||||
sys.stderr.write(
|
||||
"""Administer the ViewVC checkins database data for the CVS repository
|
||||
located at REPOS-PATH.
|
||||
|
||||
Usage: 1. %s [[-q] -q] rebuild REPOS-PATH
|
||||
2. %s [[-q] -q] update REPOS-PATH
|
||||
3. %s [[-q] -q] purge REPOS-PATH
|
||||
|
||||
1. Rebuild the commit database information for the repository located
|
||||
at REPOS-PATH, after first purging information specific to that
|
||||
repository (if any).
|
||||
|
||||
2. Update the commit database information for all unrecorded commits
|
||||
in the repository located at REPOS-PATH.
|
||||
|
||||
3. Purge information specific to the repository located at REPOS-PATH
|
||||
from the database.
|
||||
|
||||
Use the -q flag to cause this script to be less verbose; use it twice to
|
||||
invoke a peaceful state of noiselessness.
|
||||
|
||||
""" % (cmd, cmd, cmd))
|
||||
print 'Usage: %s <command> [arguments]' % (sys.argv[0])
|
||||
print 'Performs administrative functions for the CVSdb database'
|
||||
print 'Commands:'
|
||||
print ' rebuild <repository> rebuilds the CVSdb database'
|
||||
print ' for all files in the repository'
|
||||
print ' update <repository> updates the CVSdb database for'
|
||||
print ' all unrecorded commits'
|
||||
print
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
## main
|
||||
if __name__ == '__main__':
|
||||
args = sys.argv
|
||||
|
||||
# check the quietness level (0 = verbose, 1 = new commits, 2 = silent)
|
||||
quiet_level = 0
|
||||
while 1:
|
||||
try:
|
||||
index = args.index('-q')
|
||||
quiet_level = quiet_level + 1
|
||||
del args[index]
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
# validate the command
|
||||
if len(args) <= 2:
|
||||
usage()
|
||||
command = args[1].lower()
|
||||
if command not in ('rebuild', 'update', 'purge'):
|
||||
sys.stderr.write('ERROR: unknown command %s\n' % command)
|
||||
## check that a command was given
|
||||
if len(sys.argv) <= 2:
|
||||
usage()
|
||||
|
||||
# get repository and path, and do the work
|
||||
root, path_parts = RootPath(args[2], quiet_level)
|
||||
rootpath = vclib.ccvs.canonicalize_rootpath(root)
|
||||
## set the handler function for the command
|
||||
command = sys.argv[1]
|
||||
if string.lower(command) == 'rebuild':
|
||||
update = 0
|
||||
elif string.lower(command) == 'update':
|
||||
update = 1
|
||||
else:
|
||||
print 'ERROR: unknown command %s' % (command)
|
||||
usage()
|
||||
|
||||
# get repository path
|
||||
root, path_parts = RootPath(sys.argv[2])
|
||||
|
||||
## run command
|
||||
try:
|
||||
cfg = viewvc.load_config(CONF_PATHNAME)
|
||||
db = cvsdb.ConnectDatabase(cfg)
|
||||
## connect to the database we are updating
|
||||
cfg = viewvc.load_config(CONF_PATHNAME)
|
||||
db = cvsdb.ConnectDatabase(cfg)
|
||||
|
||||
if command in ('rebuild', 'purge'):
|
||||
if quiet_level < 2:
|
||||
print "Purging existing data for repository root `%s'" % root
|
||||
try:
|
||||
db.PurgeRepository(root)
|
||||
except cvsdb.UnknownRepositoryError, e:
|
||||
if command == 'purge':
|
||||
sys.stderr.write("ERROR: " + str(e) + "\n")
|
||||
sys.exit(1)
|
||||
repository = vclib.bincvs.BinCVSRepository(None, root, cfg.general)
|
||||
|
||||
RecurseUpdate(db, repository, path_parts, update)
|
||||
|
||||
if command in ('rebuild', 'update'):
|
||||
repository = vclib.ccvs.CVSRepository(None, rootpath, None,
|
||||
cfg.utilities, 0)
|
||||
RecurseUpdate(db, repository, path_parts,
|
||||
command == 'update', quiet_level)
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
print '** break **'
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -44,7 +44,7 @@ import getopt
|
||||
import re
|
||||
import cvsdb
|
||||
import viewvc
|
||||
import vclib.ccvs
|
||||
import vclib.bincvs
|
||||
|
||||
DEBUG_FLAG = 0
|
||||
|
||||
@@ -77,8 +77,6 @@ def Cvs1Dot12ArgParse(args):
|
||||
|
||||
if args[1] == '- New directory':
|
||||
return None, None
|
||||
elif args[1] == '- Imported sources':
|
||||
return None, None
|
||||
else:
|
||||
directory = args.pop(0)
|
||||
files = []
|
||||
@@ -223,8 +221,7 @@ def NextFile(s, pos = 0):
|
||||
def ProcessLoginfo(rootpath, directory, files):
|
||||
cfg = viewvc.load_config(CONF_PATHNAME)
|
||||
db = cvsdb.ConnectDatabase(cfg)
|
||||
repository = vclib.ccvs.CVSRepository(None, rootpath, None,
|
||||
cfg.utilities, 0)
|
||||
repository = vclib.bincvs.BinCVSRepository(None, rootpath, cfg.general)
|
||||
|
||||
# split up the directory components
|
||||
dirpath = filter(None, string.split(os.path.normpath(directory), os.sep))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -18,22 +18,21 @@
|
||||
|
||||
import os, sys, string
|
||||
import popen2
|
||||
import getopt
|
||||
|
||||
## ------------------------------------------------------------------------
|
||||
## Stuff common to all schemas
|
||||
##
|
||||
DATABASE_SCRIPT_COMMON="""\
|
||||
INTRO_TEXT = """\
|
||||
This script creates the database and tables in MySQL used by the ViewVC
|
||||
checkin database. You will be prompted for: database user, database user
|
||||
password, and database name. This script will use mysql to create the
|
||||
database for you. You will then need to set the appropriate parameters
|
||||
in your viewvc.conf file under the [cvsdb] section.
|
||||
"""
|
||||
|
||||
DATABASE_SCRIPT="""\
|
||||
DROP DATABASE IF EXISTS <dbname>;
|
||||
CREATE DATABASE <dbname>;
|
||||
|
||||
USE <dbname>;
|
||||
"""
|
||||
|
||||
## ------------------------------------------------------------------------
|
||||
## Version 0: The original, Bonsai-compatible schema.
|
||||
##
|
||||
DATABASE_SCRIPT_VERSION_0="""\
|
||||
DROP TABLE IF EXISTS branches;
|
||||
CREATE TABLE branches (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
@@ -93,7 +92,7 @@ CREATE TABLE files (
|
||||
DROP TABLE IF EXISTS people;
|
||||
CREATE TABLE people (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
who varchar(128) binary DEFAULT '' NOT NULL,
|
||||
who varchar(32) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE who (who)
|
||||
) TYPE=MyISAM;
|
||||
@@ -121,213 +120,25 @@ CREATE TABLE tags (
|
||||
) TYPE=MyISAM;
|
||||
"""
|
||||
|
||||
## ------------------------------------------------------------------------
|
||||
## Version 1: Adds the 'metadata' table. Adds 'descid' index to
|
||||
## 'checkins' table, and renames that table to 'commits'.
|
||||
##
|
||||
DATABASE_SCRIPT_VERSION_1="""\
|
||||
DROP TABLE IF EXISTS branches;
|
||||
CREATE TABLE branches (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
branch varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE branch (branch)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS commits;
|
||||
CREATE TABLE commits (
|
||||
type enum('Change','Add','Remove'),
|
||||
ci_when datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
||||
whoid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
dirid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
fileid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
revision varchar(32) binary DEFAULT '' NOT NULL,
|
||||
stickytag varchar(255) binary DEFAULT '' NOT NULL,
|
||||
branchid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
addedlines int(11) DEFAULT '0' NOT NULL,
|
||||
removedlines int(11) DEFAULT '0' NOT NULL,
|
||||
descid mediumint(9),
|
||||
UNIQUE repositoryid (repositoryid,dirid,fileid,revision),
|
||||
KEY ci_when (ci_when),
|
||||
KEY whoid (whoid),
|
||||
KEY repositoryid_2 (repositoryid),
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid),
|
||||
KEY descid (descid)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS descs;
|
||||
CREATE TABLE descs (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
description text,
|
||||
hash bigint(20) DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY hash (hash)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS dirs;
|
||||
CREATE TABLE dirs (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
dir varchar(255) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE dir (dir)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS files;
|
||||
CREATE TABLE files (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
file varchar(255) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE file (file)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS people;
|
||||
CREATE TABLE people (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
who varchar(128) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE who (who)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS repositories;
|
||||
CREATE TABLE repositories (
|
||||
id mediumint(9) NOT NULL auto_increment,
|
||||
repository varchar(64) binary DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE repository (repository)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS tags;
|
||||
CREATE TABLE tags (
|
||||
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
branchid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
dirid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
fileid mediumint(9) DEFAULT '0' NOT NULL,
|
||||
revision varchar(32) binary DEFAULT '' NOT NULL,
|
||||
UNIQUE repositoryid (repositoryid,dirid,fileid,branchid,revision),
|
||||
KEY repositoryid_2 (repositoryid),
|
||||
KEY dirid (dirid),
|
||||
KEY fileid (fileid),
|
||||
KEY branchid (branchid)
|
||||
) TYPE=MyISAM;
|
||||
|
||||
DROP TABLE IF EXISTS metadata;
|
||||
CREATE TABLE metadata (
|
||||
name varchar(255) binary DEFAULT '' NOT NULL,
|
||||
value text,
|
||||
PRIMARY KEY (name),
|
||||
UNIQUE name (name)
|
||||
) TYPE=MyISAM;
|
||||
INSERT INTO metadata (name, value) VALUES ('version', '1');
|
||||
"""
|
||||
|
||||
BONSAI_COMPAT="""
|
||||
WARNING: Creating Bonsai-compatible legacy database version. Some ViewVC
|
||||
features may not be available, or may not perform especially well.
|
||||
|
||||
"""
|
||||
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
def usage_and_exit(errmsg=None):
|
||||
stream = errmsg is None and sys.stdout or sys.stderr
|
||||
stream.write("""\
|
||||
Usage: %s [OPTIONS]
|
||||
|
||||
This script creates the database and tables in MySQL used by the
|
||||
ViewVC checkin database. In order to operate correctly, it needs to
|
||||
know the following: your database server hostname, database user,
|
||||
database user password, and database name. (You will be prompted for
|
||||
any of this information that you do not provide via command-line
|
||||
options.) This script will use the 'mysql' program to create the
|
||||
database for you. You will then need to set the appropriate
|
||||
parameters in the [cvsdb] section of your viewvc.conf file.
|
||||
|
||||
Options:
|
||||
|
||||
--dbname=ARG Use ARG as the ViewVC database name to create.
|
||||
[Default: ViewVC]
|
||||
|
||||
--help Show this usage message.
|
||||
|
||||
--hostname=ARG Use ARG as the hostname for the MySQL connection.
|
||||
[Default: localhost]
|
||||
|
||||
--password=ARG Use ARG as the password for the MySQL connection.
|
||||
|
||||
--username=ARG Use ARG as the username for the MySQL connection.
|
||||
|
||||
--version=ARG Create the database using the schema employed by
|
||||
version ARG of ViewVC. Valid values are:
|
||||
[ "1.0" ]
|
||||
|
||||
""" % (os.path.basename(sys.argv[0])))
|
||||
if errmsg is not None:
|
||||
stream.write("[ERROR] %s.\n" % (errmsg))
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# Parse the command-line options, if any.
|
||||
dbname = version = hostname = username = password = None
|
||||
opts, args = getopt.getopt(sys.argv[1:], '', [ 'dbname=',
|
||||
'help',
|
||||
'hostname=',
|
||||
'password=',
|
||||
'username=',
|
||||
'version=',
|
||||
])
|
||||
if len(args) > 0:
|
||||
usage_and_exit("Unexpected command-line parameters")
|
||||
for name, value in opts:
|
||||
if name == '--help':
|
||||
usage_and_exit(0)
|
||||
elif name == '--dbname':
|
||||
dbname = value
|
||||
elif name == '--hostname':
|
||||
hostname = value
|
||||
elif name == '--username':
|
||||
username = value
|
||||
elif name == '--password':
|
||||
password = value
|
||||
elif name == '--version':
|
||||
if value in ["1.0"]:
|
||||
version = value
|
||||
else:
|
||||
usage_and_exit("Invalid version specified")
|
||||
print INTRO_TEXT
|
||||
|
||||
user = raw_input("MySQL User: ")
|
||||
passwd = raw_input("MySQL Password: ")
|
||||
dbase = raw_input("ViewVC Database Name [default: ViewVC]: ")
|
||||
if not dbase:
|
||||
dbase = "ViewVC"
|
||||
|
||||
# Prompt for information not provided via command-line options.
|
||||
if hostname is None:
|
||||
hostname = raw_input("MySQL Hostname [default: localhost]: ") or ""
|
||||
if username is None:
|
||||
username = raw_input("MySQL User: ")
|
||||
if password is None:
|
||||
password = raw_input("MySQL Password: ")
|
||||
if dbname is None:
|
||||
dbname = raw_input("ViewVC Database Name [default: ViewVC]: ") or "ViewVC"
|
||||
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
|
||||
|
||||
# Create the database
|
||||
dscript = string.replace(DATABASE_SCRIPT_COMMON, "<dbname>", dbname)
|
||||
if version == "1.0":
|
||||
print BONSAI_COMPAT
|
||||
dscript = dscript + DATABASE_SCRIPT_VERSION_0
|
||||
else:
|
||||
dscript = dscript + DATABASE_SCRIPT_VERSION_1
|
||||
|
||||
host_option = hostname and "--host=%s" % (hostname) or ""
|
||||
if sys.platform == "win32":
|
||||
cmd = "mysql --user=%s --password=%s %s "\
|
||||
% (username, password, host_option)
|
||||
mysql = os.popen(cmd, "w") # popen2.Popen3 is not provided on windows
|
||||
# popen2.Popen3 is not provided on windows
|
||||
cmd = "mysql --user=%s --password=%s" % (user, passwd)
|
||||
mysql = os.popen(cmd, "w")
|
||||
mysql.write(dscript)
|
||||
status = mysql.close()
|
||||
else:
|
||||
cmd = "{ mysql --user=%s --password=%s %s ; } 2>&1" \
|
||||
% (username, password, host_option)
|
||||
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
|
||||
pipes = popen2.Popen3(cmd)
|
||||
pipes.tochild.write(dscript)
|
||||
pipes.tochild.close()
|
||||
@@ -335,12 +146,9 @@ if __name__ == "__main__":
|
||||
status = pipes.wait()
|
||||
|
||||
if status:
|
||||
print "[ERROR] The database did not create sucessfully."
|
||||
sys.exit(1)
|
||||
print "[ERROR] the database did not create sucessfully."
|
||||
sys.exit(1)
|
||||
|
||||
print "Database created successfully. Don't forget to configure the "
|
||||
print "[cvsdb] section of your viewvc.conf file."
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
sys.exit(0)
|
||||
print "Database created successfully."
|
||||
sys.exit(0)
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -42,33 +42,16 @@ if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
|
||||
import sapi
|
||||
import imp
|
||||
|
||||
# Import real ViewVC module
|
||||
fp, pathname, description = imp.find_module('viewvc', [LIBRARY_DIR])
|
||||
try:
|
||||
viewvc = imp.load_module('viewvc', fp, pathname, description)
|
||||
finally:
|
||||
if fp:
|
||||
fp.close()
|
||||
|
||||
# Import real ViewVC Query modules
|
||||
fp, pathname, description = imp.find_module('query', [LIBRARY_DIR])
|
||||
try:
|
||||
query = imp.load_module('query', fp, pathname, description)
|
||||
finally:
|
||||
if fp:
|
||||
fp.close()
|
||||
import viewvc
|
||||
import query
|
||||
reload(query) # need reload because initial import loads this stub file
|
||||
|
||||
cfg = viewvc.load_config(CONF_PATHNAME)
|
||||
|
||||
def index(req):
|
||||
server = sapi.ModPythonServer(req)
|
||||
try:
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.py"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
query.main(server, cfg, "viewvc.py")
|
||||
finally:
|
||||
server.close()
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -42,15 +42,9 @@ if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
|
||||
import sapi
|
||||
import imp
|
||||
import viewvc
|
||||
reload(viewvc) # need reload because initial import loads this stub file
|
||||
|
||||
# Import real ViewVC module
|
||||
fp, pathname, description = imp.find_module('viewvc', [LIBRARY_DIR])
|
||||
try:
|
||||
viewvc = imp.load_module('viewvc', fp, pathname, description)
|
||||
finally:
|
||||
if fp:
|
||||
fp.close()
|
||||
|
||||
def index(req):
|
||||
server = sapi.ModPythonServer(req)
|
||||
|
1261
bin/standalone.py
1261
bin/standalone.py
File diff suppressed because it is too large
Load Diff
183
bin/svndbadmin
183
bin/svndbadmin
@@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2004-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2004-2007 James Henstridge
|
||||
# Copyright (C) 2004 James Henstridge
|
||||
#
|
||||
# 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
|
||||
@@ -28,8 +27,8 @@
|
||||
#
|
||||
# If you allow changes to revision properties in your repository, you
|
||||
# might also want to set up something similar in the
|
||||
# post-revprop-change hook using "update" with the --force option to
|
||||
# keep the checkin database consistent with the repository.
|
||||
# post-revprop-change hook using "rebuild" instead of "update" to keep
|
||||
# the checkin database consistent with the repository.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
@@ -50,9 +49,9 @@ import sys
|
||||
import os
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0], "../../lib")))
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0], "../../lib")))
|
||||
|
||||
#########################################################################
|
||||
|
||||
@@ -67,22 +66,24 @@ import svn.delta
|
||||
|
||||
import cvsdb
|
||||
import viewvc
|
||||
import vclib
|
||||
|
||||
class SvnRepo:
|
||||
"""Class used to manage a connection to a SVN repository."""
|
||||
def __init__(self, path):
|
||||
def __init__(self, path, pool):
|
||||
self.scratch_pool = svn.core.svn_pool_create(pool)
|
||||
self.path = path
|
||||
self.repo = svn.repos.svn_repos_open(path)
|
||||
self.repo = svn.repos.svn_repos_open(path, pool)
|
||||
self.fs = svn.repos.svn_repos_fs(self.repo)
|
||||
self.rev_max = svn.fs.youngest_rev(self.fs)
|
||||
# youngest revision of base of file system is highest revision number
|
||||
self.rev_max = svn.fs.youngest_rev(self.fs, pool)
|
||||
def __getitem__(self, rev):
|
||||
if rev is None:
|
||||
rev = self.rev_max
|
||||
elif rev < 0:
|
||||
rev = rev + self.rev_max + 1
|
||||
assert 0 <= rev <= self.rev_max
|
||||
rev = SvnRev(self, rev)
|
||||
rev = SvnRev(self, rev, self.scratch_pool)
|
||||
svn.core.svn_pool_clear(self.scratch_pool)
|
||||
return rev
|
||||
|
||||
_re_diff_change_command = re.compile('(\d+)(?:,(\d+))?([acd])(\d+)(?:,(\d+))?')
|
||||
@@ -132,36 +133,39 @@ def _get_diff_counts(diff_fp):
|
||||
class SvnRev:
|
||||
"""Class used to hold information about a particular revision of
|
||||
the repository."""
|
||||
def __init__(self, repo, rev):
|
||||
def __init__(self, repo, rev, pool):
|
||||
self.repo = repo
|
||||
self.rev = rev
|
||||
self.pool = pool
|
||||
self.rev_roots = {} # cache of revision roots
|
||||
|
||||
subpool = svn.core.svn_pool_create(pool)
|
||||
|
||||
# revision properties ...
|
||||
revprops = svn.fs.revision_proplist(repo.fs, rev)
|
||||
self.author = str(revprops.get(svn.core.SVN_PROP_REVISION_AUTHOR,''))
|
||||
self.date = str(revprops.get(svn.core.SVN_PROP_REVISION_DATE, ''))
|
||||
self.log = str(revprops.get(svn.core.SVN_PROP_REVISION_LOG, ''))
|
||||
properties = svn.fs.revision_proplist(repo.fs, rev, pool)
|
||||
self.author = str(properties.get(svn.core.SVN_PROP_REVISION_AUTHOR,''))
|
||||
self.date = str(properties.get(svn.core.SVN_PROP_REVISION_DATE, ''))
|
||||
self.log = str(properties.get(svn.core.SVN_PROP_REVISION_LOG, ''))
|
||||
|
||||
# convert the date string to seconds since epoch ...
|
||||
try:
|
||||
self.date = svn.core.svn_time_from_cstring(self.date) / 1000000
|
||||
except:
|
||||
self.date = None
|
||||
self.date = svn.core.secs_from_timestr(self.date, pool)
|
||||
|
||||
# get a root for the current revisions
|
||||
fsroot = self._get_root_for_rev(rev)
|
||||
|
||||
# find changes in the revision
|
||||
editor = svn.repos.RevisionChangeCollector(repo.fs, rev)
|
||||
e_ptr, e_baton = svn.delta.make_editor(editor)
|
||||
svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
editor = svn.repos.RevisionChangeCollector(repo.fs, rev, pool)
|
||||
e_ptr, e_baton = svn.delta.make_editor(editor, pool)
|
||||
svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton, pool)
|
||||
|
||||
self.changes = []
|
||||
for path, change in editor.changes.items():
|
||||
|
||||
# clear the iteration subpool
|
||||
svn.core.svn_pool_clear(subpool)
|
||||
|
||||
# skip non-file changes
|
||||
if change.item_kind != svn.core.svn_node_file:
|
||||
continue
|
||||
if change.item_kind != svn.core.svn_node_file: continue
|
||||
|
||||
# deal with the change types we handle
|
||||
base_root = None
|
||||
@@ -178,7 +182,8 @@ class SvnRev:
|
||||
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)
|
||||
change.path and change.path or None,
|
||||
subpool, [])
|
||||
diff_fp = diffobj.get_pipe()
|
||||
plus, minus = _get_diff_counts(diff_fp)
|
||||
self.changes.append((path, action, plus, minus))
|
||||
@@ -187,11 +192,12 @@ class SvnRev:
|
||||
"""Fetch a revision root from a cache of such, or a fresh root
|
||||
(which is then cached for later use."""
|
||||
if not self.rev_roots.has_key(rev):
|
||||
self.rev_roots[rev] = svn.fs.revision_root(self.repo.fs, rev)
|
||||
self.rev_roots[rev] = svn.fs.revision_root(self.repo.fs, rev,
|
||||
self.pool)
|
||||
return self.rev_roots[rev]
|
||||
|
||||
|
||||
def handle_revision(db, command, repo, rev, verbose, force=0):
|
||||
def handle_revision(db, command, repo, rev, verbose):
|
||||
"""Adds a particular revision of the repository to the checkin database."""
|
||||
revision = repo[rev]
|
||||
committed = 0
|
||||
@@ -225,7 +231,7 @@ def handle_revision(db, command, repo, rev, verbose, force=0):
|
||||
|
||||
if command == 'update':
|
||||
result = db.CheckCommit(commit)
|
||||
if result and not force:
|
||||
if result:
|
||||
continue # already recorded
|
||||
|
||||
# commit to database
|
||||
@@ -238,83 +244,38 @@ def handle_revision(db, command, repo, rev, verbose, force=0):
|
||||
else:
|
||||
print "skipped (already recorded)."
|
||||
|
||||
def main(command, repository, revs=[], verbose=0, force=0):
|
||||
def main(pool, command, repository, rev=None, verbose=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
|
||||
try:
|
||||
db.PurgeRepository(repository)
|
||||
except cvsdb.UnknownRepositoryError, e:
|
||||
if command == 'purge':
|
||||
sys.stderr.write("ERROR: " + str(e) + "\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Record what must be recorded.
|
||||
if command in ('rebuild', 'update'):
|
||||
if not os.path.exists(repository):
|
||||
sys.stderr.write('ERROR: could not find repository %s\n'
|
||||
% (repository))
|
||||
sys.exit(1)
|
||||
repo = SvnRepo(repository)
|
||||
if command == 'rebuild' or (command == 'update' and not revs):
|
||||
for rev in range(repo.rev_max+1):
|
||||
handle_revision(db, command, repo, rev, verbose)
|
||||
elif command == 'update':
|
||||
if revs[0] is None:
|
||||
revs[0] = repo.rev_max
|
||||
if revs[1] is None:
|
||||
revs[1] = repo.rev_max
|
||||
revs.sort()
|
||||
for rev in range(revs[0], revs[1]+1):
|
||||
handle_revision(db, command, repo, rev, verbose, force)
|
||||
|
||||
def _rev2int(r):
|
||||
if r == 'HEAD':
|
||||
r = None
|
||||
repo = SvnRepo(repository, pool)
|
||||
if rev:
|
||||
handle_revision(db, command, repo, rev, verbose)
|
||||
else:
|
||||
r = int(r)
|
||||
if r < 0:
|
||||
raise ValueError, "invalid revision '%d'" % (r)
|
||||
return r
|
||||
for rev in range(repo.rev_max+1):
|
||||
handle_revision(db, command, repo, rev, verbose)
|
||||
|
||||
def usage():
|
||||
cmd = os.path.basename(sys.argv[0])
|
||||
sys.stderr.write(
|
||||
"""Administer the ViewVC checkins database data for the Subversion repository
|
||||
located at REPOS-PATH.
|
||||
sys.stderr.write("""
|
||||
Usage: 1. %s [-v] rebuild REPOSITORY [REVISION]
|
||||
2. %s [-v] update REPOSITORY [REVISION]
|
||||
|
||||
Usage: 1. %s [-v] rebuild REPOS-PATH
|
||||
2. %s [-v] update REPOS-PATH [REV:[REV2]] [--force]
|
||||
3. %s [-v] purge REPOS-PATH
|
||||
1. Rebuild the commit database information for REPOSITORY across all revisions
|
||||
or, optionally, only for the specified REVISION.
|
||||
|
||||
1. Rebuild the commit database information for the repository located
|
||||
at REPOS-PATH across all revisions, after first purging
|
||||
information specific to that repository (if any).
|
||||
|
||||
2. Update the commit database information for the repository located
|
||||
at REPOS-PATH across all revisions or, optionally, only for the
|
||||
specified revision REV (or revision range REV:REV2). This is just
|
||||
like rebuilding, except that, unless --force is specified, no
|
||||
commit information will be stored for commits already present in
|
||||
the database. If a range is specified, the revisions will be
|
||||
processed in ascending order, and you may specify "HEAD" to
|
||||
indicate "the youngest revision currently in the repository".
|
||||
|
||||
3. Purge information specific to the repository located at REPOS-PATH
|
||||
from the database.
|
||||
2. Update the commit database information for REPOSITORY across all revisions
|
||||
or, optionally, only for the specified REVISION. This is just like
|
||||
rebuilding, except that no commit information will be stored for
|
||||
commits already present in the database.
|
||||
|
||||
Use the -v flag to cause this script to give progress information as it works.
|
||||
|
||||
""" % (cmd, cmd, cmd))
|
||||
""" % (cmd, cmd))
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
verbose = 0
|
||||
force = 0
|
||||
|
||||
args = sys.argv
|
||||
try:
|
||||
index = args.index('-v')
|
||||
@@ -322,47 +283,29 @@ if __name__ == '__main__':
|
||||
del args[index]
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
index = args.index('--force')
|
||||
force = 1
|
||||
del args[index]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if len(args) < 3:
|
||||
usage()
|
||||
|
||||
command = string.lower(args[1])
|
||||
if command not in ('rebuild', 'update', 'purge'):
|
||||
if command not in ('rebuild', 'update'):
|
||||
sys.stderr.write('ERROR: unknown command %s\n' % command)
|
||||
usage()
|
||||
|
||||
revs = []
|
||||
repository = args[2]
|
||||
if not os.path.exists(repository):
|
||||
sys.stderr.write('ERROR: could not find repository %s\n' % repository)
|
||||
usage()
|
||||
|
||||
if len(sys.argv) > 3:
|
||||
if command == 'rebuild':
|
||||
sys.stderr.write('ERROR: rebuild no longer accepts a revision '
|
||||
'number argument. Usage update --force.')
|
||||
usage()
|
||||
elif command != 'update':
|
||||
usage()
|
||||
rev = sys.argv[3]
|
||||
try:
|
||||
revs = map(lambda x: _rev2int(x), sys.argv[3].split(':'))
|
||||
if len(revs) > 2:
|
||||
raise ValueError, "too many revisions in range"
|
||||
if len(revs) == 1:
|
||||
revs.append(revs[0])
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
sys.stderr.write('ERROR: invalid revision specification "%s"\n' \
|
||||
% sys.argv[3])
|
||||
sys.stderr.write('ERROR: revision "%s" is not numeric\n' % rev)
|
||||
usage()
|
||||
else:
|
||||
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:
|
||||
print
|
||||
print '** break **'
|
||||
sys.exit(0)
|
||||
repository = cvsdb.CleanRepository(os.path.abspath(repository))
|
||||
svn.core.run_app(main, command, repository, rev, verbose)
|
||||
|
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# viewvc: View CVS/SVN repositories via a web browser
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This is a fcgi entry point for the query ViewVC app. It's appropriate
|
||||
# for use with mod_fcgid and flup. It defines a single application function
|
||||
# that is a valid fcgi entry point.
|
||||
#
|
||||
# mod_fcgid: http://httpd.apache.org/mod_fcgid/
|
||||
# flup:
|
||||
# http://pypi.python.org/pypi/flup
|
||||
# http://trac.saddi.com/flup
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import sys, os
|
||||
|
||||
LIBRARY_DIR = None
|
||||
CONF_PATHNAME = None
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
|
||||
"../../../lib")))
|
||||
|
||||
import sapi
|
||||
import viewvc
|
||||
import query
|
||||
from flup.server import fcgi
|
||||
|
||||
def application(environ, start_response):
|
||||
server = sapi.WsgiServer(environ, start_response)
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.fcgi"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
return []
|
||||
|
||||
fcgi.WSGIServer(application).run()
|
@@ -1,45 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# viewvc: View CVS/SVN repositories via a web browser
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This is a wsgi entry point for the query ViewVC app. It's appropriate
|
||||
# for use with mod_wsgi. It defines a single application function that
|
||||
# is a valid wsgi entry point.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import sys, os
|
||||
|
||||
LIBRARY_DIR = None
|
||||
CONF_PATHNAME = None
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
|
||||
"../../../lib")))
|
||||
|
||||
import sapi
|
||||
import viewvc
|
||||
import query
|
||||
|
||||
def application(environ, start_response):
|
||||
server = sapi.WsgiServer(environ, start_response)
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
viewvc_base_url = cfg.query.viewvc_base_url
|
||||
if viewvc_base_url is None:
|
||||
viewvc_base_url = "viewvc.wsgi"
|
||||
query.main(server, cfg, viewvc_base_url)
|
||||
return []
|
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# viewvc: View CVS/SVN repositories via a web browser
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This is a fcgi entry point for the main ViewVC app. It's appropriate
|
||||
# for use with mod_fcgid and flup. It defines a single application function
|
||||
# that is a valid fcgi entry point.
|
||||
#
|
||||
# mod_fcgid: http://httpd.apache.org/mod_fcgid/
|
||||
# flup:
|
||||
# http://pypi.python.org/pypi/flup
|
||||
# http://trac.saddi.com/flup
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import sys, os
|
||||
|
||||
LIBRARY_DIR = None
|
||||
CONF_PATHNAME = None
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
|
||||
"../../../lib")))
|
||||
|
||||
import sapi
|
||||
import viewvc
|
||||
from flup.server import fcgi
|
||||
|
||||
def application(environ, start_response):
|
||||
server = sapi.WsgiServer(environ, start_response)
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
viewvc.main(server, cfg)
|
||||
return []
|
||||
|
||||
fcgi.WSGIServer(application).run()
|
@@ -1,41 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# viewvc: View CVS/SVN repositories via a web browser
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
# This is a wsgi entry point for the main ViewVC app. It's appropriate
|
||||
# for use with mod_wsgi. It defines a single application function that
|
||||
# is a valid wsgi entry point.
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import sys, os
|
||||
|
||||
LIBRARY_DIR = None
|
||||
CONF_PATHNAME = None
|
||||
|
||||
if LIBRARY_DIR:
|
||||
sys.path.insert(0, LIBRARY_DIR)
|
||||
else:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(sys.argv[0],
|
||||
"../../../lib")))
|
||||
|
||||
import sapi
|
||||
import viewvc
|
||||
|
||||
def application(environ, start_response):
|
||||
server = sapi.WsgiServer(environ, start_response)
|
||||
cfg = viewvc.load_config(CONF_PATHNAME, server)
|
||||
viewvc.main(server, cfg)
|
||||
return []
|
@@ -1,32 +0,0 @@
|
||||
#---------------------------------------------------------------------------
|
||||
#
|
||||
# MIME type mapping file for ViewVC
|
||||
#
|
||||
# Information on ViewVC is located at the following web site:
|
||||
# http://viewvc.org/
|
||||
#
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# THE FORMAT OF THIS FILE
|
||||
#
|
||||
# This file contains records -- one per line -- of the following format:
|
||||
#
|
||||
# MIME_TYPE [EXTENSION [EXTENSION ...]]
|
||||
#
|
||||
# where whitespace separates the MIME_TYPE from the EXTENSION(s),
|
||||
# and the EXTENSIONs from each other.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# text/x-csh csh
|
||||
# text/x-csrc c
|
||||
# text/x-diff diff patch
|
||||
# image/png png
|
||||
# image/jpeg jpeg jpg jpe
|
||||
#
|
||||
# By default, this file is left empty, allowing ViewVC to continue
|
||||
# consulting it first without overriding the MIME type mappings
|
||||
# found in more standard mapping files (such as those provided as
|
||||
# part of the operating system or web server software).
|
||||
#
|
||||
#
|
File diff suppressed because it is too large
Load Diff
8
elemx/java/.cvsignore
Normal file
8
elemx/java/.cvsignore
Normal file
@@ -0,0 +1,8 @@
|
||||
elx-java
|
||||
j_keywords.c
|
||||
j_keywords.h
|
||||
j_scan.c
|
||||
j_scan.h
|
||||
java.c
|
||||
java.h
|
||||
*.output
|
6
elemx/python/.cvsignore
Normal file
6
elemx/python/.cvsignore
Normal file
@@ -0,0 +1,6 @@
|
||||
elx-python
|
||||
py_keywords.c
|
||||
py_keywords.h
|
||||
python.c
|
||||
python.h
|
||||
*.output
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -39,7 +39,7 @@ def _parse(hdr, result):
|
||||
while pos < len(hdr):
|
||||
name = _re_token.match(hdr, pos)
|
||||
if not name:
|
||||
raise AcceptLanguageParseError()
|
||||
raise AcceptParseError()
|
||||
a = result.item_class(string.lower(name.group(1)))
|
||||
pos = name.end()
|
||||
while 1:
|
||||
@@ -210,7 +210,7 @@ class _LanguageSelector:
|
||||
def append(self, item):
|
||||
self.requested.append(item)
|
||||
|
||||
class AcceptLanguageParseError(Exception):
|
||||
class AcceptParseError(Exception):
|
||||
pass
|
||||
|
||||
def _test():
|
||||
|
66
lib/blame.py
66
lib/blame.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 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
|
||||
@@ -26,14 +26,15 @@
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import string
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import math
|
||||
import cgi
|
||||
import vclib
|
||||
import sapi
|
||||
import vclib.ccvs.blame
|
||||
|
||||
|
||||
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
||||
|
||||
@@ -81,7 +82,7 @@ class HTMLBlameSource:
|
||||
diff_url = None
|
||||
if item.prev_rev:
|
||||
diff_url = '%sr1=%s&r2=%s' % (self.diff_url, item.prev_rev, item.rev)
|
||||
thisline = link_includes(sapi.escape(item.text), self.repos,
|
||||
thisline = link_includes(cgi.escape(item.text), self.repos,
|
||||
self.path_parts, self.include_url)
|
||||
return _item(text=thisline, line_number=item.line_number,
|
||||
rev=item.rev, prev_rev=item.prev_rev,
|
||||
@@ -99,44 +100,73 @@ class _item:
|
||||
|
||||
|
||||
def make_html(root, rcs_path):
|
||||
import vclib.ccvs.blame
|
||||
bs = vclib.ccvs.blame.BlameSource(os.path.join(root, rcs_path))
|
||||
|
||||
count = bs.num_lines
|
||||
if count == 0:
|
||||
count = 1
|
||||
|
||||
line_num_width = int(math.log10(count)) + 1
|
||||
revision_width = 3
|
||||
author_width = 5
|
||||
line = 0
|
||||
old_revision = 0
|
||||
row_color = 'ffffff'
|
||||
row_color = ''
|
||||
inMark = 0
|
||||
rev_count = 0
|
||||
|
||||
align = ' style="text-align: %s;"'
|
||||
open_table_tag = '<table cellpadding="0" cellspacing="0">'
|
||||
startOfRow = '<tr><td colspan="3"%s><pre>'
|
||||
endOfRow = '</td></tr>'
|
||||
|
||||
print open_table_tag + (startOfRow % '')
|
||||
|
||||
sys.stdout.write('<table cellpadding="2" cellspacing="2" style="font-family: monospace; whitespace: pre;">\n')
|
||||
for line_data in bs:
|
||||
revision = line_data.rev
|
||||
thisline = line_data.text
|
||||
line = line_data.line_number
|
||||
author = line_data.author
|
||||
prev_rev = line_data.prev_rev
|
||||
|
||||
output = ''
|
||||
|
||||
if old_revision != revision and line != 1:
|
||||
if row_color == 'ffffff':
|
||||
row_color = 'e7e7e7'
|
||||
if row_color == '':
|
||||
row_color = ' style="background-color:#e7e7e7"'
|
||||
else:
|
||||
row_color = 'ffffff'
|
||||
row_color = ''
|
||||
|
||||
sys.stdout.write('<tr id="l%d" style="background-color: #%s; vertical-align: center;">' % (line, row_color))
|
||||
sys.stdout.write('<td%s>%d</td>' % (align % 'right', line))
|
||||
if not inMark:
|
||||
output = output + endOfRow + (startOfRow % row_color)
|
||||
|
||||
output = output + '<a name="%d">%*d</a>' % (line, line_num_width, line)
|
||||
|
||||
if old_revision != revision or rev_count > 20:
|
||||
sys.stdout.write('<td%s>%s</td>' % (align % 'right', author or ' '))
|
||||
sys.stdout.write('<td%s>%s</td>' % (align % 'left', revision))
|
||||
revision_width = max(revision_width, len(revision))
|
||||
output = output + ' '
|
||||
author_width = max(author_width, len(author))
|
||||
output = output + ('%-*s ' % (author_width, author))
|
||||
output = output + revision
|
||||
if prev_rev:
|
||||
output = output + '</a>'
|
||||
output = output + (' ' * (revision_width - len(revision) + 1))
|
||||
|
||||
old_revision = revision
|
||||
rev_count = 0
|
||||
else:
|
||||
sys.stdout.write('<td> </td><td> </td>')
|
||||
output = output + ' ' + (' ' * (author_width + revision_width))
|
||||
rev_count = rev_count + 1
|
||||
|
||||
sys.stdout.write('<td%s>%s</td></tr>\n' % (align % 'left', string.rstrip(thisline) or ' '))
|
||||
sys.stdout.write('</table>\n')
|
||||
output = output + thisline
|
||||
|
||||
# Close the highlighted section
|
||||
#if (defined $mark_cmd and mark_cmd != 'begin'):
|
||||
# chop($output)
|
||||
# output = output + endOfRow + (startOfRow % row_color)
|
||||
# inMark = 0
|
||||
|
||||
print output
|
||||
print endOfRow + '</table>'
|
||||
|
||||
|
||||
def main():
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -125,30 +125,16 @@ except AttributeError:
|
||||
try:
|
||||
mkdtemp = tempfile.mkdtemp
|
||||
except AttributeError:
|
||||
def mkdtemp(suffix="", prefix="tmp", dir=None):
|
||||
# mktemp() only took a single suffix argument until Python 2.3.
|
||||
# We'll do the best we can.
|
||||
oldtmpdir = os.environ.get('TMPDIR')
|
||||
try:
|
||||
for i in range(10):
|
||||
if dir:
|
||||
os.environ['TMPDIR'] = dir
|
||||
dir = tempfile.mktemp(suffix)
|
||||
if prefix:
|
||||
parent, base = os.path.split(dir)
|
||||
dir = os.path.join(parent, prefix + base)
|
||||
try:
|
||||
os.mkdir(dir, 0700)
|
||||
return dir
|
||||
except OSError, e:
|
||||
if e.errno == errno.EEXIST:
|
||||
continue # try again
|
||||
raise
|
||||
finally:
|
||||
if oldtmpdir:
|
||||
os.environ['TMPDIR'] = oldtmpdir
|
||||
elif os.environ.has_key('TMPDIR'):
|
||||
del(os.environ['TMPDIR'])
|
||||
def mkdtemp():
|
||||
for i in range(10):
|
||||
dir = tempfile.mktemp()
|
||||
try:
|
||||
os.mkdir(dir, 0700)
|
||||
return dir
|
||||
except OSError, e:
|
||||
if e.errno == errno.EEXIST:
|
||||
continue # try again
|
||||
raise
|
||||
|
||||
raise IOError, (errno.EEXIST, "No usable temporary directory name found")
|
||||
|
||||
|
437
lib/config.py
437
lib/config.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -24,148 +24,44 @@ import fnmatch
|
||||
#########################################################################
|
||||
#
|
||||
# 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:
|
||||
_base_sections = (
|
||||
# Base configuration sections.
|
||||
'authz-*',
|
||||
'cvsdb',
|
||||
'general',
|
||||
'options',
|
||||
'query',
|
||||
'templates',
|
||||
'utilities',
|
||||
)
|
||||
_force_multi_value = (
|
||||
# Configuration values with multiple, comma-separated values.
|
||||
'allowed_views',
|
||||
'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',
|
||||
)
|
||||
}
|
||||
_sections = ('general', 'options', 'cvsdb', 'templates')
|
||||
_force_multi_value = ('cvs_roots', 'forbidden',
|
||||
'svn_roots', 'languages', 'kv_files',
|
||||
'root_parents')
|
||||
|
||||
def __init__(self):
|
||||
self.root_options_overlayed = 0
|
||||
for section in self._base_sections:
|
||||
if section[-1] == '*':
|
||||
continue
|
||||
for section in self._sections:
|
||||
setattr(self, section, _sub_config())
|
||||
|
||||
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.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)
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.read(pathname)
|
||||
|
||||
for section in self._sections:
|
||||
if parser.has_section(section):
|
||||
self._process_section(parser, section, section)
|
||||
|
||||
if vhost and parser.has_section('vhosts'):
|
||||
self._process_vhost(parser, vhost)
|
||||
|
||||
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:
|
||||
@@ -178,7 +74,6 @@ class Config:
|
||||
fname = string.replace(fname, '%lang%', language)
|
||||
|
||||
parser = ConfigParser.ConfigParser()
|
||||
parser.optionxform = lambda x: x # don't case-normalize option names.
|
||||
parser.read(os.path.join(self.base, fname))
|
||||
for section in parser.sections():
|
||||
for option in parser.options(section):
|
||||
@@ -195,13 +90,7 @@ class Config:
|
||||
|
||||
return kv
|
||||
|
||||
def path(self, path):
|
||||
"""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):
|
||||
@@ -214,58 +103,28 @@ 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 (else, we've nothing to do).
|
||||
canon_vhost = self._find_canon_vhost(parser, vhost)
|
||||
if not canon_vhost:
|
||||
# none of the vhost sections matched
|
||||
return
|
||||
|
||||
# Overlay any option sections associated with this vhost name.
|
||||
cv = canon_vhost + '-'
|
||||
lcv = len(cv)
|
||||
for section in parser.sections():
|
||||
base_section = self._is_allowed_override('vhost', canon_vhost, section)
|
||||
if base_section:
|
||||
self._process_section(parser, section, base_section)
|
||||
if section[:lcv] == cv:
|
||||
self._process_section(parser, section, section[lcv:])
|
||||
|
||||
def _find_canon_vhost(self, parser, vhost):
|
||||
vhost = string.split(string.lower(vhost), ':')[0] # lower-case, no port
|
||||
vhost = string.lower(vhost)
|
||||
# Strip (ignore) port number:
|
||||
vhost = string.split(vhost, ':')[0]
|
||||
|
||||
for canon_vhost in parser.options('vhosts'):
|
||||
value = parser.get('vhosts', canon_vhost)
|
||||
patterns = map(string.lower, map(string.strip,
|
||||
@@ -276,102 +135,6 @@ class Config:
|
||||
|
||||
return None
|
||||
|
||||
def overlay_root_options(self, rootname):
|
||||
"""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
|
||||
|
||||
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."""
|
||||
try:
|
||||
return self.parser.items(section)
|
||||
except AttributeError:
|
||||
d = {}
|
||||
for option in parser.options(section):
|
||||
d[option] = parser.get(section, option)
|
||||
return d.items()
|
||||
|
||||
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 == root_authz_section:
|
||||
for key, value in self._get_parser_items(self.parser, section):
|
||||
params[key] = value
|
||||
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):
|
||||
"Set some default values in the configuration."
|
||||
|
||||
@@ -379,69 +142,27 @@ class Config:
|
||||
self.general.svn_roots = { }
|
||||
self.general.root_parents = []
|
||||
self.general.default_root = ''
|
||||
self.general.mime_types_files = ["mimetypes.conf"]
|
||||
self.general.address = ''
|
||||
self.general.rcs_path = ''
|
||||
if sys.platform == "win32":
|
||||
self.general.cvsnt_exe_path = 'cvs'
|
||||
else:
|
||||
self.general.cvsnt_exe_path = None
|
||||
self.general.use_rcsparse = 0
|
||||
self.general.svn_path = ''
|
||||
self.general.mime_types_file = ''
|
||||
self.general.address = '<a href="mailto:user@insert.your.domain.here">No admin address has been configured</a>'
|
||||
self.general.forbidden = ()
|
||||
self.general.kv_files = [ ]
|
||||
self.general.languages = ['en-us']
|
||||
|
||||
self.utilities.rcs_dir = ''
|
||||
if sys.platform == "win32":
|
||||
self.utilities.cvsnt = 'cvs'
|
||||
else:
|
||||
self.utilities.cvsnt = None
|
||||
self.utilities.svn = ''
|
||||
self.utilities.diff = ''
|
||||
self.utilities.cvsgraph = ''
|
||||
|
||||
self.options.root_as_url_component = 1
|
||||
self.options.checkout_magic = 0
|
||||
self.options.allowed_views = ['annotate', 'diff', 'markup', 'roots']
|
||||
self.options.authorizer = None
|
||||
self.options.mangle_email_addresses = 0
|
||||
self.options.default_file_view = "log"
|
||||
self.options.http_expiration_time = 600
|
||||
self.options.generate_etags = 1
|
||||
self.options.svn_ignore_mimetype = 0
|
||||
self.options.svn_config_dir = None
|
||||
self.options.use_rcsparse = 0
|
||||
self.options.sort_by = 'file'
|
||||
self.options.sort_group_dirs = 1
|
||||
self.options.hide_attic = 1
|
||||
self.options.hide_errorful_entries = 0
|
||||
self.options.log_sort = 'date'
|
||||
self.options.diff_format = 'h'
|
||||
self.options.hide_cvsroot = 1
|
||||
self.options.hr_breakable = 1
|
||||
self.options.hr_funout = 1
|
||||
self.options.hr_ignore_white = 0
|
||||
self.options.hr_ignore_keyword_subst = 1
|
||||
self.options.hr_intraline = 0
|
||||
self.options.allow_compress = 0
|
||||
self.options.template_dir = "templates"
|
||||
self.options.docroot = None
|
||||
self.options.show_subdir_lastmod = 0
|
||||
self.options.show_logs = 1
|
||||
self.options.show_log_in_markup = 1
|
||||
self.options.cross_copies = 1
|
||||
self.options.use_localtime = 0
|
||||
self.options.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.use_re_search = 0
|
||||
self.options.dir_pagesize = 0
|
||||
self.options.log_pagesize = 0
|
||||
self.options.limit_changes = 100
|
||||
|
||||
self.templates.diff = None
|
||||
self.templates.directory = None
|
||||
self.templates.error = None
|
||||
self.templates.file = None
|
||||
self.templates.graph = None
|
||||
self.templates.log = None
|
||||
self.templates.query = None
|
||||
self.templates.diff = None
|
||||
self.templates.graph = None
|
||||
self.templates.annotate = None
|
||||
self.templates.markup = None
|
||||
self.templates.error = None
|
||||
self.templates.query_form = None
|
||||
self.templates.query_results = None
|
||||
self.templates.roots = None
|
||||
@@ -456,12 +177,64 @@ class Config:
|
||||
self.cvsdb.readonly_passwd = ''
|
||||
self.cvsdb.row_limit = 1000
|
||||
self.cvsdb.rss_row_limit = 100
|
||||
self.cvsdb.check_database_for_root = 0
|
||||
|
||||
self.query.viewvc_base_url = None
|
||||
|
||||
def _startswith(somestr, substr):
|
||||
return somestr[:len(substr)] == substr
|
||||
self.options.root_as_url_component = 0
|
||||
self.options.default_file_view = "log"
|
||||
self.options.checkout_magic = 0
|
||||
self.options.sort_by = 'file'
|
||||
self.options.sort_group_dirs = 1
|
||||
self.options.hide_attic = 1
|
||||
self.options.log_sort = 'date'
|
||||
self.options.diff_format = 'h'
|
||||
self.options.hide_cvsroot = 1
|
||||
self.options.hr_breakable = 1
|
||||
self.options.hr_funout = 1
|
||||
self.options.hr_ignore_white = 1
|
||||
self.options.hr_ignore_keyword_subst = 1
|
||||
self.options.hr_intraline = 0
|
||||
self.options.allow_annotate = 1
|
||||
self.options.allow_markup = 1
|
||||
self.options.allow_compress = 1
|
||||
self.options.template_dir = "templates"
|
||||
self.options.docroot = None
|
||||
self.options.show_subdir_lastmod = 0
|
||||
self.options.show_logs = 1
|
||||
self.options.show_log_in_markup = 1
|
||||
self.options.cross_copies = 0
|
||||
self.options.py2html_path = '.'
|
||||
self.options.short_log_len = 80
|
||||
self.options.use_enscript = 0
|
||||
self.options.enscript_path = ''
|
||||
self.options.use_highlight = 0
|
||||
self.options.highlight_path = ''
|
||||
self.options.highlight_line_numbers = 1
|
||||
self.options.highlight_convert_tabs = 2
|
||||
self.options.use_php = 0
|
||||
self.options.php_exe_path = 'php'
|
||||
self.options.allow_tar = 0
|
||||
self.options.use_cvsgraph = 0
|
||||
self.options.cvsgraph_path = ''
|
||||
self.options.cvsgraph_conf = "cvsgraph.conf"
|
||||
self.options.use_re_search = 0
|
||||
self.options.use_pagesize = 0
|
||||
self.options.limit_changes = 100
|
||||
self.options.use_localtime = 0
|
||||
self.options.http_expiration_time = 600
|
||||
self.options.generate_etags = 1
|
||||
|
||||
def is_forbidden(self, module):
|
||||
if not module:
|
||||
return 0
|
||||
default = 0
|
||||
for pat in self.general.forbidden:
|
||||
if pat[0] == '!':
|
||||
default = 1
|
||||
if fnmatch.fnmatchcase(module, pat[1:]):
|
||||
return 0
|
||||
elif fnmatch.fnmatchcase(module, pat):
|
||||
return 1
|
||||
return default
|
||||
|
||||
|
||||
def _parse_roots(config_name, config_value):
|
||||
roots = { }
|
||||
@@ -473,18 +246,8 @@ def _parse_roots(config_name, config_value):
|
||||
roots[name] = path
|
||||
return roots
|
||||
|
||||
class ViewVCConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
class IllegalOverrideSection(ViewVCConfigurationError):
|
||||
def __init__(self, override_type, section_name):
|
||||
self.section_name = section_name
|
||||
self.override_type = override_type
|
||||
def __str__(self):
|
||||
return "malformed configuration: illegal %s override section: %s" \
|
||||
% (self.override_type, self.section_name)
|
||||
|
||||
class MalformedRoot(ViewVCConfigurationError):
|
||||
class MalformedRoot(Exception):
|
||||
def __init__(self, config_name, value_given):
|
||||
Exception.__init__(self, config_name, value_given)
|
||||
self.config_name = config_name
|
||||
|
224
lib/cvsdb.py
224
lib/cvsdb.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -17,17 +17,8 @@ import time
|
||||
import fnmatch
|
||||
import re
|
||||
|
||||
import vclib
|
||||
import dbi
|
||||
|
||||
## Current commits database schema version number.
|
||||
##
|
||||
## Version 0 was the original Bonsai-compatible version.
|
||||
##
|
||||
## Version 1 added the 'metadata' table (which holds the 'version' key)
|
||||
## and renamed all the 'repository'-related stuff to be 'root'-
|
||||
##
|
||||
CURRENT_SCHEMA_VERSION = 1
|
||||
|
||||
## error
|
||||
error = "cvsdb error"
|
||||
@@ -45,7 +36,6 @@ class CheckinDatabase:
|
||||
self._passwd = passwd
|
||||
self._database = database
|
||||
self._row_limit = row_limit
|
||||
self._version = None
|
||||
|
||||
## database lookup caches
|
||||
self._get_cache = {}
|
||||
@@ -57,19 +47,6 @@ class CheckinDatabase:
|
||||
self._host, self._port, self._user, self._passwd, self._database)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute("SET AUTOCOMMIT=1")
|
||||
table_list = self.GetTableList()
|
||||
if 'metadata' in table_list:
|
||||
version = self.GetMetadataValue("version")
|
||||
if version is None:
|
||||
self._version = 0
|
||||
else:
|
||||
self._version = int(version)
|
||||
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))
|
||||
|
||||
def sql_get_id(self, table, column, value, auto_set):
|
||||
sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
|
||||
@@ -169,42 +146,6 @@ class CheckinDatabase:
|
||||
|
||||
return list
|
||||
|
||||
def GetTableList(self):
|
||||
sql = "SHOW TABLES"
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql)
|
||||
list = []
|
||||
while 1:
|
||||
row = cursor.fetchone()
|
||||
if row == None:
|
||||
break
|
||||
list.append(row[0])
|
||||
return list
|
||||
|
||||
def GetMetadataValue(self, name):
|
||||
sql = "SELECT value FROM metadata WHERE name=%s"
|
||||
sql_args = (name)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
try:
|
||||
(value,) = cursor.fetchone()
|
||||
except TypeError:
|
||||
return None
|
||||
return value
|
||||
|
||||
def SetMetadataValue(self, name, value):
|
||||
assert(self._version > 0)
|
||||
sql = "REPLACE INTO metadata (name, value) VALUES (%s, %s)"
|
||||
sql_args = (name, value)
|
||||
cursor = self.db.cursor()
|
||||
try:
|
||||
cursor.execute(sql, sql_args)
|
||||
except Exception, e:
|
||||
raise Exception("Error setting metadata: '%s'\n"
|
||||
"\tname = %s\n"
|
||||
"\tvalue = %s\n"
|
||||
% (str(e), name, value))
|
||||
|
||||
def GetBranchID(self, branch, auto_set = 1):
|
||||
return self.get_id("branches", "branch", branch, auto_set)
|
||||
|
||||
@@ -296,7 +237,7 @@ class CheckinDatabase:
|
||||
self.AddCommit(commit)
|
||||
|
||||
def AddCommit(self, commit):
|
||||
ci_when = dbi.DateTimeFromTicks(commit.GetTime() or 0.0)
|
||||
ci_when = dbi.DateTimeFromTicks(commit.GetTime())
|
||||
ci_type = commit.GetTypeString()
|
||||
who_id = self.GetAuthorID(commit.GetAuthor())
|
||||
repository_id = self.GetRepositoryID(commit.GetRepository())
|
||||
@@ -309,9 +250,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 = sql + \
|
||||
sql = "REPLACE INTO checkins"\
|
||||
" (type,ci_when,whoid,repositoryid,dirid,fileid,revision,"\
|
||||
" stickytag,branchid,addedlines,removedlines,descid)"\
|
||||
"VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
|
||||
@@ -320,24 +259,7 @@ class CheckinDatabase:
|
||||
plus_count, minus_count, description_id)
|
||||
|
||||
cursor = self.db.cursor()
|
||||
try:
|
||||
cursor.execute(sql, sql_args)
|
||||
except Exception, e:
|
||||
raise Exception("Error adding commit: '%s'\n"
|
||||
"Values were:\n"
|
||||
"\ttype = %s\n"
|
||||
"\tci_when = %s\n"
|
||||
"\twhoid = %s\n"
|
||||
"\trepositoryid = %s\n"
|
||||
"\tdirid = %s\n"
|
||||
"\tfileid = %s\n"
|
||||
"\trevision = %s\n"
|
||||
"\tstickytag = %s\n"
|
||||
"\tbranchid = %s\n"
|
||||
"\taddedlines = %s\n"
|
||||
"\tremovedlines = %s\n"
|
||||
"\tdescid = %s\n"
|
||||
% ((str(e), ) + sql_args))
|
||||
cursor.execute(sql, sql_args)
|
||||
|
||||
def SQLQueryListString(self, field, query_entry_list):
|
||||
sqlList = []
|
||||
@@ -364,69 +286,52 @@ class CheckinDatabase:
|
||||
return "(%s)" % (string.join(sqlList, " OR "))
|
||||
|
||||
def CreateSQLQueryString(self, query):
|
||||
commits_table = self._version >= 1 and 'commits' or 'checkins'
|
||||
tableList = [(commits_table, None)]
|
||||
tableList = [("checkins", None)]
|
||||
condList = []
|
||||
|
||||
if len(query.repository_list):
|
||||
tableList.append(("repositories",
|
||||
"(%s.repositoryid=repositories.id)"
|
||||
% (commits_table)))
|
||||
"(checkins.repositoryid=repositories.id)"))
|
||||
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", "(checkins.branchid=branches.id)"))
|
||||
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)))
|
||||
tableList.append(("dirs", "(checkins.dirid=dirs.id)"))
|
||||
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", "(checkins.fileid=files.id)"))
|
||||
temp = self.SQLQueryListString("files.file", query.file_list)
|
||||
condList.append(temp)
|
||||
|
||||
if len(query.author_list):
|
||||
tableList.append(("people",
|
||||
"(%s.whoid=people.id)" % (commits_table)))
|
||||
tableList.append(("people", "(checkins.whoid=people.id)"))
|
||||
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)))
|
||||
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 = "(checkins.ci_when>=\"%s\")" % (str(query.from_date))
|
||||
condList.append(temp)
|
||||
|
||||
if query.to_date:
|
||||
temp = "(%s.ci_when<=\"%s\")" \
|
||||
% (commits_table, str(query.to_date))
|
||||
temp = "(checkins.ci_when<=\"%s\")" % (str(query.to_date))
|
||||
condList.append(temp)
|
||||
|
||||
if query.sort == "date":
|
||||
order_by = "ORDER BY %s.ci_when DESC,descid" % (commits_table)
|
||||
order_by = "ORDER BY checkins.ci_when DESC,descid"
|
||||
elif query.sort == "author":
|
||||
tableList.append(("people",
|
||||
"(%s.whoid=people.id)" % (commits_table)))
|
||||
tableList.append(("people", "(checkins.whoid=people.id)"))
|
||||
order_by = "ORDER BY people.who,descid"
|
||||
elif query.sort == "file":
|
||||
tableList.append(("files",
|
||||
"(%s.fileid=files.id)" % (commits_table)))
|
||||
tableList.append(("files", "(checkins.fileid=files.id)"))
|
||||
order_by = "ORDER BY files.file,descid"
|
||||
|
||||
## exclude duplicates from the table list, and split out join
|
||||
@@ -452,8 +357,8 @@ class CheckinDatabase:
|
||||
elif self._row_limit:
|
||||
limit = "LIMIT %s" % (str(self._row_limit))
|
||||
|
||||
sql = "SELECT %s.* FROM %s %s %s %s" \
|
||||
% (commits_table, tables, conditions, order_by, limit)
|
||||
sql = "SELECT checkins.* FROM %s %s %s %s" % (
|
||||
tables, conditions, order_by, limit)
|
||||
|
||||
return sql
|
||||
|
||||
@@ -504,13 +409,8 @@ 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)
|
||||
sql = "SELECT * FROM checkins WHERE "\
|
||||
" repositoryid=%s AND dirid=%s AND fileid=%s AND revision=%s"
|
||||
sql_args = (repository_id, dir_id, file_id, commit.GetRevision())
|
||||
|
||||
cursor = self.db.cursor()
|
||||
@@ -524,76 +424,6 @@ class CheckinDatabase:
|
||||
|
||||
return commit
|
||||
|
||||
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)
|
||||
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)
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql)
|
||||
|
||||
def PurgeRepository(self, repository):
|
||||
rep_id = self.GetRepositoryID(repository, auto_set=0)
|
||||
if not rep_id:
|
||||
raise UnknownRepositoryError("Unknown repository '%s'"
|
||||
% (repository))
|
||||
|
||||
if (self._version >= 1):
|
||||
self.sql_delete('repositories', 'id', rep_id)
|
||||
self.sql_purge('commits', 'repositoryid', 'id', 'repositories')
|
||||
self.sql_purge('files', 'id', 'fileid', 'commits')
|
||||
self.sql_purge('dirs', 'id', 'dirid', 'commits')
|
||||
self.sql_purge('branches', 'id', 'branchid', 'commits')
|
||||
self.sql_purge('descs', 'id', 'descid', 'commits')
|
||||
self.sql_purge('people', 'id', 'whoid', 'commits')
|
||||
else:
|
||||
sql = "SELECT * FROM checkins WHERE repositoryid=%s"
|
||||
sql_args = (rep_id, )
|
||||
cursor = self.db.cursor()
|
||||
cursor.execute(sql, sql_args)
|
||||
checkins = []
|
||||
while 1:
|
||||
try:
|
||||
(ci_type, ci_when, who_id, repository_id,
|
||||
dir_id, file_id, revision, sticky_tag, branch_id,
|
||||
plus_count, minus_count, description_id) = \
|
||||
cursor.fetchone()
|
||||
except TypeError:
|
||||
break
|
||||
checkins.append([file_id, dir_id, branch_id,
|
||||
description_id, who_id])
|
||||
|
||||
#self.sql_delete('repositories', 'id', rep_id)
|
||||
self.sql_delete('checkins', 'repositoryid', rep_id)
|
||||
for checkin in checkins:
|
||||
self.sql_delete('files', 'id', checkin[0], 'fileid')
|
||||
self.sql_delete('dirs', 'id', checkin[1], 'dirid')
|
||||
self.sql_delete('branches', 'id', checkin[2], 'branchid')
|
||||
self.sql_delete('descs', 'id', checkin[3], 'descid')
|
||||
self.sql_delete('people', 'id', checkin[4], 'whoid')
|
||||
|
||||
# Reset all internal id caches. We could be choosier here,
|
||||
# but let's just be as safe as possible.
|
||||
self._get_cache = {}
|
||||
self._get_id_cache = {}
|
||||
self._desc_id_cache = {}
|
||||
|
||||
|
||||
class DatabaseVersionError(Exception):
|
||||
pass
|
||||
class UnknownRepositoryError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
## the Commit class holds data on one commit, the representation is as
|
||||
## close as possible to how it should be committed and retrieved to the
|
||||
## database engine
|
||||
@@ -641,15 +471,10 @@ class Commit:
|
||||
return self.__revision
|
||||
|
||||
def SetTime(self, gmt_time):
|
||||
if gmt_time is None:
|
||||
### We're just going to assume that a datestamp of The Epoch
|
||||
### ain't real.
|
||||
self.__gmt_time = 0.0
|
||||
else:
|
||||
self.__gmt_time = float(gmt_time)
|
||||
self.__gmt_time = float(gmt_time)
|
||||
|
||||
def GetTime(self):
|
||||
return self.__gmt_time and self.__gmt_time or None
|
||||
return self.__gmt_time
|
||||
|
||||
def SetAuthor(self, author):
|
||||
self.__author = author
|
||||
@@ -782,7 +607,6 @@ class CheckinDatabaseQuery:
|
||||
self.directory_list = []
|
||||
self.file_list = []
|
||||
self.author_list = []
|
||||
self.comment_list = []
|
||||
|
||||
## date range in DBI 2.0 timedate objects
|
||||
self.from_date = None
|
||||
@@ -813,9 +637,6 @@ class CheckinDatabaseQuery:
|
||||
def SetAuthor(self, author, match = "exact"):
|
||||
self.author_list.append(QueryEntry(author, match))
|
||||
|
||||
def SetComment(self, comment, match = "exact"):
|
||||
self.comment_list.append(QueryEntry(comment, match))
|
||||
|
||||
def SetSortMethod(self, sort):
|
||||
self.sort = sort
|
||||
|
||||
@@ -874,8 +695,7 @@ def GetCommitListFromRCSFile(repository, path_parts, revision=None):
|
||||
directory = string.join(path_parts[:-1], "/")
|
||||
file = path_parts[-1]
|
||||
|
||||
revs = repository.itemlog(path_parts, revision, vclib.SORTBY_DEFAULT,
|
||||
0, 0, {"cvs_pass_rev": 1})
|
||||
revs = repository.itemlog(path_parts, revision, {"cvs_pass_rev": 1})
|
||||
for rev in revs:
|
||||
commit = CreateCommit()
|
||||
commit.SetRepository(repository.rootpath)
|
||||
|
26
lib/debug.py
26
lib/debug.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -17,20 +17,10 @@
|
||||
|
||||
import sys
|
||||
|
||||
# Set to non-zero to track and print processing times
|
||||
|
||||
SHOW_TIMES = 0
|
||||
|
||||
# Set to non-zero to display child process info
|
||||
SHOW_CHILD_PROCESSES = 0
|
||||
|
||||
# Set to a server-side path to force the tarball view to generate the
|
||||
# tarball as a file on the server, instead of transmitting the data
|
||||
# back to the browser. This enables easy display of error
|
||||
# considitions in the browser, as well as tarball inspection on the
|
||||
# server. NOTE: The file will be a TAR archive, *not* gzip-compressed.
|
||||
TARFILE_PATH = ''
|
||||
|
||||
|
||||
if SHOW_TIMES:
|
||||
|
||||
import time
|
||||
@@ -48,17 +38,13 @@ if SHOW_TIMES:
|
||||
else:
|
||||
_times[which] = t
|
||||
|
||||
def t_dump(out):
|
||||
out.write('<div>')
|
||||
names = _times.keys()
|
||||
names.sort()
|
||||
for name in names:
|
||||
out.write('%s: %.6fs<br/>\n' % (name, _times[name]))
|
||||
out.write('</div>')
|
||||
def dump():
|
||||
for name, value in _times.items():
|
||||
print '%s: %.6f<br />' % (name, value)
|
||||
|
||||
else:
|
||||
|
||||
t_start = t_end = t_dump = lambda *args: None
|
||||
t_start = t_end = dump = lambda *args: None
|
||||
|
||||
|
||||
class ViewVCException:
|
||||
|
129
lib/ezt.py
129
lib/ezt.py
@@ -189,10 +189,9 @@ Directives
|
||||
templates are escaped before they are put into the output stream. It
|
||||
has no effect on the literal text of the templates, only the output
|
||||
from [QUAL_NAME ...] directives. STRING can be one of "raw" "html"
|
||||
"xml" or "uri". The "raw" mode leaves the output unaltered; the "html"
|
||||
and "xml" modes escape special characters using entity escapes (like
|
||||
" and >); the "uri" mode escapes characters using hexadecimal
|
||||
escape sequences (like %20 and %7e).
|
||||
or "xml". The "raw" mode leaves the output unaltered. The "html" and
|
||||
"xml" modes escape special characters using entity escapes (like
|
||||
" and >)
|
||||
|
||||
[format CALLBACK]
|
||||
|
||||
@@ -201,7 +200,7 @@ Directives
|
||||
equivalent to "[CALLBACK QUAL_NAME]"
|
||||
"""
|
||||
#
|
||||
# Copyright (C) 2001-2007 Greg Stein. All Rights Reserved.
|
||||
# Copyright (C) 2001-2005 Greg Stein. All Rights Reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
@@ -236,7 +235,6 @@ import re
|
||||
from types import StringType, IntType, FloatType, LongType, TupleType
|
||||
import os
|
||||
import cgi
|
||||
import urllib
|
||||
try:
|
||||
import cStringIO
|
||||
except ImportError:
|
||||
@@ -249,7 +247,6 @@ except ImportError:
|
||||
FORMAT_RAW = 'raw'
|
||||
FORMAT_HTML = 'html'
|
||||
FORMAT_XML = 'xml'
|
||||
FORMAT_URI = 'uri'
|
||||
|
||||
#
|
||||
# This regular expression matches three alternatives:
|
||||
@@ -347,7 +344,7 @@ class Template:
|
||||
for_names = [ ]
|
||||
|
||||
if base_format:
|
||||
program.append((self._cmd_format, _formatters[base_format]))
|
||||
program.append((self._cmd_format, _printers[base_format]))
|
||||
|
||||
for i in range(len(parts)):
|
||||
piece = parts[i]
|
||||
@@ -405,13 +402,13 @@ class Template:
|
||||
elif cmd == 'format':
|
||||
if args[1][0]:
|
||||
# argument is a variable reference
|
||||
formatter = args[1]
|
||||
printer = args[1]
|
||||
else:
|
||||
# argument is a string constant referring to built-in formatter
|
||||
formatter = _formatters.get(args[1][1])
|
||||
if not formatter:
|
||||
# argument is a string constant referring to built-in printer
|
||||
printer = _printers.get(args[1][1])
|
||||
if not printer:
|
||||
raise UnknownFormatConstantError(str(args[1:]))
|
||||
program.append((self._cmd_format, formatter))
|
||||
program.append((self._cmd_format, printer))
|
||||
|
||||
# remember the cmd, current pos, args, and a section placeholder
|
||||
stack.append([cmd, len(program), args[1:], None])
|
||||
@@ -460,18 +457,15 @@ class Template:
|
||||
def _cmd_print(self, valrefs, ctx):
|
||||
value = _get_value(valrefs[0], ctx)
|
||||
args = map(lambda valref, ctx=ctx: _get_value(valref, ctx), valrefs[1:])
|
||||
try:
|
||||
_write_value(value, args, ctx)
|
||||
except TypeError:
|
||||
raise Exception("Unprintable value type for '%s'" % (str(valrefs[0][0])))
|
||||
_write_value(value, args, ctx)
|
||||
|
||||
def _cmd_format(self, formatter, ctx):
|
||||
if type(formatter) is TupleType:
|
||||
formatter = _get_value(formatter, ctx)
|
||||
ctx.formatters.append(formatter)
|
||||
def _cmd_format(self, printer, ctx):
|
||||
if type(printer) is TupleType:
|
||||
printer = _get_value(printer, ctx)
|
||||
ctx.printers.append(printer)
|
||||
|
||||
def _cmd_end_format(self, valref, ctx):
|
||||
ctx.formatters.pop()
|
||||
ctx.printers.pop()
|
||||
|
||||
def _cmd_include(self, (valref, reader), ctx):
|
||||
fname = _get_value(valref, ctx)
|
||||
@@ -525,8 +519,7 @@ class Template:
|
||||
((valref,), unused, section) = args
|
||||
list = _get_value(valref, ctx)
|
||||
if isinstance(list, StringType):
|
||||
raise NeedSequenceError("The value of '%s' is not a sequence"
|
||||
% (valref[0]))
|
||||
raise NeedSequenceError()
|
||||
refname = valref[0]
|
||||
ctx.for_iterators[refname] = iterator = _iter(list)
|
||||
for unused in iterator:
|
||||
@@ -637,23 +630,14 @@ def _get_value((refname, start, rest), ctx):
|
||||
# string or a sequence
|
||||
return ob
|
||||
|
||||
def _print_formatted(formatters, ctx, chunk):
|
||||
# print chunk to ctx.fp after running it sequentially through formatters
|
||||
for formatter in formatters:
|
||||
chunk = formatter(chunk)
|
||||
ctx.fp.write(chunk)
|
||||
|
||||
def _write_value(value, args, ctx):
|
||||
# value is a callback function, generates its own output
|
||||
if callable(value):
|
||||
apply(value, [ctx] + list(args))
|
||||
return
|
||||
|
||||
# squirrel away formatters in case one of them recursively calls
|
||||
# _write_value() -- we'll use them (in reverse order) to format our
|
||||
# output.
|
||||
formatters = ctx.formatters[:]
|
||||
formatters.reverse()
|
||||
# pop printer in case it recursively calls _write_value
|
||||
printer = ctx.printers.pop()
|
||||
|
||||
try:
|
||||
# if the value has a 'read' attribute, then it is a stream: copy it
|
||||
@@ -662,7 +646,7 @@ def _write_value(value, args, ctx):
|
||||
chunk = value.read(16384)
|
||||
if not chunk:
|
||||
break
|
||||
_print_formatted(formatters, ctx, chunk)
|
||||
printer(ctx, chunk)
|
||||
|
||||
# value is a substitution pattern
|
||||
elif args:
|
||||
@@ -675,58 +659,21 @@ def _write_value(value, args, ctx):
|
||||
piece = args[idx]
|
||||
else:
|
||||
piece = '<undef>'
|
||||
_print_formatted(formatters, ctx, piece)
|
||||
printer(ctx, piece)
|
||||
|
||||
# plain old value, write to output
|
||||
else:
|
||||
_print_formatted(formatters, ctx, value)
|
||||
printer(ctx, value)
|
||||
|
||||
finally:
|
||||
# restore our formatters
|
||||
formatters.reverse()
|
||||
ctx.formatters = formatters
|
||||
|
||||
|
||||
class TemplateData:
|
||||
"""A custom dictionary-like object that allows one-time definition
|
||||
of keys, and only value fetches and changes, and key deletions,
|
||||
thereafter.
|
||||
|
||||
EZT doesn't require the use of this special class -- a normal
|
||||
dict-type data dictionary works fine. But use of this class will
|
||||
assist those who want the data sent to their templates to have a
|
||||
consistent set of keys."""
|
||||
|
||||
def __init__(self, initial_data={}):
|
||||
self._items = initial_data
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._items.__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
assert self._items.has_key(key)
|
||||
return self._items.__setitem__(key, item)
|
||||
|
||||
def __delitem__(self, key):
|
||||
return self._items.__delitem__(key)
|
||||
|
||||
def keys(self):
|
||||
return self._items.keys()
|
||||
|
||||
def merge(self, template_data):
|
||||
"""Merge the data in TemplataData instance TEMPLATA_DATA into this
|
||||
instance. Avoid the temptation to use this conditionally in your
|
||||
code -- it rather defeats the purpose of this class."""
|
||||
|
||||
assert isinstance(template_data, TemplateData)
|
||||
self._items.update(template_data._items)
|
||||
ctx.printers.append(printer)
|
||||
|
||||
|
||||
class Context:
|
||||
"""A container for the execution context"""
|
||||
def __init__(self, fp):
|
||||
self.fp = fp
|
||||
self.formatters = []
|
||||
self.printers = []
|
||||
def write(self, value, args=()):
|
||||
_write_value(value, args, self)
|
||||
|
||||
@@ -839,26 +786,16 @@ class BaseUnavailableError(EZTException):
|
||||
class UnknownFormatConstantError(EZTException):
|
||||
"""The format specifier is an unknown value."""
|
||||
|
||||
def _raw_formatter(s):
|
||||
return s
|
||||
def _raw_printer(ctx, s):
|
||||
ctx.fp.write(s)
|
||||
|
||||
def _html_printer(ctx, s):
|
||||
ctx.fp.write(cgi.escape(s))
|
||||
|
||||
def _html_formatter(s):
|
||||
return cgi.escape(s)
|
||||
|
||||
def _xml_formatter(s):
|
||||
s = s.replace('&', '&')
|
||||
s = s.replace('<', '<')
|
||||
s = s.replace('>', '>')
|
||||
return s
|
||||
|
||||
def _uri_formatter(s):
|
||||
return urllib.quote(s)
|
||||
|
||||
_formatters = {
|
||||
FORMAT_RAW : _raw_formatter,
|
||||
FORMAT_HTML : _html_formatter,
|
||||
FORMAT_XML : _xml_formatter,
|
||||
FORMAT_URI : _uri_formatter,
|
||||
_printers = {
|
||||
FORMAT_RAW : _raw_printer,
|
||||
FORMAT_HTML : _html_printer,
|
||||
FORMAT_XML : _html_printer,
|
||||
}
|
||||
|
||||
# --- standard test environment ---
|
||||
|
20
lib/idiff.py
20
lib/idiff.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -20,7 +20,7 @@ import difflib
|
||||
import sys
|
||||
import re
|
||||
import ezt
|
||||
import sapi
|
||||
import cgi
|
||||
|
||||
def sidebyside(fromlines, tolines, context):
|
||||
"""Generate side by side diff"""
|
||||
@@ -49,18 +49,18 @@ def _mdiff_split(flag, (line_number, text)):
|
||||
while True:
|
||||
m = _re_mdiff.search(text, pos)
|
||||
if not m:
|
||||
segments.append(_item(text=sapi.escape(text[pos:]), type=None))
|
||||
segments.append(_item(text=cgi.escape(text[pos:]), type=None))
|
||||
break
|
||||
|
||||
if m.start() > pos:
|
||||
segments.append(_item(text=sapi.escape(text[pos:m.start()]), type=None))
|
||||
segments.append(_item(text=cgi.escape(text[pos:m.start()]), type=None))
|
||||
|
||||
if m.group(1) == "+":
|
||||
segments.append(_item(text=sapi.escape(m.group(2)), type="add"))
|
||||
segments.append(_item(text=cgi.escape(m.group(2)), type="add"))
|
||||
elif m.group(1) == "-":
|
||||
segments.append(_item(text=sapi.escape(m.group(2)), type="remove"))
|
||||
segments.append(_item(text=cgi.escape(m.group(2)), type="remove"))
|
||||
elif m.group(1) == "^":
|
||||
segments.append(_item(text=sapi.escape(m.group(2)), type="change"))
|
||||
segments.append(_item(text=cgi.escape(m.group(2)), type="change"))
|
||||
|
||||
pos = m.end()
|
||||
|
||||
@@ -166,12 +166,12 @@ def _differ_split(row, guide):
|
||||
|
||||
for m in _re_differ.finditer(guide, pos):
|
||||
if m.start() > pos:
|
||||
segments.append(_item(text=sapi.escape(line[pos:m.start()]), type=None))
|
||||
segments.append(_item(text=sapi.escape(line[m.start():m.end()]),
|
||||
segments.append(_item(text=cgi.escape(line[pos:m.start()]), type=None))
|
||||
segments.append(_item(text=cgi.escape(line[m.start():m.end()]),
|
||||
type="change"))
|
||||
pos = m.end()
|
||||
|
||||
segments.append(_item(text=sapi.escape(line[pos:]), type=None))
|
||||
segments.append(_item(text=cgi.escape(line[pos:]), type=None))
|
||||
|
||||
return _item(gap=ezt.boolean(gap), type=type, segments=segments,
|
||||
left_number=left_number, right_number=right_number)
|
||||
|
196
lib/popen.py
196
lib/popen.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -131,6 +131,194 @@ def popen(cmd, args, mode, capture_err=1):
|
||||
# crap. shouldn't be here.
|
||||
sys.exit(127)
|
||||
|
||||
def pipe_cmds(cmds, out=None):
|
||||
"""Executes a sequence of commands. The output of each command is directed to
|
||||
the input of the next command. A _pipe object is returned for writing to the
|
||||
first command's input. The output of the last command is directed to the
|
||||
"out" file object or the standard output if "out" is None. If "out" is not an
|
||||
OS file descriptor, a separate thread will be spawned to send data to its
|
||||
write() method."""
|
||||
|
||||
if out is None:
|
||||
out = sys.stdout
|
||||
|
||||
if sys.platform == "win32":
|
||||
### FIXME: windows implementation ignores "out" argument, always
|
||||
### writing last command's output to standard out
|
||||
|
||||
if debug.SHOW_CHILD_PROCESSES:
|
||||
dbgIn = StringIO.StringIO()
|
||||
hStdIn, handle = win32popen.MakeSpyPipe(1, 0, (dbgIn,))
|
||||
|
||||
i = 0
|
||||
for cmd in cmds:
|
||||
i = i + 1
|
||||
|
||||
dbgOut, dbgErr = StringIO.StringIO(), StringIO.StringIO()
|
||||
|
||||
if i < len(cmds):
|
||||
nextStdIn, hStdOut = win32popen.MakeSpyPipe(1, 1, (dbgOut,))
|
||||
x, hStdErr = win32popen.MakeSpyPipe(None, 1, (dbgErr,))
|
||||
else:
|
||||
ehandle = win32event.CreateEvent(None, 1, 0, None)
|
||||
nextStdIn, hStdOut = win32popen.MakeSpyPipe(None, 1, (dbgOut, sapi.server.file()), ehandle)
|
||||
x, hStdErr = win32popen.MakeSpyPipe(None, 1, (dbgErr,))
|
||||
|
||||
command = win32popen.CommandLine(cmd[0], cmd[1:])
|
||||
phandle, pid, thandle, tid = win32popen.CreateProcess(command, hStdIn, hStdOut, hStdErr)
|
||||
if debug.SHOW_CHILD_PROCESSES:
|
||||
debug.Process(command, dbgIn, dbgOut, dbgErr)
|
||||
|
||||
dbgIn = dbgOut
|
||||
hStdIn = nextStdIn
|
||||
|
||||
|
||||
else:
|
||||
|
||||
hStdIn, handle = win32popen.CreatePipe(1, 0)
|
||||
spool = None
|
||||
|
||||
i = 0
|
||||
for cmd in cmds:
|
||||
i = i + 1
|
||||
if i < len(cmds):
|
||||
nextStdIn, hStdOut = win32popen.CreatePipe(1, 1)
|
||||
else:
|
||||
# very last process
|
||||
nextStdIn = None
|
||||
|
||||
if sapi.server.inheritableOut:
|
||||
# send child output to standard out
|
||||
hStdOut = win32popen.MakeInheritedHandle(win32popen.FileObject2File(sys.stdout),0)
|
||||
ehandle = None
|
||||
else:
|
||||
ehandle = win32event.CreateEvent(None, 1, 0, None)
|
||||
x, hStdOut = win32popen.MakeSpyPipe(None, 1, (sapi.server.file(),), ehandle)
|
||||
|
||||
command = win32popen.CommandLine(cmd[0], cmd[1:])
|
||||
phandle, pid, thandle, tid = win32popen.CreateProcess(command, hStdIn, hStdOut, None)
|
||||
hStdIn = nextStdIn
|
||||
|
||||
return _pipe(win32popen.File2FileObject(handle, 'wb'), phandle, ehandle)
|
||||
|
||||
# flush the stdio buffers since we are about to change the FD under them
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
prev_r, parent_w = os.pipe()
|
||||
|
||||
null = os.open('/dev/null', os.O_RDWR)
|
||||
|
||||
child_pids = []
|
||||
|
||||
for cmd in cmds[:-1]:
|
||||
r, w = os.pipe()
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
# in the child
|
||||
|
||||
# hook up stdin to the "read" channel
|
||||
os.dup2(prev_r, 0)
|
||||
|
||||
# hook up stdout to the output channel
|
||||
os.dup2(w, 1)
|
||||
|
||||
# toss errors
|
||||
os.dup2(null, 2)
|
||||
|
||||
# close these extra descriptors
|
||||
os.close(prev_r)
|
||||
os.close(parent_w)
|
||||
os.close(null)
|
||||
os.close(r)
|
||||
os.close(w)
|
||||
|
||||
# time to run the command
|
||||
try:
|
||||
os.execvp(cmd[0], cmd)
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
# in the parent
|
||||
child_pids.append(pid)
|
||||
|
||||
# we don't need these any more
|
||||
os.close(prev_r)
|
||||
os.close(w)
|
||||
|
||||
# the read channel of this pipe will feed into to the next command
|
||||
prev_r = r
|
||||
|
||||
# no longer needed
|
||||
os.close(null)
|
||||
|
||||
# done with most of the commands. set up the last command to write to "out"
|
||||
if not hasattr(out, 'fileno'):
|
||||
r, w = os.pipe()
|
||||
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
# in the child (the last command)
|
||||
|
||||
# hook up stdin to the "read" channel
|
||||
os.dup2(prev_r, 0)
|
||||
|
||||
# hook up stdout to "out"
|
||||
if hasattr(out, 'fileno'):
|
||||
if out.fileno() != 1:
|
||||
os.dup2(out.fileno(), 1)
|
||||
out.close()
|
||||
|
||||
else:
|
||||
# "out" can't be hooked up directly, so use a pipe and a thread
|
||||
os.dup2(w, 1)
|
||||
os.close(r)
|
||||
os.close(w)
|
||||
|
||||
# close these extra descriptors
|
||||
os.close(prev_r)
|
||||
os.close(parent_w)
|
||||
|
||||
# run the last command
|
||||
try:
|
||||
os.execvp(cmds[-1][0], cmds[-1])
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
child_pids.append(pid)
|
||||
# not needed any more
|
||||
os.close(prev_r)
|
||||
|
||||
if not hasattr(out, 'fileno'):
|
||||
os.close(w)
|
||||
thread = _copy(r, out)
|
||||
thread.start()
|
||||
else:
|
||||
thread = None
|
||||
|
||||
# write into the first pipe, wait on the final process
|
||||
return _pipe(os.fdopen(parent_w, 'w'), child_pids, thread=thread)
|
||||
|
||||
class _copy(threading.Thread):
|
||||
def __init__(self, srcfd, destfile):
|
||||
self.srcfd = srcfd
|
||||
self.destfile = destfile
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while 1:
|
||||
s = os.read(self.srcfd, 1024)
|
||||
if not s:
|
||||
break
|
||||
self.destfile.write(s)
|
||||
finally:
|
||||
os.close(self.srcfd)
|
||||
|
||||
class _pipe:
|
||||
"Wrapper for a file which can wait() on a child process at close time."
|
||||
|
||||
@@ -176,12 +364,12 @@ class _pipe:
|
||||
else:
|
||||
if self.thread:
|
||||
self.thread.join()
|
||||
if type(self.child_pid) == type([]):
|
||||
if type(self.child_pid) == type([]):
|
||||
for pid in self.child_pid:
|
||||
exit = os.waitpid(pid, 0)[1]
|
||||
return exit
|
||||
else:
|
||||
return os.waitpid(self.child_pid, 0)[1]
|
||||
else:
|
||||
return os.waitpid(self.child_pid, 0)[1]
|
||||
return None
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
127
lib/query.py
127
lib/query.py
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -28,7 +28,6 @@ import viewvc
|
||||
import ezt
|
||||
import debug
|
||||
import urllib
|
||||
import fnmatch
|
||||
|
||||
class FormData:
|
||||
def __init__(self, form):
|
||||
@@ -274,60 +273,19 @@ def prev_rev(rev):
|
||||
r = r[:-2]
|
||||
return string.join(r, '.')
|
||||
|
||||
def is_forbidden(cfg, cvsroot_name, module):
|
||||
'''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:
|
||||
if pat[0] == '!':
|
||||
default = 1
|
||||
if fnmatch.fnmatchcase(module, pat[1:]):
|
||||
return 0
|
||||
elif fnmatch.fnmatchcase(module, pat):
|
||||
return 1
|
||||
return default
|
||||
|
||||
def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
ob = _item(num_files=len(files), files=[])
|
||||
ob.log = desc and string.replace(server.escape(desc), '\n', '<br />') or ''
|
||||
|
||||
if desc:
|
||||
ob.log = string.replace(server.escape(desc), '\n', '<br />')
|
||||
else:
|
||||
ob.log = ' '
|
||||
|
||||
for commit in files:
|
||||
repository = commit.GetRepository()
|
||||
directory = commit.GetDirectory()
|
||||
cvsroot_name = cvsroots.get(repository)
|
||||
|
||||
## find the module name (if any)
|
||||
try:
|
||||
module = filter(None, string.split(directory, '/'))[0]
|
||||
except IndexError:
|
||||
module = None
|
||||
|
||||
## skip commits we aren't supposed to show
|
||||
if module and ((module == 'CVSROOT' and cfg.options.hide_cvsroot) \
|
||||
or is_forbidden(cfg, cvsroot_name, module)):
|
||||
dir_parts = filter(None, string.split(commit.GetDirectory(), '/'))
|
||||
if dir_parts \
|
||||
and ((dir_parts[0] == 'CVSROOT' and cfg.options.hide_cvsroot) \
|
||||
or cfg.is_forbidden(dir_parts[0])):
|
||||
continue
|
||||
|
||||
ctime = commit.GetTime()
|
||||
@@ -338,18 +296,17 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
||||
ctime = time.strftime("%y/%m/%d %H:%M %Z", time.localtime(ctime))
|
||||
else:
|
||||
ctime = time.strftime("%y/%m/%d %H:%M", time.gmtime(ctime)) \
|
||||
+ ' UTC'
|
||||
+ ' UTC'
|
||||
|
||||
## make the file link
|
||||
try:
|
||||
file = (directory and directory + "/") + commit.GetFile()
|
||||
except:
|
||||
raise Exception, str([directory, commit.GetFile()])
|
||||
repository = commit.GetRepository()
|
||||
directory = commit.GetDirectory()
|
||||
file = (directory and directory + "/") + commit.GetFile()
|
||||
cvsroot_name = cvsroots.get(repository)
|
||||
|
||||
## If we couldn't find the cvsroot path configured in the
|
||||
## viewvc.conf file, or we don't have a VIEWVC_LINK, then
|
||||
## don't make the link.
|
||||
if cvsroot_name and viewvc_link:
|
||||
## if we couldn't find the cvsroot path configured in the
|
||||
## viewvc.conf file, then don't make the link
|
||||
if cvsroot_name:
|
||||
flink = '[%s] <a href="%s/%s?root=%s">%s</a>' % (
|
||||
cvsroot_name, viewvc_link, urllib.quote(file),
|
||||
cvsroot_name, file)
|
||||
@@ -388,8 +345,7 @@ def run_query(server, cfg, form_data, viewvc_link):
|
||||
files = [ ]
|
||||
|
||||
cvsroots = {}
|
||||
viewvc.expand_root_parents(cfg)
|
||||
rootitems = cfg.general.svn_roots.items() + cfg.general.cvs_roots.items()
|
||||
rootitems = cfg.general.cvs_roots.items() + cfg.general.svn_roots.items()
|
||||
for key, value in rootitems:
|
||||
cvsroots[cvsdb.CleanRepository(value)] = key
|
||||
|
||||
@@ -410,13 +366,6 @@ def run_query(server, cfg, form_data, viewvc_link):
|
||||
commits.append(build_commit(server, cfg, current_desc, files,
|
||||
cvsroots, viewvc_link))
|
||||
|
||||
# Strip out commits that don't have any files attached to them. The
|
||||
# files probably aren't present because they've been blocked via
|
||||
# forbiddenness.
|
||||
def _only_with_files(commit):
|
||||
return len(commit.files) > 0
|
||||
commits = filter(_only_with_files, commits)
|
||||
|
||||
return commits
|
||||
|
||||
def main(server, cfg, viewvc_link):
|
||||
@@ -432,33 +381,41 @@ def main(server, cfg, viewvc_link):
|
||||
commits = [ ]
|
||||
query = 'skipped'
|
||||
|
||||
docroot = cfg.options.docroot
|
||||
if docroot is None and viewvc_link:
|
||||
docroot = viewvc_link + '/' + viewvc.docroot_magic_path
|
||||
|
||||
data = ezt.TemplateData({
|
||||
script_name = server.getenv('SCRIPT_NAME', '')
|
||||
|
||||
data = {
|
||||
'cfg' : cfg,
|
||||
'address' : cfg.general.address,
|
||||
'vsn' : viewvc.__version__,
|
||||
'repository' : server.escape(form_data.repository),
|
||||
'branch' : server.escape(form_data.branch),
|
||||
'directory' : server.escape(form_data.directory),
|
||||
'file' : server.escape(form_data.file),
|
||||
'who' : server.escape(form_data.who),
|
||||
'docroot' : docroot,
|
||||
|
||||
'repository' : server.escape(form_data.repository, 1),
|
||||
'branch' : server.escape(form_data.branch, 1),
|
||||
'directory' : server.escape(form_data.directory, 1),
|
||||
'file' : server.escape(form_data.file, 1),
|
||||
'who' : server.escape(form_data.who, 1),
|
||||
'docroot' : cfg.options.docroot is None \
|
||||
and viewvc_link + '/' + viewvc.docroot_magic_path \
|
||||
or cfg.options.docroot,
|
||||
|
||||
'sortby' : form_data.sortby,
|
||||
'date' : form_data.date,
|
||||
|
||||
'query' : query,
|
||||
'commits' : commits,
|
||||
'num_commits' : len(commits),
|
||||
'rss_href' : None,
|
||||
'hours' : form_data.hours and form_data.hours or 2,
|
||||
})
|
||||
}
|
||||
|
||||
if form_data.hours:
|
||||
data['hours'] = form_data.hours
|
||||
else:
|
||||
data['hours'] = 2
|
||||
|
||||
server.header()
|
||||
|
||||
# generate the page
|
||||
server.header()
|
||||
template = viewvc.get_view_template(cfg, "query")
|
||||
template.generate(server.file(), data)
|
||||
template.generate(sys.stdout, data)
|
||||
|
||||
except SystemExit, e:
|
||||
pass
|
||||
|
107
lib/sapi.py
107
lib/sapi.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -20,27 +20,13 @@ import string
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import cgi
|
||||
|
||||
|
||||
# global server object. It will be either a CgiServer, a WsgiServer,
|
||||
# or a proxy to an AspServer or ModPythonServer object.
|
||||
# global server object. It will be either a CgiServer or a proxy to
|
||||
# an AspServer or ModPythonServer object.
|
||||
server = None
|
||||
|
||||
|
||||
# Simple HTML string escaping. Note that we always escape the
|
||||
# double-quote character -- ViewVC shouldn't ever need to preserve
|
||||
# that character as-is, and sometimes needs to embed escaped values
|
||||
# into HTML attributes.
|
||||
def escape(s):
|
||||
s = str(s)
|
||||
s = string.replace(s, '&', '&')
|
||||
s = string.replace(s, '>', '>')
|
||||
s = string.replace(s, '<', '<')
|
||||
s = string.replace(s, '"', """)
|
||||
return s
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self):
|
||||
self.pageGlobals = {}
|
||||
@@ -48,9 +34,6 @@ class Server:
|
||||
def self(self):
|
||||
return self
|
||||
|
||||
def escape(self, s):
|
||||
return escape(s)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
@@ -146,6 +129,9 @@ class CgiServer(Server):
|
||||
global server
|
||||
server = self
|
||||
|
||||
global cgi
|
||||
import cgi
|
||||
|
||||
def addheader(self, name, value):
|
||||
self.headers.append((name, value))
|
||||
|
||||
@@ -172,7 +158,11 @@ class CgiServer(Server):
|
||||
if self.iis: url = fix_iis_url(self, url)
|
||||
self.addheader('Location', url)
|
||||
self.header(status='301 Moved')
|
||||
sys.stdout.write('This document is located <a href="%s">here</a>.\n' % url)
|
||||
print 'This document is located <a href="%s">here</a>.' % url
|
||||
sys.exit(0)
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return cgi.escape(s, quote)
|
||||
|
||||
def getenv(self, name, value=None):
|
||||
ret = os.environ.get(name, value)
|
||||
@@ -186,7 +176,7 @@ class CgiServer(Server):
|
||||
def FieldStorage(fp=None, headers=None, outerboundary="",
|
||||
environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
||||
return cgi.FieldStorage(fp, headers, outerboundary, environ,
|
||||
keep_blank_values, strict_parsing)
|
||||
keep_blank_values, strict_parsing)
|
||||
|
||||
def write(self, s):
|
||||
sys.stdout.write(s)
|
||||
@@ -198,66 +188,6 @@ class CgiServer(Server):
|
||||
return sys.stdout
|
||||
|
||||
|
||||
class WsgiServer(Server):
|
||||
def __init__(self, environ, start_response):
|
||||
Server.__init__(self)
|
||||
|
||||
self._environ = environ
|
||||
self._start_response = start_response;
|
||||
self._headers = []
|
||||
self._wsgi_write = None
|
||||
self.headerSent = False
|
||||
|
||||
global server
|
||||
server = self
|
||||
|
||||
global cgi
|
||||
import cgi
|
||||
|
||||
def addheader(self, name, value):
|
||||
self._headers.append((name, value))
|
||||
|
||||
def header(self, content_type='text/html; charset=UTF-8', status=None):
|
||||
if not status:
|
||||
status = "200 OK"
|
||||
if not self.headerSent:
|
||||
self.headerSent = True
|
||||
self._headers.insert(0, ("Content-Type", content_type),)
|
||||
self._wsgi_write = self._start_response("%s" % status, self._headers)
|
||||
|
||||
def redirect(self, url):
|
||||
"""Redirect client to url. This discards any data that has been queued
|
||||
to be sent to the user. But there should never by any anyway.
|
||||
"""
|
||||
self.addheader('Location', url)
|
||||
self.header(status='301 Moved')
|
||||
self._wsgi_write('This document is located <a href="%s">here</a>.' % url)
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return cgi.escape(s, quote)
|
||||
|
||||
def getenv(self, name, value=None):
|
||||
return self._environ.get(name, value)
|
||||
|
||||
def params(self):
|
||||
return cgi.parse(environ=self._environ, fp=self._environ["wsgi.input"])
|
||||
|
||||
def FieldStorage(self, fp=None, headers=None, outerboundary="",
|
||||
environ=os.environ, keep_blank_values=0, strict_parsing=0):
|
||||
return cgi.FieldStorage(self._environ["wsgi.input"], headers,
|
||||
outerboundary, self._environ, keep_blank_values,
|
||||
strict_parsing)
|
||||
|
||||
def write(self, s):
|
||||
self._wsgi_write(s)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def file(self):
|
||||
return File(self)
|
||||
|
||||
|
||||
class AspServer(ThreadedServer):
|
||||
def __init__(self, Server, Request, Response, Application):
|
||||
ThreadedServer.__init__(self)
|
||||
@@ -289,6 +219,10 @@ class AspServer(ThreadedServer):
|
||||
|
||||
def redirect(self, url):
|
||||
self.response.Redirect(url)
|
||||
sys.exit()
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return self.server.HTMLEncode(str(s))
|
||||
|
||||
def getenv(self, name, value = None):
|
||||
ret = self.request.ServerVariables(name)()
|
||||
@@ -351,6 +285,9 @@ class ModPythonServer(ThreadedServer):
|
||||
self.request = request
|
||||
self.headerSent = 0
|
||||
|
||||
global cgi
|
||||
import cgi
|
||||
|
||||
def addheader(self, name, value):
|
||||
self.request.headers_out.add(name, value)
|
||||
|
||||
@@ -371,7 +308,11 @@ class ModPythonServer(ThreadedServer):
|
||||
self.request.headers_out['Location'] = url
|
||||
self.request.status = mod_python.apache.HTTP_MOVED_TEMPORARILY
|
||||
self.request.write("You are being redirected to <a href=\"%s\">%s</a>"
|
||||
% (url, url))
|
||||
% (url, url))
|
||||
sys.exit()
|
||||
|
||||
def escape(self, s, quote = None):
|
||||
return cgi.escape(s, quote)
|
||||
|
||||
def getenv(self, name, value = None):
|
||||
try:
|
||||
|
@@ -1,49 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"""Generic API for implementing authorization checks employed by ViewVC."""
|
||||
|
||||
import string
|
||||
import vclib
|
||||
|
||||
|
||||
class GenericViewVCAuthorizer:
|
||||
"""Abstract class encapsulating version control authorization routines."""
|
||||
|
||||
def __init__(self, username=None, params={}):
|
||||
"""Create a GenericViewVCAuthorizer object which will be used to
|
||||
validate that USERNAME has the permissions needed to view version
|
||||
control repositories (in whole or in part). PARAMS is a
|
||||
dictionary of custom parameters for the authorizer."""
|
||||
pass
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
|
||||
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
|
||||
repository ROOTNAME."""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
|
||||
class ViewVCAuthorizer(GenericViewVCAuthorizer):
|
||||
"""The uber-permissive authorizer."""
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
return 1
|
@@ -1,46 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
import vcauth
|
||||
import vclib
|
||||
import fnmatch
|
||||
import string
|
||||
|
||||
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
"""A simple top-level module authorizer."""
|
||||
def __init__(self, username, params={}):
|
||||
forbidden = params.get('forbidden', '')
|
||||
self.forbidden = map(string.strip,
|
||||
filter(None, string.split(forbidden, ',')))
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
# No path? No problem.
|
||||
if not path_parts:
|
||||
return 1
|
||||
|
||||
# Not a directory? We aren't interested.
|
||||
if pathtype != vclib.DIR:
|
||||
return 1
|
||||
|
||||
# At this point we're looking at a directory path.
|
||||
module = path_parts[0]
|
||||
default = 1
|
||||
for pat in self.forbidden:
|
||||
if pat[0] == '!':
|
||||
default = 0
|
||||
if fnmatch.fnmatchcase(module, pat[1:]):
|
||||
return 1
|
||||
elif fnmatch.fnmatchcase(module, pat):
|
||||
return 0
|
||||
return default
|
@@ -1,58 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2008 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
import vcauth
|
||||
import vclib
|
||||
import fnmatch
|
||||
import string
|
||||
import re
|
||||
|
||||
|
||||
def _split_regexp(restr):
|
||||
"""Return a 2-tuple consisting of a compiled regular expression
|
||||
object and a boolean flag indicating if that object should be
|
||||
interpreted inversely."""
|
||||
if restr[0] == '!':
|
||||
return re.compile(restr[1:]), 1
|
||||
return re.compile(restr), 0
|
||||
|
||||
|
||||
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
"""A simple regular-expression-based authorizer."""
|
||||
def __init__(self, username, params={}):
|
||||
forbidden = params.get('forbiddenre', '')
|
||||
self.forbidden = map(lambda x: _split_regexp(string.strip(x)),
|
||||
filter(None, string.split(forbidden, ',')))
|
||||
|
||||
def _check_root_path_access(self, root_path):
|
||||
default = 1
|
||||
for forbidden, negated in self.forbidden:
|
||||
if negated:
|
||||
default = 0
|
||||
if forbidden.search(root_path):
|
||||
return 1
|
||||
elif forbidden.search(root_path):
|
||||
return 0
|
||||
return default
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
return self._check_root_path_access(rootname)
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
root_path = rootname
|
||||
if path_parts:
|
||||
root_path = root_path + '/' + string.join(path_parts, '/')
|
||||
if pathtype == vclib.DIR:
|
||||
root_path = root_path + '/'
|
||||
else:
|
||||
root_path = root_path + '/'
|
||||
return self._check_root_path_access(root_path)
|
||||
|
@@ -1,237 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
# (c) 2006 Sergey Lapin <slapin@dataart.com>
|
||||
|
||||
import vcauth
|
||||
import string
|
||||
import os.path
|
||||
import debug
|
||||
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
"""Subversion authz authorizer module"""
|
||||
|
||||
def __init__(self, username, params={}):
|
||||
self.rootpaths = { } # {root -> { paths -> access boolean for USERNAME }}
|
||||
|
||||
# Get the authz file location from a passed-in parameter.
|
||||
self.authz_file = params.get('authzfile')
|
||||
if not self.authz_file:
|
||||
raise debug.ViewVCException("No authzfile configured")
|
||||
if not os.path.exists(self.authz_file):
|
||||
raise debug.ViewVCException("Configured authzfile file not found")
|
||||
|
||||
# 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()
|
||||
elif self.force_username_case == "lower":
|
||||
self.username = username.lower()
|
||||
elif not self.force_username_case:
|
||||
self.username = username
|
||||
else:
|
||||
raise debug.ViewVCException("Invalid value for force_username_case "
|
||||
"option")
|
||||
|
||||
def _get_paths_for_root(self, rootname):
|
||||
if self.rootpaths.has_key(rootname):
|
||||
return self.rootpaths[rootname]
|
||||
|
||||
paths_for_root = { }
|
||||
|
||||
# Parse the authz file, replacing ConfigParser's optionxform()
|
||||
# method with something that won't futz with the case of the
|
||||
# option names.
|
||||
cp = ConfigParser()
|
||||
cp.optionxform = lambda x: x
|
||||
cp.read(self.authz_file)
|
||||
|
||||
# Figure out if there are any aliases for the current username
|
||||
aliases = []
|
||||
if cp.has_section('aliases'):
|
||||
for alias in cp.options('aliases'):
|
||||
entry = cp.get('aliases', alias)
|
||||
if entry == self.username:
|
||||
aliases.append(alias)
|
||||
|
||||
# Figure out which groups USERNAME has a part of.
|
||||
groups = []
|
||||
if cp.has_section('groups'):
|
||||
all_groups = []
|
||||
|
||||
def _process_group(groupname):
|
||||
"""Inline function to handle groups within groups.
|
||||
|
||||
For a group to be within another group in SVN, the group
|
||||
definitions must be in the correct order in the config file.
|
||||
ie. If group A is a member of group B then group A must be
|
||||
defined before group B in the [groups] section.
|
||||
|
||||
Unfortunately, the ConfigParser class provides no way of
|
||||
finding the order in which groups were defined so, for reasons
|
||||
of practicality, this function lets you get away with them
|
||||
being defined in the wrong order. Recursion is guarded
|
||||
against though."""
|
||||
|
||||
# If we already know the user is part of this already-
|
||||
# processed group, return that fact.
|
||||
if groupname in groups:
|
||||
return 1
|
||||
# Otherwise, ensure we don't process a group twice.
|
||||
if groupname in all_groups:
|
||||
return 0
|
||||
# Store the group name in a global list so it won't be processed again
|
||||
all_groups.append(groupname)
|
||||
group_member = 0
|
||||
groupname = groupname.strip()
|
||||
entries = string.split(cp.get('groups', groupname), ',')
|
||||
for entry in entries:
|
||||
entry = string.strip(entry)
|
||||
if entry == self.username:
|
||||
group_member = 1
|
||||
break
|
||||
elif entry[0:1] == "@" and _process_group(entry[1:]):
|
||||
group_member = 1
|
||||
break
|
||||
elif entry[0:1] == "&" and entry[1:] in aliases:
|
||||
group_member = 1
|
||||
break
|
||||
if group_member:
|
||||
groups.append(groupname)
|
||||
return group_member
|
||||
|
||||
# Process the groups
|
||||
for group in cp.options('groups'):
|
||||
_process_group(group)
|
||||
|
||||
def _userspec_matches_user(userspec):
|
||||
# If there is an inversion character, recurse and return the
|
||||
# opposite result.
|
||||
if userspec[0:1] == '~':
|
||||
return not _userspec_matches_user(userspec[1:])
|
||||
|
||||
# See if the userspec applies to our current user.
|
||||
return userspec == '*' \
|
||||
or userspec == self.username \
|
||||
or (self.username is not None and userspec == "$authenticated") \
|
||||
or (self.username is None and userspec == "$anonymous") \
|
||||
or (userspec[0:1] == "@" and userspec[1:] in groups) \
|
||||
or (userspec[0:1] == "&" and userspec[1:] in aliases)
|
||||
|
||||
def _process_access_section(section):
|
||||
"""Inline function for determining user access in a single
|
||||
config secction. Return a two-tuple (ALLOW, DENY) containing
|
||||
the access determination for USERNAME in a given authz file
|
||||
SECTION (if any)."""
|
||||
|
||||
# Figure if this path is explicitly allowed or denied to USERNAME.
|
||||
allow = deny = 0
|
||||
for user in cp.options(section):
|
||||
user = string.strip(user)
|
||||
if _userspec_matches_user(user):
|
||||
# See if the 'r' permission is among the ones granted to
|
||||
# USER. If so, we can stop looking. (Entry order is not
|
||||
# relevant -- we'll use the most permissive entry, meaning
|
||||
# one 'allow' is all we need.)
|
||||
allow = string.find(cp.get(section, user), 'r') != -1
|
||||
deny = not allow
|
||||
if allow:
|
||||
break
|
||||
return allow, deny
|
||||
|
||||
# Read the other (non-"groups") sections, and figure out in which
|
||||
# repositories USERNAME or his groups have read rights. We'll
|
||||
# first check groups that have no specific repository designation,
|
||||
# then superimpose those that have a repository designation which
|
||||
# matches the one we're asking about.
|
||||
root_sections = []
|
||||
for section in cp.sections():
|
||||
|
||||
# Skip the "groups" section -- we handled that already.
|
||||
if section == 'groups':
|
||||
continue
|
||||
|
||||
if section == 'aliases':
|
||||
continue
|
||||
|
||||
# Process root-agnostic access sections; skip (but remember)
|
||||
# root-specific ones that match our root; ignore altogether
|
||||
# root-specific ones that don't match our root. While we're at
|
||||
# it, go ahead and figure out the repository path we're talking
|
||||
# about.
|
||||
if section.find(':') == -1:
|
||||
path = section
|
||||
else:
|
||||
name, path = string.split(section, ':', 1)
|
||||
if name == rootname:
|
||||
root_sections.append(section)
|
||||
continue
|
||||
|
||||
# Check for a specific access determination.
|
||||
allow, deny = _process_access_section(section)
|
||||
|
||||
# If we got an explicit access determination for this path and this
|
||||
# USERNAME, record it.
|
||||
if allow or deny:
|
||||
if path != '/':
|
||||
path = '/' + string.join(filter(None, string.split(path, '/')), '/')
|
||||
paths_for_root[path] = allow
|
||||
|
||||
# Okay. Superimpose those root-specific values now.
|
||||
for section in root_sections:
|
||||
|
||||
# Get the path again.
|
||||
name, path = string.split(section, ':', 1)
|
||||
|
||||
# Check for a specific access determination.
|
||||
allow, deny = _process_access_section(section)
|
||||
|
||||
# If we got an explicit access determination for this path and this
|
||||
# USERNAME, record it.
|
||||
if allow or deny:
|
||||
if path != '/':
|
||||
path = '/' + string.join(filter(None, string.split(path, '/')), '/')
|
||||
paths_for_root[path] = allow
|
||||
|
||||
# If the root isn't readable, there's no point in caring about all
|
||||
# the specific paths the user can't see. Just point the rootname
|
||||
# to a None paths dictionary.
|
||||
root_is_readable = 0
|
||||
for path in paths_for_root.keys():
|
||||
if paths_for_root[path]:
|
||||
root_is_readable = 1
|
||||
break
|
||||
if not root_is_readable:
|
||||
paths_for_root = None
|
||||
|
||||
self.rootpaths[rootname] = paths_for_root
|
||||
return paths_for_root
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
paths = self._get_paths_for_root(rootname)
|
||||
return (paths is not None) and 1 or 0
|
||||
|
||||
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
|
||||
# denial of access.
|
||||
paths = self._get_paths_for_root(rootname)
|
||||
if paths is None:
|
||||
return 0
|
||||
parts = path_parts[:]
|
||||
while parts:
|
||||
path = '/' + string.join(parts, '/')
|
||||
if paths.has_key(path):
|
||||
return paths[path]
|
||||
del parts[-1]
|
||||
return paths.get('/', 0)
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -27,43 +27,11 @@ UNIFIED = 1
|
||||
CONTEXT = 2
|
||||
SIDE_BY_SIDE = 3
|
||||
|
||||
# root types returned by Repository.roottype().
|
||||
CVS = 'cvs'
|
||||
SVN = 'svn'
|
||||
|
||||
# action kinds found in ChangedPath.action
|
||||
ADDED = 'added'
|
||||
DELETED = 'deleted'
|
||||
REPLACED = 'replaced'
|
||||
MODIFIED = 'modified'
|
||||
|
||||
# log sort keys
|
||||
SORTBY_DEFAULT = 0 # default/no sorting
|
||||
SORTBY_DATE = 1 # sorted by date, youngest first
|
||||
SORTBY_REV = 2 # sorted by revision, youngest first
|
||||
|
||||
|
||||
# ======================================================================
|
||||
#
|
||||
class Repository:
|
||||
"""Abstract class representing a repository."""
|
||||
|
||||
def rootname(self):
|
||||
"""Return the name of this repository."""
|
||||
|
||||
def roottype(self):
|
||||
"""Return the type of this repository (vclib.CVS, vclib.SVN, ...)."""
|
||||
|
||||
def rootpath(self):
|
||||
"""Return the location of this repository."""
|
||||
|
||||
def authorizer(self):
|
||||
"""Return the vcauth.Authorizer object associated with this
|
||||
repository, or None if no such association has been made."""
|
||||
|
||||
def open(self):
|
||||
"""Open a connection to the repository."""
|
||||
|
||||
def itemtype(self, path_parts, rev):
|
||||
"""Return the type of the item (file or dir) at the given path and revision
|
||||
|
||||
@@ -107,7 +75,7 @@ class Repository:
|
||||
New properties will be set on all of the DirEntry objects in the entries
|
||||
list. At the very least, a "rev" property will be set to a revision
|
||||
number or None if the entry doesn't have a number. Other properties that
|
||||
may be set include "date", "author", "log", "size", and "lockinfo".
|
||||
may be set include "date", "author", and "log".
|
||||
|
||||
The path is specified as a list of components, relative to the root
|
||||
of the repository. e.g. ["subdir1", "subdir2", "filename"]
|
||||
@@ -121,7 +89,7 @@ class Repository:
|
||||
options is a dictionary of implementation specific options
|
||||
"""
|
||||
|
||||
def itemlog(self, path_parts, rev, sortby, first, limit, options):
|
||||
def itemlog(self, path_parts, rev, options):
|
||||
"""Retrieve an item's log information
|
||||
|
||||
The result is a list of Revision objects
|
||||
@@ -131,28 +99,9 @@ class Repository:
|
||||
|
||||
rev is the revision of the item to return information about
|
||||
|
||||
sortby indicates the way in which the returned list should be
|
||||
sorted (SORTBY_DEFAULT, SORTBY_DATE, SORTBY_REV)
|
||||
|
||||
first is the 0-based index of the first Revision returned (after
|
||||
sorting, if any, has occured)
|
||||
|
||||
limit is the maximum number of returned Revisions, or 0 to return
|
||||
all available data
|
||||
|
||||
options is a dictionary of implementation specific options
|
||||
"""
|
||||
|
||||
def itemprops(self, path_parts, rev):
|
||||
"""Return a dictionary mapping property names to property values
|
||||
for properties stored on an item.
|
||||
|
||||
The path is specified as a list of components, relative to the root
|
||||
of the repository. e.g. ["subdir1", "subdir2", "filename"]
|
||||
|
||||
rev is the revision of the item to return information about.
|
||||
"""
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
"""Return a diff (in GNU diff format) of two file revisions
|
||||
|
||||
@@ -171,46 +120,26 @@ class Repository:
|
||||
def annotate(self, path_parts, rev):
|
||||
"""Return a list of annotate file content lines and a revision.
|
||||
|
||||
The result is a list of Annotation objects, sorted by their
|
||||
line_number components.
|
||||
"""
|
||||
The annotated lines are an collection of objects with the
|
||||
following addressable members:
|
||||
|
||||
def revinfo(self, rev):
|
||||
"""Return information about a global revision
|
||||
text - raw text of a line of file contents
|
||||
line_number - line number on which the line is found
|
||||
rev - revision in which the line was last modified
|
||||
prev_rev - revision prior to 'rev'
|
||||
author - author who last modified the line
|
||||
date - date on which the line was last modified, in seconds
|
||||
since the epoch, GMT
|
||||
|
||||
rev is the revision of the item to return information about
|
||||
|
||||
Return value is a 5-tuple containing: the date, author, log
|
||||
message, a list of ChangedPath items representing paths changed,
|
||||
and a dictionary mapping property names to property values for
|
||||
properties stored on an item.
|
||||
|
||||
Raise vclib.UnsupportedFeature if the version control system
|
||||
doesn't support a global revision concept.
|
||||
"""
|
||||
|
||||
def isexecutable(self, path_parts, rev):
|
||||
"""Return true iff a given revision of a versioned file is to be
|
||||
considered an executable program or script.
|
||||
|
||||
The path is specified as a list of components, relative to the root
|
||||
of the repository. e.g. ["subdir1", "subdir2", "filename"]
|
||||
|
||||
rev is the revision of the item to return information about
|
||||
These object are sort by their line_number components.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
# ======================================================================
|
||||
class DirEntry:
|
||||
"""Instances represent items in a directory listing"""
|
||||
"Instances represent items in a directory listing"
|
||||
|
||||
def __init__(self, name, kind, errors=[]):
|
||||
"""Create a new DirEntry() item:
|
||||
NAME: The name of the directory entry
|
||||
KIND: The path kind of the entry (vclib.DIR, vclib.FILE)
|
||||
ERRORS: A list of error strings representing problems encountered
|
||||
while determining the other info about this entry
|
||||
"""
|
||||
self.name = name
|
||||
self.kind = kind
|
||||
self.errors = errors
|
||||
@@ -218,17 +147,16 @@ class DirEntry:
|
||||
class Revision:
|
||||
"""Instances holds information about revisions of versioned resources"""
|
||||
|
||||
def __init__(self, number, string, date, author, changed, log, size, lockinfo):
|
||||
"""Create a new Revision() item:
|
||||
NUMBER: Revision in an integer-based, sortable format
|
||||
STRING: Revision as a string
|
||||
DATE: Seconds since Epoch (GMT) that this revision was created
|
||||
AUTHOR: Author of the revision
|
||||
CHANGED: Lines-changed (contextual diff) information
|
||||
LOG: Log message associated with the creation of this revision
|
||||
SIZE: Size (in bytes) of this revision's fulltext (files only)
|
||||
LOCKINFO: Information about locks held on this revision
|
||||
"""
|
||||
"""Create a new Revision() item:
|
||||
NUMBER: Revision in an integer-based, sortable format
|
||||
STRING: Revision as a string
|
||||
DATE: Seconds since Epoch (GMT) that this revision was created
|
||||
AUTHOR: Author of the revision
|
||||
CHANGED: Lines-changed (contextual diff) information
|
||||
LOG: Log message associated with the creation of this revision
|
||||
SIZE: Size (in bytes) of this revision's fulltext (files only)
|
||||
"""
|
||||
def __init__(self, number, string, date, author, changed, log, size):
|
||||
self.number = number
|
||||
self.string = string
|
||||
self.date = date
|
||||
@@ -236,69 +164,16 @@ class Revision:
|
||||
self.changed = changed
|
||||
self.log = log
|
||||
self.size = size
|
||||
self.lockinfo = lockinfo
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.number, other.number)
|
||||
|
||||
class Annotation:
|
||||
"""Instances represent per-line file annotation information"""
|
||||
|
||||
def __init__(self, text, line_number, rev, prev_rev, author, date):
|
||||
"""Create a new Annotation() item:
|
||||
TEXT: Raw text of a line of file contents
|
||||
LINE_NUMBER: Line number on which the line is found
|
||||
REV: Revision in which the line was last modified
|
||||
PREV_REV: Revision prior to 'rev'
|
||||
AUTHOR: Author who last modified the line
|
||||
DATE: Date on which the line was last modified, in seconds since
|
||||
the epoch, GMT
|
||||
"""
|
||||
self.text = text
|
||||
self.line_number = line_number
|
||||
self.rev = rev
|
||||
self.prev_rev = prev_rev
|
||||
self.author = author
|
||||
self.date = date
|
||||
|
||||
class ChangedPath:
|
||||
"""Instances represent changes to paths"""
|
||||
|
||||
def __init__(self, path_parts, rev, pathtype, base_path_parts,
|
||||
base_rev, action, copied, text_changed, props_changed):
|
||||
"""Create a new ChangedPath() item:
|
||||
PATH_PARTS: Path that was changed
|
||||
REV: Revision represented by this change
|
||||
PATHTYPE: Type of this path (vclib.DIR, vclib.FILE, ...)
|
||||
BASE_PATH_PARTS: Previous path for this changed item
|
||||
BASE_REV: Previous revision for this changed item
|
||||
ACTION: Kind of change (vclib.ADDED, vclib.DELETED, ...)
|
||||
COPIED: Boolean -- was this path copied from elsewhere?
|
||||
TEXT_CHANGED: Boolean -- did the file's text change?
|
||||
PROPS_CHANGED: Boolean -- did the item's metadata change?
|
||||
"""
|
||||
self.path_parts = path_parts
|
||||
self.rev = rev
|
||||
self.pathtype = pathtype
|
||||
self.base_path_parts = base_path_parts
|
||||
self.base_rev = base_rev
|
||||
self.action = action
|
||||
self.copied = copied
|
||||
self.text_changed = text_changed
|
||||
self.props_changed = props_changed
|
||||
|
||||
|
||||
# ======================================================================
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
class ReposNotFound(Error):
|
||||
pass
|
||||
|
||||
class UnsupportedFeature(Error):
|
||||
pass
|
||||
|
||||
class ItemNotFound(Error):
|
||||
def __init__(self, path):
|
||||
# use '/' rather than os.sep because this is for user consumption, and
|
||||
@@ -306,7 +181,6 @@ class ItemNotFound(Error):
|
||||
if type(path) in (types.TupleType, types.ListType):
|
||||
path = string.join(path, '/')
|
||||
Error.__init__(self, path)
|
||||
|
||||
class InvalidRevision(Error):
|
||||
def __init__(self, revision=None):
|
||||
if revision is None:
|
||||
@@ -314,9 +188,6 @@ class InvalidRevision(Error):
|
||||
else:
|
||||
Error.__init__(self, "Invalid revision " + str(revision))
|
||||
|
||||
class NonTextualFileContents(Error):
|
||||
pass
|
||||
|
||||
# ======================================================================
|
||||
# Implementation code used by multiple vclib modules
|
||||
|
||||
@@ -329,18 +200,12 @@ def _diff_args(type, options):
|
||||
args = []
|
||||
if type == CONTEXT:
|
||||
if options.has_key('context'):
|
||||
if options['context'] is None:
|
||||
args.append('--context=-1')
|
||||
else:
|
||||
args.append('--context=%i' % options['context'])
|
||||
args.append('--context=%i' % options['context'])
|
||||
else:
|
||||
args.append('-c')
|
||||
elif type == UNIFIED:
|
||||
if options.has_key('context'):
|
||||
if options['context'] is None:
|
||||
args.append('--unified=-1')
|
||||
else:
|
||||
args.append('--unified=%i' % options['context'])
|
||||
args.append('--unified=%i' % options['context'])
|
||||
else:
|
||||
args.append('-u')
|
||||
elif type == SIDE_BY_SIDE:
|
||||
@@ -361,14 +226,14 @@ class _diff_fp:
|
||||
"""File object reading a diff between temporary files, cleaning up
|
||||
on close"""
|
||||
|
||||
def __init__(self, temp1, temp2, info1=None, info2=None, diff_cmd='diff', diff_opts=[]):
|
||||
def __init__(self, temp1, temp2, info1=None, info2=None, diff_opts=[]):
|
||||
self.temp1 = temp1
|
||||
self.temp2 = temp2
|
||||
args = diff_opts[:]
|
||||
if info1 and info2:
|
||||
args.extend(["-L", self._label(info1), "-L", self._label(info2)])
|
||||
args.extend([temp1, temp2])
|
||||
self.fp = popen.popen(diff_cmd, args, "r")
|
||||
self.fp = popen.popen("diff", args, "r")
|
||||
|
||||
def read(self, bytes):
|
||||
return self.fp.read(bytes)
|
||||
@@ -397,26 +262,3 @@ class _diff_fp:
|
||||
def _label(self, (path, date, rev)):
|
||||
date = date and time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date))
|
||||
return "%s\t%s\t%s" % (path, date, rev)
|
||||
|
||||
|
||||
def check_root_access(repos):
|
||||
"""Return 1 iff the associated username is permitted to read REPOS,
|
||||
as determined by consulting REPOS's Authorizer object (if any)."""
|
||||
|
||||
auth = repos.authorizer()
|
||||
if not auth:
|
||||
return 1
|
||||
return auth.check_root_access(repos.rootname())
|
||||
|
||||
def check_path_access(repos, path_parts, pathtype=None, rev=None):
|
||||
"""Return 1 iff the associated username is permitted to read
|
||||
revision REV of the path PATH_PARTS (of type PATHTYPE) in repository
|
||||
REPOS, as determined by consulting REPOS's Authorizer object (if any)."""
|
||||
|
||||
auth = repos.authorizer()
|
||||
if not auth:
|
||||
return 1
|
||||
if not pathtype:
|
||||
pathtype = repos.itemtype(path_parts, rev)
|
||||
return auth.check_path_access(repos.rootname(), path_parts, pathtype, rev)
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -13,7 +13,6 @@
|
||||
"Version Control lib driver for locally accessible cvs-repositories."
|
||||
|
||||
import vclib
|
||||
import vcauth
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
@@ -26,91 +25,50 @@ import time
|
||||
import compat
|
||||
import popen
|
||||
|
||||
class BaseCVSRepository(vclib.Repository):
|
||||
def __init__(self, name, rootpath, authorizer, utilities):
|
||||
class CVSRepository(vclib.Repository):
|
||||
def __init__(self, name, rootpath):
|
||||
if not os.path.isdir(rootpath):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
self.name = name
|
||||
self.rootpath = rootpath
|
||||
self.auth = authorizer
|
||||
self.utilities = utilities
|
||||
|
||||
# See if this repository is even viewable, authz-wise.
|
||||
if not vclib.check_root_access(self):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
self.name = name
|
||||
self.rootpath = rootpath
|
||||
|
||||
def rootpath(self):
|
||||
return self.rootpath
|
||||
|
||||
def roottype(self):
|
||||
return vclib.CVS
|
||||
|
||||
def authorizer(self):
|
||||
return self.auth
|
||||
|
||||
def itemtype(self, path_parts, rev):
|
||||
basepath = self._getpath(path_parts)
|
||||
kind = None
|
||||
if os.path.isdir(basepath):
|
||||
kind = vclib.DIR
|
||||
elif os.path.isfile(basepath + ',v'):
|
||||
kind = vclib.FILE
|
||||
else:
|
||||
atticpath = self._getpath(self._atticpath(path_parts))
|
||||
if os.path.isfile(atticpath + ',v'):
|
||||
kind = vclib.FILE
|
||||
if not kind:
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
if not vclib.check_path_access(self, path_parts, kind, rev):
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
return kind
|
||||
return vclib.DIR
|
||||
if os.path.isfile(basepath + ',v'):
|
||||
return vclib.FILE
|
||||
atticpath = self._getpath(self._atticpath(path_parts))
|
||||
if os.path.isfile(atticpath + ',v'):
|
||||
return vclib.FILE
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
|
||||
def itemprops(self, path_parts, rev):
|
||||
self.itemtype(path_parts, rev) # does auth-check
|
||||
return {} # CVS doesn't support properties
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a directory."
|
||||
% (string.join(path_parts, "/")))
|
||||
|
||||
# Only RCS files (*,v) and subdirs are returned.
|
||||
data = [ ]
|
||||
|
||||
full_name = self._getpath(path_parts)
|
||||
for file in os.listdir(full_name):
|
||||
name = None
|
||||
kind, errors = _check_path(os.path.join(full_name, file))
|
||||
if kind == vclib.FILE:
|
||||
if file[-2:] == ',v':
|
||||
name = file[:-2]
|
||||
data.append(CVSDirEntry(file[:-2], kind, errors, 0))
|
||||
elif kind == vclib.DIR:
|
||||
if file != 'Attic' and file != 'CVS': # CVS directory is for fileattr
|
||||
name = file
|
||||
data.append(CVSDirEntry(file, kind, errors, 0))
|
||||
else:
|
||||
name = file
|
||||
if not name:
|
||||
continue
|
||||
if vclib.check_path_access(self, path_parts + [name], kind, rev):
|
||||
data.append(CVSDirEntry(name, kind, errors, 0))
|
||||
data.append(CVSDirEntry(file, kind, errors, 0))
|
||||
|
||||
full_name = os.path.join(full_name, 'Attic')
|
||||
if os.path.isdir(full_name):
|
||||
for file in os.listdir(full_name):
|
||||
name = None
|
||||
kind, errors = _check_path(os.path.join(full_name, file))
|
||||
if kind == vclib.FILE:
|
||||
if file[-2:] == ',v':
|
||||
name = file[:-2]
|
||||
data.append(CVSDirEntry(file[:-2], kind, errors, 1))
|
||||
elif kind != vclib.DIR:
|
||||
name = file
|
||||
if not name:
|
||||
continue
|
||||
if vclib.check_path_access(self, path_parts + [name], kind, rev):
|
||||
data.append(CVSDirEntry(name, kind, errors, 1))
|
||||
data.append(CVSDirEntry(file, kind, errors, 1))
|
||||
|
||||
return data
|
||||
|
||||
@@ -130,42 +88,38 @@ class BaseCVSRepository(vclib.Repository):
|
||||
ret_file = self._getpath(ret_parts)
|
||||
if not os.path.isfile(ret_file + ',v'):
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
|
||||
if root:
|
||||
ret = ret_file
|
||||
else:
|
||||
ret = string.join(ret_parts, "/")
|
||||
|
||||
if v:
|
||||
ret = ret + ",v"
|
||||
|
||||
return ret
|
||||
|
||||
def isexecutable(self, path_parts, rev):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
rcsfile = self.rcsfile(path_parts, 1)
|
||||
return os.access(rcsfile, os.X_OK)
|
||||
class BinCVSRepository(CVSRepository):
|
||||
def __init__(self, name, rootpath, rcs_paths):
|
||||
CVSRepository.__init__(self, name, rootpath)
|
||||
self.rcs_paths = rcs_paths
|
||||
|
||||
|
||||
class BinCVSRepository(BaseCVSRepository):
|
||||
def _get_tip_revision(self, rcs_file, rev=None):
|
||||
"""Get the (basically) youngest revision (filtered by REV)."""
|
||||
args = rcs_file,
|
||||
fp = self.rcs_popen('rlog', args, 'rt', 0)
|
||||
filename, default_branch, tags, lockinfo, msg, eof = _parse_log_header(fp)
|
||||
filename, default_branch, tags, msg, eof = _parse_log_header(fp)
|
||||
revs = []
|
||||
while not eof:
|
||||
revision, eof = _parse_log_entry(fp)
|
||||
if revision:
|
||||
revs.append(revision)
|
||||
revs = _file_log(revs, tags, lockinfo, default_branch, rev)
|
||||
revs = _file_log(revs, tags, default_branch, rev)
|
||||
if revs:
|
||||
return revs[-1]
|
||||
return None
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
if not rev or rev == 'HEAD' or rev == 'MAIN':
|
||||
rev_flag = '-p'
|
||||
else:
|
||||
@@ -235,16 +189,11 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
cvs_tags, cvs_branches
|
||||
lists of tag and branch names encountered in the directory
|
||||
"""
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a directory."
|
||||
% (string.join(path_parts, "/")))
|
||||
|
||||
subdirs = options.get('cvs_subdirs', 0)
|
||||
entries_to_fetch = []
|
||||
for entry in entries:
|
||||
if vclib.check_path_access(self, path_parts + [entry.name], None, rev):
|
||||
entries_to_fetch.append(entry)
|
||||
alltags = _get_logs(self, path_parts, entries_to_fetch, rev, subdirs)
|
||||
|
||||
dirpath = self._getpath(path_parts)
|
||||
alltags = _get_logs(self, dirpath, entries, rev, subdirs)
|
||||
|
||||
branches = options['cvs_branches'] = []
|
||||
tags = options['cvs_tags'] = []
|
||||
for name, rev in alltags.items():
|
||||
@@ -253,7 +202,7 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
else:
|
||||
tags.append(name)
|
||||
|
||||
def itemlog(self, path_parts, rev, sortby, first, limit, options):
|
||||
def itemlog(self, path_parts, rev, options):
|
||||
"""see vclib.Repository.itemlog docstring
|
||||
|
||||
rev parameter can be a revision number, a branch number, a tag name,
|
||||
@@ -273,10 +222,6 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
dictionary of Tag objects for all tags encountered
|
||||
"""
|
||||
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
|
||||
# Invoke rlog
|
||||
rcsfile = self.rcsfile(path_parts, 1)
|
||||
if rev and options.get('cvs_pass_rev', 0):
|
||||
@@ -285,7 +230,7 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
args = rcsfile,
|
||||
|
||||
fp = self.rcs_popen('rlog', args, 'rt', 0)
|
||||
filename, default_branch, tags, lockinfo, msg, eof = _parse_log_header(fp)
|
||||
filename, default_branch, tags, msg, eof = _parse_log_header(fp)
|
||||
|
||||
# Retrieve revision objects
|
||||
revs = []
|
||||
@@ -294,42 +239,26 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
if revision:
|
||||
revs.append(revision)
|
||||
|
||||
filtered_revs = _file_log(revs, tags, lockinfo, default_branch, rev)
|
||||
filtered_revs = _file_log(revs, tags, default_branch, rev)
|
||||
|
||||
options['cvs_tags'] = tags
|
||||
if sortby == vclib.SORTBY_DATE:
|
||||
filtered_revs.sort(_logsort_date_cmp)
|
||||
elif sortby == vclib.SORTBY_REV:
|
||||
filtered_revs.sort(_logsort_rev_cmp)
|
||||
|
||||
if len(filtered_revs) < first:
|
||||
return []
|
||||
if limit:
|
||||
return filtered_revs[first:first+limit]
|
||||
return filtered_revs
|
||||
|
||||
def rcs_popen(self, rcs_cmd, rcs_args, mode, capture_err=1):
|
||||
if self.utilities.cvsnt:
|
||||
cmd = self.utilities.cvsnt
|
||||
if self.rcs_paths.cvsnt_exe_path:
|
||||
cmd = self.rcs_paths.cvsnt_exe_path
|
||||
args = ['rcsfile', rcs_cmd]
|
||||
args.extend(list(rcs_args))
|
||||
else:
|
||||
cmd = os.path.join(self.utilities.rcs_dir, rcs_cmd)
|
||||
cmd = os.path.join(self.rcs_paths.rcs_path, rcs_cmd)
|
||||
args = rcs_args
|
||||
return popen.popen(cmd, args, mode, capture_err)
|
||||
|
||||
def annotate(self, path_parts, rev=None):
|
||||
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)
|
||||
return source, source.revision
|
||||
|
||||
def revinfo(self, rev):
|
||||
raise vclib.UnsupportedFeature
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
"""see vclib.Repository.rawdiff docstring
|
||||
|
||||
@@ -337,13 +266,6 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
|
||||
ignore_keyword_subst - boolean, ignore keyword substitution
|
||||
"""
|
||||
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')
|
||||
@@ -364,16 +286,15 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
|
||||
|
||||
class CVSDirEntry(vclib.DirEntry):
|
||||
def __init__(self, name, kind, errors, in_attic, absent=0):
|
||||
def __init__(self, name, kind, errors, in_attic):
|
||||
vclib.DirEntry.__init__(self, name, kind, errors)
|
||||
self.in_attic = in_attic
|
||||
self.absent = absent # meaning, no revisions found on requested tag
|
||||
|
||||
class Revision(vclib.Revision):
|
||||
def __init__(self, revstr, date=None, author=None, dead=None,
|
||||
changed=None, log=None):
|
||||
vclib.Revision.__init__(self, _revision_tuple(revstr), revstr,
|
||||
date, author, changed, log, None, None)
|
||||
date, author, changed, log, None)
|
||||
self.dead = dead
|
||||
|
||||
class Tag:
|
||||
@@ -386,14 +307,6 @@ class Tag:
|
||||
# ======================================================================
|
||||
# Functions for dealing with Revision and Tag objects
|
||||
|
||||
def _logsort_date_cmp(rev1, rev2):
|
||||
# sort on date; secondary on revision number
|
||||
return -cmp(rev1.date, rev2.date) or -cmp(rev1.number, rev2.number)
|
||||
|
||||
def _logsort_rev_cmp(rev1, rev2):
|
||||
# sort highest revision first
|
||||
return -cmp(rev1.number, rev2.number)
|
||||
|
||||
def _match_revs_tags(revlist, taglist):
|
||||
"""Match up a list of Revision objects with a list of Tag objects
|
||||
|
||||
@@ -621,25 +534,31 @@ def _parse_co_header(fp):
|
||||
raise COMalformedOutput, "Unable to find filename in co output stream"
|
||||
filename = match.group(1)
|
||||
|
||||
# look through subsequent lines for a revision. we might encounter
|
||||
# some ignorable or problematic lines along the way.
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
# look for a revision.
|
||||
match = _re_co_revision.match(line)
|
||||
if match:
|
||||
return filename, match.group(1)
|
||||
elif _re_co_missing_rev.match(line) or _re_co_side_branches.match(line):
|
||||
raise COMissingRevision, "Got missing revision error from co output stream"
|
||||
elif _re_co_warning.match(line):
|
||||
pass
|
||||
else:
|
||||
break
|
||||
# look for a revision in the second line.
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
raise COMalformedOutput, "Missing second line from co output stream"
|
||||
match = _re_co_revision.match(line)
|
||||
if match:
|
||||
return filename, match.group(1)
|
||||
elif _re_co_missing_rev.match(line) or _re_co_side_branches.match(line):
|
||||
raise COMissingRevision, "Got missing revision error from co output stream"
|
||||
elif _re_co_warning.match(line):
|
||||
pass
|
||||
else:
|
||||
raise COMalformedOutput, "Unable to find revision in co output stream"
|
||||
|
||||
# if we get here, the second line wasn't a revision, but it was a
|
||||
# warning we can ignore. look for a revision in the third line.
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
raise COMalformedOutput, "Missing third line from co output stream"
|
||||
match = _re_co_revision.match(line)
|
||||
if match:
|
||||
return filename, match.group(1)
|
||||
raise COMalformedOutput, "Unable to find revision in co output stream"
|
||||
|
||||
|
||||
# if your rlog doesn't use 77 '=' characters, then this must change
|
||||
LOG_END_MARKER = '=' * 77 + '\n'
|
||||
ENTRY_END_MARKER = '-' * 28 + '\n'
|
||||
@@ -684,14 +603,13 @@ def _parse_log_header(fp):
|
||||
If there is no revision information (e.g. the "-h" switch was passed to
|
||||
rlog), then fp will consumed the file separator line on exit.
|
||||
|
||||
Returns: filename, default branch, tag dictionary, lock dictionary,
|
||||
rlog error message, and eof flag
|
||||
Returns: filename, default branch, tag dictionary, rlog error message,
|
||||
and eof flag
|
||||
"""
|
||||
|
||||
filename = head = branch = msg = ""
|
||||
taginfo = { } # tag name => number
|
||||
lockinfo = { } # revision => locker
|
||||
state = 0 # 0 = base, 1 = parsing symbols, 2 = parsing locks
|
||||
taginfo = { } # tag name => number
|
||||
|
||||
parsing_tags = 0
|
||||
eof = None
|
||||
|
||||
while 1:
|
||||
@@ -701,35 +619,24 @@ def _parse_log_header(fp):
|
||||
eof = _EOF_LOG
|
||||
break
|
||||
|
||||
if state == 1:
|
||||
if parsing_tags:
|
||||
if line[0] == '\t':
|
||||
[ tag, rev ] = map(string.strip, string.split(line, ':'))
|
||||
taginfo[tag] = rev
|
||||
else:
|
||||
# oops. this line isn't tag info. stop parsing tags.
|
||||
state = 0
|
||||
parsing_tags = 0
|
||||
|
||||
if state == 2:
|
||||
if line[0] == '\t':
|
||||
[ locker, rev ] = map(string.strip, string.split(line, ':'))
|
||||
lockinfo[rev] = locker
|
||||
else:
|
||||
# oops. this line isn't lock info. stop parsing tags.
|
||||
state = 0
|
||||
|
||||
if state == 0:
|
||||
if not parsing_tags:
|
||||
if line[:9] == 'RCS file:':
|
||||
filename = line[10:-1]
|
||||
elif line[:5] == 'head:':
|
||||
head = line[6:-1]
|
||||
elif line[:7] == 'branch:':
|
||||
branch = line[8:-1]
|
||||
elif line[:6] == 'locks:':
|
||||
# start parsing the lock information
|
||||
state = 2
|
||||
elif line[:14] == 'symbolic names':
|
||||
# start parsing the tag information
|
||||
state = 1
|
||||
parsing_tags = 1
|
||||
elif line == ENTRY_END_MARKER:
|
||||
# end of the headers
|
||||
break
|
||||
@@ -758,7 +665,7 @@ def _parse_log_header(fp):
|
||||
eof = _EOF_ERROR
|
||||
break
|
||||
|
||||
return filename, branch, taginfo, lockinfo, msg, eof
|
||||
return filename, branch, taginfo, msg, eof
|
||||
|
||||
_re_log_info = re.compile(r'^date:\s+([^;]+);'
|
||||
r'\s+author:\s+([^;]+);'
|
||||
@@ -858,7 +765,7 @@ def _paths_eq(path1, path2):
|
||||
# ======================================================================
|
||||
# Functions for interpreting and manipulating log information
|
||||
|
||||
def _file_log(revs, taginfo, lockinfo, cur_branch, filter):
|
||||
def _file_log(revs, taginfo, cur_branch, filter):
|
||||
"""Augment list of Revisions and a dictionary of Tags"""
|
||||
|
||||
# Add artificial ViewVC tag MAIN. If the file has a default branch, then
|
||||
@@ -889,10 +796,6 @@ def _file_log(revs, taginfo, lockinfo, cur_branch, filter):
|
||||
# Match up tags and revisions
|
||||
_match_revs_tags(revs, tags)
|
||||
|
||||
# Match up lockinfo and revision
|
||||
for rev in revs:
|
||||
rev.lockinfo = lockinfo.get(rev.string)
|
||||
|
||||
# Add artificial ViewVC tag HEAD, which acts like a non-branch tag pointing
|
||||
# at the latest revision on the MAIN branch. The HEAD revision doesn't have
|
||||
# anything to do with the "head" revision number specified in the RCS file
|
||||
@@ -939,7 +842,7 @@ def _file_log(revs, taginfo, lockinfo, cur_branch, filter):
|
||||
|
||||
return filtered_revs
|
||||
|
||||
def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
|
||||
def _get_logs(repos, dirpath, entries, view_tag, get_dirs):
|
||||
alltags = { # all the tags seen in the files of this dir
|
||||
'MAIN' : '',
|
||||
'HEAD' : '1.1'
|
||||
@@ -954,15 +857,14 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
|
||||
|
||||
while len(chunk) < max_args and entries_idx < entries_len:
|
||||
entry = entries[entries_idx]
|
||||
path = _log_path(entry, repos._getpath(dir_path_parts), get_dirs)
|
||||
path = _log_path(entry, dirpath, get_dirs)
|
||||
if path:
|
||||
entry.path = path
|
||||
entry.idx = entries_idx
|
||||
chunk.append(entry)
|
||||
|
||||
# set properties even if we don't retrieve logs
|
||||
entry.rev = entry.date = entry.author = None
|
||||
entry.dead = entry.log = entry.lockinfo = None
|
||||
entry.rev = entry.date = entry.author = entry.dead = entry.log = None
|
||||
|
||||
entries_idx = entries_idx + 1
|
||||
|
||||
@@ -982,8 +884,7 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
|
||||
chunk_idx = 0
|
||||
while chunk_idx < len(chunk):
|
||||
file = chunk[chunk_idx]
|
||||
filename, default_branch, taginfo, lockinfo, msg, eof \
|
||||
= _parse_log_header(rlog)
|
||||
filename, default_branch, taginfo, msg, eof = _parse_log_header(rlog)
|
||||
|
||||
if eof == _EOF_LOG:
|
||||
# the rlog output ended early. this can happen on errors that rlog
|
||||
@@ -1071,16 +972,13 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
|
||||
file.date = wanted_entry.date
|
||||
file.author = wanted_entry.author
|
||||
file.dead = file.kind == vclib.FILE and wanted_entry.dead
|
||||
file.absent = 0
|
||||
file.log = wanted_entry.log
|
||||
file.lockinfo = lockinfo.get(file.rev)
|
||||
# suppress rlog errors if we find a usable revision in the end
|
||||
del file.errors[:]
|
||||
elif file.kind == vclib.FILE:
|
||||
file.dead = 0
|
||||
#file.errors.append("No revisions exist on %s" % (view_tag or "MAIN"))
|
||||
file.absent = 1
|
||||
|
||||
file.dead = 1
|
||||
file.errors.append("No revisions exist on %s" % (view_tag or "MAIN"))
|
||||
|
||||
# done with this file now, skip the rest of this file's revisions
|
||||
if not eof:
|
||||
_skip_file(rlog)
|
||||
@@ -1192,8 +1090,6 @@ def _newest_file(dirpath):
|
||||
newest_file = None
|
||||
newest_time = 0
|
||||
|
||||
### FIXME: This sucker is leaking unauthorized paths! ###
|
||||
|
||||
for subfile in os.listdir(dirpath):
|
||||
### filter CVS locks? stale NFS handles?
|
||||
if subfile[-2:] != ',v':
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -9,35 +9,343 @@
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
This is a Version Control library driver for locally accessible cvs-repositories.
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import string
|
||||
import re
|
||||
import cStringIO
|
||||
import tempfile
|
||||
|
||||
import vclib
|
||||
import rcsparse
|
||||
import blame
|
||||
|
||||
### The functionality shared with bincvs should probably be moved to a
|
||||
### separate module
|
||||
from vclib.bincvs import CVSRepository, Revision, Tag, \
|
||||
_file_log, _log_path
|
||||
|
||||
class CCVSRepository(CVSRepository):
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
"""see vclib.Repository.dirlogs docstring
|
||||
|
||||
rev can be a tag name or None. if set only information from revisions
|
||||
matching the tag will be retrieved
|
||||
|
||||
Option values recognized by this implementation:
|
||||
|
||||
cvs_subdirs
|
||||
boolean. true to fetch logs of the most recently modified file in each
|
||||
subdirectory
|
||||
|
||||
Option values returned by this implementation:
|
||||
|
||||
cvs_tags, cvs_branches
|
||||
lists of tag and branch names encountered in the directory
|
||||
"""
|
||||
subdirs = options.get('cvs_subdirs', 0)
|
||||
|
||||
dirpath = self._getpath(path_parts)
|
||||
alltags = { # all the tags seen in the files of this dir
|
||||
'MAIN' : '',
|
||||
'HEAD' : '1.1'
|
||||
}
|
||||
|
||||
for entry in entries:
|
||||
entry.rev = entry.date = entry.author = entry.dead = entry.log = None
|
||||
path = _log_path(entry, dirpath, subdirs)
|
||||
if path:
|
||||
entry.path = path
|
||||
try:
|
||||
rcsparse.Parser().parse(open(path, 'rb'), InfoSink(entry, rev, alltags))
|
||||
except IOError, e:
|
||||
entry.errors.append("rcsparse error: %s" % e)
|
||||
except RuntimeError, e:
|
||||
entry.errors.append("rcsparse error: %s" % e)
|
||||
except rcsparse.RCSStopParser:
|
||||
pass
|
||||
|
||||
branches = options['cvs_branches'] = []
|
||||
tags = options['cvs_tags'] = []
|
||||
for name, rev in alltags.items():
|
||||
if Tag(None, rev).is_branch:
|
||||
branches.append(name)
|
||||
else:
|
||||
tags.append(name)
|
||||
|
||||
def itemlog(self, path_parts, rev, options):
|
||||
"""see vclib.Repository.itemlog docstring
|
||||
|
||||
rev parameter can be a revision number, a branch number, a tag name,
|
||||
or None. If None, will return information about all revisions, otherwise,
|
||||
will only return information about the specified revision or branch.
|
||||
|
||||
Option values returned by this implementation:
|
||||
|
||||
cvs_tags
|
||||
dictionary of Tag objects for all tags encountered
|
||||
"""
|
||||
path = self.rcsfile(path_parts, 1)
|
||||
sink = TreeSink()
|
||||
rcsparse.Parser().parse(open(path, 'rb'), sink)
|
||||
filtered_revs = _file_log(sink.revs.values(), sink.tags,
|
||||
sink.default_branch, rev)
|
||||
for rev in filtered_revs:
|
||||
if rev.prev and len(rev.number) == 2:
|
||||
rev.changed = rev.prev.next_changed
|
||||
options['cvs_tags'] = sink.tags
|
||||
|
||||
return filtered_revs
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
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())
|
||||
|
||||
r1 = self.itemlog(path_parts1, rev1, {})[-1]
|
||||
r2 = self.itemlog(path_parts2, rev2, {})[-1]
|
||||
|
||||
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, diff_args)
|
||||
|
||||
def annotate(self, path_parts, rev=None):
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
|
||||
return source, source.revision
|
||||
|
||||
def openfile(self, path_parts, rev=None):
|
||||
path = self.rcsfile(path_parts, 1)
|
||||
sink = COSink(rev)
|
||||
rcsparse.Parser().parse(open(path, 'rb'), sink)
|
||||
revision = sink.last and sink.last.string
|
||||
return cStringIO.StringIO(string.join(sink.sstext.text, "\n")), revision
|
||||
|
||||
class MatchingSink(rcsparse.Sink):
|
||||
"""Superclass for sinks that search for revisions based on tag or number"""
|
||||
|
||||
def __init__(self, find):
|
||||
"""Initialize with tag name or revision number string to match against"""
|
||||
if not find or find == 'MAIN' or find == 'HEAD':
|
||||
self.find = None
|
||||
else:
|
||||
self.find = find
|
||||
|
||||
self.find_tag = None
|
||||
|
||||
def set_principal_branch(self, branch_number):
|
||||
if self.find is None:
|
||||
self.find_tag = Tag(None, branch_number)
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
if name == self.find:
|
||||
self.find_tag = Tag(None, revision)
|
||||
|
||||
def admin_completed(self):
|
||||
if self.find_tag is None:
|
||||
if self.find is None:
|
||||
self.find_tag = Tag(None, '')
|
||||
else:
|
||||
try:
|
||||
self.find_tag = Tag(None, self.find)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
class InfoSink(MatchingSink):
|
||||
def __init__(self, entry, tag, alltags):
|
||||
MatchingSink.__init__(self, tag)
|
||||
self.entry = entry
|
||||
self.alltags = alltags
|
||||
self.matching_rev = None
|
||||
self.perfect_match = 0
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
MatchingSink.define_tag(self, name, revision)
|
||||
self.alltags[name] = revision
|
||||
|
||||
def admin_completed(self):
|
||||
MatchingSink.admin_completed(self)
|
||||
if self.find_tag is None:
|
||||
# tag we're looking for doesn't exist
|
||||
raise rcsparse.RCSStopParser
|
||||
|
||||
def define_revision(self, revision, date, author, state, branches, next):
|
||||
if self.perfect_match:
|
||||
return
|
||||
|
||||
tag = self.find_tag
|
||||
rev = Revision(revision, date, author, state == "dead")
|
||||
|
||||
# perfect match if revision number matches tag number or if revision is on
|
||||
# trunk and tag points to trunk. imperfect match if tag refers to a branch
|
||||
# and this revision is the highest revision so far found on that branch
|
||||
perfect = ((rev.number == tag.number) or
|
||||
(not tag.number and len(rev.number) == 2))
|
||||
if perfect or (tag.is_branch and tag.number == rev.number[:-1] and
|
||||
(not self.matching_rev or
|
||||
rev.number > self.matching_rev.number)):
|
||||
self.matching_rev = rev
|
||||
self.perfect_match = perfect
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
if self.matching_rev:
|
||||
if revision == self.matching_rev.string:
|
||||
self.entry.rev = self.matching_rev.string
|
||||
self.entry.date = self.matching_rev.date
|
||||
self.entry.author = self.matching_rev.author
|
||||
self.entry.dead = self.matching_rev.dead
|
||||
self.entry.log = log
|
||||
raise rcsparse.RCSStopParser
|
||||
else:
|
||||
raise rcsparse.RCSStopParser
|
||||
|
||||
class TreeSink(rcsparse.Sink):
|
||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
||||
|
||||
def __init__(self):
|
||||
self.revs = { }
|
||||
self.tags = { }
|
||||
self.head = None
|
||||
self.default_branch = None
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.head = revision
|
||||
|
||||
def set_principal_branch(self, branch_number):
|
||||
self.default_branch = branch_number
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
# check !tags.has_key(tag_name)
|
||||
self.tags[name] = revision
|
||||
|
||||
def define_revision(self, revision, date, author, state, branches, next):
|
||||
# check !revs.has_key(revision)
|
||||
self.revs[revision] = Revision(revision, date, author, state == "dead")
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
# check revs.has_key(revision)
|
||||
rev = self.revs[revision]
|
||||
rev.log = log
|
||||
|
||||
changed = None
|
||||
added = 0
|
||||
deled = 0
|
||||
if self.head != revision:
|
||||
changed = 1
|
||||
lines = string.split(text, '\n')
|
||||
idx = 0
|
||||
while idx < len(lines):
|
||||
command = lines[idx]
|
||||
dmatch = self.d_command.match(command)
|
||||
idx = idx + 1
|
||||
if dmatch:
|
||||
deled = deled + string.atoi(dmatch.group(2))
|
||||
else:
|
||||
amatch = self.a_command.match(command)
|
||||
if amatch:
|
||||
count = string.atoi(amatch.group(2))
|
||||
added = added + count
|
||||
idx = idx + count
|
||||
elif command:
|
||||
raise "error while parsing deltatext: %s" % command
|
||||
|
||||
if len(rev.number) == 2:
|
||||
rev.next_changed = changed and "+%i -%i" % (deled, added)
|
||||
else:
|
||||
rev.changed = changed and "+%i -%i" % (added, deled)
|
||||
|
||||
class StreamText:
|
||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = string.split(text, "\n")
|
||||
|
||||
def command(self, cmd):
|
||||
adjust = 0
|
||||
add_lines_remaining = 0
|
||||
diffs = string.split(cmd, "\n")
|
||||
if diffs[-1] == "":
|
||||
del diffs[-1]
|
||||
if len(diffs) == 0:
|
||||
return
|
||||
if diffs[0] == "":
|
||||
del diffs[0]
|
||||
for command in diffs:
|
||||
if add_lines_remaining > 0:
|
||||
# Insertion lines from a prior "a" command
|
||||
self.text.insert(start_line + adjust, command)
|
||||
add_lines_remaining = add_lines_remaining - 1
|
||||
adjust = adjust + 1
|
||||
continue
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if dmatch:
|
||||
# "d" - Delete command
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
begin = start_line + adjust - 1
|
||||
del self.text[begin:begin + count]
|
||||
adjust = adjust - count
|
||||
elif amatch:
|
||||
# "a" - Add command
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
add_lines_remaining = count
|
||||
else:
|
||||
raise RuntimeError, 'Error parsing diff commands'
|
||||
|
||||
def secondnextdot(s, start):
|
||||
# find the position the second dot after the start index.
|
||||
return string.find(s, '.', string.find(s, '.', start) + 1)
|
||||
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
return os.path.normpath(rootpath)
|
||||
class COSink(MatchingSink):
|
||||
def __init__(self, rev):
|
||||
MatchingSink.__init__(self, rev)
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.head = Revision(revision)
|
||||
self.last = None
|
||||
self.sstext = None
|
||||
|
||||
def expand_root_parent(parent_path):
|
||||
# Each subdirectory of PARENT_PATH that contains a child
|
||||
# "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.
|
||||
roots = {}
|
||||
subpaths = os.listdir(parent_path)
|
||||
cvsroot = os.path.exists(os.path.join(parent_path, "CVSROOT", "config"))
|
||||
for rootname in subpaths:
|
||||
rootpath = os.path.join(parent_path, rootname)
|
||||
if cvsroot \
|
||||
or (os.path.exists(os.path.join(rootpath, "CVSROOT", "config"))):
|
||||
roots[rootname] = canonicalize_rootpath(rootpath)
|
||||
return roots
|
||||
def admin_completed(self):
|
||||
MatchingSink.admin_completed(self)
|
||||
if self.find_tag is None:
|
||||
raise vclib.InvalidRevision(self.find)
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
tag = self.find_tag
|
||||
rev = Revision(revision)
|
||||
|
||||
def CVSRepository(name, rootpath, authorizer, utilities, use_rcsparse):
|
||||
rootpath = canonicalize_rootpath(rootpath)
|
||||
if use_rcsparse:
|
||||
import ccvs
|
||||
return ccvs.CCVSRepository(name, rootpath, authorizer, utilities)
|
||||
else:
|
||||
import bincvs
|
||||
return bincvs.BinCVSRepository(name, rootpath, authorizer, utilities)
|
||||
if rev.number == tag.number:
|
||||
self.log = log
|
||||
|
||||
depth = len(rev.number)
|
||||
|
||||
if rev.number == self.head.number:
|
||||
assert self.sstext is None
|
||||
self.sstext = StreamText(text)
|
||||
elif (depth == 2 and tag.number and rev.number >= tag.number[:depth]):
|
||||
assert len(self.last.number) == 2
|
||||
assert rev.number < self.last.number
|
||||
self.sstext.command(text)
|
||||
elif (depth > 2 and rev.number[:depth-1] == tag.number[:depth-1] and
|
||||
(rev.number <= tag.number or len(tag.number) == depth-1)):
|
||||
assert len(rev.number) - len(self.last.number) in (0, 2)
|
||||
assert rev.number > self.last.number
|
||||
self.sstext.command(text)
|
||||
else:
|
||||
rev = None
|
||||
|
||||
if rev:
|
||||
#print "tag =", tag.number, "rev =", rev.number, "<br>"
|
||||
self.last = rev
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 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
|
||||
@@ -31,7 +31,6 @@ import re
|
||||
import time
|
||||
import math
|
||||
import rcsparse
|
||||
import vclib
|
||||
|
||||
class CVSParser(rcsparse.Sink):
|
||||
# Precompiled regular expressions
|
||||
@@ -268,7 +267,7 @@ class CVSParser(rcsparse.Sink):
|
||||
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
|
||||
'but the RCS file is inaccessible.') % rcs_pathname
|
||||
|
||||
rcsparse.parse(rcsfile, self)
|
||||
rcsparse.Parser().parse(rcsfile, self)
|
||||
rcsfile.close()
|
||||
|
||||
if opt_rev in [None, '', 'HEAD']:
|
||||
@@ -347,7 +346,7 @@ class CVSParser(rcsparse.Sink):
|
||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
||||
|
||||
if is_trunk_revision:
|
||||
diffs = self.deltatext_split(last_revision)
|
||||
diffs = self.deltatext_split(last_revision)
|
||||
|
||||
# Revisions on the trunk specify deltas that transform a
|
||||
# revision into an earlier revision, so invert the translation
|
||||
@@ -380,7 +379,7 @@ class CVSParser(rcsparse.Sink):
|
||||
# the trunk. They specify deltas that transform a revision
|
||||
# into a later revision.
|
||||
adjust = 0
|
||||
diffs = self.deltatext_split(revision)
|
||||
diffs = self.deltatext_split(revision)
|
||||
for command in diffs:
|
||||
if skip > 0:
|
||||
skip = skip - 1
|
||||
@@ -448,7 +447,8 @@ class BlameSource:
|
||||
author = self.parser.revision_author[rev]
|
||||
thisline = self.lines[idx]
|
||||
### TODO: Put a real date in here.
|
||||
item = vclib.Annotation(thisline, line_number, rev, prev_rev, author, None)
|
||||
item = _item(text=thisline, line_number=line_number, rev=rev,
|
||||
prev_rev=prev_rev, author=author, date=None)
|
||||
self.last = item
|
||||
self.idx = idx
|
||||
return item
|
||||
@@ -456,3 +456,9 @@ class BlameSource:
|
||||
|
||||
class BlameSequencingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _item:
|
||||
def __init__(self, **kw):
|
||||
vars(self).update(kw)
|
||||
|
||||
|
@@ -1,398 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import string
|
||||
import re
|
||||
import cStringIO
|
||||
import tempfile
|
||||
|
||||
import vclib
|
||||
import rcsparse
|
||||
import blame
|
||||
|
||||
### The functionality shared with bincvs should probably be moved to a
|
||||
### separate module
|
||||
from bincvs import BaseCVSRepository, Revision, Tag, _file_log, _log_path, _logsort_date_cmp, _logsort_rev_cmp
|
||||
|
||||
class CCVSRepository(BaseCVSRepository):
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
"""see vclib.Repository.dirlogs docstring
|
||||
|
||||
rev can be a tag name or None. if set only information from revisions
|
||||
matching the tag will be retrieved
|
||||
|
||||
Option values recognized by this implementation:
|
||||
|
||||
cvs_subdirs
|
||||
boolean. true to fetch logs of the most recently modified file in each
|
||||
subdirectory
|
||||
|
||||
Option values returned by this implementation:
|
||||
|
||||
cvs_tags, cvs_branches
|
||||
lists of tag and branch names encountered in the directory
|
||||
"""
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a directory."
|
||||
% (string.join(path_parts, "/")))
|
||||
entries_to_fetch = []
|
||||
for entry in entries:
|
||||
if vclib.check_path_access(self, path_parts + [entry.name], None, rev):
|
||||
entries_to_fetch.append(entry)
|
||||
|
||||
subdirs = options.get('cvs_subdirs', 0)
|
||||
|
||||
dirpath = self._getpath(path_parts)
|
||||
alltags = { # all the tags seen in the files of this dir
|
||||
'MAIN' : '',
|
||||
'HEAD' : '1.1'
|
||||
}
|
||||
|
||||
for entry in entries_to_fetch:
|
||||
entry.rev = entry.date = entry.author = None
|
||||
entry.dead = entry.absent = entry.log = entry.lockinfo = None
|
||||
path = _log_path(entry, dirpath, subdirs)
|
||||
if path:
|
||||
entry.path = path
|
||||
try:
|
||||
rcsparse.parse(open(path, 'rb'), InfoSink(entry, rev, alltags))
|
||||
except IOError, e:
|
||||
entry.errors.append("rcsparse error: %s" % e)
|
||||
except RuntimeError, e:
|
||||
entry.errors.append("rcsparse error: %s" % e)
|
||||
except rcsparse.RCSStopParser:
|
||||
pass
|
||||
|
||||
branches = options['cvs_branches'] = []
|
||||
tags = options['cvs_tags'] = []
|
||||
for name, rev in alltags.items():
|
||||
if Tag(None, rev).is_branch:
|
||||
branches.append(name)
|
||||
else:
|
||||
tags.append(name)
|
||||
|
||||
def itemlog(self, path_parts, rev, sortby, first, limit, options):
|
||||
"""see vclib.Repository.itemlog docstring
|
||||
|
||||
rev parameter can be a revision number, a branch number, a tag name,
|
||||
or None. If None, will return information about all revisions, otherwise,
|
||||
will only return information about the specified revision or branch.
|
||||
|
||||
Option values returned by this implementation:
|
||||
|
||||
cvs_tags
|
||||
dictionary of Tag objects for all tags encountered
|
||||
"""
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
|
||||
path = self.rcsfile(path_parts, 1)
|
||||
sink = TreeSink()
|
||||
rcsparse.parse(open(path, 'rb'), sink)
|
||||
filtered_revs = _file_log(sink.revs.values(), sink.tags, sink.lockinfo,
|
||||
sink.default_branch, rev)
|
||||
for rev in filtered_revs:
|
||||
if rev.prev and len(rev.number) == 2:
|
||||
rev.changed = rev.prev.next_changed
|
||||
options['cvs_tags'] = sink.tags
|
||||
|
||||
if sortby == vclib.SORTBY_DATE:
|
||||
filtered_revs.sort(_logsort_date_cmp)
|
||||
elif sortby == vclib.SORTBY_REV:
|
||||
filtered_revs.sort(_logsort_rev_cmp)
|
||||
|
||||
if len(filtered_revs) < first:
|
||||
return []
|
||||
if limit:
|
||||
return filtered_revs[first:first+limit]
|
||||
return filtered_revs
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
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, "/")))
|
||||
|
||||
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())
|
||||
|
||||
r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
|
||||
r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
|
||||
|
||||
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):
|
||||
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)
|
||||
return source, source.revision
|
||||
|
||||
def revinfo(self, rev):
|
||||
raise vclib.UnsupportedFeature
|
||||
|
||||
def openfile(self, path_parts, rev=None):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
path = self.rcsfile(path_parts, 1)
|
||||
sink = COSink(rev)
|
||||
rcsparse.parse(open(path, 'rb'), sink)
|
||||
revision = sink.last and sink.last.string
|
||||
return cStringIO.StringIO(string.join(sink.sstext.text, "\n")), revision
|
||||
|
||||
class MatchingSink(rcsparse.Sink):
|
||||
"""Superclass for sinks that search for revisions based on tag or number"""
|
||||
|
||||
def __init__(self, find):
|
||||
"""Initialize with tag name or revision number string to match against"""
|
||||
if not find or find == 'MAIN' or find == 'HEAD':
|
||||
self.find = None
|
||||
else:
|
||||
self.find = find
|
||||
|
||||
self.find_tag = None
|
||||
|
||||
def set_principal_branch(self, branch_number):
|
||||
if self.find is None:
|
||||
self.find_tag = Tag(None, branch_number)
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
if name == self.find:
|
||||
self.find_tag = Tag(None, revision)
|
||||
|
||||
def admin_completed(self):
|
||||
if self.find_tag is None:
|
||||
if self.find is None:
|
||||
self.find_tag = Tag(None, '')
|
||||
else:
|
||||
try:
|
||||
self.find_tag = Tag(None, self.find)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
class InfoSink(MatchingSink):
|
||||
def __init__(self, entry, tag, alltags):
|
||||
MatchingSink.__init__(self, tag)
|
||||
self.entry = entry
|
||||
self.alltags = alltags
|
||||
self.matching_rev = None
|
||||
self.perfect_match = 0
|
||||
self.lockinfo = { }
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
MatchingSink.define_tag(self, name, revision)
|
||||
self.alltags[name] = revision
|
||||
|
||||
def admin_completed(self):
|
||||
MatchingSink.admin_completed(self)
|
||||
if self.find_tag is None:
|
||||
# tag we're looking for doesn't exist
|
||||
if self.entry.kind == vclib.FILE:
|
||||
self.entry.absent = 1
|
||||
raise rcsparse.RCSStopParser
|
||||
|
||||
def set_locker(self, rev, locker):
|
||||
self.lockinfo[rev] = locker
|
||||
|
||||
def define_revision(self, revision, date, author, state, branches, next):
|
||||
if self.perfect_match:
|
||||
return
|
||||
|
||||
tag = self.find_tag
|
||||
rev = Revision(revision, date, author, state == "dead")
|
||||
rev.lockinfo = self.lockinfo.get(revision)
|
||||
|
||||
# perfect match if revision number matches tag number or if revision is on
|
||||
# trunk and tag points to trunk. imperfect match if tag refers to a branch
|
||||
# and this revision is the highest revision so far found on that branch
|
||||
perfect = ((rev.number == tag.number) or
|
||||
(not tag.number and len(rev.number) == 2))
|
||||
if perfect or (tag.is_branch and tag.number == rev.number[:-1] and
|
||||
(not self.matching_rev or
|
||||
rev.number > self.matching_rev.number)):
|
||||
self.matching_rev = rev
|
||||
self.perfect_match = perfect
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
if self.matching_rev:
|
||||
if revision == self.matching_rev.string:
|
||||
self.entry.rev = self.matching_rev.string
|
||||
self.entry.date = self.matching_rev.date
|
||||
self.entry.author = self.matching_rev.author
|
||||
self.entry.dead = self.matching_rev.dead
|
||||
self.entry.lockinfo = self.matching_rev.lockinfo
|
||||
self.entry.absent = 0
|
||||
self.entry.log = log
|
||||
raise rcsparse.RCSStopParser
|
||||
else:
|
||||
raise rcsparse.RCSStopParser
|
||||
|
||||
class TreeSink(rcsparse.Sink):
|
||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
||||
|
||||
def __init__(self):
|
||||
self.revs = { }
|
||||
self.tags = { }
|
||||
self.head = None
|
||||
self.default_branch = None
|
||||
self.lockinfo = { }
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.head = revision
|
||||
|
||||
def set_principal_branch(self, branch_number):
|
||||
self.default_branch = branch_number
|
||||
|
||||
def set_locker(self, rev, locker):
|
||||
self.lockinfo[rev] = locker
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
# check !tags.has_key(tag_name)
|
||||
self.tags[name] = revision
|
||||
|
||||
def define_revision(self, revision, date, author, state, branches, next):
|
||||
# check !revs.has_key(revision)
|
||||
self.revs[revision] = Revision(revision, date, author, state == "dead")
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
# check revs.has_key(revision)
|
||||
rev = self.revs[revision]
|
||||
rev.log = log
|
||||
|
||||
changed = None
|
||||
added = 0
|
||||
deled = 0
|
||||
if self.head != revision:
|
||||
changed = 1
|
||||
lines = string.split(text, '\n')
|
||||
idx = 0
|
||||
while idx < len(lines):
|
||||
command = lines[idx]
|
||||
dmatch = self.d_command.match(command)
|
||||
idx = idx + 1
|
||||
if dmatch:
|
||||
deled = deled + string.atoi(dmatch.group(2))
|
||||
else:
|
||||
amatch = self.a_command.match(command)
|
||||
if amatch:
|
||||
count = string.atoi(amatch.group(2))
|
||||
added = added + count
|
||||
idx = idx + count
|
||||
elif command:
|
||||
raise "error while parsing deltatext: %s" % command
|
||||
|
||||
if len(rev.number) == 2:
|
||||
rev.next_changed = changed and "+%i -%i" % (deled, added)
|
||||
else:
|
||||
rev.changed = changed and "+%i -%i" % (added, deled)
|
||||
|
||||
class StreamText:
|
||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = string.split(text, "\n")
|
||||
|
||||
def command(self, cmd):
|
||||
adjust = 0
|
||||
add_lines_remaining = 0
|
||||
diffs = string.split(cmd, "\n")
|
||||
if diffs[-1] == "":
|
||||
del diffs[-1]
|
||||
if len(diffs) == 0:
|
||||
return
|
||||
if diffs[0] == "":
|
||||
del diffs[0]
|
||||
for command in diffs:
|
||||
if add_lines_remaining > 0:
|
||||
# Insertion lines from a prior "a" command
|
||||
self.text.insert(start_line + adjust, command)
|
||||
add_lines_remaining = add_lines_remaining - 1
|
||||
adjust = adjust + 1
|
||||
continue
|
||||
dmatch = self.d_command.match(command)
|
||||
amatch = self.a_command.match(command)
|
||||
if dmatch:
|
||||
# "d" - Delete command
|
||||
start_line = string.atoi(dmatch.group(1))
|
||||
count = string.atoi(dmatch.group(2))
|
||||
begin = start_line + adjust - 1
|
||||
del self.text[begin:begin + count]
|
||||
adjust = adjust - count
|
||||
elif amatch:
|
||||
# "a" - Add command
|
||||
start_line = string.atoi(amatch.group(1))
|
||||
count = string.atoi(amatch.group(2))
|
||||
add_lines_remaining = count
|
||||
else:
|
||||
raise RuntimeError, 'Error parsing diff commands'
|
||||
|
||||
def secondnextdot(s, start):
|
||||
# find the position the second dot after the start index.
|
||||
return string.find(s, '.', string.find(s, '.', start) + 1)
|
||||
|
||||
|
||||
class COSink(MatchingSink):
|
||||
def __init__(self, rev):
|
||||
MatchingSink.__init__(self, rev)
|
||||
|
||||
def set_head_revision(self, revision):
|
||||
self.head = Revision(revision)
|
||||
self.last = None
|
||||
self.sstext = None
|
||||
|
||||
def admin_completed(self):
|
||||
MatchingSink.admin_completed(self)
|
||||
if self.find_tag is None:
|
||||
raise vclib.InvalidRevision(self.find)
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
tag = self.find_tag
|
||||
rev = Revision(revision)
|
||||
|
||||
if rev.number == tag.number:
|
||||
self.log = log
|
||||
|
||||
depth = len(rev.number)
|
||||
|
||||
if rev.number == self.head.number:
|
||||
assert self.sstext is None
|
||||
self.sstext = StreamText(text)
|
||||
elif (depth == 2 and tag.number and rev.number >= tag.number[:depth]):
|
||||
assert len(self.last.number) == 2
|
||||
assert rev.number < self.last.number
|
||||
self.sstext.command(text)
|
||||
elif (depth > 2 and rev.number[:depth-1] == tag.number[:depth-1] and
|
||||
(rev.number <= tag.number or len(tag.number) == depth-1)):
|
||||
assert len(rev.number) - len(self.last.number) in (0, 2)
|
||||
assert rev.number > self.last.number
|
||||
self.sstext.command(text)
|
||||
else:
|
||||
rev = None
|
||||
|
||||
if rev:
|
||||
#print "tag =", tag.number, "rev =", rev.number, "<br>"
|
||||
self.last = rev
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -12,55 +12,45 @@
|
||||
|
||||
"""common.py: common classes and functions for the RCS parsing tools."""
|
||||
|
||||
import calendar
|
||||
import time
|
||||
import string
|
||||
|
||||
### compat isn't in vclib right now. need to work up a solution
|
||||
import compat
|
||||
|
||||
|
||||
class Sink:
|
||||
def set_head_revision(self, revision):
|
||||
pass
|
||||
|
||||
def set_principal_branch(self, branch_name):
|
||||
pass
|
||||
|
||||
def set_access(self, accessors):
|
||||
pass
|
||||
|
||||
def define_tag(self, name, revision):
|
||||
pass
|
||||
|
||||
def set_locker(self, revision, locker):
|
||||
def set_access(self, accessors):
|
||||
pass
|
||||
def set_expansion(self, mode):
|
||||
pass
|
||||
|
||||
def set_locking(self, mode):
|
||||
"""Used to signal locking mode.
|
||||
|
||||
Called with mode argument 'strict' if strict locking
|
||||
Not called when no locking used."""
|
||||
|
||||
pass
|
||||
|
||||
def set_locker(self, revision, locker):
|
||||
pass
|
||||
def set_comment(self, comment):
|
||||
pass
|
||||
|
||||
def set_expansion(self, mode):
|
||||
def set_description(self, description):
|
||||
pass
|
||||
|
||||
def admin_completed(self):
|
||||
pass
|
||||
|
||||
def define_revision(self, revision, timestamp, author, state,
|
||||
branches, next):
|
||||
pass
|
||||
|
||||
def tree_completed(self):
|
||||
pass
|
||||
|
||||
def set_description(self, description):
|
||||
pass
|
||||
|
||||
def set_revision_info(self, revision, log, text):
|
||||
pass
|
||||
|
||||
def admin_completed(self):
|
||||
pass
|
||||
def tree_completed(self):
|
||||
pass
|
||||
def parse_completed(self):
|
||||
pass
|
||||
|
||||
@@ -72,200 +62,97 @@ class Sink:
|
||||
|
||||
class RCSParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RCSIllegalCharacter(RCSParseError):
|
||||
pass
|
||||
|
||||
|
||||
### need more work on this one
|
||||
class RCSExpected(RCSParseError):
|
||||
def __init__(self, got, wanted):
|
||||
RCSParseError.__init__(
|
||||
self,
|
||||
'Unexpected parsing error in RCS file.\n'
|
||||
'Expected token: %s, but saw: %s'
|
||||
% (wanted, got)
|
||||
)
|
||||
|
||||
RCSParseError.__init__(self, got, wanted)
|
||||
|
||||
class RCSStopParser(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
#
|
||||
# STANDARD TOKEN STREAM-BASED PARSER
|
||||
#
|
||||
|
||||
class _Parser:
|
||||
stream_class = None # subclasses need to define this
|
||||
|
||||
def _read_until_semicolon(self):
|
||||
"""Read all tokens up to and including the next semicolon token.
|
||||
|
||||
Return the tokens (not including the semicolon) as a list."""
|
||||
|
||||
tokens = []
|
||||
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
tokens.append(token)
|
||||
|
||||
return tokens
|
||||
|
||||
def _parse_admin_head(self, token):
|
||||
rev = self.ts.get()
|
||||
if rev == ';':
|
||||
# The head revision is not specified. Just drop the semicolon
|
||||
# on the floor.
|
||||
pass
|
||||
else:
|
||||
self.sink.set_head_revision(rev)
|
||||
self.ts.match(';')
|
||||
|
||||
def _parse_admin_branch(self, token):
|
||||
branch = self.ts.get()
|
||||
if branch != ';':
|
||||
self.sink.set_principal_branch(branch)
|
||||
self.ts.match(';')
|
||||
|
||||
def _parse_admin_access(self, token):
|
||||
accessors = self._read_until_semicolon()
|
||||
if accessors:
|
||||
self.sink.set_access(accessors)
|
||||
|
||||
def _parse_admin_symbols(self, token):
|
||||
while 1:
|
||||
tag_name = self.ts.get()
|
||||
if tag_name == ';':
|
||||
break
|
||||
self.ts.match(':')
|
||||
tag_rev = self.ts.get()
|
||||
self.sink.define_tag(tag_name, tag_rev)
|
||||
|
||||
def _parse_admin_locks(self, token):
|
||||
while 1:
|
||||
locker = self.ts.get()
|
||||
if locker == ';':
|
||||
break
|
||||
self.ts.match(':')
|
||||
rev = self.ts.get()
|
||||
self.sink.set_locker(rev, locker)
|
||||
|
||||
def _parse_admin_strict(self, token):
|
||||
self.sink.set_locking("strict")
|
||||
self.ts.match(';')
|
||||
|
||||
def _parse_admin_comment(self, token):
|
||||
self.sink.set_comment(self.ts.get())
|
||||
self.ts.match(';')
|
||||
|
||||
def _parse_admin_expand(self, token):
|
||||
expand_mode = self.ts.get()
|
||||
self.sink.set_expansion(expand_mode)
|
||||
self.ts.match(';')
|
||||
|
||||
admin_token_map = {
|
||||
'head' : _parse_admin_head,
|
||||
'branch' : _parse_admin_branch,
|
||||
'access' : _parse_admin_access,
|
||||
'symbols' : _parse_admin_symbols,
|
||||
'locks' : _parse_admin_locks,
|
||||
'strict' : _parse_admin_strict,
|
||||
'comment' : _parse_admin_comment,
|
||||
'expand' : _parse_admin_expand,
|
||||
'desc' : None,
|
||||
}
|
||||
stream_class = None # subclasses need to define this
|
||||
|
||||
def parse_rcs_admin(self):
|
||||
while 1:
|
||||
# Read initial token at beginning of line
|
||||
token = self.ts.get()
|
||||
|
||||
try:
|
||||
f = self.admin_token_map[token]
|
||||
except KeyError:
|
||||
# We're done once we reach the description of the RCS tree
|
||||
if token[0] in string.digits:
|
||||
self.ts.unget(token)
|
||||
return
|
||||
else:
|
||||
# Chew up "newphrase"
|
||||
# warn("Unexpected RCS token: $token\n")
|
||||
pass
|
||||
else:
|
||||
if f is None:
|
||||
self.ts.unget(token)
|
||||
return
|
||||
else:
|
||||
f(self, token)
|
||||
|
||||
def _parse_rcs_tree_entry(self, revision):
|
||||
# Parse date
|
||||
self.ts.match('date')
|
||||
date = self.ts.get()
|
||||
self.ts.match(';')
|
||||
|
||||
# Convert date into timestamp
|
||||
date_fields = string.split(date, '.')
|
||||
# According to rcsfile(5): the year "contains just the last two
|
||||
# digits of the year for years from 1900 through 1999, and all the
|
||||
# digits of years thereafter".
|
||||
if len(date_fields[0]) == 2:
|
||||
date_fields[0] = '19' + date_fields[0]
|
||||
date_fields = map(string.atoi, date_fields)
|
||||
EPOCH = 1970
|
||||
if date_fields[0] < EPOCH:
|
||||
raise ValueError, 'invalid year'
|
||||
timestamp = calendar.timegm(tuple(date_fields) + (0, 0, 0,))
|
||||
|
||||
# Parse author
|
||||
### NOTE: authors containing whitespace are violations of the
|
||||
### RCS specification. We are making an allowance here because
|
||||
### CVSNT is known to produce these sorts of authors.
|
||||
self.ts.match('author')
|
||||
author = ' '.join(self._read_until_semicolon())
|
||||
|
||||
# Parse state
|
||||
self.ts.match('state')
|
||||
state = ''
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
state = state + token + ' '
|
||||
state = state[:-1] # toss the trailing space
|
||||
|
||||
# Parse branches
|
||||
self.ts.match('branches')
|
||||
branches = self._read_until_semicolon()
|
||||
|
||||
# Parse revision of next delta in chain
|
||||
self.ts.match('next')
|
||||
next = self.ts.get()
|
||||
if next == ';':
|
||||
next = None
|
||||
else:
|
||||
self.ts.match(';')
|
||||
|
||||
# there are some files with extra tags in them. for example:
|
||||
# owner 640;
|
||||
# group 15;
|
||||
# permissions 644;
|
||||
# hardlinks @configure.in@;
|
||||
# this is "newphrase" in RCSFILE(5). we just want to skip over these.
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == 'desc' or token[0] in string.digits:
|
||||
# We're done once we reach the description of the RCS tree
|
||||
if token[0] in string.digits:
|
||||
self.ts.unget(token)
|
||||
break
|
||||
# consume everything up to the semicolon
|
||||
self._read_until_semicolon()
|
||||
return
|
||||
|
||||
self.sink.define_revision(revision, timestamp, author, state, branches,
|
||||
next)
|
||||
if token == "head":
|
||||
semi, rev = self.ts.mget(2)
|
||||
self.sink.set_head_revision(rev)
|
||||
if semi != ';':
|
||||
raise RCSExpected(semi, ';')
|
||||
elif token == "branch":
|
||||
semi, branch = self.ts.mget(2)
|
||||
if semi == ';':
|
||||
self.sink.set_principal_branch(branch)
|
||||
else:
|
||||
if branch == ';':
|
||||
self.ts.unget(semi);
|
||||
else:
|
||||
raise RCSExpected(semi, ';')
|
||||
elif token == "symbols":
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
break
|
||||
self.ts.match(':')
|
||||
tag_name = tag
|
||||
tag_rev = self.ts.get()
|
||||
self.sink.define_tag(tag_name, tag_rev)
|
||||
elif token == "comment":
|
||||
semi, comment = self.ts.mget(2)
|
||||
self.sink.set_comment(comment)
|
||||
if semi != ';':
|
||||
raise RCSExpected(semi, ';')
|
||||
elif token == "expand":
|
||||
semi, expand_mode = self.ts.mget(2)
|
||||
self.sink.set_expansion(expand_mode)
|
||||
if semi != ';':
|
||||
raise RCSExpected(semi, ';')
|
||||
elif token == "locks":
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
break
|
||||
(locker, rev) = string.split(tag,':')
|
||||
self.sink.set_locker(rev, locker)
|
||||
|
||||
tag = self.ts.get()
|
||||
if tag == "strict":
|
||||
self.sink.set_locking("strict")
|
||||
self.ts.match(';')
|
||||
else:
|
||||
self.ts.unget(tag)
|
||||
elif token == "access":
|
||||
accessors = []
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
if accessors != []:
|
||||
self.sink.set_access(accessors)
|
||||
break
|
||||
accessors = accessors + [ tag ]
|
||||
|
||||
# Chew up "newphrase"
|
||||
else:
|
||||
pass
|
||||
# warn("Unexpected RCS token: $token\n")
|
||||
|
||||
raise RuntimeError, "Unexpected EOF"
|
||||
|
||||
def parse_rcs_tree(self):
|
||||
while 1:
|
||||
@@ -276,7 +163,86 @@ class _Parser:
|
||||
self.ts.unget(revision)
|
||||
return
|
||||
|
||||
self._parse_rcs_tree_entry(revision)
|
||||
# Parse date
|
||||
semi, date, sym = self.ts.mget(3)
|
||||
if sym != 'date':
|
||||
raise RCSExpected(sym, 'date')
|
||||
if semi != ';':
|
||||
raise RCSExpected(semi, ';')
|
||||
|
||||
# Convert date into timestamp
|
||||
date_fields = string.split(date, '.') + ['0', '0', '0']
|
||||
date_fields = map(string.atoi, date_fields)
|
||||
# need to make the date four digits for timegm
|
||||
EPOCH = 1970
|
||||
if date_fields[0] < EPOCH:
|
||||
if date_fields[0] < 70:
|
||||
date_fields[0] = date_fields[0] + 2000
|
||||
else:
|
||||
date_fields[0] = date_fields[0] + 1900
|
||||
if date_fields[0] < EPOCH:
|
||||
raise ValueError, 'invalid year'
|
||||
|
||||
timestamp = compat.timegm(tuple(date_fields))
|
||||
|
||||
# Parse author
|
||||
### NOTE: authors containing whitespace are violations of the
|
||||
### RCS specification. We are making an allowance here because
|
||||
### CVSNT is known to produce these sorts of authors.
|
||||
self.ts.match('author')
|
||||
author = ''
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
author = author + token + ' '
|
||||
author = author[:-1] # toss the trailing space
|
||||
|
||||
# Parse state
|
||||
self.ts.match('state')
|
||||
state = ''
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
state = state + token + ' '
|
||||
state = state[:-1] # toss the trailing space
|
||||
|
||||
# Parse branches
|
||||
self.ts.match('branches')
|
||||
branches = [ ]
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == ';':
|
||||
break
|
||||
branches.append(token)
|
||||
|
||||
# Parse revision of next delta in chain
|
||||
next, sym = self.ts.mget(2)
|
||||
if sym != 'next':
|
||||
raise RCSExpected(sym, 'next')
|
||||
if next == ';':
|
||||
next = None
|
||||
else:
|
||||
self.ts.match(';')
|
||||
|
||||
# there are some files with extra tags in them. for example:
|
||||
# owner 640;
|
||||
# group 15;
|
||||
# permissions 644;
|
||||
# hardlinks @configure.in@;
|
||||
# this is "newphrase" in RCSFILE(5). we just want to skip over these.
|
||||
while 1:
|
||||
token = self.ts.get()
|
||||
if token == 'desc' or token[0] in string.digits:
|
||||
self.ts.unget(token)
|
||||
break
|
||||
# consume everything up to the semicolon
|
||||
while self.ts.get() != ';':
|
||||
pass
|
||||
|
||||
self.sink.define_revision(revision, timestamp, author, state, branches,
|
||||
next)
|
||||
|
||||
def parse_rcs_description(self):
|
||||
self.ts.match('desc')
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -19,18 +19,14 @@ import string
|
||||
import common
|
||||
|
||||
class _TokenStream:
|
||||
token_term = string.whitespace + ";:"
|
||||
try:
|
||||
token_term = frozenset(token_term)
|
||||
except NameError:
|
||||
pass
|
||||
token_term = string.whitespace + ';'
|
||||
|
||||
# the algorithm is about the same speed for any CHUNK_SIZE chosen.
|
||||
# grab a good-sized chunk, but not too large to overwhelm memory.
|
||||
# note: we use a multiple of a standard block size
|
||||
CHUNK_SIZE = 192 * 512 # about 100k
|
||||
|
||||
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
||||
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
||||
|
||||
def __init__(self, file):
|
||||
self.rcsfile = file
|
||||
@@ -48,17 +44,15 @@ class _TokenStream:
|
||||
# out more complex solutions.
|
||||
|
||||
buf = self.buf
|
||||
lbuf = len(buf)
|
||||
idx = self.idx
|
||||
|
||||
while 1:
|
||||
if idx == lbuf:
|
||||
if idx == len(buf):
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '':
|
||||
# signal EOF by returning None as the token
|
||||
del self.buf # so we fail if get() is called again
|
||||
del self.buf # so we fail if get() is called again
|
||||
return None
|
||||
lbuf = len(buf)
|
||||
idx = 0
|
||||
|
||||
if buf[idx] not in string.whitespace:
|
||||
@@ -66,28 +60,27 @@ class _TokenStream:
|
||||
|
||||
idx = idx + 1
|
||||
|
||||
if buf[idx] in ';:':
|
||||
if buf[idx] == ';':
|
||||
self.buf = buf
|
||||
self.idx = idx + 1
|
||||
return buf[idx]
|
||||
return ';'
|
||||
|
||||
if buf[idx] != '@':
|
||||
end = idx + 1
|
||||
token = ''
|
||||
while 1:
|
||||
# find token characters in the current buffer
|
||||
while end < lbuf and buf[end] not in self.token_term:
|
||||
while end < len(buf) and buf[end] not in self.token_term:
|
||||
end = end + 1
|
||||
token = token + buf[idx:end]
|
||||
|
||||
if end < lbuf:
|
||||
if end < len(buf):
|
||||
# we stopped before the end, so we have a full token
|
||||
idx = end
|
||||
break
|
||||
|
||||
# we stopped at the end of the buffer, so we may have a partial token
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
lbuf = len(buf)
|
||||
idx = end = 0
|
||||
|
||||
self.buf = buf
|
||||
@@ -101,24 +94,22 @@ class _TokenStream:
|
||||
chunks = [ ]
|
||||
|
||||
while 1:
|
||||
if idx == lbuf:
|
||||
if idx == len(buf):
|
||||
idx = 0
|
||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '':
|
||||
raise RuntimeError, 'EOF'
|
||||
lbuf = len(buf)
|
||||
i = string.find(buf, '@', idx)
|
||||
if i == -1:
|
||||
chunks.append(buf[idx:])
|
||||
idx = lbuf
|
||||
idx = len(buf)
|
||||
continue
|
||||
if i == lbuf - 1:
|
||||
if i == len(buf) - 1:
|
||||
chunks.append(buf[idx:i])
|
||||
idx = 0
|
||||
buf = '@' + self.rcsfile.read(self.CHUNK_SIZE)
|
||||
if buf == '@':
|
||||
raise RuntimeError, 'EOF'
|
||||
lbuf = len(buf)
|
||||
continue
|
||||
if buf[i + 1] == '@':
|
||||
chunks.append(buf[idx:i+1])
|
||||
@@ -143,7 +134,8 @@ class _TokenStream:
|
||||
|
||||
token = self.get()
|
||||
if token != match:
|
||||
raise common.RCSExpected(token, match)
|
||||
raise RuntimeError, ('Unexpected parsing error in RCS file.\n' +
|
||||
'Expected token: %s, but saw: %s' % (match, token))
|
||||
|
||||
def unget(self, token):
|
||||
"Put this token back, for the next get() to return."
|
||||
@@ -174,3 +166,75 @@ class _TokenStream:
|
||||
|
||||
class Parser(common._Parser):
|
||||
stream_class = _TokenStream
|
||||
|
||||
def parse_rcs_admin(self):
|
||||
while 1:
|
||||
# Read initial token at beginning of line
|
||||
token = self.ts.get()
|
||||
|
||||
# We're done once we reach the description of the RCS tree
|
||||
if token[0] in string.digits:
|
||||
self.ts.unget(token)
|
||||
return
|
||||
|
||||
if token == "head":
|
||||
semi, rev = self.ts.mget(2)
|
||||
self.sink.set_head_revision(rev)
|
||||
if semi != ';':
|
||||
raise common.RCSExpected(semi, ';')
|
||||
elif token == "branch":
|
||||
semi, branch = self.ts.mget(2)
|
||||
if semi == ';':
|
||||
self.sink.set_principal_branch(branch)
|
||||
else:
|
||||
if branch == ';':
|
||||
self.ts.unget(semi);
|
||||
else:
|
||||
raise common.RCSExpected(semi, ';')
|
||||
elif token == "symbols":
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
break
|
||||
(tag_name, tag_rev) = string.split(tag, ':')
|
||||
self.sink.define_tag(tag_name, tag_rev)
|
||||
elif token == "comment":
|
||||
semi, comment = self.ts.mget(2)
|
||||
self.sink.set_comment(comment)
|
||||
if semi != ';':
|
||||
raise common.RCSExpected(semi, ';')
|
||||
elif token == "expand":
|
||||
semi, expand_mode = self.ts.mget(2)
|
||||
self.sink.set_expansion(expand_mode)
|
||||
if semi != ';':
|
||||
raise RCSExpected(semi, ';')
|
||||
elif token == "locks":
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
break
|
||||
(locker, rev) = string.split(tag,':')
|
||||
self.sink.set_locker(rev, locker)
|
||||
|
||||
tag = self.ts.get()
|
||||
if tag == "strict":
|
||||
self.sink.set_locking("strict")
|
||||
self.ts.match(';')
|
||||
else:
|
||||
self.ts.unget(tag)
|
||||
elif token == "access":
|
||||
accessors = []
|
||||
while 1:
|
||||
tag = self.ts.get()
|
||||
if tag == ';':
|
||||
if accessors != []:
|
||||
self.sink.set_access(accessors)
|
||||
break
|
||||
accessors = accessors + [ tag ]
|
||||
|
||||
# Chew up "newphrase".
|
||||
else:
|
||||
pass
|
||||
# warn("Unexpected RCS token: $token\n")
|
||||
|
||||
raise RuntimeError, "Unexpected EOF"
|
||||
|
@@ -1,73 +0,0 @@
|
||||
#! /usr/bin/python
|
||||
|
||||
# (Be in -*- python -*- mode.)
|
||||
#
|
||||
# ====================================================================
|
||||
# Copyright (c) 2006-2007 CollabNet. All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://subversion.tigris.org/license-1.html.
|
||||
# If newer versions of this license are posted there, you may use a
|
||||
# newer version instead, at your option.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For exact contribution history, see the revision
|
||||
# history and logs, available at http://cvs2svn.tigris.org/.
|
||||
# ====================================================================
|
||||
|
||||
"""Parse an RCS file, showing the rcsparse callbacks that are called.
|
||||
|
||||
This program is useful to see whether an RCS file has a problem (in
|
||||
the sense of not being parseable by rcsparse) and also to illuminate
|
||||
the correspondence between RCS file contents and rcsparse callbacks.
|
||||
|
||||
The output of this program can also be considered to be a kind of
|
||||
'canonical' format for RCS files, at least in so far as rcsparse
|
||||
returns all relevant information in the file and provided that the
|
||||
order of callbacks is always the same."""
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, f, name):
|
||||
self.f = f
|
||||
self.name = name
|
||||
|
||||
def __call__(self, *args):
|
||||
self.f.write(
|
||||
'%s(%s)\n' % (self.name, ', '.join(['%r' % arg for arg in args]),)
|
||||
)
|
||||
|
||||
|
||||
class LoggingSink:
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def __getattr__(self, name):
|
||||
return Logger(self.f, name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Since there is nontrivial logic in __init__.py, we have to import
|
||||
# parse() via that file. First make sure that the directory
|
||||
# containing this script is in the path:
|
||||
sys.path.insert(0, os.path.dirname(sys.argv[0]))
|
||||
|
||||
from __init__ import parse
|
||||
|
||||
if sys.argv[1:]:
|
||||
for path in sys.argv[1:]:
|
||||
if os.path.isfile(path) and path.endswith(',v'):
|
||||
parse(
|
||||
open(path, 'rb'), LoggingSink(sys.stdout)
|
||||
)
|
||||
else:
|
||||
sys.stderr.write('%r is being ignored.\n' % path)
|
||||
else:
|
||||
parse(sys.stdin, LoggingSink(sys.stdout))
|
||||
|
||||
|
@@ -1,73 +0,0 @@
|
||||
#! /usr/bin/python
|
||||
|
||||
# (Be in -*- python -*- mode.)
|
||||
#
|
||||
# ====================================================================
|
||||
# Copyright (c) 2007 CollabNet. All rights reserved.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at http://subversion.tigris.org/license-1.html.
|
||||
# If newer versions of this license are posted there, you may use a
|
||||
# newer version instead, at your option.
|
||||
#
|
||||
# This software consists of voluntary contributions made by many
|
||||
# individuals. For exact contribution history, see the revision
|
||||
# history and logs, available at http://viewvc.tigris.org/.
|
||||
# ====================================================================
|
||||
|
||||
"""Run tests of rcsparse code."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
from cStringIO import StringIO
|
||||
from difflib import Differ
|
||||
|
||||
# Since there is nontrivial logic in __init__.py, we have to import
|
||||
# parse() via that file. First make sure that the directory
|
||||
# containing this script is in the path:
|
||||
script_dir = os.path.dirname(sys.argv[0])
|
||||
sys.path.insert(0, script_dir)
|
||||
|
||||
from __init__ import parse
|
||||
from parse_rcs_file import LoggingSink
|
||||
|
||||
|
||||
test_dir = os.path.join(script_dir, 'test-data')
|
||||
|
||||
filelist = glob.glob(os.path.join(test_dir, '*,v'))
|
||||
filelist.sort()
|
||||
|
||||
all_tests_ok = 1
|
||||
|
||||
for filename in filelist:
|
||||
sys.stderr.write('%s: ' % (filename,))
|
||||
f = StringIO()
|
||||
try:
|
||||
parse(open(filename, 'rb'), LoggingSink(f))
|
||||
except Exception, e:
|
||||
sys.stderr.write('Error parsing file: %s!\n' % (e,))
|
||||
all_tests_ok = 0
|
||||
else:
|
||||
output = f.getvalue()
|
||||
|
||||
expected_output_filename = filename[:-2] + '.out'
|
||||
expected_output = open(expected_output_filename, 'rb').read()
|
||||
|
||||
if output == expected_output:
|
||||
sys.stderr.write('OK\n')
|
||||
else:
|
||||
sys.stderr.write('Output does not match expected output!\n')
|
||||
differ = Differ()
|
||||
for diffline in differ.compare(
|
||||
expected_output.splitlines(1), output.splitlines(1)
|
||||
):
|
||||
sys.stderr.write(diffline)
|
||||
all_tests_ok = 0
|
||||
|
||||
if all_tests_ok:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
@@ -1,102 +0,0 @@
|
||||
head 1.2;
|
||||
access;
|
||||
symbols
|
||||
B_SPLIT:1.2.0.4
|
||||
B_MIXED:1.2.0.2
|
||||
T_MIXED:1.2
|
||||
B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4
|
||||
B_FROM_INITIALS:1.1.1.1.0.2
|
||||
T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1
|
||||
T_ALL_INITIAL_FILES:1.1.1.1
|
||||
vendortag:1.1.1.1
|
||||
vendorbranch:1.1.1;
|
||||
locks; strict;
|
||||
comment @# @;
|
||||
|
||||
|
||||
1.2
|
||||
date 2003.05.23.00.17.53; author jrandom; state Exp;
|
||||
branches
|
||||
1.2.2.1
|
||||
1.2.4.1;
|
||||
next 1.1;
|
||||
|
||||
1.1
|
||||
date 98.05.22.23.20.19; author jrandom; state Exp;
|
||||
branches
|
||||
1.1.1.1;
|
||||
next ;
|
||||
|
||||
1.1.1.1
|
||||
date 98.05.22.23.20.19; author jrandom; state Exp;
|
||||
branches;
|
||||
next ;
|
||||
|
||||
1.2.2.1
|
||||
date 2003.05.23.00.31.36; author jrandom; state Exp;
|
||||
branches;
|
||||
next ;
|
||||
|
||||
1.2.4.1
|
||||
date 2003.06.03.03.20.31; author jrandom; state Exp;
|
||||
branches;
|
||||
next ;
|
||||
|
||||
|
||||
desc
|
||||
@@
|
||||
|
||||
|
||||
1.2
|
||||
log
|
||||
@Second commit to proj, affecting all 7 files.
|
||||
@
|
||||
text
|
||||
@This is the file `default' in the top level of the project.
|
||||
|
||||
Every directory in the `proj' project has a file named `default'.
|
||||
|
||||
This line was added in the second commit (affecting all 7 files).
|
||||
@
|
||||
|
||||
|
||||
1.2.4.1
|
||||
log
|
||||
@First change on branch B_SPLIT.
|
||||
|
||||
This change excludes sub3/default, because it was not part of this
|
||||
commit, and sub1/subsubB/default, which is not even on the branch yet.
|
||||
@
|
||||
text
|
||||
@a5 2
|
||||
|
||||
First change on branch B_SPLIT.
|
||||
@
|
||||
|
||||
|
||||
1.2.2.1
|
||||
log
|
||||
@Modify three files, on branch B_MIXED.
|
||||
@
|
||||
text
|
||||
@a5 2
|
||||
|
||||
This line was added on branch B_MIXED only (affecting 3 files).
|
||||
@
|
||||
|
||||
|
||||
1.1
|
||||
log
|
||||
@Initial revision
|
||||
@
|
||||
text
|
||||
@d4 2
|
||||
@
|
||||
|
||||
|
||||
1.1.1.1
|
||||
log
|
||||
@Initial import.
|
||||
@
|
||||
text
|
||||
@@
|
@@ -1,26 +0,0 @@
|
||||
set_head_revision('1.2')
|
||||
define_tag('B_SPLIT', '1.2.0.4')
|
||||
define_tag('B_MIXED', '1.2.0.2')
|
||||
define_tag('T_MIXED', '1.2')
|
||||
define_tag('B_FROM_INITIALS_BUT_ONE', '1.1.1.1.0.4')
|
||||
define_tag('B_FROM_INITIALS', '1.1.1.1.0.2')
|
||||
define_tag('T_ALL_INITIAL_FILES_BUT_ONE', '1.1.1.1')
|
||||
define_tag('T_ALL_INITIAL_FILES', '1.1.1.1')
|
||||
define_tag('vendortag', '1.1.1.1')
|
||||
define_tag('vendorbranch', '1.1.1')
|
||||
set_locking('strict')
|
||||
set_comment('# ')
|
||||
admin_completed()
|
||||
define_revision('1.2', 1053649073, 'jrandom', 'Exp', ['1.2.2.1', '1.2.4.1'], '1.1')
|
||||
define_revision('1.1', 895879219, 'jrandom', 'Exp', ['1.1.1.1'], None)
|
||||
define_revision('1.1.1.1', 895879219, 'jrandom', 'Exp', [], None)
|
||||
define_revision('1.2.2.1', 1053649896, 'jrandom', 'Exp', [], None)
|
||||
define_revision('1.2.4.1', 1054610431, 'jrandom', 'Exp', [], None)
|
||||
tree_completed()
|
||||
set_description('')
|
||||
set_revision_info('1.2', 'Second commit to proj, affecting all 7 files.\n', "This is the file `default' in the top level of the project.\n\nEvery directory in the `proj' project has a file named `default'.\n\nThis line was added in the second commit (affecting all 7 files).\n")
|
||||
set_revision_info('1.2.4.1', 'First change on branch B_SPLIT.\n\nThis change excludes sub3/default, because it was not part of this\ncommit, and sub1/subsubB/default, which is not even on the branch yet.\n', 'a5 2\n\nFirst change on branch B_SPLIT.\n')
|
||||
set_revision_info('1.2.2.1', 'Modify three files, on branch B_MIXED.\n', 'a5 2\n\nThis line was added on branch B_MIXED only (affecting 3 files).\n')
|
||||
set_revision_info('1.1', 'Initial revision\n', 'd4 2\n')
|
||||
set_revision_info('1.1.1.1', 'Initial import.\n', '')
|
||||
parse_completed()
|
@@ -1,10 +0,0 @@
|
||||
head ;
|
||||
access;
|
||||
symbols;
|
||||
locks; strict;
|
||||
comment @# @;
|
||||
|
||||
|
||||
|
||||
desc
|
||||
@@
|
@@ -1,6 +0,0 @@
|
||||
set_locking('strict')
|
||||
set_comment('# ')
|
||||
admin_completed()
|
||||
tree_completed()
|
||||
set_description('')
|
||||
parse_completed()
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -25,7 +25,7 @@ _tt = TextTools
|
||||
_idchar_list = map(chr, range(33, 127)) + map(chr, range(160, 256))
|
||||
_idchar_list.remove('$')
|
||||
_idchar_list.remove(',')
|
||||
#_idchar_list.remove('.') # leave as part of 'num' symbol
|
||||
#_idchar_list.remove('.') leave as part of 'num' symbol
|
||||
_idchar_list.remove(':')
|
||||
_idchar_list.remove(';')
|
||||
_idchar_list.remove('@')
|
||||
@@ -41,10 +41,10 @@ _T_STRING_START = 40
|
||||
_T_STRING_SPAN = 60
|
||||
_T_STRING_END = 70
|
||||
|
||||
_E_COMPLETE = 100 # ended on a complete token
|
||||
_E_TOKEN = 110 # ended mid-token
|
||||
_E_STRING_SPAN = 130 # ended within a string
|
||||
_E_STRING_END = 140 # ended with string-end ('@') (could be mid-@@)
|
||||
_E_COMPLETE = 100 # ended on a complete token
|
||||
_E_TOKEN = 110 # ended mid-token
|
||||
_E_STRING_SPAN = 130 # ended within a string
|
||||
_E_STRING_END = 140 # ended with string-end ('@') (could be mid-@@)
|
||||
|
||||
_SUCCESS = +100
|
||||
|
||||
@@ -65,7 +65,7 @@ class _mxTokenStream:
|
||||
# note: we use a multiple of a standard block size
|
||||
CHUNK_SIZE = 192 * 512 # about 100k
|
||||
|
||||
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
||||
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
||||
|
||||
def __init__(self, file):
|
||||
self.rcsfile = file
|
||||
@@ -83,84 +83,40 @@ class _mxTokenStream:
|
||||
|
||||
# construct a tag table which refers to the buffer we need to parse.
|
||||
table = (
|
||||
#1: ignore whitespace. with or without whitespace, move to the next rule.
|
||||
# ignore whitespace. with or without whitespace, move to the next rule.
|
||||
(None, _tt.AllInSet, _tt.whitespace_set, +1),
|
||||
|
||||
#2
|
||||
(_E_COMPLETE, _tt.EOF + _tt.AppendTagobj, _tt.Here, +1, _SUCCESS),
|
||||
|
||||
#3: accumulate token text and exit, or move to the next rule.
|
||||
# accumulate token text and exit, or move to the next rule.
|
||||
(_UNUSED, _tt.AllInSet + _tt.AppendMatch, _idchar_set, +2),
|
||||
|
||||
#4
|
||||
(_E_TOKEN, _tt.EOF + _tt.AppendTagobj, _tt.Here, -3, _SUCCESS),
|
||||
|
||||
#5: single character tokens exit immediately, or move to the next rule
|
||||
# single character tokens exit immediately, or move to the next rule
|
||||
(_UNUSED, _tt.IsInSet + _tt.AppendMatch, _onechar_token_set, +2),
|
||||
|
||||
#6
|
||||
(_E_COMPLETE, _tt.EOF + _tt.AppendTagobj, _tt.Here, -5, _SUCCESS),
|
||||
|
||||
#7: if this isn't an '@' symbol, then we have a syntax error (go to a
|
||||
# if this isn't an '@' symbol, then we have a syntax error (go to a
|
||||
# negative index to indicate that condition). otherwise, suck it up
|
||||
# and move to the next rule.
|
||||
(_T_STRING_START, _tt.Is + _tt.AppendTagobj, '@'),
|
||||
|
||||
#8
|
||||
(None, _tt.Is, '@', +4, +1),
|
||||
#9
|
||||
(buf, _tt.Is, '@', +1, -1),
|
||||
#10
|
||||
(_T_STRING_END, _tt.Skip + _tt.AppendTagobj, 0, 0, +1),
|
||||
#11
|
||||
(_E_STRING_END, _tt.EOF + _tt.AppendTagobj, _tt.Here, -10, _SUCCESS),
|
||||
|
||||
#12
|
||||
(_E_STRING_SPAN, _tt.EOF + _tt.AppendTagobj, _tt.Here, +1, _SUCCESS),
|
||||
|
||||
#13: suck up everything that isn't an AT. go to next rule to look for EOF
|
||||
# suck up everything that isn't an AT. go to next rule to look for EOF
|
||||
(buf, _tt.AllInSet, _not_at_set, 0, +1),
|
||||
|
||||
#14: go back to look for double AT if we aren't at the end of the string
|
||||
# go back to look for double AT if we aren't at the end of the string
|
||||
(_E_STRING_SPAN, _tt.EOF + _tt.AppendTagobj, _tt.Here, -6, _SUCCESS),
|
||||
)
|
||||
|
||||
# Fast, texttools may be, but it's somewhat lacking in clarity.
|
||||
# Here's an attempt to document the logic encoded in the table above:
|
||||
#
|
||||
# Flowchart:
|
||||
# _____
|
||||
# / /\
|
||||
# 1 -> 2 -> 3 -> 5 -> 7 -> 8 -> 9 -> 10 -> 11
|
||||
# | \/ \/ \/ /\ \/
|
||||
# \ 4 6 12 14 /
|
||||
# \_______/_____/ \ / /
|
||||
# \ 13 /
|
||||
# \__________________________________________/
|
||||
#
|
||||
# #1: Skip over any whitespace.
|
||||
# #2: If now EOF, exit with code _E_COMPLETE.
|
||||
# #3: If we have a series of characters in _idchar_set, then:
|
||||
# #4: Output them as a token, and go back to #1.
|
||||
# #5: If we have a character in _onechar_token_set, then:
|
||||
# #6: Output it as a token, and go back to #1.
|
||||
# #7: If we do not have an '@', then error.
|
||||
# If we do, then log a _T_STRING_START and continue.
|
||||
# #8: If we have another '@', continue on to #9. Otherwise:
|
||||
# #12: If now EOF, exit with code _E_STRING_SPAN.
|
||||
# #13: Record the slice up to the next '@' (or EOF).
|
||||
# #14: If now EOF, exit with code _E_STRING_SPAN.
|
||||
# Otherwise, go back to #8.
|
||||
# #9: If we have another '@', then we've just seen an escaped
|
||||
# (by doubling) '@' within an @-string. Record a slice including
|
||||
# just one '@' character, and jump back to #8.
|
||||
# Otherwise, we've *either* seen the terminating '@' of an @-string,
|
||||
# *or* we've seen one half of an escaped @@ sequence that just
|
||||
# happened to be split over a chunk boundary - in either case,
|
||||
# we continue on to #10.
|
||||
# #10: Log a _T_STRING_END.
|
||||
# #11: If now EOF, exit with _E_STRING_END. Otherwise, go back to #1.
|
||||
|
||||
success, taglist, idx = _tt.tag(buf, table, start)
|
||||
|
||||
if not success:
|
||||
@@ -323,11 +279,16 @@ class _mxTokenStream:
|
||||
def match(self, match):
|
||||
if self.tokens:
|
||||
token = self.tokens.pop()
|
||||
if token != match:
|
||||
raise RuntimeError, ('Unexpected parsing error in RCS file.\n'
|
||||
'Expected token: %s, but saw: %s'
|
||||
% (match, token))
|
||||
else:
|
||||
token = self.get()
|
||||
|
||||
if token != match:
|
||||
raise common.RCSExpected(token, match)
|
||||
if token != match:
|
||||
raise RuntimeError, ('Unexpected parsing error in RCS file.\n'
|
||||
'Expected token: %s, but saw: %s'
|
||||
% (match, token))
|
||||
|
||||
def unget(self, token):
|
||||
self.tokens.append(token)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -10,46 +10,711 @@
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"Version Control lib driver for Subversion repositories"
|
||||
"Version Control lib driver for locally accessible Subversion repositories"
|
||||
|
||||
import vclib
|
||||
import os
|
||||
import os.path
|
||||
import string
|
||||
import cStringIO
|
||||
import signal
|
||||
import time
|
||||
import tempfile
|
||||
import popen
|
||||
import re
|
||||
from svn import fs, repos, core, delta
|
||||
|
||||
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
### Require Subversion 1.2.0 or better.
|
||||
if (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_PATCH) < (1, 2, 0):
|
||||
raise Exception, "Version requirement not met (needs 1.2.0 or better)"
|
||||
|
||||
|
||||
def _allow_all(root, path, pool):
|
||||
"""Generic authz_read_func that permits access to all paths"""
|
||||
return 1
|
||||
|
||||
|
||||
def _fs_path_join(base, relative):
|
||||
# Subversion filesystem paths are '/'-delimited, regardless of OS.
|
||||
joined_path = base + '/' + relative
|
||||
parts = filter(None, string.split(joined_path, '/'))
|
||||
return string.join(parts, '/')
|
||||
|
||||
|
||||
def _cleanup_path(path):
|
||||
"""Return a cleaned-up Subversion filesystem path"""
|
||||
return string.join(filter(None, string.split(path, '/')), '/')
|
||||
|
||||
|
||||
def _compare_paths(path1, path2):
|
||||
path1_len = len (path1);
|
||||
path2_len = len (path2);
|
||||
min_len = min(path1_len, path2_len)
|
||||
i = 0
|
||||
|
||||
# Are the paths exactly the same?
|
||||
if path1 == path2:
|
||||
return 0
|
||||
|
||||
# Skip past common prefix
|
||||
while (i < min_len) and (path1[i] == path2[i]):
|
||||
i = i + 1
|
||||
|
||||
# Children of paths are greater than their parents, but less than
|
||||
# greater siblings of their parents
|
||||
char1 = '\0'
|
||||
char2 = '\0'
|
||||
if (i < path1_len):
|
||||
char1 = path1[i]
|
||||
if (i < path2_len):
|
||||
char2 = path2[i]
|
||||
|
||||
if (char1 == '/') and (i == path2_len):
|
||||
return 1
|
||||
if (char2 == '/') and (i == path1_len):
|
||||
return -1
|
||||
if (i < path1_len) and (char1 == '/'):
|
||||
return -1
|
||||
if (i < path2_len) and (char2 == '/'):
|
||||
return 1
|
||||
|
||||
# Common prefix was skipped above, next character is compared to
|
||||
# determine order
|
||||
return cmp(char1, char2)
|
||||
|
||||
|
||||
def _datestr_to_date(datestr, pool):
|
||||
if datestr is None:
|
||||
return None
|
||||
return core.svn_time_from_cstring(datestr, pool) / 1000000
|
||||
|
||||
|
||||
def _fs_rev_props(fsptr, rev, pool):
|
||||
author = fs.revision_prop(fsptr, rev, core.SVN_PROP_REVISION_AUTHOR, pool)
|
||||
msg = fs.revision_prop(fsptr, rev, core.SVN_PROP_REVISION_LOG, pool)
|
||||
date = fs.revision_prop(fsptr, rev, core.SVN_PROP_REVISION_DATE, pool)
|
||||
return date, author, msg
|
||||
|
||||
|
||||
def date_from_rev(svnrepos, rev):
|
||||
if (rev < 0) or (rev > svnrepos.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
datestr = fs.revision_prop(svnrepos.fs_ptr, rev,
|
||||
core.SVN_PROP_REVISION_DATE, svnrepos.pool)
|
||||
return _datestr_to_date(datestr, svnrepos.pool)
|
||||
|
||||
|
||||
def get_location(svnrepos, path, rev, old_rev):
|
||||
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)
|
||||
results = repos.svn_repos_trace_node_locations(svnrepos.fs_ptr, path,
|
||||
rev, [old_rev],
|
||||
_allow_all, svnrepos.pool)
|
||||
except core.SubversionException, e:
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.ItemNotFound(path)
|
||||
raise
|
||||
|
||||
try:
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(old_path)
|
||||
|
||||
|
||||
def expand_root_parent(parent_path):
|
||||
roots = {}
|
||||
if re.search(_re_url, parent_path):
|
||||
pass
|
||||
def last_rev(svnrepos, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
revision older than, or equal to, LIMIT_REVISION in which path
|
||||
exists. Return that revision, and the path at which PATH exists in
|
||||
that revision."""
|
||||
|
||||
# Here's the plan, man. In the trivial case (where PEG_REVISION is
|
||||
# the same as LIMIT_REVISION), this is a no-brainer. If
|
||||
# LIMIT_REVISION is older than PEG_REVISION, we can use Subversion's
|
||||
# history tracing code to find the right location. If, however,
|
||||
# LIMIT_REVISION is younger than PEG_REVISION, we suffer from
|
||||
# Subversion's lack of forward history searching. Our workaround,
|
||||
# ugly as it may be, involves a binary search through the revisions
|
||||
# between PEG_REVISION and LIMIT_REVISION to find our last live
|
||||
# revision.
|
||||
peg_revision = svnrepos._getrev(peg_revision)
|
||||
limit_revision = svnrepos._getrev(limit_revision)
|
||||
try:
|
||||
if peg_revision == limit_revision:
|
||||
return peg_revision, path
|
||||
elif peg_revision > limit_revision:
|
||||
fsroot = svnrepos._getroot(peg_revision)
|
||||
history = fs.node_history(fsroot, path, svnrepos.scratch_pool)
|
||||
while history:
|
||||
path, peg_revision = fs.history_location(history,
|
||||
svnrepos.scratch_pool);
|
||||
if peg_revision <= limit_revision:
|
||||
return max(peg_revision, limit_revision), _cleanup_path(path)
|
||||
history = fs.history_prev(history, 1, svnrepos.scratch_pool)
|
||||
return peg_revision, _cleanup_path(path)
|
||||
else:
|
||||
### Warning: this is *not* an example of good pool usage.
|
||||
orig_id = fs.node_id(svnrepos._getroot(peg_revision), path,
|
||||
svnrepos.scratch_pool)
|
||||
while peg_revision != limit_revision:
|
||||
mid = (peg_revision + 1 + limit_revision) / 2
|
||||
try:
|
||||
mid_id = fs.node_id(svnrepos._getroot(mid), path,
|
||||
svnrepos.scratch_pool)
|
||||
except core.SubversionException, e:
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
cmp = -1
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
### Not quite right. Need a comparison function that only returns
|
||||
### true when the two nodes are the same copy, not just related.
|
||||
cmp = fs.compare_ids(orig_id, mid_id)
|
||||
|
||||
if cmp in (0, 1):
|
||||
peg_revision = mid
|
||||
else:
|
||||
limit_revision = mid - 1
|
||||
|
||||
return peg_revision, path
|
||||
finally:
|
||||
svnrepos._scratch_clear()
|
||||
|
||||
|
||||
def created_rev(svnrepos, full_name, rev):
|
||||
fsroot = svnrepos._getroot(rev)
|
||||
return fs.node_created_rev(fsroot, full_name, svnrepos.pool)
|
||||
|
||||
|
||||
class Revision(vclib.Revision):
|
||||
"Hold state for each revision's log entry."
|
||||
def __init__(self, rev, date, author, msg, size,
|
||||
filename, copy_path, copy_rev):
|
||||
vclib.Revision.__init__(self, rev, str(rev), date, author, None, msg, size)
|
||||
self.filename = filename
|
||||
self.copy_path = copy_path
|
||||
self.copy_rev = copy_rev
|
||||
|
||||
|
||||
class NodeHistory:
|
||||
def __init__(self, fs_ptr, show_all_logs):
|
||||
self.histories = {}
|
||||
self.fs_ptr = fs_ptr
|
||||
self.show_all_logs = show_all_logs
|
||||
|
||||
def add_history(self, path, revision, pool):
|
||||
# If filtering, only add the path and revision to the histories
|
||||
# list if they were actually changed in this revision (where
|
||||
# change means the path itself was changed, or one of its parents
|
||||
# was copied). This is useful for omitting bubble-up directory
|
||||
# changes.
|
||||
if not self.show_all_logs:
|
||||
rev_root = fs.revision_root(self.fs_ptr, revision, pool)
|
||||
changed_paths = fs.paths_changed(rev_root, pool)
|
||||
paths = changed_paths.keys()
|
||||
if path not in paths:
|
||||
# Look for a copied parent
|
||||
test_path = path
|
||||
found = 0
|
||||
subpool = core.svn_pool_create(pool)
|
||||
while 1:
|
||||
core.svn_pool_clear(subpool)
|
||||
off = string.rfind(test_path, '/')
|
||||
if off < 0:
|
||||
break
|
||||
test_path = test_path[0:off]
|
||||
if test_path in paths:
|
||||
copyfrom_rev, copyfrom_path = \
|
||||
fs.copied_from(rev_root, test_path, subpool)
|
||||
if copyfrom_rev >= 0 and copyfrom_path:
|
||||
found = 1
|
||||
break
|
||||
core.svn_pool_destroy(subpool)
|
||||
if not found:
|
||||
return
|
||||
self.histories[revision] = _cleanup_path(path)
|
||||
|
||||
|
||||
def _get_history(svnrepos, full_name, rev, options={}):
|
||||
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, full_name, svnrepos.pool)
|
||||
if kind is core.svn_node_file:
|
||||
show_all_logs = 1
|
||||
|
||||
# Instantiate a NodeHistory collector object.
|
||||
history = NodeHistory(svnrepos.fs_ptr, show_all_logs)
|
||||
|
||||
# Do we want to cross copy history?
|
||||
cross_copies = options.get('svn_cross_copies', 0)
|
||||
|
||||
# Get the history items for PATH.
|
||||
repos.svn_repos_history(svnrepos.fs_ptr, full_name, history.add_history,
|
||||
1, rev, cross_copies, svnrepos.pool)
|
||||
return history.histories
|
||||
|
||||
|
||||
class ChangedPath:
|
||||
def __init__(self, filename, pathtype, prop_mods, text_mods,
|
||||
base_path, base_rev, action, is_copy):
|
||||
self.filename = filename
|
||||
self.pathtype = pathtype
|
||||
self.prop_mods = prop_mods
|
||||
self.text_mods = text_mods
|
||||
self.base_path = base_path
|
||||
self.base_rev = base_rev
|
||||
self.action = action
|
||||
self.is_copy = is_copy
|
||||
|
||||
|
||||
def get_revision_info(svnrepos, rev):
|
||||
fsroot = svnrepos._getroot(rev)
|
||||
|
||||
# Get the changes for the revision
|
||||
editor = repos.ChangeCollector(svnrepos.fs_ptr, fsroot, svnrepos.pool)
|
||||
e_ptr, e_baton = delta.make_editor(editor, svnrepos.pool)
|
||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton, svnrepos.pool)
|
||||
changes = editor.get_changes()
|
||||
changedpaths = {}
|
||||
|
||||
# Copy the Subversion changes into a new hash, converting them into
|
||||
# ChangedPath objects.
|
||||
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 = 'modified'
|
||||
if not change.path:
|
||||
action = 'deleted'
|
||||
elif change.added:
|
||||
action = '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 == 'deleted':
|
||||
action = 'replaced'
|
||||
else:
|
||||
if change.action == repos.CHANGE_ACTION_ADD:
|
||||
action = 'added'
|
||||
elif change.action == repos.CHANGE_ACTION_DELETE:
|
||||
action = 'deleted'
|
||||
elif change.action == repos.CHANGE_ACTION_REPLACE:
|
||||
action = 'replaced'
|
||||
else:
|
||||
action = 'modified'
|
||||
if (action == 'added' or action == '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
|
||||
changedpaths[path] = ChangedPath(path, pathtype, change.prop_changes,
|
||||
change.text_changed, change.base_path,
|
||||
change.base_rev, action, is_copy)
|
||||
|
||||
# Actually, what we want is a sorted list of ChangedPath objects.
|
||||
change_items = changedpaths.values()
|
||||
change_items.sort(lambda a, b: _compare_paths(a.filename, b.filename))
|
||||
|
||||
# Now get the revision property info. Would use
|
||||
# editor.get_root_props(), but something is broken there...
|
||||
datestr, author, msg = _fs_rev_props(svnrepos.fs_ptr, rev, svnrepos.pool)
|
||||
date = _datestr_to_date(datestr, svnrepos.pool)
|
||||
|
||||
return date, author, msg, change_items
|
||||
|
||||
|
||||
def _log_helper(svnrepos, rev, path, pool):
|
||||
rev_root = fs.revision_root(svnrepos.fs_ptr, rev, pool)
|
||||
|
||||
# Was this path@rev the target of a copy?
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path, pool)
|
||||
|
||||
# Assemble our LogEntry
|
||||
datestr, author, msg = _fs_rev_props(svnrepos.fs_ptr, rev, pool)
|
||||
date = _datestr_to_date(datestr, pool)
|
||||
if fs.is_file(rev_root, path, pool):
|
||||
size = fs.file_length(rev_root, path, pool)
|
||||
else:
|
||||
# Any subdirectories of PARENT_PATH which themselves have a child
|
||||
# "format" are returned as roots.
|
||||
subpaths = os.listdir(parent_path)
|
||||
for rootname in subpaths:
|
||||
rootpath = os.path.join(parent_path, rootname)
|
||||
if os.path.exists(os.path.join(rootpath, "format")):
|
||||
roots[rootname] = canonicalize_rootpath(rootpath)
|
||||
return roots
|
||||
size = None
|
||||
entry = Revision(rev, date, author, msg, size, path,
|
||||
copyfrom_path and _cleanup_path(copyfrom_path),
|
||||
copyfrom_rev)
|
||||
return entry
|
||||
|
||||
|
||||
def _fetch_log(svnrepos, full_name, which_rev, options, pool):
|
||||
revs = []
|
||||
|
||||
def SubversionRepository(name, rootpath, authorizer, utilities, config_dir):
|
||||
rootpath = canonicalize_rootpath(rootpath)
|
||||
if re.search(_re_url, rootpath):
|
||||
import svn_ra
|
||||
return svn_ra.RemoteSubversionRepository(name, rootpath, authorizer,
|
||||
utilities, config_dir)
|
||||
if options.get('svn_latest_log', 0):
|
||||
rev = _log_helper(svnrepos, which_rev, full_name, pool)
|
||||
if rev:
|
||||
revs.append(rev)
|
||||
else:
|
||||
import svn_repos
|
||||
return svn_repos.LocalSubversionRepository(name, rootpath, authorizer,
|
||||
utilities, config_dir)
|
||||
history_set = _get_history(svnrepos, full_name, which_rev, options)
|
||||
history_revs = history_set.keys()
|
||||
history_revs.sort()
|
||||
history_revs.reverse()
|
||||
subpool = core.svn_pool_create(pool)
|
||||
for history_rev in history_revs:
|
||||
core.svn_pool_clear(subpool)
|
||||
rev = _log_helper(svnrepos, history_rev, history_set[history_rev],
|
||||
subpool)
|
||||
if rev:
|
||||
revs.append(rev)
|
||||
core.svn_pool_destroy(subpool)
|
||||
return revs
|
||||
|
||||
|
||||
def _get_last_history_rev(fsroot, path, pool):
|
||||
history = fs.node_history(fsroot, path, pool)
|
||||
history = fs.history_prev(history, 0, pool)
|
||||
history_path, history_rev = fs.history_location(history, pool);
|
||||
return history_rev
|
||||
|
||||
|
||||
def get_logs(svnrepos, full_name, rev, files):
|
||||
fsroot = svnrepos._getroot(rev)
|
||||
subpool = core.svn_pool_create(svnrepos.pool)
|
||||
for file in files:
|
||||
core.svn_pool_clear(subpool)
|
||||
path = _fs_path_join(full_name, file.name)
|
||||
rev = _get_last_history_rev(fsroot, path, subpool)
|
||||
datestr, author, msg = _fs_rev_props(svnrepos.fs_ptr, rev, subpool)
|
||||
date = _datestr_to_date(datestr, subpool)
|
||||
file.rev = str(rev)
|
||||
file.date = date
|
||||
file.author = author
|
||||
file.log = msg
|
||||
if file.kind == vclib.FILE:
|
||||
file.size = fs.file_length(fsroot, path, subpool)
|
||||
core.svn_pool_destroy(subpool)
|
||||
|
||||
|
||||
def get_youngest_revision(svnrepos):
|
||||
return svnrepos.youngest
|
||||
|
||||
def temp_checkout(svnrepos, path, rev, pool):
|
||||
"""Check out file revision to temporary file"""
|
||||
temp = tempfile.mktemp()
|
||||
fp = open(temp, 'wb')
|
||||
try:
|
||||
root = svnrepos._getroot(rev)
|
||||
stream = fs.file_contents(root, path, pool)
|
||||
try:
|
||||
while 1:
|
||||
chunk = core.svn_stream_read(stream, core.SVN_STREAM_CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
fp.write(chunk)
|
||||
finally:
|
||||
core.svn_stream_close(stream)
|
||||
finally:
|
||||
fp.close()
|
||||
return temp
|
||||
|
||||
class FileContentsPipe:
|
||||
def __init__(self, root, path, pool):
|
||||
self._pool = core.svn_pool_create(pool)
|
||||
self._stream = fs.file_contents(root, path, self._pool)
|
||||
self._eof = 0
|
||||
|
||||
def __del__(self):
|
||||
core.svn_pool_destroy(self._pool)
|
||||
|
||||
def read(self, len=None):
|
||||
chunk = None
|
||||
if not self._eof:
|
||||
if len is None:
|
||||
buffer = cStringIO.StringIO()
|
||||
try:
|
||||
while 1:
|
||||
hunk = core.svn_stream_read(self._stream, 8192)
|
||||
if not hunk:
|
||||
break
|
||||
buffer.write(hunk)
|
||||
chunk = buffer.getvalue()
|
||||
finally:
|
||||
buffer.close()
|
||||
|
||||
else:
|
||||
chunk = core.svn_stream_read(self._stream, len)
|
||||
if not chunk:
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readline(self):
|
||||
chunk = None
|
||||
if not self._eof:
|
||||
chunk, self._eof = core.svn_stream_readline(self._stream, '\n',
|
||||
self._pool)
|
||||
if not self._eof:
|
||||
chunk = chunk + '\n'
|
||||
if not chunk:
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readlines(self):
|
||||
lines = []
|
||||
while True:
|
||||
line = self.readline()
|
||||
if not line:
|
||||
break
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def close(self):
|
||||
return core.svn_stream_close(self._stream)
|
||||
|
||||
def eof(self):
|
||||
return self._eof
|
||||
|
||||
|
||||
_re_blameinfo = re.compile(r"\s*(\d+)\s*(.*)")
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, svn_client_path, rootpath, fs_path, rev, first_rev):
|
||||
self.idx = -1
|
||||
self.line_number = 1
|
||||
self.last = None
|
||||
self.first_rev = first_rev
|
||||
|
||||
# Do a little dance to get a URL that works in both Unix-y and
|
||||
# Windows worlds.
|
||||
rootpath = os.path.abspath(rootpath)
|
||||
if rootpath and rootpath[0] != '/':
|
||||
rootpath = '/' + rootpath
|
||||
if os.sep != '/':
|
||||
rootpath = string.replace(rootpath, os.sep, '/')
|
||||
|
||||
url = 'file://' + string.join([rootpath, fs_path], "/")
|
||||
fp = popen.popen(svn_client_path,
|
||||
('blame', "-r%d" % int(rev), "--non-interactive",
|
||||
"%s@%d" % (url, int(rev))),
|
||||
'rb', 1)
|
||||
self.fp = fp
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if idx == self.idx:
|
||||
return self.last
|
||||
if idx != self.idx + 1:
|
||||
raise BlameSequencingError()
|
||||
line = self.fp.readline()
|
||||
if not line:
|
||||
raise IndexError("No more annotations")
|
||||
m = _re_blameinfo.match(line[:17])
|
||||
if not m:
|
||||
raise vclib.Error("Could not parse blame output at line %i\n%s"
|
||||
% (idx+1, line))
|
||||
rev, author = m.groups()
|
||||
text = line[18:]
|
||||
rev = int(rev)
|
||||
prev_rev = None
|
||||
if rev > self.first_rev:
|
||||
prev_rev = rev - 1
|
||||
item = _item(text=text, line_number=idx+1, rev=rev,
|
||||
prev_rev=prev_rev, author=author, date=None)
|
||||
self.last = item
|
||||
self.idx = idx
|
||||
return item
|
||||
|
||||
|
||||
class BlameSequencingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SubversionRepository(vclib.Repository):
|
||||
def __init__(self, name, rootpath, svn_path):
|
||||
if not os.path.isdir(rootpath):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
# Initialize some stuff.
|
||||
self.pool = None
|
||||
self.apr_init = 0
|
||||
self.rootpath = rootpath
|
||||
self.name = name
|
||||
self.svn_client_path = os.path.normpath(os.path.join(svn_path, 'svn'))
|
||||
|
||||
# Register a handler for SIGTERM so we can have a chance to
|
||||
# cleanup. If ViewVC takes too long to start generating CGI
|
||||
# output, Apache will grow impatient and SIGTERM it. While we
|
||||
# don't mind getting told to bail, we want to gracefully close the
|
||||
# repository before we bail.
|
||||
def _sigterm_handler(signum, frame, self=self):
|
||||
self._close()
|
||||
sys.exit(-1)
|
||||
try:
|
||||
signal.signal(signal.SIGTERM, _sigterm_handler)
|
||||
except ValueError:
|
||||
# This is probably "ValueError: signal only works in main
|
||||
# thread", which will get thrown by the likes of mod_python
|
||||
# when trying to install a signal handler from a thread that
|
||||
# isn't the main one. We'll just not care.
|
||||
pass
|
||||
|
||||
# Initialize APR and get our top-level pool.
|
||||
core.apr_initialize()
|
||||
self.apr_init = 1
|
||||
self.pool = core.svn_pool_create(None)
|
||||
self.scratch_pool = core.svn_pool_create(self.pool)
|
||||
|
||||
# Open the repository and init some other variables.
|
||||
self.repos = repos.svn_repos_open(rootpath, self.pool)
|
||||
self.fs_ptr = repos.svn_repos_fs(self.repos)
|
||||
self.youngest = fs.youngest_rev(self.fs_ptr, self.pool)
|
||||
self._fsroots = {}
|
||||
|
||||
def __del__(self):
|
||||
self._close()
|
||||
|
||||
def _close(self):
|
||||
if self.pool:
|
||||
core.svn_pool_destroy(self.pool)
|
||||
self.pool = None
|
||||
if self.apr_init:
|
||||
core.apr_terminate()
|
||||
self.apr_init = 0
|
||||
|
||||
def _scratch_clear(self):
|
||||
core.svn_pool_clear(self.scratch_pool)
|
||||
|
||||
def itemtype(self, path_parts, rev):
|
||||
rev = self._getrev(rev)
|
||||
basepath = self._getpath(path_parts)
|
||||
kind = fs.check_path(self._getroot(rev), basepath, self.scratch_pool)
|
||||
self._scratch_clear()
|
||||
if kind == core.svn_node_dir:
|
||||
return vclib.DIR
|
||||
if kind == core.svn_node_file:
|
||||
return vclib.FILE
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
path = self._getpath(path_parts)
|
||||
rev = self._getrev(rev)
|
||||
fsroot = self._getroot(rev)
|
||||
revision = str(_get_last_history_rev(fsroot, path, self.scratch_pool))
|
||||
self._scratch_clear()
|
||||
fp = FileContentsPipe(fsroot, path, self.pool)
|
||||
return fp, revision
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
basepath = self._getpath(path_parts)
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR:
|
||||
raise vclib.Error("Path '%s' is not a directory." % basepath)
|
||||
|
||||
rev = self._getrev(rev)
|
||||
fsroot = self._getroot(rev)
|
||||
dirents = fs.dir_entries(fsroot, basepath, self.scratch_pool)
|
||||
entries = [ ]
|
||||
for entry in dirents.values():
|
||||
if entry.kind == core.svn_node_dir:
|
||||
kind = vclib.DIR
|
||||
elif entry.kind == core.svn_node_file:
|
||||
kind = vclib.FILE
|
||||
entries.append(vclib.DirEntry(entry.name, kind))
|
||||
self._scratch_clear()
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
get_logs(self, self._getpath(path_parts), self._getrev(rev), entries)
|
||||
|
||||
def itemlog(self, path_parts, rev, options):
|
||||
"""see vclib.Repository.itemlog docstring
|
||||
|
||||
Option values recognized by this implementation
|
||||
|
||||
svn_show_all_dir_logs
|
||||
boolean, default false. if set for a directory path, will include
|
||||
revisions where files underneath the directory have changed
|
||||
|
||||
svn_cross_copies
|
||||
boolean, default false. if set for a path created by a copy, will
|
||||
include revisions from before the copy
|
||||
|
||||
svn_latest_log
|
||||
boolean, default false. if set will return only newest single log
|
||||
entry
|
||||
"""
|
||||
path = self._getpath(path_parts)
|
||||
rev = self._getrev(rev)
|
||||
|
||||
revs = _fetch_log(self, path, rev, options, self.scratch_pool)
|
||||
self._scratch_clear()
|
||||
|
||||
revs.sort()
|
||||
prev = None
|
||||
for rev in revs:
|
||||
rev.prev = prev
|
||||
prev = rev
|
||||
|
||||
return revs
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
path = self._getpath(path_parts)
|
||||
rev = self._getrev(rev)
|
||||
fsroot = self._getroot(rev)
|
||||
|
||||
history_set = _get_history(self, path, rev, {'svn_cross_copies': 1})
|
||||
history_revs = history_set.keys()
|
||||
history_revs.sort()
|
||||
revision = history_revs[-1]
|
||||
first_rev = history_revs[0]
|
||||
source = BlameSource(self.svn_client_path, self.rootpath,
|
||||
path, rev, first_rev)
|
||||
return source, revision
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r1 = self._getrev(rev1)
|
||||
r2 = self._getrev(rev2)
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1, self.pool)
|
||||
temp2 = temp_checkout(self, p2, r2, self.pool)
|
||||
info1 = p1, date_from_rev(self, r1), r1
|
||||
info2 = p2, date_from_rev(self, r2), r2
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2, args)
|
||||
except vclib.svn.core.SubversionException, e:
|
||||
if e.apr_err == vclib.svn.core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.InvalidRevision
|
||||
raise
|
||||
|
||||
def _getpath(self, path_parts):
|
||||
return string.join(path_parts, '/')
|
||||
|
||||
def _getrev(self, rev):
|
||||
if rev is None or rev == 'HEAD':
|
||||
return self.youngest
|
||||
try:
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
raise vclib.InvalidRevision(rev)
|
||||
if (rev < 0) or (rev > self.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
return rev
|
||||
|
||||
def _getroot(self, rev):
|
||||
try:
|
||||
return self._fsroots[rev]
|
||||
except KeyError:
|
||||
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev, self.pool)
|
||||
return r
|
||||
|
||||
class _item:
|
||||
def __init__(self, **kw):
|
||||
vars(self).update(kw)
|
||||
|
@@ -1,598 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"Version Control lib driver for remotely accessible Subversion repositories."
|
||||
|
||||
import vclib
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
import re
|
||||
import tempfile
|
||||
import time
|
||||
import urllib
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception, _split_revprops
|
||||
from svn import core, delta, client, wc, ra
|
||||
|
||||
|
||||
### Require Subversion 1.3.1 or better. (for svn_ra_get_locations support)
|
||||
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)"
|
||||
|
||||
|
||||
### BEGIN COMPATABILITY CODE ###
|
||||
|
||||
try:
|
||||
SVN_INVALID_REVNUM = core.SVN_INVALID_REVNUM
|
||||
except AttributeError: # The 1.4.x bindings are missing core.SVN_INVALID_REVNUM
|
||||
SVN_INVALID_REVNUM = -1
|
||||
|
||||
def list_directory(url, peg_rev, rev, flag, ctx):
|
||||
try:
|
||||
dirents, locks = client.svn_client_ls3(url, peg_rev, rev, flag, ctx)
|
||||
except TypeError: # 1.4.x bindings are goofed
|
||||
dirents = client.svn_client_ls3(None, url, peg_rev, rev, flag, ctx)
|
||||
locks = {}
|
||||
return dirents, locks
|
||||
|
||||
def get_directory_props(ra_session, path, rev):
|
||||
try:
|
||||
dirents, fetched_rev, props = ra.svn_ra_get_dir(ra_session, path, rev)
|
||||
except ValueError: # older bindings are goofed
|
||||
props = ra.svn_ra_get_dir(ra_session, path, rev)
|
||||
return props
|
||||
|
||||
def client_log(url, start_rev, end_rev, log_limit, cross_copies,
|
||||
cb_func, ctx):
|
||||
try:
|
||||
client.svn_client_log4([url], start_rev, start_rev, end_rev,
|
||||
log_limit, 1, not cross_copies, 0, None,
|
||||
cb_func, ctx)
|
||||
except NameError:
|
||||
# Wrap old svn_log_message_receiver_t interface with a
|
||||
# svn_log_entry_t one.
|
||||
def cb_convert(paths, revision, author, date, message, pool):
|
||||
class svn_log_entry_t:
|
||||
pass
|
||||
log_entry = svn_log_entry_t()
|
||||
log_entry.changed_paths = paths
|
||||
log_entry.revision = revision
|
||||
log_entry.revprops = { core.SVN_PROP_REVISION_LOG : message,
|
||||
core.SVN_PROP_REVISION_AUTHOR : author,
|
||||
core.SVN_PROP_REVISION_DATE : date,
|
||||
}
|
||||
cb_func(log_entry, pool)
|
||||
client.svn_client_log2([url], start_rev, end_rev, log_limit,
|
||||
1, not cross_copies, cb_convert, ctx)
|
||||
|
||||
### END COMPATABILITY CODE ###
|
||||
|
||||
|
||||
class LogCollector:
|
||||
### TODO: Make this thing authz-aware
|
||||
|
||||
def __init__(self, path, show_all_logs, lockinfo):
|
||||
# This class uses leading slashes for paths internally
|
||||
if not path:
|
||||
self.path = '/'
|
||||
else:
|
||||
self.path = path[0] == '/' and path or '/' + path
|
||||
self.logs = []
|
||||
self.show_all_logs = show_all_logs
|
||||
self.lockinfo = lockinfo
|
||||
|
||||
def add_log(self, log_entry, pool):
|
||||
paths = log_entry.changed_paths
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
|
||||
# Changed paths have leading slashes
|
||||
changed_paths = paths.keys()
|
||||
changed_paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
this_path = None
|
||||
if self.path in changed_paths:
|
||||
this_path = self.path
|
||||
change = paths[self.path]
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path
|
||||
for changed_path in changed_paths:
|
||||
if changed_path != self.path:
|
||||
# If a parent of our path was copied, our "next previous"
|
||||
# (huh?) path will exist elsewhere (under the copy source).
|
||||
if (string.rfind(self.path, changed_path) == 0) and \
|
||||
self.path[len(changed_path)] == '/':
|
||||
change = paths[changed_path]
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path + self.path[len(changed_path):]
|
||||
if self.show_all_logs or this_path:
|
||||
entry = Revision(revision, date, author, msg, None, self.lockinfo,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
if this_path:
|
||||
self.path = this_path
|
||||
|
||||
def temp_checkout(svnrepos, path, rev):
|
||||
"""Check out file revision to temporary file"""
|
||||
temp = tempfile.mktemp()
|
||||
stream = core.svn_stream_from_aprfile(temp)
|
||||
url = svnrepos._geturl(path)
|
||||
client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev),
|
||||
svnrepos.ctx)
|
||||
core.svn_stream_close(stream)
|
||||
return temp
|
||||
|
||||
class SelfCleanFP:
|
||||
def __init__(self, path):
|
||||
self._fp = open(path, 'r')
|
||||
self._path = path
|
||||
self._eof = 0
|
||||
|
||||
def read(self, len=None):
|
||||
if len:
|
||||
chunk = self._fp.read(len)
|
||||
else:
|
||||
chunk = self._fp.read()
|
||||
if chunk == '':
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readline(self):
|
||||
chunk = self._fp.readline()
|
||||
if chunk == '':
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readlines(self):
|
||||
lines = self._fp.readlines()
|
||||
self._eof = 1
|
||||
return lines
|
||||
|
||||
def close(self):
|
||||
self._fp.close()
|
||||
os.remove(self._path)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def eof(self):
|
||||
return self._eof
|
||||
|
||||
|
||||
class RemoteSubversionRepository(vclib.Repository):
|
||||
def __init__(self, name, rootpath, authorizer, utilities, config_dir):
|
||||
self.name = name
|
||||
self.rootpath = rootpath
|
||||
self.auth = authorizer
|
||||
self.diff_cmd = utilities.diff or 'diff'
|
||||
self.config_dir = config_dir or None
|
||||
|
||||
# See if this repository is even viewable, authz-wise.
|
||||
if not vclib.check_root_access(self):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
def open(self):
|
||||
# Setup the client context baton, complete with non-prompting authstuffs.
|
||||
# TODO: svn_cmdline_setup_auth_baton() is mo' better (when available)
|
||||
core.svn_config_ensure(self.config_dir)
|
||||
self.ctx = client.svn_client_create_context()
|
||||
self.ctx.auth_baton = core.svn_auth_open([
|
||||
client.svn_client_get_simple_provider(),
|
||||
client.svn_client_get_username_provider(),
|
||||
client.svn_client_get_ssl_server_trust_file_provider(),
|
||||
client.svn_client_get_ssl_client_cert_file_provider(),
|
||||
client.svn_client_get_ssl_client_cert_pw_file_provider(),
|
||||
])
|
||||
self.ctx.config = core.svn_config_get_config(self.config_dir)
|
||||
if self.config_dir is not None:
|
||||
core.svn_auth_set_parameter(self.ctx.auth_baton,
|
||||
core.SVN_AUTH_PARAM_CONFIG_DIR,
|
||||
self.config_dir)
|
||||
ra_callbacks = ra.svn_ra_callbacks_t()
|
||||
ra_callbacks.auth_baton = self.ctx.auth_baton
|
||||
self.ra_session = ra.svn_ra_open(self.rootpath, ra_callbacks, None,
|
||||
self.ctx.config)
|
||||
self.youngest = ra.svn_ra_get_latest_revnum(self.ra_session)
|
||||
self._dirent_cache = { }
|
||||
self._revinfo_cache = { }
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
def rootpath(self):
|
||||
return self.rootpath
|
||||
|
||||
def roottype(self):
|
||||
return vclib.SVN
|
||||
|
||||
def authorizer(self):
|
||||
return self.auth
|
||||
|
||||
def itemtype(self, path_parts, rev):
|
||||
pathtype = None
|
||||
if not len(path_parts):
|
||||
pathtype = vclib.DIR
|
||||
else:
|
||||
path = self._getpath(path_parts)
|
||||
rev = self._getrev(rev)
|
||||
try:
|
||||
kind = ra.svn_ra_check_path(self.ra_session, path, rev)
|
||||
if kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
elif kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
except:
|
||||
pass
|
||||
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):
|
||||
path = self._getpath(path_parts)
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file." % path)
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
tmp_file = tempfile.mktemp()
|
||||
stream = core.svn_stream_from_aprfile(tmp_file)
|
||||
### rev here should be the last history revision of the URL
|
||||
client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev), self.ctx)
|
||||
core.svn_stream_close(stream)
|
||||
return SelfCleanFP(tmp_file), self._get_last_history_rev(path_parts, rev)
|
||||
|
||||
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 = [ ]
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for name in dirents.keys():
|
||||
entry = dirents[name]
|
||||
if entry.kind == core.svn_node_dir:
|
||||
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))
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, 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)
|
||||
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):
|
||||
continue
|
||||
dirent = dirents[entry.name]
|
||||
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
|
||||
if locks.has_key(entry.name):
|
||||
entry.lockinfo = locks[entry.name].owner
|
||||
|
||||
def itemlog(self, path_parts, rev, sortby, first, limit, options):
|
||||
assert sortby == vclib.SORTBY_DEFAULT or sortby == vclib.SORTBY_REV
|
||||
path_type = self.itemtype(path_parts, rev) # does auth-check
|
||||
path = self._getpath(path_parts)
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
cross_copies = options.get('svn_cross_copies', 0)
|
||||
log_limit = 0
|
||||
if limit:
|
||||
log_limit = first + limit
|
||||
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit,
|
||||
cross_copies, lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
revs.sort()
|
||||
prev = None
|
||||
for rev in revs:
|
||||
rev.prev = prev
|
||||
prev = rev
|
||||
revs.reverse()
|
||||
|
||||
if len(revs) < first:
|
||||
return []
|
||||
if limit:
|
||||
return revs[first:first+limit]
|
||||
return revs
|
||||
|
||||
def itemprops(self, path_parts, rev):
|
||||
path = self._getpath(path_parts)
|
||||
path_type = self.itemtype(path_parts, rev) # does auth-check
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
pairs = client.svn_client_proplist2(url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
return pairs and pairs[0][1] or {}
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
path = self._getpath(path_parts)
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file." % path)
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
|
||||
blame_data = []
|
||||
|
||||
def _blame_cb(line_no, revision, author, date,
|
||||
line, pool, blame_data=blame_data):
|
||||
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)
|
||||
|
||||
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 tuple(cached_info)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r1 = self._getrev(rev1)
|
||||
r2 = self._getrev(rev2)
|
||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
||||
raise vclib.ItemNotFound(path_parts1)
|
||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, revprops, changes = self.revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
info1 = p1, _date_from_rev(r1), r1
|
||||
info2 = p2, _date_from_rev(r2), r2
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == 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)
|
||||
|
||||
def _getpath(self, path_parts):
|
||||
return string.join(path_parts, '/')
|
||||
|
||||
def _getrev(self, rev):
|
||||
if rev is None or rev == 'HEAD':
|
||||
return self.youngest
|
||||
try:
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
raise vclib.InvalidRevision(rev)
|
||||
if (rev < 0) or (rev > self.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
return rev
|
||||
|
||||
def _geturl(self, path=None):
|
||||
if not path:
|
||||
return self.rootpath
|
||||
return self.rootpath + '/' + urllib.quote(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."""
|
||||
|
||||
dir_url = self._geturl(path)
|
||||
if path:
|
||||
key = str(rev) + '/' + path
|
||||
else:
|
||||
key = str(rev)
|
||||
dirents_locks = self._dirent_cache.get(key)
|
||||
if not dirents_locks:
|
||||
dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
dirents_locks = [dirents, locks]
|
||||
self._dirent_cache[key] = dirents_locks
|
||||
return dirents_locks[0], dirents_locks[1]
|
||||
|
||||
def _get_last_history_rev(self, path_parts, rev):
|
||||
url = self._geturl(self._getpath(path_parts))
|
||||
optrev = _rev2optrev(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, msg, revprops, changes)
|
||||
optrev = _rev2optrev(rev)
|
||||
revs = []
|
||||
|
||||
def _log_cb(log_entry, pool, retval=revs):
|
||||
### Subversion 1.5 and earlier didn't offer the 'changed_paths2'
|
||||
### hash, and in Subversion 1.6, it's offered but broken.
|
||||
try:
|
||||
changed_paths = log_entry.changed_paths2
|
||||
paths = (changed_paths or {}).keys()
|
||||
except:
|
||||
changed_paths = log_entry.changed_paths
|
||||
paths = (changed_paths or {}).keys()
|
||||
paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
action_map = { 'D' : vclib.DELETED,
|
||||
'A' : vclib.ADDED,
|
||||
'R' : vclib.REPLACED,
|
||||
'M' : vclib.MODIFIED,
|
||||
}
|
||||
changes = []
|
||||
found_readable = found_unreadable = 0
|
||||
for path in paths:
|
||||
change = changed_paths[path]
|
||||
### svn_log_changed_path_t (which we might get instead of the
|
||||
### svn_log_changed_path2_t we'd prefer) doesn't have the
|
||||
### 'node_kind' member.
|
||||
pathtype = None
|
||||
if hasattr(change, 'node_kind'):
|
||||
if change.node_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif change.node_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
### svn_log_changed_path2_t only has the 'text_modified' and
|
||||
### 'props_modified' bits in Subversion 1.7 and beyond. And
|
||||
### svn_log_changed_path_t is without.
|
||||
text_modified = props_modified = 0
|
||||
if hasattr(change, 'text_modified'):
|
||||
if change.text_modified == core.svn_tristate_true:
|
||||
text_modified = 1
|
||||
if hasattr(change, 'props_modified'):
|
||||
if change.props_modified == core.svn_tristate_true:
|
||||
props_modified = 1
|
||||
### Wrong, diddily wrong wrong wrong. Can you say,
|
||||
### "Manufacturing data left and right because it hurts to
|
||||
### figure out the right stuff?"
|
||||
action = action_map.get(change.action, vclib.MODIFIED)
|
||||
if change.copyfrom_path and change.copyfrom_rev:
|
||||
is_copy = 1
|
||||
base_path = change.copyfrom_path
|
||||
base_rev = change.copyfrom_rev
|
||||
elif action == vclib.ADDED or action == vclib.REPLACED:
|
||||
is_copy = 0
|
||||
base_path = base_rev = None
|
||||
else:
|
||||
is_copy = 0
|
||||
base_path = path
|
||||
base_rev = revision - 1
|
||||
|
||||
### Check authz rules (we 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):
|
||||
is_copy = 0
|
||||
base_path = None
|
||||
base_rev = None
|
||||
changes.append(SVNChangedPath(path, revision, pathtype, base_path,
|
||||
base_rev, action, is_copy,
|
||||
text_modified, props_modified))
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
|
||||
if found_unreadable:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
revs.append([date, author, msg, revprops, changes])
|
||||
|
||||
client_log(self.rootpath, optrev, optrev, 1, 0, _log_cb, self.ctx)
|
||||
return tuple(revs[0])
|
||||
|
||||
##--- custom --##
|
||||
|
||||
def get_youngest_revision(self):
|
||||
return self.youngest
|
||||
|
||||
def get_location(self, path, rev, old_rev):
|
||||
try:
|
||||
results = ra.get_locations(self.ra_session, path, rev, [old_rev])
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.ItemNotFound(path)
|
||||
raise
|
||||
try:
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(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))
|
||||
|
||||
def last_rev(self, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
revision older than, or equal to, LIMIT_REVISION in which path
|
||||
exists. Return that revision, and the path at which PATH exists in
|
||||
that revision."""
|
||||
|
||||
# Here's the plan, man. In the trivial case (where PEG_REVISION is
|
||||
# the same as LIMIT_REVISION), this is a no-brainer. If
|
||||
# LIMIT_REVISION is older than PEG_REVISION, we can use Subversion's
|
||||
# history tracing code to find the right location. If, however,
|
||||
# LIMIT_REVISION is younger than PEG_REVISION, we suffer from
|
||||
# Subversion's lack of forward history searching. Our workaround,
|
||||
# ugly as it may be, involves a binary search through the revisions
|
||||
# between PEG_REVISION and LIMIT_REVISION to find our last live
|
||||
# revision.
|
||||
peg_revision = self._getrev(peg_revision)
|
||||
limit_revision = self._getrev(limit_revision)
|
||||
if peg_revision == limit_revision:
|
||||
return peg_revision, path
|
||||
elif peg_revision > limit_revision:
|
||||
path = self.get_location(path, peg_revision, limit_revision)
|
||||
return limit_revision, path
|
||||
else:
|
||||
direction = 1
|
||||
while peg_revision != limit_revision:
|
||||
mid = (peg_revision + 1 + limit_revision) / 2
|
||||
try:
|
||||
path = self.get_location(path, peg_revision, mid)
|
||||
except vclib.ItemNotFound:
|
||||
limit_revision = mid - 1
|
||||
else:
|
||||
peg_revision = mid
|
||||
return peg_revision, path
|
@@ -1,840 +0,0 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"Version Control lib driver for locally accessible Subversion repositories"
|
||||
|
||||
import vclib
|
||||
import os
|
||||
import os.path
|
||||
import string
|
||||
import cStringIO
|
||||
import signal
|
||||
import time
|
||||
import tempfile
|
||||
import popen
|
||||
import re
|
||||
from svn import fs, repos, core, client, delta
|
||||
|
||||
|
||||
### Require Subversion 1.3.1 or better.
|
||||
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]
|
||||
|
||||
def _allow_all(root, path, pool):
|
||||
"""Generic authz_read_func that permits access to all paths"""
|
||||
return 1
|
||||
|
||||
|
||||
def _path_parts(path):
|
||||
return filter(None, string.split(path, '/'))
|
||||
|
||||
|
||||
def _cleanup_path(path):
|
||||
"""Return a cleaned-up Subversion filesystem path"""
|
||||
return string.join(_path_parts(path), '/')
|
||||
|
||||
|
||||
def _fs_path_join(base, relative):
|
||||
return _cleanup_path(base + '/' + relative)
|
||||
|
||||
|
||||
def _compare_paths(path1, path2):
|
||||
path1_len = len (path1);
|
||||
path2_len = len (path2);
|
||||
min_len = min(path1_len, path2_len)
|
||||
i = 0
|
||||
|
||||
# Are the paths exactly the same?
|
||||
if path1 == path2:
|
||||
return 0
|
||||
|
||||
# Skip past common prefix
|
||||
while (i < min_len) and (path1[i] == path2[i]):
|
||||
i = i + 1
|
||||
|
||||
# Children of paths are greater than their parents, but less than
|
||||
# greater siblings of their parents
|
||||
char1 = '\0'
|
||||
char2 = '\0'
|
||||
if (i < path1_len):
|
||||
char1 = path1[i]
|
||||
if (i < path2_len):
|
||||
char2 = path2[i]
|
||||
|
||||
if (char1 == '/') and (i == path2_len):
|
||||
return 1
|
||||
if (char2 == '/') and (i == path1_len):
|
||||
return -1
|
||||
if (i < path1_len) and (char1 == '/'):
|
||||
return -1
|
||||
if (i < path2_len) and (char2 == '/'):
|
||||
return 1
|
||||
|
||||
# Common prefix was skipped above, next character is compared to
|
||||
# determine order
|
||||
return cmp(char1, char2)
|
||||
|
||||
|
||||
def _rev2optrev(rev):
|
||||
assert type(rev) is int
|
||||
rt = core.svn_opt_revision_t()
|
||||
rt.kind = core.svn_opt_revision_number
|
||||
rt.value.number = rev
|
||||
return rt
|
||||
|
||||
|
||||
def _rootpath2url(rootpath, path):
|
||||
rootpath = os.path.abspath(rootpath)
|
||||
if rootpath and rootpath[0] != '/':
|
||||
rootpath = '/' + rootpath
|
||||
if os.sep != '/':
|
||||
rootpath = string.replace(rootpath, os.sep, '/')
|
||||
return 'file://' + string.join([rootpath, path], "/")
|
||||
|
||||
|
||||
# Given a dictionary REVPROPS of revision properties, pull special
|
||||
# ones out of them and return a 4-tuple containing the log message,
|
||||
# the author, the date (converted from the date string property), and
|
||||
# a dictionary of any/all other revprops.
|
||||
def _split_revprops(revprops):
|
||||
if not revprops:
|
||||
return None, None, None, {}
|
||||
special_props = []
|
||||
for prop in core.SVN_PROP_REVISION_LOG, \
|
||||
core.SVN_PROP_REVISION_AUTHOR, \
|
||||
core.SVN_PROP_REVISION_DATE:
|
||||
if revprops.has_key(prop):
|
||||
special_props.append(revprops[prop])
|
||||
del(revprops[prop])
|
||||
else:
|
||||
special_props.append(None)
|
||||
msg, author, datestr = tuple(special_props)
|
||||
date = _datestr_to_date(datestr)
|
||||
return msg, author, date, revprops
|
||||
|
||||
|
||||
def _datestr_to_date(datestr):
|
||||
try:
|
||||
return core.svn_time_from_cstring(datestr) / 1000000
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
class Revision(vclib.Revision):
|
||||
"Hold state for each revision's log entry."
|
||||
def __init__(self, rev, date, author, msg, size, lockinfo,
|
||||
filename, copy_path, copy_rev):
|
||||
vclib.Revision.__init__(self, rev, str(rev), date, author, None,
|
||||
msg, size, lockinfo)
|
||||
self.filename = filename
|
||||
self.copy_path = copy_path
|
||||
self.copy_rev = copy_rev
|
||||
|
||||
|
||||
class NodeHistory:
|
||||
"""An iterable object that returns 2-tuples of (revision, path)
|
||||
locations along a node's change history, ordered from youngest to
|
||||
oldest."""
|
||||
|
||||
def __init__(self, fs_ptr, show_all_logs, limit=0):
|
||||
self.histories = []
|
||||
self.fs_ptr = fs_ptr
|
||||
self.show_all_logs = show_all_logs
|
||||
self.oldest_rev = None
|
||||
self.limit = limit
|
||||
|
||||
def add_history(self, path, revision, pool):
|
||||
# If filtering, only add the path and revision to the histories
|
||||
# list if they were actually changed in this revision (where
|
||||
# change means the path itself was changed, or one of its parents
|
||||
# was copied). This is useful for omitting bubble-up directory
|
||||
# changes.
|
||||
if not self.oldest_rev:
|
||||
self.oldest_rev = revision
|
||||
else:
|
||||
assert(revision < self.oldest_rev)
|
||||
|
||||
if not self.show_all_logs:
|
||||
rev_root = fs.revision_root(self.fs_ptr, revision)
|
||||
changed_paths = fs.paths_changed(rev_root)
|
||||
paths = changed_paths.keys()
|
||||
if path not in paths:
|
||||
# Look for a copied parent
|
||||
test_path = path
|
||||
found = 0
|
||||
while 1:
|
||||
off = string.rfind(test_path, '/')
|
||||
if off < 0:
|
||||
break
|
||||
test_path = test_path[0:off]
|
||||
if test_path in paths:
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, test_path)
|
||||
if copyfrom_rev >= 0 and copyfrom_path:
|
||||
found = 1
|
||||
break
|
||||
if not found:
|
||||
return
|
||||
self.histories.append([revision, _cleanup_path(path)])
|
||||
if self.limit and len(self.histories) == self.limit:
|
||||
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 != _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, revprops, 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)
|
||||
history_path, history_rev = fs.history_location(history)
|
||||
return history_rev
|
||||
|
||||
def temp_checkout(svnrepos, path, rev):
|
||||
"""Check out file revision to temporary file"""
|
||||
temp = tempfile.mktemp()
|
||||
fp = open(temp, 'wb')
|
||||
try:
|
||||
root = svnrepos._getroot(rev)
|
||||
stream = fs.file_contents(root, path)
|
||||
try:
|
||||
while 1:
|
||||
chunk = core.svn_stream_read(stream, core.SVN_STREAM_CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
fp.write(chunk)
|
||||
finally:
|
||||
core.svn_stream_close(stream)
|
||||
finally:
|
||||
fp.close()
|
||||
return temp
|
||||
|
||||
class FileContentsPipe:
|
||||
def __init__(self, root, path):
|
||||
self._stream = fs.file_contents(root, path)
|
||||
self._eof = 0
|
||||
|
||||
def read(self, len=None):
|
||||
chunk = None
|
||||
if not self._eof:
|
||||
if len is None:
|
||||
buffer = cStringIO.StringIO()
|
||||
try:
|
||||
while 1:
|
||||
hunk = core.svn_stream_read(self._stream, 8192)
|
||||
if not hunk:
|
||||
break
|
||||
buffer.write(hunk)
|
||||
chunk = buffer.getvalue()
|
||||
finally:
|
||||
buffer.close()
|
||||
|
||||
else:
|
||||
chunk = core.svn_stream_read(self._stream, len)
|
||||
if not chunk:
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readline(self):
|
||||
chunk = None
|
||||
if not self._eof:
|
||||
chunk, self._eof = core.svn_stream_readline(self._stream, '\n')
|
||||
if not self._eof:
|
||||
chunk = chunk + '\n'
|
||||
if not chunk:
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readlines(self):
|
||||
lines = []
|
||||
while True:
|
||||
line = self.readline()
|
||||
if not line:
|
||||
break
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def close(self):
|
||||
return core.svn_stream_close(self._stream)
|
||||
|
||||
def eof(self):
|
||||
return self._eof
|
||||
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, local_url, rev, first_rev, config_dir):
|
||||
self.idx = -1
|
||||
self.first_rev = first_rev
|
||||
self.blame_data = []
|
||||
|
||||
ctx = client.svn_client_create_context()
|
||||
core.svn_config_ensure(config_dir)
|
||||
ctx.config = core.svn_config_get_config(config_dir)
|
||||
ctx.auth_baton = core.svn_auth_open([])
|
||||
try:
|
||||
### TODO: Is this use of FIRST_REV always what we want? Should we
|
||||
### pass 1 here instead and do filtering later?
|
||||
client.blame2(local_url, _rev2optrev(rev), _rev2optrev(first_rev),
|
||||
_rev2optrev(rev), self._blame_cb, ctx)
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_CLIENT_IS_BINARY_FILE:
|
||||
raise vclib.NonTextualFileContents
|
||||
raise
|
||||
|
||||
def _blame_cb(self, line_no, rev, author, date, text, pool):
|
||||
prev_rev = None
|
||||
if rev > self.first_rev:
|
||||
prev_rev = rev - 1
|
||||
self.blame_data.append(vclib.Annotation(text, line_no + 1, rev,
|
||||
prev_rev, author, None))
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if idx != self.idx + 1:
|
||||
raise BlameSequencingError()
|
||||
self.idx = idx
|
||||
return self.blame_data[idx]
|
||||
|
||||
|
||||
class BlameSequencingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SVNChangedPath(vclib.ChangedPath):
|
||||
"""Wrapper around vclib.ChangedPath which handles path splitting."""
|
||||
|
||||
def __init__(self, path, rev, pathtype, base_path, base_rev,
|
||||
action, copied, text_changed, props_changed):
|
||||
path_parts = _path_parts(path or '')
|
||||
base_path_parts = _path_parts(base_path or '')
|
||||
vclib.ChangedPath.__init__(self, path_parts, rev, pathtype,
|
||||
base_path_parts, base_rev, action,
|
||||
copied, text_changed, props_changed)
|
||||
|
||||
|
||||
class LocalSubversionRepository(vclib.Repository):
|
||||
def __init__(self, name, rootpath, authorizer, utilities, config_dir):
|
||||
if not (os.path.isdir(rootpath) \
|
||||
and os.path.isfile(os.path.join(rootpath, 'format'))):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
# Initialize some stuff.
|
||||
self.rootpath = rootpath
|
||||
self.name = name
|
||||
self.auth = authorizer
|
||||
self.svn_client_path = utilities.svn or 'svn'
|
||||
self.diff_cmd = utilities.diff or 'diff'
|
||||
self.config_dir = config_dir or None
|
||||
|
||||
# See if this repository is even viewable, authz-wise.
|
||||
if not vclib.check_root_access(self):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
def open(self):
|
||||
# Register a handler for SIGTERM so we can have a chance to
|
||||
# cleanup. If ViewVC takes too long to start generating CGI
|
||||
# output, Apache will grow impatient and SIGTERM it. While we
|
||||
# don't mind getting told to bail, we want to gracefully close the
|
||||
# repository before we bail.
|
||||
def _sigterm_handler(signum, frame, self=self):
|
||||
sys.exit(-1)
|
||||
try:
|
||||
signal.signal(signal.SIGTERM, _sigterm_handler)
|
||||
except ValueError:
|
||||
# This is probably "ValueError: signal only works in main
|
||||
# thread", which will get thrown by the likes of mod_python
|
||||
# when trying to install a signal handler from a thread that
|
||||
# isn't the main one. We'll just not care.
|
||||
pass
|
||||
|
||||
# Open the repository and init some other variables.
|
||||
self.repos = repos.svn_repos_open(self.rootpath)
|
||||
self.fs_ptr = repos.svn_repos_fs(self.repos)
|
||||
self.youngest = fs.youngest_rev(self.fs_ptr)
|
||||
self._fsroots = {}
|
||||
self._revinfo_cache = {}
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
def rootpath(self):
|
||||
return self.rootpath
|
||||
|
||||
def roottype(self):
|
||||
return vclib.SVN
|
||||
|
||||
def authorizer(self):
|
||||
return self.auth
|
||||
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
fsroot = self._getroot(rev)
|
||||
revision = str(_get_last_history_rev(fsroot, path))
|
||||
fp = FileContentsPipe(fsroot, path)
|
||||
return fp, revision
|
||||
|
||||
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)
|
||||
fsroot = self._getroot(rev)
|
||||
dirents = fs.dir_entries(fsroot, path)
|
||||
entries = [ ]
|
||||
for entry in dirents.values():
|
||||
if entry.kind == core.svn_node_dir:
|
||||
kind = vclib.DIR
|
||||
elif entry.kind == core.svn_node_file:
|
||||
kind = vclib.FILE
|
||||
if vclib.check_path_access(self, path_parts + [entry.name], kind, rev):
|
||||
entries.append(vclib.DirEntry(entry.name, kind))
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, 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)
|
||||
fsroot = self._getroot(self._getrev(rev))
|
||||
rev = self._getrev(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):
|
||||
continue
|
||||
path = self._getpath(entry_path_parts)
|
||||
entry_rev = _get_last_history_rev(fsroot, path)
|
||||
date, author, msg, revprops, changes = self._revinfo(entry_rev)
|
||||
entry.rev = str(entry_rev)
|
||||
entry.date = date
|
||||
entry.author = author
|
||||
entry.log = msg
|
||||
if entry.kind == vclib.FILE:
|
||||
entry.size = fs.file_length(fsroot, path)
|
||||
lock = fs.get_lock(self.fs_ptr, path)
|
||||
entry.lockinfo = lock and lock.owner or None
|
||||
|
||||
def itemlog(self, path_parts, rev, sortby, first, limit, options):
|
||||
"""see vclib.Repository.itemlog docstring
|
||||
|
||||
Option values recognized by this implementation
|
||||
|
||||
svn_show_all_dir_logs
|
||||
boolean, default false. if set for a directory path, will include
|
||||
revisions where files underneath the directory have changed
|
||||
|
||||
svn_cross_copies
|
||||
boolean, default false. if set for a path created by a copy, will
|
||||
include revisions from before the copy
|
||||
|
||||
svn_latest_log
|
||||
boolean, default false. if set will return only newest single log
|
||||
entry
|
||||
"""
|
||||
assert sortby == vclib.SORTBY_DEFAULT or sortby == vclib.SORTBY_REV
|
||||
|
||||
path = self._getpath(path_parts)
|
||||
path_type = self.itemtype(path_parts, rev) # does auth-check
|
||||
rev = self._getrev(rev)
|
||||
revs = []
|
||||
lockinfo = None
|
||||
|
||||
# See if this path is locked.
|
||||
try:
|
||||
lock = fs.get_lock(self.fs_ptr, path)
|
||||
if lock:
|
||||
lockinfo = lock.owner
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
# If our caller only wants the latest log, we'll invoke
|
||||
# _log_helper for just the one revision. Otherwise, we go off
|
||||
# into history-fetching mode. ### TODO: we could stand to have a
|
||||
# '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)
|
||||
if revision:
|
||||
revision.prev = None
|
||||
revs.append(revision)
|
||||
else:
|
||||
history = _get_history(self, path, rev, path_type,
|
||||
first + limit, options)
|
||||
if len(history) < first:
|
||||
history = []
|
||||
if limit:
|
||||
history = history[first:first+limit]
|
||||
|
||||
for hist_rev, hist_path in history:
|
||||
revision = _log_helper(self, hist_path, hist_rev, lockinfo)
|
||||
if revision:
|
||||
# If we have unreadable copyfrom data, obscure it.
|
||||
if revision.copy_path is not None:
|
||||
cp_parts = _path_parts(revision.copy_path)
|
||||
if not vclib.check_path_access(self, cp_parts, path_type,
|
||||
revision.copy_rev):
|
||||
revision.copy_path = revision.copy_rev = None
|
||||
revision.prev = None
|
||||
if len(revs):
|
||||
revs[-1].prev = revision
|
||||
revs.append(revision)
|
||||
return revs
|
||||
|
||||
def itemprops(self, path_parts, rev):
|
||||
path = self._getpath(path_parts)
|
||||
path_type = self.itemtype(path_parts, rev) # does auth-check
|
||||
rev = self._getrev(rev)
|
||||
fsroot = self._getroot(rev)
|
||||
return fs.node_proplist(fsroot, path)
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
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})
|
||||
youngest_rev, youngest_path = history[0]
|
||||
oldest_rev, oldest_path = history[-1]
|
||||
source = BlameSource(_rootpath2url(self.rootpath, path),
|
||||
youngest_rev, oldest_rev, self.config_dir)
|
||||
return source, youngest_rev
|
||||
|
||||
def _revinfo(self, rev, include_changed_paths=0):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
def _revinfo_helper(rev, include_changed_paths):
|
||||
# Get the revision property info. (Would use
|
||||
# editor.get_root_props(), but something is broken there...)
|
||||
revprops = fs.revision_proplist(self.fs_ptr, rev)
|
||||
msg, 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.
|
||||
# Either way, we need 'em, so let's get 'em.
|
||||
fsroot = self._getroot(rev)
|
||||
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
||||
e_ptr, e_baton = delta.make_editor(editor)
|
||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
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
|
||||
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
|
||||
|
||||
# If our caller doesn't care about changed paths, we must be
|
||||
# here for authz reasons only. That means the minute we've
|
||||
# found both a readable and an unreadable path, we can bail out.
|
||||
if (not include_changed_paths) and found_readable and found_unreadable:
|
||||
return date, author, None, None, None
|
||||
|
||||
# Okay, we've process all our paths. Let's filter our metadata,
|
||||
# and return the requested data.
|
||||
if found_unreadable:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
if include_changed_paths:
|
||||
return date, author, msg, revprops, changedpaths.values()
|
||||
else:
|
||||
return date, author, msg, revprops, None
|
||||
|
||||
# Consult the revinfo cache first. If we don't have cached info,
|
||||
# or our caller wants changed paths and we don't have those for
|
||||
# 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 revinfo(self, rev):
|
||||
return self._revinfo(rev, 1)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r1 = self._getrev(rev1)
|
||||
r2 = self._getrev(rev2)
|
||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
||||
raise vclib.ItemNotFound(path_parts1)
|
||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
info1 = p1, _date_from_rev(r1), r1
|
||||
info2 = p2, _date_from_rev(r2), r2
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.InvalidRevision
|
||||
raise
|
||||
|
||||
def isexecutable(self, path_parts, rev):
|
||||
props = self.itemprops(path_parts, rev) # does authz-check
|
||||
return props.has_key(core.SVN_PROP_EXECUTABLE)
|
||||
|
||||
def _getpath(self, path_parts):
|
||||
return string.join(path_parts, '/')
|
||||
|
||||
def _getrev(self, rev):
|
||||
if rev is None or rev == 'HEAD':
|
||||
return self.youngest
|
||||
try:
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
raise vclib.InvalidRevision(rev)
|
||||
if (rev < 0) or (rev > self.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
return rev
|
||||
|
||||
def _getroot(self, rev):
|
||||
try:
|
||||
return self._fsroots[rev]
|
||||
except KeyError:
|
||||
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev)
|
||||
return r
|
||||
|
||||
##--- custom --##
|
||||
|
||||
def get_youngest_revision(self):
|
||||
return self.youngest
|
||||
|
||||
def get_location(self, path, rev, old_rev):
|
||||
try:
|
||||
results = repos.svn_repos_trace_node_locations(self.fs_ptr, path,
|
||||
rev, [old_rev], _allow_all)
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.ItemNotFound(path)
|
||||
raise
|
||||
try:
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(old_path)
|
||||
|
||||
def created_rev(self, full_name, rev):
|
||||
return fs.node_created_rev(self._getroot(rev), full_name)
|
||||
|
||||
def last_rev(self, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
revision older than, or equal to, LIMIT_REVISION in which path
|
||||
exists. Return that revision, and the path at which PATH exists in
|
||||
that revision."""
|
||||
|
||||
# Here's the plan, man. In the trivial case (where PEG_REVISION is
|
||||
# the same as LIMIT_REVISION), this is a no-brainer. If
|
||||
# LIMIT_REVISION is older than PEG_REVISION, we can use Subversion's
|
||||
# history tracing code to find the right location. If, however,
|
||||
# LIMIT_REVISION is younger than PEG_REVISION, we suffer from
|
||||
# Subversion's lack of forward history searching. Our workaround,
|
||||
# ugly as it may be, involves a binary search through the revisions
|
||||
# between PEG_REVISION and LIMIT_REVISION to find our last live
|
||||
# revision.
|
||||
peg_revision = self._getrev(peg_revision)
|
||||
limit_revision = self._getrev(limit_revision)
|
||||
try:
|
||||
if peg_revision == limit_revision:
|
||||
return peg_revision, path
|
||||
elif peg_revision > limit_revision:
|
||||
fsroot = self._getroot(peg_revision)
|
||||
history = fs.node_history(fsroot, path)
|
||||
while history:
|
||||
path, peg_revision = fs.history_location(history)
|
||||
if peg_revision <= limit_revision:
|
||||
return max(peg_revision, limit_revision), _cleanup_path(path)
|
||||
history = fs.history_prev(history, 1)
|
||||
return peg_revision, _cleanup_path(path)
|
||||
else:
|
||||
orig_id = fs.node_id(self._getroot(peg_revision), path)
|
||||
while peg_revision != limit_revision:
|
||||
mid = (peg_revision + 1 + limit_revision) / 2
|
||||
try:
|
||||
mid_id = fs.node_id(self._getroot(mid), path)
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
cmp = -1
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
### Not quite right. Need a comparison function that only returns
|
||||
### true when the two nodes are the same copy, not just related.
|
||||
cmp = fs.compare_ids(orig_id, mid_id)
|
||||
|
||||
if cmp in (0, 1):
|
||||
peg_revision = mid
|
||||
else:
|
||||
limit_revision = mid - 1
|
||||
|
||||
return peg_revision, path
|
||||
finally:
|
||||
pass
|
444
lib/vclib/svn_ra/__init__.py
Normal file
444
lib/vclib/svn_ra/__init__.py
Normal file
@@ -0,0 +1,444 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
# distribution or at http://viewvc.org/license-1.html.
|
||||
#
|
||||
# For more information, visit http://viewvc.org/
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
"Version Control lib driver for remotely accessible Subversion repositories."
|
||||
|
||||
import vclib
|
||||
import sys
|
||||
import os
|
||||
import string
|
||||
import re
|
||||
import tempfile
|
||||
import popen2
|
||||
import time
|
||||
from vclib.svn import Revision, ChangedPath, _datestr_to_date, _compare_paths, _cleanup_path
|
||||
from svn import core, delta, client, wc, ra
|
||||
|
||||
|
||||
### Require Subversion 1.3.0 or better. (for svn_ra_get_locations support)
|
||||
if (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_PATCH) < (1, 3, 0):
|
||||
raise Exception, "Version requirement not met (needs 1.3.0 or better)"
|
||||
|
||||
|
||||
def _rev2optrev(rev):
|
||||
assert type(rev) is int
|
||||
rt = core.svn_opt_revision_t()
|
||||
rt.kind = core.svn_opt_revision_number
|
||||
rt.value.number = rev
|
||||
return rt
|
||||
|
||||
|
||||
def date_from_rev(svnrepos, rev):
|
||||
datestr = ra.svn_ra_rev_prop(svnrepos.ra_session, rev,
|
||||
'svn:date', svnrepos.pool)
|
||||
return _datestr_to_date(datestr, svnrepos.pool)
|
||||
|
||||
|
||||
def get_location(svnrepos, path, rev, old_rev):
|
||||
try:
|
||||
results = ra.get_locations(svnrepos.ra_session, path, rev,
|
||||
[old_rev], svnrepos.pool)
|
||||
except core.SubversionException, e:
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.ItemNotFound(path)
|
||||
raise
|
||||
|
||||
try:
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(old_path)
|
||||
|
||||
|
||||
def last_rev(svnrepos, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
revision older than, or equal to, LIMIT_REVISION in which path
|
||||
exists. Return that revision, and the path at which PATH exists in
|
||||
that revision."""
|
||||
|
||||
# Here's the plan, man. In the trivial case (where PEG_REVISION is
|
||||
# the same as LIMIT_REVISION), this is a no-brainer. If
|
||||
# LIMIT_REVISION is older than PEG_REVISION, we can use Subversion's
|
||||
# history tracing code to find the right location. If, however,
|
||||
# LIMIT_REVISION is younger than PEG_REVISION, we suffer from
|
||||
# Subversion's lack of forward history searching. Our workaround,
|
||||
# ugly as it may be, involves a binary search through the revisions
|
||||
# between PEG_REVISION and LIMIT_REVISION to find our last live
|
||||
# revision.
|
||||
peg_revision = svnrepos._getrev(peg_revision)
|
||||
limit_revision = svnrepos._getrev(limit_revision)
|
||||
if peg_revision == limit_revision:
|
||||
return peg_revision, path
|
||||
elif peg_revision > limit_revision:
|
||||
path = get_location(svnrepos, path, peg_revision, limit_revision)
|
||||
return limit_revision, path
|
||||
else:
|
||||
### Warning: this is *not* an example of good pool usage.
|
||||
direction = 1
|
||||
while peg_revision != limit_revision:
|
||||
mid = (peg_revision + 1 + limit_revision) / 2
|
||||
try:
|
||||
path = get_location(svnrepos, path, peg_revision, mid)
|
||||
except vclib.ItemNotFound:
|
||||
limit_revision = mid - 1
|
||||
else:
|
||||
peg_revision = mid
|
||||
return peg_revision, path
|
||||
|
||||
|
||||
def created_rev(svnrepos, full_name, rev):
|
||||
kind = ra.svn_ra_check_path(svnrepos.ra_session, full_name, rev,
|
||||
svnrepos.pool)
|
||||
if kind == core.svn_node_dir:
|
||||
props = ra.svn_ra_get_dir(svnrepos.ra_session, full_name,
|
||||
rev, svnrepos.pool)
|
||||
return int(props[core.SVN_PROP_ENTRY_COMMITTED_REV])
|
||||
return core.SVN_INVALID_REVNUM
|
||||
|
||||
|
||||
class LastHistoryCollector:
|
||||
def __init__(self):
|
||||
self.has_history = 0
|
||||
|
||||
def add_history(self, paths, revision, author, date, message, pool):
|
||||
if not self.has_history:
|
||||
self.has_history = 1
|
||||
self.revision = revision
|
||||
self.author = author
|
||||
self.date = date
|
||||
self.message = message
|
||||
self.changes = []
|
||||
|
||||
if not paths:
|
||||
return
|
||||
changed_paths = paths.keys()
|
||||
changed_paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
action_map = { 'D' : 'deleted',
|
||||
'A' : 'added',
|
||||
'R' : 'replaced',
|
||||
'M' : 'modified',
|
||||
}
|
||||
for changed_path in changed_paths:
|
||||
change = paths[changed_path]
|
||||
action = action_map.get(change.action, '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:
|
||||
self.changes.append(ChangedPath(changed_path[1:], None, 0, 0,
|
||||
change.copyfrom_path,
|
||||
change.copyfrom_rev, action, 1))
|
||||
else:
|
||||
self.changes.append(ChangedPath(changed_path[1:], None, 0, 0,
|
||||
changed_path[1:], 0, action, 0))
|
||||
|
||||
def get_history(self):
|
||||
if not self.has_history:
|
||||
return None, None, None, None, None
|
||||
return self.revision, self.author, self.date, self.message, self.changes
|
||||
|
||||
|
||||
def _get_rev_details(svnrepos, rev, pool):
|
||||
lhc = LastHistoryCollector()
|
||||
client.svn_client_log([svnrepos.rootpath],
|
||||
_rev2optrev(rev), _rev2optrev(rev),
|
||||
1, 0, lhc.add_history, svnrepos.ctx, pool)
|
||||
return lhc.get_history()
|
||||
|
||||
|
||||
def get_revision_info(svnrepos, rev):
|
||||
rev, author, date, log, changes = \
|
||||
_get_rev_details(svnrepos, rev, svnrepos.pool)
|
||||
return _datestr_to_date(date, svnrepos.pool), author, log, changes
|
||||
|
||||
|
||||
class LogCollector:
|
||||
def __init__(self, path, show_all_logs):
|
||||
# This class uses leading slashes for paths internally
|
||||
if not path:
|
||||
self.path = '/'
|
||||
else:
|
||||
self.path = path[0] == '/' and path or '/' + path
|
||||
self.logs = []
|
||||
self.show_all_logs = show_all_logs
|
||||
|
||||
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))
|
||||
this_path = None
|
||||
if self.path in changed_paths:
|
||||
this_path = self.path
|
||||
change = paths[self.path]
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path
|
||||
for changed_path in changed_paths:
|
||||
if changed_path != self.path:
|
||||
# If a parent of our path was copied, our "next previous"
|
||||
# (huh?) path will exist elsewhere (under the copy source).
|
||||
if (string.rfind(self.path, changed_path) == 0) and \
|
||||
self.path[len(changed_path)] == '/':
|
||||
change = paths[changed_path]
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path + self.path[len(changed_path):]
|
||||
if self.show_all_logs or this_path:
|
||||
date = _datestr_to_date(date, pool)
|
||||
entry = Revision(revision, date, author, message, None,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
if this_path:
|
||||
self.path = this_path
|
||||
|
||||
|
||||
def get_logs(svnrepos, full_name, rev, files):
|
||||
dirents = svnrepos._get_dirents(full_name, rev)
|
||||
subpool = core.svn_pool_create(svnrepos.pool)
|
||||
rev_info_cache = { }
|
||||
for file in files:
|
||||
core.svn_pool_clear(subpool)
|
||||
entry = dirents[file.name]
|
||||
if rev_info_cache.has_key(entry.created_rev):
|
||||
rev, author, date, log = rev_info_cache[entry.created_rev]
|
||||
else:
|
||||
### i think this needs some get_last_history action to be accurate
|
||||
rev, author, date, log, changes = \
|
||||
_get_rev_details(svnrepos, entry.created_rev, subpool)
|
||||
rev_info_cache[entry.created_rev] = rev, author, date, log
|
||||
file.rev = rev
|
||||
file.author = author
|
||||
file.date = _datestr_to_date(date, subpool)
|
||||
file.log = log
|
||||
file.size = entry.size
|
||||
core.svn_pool_destroy(subpool)
|
||||
|
||||
def get_youngest_revision(svnrepos):
|
||||
return svnrepos.youngest
|
||||
|
||||
def temp_checkout(svnrepos, path, rev, pool):
|
||||
"""Check out file revision to temporary file"""
|
||||
temp = tempfile.mktemp()
|
||||
stream = core.svn_stream_from_aprfile(temp, pool)
|
||||
url = svnrepos.rootpath + (path and '/' + path)
|
||||
client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev),
|
||||
svnrepos.ctx, pool)
|
||||
core.svn_stream_close(stream)
|
||||
return temp
|
||||
|
||||
class SelfCleanFP:
|
||||
def __init__(self, path):
|
||||
self._fp = open(path, 'r')
|
||||
self._path = path
|
||||
self._eof = 0
|
||||
|
||||
def read(self, len):
|
||||
if len:
|
||||
chunk = self._fp.read(len)
|
||||
else:
|
||||
chunk = self._fp.read()
|
||||
if chunk == '':
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def readline(self):
|
||||
chunk = self._fp.readline()
|
||||
if chunk == '':
|
||||
self._eof = 1
|
||||
return chunk
|
||||
|
||||
def close(self):
|
||||
self._fp.close()
|
||||
os.remove(self._path)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def eof(self):
|
||||
return self._eof
|
||||
|
||||
|
||||
class SubversionRepository(vclib.Repository):
|
||||
def __init__(self, name, rootpath):
|
||||
# Init the client app
|
||||
core.apr_initialize()
|
||||
pool = core.svn_pool_create(None)
|
||||
core.svn_config_ensure(None, pool)
|
||||
|
||||
# Start populating our members
|
||||
self.pool = pool
|
||||
self.name = name
|
||||
self.rootpath = rootpath
|
||||
|
||||
# Setup the client context baton, complete with non-prompting authstuffs.
|
||||
ctx = client.svn_client_ctx_t()
|
||||
providers = []
|
||||
providers.append(client.svn_client_get_simple_provider(pool))
|
||||
providers.append(client.svn_client_get_username_provider(pool))
|
||||
providers.append(client.svn_client_get_ssl_server_trust_file_provider(pool))
|
||||
providers.append(client.svn_client_get_ssl_client_cert_file_provider(pool))
|
||||
providers.append(client.svn_client_get_ssl_client_cert_pw_file_provider(pool))
|
||||
ctx.auth_baton = core.svn_auth_open(providers, pool)
|
||||
ctx.config = core.svn_config_get_config(None, pool)
|
||||
self.ctx = ctx
|
||||
|
||||
ra_callbacks = ra.svn_ra_callbacks_t()
|
||||
ra_callbacks.auth_baton = ctx.auth_baton
|
||||
self.ra_session = ra.svn_ra_open(self.rootpath, ra_callbacks, None,
|
||||
ctx.config, pool)
|
||||
self.youngest = ra.svn_ra_get_latest_revnum(self.ra_session, pool)
|
||||
self._dirent_cache = { }
|
||||
|
||||
def __del__(self):
|
||||
core.svn_pool_destroy(self.pool)
|
||||
core.apr_terminate()
|
||||
|
||||
def itemtype(self, path_parts, rev):
|
||||
path = self._getpath(path_parts[:-1])
|
||||
rev = self._getrev(rev)
|
||||
if not len(path_parts):
|
||||
return vclib.DIR
|
||||
dirents = self._get_dirents(path, rev)
|
||||
try:
|
||||
entry = dirents[path_parts[-1]]
|
||||
if entry.kind == core.svn_node_dir:
|
||||
return vclib.DIR
|
||||
if entry.kind == core.svn_node_file:
|
||||
return vclib.FILE
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
|
||||
def openfile(self, path_parts, rev):
|
||||
rev = self._getrev(rev)
|
||||
url = self.rootpath
|
||||
if len(path_parts):
|
||||
url = self.rootpath + '/' + self._getpath(path_parts)
|
||||
tmp_file = tempfile.mktemp()
|
||||
stream = core.svn_stream_from_aprfile(tmp_file, self.pool)
|
||||
### rev here should be the last history revision of the URL
|
||||
client.svn_client_cat(core.Stream(stream), url,
|
||||
_rev2optrev(rev), self.ctx, self.pool)
|
||||
core.svn_stream_close(stream)
|
||||
return SelfCleanFP(tmp_file), rev
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
path = self._getpath(path_parts)
|
||||
rev = self._getrev(rev)
|
||||
entries = [ ]
|
||||
dirents = self._get_dirents(path, rev)
|
||||
for name in dirents.keys():
|
||||
entry = dirents[name]
|
||||
if entry.kind == core.svn_node_dir:
|
||||
kind = vclib.DIR
|
||||
elif entry.kind == core.svn_node_file:
|
||||
kind = vclib.FILE
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
get_logs(self, self._getpath(path_parts), self._getrev(rev), entries)
|
||||
|
||||
def itemlog(self, path_parts, rev, options):
|
||||
full_name = self._getpath(path_parts)
|
||||
rev = self._getrev(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(full_name, options.get('svn_show_all_dir_logs', 0))
|
||||
dir_url = self.rootpath
|
||||
if full_name:
|
||||
dir_url = dir_url + '/' + full_name
|
||||
|
||||
cross_copies = options.get('svn_cross_copies', 0)
|
||||
client.svn_client_log([dir_url], _rev2optrev(rev), _rev2optrev(1),
|
||||
1, not cross_copies, lc.add_log,
|
||||
self.ctx, self.pool)
|
||||
revs = lc.logs
|
||||
revs.sort()
|
||||
prev = None
|
||||
for rev in revs:
|
||||
rev.prev = prev
|
||||
prev = rev
|
||||
|
||||
return revs
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
path = self._getpath(path_parts)
|
||||
rev = self._getrev(rev)
|
||||
url = self.rootpath + (path and '/' + path)
|
||||
|
||||
blame_data = []
|
||||
|
||||
def _blame_cb(line_no, revision, author, date,
|
||||
line, pool, blame_data=blame_data):
|
||||
prev_rev = None
|
||||
if revision > 1:
|
||||
prev_rev = revision - 1
|
||||
blame_data.append(_item(text=line, line_number=line_no+1,
|
||||
rev=revision, prev_rev=prev_rev,
|
||||
author=author, date=None))
|
||||
|
||||
client.svn_client_blame(url, _rev2optrev(1), _rev2optrev(rev),
|
||||
_blame_cb, self.ctx, self.pool)
|
||||
|
||||
return blame_data, rev
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r1 = self._getrev(rev1)
|
||||
r2 = self._getrev(rev2)
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1, self.pool)
|
||||
temp2 = temp_checkout(self, p2, r2, self.pool)
|
||||
info1 = p1, date_from_rev(self, r1), r1
|
||||
info2 = p2, date_from_rev(self, r2), r2
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2, args)
|
||||
except vclib.svn.core.SubversionException, e:
|
||||
if e.apr_err == vclib.svn.core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.InvalidRevision
|
||||
raise
|
||||
|
||||
def _getpath(self, path_parts):
|
||||
return string.join(path_parts, '/')
|
||||
|
||||
def _getrev(self, rev):
|
||||
if rev is None or rev == 'HEAD':
|
||||
return self.youngest
|
||||
try:
|
||||
rev = int(rev)
|
||||
except ValueError:
|
||||
raise vclib.InvalidRevision(rev)
|
||||
if (rev < 0) or (rev > self.youngest):
|
||||
raise vclib.InvalidRevision(rev)
|
||||
return rev
|
||||
|
||||
def _get_dirents(self, path, rev):
|
||||
if path:
|
||||
key = str(rev) + '/' + path
|
||||
dir_url = self.rootpath + '/' + path
|
||||
else:
|
||||
key = str(rev)
|
||||
dir_url = self.rootpath
|
||||
dirents = self._dirent_cache.get(key)
|
||||
if dirents:
|
||||
return dirents
|
||||
dirents = client.svn_client_ls(dir_url, _rev2optrev(rev), 0,
|
||||
self.ctx, self.pool)
|
||||
self._dirent_cache[key] = dirents
|
||||
return dirents
|
||||
|
||||
|
||||
class _item:
|
||||
def __init__(self, **kw):
|
||||
vars(self).update(kw)
|
3115
lib/viewvc.py
3115
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-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
|
@@ -1,82 +0,0 @@
|
||||
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.
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 10 KiB |
@@ -1,109 +0,0 @@
|
||||
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. Also, drop a copy of the
|
||||
archive files into the root directory of the viewvc.org website
|
||||
(unversioned).
|
||||
|
||||
15. On trunk, update the websites (both the viewvc.org/ and www/ ones)
|
||||
to refer to the new release files, and copy the CHANGES for the
|
||||
new release into trunk's CHANGES file.
|
||||
|
||||
16. Edit the file 'lib/viewvc.py' again, incrementing the patch number
|
||||
assigned to the __version__ variable. Add a new empty block in
|
||||
the branch's CHANGES file. Commit your changes:
|
||||
|
||||
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.)
|
||||
|
||||
18. Send to the announce@ list a message explaining all the cool new
|
||||
features, and post similar announcements to other places interested
|
||||
in this sort of stuff, such as Freshmeat (http://www.freshmeat.net).
|
@@ -1,78 +0,0 @@
|
||||
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
|
43
templates/annotate.ezt
Normal file
43
templates/annotate.ezt
Normal file
@@ -0,0 +1,43 @@
|
||||
[# setup page definitions][define page_title]Annotation of /[where][end][define help_href][docroot]/help_rootview.html[end][# end][include "include/header.ezt" "annotate"]
|
||||
[include "include/file_header.ezt"]
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
Revision [if-any revision_href]<a href="[revision_href]"><strong>[rev]</strong></a>[else]<strong>[rev]</strong>[end] -
|
||||
(<a href="[view_href]"><strong>view</strong></a>)
|
||||
(<a href="[download_href]"><strong>download</strong></a>)
|
||||
[if-any download_text_href](<a href="[download_text_href]"><strong>as text</strong></a>)[end]
|
||||
[if-any orig_path]
|
||||
<br />Original Path: <a href="[orig_href]"><em>[orig_path]</em></a>
|
||||
[end]
|
||||
</p>
|
||||
|
||||
|
||||
[define class1]vc_row_even[end]
|
||||
[define class2]vc_row_odd[end]
|
||||
[define last_rev]0[end]
|
||||
[define rowclass][class1][end]
|
||||
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
[for lines]
|
||||
[is lines.rev last_rev]
|
||||
[else]
|
||||
[is rowclass class1]
|
||||
[define rowclass][class2][end]
|
||||
[else]
|
||||
[define rowclass][class1][end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<tr class="[rowclass]" id="l[lines.line_number]">
|
||||
<td class="vc_blame_line">[lines.line_number] :</td>
|
||||
<td class="vc_blame_author">[is lines.rev last_rev] [else][lines.author][end]</td>
|
||||
<td class="vc_blame_rev">[is lines.rev last_rev] [else][if-any lines.diff_url]<a href="[lines.diff_url]">[end][lines.rev][if-any lines.diff_url]</a>[end][end]</td>
|
||||
<td class="vc_blame_text">[lines.text]</td>
|
||||
</tr>
|
||||
[define last_rev][lines.rev][end]
|
||||
[end]
|
||||
</table>
|
||||
|
||||
[include "include/footer.ezt"]
|
@@ -12,24 +12,19 @@
|
||||
<pre class="vc_raw_diff">[raw_diff]</pre>
|
||||
[end]
|
||||
|
||||
[define left_view_href][if-any left.prefer_markup][left.view_href][else][if-any left.download_href][left.download_href][end][end][end]
|
||||
[define right_view_href][if-any right.prefer_markup][right.view_href][else][if-any right.download_href][right.download_href][end][end][end]
|
||||
|
||||
[if-any changes]
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr class="vc_diff_header">
|
||||
<th style="width:6%;"></th>
|
||||
<th style="width:47%; vertical-align:top;">
|
||||
[is left.path right.path][else][left.path][end]
|
||||
revision [if-any left_view_href]<a href="[left_view_href]">[end][left.rev][if-any left_view_href]</a>[end][if-any left.author] by <em>[left.author]</em>[end],
|
||||
[left.date]
|
||||
[if-any left.tag]<br />Tag: [left.tag][end]
|
||||
[is path_left path_right][else][path_left][end]
|
||||
revision [rev_left], [date_left]
|
||||
[if-any tag_left]<br />Tag: [tag_left][end]
|
||||
</th>
|
||||
<th style="width:47%; vertical-align:top;">
|
||||
[is left.path right.path][else][right.path][end]
|
||||
revision [if-any right_view_href]<a href="[right_view_href]">[end][right.rev][if-any right_view_href]</a>[end][if-any right.author] by <em>[right.author]</em>[end],
|
||||
[right.date]
|
||||
[if-any right.tag]<br />Tag: [right.tag][end]
|
||||
[is path_left path_right][else][path_right][end]
|
||||
revision [rev_right], [date_right]
|
||||
[if-any tag_right]<br />Tag: [tag_right][end]
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -49,7 +44,7 @@
|
||||
[else]
|
||||
[is changes.type "add"]
|
||||
<tr>
|
||||
<td class="vc_diff_line_number" 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 id="l[changes.line_number]">[if-any annotate_href]<a href="[annotate_href]#l[changes.line_number]">[changes.line_number]</a>[else][changes.line_number][end]</td>
|
||||
<td class="vc_diff_empty"> </td>
|
||||
<td class="vc_diff_add"> [changes.right]</td>
|
||||
</tr>
|
||||
@@ -64,7 +59,7 @@
|
||||
[is changes.type "change"]
|
||||
<tr>
|
||||
[if-any changes.have_right]
|
||||
<td class="vc_diff_line_number" 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 id="l[changes.line_number]">[if-any annotate_href]<a href="[annotate_href]#l[changes.line_number]">[changes.line_number]</a>[else][changes.line_number][end]</td>
|
||||
[else]
|
||||
<td></td>
|
||||
[end]
|
||||
@@ -112,7 +107,7 @@
|
||||
</tr>
|
||||
[else]
|
||||
<tr>
|
||||
<td class="vc_diff_line_number" 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 id="l[changes.line_number]">[if-any annotate_href]<a href="[annotate_href]#l[changes.line_number]">[changes.line_number]</a>[else][changes.line_number][end]</td>
|
||||
<td class="vc_diff_nochange"> [changes.left]</td>
|
||||
<td class="vc_diff_nochange"> [changes.right]</td>
|
||||
</tr>
|
||||
@@ -134,12 +129,12 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
[is left.path right.path][else][left.path][end]
|
||||
Revision [left.rev]
|
||||
[is path_left path_right][else][path_left][end]
|
||||
Revision [rev_left]
|
||||
</th>
|
||||
<th colspan="2">
|
||||
[is left.path right.path][else][right.path][end]
|
||||
Revision [right.rev]
|
||||
[is path_left path_right][else][path_right][end]
|
||||
Revision [rev_right]
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -165,8 +160,8 @@
|
||||
<table class="vc_idiff">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>r[left.rev]</th>
|
||||
<th>r[right.rev]</th>
|
||||
<th>r[rev_left]</th>
|
||||
<th>r[rev_right]</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -196,11 +191,10 @@
|
||||
<td>
|
||||
<form method="get" action="[diff_format_action]">
|
||||
<div>
|
||||
[for diff_format_hidden_values]<input type="hidden" name="[diff_format_hidden_values.name]" value="[diff_format_hidden_values.value]"/>[end]
|
||||
[diff_format_hidden_values]
|
||||
<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>
|
||||
@@ -218,7 +212,7 @@
|
||||
<td>Legend:<br />
|
||||
<table cellspacing="0" cellpadding="1">
|
||||
<tr>
|
||||
<td style="text-align:center;" class="vc_diff_remove">Removed from v.[left.rev]</td>
|
||||
<td style="text-align:center;" class="vc_diff_remove">Removed from v.[rev_left]</td>
|
||||
<td class="vc_diff_empty"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -226,7 +220,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="vc_diff_empty"> </td>
|
||||
<td style="text-align:center;" class="vc_diff_add">Added in v.[right.rev]</td>
|
||||
<td style="text-align:center;" class="vc_diff_add">Added in v.[rev_right]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
@@ -1,34 +1,25 @@
|
||||
[include "include/dir_header.ezt"]
|
||||
|
||||
<table cellspacing="1" cellpadding="2" class="fixed">
|
||||
<table cellspacing="1" cellpadding="2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 200px" class="vc_header[is sortby "file"]_sort[end]">
|
||||
[if-any sortby_file_href]<a href="[sortby_file_href]#dirlist">File</a>[else]File[end]
|
||||
<th class="vc_header[is sortby "file"]_sort[end]" colspan="2">
|
||||
<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 style="width: 96px" class="vc_header"></th>
|
||||
[if-any sortby_rev_href]
|
||||
<th class="vc_header[is sortby "rev"]_sort[end]">
|
||||
<a href="[sortby_rev_href]#dirlist">Last Change</a>
|
||||
<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]
|
||||
[else]
|
||||
<th class="vc_header[is sortby "date"]_sort[end]">
|
||||
[if-any sortby_date_href]<a href="[sortby_date_href]#dirlist">Last Change</a>[else]Last Change[end]
|
||||
[is sortby "date"]
|
||||
<img class="vc_sortarrow" alt="[is sortdir "down"](date)[end]"
|
||||
width="13" height="13"
|
||||
src="[docroot]/images/[is sortdir "up"]up[else]down[end].png" />
|
||||
[end]
|
||||
[end]
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -36,92 +27,79 @@
|
||||
<tbody>
|
||||
[if-any up_href]
|
||||
<tr class="vc_row_odd">
|
||||
<td style="width: 200px">
|
||||
<td>
|
||||
<a href="[up_href]">
|
||||
<img src="[docroot]/images/back_small.png" alt="" class="vc_icon"
|
||||
<img src="[docroot]/images/back_small.png" alt="" width="16" height="16"
|
||||
/> Parent Directory</a>
|
||||
</td>
|
||||
<td style="width: 96px; font-size: 0;"></td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
[end]
|
||||
[for entries]
|
||||
<tr class="vc_row_[if-index entries even]even[else]odd[end]">
|
||||
<td style="width: 200px">
|
||||
<td>
|
||||
<a name="[entries.anchor]" href="[is entries.pathtype "dir"][entries.view_href][else][if-any entries.prefer_markup][entries.view_href][else][entries.download_href][end][end]" 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" />
|
||||
<img src="[docroot]/images/[is entries.pathtype "dir"]dir[else][is entries.state "dead"]broken[else]text[end][end].png" alt="" width="16" height="16" />
|
||||
[entries.name][is entries.pathtype "dir"]/[end]</a>
|
||||
[if-any entries.lockinfo]<img src="[docroot]/images/lock.png" alt="locked" class="vc_icon" title="Locked by [entries.lockinfo]" />[end]
|
||||
[is entries.state "dead"](dead)[end]
|
||||
</td>
|
||||
|
||||
[if-any entries.errors]
|
||||
<td colspan="2">[for entries.errors]<em>[entries.errors]</em>[end]</td>
|
||||
[else]
|
||||
[define view_icon_link][end]
|
||||
[define graph_icon_link][end]
|
||||
[define download_icon_link][end]
|
||||
[define annotate_icon_link][end]
|
||||
[define log_icon_link][if-any entries.log_href]<a
|
||||
href="[entries.log_href]"
|
||||
title="View Log"><img
|
||||
src="[docroot]/images/log.png"
|
||||
alt="View Log"
|
||||
class="vc_icon" /></a>[end][end]
|
||||
<td style="width:1%; white-space: nowrap">
|
||||
|
||||
[is entries.pathtype "dir"]
|
||||
[is roottype "cvs"]
|
||||
[# no point in showing icon when there's only one to choose from]
|
||||
[else]
|
||||
[define view_icon_link]<a
|
||||
href="[entries.view_href]"
|
||||
title="View Directory Listing"><img
|
||||
src="[docroot]/images/list.png"
|
||||
alt="View Directory Listing"
|
||||
class="vc_icon" /></a>[end]
|
||||
[# Icon column. We might want to add more icons like a tarball
|
||||
# icon for directories or a diff to previous icon for files. ]
|
||||
|
||||
[if-any entries.log_href]
|
||||
<a href="[entries.log_href]"><img
|
||||
src="[docroot]/images/log.png"
|
||||
alt="View Log" width="16" height="16" /></a>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[is entries.pathtype "file"]
|
||||
[define view_icon_link][if-any entries.view_href]<a
|
||||
href="[entries.view_href]"
|
||||
title="View File"><img
|
||||
src="[docroot]/images/view.png"
|
||||
alt="View File"
|
||||
class="vc_icon" /></a>[end][end]
|
||||
[is entries.pathtype "dir"]
|
||||
[is roottype "cvs"]
|
||||
[# no point in showing icon when there's only one to choose from]
|
||||
[else]
|
||||
<a href="[entries.view_href]"><img
|
||||
src="[docroot]/images/list.png"
|
||||
alt="View Directory Listing" width="16" height="16" /></a>
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[define graph_icon_link][if-any entries.graph_href]<a
|
||||
href="[entries.graph_href]"
|
||||
[is entries.pathtype "file"]
|
||||
[if-any entries.graph_href]
|
||||
<a href="[entries.graph_href]"
|
||||
title="View Revision Graph"><img
|
||||
src="[docroot]/images/cvsgraph_16x16.png"
|
||||
alt="View Revision Graph"
|
||||
class="vc_icon" /></a>[end][end]
|
||||
src="[docroot]/images/cvsgraph_16x16.png"
|
||||
alt="View Revision Graph" width="16" height="16" />
|
||||
</a>
|
||||
[end]
|
||||
|
||||
[define download_icon_link][if-any entries.download_href]<a
|
||||
href="[entries.download_href]"
|
||||
title="Download File"><img
|
||||
src="[docroot]/images/download.png"
|
||||
alt="Download File"
|
||||
class="vc_icon" /></a>[end][end]
|
||||
[if-any entries.view_href]
|
||||
<a href="[entries.view_href]"><img
|
||||
src="[docroot]/images/view.png"
|
||||
alt="View File" width="16" height="16" /></a>
|
||||
[end]
|
||||
|
||||
[define annotate_icon_link][if-any entries.annotate_href]<a
|
||||
href="[entries.annotate_href]"
|
||||
title="Annotate File"><img
|
||||
src="[docroot]/images/annotate.png"
|
||||
alt="Annotate File"
|
||||
class="vc_icon" /></a>[end][end]
|
||||
[end]
|
||||
|
||||
<td style="width: 96px"
|
||||
>[# Icon column. We might want to add more icons like a tarball
|
||||
# icon for directories or a diff to previous icon for files.
|
||||
# Make sure this sucker has no whitespace in it, or the fixed
|
||||
# widthness of will suffer for large font sizes
|
||||
][log_icon_link][view_icon_link][graph_icon_link][download_icon_link][annotate_icon_link]</td>
|
||||
[if-any entries.download_href]
|
||||
<a href="[entries.download_href]"><img
|
||||
src="[docroot]/images/download.png"
|
||||
alt="Download File" width="16" height="16" /></a>
|
||||
[end]
|
||||
|
||||
[if-any entries.annotate_href]
|
||||
<a href="[entries.annotate_href]"><img
|
||||
src="[docroot]/images/annotate.png"
|
||||
alt="Annotate File" width="16" height="16" /></a>
|
||||
[end]
|
||||
[end]
|
||||
</td>
|
||||
<td>
|
||||
[if-any entries.rev]
|
||||
<strong>[if-any entries.revision_href]<a href="[entries.revision_href]" title="Revision [entries.rev]">[entries.rev]</a>[else][entries.rev][end]</strong>
|
||||
<strong>[if-any entries.revision_href]<a href="[entries.revision_href]">[entries.rev]</a>[else][entries.rev][end]</strong>
|
||||
([entries.ago] ago)
|
||||
by <em>[entries.author]</em>:
|
||||
[entries.log]
|
||||
|
@@ -4,45 +4,50 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="vc_header[is sortby "file"]_sort[end]" colspan="2">
|
||||
[if-any sortby_file_href]<a href="[sortby_file_href]#dirlist">File</a>[else]File[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]">
|
||||
[if-any sortby_rev_href]<a href="[sortby_rev_href]#dirlist">Rev.</a>[else]Rev.[end]
|
||||
<a href="[sortby_rev_href]#dirlist">Rev.
|
||||
[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>
|
||||
<th class="vc_header[is sortby "date"]_sort[end]">
|
||||
[if-any sortby_date_href]<a href="[sortby_date_href]#dirlist">Age</a>[else]Age[end]
|
||||
<a href="[sortby_date_href]#dirlist">Age
|
||||
[is sortby "date"]
|
||||
<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 "author"]_sort[end]">
|
||||
[if-any sortby_author_href]<a href="[sortby_author_href]#dirlist">Author</a>[else]Author[end]
|
||||
<a href="[sortby_author_href]#dirlist">Author
|
||||
[is sortby "author"]
|
||||
<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>
|
||||
[is cfg.options.show_logs "1"]
|
||||
<th class="vc_header[is sortby "log"]_sort[end]">
|
||||
[if-any sortby_log_href]<a href="[sortby_log_href]#dirlist">Last log entry</a>[else]Last log entry[end]
|
||||
<a href="[sortby_log_href]#dirlist">Last log entry
|
||||
[is sortby "log"]
|
||||
<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>
|
||||
[end]
|
||||
</tr>
|
||||
@@ -53,7 +58,7 @@
|
||||
<tr class="vc_row_odd">
|
||||
<td colspan="2">
|
||||
<a href="[up_href]">
|
||||
<img src="[docroot]/images/back_small.png" alt="" class="vc_icon"
|
||||
<img src="[docroot]/images/back_small.png" alt="" width="16" height="16"
|
||||
/> Parent Directory</a>
|
||||
</td>
|
||||
<td> </td>
|
||||
@@ -72,7 +77,7 @@
|
||||
[else]
|
||||
<a name="[entries.anchor]" href="[entries.log_href]" title="View file revision log">
|
||||
[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" />
|
||||
<img src="[docroot]/images/[is entries.pathtype "dir"]dir[else][is entries.state "dead"]broken[else]text[end][end].png" alt="" width="16" height="16" />
|
||||
[entries.name][is entries.pathtype "dir"]/[end]</a>
|
||||
[is entries.state "dead"](dead)[end]
|
||||
</td>
|
||||
@@ -80,7 +85,7 @@
|
||||
<td style="width:1%"><a href="[entries.graph_href]"
|
||||
title="View Revision Graph"><img
|
||||
src="[docroot]/images/cvsgraph_16x16.png"
|
||||
alt="View Revision Graph" class="vc_icon" />
|
||||
alt="View Revision Graph" width="16" height="16" />
|
||||
</a></td>
|
||||
[end]
|
||||
[if-any entries.errors]
|
||||
@@ -91,16 +96,13 @@
|
||||
[is entries.pathtype "dir"]
|
||||
<td> [if-any entries.rev]<a href="[entries.log_href]" title="View directory revision log"><strong>[entries.rev]</strong></a>[end]</td>
|
||||
[else]
|
||||
[define rev_href][if-any entries.prefer_markup][entries.view_href][else][if-any entries.download_href][entries.download_href][end][end][end]
|
||||
<td style="white-space: nowrap;"> [if-any entries.rev][if-any rev_href]<a href="[rev_href]" title="[if-any entries.prefer_markup]View[else]Download[end] file contents">[end]<strong>[entries.rev]</strong>[if-any rev_href]</a>[end][end]
|
||||
[if-any entries.lockinfo]<img src="[docroot]/images/lock.png" alt="locked" class="vc_icon" title="Locked by [entries.lockinfo]" />[end]
|
||||
</td>
|
||||
<td> [if-any entries.rev]<a href="[if-any entries.prefer_markup][entries.view_href][else][entries.download_href][end]" title="[if-any entries.prefer_markup]View[else]Download[end] file contents"><strong>[entries.rev]</strong></a>[end]</td>
|
||||
[end]
|
||||
<td> [entries.ago]</td>
|
||||
<td> [entries.author]</td>
|
||||
[is cfg.options.show_logs "1"]
|
||||
[if-any entries.short_log]
|
||||
<td> [entries.short_log][is entries.pathtype "dir"][is roottype "cvs"]
|
||||
[if-any entries.log]
|
||||
<td> [entries.log][is entries.pathtype "dir"][is roottype "cvs"]
|
||||
<em>(from [entries.log_file]/[entries.log_rev])</em>[end][end]</td>
|
||||
[else]
|
||||
<td> </td>
|
||||
|
@@ -4,23 +4,31 @@
|
||||
<head>
|
||||
<title>ViewVC Help: Directory View</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div><a href="http://viewvc.org/index.html"><img src="images/viewvc-logo.png" alt="ViewVC logotype" /></a></div>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td><a href="http://viewvc.org/index.html"><img
|
||||
src="images/logo.png" alt="ViewVC logotype" /></a>
|
||||
</td>
|
||||
<td>
|
||||
<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 />
|
||||
<a href="help_query.html">Query Database</a><br />
|
||||
</td><td>
|
||||
|
||||
<h1>ViewVC Help: Directory View</h1>
|
||||
<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
|
||||
|
@@ -4,24 +4,32 @@
|
||||
<head>
|
||||
<title>ViewVC Help: Log View</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div><a href="http://viewvc.org/index.html"><img src="images/viewvc-logo.png" alt="ViewVC logotype" /></a></div>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td><a href="http://viewvc.org/index.html"><img
|
||||
src="images/logo.png" alt="ViewVC logotype" /></a>
|
||||
</td>
|
||||
<td>
|
||||
<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 />
|
||||
<a href="help_query.html">Query Database</a><br />
|
||||
</td><td>
|
||||
|
||||
<h1>ViewVC Help: Log View</h1>
|
||||
|
||||
<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
|
||||
|
@@ -4,59 +4,64 @@
|
||||
<head>
|
||||
<title>ViewVC Help: Query The Commit Database</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div><a href="http://viewvc.org/index.html"><img src="images/viewvc-logo.png" alt="ViewVC logotype" /></a></div>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr><td>
|
||||
<h3>Help:</h3>
|
||||
<a href="help_rootview.html">General</a><br />
|
||||
<a href="help_dirview.html">Directory View</a><br />
|
||||
<a href="help_log.html">Log View</a><br />
|
||||
<strong>Query Database</strong>
|
||||
</td><td>
|
||||
|
||||
<h1>ViewVC Help: Query The Commit Database</h1>
|
||||
|
||||
<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>
|
||||
<hr />
|
||||
<address><a href="mailto:users@viewvc.tigris.org">ViewVC Users Mailinglist</a></address>
|
||||
</body>
|
||||
</html>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td><a href=".."><img
|
||||
src="images/logo.png" alt="ViewVC logotype" /></a>
|
||||
</td>
|
||||
<td><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>
|
||||
|
@@ -4,152 +4,164 @@
|
||||
<head>
|
||||
<title>ViewVC Help: General</title>
|
||||
<link rel="stylesheet" href="help.css" type="text/css" />
|
||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<div><a href="http://viewvc.org/index.html"><img src="images/viewvc-logo.png" alt="ViewVC logotype" /></a></div>
|
||||
<table>
|
||||
<col class="menu" />
|
||||
<col />
|
||||
<tr>
|
||||
<td><a href=".."><img
|
||||
src="images/logo.png" alt="ViewVC logotype" /></a>
|
||||
</td>
|
||||
<td>
|
||||
<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 />
|
||||
<a href="help_query.html">Query Database</a><br />
|
||||
</td><td>
|
||||
|
||||
<h1>ViewVC Help: General</h1>
|
||||
<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>
|
||||
|
||||
<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="multiple-repositories"><strong>Root Listing
|
||||
View</strong></a> - Show a list of repositories configured for
|
||||
display in ViewVC.</li>
|
||||
|
||||
<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-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. <em>This view is disabled in the default ViewVC
|
||||
configuration.</em></li>
|
||||
|
||||
<li><a name="view-annotate"><a name="view-markup"><strong>File
|
||||
Contents View</strong></a></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. This view can optionally show line-based
|
||||
annotation data for the file, containing the revision number
|
||||
where each line 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="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>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 303 B |
BIN
templates/docroot/images/logo.png
Normal file
BIN
templates/docroot/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.2 KiB |
@@ -6,7 +6,6 @@
|
||||
html, body {
|
||||
color: #000000;
|
||||
background-color: #ffffff;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
a:link { color: #0000ff; }
|
||||
@@ -22,36 +21,12 @@ table {
|
||||
table.auto {
|
||||
width: auto;
|
||||
}
|
||||
table.fixed {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
table.fixed td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
tr, td, th { vertical-align: top; }
|
||||
th { white-space: nowrap; }
|
||||
form { margin: 0; }
|
||||
|
||||
|
||||
/*** Icons ***/
|
||||
.vc_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: none;
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
|
||||
/*** Navigation Headers ***/
|
||||
/** Navigation Headers ***/
|
||||
.vc_navheader {
|
||||
background-color: #cccccc;
|
||||
padding: .25em;
|
||||
}
|
||||
.vc_navheader .pathdiv {
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,10 +47,7 @@ form { margin: 0; }
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.vc_row_odd {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.vc_row_special {
|
||||
background-color: #ffff7f;
|
||||
background-color: #ccccee;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,102 +62,44 @@ form { margin: 0; }
|
||||
}
|
||||
|
||||
|
||||
/*** Properties Listing ***/
|
||||
.vc_properties {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
|
||||
/*** File Content Markup Styles ***/
|
||||
/*** Markup Summary Header ***/
|
||||
.vc_summary {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
#vc_file td {
|
||||
|
||||
/*** Highlight Markup Styles ***/
|
||||
#vc_markup .num { color: #000000; }
|
||||
#vc_markup .esc { color: #bd8d8b; }
|
||||
#vc_markup .str { color: #bd8d8b; }
|
||||
#vc_markup .dstr { color: #bd8d8b; }
|
||||
#vc_markup .slc { color: #ac2020; font-style: italic; }
|
||||
#vc_markup .com { color: #ac2020; font-style: italic; }
|
||||
#vc_markup .dir { color: #000000; }
|
||||
#vc_markup .sym { color: #000000; }
|
||||
#vc_markup .line { color: #555555; }
|
||||
#vc_markup .kwa { color: #9c20ee; font-weight: bold; }
|
||||
#vc_markup .kwb { color: #208920; }
|
||||
#vc_markup .kwc { color: #0000ff; }
|
||||
#vc_markup .kwd { color: #404040; }
|
||||
|
||||
/*** Py2html Markup Styles ***/
|
||||
#vc_markup .PY_STRING { color: #bd8d8b; }
|
||||
#vc_markup .PY_COMMENT { color: #ac2020; font-style: italic; }
|
||||
#vc_markup .PY_KEYWORD { color: #9c20ee; font-weight: bold; }
|
||||
#vc_markup .PY_IDENTIFIER { color: #404040; }
|
||||
|
||||
/*** Line numbers outputted by highlight colorizer ***/
|
||||
.line {
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
border-right-color: #505050;
|
||||
padding: 1px;
|
||||
background-color: #eeeeee;
|
||||
color: #505050;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
.vc_file_line_number {
|
||||
border-right-width: 1px;
|
||||
background-color: #eeeeee;
|
||||
color: #505050;
|
||||
text-align: right;
|
||||
}
|
||||
.vc_file_line_author, .vc_file_line_rev {
|
||||
border-right-width: 1px;
|
||||
text-align: right;
|
||||
}
|
||||
.vc_file_line_text {
|
||||
border-right-width: 0px;
|
||||
background-color: white;
|
||||
font-family: monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
width: 100%;
|
||||
}
|
||||
.pygments-c { color: #408080; font-style: italic } /* Comment */
|
||||
.pygments-err { border: 1px solid #FF0000 } /* Error */
|
||||
.pygments-k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.pygments-o { color: #666666 } /* Operator */
|
||||
.pygments-cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||
.pygments-cp { color: #BC7A00 } /* Comment.Preproc */
|
||||
.pygments-c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||
.pygments-cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||
.pygments-gd { color: #A00000 } /* Generic.Deleted */
|
||||
.pygments-ge { font-style: italic } /* Generic.Emph */
|
||||
.pygments-gr { color: #FF0000 } /* Generic.Error */
|
||||
.pygments-gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.pygments-gi { color: #00A000 } /* Generic.Inserted */
|
||||
.pygments-go { color: #808080 } /* Generic.Output */
|
||||
.pygments-gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.pygments-gs { font-weight: bold } /* Generic.Strong */
|
||||
.pygments-gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.pygments-gt { color: #0040D0 } /* Generic.Traceback */
|
||||
.pygments-kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.pygments-kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.pygments-kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.pygments-kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.pygments-kt { color: #B00040 } /* Keyword.Type */
|
||||
.pygments-m { color: #666666 } /* Literal.Number */
|
||||
.pygments-s { color: #BA2121 } /* Literal.String */
|
||||
.pygments-na { color: #7D9029 } /* Name.Attribute */
|
||||
.pygments-nb { color: #008000 } /* Name.Builtin */
|
||||
.pygments-nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.pygments-no { color: #880000 } /* Name.Constant */
|
||||
.pygments-nd { color: #AA22FF } /* Name.Decorator */
|
||||
.pygments-ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||
.pygments-ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||
.pygments-nf { color: #0000FF } /* Name.Function */
|
||||
.pygments-nl { color: #A0A000 } /* Name.Label */
|
||||
.pygments-nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.pygments-nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.pygments-nv { color: #19177C } /* Name.Variable */
|
||||
.pygments-ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.pygments-w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.pygments-mf { color: #666666 } /* Literal.Number.Float */
|
||||
.pygments-mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.pygments-mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.pygments-mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.pygments-sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.pygments-sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.pygments-sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.pygments-s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.pygments-se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||
.pygments-sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.pygments-si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||
.pygments-sx { color: #008000 } /* Literal.String.Other */
|
||||
.pygments-sr { color: #BB6688 } /* Literal.String.Regex */
|
||||
.pygments-s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.pygments-ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.pygments-bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.pygments-vc { color: #19177C } /* Name.Variable.Class */
|
||||
.pygments-vg { color: #19177C } /* Name.Variable.Global */
|
||||
.pygments-vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.pygments-il { color: #666666 } /* Literal.Number.Integer.Long */
|
||||
|
||||
|
||||
/*** Diff Styles ***/
|
||||
.vc_diff_header {
|
||||
@@ -226,15 +140,13 @@ form { margin: 0; }
|
||||
font-family: sans-serif;
|
||||
font-size: smaller;
|
||||
}
|
||||
.vc_diff_line_number {
|
||||
}
|
||||
.vc_raw_diff {
|
||||
background-color: #cccccc;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
|
||||
/*** Intraline Diff Styles ***/
|
||||
|
||||
.vc_idiff_add {
|
||||
background-color: #aaffaa;
|
||||
}
|
||||
@@ -247,6 +159,7 @@ form { margin: 0; }
|
||||
.vc_idiff_empty {
|
||||
background-color:#e0e0e0;
|
||||
}
|
||||
|
||||
table.vc_idiff col.content {
|
||||
width: 50%;
|
||||
}
|
||||
@@ -264,6 +177,21 @@ table.vc_idiff tbody th {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
/*** Annotate Styles ***/
|
||||
.vc_blame_line, .vc_blame_author, .vc_blame_rev {
|
||||
font-family: monospace;
|
||||
font-size: smaller;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.vc_blame_text {
|
||||
font-family: monospace;
|
||||
font-size: smaller;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*** Query Form ***/
|
||||
.vc_query_form {
|
||||
|
@@ -9,43 +9,16 @@
|
||||
<h3>An Exception Has Occurred</h3>
|
||||
|
||||
[if-any msg]
|
||||
<p>[msg]</p>
|
||||
<p><pre>[msg]</pre></p>
|
||||
[end]
|
||||
|
||||
[if-any status]
|
||||
<h4>HTTP Response Status</h4>
|
||||
<p><pre>[status]</pre></p>
|
||||
<hr />
|
||||
[end]
|
||||
|
||||
[if-any msg][else]
|
||||
<h4>Python Traceback</h4>
|
||||
<p><pre>
|
||||
[stacktrace]
|
||||
</pre></p>
|
||||
[end]
|
||||
|
||||
[# Here follows a bunch of space characters, present to ensure that
|
||||
our error message is larger than 512 bytes so that IE's "Friendly
|
||||
Error Message" won't show. For more information, see
|
||||
http://oreillynet.com/onjava/blog/2002/09/internet_explorer_subverts_err.html]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,123 +0,0 @@
|
||||
[# ------------------------------------------------------------------------- ]
|
||||
[# CUSTOMIZE ME: To avoid displaying "binary garbage" -- the contents of ]
|
||||
[# files with non-human-readable file formats -- change the value of the ]
|
||||
[# hide_binary_garbage variable below to 1. ]
|
||||
[# ------------------------------------------------------------------------- ]
|
||||
|
||||
[define hide_binary_garbage]0[end]
|
||||
|
||||
[# ------------------------------------------------------------------------- ]
|
||||
|
||||
[# setup page definitions]
|
||||
[define page_title]Contents of /[where][end]
|
||||
[define help_href][docroot]/help_rootview.html[end]
|
||||
[# end]
|
||||
|
||||
[include "include/header.ezt" "markup"]
|
||||
[include "include/file_header.ezt"]
|
||||
<hr />
|
||||
<div class="vc_summary">
|
||||
Revision [if-any revision_href]<a href="[revision_href]"><strong>[rev]</strong></a>[else]<strong>[rev]</strong>[end] -
|
||||
([is annotation "annotated"]<a href="[view_href]"><strong>hide annotations</strong></a>[else]<a href="[annotate_href]"><strong>show annotations</strong></a>[end])
|
||||
[if-any download_href](<a href="[download_href]"><strong>download</strong></a>)[end]
|
||||
[if-any download_text_href](<a href="[download_text_href]"><strong>as text</strong></a>)[end]
|
||||
|
||||
[if-any vendor_branch] <em>(vendor branch)</em>[end]
|
||||
<br /><em>[if-any date][date][else](unknown date)[end]</em>
|
||||
[if-any ago]([ago] ago)[end]
|
||||
by <em>[if-any author][author][else](unknown author)[end]</em>
|
||||
[if-any orig_path]
|
||||
<br />Original Path: <a href="[orig_href]"><em>[orig_path]</em></a>
|
||||
[end]
|
||||
|
||||
[if-any branches]
|
||||
<br />Branch: <strong>[branches]</strong>
|
||||
[end]
|
||||
[if-any tags]
|
||||
<br />CVS Tags: <strong>[tags]</strong>
|
||||
[end]
|
||||
[if-any branch_points]
|
||||
<br />Branch point for: <strong>[branch_points]</strong>
|
||||
[end]
|
||||
[is roottype "cvs"]
|
||||
[if-any changed]
|
||||
<br />Changes since <strong>[prev]: [changed] lines</strong>
|
||||
[end]
|
||||
[end]
|
||||
[if-any mime_type]
|
||||
<br />File MIME type: [mime_type]
|
||||
[end]
|
||||
[is roottype "svn"][if-any size]
|
||||
<br />File size: [size] byte(s)
|
||||
[end][end]
|
||||
[if-any lockinfo]
|
||||
<br />Lock status: <img src="[docroot]/images/lock.png" alt="Locked" width="16" height="16" /> [lockinfo]
|
||||
[end]
|
||||
[is annotation "binary"]
|
||||
<br /><strong>Unable to calculate annotation data on binary file contents.</strong>
|
||||
[end]
|
||||
[is annotation "error"]
|
||||
<br /><strong>Error occurred while calculating annotation data.</strong>
|
||||
[end]
|
||||
[is state "dead"]
|
||||
<br /><strong><em>FILE REMOVED</em></strong>
|
||||
[end]
|
||||
[if-any log]
|
||||
<pre class="vc_log">[log]</pre>
|
||||
[end]
|
||||
</div>
|
||||
|
||||
[if-any prefer_markup][define hide_binary_garbage]0[end][end]
|
||||
[if-any image_src_href][define hide_binary_garbage]0[end][end]
|
||||
|
||||
[is hide_binary_garbage "1"]
|
||||
<p><strong>This file's contents are not viewable.
|
||||
[if-any download_href]Please <a href="[download_href]">download</a>
|
||||
this version of the file in order to view it.[end]</strong></p>
|
||||
[else]
|
||||
|
||||
[define last_rev]0[end]
|
||||
[define rowclass]vc_row_even[end]
|
||||
|
||||
[if-any lines]
|
||||
|
||||
<div id="vc_file">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
[for lines]
|
||||
[is lines.rev last_rev]
|
||||
[else]
|
||||
[is lines.rev rev]
|
||||
[define rowclass]vc_row_special[end]
|
||||
[else]
|
||||
[is rowclass "vc_row_even"]
|
||||
[define rowclass]vc_row_odd[end]
|
||||
[else]
|
||||
[define rowclass]vc_row_even[end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<tr class="[rowclass]" id="l[lines.line_number]">
|
||||
<td class="vc_file_line_number">[lines.line_number]</td>
|
||||
[is annotation "annotated"]
|
||||
<td class="vc_file_line_author">[is lines.rev last_rev] [else][lines.author][end]</td>
|
||||
<td class="vc_file_line_rev">[is lines.rev last_rev] [else][if-any lines.diff_href]<a href="[lines.diff_href]">[end][lines.rev][if-any lines.diff_href]</a>[end][end]</td>
|
||||
[end]
|
||||
<td class="vc_file_line_text">[lines.text]</td>
|
||||
</tr>
|
||||
[define last_rev][lines.rev][end]
|
||||
[end]
|
||||
</table>
|
||||
</div>
|
||||
|
||||
[else]
|
||||
[if-any image_src_href]
|
||||
<div id="vc_file_image">
|
||||
<img src="[image_src_href]" alt="" />
|
||||
</div>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[include "include/props.ezt"]
|
||||
[include "include/footer.ezt"]
|
@@ -15,7 +15,7 @@
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
[for diff_select_hidden_values]<input type="hidden" name="[diff_select_hidden_values.name]" value="[diff_select_hidden_values.value]"/>[end]
|
||||
[diff_select_hidden_values]
|
||||
Diffs between
|
||||
[if-any tags]
|
||||
<select name="r1">
|
||||
@@ -55,7 +55,6 @@
|
||||
<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>
|
||||
|
@@ -1,9 +1,38 @@
|
||||
[# if you want to disable tarball generation remove the following: ]
|
||||
[if-any tarball_href]
|
||||
<hr/>
|
||||
<p style="margin:0;"><a href="[tarball_href]">Download GNU tarball</a></p>
|
||||
[if-any search_re_form]
|
||||
<hr />
|
||||
[# this table holds the selectors on the left, and reset on the right ]
|
||||
<table class="auto">
|
||||
<tr>
|
||||
<td>Show files containing the regular expression:</td>
|
||||
<td>
|
||||
<form method="get" action="[search_re_action]">
|
||||
<div>
|
||||
[search_re_hidden_values]
|
||||
<input type="text" name="search" value="[search_re]" />
|
||||
<input type="submit" value="Show" />
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
[if-any search_re]
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<form method="get" action="[search_tag_action]">
|
||||
<div>
|
||||
[search_tag_hidden_values]
|
||||
<input type="submit" value="Show all files" />
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
[end]
|
||||
</table>
|
||||
[end]
|
||||
|
||||
[include "props.ezt"]
|
||||
[include "footer.ezt"]
|
||||
[# if you want to disable tarball generation remove the following: ]
|
||||
[if-any tarball_href]
|
||||
<p style="margin:0;"><a href="[tarball_href]">Download GNU tarball</a></p>
|
||||
[end]
|
||||
|
||||
[include "footer.ezt"]
|
||||
|
@@ -24,34 +24,15 @@
|
||||
[is roottype "svn"]
|
||||
<tr>
|
||||
<td>Directory revision:</td>
|
||||
<td><a href="[tree_rev_href]" title="Revision [tree_rev]">[tree_rev]</a>[if-any youngest_rev] (of <a href="[youngest_rev_href]" title="Revision [youngest_rev]">[youngest_rev]</a>)[end]</td>
|
||||
<td><a href="[tree_rev_href]">[tree_rev]</a>[if-any youngest_rev] (of <a href="[youngest_rev_href]">[youngest_rev]</a>)[end]</td>
|
||||
</tr>
|
||||
[end]
|
||||
<tr>
|
||||
<td>Sticky [is roottype "cvs"]Tag[else]Revision[end]:</td>
|
||||
<td>[include "pathrev_form.ezt"]</td>
|
||||
</tr>
|
||||
|
||||
[if-any search_re_action]
|
||||
<tr>
|
||||
<td>Filter files by content:</td>
|
||||
<td><form method="get" action="[search_re_action]" style="display: inline;">
|
||||
<div style="display: 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 Regexp" />
|
||||
</div>
|
||||
</form>
|
||||
[if-any search_re]
|
||||
<form method="get" action="[search_re_action]" style="display: inline;">
|
||||
<div style="display: 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]
|
||||
</td>
|
||||
</tr>
|
||||
[if-any search_re]
|
||||
<tr><td>Current search:</td><td><strong>[search_re]</strong></td></tr>
|
||||
[end]
|
||||
|
||||
[if-any queryform_href]
|
||||
@@ -60,25 +41,24 @@
|
||||
<td><a href="[queryform_href]">Query revision history</a></td>
|
||||
</tr>
|
||||
[end]
|
||||
|
||||
</table>
|
||||
|
||||
[is picklist_len "0"]
|
||||
[else]
|
||||
[is picklist_len "1"]
|
||||
[is cfg.options.use_pagesize "0"]
|
||||
[else]
|
||||
<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]
|
||||
<input type="submit" value="Go to:" />
|
||||
<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>
|
||||
</form>
|
||||
[is picklist_len "1"]
|
||||
[else]
|
||||
<form method="get" action="[dir_paging_action]">
|
||||
[dir_paging_hidden_values]
|
||||
<input type="submit" value="Go to:" />
|
||||
<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>
|
||||
</form>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
<p><a name="dirlist"></a></p>
|
||||
|
||||
<hr />
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<p style="margin:0;">
|
||||
[is pathtype "file"]
|
||||
<a href="[up_href]"><img src="[docroot]/images/back_small.png" class="vc_icon" alt="Parent Directory" /> Parent Directory</a>
|
||||
<a href="[up_href]"><img src="[docroot]/images/back_small.png" width="16" height="16" alt="Parent Directory" /> Parent Directory</a>
|
||||
[if-any log_href]
|
||||
| <a href="[log_href]"><img src="[docroot]/images/log.png" class="vc_icon" alt="Revision Log" /> Revision Log</a>
|
||||
| <a href="[log_href][if-any log_href_rev]#rev[log_href_rev][end]"><img src="[docroot]/images/log.png" width="16" height="16" alt="Revision Log" /> Revision Log</a>
|
||||
[end]
|
||||
[if-any graph_href]
|
||||
| <a href="[graph_href]"><img src="[docroot]/images/cvsgraph_16x16.png" class="vc_icon" alt="View Revision Graph" /> Revision Graph</a>
|
||||
| <a href="[graph_href]"><img src="[docroot]/images/cvsgraph_16x16.png" width="16" height="16" alt="View Revision Graph" /> Revision Graph</a>
|
||||
[end]
|
||||
[is view "diff"]
|
||||
| <a href="[patch_href]"><img src="[docroot]/images/diff.png" class="vc_icon" alt="View Patch" /> Patch</a>
|
||||
| <a href="[patch_href]"><img src="[docroot]/images/diff.png" width="16" height="16" alt="View Patch" /> Patch</a>
|
||||
[end]
|
||||
[else]
|
||||
<a href="[view_href]"><img src="[docroot]/images/dir.png" class="vc_icon" alt="View Directory Listing" /> Directory Listing</a>
|
||||
<a href="[view_href]"><img src="[docroot]/images/dir.png" width="16" height="16" alt="View Directory Listing" /> Directory Listing</a>
|
||||
[end]
|
||||
</p>
|
||||
|
@@ -4,12 +4,12 @@
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>[if-any cfg.general.address]<address><a href="mailto:[cfg.general.address]">[cfg.general.address]</a></address>[else] [end]</td>
|
||||
<td style="text-align: right;">[if-any help_href]<strong><a href="[help_href]">ViewVC Help</a></strong>[else] [end]</td>
|
||||
<td><address>[cfg.general.address]</address></td>
|
||||
<td style="text-align: right;"><strong><a href="[help_href]">ViewVC Help</a></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Powered by <a href="http://viewvc.tigris.org/">ViewVC [vsn]</a></td>
|
||||
<td style="text-align: right;">[if-any rss_href]<a href="[rss_href]" title="RSS 2.0 feed"><img src="[docroot]/images/feed-icon-16x16.jpg" class="vc_icon" alt="RSS 2.0 feed" /></a>[else] [end]</td>
|
||||
<td style="text-align: right;">[if-any rss_href]<a href="[rss_href]" title="RSS 2.0 feed"><img src="[docroot]/images/feed-icon-16x16.jpg" alt="RSS 2.0 feed"/></a>[else] [end]</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@@ -5,19 +5,71 @@
|
||||
<head>
|
||||
<title>[if-any rootname][[][rootname]][else]ViewVC[end] [page_title]</title>
|
||||
<meta name="generator" content="ViewVC [vsn]" />
|
||||
<link rel="shortcut icon" href="[docroot]/images/favicon.ico" />
|
||||
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
|
||||
[if-any rss_href]<link rel="alternate" type="application/rss+xml" title="RSS [[][rootname]][where]" href="[rss_href]" />[end]
|
||||
</head>
|
||||
<body>
|
||||
<div class="vc_navheader">
|
||||
<table><tr>
|
||||
<td><strong>[if-any roots_href]<a href="[roots_href]"><span class="pathdiv">/</span></a>[else]<span class="pathdiv">/</span>[end][if-any nav_path][for nav_path][if-any nav_path.href]<a href="[nav_path.href]">[end][if-index nav_path first][[][nav_path.name]][else][nav_path.name][end][if-any nav_path.href]</a>[end][if-index nav_path last][else]<span class="pathdiv">/</span>[end][end][end]</strong></td>
|
||||
<td style="text-align: right;">[if-any username]Logged in as: <strong>[username]</strong>[end]</td>
|
||||
</tr></table>
|
||||
[if-any roots]
|
||||
<form method="get" action="[change_root_action]">
|
||||
[end]
|
||||
<table style="padding:0.1em;">
|
||||
<tr>
|
||||
<td>
|
||||
[if-any nav_path]<strong>
|
||||
[for nav_path]
|
||||
[if-any nav_path.href]<a href="[nav_path.href]">[end]
|
||||
[if-index nav_path first]
|
||||
[[][nav_path.name]][else]
|
||||
[nav_path.name][end][if-any nav_path.href]</a>[end]
|
||||
[if-index nav_path last][else]/[end]
|
||||
[end]
|
||||
</strong>
|
||||
[end]
|
||||
</td>
|
||||
<td style="text-align:right;">
|
||||
[if-any roots]
|
||||
[change_root_hidden_values]
|
||||
<strong>Repository:</strong>
|
||||
<select name="root" onchange="submit()">
|
||||
[define cvs_root_options][end]
|
||||
[define svn_root_options][end]
|
||||
<option value="*viewroots*"[is view "roots"] selected="selected"[else][end]>Repository Listing</option>
|
||||
[for roots]
|
||||
[define root_option][end]
|
||||
[is roots.name rootname]
|
||||
[define root_option]<option selected="selected">[roots.name]</option>[end]
|
||||
[else]
|
||||
[define root_option]<option>[roots.name]</option>[end]
|
||||
[end]
|
||||
[is roots.type "cvs"]
|
||||
[define cvs_root_options][cvs_root_options][root_option][end]
|
||||
[else]
|
||||
[is roots.type "svn"]
|
||||
[define svn_root_options][svn_root_options][root_option][end]
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
[is cvs_root_options ""][else]
|
||||
<optgroup label="CVS Repositories">[cvs_root_options]</optgroup>
|
||||
[end]
|
||||
[is svn_root_options ""][else]
|
||||
<optgroup label="Subversion Repositories">[svn_root_options]</optgroup>
|
||||
[end]
|
||||
</select>
|
||||
<input type="submit" value="Go" />
|
||||
[else]
|
||||
|
||||
[end]
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
[if-any roots]
|
||||
</form>
|
||||
[end]
|
||||
</div>
|
||||
|
||||
<div style="float: right; padding: 5px;"><a href="http://www.viewvc.org/" title="ViewVC Home"><img src="[docroot]/images/viewvc-logo.png" alt="ViewVC logotype" width="240" height="70" /></a></div>
|
||||
<div style="float: right; padding: 5px;"><a href="http://www.viewvc.org/"><img src="[docroot]/images/logo.png" alt="ViewVC logotype" width="128" height="48" /></a></div>
|
||||
<h1>[page_title]</h1>
|
||||
|
||||
|
||||
|
@@ -19,14 +19,14 @@
|
||||
[end]
|
||||
|
||||
[is pathtype "file"]
|
||||
[if-any head_view_href]
|
||||
[if-any view_href]
|
||||
<tr>
|
||||
<td>Links to HEAD:</td>
|
||||
<td>
|
||||
(<a href="[head_view_href]">view</a>)
|
||||
[if-any head_download_href](<a href="[head_download_href]">download</a>)[end]
|
||||
[if-any head_download_text_href](<a href="[head_download_text_href]">as text</a>)[end]
|
||||
[if-any head_annotate_href](<a href="[head_annotate_href]">annotate</a>)[end]
|
||||
(<a href="[view_href]">view</a>)
|
||||
[if-any download_href](<a href="[download_href]">download</a>)[end]
|
||||
[if-any download_text_href](<a href="[download_text_href]">as text</a>)[end]
|
||||
[if-any annotate_href](<a href="[annotate_href]">annotate</a>)[end]
|
||||
</td>
|
||||
</tr>
|
||||
[end]
|
||||
|
@@ -1,20 +1,18 @@
|
||||
[is picklist_len "0"]
|
||||
[is cfg.options.use_pagesize "0"]
|
||||
[else]
|
||||
[is picklist_len "1"]
|
||||
[else]
|
||||
<hr />
|
||||
<form method="get" action="[log_paging_action]">
|
||||
[for log_paging_hidden_values]<input type="hidden" name="[log_paging_hidden_values.name]" value="[log_paging_hidden_values.value]"/>[end]
|
||||
[log_paging_hidden_values]
|
||||
<input type="submit" value="Go to:">
|
||||
<select name="log_pagestart" onchange="submit()">
|
||||
[for picklist]
|
||||
[if-any picklist.more]
|
||||
<option [is picklist.count log_pagestart]selected[end] value="[picklist.count]">Page [picklist.page]: [picklist.start] ...</option>
|
||||
[else]
|
||||
<option [is picklist.count log_pagestart]selected[end] value="[picklist.count]">Page [picklist.page]: [picklist.start] - [picklist.end]</option>
|
||||
[end]
|
||||
[end]
|
||||
</select>
|
||||
</form>
|
||||
[end]
|
||||
[end]
|
||||
[end]
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<form method="get" action="[pathrev_action]" style="display: inline">
|
||||
<div style="display: inline">
|
||||
[for pathrev_hidden_values]<input type="hidden" name="[pathrev_hidden_values.name]" value="[pathrev_hidden_values.value]"/>[end]
|
||||
[pathrev_hidden_values]
|
||||
[is roottype "cvs"]
|
||||
[define pathrev_selected][pathrev][end]
|
||||
<select name="pathrev" onchange="submit()">
|
||||
@@ -41,7 +41,7 @@
|
||||
[if-any pathrev]
|
||||
<form method="get" action="[pathrev_clear_action]" style="display: inline">
|
||||
<div style="display: inline">
|
||||
[for pathrev_clear_hidden_values]<input type="hidden" name="[pathrev_clear_hidden_values.name]" value="[pathrev_clear_hidden_values.value]"/>[end]
|
||||
[pathrev_clear_hidden_values]
|
||||
[if-any lastrev]
|
||||
[is pathrev lastrev][else]<input type="submit" value="Set to [lastrev]" />[end]
|
||||
(<i>Current path doesn't exist after revision <strong>[lastrev]</strong></i>)
|
||||
|
@@ -1,26 +0,0 @@
|
||||
[if-any properties]
|
||||
<hr/>
|
||||
<div class="vc_properties">
|
||||
<h2>Properties</h2>
|
||||
<table cellspacing="1" cellpadding="2" class="auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="vc_header_sort">Name</th>
|
||||
<th class="vc_header">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[for properties]
|
||||
<tr class="vc_row_[if-index properties even]even[else]odd[end]">
|
||||
<td><strong>[properties.name]</strong></td>
|
||||
[if-any properties.undisplayable]
|
||||
<td><em>Property value is undisplayable.</em></td>
|
||||
[else]
|
||||
<td style="white-space: pre;">[properties.value]</td>
|
||||
[end]
|
||||
</tr>
|
||||
[end]
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
[end]
|
@@ -1,10 +1,8 @@
|
||||
[is roottype "svn"]
|
||||
[else]
|
||||
<form method="get" action="[logsort_action]">
|
||||
<div>
|
||||
<hr />
|
||||
<a name="logsort"></a>
|
||||
[for logsort_hidden_values]<input type="hidden" name="[logsort_hidden_values.name]" value="[logsort_hidden_values.value]"/>[end]
|
||||
[logsort_hidden_values]
|
||||
Sort log by:
|
||||
<select name="logsort" onchange="submit()">
|
||||
<option value="cvs" [is logsort "cvs"]selected="selected"[end]>Not sorted</option>
|
||||
@@ -14,4 +12,3 @@
|
||||
<input type="submit" value=" Sort " />
|
||||
</div>
|
||||
</form>
|
||||
[end]
|
@@ -6,7 +6,6 @@
|
||||
[for entries]
|
||||
[if-index entries first][define first_revision][entries.rev][end][end]
|
||||
[if-index entries last][define last_revision][entries.rev][end][end]
|
||||
|
||||
<div>
|
||||
<hr />
|
||||
|
||||
@@ -20,12 +19,10 @@
|
||||
[end]
|
||||
|
||||
Revision [is roottype "svn"]<a href="[entries.revision_href]"><strong>[entries.rev]</strong></a>[else]<strong>[entries.rev]</strong>[end] -
|
||||
[if-any entries.view_href]
|
||||
[is pathtype "file"]
|
||||
(<a href="[entries.view_href]">view</a>)
|
||||
[else]
|
||||
<a href="[entries.view_href]">Directory Listing</a>
|
||||
[end]
|
||||
[is pathtype "file"]
|
||||
(<a href="[entries.view_href]">view</a>)
|
||||
[else]
|
||||
<a href="[entries.view_href]">Directory Listing</a>
|
||||
[end]
|
||||
[if-any entries.download_href](<a href="[entries.download_href]">download</a>)[end]
|
||||
[if-any entries.download_text_href](<a href="[entries.download_text_href]">as text</a>)[end]
|
||||
@@ -51,9 +48,7 @@
|
||||
[if-index entries last]Added[else]Modified[end]
|
||||
[end]
|
||||
|
||||
<em>[if-any entries.date][entries.date][else](unknown date)[end]</em>
|
||||
[if-any entries.ago]([entries.ago] ago)[end]
|
||||
by <em>[if-any entries.author][entries.author][else](unknown author)[end]</em>
|
||||
<em>[entries.date]</em> ([entries.ago] ago) by <em>[entries.author]</em>
|
||||
|
||||
[if-any entries.orig_path]
|
||||
<br />Original Path: <a href="[entries.orig_href]"><em>[entries.orig_path]</em></a>
|
||||
@@ -98,10 +93,6 @@
|
||||
[end]
|
||||
[end]
|
||||
|
||||
[if-any entries.lockinfo]
|
||||
<br />Lock status: <img src="[docroot]/images/lock.png" alt="Locked" width="16" height="16" /> [entries.lockinfo]
|
||||
[end]
|
||||
|
||||
[is entries.state "dead"]
|
||||
<br /><strong><em>FILE REMOVED</em></strong>
|
||||
[else]
|
||||
|
@@ -34,12 +34,10 @@
|
||||
|
||||
[# Tasks column]
|
||||
<td>
|
||||
[if-any entries.view_href]
|
||||
[is pathtype "file"]
|
||||
<a href="[entries.view_href]"><strong>View</strong></a><br />
|
||||
[else]
|
||||
<a href="[entries.view_href]"><strong>Directory Listing</strong></a><br />
|
||||
[end]
|
||||
[is pathtype "file"]
|
||||
<a href="[entries.view_href]"><strong>View</strong></a><br />
|
||||
[else]
|
||||
<a href="[entries.view_href]"><strong>Directory Listing</strong></a><br />
|
||||
[end]
|
||||
[if-any entries.download_href]<a href="[entries.download_href]"><strong>Download</strong></a><br />[end]
|
||||
[if-any entries.download_text_href]<a href="[entries.download_text_href]"><strong>As text</strong></a><br />[end]
|
||||
@@ -110,7 +108,7 @@
|
||||
[# Tags ]
|
||||
[if-any entries.tags]
|
||||
<form method=get action="[pathrev_action]" >
|
||||
[for pathrev_hidden_values]<input type="hidden" name="[pathrev_hidden_values.name]" value="[pathrev_hidden_values.value]"/>[end]
|
||||
[pathrev_hidden_values]
|
||||
<select name="pathrev" onChange="submit()">
|
||||
<option value="" [is pathrev ""]selected[end]>Show all tags</option>
|
||||
[for entries.tags]
|
||||
@@ -128,8 +126,7 @@
|
||||
[is roottype "svn"]
|
||||
[if-index entries last]Added[else]Modified[end]
|
||||
[end]
|
||||
[if-any entries.ago][entries.ago] ago<br />[end]
|
||||
[if-any entries.date]<em>[entries.date]</em>[end]
|
||||
[entries.ago] ago<br /><em>[entries.date]</em>
|
||||
[is roottype "cvs"]
|
||||
[if-any entries.prev]
|
||||
[if-any entries.changed]
|
||||
@@ -147,14 +144,9 @@
|
||||
</tr>
|
||||
<tr class="vc_row_[if-index entries even]even[else]odd[end]">
|
||||
<td colspan=5>
|
||||
|
||||
[if-any entries.lockinfo]
|
||||
<strong>Lock status</strong>: <img src="[docroot]/images/lock.png" alt="Locked" width="16" height="16" /> [entries.lockinfo]<br />
|
||||
[end]
|
||||
|
||||
[is roottype "svn"]
|
||||
[if-any entries.orig_path]
|
||||
<strong>Original Path</strong>: <a href="[entries.orig_href]"><em>[entries.orig_path]</em></a><br />
|
||||
Original Path: <a href="[entries.orig_href]"><em>[entries.orig_path]</em></a><br />
|
||||
[end]
|
||||
|
||||
[if-any entries.size]
|
||||
|
50
templates/markup.ezt
Normal file
50
templates/markup.ezt
Normal file
@@ -0,0 +1,50 @@
|
||||
[# setup page definitions]
|
||||
[define page_title]View of /[where][end]
|
||||
[define help_href][docroot]/help_rootview.html[end]
|
||||
[# end]
|
||||
|
||||
[include "include/header.ezt" "markup"]
|
||||
[include "include/file_header.ezt"]
|
||||
<hr />
|
||||
<div class="vc_summary">
|
||||
Revision [if-any revision_href]<a href="[revision_href]"><strong>[rev]</strong></a>[else]<strong>[rev]</strong>[end] -
|
||||
(<a href="[download_href]"><strong>download</strong></a>)
|
||||
[if-any download_text_href](<a href="[download_text_href]"><strong>as text</strong></a>)[end]
|
||||
[if-any annotate_href](<a href="[annotate_href]"><strong>annotate</strong></a>)[end]
|
||||
|
||||
[if-any vendor_branch] <em>(vendor branch)</em>[end]
|
||||
<br />[if-any date]<em>[date]</em>[end]
|
||||
[if-any ago]([ago] ago)[end]
|
||||
[if-any author]by <em>[author]</em>[end]
|
||||
[if-any orig_path]
|
||||
<br />Original Path: <a href="[orig_href]"><em>[orig_path]</em></a>
|
||||
[end]
|
||||
|
||||
[if-any branches]
|
||||
<br />Branch: <strong>[branches]</strong>
|
||||
[end]
|
||||
[if-any tags]
|
||||
<br />CVS Tags: <strong>[tags]</strong>
|
||||
[end]
|
||||
[if-any branch_points]
|
||||
<br />Branch point for: <strong>[branch_points]</strong>
|
||||
[end]
|
||||
[is roottype "cvs"]
|
||||
[if-any changed]
|
||||
<br />Changes since <strong>[prev]: [changed] lines</strong>
|
||||
[end]
|
||||
[end]
|
||||
[is roottype "svn"][if-any size]
|
||||
<br />File size: [size] byte(s)
|
||||
[end][end]
|
||||
[is state "dead"]
|
||||
<br /><strong><em>FILE REMOVED</em></strong>
|
||||
[end]
|
||||
[if-any log]
|
||||
<pre class="vc_log">[log]</pre>
|
||||
[end]
|
||||
</div>
|
||||
|
||||
<div id="vc_markup"><pre>[markup]</pre></div>
|
||||
|
||||
[include "include/footer.ezt"]
|
@@ -4,13 +4,13 @@
|
||||
<!-- ViewVC :: http://www.viewvc.org/ -->
|
||||
<head>
|
||||
<title>Checkin Database Query</title>
|
||||
[if-any docroot]<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />[end]
|
||||
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
[# setup page definitions]
|
||||
[define help_href][if-any docroot][docroot]/help_query.html[end][end]
|
||||
[define help_href][docroot]/help_query.html[end]
|
||||
[# end]
|
||||
|
||||
<p>
|
||||
@@ -202,21 +202,21 @@
|
||||
</td>
|
||||
|
||||
[# uncommment, if you want a separate Description column:
|
||||
{if-index commits.files first}
|
||||
{if-index commits.files first{
|
||||
<td style="vertical-align:top;" rowspan="{commits.num_files}">
|
||||
{if-any commits.log}{commits.log}{else} {end}
|
||||
{commits.log}
|
||||
</td>
|
||||
{end}
|
||||
|
||||
(substitute brackets for the braces)
|
||||
]
|
||||
</tr>
|
||||
[# and also take the following out in the "Description column" case:]
|
||||
[# and also take the following out in the "Description column"-case:]
|
||||
[if-index commits.files last]
|
||||
<tr class="vc_row_[if-index commits even]even[else]odd[end]">
|
||||
<td> </td>
|
||||
<td colspan="5"><strong>Log:</strong><br />
|
||||
<pre class="vc_log">[if-any commits.log][commits.log][else] [end]</pre></td>
|
||||
<pre class="vc_log">[commits.log]</pre></td>
|
||||
</tr>
|
||||
[end]
|
||||
[# ---]
|
||||
|
@@ -6,13 +6,13 @@
|
||||
[include "include/header.ezt" "query"]
|
||||
|
||||
<p><a href="[dir_href]">
|
||||
<img src="[docroot]/images/dir.png" class="vc_icon" alt="Directory" />
|
||||
<img src="[docroot]/images/dir.png" width="16" height="16" alt="Directory" />
|
||||
Browse Directory</a></p>
|
||||
|
||||
<form action="[query_action]" method="get">
|
||||
|
||||
<div class="vc_query_form">
|
||||
[for query_hidden_values]<input type="hidden" name="[query_hidden_values.name]" value="[query_hidden_values.value]"/>[end]
|
||||
[query_hidden_values]
|
||||
<table cellspacing="0" cellpadding="5" class="auto">
|
||||
[is roottype "cvs"]
|
||||
[# For subversion, the branch field is not used ]
|
||||
@@ -102,32 +102,6 @@ Browse Directory</a></p>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align:right;vertical-align:top;">Comment:</th>
|
||||
<td>
|
||||
<input type="text" name="comment" value="[comment]" /><br />
|
||||
<label for="comment_match_exact">
|
||||
<input type="radio" name="comment_match" id="comment_match_exact"
|
||||
value="exact" [is comment_match "exact"]checked=""[end] />
|
||||
Exact match
|
||||
</label>
|
||||
<label for="comment_match_glob">
|
||||
<input type="radio" name="comment_match" id="comment_match_glob"
|
||||
value="glob" [is comment_match "glob"]checked=""[end] />
|
||||
Glob pattern match
|
||||
</label>
|
||||
<label for="comment_match_regex">
|
||||
<input type="radio" name="comment_match" id="comment_match_regex"
|
||||
value="regex" [is comment_match "regex"]checked=""[end] />
|
||||
Regex match
|
||||
</label>
|
||||
<label for="comment_match_notregex">
|
||||
<input type="radio" name="comment_match" id="comment_match_notregex"
|
||||
value="notregex" [is comment_match "notregex"]checked=""[end] />
|
||||
Regex doesn't match
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align:right;vertical-align:top;">Sort By:</th>
|
||||
<td>
|
||||
|
@@ -34,8 +34,7 @@
|
||||
<tbody>
|
||||
<tr class="vc_row_[if-index commits even]even[else]odd[end]">
|
||||
<td style="vertical-align: top;">
|
||||
[define rev_href][if-any commits.files.prefer_markup][commits.files.view_href][else][if-any commits.files.download_href][commits.files.download_href][end][end][end]
|
||||
[if-any commits.files.rev][if-any rev_href]<a href="[rev_href]">[end][commits.files.rev][if-any rev_href]</a>[end][else] [end]
|
||||
[if-any commits.files.rev]<a href="[if-any commits.files.prefer_markup][commits.files.view_href][else][commits.files.download_href][end]">[commits.files.rev]</a>[else] [end]
|
||||
</td>
|
||||
<td style="vertical-align: top;">
|
||||
<a href="[commits.files.dir_href]">[commits.files.dir]/</a>
|
||||
|
@@ -6,33 +6,26 @@
|
||||
[include "include/header.ezt" "revision"]
|
||||
|
||||
<hr />
|
||||
|
||||
<form method="get" action="[jump_rev_action]">
|
||||
<table cellspacing="1" cellpadding="2" style="width: auto;">
|
||||
<tr align="left">
|
||||
<th>Jump to revision:</th>
|
||||
<td>
|
||||
[for jump_rev_hidden_values]<input type="hidden" name="[jump_rev_hidden_values.name]" value="[jump_rev_hidden_values.value]"/>[end]
|
||||
[jump_rev_hidden_values]
|
||||
<input type="text" name="revision" value="[rev]" />
|
||||
<input type="submit" value="Go" />
|
||||
[if-any prev_href]
|
||||
<a href="[prev_href]" title="Previous Revision"><img src="[docroot]/images/back.png" alt="Previous" width="20" height="22" /></a>[end]
|
||||
[if-any next_href] <a href="[next_href]" title="Next Revision"><img src="[docroot]/images/forward.png" width="20" height="22" alt="Next" /></a>[end]
|
||||
<a href="[prev_href]"><img src="[docroot]/images/back.png" alt="Previous" width="20" height="22" /></a>[end]
|
||||
[if-any next_href] <a href="[next_href]"><img src="[docroot]/images/forward.png" width="20" height="22" alt="Next" /></a>[end]
|
||||
</td>
|
||||
</tr>
|
||||
<tr align="left">
|
||||
<th>Author:</th>
|
||||
<td>[if-any author][author][else]<em>(unknown author)</em>[end]</td>
|
||||
<td>[author]</td>
|
||||
</tr>
|
||||
<tr align="left">
|
||||
<th>Date:</th>
|
||||
<td>[if-any date][date][else]<em>(unknown date)</em>[end]
|
||||
[if-any ago]<em>([ago] ago)</em>[end]</td>
|
||||
</tr>
|
||||
<tr align="left">
|
||||
<th>Changed paths:</th>
|
||||
<td><strong>[num_changes]</strong>
|
||||
[if-any more_changes](showing only [limit_changes]; <a href="[more_changes_href]">show all</a>)[end][if-any first_changes](<a href="[first_changes_href]">show only first [first_changes]</a>)[end]</td>
|
||||
<td>[date] <em>([ago] ago)</em></td>
|
||||
</tr>
|
||||
<tr align="left">
|
||||
<th>Log Message:</th>
|
||||
@@ -43,7 +36,18 @@
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Changed paths</h2>
|
||||
<p><strong>Changed paths:</strong></p>
|
||||
|
||||
[if-any more_changes]
|
||||
<div>
|
||||
Only [limit_changes] changes shown,
|
||||
<a href="[more_changes_href]">display [more_changes] more changes...</a>
|
||||
</div>
|
||||
[end]
|
||||
|
||||
[if-any first_changes]
|
||||
<div><a href="[first_changes_href]">Show only first [first_changes] changes...</div>
|
||||
[end]
|
||||
|
||||
<table cellspacing="1" cellpadding="2">
|
||||
<thead>
|
||||
@@ -56,11 +60,11 @@
|
||||
[if-any changes]
|
||||
[for changes]
|
||||
<tr class="vc_row_[if-index changes even]even[else]odd[end]">
|
||||
<td>[if-any changes.view_href]<a href="[changes.view_href]" title="View [is changes.pathtype "dir"]Directory[else]File[end] Contents">[end]<img src="[docroot]/images/[is changes.pathtype "dir"]dir[else]text[end].png" class="vc_icon" alt="Directory" />[changes.path][is changes.pathtype "dir"]/[end][if-any changes.view_href]</a>[end]
|
||||
<td>[if-any changes.view_href]<a href="[changes.view_href]">[end]<img src="[docroot]/images/[is changes.pathtype "dir"]dir[else]text[end].png" width="16" height="16" alt="Directory" />[changes.path][is changes.pathtype "dir"]/[end][if-any changes.view_href]</a>[end]
|
||||
[if-any changes.is_copy]<br /><em>(Copied from [changes.copy_path], r[changes.copy_rev])</em>[end]
|
||||
</td>
|
||||
<td>[if-any changes.log_href]<a href="[changes.log_href]" title="View Log">[end][changes.action][if-any changes.log_href]</a>[end]
|
||||
[if-any changes.text_mods], [if-any changes.diff_href]<a href="[changes.diff_href]" title="View Diff">[end]text changed[if-any changes.diff_href]</a>[end][end]
|
||||
<td>[if-any changes.log_href]<a href="[changes.log_href]">[end][changes.action][if-any changes.log_href]</a>[end]
|
||||
[if-any changes.text_mods], [if-any changes.diff_href]<a href="[changes.diff_href]">[end]text changed[if-any changes.diff_href]</a>[end][end]
|
||||
[if-any changes.prop_mods], props changed[end]
|
||||
</td>
|
||||
</tr>
|
||||
@@ -70,13 +74,7 @@
|
||||
<td colspan="5">No changed paths.</td>
|
||||
</tr>
|
||||
[end]
|
||||
[if-any more_changes]
|
||||
<tr>
|
||||
<td colspan="5">[[]<a href="[more_changes_href]">...</a>]</td>
|
||||
</tr>
|
||||
[end]
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
[include "include/props.ezt"]
|
||||
[include "include/footer.ezt"]
|
||||
|
@@ -17,7 +17,7 @@
|
||||
<tr class="vc_row_[if-index roots even]even[else]odd[end]">
|
||||
<td>
|
||||
<a href="[roots.href]">
|
||||
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
|
||||
<img src="[docroot]/images/dir.png" alt="" width="16" height="16" />
|
||||
[roots.name]</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@@ -1,17 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<link>[rss_link_href]</link>
|
||||
<title>[rootname] checkins[if-any where] (in [where])[end]</title>
|
||||
|
||||
<description>[is roottype "svn"]Subversion[else]CVS[end] commits to the[if-any where] [where] directory of the[end] [rootname] repository</description>
|
||||
|
||||
[for commits]<item>
|
||||
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [format "xml"][commits.short_log][end]</title>
|
||||
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [commits.short_log]</title>
|
||||
[if-any commits.rss_url]<link>[commits.rss_url]</link>[end]
|
||||
<author>[commits.author]</author>
|
||||
<pubDate>[if-any commits.rss_date][commits.rss_date][else](unknown date)[end]</pubDate>
|
||||
<description><pre>[format "xml"][format "html"][commits.log][end][end]</pre></description>
|
||||
<pubDate>[commits.rss_date]</pubDate>
|
||||
<description>[commits.log]</description>
|
||||
</item>[end]
|
||||
</channel>
|
||||
</rss>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -17,18 +17,17 @@
|
||||
|
||||
### Validate input
|
||||
if test $# != 2 && test $# != 1; then
|
||||
echo "Usage: $0 TARGET-DIRECTORY [BRANCH]"
|
||||
echo "Usage: $0 TARGET-DIRECTORY [TAGNAME]"
|
||||
echo ""
|
||||
echo "If BRANCH (i.e. \"tags/1.1.0\" or \"branches/1.0.x\") is not provided,"
|
||||
echo "the release will be rolled from trunk."
|
||||
echo "If TAGNAME is not provided, the release will be rolled from trunk."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET=${1}
|
||||
if test $# = 1; then
|
||||
if test $# == 1; then
|
||||
ROOT=trunk
|
||||
else
|
||||
ROOT=${2}
|
||||
ROOT=tags/${2}
|
||||
fi
|
||||
|
||||
if test -e ${TARGET}; then
|
||||
@@ -37,8 +36,7 @@ if test -e ${TARGET}; then
|
||||
fi
|
||||
|
||||
### Grab an export from the Subversion repository.
|
||||
EXPORT_URL="http://viewvc.tigris.org/svn/viewvc/${ROOT}"
|
||||
echo "Exporting '${EXPORT_URL}' into '${TARGET}'"
|
||||
echo "Exporting into:" ${TARGET}
|
||||
|
||||
for PLATFORM in unix windows; do
|
||||
if test ${PLATFORM} = windows; then
|
||||
@@ -47,44 +45,30 @@ for PLATFORM in unix windows; do
|
||||
EOL="--native-eol LF"
|
||||
fi
|
||||
|
||||
echo "Beginning build for ${PLATFORM}:"
|
||||
|
||||
echo " Exporting source code..."
|
||||
svn export --quiet ${EOL} ${EXPORT_URL} ${TARGET}
|
||||
svn export ${EOL} http://viewvc.tigris.org/svn/viewvc/${ROOT} ${TARGET}
|
||||
|
||||
### Various shifting, cleanup.
|
||||
|
||||
# Documentation is now also distributed together with the release, but
|
||||
# we still copy the license file to its traditional place (it is small
|
||||
# and many files still contain comments refering to this location):
|
||||
|
||||
# Remove some not useful directories
|
||||
for JUNK in elemx \
|
||||
notes \
|
||||
tests \
|
||||
tools \
|
||||
tparse \
|
||||
viewcvs.sourceforge.net \
|
||||
viewvc.org \
|
||||
www; do
|
||||
if [ -d ${TARGET}/${JUNK} ]; then
|
||||
echo " Removing ${TARGET}/${JUNK}..."
|
||||
rm -r ${TARGET}/${JUNK}
|
||||
fi
|
||||
done
|
||||
rm -r ${TARGET}/{elemx,tests,tools,tparse,viewcvs.sourceforge.net,www}
|
||||
|
||||
# Make sure permissions are reasonable:
|
||||
echo " Normalizing permissions..."
|
||||
find ${TARGET} -print | xargs chmod uoa+r
|
||||
find ${TARGET} -type d -print | xargs chmod uoa+x
|
||||
|
||||
if test ${PLATFORM} = windows; then
|
||||
# Create also a ZIP file for those poor souls :-) still using Windows:
|
||||
echo " Creating ZIP archive..."
|
||||
zip -qor9 ${TARGET}.zip ${TARGET}
|
||||
else
|
||||
# Cut the tarball:
|
||||
echo " Creating tarball archive..."
|
||||
tar cf - ${TARGET} | gzip -9 > ${TARGET}.tar.gz
|
||||
fi
|
||||
|
||||
# remove target directory
|
||||
rm -r ${TARGET}
|
||||
done
|
||||
echo "Done."
|
||||
echo 'Done.'
|
||||
|
1
tparse/.cvsignore
Normal file
1
tparse/.cvsignore
Normal file
@@ -0,0 +1 @@
|
||||
build
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user