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

Compare commits

..

16 Commits
1.1.1 ... 1.0.1

Author SHA1 Message Date
cmpilato
a67b467261 Oops. Stick a datestamp in the CHANGES file.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.0.1@1389 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-20 22:15:26 +00:00
cmpilato
5135318425 Tag the 1.0.1 final release.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.0.1@1388 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-20 22:11:36 +00:00
cmpilato
9693f2b981 * CHANGES
Note recent change that uses real modification times for Subversion
  directories in generated tarballs.

git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1387 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-20 22:08:53 +00:00
cmpilato
69ab22922f Backport fix for issue #250 (r1384) to the 1.0.x branch.
* lib/viewvc.py
  (generate_tarball): Add new (optional) dir_mtime parameter, used to specify
    the modification time of the current directory while generating tarball
    data.  Now, allow empty directories in Subversion tarballs (while
    preserving the "pruning" behavior for CVS tarballs).

* CHANGES
  Note the change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1385 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-14 20:06:34 +00:00
cmpilato
d422c3abc9 Backport fix for issue #249 (r1382) to the 1.0.x branch.
* lib/query.py
  (main): Add 'rss_href' to the data dictionary so the template
    doesn't freak out.

* CHANGES
  Note this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1383 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-14 18:35:29 +00:00
cmpilato
ded0015f5c Backport fix for issue #241 (r1377) to the 1.0.x branch.
* lib/viewvc.py
  (get_file_view_info): Fix an oops which caused some whack URL
    generation for download-as-text links on files whose path has changed
    over time.

* CHANGES
  Update to reflect this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1378 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-14 15:53:09 +00:00
cmpilato
6232555f79 Backport fix for issue #237 (r1375) to the 1.0.x branch.
* lib/vclib/bincvs/__init__.py
  (_tag_tuple): Return () instead of raising a ValueError for
    single-slot revisions.

* CHANGES
  Update to reflect this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1376 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-07-14 15:31:44 +00:00
cmpilato
81fa4ce6fd Syncronize the 1.0.x CHANGES file with recent formatting fixes from trunk.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1369 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-05-26 15:49:25 +00:00
cmpilato
98378e7560 Backport the patch from r1367 (from issue #239), and not the change in
the CHANGES file.
   
* templates/include/footer.ezt
  Fix an XHTML validation error caused by not closing an <A> tag.

* CHANGES
  Note the bugfix.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1368 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-05-26 15:48:00 +00:00
cmpilato
8da2897df2 Backport r1357 to the 1.0.x release branch. That log message read:
Fix exception on log page when use_pagesize is enabled. Patch from
   Jay Rossiter / Signe <signe@cothlamadh.net> on users' list.
   
   * lib/viewvc.py
     (view_log): call paging() with right arguments


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1361 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-05-18 17:31:55 +00:00
cmpilato
45bca25fbc * CHANGES
Add change for r1357's bugfix.

git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1359 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-05-18 17:27:57 +00:00
cmpilato
a54b3bcbbe * lib/viewvc.py
(__version__): Bump to 1.0.1-dev.

* CHANGES
  Give a date for the 1.0.0 release.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1352 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-05-01 21:20:24 +00:00
cmpilato
2612b3bd0a Merge r1339 from trunk into the 1.0.x branch.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1340 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-04-20 06:55:11 +00:00
rey4
588f19cb0b Merge trunk revision 1333 into 1.0 branch
* branches/1.0.x


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1336 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-04-14 14:33:45 +00:00
rey4
6d540deba2 Merge trunk doc updates from 1330 and 1331 onto 1.0 branch
* CHANGES
* INSTALL
* viewvc.org/index.html
* viewvc.org/upgrading.html
* windows/README
* www/index.html


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1332 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-04-09 16:09:09 +00:00
cmpilato
9f612d3b29 Branch for 1.0.0 release.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.0.x@1327 8cb11bc2-c004-0410-86c3-e597b4017df7
2006-04-06 19:53:43 +00:00
120 changed files with 6605 additions and 11473 deletions

119
CHANGES
View File

@@ -1,121 +1,3 @@
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.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)
* fix loginfo-handler's support for CVS 1.12.9 (issues #151, #257)
* make viewvc-install able to run from an arbitrary location
* update viewvc-install's output for readability
* fix bug writing commits to non-MyISAM databases (issue #262)
* allow long paths in generated tarballs (issue #12)
* fix bug interpreting EZT substitute patterns
* fix broken markup view disablement
* fix broken directory view link generation in directory log view
* fix Windows-specific viewvc-install bugs
* fix broke query result links for Subversion deleted items (issue #296)
* fix some output XHTML validation buglets
* fix database query cache staleness problems (issue #180)
Version 1.0.3 (released 13-Oct-2006)
* fix bug in path shown for Subversion deleted-under-copy items (issue #265)
* security fix: declare charset for views to avoid IE UTF7 XSS attack
Version 1.0.2 (released 29-Sep-2006)
* minor documentation fixes
* fix Subversion annotate functionality on Windows (issue #18)
* fix annotate assertions on uncanonicalized #include paths (issue #208)
* make RSS URL method match the method used to generate it (issue #245)
* fix Subversion annotation to run non-interactively, preventing hangs
* fix bug in custom syntax highlighter fallback logic
* fix bug in PHP CGI hack to avoid force-cgi-redirect errors
Version 1.0.1 (released 20-Jul-2006)
* fix exception on log page when use_pagesize is enabled
@@ -258,6 +140,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

View File

@@ -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

385
INSTALL
View File

@@ -1,12 +1,11 @@
CONTENTS
--------
TO THE IMPATIENT
SECURITY INFORMATION
INSTALLING VIEWVC
APACHE CONFIGURATION
UPGRADING VIEWVC
SQL CHECKIN DATABASE
ENABLING SYNTAX COLORATION
ENSCRIPT AND HIGHLIGHT CONFIGURATION
CVSGRAPH CONFIGURATION
IF YOU HAVE PROBLEMS...
@@ -32,7 +31,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,57 +42,32 @@ 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/)
Quick sanity check:
GUI Operation:
If you just want to see what your repository looks like when seen
through ViewVC, type:
If you just want to see what your CVS repository looks like with
ViewVC, type "bin/standalone.py -g -r /PATH/TO/CVS/ROOT". This
will start a tiny webserver serving at http://localhost:7467/.
PLEASE NOTE: This requires Python with thread support enabled and
the Tkinter GUI. If you don't have one of these, omit the '-g' option.
$ bin/standalone.py -r /PATH/TO/REPOSITORY
This will start a tiny ViewVC server at http://localhost:49152/viewvc/,
to which you can connect with your browser.
Standard operation:
To start installing right away (on UNIX): type "./viewvc-install"
in the current directory and answer the prompts. When it
finishes, edit the file viewvc.conf in the installation directory
to tell ViewVC the paths to your CVS and Subversion repositories.
Next, configure your web server (in the way appropriate to that browser)
to run <VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/viewvc.cgi. The section
`INSTALLING VIEWVC' below is still recommended reading.
SECURITY INFORMATION
--------------------
ViewVC provides a feature which allows version controlled content to
be served to web browsers just like static web server content. So, if
you have a directory full of interrelated HTML files that is housed in
your version control repository, ViewVC can serve those files as HTML.
You'll see in your web browser what you'd see if the files were part
of your website, with working references to stylesheets and images and
links to other pages.
It is important to realize, however, that as useful as that feature
is, there is some risk security-wise in its use. Essentially, anyone
with commit access to the CVS or Subversion repositories served by
ViewVC has the ability to affect site content. If a discontented or
ignorant user commits malicious HTML to a version controlled file
(perhaps just by way of documenting examples of such), that malicious
HTML is effectively published and live on your ViewVC instance.
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.
to tell viewvc the paths to your CVS and Subversion repositories. Next,
configure your web server to run <INSTALL>/cgi/viewvc.cgi, as
appropriate for your web server. The section `INSTALLING VIEWVC'
below is still recommended reading.
INSTALLING VIEWVC
@@ -133,13 +107,10 @@ installation instructions.
3) Edit <VIEWVC_INSTALLATION_DIRECTORY>/viewvc.conf for your specific
configuration. In particular, examine the following configuration options:
cvs_roots (for CVS)
svn_roots (for Subversion)
root_parents (for CVS or Subversion)
cvs_roots
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,
@@ -147,22 +118,22 @@ installation instructions.
then edit the files in <VIEWVC_INSTALLATION_DIRECTORY>/templates.
You need knowledge about HTML to edit the templates.
4) The CGI programs are in <VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/. You can
4) The CGI programs are in <VIEWVC_INSTALLATION_DIRECTORY>/www/cgi/. You can
symlink to this directory from somewhere in your published HTTP server
path if your webserver is configured to follow symbolic links. You can
also copy the installed <VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
also copy the installed <VIEWVC_INSTALLATION_DIRECTORY>/www/cgi/*.cgi
scripts after the install (unlike the other files in ViewVC, the scripts
under bin/ can be moved).
under www/ can be moved).
If you are using Apache, then see below at the section titled
APACHE CONFIGURATION.
If you are using Apache, then see below at the section
titled APACHE CONFIGURATION.
NOTE: for security reasons, it is not advisable to install ViewVC
directly into your published HTTP directory tree (due to the MySQL
passwords in viewvc.conf).
That's it for repository browsing. Instructions for getting the SQL
checkin database working are below.
5) That's it for repository browsing. Instructions for getting the
SQL checkin database working are below.
APACHE CONFIGURATION
@@ -178,26 +149,26 @@ 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
ScriptAlias /viewvc <VIEWVC_INSTALLATION_DIRECTORY>/www/cgi/viewvc.cgi
into your httpd.conf file. Choose the location in httpd.conf where
also the other ScriptAlias lines reside. Some examples:
ScriptAlias /viewvc /usr/local/viewvc-1.0/bin/cgi/viewvc.cgi
ScriptAlias /query /usr/local/viewvc-1.0/bin/cgi/query.cgi
ScriptAlias /viewvc /usr/local/viewvc-1.0/www/cgi/viewvc.cgi
ScriptAlias /query /usr/local/viewvc-1.0/www/cgi/query.cgi
continue with step 3).
or alternatively METHOD B:
2) Copy the CGI scripts from
<VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
<VIEWVC_INSTALLATION_DIRECTORY>/www/cgi/*.cgi
to the /cgi-bin/ directory configured in your httpd.conf file.
continue with step 3).
and then there's METHOD C:
2) Copy the CGI scripts from
<VIEWVC_INSTALLATION_DIRECTORY>/bin/cgi/*.cgi
<VIEWVC_INSTALLATION_DIRECTORY>/www/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:
@@ -212,7 +183,7 @@ and then there's METHOD C:
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/
<VIEWVC_INSTALLATION_DIRECTORY>/www/mod_python/
to a directory being served by apache.
In httpd.conf, make sure that "AllowOverride All" or at least
@@ -220,8 +191,8 @@ or if you've got Mod_Python installed you can use METHOD D:
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.
continue with step 3).
@@ -235,37 +206,28 @@ or if you've got Mod_Python installed you can use METHOD D:
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
AuthUserFile /path/to/passwd/file
AuthName "Client Access"
AuthType Basic
require valid-user
</Location>
<Location "<url to viewvc.cgi>/<modname_you_wish_to_access_ctl>">
AllowOverride None
AuthUserFile /path/to/passwd/file
AuthName "Client Access"
AuthType Basic
require valid-user
</Location>
WARNING: If you enable the "checkout_magic" or "allow_tar" options, you
will need to add additional location directives to prevent people
from sneaking in with URLs like:
http://<server_name>/viewvc/*checkout*/<module_name>
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.
http://<server_name>/viewvc/*checkout*/<module_name>
http://<server_name>/viewvc/~checkout~/<module_name>
http://<server_name>/viewvc/<module_name>.tar.gz?view=tar
UPGRADING VIEWVC
-----------------
Please read the file upgrading-howto.html in the docs/ subdirectory.
Please read the file upgrading.html in the viewvc.org/ subdirectory or
at <http://viewvc.org/upgrading.html>.
SQL CHECKIN DATABASE
@@ -284,7 +246,7 @@ there are some additional steps required to get the database working.
Optionally, you can create a second user with read-only access to the
database.
3) Run the <VIEWVC_INSTALLATION_DIRECTORY>/bin/make-database script. It will
3) Run the <VIEWVC_INSTALLATION_DIRECTORY>/make-database script. It will
prompt you for your MySQL user, password, and the name of database you
want to create. The database name defaults to "ViewVC". This script
creates the database and sets up the empty tables. If you run this on a
@@ -296,106 +258,91 @@ there are some additional steps required to get the database working.
enabled = 1 # Whether to enable query support in viewvc.cgi
host = # MySQL database server host
port = # MySQL database server port (default is 3306)
database_name = # name of database you created with make-database
user = # read/write database user
database_name = # the name of the database you created with
# make-database
user = # the read/write database user
passwd = # password for read/write database user
readonly_user = # read-only database user
readonly_passwd = # password for the read-only user
readonly_user = # the readonly database user -- it's pretty
# safe to use the read/write user here
readonly_passwd = # password for the readonly user
Note that it's pretty safe in this instance for your read-only user
and your read-write user to be the same.
5) Two programs are provided for updating the checkin database from a
CVS repository, cvsdbadmin and loginfo-handler. They serve two
different purposes. The cvsdbadmin program walks through your CVS
repository and adds every commit in every file. This is commonly
used for initializing the database from a repository which has been
in use. The loginfo-handler script is executed by the CVS server's
CVSROOT/loginfo system upon each commit. It makes real-time
updates to the checkin database as commits are made to the
repository.
5) At this point, you need to tell your version control system(s) to
publish their commit information to the database. This is done
using utilities that ViewVC provides.
To build a database of all the commits in the CVS repository /home/cvs,
invoke: "./cvsdbadmin rebuild /home/cvs". If you want to update
the checkin database, invoke: "./cvsdbadmin update /home/cvs". The
update mode checks to see if a commit is already in the database,
and only adds it if it is absent.
To publish CVS commits into the database:
To get real-time updates, you'll want to checkout the CVSROOT module
from your CVS repository and edit CVSROOT/loginfo. Add the line:
Two programs are provided for updating the checkin database from
a CVS repository, cvsdbadmin and loginfo-handler. They serve
two different purposes. The cvsdbadmin program walks through
your CVS repository and adds every commit in every file. This
is commonly used for initializing the database from a repository
which has been in use. The loginfo-handler script is executed
by the CVS server's CVSROOT/loginfo system upon each commit. It
makes real-time updates to the checkin database as commits are
made to the repository.
ALL <VIEWVC_INSTALLATION_DIRECTORY>/loginfo-handler %{sVv}
To build a database of all the commits in the CVS repository
/home/cvs, invoke: "./cvsdbadmin rebuild /home/cvs". If you
want to update the checkin database, invoke: "./cvsdbadmin
update /home/cvs". The update mode checks to see if a commit is
already in the database, and only adds it if it is absent.
If you have other scripts invoked by CVSROOT/loginfo, you will want
to make sure to change any running under the "DEFAULT" keyword to
"ALL" like the loginfo handler, and probably carefully read the
execution rules for CVSROOT/loginfo from the CVS manual.
To get real-time updates, you'll want to checkout the CVSROOT
module from your CVS repository and edit CVSROOT/loginfo. For
folks running CVS 1.12 or better, add this line:
ALL <VIEWVC_INSTALLATION_DIRECTORY>/bin/loginfo-handler %p %{sVv}
If you are running CVS 1.11 or earlier, you'll want a slightly
different command line in CVSROOT/loginfo:
ALL <VIEWVC_INSTALLATION_DIRECTORY>/bin/loginfo-handler %{sVv}
If you are running the Unix port of CVS-NT, you'll need to use a
slightly different command line:
If you have other scripts invoked by CVSROOT/loginfo, you will
want to make sure to change any running under the "DEFAULT"
keyword to "ALL" like the loginfo handler, and probably
carefully read the execution rules for CVSROOT/loginfo from the
CVS manual.
If you are running the Unix port of CVS-NT, the handler script
need to know about it. CVS-NT delivers commit information to
loginfo scripts differently than the way mainstream CVS does.
Your command line should look like this:
ALL <VIEWVC_INSTALLATION_DIRECTORY>/bin/loginfo-handler %{sVv} cvsnt
ALL <VIEWVC_INSTALLATION_DIRECTORY>/loginfo-handler %{sVv} cvsnt
The extra 'cvsnt' parameter tells the handler script to parse the
commit information in a different way.
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"
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.
For Subversion repositories, there is a single script called
svndbadmin that performs both of the above tasks.
You should be ready to go. Click one of the "Query revision history"
links in ViewVC directory listings and give it a try.
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>/svndbadmin rebuild "$REPOS" "$REV"
If you allow revision property changes in your repository, 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.
6) You should be ready to go. Click one of the "Query revision history"
links in ViewVC directory listings and give it a try.
ENABLING SYNTAX COLORATION
--------------------------
ENSCRIPT AND HIGHLIGHT CONFIGURATION
------------------------------------
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 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 the 'use_enscript' or 'use_highlight' options in viewvc.conf to 1.
3) You may also need to set 'enscript_path' and 'highlight_path' options
if the executables are not located on the system PATH.
4) That's it!
CVSGRAPH CONFIGURATION
@@ -424,52 +371,35 @@ a reasonable number of revisions and branches.
4) There is a file <VIEWVC_INSTALLATION_DIRECTORY>/cvsgraph.conf that
you may want to edit if desired to set color and font characteristics.
See the cvsgraph.conf documentation. No edits are required in
cvsgraph.conf for operation with ViewVC.
cvsgraph.conf for operation with viewvc.
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.)
ViewVC supports browsing of Subversion repositories. To use ViewVC
with Subversion, make sure you have both 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
from within your Python interpreter. Here's an example of doing so
which doubles as a quick way to check what version of the Subversion
Python binding you have:
bindings has been successful if you can import the 'svn.repos' module
from within your Python interpreter:
% python
Python 2.2.2 (#1, Oct 29 2002, 02:47:30)
[GCC 2.96 20000731 (Red Hat Linux 7.2 2.96-108.7.2)] on linux2
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'
>>>
>>> import svn.repos
>>>
Note that by default, Subversion installs its bindings in a location
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.
For example, you might want to create .pth file in your Python
installation directory's site-packages area which tells Python where
to find additional modules (in this case, you Subversion Python
bindings). You would do this as follows (and as root):
$ echo "/path/to/svn/bindings" > /path/to/python/site-packages/svn.pth
(Though, obviously, with the correct paths specified.)
search path, or by relocation the bindings to some place in that
search path.
Configuration of the Subversion repositories happens in much the same
way as with CVS repositories, only with the 'svn_roots' configuration
@@ -479,42 +409,39 @@ variable instead of the 'cvs_roots' one.
IF YOU HAVE PROBLEMS ...
------------------------
If nothing seems to work:
If you've trouble to make viewvc.cgi work:
* Check if you can execute CGI-scripts (Apache needs to have an
ScriptAlias /cgi-bin or cgi-script Handler defined). Try to
execute a simple CGI-script that often comes with the distribution
of the webserver; locate the logfiles and try to find hints which
explain the malfunction
=== If nothing seems to work:
* View the entries in the webserver's error.log
o check if you can execute CGI-scripts (Apache needs to have an
ScriptAlias /cgi-bin or cgi-script Handler defined). Try to
execute a simple CGI-script that often comes with the distribution
of the webserver; locate the logfiles and try to find hints
which explain the malfunction
o view the entries in the webserver's error.log
If ViewVC seems to work but doesn't show the expected result (Typical
error: you can't see any files)
=== If viewvc seems to work but doesn't show the expected result
(Typical error: you can't see any files)
* Check whether the CGI-script has read-permissions to your
CVS-Repository. The CGI-script generally runs as the same user
that the web server does, often user 'nobody' or 'httpd'.
o check whether the CGI-script has read-permissions to your
CVS-Repository. The CGI-script often runs as the user 'nobody'
or 'httpd' ..
* Does ViewVC find your RCS utilities? (edit rcs_dir)
o does viewvc find your RCS utilities? (edit rcs_path)
If something else happens or you can't get it to work:
=== If something else happens or you can't get it to work:
* Check the ViewVC home page:
o check the ViewVC home page:
http://viewvc.org/
http://viewvc.org/
* Review the ViewVC mailing list archive to see if somebody else had
the same problem, and it was solved:
o review the ViewVC mailing list archive to see if somebody else had
the same problem, and it was solved:
http://viewvc.tigris.org/servlets/SummarizeList?listName=users
http://viewvc.tigris.org/servlets/SummarizeList?listName=users
* Check the ViewVC issue database to see if the problem you are
seeing is the result of a known bug:
o send mail to the ViewVC mailing list: users@viewvc.tigris.org
http://viewvc.tigris.org/issues/query.cgi
* Send mail to the ViewVC mailing list, users@viewvc.tigris.org.
NOTE: make sure you provide an accurate description of the problem
-- including the version of ViewVC you are using -- and any
relevant tracebacks or error logs.
NOTE: make sure you provide an accurate description of the problem
and any relevant tracebacks or error logs.

View File

View File

@@ -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,71 +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
db.PurgeRepository(root)
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 **'

View File

@@ -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
@@ -32,9 +32,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")))
#########################################################################
@@ -44,18 +44,14 @@ import getopt
import re
import cvsdb
import viewvc
import vclib.ccvs
import vclib.bincvs
DEBUG_FLAG = 0
## output functions
def debug(text):
def debug(text):
if DEBUG_FLAG:
if type(text) != (type([])):
text = [text]
for line in text:
line = line.rstrip('\n\r')
print 'DEBUG(viewvc-loginfo):', line
print 'DEBUG(viewvc-loginfo):', text
def warning(text):
print 'WARNING(viewvc-loginfo):', text
@@ -65,166 +61,144 @@ def error(text):
sys.exit(1)
_re_revisions = re.compile(
r",(?P<old>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and first revision
r",(?P<new>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and second revision
r"(?:$| )" # space or end of string
r",(?P<old>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and first revision number
r",(?P<new>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and second revision number
r"(?:$| )" # space or end of string
)
def Cvs1Dot12ArgParse(args):
"""CVS 1.12 introduced a new loginfo format while provides the various
pieces of interesting version information to the handler script as
individual arguments instead of as a single string."""
if args[1] == '- New directory':
return None, None
elif args[1] == '- Imported sources':
return None, None
else:
directory = args.pop(0)
files = []
while len(args) >= 3:
files.append(args[0:3])
args = args[3:]
return directory, files
def HeuristicArgParse(s, repository):
"""Older versions of CVS (except for CVSNT) do not escape spaces in file
and directory names that are passed to the loginfo handler. Since the input
to loginfo is a space separated string, this can lead to ambiguities. This
function attempts to guess intelligently which spaces are separators and
which are part of file or directory names. It disambiguates spaces in
filenames from the separator spaces between files by assuming that every
space which is preceded by two well-formed revision numbers is in fact a
separator. It disambiguates the first separator space from spaces in the
directory name by choosing the longest possible directory name that
actually exists in the repository"""
"""Current versions of CVS (except for CVSNT) do not escape spaces in file
and directory names that are passed to the loginfo handler. Since the input
to loginfo is a space separated string, this can lead to ambiguities. This
function attempts to guess intelligently which spaces are separators and
which are part of file or directory names. It disambiguates spaces in
filenames from the separator spaces between files by assuming that every
space which is preceded by two well-formed revision numbers is in fact a
separator. It disambiguates the first separator space from spaces in the
directory name by choosing the longest possible directory name that actually
exists in the repository"""
if (s[-16:] == ' - New directory'
or s[:26] == ' - New directory,NONE,NONE'):
return None, None
if (s[-19:] == ' - Imported sources'
or s[-29:] == ' - Imported sources,NONE,NONE'):
return None, None
file_data_list = []
start = 0
while 1:
m = _re_revisions.search(s, start)
if start == 0:
if m is None:
error('Argument "%s" does not contain any revision numbers' \
% s)
directory, filename = FindLongestDirectory(s[:m.start()],
repository)
if directory is None:
error('Argument "%s" does not start with a valid directory' \
% s)
debug('Directory name is "%s"' % directory)
else:
if m is None:
warning('Failed to interpret past position %i in the loginfo '
'argument, leftover string is "%s"' \
% start, pos[start:])
filename = s[start:m.start()]
old_version, new_version = m.group('old', 'new')
file_data_list.append((filename, old_version, new_version))
debug('File "%s", old revision %s, new revision %s'
% (filename, old_version, new_version))
start = m.end()
if start == len(s): break
return directory, file_data_list
def FindLongestDirectory(s, repository):
"""Splits the first part of the argument string into a directory name
and a file name, either of which may contain spaces. Returns the longest
possible directory name that actually exists"""
parts = string.split(s, " ")
for i in range(len(parts)-1, 0, -1):
directory = string.join(parts[:i])
filename = string.join(parts[i:])
if os.path.isdir(os.path.join(repository, directory)):
return directory, filename
if s[-16:] == ' - New directory':
return None, None
if s[-19:] == r' - Imported sources':
return None, None
file_data_list = []
start = 0
while 1:
m = _re_revisions.search(s, start)
if start == 0:
if m is None:
error('Argument "%s" does not contain any revision numbers' % s)
directory, filename = FindLongestDirectory(s[:m.start()], repository)
if directory is None:
error('Argument "%s" does not start with a valid directory' % s)
debug('Directory name is "%s"' % directory)
else:
if m is None:
warning('Failed to interpret past position %i in the loginfo argument, '
'leftover string is "%s"' % start, pos[start:])
filename = s[start:m.start()]
old_version, new_version = m.group('old', 'new')
file_data_list.append((filename, old_version, new_version))
debug('File "%s", old revision %s, new revision %s'
% (filename, old_version, new_version))
start = m.end()
if start == len(s): break
return directory, file_data_list
def FindLongestDirectory(s, repository):
"""Splits the first part of the argument string into a directory name
and a file name, either of which may contain spaces. Returns the longest
possible directory name that actually exists"""
parts = string.split(s, " ")
for i in range(len(parts)-1, 0, -1):
directory = string.join(parts[:i])
filename = string.join(parts[i:])
if os.path.isdir(os.path.join(repository, directory)):
return directory, filename
return None, None
_re_cvsnt_revisions = re.compile(
r"(?P<filename>.*)" # comma and first revision
r",(?P<old>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and first revision
r",(?P<new>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and second revision
r"$" # end of string
r"(?P<filename>.*)" # comma and first revision number
r",(?P<old>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and first revision number
r",(?P<new>(?:\d+\.\d+)(?:\.\d+\.\d+)*|NONE)" # comma and second revision number
r"$" # end of string
)
def CvsNtArgParse(s, repository):
"""CVSNT escapes all spaces in filenames and directory names with
backslashes"""
"""CVSNT escapes all spaces in filenames and directory names with
backslashes"""
if s[-18:] == r' -\ New\ directory':
return None, None
if s[-18:] == r' -\ New\ directory':
return None, None
if s[-21:] == r' -\ Imported\ sources':
return None, None
if s[-21:] == r' -\ Imported\ sources':
return None, None
file_data_list = []
directory, pos = NextFile(s)
debug('Directory name is "%s"' % directory)
file_data_list = []
while 1:
fileinfo, pos = NextFile(s, pos)
if fileinfo is None:
break
directory, pos = NextFile(s)
debug('Directory name is "%s"' % directory)
m = _re_cvsnt_revisions.match(fileinfo)
if m is None:
warning('Can\'t parse file information in "%s"' % fileinfo)
continue
while 1:
fileinfo, pos = NextFile(s, pos)
if fileinfo is None:
break
file_data = m.group('filename', 'old', 'new')
file_data_list.append(file_data)
m = _re_cvsnt_revisions.match(fileinfo)
if m is None:
warning('Can\'t parse file information in "%s"' % fileinfo)
continue
debug('File "%s", old revision %s, new revision %s' % file_data)
file_data = m.group('filename', 'old', 'new')
return directory, file_data_list
debug('File "%s", old revision %s, new revision %s' % file_data)
file_data_list.append(file_data)
return directory, file_data_list
def NextFile(s, pos = 0):
escaped = 0
ret = ''
i = pos
while i < len(s):
c = s[i]
if escaped:
ret += c
escaped = 0
elif c == '\\':
escaped = 1
elif c == ' ':
return ret, i + 1
else:
ret += c
i += 1
escaped = 0
ret = ''
i = pos
while i < len(s):
c = s[i]
if escaped:
ret += c
escaped = 0
elif c == '\\':
escaped = 1
elif c == ' ':
return ret, i + 1
else:
ret += c
i += 1
return ret or None, i
return ret or None, i
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))
@@ -261,58 +235,41 @@ if __name__ == '__main__':
debug('Repository name is "%s"' % repository)
## parse arguments
argc = len(sys.argv)
debug('Got %d arguments:' % (argc))
debug(map(lambda x: ' ' + x, sys.argv))
# if we have more than 3 arguments, we are likely using the
# newer loginfo format introduced in CVS 1.12:
#
# ALL <path>/bin/loginfo-handler %p %{sVv}
if argc > 3:
directory, files = Cvs1Dot12ArgParse(sys.argv[1:])
if len(sys.argv) > 1:
# the first argument should contain file version information
arg = sys.argv[1]
else:
if len(sys.argv) > 1:
# the first argument should contain file version information
arg = sys.argv[1]
# if there are no arguments, read version information from first line
# of input like old versions of viewcvs
arg = string.rstrip(sys.stdin.readline())
if len(sys.argv) > 2:
# if there is a second argument it indicates which parser should be
# used to interpret the version information
if sys.argv[2] == 'cvs':
fun = HeuristicArgParse
elif sys.argv[2] == 'cvsnt':
fun = CvsNtArgParse
else:
# if there are no arguments, read version information from
# first line of input like old versions of ViewCVS did
arg = string.rstrip(sys.stdin.readline())
if len(sys.argv) > 2:
# if there is a second argument it indicates which parser
# should be used to interpret the version information
if sys.argv[2] == 'cvs':
fun = HeuristicArgParse
elif sys.argv[2] == 'cvsnt':
fun = CvsNtArgParse
else:
error('Bad arguments')
error('Bad arguments')
else:
# if there is no second argument, guess which parser to use based
# on the operating system. Since CVSNT now runs on Windows and
# Linux, the guess isn't neccessarily correct
if sys.platform == "win32":
fun = CvsNtArgParse
else:
# if there is no second argument, guess which parser to use based
# on the operating system. Since CVSNT now runs on Windows and
# Linux, the guess isn't necessarily correct
if sys.platform == "win32":
fun = CvsNtArgParse
else:
fun = HeuristicArgParse
directory, files = fun(arg, repository)
fun = HeuristicArgParse
debug('Discarded from stdin:')
debug(map(lambda x: ' ' + x, sys.stdin.readlines())) # consume stdin
if len(sys.argv) > 3:
error('Bad arguments')
repository = cvsdb.CleanRepository(repository)
debug('Repository: %s' % (repository))
debug('Directory: %s' % (directory))
debug('Files: %s' % (str(files)))
directory, files = fun(arg, repository)
if files is None:
debug('Not a checkin, nothing to do')
debug('Not a checkin, nothing to do')
else:
ProcessLoginfo(repository, directory, files)
ProcessLoginfo(repository, directory, files)
sys.exit(0)

