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

Compare commits

..

1 Commits
V0_9_3 ... V0_7

Author SHA1 Message Date
(no author)
f7920e796c This commit was manufactured by cvs2svn to create tag 'V0_7'.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/V0_7@197 8cb11bc2-c004-0410-86c3-e597b4017df7
2001-05-30 09:19:18 +00:00
56 changed files with 2586 additions and 8327 deletions

68
CHANGES
View File

@@ -1,68 +0,0 @@
Version 0.9.3 (released 17-May-2005)
* security fix: disallow bad "content-type" input [CAN-2004-1062]
* security fix: disallow bad "sortby" and "cvsroot" input [CAN-2002-0771]
* security fix: omit forbidden/hidden modules from tarballs [CAN-2002-0771]
Version 0.9.2 (released 15-Jan-2001)
* fix redirects to Attic for diffs
* fix diffs that have no changes (causing an infinite loop)
Version 0.9.1 (released 26-Dec-2001)
* fix a problem with some syntax in ndiff.py which isn't compatible
with Python 1.5.2 (causing problems at install time)
* remove a debug statement left in the code which continues to
append lines to /tmp/log
Version 0.9 (released 23-Dec-2001)
* create templates for the rest of the pages: markup pages, graphs,
annotation, and diff.
* add multiple language support and dynamic selection based on the
Accept-Language request header
* add support for key/value files to provide a way for user-defined
variables within templates
* add optional regex searching for file contents
* add new templates for the navigation header and the footer
* EZT changes:
- add formatting into print directives
- add parameters to [include] directives
- relax what can go in double quotes
- [include] directives are now relative to the current template
- throw an exception for unclosed blocks
* changes to standalone.py: add flag for regex search
* add more help pages
* change installer to optionally show diffs
* fix to log.ezt and log_table.ezt to select "Side by Side" properly
* 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
* big update of cvs commit database
- updated MySQL support
- new CGI
- better database caching
- switch from old templates to new EZT templates (and integration
of look-and-feel)
* optional usage of CVSGraph is now builtin
* standalone server (for testing) is now provided
* shifted some options from viewcvs.conf to the templates
* the help at the top of the pages has been shifted to separate help
pages, so experienced users don't have to keep seeing it
* paths in viewcvs.conf don't require trailing slashes any more
* tweak the colorizing for Pascal and Fortran files
* fix file readability problem where the user had access via the
group, but the process' group did not match that group
* some Daylight Savings Time fixes in the CVS commit database
* fix tarball generation (the file name) for the root dir
* changed default human-readable-diff colors to "stoplight" metaphor
* web site and doc revamps
* fix the mime types on the download, view, etc links
* improved error response when the cvs root is missing
* don't try to process vhosts if the config section is not present
* various bug fixes and UI tweaks

217
INSTALL
View File