View File

@@ -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,29 +18,28 @@
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,
branch varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE branch (branch)
) TYPE=MyISAM;
);
DROP TABLE IF EXISTS checkins;
CREATE TABLE checkins (
@@ -63,7 +62,7 @@ CREATE TABLE checkins (
KEY dirid (dirid),
KEY fileid (fileid),
KEY branchid (branchid)
) TYPE=MyISAM;
);
DROP TABLE IF EXISTS descs;
CREATE TABLE descs (
@@ -72,7 +71,7 @@ CREATE TABLE descs (
hash bigint(20) DEFAULT '0' NOT NULL,
PRIMARY KEY (id),
KEY hash (hash)
) TYPE=MyISAM;
);
DROP TABLE IF EXISTS dirs;
CREATE TABLE dirs (
@@ -80,7 +79,7 @@ CREATE TABLE dirs (
dir varchar(255) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE dir (dir)
) TYPE=MyISAM;
);
DROP TABLE IF EXISTS files;
CREATE TABLE files (
@@ -88,15 +87,15 @@ CREATE TABLE files (
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,
who varchar(32) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE who (who)
) TYPE=MyISAM;
);
DROP TABLE IF EXISTS repositories;
CREATE TABLE repositories (
@@ -104,7 +103,7 @@ CREATE TABLE repositories (
repository varchar(64) binary DEFAULT '' NOT NULL,
PRIMARY KEY (id),
UNIQUE repository (repository)
) TYPE=MyISAM;
);
DROP TABLE IF EXISTS tags;
CREATE TABLE tags (
@@ -118,216 +117,28 @@ CREATE TABLE tags (
KEY dirid (dirid),
KEY fileid (fileid),
KEY branchid (branchid)
) 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)

View File

@@ -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,23 +42,9 @@ 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)

View File

@@ -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)

View File

@@ -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
@@ -54,16 +54,14 @@ import compat; compat.for_standalone()
class Options:
port = 49152 # default TCP/IP port used for the server
port = 7467 # default TCP/IP port used for the server
start_gui = 0 # No GUI unless requested.
daemon = 0 # stay in the foreground by default
repositories = {} # use default repositories specified in config
if sys.platform == 'mac':
host = '127.0.0.1'
else:
host = 'localhost'
script_alias = 'viewvc'
config_file = None
# --- web browser interface: ----------------------------------------------
@@ -226,16 +224,9 @@ If this doesn't work, please click on the link above.
save_stdout = sys.stdout
save_stderr = sys.stderr
# For external tools like enscript we also need to redirect
# the real stdout file descriptor.
#
# FIXME: This code used to carry the following comment:
#
# (On windows, reassigning the sys.stdout variable is sufficient
# because pipe_cmds makes it the standard output for child
# processes.)
#
# But we no longer use pipe_cmds. So at the very least, the
# comment is stale. Is the code okay, though?
# the real stdout file descriptor. (On windows, reassigning the
# sys.stdout variable is sufficient because pipe_cmds makes it
# the standard output for child processes.)
if sys.platform != "win32": save_realstdout = os.dup(1)
try:
try:
@@ -292,16 +283,10 @@ If this doesn't work, please click on the link above.
# XXX Move this code out of this function.
# Early loading of configuration here. Used to
# allow tinkering with some configuration settings:
handle_config(options.config_file)
handle_config()
if options.repositories:
cfg.general.default_root = "Development"
for repo_name in options.repositories.keys():
repo_path = os.path.normpath(options.repositories[repo_name])
if os.path.exists(os.path.join(repo_path, "CVSROOT",
"config")):
cfg.general.cvs_roots[repo_name] = repo_path
elif os.path.exists(os.path.join(repo_path, "format")):
cfg.general.svn_roots[repo_name] = repo_path
cfg.general.cvs_roots.update(options.repositories)
elif cfg.general.cvs_roots.has_key("Development") and \
not os.path.isdir(cfg.general.cvs_roots["Development"]):
sys.stderr.write("*** No repository found. Please use the -r option.\n")
@@ -313,11 +298,12 @@ If this doesn't work, please click on the link above.
cfg.options.docroot = None
# if cvsnt isn't found, fall back to rcs
if (cfg.conf_path is None and cfg.utilities.cvsnt):
if (cfg.conf_path is None
and cfg.general.cvsnt_exe_path):
import popen
cvsnt_works = 0
try:
fp = popen.popen(cfg.utilities.cvsnt, ['--version'], 'rt')
fp = popen.popen(cfg.general.cvsnt_exe_path, ['--version'], 'rt')
try:
while 1:
line = fp.readline()
@@ -332,16 +318,16 @@ If this doesn't work, please click on the link above.
except:
pass
if not cvsnt_works:
cfg.utilities.cvsnt = None
cfg.cvsnt_exe_path = None
ViewVC_Server(host, port, callback).serve_until_quit()
except (KeyboardInterrupt, select.error):
pass
print 'server stopped'
def handle_config(config_file):
def handle_config():
global cfg
cfg = viewvc.load_config(config_file or CONF_PATHNAME)
cfg = viewvc.load_config(CONF_PATHNAME)
# --- graphical interface: --------------------------------------------------
@@ -382,7 +368,7 @@ def gui(host, port):
# Early loading of configuration here. Used to
# allow tinkering with configuration settings through the gui:
handle_config(options.config_file)
handle_config()
if not LIBRARY_DIR:
cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist"
@@ -396,6 +382,14 @@ def gui(host, port):
command=self.toggle_use_cvsgraph)
self.cvsgraph_toggle.pack(side='top', anchor='w')
# enscript toggle:
self.enscript_ivar = Tkinter.IntVar()
self.enscript_ivar.set(cfg.options.use_enscript)
self.enscript_toggle = Tkinter.Checkbutton(self.options_frm,
text="enable enscript (needs binary)", var=self.enscript_ivar,
command=self.toggle_use_enscript)
self.enscript_toggle.pack(side='top', anchor='w')
# show_subdir_lastmod toggle:
self.subdirmod_ivar = Tkinter.IntVar()
self.subdirmod_ivar.set(cfg.options.show_subdir_lastmod)
@@ -421,31 +415,20 @@ def gui(host, port):
command=self.toggle_use_localtime)
self.use_localtime_toggle.pack(side='top', anchor='w')
# log_pagesize integer var:
self.log_pagesize_lbl = Tkinter.Label(self.options_frm,
text='Paging (number of items per log page, 0 disables):')
self.log_pagesize_lbl.pack(side='top', anchor='w')
self.log_pagesize_ivar = Tkinter.IntVar()
self.log_pagesize_ivar.set(cfg.options.log_pagesize)
self.log_pagesize_entry = Tkinter.Entry(self.options_frm,
width=10, textvariable=self.log_pagesize_ivar)
self.log_pagesize_entry.bind('<Return>', self.set_log_pagesize)
self.log_pagesize_entry.pack(side='top', anchor='w')
# dir_pagesize integer var:
self.dir_pagesize_lbl = Tkinter.Label(self.options_frm,
text='Paging (number of items per dir page, 0 disables):')
self.dir_pagesize_lbl.pack(side='top', anchor='w')
self.dir_pagesize_ivar = Tkinter.IntVar()
self.dir_pagesize_ivar.set(cfg.options.dir_pagesize)
self.dir_pagesize_entry = Tkinter.Entry(self.options_frm,
width=10, textvariable=self.dir_pagesize_ivar)
self.dir_pagesize_entry.bind('<Return>', self.set_dir_pagesize)
self.dir_pagesize_entry.pack(side='top', anchor='w')
# use_pagesize integer var:
self.usepagesize_lbl = Tkinter.Label(self.options_frm,
text='Paging (number of items per page, 0 disables):')
self.usepagesize_lbl.pack(side='top', anchor='w')
self.use_pagesize_ivar = Tkinter.IntVar()
self.use_pagesize_ivar.set(cfg.options.use_pagesize)
self.use_pagesize_entry = Tkinter.Entry(self.options_frm,
width=10, textvariable=self.use_pagesize_ivar)
self.use_pagesize_entry.bind('<Return>', self.set_use_pagesize)
self.use_pagesize_entry.pack(side='top', anchor='w')
# directory view template:
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
text='Choose HTML Template for the Directory pages:')
text='Chooose HTML Template for the Directory pages:')
self.dirtemplate_lbl.pack(side='top', anchor='w')
self.dirtemplate_svar = Tkinter.StringVar()
self.dirtemplate_svar.set(cfg.templates.directory)
@@ -464,7 +447,7 @@ def gui(host, port):
# log view template:
self.logtemplate_lbl = Tkinter.Label(self.options_frm,
text='Choose HTML Template for the Log pages:')
text='Chooose HTML Template for the Log pages:')
self.logtemplate_lbl.pack(side='top', anchor='w')
self.logtemplate_svar = Tkinter.StringVar()
self.logtemplate_svar.set(cfg.templates.log)
@@ -517,6 +500,9 @@ def gui(host, port):
def toggle_use_cvsgraph(self, event=None):
cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
def toggle_use_enscript(self, event=None):
cfg.options.use_enscript = self.enscript_ivar.get()
def toggle_use_localtime(self, event=None):
cfg.options.use_localtime = self.use_localtime_ivar.get()
@@ -526,11 +512,8 @@ def gui(host, port):
def toggle_useresearch(self, event=None):
cfg.options.use_re_search = self.useresearch_ivar.get()
def set_log_pagesize(self, event=None):
cfg.options.log_pagesize = self.log_pagesize_ivar.get()
def set_dir_pagesize(self, event=None):
cfg.options.dir_pagesize = self.dir_pagesize_ivar.get()
def set_use_pagesize(self, event=None):
cfg.options.use_pagesize = self.use_pagesize_ivar.get()
def set_templates_log(self, event=None):
cfg.templates.log = self.logtemplate_svar.get()
@@ -587,9 +570,8 @@ def cli(argv):
class BadUsage(Exception): pass
try:
opts, args = getopt.getopt(argv[1:], 'gdc:p:r:h:s:',
['gui', 'daemon', 'config-file=', 'host=',
'port=', 'repository=', 'script-alias='])
opts, args = getopt.getopt(argv[1:], 'gp:r:h:s:',
['gui', 'port=', 'repository=', 'script-alias='])
for opt, val in opts:
if opt in ('-g', '--gui'):
options.start_gui = 1
@@ -600,29 +582,16 @@ def cli(argv):
options.repositories[symbolic_name] = val
else:
options.repositories["Development"] = val
elif opt in ('-d', '--daemon'):
options.daemon = 1
elif opt in ('-p', '--port'):
try:
options.port = int(val)
except ValueError:
raise BadUsage, "Port '%s' is not a valid port number" \
% (val)
raise BadUsage
elif opt in ('-h', '--host'):
options.host = val
elif opt in ('-s', '--script-alias'):
options.script_alias = \
string.join(filter(None, string.split(val, '/')), '/')
elif opt in ('-c', '--config-file'):
options.config_file = val
if options.start_gui and options.config_file:
raise BadUsage, "--config-file option is not valid in GUI mode."
if not options.start_gui and not options.port:
raise BadUsage, "You must supply a valid port, or run in GUI mode."
if options.daemon:
pid = os.fork()
if pid != 0:
sys.exit()
if options.start_gui:
gui(options.host, options.port)
return
@@ -632,50 +601,42 @@ def cli(argv):
options.script_alias)
serve(options.host, options.port, ready)
return
except (getopt.error, BadUsage), err:
raise BadUsage
except (getopt.error, BadUsage):
cmd = os.path.basename(sys.argv[0])
port = options.port
host = options.host
script_alias = options.script_alias
if str(err):
sys.stderr.write("ERROR: %s\n\n" % (str(err)))
sys.stderr.write("""Usage: %(cmd)s [OPTIONS]
print """ViewVC standalone - a simple standalone HTTP-Server
Run a simple, standalone HTTP server configured to serve up ViewVC
requests.
Usage: %(cmd)s [OPTIONS]
Options:
Available Options:
--config-file=PATH (-c) Use the file at PATH as the ViewVC configuration
file. If not specified, ViewVC will try to use
the configuration file in its installation tree;
otherwise, built-in default values are used.
(Not valid in GUI mode.)
--daemon (-d) Background the server process.
--host=HOST (-h) Start the server listening on HOST. You need
to provide the hostname if you want to
access the standalone server from a remote
machine. [default: %(host)s]
-h <host>, --host=<host>:
Start the HTTP server listening on <host>. You need to provide
the hostname if you want to access the standalone server from a
remote machine. [default: %(host)s]
--port=PORT (-p) Start the server on the given PORT.
[default: %(port)d]
-p <port>, --port=<port>:
Start an HTTP server on the given port. [default: %(port)d]
--repository=PATH (-r) Serve up the Subversion or CVS repository located
at PATH. This option may be used more than once.
-r <path>, --repository=<path>:
Specify a path for a CVS repository. Repository definitions are
typically read from the viewvc.conf file, if available. This
option may be used more than once.
--script-alias=PATH (-s) Specify the ScriptAlias, the artificial path
location that at which ViewVC appears to be
located. For example, if your ScriptAlias is
"cgi-bin/viewvc", then ViewVC will be accessible
at "http://%(host)s:%(port)s/cgi-bin/viewvc".
[default: %(script_alias)s]
--gui (-g) Pop up a graphical interface for serving and
testing ViewVC. NOTE: this requires a valid
X11 display connection.
""" % locals())
-s <path>, --script-alias=<path>:
Specify the ScriptAlias, the artificial path location that at
which ViewVC appears to be located. For example, if your
ScriptAlias is "cgi-bin/viewvc", then ViewVC will appear to be
accessible at the URL "http://%(host)s:%(port)s/cgi-bin/viewvc".
[default: %(script_alias)s]
-g, --gui:
Pop up a graphical interface for serving and testing ViewVC.
NOTE: this requires a valid X11 display connection.
""" % locals()
if __name__ == '__main__':
options = Options()

View File

@@ -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,71 +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)
if command in ('rebuild', 'purge'):
if verbose:
print "Purging commit info for repository root `%s'" % repository
db.PurgeRepository(repository)
repo = SvnRepo(repository)
if command == 'rebuild' or (command == 'update' and not revs):
repo = SvnRepo(repository, pool)
if rev:
handle_revision(db, command, repo, rev, verbose)
else:
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
else:
r = int(r)
if r < 0:
raise ValueError, "invalid revision '%d'" % (r)
return r
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')
@@ -310,52 +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()
repository = args[2]
if not os.path.exists(repository):
sys.stderr.write('ERROR: could not find repository %s\n' % args[2])
sys.stderr.write('ERROR: could not find repository %s\n' % repository)
usage()
repository = vclib.svn.canonicalize_rootpath(repository)
revs = []
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 = 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)

View File

@@ -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).
#
#

View File