@@ -1,86 +1,25 @@
CONTENTS
--------
TO THE IMPATIENT
INSTALLING VIEWCVS
UPGRADING VIEWCVS
SQL CHECKIN DATABASE
ENSCRIPT CONFIGURATION
CVSGRAPH CONFIGURATION
IF YOU HAVE PROBLEMS...
TO THE IMPATIENT
----------------
Congratulations on getting this far. :-)
Prerequisites: Python 1.5 or later
(http://www.python.org/)
RCS, Revision Control System
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
read-only, physical access to a CVS repository
(See http://www.cvshome.org/ for more information)
Optional: a web server capable of running CGI programs
(for example, Apache at http://httpd.apache.org/)
GNU-diff to replace broken diff implementations
(http://www.gnu.org/software/diffutils/diffutils.html)
MySQL to create and query a commit database
(http://www.mysql.com/)
(http://sourceforge.net/projects/mysql-python)
(and Python 1.5.2 or later)
Enscript to colorize code displayed from the CVS repository
(http://people.ssh.com/mtr/genscript/)
CvsGraph for a graphical representation of the CVS revisions
(http://www.akhphd.au.dk/~bertho/cvsgraph/)
GUI Operation:
If you just want to see what your CVS repository looks like with
ViewCVS, type "./standalone.py -g -r /PATH/TO/CVS/ROOT". This
will start a tiny webserver serving at http://localhost:7467/.
Standard operation:
To start installing right away (on UNIX): type "./viewcvs-install"
in the current directory and answer the prompts. When it
finishes, edit the file viewcvs.conf in the installation directory
to tell viewcvs the paths to your CVS repositories. Next,
configure your web server to run <INSTALL>/cgi/viewcvs.cgi, as
appropriate for your web server. The section `INSTALLING VIEWCVS'
below is still recommended reading.
INSTALLING VIEWCVS
------------------
1) To get viewcvs.cgi to work, make sure that you have Python 1.5 or
greater installed and a webserver which is capable of executing
CGI scripts (either based on the .cgi extension, or by placing the
script within a specific directory).
1) To get viewcvs.cgi to work, make sure that you have Python 1.5
installed and a webserver which is capable of executing cgi-scripts
(either based on the .cgi extension, or by placing the script
within a specific directory).
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
and "co". This script was tested against RedHat's rcs-5.7-10.rpm
Note, that the viewcvs.cgi script needs to have READ-ONLY, physical
access to the CVS repository (or a copy of it). Therefore, rsh/ssh or
pserver access to the repository will not work.
For the more human readable diff formats you need a modern diff utility.
If you are using Linux, this is no problem. But on commercial unices
you might want to install GNU-diff to be able to use unified or
side-by-side diffs.
If you want to use cvsgraph, you have to obtain and install this
separately. See below. This was tested with cvsgraph-1.1.2.
pserver access doesn't work yet.
For the checkin database to work, you will need MySQL >= 3.22,
and the Python DBAPI 2.0 module, MySQLdb. This was tested with
MySQLdb 0.9.1.
MySQLdb 1.12.
2) Installation is handled by the ./viewcvs-install script. Run this
script and you will be prompted for a installation root path.
The default is /usr/local/viewcvs-VERSION, where VERSION is
the version of this ViewCVS release. The installer sets the install
The default is /usr/local/viewcvs. The installer sets the install
path in some of the files, and ViewCVS cannot be moved to a
different path after the install.
@@ -94,8 +33,8 @@ INSTALLING VIEWCVS
a previous installation. It will always overwrite program files,
however.
3) Edit <VIEWCVS_INSTALLATION_DIRECTORY>/viewcvs.conf for your specific
configuration. In particular, examine the following configuration options:
3) Edit <install-root>viewcvs.conf for your specific configuration.
In particular, examine the following configuration options:
cvs_roots
default_root
@@ -103,20 +42,13 @@ INSTALLING VIEWCVS
mime_types_file
There are some other options that are usually nice to change. See
viewcvs.conf for more information. ViewCVS provides a working,
default look. However, if you want to customize the look of ViewCVS
then edit the files in <VIEWCVS_INSTALLATION_DIRECTORY>/templates.
You need knowledge about HTML to edit the templates.
viewcvs.conf for more information.
4) The CGI programs are in <VIEWCVS_INSTALLATION_DIRECTORY>/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 <VIEWCVS_INSTALLATION_DIRECTORY>/cgi/*.cgi scripts
after the install (unlike the other files in ViewCVS, the CGI scripts can
be moved).
If you are using Apache, then the ScriptAlias directive is very
useful for pointing directly to the viwecvs.cgi script.
4) The CGI programs are in <install-root>/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 <install-root>/cgi/*.cgi scripts after the install
(unlike the other files in ViewCVS, the CGI scripts can be moved).
NOTE: for security reasons, it is not advisable to install ViewCVS
directly into your published HTTP directory tree (due to the MySQL
@@ -130,40 +62,33 @@ WARNING: ViewCVS has not been tested on web servers operating on the
Win32 platform.
UPGRADING VIEWCVS
-----------------
Please read the file upgrading.html in the website subdirectory or
at <http://viewcvs.sourceforge.net/upgrading.html>.
SQL CHECKIN DATABASE
SQL Checkin Database
--------------------
This feature is a clone of the Mozilla Project's Bonsai database. It
catalogs every commit in the CVS repository into a SQL database. In fact,
the databases are 100% compatible.
Various queries can be performed on the database. After installing ViewCVS,
Various queries can be preformed on the database. After installing ViewCVS,
there are some additional steps required to get the database working.
1) You need MySQL >= 3.22, and the Python module MySQLdb 0.9.0 installed.
1) You need MySQL >= 3.22, and the Python module MySQLdb >= 1.12 installed.
Python 1.5.2 is REQUIRED by MySQLdb, therefore to use this part of
ViewCVS you must be using Python 1.5.2. Additionally you will need the
mxDateTime extension. I've tested with version 1.3.0
ViewCVS you must be useing Python 1.5.2.
2) You need to create a MySQL user who has permission to create databases.
Optionally, you can create a second user with read-only access to the
database.
3) Run the <VIEWCVS_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 "ViewCVS". This script
creates the database and sets up the empty tables. If you run this on a
existing ViewCVS database, you will lose all your data!
3) Run the <install-root>/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 "ViewCVS". This script creates
the database and sets up the empty tables. If you run this on a
existing ViewCVS database, you will loose all your data!
4) Edit your <VIEWCVS_INSTALLATION_DIRECTORY>/viewcvs.conf file.
There is a [cvsdb] section. You will need to set:
4) Edit your <install-root>/viewcvs.conf file. There is a [cvsdb]
section. You will need to set:
host = # MySQL database server host
@@ -178,7 +103,7 @@ there are some additional steps required to get the database working.
5) Two programs are provided for updating the checkin database,
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
every commit in every file. This is commonly used for initalizing
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
@@ -188,99 +113,31 @@ there are some additional steps required to get the database working.
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.
and only adds it if it is abscent.
To get real-time updates, you'll want to checkout the CVSROOT module
from your CVS repository and edit CVSROOT/loginfo. Add the line:
ALL (echo %{sVv}; cat) | <VIEWCVS_INSTALLATION_DIRECTORY>/loginfo-handler
ALL (echo %{sVv}; cat) | <install-root>/loginfo-handler
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.
6) You may want to modify the HTML template file:
6) You may want to modify the HTML template files:
<VIEWCVS_INSTALLATION_DIRECTORY>/templates/query.ezt
<install-root>/html-templates/queryformtemplate.html
<install-root>/html-templates/querytemplate.html
This is used by the query.cgi script to generate part of its HTML output.
At some point the currently hardcoded table output will also vanish.
They're used by the queryform.cgi and query.cgi scripts generate
HTML output. At some point, viewcvs.cgi, query.cgi, and queryform.cgi
will use the same mechanism for HTML generation, but not yet.
7) You should be ready to go. Load up the query.cgi script and give
7) You should be ready to go. Load up the queryform.cgi script and give
it a try.
ENSCRIPT CONFIGURATION
----------------------
Enscript is program that can colorize sourcecode of a lot of languages.
Linux distributions like for example SuSE Linux from at least 7.0
up to the recently released 7.3 already contain a precompiled and
configured enscript 1.6.2 package.
1) Download genscript from http://people.ssh.com/mtr/genscript/
2) Configure and compile per instructions with enscript.
(I 've not done this, since I'm using the precompiled package
delivered with SuSE Linux)
3) Set the 'use_enscript' option in viewcvs.conf to 1.
4) That's it!
5) If you want to colorize exotic languages, you might have to
patch 'lib/viewcvs.py' and add a new highlighting file to enscript.
I've done this for Modula-2 and submitted the file to the
enscript maintainer long ago. If interested in this patch for
enscript mailto:pefu@sourceforge.net
CVSGRAPH CONFIGURATION
----------------------
CvsGraph is a program that can display a clickable, graphical tree
of files in a CVS repository.
WARNING: Under certain circumstances (many revisions of a file
or many branches or both) cvsgraph can generate very huge images.
Especially on thin clients these images may crash the Web-Browser.
Currently there is no known way to avoid this behavior of cvsgraph.
So you have been warned!
Nevertheless cvsgraph can be quite helpful on repositories with
a reasonable number of revisions and branches.
1) Install viewcvs according to instructions in 'INSTALLING
VIEWCVS' section above. The installation directory is where
the 'viewcvs-install' script copied and configured the viewcvs
programs.
2) Download CvsGraph from http://www.akhphd.au.dk/~bertho/cvsgraph/
3) Configure and compile per instructions with CvsGraph. I had
problems with 'configure' finding the gd library. Had to create
a link from libgd.so to libgd.do.4.0.0. On Solaris you might
want to edit the link command line and add the option -R if
you have you libraries at non-standard location.
4) Place the 'cvsgraph' executable into a directory readable by the
userid running the web server. (default is '/usr/local/bin' if
you simply type 'make install' in the cvsgraph directory).
5) Check the setting of the 'cvsgraph_path' option in viewcvs.conf:
/usr/local/bin/ is most often NOT contained in $PATH of the
webserver process (e.g. Apache), so you will have to edit this.
Set the 'use_cvsgraph' option in viewcvs.conf to 1.
6) That's it!
7) There is a file <VIEWCVS_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 viewcvs.
IF YOU HAVE PROBLEMS ...
------------------------
@@ -311,7 +168,7 @@ If you've trouble to make viewcvs.cgi work:
CVS-Repository. The CGI-script often runs as the user 'nobody'
or 'httpd' ..
o does viewcvs find your RCS utilities? (edit rcs_path)
o does viewcvs find your RCS utililties? (edit rcs_path)
=== If something else happens or you can't get it to work:

3
README
View File

@@ -1,3 +0,0 @@
ViewCVS -- Viewing the content of CVS repositories with a Webbrowser.
Please read the file INSTALL for more information.

28
TODO
View File

@@ -1,13 +1,3 @@
PREFACE
-------
This file will go away soon after release 0.8. Please use the SourceForge
tracker to resubmit any of the items listed below, if you think, it is
still an issue:
http://sourceforge.net/tracker/?group_id=18760
Before reporting please check, whether someone else has already done this.
Working patches increase the chance to be included into the next release.
-- PeFu / October 2001
TODO ITEMS
----------
*) add Tamminen Eero's comments on how to make Linux directly execute
@@ -35,19 +25,19 @@ TODO ITEMS
*) add a check for the rcs programs/paths to viewcvs-install. clarify the
dependency on RCS in the docs.
*) add a page that describes how to reach anonymous CVS for ViewCVS
*) have a "check" mode that verifies binaries are available on rcs_path
-> alternately (probably?): use rcsparse rather than external tools
KNOWN BUGS
----------
*) time.timezone seems to not be available on some 1.5.2 installs.
I was unable to verify this. On RedHat and SuSE Linux this bug
is non existant.
*) from Dieter Deyke, Jan 12: if the CVS revisions differ by just a
keyword, then the diff output chokes.
*) With old repositories containing many branches, tags or thousands
or revisions, the cvsgraph feature becomes unusable (see INSTALL).
ViewCVS can't do much about this, but it might be possible to
investigate the number of branches, tags and revision in advance
and disable the cvsgraph links, if the numbers exceed a certain
treshold.
*) no scroll bar in Netscape browser when you click "as text" on an
HTML document.
*) time.timezone seems to not be available on some 1.5.2 installs

View File

@@ -1,192 +0,0 @@
# CvsGraph configuration
#
# - Empty lines and whitespace are ignored.
#
# - Comments start with '#' and everything until
# end of line is ignored.
#
# - Strings are C-style strings in which characters
# may be escaped with '\' and written in octal
# and hex escapes. Note that '\' must be escaped
# if it is to be entered as a character.
#
# - Some strings are expanded with printf like
# conversions which start with '%'. Not all
# are applicable at all times, in which case they
# will expand to noting.
# %c = cvsroot (with trailing '/')
# %C = cvsroot (*without* trailing '/')
# %m = module (with trailing '/')
# %M = module (*without* trailing '/')
# %f = filename without path
# %F = filename without path and with ",v" stripped
# %p = path part of filename (with trailing '/')
# %r = number of revisions
# %b = number of branches
# %% = '%'
# %R = the revision number (e.g. '1.2.4.4')
# %P = previous revision number
# %B = the branch number (e.g. '1.2.4')
# %d = date of revision
# %a = author of revision
# %s = state of revision
# %t = current tag of branch or revision
# %0..%9 = command-line argument -0 .. -9
# ViewCVS currently uses the following two command-line arguments to
# pass URL information to cvsgraph:
# -6 request.amp_query (the query preceeded with '&')
# -7 request.qmark_query (the query preceed with '?')
#
# - Numbers may be entered as octal, decimal or
# hex as in 0117, 79 and 0x4f respectively.
#
# - Fonts are numbered 0..4 (defined as in libgd)
# 0 = tiny
# 1 = small
# 2 = medium (bold)
# 3 = large
# 4 = giant
#
# - Colors are a string like html-type colors in
# the form "#rrggbb" with parts written in hex
# rr = red (00..ff)
# gg = green (00-ff)
# bb = blue (00-ff)
#
# - There are several reserved words besides of the
# feature-keywords. These additional reserved words
# expand to numerical values:
# * false = 0
# * true = 1
# * left = 0
# * center = 1
# * right = 2
# * gif = 0
# * png = 1
# * jpeg = 2
# * tiny = 0
# * small = 1
# * medium = 2
# * large = 3
# * giant = 4
# cvsroot <string>
# The *absolute* base directory where the
# CSV/RCS repository can be found
# cvsmodule <string>
#
cvsroot = "--unused--"; # unused with ViewCVS, will be overridden
cvsmodule = ""; # unused with ViewCVS -- please leave it blank
# color_bg <color>
# The background color of the image
color_bg = "#ffffff";
# date_format <string>
# The strftime(3) format string for date and time
date_format = "%d-%b-%Y %H:%M:%S";
box_shadow = true;
tag_font = medium;
tag_color = "#007000";
rev_font = giant;
rev_color = "#000000";
rev_bgcolor = "#f0f0f0";
rev_separator = 1;
rev_minline = 15;
rev_maxline = 30;
rev_lspace = 5;
rev_rspace = 5;
rev_tspace = 3;
rev_bspace = 3;
rev_text = "%d"; # or "%d\n%a, %s" for author and state too
rev_text_font = tiny;
rev_text_color = "#500020";
# branch_font <number>
# The font of the number and tags
# branch_color <color>
# All branch element's color
# branch_[lrtb]space <number>
# Interior spacing (margin)
# branch_margin <number>
# Exterior spacing
# branch_connect <number>
# Length of the vertical connector
branch_font = medium;
branch_color = "#0000c0";
branch_bgcolor = "#ffffc0";
branch_lspace = 5;
branch_rspace = 5;
branch_tspace = 3;
branch_bspace = 3;
branch_margin = 15;
branch_connect = 8;
# title <string>
# The title string is expanded (see above for details)
# title_[xy] <number>
# Postion of title
# title_font <number>
# The font
# title_align <number>
# 0 = left
# 1 = center
# 2 = right
# title_color <color>
title = "%C: %p%F\nRevisions: %r, Branches: %b";
title_x = 10;
title_y = 5;
title_font = small;
title_align = left;
title_color = "#800000";
# Margins of the image
# Note: the title is outside the margin
margin_top = 35;
margin_bottom = 10;
margin_left = 10;
margin_right = 10;
# Image format(s)
# image_type <number|{gif,jpeg,png}>
# gif (0) = Create gif image
# png (1) = Create png image
# jpeg (2) = Create jpeg image
# Image types are available if they can be found in
# the gd library. Newer versions of gd do not have
# gif anymore. CvsGraph will automatically generate
# png images instead.
# image_quality <number>
# The quality of a jpeg image (1..100)
image_type = png;
image_quality = 75;
# HTML ImageMap generation
# map_name <string>
# The name= attribute in <map name="mapname">...</map>
# map_branch_href <string>
# map_branch_alt <string>
# map_rev_href <string>
# map_rev_alt <string>
# map_diff_href <string>
# map_diff_alt <string>
# These are the href= and alt= attributes in the <area>
# tags of html. The strings are expanded (see above).
map_name = "MyMapName";
map_branch_href = "href=\"%m%F?only_with_tag=%t%8%6\"";
map_branch_alt = "alt=\"%0 %t (%B)\"";
# You might want to experiment with the following setting:
# 1. The default setting will take you to a ViewCVS generated page displaying
# that revision of the file, if you click into a revision box:
map_rev_href = "href=\"%m%F?rev=%R&content-type=text/vnd.viewcvs-markup%6\"";
# 2. This alternative setting will take you to the anchor representing this
# revision on a ViewCVS generated Log page for that file:
# map_rev_href = "href=\"%m%F%7#rev%R\"";
#
map_rev_alt = "alt=\"%1 %t (%R)\"";
map_diff_href = "href=\"%m%F.diff?r1=%P&r2=%R%8%6\"";
map_diff_alt = "alt=\"%2 %P &lt;-&gt; %R\"";

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*-python-*-
# -*- Mode: python -*-
#
# Copyright (C) 1999-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000-2001 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 ViewCVS
@@ -13,12 +13,10 @@
#
# -----------------------------------------------------------------------
#
# query.cgi: View CVS commit database by web browser
# CGI script to process and display queries to CVSdb
#
# -----------------------------------------------------------------------
#
# This is a teeny stub to launch the main ViewCVS app. It checks the load
# average, then loads the (precompiled) viewcvs.py file and runs it.
# This script is part of the ViewCVS package. More information can be
# found at http://viewcvs.sourceforge.net/.
#
# -----------------------------------------------------------------------
#
@@ -32,12 +30,10 @@
#
LIBRARY_DIR = None
CONF_PATHNAME = None
HTML_TEMPLATE_DIR = None
#########################################################################
#
# Adjust sys.path to include our library directory
#
import sys
if LIBRARY_DIR:
@@ -47,5 +43,272 @@ else:
#########################################################################
import query
query.run_cgi()
import os
import string
import cgi
import time
import cvsdbapi
## tuple of alternating row colors
Colors = ("#ccccee", "#ffffff")
## returns a tuple-list (mod-str, string)
def listparse_string(str):
return_list = []
cmd = ""
temp = ""
escaped = 0
state = "eat leading whitespace"
for c in str:
## handle escaped charactors
if not escaped and c == "\\":
escaped = 1
continue
## strip leading white space
if state == "eat leading whitespace":
if c in string.whitespace:
continue
else:
state = "get command or data"
## parse to '"' or ","
if state == "get command or data":
## just add escaped charactors
if escaped:
escaped = 0
temp = temp + c
continue
## the data is in quotes after the command
elif c == "\"":
cmd = temp
temp = ""
state = "get quoted data"
continue
## this tells us there was no quoted data, therefore no
## command; add the command and start over
elif c == ",":
## strip ending whitespace on un-quoted data
temp = string.rstrip(temp)
return_list.append( ("", temp) )
temp = ""
state = "eat leading whitespace"
continue
## record the data
else:
temp = temp + c
continue
## parse until ending '"'
if state == "get quoted data":
## just add escaped charactors
if escaped:
escaped = 0
temp = temp + c
continue
## look for ending '"'
elif c == "\"":
return_list.append( (cmd, temp) )
cmd = ""
temp = ""
state = "eat comma after quotes"
continue
## record the data
else:
temp = temp + c
continue
## parse until ","
if state == "eat comma after quotes":
if c in string.whitespace:
continue
elif c == ",":
state = "eat leading whitespace"
continue
else:
print "format error"
sys.exit(1)
if cmd or temp:
return_list.append( (cmd, temp) )
return return_list
def decode_command(cmd):
if cmd == "r":
return "regex"
elif cmd == "l":
return "like"
else:
return "exact"
def FormToCheckinQuery(form):
query = cvsdbapi.CreateCheckinQuery()
if form.has_key("repository"):
temp = form["repository"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetRepository(str, cmd)
if form.has_key("branch"):
temp = form["branch"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetBranch(str, cmd)
if form.has_key("directory"):
temp = form["directory"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetDirectory(str, cmd)
if form.has_key("file"):
temp = form["file"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetFile(str, cmd)
if form.has_key("who"):
temp = form["who"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetAuthor(str, cmd)
if form.has_key("sortby"):
temp = form["sortby"].value
if temp == "date":
query.SetSortMethod("date")
elif temp == "author":
query.SetSortMethod("author")
else:
query.SetSortMethod("file")
if form.has_key("date"):
temp = form["date"].value
if temp == "hours":
if form.has_key("hours"):
hours = string.atoi(form["hours"].value)
else:
hours = 2
query.SetFromDateHoursAgo(hours)
elif temp == "day":
query.SetFromDateDaysAgo(1)
elif temp == "week":
query.SetFromDateDaysAgo(7)
elif temp == "month":
query.SetFromDateDaysAgo(31)
return query
def PrintCommitRow(commit, color):
cTime = commit.GetTime()
if not cTime:
cTime = "&nbsp";
else:
cTime = time.strftime("%y/%m/%d %H:%M", time.localtime(cTime))
cAuthor = commit.GetAuthor()
if not cAuthor:
cAuthor = "&nbsp";
cFile = os.path.join(commit.GetDirectory(), commit.GetFile())
if not cFile:
cFile = "&nbsp";
cRevision = commit.GetRevision()
if not cRevision:
cRevision = "&nbsp";
cBranch = commit.GetBranch()
if not cBranch:
cBranch = "&nbsp";
cPlusMinus = '%d/%d' % (commit.GetPlusCount(), commit.GetMinusCount())
cDescription = commit.GetDescription()
if not cDescription:
cDescription = "&nbsp";
else:
cDescription = cgi.escape(cDescription)
cDescription = string.replace(cDescription, '\n', '<br>')
print '<tr bgcolor="%s"><td align=left valign=top>%s</td>\
<td align=left valign=top>%s</td>\
<td align=left valign=top>%s</td>\
<td align=left valign=top>%s</td>\
<td align=left valign=top>%s</td>\
<td aligh=left valign=top>%s</td>\
<td align=left valign=top>%s</td></tr>' % (
color, cTime, cAuthor, cFile, cRevision, cBranch,
cPlusMinus, cDescription)
def PrintCommitRows(commit_list):
color_index = 0
for commit in commit_list:
PrintCommitRow(commit, Colors[color_index])
color_index = (color_index + 1) % len(Colors)
g_iColorIndex = 0
def CommitCallback(commit):
global g_iColorIndex
PrintCommitRow(commit, Colors[g_iColorIndex])
g_iColorIndex = (g_iColorIndex + 1) % len(Colors)
def RunQuery(query):
query.SetCommitCB(CommitCallback)
db = cvsdbapi.ConnectDatabaseReadOnly()
db.RunQuery(query)
class HTMLTemplate:
def __init__(self, filename):
self.template = open(filename, 'r').read()
def Print1(self):
index = string.find(self.template, '<!-- INSERT QUERY ROWS -->')
print self.template[:index]
def Print2(self):
index = string.find(self.template, '<!-- INSERT QUERY ROWS -->')
print self.template[index:]
def Main():
print "Content-type: text/html\n\n"
template_path = os.path.join(HTML_TEMPLATE_DIR, "querytemplate.html")
template = HTMLTemplate(template_path)
template.Print1()
form = cgi.FieldStorage()
query = FormToCheckinQuery(form)
RunQuery(query)
template.Print2()
if __name__ == '__main__':
Main()

54
cgi/queryform.cgi Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
LIBRARY_DIR = None
CONF_PATHNAME = None
HTML_TEMPLATE_DIR = None
# Adjust sys.path to include our library directory
import sys
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path[:0] = ['../lib'] # any other places to look?
#########################################################################
import os, string
def HTMLHeader():
print "Content-type: text/html"
print
def Main():
HTMLHeader()
template_path = os.path.join(HTML_TEMPLATE_DIR, "queryformtemplate.html")
print open(template_path, "r").read()
if __name__ == '__main__':
Main()

View File

@@ -22,10 +22,13 @@
#
# address
# main_title
# logo
# forbidden
#
# long_intro
# repository_info
#
# use_enscript
# use_cvsgraph
#
# For Python source colorization:
#
@@ -35,9 +38,6 @@
#
# icons
#
# Also, review the .ezt templates in the templates/ directory to adjust them
# for your particular site.
#
#
# FORMAT INFORMATION
@@ -73,6 +73,7 @@ cvs_roots =
default_root = Development
# uncomment if the RCS binaries are not on the standard path
# NOTE: the trailing slash is required!
#rcs_path = /usr/bin/
#
@@ -85,9 +86,8 @@ default_root = Development
# 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 CVS maintainer.
address = <a href="mailto:cvs-admin@insert.your.domain.here">No CVS admin address has been configured</a>
# This address is shown in the footer. It should contain the CVS maintainer.
address = <a href="mailto:gstein@lyra.org">gstein@lyra.org</a>
# this title is used on the main entry page
main_title = CVS Repository
@@ -136,108 +136,6 @@ forbidden =
# 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 ViewCVS. 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
#
#---------------------------------------------------------------------------
[templates]
#
# The templates are specified relative to the configuration file. Absolute
# paths may be used, if you want to keep these elsewhere.
#
# 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.
#
query = templates/query.ezt
footer = templates/footer.ezt
diff = templates/diff.ezt
graph = templates/graph.ezt
annotate = templates/annotate.ezt
markup = templates/markup.ezt
directory = templates/directory.ezt
# For an alternate form, where the first column displays a revision number
# and brings you to the log view (and the filename displays the HEAD), then
# you may use this template:
# directory = templates/dir_alternate.ezt
log = templates/log.ezt
# For a log view where the revisions are displayed in a table, you may
# want to try this template:
# log = templates/log_table.ezt
#---------------------------------------------------------------------------
[cvsdb]
@@ -247,7 +145,136 @@ log = templates/log.ezt
#passwd =
#readonly_user =
#readonly_passwd =
#row_limit = 1000
#---------------------------------------------------------------------------
[images]
#
# All images are defined with three values: URL, WIDTH, HEIGHT
#
# this logo will appear on the page
logo = /icons/apache_pb.gif, 259, 32
#
# these icons represent a back-pointer, a directory (folder), and a file.
# they are normally available in a standard Apache distribution, along
# with larger versions if these are too small for you.
#
back_icon = /icons/small/back.gif, 16, 16
dir_icon = /icons/small/dir.gif, 16, 16
file_icon = /icons/small/text.gif, 16, 16
#---------------------------------------------------------------------------
[colors]
# background color of log entry in markup
markup_log = #ffffff
# color of change-section headings in a diff
diff_heading = #99cccc
# color of "empty" lines
diff_empty = #cccccc
# removed lines
diff_remove = #ff9999
# changed lines
diff_change = #99ff99
# added lines
diff_add = #ccccff
# empty lines in a change block (one part smaller than the other)
diff_dark_change = #99cc99
# even/odd row colors
even_odd = #ccccee, #ffffff
# navigation header (in diff screen, file view, annotation, etc)
nav_header = #9999ee
# color of text on most pages
text = #000000
# color of standard background
background = #ffffff
# color of alternate background (diffs, file view, annotations, etc)
alt_background = #eeeeee
# table header colors (normal and the sorted-by column)
column_header_normal = #cccccc
column_header_sorted = #88ff88
# Uncomment the following line for colored borders in tables
#table_border = #999999
#---------------------------------------------------------------------------
[text]
#
# WARNING: if you continue the text onto multiple lines, then make SURE that
# you indent the continuations.
#
short_intro =
<p>
Click on a directory to enter that directory. Click on a file to display
its revision history and to get a chance to display diffs between
revisions.
</p>
long_intro =
<p>
This is a WWW interface for CVS Repositories.
You can browse the file hierarchy by picking directories
(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
If you pick a file, you will see the revision history
for that file.
Selecting a revision number will download that revision of
the file. There is a link at each revision to display
diffs between that revision and the previous one, and
a form at the bottom of the page that allows you to
display diffs between arbitrary revisions.
</p>
<p>
This script
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
script by Henner Zeller
&lt;<a href="mailto:zeller@think.de">zeller@think.de</a>&gt;;
it is covered by the
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD
Licence</a>.
If you would like to use this CGI script on your own web server and
CVS tree, see Greg's
<a href="http://viewcvs.sourceforge.net/">ViewCVS distribution
site</a>.
Please send any suggestions, comments, etc. to
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
</p>
doc_info =
<h3>CVS Documentation</h3>
<blockquote>
<p>
<a href="http://cvsbook.red-bean.com/">Karl Fogel's CVS book</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>
repository_info =
<!-- insert repository access instructions here -->
#---------------------------------------------------------------------------
[options]
@@ -261,9 +288,9 @@ log = templates/log.ezt
# log Sort by log message
sort_by = file
# 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: Hide or show files in Attic
# 1 Hide files in Attic
# 0 Show files in Attic
hide_attic = 1
# log_sort: Sort order for CVS logs
@@ -277,7 +304,7 @@ log_sort = date
# u Unified diff
# c Context diff
# s Side by side
# l Long human readable (more context)
# H Long human readable
diff_format = h
# hide_cvsroot: Don't show the CVSROOT directory
@@ -285,6 +312,14 @@ diff_format = h
# 0 Show CVSROOT directory
hide_cvsroot = 1
# hide_non_readable: Don't show entries which cannot be read
# 1 Hide non-readable entries
# 0 Show non-readble entries
hide_non_readable = 1
# Show author of last change
show_author = 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
@@ -307,6 +342,9 @@ hr_ignore_white = 1
hr_ignore_keyword_subst = 1
# allow annotation of files.
# NOTE: this requires rw-access to the CVSROOT/history file, and rw-access to
# the subdirectory to place the lock... so you maybe don't want it
# WARNING: this is not yet implemented!!
allow_annotate = 1
# allow pretty-printed version of files
@@ -317,6 +355,19 @@ allow_markup = 1
# [make sure to have gzip in the path]
allow_compress = 1
# Make use of javascript functions to skip the need for submitting a form.
# For example, this way you can select one of your CVS roots without
# pressing 'Go' (... if you have more than one CVSROOT defined)
use_java_script = 1
# open Download-Links in another window
open_extern_window = 1
# The size of this extern window; this size option needs use_java_script
# to be defined
extern_window_width = 600
extern_window_height = 440
# If you have files which automatically refers to other files
# (such as HTML) then this allows you to browse the checked
# out files as if outside CVS.
@@ -341,6 +392,7 @@ show_log_in_markup = 1
# == Configuration defaults ==
# Defaults for configuration variables that shouldn't need
# to be configured..
allow_version_select = 1
#
# If you want to use Marc-Andrew Lemburg's py2html (and Just van Rossum's
@@ -356,14 +408,20 @@ py2html_path = .
# shown in the directory view
short_log_len = 80
table_padding = 2
diff_font_face = Helvetica,Arial
diff_font_size = -1
# the width of the textinput in the request-diff-form
input_text_size = 12
# should we use 'enscript' for syntax coloring?
use_enscript = 0
#
# if the enscript program is not on the path, set this value
# Note: there should be a trailing slash
#
enscript_path =
# enscript_path = /usr/bin/
@@ -382,50 +440,6 @@ disable_enscript_lang =
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 = <VIEWCVS_INSTALL_DIRECTORY>/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
#---------------------------------------------------------------------------
[vhosts]
### DOC

View File

@@ -0,0 +1,81 @@
<html>
<head><title>CVSdb Query Form</title></head>
<body bgcolor="#ffffff">
<h1>CVSdb Query Form for GNOME CVS Repository</h1>
<form method=get action='query.cgi'>
<table border cellpading=8 cellspacing=0>
<tr>
<td align=right>Repository:</td>
<td><input type=text name=repository size=25></td>
</tr>
<tr>
<td align=right>Branch:</td>
<td><input type=text name=branch size=25></td>
</tr>
<tr>
<td align=right>Directory:</td>
<td><input type=text name=directory value='' size=45></td>
</tr>
<tr>
<td align=right>File:</td>
<td><input type=text name=file value='' size=45></td>
</tr>
<tr>
<td align=right>Who:</td>
<td><input type=text name=who value='' size=45></td>
</tr>
<tr>
<td align=right>Sort By:</td>
<td>
<select name=sortby>
<option value="date">Date</option>
<option value="author">Author</option>
<option value="file">File</option>
</select>
</td>
</tr>
<tr>
<td align=right valign=top><br>Date:</td>
<td colspan=2>
<table BORDER=0 CELLSPACING=0 CELLPADDING=0>
<tr>
<td><input type=radio name=date checked value=hours></td>
<td>
In the last <input type=text name=hours value=2 size=4> hours
</td>
</tr>
<tr>
<td><input type=radio name=date value=day></td>
<td>In the last day</td>
</tr>
<tr>
<td><input type=radio name=date value=week></td>
<td>In the last week</td>
</tr>
<tr>
<td><input type=radio name=date value=month></td>
<td>In the last month</td>
</tr>
<tr>
<td><input type=radio name=date value=all></td>
<td>Since the beginning of time</td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan=2><input type=submit value='Run Query'></td>
</tr>
</table>
</form>
</html>

View File

@@ -0,0 +1,30 @@
<html>
<head><title>CVSdb Query</title></head>
<body bgcolor="#ffffff">
<h1>CVSdb Query</h1>
<table width="100%" border=0 cellspacing=0 cellpadding=2>
<tr bgcolor="#88ff88">
<th align=left valign=top>Date</th>
<th align=left valign=top>Author</th>
<th align=left valign=top>File</th>
<th align=left valign=top>Revision</th>
<th align=left valign=top>Branch</th>
<th align=left valign=top>+/-</th>
<th align=left valign=top>Description</th>
</tr>
<!-- INSERT QUERY ROWS -->
<tr bgcolor="#88ff88">
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
<th align=left valign=top>&nbsp</th>
</tr>
</table>
</body>
</html>

View File

@@ -1,190 +0,0 @@
# -*-python-*-
#
# Copyright (C) 1999-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
# accept.py: parse/handle the various Accept headers from the client
#
# -----------------------------------------------------------------------
#
import re
import string
def language(hdr):
"Parse an Accept-Language header."
# parse the header, storing results in a _LanguageSelector object
return _parse(hdr, _LanguageSelector())
# -----------------------------------------------------------------------
_re_token = re.compile(r'\s*([^\s;,"]+|"[^"]*")+\s*')
_re_param = re.compile(r';\s*([^;,"]+|"[^"]*")+\s*')
_re_split_param = re.compile(r'([^\s=])\s*=\s*(.*)')
def _parse(hdr, result):
# quick exit for empty or not-supplied header
if not hdr:
return result
pos = 0
while pos < len(hdr):
name = _re_token.match(hdr, pos)
if not name:
raise AcceptParseError()
a = result.item_class(string.lower(name.group(1)))
pos = name.end()
while 1:
# are we looking at a parameter?
match = _re_param.match(hdr, pos)
if not match:
break
param = match.group(1)
pos = match.end()
# split up the pieces of the parameter
match = _re_split_param.match(param)
if not match:
# the "=" was probably missing
continue
pname = string.lower(match.group(1))
if pname == 'q' or pname == 'qs':
try:
a.quality = float(match.group(2))
except ValueError:
# bad float literal
pass
elif pname == 'level':
try:
a.level = float(match.group(2))
except ValueError:
# bad float literal
pass
elif pname == 'charset':
a.charset = string.lower(match.group(2))
result.append(a)
if hdr[pos:pos+1] == ',':
pos = pos + 1
return result
class _AcceptItem:
def __init__(self, name):
self.name = name
self.quality = 1.0
self.level = 0.0
self.charset = ''
def __str__(self):
s = self.name
if self.quality != 1.0:
s = '%s;q=%.3f' % (s, self.quality)
if self.level != 0.0:
s = '%s;level=%.3f' % (s, self.level)
if self.charset:
s = '%s;charset=%s' % (s, self.charset)
return s
class _LanguageRange(_AcceptItem):
def matches(self, tag):
"Match the tag against self. Returns the qvalue, or None if non-matching."
if tag == self.name:
return self.quality
# are we a prefix of the available language-tag
name = self.name + '-'
if tag[:len(name)] == name:
return self.quality
return None
class _LanguageSelector:
item_class = _LanguageRange
def __init__(self):
self.requested = [ ]
def select_from(self, avail):
"""Select one of the available choices based on the request.
Note: if there isn't a match, then the first available choice is
considered the default.
avail is a list of language-tag strings of available languages
"""
# tuples of (qvalue, language-tag)
matches = [ ]
# try matching all pairs of desired vs available, recording the
# resulting qvalues. we also need to record the longest language-range
# that matches since the most specific range "wins"
for tag in avail:
longest = 0
final = 0.0
# check this tag against the requests from the user
for want in self.requested:
qvalue = want.matches(tag)
#print 'have %s. want %s. qvalue=%s' % (tag, want.name, qvalue)
if qvalue is not None and len(want.name) > longest:
# we have a match and it is longer than any we may have had
final = qvalue
longest = len(want.name)
# a non-zero qvalue is a potential match
if final:
matches.append((final, tag))
# if we have any matches, then look at the highest qvalue
if matches:
matches.sort()
qvalue, tag = matches[-1]
if len(matches) >= 2 and matches[-2][0] == qvalue:
#print "non-deterministic choice", avail
pass
# if the qvalue is non-zero, then we have a valid match
if qvalue:
return tag
# the qvalue is zero (non-match). drop thru to return the default
# return the default language tag
return avail[0]
def append(self, item):
self.requested.append(item)
class AcceptParseError(Exception):
pass
def _test():
s = language('en')
assert s.select_from(['en']) == 'en'
assert s.select_from(['en', 'de']) == 'en'
assert s.select_from(['de', 'en']) == 'en'
s = language('fr, de;q=0.9, en-gb;q=0.7, en;q=0.6, en-gb-foo;q=0.8')
assert s.select_from(['en']) == 'en'
assert s.select_from(['en-gb-foo']) == 'en-gb-foo'
assert s.select_from(['de', 'fr']) == 'fr'
assert s.select_from(['de', 'en-gb']) == 'de'
assert s.select_from(['en-gb', 'en-gb-foo']) == 'en-gb-foo'
assert s.select_from(['en-bar']) == 'en-bar'
assert s.select_from(['en-gb-bar', 'en-gb-foo']) == 'en-gb-foo'
# non-deterministic. en-gb;q=0.7 matches both avail tags.
#assert s.select_from(['en-gb-bar', 'en-gb']) == 'en-gb'

View File

@@ -1,199 +0,0 @@
#! /usr/bin/env python
# This file was automatically generated! DO NOT EDIT!
# Howto regenerate: see ../tools/bin2inline.py
# $Id$
# You have been warned. But if you want to edit, go ahead using your
# favorite editor :-)
## vim:ts=4:et:nowrap
# [Emacs: -*- python -*-]
_ap_icons = {
"/icons/apache_pb.gif" :
'GIF89a\003\001 \000\367\000\000\377\377\377'
'\316\316\316\245\245\245\204\204\204ssskkkZ'
'ZZ!\030\030\377B\030\3771\000\275R\020\336\255'
'\204\357\234B\377\204\000\377\316\030\377\316\000\316\316\306'
'\275\275\3061\000\377c\000\377\234\000\377\357\000\377\347'
'J\357\336{\336\326\245\316\377\000\234\357J\214\377\000'
'c\347\204\234\357Rc\377\000\030\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
'\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004'
'\001\000\000\001\000,\000\000\000\000\003\001 \000G\010'
'\377\000\003\010\034H\260\240\301\203\010\023*\\\310\260'
'\241\303\207\020#J\234H\261\242E\206\021\016\034\030'
'0\260\300\200\001\002\002l0\260!\303\006\017(Q'
'&X\331\300A\204\201\007\376\3753\020\363_\004\231'
'\007h\376;\020 \246\001\0032\011\370\024\2603\302'
'\200\231=e\006%03\002P\002\001p\352\374W'
'\020\350L\253\004\254\336\264y\261\253\327\257`\303\026'
'|Iv\203\331\012\033*P\2400a\302?\011J'
'\343\276\225@w\002\333\265\0252P\3100!\203I'
'\277\005P\376;\231\022\001\002\001._\212]\314\270'
'\261\343\307b#Dh0\000A\203\225\011R\012N'
'\371\317\203\001\017f\375\206\366+\332d^\275\245\323'
"f\030\360\240u\353\006\017\032\314\374'\200\243\300\013"
"u'Hh\333\226\356\356\335\272%\\0xa\355"
'\332\341\022/\220^\316\274y\006\015\0065{X\330'
'A3\206\202\0300\257d\270@{\202\203\014\032\210'
'\377o\000\371\253\200\363\347\313/\214\220\236 \373\220'
'\352\343\313\237\357\360\345y\271o\347\266]\233!o'
'\337\275\031L\225\222\001\010(PSQ\002\035\305S'
'T\264\011$\300\201P\305\264\240@L)\025\222S'
'rU\025\327F\016\312\244\030} \2068_\004\010'
'\030\260Rg\240\225d\332r\024T`\327ZnY'
'\205\237\\p\375\366\033[\025\2505\300^\033\000\230'
'\300\000-\271\324P\004\272\265\025\200\005\277M\360\341'
'A\305\031G\301BN"\207\335r\303Y\260\334u'
'\011I\267\220\006\326a\347\035w_\032\024\336x"'
'\226i\346\231h\246\251\346\232l\266\351\246A\017.'
'9\320y\005(\346\200\000\015\340\331PM\002X\305'
'\221Q\006\014`\337O/\031\000\225S\006\220E\200'
'\240\024\032 \200S\360\011@\000|\002E@@V'
'2i\370\317\000\216\006\320g\242\236\032\372\346\250e'
'\032U\023P \355\367_\217*J\247RM\037n'
'\377\005U\205\222)\365\023N\001\034E\033zIM'
'\010\324\001\350=*S\242Vi\352\351\260\001\310('
"'\251\314\252\367\350O\004\024\020\000_\301\3218\227"
'\001\022\030p\227\213\250M0\022a\011\240\310\222x'
'\206!\260\254|[Q\332\354\272"\032\225\222Y\241'
'\251\325\237\213l\3015c~\276\365\266\037\275x\241'
'6\322_&\201\326\300\271\023\345;\201\005\0155\351'
'\344\302\014\033\207%B\0324\367pB\030\270j\261'
'\253\034\030\224\035f\035\200\251\035x\343\205,\362\310'
'\013<\206\247\211\232\015\206\222\212\244\261\314\234qj'
'\3355\301On\331;#P\015\030\020\333k\2559'
" \244\306ENP\020\222ENL\220\302\015'\275"
'\226\321\312\221&\345m\313e|\220\226\025m\354\335'
'\325Xc\006\362\310"\227<\252d\001HfT\003'
'\033!p\000\002)\036\340\301\000\033\014\000\332\333\360'
'\232\205\322\001q\033\345\300\000w\252{d\276F\037'
'\377\204Ap\022 <\020\322O7\304\\\337~\037'
'>\020\007\0277\216R\307^~\274Pw\222\0274'
'&\327\230\213\007\001\273\234w\356\371\347\240\207.\372'
'\350\244\227\3169\301a\203M\321\201\310\212X,A'
'\257\233.;D\246\352-)\243hk\226\200e\003'
'#T\323\261\377L*\227\260\233\022e\323\257\311*'
'\005lRV\361t\340\001/U\030\227\246\312oe'
'\000\363\263go\320\000\032\011\012@\001\005@\205Z'
'\005\004\2304RJ,\355~\030\353\300\032?\253L'
'\222\342$!\374\025j\224)\237\317\356d?\2602'
'q\024\273@\257\363\311V|\242\275\002\036DF\267'
'\252\213\223\374\223\232\317x@)\0360\314\300jB'
'A\000\302OW\327\263\236\244\234\267\2239\015\353S'
'I\311\020\354\360\023\022\343]\317\200(\014[\001\200'
'b\000\217\330L)\276\221\000\214\214\343\257\275\020\000'
'\\\036X\211\002,\203\030\325%\204=\014yTW'
'\377X\230B\024\212\215=/\351Vo^\010\303|'
'\311\320.}\351V\3344\343\300\0346\240\\z\203'
'\314\005rR\304.V*l\271\353\221_\3622\303'
'\266\334k.7z"\031\333\242\032\322\334\020\\\231'
')\014\352(R\227\302%Dix\274\000\342\012r'
'\001\013\370Qp\015\201\000\007\030\347\270\224p\000q'
'\030\350\200";\3405\205Pn%\215$\010\003&'
'I\311JZ\262\222\221\264\310d\334\366.\227\235\246'
'E0b"~jd\243\375\340H-1\243@\001'
'\344V\030\004\260\315\\\022\311\315o\000\231\020\244Q'
'\340\002\270\314\245.\011\227\020\347\370e\217\003\221\316'
' \207IL\016h\240:)\331\034A\254\346\001\310'
'92L\226\013\331\002\246I\315jZ\2231w\273'
'L\034W\026\232\264\250\34649B\245]\012\000\027'
'\031\235Q&\300\341\015o\332&\236\330\300\346g\010'
'!\022p\006B\264\335\240\316\226\273\314g\2244F'
'\363\245\301])K\232Y\010\006\016\211\310\254\031\364'
"j[\023\317%'\231I\365\214\315D+\331\014J"
'H\302\315n.\3474\375\361\313^\310\330\242\276\304'
'\314\0003\273U\\~b\000\361\300S \177K\222'
':y\223$\304\331\022\217\306\321#\037}IS\251'
'\025\204j\011\341\200"\017\2511\357\024\223\003\326|'
'\244\326\304$\315\240.@\231i\222Lm\\\351\235'
'\034\272\252G\025e\245\001\376B\030\222\250\250\000\355'
'\204\215k|\366!\334\000G\217\030\010k\000\302J'
'\326\013,\261p\274\214H\323\374\002V\262\2725\254'
'\034X\016t\010"\314\237\016\363\230\232A*J\241'
'\231\020\241~\207\250\343\241\244Q\257\351E\332\351r'
'\216\3569\354@0\240K`\372M\227\020\031\3500'
"'&Y\273ZV\257\003\201@5\035+\020\315V"
'\363 \203\015-5\013K\332\322\232\366\264\250=\010'
'@\002\004\004\000;',
"/icons/small/back.gif" :
'GIF89a\020\000\020\000\242\377\000!!!'
'111ZZZ\204\204\204\214\214\214\300\300\300\000'
'\000\000\000\000\000!\371\004\001\000\000\005\000,\000\000'
'\000\000\020\000\020\000\000\003FX\272\334\015\300I`'
'L\224\213V\213\213\320\025 t\304f\020]Qn'
'(i\266\303\240\310\312Z\215D,\010\255\022\230\236'
'\20000Z\010L\203\241\320a"*\035 \321\260'
'\267\260\235\204E\307q3\310J\010`p#\001\000'
';',
"/icons/small/dir.gif" :
'GIF89a\020\000\020\000\242\377\000\377\336\255'
'\377\334\256\300\300\300\270\240}WK;+%\035\000'
'\000\000\000\000\000!\371\004\001\000\000\002\000,\000\000'
'\000\000\020\000\020\000\000\003I(b\246\376\316\2241'
'\230\262\320\2002\253\031\001v\001$GQA\025\221'
'\254y6#\333v\360"\337_\255\335dE\350\274'
'\236\341\267\012\372t\204\001\357\370`$e\314\314P'
'\0118\026 \227\251\365\212]\014\277\234n\204\021\026'
'$\000\000;',
"/icons/small/text.gif" :
'GIF89a\020\000\020\000\242\377\000\377\377\377'
'999kkk\214\214\214\300\300\300\316\316\316\347'
'\347\347\000\000\000!\371\004\001\000\000\004\000,\000\000'
'\000\000\020\000\020\000\000\003EH\272\334\276\343\025@'
"k1\261\315J'\326\\W\024D\246LA\232\002"
'j\300xFlP\212\360\262me\330\013,\307\024'
'\336\011\327Z\011\011\276\337\354\210*\032oJ\031\200'
'I,No!\316\261$\350z\275\256E\002\000;',
}
def serve_icon(pathname, fp):
if _ap_icons.has_key(pathname):
fp.write(_ap_icons[pathname])
else:
raise OSError # icon not found

160
lib/commit.py Normal file
View File

@@ -0,0 +1,160 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
import os
## 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
class Commit:
## static constants for type of commit
CHANGE = 0
ADD = 1
REMOVE = 2
def __init__(self):
self.__directory = ''
self.__file = ''
self.__repository = ''
self.__revision = ''
self.__author = ''
self.__branch = ''
self.__pluscount = ''
self.__minuscount = ''
self.__description = ''
self.__gmt_time = 0.0
self.__type = Commit.CHANGE
def SetRepository(self, repository):
## clean up repository path; make sure it doesn't end with a
## path seperator
while repository[-1] == os.sep:
repository = repository[:-1]
self.__repository = repository
def GetRepository(self):
return self.__repository
def SetDirectory(self, dir):
## clean up directory path; make sure it doesn't begin
## or end with a path seperator
while dir[0] == os.sep:
dir = dir[1:]
while dir[-1] == os.sep:
dir = dir[:-1]
self.__directory = dir
def GetDirectory(self):
return self.__directory
def SetFile(self, file):
## clean up filename; make sure it doesn't begin
## or end with a path seperator
while file[0] == os.sep:
file = file[1:]
while file[-1] == os.sep:
file = file[:-1]
self.__file = file
def GetFile(self):
return self.__file
def SetRevision(self, revision):
self.__revision = revision
def GetRevision(self):
return self.__revision
def SetTime(self, gmt_time):
self.__gmt_time = float(gmt_time)
def GetTime(self):
return self.__gmt_time
def SetAuthor(self, author):
self.__author = author
def GetAuthor(self):
return self.__author
def SetBranch(self, branch):
if not branch:
self.__branch = ''
else:
self.__branch = branch
def GetBranch(self):
return self.__branch
def SetPlusCount(self, pluscount):
self.__pluscount = pluscount
def GetPlusCount(self):
return self.__pluscount
def SetMinusCount(self, minuscount):
self.__minuscount = minuscount
def GetMinusCount(self):
return self.__minuscount
def SetDescription(self, description):
self.__description = description
def GetDescription(self):
return self.__description
def SetTypeChange(self):
self.__type = Commit.CHANGE
def SetTypeAdd(self):
self.__type = Commit.ADD
def SetTypeRemove(self):
self.__type = Commit.REMOVE
def GetType(self):
return self.__type
def GetTypeString(self):
if self.__type == Commit.CHANGE:
return 'Change'
elif self.__type == Commit.ADD:
return 'Add'
elif self.__type == Commit.REMOVE:
return 'Remove'
## entrypoints
def CreateCommit():
return Commit()
def PrintCommit(commit):
print os.path.join(commit.GetDirectory(), commit.GetFile()),\
commit.GetRevision(),\
commit.GetAuthor()
if commit.GetBranch():
print commit.GetBranch()
print commit.GetDescription()
print

View File

@@ -30,7 +30,7 @@ import fnmatch
#
# There are three forms of configuration:
#
# 1) edit the viewcvs.conf created by the viewcvs-install(er)
# 1) copy viewcvs.conf.dist to viewcvs.conf and edit
# 2) as (1), but delete all unchanged entries from viewcvs.conf
# 3) do not use viewcvs.conf and just edit the defaults in this file
#
@@ -41,9 +41,9 @@ import fnmatch
#########################################################################
class Config:
_sections = ('general', 'options', 'cvsdb', 'templates')
_force_multi_value = ('cvs_roots', 'forbidden', 'disable_enscript_lang',
'languages', 'kv_files')
_sections = ('general', 'images', 'options', 'colors', 'text', 'cvsdb')
_force_multi_value = ('cvs_roots', 'forbidden', 'even_odd',
'disable_enscript_lang')
def __init__(self):
for section in self._sections:
@@ -52,8 +52,6 @@ class Config:
def load_config(self, fname, vhost=None):
this_dir = os.path.dirname(sys.argv[0])
pathname = os.path.join(this_dir, fname)
self.base = os.path.dirname(pathname)
parser = ConfigParser.ConfigParser()
parser.read(pathname)
@@ -61,44 +59,15 @@ class Config:
if parser.has_section(section):
self._process_section(parser, section, section)
if vhost and parser.has_section('vhosts'):
if vhost:
self._process_vhost(parser, vhost)
def load_kv_files(self, language):
kv = _sub_config()
for fname in self.general.kv_files:
if fname[0] == '[':
idx = string.index(fname, ']')
parts = string.split(fname[1:idx], '.')
fname = string.strip(fname[idx+1:])
else:
parts = [ ]
fname = string.replace(fname, '%lang%', language)
parser = ConfigParser.ConfigParser()
parser.read(os.path.join(self.base, fname))
for section in parser.sections():
for option in parser.options(section):
full_name = parts + [section]
ob = kv
for name in full_name:
try:
ob = getattr(ob, name)
except AttributeError:
c = _sub_config()
setattr(ob, name, c)
ob = c
setattr(ob, option, parser.get(section, option))
return kv
def _process_section(self, parser, section, subcfg_name):
sc = getattr(self, subcfg_name)
for opt in parser.options(section):
value = parser.get(section, opt)
if opt in self._force_multi_value:
if opt in self._force_multi_value or subcfg_name == 'images':
value = map(string.strip, filter(None, string.split(value, ',')))
else:
try:
@@ -149,20 +118,9 @@ class Config:
self.general.default_root = "Development"
self.general.rcs_path = ''
self.general.mime_types_file = ''
self.general.address = '<a href="mailto:user@insert.your.domain.here">No CVS admin address has been configured</a>'
self.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
self.general.main_title = 'CVS Repository'
self.general.forbidden = ()
self.general.kv_files = [ ]
self.general.languages = ['en-us']
self.templates.directory = 'templates/directory.ezt'
self.templates.log = 'templates/log.ezt'
self.templates.query = 'templates/query.ezt'
self.templates.footer = 'templates/footer.ezt'
self.templates.diff = 'templates/diff.ezt'
self.templates.graph = 'templates/graph.ezt'
self.templates.annotate = 'templates/annotate.ezt'
self.templates.markup = 'templates/markup.ezt'
self.cvsdb.enabled = 0
self.cvsdb.host = ''
@@ -171,36 +129,128 @@ class Config:
self.cvsdb.passwd = ''
self.cvsdb.readonly_user = ''
self.cvsdb.readonly_passwd = ''
self.cvsdb.row_limit = 1000
self.images.logo = "/icons/apache_pb.gif", 259, 32
self.images.back_icon = "/icons/small/back.gif", 16, 16
self.images.dir_icon = "/icons/small/dir.gif", 16, 16
self.images.file_icon = "/icons/small/text.gif", 16, 16
self.colors.markup_log = "#ffffff"
self.colors.diff_heading = "#99cccc"
self.colors.diff_empty = "#cccccc"
self.colors.diff_remove = "#ff9999"
self.colors.diff_change = "#99ff99"
self.colors.diff_add = "#ccccff"
self.colors.diff_dark_change = "#99cc99"
self.colors.even_odd = ("#ccccee", "#ffffff")
self.colors.nav_header = "#9999ee"
self.colors.text = "#000000"
self.colors.background = "#ffffff"
self.colors.alt_background = "#eeeeee"
self.colors.column_header_normal = "#cccccc"
self.colors.column_header_sorted = "#88ff88"
self.colors.table_border = None # no border
self.options.sort_by = 'file'
self.options.hide_attic = 1
self.options.log_sort = 'date'
self.options.diff_format = 'h'
self.options.hide_cvsroot = 1
self.options.hide_non_readable = 1
self.options.show_author = 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.allow_annotate = 1
self.options.allow_annotate = 0 ### doesn't work yet!
self.options.allow_markup = 1
self.options.allow_compress = 1
self.options.use_java_script = 1
self.options.open_extern_window = 1
self.options.extern_window_width = 600
self.options.extern_window_height = 440
self.options.checkout_magic = 1
self.options.show_subdir_lastmod = 0
self.options.show_logs = 1
self.options.show_log_in_markup = 1
self.options.allow_version_select = 1
self.options.py2html_path = '.'
self.options.short_log_len = 80
self.options.table_padding = 2
self.options.diff_font_face = 'Helvetica,Arial'
self.options.diff_font_size = -1
self.options.input_text_size = 12
self.options.use_enscript = 0
self.options.enscript_path = ''
self.options.disable_enscript_lang = ()
self.options.allow_tar = 0
self.options.use_cvsgraph = 0
self.options.cvsgraph_path = ''
self.options.cvsgraph_conf = "<VIEWCVS_INSTALL_DIRECTORY>/cvsgraph.conf"
self.options.use_re_search = 0
self.text.long_intro = """\
<p>
This is a WWW interface for CVS Repositories.
You can browse the file hierarchy by picking directories
(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
If you pick a file, you will see the revision history
for that file.
Selecting a revision number will download that revision of
the file. There is a link at each revision to display
diffs between that revision and the previous one, and
a form at the bottom of the page that allows you to
display diffs between arbitrary revisions.
</p>
<p>
This script
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
script by Henner Zeller
&lt;<a href="mailto:zeller@think.de">zeller@think.de</a>&gt;;
it is covered by the
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD-License</a>.
If you would like to use this CGI script on your own web server and
CVS tree, see Greg's
<a href="http://viewcvs.sourceforge.net/">ViewCVS distribution
site</a>.
Please send any suggestions, comments, etc. to
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
</p>
"""
# ' stupid emacs...
self.text.doc_info = """
<h3>CVS Documentation</h3>
<blockquote>
<p>
<a href="http://cvsbook.red-bean.com/">Karl Fogel's CVS book</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>
"""
# Fill in stuff on (say) anonymous pserver access here. For example, what
# access mechanism, login, path, etc should be used.
self.text.repository_info = """
<!-- insert repository access instructions here -->
"""
self.text.short_intro = """\
<p>
Click on a directory to enter that directory. Click on a file to display
its revision history and to get a chance to display diffs between revisions.
</p>
"""
def is_forbidden(self, module):
if not module:
@@ -216,7 +266,13 @@ class Config:
return default
class _sub_config:
pass
def get_image(self, which):
text = '[%s]' % string.upper(which)
path, width, height = getattr(self, which)
if path:
return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
(path, text, width, height)
return text
if not hasattr(sys, 'hexversion'):
# Python 1.5 or 1.5.1. fix the syntax for ConfigParser options.

View File

@@ -1,753 +0,0 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000 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 ViewCVS
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
CONF_PATHNAME = None
#########################################################################
import os
import sys
import string
import time
import config
import dbi
import rlog
## load configuration file, the data is used globally here
if CONF_PATHNAME:
_cfg_pathname = CONF_PATHNAME
else:
# developer assistance: running from a CVS working copy
_cfg_pathname = os.path.join(os.path.dirname(__file__), os.pardir, 'cgi',
'viewcvs.conf')
cfg = config.Config()
cfg.set_defaults()
cfg.load_config(_cfg_pathname)
## 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
## complient database interface
class CheckinDatabase:
def __init__(self, host, user, passwd, database):
self._host = host
self._user = user
self._passwd = passwd
self._database = database
## database lookup caches
self._get_cache = {}
self._get_id_cache = {}
self._desc_id_cache = {}
def Connect(self):
self.db = dbi.connect(
self._host, self._user, self._passwd, self._database)
def sql_get_id(self, table, column, value, auto_set):
sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
sql_args = (value, )
cursor = self.db.cursor()
cursor.execute(sql, sql_args)
try:
(id, ) = cursor.fetchone()
except TypeError:
if not auto_set:
return None
else:
return str(int(id))
## insert the new identifier
sql = "INSERT INTO %s(%s) VALUES(%%s)" % (table, column)
sql_args = (value, )
cursor.execute(sql, sql_args)
return self.sql_get_id(table, column, value, 0)
def get_id(self, table, column, value, auto_set):
## attempt to retrieve from cache
try:
return self._get_id_cache[table][column][value]
except KeyError:
pass
id = self.sql_get_id(table, column, value, auto_set)
if id == None:
return None
## add to cache
try:
temp = self._get_id_cache[table]
except KeyError:
temp = self._get_id_cache[table] = {}
try:
temp2 = temp[column]
except KeyError:
temp2 = temp[column] = {}
temp2[value] = id
return id
def sql_get(self, table, column, id):
sql = "SELECT %s FROM %s WHERE id=%%s" % (column, table)
sql_args = (id, )
cursor = self.db.cursor()
cursor.execute(sql, sql_args)
try:
(value, ) = cursor.fetchone()
except TypeError:
return None
return value
def get(self, table, column, id):
## attempt to retrieve from cache
try:
return self._get_cache[table][column][id]
except KeyError:
pass
value = self.sql_get(table, column, id)
if value == None:
return None
## add to cache
try:
temp = self._get_cache[table]
except KeyError:
temp = self._get_cache[table] = {}
try:
temp2 = temp[column]
except KeyError:
temp2 = temp[column] = {}
temp2[id] = value
return value
def get_list(self, table, field_index):
sql = "SELECT * FROM %s" % (table)
cursor = self.db.cursor()
cursor.execute(sql)
list = []
while 1:
row = cursor.fetchone()
if row == None:
break
list.append(row[field_index])
return list
def GetBranchID(self, branch, auto_set = 1):
return self.get_id("branches", "branch", branch, auto_set)
def GetBranch(self, id):
return self.get("branches", "branch", id)
def GetDirectoryID(self, dir, auto_set = 1):
return self.get_id("dirs", "dir", dir, auto_set)
def GetDirectory(self, id):
return self.get("dirs", "dir", id)
def GetFileID(self, file, auto_set = 1):
return self.get_id("files", "file", file, auto_set)
def GetFile(self, id):
return self.get("files", "file", id)
def GetAuthorID(self, author, auto_set = 1):
return self.get_id("people", "who", author, auto_set)
def GetAuthor(self, id):
return self.get("people", "who", id)
def GetRepositoryID(self, repository, auto_set = 1):
return self.get_id("repositories", "repository", repository, auto_set)
def GetRepository(self, id):
return self.get("repositories", "repository", id)
def SQLGetDescriptionID(self, description, auto_set = 1):
## lame string hash, blame Netscape -JMP
hash = len(description)
sql = "SELECT id FROM descs WHERE hash=%s AND description=%s"
sql_args = (hash, description)
cursor = self.db.cursor()
cursor.execute(sql, sql_args)
try:
(id, ) = cursor.fetchone()
except TypeError:
if not auto_set:
return None
else:
return str(int(id))
sql = "INSERT INTO descs (hash,description) values (%s,%s)"
sql_args = (hash, description)
cursor.execute(sql, sql_args)
return self.GetDescriptionID(description, 0)
def GetDescriptionID(self, description, auto_set = 1):
## attempt to retrieve from cache
hash = len(description)
try:
return self._desc_id_cache[hash][description]
except KeyError:
pass
id = self.SQLGetDescriptionID(description, auto_set)
if id == None:
return None
## add to cache
try:
temp = self._desc_id_cache[hash]
except KeyError:
temp = self._desc_id_cache[hash] = {}
temp[description] = id
return id
def GetDescription(self, id):
return self.get("descs", "description", id)
def GetRepositoryList(self):
return self.get_list("repositories", 1)
def GetBranchList(self):
return self.get_list("branches", 1)
def GetAuthorList(self):
return self.get_list("people", 1)
def AddCommitList(self, commit_list):
for commit in commit_list:
self.AddCommit(commit)
def AddCommit(self, commit):
## MORE TIME HELL: the MySQLdb module doesn't construct times
## correctly when created with TimestampFromTicks -- it doesn't
## account for daylight savings time, so we use Python's time
## module to do the conversion
temp = time.localtime(commit.GetTime())
ci_when = dbi.Timestamp(
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
ci_type = commit.GetTypeString()
who_id = self.GetAuthorID(commit.GetAuthor())
repository_id = self.GetRepositoryID(commit.GetRepository())
directory_id = self.GetDirectoryID(commit.GetDirectory())
file_id = self.GetFileID(commit.GetFile())
revision = commit.GetRevision()
sticky_tag = "NULL"
branch_id = self.GetBranchID(commit.GetBranch())
plus_count = commit.GetPlusCount()
minus_count = commit.GetMinusCount()
description_id = self.GetDescriptionID(commit.GetDescription())
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)"
sql_args = (ci_type, ci_when, who_id, repository_id,
directory_id, file_id, revision, sticky_tag, branch_id,
plus_count, minus_count, description_id)
cursor = self.db.cursor()
cursor.execute(sql, sql_args)
def SQLQueryListString(self, sqlString, query_entry_list):
sqlList = []
for query_entry in query_entry_list:
## figure out the correct match type
if query_entry.match == "exact":
match = "="
elif query_entry.match == "like":
match = " LIKE "
elif query_entry.match == "regex":
match = " REGEXP "
sqlList.append(sqlString % (match, query_entry.data))
return "(%s)" % (string.join(sqlList, " OR "))
def CreateSQLQueryString(self, query):
tableList = ["checkins"]
condList = []
## XXX: this is to exclude .ver files -- RN specific hack --JMP
tableList.append("files")
temp = "(checkins.fileid=files.id AND files.file NOT LIKE \"%.ver\")"
condList.append(temp)
## XXX
if len(query.repository_list):
tableList.append("repositories")
sql = "(checkins.repositoryid=repositories.id AND "\
"repositories.repository%s\"%s\")"
temp = self.SQLQueryListString(sql, query.repository_list)
condList.append(temp)
if len(query.branch_list):
tableList.append("branches")
sql = "(checkins.branchid=branches.id AND "\
"branches.branch%s\"%s\")"
temp = self.SQLQueryListString(sql, query.branch_list)
condList.append(temp)
if len(query.directory_list):
tableList.append("dirs")
sql = "(checkins.dirid=dirs.id AND dirs.dir%s\"%s\")"
temp = self.SQLQueryListString(sql, query.directory_list)
condList.append(temp)
if len(query.file_list):
tableList.append("files")
sql = "(checkins.fileid=files.id AND files.file%s\"%s\")"
temp = self.SQLQueryListString(sql, query.file_list)
condList.append(temp)
if len(query.author_list):
tableList.append("people")
sql = "(checkins.whoid=people.id AND people.who%s\"%s\")"
temp = self.SQLQueryListString(sql, query.author_list)
condList.append(temp)
if query.from_date:
temp = "(checkins.ci_when>=\"%s\")" % (str(query.from_date))
condList.append(temp)
if query.to_date:
temp = "(checkins.ci_when<=\"%s\")" % (str(query.to_date))
condList.append(temp)
if query.sort == "date":
order_by = "ORDER BY checkins.ci_when DESC"
elif query.sort == "author":
order_by = "ORDER BY checkins.whoid"
elif query.sort == "file":
order_by = "ORDER BY checkins.fileid"
## exclude duplicates from the table list
for table in tableList[:]:
while tableList.count(table) > 1:
tableList.remove(table)
tables = string.join(tableList, ",")
conditions = string.join(condList, " AND ")
## limit the number of rows requested or we could really slam
## a server with a large database
limit = ""
if cfg.cvsdb.row_limit:
limit = "LIMIT %s" % (str(cfg.cvsdb.row_limit))
sql = "SELECT checkins.* FROM %s WHERE %s %s %s" % (
tables, conditions, order_by, limit)
return sql
def RunQuery(self, query):
sql = self.CreateSQLQueryString(query)
cursor = self.db.cursor()
cursor.execute(sql)
while 1:
row = cursor.fetchone()
if not row:
break
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
dbRemovedLines, dbDescID) = row
commit = CreateCommit()
## TIME, TIME, TIME is all fucked up; dateobject.gmticks()
## is broken, dateobject.ticks() returns somthing like
## GMT ticks, except it forgets about daylight savings
## time -- we handle it ourself in the following painful way
gmt_time = time.mktime(
(dbCI_When.year, dbCI_When.month, dbCI_When.day,
dbCI_When.hour, dbCI_When.minute, dbCI_When.second,
0, 0, dbCI_When.dst))
commit.SetTime(gmt_time)
commit.SetFile(self.GetFile(dbFileID))
commit.SetDirectory(self.GetDirectory(dbDirID))
commit.SetRevision(dbRevision)
commit.SetRepository(self.GetRepository(dbRepositoryID))
commit.SetAuthor(self.GetAuthor(dbAuthorID))
commit.SetBranch(self.GetBranch(dbBranchID))
commit.SetPlusCount(dbAddedLines)
commit.SetMinusCount(dbRemovedLines)
commit.SetDescription(self.GetDescription(dbDescID))
query.AddCommit(commit)
def CheckCommit(self, commit):
repository_id = self.GetRepositoryID(commit.GetRepository(), 0)
if repository_id == None:
return None
dir_id = self.GetDirectoryID(commit.GetDirectory(), 0)
if dir_id == None:
return None
file_id = self.GetFileID(commit.GetFile(), 0)
if file_id == None:
return None
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()
cursor.execute(sql, sql_args)
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:
return None
return commit
## 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
class Commit:
## static constants for type of commit
CHANGE = 0
ADD = 1
REMOVE = 2
def __init__(self):
self.__directory = ''
self.__file = ''
self.__repository = ''
self.__revision = ''
self.__author = ''
self.__branch = ''
self.__pluscount = ''
self.__minuscount = ''
self.__description = ''
self.__gmt_time = 0.0
self.__type = Commit.CHANGE
def SetRepository(self, repository):
## clean up repository path; make sure it doesn't end with a
## path seperator
while len(repository) and repository[-1] == os.sep:
repository = repository[:-1]
self.__repository = repository
def GetRepository(self):
return self.__repository
def SetDirectory(self, dir):
## clean up directory path; make sure it doesn't begin
## or end with a path seperator
while len(dir) and dir[0] == os.sep:
dir = dir[1:]
while len(dir) and dir[-1] == os.sep:
dir = dir[:-1]
self.__directory = dir
def GetDirectory(self):
return self.__directory
def SetFile(self, file):
## clean up filename; make sure it doesn't begin
## or end with a path seperator
while len(file) and file[0] == os.sep:
file = file[1:]
while len(file) and file[-1] == os.sep:
file = file[:-1]
self.__file = file
def GetFile(self):
return self.__file
def SetRevision(self, revision):
self.__revision = revision
def GetRevision(self):
return self.__revision
def SetTime(self, gmt_time):
self.__gmt_time = float(gmt_time)
def GetTime(self):
return self.__gmt_time
def SetAuthor(self, author):
self.__author = author
def GetAuthor(self):
return self.__author
def SetBranch(self, branch):
if not branch:
self.__branch = ''
else:
self.__branch = branch
def GetBranch(self):
return self.__branch
def SetPlusCount(self, pluscount):
self.__pluscount = pluscount
def GetPlusCount(self):
return self.__pluscount
def SetMinusCount(self, minuscount):
self.__minuscount = minuscount
def GetMinusCount(self):
return self.__minuscount
def SetDescription(self, description):
self.__description = description
def GetDescription(self):
return self.__description
def SetTypeChange(self):
self.__type = Commit.CHANGE
def SetTypeAdd(self):
self.__type = Commit.ADD
def SetTypeRemove(self):
self.__type = Commit.REMOVE
def GetType(self):
return self.__type
def GetTypeString(self):
if self.__type == Commit.CHANGE:
return 'Change'
elif self.__type == Commit.ADD:
return 'Add'
elif self.__type == Commit.REMOVE:
return 'Remove'
## QueryEntry holds data on one match-type in the SQL database
## match is: "exact", "like", or "regex"
class QueryEntry:
def __init__(self, data, match):
self.data = data
self.match = match
## CheckinDatabaseQueryData is a object which contains the search parameters
## for a query to the CheckinDatabase
class CheckinDatabaseQuery:
def __init__(self):
## sorting
self.sort = "date"
## repository to query
self.repository_list = []
self.branch_list = []
self.directory_list = []
self.file_list = []
self.author_list = []
## date range in DBI 2.0 timedate objects
self.from_date = None
self.to_date = None
## list of commits -- filled in by CVS query
self.commit_list = []
## commit_cb provides a callback for commits as they
## are added
self.commit_cb = None
def SetRepository(self, repository, match = "exact"):
self.repository_list.append(QueryEntry(repository, match))
def SetBranch(self, branch, match = "exact"):
self.branch_list.append(QueryEntry(branch, match))
def SetDirectory(self, directory, match = "exact"):
self.directory_list.append(QueryEntry(directory, match))
def SetFile(self, file, match = "exact"):
self.file_list.append(QueryEntry(file, match))
def SetAuthor(self, author, match = "exact"):
self.author_list.append(QueryEntry(author, match))
def SetSortMethod(self, sort):
self.sort = sort
def SetFromDateObject(self, ticks):
self.from_date = dbi.TimestampFromTicks(ticks)
def SetToDateObject(self, ticks):
self.to_date = dbi.TimestampFromTicks(ticks)
def SetFromDateHoursAgo(self, hours_ago):
ticks = time.time() - (3600 * hours_ago)
self.from_date = dbi.TimestampFromTicks(ticks)
def SetFromDateDaysAgo(self, days_ago):
ticks = time.time() - (86400 * days_ago)
self.from_date = dbi.TimestampFromTicks(ticks)
def SetToDateDaysAgo(self, days_ago):
ticks = time.time() - (86400 * days_ago)
self.to_date = dbi.TimestampFromTicks(ticks)
def AddCommit(self, commit):
self.commit_list.append(commit)
if self.commit_cb:
self.commit_cb(commit)
def SetCommitCB(self, callback):
self.commit_cb = callback
##
## entrypoints
##
def CreateCheckinDatabase(host, user, passwd, database):
return CheckinDatabase(host, user, passwd, database)
def CreateCommit():
return Commit()
def CreateCheckinQuery():
return CheckinDatabaseQuery()
def ConnectDatabaseReadOnly():
global gCheckinDatabaseReadOnly
if gCheckinDatabaseReadOnly:
return gCheckinDatabaseReadOnly
gCheckinDatabaseReadOnly = CreateCheckinDatabase(
cfg.cvsdb.host,
cfg.cvsdb.readonly_user,
cfg.cvsdb.readonly_passwd,
cfg.cvsdb.database_name)
gCheckinDatabaseReadOnly.Connect()
return gCheckinDatabaseReadOnly
def ConnectDatabase():
global gCheckinDatabase
gCheckinDatabase = CreateCheckinDatabase(
cfg.cvsdb.host,
cfg.cvsdb.user,
cfg.cvsdb.passwd,
cfg.cvsdb.database_name)
gCheckinDatabase.Connect()
return gCheckinDatabase
def RLogDataToCommitList(repository, rlog_data):
commit_list = []
## the filename in rlog_data contains the entire path of the
## repository; we strip that out here
temp = rlog_data.filename[len(repository):]
directory, file = os.path.split(temp)
for rlog_entry in rlog_data.rlog_entry_list:
commit = CreateCommit()
commit.SetRepository(repository)
commit.SetDirectory(directory)
commit.SetFile(file)
commit.SetRevision(rlog_entry.revision)
commit.SetAuthor(rlog_entry.author)
commit.SetDescription(rlog_entry.description)
commit.SetTime(rlog_entry.time)
commit.SetPlusCount(rlog_entry.pluscount)
commit.SetMinusCount(rlog_entry.minuscount)
commit.SetBranch(rlog_data.LookupBranch(rlog_entry))
if rlog_entry.type == rlog_entry.CHANGE:
commit.SetTypeChange()
elif rlog_entry.type == rlog_entry.ADD:
commit.SetTypeAdd()
elif rlog_entry.type == rlog_entry.REMOVE:
commit.SetTypeRemove()
commit_list.append(commit)
return commit_list
def GetCommitListFromRCSFile(repository, filename):
try:
rlog_data = rlog.GetRLogData(cfg, filename)
except rlog.error, e:
raise error, e
commit_list = RLogDataToCommitList(repository, rlog_data)
return commit_list
def GetUnrecordedCommitList(repository, filename):
commit_list = GetCommitListFromRCSFile(repository, filename)
db = ConnectDatabase()
unrecorded_commit_list = []
for commit in commit_list:
result = db.CheckCommit(commit)
if not result:
unrecorded_commit_list.append(commit)
return unrecorded_commit_list

142
lib/cvsdbapi.py Normal file
View File

@@ -0,0 +1,142 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
CONF_PATHNAME = None
#########################################################################
import os
import database
import query
import rlog
import commit
import config
## error
error = 'cvsdbapi error'
## database
CreateCheckinDatabase = database.CreateCheckinDatabase
CreateCheckinQuery = query.CreateCheckinQuery
## rlog
GetRLogData = rlog.GetRLogData
## commit
CreateCommit = commit.CreateCommit
PrintCommit = commit.PrintCommit
## cached (active) database connections
gCheckinDatabase = None
gCheckinDatabaseReadOnly = None
## load configuration file, the data is used globally here
cfg = config.Config()
cfg.set_defaults()
cfg.load_config(CONF_PATHNAME)
def ConnectDatabaseReadOnly():
global gCheckinDatabaseReadOnly
if gCheckinDatabaseReadOnly:
return gCheckinDatabaseReadOnly
gCheckinDatabaseReadOnly = database.CreateCheckinDatabase(
cfg.cvsdb.host,
cfg.cvsdb.readonly_user,
cfg.cvsdb.readonly_passwd,
cfg.cvsdb.database_name)
gCheckinDatabaseReadOnly.Connect()
return gCheckinDatabaseReadOnly
def ConnectDatabase():
global gCheckinDatabase
gCheckinDatabase = database.CreateCheckinDatabase(
cfg.cvsdb.host,
cfg.cvsdb.user,
cfg.cvsdb.passwd,
cfg.cvsdb.database_name)
gCheckinDatabase.Connect()
return gCheckinDatabase
def RLogDataToCommitList(repository, rlog_data):
commit_list = []
## the filename in rlog_data contains the entire path of the
## repository; we strip that out here
temp = rlog_data.filename[len(repository):]
directory, file = os.path.split(temp)
for rlog_entry in rlog_data.rlog_entry_list:
commit = CreateCommit()
commit.SetRepository(repository)
commit.SetDirectory(directory)
commit.SetFile(file)
commit.SetRevision(rlog_entry.revision)
commit.SetAuthor(rlog_entry.author)
commit.SetDescription(rlog_entry.description)
commit.SetTime(rlog_entry.time)
commit.SetPlusCount(rlog_entry.pluscount)
commit.SetMinusCount(rlog_entry.minuscount)
commit.SetBranch(rlog_data.LookupBranch(rlog_entry))
if rlog_entry.type == rlog_entry.CHANGE:
commit.SetTypeChange()
elif rlog_entry.type == rlog_entry.ADD:
commit.SetTypeAdd()
elif rlog_entry.type == rlog_entry.REMOVE:
commit.SetTypeRemove()
commit_list.append(commit)
return commit_list
def GetCommitListFromRCSFile(repository, filename):
try:
rlog_data = GetRLogData(filename)
except rlog.error, e:
raise error, e
commit_list = RLogDataToCommitList(repository, rlog_data)
return commit_list
def GetUnrecordedCommitList(repository, filename):
commit_list = GetCommitListFromRCSFile(repository, filename)
db = ConnectDatabase()
unrecorded_commit_list = []
for commit in commit_list:
result = db.CheckCommit(commit)
if not result:
unrecorded_commit_list.append(commit)
return unrecorded_commit_list

408
lib/database.py Normal file
View File

@@ -0,0 +1,408 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
import os
import sys
import string
import time
import dbi
from commit import CreateCommit, PrintCommit
## base strings used in SQL querries, these should be static members
## of the CheckinDatabase class
sqlBase = 'SELECT checkins.type, checkins.ci_when,checkins. whoid, checkins.repositoryid, checkins.dirid, checkins.fileid, checkins.revision, checkins.stickytag, checkins.branchid, checkins.addedlines, checkins.removedlines, checkins.descid FROM %s WHERE %s %s'
sqlRepository = '(checkins.repositoryid = repositories.id AND repositories.repository %s "%s")'
sqlBranch = '(checkins.branchid = branches.id AND branches.branch %s "%s")'
sqlDirectory = '(checkins.dirid = dirs.id AND dirs.dir %s "%s")'
sqlFile = '(checkins.fileid = files.id AND files.file %s "%s")'
sqlAuthor = '(checkins.whoid = people.id AND people.who %s "%s")'
sqlFromDate ='(checkins.ci_when >= "%s")'
sqlToDate = '(checkins.ci_when <= "%s")'
sqlSortByDate = 'ORDER BY checkins.ci_when DESC'
sqlSortByAuthor = 'ORDER BY checkins.whoid'
sqlSortByFile = 'ORDER BY checkins.fileid'
sqlExcludeVersionFiles = '(checkins.fileid = files.id AND files.file NOT LIKE "%%.ver")'
sqlCheckCommit = 'SELECT * FROM checkins WHERE checkins.repositoryid=%s AND checkins.dirid=%s AND checkins.fileid=%s AND checkins.revision=%s'
## 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
## complient database interface
class CheckinDatabase:
def __init__(self, host, user, passwd, database):
self.dbHost = host
self.dbUser = user
self.dbPasswd = passwd
self.dbDatabase = database
## cache Value lookups
self.dbGetCache = {}
self.dbGetIDCache = {}
self.dbDescriptionIDCache = {}
def Connect(self):
self.dbConn = dbi.connect(
self.dbHost, self.dbUser, self.dbPasswd, self.dbDatabase)
def SQLGetID(self, table, field, identifier, auto_set):
sql = 'SELECT id FROM %s x WHERE x.%s="%s"' % (
table, field, identifier)
cursor = self.dbConn.cursor()
cursor.execute(sql)
row = cursor.fetchone()
if row:
return row[0]
if not auto_set:
return None
## insert the new identifier
sql = 'INSERT INTO %s (%s) VALUES ("%s")' % (table, field, identifier)
cursor.execute(sql)
return self.SQLGetID(table, field, identifier, 0)
def GetID(self, table, field, identifier, auto_set):
## attempt to retrieve from cache
try:
return self.dbGetIDCache[table][field][identifier]
except KeyError:
pass
id = self.SQLGetID(table, field, identifier, auto_set)
if not id:
return id
## add to cache
if not self.dbGetIDCache.has_key(table):
self.dbGetIDCache[table] = {}
if not self.dbGetIDCache[table].has_key(field):
self.dbGetIDCache[table][field] = {}
self.dbGetIDCache[table][field][identifier] = id
return id
def SQLGet(self, table, field, id):
sql = 'SELECT %s FROM %s x WHERE x.id="%s"' % (field, table, id)
cursor = self.dbConn.cursor()
cursor.execute(sql)
row = cursor.fetchone()
if not row:
return None
return row[0]
def Get(self, table, field, id):
## attempt to retrieve from cache
try:
return self.dbGetCache[table][field][id]
except KeyError:
pass
value = self.SQLGet(table, field, id)
if not value:
return None
## add to cache
if not self.dbGetCache.has_key(table):
self.dbGetCache[table] = {}
if not self.dbGetCache[table].has_key(field):
self.dbGetCache[table][field] = {}
self.dbGetCache[table][field][id] = value
return value
def GetBranchID(self, branch, auto_set = 1):
return self.GetID('branches', 'branch', branch, auto_set)
def GetBranch(self, id):
return self.Get('branches', 'branch', id)
def GetDirectoryID(self, dir, auto_set = 1):
return self.GetID('dirs', 'dir', dir, auto_set)
def GetDirectory(self, id):
return self.Get('dirs', 'dir', id)
def GetFileID(self, file, auto_set = 1):
return self.GetID('files', 'file', file, auto_set)
def GetFile(self, id):
return self.Get('files', 'file', id)
def GetAuthorID(self, author, auto_set = 1):
return self.GetID('people', 'who', author, auto_set)
def GetAuthor(self, id):
return self.Get('people', 'who', id)
def GetRepositoryID(self, repository, auto_set = 1):
return self.GetID('repositories', 'repository', repository, auto_set)
def GetRepository(self, id):
return self.Get('repositories', 'repository', id)
def SQLGetDescriptionID(self, description, auto_set = 1):
## lame string hash, blame Netscape -JMP
hash = len(description)
cursor = self.dbConn.cursor()
cursor.execute(
'SELECT id FROM descs WHERE hash=%s and description=%s',
(hash, description))
row = cursor.fetchone()
if row:
return row[0]
if not auto_set:
return None
cursor = self.dbConn.cursor()
cursor.execute(
'INSERT INTO descs (hash, description) values (%s, %s)',
(hash, description))
return self.GetDescriptionID(description, 0)
def GetDescriptionID(self, description, auto_set = 1):
## lame string hash, blame Netscape -JMP
hash = len(description)
## attempt to retrieve from cache
try:
return self.dbDescriptionIDCache[hash][description]
except KeyError:
pass
id = self.SQLGetDescriptionID(description, auto_set)
if not id:
return id
## add to cache
if not self.dbDescriptionIDCache.has_key(hash):
self.dbDescriptionIDCache[hash] = {}
self.dbDescriptionIDCache[hash][description] = id
return id
def GetDescription(self, id):
return self.Get('descs', 'description', id)
def GetList(self, table, field_index):
sql = 'SELECT * FROM %s' % (table)
cursor = self.dbConn.cursor()
cursor.execute(sql)
list = []
while 1:
row = cursor.fetchone()
if not row:
break
list.append(row[field_index])
return list
def GetRepositoryList(self):
return self.GetList('repositories', 1)
def GetBranchList(self):
return self.GetList('branches', 1)
def GetAuthorList(self):
return self.GetList('people', 1)
def AddCommitList(self, commit_list):
for commit in commit_list:
self.AddCommit(commit)
def AddCommit(self, commit):
dbType = commit.GetTypeString()
## MORE TIME HELL: the MySQLdb module doesn't construct times
## correctly when created with TimestampFromTicks -- it doesn't
## account for daylight savings time, so we use Python's time
## module to do the conversion
temp = time.localtime(commit.GetTime())
dbCI_When = dbi.Timestamp(
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
dbWhoID = self.GetAuthorID(commit.GetAuthor())
dbRepositoryID = self.GetRepositoryID(commit.GetRepository())
dbDirectoryID = self.GetDirectoryID(commit.GetDirectory())
dbFileID = self.GetFileID(commit.GetFile())
dbRevision = commit.GetRevision()
dbStickyTag = 'NULL'
dbBranchID = self.GetBranchID(commit.GetBranch())
dbPlusCount = commit.GetPlusCount()
dbMinusCount = commit.GetMinusCount()
dbDescriptionID = self.GetDescriptionID(commit.GetDescription())
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)'
sqlArguments = (
dbType, dbCI_When, dbWhoID, dbRepositoryID, dbDirectoryID,
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbPlusCount,
dbMinusCount, dbDescriptionID)
cursor = self.dbConn.cursor()
cursor.execute(sql, sqlArguments)
def SQLQueryListString(self, sqlString, query_entry_list):
sqlList = []
for query_entry in query_entry_list:
## figure out the correct match type
if query_entry.match == "exact":
match = "="
elif query_entry.match == "like":
match = "LIKE"
elif query_entry.match == "regex":
match = "REGEXP"
sqlList.append(sqlString % (match, query_entry.data))
return "(%s)" % (string.join(sqlList, " OR "))
def CreateSQLQueryString(self, query):
tableList = ['checkins']
condList = []
## XXX: this is to exclude .ver files -- RN specific hack --JMP
tableList.append("files")
condList.append(sqlExcludeVersionFiles)
if len(query.repository_list):
tableList.append("repositories")
condList.append(
self.SQLQueryListString(sqlRepository, query.repository_list))
if len(query.branch_list):
tableList.append("branches")
condList.append(
self.SQLQueryListString(sqlBranch, query.branch_list))
if len(query.directory_list):
tableList.append("dirs")
condList.append(
self.SQLQueryListString(sqlDirectory, query.directory_list))
if len(query.file_list):
tableList.append("files")
condList.append(
self.SQLQueryListString(sqlFile, query.file_list))
if len(query.author_list):
tableList.append("people")
condList.append(
self.SQLQueryListString(sqlAuthor, query.author_list))
if query.from_date:
condList.append(sqlFromDate % (str(query.from_date)))
if query.to_date:
condList.append(sqlToDate % (str(query.to_date)))
if query.sort == "date":
order_by = sqlSortByDate
elif query.sort == "author":
order_by = sqlSortByAuthor
elif query.sort == "file":
order_by = sqlSortByFile
## exclude duplicates from the table list
for table in tableList[:]:
while tableList.count(table) > 1:
tableList.remove(table)
sql = sqlBase % (
string.join(tableList, ', '),
string.join(condList, ' AND '),
order_by)
return sql
def RunQuery(self, query):
sql = self.CreateSQLQueryString(query)
cursor = self.dbConn.cursor()
cursor.execute(sql)
while 1:
row = cursor.fetchone()
if not row:
break
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
dbRemovedLines, dbDescID) = row
commit = CreateCommit()
## TIME, TIME, TIME is all fucked up; dateobject.gmticks()
## is broken, dateobject.ticks() returns somthing like
## GMT ticks, except it forgets about daylight savings
## time -- we handle it ourself in the following painful way
gmt_time = time.mktime(
(dbCI_When.year, dbCI_When.month, dbCI_When.day,
dbCI_When.hour, dbCI_When.minute, dbCI_When.second,
0, 0, dbCI_When.dst))
commit.SetTime(gmt_time)
commit.SetFile(self.GetFile(dbFileID))
commit.SetDirectory(self.GetDirectory(dbDirID))
commit.SetRevision(dbRevision)
commit.SetRepository(self.GetRepository(dbRepositoryID))
commit.SetAuthor(self.GetAuthor(dbAuthorID))
commit.SetBranch(self.GetBranch(dbBranchID))
commit.SetPlusCount(dbAddedLines)
commit.SetMinusCount(dbRemovedLines)
commit.SetDescription(self.GetDescription(dbDescID))
query.AddCommit(commit)
def CheckCommit(self, commit):
dbRepositoryID = self.GetRepositoryID(commit.GetRepository(), 0)
if dbRepositoryID == None:
return None
dbDirID = self.GetDirectoryID(commit.GetDirectory(), 0)
if dbDirID == None:
return None
dbFileID = self.GetFileID(commit.GetFile(), 0)
if dbFileID == None:
return None
sqlArguments = (dbRepositoryID, dbDirID, dbFileID,
commit.GetRevision())
cursor = self.dbConn.cursor()
cursor.execute(sqlCheckCommit, sqlArguments)
row = cursor.fetchone()
if not row:
return None
return commit
## entrypoints
def CreateCheckinDatabase(host, user, passwd, database):
return CheckinDatabase(host, user, passwd, database)

View File

@@ -1,42 +0,0 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
# Note: a t_start/t_end pair consumes about 0.00005 seconds on a P3/700.
# the lambda form (when debugging is disabled) should be even faster.
#
if 0:
import time
_timers = { }
_times = { }
def t_start(which):
_timers[which] = time.time()
def t_end(which):
t = time.time() - _timers[which]
if _times.has_key(which):
_times[which] = _times[which] + t
else:
_times[which] = t
def dump():
for name, value in _times.items():
print '%s: %.6f<br>' % (name, value)
else:
t_start = t_end = dump = lambda *args: None

View File

@@ -1,786 +0,0 @@
#! /usr/bin/env python
# Backported to Python 1.5.2 for the ViewCVS project by pf@artcom-gmbh.de
# 24-Dec-2001, original version "stolen" from Python-2.1.1
"""
Module difflib -- helpers for computing deltas between objects.
Function get_close_matches(word, possibilities, n=3, cutoff=0.6):
Use SequenceMatcher to return list of the best "good enough" matches.
word is a sequence for which close matches are desired (typically a
string).
possibilities is a list of sequences against which to match word
(typically a list of strings).
Optional arg n (default 3) is the maximum number of close matches to
return. n must be > 0.
Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities
that don't score at least that similar to word are ignored.
The best (no more than n) matches among the possibilities are returned
in a list, sorted by similarity score, most similar first.
>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
['apple', 'ape']
>>> import keyword
>>> get_close_matches("wheel", keyword.kwlist)
['while']
>>> get_close_matches("apple", keyword.kwlist)
[]
>>> get_close_matches("accept", keyword.kwlist)
['except']
Class SequenceMatcher
SequenceMatcher is a flexible class for comparing pairs of sequences of any
type, so long as the sequence elements are hashable. The basic algorithm
predates, and is a little fancier than, an algorithm published in the late
1980's by Ratcliff and Obershelp under the hyperbolic name "gestalt pattern
matching". The basic idea is to find the longest contiguous matching
subsequence that contains no "junk" elements (R-O doesn't address junk).
The same idea is then applied recursively to the pieces of the sequences to
the left and to the right of the matching subsequence. This does not yield
minimal edit sequences, but does tend to yield matches that "look right"
to people.
Example, comparing two strings, and considering blanks to be "junk":
>>> s = SequenceMatcher(lambda x: x == " ",
... "private Thread currentThread;",
... "private volatile Thread currentThread;")
>>>
.ratio() returns a float in [0, 1], measuring the "similarity" of the
sequences. As a rule of thumb, a .ratio() value over 0.6 means the
sequences are close matches:
>>> print round(s.ratio(), 3)
0.866
>>>
If you're only interested in where the sequences match,
.get_matching_blocks() is handy:
>>> for block in s.get_matching_blocks():
... print "a[%d] and b[%d] match for %d elements" % block
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 6 elements
a[14] and b[23] match for 15 elements
a[29] and b[38] match for 0 elements
Note that the last tuple returned by .get_matching_blocks() is always a
dummy, (len(a), len(b), 0), and this is the only case in which the last
tuple element (number of elements matched) is 0.
If you want to know how to change the first sequence into the second, use
.get_opcodes():
>>> for opcode in s.get_opcodes():
... print "%6s a[%d:%d] b[%d:%d]" % opcode
equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
equal a[8:14] b[17:23]
equal a[14:29] b[23:38]
See Tools/scripts/ndiff.py for a fancy human-friendly file differencer,
which uses SequenceMatcher both to view files as sequences of lines, and
lines as sequences of characters.
See also function get_close_matches() in this module, which shows how
simple code building on SequenceMatcher can be used to do useful work.
Timing: Basic R-O is cubic time worst case and quadratic time expected
case. SequenceMatcher is quadratic time for the worst case and has
expected-case behavior dependent in a complicated way on how many
elements the sequences have in common; best case time is linear.
SequenceMatcher methods:
__init__(isjunk=None, a='', b='')
Construct a SequenceMatcher.
Optional arg isjunk is None (the default), or a one-argument function
that takes a sequence element and returns true iff the element is junk.
None is equivalent to passing "lambda x: 0", i.e. no elements are
considered to be junk. For example, pass
lambda x: x in " \\t"
if you're comparing lines as sequences of characters, and don't want to
synch up on blanks or hard tabs.
Optional arg a is the first of two sequences to be compared. By
default, an empty string. The elements of a must be hashable.
Optional arg b is the second of two sequences to be compared. By
default, an empty string. The elements of b must be hashable.
set_seqs(a, b)
Set the two sequences to be compared.
>>> s = SequenceMatcher()
>>> s.set_seqs("abcd", "bcde")
>>> s.ratio()
0.75
set_seq1(a)
Set the first sequence to be compared.
The second sequence to be compared is not changed.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.set_seq1("bcde")
>>> s.ratio()
1.0
>>>
SequenceMatcher computes and caches detailed information about the
second sequence, so if you want to compare one sequence S against many
sequences, use .set_seq2(S) once and call .set_seq1(x) repeatedly for
each of the other sequences.
See also set_seqs() and set_seq2().
set_seq2(b)
Set the second sequence to be compared.
The first sequence to be compared is not changed.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.set_seq2("abcd")
>>> s.ratio()
1.0
>>>
SequenceMatcher computes and caches detailed information about the
second sequence, so if you want to compare one sequence S against many
sequences, use .set_seq2(S) once and call .set_seq1(x) repeatedly for
each of the other sequences.
See also set_seqs() and set_seq1().
find_longest_match(alo, ahi, blo, bhi)
Find longest matching block in a[alo:ahi] and b[blo:bhi].
If isjunk is not defined:
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
alo <= i <= i+k <= ahi
blo <= j <= j+k <= bhi
and for all (i',j',k') meeting those conditions,
k >= k'
i <= i'
and if i == i', j <= j'
In other words, of all maximal matching blocks, return one that starts
earliest in a, and of all those maximal matching blocks that start
earliest in a, return the one that starts earliest in b.
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
(0, 4, 5)
If isjunk is defined, first the longest matching block is determined as
above, but with the additional restriction that no junk element appears
in the block. Then that block is extended as far as possible by
matching (only) junk elements on both sides. So the resulting block
never matches on junk except as identical junk happens to be adjacent
to an "interesting" match.
Here's the same example as before, but considering blanks to be junk.
That prevents " abcd" from matching the " abcd" at the tail end of the
second sequence directly. Instead only the "abcd" can match, and
matches the leftmost "abcd" in the second sequence:
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
(1, 0, 4)
If no blocks match, return (alo, blo, 0).
>>> s = SequenceMatcher(None, "ab", "c")
>>> s.find_longest_match(0, 2, 0, 1)
(0, 0, 0)
get_matching_blocks()
Return list of triples describing matching subsequences.
Each triple is of the form (i, j, n), and means that
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in i
and in j.
The last triple is a dummy, (len(a), len(b), 0), and is the only triple
with n==0.
>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[(0, 0, 2), (3, 2, 2), (5, 4, 0)]
get_opcodes()
Return list of 5-tuples describing how to turn a into b.
Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple has
i1 == j1 == 0, and remaining tuples have i1 == the i2 from the tuple
preceding it, and likewise for j1 == the previous j2.
The tags are strings, with these meanings:
'replace': a[i1:i2] should be replaced by b[j1:j2]
'delete': a[i1:i2] should be deleted.
Note that j1==j2 in this case.
'insert': b[j1:j2] should be inserted at a[i1:i1].
Note that i1==i2 in this case.
'equal': a[i1:i2] == b[j1:j2]
>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
... print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
delete a[0:1] (q) b[0:0] ()
equal a[1:3] (ab) b[0:2] (ab)
replace a[3:4] (x) b[2:3] (y)
equal a[4:6] (cd) b[3:5] (cd)
insert a[6:6] () b[5:6] (f)
ratio()
Return a measure of the sequences' similarity (float in [0,1]).
Where T is the total number of elements in both sequences, and M is the
number of matches, this is 2,0*M / T. Note that this is 1 if the
sequences are identical, and 0 if they have nothing in common.
.ratio() is expensive to compute if you haven't already computed
.get_matching_blocks() or .get_opcodes(), in which case you may want to
try .quick_ratio() or .real_quick_ratio() first to get an upper bound.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
quick_ratio()
Return an upper bound on .ratio() relatively quickly.
This isn't defined beyond that it is an upper bound on .ratio(), and
is faster to compute.
real_quick_ratio():
Return an upper bound on ratio() very quickly.
This isn't defined beyond that it is an upper bound on .ratio(), and
is faster to compute than either .ratio() or .quick_ratio().
"""
TRACE = 0
class SequenceMatcher:
def __init__(self, isjunk=None, a='', b=''):
"""Construct a SequenceMatcher.
Optional arg isjunk is None (the default), or a one-argument
function that takes a sequence element and returns true iff the
element is junk. None is equivalent to passing "lambda x: 0", i.e.
no elements are considered to be junk. For example, pass
lambda x: x in " \\t"
if you're comparing lines as sequences of characters, and don't
want to synch up on blanks or hard tabs.
Optional arg a is the first of two sequences to be compared. By
default, an empty string. The elements of a must be hashable. See
also .set_seqs() and .set_seq1().
Optional arg b is the second of two sequences to be compared. By
default, an empty string. The elements of b must be hashable. See
also .set_seqs() and .set_seq2().
"""
# Members:
# a
# first sequence
# b
# second sequence; differences are computed as "what do
# we need to do to 'a' to change it into 'b'?"
# b2j
# for x in b, b2j[x] is a list of the indices (into b)
# at which x appears; junk elements do not appear
# b2jhas
# b2j.has_key
# fullbcount
# for x in b, fullbcount[x] == the number of times x
# appears in b; only materialized if really needed (used
# only for computing quick_ratio())
# matching_blocks
# a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];
# ascending & non-overlapping in i and in j; terminated by
# a dummy (len(a), len(b), 0) sentinel
# opcodes
# a list of (tag, i1, i2, j1, j2) tuples, where tag is
# one of
# 'replace' a[i1:i2] should be replaced by b[j1:j2]
# 'delete' a[i1:i2] should be deleted
# 'insert' b[j1:j2] should be inserted
# 'equal' a[i1:i2] == b[j1:j2]
# isjunk
# a user-supplied function taking a sequence element and
# returning true iff the element is "junk" -- this has
# subtle but helpful effects on the algorithm, which I'll
# get around to writing up someday <0.9 wink>.
# DON'T USE! Only __chain_b uses this. Use isbjunk.
# isbjunk
# for x in b, isbjunk(x) == isjunk(x) but much faster;
# it's really the has_key method of a hidden dict.
# DOES NOT WORK for x in a!
self.isjunk = isjunk
self.a = self.b = None
self.set_seqs(a, b)
def set_seqs(self, a, b):
"""Set the two sequences to be compared.
>>> s = SequenceMatcher()
>>> s.set_seqs("abcd", "bcde")
>>> s.ratio()
0.75
"""
self.set_seq1(a)
self.set_seq2(b)
def set_seq1(self, a):
"""Set the first sequence to be compared.
The second sequence to be compared is not changed.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.set_seq1("bcde")
>>> s.ratio()
1.0
>>>
SequenceMatcher computes and caches detailed information about the
second sequence, so if you want to compare one sequence S against
many sequences, use .set_seq2(S) once and call .set_seq1(x)
repeatedly for each of the other sequences.
See also set_seqs() and set_seq2().
"""
if a is self.a:
return
self.a = a
self.matching_blocks = self.opcodes = None
def set_seq2(self, b):
"""Set the second sequence to be compared.
The first sequence to be compared is not changed.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.set_seq2("abcd")
>>> s.ratio()
1.0
>>>
SequenceMatcher computes and caches detailed information about the
second sequence, so if you want to compare one sequence S against
many sequences, use .set_seq2(S) once and call .set_seq1(x)
repeatedly for each of the other sequences.
See also set_seqs() and set_seq1().
"""
if b is self.b:
return
self.b = b
self.matching_blocks = self.opcodes = None
self.fullbcount = None
self.__chain_b()
# For each element x in b, set b2j[x] to a list of the indices in
# b where x appears; the indices are in increasing order; note that
# the number of times x appears in b is len(b2j[x]) ...
# when self.isjunk is defined, junk elements don't show up in this
# map at all, which stops the central find_longest_match method
# from starting any matching block at a junk element ...
# also creates the fast isbjunk function ...
# note that this is only called when b changes; so for cross-product
# kinds of matches, it's best to call set_seq2 once, then set_seq1
# repeatedly
def __chain_b(self):
# Because isjunk is a user-defined (not C) function, and we test
# for junk a LOT, it's important to minimize the number of calls.
# Before the tricks described here, __chain_b was by far the most
# time-consuming routine in the whole module! If anyone sees
# Jim Roskind, thank him again for profile.py -- I never would
# have guessed that.
# The first trick is to build b2j ignoring the possibility
# of junk. I.e., we don't call isjunk at all yet. Throwing
# out the junk later is much cheaper than building b2j "right"
# from the start.
b = self.b
self.b2j = b2j = {}
self.b2jhas = b2jhas = b2j.has_key
for i in xrange(len(b)):
elt = b[i]
if b2jhas(elt):
b2j[elt].append(i)
else:
b2j[elt] = [i]
# Now b2j.keys() contains elements uniquely, and especially when
# the sequence is a string, that's usually a good deal smaller
# than len(string). The difference is the number of isjunk calls
# saved.
isjunk, junkdict = self.isjunk, {}
if isjunk:
for elt in b2j.keys():
if isjunk(elt):
junkdict[elt] = 1 # value irrelevant; it's a set
del b2j[elt]
# Now for x in b, isjunk(x) == junkdict.has_key(x), but the
# latter is much faster. Note too that while there may be a
# lot of junk in the sequence, the number of *unique* junk
# elements is probably small. So the memory burden of keeping
# this dict alive is likely trivial compared to the size of b2j.
self.isbjunk = junkdict.has_key
def find_longest_match(self, alo, ahi, blo, bhi):
"""Find longest matching block in a[alo:ahi] and b[blo:bhi].
If isjunk is not defined:
Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
alo <= i <= i+k <= ahi
blo <= j <= j+k <= bhi
and for all (i',j',k') meeting those conditions,
k >= k'
i <= i'
and if i == i', j <= j'
In other words, of all maximal matching blocks, return one that
starts earliest in a, and of all those maximal matching blocks that
start earliest in a, return the one that starts earliest in b.
>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
(0, 4, 5)
If isjunk is defined, first the longest matching block is
determined as above, but with the additional restriction that no
junk element appears in the block. Then that block is extended as
far as possible by matching (only) junk elements on both sides. So
the resulting block never matches on junk except as identical junk
happens to be adjacent to an "interesting" match.
Here's the same example as before, but considering blanks to be
junk. That prevents " abcd" from matching the " abcd" at the tail
end of the second sequence directly. Instead only the "abcd" can
match, and matches the leftmost "abcd" in the second sequence:
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
(1, 0, 4)
If no blocks match, return (alo, blo, 0).
>>> s = SequenceMatcher(None, "ab", "c")
>>> s.find_longest_match(0, 2, 0, 1)
(0, 0, 0)
"""
# CAUTION: stripping common prefix or suffix would be incorrect.
# E.g.,
# ab
# acab
# Longest matching block is "ab", but if common prefix is
# stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
# strip, so ends up claiming that ab is changed to acab by
# inserting "ca" in the middle. That's minimal but unintuitive:
# "it's obvious" that someone inserted "ac" at the front.
# Windiff ends up at the same place as diff, but by pairing up
# the unique 'b's and then matching the first two 'a's.
a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
besti, bestj, bestsize = alo, blo, 0
# find longest junk-free match
# during an iteration of the loop, j2len[j] = length of longest
# junk-free match ending with a[i-1] and b[j]
j2len = {}
nothing = []
for i in xrange(alo, ahi):
# look at all instances of a[i] in b; note that because
# b2j has no junk keys, the loop is skipped if a[i] is junk
j2lenget = j2len.get
newj2len = {}
for j in b2j.get(a[i], nothing):
# a[i] matches b[j]
if j < blo:
continue
if j >= bhi:
break
k = newj2len[j] = j2lenget(j-1, 0) + 1
if k > bestsize:
besti, bestj, bestsize = i-k+1, j-k+1, k
j2len = newj2len
# Now that we have a wholly interesting match (albeit possibly
# empty!), we may as well suck up the matching junk on each
# side of it too. Can't think of a good reason not to, and it
# saves post-processing the (possibly considerable) expense of
# figuring out what to do with it. In the case of an empty
# interesting match, this is clearly the right thing to do,
# because no other kind of match is possible in the regions.
while besti > alo and bestj > blo and \
isbjunk(b[bestj-1]) and \
a[besti-1] == b[bestj-1]:
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
while besti+bestsize < ahi and bestj+bestsize < bhi and \
isbjunk(b[bestj+bestsize]) and \
a[besti+bestsize] == b[bestj+bestsize]:
bestsize = bestsize + 1
if TRACE:
print "get_matching_blocks", alo, ahi, blo, bhi
print " returns", besti, bestj, bestsize
return besti, bestj, bestsize
def get_matching_blocks(self):
"""Return list of triples describing matching subsequences.
Each triple is of the form (i, j, n), and means that
a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
i and in j.
The last triple is a dummy, (len(a), len(b), 0), and is the only
triple with n==0.
>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[(0, 0, 2), (3, 2, 2), (5, 4, 0)]
"""
if self.matching_blocks is not None:
return self.matching_blocks
self.matching_blocks = []
la, lb = len(self.a), len(self.b)
self.__helper(0, la, 0, lb, self.matching_blocks)
self.matching_blocks.append( (la, lb, 0) )
if TRACE:
print '*** matching blocks', self.matching_blocks
return self.matching_blocks
# builds list of matching blocks covering a[alo:ahi] and
# b[blo:bhi], appending them in increasing order to answer
def __helper(self, alo, ahi, blo, bhi, answer):
i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)
# a[alo:i] vs b[blo:j] unknown
# a[i:i+k] same as b[j:j+k]
# a[i+k:ahi] vs b[j+k:bhi] unknown
if k:
if alo < i and blo < j:
self.__helper(alo, i, blo, j, answer)
answer.append(x)
if i+k < ahi and j+k < bhi:
self.__helper(i+k, ahi, j+k, bhi, answer)
def get_opcodes(self):
"""Return list of 5-tuples describing how to turn a into b.
Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
tuple preceding it, and likewise for j1 == the previous j2.
The tags are strings, with these meanings:
'replace': a[i1:i2] should be replaced by b[j1:j2]
'delete': a[i1:i2] should be deleted.
Note that j1==j2 in this case.
'insert': b[j1:j2] should be inserted at a[i1:i1].
Note that i1==i2 in this case.
'equal': a[i1:i2] == b[j1:j2]
>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
... print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" %
... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))
delete a[0:1] (q) b[0:0] ()
equal a[1:3] (ab) b[0:2] (ab)
replace a[3:4] (x) b[2:3] (y)
equal a[4:6] (cd) b[3:5] (cd)
insert a[6:6] () b[5:6] (f)
"""
if self.opcodes is not None:
return self.opcodes
i = j = 0
self.opcodes = answer = []
for ai, bj, size in self.get_matching_blocks():
# invariant: we've pumped out correct diffs to change
# a[:i] into b[:j], and the next matching block is
# a[ai:ai+size] == b[bj:bj+size]. So we need to pump
# out a diff to change a[i:ai] into b[j:bj], pump out
# the matching block, and move (i,j) beyond the match
tag = ''
if i < ai and j < bj:
tag = 'replace'
elif i < ai:
tag = 'delete'
elif j < bj:
tag = 'insert'
if tag:
answer.append( (tag, i, ai, j, bj) )
i, j = ai+size, bj+size
# the list of matching blocks is terminated by a
# sentinel with size 0
if size:
answer.append( ('equal', ai, i, bj, j) )
return answer
def ratio(self):
"""Return a measure of the sequences' similarity (float in [0,1]).
Where T is the total number of elements in both sequences, and
M is the number of matches, this is 2,0*M / T.
Note that this is 1 if the sequences are identical, and 0 if
they have nothing in common.
.ratio() is expensive to compute if you haven't already computed
.get_matching_blocks() or .get_opcodes(), in which case you may
want to try .quick_ratio() or .real_quick_ratio() first to get an
upper bound.
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
"""
matches = reduce(lambda sum, triple: sum + triple[-1],
self.get_matching_blocks(), 0)
return 2.0 * matches / (len(self.a) + len(self.b))
def quick_ratio(self):
"""Return an upper bound on ratio() relatively quickly.
This isn't defined beyond that it is an upper bound on .ratio(), and
is faster to compute.
"""
# viewing a and b as multisets, set matches to the cardinality
# of their intersection; this counts the number of matches
# without regard to order, so is clearly an upper bound
if self.fullbcount is None:
self.fullbcount = fullbcount = {}
for elt in self.b:
fullbcount[elt] = fullbcount.get(elt, 0) + 1
fullbcount = self.fullbcount
# avail[x] is the number of times x appears in 'b' less the
# number of times we've seen it in 'a' so far ... kinda
avail = {}
availhas, matches = avail.has_key, 0
for elt in self.a:
if availhas(elt):
numb = avail[elt]
else:
numb = fullbcount.get(elt, 0)
avail[elt] = numb - 1
if numb > 0:
matches = matches + 1
return 2.0 * matches / (len(self.a) + len(self.b))
def real_quick_ratio(self):
"""Return an upper bound on ratio() very quickly.
This isn't defined beyond that it is an upper bound on .ratio(), and
is faster to compute than either .ratio() or .quick_ratio().
"""
la, lb = len(self.a), len(self.b)
# can't have more matches than the number of elements in the
# shorter sequence
return 2.0 * min(la, lb) / (la + lb)
def get_close_matches(word, possibilities, n=3, cutoff=0.6):
"""Use SequenceMatcher to return list of the best "good enough" matches.
word is a sequence for which close matches are desired (typically a
string).
possibilities is a list of sequences against which to match word
(typically a list of strings).
Optional arg n (default 3) is the maximum number of close matches to
return. n must be > 0.
Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities
that don't score at least that similar to word are ignored.
The best (no more than n) matches among the possibilities are returned
in a list, sorted by similarity score, most similar first.
>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
['apple', 'ape']
>>> import keyword
>>> get_close_matches("wheel", keyword.kwlist)
['while']
>>> get_close_matches("apple", keyword.kwlist)
[]
>>> get_close_matches("accept", keyword.kwlist)
['except']
"""
if not n > 0:
raise ValueError("n must be > 0: " + `n`)
if not 0.0 <= cutoff <= 1.0:
raise ValueError("cutoff must be in [0.0, 1.0]: " + `cutoff`)
result = []
s = SequenceMatcher()
s.set_seq2(word)
for x in possibilities:
s.set_seq1(x)
if s.real_quick_ratio() >= cutoff and \
s.quick_ratio() >= cutoff and \
s.ratio() >= cutoff:
result.append((s.ratio(), x))
# Sort by score.
result.sort()
# Retain only the best n.
result = result[-n:]
# Move best-scorer to head of list.
result.reverse()
# Strip scores.
# Python 2.x list comprehensions: return [x for score, x in result]
return_result = []
for score, x in result:
return_result.append(x)
return return_result
def _test():
import doctest, difflib
return doctest.testmod(difflib)
if __name__ == "__main__":
_test()

View File

@@ -1,523 +0,0 @@
#!/usr/bin/env python
"""ezt.py -- easy templating
ezt templates are very similar to standard HTML files. But additionaly
they contain directives sprinkled in between. With these directives
it possible to generate the dynamic content from the ezt templates.
These directives are enclosed in square brackets. If you are a
C-programmer, you might be familar with the #ifdef directives of the
C preprocessor 'cpp'. ezt provides a similar concept for HTML. Additionally
EZT has a 'for' directive, which allows to iterate (repeat) certain
subsections of the template according to sequence of data items
provided by the application.
The HTML rendering is performed by the method generate() of the Template
class. Building template instances can either be done using external
EZT files (convention: use the suffix .ezt for such files):
>>> template = Template("../templates/log.ezt")
or by calling the parse() method of a template instance directly with
a EZT template string:
>>> template = Template()
>>> template.parse('''<html><head>
... <title>[title_string]</title></head>
... <body><h1>[title_string]</h1>
... [for a_sequence] <p>[a_sequence]</p>
... [end] <hr>
... The [person] is [if-any state]in[else]out[end].
... </body>
... </html>
... ''')
The application should build a dictionary 'data' and pass it together
with the output fileobject to the templates generate method:
>>> data = {'title_string' : "A Dummy Page",
... 'a_sequence' : ['list item 1', 'list item 2', 'another element'],
... 'person': "doctor",
... 'state' : None }
>>> import sys
>>> template.generate(sys.stdout, data)
<html><head>
<title>A Dummy Page</title></head>
<body><h1>A Dummy Page</h1>
<p>list item 1</p>
<p>list item 2</p>
<p>another element</p>
<hr>
The doctor is out.
</body>
</html>
Directives
==========
Several directives allow the use of dotted qualified names refering to objects
or attributes of objects contained in the data dictionary given to the
.generate() method.
Simple directives
-----------------
[QUAL_NAME]
This directive is simply replaced by the value of identifier from the data
dictionary. QUAL_NAME might be a dotted qualified name refering to some
instance attribute of objects contained in the dats dictionary.
Numbers are converted to string though.
[include "filename"] or [include QUAL_NAME]
This directive is replaced by content of the named include file.
Block directives
----------------
[for QUAL_NAME] ... [end]
The text within the [for ...] directive and the corresponding [end]
is repeated for each element in the sequence referred to by the qualified
name in the for directive. Within the for block this identifiers now
refers to the actual item indexed by this loop iteration.
[if-any QUAL_NAME] ... [else] ... [end]
Test if the value QUAL_NAME is not None or an empty string or list.
The [else] clause is optional. CAUTION: Numeric values are converted to string,
so if QUAL_NAME refers to a numeric value 0, the then-clause is
substituted!
[if-index odd] ... [else] ... [end]
[if-index even] ... [else] ... [end]
[if-index first] ... [else] ... [end]
[if-index last] ... [else] ... [end]
[if-index NUMBER] ... [else] ... [end]
These five directives work similar to [if-any], but are only useful
within a [for ...]-block (see above). The odd/even directives are
for example useful to choose different background colors for adjacent rows
in a table. Similar the first/last directives might be used to
remove certain parts (for example "Diff to previous" doesn't make sense,
if there is no previous).
[is QUAL_NAME STRING] ... [else] ... [end]
[is QUAL_NAME QUAL_NAME] ... [else] ... [end]
The [is ...] directive is similar to the other conditional directives
above. But it allows to compare two value references or a value reference
with some constant string.
"""
#
# Copyright (C) 2001 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
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# This software is maintained by Greg and is available at:
# http://viewcvs.sourceforge.net/
# it is also used by the following projects:
# http://edna.sourceforge.net/
#
import string
import re
from types import StringType, IntType, FloatType
import os
#
# This regular expression matches three alternatives:
# expr: DIRECTIVE | BRACKET | COMMENT
# DIRECTIVE: '[' ITEM (whitespace ITEM)* ']
# ITEM: STRING | NAME
# STRING: '"' (not-slash-or-dquote | '\' anychar)* '"'
# NAME: (alphanum | '_' | '-' | '.')+
# BRACKET: '[[]'
# COMMENT: '[#' not-rbracket* ']'
#
# When used with the split() method, the return value will be composed of
# non-matching text and the two paren groups (DIRECTIVE and BRACKET). Since
# the COMMENT matches are not placed into a group, they are considered a
# "splitting" value and simply dropped.
#
_item = r'(?:"(?:[^\\"]|\\.)*"|[-\w.]+)'
_re_parse = re.compile(r'\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % (_item, _item))
_re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+')
# block commands and their argument counts
_block_cmd_specs = { 'if-any':1, 'if-index':2, 'for':1, 'is':2 }
_block_cmds = _block_cmd_specs.keys()
# two regular expresssions for compressing whitespace. the first is used to
# compress any whitespace including a newline into a single newline. the
# second regex is used to compress runs of whitespace into a single space.
_re_newline = re.compile('[ \t\r\f\v]*\n\\s*')
_re_whitespace = re.compile(r'\s\s+')
# this regex is used to substitute arguments into a value. we split the value,
# replace the relevant pieces, and then put it all back together. splitting
# will produce a list of: TEXT ( splitter TEXT )*. splitter will be '%' or
# an integer.
_re_subst = re.compile('%(%|[0-9]+)')
class Template:
def __init__(self, fname=None):
if fname:
self.parse_file(fname)
def parse_file(self, fname):
"""fname -> a string object with pathname of file containg an EZT template.
"""
self.program = self._parse_file(fname)
def parse(self, text):
"""text -> a string object containing the HTML template.
parse the template program into: (TEXT DIRECTIVE BRACKET)* TEXT
DIRECTIVE will be '[directive]' or None
BRACKET will be '[[]' or None
"""
self.program = self._parse(text)
def generate(self, fp, data):
ctx = _context()
ctx.data = data
ctx.for_index = { }
self._execute(self.program, fp, ctx)
def _parse_file(self, fname, for_names=None, file_args=()):
return self._parse(open(fname, "rt").read(), for_names, file_args,
os.path.dirname(fname))
def _parse(self, text, for_names=None, file_args=(), base=None):
"""text -> string object containing the HTML template.
This is a private helper function doing the real work for method parse.
It returns the parsed template as a 'program'. This program is a sequence
made out of strings or (function, argument) 2-tuples.
Note: comment directives [# ...] are automatically dropped by _re_parse.
"""
parts = _re_parse.split(text)
program = [ ]
stack = [ ]
if not for_names:
for_names = [ ]
for i in range(len(parts)):
piece = parts[i]
which = i % 3 # discriminate between: TEXT DIRECTIVE BRACKET
if which == 0:
# TEXT. append if non-empty.
if piece:
piece = _re_whitespace.sub(' ', _re_newline.sub('\n', piece))
program.append(piece)
elif which == 2:
# BRACKET directive. append '[' if present.
if piece:
program.append('[')
elif piece:
# DIRECTIVE is present.
args = _re_args.findall(piece)
cmd = args[0]
if cmd == 'else':
if len(args) > 1:
raise ArgCountSyntaxError()
### check: don't allow for 'for' cmd
idx = stack[-1][1]
true_section = program[idx:]
del program[idx:]
stack[-1][3] = true_section
elif cmd == 'end':
if len(args) > 1:
raise ArgCountSyntaxError()
# note: true-section may be None
cmd, idx, args, true_section = stack.pop()
else_section = program[idx:]
func = getattr(self, '_cmd_' + re.sub('-', '_', cmd))
program[idx:] = [ (func, (args, true_section, else_section)) ]
if cmd == 'for':
for_names.pop()
elif cmd in _block_cmds:
if len(args) > _block_cmd_specs[cmd] + 1:
raise ArgCountSyntaxError()
### this assumes arg1 is always a ref
args[1] = _prepare_ref(args[1], for_names, file_args)
# handle arg2 for the 'is' command
if cmd == 'is':
args[2] = _prepare_ref(args[2], for_names, file_args)
elif cmd == 'for':
for_names.append(args[1][0])
# remember the cmd, current pos, args, and a section placeholder
stack.append([cmd, len(program), args[1:], None])
elif cmd == 'include':
if args[1][0] == '"':
include_filename = args[1][1:-1]
if base:
include_filename = os.path.join(base, include_filename)
f_args = [ ]
for arg in args[2:]:
f_args.append(_prepare_ref(arg, for_names, file_args))
program.extend(self._parse_file(include_filename, for_names,
f_args))
else:
if len(args) != 2:
raise ArgCountSyntaxError()
program.append((self._cmd_include,
(_prepare_ref(args[1], for_names, file_args),
base)))
else:
# implied PRINT command
if len(args) > 1:
f_args = [ ]
for arg in args:
f_args.append(_prepare_ref(arg, for_names, file_args))
program.append((self._cmd_format, (f_args[0], f_args[1:])))
else:
program.append((self._cmd_print,
_prepare_ref(args[0], for_names, file_args)))
if stack:
### would be nice to say which blocks...
raise UnclosedBlocksError()
return program
def _execute(self, program, fp, ctx):
"""This private helper function takes a 'program' sequence as created
by the method '_parse' and executes it step by step. strings are written
to the file object 'fp' and functions are called.
"""
for step in program:
if isinstance(step, StringType):
fp.write(step)
else:
step[0](step[1], fp, ctx)
def _cmd_print(self, valref, fp, ctx):
value = _get_value(valref, ctx)
# if the value has a 'read' attribute, then it is a stream: copy it
if hasattr(value, 'read'):
while 1:
chunk = value.read(16384)
if not chunk:
break
fp.write(chunk)
else:
fp.write(value)
def _cmd_format(self, (valref, args), fp, ctx):
fmt = _get_value(valref, ctx)
parts = _re_subst.split(fmt)
for i in range(len(parts)):
piece = parts[i]
if i%2 == 1 and piece != '%':
idx = int(piece)
if idx < len(args):
piece = _get_value(args[idx], ctx)
else:
piece = '<undef>'
fp.write(piece)
def _cmd_include(self, (valref, base), fp, ctx):
fname = _get_value(valref, ctx)
if base:
fname = os.path.join(base, fname)
### note: we don't have the set of for_names to pass into this parse.
### I don't think there is anything to do but document it.
self._execute(self._parse_file(fname), fp, ctx)
def _cmd_if_any(self, args, fp, ctx):
"If the value is a non-empty string or non-empty list, then T else F."
((valref,), t_section, f_section) = args
value = _get_value(valref, ctx)
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_if_index(self, args, fp, ctx):
((valref, value), t_section, f_section) = args
list, idx = ctx.for_index[valref[0]]
if value == 'even':
value = idx % 2 == 0
elif value == 'odd':
value = idx % 2 == 1
elif value == 'first':
value = idx == 0
elif value == 'last':
value = idx == len(list)-1
else:
value = idx == int(value)
self._do_if(value, t_section, f_section, fp, ctx)
def _cmd_is(self, args, fp, ctx):
((left_ref, right_ref), t_section, f_section) = args
value = _get_value(right_ref, ctx)
value = string.lower(_get_value(left_ref, ctx)) == string.lower(value)
self._do_if(value, t_section, f_section, fp, ctx)
def _do_if(self, value, t_section, f_section, fp, ctx):
if t_section is None:
t_section = f_section
f_section = None
if value:
section = t_section
else:
section = f_section
if section is not None:
self._execute(section, fp, ctx)
def _cmd_for(self, args, fp, ctx):
((valref,), unused, section) = args
list = _get_value(valref, ctx)
if isinstance(list, StringType):
raise NeedSequenceError()
refname = valref[0]
ctx.for_index[refname] = idx = [ list, 0 ]
for item in list:
self._execute(section, fp, ctx)
idx[1] = idx[1] + 1
del ctx.for_index[refname]
def boolean(value):
"Return a value suitable for [if-any bool_var] usage in a template."
if value:
return 'yes'
return None
def _prepare_ref(refname, for_names, file_args):
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang"
for_names -> a list of active for sequences.
Returns a `value reference', a 3-Tupel made out of (refname, start, rest),
for fast access later.
"""
# is the reference a string constant?
if refname[0] == '"':
return None, refname[1:-1], None
# if this is an include-argument, then just return the prepared ref
if refname[:3] == 'arg':
try:
idx = int(refname[3:])
except ValueError:
pass
else:
if idx < len(file_args):
return file_args[idx]
parts = string.split(refname, '.')
start = parts[0]
rest = parts[1:]
while rest and (start in for_names):
# check if the next part is also a "for name"
name = start + '.' + rest[0]
if name in for_names:
start = name
del rest[0]
else:
break
return refname, start, rest
def _get_value((refname, start, rest), ctx):
"""(refname, start, rest) -> a prepared `value reference' (see above).
ctx -> an execution context instance.
Does a name space lookup within the template name space. Active
for blocks take precedence over data dictionary members with the
same name.
"""
if rest is None:
# it was a string constant
return start
if ctx.for_index.has_key(start):
list, idx = ctx.for_index[start]
ob = list[idx]
elif ctx.data.has_key(start):
ob = ctx.data[start]
else:
raise UnknownReference(refname)
# walk the rest of the dotted reference
for attr in rest:
try:
ob = getattr(ob, attr)
except AttributeError:
raise UnknownReference(refname)
# make sure we return a string instead of some various Python types
if isinstance(ob, IntType) or isinstance(ob, FloatType):
return str(ob)
if ob is None:
return ''
# string or a sequence
return ob
class _context:
"""A container for the execution context"""
class ArgCountSyntaxError(Exception):
pass
class UnknownReference(Exception):
pass
class NeedSequenceError(Exception):
pass
class UnclosedBlocksError(Exception):
pass
# --- standard test environment ---
def test_parse():
assert _re_parse.split('[a]') == ['', '[a]', None, '']
assert _re_parse.split('[a] [b]') == \
['', '[a]', None, ' ', '[b]', None, '']
assert _re_parse.split('[a c] [b]') == \
['', '[a c]', None, ' ', '[b]', None, '']
assert _re_parse.split('x [a] y [b] z') == \
['x ', '[a]', None, ' y ', '[b]', None, ' z']
assert _re_parse.split('[a "b" c "d"]') == \
['', '[a "b" c "d"]', None, '']
assert _re_parse.split(r'["a \"b[foo]" c.d f]') == \
['', '["a \\"b[foo]" c.d f]', None, '']
def _test(argv):
import doctest, ezt
verbose = "-v" in argv
return doctest.testmod(ezt, verbose=verbose)
if __name__ == "__main__":
# invoke unit test for this module:
import sys
sys.exit(_test(sys.argv)[0])

View File

@@ -1,347 +0,0 @@
#! /usr/bin/env python
# Module ndiff version 1.6.0
# Released to the public domain 08-Dec-2000,
# by Tim Peters (tim.one@home.com).
# Backported to Python 1.5.2 for ViewCVS by pf@artcom-gmbh.de, 24-Dec-2001
# Provided as-is; use at your own risk; no warranty; no promises; enjoy!
"""ndiff [-q] file1 file2
or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
Print a human-friendly file difference report to stdout. Both inter-
and intra-line differences are noted. In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
In the first form, if -q ("quiet") is not specified, the first two lines
of output are
-: file1
+: file2
Each remaining line begins with a two-letter code:
"- " line unique to file1
"+ " line unique to file2
" " line common to both files
"? " line not present in either input file
Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file. These lines can be
confusing if the source files contain tab characters.
The first file can be recovered by retaining only lines that begin with
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
The second file can be recovered similarly, but by retaining only " " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through
sed -n '/^[+ ] /s/^..//p'
See module comments for details and programmatic interface.
"""
__version__ = 1, 6, 1
# SequenceMatcher tries to compute a "human-friendly diff" between
# two sequences (chiefly picturing a file as a sequence of lines,
# and a line as a sequence of characters, here). Unlike e.g. UNIX(tm)
# diff, the fundamental notion is the longest *contiguous* & junk-free
# matching subsequence. That's what catches peoples' eyes. The
# Windows(tm) windiff has another interesting notion, pairing up elements
# that appear uniquely in each sequence. That, and the method here,
# appear to yield more intuitive difference reports than does diff. This
# method appears to be the least vulnerable to synching up on blocks
# of "junk lines", though (like blank lines in ordinary text files,
# or maybe "<P>" lines in HTML files). That may be because this is
# the only method of the 3 that has a *concept* of "junk" <wink>.
#
# Note that ndiff makes no claim to produce a *minimal* diff. To the
# contrary, minimal diffs are often counter-intuitive, because they
# synch up anywhere possible, sometimes accidental matches 100 pages
# apart. Restricting synch points to contiguous matches preserves some
# notion of locality, at the occasional cost of producing a longer diff.
#
# With respect to junk, an earlier version of ndiff simply refused to
# *start* a match with a junk element. The result was cases like this:
# before: private Thread currentThread;
# after: private volatile Thread currentThread;
# If you consider whitespace to be junk, the longest contiguous match
# not starting with junk is "e Thread currentThread". So ndiff reported
# that "e volatil" was inserted between the 't' and the 'e' in "private".
# While an accurate view, to people that's absurd. The current version
# looks for matching blocks that are entirely junk-free, then extends the
# longest one of those as far as possible but only with matching junk.
# So now "currentThread" is matched, then extended to suck up the
# preceding blank; then "private" is matched, and extended to suck up the
# following blank; then "Thread" is matched; and finally ndiff reports
# that "volatile " was inserted before "Thread". The only quibble
# remaining is that perhaps it was really the case that " volatile"
# was inserted after "private". I can live with that <wink>.
#
# NOTE on junk: the module-level names
# IS_LINE_JUNK
# IS_CHARACTER_JUNK
# can be set to any functions you like. The first one should accept
# a single string argument, and return true iff the string is junk.
# The default is whether the regexp r"\s*#?\s*$" matches (i.e., a
# line without visible characters, except for at most one splat).
# The second should accept a string of length 1 etc. The default is
# whether the character is a blank or tab (note: bad idea to include
# newline in this!).
#
# After setting those, you can call fcompare(f1name, f2name) with the
# names of the files you want to compare. The difference report
# is sent to stdout. Or you can call main(args), passing what would
# have been in sys.argv[1:] had the cmd-line form been used.
from difflib import SequenceMatcher
import string
TRACE = 0
# define what "junk" means
import re
def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match):
return pat(line) is not None
def IS_CHARACTER_JUNK(ch, ws=" \t"):
return ch in ws
del re
# meant for dumping lines
def dump(tag, x, lo, hi):
for i in xrange(lo, hi):
print tag, x[i],
def plain_replace(a, alo, ahi, b, blo, bhi):
assert alo < ahi and blo < bhi
# dump the shorter block first -- reduces the burden on short-term
# memory if the blocks are of very different sizes
if bhi - blo < ahi - alo:
dump('+', b, blo, bhi)
dump('-', a, alo, ahi)
else:
dump('-', a, alo, ahi)
dump('+', b, blo, bhi)
# When replacing one block of lines with another, this guy searches
# the blocks for *similar* lines; the best-matching pair (if any) is
# used as a synch point, and intraline difference marking is done on
# the similar pair. Lots of work, but often worth it.
def fancy_replace(a, alo, ahi, b, blo, bhi):
if TRACE:
print '*** fancy_replace', alo, ahi, blo, bhi
dump('>', a, alo, ahi)
dump('<', b, blo, bhi)
# don't synch up unless the lines have a similarity score of at
# least cutoff; best_ratio tracks the best score seen so far
best_ratio, cutoff = 0.74, 0.75
cruncher = SequenceMatcher(IS_CHARACTER_JUNK)
eqi, eqj = None, None # 1st indices of equal lines (if any)
# search for the pair that matches best without being identical
# (identical lines must be junk lines, & we don't want to synch up
# on junk -- unless we have to)
for j in xrange(blo, bhi):
bj = b[j]
cruncher.set_seq2(bj)
for i in xrange(alo, ahi):
ai = a[i]
if ai == bj:
if eqi is None:
eqi, eqj = i, j
continue
cruncher.set_seq1(ai)
# computing similarity is expensive, so use the quick
# upper bounds first -- have seen this speed up messy
# compares by a factor of 3.
# note that ratio() is only expensive to compute the first
# time it's called on a sequence pair; the expensive part
# of the computation is cached by cruncher
if cruncher.real_quick_ratio() > best_ratio and \
cruncher.quick_ratio() > best_ratio and \
cruncher.ratio() > best_ratio:
best_ratio, best_i, best_j = cruncher.ratio(), i, j
if best_ratio < cutoff:
# no non-identical "pretty close" pair
if eqi is None:
# no identical pair either -- treat it as a straight replace
plain_replace(a, alo, ahi, b, blo, bhi)
return
# no close pair, but an identical pair -- synch up on that
best_i, best_j, best_ratio = eqi, eqj, 1.0
else:
# there's a close pair, so forget the identical pair (if any)
eqi = None
# a[best_i] very similar to b[best_j]; eqi is None iff they're not
# identical
if TRACE:
print '*** best_ratio', best_ratio, best_i, best_j
dump('>', a, best_i, best_i+1)
dump('<', b, best_j, best_j+1)
# pump out diffs from before the synch point
fancy_helper(a, alo, best_i, b, blo, best_j)
# do intraline marking on the synch pair
aelt, belt = a[best_i], b[best_j]
if eqi is None:
# pump out a '-', '?', '+', '?' quad for the synched lines
atags = btags = ""
cruncher.set_seqs(aelt, belt)
for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes():
la, lb = ai2 - ai1, bj2 - bj1
if tag == 'replace':
atags = atags + '^' * la
btags = btags + '^' * lb
elif tag == 'delete':
atags = atags + '-' * la
elif tag == 'insert':
btags = btags + '+' * lb
elif tag == 'equal':
atags = atags + ' ' * la
btags = btags + ' ' * lb
else:
raise ValueError, 'unknown tag ' + `tag`
printq(aelt, belt, atags, btags)
else:
# the synch pair is identical
print ' ', aelt,
# pump out diffs from after the synch point
fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi)
def fancy_helper(a, alo, ahi, b, blo, bhi):
if alo < ahi:
if blo < bhi:
fancy_replace(a, alo, ahi, b, blo, bhi)
else:
dump('-', a, alo, ahi)
elif blo < bhi:
dump('+', b, blo, bhi)
# Crap to deal with leading tabs in "?" output. Can hurt, but will
# probably help most of the time.
def printq(aline, bline, atags, btags):
common = min(count_leading(aline, "\t"),
count_leading(bline, "\t"))
common = min(common, count_leading(atags[:common], " "))
print "-", aline,
if count_leading(atags, " ") < len(atags):
print "?", "\t" * common + atags[common:]
print "+", bline,
if count_leading(btags, " ") < len(btags):
print "?", "\t" * common + btags[common:]
def count_leading(line, ch):
i, n = 0, len(line)
while i < n and line[i] == ch:
i = i+1
return i
def fail(msg):
import sys
out = sys.stderr.write
out(msg + "\n\n")
out(__doc__)
return 0
# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
try:
return open(fname, 'r')
except IOError, detail:
return fail("couldn't open " + fname + ": " + str(detail))
# open two files & spray the diff to stdout; return false iff a problem
def fcompare(f1name, f2name):
f1 = fopen(f1name)
f2 = fopen(f2name)
if not f1 or not f2:
return 0
a = f1.readlines(); f1.close()
b = f2.readlines(); f2.close()
cruncher = SequenceMatcher(IS_LINE_JUNK, a, b)
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
if tag == 'replace':
fancy_replace(a, alo, ahi, b, blo, bhi)
elif tag == 'delete':
dump('-', a, alo, ahi)
elif tag == 'insert':
dump('+', b, blo, bhi)
elif tag == 'equal':
dump(' ', a, alo, ahi)
else:
raise ValueError, 'unknown tag ' + `tag`
return 1
# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem
def main(args):
import getopt
try:
opts, args = getopt.getopt(args, "qr:")
except getopt.error, detail:
return fail(str(detail))
noisy = 1
qseen = rseen = 0
for opt, val in opts:
if opt == "-q":
qseen = 1
noisy = 0
elif opt == "-r":
rseen = 1
whichfile = val
if qseen and rseen:
return fail("can't specify both -q and -r")
if rseen:
if args:
return fail("no args allowed with -r option")
if whichfile in "12":
restore(whichfile)
return 1
return fail("-r value must be 1 or 2")
if len(args) != 2:
return fail("need 2 filename args")
f1name, f2name = args
if noisy:
print '-:', f1name
print '+:', f2name
return fcompare(f1name, f2name)
def restore(which):
import sys
tag = {"1": "- ", "2": "+ "}[which]
prefixes = (" ", tag)
for line in sys.stdin.readlines():
if line[:2] in prefixes:
print line[2:],
if __name__ == '__main__':
import sys
args = sys.argv[1:]
if "-profile" in args:
import profile, pstats
args.remove("-profile")
statf = "ndiff.pro"
profile.run("main(args)", statf)
stats = pstats.Stats(statf)
stats.strip_dirs().sort_stats('time').print_stats()
else:
main(args)

View File

@@ -74,10 +74,7 @@ def popen(cmd, args, mode, capture_err=1):
try:
os.execvp(cmd, (cmd,) + tuple(args))
except:
# aid debugging, if the os.execvp above fails for some reason:
import string
print "<h2>exec failed:</h2><pre>", cmd, string.join(args), "</pre>"
raise
pass
# crap. shouldn't be here.
sys.exit(127)

View File

@@ -1,431 +1,102 @@
#!/usr/bin/python
# -*- Mode: python -*-
#
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
# CGI script to process and display queries to CVSdb
#
# This script is part of the ViewCVS package. More information can be
# found at http://www.lyra.org/viewcvs/.
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
CONF_PATHNAME = None
#########################################################################
import os
import sys
import string
import cgi
import time
import dbi
import cvsdb
import viewcvs
import ezt
class FormData:
def __init__(self, form):
self.valid = 0
## QueryEntry holds data on one match-type in the SQL database
## match is: "exact", "like", or "regex"
self.repository = ""
self.branch = ""
self.directory = ""
self.file = ""
self.who = ""
self.sortby = ""
self.date = ""
self.hours = 0
class QueryEntry:
def __init__(self, data, match):
self.data = data
self.match = match
self.decode_thyself(form)
def decode_thyself(self, form):
try:
self.repository = string.strip(form["repository"].value)
except KeyError:
pass
except TypeError:
pass
else:
self.valid = 1
## CheckinDatabaseQueryData is a object which contains the search parameters
## for a query to the CheckinDatabase
try:
self.branch = string.strip(form["branch"].value)
except KeyError:
pass
except TypeError:
pass
else:
self.valid = 1
class CheckinDatabaseQuery:
def __init__(self):
## sorting
self.sort = "date"
try:
self.directory = string.strip(form["directory"].value)
except KeyError:
pass
except TypeError:
pass
else:
self.valid = 1
## repository to query
self.repository_list = []
self.branch_list = []
self.directory_list = []
self.file_list = []
self.author_list = []
try:
self.file = string.strip(form["file"].value)
except KeyError:
pass
except TypeError:
pass
else:
self.valid = 1
## date range in DBI 2.0 timedate objects
self.from_date = None
self.to_date = None
try:
self.who = string.strip(form["who"].value)
except KeyError:
pass
except TypeError:
pass
else:
self.valid = 1
## list of commits -- filled in by CVS query
self.commit_list = []
try:
self.sortby = string.strip(form["sortby"].value)
except KeyError:
pass
except TypeError:
pass
## commit_cb provides a callback for commits as they
## are added
self.commit_cb = None
try:
self.date = string.strip(form["date"].value)
except KeyError:
pass
except TypeError:
pass
def SetRepository(self, repository, match = "exact"):
self.repository_list.append(QueryEntry(repository, match))
try:
self.hours = int(form["hours"].value)
except KeyError:
pass
except TypeError:
pass
except ValueError:
pass
else:
self.valid = 1
def SetBranch(self, branch, match = "exact"):
self.branch_list.append(QueryEntry(branch, match))
## returns a tuple-list (mod-str, string)
def listparse_string(str):
return_list = []
def SetDirectory(self, directory, match = "exact"):
self.directory_list.append(QueryEntry(directory, match))
cmd = ""
temp = ""
escaped = 0
state = "eat leading whitespace"
def SetFile(self, file, match = "exact"):
self.file_list.append(QueryEntry(file, match))
for c in str:
## handle escaped charactors
if not escaped and c == "\\":
escaped = 1
continue
def SetAuthor(self, author, match = "exact"):
self.author_list.append(QueryEntry(author, match))
## strip leading white space
if state == "eat leading whitespace":
if c in string.whitespace:
continue
else:
state = "get command or data"
def SetSortMethod(self, sort):
self.sort = sort
## parse to '"' or ","
if state == "get command or data":
def SetFromDateObject(self, ticks):
self.from_date = dbi.TimestampFromTicks(ticks)
## just add escaped charactors
if escaped:
escaped = 0
temp = temp + c
continue
def SetToDateObject(self, ticks):
self.to_date = dbi.TimestampFromTicks(ticks)
## the data is in quotes after the command
elif c == "\"":
cmd = temp
temp = ""
state = "get quoted data"
continue
def SetFromDateHoursAgo(self, hours_ago):
ticks = time.time() - (3600 * hours_ago)
self.from_date = dbi.TimestampFromTicks(ticks)
## this tells us there was no quoted data, therefore no
## command; add the command and start over
elif c == ",":
## strip ending whitespace on un-quoted data
temp = string.rstrip(temp)
return_list.append( ("", temp) )
temp = ""
state = "eat leading whitespace"
continue
def SetFromDateDaysAgo(self, days_ago):
ticks = time.time() - (86400 * days_ago)
self.from_date = dbi.TimestampFromTicks(ticks)
## record the data
else:
temp = temp + c
continue
def SetToDateDaysAgo(self, days_ago):
ticks = time.time() - (86400 * days_ago)
self.to_date = dbi.TimestampFromTicks(ticks)
## parse until ending '"'
if state == "get quoted data":
def AddCommit(self, commit):
self.commit_list.append(commit)
if self.commit_cb:
self.commit_cb(commit)
## just add escaped charactors
if escaped:
escaped = 0
temp = temp + c
continue
def SetCommitCB(self, callback):
self.commit_cb = callback
## look for ending '"'
elif c == "\"":
return_list.append( (cmd, temp) )
cmd = ""
temp = ""
state = "eat comma after quotes"
continue
## record the data
else:
temp = temp + c
continue
## parse until ","
if state == "eat comma after quotes":
if c in string.whitespace:
continue
elif c == ",":
state = "eat leading whitespace"
continue
else:
print "format error"
sys.exit(1)
if cmd or temp:
return_list.append((cmd, temp))
return return_list
def decode_command(cmd):
if cmd == "r":
return "regex"
elif cmd == "l":
return "like"
else:
return "exact"
def form_to_cvsdb_query(form_data):
query = cvsdb.CreateCheckinQuery()
if form_data.repository:
for cmd, str in listparse_string(form_data.repository):
cmd = decode_command(cmd)
query.SetRepository(str, cmd)
if form_data.branch:
for cmd, str in listparse_string(form_data.branch):
cmd = decode_command(cmd)
query.SetBranch(str, cmd)
if form_data.directory:
for cmd, str in listparse_string(form_data.directory):
cmd = decode_command(cmd)
query.SetDirectory(str, cmd)
if form_data.file:
for cmd, str in listparse_string(form_data.file):
cmd = decode_command(cmd)
query.SetFile(str, cmd)
if form_data.who:
for cmd, str in listparse_string(form_data.who):
cmd = decode_command(cmd)
query.SetAuthor(str, cmd)
if form_data.sortby == "author":
query.SetSortMethod("author")
elif form_data.sortby == "file":
query.SetSortMethod("file")
else:
query.SetSortMethod("date")
if form_data.date:
if form_data.date == "hours" and form_data.hours:
query.SetFromDateHoursAgo(form_data.hours)
elif form_data.date == "day":
query.SetFromDateDaysAgo(1)
elif form_data.date == "week":
query.SetFromDateDaysAgo(7)
elif form_data.date == "month":
query.SetFromDateDaysAgo(31)
return query
def cvsroot_name_from_path(cvsroot):
## we need to resolve the cvsroot path from the database
## to the name given to it in the viewcvs.conf file
for key, value in cfg.general.cvs_roots.items():
if value == cvsroot:
return key
return None
def build_commit(desc, files):
ob = _item(num_files=len(files), files=[])
if desc:
ob.desc = string.replace(cgi.escape(desc), '\n', '<br>')
else:
ob.desc = '&nbsp;'
for commit in files:
ctime = commit.GetTime()
if not ctime:
ctime = "&nbsp";
else:
ctime = time.strftime("%y/%m/%d %H:%M", time.localtime(ctime))
## make the file link
file = os.path.join(commit.GetDirectory(), commit.GetFile())
file_full_path = os.path.join(commit.GetRepository(), file)
## if we couldn't find the cvsroot path configured in the
## viewcvs.conf file, then don't make the link
cvsroot_name = cvsroot_name_from_path(commit.GetRepository())
if cvsroot_name:
flink = '<a href="viewcvs.cgi/%s?cvsroot=%s">%s</a>' \
% (file, cvsroot_name, file_full_path)
else:
flink = file_full_path
ob.files.append(_item(date=ctime,
author=commit.GetAuthor(),
link=flink,
rev=commit.GetRevision(),
branch=commit.GetBranch(),
plus=int(commit.GetPlusCount()),
minus=int(commit.GetMinusCount()),
))
return ob
def run_query(form_data):
query = form_to_cvsdb_query(form_data)
db = cvsdb.ConnectDatabaseReadOnly()
db.RunQuery(query)
if not query.commit_list:
return [ ]
commits = [ ]
files = [ ]
current_desc = query.commit_list[0].GetDescription()
for commit in query.commit_list:
desc = commit.GetDescription()
if current_desc == desc:
files.append(commit)
continue
commits.append(build_commit(current_desc, files))
files = [ commit ]
current_desc = desc
## add the last file group to the commit list
commits.append(build_commit(current_desc, files))
return commits
def handle_config():
viewcvs.handle_config()
global cfg
cfg = viewcvs.cfg
def main():
handle_config()
form = cgi.FieldStorage()
form_data = FormData(form)
if form_data.valid:
commits = run_query(form_data)
query = None
else:
commits = [ ]
query = 'skipped'
data = {
'cfg' : cfg,
'address' : cfg.general.address,
'vsn' : viewcvs.__version__,
'repository' : cgi.escape(form_data.repository, 1),
'branch' : cgi.escape(form_data.branch, 1),
'directory' : cgi.escape(form_data.directory, 1),
'file' : cgi.escape(form_data.file, 1),
'who' : cgi.escape(form_data.who, 1),
'sortby' : form_data.sortby,
'date' : form_data.date,
'query' : query,
'commits' : commits,
'num_commits' : len(commits),
}
if form_data.hours:
data['hours'] = form_data.hours
else:
data['hours'] = 2
template = ezt.Template()
template.parse_file(os.path.join(viewcvs.g_install_dir,
cfg.templates.query))
viewcvs.http_header()
# generate the page
template.generate(sys.stdout, data)
def run_cgi():
### be nice to share all this logic with viewcvs.run_cgi
try:
main()
except SystemExit, e:
# don't catch SystemExit (caused by sys.exit()). propagate the exit code
sys.exit(e[0])
except:
info = sys.exc_info()
viewcvs.http_header()
print '<html><head><title>Python Exception Occurred</title></head>'
print '<body bgcolor=white><h1>Python Exception Occurred</h1>'
import traceback
lines = apply(traceback.format_exception, info)
print '<pre>'
print cgi.escape(string.join(lines, ''))
print '</pre>'
viewcvs.html_footer()
class _item:
def __init__(self, **kw):
vars(self).update(kw)
## entrypoints
def CreateCheckinQuery():
return CheckinDatabaseQuery()

View File

@@ -13,10 +13,24 @@
# -----------------------------------------------------------------------
#
import os
import string
import re
import time
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
CONF_PATHNAME = None
#########################################################################
import os, sys, string, re, time, config
## load configuration file, the data is used globally here
cfg = config.Config()
cfg.set_defaults()
cfg.load_config(CONF_PATHNAME)
## RLogOutputParser uses the output of rlog to build a list of Commit
@@ -80,7 +94,7 @@ class RLogEntry:
class RLog:
"Provides a alternative file-like interface for running 'rlog'."
def __init__(self, cfg, filename, revision, date):
def __init__(self, filename, revision, date):
self.filename = self.fix_filename(filename)
self.checkout_filename = self.create_checkout_filename(self.filename)
self.revision = revision
@@ -310,10 +324,7 @@ class RLogOutputParser:
gmt_time = \
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
if time.localtime(gmt_time)[8] == 1:
# dst time active?
# XXX: This is still wrong in those both nights,
# where the switch between DST and normal time occurs.
if time.daylight:
gmt_time = gmt_time - time.altzone
else:
gmt_time = gmt_time - time.timezone
@@ -333,7 +344,7 @@ class RLogOutputParser:
## entrypoints
def GetRLogData(cfg, path, revision = '', date = ''):
rlog = RLog(cfg, path, revision, date)
def GetRLogData(path, revision = '', date = ''):
rlog = RLog(path, revision, date)
rlog_parser = RLogOutputParser(rlog)
return rlog_parser.rlog_data

File diff suppressed because it is too large Load Diff

View File

@@ -1,514 +0,0 @@
#!/usr/bin/env python
# $Id$
# vim:sw=4:ts=4:et:nowrap
# [Emacs: -*- python -*-]
#
# Copyright (C) 1999-2001 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 ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# This file: Peter Funk, Oldenburger Str.86, 27777 Ganderkesee, Germany
# ViewCVS project: Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# Note: this module is designed to deploy instantly and run under any
# version of Python from 1.5 and up. That's why some 2.0 features
# (like string methods) are conspicuously avoided.
# XXX Security issues?
"""Run "standalone.py -p <port>" to start an HTTP server on a given port
on the local machine to generate ViewCVS web pages.
"""
__author__ = "Peter Funk <pf@artcom-gmbh.de>"
__date__ = "11 November 2001"
__version__ = "$Revision$"
__credits__ = """Guido van Rossum, for an excellent programming language.
Greg Stein, for writing ViewCVS in the first place.
Ka-Ping Yee, for the GUI code and the framework stolen from pydoc.py.
"""
# INSTALL-TIME CONFIGURATION
#
# This value will be set during the installation process. During
# development, it will remain None.
#
LIBRARY_DIR = None
import sys
import os
import string
import urllib
import socket
import select
import BaseHTTPServer
class Options:
port = 7467 # default TCP/IP port used for the server
start_gui = 0 # No GUI unless requested.
repository = None # use default repository specified in config
# --- web browser interface: ----------------------------------------------
def serve(port, callback=None):
"""start a HTTP server on the given port. call 'callback' when the
server is ready to serve"""
class ViewCVS_Handler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
"""Serve a GET request."""
if not self.path or self.path == "/":
self.redirect()
elif self.is_viewcvs():
self.run_viewcvs()
elif self.path[:7] == "/icons/":
# XXX icon type should not be hardcoded to GIF:
self.send_response(200)
self.send_header("Content-type", "image/gif")
self.end_headers()
apache_icons.serve_icon(self.path, self.wfile)
else:
self.send_error(404)
def do_POST(self):
"""Serve a POST request."""
if self.is_viewcvs():
self.run_viewcvs()
else:
self.send_error(501, "Can only POST to viewcvs")
def is_viewcvs(self):
"""Check whether self.path matches the hardcoded ScriptAlias
/viewcvs"""
if self.path[:8] == "/viewcvs":
return 1
return 0
def redirect(self):
"""redirect the browser to the viewcvs URL"""
self.send_response(301, "moved (redirection follows)")
self.send_header("Content-type", "text/html")
self.send_header("Location", self.server.url + 'viewcvs/')
self.end_headers()
self.wfile.write("""<html>
<head>
<meta http-equiv="refresh" content="1; URL=%s">
</head>
<body>
<h1>Redirection to <a href="%s">ViewCVS</a></h1>
Wait a second. You will be automatically redirected to <b>ViewCVS</b>.
If this doesn't work, please click on the link above.
</body>
</html>
""" % tuple([self.server.url + "viewcvs/"]*2))
def run_viewcvs(self):
"""This is a quick and dirty cut'n'rape from Pythons
standard library module CGIHTTPServer."""
assert self.path[:8] == "/viewcvs"
viewcvs_url, rest = self.server.url[:-1]+"/viewcvs", self.path[8:]
i = string.rfind(rest, '?')
if i >= 0:
rest, query = rest[:i], rest[i+1:]
else:
query = ''
i = string.find(rest, '/')
if i >= 0:
script, rest = rest[:i], rest[i:]
else:
script, rest = rest, ''
scriptname = viewcvs_url + script
# sys.stderr.write("Debug: '"+scriptname+"' '"+rest+"' '"+query+"'\n")
env = os.environ
# Since we're going to modify the env in the parent, provide empty
# values to override previously set values
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
if env.has_key(k):
env[k] = ""
# XXX Much of the following could be prepared ahead of time!
env['SERVER_SOFTWARE'] = self.version_string()
env['SERVER_NAME'] = self.server.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PROTOCOL'] = self.protocol_version
env['SERVER_PORT'] = str(self.server.server_port)
env['REQUEST_METHOD'] = self.command
uqrest = urllib.unquote(rest)
env['PATH_INFO'] = uqrest
env['SCRIPT_NAME'] = scriptname
if query:
env['QUERY_STRING'] = query
host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0]
# AUTH_TYPE
# REMOTE_USER
# REMOTE_IDENT
if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type
else:
env['CONTENT_TYPE'] = self.headers.typeheader
length = self.headers.getheader('content-length')
if length:
env['CONTENT_LENGTH'] = length
accept = []
for line in self.headers.getallmatchingheaders('accept'):
if line[:1] in string.whitespace:
accept.append(string.strip(line))
else:
accept = accept + string.split(line[7:], ',')
env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
ua = self.headers.getheader('user-agent')
if ua:
env['HTTP_USER_AGENT'] = ua
# XXX Other HTTP_* headers
decoded_query = string.replace(query, '+', ' ')
self.send_response(200)
# FIXME: I'm not sure about this: Sometimes it hurts, sometimes
# it is required. Please enlight me
if 1:
self.send_header("Content-type", "text/html")
self.end_headers()
# Preserve state, because we execute script in current process:
save_argv = sys.argv
save_stdin = sys.stdin
save_stdout = sys.stdout
save_stderr = sys.stderr
# For external tools like enscript we also need to redirect
# the real stdout file descriptor:
save_realstdout = os.dup(1)
try:
try:
sys.stdout = self.wfile
os.close(1)
assert os.dup(self.wfile.fileno()) == 1
sys.stdin = self.rfile
viewcvs.run_cgi()
finally:
sys.argv = save_argv
sys.stdin = save_stdin
sys.stdout.flush()
os.close(1)
assert os.dup(save_realstdout) == 1
sys.stdout = save_stdout
sys.stderr = save_stderr
except SystemExit, status:
self.log_error("ViewCVS exit status %s", str(status))
else:
self.log_error("ViewCVS exited ok")
class ViewCVS_Server(BaseHTTPServer.HTTPServer):
def __init__(self, port, callback):
host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
self.address = ('', port)
self.url = 'http://%s:%d/' % (host, port)
self.callback = callback
BaseHTTPServer.HTTPServer.__init__(self, self.address,
self.handler)
def serve_until_quit(self):
self.quit = 0
while not self.quit:
rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
if rd:
self.handle_request()
def server_activate(self):
BaseHTTPServer.HTTPServer.server_activate(self)
if self.callback:
self.callback(self)
def server_bind(self):
# set SO_REUSEADDR (if available on this platform)
if hasattr(socket, 'SOL_SOCKET') \
and hasattr(socket, 'SO_REUSEADDR'):
self.socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
BaseHTTPServer.HTTPServer.server_bind(self)
ViewCVS_Server.handler = ViewCVS_Handler
try:
# XXX Move this code out of this function.
# Early loading of configuration here. Used to
# allow tinkering with some configuration settings:
viewcvs.handle_config()
if options.repository:
if viewcvs.cfg.general.cvs_roots.has_key("Development"):
viewcvs.cfg.general.cvs_roots["Development"] = options.repository
else:
sys.stderr.write("*** No default ViewCVS configuration. Edit viewcvs.conf\n")
raise KeyboardInterrupt # Hack!
elif viewcvs.cfg.general.cvs_roots.has_key("Development") and \
not os.path.isdir(viewcvs.cfg.general.cvs_roots["Development"]):
sys.stderr.write("*** No repository found. Please use the -r option.\n")
sys.stderr.write(" Use --help for more info.\n")
raise KeyboardInterrupt # Hack!
os.close(0) # To avoid problems with shell job control
ViewCVS_Server(port, callback).serve_until_quit()
except (KeyboardInterrupt, select.error):
pass
print 'server stopped'
# --- graphical interface: --------------------------------------------------
def gui(port):
"""Graphical interface (starts web server and pops up a control window)."""
class GUI:
def __init__(self, window, port):
self.window = window
self.server = None
self.scanner = None
import Tkinter
self.server_frm = Tkinter.Frame(window)
self.title_lbl = Tkinter.Label(self.server_frm,
text='Starting server...\n ')
self.open_btn = Tkinter.Button(self.server_frm,
text='open browser', command=self.open, state='disabled')
self.quit_btn = Tkinter.Button(self.server_frm,
text='quit serving', command=self.quit, state='disabled')
self.window.title('ViewCVS standalone')
self.window.protocol('WM_DELETE_WINDOW', self.quit)
self.title_lbl.pack(side='top', fill='x')
self.open_btn.pack(side='left', fill='x', expand=1)
self.quit_btn.pack(side='right', fill='x', expand=1)
# Early loading of configuration here. Used to
# allow tinkering with configuration settings through the gui:
viewcvs.handle_config()
if not LIBRARY_DIR:
viewcvs.cfg.options.cvsgraph_conf = "../cgi/cvsgraph.conf.dist"
self.options_frm = Tkinter.Frame(window)
# cvsgraph toggle:
self.cvsgraph_ivar = Tkinter.IntVar()
self.cvsgraph_ivar.set(viewcvs.cfg.options.use_cvsgraph)
self.cvsgraph_toggle = Tkinter.Checkbutton(self.options_frm,
text="enable cvsgraph (needs binary)", var=self.cvsgraph_ivar,
command=self.toggle_use_cvsgraph)
self.cvsgraph_toggle.pack(side='top', anchor='w')
# enscript toggle:
self.enscript_ivar = Tkinter.IntVar()
self.enscript_ivar.set(viewcvs.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(viewcvs.cfg.options.show_subdir_lastmod)
self.subdirmod_toggle = Tkinter.Checkbutton(self.options_frm,
text="show subdir last mod (dir view)", var=self.subdirmod_ivar,
command=self.toggle_subdirmod)
self.subdirmod_toggle.pack(side='top', anchor='w')
# use_re_search toggle:
self.useresearch_ivar = Tkinter.IntVar()
self.useresearch_ivar.set(viewcvs.cfg.options.use_re_search)
self.useresearch_toggle = Tkinter.Checkbutton(self.options_frm,
text="allow regular expr search", var=self.useresearch_ivar,
command=self.toggle_useresearch)
self.useresearch_toggle.pack(side='top', anchor='w')
# directory view template:
self.dirtemplate_lbl = Tkinter.Label(self.options_frm,
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(viewcvs.cfg.templates.directory)
self.dirtemplate_entry = Tkinter.Entry(self.options_frm,
width = 40, textvariable=self.dirtemplate_svar)
self.dirtemplate_entry.bind('<Return>', self.set_templates_directory)
self.dirtemplate_entry.pack(side='top', anchor='w')
self.templates_dir = Tkinter.Radiobutton(self.options_frm,
text="directory.ezt", value="templates/directory.ezt",
var=self.dirtemplate_svar, command=self.set_templates_directory)
self.templates_dir.pack(side='top', anchor='w')
self.templates_dir_alt = Tkinter.Radiobutton(self.options_frm,
text="dir_alternate.ezt", value="templates/dir_alternate.ezt",
var=self.dirtemplate_svar, command=self.set_templates_directory)
self.templates_dir_alt.pack(side='top', anchor='w')
# log view template:
self.logtemplate_lbl = Tkinter.Label(self.options_frm,
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(viewcvs.cfg.templates.log)
self.logtemplate_entry = Tkinter.Entry(self.options_frm,
width = 40, textvariable=self.logtemplate_svar)
self.logtemplate_entry.bind('<Return>', self.set_templates_log)
self.logtemplate_entry.pack(side='top', anchor='w')
self.templates_log = Tkinter.Radiobutton(self.options_frm,
text="log.ezt", value="templates/log.ezt",
var=self.logtemplate_svar, command=self.set_templates_log)
self.templates_log.pack(side='top', anchor='w')
self.templates_log_table = Tkinter.Radiobutton(self.options_frm,
text="log_table.ezt", value="templates/log_table.ezt",
var=self.logtemplate_svar, command=self.set_templates_log)
self.templates_log_table.pack(side='top', anchor='w')
# query view template:
self.querytemplate_lbl = Tkinter.Label(self.options_frm,
text='Template for the database query page:')
self.querytemplate_lbl.pack(side='top', anchor='w')
self.querytemplate_svar = Tkinter.StringVar()
self.querytemplate_svar.set(viewcvs.cfg.templates.query)
self.querytemplate_entry = Tkinter.Entry(self.options_frm,
width = 40, textvariable=self.querytemplate_svar)
self.querytemplate_entry.bind('<Return>', self.set_templates_query)
self.querytemplate_entry.pack(side='top', anchor='w')
self.templates_query = Tkinter.Radiobutton(self.options_frm,
text="query.ezt", value="templates/query.ezt",
var=self.querytemplate_svar, command=self.set_templates_query)
self.templates_query.pack(side='top', anchor='w')
# pack and set window manager hints:
self.server_frm.pack(side='top', fill='x')
self.options_frm.pack(side='top', fill='x')
self.window.update()
self.minwidth = self.window.winfo_width()
self.minheight = self.window.winfo_height()
self.expanded = 0
self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
self.window.wm_minsize(self.minwidth, self.minheight)
import threading
threading.Thread(target=serve, args=(port, self.ready)).start()
def toggle_use_cvsgraph(self, event=None):
viewcvs.cfg.options.use_cvsgraph = self.cvsgraph_ivar.get()
def toggle_use_enscript(self, event=None):
viewcvs.cfg.options.use_enscript = self.enscript_ivar.get()
def toggle_subdirmod(self, event=None):
viewcvs.cfg.options.show_subdir_lastmod = self.subdirmod_ivar.get()
def toggle_useresearch(self, event=None):
viewcvs.cfg.options.use_re_search = self.useresearch_ivar.get()
def set_templates_log(self, event=None):
viewcvs.cfg.templates.log = self.logtemplate_svar.get()
def set_templates_directory(self, event=None):
viewcvs.cfg.templates.directory = self.dirtemplate_svar.get()
def set_templates_query(self, event=None):
viewcvs.cfg.templates.query = self.querytemplate_svar.get()
def ready(self, server):
"""used as callback parameter to the serve() function"""
self.server = server
self.title_lbl.config(
text='ViewCVS standalone server at\n' + server.url)
self.open_btn.config(state='normal')
self.quit_btn.config(state='normal')
def open(self, event=None, url=None):
"""opens a browser window on the local machine"""
url = url or self.server.url
try:
import webbrowser
webbrowser.open(url)
except ImportError: # pre-webbrowser.py compatibility
if sys.platform == 'win32':
os.system('start "%s"' % url)
elif sys.platform == 'mac':
try:
import ic
ic.launchurl(url)
except ImportError: pass
else:
rc = os.system('netscape -remote "openURL(%s)" &' % url)
if rc: os.system('netscape "%s" &' % url)
def quit(self, event=None):
if self.server:
self.server.quit = 1
self.window.quit()
import Tkinter
try:
gui = GUI(Tkinter.Tk(), port)
Tkinter.mainloop()
except KeyboardInterrupt:
pass
# --- command-line interface: ----------------------------------------------
def cli(argv):
"""Command-line interface (looks at argv to decide what to do)."""
import getopt
class BadUsage(Exception): pass
try:
opts, args = getopt.getopt(argv[1:], 'gp:r:',
['gui', 'port=', 'repository='])
for opt, val in opts:
if opt in ('-g', '--gui'):
options.start_gui = 1
elif opt in ('-r', '--repository'):
options.repository = val
elif opt in ('-p', '--port'):
try:
options.port = int(val)
except ValueError:
raise BadUsage
if options.start_gui:
gui(options.port)
return
elif options.port:
def ready(server):
print 'server ready at %s' % server.url
serve(options.port, ready)
return
raise BadUsage
except (getopt.error, BadUsage):
cmd = sys.argv[0]
port = options.port
print """ViewCVS standalone - a simple standalone HTTP-Server
Usage: %(cmd)s [ <options> ]
Available Options:
-p <port> or --port=<port>
Start an HTTP server on the given port on the local machine.
Default port is %(port)d.
-r <path> or --repository=<path>
Specify another path for the default CVS repository "Development".
If you don't have your repository at /home/cvsroot you will need to
use this option or you have to install first and edit viewcvs.conf.
-g or --gui
Pop up a graphical interface for serving and testing ViewCVS.
""" % locals()
if __name__ == '__main__':
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path[:0] = ['lib']
os.chdir('lib')
import viewcvs
import apache_icons
options = Options()
cli(sys.argv)

View File

@@ -1,3 +0,0 @@
[include "header.ezt" "annotate"]
<hr noshade>

View File

@@ -1,88 +0,0 @@
[include "header.ezt" "diff"]
<h3 align=center>Diff for /[where] between version [rev1] and [rev2]</h3>
<table border=0 cellspacing=0 cellpadding=0 width="100%">
<tr bgcolor=white>
<th width="50%" valign=top>
version [rev1][date1]
[if-any tag1]<br>Tag: [tag1][end]
</th>
<th width="50%" valign=top>
version [rev2][date2]
[if-any tag2]<br>Tag: [tag2][end]
</th>
</tr>
[for changes]
[is changes.type "header"]
<tr bgcolor="#99cccc"><td width="50%">
<table width="100%" border=1 cellpadding=5><tr>
<td><b>Line [changes.line1]</b>&nbsp;<font
size="-1">[changes.extra]</font></td>
</tr></table></td><td width="50%">
<table width="100%" border=1 cellpadding=5><tr>
<td><b>Line [changes.line2]</b>&nbsp;<font
size="-1">[changes.extra]</font></td>
</tr></table>
</td></tr>
[else]
[is changes.type "add"]
<tr>
<td bgcolor="#cccccc">&nbsp;</td>
<td bgcolor="#aaffaa">[changes.right]</td>
</tr>
[else]
[is changes.type "remove"]
<tr>
<td bgcolor="#ffaaaa">[changes.left]</td>
<td bgcolor="#cccccc">&nbsp;</td>
</tr>
[else]
[is changes.type "change"]
<tr>
[if-any changes.have_left]<td bgcolor="#ffff77">[changes.left]</td>
[else]<td bgcolor="#eeee77">&nbsp;</td>[end]
[if-any changes.have_right]<td bgcolor="#ffff77">[changes.right]</td>
[else]<td bgcolor="#eeee77">&nbsp;</td>[end]
</tr>
[else]
[is changes.type "no-changes"]
<tr><td colspan=2>&nbsp;</td></tr>
<tr bgcolor="#cccccc"><td colspan=2 align=center>
<br><b>- No changes -</b><br>&nbsp;
</td></tr>
[else][# a line of context]
<tr><td>[changes.left]</td><td>[changes.right]</td></tr>
[end][end][end][end][end]
[end]
</table><br><hr noshade width="100%">
<table border=0 cellpadding=10><tr><td>
<table border=1><tr><td>Legend:<br>
<table border=0 cellspacing=0 cellpadding=1>
<tr>
<td align=center bgcolor="#ffaaaa">Removed from v.[rev1]</td>
<td bgcolor="#cccccc">&nbsp;</td>
</tr>
<tr bgcolor="#ffff77"><td align=center colspan=2>changed lines</td></tr>
<tr>
<td bgcolor="#cccccc">&nbsp;</td>
<td align=center bgcolor="#aaffaa">Added in v.[rev2]</td>
</tr>
</table></td></tr></table></td>
<td><form method="GET" action="[request.url]">
[hidden_values]
<select name="diff_format" onchange="submit()">
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
</select>
<input type=submit value="Show">
</form></td></tr>
</table>
[include "footer.ezt"]

View File

@@ -1,307 +0,0 @@
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><head>
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
by Greg Stein -- mailto:gstein@lyra.org
-->
<title>[if-any where][where][else][cfg.general.main_title][end]</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=2><h1>[if-any where][where][else][cfg.general.main_title][end]</h1></td>
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
width=259 height=32></td>
</tr>
<tr>
<td align=right><h3><b><a target="_blank"
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">ViewCVS and CVS Help</a></b></h3></td>
</tr>
</table>
[if-any where][else]
<!-- you may insert repository access instructions here -->
[if-any roots]
<h3>Project Root</h3>
<form method=GET action="./">
<select name=cvsroot onchange="submit()">
[for roots]
[is roots current_root]
<option selected>[roots]</option>
[else]
<option>[roots]</option>
[end]
[end]
</select>
<input type=submit value="Go">
</form>
[end]
[end]
<p><a name="dirlist"></a></p>
[if-any where]
<table>
<tr><td>Current directory:</td><td><b>[nav_path]</b></td></tr>
[if-any view_tag]
<tr><td>Current tag:</td><td><b>[view_tag]</b></td></tr>
[end]
[if-any search_re]
<tr><td>Current search:</td><td><b>[search_re]</b></td></tr>
[end]
[is num_files "0"]
[else]
<tr><td>Files shown:</td><td><b>[files_shown]</b></td></tr>
[end]
</table>
[end]
<hr noshade>
[# if you want a colored border around the table of directory
information, then add this additional table tag:
<table border=0 cellpadding=0 width="100%"><tr>
<td bgcolor="#999999">
]
<table width="100%" border=0 cellspacing=1 cellpadding=2>
<tr>
[if-any have_logs]
[is sortby "rev"]
<th align=left bgcolor="#88ff88">Rev.</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_rev_href]"#dirlist>Rev.</a></th>
[end]
[is sortby "file"]
<th align=left bgcolor="#88ff88"
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
>File</th>
[else]
<th align=left bgcolor="#cccccc"
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
><a href="./[sortby_file_href]#dirlist">File</a></th>
[end]
[is sortby "date"]
<th align=left bgcolor="#88ff88">Age</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_date_href]#dirlist">Age</a></th>
[end]
[is sortby "author"]
<th align=left bgcolor="#88ff88">Author</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_author_href]#dirlist">Author</a></th>
[end]
[is sortby "log"]
<th align=left bgcolor="#88ff88">Last log entry</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_log_href]#dirlist">Last log entry</a></th>
[end]
[else]
<th align=left bgcolor="#cccccc">File</th>
[end]
</tr>
[for rows]
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
[is rows.type "unreadable"]
[if-any have_logs]
<td>&nbsp;</td> [# revision ]
[end]
<td><a name="[rows.anchor]">[rows.name]</a>
[if-any have_logs]
</td>
<td colspan=[is cfg.options.use_cvsgraph "1"]4[else]3[end]>
<i>this entry is unreadable</i>
</td>
[else]
- <i>this entry is unreadable</i>
[end]
</td>
[else]
[is rows.type "dir"]
[if-any have_logs]
<td>&nbsp;</td> [# revision ]
[end]
<td [if-any have_logs][is cfg.options.use_cvsgraph "1"]colspan=2[end][end]>
<a name="[rows.anchor]" href="[rows.href]">
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
[rows.name]
</a>
[is rows.name "Attic/"]
&nbsp; <a href="./[show_attic_href]#dirlist">[[]show contents]</a>
[end]
</td>
[is rows.cvs "error"]
[# for an error to occur, we must have some logs. always use colspan]
<td colspan=3>
<i>Last modification unavailable - could not read CVS information</i>
</td>
[else]
[if-any have_logs]
[is rows.cvs "none"]
<td>&nbsp;</td> [# age ]
<td>&nbsp;</td> [# author ]
<td>&nbsp;</td> [# log ]
[else]
<td>&nbsp;[rows.time]</td>
[if-any rows.author]
<td>&nbsp;[rows.author]</td>
[end]
[if-any rows.show_log]
<td>&nbsp;[rows.log_file]/[rows.log_rev]<br>
&nbsp;<font size="-1">[rows.log]</font></td>
[end]
[end]
[end]
[end]
[else]
[is rows.cvs "error"]
<td>&nbsp;</td>
<td [is cfg.options.use_cvsgraph "1"]colspan=2[end]>
<a name="[rows.anchor]">[rows.name]</a>
</td>
<td colspan=3><i>CVS information is unreadable</i></td>
[else]
<td>&nbsp;<a href="[rows.href]"><b>[rows.rev]</b></a></td>
<td width="1%"><a name="[rows.anchor]" href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
[# to display the revision in a separate window, you could use:
<a name="{rows.anchor}" href="{rows.rev_href}" target="cvs_checkout"
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
'resizeable=1,scrollbars=1')"><b>{rows.rev}</b></a>
(substitute brackets for the braces)
]
<img src="/icons/small/text.gif" alt="(file)" border=0
width=16 height=16>
[rows.name]
</a>
[is rows.state "dead"]
[# don't let this phrase/link be wrapped ]
[if-any view_tag](not&nbsp;exist)[else](in&nbsp;the&nbsp;Attic)[end]&nbsp;<a href="./[hide_attic_href]#dirlist">[[]hide]</a>
[end]
</td>
[if-any rows.graph_href]
<td width="1%"><a href="[rows.graph_href]"><img
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
alt="(graph)" width=16 height=16 border=0>
</a></td>
[end]
<td>&nbsp;[rows.time]</td>
[if-any rows.author]
<td>&nbsp;[rows.author]</td>
[end]
[if-any rows.show_log]
<td>&nbsp;[rows.log]</td>
[end]
[end]
[end]
[end]
</tr>
[end]
</table>
[# if you are using a table border (see above), then add:
</td></tr></table>
]
[if-any no_match]
<p><b>NOTE:</b> There are [num_files] files, but none match the
current selection criteria.
[end]
[if-any unreadable]
<hr size=1 noshade>
<b>NOTE:</b> One or more files were unreadable. The files in the CVS
repository should be readable by the web server process. Please
report this condition to the administrator of this CVS repository.
[end]
[if-any selection_form]
<hr size=1 noshade>
[# this table holds the selectors on the left, and reset on the right ]
<table><tr><td>
<form method=GET action="./">
[for params]
<input type=hidden name="[params.name]" value="[params.value]">
[end]
<table>
[if-any has_tags]
<tr>
<td>Show files using tag:</td>
<td>
<select name=only_with_tag onchange="submit()">
[if-any branch_tags]
<option value="">- Branches -</option>
[for branch_tags]
[is branch_tags view_tag]
<option selected>[branch_tags]</option>
[else]
<option>[branch_tags]</option>
[end]
[end]
[end]
<option value="">- Non-branch tags -</option>
[for plain_tags]
[is plain_tags view_tag]
<option selected>[plain_tags]</option>
[else]
<option>[plain_tags]</option>
[end]
[end]
</select></td>
</tr>
[end]
[is cfg.options.use_re_search "1"]
<tr>
<td>Show files containing the regular expression:</td>
<td><input type="text" name="search" value="[search_re]">
</tr>
[end]
<tr><td>&nbsp;</td><td><input type="submit" value="Show"></td></tr>
</table>
</form>
</td>
[if-any view_tag]
<td valign=bottom><form method=GET action="./">
[for params]
<input type=hidden name="[params.name]" value="[params.value]">
[end]
<input type="submit" value="Show all files">
</form></td>
[else][if-any search_re]
<td valign=bottom><form method=GET action="./">
[for params]
<input type=hidden name="[params.name]" value="[params.value]">
[end]
<input type="submit" value="Show all files">
</form></td>
[end][end]
</tr></table>
[end]
[# if you want to disable tarball generation remove the following: ]
[if-any tarball_href]
<a href="[tarball_href]">Download tarball</a>
[end]
[include "footer.ezt"]

View File

@@ -1,305 +0,0 @@
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><head>
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
by Greg Stein -- mailto:gstein@lyra.org
-->
<title>[if-any where][where][else][cfg.general.main_title][end]</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=2><h1>[if-any where][where][else][cfg.general.main_title][end]</h1></td>
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
width=259 height=32></td>
</tr>
<tr>
<td align=right><h3><b><a target="_blank"
href="[request.script_name]/*docroot*/help_[if-any where]dir[else]root[end]view.html">ViewCVS and CVS Help</a></b></h3></td>
</tr>
</table>
[if-any where][else]
<!-- you may insert repository access instructions here -->
[if-any roots]
<h3>Project Root</h3>
<form method=GET action="./">
<select name=cvsroot onchange="submit()">
[for roots]
[is roots current_root]
<option selected>[roots]</option>
[else]
<option>[roots]</option>
[end]
[end]
</select>
<input type=submit value="Go">
</form>
[end]
[end]
<p><a name="dirlist"></a></p>
[if-any where]
<table>
<tr><td>Current directory:</td><td><b>[nav_path]</b></td></tr>
[if-any view_tag]
<tr><td>Current tag:</td><td><b>[view_tag]</b></td></tr>
[end]
[if-any search_re]
<tr><td>Current search:</td><td><b>[search_re]</b></td></tr>
[end]
[is num_files "0"]
[else]
<tr><td>Files shown:</td><td><b>[files_shown]</b></td></tr>
[end]
</table>
[end]
<hr noshade>
[# if you want a colored border around the table of directory
information, then add this additional table tag:
<table border=0 cellpadding=0 width="100%"><tr>
<td bgcolor="#999999">
]
<table width="100%" border=0 cellspacing=1 cellpadding=2>
<tr>
[if-any have_logs]
[is sortby "file"]
<th align=left bgcolor="#88ff88"
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
>File</th>
[else]
<th align=left bgcolor="#cccccc"
[is cfg.options.use_cvsgraph "1"]colspan=2[end]
><a href="./[sortby_file_href]#dirlist">File</a></th>
[end]
[is sortby "rev"]
<th align=left bgcolor="#88ff88">Rev.</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_rev_href]"#dirlist>Rev.</a></th>
[end]
[is sortby "date"]
<th align=left bgcolor="#88ff88">Age</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_date_href]#dirlist">Age</a></th>
[end]
[is sortby "author"]
<th align=left bgcolor="#88ff88">Author</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_author_href]#dirlist">Author</a></th>
[end]
[is sortby "log"]
<th align=left bgcolor="#88ff88">Last log entry</th>
[else]
<th align=left bgcolor="#cccccc"
><a href="./[sortby_log_href]#dirlist">Last log entry</a></th>
[end]
[else]
<th align=left bgcolor="#cccccc">File</th>
[end]
</tr>
[for rows]
<tr bgcolor="[if-index rows even]#ffffff[else]#ccccee[end]">
[is rows.type "unreadable"]
<td><a name="[rows.anchor]">[rows.name]</a>
[if-any have_logs]
</td>
<td colspan=[is cfg.options.use_cvsgraph "1"]5[else]4[end]>
<i>this entry is unreadable</i>
</td>
[else]
- <i>this entry is unreadable</i>
[end]
</td>
[else]
[is rows.type "dir"]
<td [if-any have_logs][is cfg.options.use_cvsgraph "1"]colspan=2[end][end]>
<a name="[rows.anchor]" href="[rows.href]">
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
[rows.name]
</a>
[is rows.name "Attic/"]
&nbsp; <a href="./[show_attic_href]#dirlist">[[]show contents]</a>
[end]
</td>
[is rows.cvs "error"]
[# for an error to occur, we must have some logs. always use colspan]
<td colspan=4>
<i>Last modification unavailable - could not read CVS information</i>
</td>
[else]
[if-any have_logs]
<td>&nbsp;</td> [# revision ]
[is rows.cvs "none"]
<td>&nbsp;</td> [# age ]
<td>&nbsp;</td> [# author ]
<td>&nbsp;</td> [# log ]
[else]
<td>&nbsp;[rows.time]</td>
[if-any rows.author]
<td>&nbsp;[rows.author]</td>
[end]
[if-any rows.show_log]
<td>&nbsp;[rows.log_file]/[rows.log_rev]<br>
&nbsp;<font size="-1">[rows.log]</font></td>
[end]
[end]
[end]
[end]
[else]
[is rows.cvs "error"]
<td [is cfg.options.use_cvsgraph "1"]colspan=2[end]>
<a name="[rows.anchor]">[rows.name]</a>
</td>
<td colspan=4><i>CVS information is unreadable</i></td>
[else]
<td><a name="[rows.anchor]" href="[rows.href]">
<img src="/icons/small/text.gif" alt="(file)" border=0
width=16 height=16>
[rows.name]
</a>
[is rows.state "dead"]
[# don't let this phrase/link be wrapped ]
[if-any view_tag](not&nbsp;exist)[else](in&nbsp;the&nbsp;Attic)[end]&nbsp;<a href="./[hide_attic_href]#dirlist">[[]hide]</a>
[end]
</td>
[if-any rows.graph_href]
<td width="1%"><a href="[rows.graph_href]"><img
src="[request.script_name]/*docroot*/images/cvsgraph_16x16.png"
alt="(graph)" width=16 height=16 border=0>
</a></td>
[end]
<td>&nbsp;
<a href="[rows.rev_href]&content-type=text/vnd.viewcvs-markup">
<b>[rows.rev]</b>
</a>
[# to display the revision in a separate window, you could use:
<a href="{rows.rev_href}" target="cvs_checkout"
onClick="window.open('{rows.rev_href}', 'cvs_checkout',
'resizeable=1,scrollbars=1')"><b>{rows.rev}</b></a>
(substitute brackets for the braces)
]
</td>
<td>&nbsp;[rows.time]</td>
[if-any rows.author]
<td>&nbsp;[rows.author]</td>
[end]
[if-any rows.show_log]
<td>&nbsp;[rows.log]</td>
[end]
[end]
[end]
[end]
</tr>
[end]
</table>
[# if you are using a table border (see above), then add:
</td></tr></table>
]
[if-any no_match]
<p><b>NOTE:</b> There are [num_files] files, but none match the
current selection criteria.
[end]
[if-any unreadable]
<hr size=1 noshade>
<b>NOTE:</b> One or more files were unreadable. The files in the CVS
repository should be readable by the web server process. Please
report this condition to the administrator of this CVS repository.
[end]
[if-any selection_form]
<hr size=1 noshade>
[# this table holds the selectors on the left, and reset on the right ]
<table><tr><td>
<form method=GET action="./">
[for params]
<input type=hidden name="[params.name]" value="[params.value]">
[end]
<table>
[if-any has_tags]
<tr>
<td>Show files using tag:</td>
<td>
<select name=only_with_tag onchange="submit()">
[if-any branch_tags]
<option value="">- Branches -</option>
[for branch_tags]
[is branch_tags view_tag]
<option selected>[branch_tags]</option>
[else]
<option>[branch_tags]</option>
[end]
[end]
[end]
<option value="">- Non-branch tags -</option>
[for plain_tags]
[is plain_tags view_tag]
<option selected>[plain_tags]</option>
[else]
<option>[plain_tags]</option>
[end]
[end]
</select></td>
</tr>
[end]
[is cfg.options.use_re_search "1"]
<tr>
<td>Show files containing the regular expression:</td>
<td><input type="text" name="search" value="[search_re]">
</tr>
[end]
<tr><td>&nbsp;</td><td><input type="submit" value="Show"></td></tr>
</table>
</form>
</td>
[if-any view_tag]
<td valign=bottom><form method=GET action="./">
[for params]
<input type=hidden name="[params.name]" value="[params.value]">
[end]
<input type="submit" value="Show all files">
</form></td>
[else][if-any search_re]
<td valign=bottom><form method=GET action="./">
[for params]
<input type=hidden name="[params.name]" value="[params.value]">
[end]
<input type="submit" value="Show all files">
</form></td>
[end][end]
</tr></table>
[end]
[# if you want to disable tarball generation remove the following: ]
[if-any tarball_href]
<a href="[tarball_href]">Download tarball</a>
[end]
[include "footer.ezt"]

View File

@@ -1,9 +0,0 @@
[# standard footer used by all ViewCVS pages ]
<hr noshade>
<table width="100%" border=0 cellpadding=0 cellspacing=0><tr>
<td align=left><address>[cfg.general.address]</address></td>
<td align=right>
Powered by<br><a href="http://viewcvs.sourceforge.net/">ViewCVS [vsn]</a>
</td></tr></table>
</body></html>

View File

@@ -1,12 +0,0 @@
[include "header.ezt" "graph"]
<center>
<h1>Revision graph of [request.where]</h1>
[imagemap]
<img border="0" usemap="#MyMapName"
src="[request.url]?graph=[rev]&makeimage=1[request.amp_query]"
alt="Revisions of [request.where]">
</center>
[include "footer.ezt"]

View File

@@ -1,25 +0,0 @@
<html><head>
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
by Greg Stein -- mailto:gstein@lyra.org
-->
[### NOTE: the "diff" is the TITLE param to navigate_header() ]
<title>[path]/[filename] - [arg0] - [rev]</title>
</head>
<body bgcolor="#eeeeee">
<table width="100%" border=0 cellspacing=0 cellpadding=1 bgcolor="#9999ee">
<tr valign=bottom>
<td>
<a href="[file_url][qquery]#rev[rev]">
<img src="/icons/small/back.gif" alt="(file)" border=0 width=16 height=16>
</a>
<b>Return to
<a href="[file_url][qquery]#rev[rev]">[filename]</a>
CVS log</b>
<img src="/icons/small/text.gif" alt="(file)" border=0 width=16 height=16>
</td>
<td align=right>
<img src="/icons/small/dir.gif" alt="(dir)" border=0 width=16 height=16>
<b>Up to [nav_path]</b>
</td>
</tr>
</table>

View File

@@ -1,246 +0,0 @@
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><head>
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
by Greg Stein -- mailto:gstein@lyra.org
-->
<title>CVS log for [where]</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=2><h1>CVS log for [where]</h1></td>
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
width=259 height=32></td>
</tr>
<tr>
<td align=right><h3><b><a target="_blank"
href="[request.script_name]/*docroot*/help_log.html">Help</a></b></h3></td>
</tr>
</table>
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
width=16 height=16></a>
<b>Up to [nav_path]</b><p>
<a href="#diff">Request diff between arbitrary revisions</a>
[if-any graph_href]
/ <a href="[graph_href]">Display revisions graphically</a>
[end]
<hr noshade>
[if-any branch]
Default branch: [branch]
<br>
Bookmark a link to:
[if-any viewable]
<a href="[head_href]"><b>HEAD</b></a>
/
(<a href="[head_abs_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
><b>download</b></a>)
[else]
<a href="[head_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
><b>HEAD</b></a>
[end]
[else]
No default branch
[end]
<br>
[if-any view_tag]
Current tag: [view_tag] <br>
[end]
[for entries]
<hr size=1 noshade>
[is entries.state "dead"]
Revision <b>[entries.rev]</b>
[else]
<a name="rev[entries.rev]"></a>
[for entries.tag_names]<a name="[entries.tag_names]"></a>
[end]
[for entries.branch_names]<a name="[entries.branch_names]"></a>
[end]
Revision
[if-any viewable]
<a href="[entries.href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
><b>[entries.rev]</b></a>
[else]
<a href="[entries.view_href]"><b>[entries.rev]</b></a>
/
(<a href="[entries.href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
><b>download</b></a>)
[end]
[is mime_type "text/plain"]
[else]
/
(<a href="[entries.text_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1')"><b>as text</b></a>)
[end]
[if-any viewable]
/
<a href="[entries.view_href]"><b>(view)</b></a>
[end]
[# if you don't want to allow annotation, then remove this line]
- <a href="[href]?annotate=[entries.rev][query]">annotate</a>
[# if you don't want to allow select for diffs then remove this section]
[is entries.rev rev_selected]
- <b>[[]selected]</b>
[else]
- <a href="[href]?r1=[entries.rev][query]">[[]select for diffs]</a>
[end]
[end]
[if-any entries.vendor_branch]
<i>(vendor branch)</i>
[end]
, <i>[entries.utc_date] UTC</i> ([entries.ago] ago) by <i>[entries.author]</i>
[if-any entries.branches]
<br>Branch:
[for entries.branches]
<a href="[entries.branches.href]"><b>[entries.branches.name]</b></a>[if-index entries.branches last][else],[end]
[end]
[end]
[if-any entries.tags]
<br>CVS Tags:
[for entries.tags]
<a href="[entries.tags.href]"><b>[entries.tags.name]</b></a>[if-index entries.tags last][else],[end]
[end]
[end]
[if-any entries.branch_points]
<br>Branch point for:
[for entries.branch_points]
<a href="[entries.branch_points.href]"><b>[entries.branch_points.name]</b></a>[if-index entries.branch_points last][else],[end]
[end]
[end]
[if-any entries.prev]
[if-any entries.changed]
<br>Changes since <b>[entries.prev]: [entries.changed] lines</b>
[end]
[end]
[is entries.state "dead"]
<br><b><i>FILE REMOVED</i></b>
[else]
[if-any entries.prev]
<br>Diff to <a href="[href].diff?r1=[entries.prev]&r2=[entries.rev][query]">previous [entries.prev]</a>
[if-any human_readable]
[else]
(<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
[end]
[end]
[if-any entries.branch_point]
to <a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev][query]">branch point [entries.branch_point]</a>
[if-any human_readable]
[else]
(<a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
[end]
[end]
[if-any entries.next_main]
to <a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev][query]">next main [entries.next_main]</a>
[if-any human_readable]
[else]
(<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
[end]
[end]
[if-any entries.to_selected]
[if-any entries.prev][else]<br>Diff[end]
to <a href="[href].diff?r1=[rev_selected]&r2=[entries.rev][query]">selected [rev_selected]</a>
[if-any human_readable]
[else]
(<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev]&diff_format=h[query]">colored</a>)
[end]
[end]
[end]
<pre>[entries.html_log]</pre>
[end]
<a name=diff></a>
<hr noshade>
This form allows you to request diffs between any two revisions of
a file. You may select a symbolic revision name using the selection
box or you may type in a numeric name using the type-in text box.
<p>
<form method="GET" action="[href].diff[qquery]" name="diff_select">
Diffs between
<select name="r1">
<option value="text" selected>Use Text Field</option>
[for tags]
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
[end]
</select>
<input type="TEXT" size="12" name="tr1" value="[tr1]"
onChange="document.diff_select.r1.selectedIndex=0">
and
<select name="r2">
<option value="text" selected>Use Text Field</option>
[for tags]
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
[end]
</select>
<input type="TEXT" size="12" name="tr2" value="[tr2]"
onChange="document.diff_select.r1.selectedIndex=0">
<br>Type of Diff should be a
<select name="diff_format" onchange="submit()">
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
</select>
<input type=submit value=" Get Diffs "></form>
<hr noshade>
[if-any branch_names]
<a name=branch></a>
<form method="GET" action="[href]">
[hidden_values]
View only Branch:
<select name="only_with_tag" onchange="submit()">
<option value="" [is view_tag ""]selected[end]>Show all branches</option>
[for branch_names]
<option value="[branch_names]" [is branch_names view_tag]selected[end]>[branch_names]</option>
[end]
</select>
<input type=submit value=" View Branch ">
</form>
[end]
<a name=logsort></a>
<form method="GET" action="[href]">
[hidden_values]
Sort log by:
<select name="logsort" onchange="submit()">
<option value="cvs" [is logsort "cvs"]selected[end]>Not sorted</option>
<option value="date" [is logsort "date"]selected[end]>Commit date</option>
<option value="rev" [is logsort "rev"]selected[end]>Revision</option>
</select>
<input type=submit value=" Sort ">
</form>
[include "footer.ezt"]

View File

@@ -1,251 +0,0 @@
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><head>
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
by Greg Stein -- mailto:gstein@lyra.org
-->
<title>CVS log for [where]</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr>
<td rowspan=2><h1>CVS log for [where]</h1></td>
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
width=259 height=32></td>
</tr>
<tr>
<td align=right><h3><b><a target="_blank"
href="[request.script_name]/*docroot*/help_logtable.html">Help</a></b></h3></td>
</tr>
</table>
<a href="[back_url]"><img src="/icons/small/back.gif" alt="(back)" border=0
width=16 height=16></a>
<b>Up to [nav_path]</b><p>
<a href="#diff">Request diff between arbitrary revisions</a>
[if-any graph_href]
/ <a href="[graph_href]">Display revisions graphically</a>
[end]
<hr noshade>
[if-any branch]
Default branch: [branch]
<br>
Bookmark a link to:
[if-any viewable]
<a href="[head_href]"><b>HEAD</b></a>
/
(<a href="[head_abs_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
><b>download</b></a>)
[else]
<a href="[head_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]')"
><b>HEAD</b></a>
[end]
[else]
No default branch
[end]
<br>
[if-any view_tag]
Current tag: [view_tag] <br>
[end]
<hr noshade>
<table width="100%" border=0 cellspacing=1 cellpadding=2>
<th align=left bgcolor="#88ff88">Revision</th>
<th align=left bgcolor="#88ff88">Tasks</th>
<th align=left bgcolor="#88ff88">Diffs</th>
<th align=left bgcolor="#88ff88">Branches and Tags</th>
<th align=left bgcolor="#88ff88">Age</th>
<th align=left bgcolor="#88ff88">Author</th>
[for entries]
<tr valign="top" bgcolor="[if-index entries even]#ffffff[else]#ccccee[end]">
[# Revision column]
<td rowspan=2>
<b>[entries.rev]</b>
<a name="rev[entries.rev]"></a>
</td>
<td>
[# Tasks column]
<a href="[entries.href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,menubar=1,scrollbars=1[is mime_type "text/html"],status,
toolbar[end]')"><b>Download</b></a><br>
[is mime_type "text/plain"]
[else]
<a href="[entries.text_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1')"><b>View as text</b></a><br>
[end]
[if-any viewable]
<a href="[entries.view_href]"><b>View [entries.rev]</b></a><br>
[end]
[# if you don't want to allow annotation, then remove this line]
<a href="[href]?annotate=[entries.rev][query]"><b>View annotated</b></a><br>
</td>
<td>
[# if you don't want to allow select for diffs then remove this section]
[is entries.rev rev_selected]
<b>[[]selected]</b><br>
[else]
<a href="[href]?r1=[entries.rev][query]"><b>[[]select&nbsp;for&nbsp;diffs]</b></a><br>
[end]
[is entries.state "dead"]
<b><i>FILE REMOVED</i></b>
[else]
[if-any entries.prev]
<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev][query]"><b>Diff&nbsp;to&nbsp;previous&nbsp;[entries.prev]</b></a><br>
[if-any human_readable]
[else]
<a href="[href].diff?r1=[entries.prev]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
[end]
[end]
[if-any entries.to_selected]
<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev][query]"><b>Diff&nbsp;to&nbsp;selected&nbsp;[rev_selected]</b></a><br>
[if-any human_readable]
[else]
<a href="[href].diff?r1=[rev_selected]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
[end]
[end]
[end]
</td>
<td>
[# Branches column]
[if-any entries.vendor_branch]
<i>vendor branch</i><br>
[end]
[if-any entries.branches]
[for entries.branches]
<a href="[entries.branches.href]"><b>[entries.branches.name]</b></a><br>
[end]
[end]
[if-any entries.branch_points]
Branch point for:
[for entries.branch_points]
<a href="[entries.branch_points.href]"><b>[entries.branch_points.name]</b></a><br>
[end]
[end]
[if-any entries.next_main]
<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev][query]"><b>Diff&nbsp;to&nbsp;next&nbsp;MAIN&nbsp;[entries.next_main]</b></a><br>
[if-any human_readable]
[else]
<a href="[href].diff?r1=[entries.next_main]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
[end]
[end]
[if-any entries.branch_point]
<a href="[href].diff?r1=[entries.branch_point]&r2=[entries.rev][query]"><b>Diff&nbsp;to&nbsp;branch&nbsp;point&nbsp;[entries.branch_point]</b></a><br>
[if-any human_readable]
[else]
<a href="[href].diff?r1=[entries.branches.name]&r2=[entries.rev]&diff_format=h[query]"><b>colored</b></a><br>
[end]
[end]
[# Tags ]
[if-any entries.tags]
<form action="[href]" >
<select name="only_with_tag" onChange="submit()">
<option value="" [is view_tag ""]selected[end]>Show all tags</option>
[for entries.tags]
<option [is view_tag entries.tags.name]selected[end]>[entries.tags.name]</option>
[end]
</select>
</form>
[else]&nbsp;
[end]
</td>
[# Time column]
<td>
[entries.ago] ago<br><i>[entries.utc_date]</i>
</td>
[# Author column]
<td>
[entries.author]
</td>
</tr>
<tr bgcolor="[if-index entries even]#ffffff[else]#ccccee[end]">
<td colspan=5><b>Log: </b><i><pre>[entries.html_log]</pre></i></td>
</tr>
[end]
</table>
<a name=diff></a>
<hr noshade>
This form allows you to request diffs between any two revisions of
a file. You may select a symbolic revision name using the selection
box or you may type in a numeric name using the type-in text box.
<p>
<form method="GET" action="[href].diff[qquery]" name="diff_select">
Diffs between
<select name="r1">
<option value="text" selected>Use Text Field</option>
[for tags]
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
[end]
</select>
<input type="TEXT" size="12" name="tr1" value="[tr1]"
onChange="document.diff_select.r1.selectedIndex=0">
and
<select name="r2">
<option value="text" selected>Use Text Field</option>
[for tags]
<option value="[tags.rev]:[tags.name]">[tags.name]</option>
[end]
</select>
<input type="TEXT" size="12" name="tr2" value="[tr2]"
onChange="document.diff_select.r1.selectedIndex=0">
<br>Type of Diff should be a
<select name="diff_format" onchange="submit()">
<option value="h" [is diff_format "h"]selected[end]>Colored Diff</option>
<option value="l" [is diff_format "l"]selected[end]>Long Colored Diff</option>
<option value="u" [is diff_format "u"]selected[end]>Unidiff</option>
<option value="c" [is diff_format "c"]selected[end]>Context Diff</option>
<option value="s" [is diff_format "s"]selected[end]>Side by Side</option>
</select>
<input type=submit value=" Get Diffs "></form>
<hr noshade>
[if-any branch_names]
<a name=branch></a>
<form method="GET" action="[href]">
[hidden_values]
View only Branch:
<select name="only_with_tag" onchange="submit()">
<option value="" [is view_tag ""]selected[end]>Show all branches</option>
[for branch_names]
<option value="[branch_names]" [is branch_names view_tag]selected[end]>[branch_names]</option>
[end]
</select>
<input type=submit value=" View Branch ">
</form>
[end]
<a name=logsort></a>
<form method="GET" action="[href]">
[hidden_values]
Sort log by:
<select name="logsort" onchange="submit()">
<option value="cvs" [is logsort "cvs"]selected[end]>Not sorted</option>
<option value="date" [is logsort "date"]selected[end]>Commit date</option>
<option value="rev" [is logsort "rev"]selected[end]>Revision</option>
</select>
<input type=submit value=" Sort ">
</form>
[include "footer.ezt"]

View File

@@ -1,49 +0,0 @@
[include "header.ezt" "view"]
<hr noshade>
<table width="100%"><tr><td bgcolor="#ffffff">
File: [nav_file]
(<a href="[href]" target="cvs_checkout"
onClick="window.open('about:blank','cvs_checkout',
'resizeable=1,scrollbars=1[is mime_type "text/html"],status,toolbar[end]');"
><b>download</b></a>)
[is mime_type "text/plain"]
[else]
/
(<a href="[text_href]" target="cvs_checkout"
onClick="window.open('about:blank', 'cvs_checkout',
'resizeable=1,scrollbars=1')"><b>as text</b></a>)
[end]
<br>
[if-any log]
Revision: <b>[rev]</b>[if-any vendor_branch] <i>(vendor branch)</i>[end],
<i>[utc_date] UTC</i> ([ago] ago) by <i>[author]</i>
[if-any branches]
<br>Branch: <b>[branches]</b>
[end]
[if-any tags]
<br>CVS Tags: <b>[tags]</b>
[end]
[if-any branch_points]
<br>Branch point for: <b>[branch_points]</b>
[end]
[if-any prev]
[if-any changed]
<br>Changes since <b>[prev]: [changed] lines</b>
[end]
[end]
[is state "dead"]
<br><b><i>FILE REMOVED</i></b>
[end]
<pre>[log]</pre>
[else]
Revision: <b>[rev]</b><br>
[if-any tag]
Tag: <b>[tag]</b><br>
[end]
[end]
</td></tr></table>
<hr noshade>

View File

@@ -1,228 +0,0 @@
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><head>
<!-- ViewCVS -- http://viewcvs.sourceforge.net/
by Greg Stein -- mailto:gstein@lyra.org
-->
<title>[cfg.general.main_title]</title>
</head>
<body text="#000000" bgcolor="#ffffff">
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr>
<td><h1>[cfg.general.main_title]</h1></td>
<td align=right><img src="/icons/apache_pb.gif" alt="(logo)" border=0
width=259 height=32></td>
</tr>
</table>
<p>
Select your parameters for querying the CVS commit database. 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 <i>jpaint</i> and <i>gstein</i>, just type: <b>jpaint,
gstein</b> in the <i>Author</i> 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:
<b>"jpaint", "gstein"</b>.
</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 charactor preceeds the
first quote. The command charactor <b>l</b> is for wildcard
searches, and the wildcard charactor is a percent (<b>%</b>). The
command charactor for regular expressions is <b>r</b>, 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 <i>.py</i> extention is:
<b>l"%.py"</b> in the <i>File</i> input box. The same search done
with a regular expression is: <b>r".*\.py"</b>.
</p>
<p>
All search types can be mixed, as long as they are seperated by
commas.
</p>
<form method=get action="query.cgi">
<table border=0 cellspacing=0 cellpadding=2 width=100% bgcolor=e6e6e6>
<tr>
<td>
<table>
<tr>
<td valign=top>
<table>
<tr>
<td align=right>CVS Repository:</td>
<td>
<input type=text name=repository size=40 value="[repository]">
</td>
</tr>
<tr>
<td align=right>CVS Branch:</td>
<td>
<input type=text name=branch size=40 value="[branch]">
</td>
</tr>
<tr>
<td align=right>Directory:</td>
<td>
<input type=text name=directory size=40 value="[directory]">
</td>
</tr>
<tr>
<td align=right>File:</td>
<td>
<input type=text name=file size=40 value="[file]">
</td>
</tr>
<tr>
<td align=right>Author:</td>
<td>
<input type=text name=who size=40 value="[who]">
</td>
</tr>
</table>
</td>
<td valign=top>
<table>
<tr>
<td align=left>Sort By:</td>
<td>
<select name=sortby>
<option value=date [is sortby "date"]selected[end]>Date</option>
<option value=author [is sortby "author"]selected[end]>Author</option>
<option value=file [is sortby "file"]selected[end]>File</option>
</select>
</td>
</tr>
<tr>
<td colspan=2>
<table border=0 cellspacing=0 cellpadding=0>
<tr>
<td>Date:</td>
</tr>
<tr>
<td><input type=radio name=date value=hours
[is date "hours"]checked[end]></td>
<td>In the last
<input type=text name=hours value=[hours] size=4>hours
</td>
</tr>
<tr>
<td><input type=radio name=date value=day
[is date "day"]checked[end]></td>
<td>In the last day</td>
</tr>
<tr>
<td><input type=radio name=date value=week
[is date "week"]checked[end]></td>
<td>In the last week</td>
</tr>
<tr>
<td><input type=radio name=date value=month
[is date "month"]checked[end]></td>
<td>In the last month</td>
</tr>
<tr>
<td><input type=radio name=date value=all
[is date "all"]checked[end]></td>
<td>Since the beginning of time</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<input type=submit value="Search">
</td>
</tr>
</table>
</form>
[is query "skipped"]
[else]
<p><b>[num_commits]</b> matches found.</p>
[if-any commits]
<table width="100%" border=0 cellspacing=0 cellpadding=2>
<tr bgcolor="#88ff88">
<th align=left valign=top>Revision</th>
<th align=left valign=top>File</th>
<th align=left valign=top>Branch</th>
<th align=left valign=top>+/-</th>
<th align=left valign=top>Date</th>
<th align=left valign=top>Author</th>
[# uncommment, if you want a separate Description column: (also see below)
<th align=left valign=top>Description</th>
]
</tr>
[for commits]
[for commits.files]
<tr bgcolor="[if-index commits even]#ffffff[else]#ccccee[end]">
<td align=left valign=top>
[if-any commits.files.rev][commits.files.rev][else]&nbsp;[end]
</td>
<td align=left valign=top>[commits.files.link]</td>
<td align=left valign=top>
[if-any commits.files.branch][commits.files.branch][else]&nbsp;[end]
</td>
<td align=left valign=top>[commits.files.plus]/[commits.files.minus]</td>
<td align=left valign=top>
[if-any commits.files.date][commits.files.date][else]&nbsp;[end]
</td>
<td align=left valign=top>
[if-any commits.files.author][commits.files.author][else]&nbsp;[end]
</td>
[# uncommment, if you want a separate Description column:
{if-index commits.files first{
<td align=left valign=top rowspan={commits.num_files}>
{commits.desc}
</td>
{end}
(substitute brackets for the braces)
]
</tr>
[# and also take the following out in the "Description column"-case:]
[if-index commits.files last]
<tr bgcolor="[if-index commits even]#ffffff[else]#ccccee[end]">
<td>&nbsp;</td>
<td colspan=5><b>Log:</b><br>
<pre>[commits.desc]</pre></td>
</tr>
[end]
[# ---]
[end]
[end]
<tr bgcolor="#88ff88">
<th align=left valign=top>&nbsp;</th>
<th align=left valign=top>&nbsp;</th>
<th align=left valign=top>&nbsp;</th>
<th align=left valign=top>&nbsp;</th>
<th align=left valign=top>&nbsp;</th>
<th align=left valign=top>&nbsp;</th>
<th align=left valign=top>&nbsp;</th>
</tr>
</table>
[end]
[end]
[include "footer.ezt"]

View File

@@ -1,116 +0,0 @@
#!/usr/bin/env python
## vim:ts=4:et:nowrap
# [Emacs: -*- python -*-]
"""bin2inline_py.py -- creates Python source from directories containing images.
This is a very simple tool to pack a lot of small icons into a single file.
This version is a quick rape-and-past for the ViewCVS project.
Run this script from within the tools subdirectory to recreate the source file
'../lib/apache_icons.py'.
"""
import sys, os, string, fnmatch
PREAMBLE="""#! /usr/bin/env python
# This file was automatically generated! DO NOT EDIT!
# Howto regenerate: see ../tools/bin2inline.py
# $Id$
# You have been warned. But if you want to edit, go ahead using your
# favorite editor :-)
## vim:ts=4:et:nowrap
# [Emacs: -*- python -*-]
_ap_icons = """
def encodefile(filename):
"""returns the binary content of 'filename' as string"""
s = open(filename, 'rb').read()
result = [ ]
while s:
result.append(repr(s[:16]))
s = s[16:]
return string.join(result, '\n ')
class Encode:
"""Starting at a given directory find all files matching a certain
filename pattern in this subtree, encode them as base64 strings and
return a Python language dictionary with the filenames as keys and
the files contents as values.
"""
def __init__(self, startdir=os.curdir, fn_pattern="*.gif"):
self.startdir = startdir
self.fn_pattern = fn_pattern
self.walk()
def walk(self):
"""walk through the subtree starting at self.startdir"""
self.result = ['{\n']
os.path.walk(self.startdir, self.visitor, None)
self.result.append('}\n')
def visitor(self, dummy, dirname, filenames):
"""A visitor compatible with os.path.walk()."""
for candidate in filenames:
pathname = os.path.join(dirname, candidate)
if not os.path.isfile(pathname):
continue
if self.match(pathname):
self.put_item(pathname)
def match(self, candidate):
"""should return false, if pathname 'candidate' should be skipped.
"""
return fnmatch.fnmatch(candidate, self.fn_pattern)
def put_item(self, pathname):
self.result.append(' "%s" :\n %s,\n\n'
% (self.compute_key(pathname),
encodefile(pathname)))
def compute_key(self, pathname):
"""computes the dictionary key. Tkinter compatible"""
return os.path.splitext(pathname)[0]
def __str__(self):
return string.join(self.result, '')
#
# The remainder of this module is best described as hack, run and throw away
# software. You may want to edit it, if you want to other icons.
#
class WebserverIconEncode(Encode):
minimal_list = [ # List of icons actually used by ViewCVS as of 2001-11-17
"/icons/apache_pb.gif",
"/icons/small/back.gif",
"/icons/small/dir.gif",
"/icons/small/text.gif",
]
def match(self, candidate):
return self.compute_key(candidate) in self.minimal_list
def compute_key(self, pathname):
l = len(self.startdir)
if pathname[:l] == self.startdir:
return pathname[l:]
return pathname
POSTAMBLE="""
def serve_icon(pathname, fp):
if _ap_icons.has_key(pathname):
fp.write(_ap_icons[pathname])
else:
raise OSError # icon not found
"""
if __name__ == "__main__":
import sys
f = open("../lib/apache_icons.py", "wt")
f.write(PREAMBLE)
f.write(str(WebserverIconEncode(startdir="/usr/local/httpd")))
f.write(POSTAMBLE)

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://www.lyra.org/viewcvs/
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
@@ -40,15 +40,13 @@ else:
#########################################################################
import os
import string
import cvsdb
import os, string, cvsdbapi
def UpdateFile(db, repository, path):
try:
commit_list = cvsdb.GetUnrecordedCommitList(repository, path)
except cvsdb.error, e:
commit_list = cvsdbapi.GetUnrecordedCommitList(repository, path)
except cvsdbapi.error, e:
print '[ERROR] %s' % (e)
return
@@ -80,15 +78,15 @@ def RecurseUpdate(db, repository, directory):
def CommandUpdate():
## connect to the database we are updating
db = cvsdb.ConnectDatabase()
db = cvsdbapi.ConnectDatabase()
repository = sys.argv[2]
RecurseUpdate(db, repository, repository)
def RebuildFile(db, repository, path):
try:
commit_list = cvsdb.GetCommitListFromRCSFile(repository, path)
except cvsdb.error, e:
commit_list = cvsdbapi.GetCommitListFromRCSFile(repository, path)
except cvsdbapi.error, e:
print '[ERROR] %s' % (e)
return
@@ -120,7 +118,7 @@ def RecurseRebuild(db, repository, directory):
def CommandRebuild():
## connect to the database we are updating
db = cvsdb.ConnectDatabase()
db = cvsdbapi.ConnectDatabase()
repository = sys.argv[2]
RecurseRebuild(db, repository, repository)

View File

@@ -1,15 +1,15 @@
#!/usr/bin/python
# -*- Mode: python -*-
#
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000-2001 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 ViewCVS
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://www.lyra.org/viewcvs/
# gstein@lyra.org, http://viewcvs.sourceforge.net/
#
# -----------------------------------------------------------------------
#
@@ -39,20 +39,14 @@ else:
#########################################################################
import os
import string
import getopt
import re
import cvsdb
import rlog
import config
import os, string, getopt, re, cvsdbapi
DEBUG_FLAG = 0
## pre-compiled regular expressions
_re_fileversion = re.compile("([^,]+)\,([^,]+)\,([^,]+)")
## output functions
def debug(text):
if DEBUG_FLAG:
@@ -65,7 +59,13 @@ def error(text):
print 'ERROR(loginfo): %s' % (text)
sys.exit(1)
class FileData:
CHANGED = 1
ADDED = 2
REMOVED = 3
def __init__(self, file, directory, old_version, new_version):
self.file = file
self.directory = directory
@@ -75,43 +75,40 @@ class FileData:
## set the state of this file from the
## old_version and new_version information
if self.old_version == 'NONE':
self.ctype = "added"
self.type = self.ADDED
elif self.new_version == 'NONE':
self.ctype = "removed"
self.type = self.REMOVED
else:
self.ctype = "changed"
self.type = self.CHANGED
def CommitFromFileData(cfg, repository, file_data):
## construct the full path for the RCS file
def CommitFromFileData(repository, file_data):
## consturct the full path for the RCS file
filename = os.path.join(repository, file_data.directory, file_data.file)
## get the 'rlog' output for just this revision, and then convert
## to a commit object
rlog_data = rlog.GetRLogData(cfg, filename, file_data.new_version)
commit_list = cvsdb.RLogDataToCommitList(repository, rlog_data)
rlog_data = cvsdbapi.GetRLogData(filename, file_data.new_version)
commit_list = cvsdbapi.RLogDataToCommitList(repository, rlog_data)
commit = commit_list[0]
## set the type of commit from the file_data setting
if file_data.ctype == "changed":
if file_data.type == file_data.CHANGED:
commit.SetTypeChange()
elif file_data.ctype == "added":
elif file_data.type == file_data.ADDED:
commit.SetTypeAdd()
elif file_data.ctype == "removed":
elif file_data.type == file_data.REMOVED:
commit.SetTypeRemove()
return commit
def GetUnrecordedCommitList(repository, file_data):
filename = os.path.join(repository, file_data.directory, file_data.file)
return cvsdb.GetUnrecordedCommitList(repository, filename)
return cvsdbapi.GetUnrecordedCommitList(repository, filename)
def ProcessLoginfo(repository, stdin_list):
## XXX This is a somewhat dirty hack:
## cvsdb already loads the configuration file and provides a cfg
## instance. Pick it from there in order to be able to pass it down
## to rlog (see below).
cfg = cvsdb.cfg
## the first line in stdin is a space-separated list; the first
## item in the list is the directory path being updated this run;
## the rest of the items are the files being updated
@@ -158,14 +155,14 @@ def ProcessLoginfo(repository, stdin_list):
## given enough information to find it in the rlog output!
## So instead, we rlog everything in the removed file, and
## add any commits not already in the database
if file_data.ctype == "removed":
if file_data.type == file_data.REMOVED:
temp = GetUnrecordedCommitList(repository, file_data)
commit_list = commit_list + temp
else:
commit_list.append(CommitFromFileData(cfg, repository, file_data))
commit_list.append(CommitFromFileData(repository, file_data))
## add to the database
db = cvsdb.ConnectDatabase()
db = cvsdbapi.ConnectDatabase()
db.AddCommitList(commit_list)

View File

@@ -18,9 +18,7 @@
#
# -----------------------------------------------------------------------
#
import os, sys, string
import popen2
INTRO_TEXT = """\
This script creates the database and tables in MySQL used by the ViewCVS
@@ -135,11 +133,9 @@ if __name__ == "__main__":
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
pipes = popen2.Popen3(cmd)
pipes.tochild.write(dscript)
pipes.tochild.close()
print pipes.fromchild.read()
status = pipes.wait()
mysql = os.popen(cmd, "w")
mysql.write(dscript)
status = mysql.close()
if status:
print "[ERROR] the database did not create sucessfully."

View File

@@ -17,7 +17,7 @@
# -----------------------------------------------------------------------
#
if test $# != 2; then
if [ "x$1" = "x" ]; then
echo "USAGE: $0 tagname target-directory"
exit 1
fi
@@ -31,23 +31,9 @@ fi
echo 'Checking out into:' $2
cvs -d :pserver:anonymous@cvs.viewcvs.sourceforge.net:/cvsroot/viewcvs export -r $1 -d $2 viewcvs
# 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):
cp $2/website/license-1.html $2/LICENSE.html
# rm -r $2/website
# remove some tools only useful for ViewCVS developers:
# various shifting, cleanup
mv $2/website/license-1.html $2/LICENSE.html
rm -r $2/website
rm $2/tools/make-release
rm -f $2/tools/bin2inline_py.py
# Make sure, permissions are reasonable:
find $2 -print | xargs chmod uoa+r
find $2 -type d -print | xargs chmod uoa+x
# cut the tarball:
tar cf - $2 | gzip -9 > $2.tar.gz
# create also a ZIP file for those poor souls :-) still using Windows:
zip -qor9 $2.zip $2
echo 'Done.'

View File

@@ -27,93 +27,61 @@ import string
import re
import traceback
import py_compile
import StringIO
# get access to our library modules
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
import compat
import viewcvs
import ndiff
version = viewcvs.__version__
## installer text
INFO_TEXT = """\
This is the ViewCVS %s installer.
It will allow you to choose the install path for ViewCVS. You will
now be asked some installation questions.
This is the ViewCVS installer. It will allow you to choose the install
path for ViewCVS. 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
ROOT_DIR = "/usr/local/viewcvs-" + version
ROOT_DIR = "/usr/local/viewcvs-dev"
## 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)
## search-and-replace, flag for prompt before replace, compile_it)
##
FILE_INFO_LIST = [
("cgi/viewcvs.cgi", "cgi/viewcvs.cgi", 0755, 1, 0, 0),
("cgi/queryform.cgi", "cgi/queryform.cgi", 0755, 1, 0, 0),
("cgi/query.cgi", "cgi/query.cgi", 0755, 1, 0, 0),
("standalone.py", "standalone.py", 0755, 1, 0, 0),
("cgi/viewcvs.conf.dist", "viewcvs.conf", 0644, 1,
"""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),
("cgi/cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),
("cgi/viewcvs.conf.dist", "viewcvs.conf", 0644, 0, 1, 0),
("lib/PyFontify.py", "lib/PyFontify.py", 0644, 0, 0, 1),
("lib/blame.py", "lib/blame.py", 0644, 0, 0, 1),
("lib/commit.py", "lib/commit.py", 0644, 0, 0, 1),
("lib/compat.py", "lib/compat.py", 0644, 0, 0, 1),
("lib/config.py", "lib/config.py", 0644, 0, 0, 1),
("lib/cvsdb.py", "lib/cvsdb.py", 0644, 1, 0, 1),
("lib/cvsdbapi.py", "lib/cvsdbapi.py", 0644, 1, 0, 1),
("lib/database.py", "lib/database.py", 0644, 0, 0, 1),
("lib/dbi.py", "lib/dbi.py", 0644, 0, 0, 1),
("lib/debug.py", "lib/debug.py", 0644, 0, 0, 1),
("lib/popen.py", "lib/popen.py", 0644, 0, 0, 1),
("lib/py2html.py", "lib/py2html.py", 0644, 0, 0, 1),
("lib/query.py", "lib/query.py", 0644, 1, 0, 1),
("lib/rcsparse.py", "lib/rcsparse.py", 0644, 0, 0, 1),
("lib/rlog.py", "lib/rlog.py", 0644, 0, 0, 1),
("lib/rcsparse.py", "lib/rcsparse.py", 0644, 1, 0, 1),
("lib/rlog.py", "lib/rlog.py", 0644, 1, 0, 1),
("lib/viewcvs.py", "lib/viewcvs.py", 0644, 1, 0, 1),
("lib/ezt.py", "lib/ezt.py", 0644, 0, 0, 1),
("lib/apache_icons.py", "lib/apache_icons.py", 0644, 0, 0, 1),
("lib/accept.py", "lib/accept.py", 0644, 0, 0, 1),
("templates/annotate.ezt", "templates/annotate.ezt", 0644, 0, 1, 0),
("templates/diff.ezt", "templates/diff.ezt", 0644, 0, 1, 0),
("templates/directory.ezt", "templates/directory.ezt", 0644, 0, 1, 0),
("templates/dir_alternate.ezt", "templates/dir_alternate.ezt", 0644, 0, 1, 0),
("templates/footer.ezt", "templates/footer.ezt", 0644, 0, 1, 0),
("templates/graph.ezt", "templates/graph.ezt", 0644, 0, 1, 0),
("templates/header.ezt", "templates/header.ezt", 0644, 0, 1, 0),
("templates/log.ezt", "templates/log.ezt", 0644, 0, 1, 0),
("templates/log_table.ezt", "templates/log_table.ezt", 0644, 0, 1, 0),
("templates/markup.ezt", "templates/markup.ezt", 0644, 0, 1, 0),
("templates/query.ezt", "templates/query.ezt", 0644, 0, 1, 0),
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
("tools/cvsdbadmin", "cvsdbadmin", 0755, 1, 0, 0),
("tools/make-database", "make-database", 0755, 1, 0, 0),
("website/help_rootview.html", "doc/help_rootview.html", 0644, 0, 0, 0),
("website/help_dirview.html", "doc/help_dirview.html", 0644, 0, 0, 0),
("website/help_query.html", "doc/help_query.html", 0644, 0, 0, 0),
("website/help_log.html", "doc/help_log.html", 0644, 0, 0, 0),
("website/help_logtable.html", "doc/help_logtable.html", 0644, 0, 0, 0),
("website/images/logo.png", "doc/images/logo.png", 0644, 0, 0, 0),
("website/images/chalk.jpg", "doc/images/chalk.jpg", 0644, 0, 0, 0),
("website/images/cvsgraph_16x16.png", "doc/images/cvsgraph_16x16.png", 0644, 0, 0, 0),
("website/images/cvsgraph_32x32.png", "doc/images/cvsgraph_32x32.png", 0644, 0, 0, 0),
("html-templates/queryformtemplate.html",
"html-templates/queryformtemplate.html", 0644, 0, 1, 0),
("html-templates/querytemplate.html",
"html-templates/querytemplate.html", 0644, 0, 1, 0),
]
@@ -145,99 +113,49 @@ def SetOnePath(contents, var, value):
repl = '%s = "%s"' % (var, os.path.join(ROOT_DIR, value))
return re.sub(pattern, repl, contents)
def SetPythonPaths(contents):
if contents[:2] == '#!':
shbang = '#!' + sys.executable
contents = re.sub('^#![^\n]*', shbang, contents)
contents = re.sub("<VIEWCVS_INSTALL_DIRECTORY>", ROOT_DIR, contents)
contents = SetOnePath(contents, 'LIBRARY_DIR', 'lib')
contents = SetOnePath(contents, 'CONF_PATHNAME', 'viewcvs.conf')
contents = SetOnePath(contents, 'HTML_TEMPLATE_DIR', 'html-templates')
return contents
def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
compile_it):
dest_path = os.path.join(ROOT_DIR, dest_path)
dest_path = os.path.join(ROOT_DIR, dest_path)
if prompt_replace and os.path.exists(dest_path):
# Collect ndiff output from ndiff
sys.stdout = StringIO.StringIO()
ndiff.main([dest_path,src_path])
ndiff_output = sys.stdout.getvalue()
if prompt_replace and os.path.exists(dest_path):
temp = raw_input("File %s exists, overwright? [y/N]: " % (dest_path))
if not temp or string.lower(temp[0]) != "y":
return
# Return everything to normal
sys.stdout = sys.__stdout__
try:
contents = open(src_path, "r").read()
except IOError, e:
Error(str(e))
# 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
if set_python_paths:
contents = SetPythonPaths(contents)
if total == "\n":
print " File %s exists,\n but there is no difference between target and source files.\n" % (dest_path)
return
## write the file to the destination location
path, basename = os.path.split(dest_path)
MkDir(path)
if type(prompt_replace) == type(""):
print prompt_replace
while 1:
temp = raw_input("\n File %s\n exists and is different from source file.\n DO YOU WANT TO,\n overwrite [o]\n do not overwrite [d]\n view differences [v]: " % (dest_path))
print
try:
open(dest_path, "w").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)
temp = string.lower(temp[0])
os.chmod(dest_path, mode)
if temp == "d":
return
if temp == "v":
print total
print "\nLEGEND\n A leading '- ' indicates line to remove from installed file\n A leading '+ ' indicates line to add to installed file\n A leading '? ' shows intraline differences."
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
def ReplaceFile(src_path, dest_path, mode, set_python_paths, prompt_replace, compile_it):
try:
contents = open(src_path, "r").read()
except IOError, e:
Error(str(e))
if set_python_paths:
contents = SetPythonPaths(contents)
## write the file to the destination location
path, basename = os.path.split(dest_path)
MkDir(path)
try:
open(dest_path, "w").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(dest_path, mode)
if compile_it:
py_compile.compile(dest_path)
return
if compile_it:
py_compile.compile(dest_path)
## MAIN
@@ -255,7 +173,7 @@ if __name__ == "__main__":
print "Installing ViewCVS to:", ROOT_DIR
for args in FILE_INFO_LIST:
print " ", args[0]
print " ", args[0]
apply(InstallFile, args)
print

View File

@@ -1,173 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>Contributing to ViewCVS Development</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>Contributing to ViewCVS Development</h1>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
<tr><td width="1%" valign=top>
<a href="index.html">Home</a><br>
<a href="upgrading.html">Upgrading</a><br>
Contributing<br>
<a href="license-1.html">License</a><br>
<a href="who.html">Who</a><br>
</td><td colspan=2>
<p>
Contributions to ViewCVS are very welcome.
</p>
<h2>Getting Started</h2>
<p>
Some basic knowledge about <a href="http://www.python.org"><i>Python</i></a> and
development Tools like <code>diff</code> is required. Best is you start
with a fresh snapshot, which you may obtain from the
<a href="http://sourceforge.net/cvs/?group_id=18760">CVS-Repository</a>
at SourceForge.
</p>
<h2>Testing and reporting</h2>
<p>
Testing usability and the installation process on different platforms is
also a valuable contribution. Please report your results back to
us developers. But always tell us, which version of ViewCVS
you used on which platform.
</p><p>
If you are using the latest current development version please note
the date of your latest download or cvs update when reporting.
</p>
<h2>Does a Certain Coding Style Guide apply?</h2>
<p>
Maintain whatever style is present in the code being modified. New
code can use anything sane (which generally means
<a href="http://python.sourceforge.net/peps/pep-0008.html"><i>PEP&nbsp;8</i></a>).
Greg's only real peeve is if someone writes a function call as:
<code>some_func&nbsp;(args)</code> -- that space is Huge Badness.
Otherwise... <i>shrug</i>.
</p>
<h2>Submitting patches</h2>
<p>
Please use the
<a href="http://sourceforge.net/tracker/?func=add&group_id=18760&atid=318760">SourceForge&nbsp;project&nbsp;patch&nbsp;manager</a>
to submit your patches. Unified context diffs relative to the latest
development version are preferred.
</p><p>
If you have commit access, then you should know what
you're doing. Just make changes directly. Subscribing to
the <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev"><i>developer&nbsp;mailing&nbsp;list</i></a>
is recommended in any case.
</p>
<h2>Preserving security</h2>
<p>
Since ViewCVS is used in the internet, security is a major issue.
If you need to pass data from the request into an external program,
please don't use <code>os.system()</code> or <code>os.popen()</code>.
Please use the module <code>lib/popen</code> that comes with ViewCVS
instead.
</p>
<h2>Adding new features</h2>
<p>
If a new file or module is added, a new line in the installer program
<code>viewcvs-install</code> is required.
</p><p>
The library subdirectory contains a module <code>debug</code>, which may
useful to make performance tests.
</p><p>
If you need a new configuration option think carefully, into
which section it belongs. Try to keep the content of
<code>cgi/viewcvs.conf.dist</code> file and the library module
<code>lib/config.py</code> in sync.
</p>
<h2>Hacking on templates</h2>
<p>
The library module <code>ezt</code> contains a module doc string which
describes the directives used in the HTML templates, which can be found
in the <code>templates</code> sub directory.
</p>
<h2>Cutting a release</h2>
<p>
Also there actually is a script <code>tools/make-release</code>,
which creates a release directory, all other steps required to get
a ViewCVS release out of the door will be currently executed manually
by Greg Stein.
</p><p>
Nevertheless in case he ever wants to retire from this job, it is
probably a good idea to write the procedure down here:
<ol>
<li>Add a new subsection to the file
<code>website/upgrading.html</code> describing all user visible
changes for users of previous releases of ViewCVS.
<li>Test, Test, Test! At the time of this writing (0.8-dev) there
is no automatic testsuite available. So just run with permuting
different <code>viewcvs.conf</code> settings and ... pray.
<li>Review any <a
href="http://sourceforge.net/tracker/?atid=118760&group_id=18760&func=browse">
bug reports, that are still marked open.</a>
<li>Edit the file <code>lib/viewcvs.py</code> and remove the
<tt>"-dev"</tt> suffix from <code>__version__</code>. The remainder
should be of the form X.Y, where X is a positive number and
Y is a single digit.
<li>commit this changed version of <code>lib/viewcvs.py</code>
and than <code>cvs tag V</code><i>X</i><code>_</code><i>Y</i>, where
<i>X</i> and <i>Y</i> should be replaced by the release number
from above. If a developer is willing to volunteer as a
bug fix patch release manager, it is now possible to start here
at this point with a feature freezed branch using the command
<blockquote>
<code>cvs tag -b V</code><i>X</i><code>_</code><i>Y</i><code>_maint</code>
</blockquote>
<li>go into an empty directory and run the command:
<blockquote>
<code>tools/make-release V</code><i>X_Y</i> <code>viewcvs-</code><i>X.Y</i>
</blockquote>
This step requires anonymous CVS access to repository at SourceForge.
<li>pack the content of this <code>viewcvs-</code><i>X.Y</i> directory
into a tarball.
<li>Upload the created tarball into the download files section of the
ViewCVS project at SourceForge.
<br><b>Greg:</b><i>Could you please elaborate this step?</i>
<li>Edit the file <code>lib/viewcvs.py</code> again and this time
increment the <code>__version__</code> for the next release cycle,
again append the <code>"-dev"</code> to the version and again
<blockquote>
<code>cvs commit -m "new release cycle begins" lib/viewcvs.py</code>.
</blockquote>
<li>Write an announcement explaining all the cool new features
and put it out to:
<ul>
<li><a href="http://www.freshmeat.net"><i>www.freshmeat.net</i></a>
<li><a href="http://www.vex.net/parnassus/apyllo.py?i=91022454"><i>Vaults of Parnassus</i></a>
<li><a href="news:comp.lang.python">comp.lang.python</a>
<li><i>Where else? I dunno. Suggestions welcome.</i>
</ul>
</ol>
</td></tr>
</table>
<hr>
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
<!-- Created: Thu Oct 18 09:05:40 CEST 2001 -->
<!-- hhmts start -->
Last modified: Mon Nov 19 20:25:38 CEST 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,53 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ViewCVS Help: Directory View</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>ViewCVS Help: Directory View</h1>
</td>
</tr>
<tr><td width="1%" valign=top bgcolor="#ffffff">
<h3>Help</h3>
<a href="help_rootview.html">General</a><br>
<b>Directory&nbsp;View</b><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>
<a href="help_query.html">Query&nbsp;Database</a><br>
<h3>Internet</h3>
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
</td><td colspan=2>
<p>
Click on a directory to enter that directory. Click in the leftmost
column of a row to display the revision history of a file and to get
a chance to display diffs between revisions. Click on second column
to view the content of the latest revision of that file.
</p>
<p>
Directories are always displayed first in alphabetically order.
Ordinary files follow and are sorted according to the selected
sort criteria. You may click on a particular column header to
select this column as sort criteria. It is than displayed in
another color (e.g. light green, if using the default configuration).
</p>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
<!-- hhmts start -->
Last modified: Tue Nov 13 20:40:02 CEST 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,61 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ViewCVS Help: Classic CVS Log View</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>ViewCVS Help: Classic CVS Log View</h1>
</td>
</tr>
<tr><td width="1%" valign=top bgcolor="#ffffff">
<h3>Help</h3>
<a href="help_rootview.html">General</a><br>
<a href="help_dirview.html">Directory&nbsp;View</a><br>
<b>Classic&nbsp;Log&nbsp;View</b><br>
<a href="help_logtable.html">Alternative&nbsp;Log&nbsp;View</a><br>
<a href="help_query.html">Query&nbsp;Database</a><br>
<h3>Internet</h3>
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
</td><td colspan=2>
<p>
The log view displays the revision history of the selected source
file. For each revision the following information is displayed:
<ul>
<li>the revision number (clickable to download it)</li>
<li>a link to view this revision annotated</li>
<li>a link to select this revision for diffs (see below)</li>
<li>the date and age of this change</li>
<li>and the author of this modification.</li>
<li>The CVS branch (usually <code>MAIN</code>, if not on a branch).</li>
<li>Possibly a list of CVS tags bound to this revision (if any).</li>
<li>The size of this change measured in added and removed lines of
code.</li>
<li>Links to view Diffs to the previous revision or possibly to
an arbitrary selected revision (if any, see above).
<li>And last but not least, the commit log message which should tell
about the reason for this change.</li>
</ul>
</p><p>
At the bottom of such a page you will find a form which allows
to request diffs between arbitrary revisions.
</p>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
<!-- hhmts start -->
Last modified: Wed Dec 12 13:56:52 CET 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,59 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ViewCVS Help: CVS Log Table View</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="http://viewcvs.sf.net/index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>ViewCVS Help: CVS Log Table View</h1>
</td>
</tr>
<tr><td width="1%" valign=top bgcolor="#ffffff">
<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">Classic&nbsp;Log&nbsp;View</a><br>
<b>Alternative&nbsp;Log&nbsp;View</b><br>
<a href="help_query.html">Query&nbsp;Database</a><br>
<h3>Internet</h3>
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
</td><td colspan=2>
<p>
The table based log view must be enabled in the site wide
ViewCVS configuration file.
</p><p>
It displays the revision history of the selected source
file in a table containing the following columns:
<ul>
<li><b>Revision</b>: The CVS revision number.</li>
<li><b>Tasks</b>: This column contains several links to view
a revision.</li>
<li><b>Diffs</b>: Several links to view the changes between this
and other revisions.
<li><b>Branches and Tags</b>: Displays the branch and a pulldown
menu button displaying all tags bound to that revision</li>
<li>FIXME: Document the other columns</li>
</ul>
</p><p>
At the bottom of such a page you will find a form which allows
to request diffs between arbitrary revisions.
</p>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
<!-- Created: Thu Oct 25 22:08:29 CEST 2001 -->
<!-- hhmts start -->
Last modified: Sat Dec 8 23:26:52 CET 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,64 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html><head>
<title>ViewCVS Help: Query The Commit Database</title>
</head>
<body background="images/chalk.jpg">
<table width="100%" border=0 cellspacing=5 cellpadding=0>
<tr>
<td width="1%"><a href=".."><img border=0
src="images/logo.png"></a>
</td>
<td><h1>ViewCVS Help: Query The Commit Database</h1></td>
</tr>
<tr><td width="1%" valign="top" bgcolor="#ffffff">
<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>
<b>Query&nbsp;Database</b>
<h3>Internet</h3>
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
<a href="http://viewcvs.sf.net/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 <i>jpaint</i> and <i>gstein</i>, just type: <code>jpaint,
gstein</code> in the <i>Author</i> 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 <i>.py</i> extention is:
<code>l"%.py"</code> in the <i>File</i> 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

@@ -1,83 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ViewCVS Help: Toplevel Directory View</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href=".."><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>ViewCVS Help: Toplevel Directory View</h1>
</td>
</tr>
<tr><td width="1%" valign=top bgcolor="#ffffff">
<h3>Help</h3>
<b>General</b>
<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>
<a href="help_query.html">Query&nbsp;Database</a><br>
<h3>Internet</h3>
<a href="http://viewcvs.sf.net/index.html">Home</a><br>
<a href="http://viewcvs.sf.net/upgrading.html">Upgrading</a><br>
<a href="http://viewcvs.sf.net/contributing.html">Contributing</a><br>
<a href="http://viewcvs.sf.net/license-1.html">License</a><br>
</td><td colspan=2>
<p><b>ViewCVS</b> is a WWW interface for CVS Repositories.</b>
You can browse the
file hierarchy by picking directories (which have slashes after
them, <i>e.g.</i>, <b>src/</b>). If you pick a file, you will see
the revision history for that file. Selecting a revision number
will display that revision of the file. There is a link at each
revision to display diffs between that revision and the previous
one, and a form at the bottom of the page that allows you to
display diffs between arbitrary revisions.
</p>
<h3>ViewCVS History and Credits</h3>
<p>
This program
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
script by Henner Zeller
&lt;<a href="mailto:zeller@think.de">zeller@think.de</a>&gt;;
it is covered by the
<a href="http://www.opensource.org/licenses/bsd-license.html">BSD-License</a>.
If you would like to use this CGI script on your own web server and
CVS tree, see Greg's
<a href="http://viewcvs.sourceforge.net/">ViewCVS&nbsp;distribution&nbsp;site</a>.
Please send any suggestions, comments, etc. to the
<a href="mailto:viewcvs-dev@lyra.org">ViewCVS&nbsp;Developers&nbsp;Mailinglist</a>.
</p>
<h3>Documentation about CVS</h3>
<blockquote>
<p>
<a href="http://cvsbook.red-bean.com/">Karl Fogel's CVS book</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>
<!-- insert repository access instructions here -->
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
<!-- Created: Thu Oct 25 22:16:29 CEST 2001 -->
<!-- hhmts start -->
Last modified: Tue Nov 13 20:41:02 CEST 2001
<!-- hhmts end -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -3,145 +3,108 @@
<head>
<title>ViewCVS: Viewing CVS Repositories</title>
</head>
<!-- Editors: Please keep all links to external sites in italics. -->
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><img border=0 src="images/logo.png"></td>
<td>
<h1>ViewCVS: Viewing CVS Repositories</h1>
</td>
<td align=center valign=top bgcolor="white" width="1%">
<b>Quickstart:</b>
<a href="viewcvs-0.9.3.tar.gz">download</a>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
<tr><td width="1%" valign=top>
<h3>Links:</h3>
<a href="http://viewcvs.sourceforge.net">Home</a><br>
<a href="upgrading.html">Upgrading</a><br>
<a href="contributing.html">Contributing</a><br>
<a href="license-1.html">License</a><br>
<a href="who.html">Who</a><br>
<h3>Sections:</h3>
<a href="#Features">Features</a><br>
<a href="#History">History</a><br>
<a href="#Mail">Mailing Lists</a><br>
<a href="#Cvsweb">vs.&nbsp;cvsweb</a><br>
<a href="#Download">Download</a><br>
<a href="#Future">Future&nbsp;directions</a><br>
<a href="#Colorize">Colorization</a><br>
</td>
<td colspan=3>
<hr width="75&#37;">
<h2><a name="Features">Features</a></h2>
<body background="/images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td>
<h1>ViewCVS: Viewing CVS Repositories</h1>
</td>
<td align=center valign=top bgcolor="white" width="1%">
<b>Quickstart:</b>
<a href="viewcvs-0.6.tar.gz">download</a>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
</table>
<p>
The ViewCVS software was inspired by
<a href="http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/"><i>cvsweb</i></a>
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
I wanted to make some changes and updates, but cvsweb was
implemented in Perl. While I can manage some Perl, cvsweb was
rather unmaintainable for me. So I undertook the
task to convert the software to
<a href="http://www.python.org/"><i>Python</i></a>. As a result,
I've actually been able to go <em>way</em> beyond the simple
changes that I had envisioned.
</p>
<p>
ViewCVS can browse directories, change logs, and specific
revisions of files. It can display diffs between versions and
show selections of files based on tags or branches. In addition,
ViewCVS has "annotation" or "blame" support, Bonsai-like query
facilities, template-based page generation, and support for
individually configuring virtual hosts.
It also includes support for
<a href="http://www.akhphd.au.dk/~bertho/cvsgraph/"><i>CvsGraph</i></a>
-- a program to display the tree of revisions and branches
graphically.
ViewCVS has "annotation" or "blame" support, and the beginnings
of Bonsai-like query facilities.
</p>
<p>
Currently, the functionality of ViewCVS surpasses that of cvsweb.
See <a href="#Cvsweb">below</a> for a list of additional features.
ViewCVS is currently at version 0.6. It was a port of the cvsweb
script, but has had numerous cleanups and other modifications,
based on some of Python's strengths. There is still some minor
"badness" remaining from the Perl code, but I've been working on
flushing that out, while adding new features. Currently, the
functionality of ViewCVS surpasses that of cvsweb.
</p>
<p>
The software is available for download:
</p>
<blockquote>
<a href="viewcvs-0.6.tar.gz">Version 0.6 of ViewCVS</a>
</blockquote>
<p>
Of course, it is also available through ViewCVS itself:
</p>
<blockquote>
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/">http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/</a>
</blockquote>
<p>
ViewCVS requires <strong>Python 1.5</strong> (which has been out
for a couple years and is readily available for your favorite
operating system). If you choose to use the SQL Checkin Database
feature, then you must use <strong>Python 1.5.2</strong> and the
<a href="http://dustman.net/andy/python/MySQLdb"><i>MySQLdb
module</i></a> installed.
</p>
<hr width="75&#37;">
<h2><a name="History">History</a></h2>
<p>
The ViewCVS software was inspired by
<a href="http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/"><i>cvsweb</i></a>
(originally written by Bill Fenner and then further developed by
<a href="mailto:zeller@think.de">Henner Zeller</a>).
Greg Stein wanted to make some changes and updates, but cvsweb was
implemented in Perl. He wrote:
<blockquote><i>While I can manage some Perl, cvsweb was
rather unmaintainable for me. So I undertook the
task to convert the software to
<a href="http://www.python.org/">Python</a>. As a result,
I've actually been able to go <em>way</em> beyond the simple
changes that I had envisioned.
</i></blockquote>
</p>
<p>
ViewCVS started as a port of the cvsweb script, but has had
numerous cleanups and other modifications, based on some of
Python's strengths. There is still some minor "badness"
remaining from the Perl code, but Greg has been working on
flushing that out, while adding new features.
</p>
<p>
ViewCVS has been developed by the <a href="who.html">ViewCVS
Group</a> and is made available under a
<a href="license-1.html">BSD-type license</a>.
</p>
<p>
ViewCVS requires <strong>Python 1.5</strong> or later (Python
1.5 has been out for a couple years and is readily available for
your favorite operating system). If you choose to use the SQL Checkin
Database feature, then you must use <strong>Python 1.5.2</strong>
or later and have the
<a href="http://sourceforge.net/projects/mysql-python"><i>MySQLdb
module</i></a> installed which itself requires
Marc-Andre Lemburgs
<a href="http://www.lemburg.com/files/python/eGenix-mx-Extensions.html#Download-mxBASE"><i>mxDateTime extension</i></a>.
</p>
<hr width="75&#37;">
<h2><a name="Mail">Mailing Lists</a></h2>
<h2>Mailing List</h2>
<p>
If you have any comments, questions, or suggestions,
If you have any comments, questions, suggestions, or patches,
then please send them to the
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs"><i>ViewCVS
mailing list</i></a>, which is also
<a href="http://mailman.lyra.org/pipermail/viewcvs/"><i>archived</i></a>.
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs">ViewCVS
mailing list</a>.
</p>
<p>
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev"><i>mailing
list for ViewCVS developers</i></a> is also available
(<a href="http://mailman.lyra.org/pipermail/viewcvs-dev/"><i>Archive</i></a>).
</p>
<p>
ViewCVS is an <a href="http://www.opensource.org/"><i>Open
Source</i></a> project, and all
<a href="contributing.html">contributions</a> are welcome.
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev">mailing
list for ViewCVS developers</a> is also available.
</p>
<hr width="75&#37;">
<h2><a name="Cvsweb">Additional features over cvsweb</a></h2>
<h2>Additional features over cvsweb</h2>
<p>
<strong>New to version 0.5:</strong>
</p>
<ul>
<li>
Template support: you can now customize the look and feel of
ViewCVS by editing the provided EZT templates, which are used
to generate the pages.
</li>
<li>
Internationalization support: ViewCVS will parse and handle
the Accept-Language request header, and can select different
inputs for localized page generation.
</li>
<li>
Colorization for many file types via <code>enscript</code>.
</li>
<li>
Bonsai-like query features. (Requires MySQL and some other
prerequisites)
Bonsai-like query features.
</li>
<li>
Annotation/blame viewing support against a <strong>read-only</strong>
Annotation/blame support against a <strong>read-only</strong>
repository.
</li>
<li>
@@ -150,18 +113,12 @@
across virtual hosts, yet still be able to fine-tune the
options when necessary.
</li>
<li>
Automatic generation of tarballs for the HEAD or a specified
tag.
</li>
<li>
Runs either as CGI script, called from an installed web server
(such as <a href="http://httpd.apache.org/"><i>Apache</i></a>),
or as a standalone server.
</li>
<li>
Searching files for matches for a regular expression.
</li>
</ul>
<p>
Other improvements over cvsweb:
</p>
<ul>
<li>Better reporting for unreadable files.</li>
<li>
More robust when given varying <code>rcsdiff</code> or
@@ -187,98 +144,48 @@
<br>
<small>(cvsweb had a hole due to a popen() call)</small>
</li>
<li>
Last but not least: it doesn't suffer from the "unmaintainable
code effect" that hits most Perl projects sooner or later:
<blockquote><i>[Perl] combines all the worst aspects of C and Lisp:
a billion different sublanguages in one monolithic executable.
It combines the power of C with the readability of PostScript.</i>
--&nbsp;Jamie&nbsp;Zawinski
</blockquote>
</li>
</ul>
<p>
The changes present in each release are available in
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/viewcvs/CHANGES?rev=HEAD">ViewCVS's
CHANGES file</a>.
</p>
<hr width="75&#37;">
<h2><a name="Download">Download</a></h2>
<p>
The software is available for download:
</p>
<blockquote>
<a href="viewcvs-0.9.3.tar.gz">Version 0.9.3 of ViewCVS as a gzipped
tar</a>
<br>
<a href="viewcvs-0.9.3.zip">Version 0.9.3 of ViewCVS as a ZIP
file</a>
</blockquote>
<p>
Of course the current development version is also available
through ViewCVS itself:
</p>
<blockquote>
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/">http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/</a>
</blockquote>
<p>
You can also
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/viewcvs/CHANGES?rev=HEAD">see
the changes</a> for this release.
</p>
<hr width="75&#37;">
<h2><a name="Future">Future features, directions</a></h2>
<p>
ViewCVS is a Open Source project. So any future development depends
on the contributions that will be made by its user community.
Certainly working patches have a greater chance to become realized
quickly than feature requests. <i>But don't hesitate to submit your
suggestions! Send mail to the
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
mailing list or even better use the
<a href="http://sourceforge.net/tracker/?func=add&group_id=18760&atid=368760">SF&nbsp;tracker</a>.
</i>
Future features, coming soon:
</p>
<ul>
<li>See the feature requests already submitted through
the <a href="http://sourceforge.net/tracker/?atid=368760&group_id=18760&func=browse">SF feature request tracker</a>
</li>
<li>UI streamlining/simplification</li>
<li>Integration with CVS checkin auto-mail scripts</li>
<li>Tighter integration with the query features</li>
<p>
<li>Tighter integration the query features</li>
<li>
<i>Suggestions? Send mail to the
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
mailing list.
</i>
</li>
</ul>
<p>
And another longer term pet of Greg Stein:
Longer term:
</p>
<ul>
<li>Integration with an indexer such as LXR</li>
</ul>
</p>
<hr width="75&#37;">
<h2><a name="Colorize">Colorization of files</a></h2>
<h2>Colorization of files</h2>
<p>
ViewCVS can make use of the <code>enscript</code> program to
colorize files in the CVS repository. If <code>enscript</code>
is present on your system, then set the
<code>use_enscript</code> option in the
<code>viewcvs.conf</code> configuration file to <code>1</code>.
If necessary,
<code>viewcvs.conf</code> configuration file. If necessary,
update the <code>enscript_path</code> option to point to your
installation directory. ... That's it! Now, as you view files
through ViewCVS, they will be colored.
</p>
<h3>Colorization of Python files</h3>
<p>
ViewCVS currently also comes with a builtin colorizer for Python
source files. This may go away in a future version, given the new
ViewCVS currently comes with a builtin colorizer for Python
source files. This may go away, given the new
<code>enscript</code> support...
</p>
<p>
@@ -294,12 +201,11 @@
you want to use this feature.
</p>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
<hr>
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
<!-- hhmts start -->
Last modified: Tue Jan 15 01:51:03 PST 2002
Last modified: Fri May 11 14:22:36 PDT 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -4,26 +4,8 @@
<title>ViewCVS License Agreement (v1)</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>ViewCVS License Agreement (v1)</h1>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
<tr><td width="1%" valign=top>
<a href="index.html">Overview</a><br>
<a href="upgrading.html">Upgrading</a><br>
<a href="contributing.html">Contributing</a><br>
License<br>
<a href="who.html">Who</a><br>
</td><td colspan=2>
<body>
<h1>ViewCVS License Agreement (v1)</h1>
<p>
The following text constitutes the license agreement for the
@@ -91,12 +73,11 @@
SUCH DAMAGE.
</p>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
<hr>
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Mon May 8 19:01:27 PDT 2000 -->
<!-- hhmts start -->
Last modified: Sat Oct 20 16:09:37 PDT 2001
Last modified: Sat May 12 15:53:33 PDT 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,356 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>Upgrading a ViewCVS Installation</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>Upgrading a ViewCVS Installation</h1>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
<tr><td width="1%" valign=top>
<a href="index.html">Overview</a><br>
Upgrading<br>
<a href="contributing.html">Contributing</a><br>
<a href="license-1.html">License</a><br>
<a href="who.html">Who</a><br>
</td><td colspan=2>
<p>
This document describes some of the things that you will need to
consider, change, or handle when upgrading an existing ViewCVS
installation to a newer version.
</p>
<p>
It is always recommended to install the new version in a fresh directory
and to carefully compare the configuration files. A possible approach
is to name the directories <code>/usr/local/viewcvs-0.6</code>,
<code>/usr/local/viewcvs-0.7</code> and so on and than create a
symbolic link <code>viewcvs</code> pointing to the production
version. This way you can easily test several versions and switch
back, if your users start to complain.
</p>
<ul>
<li><a href="#from8">Upgrading from ViewCVS 0.8</a></li>
<li><a href="#from7">Upgrading from ViewCVS 0.7 or earlier</a></li>
</ul>
<hr>
<h2><a name="from8">Upgrading from ViewCVS 0.8</a></h2>
<p>
This section discusses how to upgrade ViewCVS 0.8 to version
0.9 or a later version of the software.
</p>
<h3>Configuration Options</h3>
<p>
More templates were introduced in version 0.8 of the software,
which made many of the configuration options obsolete. This
section covers which options were removed. If you made any
changes to these options, then you will need to make
corresponding changes in the templates.
</p>
<blockquote>
<dl>
<dt>
Colors:
<strong>diff_heading</strong>,
<strong>diff_empty</strong>,
<strong>diff_remove</strong>,
<strong>diff_change</strong>,
<strong>diff_add</strong>,
and <strong>diff_dark_change</strong>
</dt>
<dd>
These options have been incorporated into the
<code>diff.ezt</code> template.
<p></p>
</dd>
<dt><strong>markup_log</strong></dt>
<dd>
This option has been incorporated into the
<code>markup.ezt</code> template.
<p></p>
</dd>
<dt>Colors: <strong>nav_header</strong>
and <strong>alt_background</strong></dt>
<dd>
These options have been incorporated into the
<code>header.ezt</code> template.
<p></p>
</dd>
<dt>
Images:
<strong>back_icon</strong>,
<strong>dir_icon</strong>,
and <strong>file_icon</strong>
</dt>
<dd>
These options have been incorporated into the
<code>directory.ezt</code>, <code>header.ezt</code>,
<code>log.ezt</code>, <code>log_table.ezt</code>, and
<code>query.ezt</code> templates.
<p></p>
</dd>
<dt><strong>use_java_script</strong>
and <strong>open_extern_window</strong></dt>
<dd>
The templates now use JavaScript in all applicable places,
and open external windows for most downloading and viewing
of files. If you wish to not use JavaScript and/or external
windows, then remove the feature(s) from the templates.
<p></p>
</dd>
<dt><strong>show_author</strong></dt>
<dd>
Changing this option would be quite strange and rare. If you
do not want to show the author for the revisions, then you
should remove it from the various templates.
<p></p>
</dd>
<dt><strong>hide_non_readable</strong></dt>
<dd>
This option was never used, so it has been removed.
<p></p>
</dd>
<dt><strong>flip_links_in_dirview</strong></dt>
<dd>
This option is no longer available. If you want the links in
your directory view flipped, then you may use the
<code>dir_alternate.ezt</code> template.
<p></p>
</dd>
</dl>
</blockquote>
<h3>Template Variables</h3>
<p>
Some template variables that were available in 0.8 have been
removed in 0.9. If you have custom templates that refer to these
variables, then you will need to modify your templates.
</p>
<blockquote>
<dl>
<dt><code>directory.ezt</code>: <var>headers</var></dt>
<dd>
The headers are now listed explicitly in the template,
rather than made available through a list.
<p></p>
</dd>
<dt>
<code>directory.ezt</code>:
<var>rows.cols</var>,
and <var>rows.span</var>
</dt>
<dd>
These variables were used in conjunction with the
<var>headers</var> variable to control the column
displays. This is now controlled explicitly within the
templates.
<p></p>
</dd>
<dt><code>directory.ezt</code>:
<var>rev_in_front</var></dt>
<dd>
This was used to indicate that revision links should
be used in the first column, rather than in their
standard place in the second column. Changing the
links should now be done in the template, rather than
according to this variable. You may want to look at
the <code>dir_alternate.ezt</code> template, which has
the revision in front.
<p></p>
</dd>
<dt><code>directory.ezt</code>:
<var>rows.attic</var>
and <var>rows.hide_attic_href</var></dt>
<dd>
These variable were used to manage the hide and
showing of the contents of the <code>Attic/</code>
subdirectory. Several new variables were introduced
which can be used to replace this functionality:
<var>show_attic_href</var>,
<var>hide_attic_href</var>, and <var>rows.state</var>.
<p></p>
</dd>
</dl>
</blockquote>
<hr>
<h2><a name="from7">Upgrading from ViewCVS 0.7 or earlier</a></h2>
<p>
This section discusses how to upgrade ViewCVS 0.7, or earlier,
to 0.8 or a later version of the software.
</p>
<p>
<strong>NOTE:</strong> these changes will bring you up to the
requirements of version 0.8. You must also follow the directions
for <a href="#from8">upgrading from 0.8</a>.
</p>
<h3>Configuration Options</h3>
<p>
The largest change from 0.7 to 0.8, that you will need to deal
with, is the introduction of templates. This shifted many
configuration file options into the templates, for more direct
editing of the output style, colors, and layout. Below is a list
of options that no longer exist, and where you can find their
counterpart in the current version of ViewCVS.
</p>
<p>
The following options have all been removed in ViewCVS 0.8. If
you made local changes to your ViewCVS configuration, then you
will need to edit templates in the <code>templates/</code>
subdirectory.
</p>
<blockquote>
<dl>
<dt>
The [text] section:
<strong>short_intro</strong>,
<strong>long_intro</strong>,
and <strong>doc_info</strong>
</dt>
<dd>
These options have been incorporated into the
<code>doc/help_rootview.html</code> page and the
<code>doc/help_dirview.html</code> page.
<p></p>
</dd>
<dt><strong>repository_info</strong></dt>
<dd>
This option is now incorporated into the
<code>directory.ezt</code> template.
<p></p>
</dd>
<dt><strong>table_padding</strong></dt>
<dd>
The table padding values can be changed in the
<code>directory.ezt</code> template.
<p></p>
</dd>
<dt><strong>table_border</strong></dt>
<dd>
Edit <code>directory.ezt</code> to add a border around the
directory table.
<p></p>
</dd>
<dt>
<strong>column_header_normal</strong> and
<strong>column_header_sorted</strong>
</dt>
<dd>
Edit <code>directory.ezt</code> to modify the colors of the
column headers.
<p></p>
</dd>
<dt>
<strong>extern_window_width</strong> and
<strong>extern_window_height</strong>
</dt>
<dd>
These options were never used and have been removed.
<p></p>
</dd>
<dt><strong>logo</strong></dt>
<dd>
Edit the templates directly (<code>directory.ezt</code>,
<code>log.ezt</code> or <code>log_table.ezt</code> and if
needed <code>query.ezt</code>) to alter the URL and size of
your logo.
<p></p>
</dd>
<dt><strong>allow_version_select</strong></dt>
<dd>
Edit the <code>log.ezt</code> template if you want to remove
the link which allows the user to select a revision for a
diff.
<p></p>
</dd>
<dt><strong>input_text_size</strong></dt>
<dd>
Edit the <code>log.ezt</code> template if you want to change
the size of the entry box for revisions for performing
diffs.
<p></p>
</dd>
<dt><strong>even_odd</strong></dt>
<dd>
Edit the <code>directory.ezt</code> and
<code>query.ezt</code> templates if you want to change the
colors of the rows in the directory and query result tables.
<p></p>
</dd>
<dt>Colors: <strong>text</strong>
and <strong>background</strong></dt>
<dd>
These options have been incorporated into the
<code>directory.ezt</code>, <code>log.ezt</code>, and
<code>log_table.ezt</code> templates.
<p></p>
</dd>
</dl>
</blockquote>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs@lyra.org">ViewCVS Users Group</a></address>
<!-- Created: Mon Sep 24 04:23:53 PDT 2001 -->
<!-- hhmts start -->
Last modified: Sat Dec 22 20:05:14 PST 2001
<!-- hhmts end -->
</body>
</html>

View File

@@ -4,27 +4,8 @@
<title>The ViewCVS Group</title>
</head>
<body background="images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td width="1%"><a href="index.html"><img border=0
src="images/logo.png"></a>
</td>
<td>
<h1>The ViewCVS Group</h1>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
<tr><td width="1%" valign=top>
<a href="index.html">Overview</a><br>
<a href="upgrading.html">Upgrading</a><br>
<a href="contributing.html">Contributing</a><br>
<a href="license-1.html">License</a><br>
Who<br>
</td><td colspan=2>
<body background="/images/chalk.jpg">
<h1>The ViewCVS Group</h1>
<p>
The ViewCVS Group is an informal group of people working on and
@@ -32,28 +13,20 @@
</p>
<ul>
<li><a href="http://www.lyra.org/greg/"><b>Greg Stein</b></a></li>
<li>Tanaka Akira</li>
<li>Tim Cera</li>
<li>Peter Funk</li>
<li>Jay Painter</li>
</ul>
<p>
In 2001 the project has been moved to SourceForge and some
<a href="http://sourceforge.net/project/memberlist.php?group_id=18760">more
developers</a> were given commit access.
</p>
<p>
Please note that the <a href="./">ViewCVS</a> package is offered
under a BSD-type license, which is detailed on the
<a href="license-1.html">ViewCVS License</a> page.
</p>
</td></tr></table>
<hr>
<address><a href="mailto:viewcvs-dev@lyra.org">ViewCVS Group</a></address>
<hr>
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Mon May 8 19:08:58 PDT 2000 -->
<!-- hhmts start -->
Last modified: Thu Oct 25 01:47:02 PDT 2001
Last modified: Fri May 11 14:10:54 PDT 2001
<!-- hhmts end -->
</body>
</html>