@@ -1,813 +0,0 @@
#---------------------------------------------------------------------------
#
# Configuration file for ViewVC
#
# Information on ViewVC is located at the following web site:
# http://viewvc.org/
#
#---------------------------------------------------------------------------
# THE FORMAT OF THIS CONFIGURATION FILE
#
# This file is delineated by sections, specified in [brackets]. Within
# each section, are a number of configuration settings. These settings
# take the form of: name = value. Values may be continued on the
# following line by indenting the continued line.
#
# WARNING: Indentation *always* means continuation. Name=value lines
# should always start in column zero.
#
# Comments should always start in column zero, and are identified
# with "#".
#
# Certain configuration settings may have multiple values. These should
# be separated by a comma. The settings where this is allowed are noted
# below. Any other setting that requires special syntax is noted at that
# setting.
#
#
# SOME TERMINOLOGY USED HEREIN
#
# "root" - This is a CVS or Subversion repository. For Subversion, the
# meaning is pretty clear, as the virtual, versioned directory tree
# stored inside a Subversion repository looks nothing like the actual
# tree visible with shell utilities that holds the repository. For
# CVS, this is more confusing, because CVS's repository layout mimics
# (actually, defines) the layout of the stuff housed in the repository.
# But a CVS repository can be identified by the presence of a CVSROOT
# subdirectory in its root directory.
#
# "module" - A module is a top-level subdirectory of a root, usually
# associated with the concept of a single "project" among many housed
# within a single repository.
#
#
# BASIC VIEWVC CONFIGURATION HINTS
#
# While ViewVC has quite a few configuration options, you generally
# only need to change a small subset of them to get your ViewVC
# installation working properly. Here are some options that we
# recommend you pay attention to. Of course, don't try to change the
# options here -- do so in the relevant section of the configuration
# file below.
#
# For correct operation, you will probably need to change the following
# configuration variables:
#
# cvs_roots (for CVS)
# svn_roots (for Subversion)
# root_parents (for CVS or Subversion)
# default_root
# root_as_url_component
# rcs_dir
# mime_types_files
# the many options in the [utilities] section
#
# It is usually desirable to change the following variables:
#
# address
# forbidden
#
# To optimize delivery of ViewVC static files:
#
# docroot
#
# To customize the display of ViewVC for your site:
#
# template_dir
# the [templates] override section
#
#---------------------------------------------------------------------------
[general]
#
# This setting specifies each of the CVS roots on your system and assigns
# names to them. Each root should be given by a "name: path" value. Multiple
# roots should be separated by commas and can be placed on separate lines.
#
cvs_roots = cvs: /home/cvsroot
#
# This setting specifies each of the Subversion roots (repositories)
# on your system and assigns names to them. Each root should be given
# by a "name: path" value. Multiple roots should be separated by
# commas and can be placed on separate lines.
#
#svn_roots = svn: /home/svnrepos
# The 'root_parents' setting specifies a list of directories in which
# any number of repositories may reside. Rather than force you to add
# a new entry to 'cvs_roots' or 'svn_roots' each time you create a new
# repository, ViewVC rewards you for organising all your repositories
# under a few parent directories by allowing you to simply specifiy
# just those parent directories. ViewVC will then notice each
# repository in that directory as a new root whose name is the
# subdirectory of the parent path in which that repository lives.
#
# You can specify multiple parent paths separated by commas or new lines.
#
# Caution: these names can, of course, clash with names you have
# defined in your cvs_roots or svn_roots configuration items. If this
# occurs, you can either rename the offending repository on disk, or
# grant new names to the clashing item in cvs_roots or svn_roots.
# Each parent path is processed sequentially, so repositories under
# later parent paths may override earlier ones.
#
#root_parents = /home/svn-repositories : svn,
# /home/cvs-repositories : cvs
# This is the name of the default root. Valid names include those
# explicitly listed in the cvs_roots and svn_roots configuration
# options, as well as those implicitly indicated by virtue of being
# the basenames of repositories found in the root_parents option
# locations.
#
# Note: This setting is ignored when root_as_url_component is enabled.
default_root = cvs
#
# This is a list of pathnames to a set of MIME type mapping files to
# help ViewVC guess the correct MIME type of a versioned file. The
# pathnames listed here are specified in order of authoritativeness
# either as absolute paths or relative to this configuration file.
#
# As a convenience, ViewVC provides a MIME type mapping file
# (mimetypes.conf) which is, by default, the preferred provider of
# MIME type mapping answers, but which is also empty. If you find
# that ViewVC is unable to accurately guess MIME types based on the
# extensions of some of your versioned files, you can add records of
# your preferred mappings to the provided mimetypes.conf file (or to
# your system's mapping files, if you wish).
#
# You might, for example, wish to have ViewVC also consult the mapping
# files provided by your operating system and Apache.
#
# mime_types_files = mimetypes.conf,
# /etc/mime.types,
# /usr/local/apache2/conf/mime.types
mime_types_files = mimetypes.conf
# The address of the local repository maintainer. (This option is
# provided only as a convenience for ViewVC installations which are
# using the default template set, where the value of this option will
# be displayed in the footer of every ViewVC page.)
address =
#
# This option provides a mechanism for custom key/value pairs to be
# available to templates. These are stored in key/value (KV) files.
#
# The paths of the KV files are listed here, specified either as
# absolute paths or relative to this configuration file. The files
# use the same format as this configuration file, containing one or
# more user-defined sections, and user-defined options in those
# sections. ViewVC makes these options available to template authors
# as:
#
# kv.SECTION.OPTION
#
# Note that an option name can be dotted. For example:
#
# [my_images]
# logos.small = /images/small-logo.png
# logos.big = /images/big-logo.png
#
# Templates can use these with a directive like: [kv.my_images.logos.small]
#
# Note that section names which are common to multiple KV files will
# be merged. If two files have a [my_images] section, then the
# options in those two like-named sections will be merged together.
# If two files have the same option name in a section, then one will
# overwrite the other (and which one "wins" is unspecified).
#
# To further categorize the KV files, and how the values are provided to
# the templates, a KV file name may be annotated with an additional level
# of dotted naming. For example:
#
# kv_files = [asf]kv/images.conf
#
# Assuming the same section as above, the template would refer to an image
# using [kv.asf.my_images.logos.small]
#
# Lastly, it is possible to use %lang% in the filenames to specify a
# substitution of the selected language-tag.
#
# Example:
# kv_files = kv/file1.conf, kv/file2.conf, [i18n]kv/%lang%_data.conf
#
kv_files =
#
# This option is a comma-separated list of language-tag values
# available to ViewVC. The first language-tag listed is the default
# language, and will be used if an Accept-Language header is not
# present in the request, or none of the user's requested languages
# are available. If there are ties on the selection of a language,
# then the first to appear in the list is chosen.
#
# Some examples:
#
# languages = en-us, de
# languages = en-us, en-gb, de
# languages = de, fr, en-us
#
languages = en-us
#---------------------------------------------------------------------------
[utilities]
# ViewVC uses (sometimes optionally) various third-party programs to do some
# of the heavy lifting. Generally, it will attempt to execute those utility
# programs in such a way that if they are found in ViewVC's executable
# search path ($PATH, %PATH%, etc.) all is well. But sometimes these tools
# aren't installed in the executable search path, so here's where you can
# tell ViewVC where to find them.
#
# NOTE: Options with a "_dir" suffix are for configuring the directories
# in which certain programs live; otherwise, the option value should
# point to the actual program.
# RCS utilities, used for viewing CVS repositories
rcs_dir =
# rcs_dir = /usr/bin/
# ViewVC can use CVSNT (www.cvsnt.org) instead of the RCS utilities to
# retrieve information from CVS repositories. To enable use of CVSNT,
# set the "cvsnt" value to the path of the CVSNT executable. (If CVSNT
# is on the standard path, you can also set it to the name of the
# CVSNT executable). By default "cvsnt" is set to "cvs" on Windows and
# is not set on other platforms.
cvsnt =
# cvsnt =
# cvsnt = cvs
# cvsnt = K:\Program Files\cvsnt\cvs.exe
# cvsnt = = /usr/bin/cvs
# Subversion command-line client, used for viewing Subversion repositories
svn =
# svn = /usr/bin/svn
# GNU diff, used for showing file version differences
diff =
# diff = /usr/bin/diff
# CvsGraph, a graphical CVS version graph generator (see options.use_cvsgraph)
cvsgraph =
# cvsgraph = /usr/local/bin/cvsgraph
#---------------------------------------------------------------------------
[options]
# root_as_url_component: Interpret the first path component in the URL
# after the script location as the root to use. This is an
# alternative to using the "root=" query key. If ViewVC is configured
# with multiple repositories, this results in more natural looking
# ViewVC URLs.
# Note: Enabling this option will break backwards compatibility with
# any old ViewCVS URL which doesn't have an explicit "root" parameter.
root_as_url_component = 1
# checkout_magic: Use checkout links with magic /*checkout*/ prefixes so
# checked out HTML pages can have working links to other repository files
# Note: This option is DEPRECATED and should not be used in new ViewVC
# installations. Setting "default_file_view = co" achieves the same effect
checkout_magic = 0
# allowed_views: List the ViewVC views which are enabled. Views not
# in this comma-delited list will not be served (or, will return an
# error on attempted access).
# Possible values: "annotate", "co", "diff", "markup", "roots", "tar"
allowed_views = annotate, diff, markup, roots
# authorizer: The name of the ViewVC authorizer plugin to use when
# authorizing access to repository contents. This value must be the
# name of a Python module addressable as vcauth.MODULENAME (most
# easily accomplished by placing it in ViewVC's lib/vcauth/ directory)
# and which implements a ViewVCAuthorizer class (as a subclass of
# vcauth.GenericViewVCAuthorizer). You can provide custom parameters
# to the authorizer module by defining configuration sections named
# authz-MODULENAME and adding the parameter keys and values there.
#
# ViewVC provides the following modules:
# svnauthz - based on Subversion authz files
# forbidden - simple path glob matches against top-level root directories
# forbiddenre - root and path matches against regular expressions
#
# NOTE: Only one authorizer may be in use for a given ViewVC request.
# It doesn't matter if you configure the parameters of multiple
# authorizer plugins -- only the authorizer whose name is configured
# here (or effectively configured here via per-vhost or per-root
# configuration) will be activated.
authorizer =
# hide_cvsroot: Don't show the CVSROOT directory
# 1 Hide CVSROOT directory
# 0 Show CVSROOT directory
# NOTE: Someday this option may be removed in favor of letting
# individual authorizer plugin hide the CVSROOT.
hide_cvsroot = 1
# mangle_email_addresses: Mangle email addresses in marked-up output.
# There are various levels of mangling available:
# 0 - No mangling; markup un-mangled email addresses as hyperlinks
# 1 - Obfuscation (using entity encoding); no hyperlinking
# 2 - Data-dropping address truncation; no hyperlinking
# Note: this will not effect the display of versioned file contents, only
# addresses that appear in version control metadata (e.g. log messages).
mangle_email_addresses = 0
# default_file_view: "log", "co", or "markup"
# Controls whether the default view for file URLs is a checkout view or
# a log view. "log" is the default for backwards compatibility with old
# ViewCVS URLs, but "co" has the advantage that it allows ViewVC to serve
# static HTML pages directly from a repository with working links
# to other repository files
# Note: Changing this option may break compatibility with existing
# bookmarked URLs.
# Also note: If you choose one of the "co" or "markup" views, be sure
# to enable it (via the allowed_views option)
default_file_view = log
# http_expiration_time: Expiration time (in seconds) for cacheable
# pages served by ViewVC. Note that in most cases, a cache aware
# client will only revalidate the page after it expires (using the
# If-Modified-Since and/or If-None-Match headers) and that browsers
# will also revalidate the page when the reload button is pressed.
# Set to 0 to disable the transmission of these caching headers.
http_expiration_time = 600
# generate_etags: Generate Etag headers for relevant pages to assist
# in browser caching.
# 1 Generate Etags
# 0 Don't generate Etags
generate_etags = 1
# Don't use the svn:mime-type property to determine how to display a
# file in the markup view. This is especially helpful when versioned
# images carry the default Subversion-calculated MIME type of
# "application/octet-stream" (which isn't recognized as viewable type
# by browsers).
svn_ignore_mimetype = 0
# svn_config_dir: Path of the Subversion runtime configuration
# directory ViewVC should consult for various things, including cached
# remote authentication credentials. If unset, Subversion will use
# the default location(s) ($HOME/.subversion, etc.)
svn_config_dir =
# use the rcsparse Python module to retrieve CVS repository
# information instead of invoking rcs utilities [EXPERIMENTAL]
use_rcsparse = 0
# sort_by: File sort order
# file Sort by filename
# rev Sort by revision number
# date Sort by commit date
# author Sort by author
# log Sort by log message
sort_by = file
# sort_group_dirs: Group directories when sorting
# 1 Group directories together
# 0 No grouping -- sort directories as any other item would be sorted
sort_group_dirs = 1
# hide_attic: Hide or show the contents of the Attic subdirectory
# 1 Hide dead files inside Attic subdir
# 0 Show the files which are inside the Attic subdir
hide_attic = 1
# hide_errorful_entries: Hide or show errorful directory entries
# (perhaps due to not being readable, or some other rlog parsing
# error, etc.)
# 1 Hide errorful entries from the directory display
# 0 Show errorful entries (with their errors) in the directory display
hide_errorful_entries = 0
# log_sort: Sort order for log messages
# date Sort revisions by date
# rev Sort revision by revision number
# none Use the version control system's ordering
log_sort = date
# diff_format: Default diff format
# h Human readable
# u Unified diff
# c Context diff
# s Side by side
# l Long human readable (more context)
# f Full human readable (entire file)
diff_format = h
# hr_breakable: Diff view line breaks
# 1 lines break at spaces
# 0 no line breaking
# Or, use a positive integer > 1 to cut lines after that many characters
hr_breakable = 1
# give out function names in human readable diffs
# this just makes sense if we have C-files, otherwise
# diff's heuristic doesn't work well ..
# ( '-p' option to diff)
hr_funout = 0
# ignore whitespaces for human readable diffs
# (indendation and stuff ..)
# ( '-w' option to diff)
hr_ignore_white = 0
# ignore diffs which are caused by
# keyword-substitution like $Id - Stuff
# ( '-kk' option to rcsdiff)
hr_ignore_keyword_subst = 1
# Enable highlighting of intraline changes in human readable diffs
# this feature is experimental and currently requires python 2.4
hr_intraline = 0
# allow compression with gzip of output if the Browser accepts it
# (HTTP_ACCEPT_ENCODING contains "gzip")
#allow_compress = 1
# The directory which contains the EZT templates used by ViewVC to
# customize the display of the various output views. ViewVC looks in
# this directory for files with names that match the name of the view
# ("log", "directory", etc.) plus the ".ezt" extension. If specified
# as a relative path, it is relative to the directory where this config
# file resides; absolute paths may be used as well.
#
# If %lang% occurs in the pathname, then the selected language will be
# substituted.
#
# See also the [templates] configuration section, where you can
# override templates on a per-view basis.
#
template_dir = templates
# Web path to a directory that contains ViewVC static files
# (stylesheets, images, etc.) If set, static files will get
# downloaded directory from this location. If unset, static files
# will be served by the ViewVC script (at a likely performance
# penalty, and from the "docroot" subdirectory of the directory
# specified by the "template_dir" option).
#docroot = /docroot
# Show last changelog message for CVS subdirectories
# NOTE: The current implementation makes many assumptions and may show
# the incorrect file at some times. The main assumption is that the
# last modified file has the newest filedate. But some CVS operations
# touches the file without even when a new version is not checked in,
# and TAG based browsing essentially puts this out of order, unless
# the last checkin was on the same tag as you are viewing. Enable
# this if you like the feature, but don't rely on correct results.
#
# ** WARNING: Enabling this will currently leak unauthorized path names **
show_subdir_lastmod = 0
# Show the most recent log entry in directory listings.
show_logs = 1
# Show log when viewing file contents
show_log_in_markup = 1
# Cross filesystem copies when traversing Subversion file revision histories.
cross_copies = 1
# Display dates as UTC or in local time zone
use_localtime = 0
#use_localtime = 1
### CONFIGURATION DEFAULTS ###
###
### Defaults for configuration variables that shouldn't need
### to be configured..
# the length to which the most recent log entry should be truncated when
# shown in the directory view
short_log_len = 80
# should we colorize known file content syntaxes? (requires Pygments module)
enable_syntax_coloration = 1
# Use CvsGraph. See http://www.akhphd.au.dk/~bertho/cvsgraph/ for
# documentation and download.
use_cvsgraph = 0
#use_cvsgraph = 1
# Location of the customized cvsgraph configuration file.
cvsgraph_conf = cvsgraph.conf
#
# Set to enable regular expression search of all files in a directory
#
# WARNING:
#
# Enabling this option can consume HUGE amounts of server time. A
# "checkout" must be performed on *each* file in a directory, and
# the result needs to be searched for a match against the regular
# expression.
#
#
# SECURITY WARNING: Denial Of Service
#
# Since a user can enter the regular expression, it is possible for
# them to enter an expression with many alternatives and a lot of
# backtracking. Executing that search over thousands of lines over
# dozens of files can easily tie up a server for a long period of
# time.
#
# This option should only be used on sites with trusted users. It is
# highly inadvisable to use this on a public site.
#
use_re_search = 0
# use_re_search = 1
#
# Split directory listings across pages as needed. Allows ViewVC to
# present discrete pages to the users instead of the entire directory.
# Set dir_pagesize to the number of entries you want displayed on a page,
# or 0 to disable pagination.
#
dir_pagesize = 0
# dir_pagesize = 20
#
# Split revision log listings across multiple pages as needed. Allows
# ViewVC to present discrete pages to the users instead of the entire log.
# Set log_pagesize to the number of entries you want displayed on a page,
# or 0 to disable pagination.
#
log_pagesize = 0
# log_pagesize = 20
# Limit number of changed paths shown per commit in the Subversion revision
# view and in query results. This is not a hard limit (the UI provides
# options to show all changed paths), but it prevents ViewVC from generating
# enormous and hard to read pages by default when they happen to contain
# import or merge commits affecting hundreds or thousands of files.
# Set to 0 to disable the limit.
limit_changes = 100
#---------------------------------------------------------------------------
[templates]
# You can override the templates used by various ViewVC views in this
# section. By default, ViewVC will look for templates in the
# directory specified by the "template_dir" configuration option (see
# the documentation for that option for details). But if you want to
# use a different template for a particular view, simply uncomment the
# appropriate option below and specify the currect location of the EZT
# template file you wish to use for that view.
#
# Templates are specified relative to the configured template
# directory (see the "template_dir" option), but absolute paths may
# also be used as well.
#
# If %lang% occurs in the pathname, then the selected language will be
# substituted.
#
# Note: the selected language is defined by the "languages" item in the
# [general] section, and based on the request's Accept-Language
# header.
#
#diff = diff.ezt
#directory = directory.ezt
### an alternative directory view
#directory = dir_new.ezt
#error = error.ezt
#file = file.ezt
#graph = graph.ezt
#log = log.ezt
### a table-based alternative log view
#log = log_table.ezt
#query = query.ezt
#query_form = query_form.ezt
#query_results = query_results.ezt
#revision = revision.ezt
#roots = roots.ezt
#---------------------------------------------------------------------------
[cvsdb]
# Set to 1 to enable the database integration feature, 0 otherwise.
enabled = 0
# Database hostname and port.
#host = localhost
#port = 3306
# ViewVC database name.
#database_name = ViewVC
# Username and password of user with read/write privileges to the ViewVC
# database.
#user =
#passwd =
# Username and password of user with read privileges to the ViewVC
# database.
#readonly_user =
#readonly_passwd =
# Limit the number of rows returned by a given query to this number.
#row_limit = 1000
# Limit the number of rows returned by a given query made as part an
# RSS feed request to this number. (Keeping in mind that RSS readers
# tend to poll regularly for new data, you might want to keep this set
# to a conservative number.)
#rss_row_limit = 100
# Check if the repository is found in the database before showing
# the query link and RSS feeds. Set to 1 to enable check.
#
# WARNING: Enabling this check adds the cost of a database connection
# and query to most ViewVC requests. If all your roots are represented
# in the commits database, or if you don't care about the creation of
# RSS and query links that might lead ultimately to error pages for
# certain of your roots, or if you simply don't want to add this extra
# cost to your ViewVC requests, leave this disabled.
#check_database_for_root = 0
#---------------------------------------------------------------------------
[vhosts]
# Virtual hosts are individual logical servers accessible via
# different hostnames, but which are all really the same physical
# computer. For example, you might have your web server configured to
# accept incoming traffic for both http://www.yourdomain.com/ and
# http://viewvc.yourdomain.com/. Users pointing their web browsers at
# each of those two URLs might see entirely different content via one
# URL versus the other, but all that content actually lives on the
# same computer, is served up via the same web server, and so
# on. It just *looks* like its coming from multiple servers.
#
# ViewVC allows you to customize its configuration options for
# individual virtual hosts. You might, for example, wish to expose
# all of your Subversion repositories at http://svn.yourdomain.com/viewvc/
# and all your CVS ones at http://cvs.yourdomain.com/viewvc/, with no
# cross-exposure. Using ViewVC's virtual host (vhost) configuration
# support, you can do this. Simply create two vhost configurations
# (one for each of your hostnames), then configure the cvs_roots
# option only for the vhost associated with cvs.yourdomain.com, and
# configure the svn_roots option only for the vhost associated with
# svn.yourdomain.com.
#
# This section is a freeform configuration section, where you create
# both the option names and their values. The names of the options
# are then treated as canonical names of virtual hosts, and their
# values are defined to be comma-delimited lists of hostname globs
# against which incoming ViewVC requests will be matched to figure out
# which vhost they apply to.
#
# After you've named and defined your vhosts, you may then create new
# configuration sections whose names are of the form
# vhost-VHOSTNAME/CONFIGSECTION. VHOSTNAME here is the canonical name
# of one of the virtual hosts you defined under the [vhosts] section.
# Inside those configuration sections, you override the standard
# ViewVC options typically found in the base configuration section
# named CONFIGSECTION ("general", "option", etc.)
#
# Here is an example:
#
# [vhosts]
# libs = libs.yourdomain.*, *.yourlibs.*
# gui = guiproject.yourdomain.*
#
# [vhost-libs/general]
# cvs_roots =
# svn_roots = svnroot: /var/svn/libs-repos
# default_root = svnroot
#
# [vhost-libs/options]
# show_logs = 1
#
# [vhost-gui/general]
# cvs_roots = cvsroot: /var/cvs/guiproject
# svn_roots =
# default_root = cvsroot
#
#---------------------------------------------------------------------------
# ViewVC recognizes per-root configuration overrides, too. To
# override the value of a configuration parameter only for a single
# root, create a configuration section whose names is of the form
# root-ROOTNAME/CONFIGSECTION. ROOTNAME here is the name of the root
# as defined explicitly in cvs_roots or svn_roots or implicitly as the
# basename of a root path in root_parents. Options found in this new
# configuration section override for this one root the corresponding
# options found in the base configuration section CONFIGSECTION
# ("options", "authz-*", etc.)
#
# Here is an example showing how to enable Subversion authz-based
# authorization for only the single root named "svnroot":
#
# [root-svnroot/options]
# authorizer = svnauthz
#
# [root-svnroot/authz-svnauthz]
# authzfile = /path/to/authzfile
#
#---------------------------------------------------------------------------
[authz-forbidden]
# The "forbidden" authorizer forbids access to repository modules,
# defined to be top-level subdirectories in a repository. You can use
# a simple list of modules, or something more complex:
#
# *) The "!" can be used before a module to explicitly state that it
# is NOT forbidden. Whenever this form is seen, then all modules will
# be forbidden unless one of the "!" modules match.
#
# *) Shell-style "glob" expressions may be used. "*" will match any
# sequence of zero or more characters, "?" will match any single
# character, "[seq]" will match any character in seq, and "[!seq]"
# will match any character not in seq.
#
# *) Tests are performed in sequence. The first match will terminate the
# testing. This allows for more complex allow/deny patterns.
#
# Tests are case-sensitive.
#
# NOTE: Again, this is for the hiding of modules within repositories, *not*
# for the hiding of repositories (roots) themselves.
#
# Some examples:
#
# Disallow "example" but allow all others:
# forbidden = example
#
# Disallow "example1" and "example2" but allow all others:
# forbidden = example1, example2
#
# Allow *only* "example1" and "example2":
# forbidden = !example1, !example2
#
# Forbid modules starting with "x":
# forbidden = x*
#
# Allow modules starting with "x" but no others:
# forbidden = !x*
#
# Allow "xml", forbid other modules starting with "x", and allow the rest:
# forbidden = !xml, x*, !*
#
forbidden =
#---------------------------------------------------------------------------
[authz-forbiddenre]
# The "forbiddenre" authorizer forbids access to repositories and
# repository paths by comparing a list of regular expressions
# (separated by commas) against paths consisting of the repository (or
# root) name plus the path of the versioned file or directory to be
# tested. For example, to see if the user is authorized to see the
# path "/trunk/www/index.html" in the repository whose root name is
# "svnrepos", this authorizer will check the path
# "svnrepos/trunk/www/index.html" against the list of forbidden
# regular expressions. Directory paths will be terminated by a forward
# slash.
#
# Like the "forbidden" authorizer...
#
# *) The "!" can be used before a module to explicitly state that it
# is NOT forbidden. Whenever this form is seen, then all modules will
# be forbidden unless one of the "!" modules match.
#
# *) Tests are performed in sequence. The first match will terminate the
# testing. This allows for more complex allow/deny patterns.
#
# Unlike the "forbidden" authorizer, you can can use this to hide roots, too.
#
# Some examples:
#
# Disallow files named "PRIVATE", but allow all others:
# forbiddenre = /PRIVATE$
#
# Disallow the "hidden" repository, allowing all others:
# forbiddenre = ^hidden(/|$)
#
# Allow only the "example1" and "example2" roots and the paths inside them,
# disallowing all others (which can be done in multiple ways):
# forbiddenre = !^example1(/|$), !^example2(/|$)/
# forbiddenre = !^example[12](/|$)
#
# Only allow visibility of HTML files and the directories that hold them:
# forbiddenre = !^([^/]+|.*(/|\.html))$
#
forbiddenre =
#---------------------------------------------------------------------------
[authz-svnauthz]
# The "svnauthz" authorizer uses a Subversion authz configuration file
# to determine access to repository paths. This option specifies the
# location of that file using an absolute path.
#
authzfile =
#---------------------------------------------------------------------------

8
elemx/java/.cvsignore Normal file
View 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
View File

@@ -0,0 +1,6 @@
elx-python
py_keywords.c
py_keywords.h
python.c
python.h
*.output

View File

@@ -51,12 +51,16 @@ keywordsList = ["and", "assert", "break", "class", "continue", "def",
"return", "try", "while",
]
# First a little helper, since I don't like to repeat things. (Tismer speaking)
def replace(where, what, with):
return string.join(string.split(where, what), with)
# A regexp for matching Python comments.
commentPat = "#.*"
# A regexp for matching simple quoted strings.
pat = "q[^q\\n]*(\\[\000-\377][^q\\n]*)*q"
quotePat = string.replace(pat, "q", "'") + "|" + string.replace(pat, 'q', '"')
quotePat = replace(pat, "q", "'") + "|" + replace(pat, 'q', '"')
# A regexp for matching multi-line tripled-quoted strings. (Way to go, Tim!)
pat = """
@@ -78,8 +82,7 @@ pat = """
qqq
"""
pat = string.join(string.split(pat), '') # get rid of whitespace
tripleQuotePat = string.replace(pat, "q", "'") + "|" \
+ string.replace(pat, 'q', '"')
tripleQuotePat = replace(pat, "q", "'") + "|" + replace(pat, 'q', '"')
# A regexp which matches all and only Python keywords. This will let
# us skip the uninteresting identifier references.

View File

@@ -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():

View File

@@ -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
@@ -26,7 +26,6 @@
#
# -----------------------------------------------------------------------
import sys
import string
import os
import re
@@ -34,6 +33,7 @@ import time
import math
import cgi
import vclib
import vclib.ccvs.blame
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
@@ -42,28 +42,25 @@ def link_includes(text, repos, path_parts, include_url):
match = re_includes.match(text)
if match:
incfile = match.group(3)
include_path_parts = path_parts[:-1]
for part in filter(None, string.split(incfile, '/')):
if part == "..":
if not include_path_parts:
# nothing left to pop; don't bother marking up this include.
return text
include_path_parts.pop()
elif part and part != ".":
include_path_parts.append(part)
include_path = None
try:
if repos.itemtype(include_path_parts, None) == vclib.FILE:
include_path = string.join(include_path_parts, '/')
except vclib.ItemNotFound:
pass
# check current directory and parent directory for file
for depth in (-1, -2):
include_path = path_parts[:depth] + [incfile]
try:
# will throw if path doesn't exist
if repos.itemtype(include_path, None) == vclib.FILE:
break
except vclib.ItemNotFound:
pass
else:
include_path = None
if include_path:
return '#%sinclude%s<a href="%s">"%s"</a>' % \
(match.group(1), match.group(2),
string.replace(include_url, '/WHERE/', include_path), incfile)
url = string.replace(include_url, '/WHERE/',
string.join(include_path, '/'))
return '#%sinclude%s<a href="%s">"%s"</a>' % \
(match.group(1), match.group(2), url, incfile)
return text
@@ -100,44 +97,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 '&nbsp;'))
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>&nbsp;</td><td>&nbsp;</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 '&nbsp;'))
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():

View File

@@ -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")

View File

@@ -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
@@ -38,29 +38,28 @@ import fnmatch
#########################################################################
class Config:
_sections = ('general', 'utilities', 'options', 'cvsdb', 'templates')
_force_multi_value = ('cvs_roots', 'svn_roots', 'languages', 'kv_files',
'root_parents', 'allowed_views', 'mime_types_files')
_sections = ('general', 'options', 'cvsdb', 'templates')
_force_multi_value = ('cvs_roots', 'forbidden',
'svn_roots', 'languages', 'kv_files',
'root_parents')
def __init__(self):
for section in self._sections:
setattr(self, section, _sub_config())
def load_config(self, pathname, vhost=None, rootname=None):
def load_config(self, pathname, vhost=None):
self.conf_path = os.path.isfile(pathname) and pathname or None
self.base = os.path.dirname(pathname)
self.parser = ConfigParser.ConfigParser()
self.parser.read(self.conf_path or [])
parser = ConfigParser.ConfigParser()
parser.read(pathname)
for section in self._sections:
if self.parser.has_section(section):
self._process_section(self.parser, section, section)
if parser.has_section(section):
self._process_section(parser, section, section)
if vhost and self.parser.has_section('vhosts'):
self._process_vhost(self.parser, vhost)
if rootname:
self._process_root_options(self.parser, rootname)
if vhost and parser.has_section('vhosts'):
self._process_vhost(parser, vhost)
def load_kv_files(self, language):
kv = _sub_config()
@@ -91,10 +90,6 @@ 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):
sc = getattr(self, subcfg_name)
@@ -114,23 +109,22 @@ class Config:
setattr(sc, opt, value)
def _process_vhost(self, parser, vhost):
# find a vhost name for this vhost, if any (if not, 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 = 'vhost-%s/' % (canon_vhost)
cv = canon_vhost + '-'
lcv = len(cv)
for section in parser.sections():
if section[:lcv] == cv:
base_section = section[lcv:]
if base_section not in self._sections:
raise IllegalOverrideSection('vhost', section)
self._process_section(parser, section, base_section)
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,
@@ -141,55 +135,6 @@ class Config:
return None
def _process_root_options(self, parser, rootname):
rn = 'root-%s/' % (rootname)
lrn = len(rn)
for section in parser.sections():
if section[:lrn] == rn:
base_section = section[lrn:]
if base_section in self._sections:
if base_section == 'general':
raise IllegalOverrideSection('root', section)
self._process_section(parser, section, base_section)
elif _startswith(base_section, 'authz-'):
pass
else:
raise IllegalOverrideSection('root', section)
def overlay_root_options(self, rootname):
"Overly per-root options atop the existing option set."
if not self.conf_path:
return
self._process_root_options(self.parser, rootname)
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_params(self, authorizer, rootname=None):
if not self.conf_path:
return {}
params = {}
authz_section = 'authz-%s' % (authorizer)
for section in self.parser.sections():
if section == authz_section:
for key, value in self._get_parser_items(self.parser, section):
params[key] = value
if rootname:
root_authz_section = 'root-%s/authz-%s' % (rootname, authorizer)
for section in self.parser.sections():
if section == root_authz_section:
for key, value in self._get_parser_items(self.parser, section):
params[key] = value
return params
def set_defaults(self):
"Set some default values in the configuration."
@@ -197,67 +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 = 0
self.options.use_localtime = 0
self.options.short_log_len = 80
self.options.enable_syntax_coloration = 1
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
@@ -272,10 +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
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 = { }
@@ -287,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

View File

@@ -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,21 +17,17 @@ 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"
## cached (active) database connections
gCheckinDatabase = None
gCheckinDatabaseReadOnly = None
## CheckinDatabase provides all interfaces needed to the SQL database
## back-end; it needs to be subclassed, and have its "Connect" method
## defined to actually be complete; it should run well off of any DBI 2.0
@@ -45,7 +41,6 @@ class CheckinDatabase:
self._passwd = passwd
self._database = database
self._row_limit = row_limit
self._version = None
## database lookup caches
self._get_cache = {}
@@ -55,21 +50,6 @@ class CheckinDatabase:
def Connect(self):
self.db = dbi.connect(
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 Exception("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 +149,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 +240,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())
@@ -305,13 +249,11 @@ class CheckinDatabase:
revision = commit.GetRevision()
sticky_tag = "NULL"
branch_id = self.GetBranchID(commit.GetBranch())
plus_count = commit.GetPlusCount() or '0'
minus_count = commit.GetMinusCount() or '0'
plus_count = commit.GetPlusCount()
minus_count = commit.GetMinusCount()
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 +262,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 +289,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 +360,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 +412,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,69 +427,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 Exception, "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 = {}
## 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
@@ -634,15 +474,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
@@ -775,7 +610,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
@@ -806,9 +640,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
@@ -846,20 +677,39 @@ def CreateCommit():
def CreateCheckinQuery():
return CheckinDatabaseQuery()
def ConnectDatabase(cfg, readonly=0):
if readonly:
user = cfg.cvsdb.readonly_user
passwd = cfg.cvsdb.readonly_passwd
else:
user = cfg.cvsdb.user
passwd = cfg.cvsdb.passwd
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd,
cfg.cvsdb.database_name, cfg.cvsdb.row_limit)
db.Connect()
return db
def ConnectDatabaseReadOnly(cfg):
return ConnectDatabase(cfg, 1)
global gCheckinDatabaseReadOnly
if gCheckinDatabaseReadOnly:
return gCheckinDatabaseReadOnly
gCheckinDatabaseReadOnly = CheckinDatabase(
cfg.cvsdb.host,
cfg.cvsdb.port,
cfg.cvsdb.readonly_user,
cfg.cvsdb.readonly_passwd,
cfg.cvsdb.database_name,
cfg.cvsdb.row_limit)
gCheckinDatabaseReadOnly.Connect()
return gCheckinDatabaseReadOnly
def ConnectDatabase(cfg):
global gCheckinDatabase
if gCheckinDatabase:
return gCheckinDatabase
gCheckinDatabase = CheckinDatabase(
cfg.cvsdb.host,
cfg.cvsdb.port,
cfg.cvsdb.user,
cfg.cvsdb.passwd,
cfg.cvsdb.database_name,
cfg.cvsdb.row_limit)
gCheckinDatabase.Connect()
return gCheckinDatabase
def GetCommitListFromRCSFile(repository, path_parts, revision=None):
commit_list = []
@@ -867,8 +717,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)

View File

@@ -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:

View File

@@ -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
&quot; and &gt;); 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
&quot; and &gt;)
[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:
@@ -460,10 +457,7 @@ 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, printer, ctx):
if type(printer) is TupleType:
@@ -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:
@@ -666,7 +659,7 @@ def _write_value(value, args, ctx):
piece = args[idx]
else:
piece = '<undef>'
printer(ctx, piece)
printer(ctx, piece)
# plain old value, write to output
else:
@@ -676,41 +669,6 @@ def _write_value(value, args, ctx):
ctx.printers.append(printer)
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)
class Context:
"""A container for the execution context"""
def __init__(self, fp):
@@ -834,14 +792,10 @@ def _raw_printer(ctx, s):
def _html_printer(ctx, s):
ctx.fp.write(cgi.escape(s))
def _uri_printer(ctx, s):
ctx.fp.write(urllib.quote(s))
_printers = {
FORMAT_RAW : _raw_printer,
FORMAT_HTML : _html_printer,
FORMAT_XML : _html_printer,
FORMAT_URI : _uri_printer,
}
# --- standard test environment ---

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-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):

View File

@@ -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
@@ -28,7 +28,6 @@ import viewvc
import ezt
import debug
import urllib
import fnmatch
class FormData:
def __init__(self, form):
@@ -274,20 +273,6 @@ def prev_rev(rev):
r = r[:-2]
return string.join(r, '.')
def is_forbidden(cfg, cvsroot_name, module):
auth_params = cfg.get_authorizer_params('forbidden', cvsroot_name)
forbidden = auth_params.get('forbidden', '')
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=[])
@@ -297,19 +282,10 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
ob.log = '&nbsp;'
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()
@@ -320,13 +296,13 @@ 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, then don't make the link
@@ -369,7 +345,7 @@ def run_query(server, cfg, form_data, viewvc_link):
files = [ ]
cvsroots = {}
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
@@ -390,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):
@@ -412,10 +381,13 @@ def main(server, cfg, viewvc_link):
commits = [ ]
query = 'skipped'
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, 1),
'branch' : server.escape(form_data.branch, 1),
'directory' : server.escape(form_data.directory, 1),
@@ -432,13 +404,18 @@ def main(server, cfg, viewvc_link):
'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

View File

@@ -135,7 +135,7 @@ class CgiServer(Server):
def addheader(self, name, value):
self.headers.append((name, value))
def header(self, content_type='text/html; charset=UTF-8', status=None):
def header(self, content_type='text/html', status=None):
if not self.headerSent:
self.headerSent = 1
@@ -209,10 +209,7 @@ class AspServer(ThreadedServer):
if not self.headerSent:
try:
self.headerSent = 1
if content_type is None:
self.response.ContentType = 'text/html; charset=UTF-8'
else:
self.response.ContentType = content_type
if content_type is not None: self.response.ContentType = content_type
if status is not None: self.response.Status = status
except AttributeError:
pass
@@ -293,7 +290,7 @@ class ModPythonServer(ThreadedServer):
def header(self, content_type=None, status=None):
if content_type is None:
self.request.content_type = 'text/html; charset=UTF-8'
self.request.content_type = 'text/html'
else:
self.request.content_type = content_type
self.headerSent = 1

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -1,223 +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.username = username
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")
def _get_paths_for_root(self, rootname):
if self.rootpaths.has_key(rootname):
return self.rootpaths[rootname]
paths_for_root = { }
# Parse the authz file.
cp = ConfigParser()
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)

View File

@@ -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,44 +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 4-tuple containing the date, author, log
message, and a list of ChangedPath items representing paths changed
Raise vclib.UnsupportedFeature if the version control system
doesn't support a global revision concept.
"""
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
@@ -216,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
@@ -234,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
@@ -304,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:
@@ -312,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
@@ -327,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:
@@ -359,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)
@@ -395,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)

View File

@@ -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':

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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')

View File

@@ -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,14 +19,14 @@ import string
import common
class _TokenStream:
token_term = string.whitespace + ';:'
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
@@ -51,7 +51,7 @@ class _TokenStream:
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
idx = 0
@@ -60,10 +60,10 @@ class _TokenStream:
idx = idx + 1
if buf[idx] == ';' or buf[idx] == ':':
if buf[idx] == ';':
self.buf = buf
self.idx = idx + 1
return buf[idx]
return ';'
if buf[idx] != '@':
end = idx + 1
@@ -134,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."
@@ -165,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"

View File

@@ -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))

View File

@@ -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)

View File

@@ -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
@@

View File

@@ -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()

View File

@@ -1,10 +0,0 @@
head ;
access;
symbols;
locks; strict;
comment @# @;
desc
@@

View File

@@ -1,6 +0,0 @@
set_locking('strict')
set_comment('# ')
admin_completed()
tree_completed()
set_description('')
parse_completed()

View File

@@ -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)

View File

@@ -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,695 @@
#
# -----------------------------------------------------------------------
"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
class ChangedPathSet:
def __init__(self):
self.changes = { }
def add_change(self, change):
if change.path:
change.path = _cleanup_path(change.path)
if change.base_path:
change.base_path = _cleanup_path(change.base_path)
path = change.path
action = 'modified'
is_copy = 0
if not change.path:
action = 'deleted'
path = change.base_path
elif change.added:
action = 'added'
replace_check_path = path
if change.base_path and change.base_rev:
is_copy = 1
replace_check_path = change.base_path
if self.changes.has_key(replace_check_path) \
and self.changes[replace_check_path].action == 'deleted':
action = 'replaced'
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
self.changes[path] = ChangedPath(path, pathtype, change.prop_changes,
change.text_changed, change.base_path,
change.base_rev, action, is_copy)
def get_changes(self):
changes = self.changes.values()
changes.sort(lambda a, b: _compare_paths(a.filename, b.filename))
return changes
def get_revision_info(svnrepos, rev):
fsroot = svnrepos._getroot(rev)
# Get the changes for the revision
cps = ChangedPathSet()
editor = repos.ChangeCollector(svnrepos.fs_ptr, fsroot,
svnrepos.pool, cps.add_change)
e_ptr, e_baton = delta.make_editor(editor, svnrepos.pool)
repos.svn_repos_replay(fsroot, e_ptr, e_baton, svnrepos.pool)
# 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, cps.get_changes()
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
rootpath = os.path.abspath(rootpath)
url = 'file://' + string.join([rootpath, fs_path], "/")
fp = popen.popen(svn_client_path,
('blame', "-r%d" % int(rev), "%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)

View File

@@ -1,548 +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 popen2
import time
import urllib
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception
from svn 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
### 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, 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:
entry = Revision(revision, _datestr_to_date(date), author, message, None,
self.lockinfo, self.path[1:], None, None)
self.logs.append(entry)
if this_path:
self.path = this_path
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_ctx_t()
self.ctx.auth_baton = core.svn_auth_open([
client.svn_client_get_simple_provider(),
client.svn_client_get_username_provider(),
client.svn_client_get_ssl_server_trust_file_provider(),
client.svn_client_get_ssl_client_cert_file_provider(),
client.svn_client_get_ssl_client_cert_pw_file_provider(),
])
self.ctx.config = core.svn_config_get_config(self.config_dir)
if self.config_dir is not None:
core.svn_auth_set_parameter(self.ctx.auth_baton,
core.SVN_AUTH_PARAM_CONFIG_DIR,
self.config_dir)
ra_callbacks = ra.svn_ra_callbacks_t()
ra_callbacks.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, 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.svn_client_log2([url], _rev2optrev(rev), _rev2optrev(1),
log_limit, 1, not cross_copies,
lc.add_log, self.ctx)
revs = lc.logs
revs.sort()
prev = None
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 cached_info[0], cached_info[1], cached_info[2], cached_info[3]
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
p1 = self._getpath(path_parts1)
p2 = self._getpath(path_parts2)
r1 = self._getrev(rev1)
r2 = self._getrev(rev2)
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
raise vclib.ItemNotFound(path_parts1)
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
raise vclib.ItemNotFound(path_parts2)
args = vclib._diff_args(type, options)
def _date_from_rev(rev):
date, author, msg, changes = self.revinfo(rev)
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, message, changes)
optrev = _rev2optrev(rev)
revs = []
def _log_cb(changed_paths, revision, author,
datestr, message, pool, retval=revs):
date = _datestr_to_date(datestr)
action_map = { 'D' : vclib.DELETED,
'A' : vclib.ADDED,
'R' : vclib.REPLACED,
'M' : vclib.MODIFIED,
}
paths = (changed_paths or {}).keys()
paths.sort(lambda a, b: _compare_paths(a, b))
changes = []
found_readable = found_unreadable = 0
for path in paths:
pathtype = None
change = changed_paths[path]
action = action_map.get(change.action, vclib.MODIFIED)
### Wrong, diddily wrong wrong wrong. Can you say,
### "Manufacturing data left and right because it hurts to
### figure out the right stuff?"
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, 0, 0))
found_readable = 1
else:
found_unreadable = 1
if found_unreadable:
message = None
if not found_readable:
author = None
date = None
revs.append([date, author, message, changes])
client.svn_client_log([self.rootpath], optrev, optrev,
1, 0, _log_cb, self.ctx)
return revs[0][0], revs[0][1], revs[0][2], revs[0][3]
##--- 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

View File

@@ -1,822 +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 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], "/")
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, 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):
self.idx = -1
self.first_rev = first_rev
self.blame_data = []
ctx = client.ctx_t()
core.svn_config_ensure(None)
ctx.config = core.svn_config_get_config(None)
ctx.auth_baton = core.svn_auth_open([])
try:
### TODO: Is this use of FIRST_REV always what we want? Should we
### 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
# 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, 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)
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 = revprops.get(core.SVN_PROP_REVISION_LOG)
author = revprops.get(core.SVN_PROP_REVISION_AUTHOR)
datestr = revprops.get(core.SVN_PROP_REVISION_DATE)
date = _datestr_to_date(datestr)
# Optimization: If our caller doesn't care about the changed
# paths, and we don't need them to do authz determinations, let's
# get outta here.
if self.auth is None and not include_changed_paths:
return date, author, msg, None
# If we get here, then we either need the changed paths because we
# were asked for them, or we need them to do authorization checks.
# Either way, we need 'em, so let's get 'em.
fsroot = self._getroot(rev)
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
e_ptr, e_baton = delta.make_editor(editor)
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
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
# 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, changedpaths.values()
else:
return date, author, msg, None
# Consult the revinfo cache first. If we don't have cached info,
# or our caller wants changed paths and we don't have those for
# 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[3] is None):
cached_info = _revinfo_helper(rev, include_changed_paths)
self._revinfo_cache[rev] = cached_info
return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
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, 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

View 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)

File diff suppressed because it is too large Load Diff

View File

@@ -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
@@ -15,18 +15,8 @@
# -----------------------------------------------------------------------
import os, sys, traceback, string, thread
try:
import win32api
except ImportError, e:
raise ImportError, str(e) + """
Did you install the Python for Windows Extensions?
http://sourceforge.net/projects/pywin32/
"""
import win32process, win32pipe, win32con
import win32event, win32file, winerror
import win32event, win32file, win32api, winerror
import pywintypes, msvcrt
# Buffer size for spooling

View File

@@ -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

View File

@@ -1,87 +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.
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. 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.
8. Update your working copy to HEAD, and tag the release:
svn update
svn cp -m "Tag the X.Y.Z final release." . \
http://viewvc.tigris.org/svn/viewvc/tags/X.Y.Z
9. Go into an empty directory and run the 'make-release' script:
tools/make-release viewvc-X.Y.Z tags/X.Y.Z
10. Verify the archive files:
- do they have a LICENSE.html file?
- do they have necessary include documentation?
- do they *not* have unnecessary stuff?
- do they install and work correctly?
11. Upload the created archive files (tar.gz and zip) into the Files
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).
12. On trunk, update the websites (both the viewvc.org/ and www/ ones)
to refer to the new release files, and copy the CHANGES for the
new release into trunk's CHANGES file.
13. Edit the file 'lib/viewvc.py' again, re-adding the "-dev" suffix
and incrementing the patch number assigned to the __version__
variable, and add a new empty block in the branch's CHANGES file,
and commit:
svn ci -m "Begin a new release cycle."
14. Edit the Issue Tracker configuration options, adding a new Version
for the just-released one, and a new Milestone for the next patch
(and possibly, minor or major) release. (For the Milestone sort
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
15. Write an announcement explaining all the cool new features and
post it to the announce@ list, to the project's News area, and to
other places interested in this sort of stuff, such as Freshmeat
(http://www.freshmeat.net).

View File

@@ -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
View 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]&nbsp;[else][lines.author][end]</td>
<td class="vc_blame_rev">[is lines.rev last_rev]&nbsp;[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"]

View File

@@ -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],
[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],
[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">&nbsp;</td>
<td class="vc_diff_add">&nbsp;[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">&nbsp;[changes.left]</td>
<td class="vc_diff_nochange">&nbsp;[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">&nbsp;</td>
</tr>
<tr>
@@ -226,7 +220,7 @@
</tr>
<tr>
<td class="vc_diff_empty">&nbsp;</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>

View File

@@ -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"
/>&nbsp;Parent&nbsp;Directory</a>
</td>
<td style="width: 96px; font-size: 0;"></td>
<td>&nbsp;</td>
<td>&nbsp;</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]

View File

@@ -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"
/>&nbsp;Parent&nbsp;Directory</a>
</td>
<td>&nbsp;</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>&nbsp;[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;">&nbsp;[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>&nbsp;[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>&nbsp;[entries.ago]</td>
<td>&nbsp;[entries.author]</td>
[is cfg.options.show_logs "1"]
[if-any entries.short_log]
<td>&nbsp;[entries.short_log][is entries.pathtype "dir"][is roottype "cvs"]
[if-any entries.log]
<td>&nbsp;[entries.log][is entries.pathtype "dir"][is roottype "cvs"]
<em>(from [entries.log_file]/[entries.log_rev])</em>[end][end]</td>
[else]
<td>&nbsp;</td>

View File

@@ -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&nbsp;View</strong><br />
<a href="help_log.html">Log&nbsp;View</a><br />
<a href="help_query.html">Query&nbsp;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

View File

@@ -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&nbsp;View</a><br />
<strong>Log&nbsp;View</strong><br />
<a href="help_query.html">Query&nbsp;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

View File

@@ -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&nbsp;View</a><br />
<a href="help_log.html">Log&nbsp;View</a><br />
<strong>Query&nbsp;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&nbsp;Help:</h3>
<a href="help_rootview.html">General</a><br />
<a href="help_dirview.html">Directory&nbsp;View</a><br />
<a href="help_log.html">Classic&nbsp;Log&nbsp;View</a><br />
<a href="help_logtable.html">Alternative&nbsp;Log&nbsp;View</a><br />
<strong>Query&nbsp;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>

View File

@@ -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&nbsp;View</a><br />
<a href="help_log.html">Log&nbsp;View</a><br />
<a href="help_query.html">Query&nbsp;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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -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,105 +62,44 @@ form { margin: 0; }
}
/*** Properties Listing ***/
.vc_properties {
margin: 1em 0;
}
.vc_properties h2 {
font-size: 115%;
}
/*** 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 {
@@ -229,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;
}
@@ -250,6 +159,7 @@ form { margin: 0; }
.vc_idiff_empty {
background-color:#e0e0e0;
}
table.vc_idiff col.content {
width: 50%;
}
@@ -267,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 {

View File

@@ -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>

View File

@@ -1,104 +0,0 @@
[# 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>
[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]&nbsp;[else][lines.author][end]</td>
<td class="vc_file_line_rev">[is lines.rev last_rev]&nbsp;[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]
[include "include/props.ezt"]
[include "include/footer.ezt"]

View File

@@ -9,13 +9,13 @@
enter a numeric revision.
[end]
</p>
<form method="get" action="[diff_select_action]" id="diff_select">
<form method="get" action="[diff_select_action]" name="diff_select">
<table cellpadding="2" cellspacing="0" class="auto">
<tr>
<td>&nbsp;</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">
@@ -26,7 +26,7 @@
</select>
<input type="text" size="12" name="tr1"
value="[if-any rev_selected][rev_selected][else][first_revision][end]"
onchange="document.getElementById('diff_select').r1.selectedIndex=0" />
onchange="document.diff_select.r1.selectedIndex=0" />
[else]
<input type="text" size="12" name="r1"
value="[if-any rev_selected][rev_selected][else][first_revision][end]" />
@@ -42,7 +42,7 @@
</select>
<input type="text" size="12" name="tr2"
value="[last_revision]"
onchange="document.getElementById('diff_select').r2.selectedIndex=0" />
onchange="document.diff_select.r1.selectedIndex=0" />
[else]
<input type="text" size="12" name="r2" value="[last_revision]" />
[end]
@@ -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>

View File

@@ -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>&nbsp;</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 tarball</a></p>
[end]
[include "footer.ezt"]

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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]&nbsp;[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]&nbsp;[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"/>[else]&nbsp;[end]</td>
</tr>
</table>

View File

@@ -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" type="image/x-icon" />
<link rel="stylesheet" href="[docroot]/styles.css" type="text/css" />
[if-any rss_href]<link rel="alternate" type="application/rss+xml" title="RSS [[][rootname]][where]" href="[rss_href]" />[end]
[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]
&nbsp;
[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>

View File

@@ -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]

View File

@@ -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]

View File

@@ -1,6 +1,5 @@
<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()">
@@ -35,19 +34,16 @@
<input type="text" name="pathrev" value="[pathrev]" size="6"/>
[end]
<input type="submit" value="Set" />
</div>
</form>
[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]
[is pathrev lastrev][else]<input type="submit" value="Set to [lastrev]">[end]
(<i>Current path doesn't exist after revision <strong>[lastrev]</strong></i>)
[else]
<input type="submit" value="Clear" />
<input type="submit" value="Clear">
[end]
</div>
</form>
[end]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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
View 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"]

View File

@@ -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>

View File

@@ -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]&nbsp;[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]&nbsp;[end]
</td>
<td style="vertical-align: top;">
<a href="[commits.files.dir_href]">[commits.files.dir]/</a>

View File

@@ -11,27 +11,21 @@
<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>
@@ -44,6 +38,17 @@
<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>
<tr align="left">
@@ -55,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>
@@ -69,11 +74,6 @@
<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>

View File

@@ -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>

View File

@@ -1,7 +1,6 @@
<?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>
@@ -10,8 +9,8 @@
<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>&lt;pre&gt;[format "xml"][commits.log][end]&lt;/pre&gt;</description>
<pubDate>[commits.rss_date]</pubDate>
<description>[commits.log]</description>
</item>[end]
</channel>
</rss>

View File

@@ -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
View File

@@ -0,0 +1 @@
build

0
tparse/Setup.py Executable file → Normal file
View File

View File

@@ -38,7 +38,7 @@ using namespace std;
#define Whitespace(c) (c == ' ' || c == '\t' || c == '\014' || c == '\n' || \
c == '\r')
#define Token_term(c) (c == ' ' || c == '\t' || c == '\014' || c == '\n' || \
c == '\r' || c == ';' || c == ':')
c == '\r' || c == ';')
#define isdigit(c) ((c-'0')<10)
@@ -120,10 +120,10 @@ rcstoken *TokenParser::get(int allow_eof)
idx++;
}
if (buf[idx] == ';' || buf[idx] == ':')
if (buf[idx] == ';')
{
idx++;
(*token) = buf[idx];
(*token) = ';';
return token.release();
}
@@ -239,14 +239,27 @@ void tparseParser::parse_rcs_admin()
{
while (1)
{
auto_ptr<rcstoken> rev;
auto_ptr<rcstoken> tag, rev;
char *second;
// delete token;
token.reset(tokenstream->get(FALSE));
if (*token == ';')
break;
tokenstream->match(':');
rev.reset(tokenstream->get(FALSE));
sink->define_tag(*token, *rev);
second = index(token->data, ':');
if (second)
{
tag.reset(token->copy_begin_len(0, second - token->data));
second++;
rev.reset(new rcstoken(second));
}
else
{
tag = token;
tokenstream->match(':');
rev.reset(tokenstream->get(FALSE));
};
sink->define_tag(*tag, *rev);
}
continue;
}

View File

@@ -1,5 +1,5 @@
/*
# 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
@@ -30,7 +30,7 @@
#include <memory> /* for auto_ptr */
#include <algorithm> /* for iterator */
#include <exception> /* for exception */
#include <istream> /* for istream */
#include <iostream> /* for istream */
#include <list> /* for list<> */
#include <string> /* for string */
@@ -121,7 +121,7 @@ class rcstoken
free(data);
data = NULL;
};
void init(const char *mydata, size_t mylen);
void rcstoken::init(const char *mydata, size_t mylen);
int null_token()
{
return data == NULL;

View File

@@ -0,0 +1,2 @@
*.tar.gz
*.zip

View File

@@ -0,0 +1 @@
RedirectMatch ^(.*)$ http://www.viewvc.org/$1

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- Mode: 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
@@ -11,7 +11,10 @@
#
# -----------------------------------------------------------------------
#
# Install script for ViewVC
# install script for viewvc -- temporary?
#
# ### this will eventually be replaced by autoconf plus tools. an
# ### interactive front-end to ./configure may be provided.
#
# -----------------------------------------------------------------------
@@ -24,7 +27,7 @@ import py_compile
import getopt
import StringIO
# Get access to our library modules.
# get access to our library modules
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
import compat
@@ -33,443 +36,324 @@ import compat_ndiff
version = viewvc.__version__
## Installer defaults.
## installer text
INFO_TEXT = """
This is the ViewVC %s installer.
It will allow you to choose the install path for ViewVC. You will
now be asked some installation questions.
Defaults are given in square brackets. Just hit [Enter] if a default
is okay.
""" % version
## installer defaults
DESTDIR = None
ROOT_DIR = None
CLEAN_MODE = None
## List of files for installation.
## tuple (source path,
## destination path,
## mode,
## boolean -- search-and-replace?
## boolean -- prompt before replacing?
## boolean -- compile?)
## list of files for installation
## tuple (source path, destination path, install mode, true/false flag for
## search-and-replace, flag or text for prompt before replace,
## compile_it)
##
FILE_INFO_LIST = [
("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0),
("bin/cgi/query.cgi", "bin/cgi/query.cgi", 0755, 1, 0, 0),
("bin/mod_python/viewvc.py", "bin/mod_python/viewvc.py", 0755, 1, 0, 0),
("bin/mod_python/query.py", "bin/mod_python/query.py", 0755, 1, 0, 0),
("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0),
("bin/cgi/query.cgi", "bin/cgi/query.cgi", 0755, 1, 0, 0),
("bin/mod_python/viewvc.py", "bin/mod_python/viewvc.py", 0755, 1, 0, 0),
("bin/mod_python/query.py", "bin/mod_python/query.py", 0755, 1, 0, 0),
("bin/mod_python/handler.py", "bin/mod_python/handler.py", 0755, 1, 0, 0),
("bin/mod_python/.htaccess", "bin/mod_python/.htaccess", 0755, 0, 0, 0),
("bin/standalone.py", "bin/standalone.py", 0755, 1, 0, 0),
("bin/loginfo-handler", "bin/loginfo-handler", 0755, 1, 0, 0),
("bin/cvsdbadmin", "bin/cvsdbadmin", 0755, 1, 0, 0),
("bin/svndbadmin", "bin/svndbadmin", 0755, 1, 0, 0),
("bin/make-database", "bin/make-database", 0755, 1, 0, 0),
("conf/viewvc.conf.dist", "viewvc.conf.dist", 0644, 0, 0, 0),
("conf/viewvc.conf.dist", "viewvc.conf", 0644, 0, 1, 0),
("conf/cvsgraph.conf.dist", "cvsgraph.conf.dist", 0644, 0, 0, 0),
("conf/cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),
("conf/mimetypes.conf.dist", "mimetypes.conf.dist", 0644, 0, 0, 0),
("conf/mimetypes.conf.dist", "mimetypes.conf", 0644, 0, 1, 0),
("bin/mod_python/.htaccess", "bin/mod_python/.htaccess", 0755, 0, 0, 0),
("bin/standalone.py", "bin/standalone.py", 0755, 1, 0, 0),
("viewvc.conf.dist", "viewvc.conf", 0644, 0,
"""Note: If you are upgrading from viewcvs-0.7 or earlier:
The section [text] has been removed from viewcvs.conf. The functionality
went into the new files in subdirectory templates.""", 0),
("cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),
("bin/loginfo-handler", "bin/loginfo-handler", 0755, 1, 0, 0),
("bin/cvsdbadmin", "bin/cvsdbadmin", 0755, 1, 0, 0),
("bin/svndbadmin", "bin/svndbadmin", 0755, 1, 0, 0),
("bin/make-database", "bin/make-database", 0755, 1, 0, 0),
]
if sys.platform == "win32":
FILE_INFO_LIST.extend([
("bin/asp/viewvc.asp", "bin/asp/viewvc.asp", 0755, 1, 0, 0),
("bin/asp/query.asp", "bin/asp/query.asp", 0755, 1, 0, 0),
("bin/asp/viewvc.asp", "bin/asp/viewvc.asp", 0755, 1, 0, 0),
("bin/asp/query.asp", "bin/asp/query.asp", 0755, 1, 0, 0),
])
## List of directories for installation.
## type (source path,
## destination path,
## boolean -- prompt before replacing?)
TREE_LIST = [
("lib", "lib", 0),
("templates", "templates", 1),
("templates-contrib", "templates-contrib", 1),
]
("lib", "lib", 0),
("templates", "templates", 1),
]
# used to escape substitution strings passed to re.sub(). re.escape() is no
# good because it blindly puts backslashes in front of anything that is not
# a number or letter regardless of whether the resulting sequence will be
# interpreted.
def ReEscape(str):
return string.replace(str, "\\", "\\\\")
## List of file extensions we can't show diffs for.
BINARY_FILE_EXTS = [
'.png',
'.gif',
'.jpg',
]
def _escape(str):
"""Callback function for re.sub().
re.escape() is no good because it blindly puts backslashes in
front of anything that is not a number or letter regardless of
whether the resulting sequence will be interpreted."""
return string.replace(str, "\\", "\\\\")
def _actual_src_path(path):
"""Return the real on-disk location of PATH, which is relative to
the ViewVC source directory."""
return os.path.join(os.path.dirname(sys.argv[0]),
string.replace(path, '/', os.sep))
def error(text, etype=None, evalue=None):
"""Print error TEXT to stderr, pretty printing the optional
exception type and value (ETYPE and EVALUE, respective), and then
exit the program with an errorful code."""
sys.stderr.write("\n[ERROR] %s\n" % (text))
def Error(text, etype=None, evalue=None):
print
print "[ERROR] %s" % text
if etype:
traceback.print_exception(etype, evalue, None, file=sys.stderr)
print '[ERROR] ',
traceback.print_exception(etype, evalue, None, file=sys.stdout)
sys.exit(1)
def replace_var(contents, var, value):
"""Replace instances of the variable VAR as found in file CONTENTS
with VALUE."""
def MkDir(path):
try:
compat.makedirs(path)
except os.error, e:
if e[0] == 17:
# EEXIST: file exists
return
if e[0] == 13:
# EACCES: permission denied
Error("You do not have permission to create directory %s" % path)
Error("Unknown error creating directory %s" % path, OSError, e)
def SetOnePath(contents, var, value):
pattern = re.compile('^' + var + r'\s*=\s*.*$', re.MULTILINE)
repl = '%s = r"%s"' % (var, os.path.join(ROOT_DIR, value))
return re.sub(pattern, _escape(repl), contents)
return re.sub(pattern, ReEscape(repl), contents)
def replace_paths(contents):
"""Replace all ViewVC path placeholders found in file CONTENTS."""
def SetPythonPaths(contents):
if contents[:2] == '#!':
shbang = '#!' + sys.executable
contents = re.sub('^#![^\n]*', _escape(shbang), contents)
contents = replace_var(contents, 'LIBRARY_DIR', 'lib')
contents = replace_var(contents, 'CONF_PATHNAME', 'viewvc.conf')
contents = re.sub('^#![^\n]*', ReEscape(shbang), contents)
contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewvc.conf')
return contents
def install_file(src_path, dst_path, mode, subst_path_vars,
prompt_replace, compile_it):
"""Install a single file whose source is at SRC_PATH (which is
relative to the ViewVC source directory) into the location
DST_PATH (which is relative both to the global ROOT_DIR and
DESTDIR settings), and set the file's MODE. If SUBST_PATH_VARS is
set, substitute path variables in the file's contents. If
PROMPT_REPLACE is set (and is not overridden by global setting
CLEAN_MODE), prompt the user for how to deal with already existing
files that differ from the to-be-installed version. If COMPILE_IT
is set, compile the file as a Python module."""
def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
compile_it):
dest_path = os.path.join(ROOT_DIR, dest_path)
src_path = _actual_src_path(src_path)
dst_path = os.path.join(ROOT_DIR, string.replace(dst_path, '/', os.sep))
destdir_path = DESTDIR + dst_path
if prompt_replace and os.path.exists(DESTDIR + dest_path):
# Collect ndiff output from ndiff
sys.stdout = StringIO.StringIO()
compat_ndiff.main([DESTDIR + dest_path, src_path])
ndiff_output = sys.stdout.getvalue()
overwrite = None
if not (prompt_replace and os.path.exists(destdir_path)):
# If the file doesn't already exist, or we've been instructed to
# replace it without prompting, then drop in the new file and get
# outta here.
overwrite = 1
else:
# If we're here, then the file already exists, and we've possibly
# got to prompt the user for what to do about that.
# Collect ndiff output from ndiff
sys.stdout = StringIO.StringIO()
compat_ndiff.main([destdir_path, src_path])
ndiff_output = sys.stdout.getvalue()
# Return everything to normal
sys.stdout = sys.__stdout__
# Return everything to normal
sys.stdout = sys.__stdout__
# Collect the '+ ' and '- ' lines
# total collects the difference lines to be printed later
total = ""
# I use flag to throw out match lines.
flag = 1
for line in string.split(ndiff_output,'\n'):
# Print line if it is a difference line
if line[:2] == "+ " or line[:2] == "- " or line[:2] == "? ":
total = total + line + "\n"
flag = 1
else:
# Compress lines that are the same to print one blank line
if flag:
total = total + "\n"
flag = 0
# Collect the '+ ' and '- ' lines.
diff_lines = []
looking_at_diff_lines = 0
for line in string.split(ndiff_output, '\n'):
# Print line if it is a difference line
if line[:2] == "+ " or line[:2] == "- " or line[:2] == "? ":
diff_lines.append(line)
looking_at_diff_lines = 1
else:
# Compress lines that are the same to print one blank line
if looking_at_diff_lines:
diff_lines.append("")
looking_at_diff_lines = 0
if total == "\n":
print " File %s exists,\n but there is no difference between target and source files.\n" % (DESTDIR + dest_path)
return
# If there are no differences, we're done here.
if not diff_lines:
overwrite = 1
else:
# If we get here, there are differences.
if CLEAN_MODE == 'true':
overwrite = 1
elif CLEAN_MODE == 'false':
overwrite = 0
else:
print "File %s exists and is different from source file." \
% (destdir_path)
while 1:
name, ext = os.path.splitext(src_path)
if ext in BINARY_FILE_EXTS:
temp = raw_input("Do you want to [O]verwrite or "
"[D]o not overwrite? ")
else:
temp = raw_input("Do you want to [O]verwrite, [D]o "
"not overwrite, or [V]iew "
"differences? ")
temp = string.lower(temp[0])
if temp == "v" and ext not in BINARY_FILE_EXTS:
print """
---------------------------------------------------------------------------"""
print string.join(diff_lines, '\n') + '\n'
print """
LEGEND
A leading '- ' indicates line to remove from installed file
A leading '+ ' indicates line to add to installed file
A leading '? ' shows intraline differences.
---------------------------------------------------------------------------"""
elif temp == "d":
overwrite = 0
elif temp == "o":
overwrite = 1
if type(prompt_replace) == type(""):
print prompt_replace
while 1:
temp = raw_input("""
File %s exists and is different from source file.
DO YOU WANT TO,
overwrite [o]
do not overwrite [d]
view differences [v]: """ % (DESTDIR + dest_path))
print
if overwrite is not None:
break
temp = string.lower(temp[0])
assert overwrite is not None
if not overwrite:
print " preserved %s" % (dst_path)
if temp == "d":
return
### If we get here, we're creating or overwriting the existing file.
if temp == "v":
if string.lower(src_path[-4:]) in [ '.gif', '.png', '.jpg' ]:
print 'Can not print differences between binary files'
else:
print total
print """
LEGEND
A leading '- ' indicates line to remove from installed file
A leading '+ ' indicates line to add to installed file
A leading '? ' shows intraline differences."""
# Read the source file's contents.
try:
contents = open(src_path, "rb").read()
except IOError, e:
error(str(e))
if temp == "o":
ReplaceFile(src_path, dest_path, mode, set_python_paths,
prompt_replace, compile_it)
return
else:
ReplaceFile(src_path, dest_path, mode, set_python_paths,
prompt_replace, compile_it)
return
# (Optionally) substitute ViewVC path variables.
if subst_path_vars:
contents = replace_paths(contents)
def ReplaceFile(src_path, dest_path, mode, set_python_paths,
prompt_replace, compile_it):
try:
contents = open(src_path, "rb").read()
except IOError, e:
Error(str(e))
# Ensure the existence of the containing directories.
dst_parent = os.path.dirname(destdir_path)
if not os.path.exists(dst_parent):
try:
compat.makedirs(dst_parent)
print " created %s%s" % (dst_parent, os.sep)
except os.error, e:
if e.errno == 17: # EEXIST: file exists
return
if e.errno == 13: # EACCES: permission denied
error("You do not have permission to create directory %s" \
% (dst_parent))
error("Unknown error creating directory %s" \
% (dst_parent, OSError, e))
if set_python_paths:
contents = SetPythonPaths(contents)
# Now, write the file contents to their destination.
try:
exists = os.path.exists(destdir_path)
open(destdir_path, "wb").write(contents)
print " %s %s" \
% (exists and 'replaced ' or 'installed', dst_path)
except IOError, e:
if e.errno == 13:
# EACCES: permission denied
error("You do not have permission to write file %s" % (dst_path))
error("Unknown error writing file %s" % (dst_path, IOError, e))
# Set the files's mode.
os.chmod(destdir_path, mode)
# (Optionally) compile the file.
if compile_it:
py_compile.compile(destdir_path, destdir_path + "c" , dst_path)
## write the file to the destination location
path, basename = os.path.split(DESTDIR + dest_path)
MkDir(path)
try:
open(DESTDIR + dest_path, "wb").write(contents)
except IOError, e:
if e[0] == 13:
# EACCES: permission denied
Error("You do not have permission to write file %s" % dest_path)
Error("Unknown error writing file %s" % dest_path, IOError, e)
os.chmod(DESTDIR + dest_path, mode)
if compile_it:
py_compile.compile(DESTDIR + dest_path,
DESTDIR + dest_path + "c" , dest_path)
return
def install_tree(src_path, dst_path, prompt_replace):
"""Install a tree whose source is at SRC_PATH (which is relative
to the ViewVC source directory) into the location DST_PATH (which
is relative both to the global ROOT_DIR and DESTDIR settings). If
PROMPT_REPLACE is set (and is not overridden by global setting
CLEAN_MODE), prompt the user for how to deal with already existing
files that differ from the to-be-installed version."""
files = os.listdir(src_path)
files.sort()
for fname in files:
# eliminate some items which appear in a development area
if fname == 'CVS' or fname == '.svn' or fname == '_svn' \
or fname[-4:] == '.pyc' or fname[-5:] == '.orig' \
or fname[-4:] == '.rej' or fname[0] == '.' \
or fname[-1] == '~':
continue
orig_src_path = src_path
orig_dst_path = dst_path
src_path = _actual_src_path(src_path)
dst_path = os.path.join(ROOT_DIR, string.replace(dst_path, '/', os.sep))
destdir_path = os.path.join(DESTDIR + dst_path)
# Get a list of items in the directory.
files = os.listdir(src_path)
files.sort()
for fname in files:
# Ignore some stuff found in development directories, but not
# intended for installation.
if fname == 'CVS' or fname == '.svn' or fname == '_svn' \
or fname[-4:] == '.pyc' or fname[-5:] == '.orig' \
or fname[-4:] == '.rej' or fname[0] == '.' \
or fname[-1] == '~':
continue
orig_src_child = orig_src_path + '/' + fname
orig_dst_child = orig_dst_path + '/' + fname
# If the item is a subdirectory, recurse. Otherwise, install the file.
if os.path.isdir(os.path.join(src_path, fname)):
install_tree(orig_src_child, orig_dst_child, prompt_replace)
else:
set_paths = 0
compile_it = fname[-3:] == '.py'
install_file(orig_src_child, orig_dst_child, 0644,
set_paths, prompt_replace, compile_it)
# Check for .py and .pyc files that don't belong in installation.
for fname in os.listdir(destdir_path):
if not os.path.isfile(os.path.join(destdir_path, fname)) or \
not ((fname[-3:] == '.py' and fname not in files) or
(fname[-4:] == '.pyc' and fname[:-1] not in files)):
continue
# If we get here, there's cruft.
delete = None
if CLEAN_MODE == 'true':
delete = 1
elif CLEAN_MODE == 'false':
delete = 0
else:
print "File %s does not belong in ViewVC %s." \
% (dst_path, version)
while 1:
temp = raw_input("Do you want to [D]elete it, or [L]eave "
"it as is? ")
temp = string.lower(temp[0])
if temp == "l":
delete = 0
elif temp == "d":
delete = 1
if delete is not None:
break
assert delete is not None
if delete:
print " deleted %s" % (os.path.join(dst_path, fname))
os.unlink(os.path.join(destdir_path, fname))
else:
print " preserved %s" % (os.path.join(dst_path, fname))
def usage_and_exit(errstr=None):
stream = errstr and sys.stderr or sys.stdout
stream.write("""Usage: %s [OPTIONS]
Installs the ViewVC web-based version control repository browser.
Options:
--help, -h, -? Show this usage message and exit.
--prefix=DIR Install ViewVC into the directory DIR. If not provided,
the script will prompt for this information.
--destdir=DIR Use DIR as the DESTDIR. This is generally only used
by package maintainers. If not provided, the script will
prompt for this information.
--clean-mode= If 'true', overwrite existing ViewVC configuration files
found in the target directory, and purge Python modules
from the target directory that aren't part of the ViewVC
distribution. If 'false', do not overwrite configuration
files, and do not purge any files from the target
directory. If not specified, the script will prompt
for the appropriate action on a per-file basis.
""" % (os.path.basename(sys.argv[0])))
if errstr:
stream.write("ERROR: %s\n\n" % (errstr))
sys.exit(1)
src = os.path.join(src_path, fname)
dst = os.path.join(dst_path, fname)
if os.path.isdir(src):
install_tree(src, dst, prompt_replace)
else:
sys.exit(0)
print " ", src
set_paths = 0
compile_it = fname[-3:] == '.py'
InstallFile(src, dst, 0644, set_paths, prompt_replace, compile_it)
# prompt to delete all .py and .pyc files that don't belong in installation
full_dst_path = os.path.join(DESTDIR + ROOT_DIR, dst_path)
for fname in os.listdir(full_dst_path):
if not os.path.isfile(os.path.join(full_dst_path, fname)) or \
not ((fname[-3:] == '.py' and fname not in files) or
(fname[-4:] == '.pyc' and fname[:-1] not in files)):
continue
while 1:
temp = raw_input("""
File %s does not belong in ViewVC %s.
DO YOU WANT TO,
delete [d]
leave as is [l]: """ % (os.path.join(dst_path, fname), version))
print
temp = string.lower(temp[0])
if temp == "l":
break
if temp == "d":
os.unlink(os.path.join(full_dst_path, fname))
break
## MAIN
if __name__ == "__main__":
# Option parsing.
# option parsing
try:
optlist, args = getopt.getopt(sys.argv[1:], "h?",
['prefix=',
'destdir=',
'clean-mode=',
'help'])
optlist, args = getopt.getopt(sys.argv[1:], "", ['prefix=', 'destdir='])
except getopt.GetoptError, e:
usage_and_exit(str(e))
Error("Invalid option", getopt.GetoptError, e)
for opt, arg in optlist:
if opt == '--help' or opt == '-h' or opt == '-?':
usage_and_exit()
if opt == '--prefix':
ROOT_DIR = arg
if opt == '--destdir':
DESTDIR = arg
if opt == '--clean-mode':
arg = arg.lower()
if arg not in ('true', 'false'):
usage_and_exit("Invalid value for --overwrite parameter.")
CLEAN_MODE = arg
if opt == '--prefix':
ROOT_DIR = arg
if opt == '--destdir':
DESTDIR = arg
# Print the header greeting.
print """This is the ViewVC %s installer.
## print greeting
print INFO_TEXT
It will allow you to choose the install path for ViewVC. You will now
be asked some installation questions. Defaults are given in square brackets.
Just hit [Enter] if a default is okay.
""" % version
# Prompt for ROOT_DIR if none provided.
## prompt for ROOT_DIR if none provided
if ROOT_DIR is None:
if sys.platform == "win32":
pf = os.getenv("ProgramFiles", "C:\\Program Files")
default = os.path.join(pf, "viewvc-" + version)
else:
default = "/usr/local/viewvc-" + version
temp = string.strip(raw_input("Installation path [%s]: " \
% default))
print
if len(temp):
ROOT_DIR = temp
else:
ROOT_DIR = default
# Prompt for DESTDIR if none provided.
if DESTDIR is None:
default = ''
temp = string.strip(raw_input(
"DESTDIR path (generally only used by package "
"maintainers) [%s]: " \
% default))
print
if len(temp):
DESTDIR = temp
else:
DESTDIR = default
# Install the files.
print "Installing ViewVC to %s%s:" \
% (ROOT_DIR, DESTDIR and " (DESTDIR = %s)" % (DESTDIR) or "")
for args in FILE_INFO_LIST:
apply(install_file, args)
for args in TREE_LIST:
apply(install_tree, args)
if sys.platform == "win32":
pf = os.getenv("ProgramFiles", "C:\\Program Files")
default = os.path.join(pf, "viewvc-" + version)
else:
default = "/usr/local/viewvc-" + version
temp = string.strip(raw_input("Installation path [%s]: " % default))
print
if len(temp):
ROOT_DIR = temp
else:
ROOT_DIR = default
# Print some final thoughts.
## prompt for DESTDIR if none provided
if DESTDIR is None:
default = ''
temp = string.strip(raw_input(
"DESTDIR path (generally, only package maintainers will need "
"to change\nthis) [%s]: " % default))
print
if len(temp):
DESTDIR = temp
else:
DESTDIR = default
## install the files
print "Installing ViewVC to:", ROOT_DIR,
if DESTDIR:
print "(DESTDIR = %s)" % (DESTDIR)
else:
print
for args in FILE_INFO_LIST:
print " ", args[0]
apply(InstallFile, args)
for args in TREE_LIST:
apply(install_tree, args)
print """
ViewVC file installation complete.
ViewVC File Installation Complete
Consult the INSTALL document for detailed information on completing the
installation and configuration of ViewVC on your system. Here's a brief
overview of the remaining steps:
Consult INSTALL for detailed information to finish the installation
and configure ViewVC for your system.
1) Edit the %s file.
Overview of remaining steps:
2) Either configure an existing web server to run
%s.
Or, copy %s to an
already-configured cgi-bin directory.
Or, use the standalone server provided by this distribution at
%s.
""" % (os.path.join(ROOT_DIR, 'viewvc.conf'),
os.path.join(ROOT_DIR, 'bin', 'cgi', 'viewvc.cgi'),
os.path.join(ROOT_DIR, 'bin', 'cgi', 'viewvc.cgi'),
os.path.join(ROOT_DIR, 'bin', 'standalone.py'))
1) Edit the %s file.
2) Configure an existing web server to run (or copy to cgi-bin)
%s.
OR
Run the web server that comes with ViewVC at
%s.
""" % (
os.path.join(ROOT_DIR, 'viewvc.conf'),
os.path.join(ROOT_DIR, 'bin', 'cgi', 'viewvc.cgi'),
os.path.join(ROOT_DIR, 'standalone.py'))

615
viewvc.conf.dist Normal file
View File

@@ -0,0 +1,615 @@
#---------------------------------------------------------------------------
#
# Configuration file for ViewVC
#
# Information on ViewVC is located at the following web site:
# http://viewvc.org/
#
#---------------------------------------------------------------------------
#
# BASIC CONFIGURATION
#
# For correct operation, you will probably need to change the following
# configuration variables:
#
# cvs_roots (for CVS)
# svn_roots (for Subversion)
# root_parents (for CVS or Subversion)
# default_root
# rcs_path or cvsnt_exe_path
# mime_types_file
#
# It is usually desirable to change the following variables:
#
# address
# forbidden
#
# use_enscript
# use_cvsgraph
#
# For Python source colorization:
#
# py2html_path
#
# To optimize delivery of ViewVC static files:
#
# docroot
#
# To customize the display of ViewVC for your site:
#
# template_dir
# the [templates] override section
#
#
# FORMAT INFORMATION
#
# This file is delineated by sections, specified in [brackets]. Within each
# section, are a number of configuration settings. These settings take the
# form of: name = value. Values may be continued on the following line by
# indenting the continued line.
#
# WARNING: indentation *always* means continuation. name=value lines should
# always start in column zero.
#
# Comments should always start in column zero, and are identified with "#".
#
# Certain configuration settings may have multiple values. These should be
# separated by a comma. The settings where this is allowed are noted below.
#
# Any other setting that requires special syntax is noted at that setting.
#
#---------------------------------------------------------------------------
[general]
#
# This setting specifies each of the CVS roots on your system and assigns
# names to them. Each root should be given by a "name: path" value. Multiple
# roots should be separated by commas and can be placed on separate lines.
#
cvs_roots = cvs: /home/cvsroot
#
# This setting specifies each of the Subversion roots (repositories)
# on your system and assigns names to them. Each root should be given
# by a "name: path" value. Multiple roots should be separated by
# commas and can be placed on separate lines.
#
#svn_roots = svn: /home/svnrepos
# The 'root_parents' setting specifies a list of directories in which
# any number of repositories may reside. Rather than force you to add
# a new entry to 'cvs_roots' or 'svn_roots' each time you create a new
# repository, ViewVC rewards you for organising all your repositories
# under a few parent directories by allowing you to simply specifiy
# just those parent directories. ViewVC will then notice each
# repository in that directory as a new root whose name is the
# subdirectory of the parent path in which that repository lives.
#
# You can specify multiple parent paths separated by commas or new lines.
#
# Caution: these names can, of course, clash with names you have
# defined in your cvs_roots or svn_roots configuration items. If this
# occurs, you can either rename the offending repository on disk, or
# grant new names to the clashing item in cvs_roots or svn_roots.
# Each parent path is processed sequentially, so repositories under
# later parent paths may override earlier ones.
#
#root_parents = /home/svn-repositories : svn,
# /home/cvs-repositories : cvs
# this is the name of the default root
# (ignored when root_as_url_component is turned on)
default_root = cvs
# uncomment if the RCS binaries are not on the standard path
#rcs_path = /usr/bin/
# ViewVC can use CVSNT (www.cvsnt.org) instead of the RCS
# utilities to retrieve information from CVS repositories.
# To enable use of CVSNT, set the "cvsnt_exe_path" value
# to the path of the CVSNT executable. (If CVSNT is on the
# standard path, you can also set it to the name of the
# CVSNT executable). By default "cvsnt_exe_path" is set to
# "cvs" on Windows and is not set on other platforms.
#
#cvsnt_exe_path =
#cvsnt_exe_path = cvs
#cvsnt_exe_path = K:\Program Files\cvsnt\cvs.exe
#cvsnt_exe_path = /usr/bin/cvs
# use rcsparse python module to retrieve cvs repository information instead
# of invoking rcs utilities. this feature is experimental
use_rcsparse = 0
# uncomment if the svn command-line utilities are not on the standard path
#svn_path = /usr/bin/
#
# This is a pathname to a MIME types file to help viewvc to guess the
# correct MIME type on checkout.
#
# If you are having problems with the default guess on the MIME type, then
# uncomment this option and point it at a MIME type file.
#
# For example, you can use the mime.types from apache here:
#mime_types_file = /usr/local/apache/conf/mime.types
# This address is shown in the footer of the generated pages.
# It must be replaced with the address of the local repository maintainer.
address = <a href="mailto:cvs-admin@insert.your.domain.here">No admin address has been configured</a>
#
# This should contain a list of modules in the repository that should not be
# displayed (by default or by explicit path specification).
#
# This configuration can be a simple list of modules, or it can get quite
# complex:
#
# *) The "!" can be used before a module to explicitly state that it
# is NOT forbidden. Whenever this form is seen, then all modules will
# be forbidden unless one of the "!" modules match.
#
# *) Shell-style "glob" expressions may be used. "*" will match any
# sequence of zero or more characters, "?" will match any single
# character, "[seq]" will match any character in seq, and "[!seq]"
# will match any character not in seq.
#
# *) Tests are performed in sequence. The first match will terminate the
# testing. This allows for more complex allow/deny patterns.
#
# Tests are case-sensitive.
#
forbidden =
# Some examples:
#
# Disallow "example" but allow all others:
# forbidden = example
#
# Disallow "example1" and "example2" but allow all others:
# forbidden = example1, example2
#
# Allow *only* "example1" and "example2":
# forbidden = !example1, !example2
#
# Forbid modules starting with "x":
# forbidden = x*
#
# Allow modules starting with "x" but no others:
# forbidden = !x*
#
# Allow "xml", forbid other modules starting with "x", and allow the rest:
# forbidden = !xml, x*, !*
#
#
# This option provides a mechanism for custom key/value pairs to be
# available to templates. These are stored in key/value files (KV files).
#
# Pathnames to the KV files are listed here, specified as absolute paths
# or relative to this configuration file. The kV files follow the same
# format as this configuration file. It may have multiple, user-defined
# sections, and user-defined options in those sections. These are all
# placed into a structure available to the templates as:
#
# kv.SECTION.OPTION
#
# Note that an option name can be dotted. For example:
#
# [my_images]
# logos.small = /images/small-logo.png
# logos.big = /images/big-logo.png
#
# Templates can use these with a directive like: [kv.my_images.logos.small]
#
# Note that sections across multiple files will be merged. If two files
# have a [my_images] section, then the options will be merged together.
# If two files have the same option name in a section, then one will
# overwrite the other (it is unspecified regarding which "wins").
#
# To further categorize the KV files, and how the values are provided to
# the templates, a KV file name may be annotated with an additional level
# of dotted naming. For example:
#
# kv_files = [asf]kv/images.conf
#
# Assuming the same section as above, the template would refer to an image
# using [kv.asf.my_images.logos.small]
#
# Lastly, it is possible to use %lang% in the filenames to specify a
# substitution of the selected language-tag.
#
kv_files =
# example:
# kv_files = kv/file1.conf, kv/file2.conf, [i18n]kv/%lang%_data.conf
#
#
# The languages available to ViewVC. There are several i18n mechanisms
# available:
#
# 1) using key/value extension system and reading KV files based on
# the selected language
# 2) GNU gettext to substitute text in the templates
# 3) using different templates, based on the selected language
#
# ### NOTE: at the moment, the GNU gettext style is not implemented
#
# This option is a comma-separated list of language-tag values. The first
# language-tag listed is the default language, and will be used if an
# Accept-Language header is not present in the request, or none of the
# user's requested languages are available. If there are ties on the
# selection of a language, then the first to appear in the list is chosen.
#
languages = en-us
# other examples:
#
# languages = en-us, de
# languages = en-us, en-gb, de
# languages = de, fr, en-us
#
#---------------------------------------------------------------------------
[options]
# root_as_url_component: Interpret the first path component in the URL
# after the script location as the root to use. This is an
# alternative to using the "root=" query key. If ViewVC is configured
# with multiple repositories, this results in more natural looking
# ViewVC URLs.
# Note: Enabling this option will break backwards compatibility with
# any old ViewCVS URL which doesn't have an explicit "root" parameter.
root_as_url_component = 0
# default_file_view: "log" or "co"
# Controls whether the default view for file URLs is a checkout view or
# a log view. "log" is the default for backwards compatibility with old
# ViewCVS URLs, but "co" has the advantage that it allows ViewVC to serve
# static HTML pages directly from a repository with working links
# to other repository files
# Note: Changing this option may cause old ViewCVS URLs that referred
# to log pages to load checkout pages instead.
default_file_view = log
# checkout_magic: Use checkout links with magic /*checkout*/ prefixes so
# checked out HTML pages can have working links to other repository files
# Note: This option is DEPRECATED and should not be used in new ViewVC
# installations. Setting "default_file_view = co" achieves the same effect
checkout_magic = 0
# http_expiration_time: Expiration time (in seconds) for cacheable
# pages served by ViewVC. Note that in most cases, a cache aware
# client will only revalidate the page after it expires (using the
# If-Modified-Since and/or If-None-Match headers) and that browsers
# will also revalidate the page when the reload button is pressed.
# Set to 0 to disable the transmission of these caching headers.
http_expiration_time = 600
# generate_etags: Generate Etag headers for relevant pages to assist
# in browser caching.
# 1 Generate Etags
# 0 Don't generate Etags
generate_etags = 1
# sort_by: File sort order
# file Sort by filename
# rev Sort by revision number
# date Sort by commit date
# author Sort by author
# log Sort by log message
sort_by = file
# sort_group_dirs: Group directories when sorting
# 1 Group directories together
# 0 No grouping -- sort directories as any other item would be sorted
sort_group_dirs = 1
# hide_attic: Hide or show the contents of the Attic subdirectory
# 1 Hide dead files inside Attic subdir
# 0 Show the files which are inside the Attic subdir
hide_attic = 1
# log_sort: Sort order for log messages
# date Sort revisions by date
# rev Sort revision by revision number
# cvs Don't sort them. Same order as CVS/RCS shows them.
log_sort = date
# diff_format: Default diff format
# h Human readable
# u Unified diff
# c Context diff
# s Side by side
# l Long human readable (more context)
diff_format = h
# hide_cvsroot: Don't show the CVSROOT directory
# 1 Hide CVSROOT directory
# 0 Show CVSROOT directory
hide_cvsroot = 1
# set to 1 to make lines break at spaces,
# set to 0 to make no-break lines,
# set to a positive integer to make the lines cut at that length
hr_breakable = 1
# give out function names in human readable diffs
# this just makes sense if we have C-files, otherwise
# diff's heuristic doesn't work well ..
# ( '-p' option to diff)
hr_funout = 0
# ignore whitespaces for human readable diffs
# (indendation and stuff ..)
# ( '-w' option to diff)
hr_ignore_white = 1
# ignore diffs which are caused by
# keyword-substitution like $Id - Stuff
# ( '-kk' option to rcsdiff)
hr_ignore_keyword_subst = 1
# Enable highlighting of intraline changes in human readable diffs
# this feature is experimental and currently requires python 2.4
#
hr_intraline = 0
# allow annotation of files.
allow_annotate = 1
# allow pretty-printed version of files
allow_markup = 1
# allow compression with gzip of output if the Browser accepts it
# (HTTP_ACCEPT_ENCODING=gzip)
# [make sure to have gzip in the path]
allow_compress = 1
# The directory which contains the EZT templates used by ViewVC to
# customize the display of the various output views. ViewVC looks in
# this directory for files with names that match the name of the view
# ("log", "directory", etc.) plus the ".ezt" extension. If specified
# as a relative path, it is relative to the ViewVC installation
# directory; absolute paths may be used as well.
#
# If %lang% occurs in the pathname, then the selected language will be
# substituted.
#
template_dir = templates
# Web path to a directory that contains ViewVC static files
# (stylesheets, images, etc.) If set, static files will get
# downloaded directory from this location. If unset, static files
# will be served by the ViewVC script (at a likely performance
# penalty, and from the "docroot" subdirectory of the directory
# specified by the "template_dir" option).
#docroot = /docroot
# Show last changelog message for sub directories
# The current implementation makes many assumptions and may show the
# incorrect file at some times. The main assumption is that the last
# modified file has the newest filedate. But some CVS operations
# touches the file without even when a new version is not checked in,
# and TAG based browsing essentially puts this out of order, unless
# the last checkin was on the same tag as you are viewing.
# Enable this if you like the feature, but don't rely on correct results.
show_subdir_lastmod = 0
# show a portion of the most recent log entry in directory listings
show_logs = 1
# Show log when viewing file contents
show_log_in_markup = 1
# Cross filesystem copies when traversing Subversion file revision histories.
cross_copies = 1
# Display dates as UTC or in local time zone
use_localtime = 0
#use_localtime = 1
# == Configuration defaults ==
# Defaults for configuration variables that shouldn't need
# to be configured..
#
# If you want to use Marc-Andrew Lemburg's py2html (and Just van Rossum's
# PyFontify) to colorize Python files, then you may need to change this
# variable to point to their directory location.
#
# This directory AND the standard Python path will be searched.
#
py2html_path = .
#py2html_path = /usr/local/lib/python1.5/site-python
# the length to which the most recent log entry should be truncated when
# shown in the directory view
short_log_len = 80
# should we use 'enscript' for syntax coloring?
use_enscript = 0
#
# if the enscript program is not on the path, set this value
#
enscript_path =
# enscript_path = /usr/bin/
# should we use 'highlight' for syntax coloring?
# NOTE: use_enscript has to be 0 or enscript will be used instead
use_highlight = 0
#
# if the highlight program is not on the path, set this value
#
# highlight_path = /usr/bin
# should we add line numbers?
highlight_line_numbers = 1
# convert tabs to ## spaces (use 0 for no conversion)
highlight_convert_tabs = 2
# use php to colorize .php and .inc files?
use_php = 0
# path to php executable
php_exe_path = php
# php_exe_path = /usr/local/bin/php
# php_exe_path = C:\Program Files\php\cli\php.exe
#
# ViewVC can generate tarball from a repository on the fly.
#
allow_tar = 0
# allow_tar = 1
#
# Use CvsGraph. See http://www.akhphd.au.dk/~bertho/cvsgraph/ for
# documentation and download.
#
use_cvsgraph = 0
# use_cvsgraph = 1
#
# if the cvsgraph program is not on the path, set this value
#
cvsgraph_path =
# cvsgraph_path = /usr/local/bin/
#
# Location of the customized cvsgraph configuration file.
# You will need an absolute pathname here:
#
cvsgraph_conf = cvsgraph.conf
#
# Set to enable regular expression search of all files in a directory
#
# WARNING:
#
# Enabling this option can consume HUGE amounts of server time. A
# "checkout" must be performed on *each* file in a directory, and
# the result needs to be searched for a match against the regular
# expression.
#
#
# SECURITY WARNING: Denial Of Service
#
# Since a user can enter the regular expression, it is possible for
# them to enter an expression with many alternatives and a lot of
# backtracking. Executing that search over thousands of lines over
# dozens of files can easily tie up a server for a long period of
# time.
#
# This option should only be used on sites with trusted users. It is
# highly inadvisable to use this on a public site.
#
use_re_search = 0
# use_re_search = 1
#
# Split directories and logs into pages.
# Allows ViewVC to present discrete pages to the users instead of the
# entire log or directory.
# Set use_pagesize to the number of entries you want displayed on a page.
#
use_pagesize = 0
# use_pagesize = 20
# Limit number of changed paths shown per commit in the Subversion revision
# view and in query results. This is not a hard limit (the UI provides
# options to show all changed paths), but it prevents ViewVC from generating
# enormous and hard to read pages by default when they happen to contain
# import or merge commits affecting hundreds or thousands of files.
# Set to 0 to disable the limit.
limit_changes = 100
#---------------------------------------------------------------------------
[templates]
# You can override the templates used by various ViewVC views in this
# section. By default, ViewVC will look for templates in the
# directory specified by the "template_dir" configuration option (see
# the documentation for that option for details). But if you want to
# use a different template for a particular view, simply uncomment the
# appropriate option below and specify the currect location of the EZT
# template file you wish to use for that view.
#
# Templates are specified relative to the installation directory, but
# absolute paths may also be used as well.
#
# If %lang% occurs in the pathname, then the selected language will be
# substituted.
#
# Note: the selected language is defined by the "languages" item in the
# [general] section, and based on the request's Accept-Language
# header.
#
#directory = templates/directory.ezt
### an alternative directory view
#directory = templates/dir_new.ezt
#log = templates/log.ezt
### a table-based alternative log view
#log = templates/log_table.ezt
#query = templates/query.ezt
#diff = templates/diff.ezt
#graph = templates/graph.ezt
#annotate = templates/annotate.ezt
#markup = templates/markup.ezt
#revision = templates/revision.ezt
#query_form = templates/query_form.ezt
#query_results = templates/query_results.ezt
#error = templates/error.ezt
#roots = templates/roots.ezt
#---------------------------------------------------------------------------
[cvsdb]
enabled = 0
#host = localhost
#port = 3306
#database_name = ViewVC
#user =
#passwd =
#readonly_user =
#readonly_passwd =
#row_limit = 1000
#---------------------------------------------------------------------------
[vhosts]
### DOC
# vhost1 = glob1, glob2
# vhost2 = glob3, glob4
# [vhost1-section]
# option = value
# [vhost1-othersection]
# option = value
# [vhost2-section]
# option = value
#
# Here is an example:
#
# [vhosts]
# lyra = *lyra.org
#
# [lyra-general]
# forbidden = hideme
#
# [lyra-options]
# show_logs = 0
#
# Note that "lyra" is the "canonical" name for all hosts in the lyra.org
# domain. This canonical name is then used within the additional, vhost-
# specific sections to override specific values in the common sections.
#
#---------------------------------------------------------------------------

Some files were not shown because too many files have changed in this diff Show More