mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d08da227e9 |
125
INSTALL
125
INSTALL
@@ -9,32 +9,20 @@ INSTALLING VIEWCVS
|
|||||||
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
|
You need to have RCS installed. Specifically, "rlog", "rcsdiff",
|
||||||
and "co". This script was tested against RedHat's rcs-5.7-10.rpm
|
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
|
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
|
access to the repository (or a copy of it). Therefore, rsh/ssh or
|
||||||
pserver access doesn't work yet.
|
pserver access doesn't work yet.
|
||||||
|
|
||||||
For the checkin database to work, you will need MySQL >= 3.22,
|
2) Copy viewcvs.cgi to the cgi-script location of your web server.
|
||||||
and the Python DBAPI 2.0 module, MySQLdb. This was tested with
|
|
||||||
MySQLdb 1.12.
|
|
||||||
|
|
||||||
2) Installation is handled by the ./viewcvs-install script. Run this
|
If Python is not located in /usr/local/bin, then you'll need to
|
||||||
script and you will be prompted for a installation root path.
|
edit the first line of viewcvs.cgi.
|
||||||
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.
|
|
||||||
|
|
||||||
Note: while 'root' is usually required to create /usr/local/viewcvs,
|
3) Copy viewcvs.conf.dist to the same directory and RENAME it to
|
||||||
ViewCVS does not have to be installed as root, nor does it run as root.
|
viewcvs.conf
|
||||||
It is just as valid to place ViewCVS in a home directory, too.
|
|
||||||
|
|
||||||
Note: viewcvs-install will create directories if needed. It will
|
4) Edit viewcvs.conf for your specific configuration. In particular,
|
||||||
prompt before overwriting files that may have been modified (such
|
examine the following configuration options:
|
||||||
as viewcvs.conf), thus making it safe to install over the top of
|
|
||||||
a previous installation. It will always overwrite program files,
|
|
||||||
however.
|
|
||||||
|
|
||||||
3) Edit <install-root>viewcvs.conf for your specific configuration.
|
|
||||||
In particular, examine the following configuration options:
|
|
||||||
|
|
||||||
cvs_roots
|
cvs_roots
|
||||||
default_root
|
default_root
|
||||||
@@ -44,98 +32,11 @@ INSTALLING VIEWCVS
|
|||||||
There are some other options that are usually nice to change. See
|
There are some other options that are usually nice to change. See
|
||||||
viewcvs.conf for more information.
|
viewcvs.conf for more information.
|
||||||
|
|
||||||
4) The CGI programs are in <install-root>/cgi/. You can symlink to this
|
5) That's it. Try it out.
|
||||||
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
|
Warning: ViewCVS has not been tested on web servers operating on the
|
||||||
directly into your published HTTP directory tree (due to the MySQL
|
Win32 platform.
|
||||||
passwords in viewcvs.conf).
|
|
||||||
|
|
||||||
5) That's it for repository browsing. Instructions for getting the
|
|
||||||
SQL checkin database working are below.
|
|
||||||
|
|
||||||
|
|
||||||
WARNING: ViewCVS has not been tested on web servers operating on the
|
|
||||||
Win32 platform.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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 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 >= 1.12 installed.
|
|
||||||
Python 1.5.2 is REQUIRED by MySQLdb, therefore to use this part of
|
|
||||||
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 <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 <install-root>/viewcvs.conf file. There is a [cvsdb]
|
|
||||||
section. You will need to set:
|
|
||||||
|
|
||||||
|
|
||||||
host = # MySQL database server host
|
|
||||||
database_name = # the name of the database you created with
|
|
||||||
# make-database
|
|
||||||
user = # the read/write database user
|
|
||||||
passwd = # password for read/write database user
|
|
||||||
readonly_user = # the readonly database user -- it's pretty
|
|
||||||
# safe to use the read/write user here
|
|
||||||
readonly_passwd = # password for the readonly user
|
|
||||||
|
|
||||||
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 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
|
|
||||||
database as commits are made to the repository.
|
|
||||||
|
|
||||||
To build a database of all the commits in the CVS repository /home/cvs,
|
|
||||||
invoke: "./cvsdbadmin rebuild /home/cvs". If you want to update
|
|
||||||
the checkin database, invoke: "./cvsdbadmin update /home/cvs". The
|
|
||||||
update mode checks to see if a commit is already in the database,
|
|
||||||
and only adds it if it is 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) | <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 files:
|
|
||||||
|
|
||||||
<install-root>/html-templates/queryformtemplate.html
|
|
||||||
<install-root>/html-templates/querytemplate.html
|
|
||||||
|
|
||||||
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 queryform.cgi script and give
|
|
||||||
it a try.
|
|
||||||
|
|
||||||
|
|
||||||
IF YOU HAVE PROBLEMS ...
|
IF YOU HAVE PROBLEMS ...
|
||||||
@@ -174,7 +75,7 @@ If you've trouble to make viewcvs.cgi work:
|
|||||||
|
|
||||||
o check the ViewCVS home page:
|
o check the ViewCVS home page:
|
||||||
|
|
||||||
http://viewcvs.sourceforge.net/
|
http://www.lyra.org/greg/python/viewcvs/
|
||||||
|
|
||||||
o review the ViewCVS mailing list archive to see if somebody else had
|
o review the ViewCVS mailing list archive to see if somebody else had
|
||||||
the same problem, and it was solved:
|
the same problem, and it was solved:
|
||||||
|
22
TODO
22
TODO
@@ -14,30 +14,14 @@ TODO ITEMS
|
|||||||
*) committing with a specific revision number:
|
*) committing with a specific revision number:
|
||||||
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
|
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
|
||||||
|
|
||||||
*) add capability similar to cvs2cl.pl:
|
|
||||||
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
|
|
||||||
suggestion from Chris Meyer <cmeyer@gatan.com>.
|
|
||||||
|
|
||||||
*) add a tree view of the directory structure (and files?)
|
|
||||||
|
|
||||||
*) include a ConfigParser.py to help older Python installations
|
|
||||||
|
|
||||||
*) add a check for the rcs programs/paths to 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
|
KNOWN BUGS
|
||||||
----------
|
----------
|
||||||
|
*) from Harri Pasanen, Feb 7: when hideattic is set, files added in a
|
||||||
|
branch are not visible.
|
||||||
|
|
||||||
*) from Dieter Deyke, Jan 12: if the CVS revisions differ by just a
|
*) from Dieter Deyke, Jan 12: if the CVS revisions differ by just a
|
||||||
keyword, then the diff output chokes.
|
keyword, then the diff output chokes.
|
||||||
|
|
||||||
*) no scroll bar in Netscape browser when you click "as text" on an
|
*) no scroll bar in Netscape browser when you click "as text" on an
|
||||||
HTML document.
|
HTML document.
|
||||||
|
|
||||||
*) time.timezone seems to not be available on some 1.5.2 installs
|
|
||||||
|
529
cgi/granny.cgi
529
cgi/granny.cgi
@@ -1,529 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- Mode: python -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Granny.py - display CVS annotations in HTML
|
|
||||||
with lines colored by code age in days.
|
|
||||||
|
|
||||||
Original Perl version by J. Gabriel Foster (gabe@sgrail.com)
|
|
||||||
Posted to info-cvs on 02/27/1997
|
|
||||||
You can still get the original shell archive here:
|
|
||||||
http://www.geocrawler.com/archives/3/382/1997/2/0/2103824/
|
|
||||||
|
|
||||||
Perl modifications for NT by Marc Paquette (marcpa@cam.org)
|
|
||||||
|
|
||||||
Python port and CGI modifications by Brian Lenihan (brianl@real.com)
|
|
||||||
|
|
||||||
From the original granny.pl README:
|
|
||||||
|
|
||||||
--
|
|
||||||
What is Granny? Why do I care?
|
|
||||||
|
|
||||||
Granny is a tool for viewing cvs annotation information graphically. Using
|
|
||||||
Netscape, granny indicates the age of lines by color. Red lines are new,
|
|
||||||
blue lines are old. This information can be very useful in determining
|
|
||||||
what lines are 'hot'. New lines are more likely to contain bugs, and this
|
|
||||||
is an easy way to visualize that information.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
Netscape (version 2.0 or better)
|
|
||||||
Perl5
|
|
||||||
CVS 1.9 (1.8 should work, but I have not tried it.)
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
Put granny somewhere in your path. You may need to edit the
|
|
||||||
first line to point to your perl5 binary and libraries.
|
|
||||||
|
|
||||||
What to do:
|
|
||||||
Run granny just like you would 'cvs annotate' for a single file.
|
|
||||||
granny thefile.C
|
|
||||||
|
|
||||||
To find out who is to blame for that new 'feature'.
|
|
||||||
granny -U thefile.C
|
|
||||||
|
|
||||||
For all your options:
|
|
||||||
granny -h
|
|
||||||
|
|
||||||
Questions, Comments, Assertions?
|
|
||||||
send e-mail to the author: Gabe Foster (gabe@sgrail.com)
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
I'm not the first person to try this sort of display, I just read about it
|
|
||||||
in a magazine somewhere and decided that cvs had all the information
|
|
||||||
I needed. To whomever first had this idea, it's a great one.
|
|
||||||
|
|
||||||
Granny is free, please use it as you see fit. I give no warranties.
|
|
||||||
As a courtesy, I ask that you tell me about any modifications you have made,
|
|
||||||
and also ask that you acknowledge my work if you use Granny in your own
|
|
||||||
software.
|
|
||||||
--
|
|
||||||
|
|
||||||
|
|
||||||
Granny.py:
|
|
||||||
|
|
||||||
granny.py [-h][-d days][-i input][-o output][-DUV] file
|
|
||||||
|
|
||||||
|
|
||||||
-h: Get this help display.
|
|
||||||
-i: Specify the input file. (Use - for stdin.)
|
|
||||||
-o: Specify the output file. (Use - for stdout.)
|
|
||||||
-d: Specify the day range for the coloring.\n
|
|
||||||
-r: Specify the cvs revision of the file.
|
|
||||||
-D: Display the date the line was last edited.
|
|
||||||
-U Display the user that last edited the line.
|
|
||||||
-V: Display the version the line was last edited.
|
|
||||||
|
|
||||||
By default, granny.py executes cvs annotate on a FILE and
|
|
||||||
runs netscape to display the graphic.
|
|
||||||
|
|
||||||
It is assumed that cvs and Netscape (for command line version) are
|
|
||||||
in your path.
|
|
||||||
|
|
||||||
If granny.py is placed in the cgi-bin directory of your Web
|
|
||||||
server, it will act as a CGI script. The working directory
|
|
||||||
defaults to /usr/tmp, but it can be overridden in the class
|
|
||||||
constructor:
|
|
||||||
|
|
||||||
A = CGIAnnotate(tempdir='/tmp')
|
|
||||||
|
|
||||||
Required fields:
|
|
||||||
|
|
||||||
root The full path to the cvs root directory.
|
|
||||||
Name The module/filename of the annotated file.
|
|
||||||
|
|
||||||
Optional fields:
|
|
||||||
|
|
||||||
rev The cvs revision number to use. (default HEAD).
|
|
||||||
|
|
||||||
Set the following fields to display extra info:
|
|
||||||
|
|
||||||
showUser Display the user that last edited the line.
|
|
||||||
showVersion Display version that the line was last edited in.
|
|
||||||
showDate Display the date the line was last edited.
|
|
||||||
|
|
||||||
http://yourserver.yourdomain.com/cgi-bin/granny.py?root=/cvsroot&Name=module/file
|
|
||||||
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
|
|
||||||
Add support for determining the MIME type of files and/or a binary check.
|
|
||||||
- easily done by parsing Apache (mime.conf) or Roxen (extensions) MIME
|
|
||||||
files.
|
|
||||||
Consider adding buttons to HTML for optional display fields.
|
|
||||||
Add support for launching other browsers.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import string
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import getopt
|
|
||||||
import cStringIO
|
|
||||||
import tempfile
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
|
|
||||||
month_num = {
|
|
||||||
'Jan' : 1, 'Feb' : 2, 'Mar' : 3, 'Apr' : 4, 'May' : 5, 'Jun' : 6,
|
|
||||||
'Jul' : 7, 'Aug' : 8, 'Sep' : 9, 'Oct' : 10, 'Nov' : 11, 'Dec' : 12
|
|
||||||
}
|
|
||||||
|
|
||||||
class Annotate:
|
|
||||||
def __init__(self):
|
|
||||||
self.day_range = 365
|
|
||||||
self.counter = 0
|
|
||||||
self.color_table = {}
|
|
||||||
self.user = {}
|
|
||||||
self.version = {}
|
|
||||||
self.rtime = {}
|
|
||||||
self.source = {}
|
|
||||||
self.tmp = None
|
|
||||||
self.tmpfile = None
|
|
||||||
self.revision = ''
|
|
||||||
self.showUser = 0
|
|
||||||
self.showDate = 0
|
|
||||||
self.showVersion = 0
|
|
||||||
self.set_today()
|
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
self.process_args()
|
|
||||||
self.parse_raw_annotated_file()
|
|
||||||
self.write_annotated_html_file()
|
|
||||||
if self.tmp:
|
|
||||||
self.display_annotated_html_file()
|
|
||||||
finally:
|
|
||||||
if sys.exc_info()[0] is not None:
|
|
||||||
traceback.print_exc()
|
|
||||||
self.cleanup()
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
if self.tmp:
|
|
||||||
self.tmp.close()
|
|
||||||
os.unlink(self.tmpfile)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def getoutput(self, cmd):
|
|
||||||
"""
|
|
||||||
Get stdin and stderr from cmd and
|
|
||||||
return exit status and captured output
|
|
||||||
"""
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
# os.popen is broken on win32, but seems to work so far...
|
|
||||||
pipe = os.popen('%s 2>&1' % cmd, 'r')
|
|
||||||
else:
|
|
||||||
pipe = os.popen('{ %s ; } 2>&1' % cmd, 'r')
|
|
||||||
text = pipe.read()
|
|
||||||
sts = pipe.close()
|
|
||||||
if sts == None:
|
|
||||||
sts = 0
|
|
||||||
if text[:-1] == '\n':
|
|
||||||
text = text[-1:]
|
|
||||||
return sts, text
|
|
||||||
|
|
||||||
|
|
||||||
def set_today(self):
|
|
||||||
"""
|
|
||||||
compute the start of this day
|
|
||||||
"""
|
|
||||||
(year,mon,day,hour,min,sec,dow,doy,dst) = time.gmtime(time.time())
|
|
||||||
self.today = time.mktime((year,mon,day,0,0,0,0,0,0))
|
|
||||||
|
|
||||||
|
|
||||||
def get_today(self):
|
|
||||||
return self.today
|
|
||||||
|
|
||||||
|
|
||||||
# entify stuff which breaks HTML display
|
|
||||||
# this was lifted from some Zope Code in
|
|
||||||
# StructuredText.py
|
|
||||||
#
|
|
||||||
# XXX try it with string.replace and run it in the profiler
|
|
||||||
|
|
||||||
def html_quote(self,v,
|
|
||||||
character_entities=((re.compile('&'), '&'),
|
|
||||||
(re.compile("<"), '<' ),
|
|
||||||
(re.compile(">"), '>' ),
|
|
||||||
(re.compile('"'), '"'))):
|
|
||||||
gsub = re.sub
|
|
||||||
|
|
||||||
text=str(v)
|
|
||||||
for regexp,name in character_entities:
|
|
||||||
text=gsub(regexp,name,text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def display_annotated_html_file(self):
|
|
||||||
if os.name == 'nt':
|
|
||||||
path = '"C:\\Program Files\\Netscape\\Communicator'\
|
|
||||||
'\\Program\\Netscape"'
|
|
||||||
|
|
||||||
if os.system('%s %s' % (path, self.tmpfile)) != 0:
|
|
||||||
sys.stderr.write('%s: Unable to start Netscape' % sys.argv[0])
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
if os.system('netscape -remote openFile\(%s\)' %
|
|
||||||
self.tmpfile) != 0:
|
|
||||||
sys.stderr.write('%s: Trying to run netscape, please wait\n' %
|
|
||||||
sys.argv[0])
|
|
||||||
if os.system('netscape &') == 0:
|
|
||||||
for i in range(10):
|
|
||||||
time.sleep(1)
|
|
||||||
if os.system('netscape -remote openFile\(%s\)' %
|
|
||||||
self.tmpfile) == 0:
|
|
||||||
break
|
|
||||||
if i == 10:
|
|
||||||
sys.stderr.write('%s:Unable to start netscape\n' %
|
|
||||||
sys.argv[0])
|
|
||||||
else:
|
|
||||||
sys.stderr.write('%s:Unable to start netscape\n' %
|
|
||||||
sys.argv[0])
|
|
||||||
|
|
||||||
# give Netscape time to read the file
|
|
||||||
# XXX big files may raise an OSError exception on NT
|
|
||||||
# if the sleep is too short.
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
|
|
||||||
def get_opts(self):
|
|
||||||
opt_dict = {}
|
|
||||||
if not len(sys.argv[1:]) > 0:
|
|
||||||
self.usage()
|
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'DUVhi:o:d:r:')
|
|
||||||
for k,v in opts:
|
|
||||||
opt_dict[k] = v
|
|
||||||
return opt_dict
|
|
||||||
|
|
||||||
|
|
||||||
def process_args(self):
|
|
||||||
opts = self.get_opts()
|
|
||||||
|
|
||||||
if opts.has_key('-r'):
|
|
||||||
self.revision = '-r%s' % opts['-r']
|
|
||||||
|
|
||||||
if opts.has_key('-h'):
|
|
||||||
self.usage(help=1)
|
|
||||||
|
|
||||||
if opts.has_key('-i'):
|
|
||||||
if opts['-i'] != '-':
|
|
||||||
self.filename = v
|
|
||||||
infile = open(filename, 'r')
|
|
||||||
sys.stdin = infile
|
|
||||||
else:
|
|
||||||
self.file = sys.stdin
|
|
||||||
else:
|
|
||||||
self.filename = sys.argv[len(sys.argv) - 1]
|
|
||||||
|
|
||||||
cmd = 'cvs annotate %s %s' % (self.revision, self.filename)
|
|
||||||
|
|
||||||
status, text = self.getoutput(cmd)
|
|
||||||
|
|
||||||
if status != 0 or text == '':
|
|
||||||
sys.stderr.write("Can't run cvs annotate on %s\n" %
|
|
||||||
self.filename)
|
|
||||||
sys.stderr.write('%s\n' % text)
|
|
||||||
sys.exit(1)
|
|
||||||
self.file = cStringIO.StringIO(text)
|
|
||||||
|
|
||||||
|
|
||||||
if opts.has_key('-o'):
|
|
||||||
if opts['-o'] != '-':
|
|
||||||
outfile = open(v, 'w')
|
|
||||||
sys.stdout = outfile
|
|
||||||
else:
|
|
||||||
# this could be done without a temp file
|
|
||||||
target = sys.argv[len(sys.argv) -1]
|
|
||||||
self.tmpfile = tempfile.mktemp()
|
|
||||||
self.tmp = open(self.tmpfile, 'w')
|
|
||||||
sys.stdout = self.tmp
|
|
||||||
|
|
||||||
|
|
||||||
if opts.has_key('-d'):
|
|
||||||
if opts['-d'] > 0:
|
|
||||||
self.day_range = opts['-d']
|
|
||||||
|
|
||||||
if opts.has_key('-D'):
|
|
||||||
self.showDate = 1
|
|
||||||
|
|
||||||
if opts.has_key('-U'):
|
|
||||||
self.showUser = 1
|
|
||||||
|
|
||||||
if opts.has_key('-V'):
|
|
||||||
self.showVersion = 1
|
|
||||||
|
|
||||||
|
|
||||||
def parse_raw_annotated_file(self):
|
|
||||||
ann = re.compile('((\d+\.?)+)\s+\((\w+)\s+(\d\d)-'\
|
|
||||||
'(\w{3})-(\d\d)\): (.*)')
|
|
||||||
|
|
||||||
text = self.file.read()
|
|
||||||
lines = string.split(text, '\n')
|
|
||||||
for line in lines:
|
|
||||||
# Parse an annotate string
|
|
||||||
m = ann.search(line)
|
|
||||||
if m:
|
|
||||||
self.version[self.counter] = m.group(1)
|
|
||||||
self.user[self.counter] = m.group(3)
|
|
||||||
oldtime = self.today - time.mktime((
|
|
||||||
int(m.group(6)),
|
|
||||||
int(month_num[m.group(5)]),
|
|
||||||
int(m.group(4)),0,0,0,0,0,0))
|
|
||||||
|
|
||||||
self.rtime[self.counter] = oldtime / 86400
|
|
||||||
self.source[self.counter] = self.html_quote(m.group(7))
|
|
||||||
else:
|
|
||||||
self.source[self.counter] = self.html_quote(line)
|
|
||||||
pass
|
|
||||||
self.counter = self.counter + 1
|
|
||||||
|
|
||||||
|
|
||||||
def write_annotated_html_file(self):
|
|
||||||
if os.environ.has_key('SCRIPT_NAME'):
|
|
||||||
print 'Status: 200 OK\r\n'
|
|
||||||
print 'Content-type: text/html\r\n\r\n'
|
|
||||||
|
|
||||||
print ('<html><head><title>%s</title></head>\n' \
|
|
||||||
'<body bgcolor="#000000">\n' \
|
|
||||||
'<font color="#FFFFFF"><H1>File %s</H1>\n' \
|
|
||||||
'<H3>Code age in days</H2>' % (self.filename, self.filename))
|
|
||||||
|
|
||||||
for i in range(self.day_range + 1):
|
|
||||||
self.color_table[i] = \
|
|
||||||
self.hsvtorgb(240.0 * i / self.day_range, 1.0, 1.0)
|
|
||||||
|
|
||||||
step = self.day_range / 40
|
|
||||||
if step < 5:
|
|
||||||
step = 1
|
|
||||||
while self.day_range/step > 40:
|
|
||||||
step = step + 1
|
|
||||||
if step >= 5:
|
|
||||||
if step != 5:
|
|
||||||
step = step + 5 - (step % 5)
|
|
||||||
while self.day_range / step > 20:
|
|
||||||
step = step + 5
|
|
||||||
|
|
||||||
for i in range(self.day_range + 1, step):
|
|
||||||
print '<font color=%s>%s ' % (self.color_table[i], i),
|
|
||||||
print '<pre><code>'
|
|
||||||
|
|
||||||
for i in range(self.counter):
|
|
||||||
if self.showUser and self.user.has_key(i):
|
|
||||||
print '%s%s ' % ('<font color=#FFFFFF>',
|
|
||||||
string.ljust(self.user[i],10)),
|
|
||||||
|
|
||||||
if self.showVersion and self.version.has_key(i):
|
|
||||||
print '%s%s ' % ('<font color=#FFFFFF>',
|
|
||||||
string.ljust(self.version[i],6)),
|
|
||||||
|
|
||||||
if self.showDate and self.rtime.has_key(i):
|
|
||||||
(year,mon,day,hour,min,sec,dow,doy,dst) = time.gmtime(
|
|
||||||
self.today - self.rtime[i] * 86400)
|
|
||||||
|
|
||||||
print '<font color=#FFFFFF>%02d/%02d/%4d ' % (mon, day, year),
|
|
||||||
|
|
||||||
|
|
||||||
if self.rtime.get(i, self.day_range) < self.day_range:
|
|
||||||
fcolor = self.color_table.get(
|
|
||||||
self.rtime[i],
|
|
||||||
self.color_table[self.day_range])
|
|
||||||
else:
|
|
||||||
fcolor = self.color_table[self.day_range]
|
|
||||||
|
|
||||||
print '<font color=%s> %s' % (fcolor, self.source[i])
|
|
||||||
|
|
||||||
print ('</code></pre>\n' \
|
|
||||||
'<font color=#FFFFFF>\n' \
|
|
||||||
'<H5>Granny original Perl version by' \
|
|
||||||
'<I>J. Gabriel Foster</I>\n' \
|
|
||||||
'<ADDRESS><A HREF=\"mailto:gabe@sgrail.com\">'\
|
|
||||||
'gabe@sgrail.com</A></ADDRESS>\n' \
|
|
||||||
'Python version by <I>Brian Lenihan</I>\n' \
|
|
||||||
'<ADDRESS><A HREF=\"mailto:brianl@real.com\">' \
|
|
||||||
'brianl@real.com</A></ADDRESS>\n' \
|
|
||||||
'</body></html>')
|
|
||||||
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
|
|
||||||
def hsvtorgb(self,h,s,v):
|
|
||||||
"""
|
|
||||||
a veritable technicolor spew
|
|
||||||
"""
|
|
||||||
if s == 0.0:
|
|
||||||
r = v; g = v; b = v
|
|
||||||
else:
|
|
||||||
if h < 0:
|
|
||||||
h = h + 360.0
|
|
||||||
elif h >= 360.0:
|
|
||||||
h = h - 360.0
|
|
||||||
h = h / 60.0
|
|
||||||
i = int(h)
|
|
||||||
f = h - i
|
|
||||||
|
|
||||||
if s > 1.0:
|
|
||||||
s = 1.0
|
|
||||||
p = v * (1.0 - s)
|
|
||||||
q = v * (1.0 - (s * f))
|
|
||||||
t = v * (1.0 - (s * (1.0 - f)))
|
|
||||||
|
|
||||||
if i == 0: r = v; g = t; b = p
|
|
||||||
elif i == 1: r = q; g = v; b = p
|
|
||||||
elif i == 2: r = p; g = v; b = t
|
|
||||||
elif i == 3: r = p; g = q; b = v
|
|
||||||
elif i == 4: r = t; g = p; b = v
|
|
||||||
elif i == 5: r = v; g = p; b = q
|
|
||||||
|
|
||||||
return '#%02X%02X%02X' % (r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def usage(self, help=None):
|
|
||||||
sys.stderr.write('\nusage: %s %s\n\n' % (
|
|
||||||
sys.argv[0],
|
|
||||||
'[-hDUV][-d days][-i input][-o output][-r rev] FILE')
|
|
||||||
)
|
|
||||||
|
|
||||||
if help is not None:
|
|
||||||
sys.stderr.write(
|
|
||||||
'-h: Get this help display.\n' \
|
|
||||||
'-i: Specify the input file. (Use - for stdin.)\n' \
|
|
||||||
'-o: Specify the output file. (Use - for stdout.)\n' \
|
|
||||||
'-d: Specify the day range for the coloring.\n' \
|
|
||||||
'-r: Specify the cvs revision of the file.\n' \
|
|
||||||
'-D: Display the date the line was last edited.\n' \
|
|
||||||
'-U Display the user that last edited the line.\n' \
|
|
||||||
'-V: Display the version the line was last edited.\n\n' \
|
|
||||||
'By default, %s executes a cvs annotate on a FILE and\n' \
|
|
||||||
'runs netscape to display the graphical ' \
|
|
||||||
'annotation\n' % sys.argv[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CGIAnnotate(Annotate):
|
|
||||||
def __init__(self,tempdir='/usr/tmp'):
|
|
||||||
Annotate.__init__(self)
|
|
||||||
if os.name == 'nt':
|
|
||||||
self.tempdir = os.environ.get('TEMP') or os.environ.get('TMP')
|
|
||||||
else:
|
|
||||||
# XXX need a sanity check here
|
|
||||||
self.tempdir = tempdir
|
|
||||||
os.chdir(self.tempdir)
|
|
||||||
|
|
||||||
def process_args(self):
|
|
||||||
f = cgi.FieldStorage()
|
|
||||||
cvsroot = f['root'].value
|
|
||||||
if f.has_key('showUser'):
|
|
||||||
self.showUser = 1
|
|
||||||
if f.has_key('showDate'):
|
|
||||||
self.showDate = 1
|
|
||||||
if f.has_key('showVersion'):
|
|
||||||
self.showVersion = 1
|
|
||||||
if f.has_key('rev'):
|
|
||||||
self.revision = '-r%s' % f['rev'].value
|
|
||||||
path = f['Name'].value
|
|
||||||
module = os.path.dirname(path)
|
|
||||||
self.workingdir = 'ann.%s' % os.getpid()
|
|
||||||
self.filename = os.path.basename(path)
|
|
||||||
|
|
||||||
os.mkdir(self.workingdir)
|
|
||||||
os.chdir(os.path.join(self.tempdir, self.workingdir))
|
|
||||||
os.system('cvs -d %s co %s' % (cvsroot, path))
|
|
||||||
os.chdir(module)
|
|
||||||
|
|
||||||
cmd = 'cvs annotate %s %s' % (self.revision, self.filename)
|
|
||||||
status, text = self.getoutput(cmd)
|
|
||||||
|
|
||||||
if status != 0 or text == '':
|
|
||||||
text = "Can't run cvs annotate on %s\n" % path
|
|
||||||
self.file = cStringIO.StringIO(text)
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
os.chdir(self.tempdir)
|
|
||||||
os.system('rm -rf %s' % self.workingdir)
|
|
||||||
|
|
||||||
|
|
||||||
def display_annotated_html_file(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def usage(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if os.environ.has_key('SCRIPT_NAME'):
|
|
||||||
import cgi
|
|
||||||
A = CGIAnnotate()
|
|
||||||
else:
|
|
||||||
A = Annotate()
|
|
||||||
A.run()
|
|
||||||
|
|
314
cgi/query.cgi
314
cgi/query.cgi
@@ -1,314 +0,0 @@
|
|||||||
#!/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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# CGI script to process and display queries to CVSdb
|
|
||||||
#
|
|
||||||
# This script is part of the ViewCVS package. More information can be
|
|
||||||
# found at 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
|
|
||||||
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 = " ";
|
|
||||||
else:
|
|
||||||
cTime = time.strftime("%y/%m/%d %H:%M", time.localtime(cTime))
|
|
||||||
|
|
||||||
cAuthor = commit.GetAuthor()
|
|
||||||
if not cAuthor:
|
|
||||||
cAuthor = " ";
|
|
||||||
|
|
||||||
cFile = os.path.join(commit.GetDirectory(), commit.GetFile())
|
|
||||||
if not cFile:
|
|
||||||
cFile = " ";
|
|
||||||
|
|
||||||
cRevision = commit.GetRevision()
|
|
||||||
if not cRevision:
|
|
||||||
cRevision = " ";
|
|
||||||
|
|
||||||
cBranch = commit.GetBranch()
|
|
||||||
if not cBranch:
|
|
||||||
cBranch = " ";
|
|
||||||
|
|
||||||
cPlusMinus = '%d/%d' % (commit.GetPlusCount(), commit.GetMinusCount())
|
|
||||||
|
|
||||||
cDescription = commit.GetDescription()
|
|
||||||
if not cDescription:
|
|
||||||
cDescription = " ";
|
|
||||||
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()
|
|
@@ -1,54 +0,0 @@
|
|||||||
#!/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()
|
|
2344
cgi/viewcvs.cgi
2344
cgi/viewcvs.cgi
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
# Configuration file for ViewCVS
|
# Configuration file for ViewCVS
|
||||||
#
|
#
|
||||||
# Information on ViewCVS is located at the following web site:
|
# Information on ViewCVS is located at the following web site:
|
||||||
# http://viewcvs.sourceforge.net/
|
# http://www.lyra.org/greg/python/viewcvs/
|
||||||
#
|
#
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -28,8 +28,6 @@
|
|||||||
# long_intro
|
# long_intro
|
||||||
# repository_info
|
# repository_info
|
||||||
#
|
#
|
||||||
# use_enscript
|
|
||||||
#
|
|
||||||
# For Python source colorization:
|
# For Python source colorization:
|
||||||
#
|
#
|
||||||
# py2html_path
|
# py2html_path
|
||||||
@@ -96,55 +94,9 @@ main_title = CVS Repository
|
|||||||
# This should contain a list of modules in the repository that should not be
|
# This should contain a list of modules in the repository that should not be
|
||||||
# displayed (by default or by explicit path specification).
|
# displayed (by default or by explicit path specification).
|
||||||
#
|
#
|
||||||
# This configuration can be a simple list of modules, or it can get quite
|
|
||||||
# complex:
|
|
||||||
#
|
|
||||||
# *) The "!" can be used before a module to explicitly state that it
|
|
||||||
# is NOT forbidden. Whenever this form is seen, then all modules will
|
|
||||||
# be forbidden unless one of the "!" modules match.
|
|
||||||
#
|
|
||||||
# *) Shell-style "glob" expressions may be used. "*" will match any
|
|
||||||
# sequence of zero or more characters, "?" will match any single
|
|
||||||
# character, "[seq]" will match any character in seq, and "[!seq]"
|
|
||||||
# will match any character not in seq.
|
|
||||||
#
|
|
||||||
# *) Tests are performed in sequence. The first match will terminate the
|
|
||||||
# testing. This allows for more complex allow/deny patterns.
|
|
||||||
#
|
|
||||||
# Tests are case-sensitive.
|
|
||||||
#
|
|
||||||
forbidden =
|
forbidden =
|
||||||
|
# forbidden = example
|
||||||
# Some examples:
|
# forbidden = example1, example2
|
||||||
#
|
|
||||||
# Disallow "example" but allow all others:
|
|
||||||
# forbidden = example
|
|
||||||
#
|
|
||||||
# Disallow "example1" and "example2" but allow all others:
|
|
||||||
# forbidden = example1, example2
|
|
||||||
#
|
|
||||||
# Allow *only* "example1" and "example2":
|
|
||||||
# forbidden = !example1, !example2
|
|
||||||
#
|
|
||||||
# Forbid modules starting with "x":
|
|
||||||
# forbidden = x*
|
|
||||||
#
|
|
||||||
# Allow modules starting with "x" but no others:
|
|
||||||
# forbidden = !x*
|
|
||||||
#
|
|
||||||
# Allow "xml", forbid other modules starting with "x", and allow the rest:
|
|
||||||
# forbidden = !xml, x*, !*
|
|
||||||
#
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
[cvsdb]
|
|
||||||
|
|
||||||
#host = localhost
|
|
||||||
#database_name = ViewCVS
|
|
||||||
#user =
|
|
||||||
#passwd =
|
|
||||||
#readonly_user =
|
|
||||||
#readonly_passwd =
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
[images]
|
[images]
|
||||||
@@ -240,7 +192,7 @@ long_intro =
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This script
|
This script
|
||||||
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
|
(<a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS</a>)
|
||||||
has been written by Greg Stein
|
has been written by Greg Stein
|
||||||
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
||||||
based on the
|
based on the
|
||||||
@@ -252,7 +204,7 @@ long_intro =
|
|||||||
Licence</a>.
|
Licence</a>.
|
||||||
If you would like to use this CGI script on your own web server and
|
If you would like to use this CGI script on your own web server and
|
||||||
CVS tree, see Greg's
|
CVS tree, see Greg's
|
||||||
<a href="http://viewcvs.sourceforge.net/">ViewCVS distribution
|
<a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS distribution
|
||||||
site</a>.
|
site</a>.
|
||||||
Please send any suggestions, comments, etc. to
|
Please send any suggestions, comments, etc. to
|
||||||
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
|
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
|
||||||
@@ -262,9 +214,9 @@ doc_info =
|
|||||||
<h3>CVS Documentation</h3>
|
<h3>CVS Documentation</h3>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>
|
<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
|
<a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
|
||||||
User's Guide</a><br>
|
User's Guide</a><br>
|
||||||
|
<a href="http://www.arc.unm.edu/~rsahu/cvs.html">CVS Tutorial</a><br>
|
||||||
<a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</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
|
<a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet
|
||||||
another CVS tutorial (a little old, but nice)</a><br>
|
another CVS tutorial (a little old, but nice)</a><br>
|
||||||
@@ -345,7 +297,7 @@ hr_ignore_keyword_subst = 1
|
|||||||
# NOTE: this requires rw-access to the CVSROOT/history file, and rw-access to
|
# 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
|
# the subdirectory to place the lock... so you maybe don't want it
|
||||||
# WARNING: this is not yet implemented!!
|
# WARNING: this is not yet implemented!!
|
||||||
allow_annotate = 1
|
allow_annotate = 0
|
||||||
|
|
||||||
# allow pretty-printed version of files
|
# allow pretty-printed version of files
|
||||||
allow_markup = 1
|
allow_markup = 1
|
||||||
@@ -416,59 +368,4 @@ diff_font_size = -1
|
|||||||
# the width of the textinput in the request-diff-form
|
# the width of the textinput in the request-diff-form
|
||||||
input_text_size = 12
|
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/
|
|
||||||
|
|
||||||
#
|
|
||||||
# ViewCVS has its own set of mappings from filename extensions and filenames
|
|
||||||
# to languages. If the language is not supported by enscript, then it can
|
|
||||||
# be listed here to disable the use of enscript.
|
|
||||||
#
|
|
||||||
disable_enscript_lang =
|
|
||||||
# disable_enscript_lang = perl, cpp
|
|
||||||
|
|
||||||
#
|
|
||||||
# ViewCVS can generate tarball from a repository on the fly.
|
|
||||||
#
|
|
||||||
allow_tar = 0
|
|
||||||
# allow_tar = 1
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
[vhosts]
|
|
||||||
### DOC
|
|
||||||
|
|
||||||
# vhost1 = glob1, glob2
|
|
||||||
# vhost2 = glob3, glob4
|
|
||||||
|
|
||||||
# [vhost1-section]
|
|
||||||
# option = value
|
|
||||||
# [vhost1-othersection]
|
|
||||||
# option = value
|
|
||||||
# [vhost2-section]
|
|
||||||
# option = value
|
|
||||||
|
|
||||||
#
|
|
||||||
# Here is an example:
|
|
||||||
#
|
|
||||||
# [vhosts]
|
|
||||||
# lyra = *lyra.org
|
|
||||||
#
|
|
||||||
# [lyra-general]
|
|
||||||
# forbidden = hideme
|
|
||||||
#
|
|
||||||
# [lyra-options]
|
|
||||||
# show_logs = 0
|
|
||||||
#
|
|
||||||
# Note that "lyra" is the "canonical" name for all hosts in the lyra.org
|
|
||||||
# domain. This canonical name is then used within the additional, vhost-
|
|
||||||
# specific sections to override specific values in the common sections.
|
|
||||||
#
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
|
@@ -1,81 +0,0 @@
|
|||||||
<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>
|
|
@@ -1,30 +0,0 @@
|
|||||||
<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> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
<th align=left valign=top> </th>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
153
lib/PyFontify.py
153
lib/PyFontify.py
@@ -1,153 +0,0 @@
|
|||||||
"""Module to analyze Python source code; for syntax coloring tools.
|
|
||||||
|
|
||||||
Interface:
|
|
||||||
tags = fontify(pytext, searchfrom, searchto)
|
|
||||||
|
|
||||||
The 'pytext' argument is a string containing Python source code.
|
|
||||||
The (optional) arguments 'searchfrom' and 'searchto' may contain a slice in pytext.
|
|
||||||
The returned value is a list of tuples, formatted like this:
|
|
||||||
[('keyword', 0, 6, None), ('keyword', 11, 17, None), ('comment', 23, 53, None), etc. ]
|
|
||||||
The tuple contents are always like this:
|
|
||||||
(tag, startindex, endindex, sublist)
|
|
||||||
tag is one of 'keyword', 'string', 'comment' or 'identifier'
|
|
||||||
sublist is not used, hence always None.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Based on FontText.py by Mitchell S. Chapman,
|
|
||||||
# which was modified by Zachary Roadhouse,
|
|
||||||
# then un-Tk'd by Just van Rossum.
|
|
||||||
# Many thanks for regular expression debugging & authoring are due to:
|
|
||||||
# Tim (the-incredib-ly y'rs) Peters and Cristian Tismer
|
|
||||||
# So, who owns the copyright? ;-) How about this:
|
|
||||||
# Copyright 1996-1997:
|
|
||||||
# Mitchell S. Chapman,
|
|
||||||
# Zachary Roadhouse,
|
|
||||||
# Tim Peters,
|
|
||||||
# Just van Rossum
|
|
||||||
|
|
||||||
__version__ = "0.3.1"
|
|
||||||
|
|
||||||
import string, regex
|
|
||||||
|
|
||||||
# First a little helper, since I don't like to repeat things. (Tismer speaking)
|
|
||||||
import string
|
|
||||||
def replace(where, what, with):
|
|
||||||
return string.join(string.split(where, what), with)
|
|
||||||
|
|
||||||
# This list of keywords is taken from ref/node13.html of the
|
|
||||||
# Python 1.3 HTML documentation. ("access" is intentionally omitted.)
|
|
||||||
keywordsList = [
|
|
||||||
"del", "from", "lambda", "return",
|
|
||||||
"and", "elif", "global", "not", "try",
|
|
||||||
"break", "else", "if", "or", "while",
|
|
||||||
"class", "except", "import", "pass",
|
|
||||||
"continue", "finally", "in", "print",
|
|
||||||
"def", "for", "is", "raise"]
|
|
||||||
|
|
||||||
# Build up a regular expression which will match anything
|
|
||||||
# interesting, including multi-line triple-quoted strings.
|
|
||||||
commentPat = "#.*"
|
|
||||||
|
|
||||||
pat = "q[^\q\n]*\(\\\\[\000-\377][^\q\n]*\)*q"
|
|
||||||
quotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')
|
|
||||||
|
|
||||||
# Way to go, Tim!
|
|
||||||
pat = """
|
|
||||||
qqq
|
|
||||||
[^\\q]*
|
|
||||||
\(
|
|
||||||
\( \\\\[\000-\377]
|
|
||||||
\| q
|
|
||||||
\( \\\\[\000-\377]
|
|
||||||
\| [^\\q]
|
|
||||||
\| q
|
|
||||||
\( \\\\[\000-\377]
|
|
||||||
\| [^\\q]
|
|
||||||
\)
|
|
||||||
\)
|
|
||||||
\)
|
|
||||||
[^\\q]*
|
|
||||||
\)*
|
|
||||||
qqq
|
|
||||||
"""
|
|
||||||
pat = string.join(string.split(pat), '') # get rid of whitespace
|
|
||||||
tripleQuotePat = replace(pat, "q", "'") + "\|" + replace(pat, 'q', '"')
|
|
||||||
|
|
||||||
# Build up a regular expression which matches all and only
|
|
||||||
# Python keywords. This will let us skip the uninteresting
|
|
||||||
# identifier references.
|
|
||||||
# nonKeyPat identifies characters which may legally precede
|
|
||||||
# a keyword pattern.
|
|
||||||
nonKeyPat = "\(^\|[^a-zA-Z0-9_.\"']\)"
|
|
||||||
|
|
||||||
keyPat = nonKeyPat + "\("
|
|
||||||
for keyword in keywordsList:
|
|
||||||
keyPat = keyPat + keyword + "\|"
|
|
||||||
keyPat = keyPat[:-2] + "\)" + nonKeyPat
|
|
||||||
|
|
||||||
matchPat = keyPat + "\|" + commentPat + "\|" + tripleQuotePat + "\|" + quotePat
|
|
||||||
matchRE = regex.compile(matchPat)
|
|
||||||
|
|
||||||
idKeyPat = "[ \t]*[A-Za-z_][A-Za-z_0-9.]*" # Ident w. leading whitespace.
|
|
||||||
idRE = regex.compile(idKeyPat)
|
|
||||||
|
|
||||||
|
|
||||||
def fontify(pytext, searchfrom = 0, searchto = None):
|
|
||||||
if searchto is None:
|
|
||||||
searchto = len(pytext)
|
|
||||||
# Cache a few attributes for quicker reference.
|
|
||||||
search = matchRE.search
|
|
||||||
group = matchRE.group
|
|
||||||
idSearch = idRE.search
|
|
||||||
idGroup = idRE.group
|
|
||||||
|
|
||||||
tags = []
|
|
||||||
tags_append = tags.append
|
|
||||||
commentTag = 'comment'
|
|
||||||
stringTag = 'string'
|
|
||||||
keywordTag = 'keyword'
|
|
||||||
identifierTag = 'identifier'
|
|
||||||
|
|
||||||
start = 0
|
|
||||||
end = searchfrom
|
|
||||||
while 1:
|
|
||||||
start = search(pytext, end)
|
|
||||||
if start < 0 or start >= searchto:
|
|
||||||
break # EXIT LOOP
|
|
||||||
match = group(0)
|
|
||||||
end = start + len(match)
|
|
||||||
c = match[0]
|
|
||||||
if c not in "#'\"":
|
|
||||||
# Must have matched a keyword.
|
|
||||||
if start <> searchfrom:
|
|
||||||
# there's still a redundant char before and after it, strip!
|
|
||||||
match = match[1:-1]
|
|
||||||
start = start + 1
|
|
||||||
else:
|
|
||||||
# this is the first keyword in the text.
|
|
||||||
# Only a space at the end.
|
|
||||||
match = match[:-1]
|
|
||||||
end = end - 1
|
|
||||||
tags_append((keywordTag, start, end, None))
|
|
||||||
# If this was a defining keyword, look ahead to the
|
|
||||||
# following identifier.
|
|
||||||
if match in ["def", "class"]:
|
|
||||||
start = idSearch(pytext, end)
|
|
||||||
if start == end:
|
|
||||||
match = idGroup(0)
|
|
||||||
end = start + len(match)
|
|
||||||
tags_append((identifierTag, start, end, None))
|
|
||||||
elif c == "#":
|
|
||||||
tags_append((commentTag, start, end, None))
|
|
||||||
else:
|
|
||||||
tags_append((stringTag, start, end, None))
|
|
||||||
return tags
|
|
||||||
|
|
||||||
|
|
||||||
def test(path):
|
|
||||||
f = open(path)
|
|
||||||
text = f.read()
|
|
||||||
f.close()
|
|
||||||
tags = fontify(text)
|
|
||||||
for tag, start, end, sublist in tags:
|
|
||||||
print tag, `text[start:end]`
|
|
576
lib/blame.py
576
lib/blame.py
@@ -1,576 +0,0 @@
|
|||||||
#!/usr/local/bin/python
|
|
||||||
# -*-python-*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
|
|
||||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
|
||||||
#
|
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
|
||||||
# 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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# blame.py: Annotate each line of a CVS file with its author,
|
|
||||||
# revision #, date, etc.
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# This software is being maintained as part of the ViewCVS project.
|
|
||||||
# Information is available at:
|
|
||||||
# http://viewcvs.sourceforge.net/
|
|
||||||
#
|
|
||||||
# This file is based on the cvsblame.pl portion of the Bonsai CVS tool,
|
|
||||||
# developed by Steve Lamm for Netscape Communications Corporation. More
|
|
||||||
# information about Bonsai can be found at
|
|
||||||
# http://www.mozilla.org/bonsai.html
|
|
||||||
#
|
|
||||||
# cvsblame.pl, in turn, was based on Scott Furman's cvsblame script
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import string
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import math
|
|
||||||
import cgi
|
|
||||||
import rcsparse
|
|
||||||
|
|
||||||
class CVSParser(rcsparse.Sink):
|
|
||||||
# Precompiled regular expressions
|
|
||||||
trunk_rev = re.compile('^[0-9]+\\.[0-9]+$')
|
|
||||||
last_branch = re.compile('(.*)\\.[0-9]+')
|
|
||||||
is_branch = re.compile('(.*)\\.0\\.([0-9]+)')
|
|
||||||
d_command = re.compile('^d(\d+)\\s(\\d+)')
|
|
||||||
a_command = re.compile('^a(\d+)\\s(\\d+)')
|
|
||||||
|
|
||||||
SECONDS_PER_DAY = 86400
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.Reset()
|
|
||||||
|
|
||||||
def Reset(self):
|
|
||||||
self.last_revision = {}
|
|
||||||
self.prev_revision = {}
|
|
||||||
self.revision_date = {}
|
|
||||||
self.revision_author = {}
|
|
||||||
self.revision_branches = {}
|
|
||||||
self.next_delta = {}
|
|
||||||
self.prev_delta = {}
|
|
||||||
self.tag_revision = {}
|
|
||||||
self.revision_symbolic_name = {}
|
|
||||||
self.timestamp = {}
|
|
||||||
self.revision_ctime = {}
|
|
||||||
self.revision_age = {}
|
|
||||||
self.revision_log = {}
|
|
||||||
self.revision_deltatext = {}
|
|
||||||
self.revision_map = []
|
|
||||||
self.lines_added = {}
|
|
||||||
self.lines_removed = {}
|
|
||||||
|
|
||||||
# Map a tag to a numerical revision number. The tag can be a symbolic
|
|
||||||
# branch tag, a symbolic revision tag, or an ordinary numerical
|
|
||||||
# revision number.
|
|
||||||
def map_tag_to_revision(self, tag_or_revision):
|
|
||||||
try:
|
|
||||||
revision = self.tag_revision[tag_or_revision]
|
|
||||||
match = self.is_branch.match(revision)
|
|
||||||
if match:
|
|
||||||
branch = match.group(1) + '.' + match.group(2)
|
|
||||||
if self.last_revision.get(branch):
|
|
||||||
return self.last_revision[branch]
|
|
||||||
else:
|
|
||||||
return match.group(1)
|
|
||||||
else:
|
|
||||||
return revision
|
|
||||||
except:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
# Construct an ordered list of ancestor revisions to the given
|
|
||||||
# revision, starting with the immediate ancestor and going back
|
|
||||||
# to the primordial revision (1.1).
|
|
||||||
#
|
|
||||||
# Note: The generated path does not traverse the tree the same way
|
|
||||||
# that the individual revision deltas do. In particular,
|
|
||||||
# the path traverses the tree "backwards" on branches.
|
|
||||||
def ancestor_revisions(self, revision):
|
|
||||||
ancestors = []
|
|
||||||
revision = self.prev_revision.get(revision)
|
|
||||||
while revision:
|
|
||||||
ancestors.append(revision)
|
|
||||||
revision = self.prev_revision.get(revision)
|
|
||||||
|
|
||||||
return ancestors
|
|
||||||
|
|
||||||
# Split deltatext specified by rev to each line.
|
|
||||||
def deltatext_split(self, rev):
|
|
||||||
lines = string.split(self.revision_deltatext[rev], '\n')
|
|
||||||
if lines[-1] == '':
|
|
||||||
del lines[-1]
|
|
||||||
return lines
|
|
||||||
|
|
||||||
# Extract the given revision from the digested RCS file.
|
|
||||||
# (Essentially the equivalent of cvs up -rXXX)
|
|
||||||
def extract_revision(self, revision):
|
|
||||||
path = []
|
|
||||||
add_lines_remaining = 0
|
|
||||||
start_line = 0
|
|
||||||
count = 0
|
|
||||||
while revision:
|
|
||||||
path.append(revision)
|
|
||||||
revision = self.prev_delta.get(revision)
|
|
||||||
path.reverse()
|
|
||||||
path = path[1:] # Get rid of head revision
|
|
||||||
|
|
||||||
text = self.deltatext_split(self.head_revision)
|
|
||||||
|
|
||||||
# Iterate, applying deltas to previous revision
|
|
||||||
for revision in path:
|
|
||||||
adjust = 0
|
|
||||||
diffs = self.deltatext_split(revision)
|
|
||||||
self.lines_added[revision] = 0
|
|
||||||
self.lines_removed[revision] = 0
|
|
||||||
lines_added_now = 0
|
|
||||||
lines_removed_now = 0
|
|
||||||
|
|
||||||
for command in diffs:
|
|
||||||
dmatch = self.d_command.match(command)
|
|
||||||
amatch = self.a_command.match(command)
|
|
||||||
if add_lines_remaining > 0:
|
|
||||||
# Insertion lines from a prior "a" command
|
|
||||||
text.insert(start_line + adjust, command)
|
|
||||||
add_lines_remaining = add_lines_remaining - 1
|
|
||||||
adjust = adjust + 1
|
|
||||||
elif dmatch:
|
|
||||||
# "d" - Delete command
|
|
||||||
start_line = string.atoi(dmatch.group(1))
|
|
||||||
count = string.atoi(dmatch.group(2))
|
|
||||||
begin = start_line + adjust - 1
|
|
||||||
del text[begin:begin + count]
|
|
||||||
adjust = adjust - count
|
|
||||||
lines_removed_now = lines_removed_now + count
|
|
||||||
elif amatch:
|
|
||||||
# "a" - Add command
|
|
||||||
start_line = string.atoi(amatch.group(1))
|
|
||||||
count = string.atoi(amatch.group(2))
|
|
||||||
add_lines_remaining = count
|
|
||||||
lines_added_now = lines_added_now + count
|
|
||||||
else:
|
|
||||||
raise RuntimeError, 'Error parsing diff commands'
|
|
||||||
|
|
||||||
self.lines_added[revision] = self.lines_added[revision] + lines_added_now
|
|
||||||
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
|
|
||||||
return text
|
|
||||||
|
|
||||||
def set_head_revision(self, revision):
|
|
||||||
self.head_revision = revision
|
|
||||||
|
|
||||||
def set_principal_branch(self, branch_name):
|
|
||||||
self.principal_branch = branch_name
|
|
||||||
|
|
||||||
def define_tag(self, name, revision):
|
|
||||||
# Create an associate array that maps from tag name to
|
|
||||||
# revision number and vice-versa.
|
|
||||||
self.tag_revision[name] = revision
|
|
||||||
|
|
||||||
### actually, this is a bit bogus... a rev can have multiple names
|
|
||||||
self.revision_symbolic_name[revision] = name
|
|
||||||
|
|
||||||
def set_comment(self, comment):
|
|
||||||
self.file_description = comment
|
|
||||||
|
|
||||||
def set_description(self, description):
|
|
||||||
self.rcs_file_description = description
|
|
||||||
|
|
||||||
# Construct dicts that represent the topology of the RCS tree
|
|
||||||
# and other arrays that contain info about individual revisions.
|
|
||||||
#
|
|
||||||
# The following dicts are created, keyed by revision number:
|
|
||||||
# self.revision_date -- e.g. "96.02.23.00.21.52"
|
|
||||||
# self.timestamp -- seconds since 12:00 AM, Jan 1, 1970 GMT
|
|
||||||
# self.revision_author -- e.g. "tom"
|
|
||||||
# self.revision_branches -- descendant branch revisions, separated by spaces,
|
|
||||||
# e.g. "1.21.4.1 1.21.2.6.1"
|
|
||||||
# self.prev_revision -- revision number of previous *ancestor* in RCS tree.
|
|
||||||
# Traversal of this array occurs in the direction
|
|
||||||
# of the primordial (1.1) revision.
|
|
||||||
# self.prev_delta -- revision number of previous revision which forms
|
|
||||||
# the basis for the edit commands in this revision.
|
|
||||||
# This causes the tree to be traversed towards the
|
|
||||||
# trunk when on a branch, and towards the latest trunk
|
|
||||||
# revision when on the trunk.
|
|
||||||
# self.next_delta -- revision number of next "delta". Inverts prev_delta.
|
|
||||||
#
|
|
||||||
# Also creates self.last_revision, keyed by a branch revision number, which
|
|
||||||
# indicates the latest revision on a given branch,
|
|
||||||
# e.g. self.last_revision{"1.2.8"} == 1.2.8.5
|
|
||||||
def define_revision(self, revision, timestamp, author, state,
|
|
||||||
branches, next):
|
|
||||||
self.tag_revision[revision] = revision
|
|
||||||
branch = self.last_branch.match(revision).group(1)
|
|
||||||
self.last_revision[branch] = revision
|
|
||||||
|
|
||||||
#self.revision_date[revision] = date
|
|
||||||
self.timestamp[revision] = timestamp
|
|
||||||
|
|
||||||
# Pretty print the date string
|
|
||||||
ltime = time.localtime(self.timestamp[revision])
|
|
||||||
formatted_date = time.strftime("%d %b %Y %H:%M", ltime)
|
|
||||||
self.revision_ctime[revision] = formatted_date
|
|
||||||
|
|
||||||
# Save age
|
|
||||||
self.revision_age[revision] = ((time.time() - self.timestamp[revision])
|
|
||||||
/ self.SECONDS_PER_DAY)
|
|
||||||
|
|
||||||
# save author
|
|
||||||
self.revision_author[revision] = author
|
|
||||||
|
|
||||||
# ignore the state
|
|
||||||
|
|
||||||
# process the branch information
|
|
||||||
branch_text = ''
|
|
||||||
for branch in branches:
|
|
||||||
self.prev_revision[branch] = revision
|
|
||||||
self.next_delta[revision] = branch
|
|
||||||
self.prev_delta[branch] = revision
|
|
||||||
branch_text = branch_text + branch + ''
|
|
||||||
self.revision_branches[revision] = branch_text
|
|
||||||
|
|
||||||
# process the "next revision" information
|
|
||||||
if next:
|
|
||||||
self.next_delta[revision] = next
|
|
||||||
self.prev_delta[next] = revision
|
|
||||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
|
||||||
if is_trunk_revision:
|
|
||||||
self.prev_revision[revision] = next
|
|
||||||
else:
|
|
||||||
self.prev_revision[next] = revision
|
|
||||||
|
|
||||||
# Construct associative arrays containing info about individual revisions.
|
|
||||||
#
|
|
||||||
# The following associative arrays are created, keyed by revision number:
|
|
||||||
# revision_log -- log message
|
|
||||||
# revision_deltatext -- Either the complete text of the revision,
|
|
||||||
# in the case of the head revision, or the
|
|
||||||
# encoded delta between this revision and another.
|
|
||||||
# The delta is either with respect to the successor
|
|
||||||
# revision if this revision is on the trunk or
|
|
||||||
# relative to its immediate predecessor if this
|
|
||||||
# revision is on a branch.
|
|
||||||
def set_revision_info(self, revision, log, text):
|
|
||||||
self.revision_log[revision] = log
|
|
||||||
self.revision_deltatext[revision] = text
|
|
||||||
|
|
||||||
def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
|
|
||||||
# Args in: opt_rev - requested revision
|
|
||||||
# opt_m - time since modified
|
|
||||||
# Args out: revision_map
|
|
||||||
# timestamp
|
|
||||||
# revision_deltatext
|
|
||||||
|
|
||||||
# CheckHidden(rcs_pathname);
|
|
||||||
try:
|
|
||||||
rcsfile = open(rcs_pathname, 'r')
|
|
||||||
except:
|
|
||||||
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
|
|
||||||
'but the RCS file is inaccessible.') % rcs_pathname
|
|
||||||
|
|
||||||
rcsparse.Parser().parse(rcsfile, self)
|
|
||||||
rcsfile.close()
|
|
||||||
|
|
||||||
if opt_rev in [None, '', 'HEAD']:
|
|
||||||
# Explicitly specified topmost revision in tree
|
|
||||||
revision = self.head_revision
|
|
||||||
else:
|
|
||||||
# Symbolic tag or specific revision number specified.
|
|
||||||
revision = self.map_tag_to_revision(opt_rev)
|
|
||||||
if revision == '':
|
|
||||||
raise RuntimeError, 'error: -r: No such revision: ' + opt_rev
|
|
||||||
|
|
||||||
# The primordial revision is not always 1.1! Go find it.
|
|
||||||
primordial = revision
|
|
||||||
while self.prev_revision.get(primordial):
|
|
||||||
primordial = self.prev_revision[primordial]
|
|
||||||
|
|
||||||
# Don't display file at all, if -m option is specified and no
|
|
||||||
# changes have been made in the specified file.
|
|
||||||
if opt_m_timestamp and self.timestamp[revision] < opt_m_timestamp:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
# Figure out how many lines were in the primordial, i.e. version 1.1,
|
|
||||||
# check-in by moving backward in time from the head revision to the
|
|
||||||
# first revision.
|
|
||||||
line_count = 0
|
|
||||||
if self.revision_deltatext.get(self.head_revision):
|
|
||||||
tmp_array = self.deltatext_split(self.head_revision)
|
|
||||||
line_count = len(tmp_array)
|
|
||||||
|
|
||||||
skip = 0
|
|
||||||
|
|
||||||
rev = self.prev_revision.get(self.head_revision)
|
|
||||||
while rev:
|
|
||||||
diffs = self.deltatext_split(rev)
|
|
||||||
for command in diffs:
|
|
||||||
dmatch = self.d_command.match(command)
|
|
||||||
amatch = self.a_command.match(command)
|
|
||||||
if skip > 0:
|
|
||||||
# Skip insertion lines from a prior "a" command
|
|
||||||
skip = skip - 1
|
|
||||||
elif dmatch:
|
|
||||||
# "d" - Delete command
|
|
||||||
start_line = string.atoi(dmatch.group(1))
|
|
||||||
count = string.atoi(dmatch.group(2))
|
|
||||||
line_count = line_count - count
|
|
||||||
elif amatch:
|
|
||||||
# "a" - Add command
|
|
||||||
start_line = string.atoi(amatch.group(1))
|
|
||||||
count = string.atoi(amatch.group(2))
|
|
||||||
skip = count;
|
|
||||||
line_count = line_count + count
|
|
||||||
else:
|
|
||||||
raise RuntimeError, 'error: illegal RCS file'
|
|
||||||
|
|
||||||
rev = self.prev_revision.get(rev)
|
|
||||||
|
|
||||||
# Now, play the delta edit commands *backwards* from the primordial
|
|
||||||
# revision forward, but rather than applying the deltas to the text of
|
|
||||||
# each revision, apply the changes to an array of revision numbers.
|
|
||||||
# This creates a "revision map" -- an array where each element
|
|
||||||
# represents a line of text in the given revision but contains only
|
|
||||||
# the revision number in which the line was introduced rather than
|
|
||||||
# the line text itself.
|
|
||||||
#
|
|
||||||
# Note: These are backward deltas for revisions on the trunk and
|
|
||||||
# forward deltas for branch revisions.
|
|
||||||
|
|
||||||
# Create initial revision map for primordial version.
|
|
||||||
self.revision_map = [primordial] * line_count
|
|
||||||
|
|
||||||
ancestors = [revision, ] + self.ancestor_revisions(revision)
|
|
||||||
ancestors = ancestors[:-1] # Remove "1.1"
|
|
||||||
last_revision = primordial
|
|
||||||
ancestors.reverse()
|
|
||||||
for revision in ancestors:
|
|
||||||
is_trunk_revision = self.trunk_rev.match(revision) is not None
|
|
||||||
|
|
||||||
if is_trunk_revision:
|
|
||||||
diffs = self.deltatext_split(last_revision)
|
|
||||||
|
|
||||||
# Revisions on the trunk specify deltas that transform a
|
|
||||||
# revision into an earlier revision, so invert the translation
|
|
||||||
# of the 'diff' commands.
|
|
||||||
for command in diffs:
|
|
||||||
if skip > 0:
|
|
||||||
skip = skip - 1
|
|
||||||
else:
|
|
||||||
dmatch = self.d_command.match(command)
|
|
||||||
amatch = self.a_command.match(command)
|
|
||||||
if dmatch:
|
|
||||||
start_line = string.atoi(dmatch.group(1))
|
|
||||||
count = string.atoi(dmatch.group(2))
|
|
||||||
temp = []
|
|
||||||
while count > 0:
|
|
||||||
temp.append(revision)
|
|
||||||
count = count - 1
|
|
||||||
self.revision_map = (self.revision_map[:start_line - 1] +
|
|
||||||
temp + self.revision_map[start_line - 1:])
|
|
||||||
elif amatch:
|
|
||||||
start_line = string.atoi(amatch.group(1))
|
|
||||||
count = string.atoi(amatch.group(2))
|
|
||||||
del self.revision_map[start_line:start_line + count]
|
|
||||||
skip = count
|
|
||||||
else:
|
|
||||||
raise RuntimeError, 'Error parsing diff commands'
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Revisions on a branch are arranged backwards from those on
|
|
||||||
# the trunk. They specify deltas that transform a revision
|
|
||||||
# into a later revision.
|
|
||||||
adjust = 0
|
|
||||||
diffs = self.deltatext_split(revision)
|
|
||||||
for command in diffs:
|
|
||||||
if skip > 0:
|
|
||||||
skip = skip - 1
|
|
||||||
else:
|
|
||||||
dmatch = self.d_command.match(command)
|
|
||||||
amatch = self.a_command.match(command)
|
|
||||||
if dmatch:
|
|
||||||
start_line = string.atoi(dmatch.group(1))
|
|
||||||
count = string.atoi(dmatch.group(2))
|
|
||||||
del self.revision_map[start_line + adjust - 1:start_line + adjust - 1 + count]
|
|
||||||
adjust = adjust - count
|
|
||||||
elif amatch:
|
|
||||||
start_line = string.atoi(amatch.group(1))
|
|
||||||
count = string.atoi(amatch.group(2))
|
|
||||||
skip = count
|
|
||||||
temp = []
|
|
||||||
while count > 0:
|
|
||||||
temp.append(revision)
|
|
||||||
count = count - 1
|
|
||||||
self.revision_map = (self.revision_map[:start_line + adjust] +
|
|
||||||
temp + self.revision_map[start_line + adjust:])
|
|
||||||
adjust = adjust + skip
|
|
||||||
else:
|
|
||||||
raise RuntimeError, 'Error parsing diff commands'
|
|
||||||
|
|
||||||
last_revision = revision
|
|
||||||
|
|
||||||
return revision
|
|
||||||
|
|
||||||
|
|
||||||
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
|
||||||
re_filename = re.compile('(.*[\\\\/])?(.+)')
|
|
||||||
|
|
||||||
def link_includes(text, root, rcs_path, sticky = None):
|
|
||||||
match = re_includes.match(text)
|
|
||||||
if match:
|
|
||||||
incfile = match.group(3)
|
|
||||||
for rel_path in ('', 'Attic', '..'):
|
|
||||||
trial_root = os.path.join(rcs_path, rel_path)
|
|
||||||
file = os.path.join(root, trial_root)
|
|
||||||
file = os.path.normpath(os.path.join(file, incfile + ',v'))
|
|
||||||
if os.access(file, os.F_OK):
|
|
||||||
url = os.path.join(rel_path, incfile)
|
|
||||||
if sticky:
|
|
||||||
url = url + '?' + sticky
|
|
||||||
return '#%sinclude%s"<a href="%s">%s</a>"' % \
|
|
||||||
(match.group(1), match.group(2), url, incfile)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def make_html(root, rcs_path, opt_rev = None, sticky = None):
|
|
||||||
filename = os.path.join(root, rcs_path)
|
|
||||||
parser = CVSParser()
|
|
||||||
revision = parser.parse_cvs_file(filename, opt_rev)
|
|
||||||
count = len(parser.revision_map)
|
|
||||||
text = parser.extract_revision(revision)
|
|
||||||
if len(text) != count:
|
|
||||||
raise RuntimeError, 'Internal consistency error'
|
|
||||||
|
|
||||||
match = re_filename.match(rcs_path)
|
|
||||||
if not match:
|
|
||||||
raise RuntimeError, 'Unable to parse filename'
|
|
||||||
file_head = match.group(1)
|
|
||||||
file_tail = match.group(2)
|
|
||||||
|
|
||||||
open_table_tag = '<table border=0 cellpadding=0 cellspacing=0 width="100%">'
|
|
||||||
startOfRow = '<tr><td colspan=3%s><pre>'
|
|
||||||
endOfRow = '</td></tr>'
|
|
||||||
|
|
||||||
print open_table_tag + (startOfRow % '')
|
|
||||||
|
|
||||||
if count == 0:
|
|
||||||
count = 1
|
|
||||||
|
|
||||||
line_num_width = int(math.log10(count)) + 1
|
|
||||||
revision_width = 3
|
|
||||||
author_width = 5
|
|
||||||
line = 0
|
|
||||||
usedlog = {}
|
|
||||||
usedlog[revision] = 1
|
|
||||||
old_revision = 0
|
|
||||||
row_color = ''
|
|
||||||
lines_in_table = 0
|
|
||||||
inMark = 0
|
|
||||||
rev_count = 0
|
|
||||||
|
|
||||||
for revision in parser.revision_map:
|
|
||||||
thisline = text[line]
|
|
||||||
line = line + 1
|
|
||||||
usedlog[revision] = 1
|
|
||||||
line_in_table = lines_in_table + 1
|
|
||||||
|
|
||||||
# Escape HTML meta-characters
|
|
||||||
thisline = cgi.escape(thisline)
|
|
||||||
|
|
||||||
# Add a link to traverse to included files
|
|
||||||
if 1: # opt_includes
|
|
||||||
thisline = link_includes(thisline, root, file_head, sticky)
|
|
||||||
|
|
||||||
output = ''
|
|
||||||
|
|
||||||
# Highlight lines
|
|
||||||
#mark_cmd;
|
|
||||||
#if (defined($mark_cmd = $mark_line{$line}) and mark_cmd != 'end':
|
|
||||||
# output = output + endOfRow + '<tr><td bgcolor=LIGHTGREEN width="100%"><pre>'
|
|
||||||
# inMark = 1
|
|
||||||
|
|
||||||
if old_revision != revision and line != 1:
|
|
||||||
if row_color == '':
|
|
||||||
row_color = ' bgcolor="#e7e7e7"'
|
|
||||||
else:
|
|
||||||
row_color = ''
|
|
||||||
|
|
||||||
if not inMark:
|
|
||||||
if lines_in_table > 100:
|
|
||||||
output = output + endOfRow + '</table>' + open_table_tag + (startOfRow % row_color)
|
|
||||||
lines_in_table = 0
|
|
||||||
else:
|
|
||||||
output = output + endOfRow + (startOfRow % row_color)
|
|
||||||
|
|
||||||
elif lines_in_table > 200 and not inMark:
|
|
||||||
output = output + endOfRow + '</table>' + open_table_tag + (startOfRow % row_color)
|
|
||||||
lines_in_table = 0
|
|
||||||
|
|
||||||
output = output + "<a name=%d></a>" % (line, )
|
|
||||||
if 1: # opt_line_nums
|
|
||||||
output = output + ('%%%dd' % (line_num_width, )) % (line, )
|
|
||||||
|
|
||||||
if old_revision != revision or rev_count > 20:
|
|
||||||
revision_width = max(revision_width, len(revision))
|
|
||||||
|
|
||||||
if parser.prev_revision.get(revision):
|
|
||||||
fname = file_tail[:-2] # strip the ",v"
|
|
||||||
url = '%s.diff?r1=%s&r2=%s' % \
|
|
||||||
(fname, parser.prev_revision[revision], revision)
|
|
||||||
if sticky:
|
|
||||||
url = url + '&' + sticky
|
|
||||||
output = output + ' <a href="%s"' % (url, )
|
|
||||||
if 0: # use_layers
|
|
||||||
output = output + " onmouseover='return log(event,\"%s\",\"%s\");'" % (
|
|
||||||
parser.prev_revision[revision], revision)
|
|
||||||
output = output + ">"
|
|
||||||
else:
|
|
||||||
output = output + " "
|
|
||||||
parser.prev_revision[revision] = ''
|
|
||||||
|
|
||||||
author = parser.revision_author[revision]
|
|
||||||
# $author =~ s/%.*$//;
|
|
||||||
author_width = max(author_width, len(author))
|
|
||||||
output = output + ('%%-%ds ' % (author_width, )) % (author, )
|
|
||||||
output = output + revision
|
|
||||||
if parser.prev_revision.get(revision):
|
|
||||||
output = output + '</a>'
|
|
||||||
output = output + (' ' * (revision_width - len(revision) + 1))
|
|
||||||
|
|
||||||
old_revision = revision
|
|
||||||
rev_count = 0
|
|
||||||
else:
|
|
||||||
output = output + ' ' + (' ' * (author_width + revision_width))
|
|
||||||
rev_count = rev_count + 1
|
|
||||||
|
|
||||||
output = output + thisline
|
|
||||||
|
|
||||||
# Close the highlighted section
|
|
||||||
#if (defined $mark_cmd and mark_cmd != 'begin'):
|
|
||||||
# chop($output)
|
|
||||||
# output = output + endOfRow + (startOfRow % row_color)
|
|
||||||
# inMark = 0
|
|
||||||
|
|
||||||
print output
|
|
||||||
print endOfRow + '</table>'
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print 'USAGE: %s cvsroot rcs-file' % sys.argv[0]
|
|
||||||
sys.exit(1)
|
|
||||||
make_html(sys.argv[1], sys.argv[2])
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
160
lib/commit.py
160
lib/commit.py
@@ -1,160 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
@@ -1,68 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# compat.py: compatibility functions for operation across Python 1.5.x
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import urllib
|
|
||||||
import string
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# urllib.urlencode() is new to Python 1.5.2
|
|
||||||
#
|
|
||||||
try:
|
|
||||||
urlencode = urllib.urlencode
|
|
||||||
except AttributeError:
|
|
||||||
def urlencode(dict):
|
|
||||||
"Encode a dictionary as application/x-url-form-encoded."
|
|
||||||
if not dict:
|
|
||||||
return ''
|
|
||||||
quote = urllib.quote_plus
|
|
||||||
keyvalue = [ ]
|
|
||||||
for key, value in dict.items():
|
|
||||||
keyvalue.append(quote(key) + '=' + quote(str(value)))
|
|
||||||
return string.join(keyvalue, '&')
|
|
||||||
|
|
||||||
#
|
|
||||||
# time.strptime() is new to Python 1.5.2
|
|
||||||
#
|
|
||||||
if hasattr(time, 'strptime'):
|
|
||||||
def cvs_strptime(timestr):
|
|
||||||
'Parse a CVS-style date/time value.'
|
|
||||||
return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
|
|
||||||
else:
|
|
||||||
_re_rev_date = re.compile('([0-9]{4})/([0-9][0-9])/([0-9][0-9]) '
|
|
||||||
'([0-9][0-9]):([0-9][0-9]):([0-9][0-9])')
|
|
||||||
def cvs_strptime(timestr):
|
|
||||||
'Parse a CVS-style date/time value.'
|
|
||||||
matches = _re_rev_date.match(timestr).groups()
|
|
||||||
return tuple(map(int, matches)) + (0, 1, 0)
|
|
||||||
|
|
||||||
#
|
|
||||||
# os.makedirs() is new to Python 1.5.2
|
|
||||||
#
|
|
||||||
try:
|
|
||||||
makedirs = os.makedirs
|
|
||||||
except AttributeError:
|
|
||||||
def makedirs(path, mode=0777):
|
|
||||||
head, tail = os.path.split(path)
|
|
||||||
if head and tail and not os.path.exists(head):
|
|
||||||
makedirs(head, mode)
|
|
||||||
os.mkdir(path, mode)
|
|
282
lib/config.py
282
lib/config.py
@@ -1,282 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# config.py: configuration utilities
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import string
|
|
||||||
import ConfigParser
|
|
||||||
import fnmatch
|
|
||||||
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# CONFIGURATION
|
|
||||||
#
|
|
||||||
# There are three forms of configuration:
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
#
|
|
||||||
# Most users will want to use (1), but there are slight speed advantages
|
|
||||||
# to the other two options. Note that viewcvs.conf values are a bit easier
|
|
||||||
# to work with since it is raw text, rather than python literal values.
|
|
||||||
#
|
|
||||||
#########################################################################
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
_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:
|
|
||||||
setattr(self, section, _sub_config())
|
|
||||||
|
|
||||||
def load_config(self, fname, vhost=None):
|
|
||||||
this_dir = os.path.dirname(sys.argv[0])
|
|
||||||
pathname = os.path.join(this_dir, fname)
|
|
||||||
parser = ConfigParser.ConfigParser()
|
|
||||||
parser.read(pathname)
|
|
||||||
|
|
||||||
for section in self._sections:
|
|
||||||
if parser.has_section(section):
|
|
||||||
self._process_section(parser, section, section)
|
|
||||||
|
|
||||||
if vhost:
|
|
||||||
self._process_vhost(parser, vhost)
|
|
||||||
|
|
||||||
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 or subcfg_name == 'images':
|
|
||||||
value = map(string.strip, filter(None, string.split(value, ',')))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
value = int(value)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if opt == 'cvs_roots':
|
|
||||||
roots = { }
|
|
||||||
for root in value:
|
|
||||||
name, path = map(string.strip, string.split(root, ':'))
|
|
||||||
roots[name] = path
|
|
||||||
value = roots
|
|
||||||
setattr(sc, opt, value)
|
|
||||||
|
|
||||||
def _process_vhost(self, parser, vhost):
|
|
||||||
canon_vhost = self._find_canon_vhost(parser, vhost)
|
|
||||||
if not canon_vhost:
|
|
||||||
# none of the vhost sections matched
|
|
||||||
return
|
|
||||||
|
|
||||||
cv = canon_vhost + '-'
|
|
||||||
lcv = len(cv)
|
|
||||||
for section in parser.sections():
|
|
||||||
if section[:lcv] == cv:
|
|
||||||
self._process_section(parser, section, section[lcv:])
|
|
||||||
|
|
||||||
def _find_canon_vhost(self, parser, vhost):
|
|
||||||
vhost = string.lower(vhost)
|
|
||||||
|
|
||||||
for canon_vhost in parser.options('vhosts'):
|
|
||||||
value = parser.get('vhosts', canon_vhost)
|
|
||||||
patterns = map(string.lower, map(string.strip,
|
|
||||||
filter(None, string.split(value, ','))))
|
|
||||||
for pat in patterns:
|
|
||||||
if fnmatch.fnmatchcase(vhost, pat):
|
|
||||||
return canon_vhost
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_defaults(self):
|
|
||||||
"Set some default values in the configuration."
|
|
||||||
|
|
||||||
self.general.cvs_roots = {
|
|
||||||
# user-visible-name : path
|
|
||||||
"Development" : "/home/cvsroot",
|
|
||||||
}
|
|
||||||
self.general.default_root = "Development"
|
|
||||||
self.general.rcs_path = ''
|
|
||||||
self.general.mime_types_file = ''
|
|
||||||
self.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
|
|
||||||
self.general.main_title = 'CVS Repository'
|
|
||||||
self.general.forbidden = ()
|
|
||||||
|
|
||||||
self.cvsdb.enabled = 0
|
|
||||||
self.cvsdb.host = ''
|
|
||||||
self.cvsdb.database_name = ''
|
|
||||||
self.cvsdb.user = ''
|
|
||||||
self.cvsdb.passwd = ''
|
|
||||||
self.cvsdb.readonly_user = ''
|
|
||||||
self.cvsdb.readonly_passwd = ''
|
|
||||||
|
|
||||||
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 = 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.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
|
|
||||||
<<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>>
|
|
||||||
based on the
|
|
||||||
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
|
|
||||||
script by Henner Zeller
|
|
||||||
<<a href="mailto:zeller@think.de">zeller@think.de</a>>;
|
|
||||||
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:
|
|
||||||
return 0
|
|
||||||
default = 0
|
|
||||||
for pat in self.general.forbidden:
|
|
||||||
if pat[0] == '!':
|
|
||||||
default = 1
|
|
||||||
if fnmatch.fnmatchcase(module, pat[1:]):
|
|
||||||
return 0
|
|
||||||
elif fnmatch.fnmatchcase(module, pat):
|
|
||||||
return 1
|
|
||||||
return default
|
|
||||||
|
|
||||||
class _sub_config:
|
|
||||||
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.
|
|
||||||
import regex
|
|
||||||
ConfigParser.option_cre = regex.compile('^\([-A-Za-z0-9._]+\)\(:\|['
|
|
||||||
+ string.whitespace
|
|
||||||
+ ']*=\)\(.*\)$')
|
|
142
lib/cvsdbapi.py
142
lib/cvsdbapi.py
@@ -1,142 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# 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
408
lib/database.py
@@ -1,408 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
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)
|
|
71
lib/dbi.py
71
lib/dbi.py
@@ -1,71 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import MySQLdb
|
|
||||||
|
|
||||||
|
|
||||||
dbi_error = "dbi error"
|
|
||||||
|
|
||||||
|
|
||||||
## make some checks in MySQLdb
|
|
||||||
_no_datetime = """\
|
|
||||||
ERROR: Your version of MySQLdb requires the mxDateTime module
|
|
||||||
for the Timestamp() and TimestampFromTicks() methods.
|
|
||||||
You will need to install mxDateTime to use the ViewCVS
|
|
||||||
database.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not hasattr(MySQLdb, "Timestamp") or \
|
|
||||||
not hasattr(MySQLdb, "TimestampFromTicks"):
|
|
||||||
sys.stderr.write(_no_datetime)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
class Cursor:
|
|
||||||
def __init__(self, mysql_cursor):
|
|
||||||
self.__cursor = mysql_cursor
|
|
||||||
|
|
||||||
def execute(self, *args):
|
|
||||||
apply(self.__cursor.execute, args)
|
|
||||||
|
|
||||||
def fetchone(self):
|
|
||||||
try:
|
|
||||||
row = self.__cursor.fetchone()
|
|
||||||
except IndexError:
|
|
||||||
row = None
|
|
||||||
|
|
||||||
return row
|
|
||||||
|
|
||||||
|
|
||||||
class Connection:
|
|
||||||
def __init__(self, host, user, passwd, db):
|
|
||||||
self.__mysql = MySQLdb.connect(
|
|
||||||
host=host, user=user, passwd=passwd, db=db)
|
|
||||||
|
|
||||||
def cursor(self):
|
|
||||||
return Cursor(self.__mysql.cursor())
|
|
||||||
|
|
||||||
|
|
||||||
def Timestamp(year, month, date, hour, minute, second):
|
|
||||||
return MySQLdb.Timestamp(year, month, date, hour, minute, second)
|
|
||||||
|
|
||||||
|
|
||||||
def TimestampFromTicks(ticks):
|
|
||||||
return MySQLdb.TimestampFromTicks(ticks)
|
|
||||||
|
|
||||||
|
|
||||||
def connect(host, user, passwd, db):
|
|
||||||
return Connection(host, user, passwd, db)
|
|
187
lib/popen.py
187
lib/popen.py
@@ -1,187 +0,0 @@
|
|||||||
#
|
|
||||||
# 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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# popen.py: a replacement for os.popen()
|
|
||||||
#
|
|
||||||
# This implementation of popen() provides a cmd + args calling sequence,
|
|
||||||
# rather than a system() type of convention. The shell facilities are not
|
|
||||||
# available, but that implies we can avoid worrying about shell hacks in
|
|
||||||
# the arguments.
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def popen(cmd, args, mode, capture_err=1):
|
|
||||||
# flush the stdio buffers since we are about to change the FD under them
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
r, w = os.pipe()
|
|
||||||
pid = os.fork()
|
|
||||||
if pid:
|
|
||||||
# in the parent
|
|
||||||
|
|
||||||
# close the descriptor that we don't need and return the other one.
|
|
||||||
if mode == 'r':
|
|
||||||
os.close(w)
|
|
||||||
return _pipe(os.fdopen(r, 'r'), pid)
|
|
||||||
os.close(r)
|
|
||||||
return _pipe(os.fdopen(w, 'w'), pid)
|
|
||||||
|
|
||||||
# in the child
|
|
||||||
|
|
||||||
# we'll need /dev/null for the discarded I/O
|
|
||||||
null = os.open('/dev/null', os.O_RDWR)
|
|
||||||
|
|
||||||
if mode == 'r':
|
|
||||||
# hook stdout/stderr to the "write" channel
|
|
||||||
os.dup2(w, 1)
|
|
||||||
# "close" stdin; the child shouldn't use it
|
|
||||||
### this isn't quite right... we may want the child to read from stdin
|
|
||||||
os.dup2(null, 0)
|
|
||||||
# what to do with errors?
|
|
||||||
if capture_err:
|
|
||||||
os.dup2(w, 2)
|
|
||||||
else:
|
|
||||||
os.dup2(null, 2)
|
|
||||||
else:
|
|
||||||
# hook stdin to the "read" channel
|
|
||||||
os.dup2(r, 0)
|
|
||||||
# "close" stdout/stderr; the child shouldn't use them
|
|
||||||
### this isn't quite right... we may want the child to write to these
|
|
||||||
os.dup2(null, 1)
|
|
||||||
os.dup2(null, 2)
|
|
||||||
|
|
||||||
# don't need these FDs any more
|
|
||||||
os.close(null)
|
|
||||||
os.close(r)
|
|
||||||
os.close(w)
|
|
||||||
|
|
||||||
# the stdin/stdout/stderr are all set up. exec the target
|
|
||||||
try:
|
|
||||||
os.execvp(cmd, (cmd,) + tuple(args))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# crap. shouldn't be here.
|
|
||||||
sys.exit(127)
|
|
||||||
|
|
||||||
def pipe_cmds(cmds):
|
|
||||||
# flush the stdio buffers since we are about to change the FD under them
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
prev_r, parent_w = os.pipe()
|
|
||||||
|
|
||||||
null = os.open('/dev/null', os.O_RDWR)
|
|
||||||
|
|
||||||
for cmd in cmds[:-1]:
|
|
||||||
r, w = os.pipe()
|
|
||||||
pid = os.fork()
|
|
||||||
if not pid:
|
|
||||||
# in the child
|
|
||||||
|
|
||||||
# hook up stdin to the "read" channel
|
|
||||||
os.dup2(prev_r, 0)
|
|
||||||
|
|
||||||
# hook up stdout to the output channel
|
|
||||||
os.dup2(w, 1)
|
|
||||||
|
|
||||||
# toss errors
|
|
||||||
os.dup2(null, 2)
|
|
||||||
|
|
||||||
# close these extra descriptors
|
|
||||||
os.close(prev_r)
|
|
||||||
os.close(parent_w)
|
|
||||||
os.close(null)
|
|
||||||
os.close(r)
|
|
||||||
os.close(w)
|
|
||||||
|
|
||||||
# time to run the command
|
|
||||||
try:
|
|
||||||
os.execvp(cmd[0], cmd)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
sys.exit(127)
|
|
||||||
|
|
||||||
# in the parent
|
|
||||||
|
|
||||||
# we don't need these any more
|
|
||||||
os.close(prev_r)
|
|
||||||
os.close(w)
|
|
||||||
|
|
||||||
# the read channel of this pipe will feed into to the next command
|
|
||||||
prev_r = r
|
|
||||||
|
|
||||||
# no longer needed
|
|
||||||
os.close(null)
|
|
||||||
|
|
||||||
# done with most of the commands. set up the last command to write to stdout
|
|
||||||
pid = os.fork()
|
|
||||||
if not pid:
|
|
||||||
# in the child (the last command)
|
|
||||||
|
|
||||||
# hook up stdin to the "read" channel
|
|
||||||
os.dup2(prev_r, 0)
|
|
||||||
|
|
||||||
# close these extra descriptors
|
|
||||||
os.close(prev_r)
|
|
||||||
os.close(parent_w)
|
|
||||||
|
|
||||||
# run the last command
|
|
||||||
try:
|
|
||||||
os.execvp(cmds[-1][0], cmds[-1])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
sys.exit(127)
|
|
||||||
|
|
||||||
# not needed any more
|
|
||||||
os.close(prev_r)
|
|
||||||
|
|
||||||
# write into the first pipe, wait on the final process
|
|
||||||
return _pipe(os.fdopen(parent_w, 'w'), pid)
|
|
||||||
|
|
||||||
|
|
||||||
class _pipe:
|
|
||||||
"Wrapper for a file which can wait() on a child process at close time."
|
|
||||||
|
|
||||||
def __init__(self, file, child_pid):
|
|
||||||
self.file = file
|
|
||||||
self.child_pid = child_pid
|
|
||||||
|
|
||||||
def eof(self):
|
|
||||||
pid, status = os.waitpid(self.child_pid, os.WNOHANG)
|
|
||||||
if pid:
|
|
||||||
self.file.close()
|
|
||||||
self.file = None
|
|
||||||
return status
|
|
||||||
return None
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.file:
|
|
||||||
self.file.close()
|
|
||||||
self.file = None
|
|
||||||
return os.waitpid(self.child_pid, 0)[1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self.file, name)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.file:
|
|
||||||
self.close()
|
|
489
lib/py2html.py
489
lib/py2html.py
@@ -1,489 +0,0 @@
|
|||||||
#!/usr/local/bin/python -u
|
|
||||||
|
|
||||||
""" Python Highlighter for HTML Version: 0.5
|
|
||||||
|
|
||||||
py2html.py [options] files...
|
|
||||||
|
|
||||||
options:
|
|
||||||
-h print help
|
|
||||||
- read from stdin, write to stdout
|
|
||||||
-stdout read from files, write to stdout
|
|
||||||
-files read from files, write to filename+'.html' (default)
|
|
||||||
-format:
|
|
||||||
html output HTML page (default)
|
|
||||||
rawhtml output pure HTML (without headers, titles, etc.)
|
|
||||||
-mode:
|
|
||||||
color output in color (default)
|
|
||||||
mono output b/w (for printing)
|
|
||||||
-title:Title use 'Title' as title of the generated page
|
|
||||||
-bgcolor:color use color as background-color for page
|
|
||||||
-header:file use contents of file as header
|
|
||||||
-footer:file use contents of file as footer
|
|
||||||
-URL replace all occurances of 'URL: link' with
|
|
||||||
'<A HREF="link">link</A>'; this is always enabled
|
|
||||||
in CGI mode
|
|
||||||
-v verbose
|
|
||||||
|
|
||||||
Takes the input, assuming it is Python code and formats it into
|
|
||||||
colored HTML. When called without parameters the script tries to
|
|
||||||
work in CGI mode. It looks for a field 'script=URL' and tries to
|
|
||||||
use that URL as input file. If it can't find this field, the path
|
|
||||||
info (the part of the URL following the CGI script name) is
|
|
||||||
tried. In case no host is given, the host where the CGI script
|
|
||||||
lives and HTTP are used.
|
|
||||||
|
|
||||||
* Uses Just van Rossum's PyFontify version 0.3 to tag Python scripts.
|
|
||||||
You can get it via his homepage on starship:
|
|
||||||
URL: http://starship.skyport.net/crew/just
|
|
||||||
"""
|
|
||||||
__comments__ = """
|
|
||||||
|
|
||||||
The following snippet is a small shell script I use for viewing
|
|
||||||
Python scripts per less on Unix:
|
|
||||||
#!/bin/sh
|
|
||||||
# Browse pretty printed Python code using ANSI codes for highlighting
|
|
||||||
py2html -stdout -format:ansi -mode:mono $* | less -r
|
|
||||||
|
|
||||||
History:
|
|
||||||
|
|
||||||
0.5: Added a few suggestions by Kevin Ng to make the CGI version
|
|
||||||
a little more robust.
|
|
||||||
|
|
||||||
"""
|
|
||||||
__copyright__ = """
|
|
||||||
-----------------------------------------------------------------------------
|
|
||||||
(c) Copyright by Marc-Andre Lemburg, 1998 (mailto:mal@lemburg.com)
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software and its
|
|
||||||
documentation for any purpose and without fee or royalty is hereby granted,
|
|
||||||
provided that the above copyright notice appear in all copies and that
|
|
||||||
both that copyright notice and this permission notice appear in
|
|
||||||
supporting documentation or portions thereof, including modifications,
|
|
||||||
that you make.
|
|
||||||
|
|
||||||
THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
|
||||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
|
||||||
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
|
||||||
FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
|
||||||
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
|
|
||||||
"""
|
|
||||||
|
|
||||||
__version__ = '0.5'
|
|
||||||
|
|
||||||
__cgifooter__ = ('\n<PRE># code highlighted using <A HREF='
|
|
||||||
'"http://starship.skyport.net/~lemburg/">py2html.py</A> '
|
|
||||||
'version %s</PRE>\n' % __version__)
|
|
||||||
|
|
||||||
import sys,string,re
|
|
||||||
|
|
||||||
# Adjust path so that PyFontify is found...
|
|
||||||
sys.path.append('.')
|
|
||||||
|
|
||||||
### Constants
|
|
||||||
|
|
||||||
# URL of the input form the user is redirected to in case no script=xxx
|
|
||||||
# form field is given. The URL *must* be absolute. Leave blank to
|
|
||||||
# have the script issue an error instead.
|
|
||||||
INPUT_FORM = 'http://starship.skyport.net/~lemburg/SoftwareDescriptions.html#py2html.py'
|
|
||||||
|
|
||||||
### Helpers
|
|
||||||
|
|
||||||
def fileio(file, mode='rb', data=None, close=0):
|
|
||||||
|
|
||||||
if type(file) == type(''):
|
|
||||||
f = open(file,mode)
|
|
||||||
close = 1
|
|
||||||
else:
|
|
||||||
f = file
|
|
||||||
if data:
|
|
||||||
f.write(data)
|
|
||||||
else:
|
|
||||||
data = f.read()
|
|
||||||
if close: f.close()
|
|
||||||
return data
|
|
||||||
|
|
||||||
### Converter class
|
|
||||||
|
|
||||||
class PrettyPrint:
|
|
||||||
|
|
||||||
""" generic Pretty Printer class
|
|
||||||
|
|
||||||
* supports tagging Python scripts in the following ways:
|
|
||||||
|
|
||||||
# format/mode | color mono
|
|
||||||
# --------------------------
|
|
||||||
# rawhtml | x x (HTML without headers, etc.)
|
|
||||||
# html | x x (a HTML page with HEAD&BODY:)
|
|
||||||
# ansi | x (with Ansi-escape sequences)
|
|
||||||
|
|
||||||
* interfaces:
|
|
||||||
|
|
||||||
file_filter -- takes two files: input & output (may be stdin/stdout)
|
|
||||||
filter -- takes a string and returns the highlighted version
|
|
||||||
|
|
||||||
* to create an instance use:
|
|
||||||
|
|
||||||
c = PrettyPrint(tagfct,format,mode)
|
|
||||||
|
|
||||||
where format and mode must be strings according to the
|
|
||||||
above table if you plan to use PyFontify.fontify as
|
|
||||||
tagfct
|
|
||||||
|
|
||||||
* the tagfct has to take one argument, text, and return a taglist
|
|
||||||
(format: [(id,left,right,sublist),...], where id is the
|
|
||||||
"name" given to the slice left:right in text and sublist is a
|
|
||||||
taglist for tags inside the slice or None)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# misc settings
|
|
||||||
title = ''
|
|
||||||
bgcolor = '#FFFFFF'
|
|
||||||
header = ''
|
|
||||||
footer = ''
|
|
||||||
replace_URLs = 0
|
|
||||||
# formats to be used
|
|
||||||
formats = {}
|
|
||||||
|
|
||||||
def __init__(self,tagfct=None,format='html',mode='color'):
|
|
||||||
|
|
||||||
self.tag = tagfct
|
|
||||||
self.set_mode = getattr(self,'set_mode_'+format+'_'+mode)
|
|
||||||
self.filter = getattr(self,'filter_'+format)
|
|
||||||
self.set_mode()
|
|
||||||
|
|
||||||
def file_filter(self,infile,outfile):
|
|
||||||
|
|
||||||
text = fileio(infile,'r')
|
|
||||||
if type(infile) == type('') and self.title == '':
|
|
||||||
self.title = infile
|
|
||||||
fileio(outfile,'w',self.filter(text))
|
|
||||||
|
|
||||||
### set pre- and postfixes for formats & modes
|
|
||||||
#
|
|
||||||
# These methods must set self.formats to a dictionary having
|
|
||||||
# an entry for every tag returned by the tagging function.
|
|
||||||
#
|
|
||||||
# The format used is simple:
|
|
||||||
# tag:(prefix,postfix)
|
|
||||||
# where prefix and postfix are either strings or callable objects,
|
|
||||||
# that return a string (they are called with the matching tag text
|
|
||||||
# as only parameter). prefix is inserted in front of the tag, postfix
|
|
||||||
# is inserted right after the tag.
|
|
||||||
|
|
||||||
def set_mode_html_color(self):
|
|
||||||
|
|
||||||
self.formats = {
|
|
||||||
'all':('<PRE>','</PRE>'),
|
|
||||||
'comment':('<FONT COLOR=#1111CC>','</FONT>'),
|
|
||||||
'keyword':('<FONT COLOR=#3333CC><B>','</B></FONT>'),
|
|
||||||
'parameter':('<FONT COLOR=#000066>','</FONT>'),
|
|
||||||
'identifier':( lambda x,strip=string.strip:
|
|
||||||
'<A NAME="%s"><FONT COLOR=#CC0000><B>' % (strip(x)),
|
|
||||||
'</B></FONT></A>'),
|
|
||||||
'string':('<FONT COLOR=#115511>','</FONT>')
|
|
||||||
}
|
|
||||||
|
|
||||||
set_mode_rawhtml_color = set_mode_html_color
|
|
||||||
|
|
||||||
def set_mode_html_mono(self):
|
|
||||||
|
|
||||||
self.formats = {
|
|
||||||
'all':('<PRE>','</PRE>'),
|
|
||||||
'comment':('',''),
|
|
||||||
'keyword':( '<U>','</U>'),
|
|
||||||
'parameter':('',''),
|
|
||||||
'identifier':( lambda x,strip=string.strip:
|
|
||||||
'<A NAME="%s"><B>' % (strip(x)),
|
|
||||||
'</B>'),
|
|
||||||
'string':('','')
|
|
||||||
}
|
|
||||||
|
|
||||||
set_mode_rawhtml_mono = set_mode_html_mono
|
|
||||||
|
|
||||||
def set_mode_ansi_mono(self):
|
|
||||||
|
|
||||||
self.formats = {
|
|
||||||
'all':('',''),
|
|
||||||
'comment':('\033[2m','\033[m'),
|
|
||||||
'keyword':('\033[4m','\033[m'),
|
|
||||||
'parameter':('',''),
|
|
||||||
'identifier':('\033[1m','\033[m'),
|
|
||||||
'string':('','')
|
|
||||||
}
|
|
||||||
|
|
||||||
### filter for Python scripts given as string
|
|
||||||
|
|
||||||
def escape_html(self,text):
|
|
||||||
|
|
||||||
t = (('<','<'),('>','>'))
|
|
||||||
for x,y in t:
|
|
||||||
text = string.join(string.split(text,x),y)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def filter_html(self,text):
|
|
||||||
|
|
||||||
output = self.fontify(self.escape_html(text))
|
|
||||||
if self.replace_URLs:
|
|
||||||
output = re.sub('URL:([ \t]+)([^ \n\r<]+)',
|
|
||||||
'URL:\\1<A HREF="\\2">\\2</A>',output)
|
|
||||||
html = """<HTML><HEAD><TITLE>%s</TITLE></HEAD>
|
|
||||||
<BODY BGCOLOR=%s>
|
|
||||||
<!--header-->%s
|
|
||||||
<!--script-->%s
|
|
||||||
<!--footer-->%s
|
|
||||||
</BODY>\n"""%(self.title,self.bgcolor,self.header,output,self.footer)
|
|
||||||
return html
|
|
||||||
|
|
||||||
def filter_rawhtml(self,text):
|
|
||||||
|
|
||||||
output = self.fontify(self.escape_html(text))
|
|
||||||
if self.replace_URLs:
|
|
||||||
output = re.sub('URL:([ \t]+)([^ \n\r<]+)',
|
|
||||||
'URL:\\1<A HREF="\\2">\\2</A>',output)
|
|
||||||
return self.header+output+self.footer
|
|
||||||
|
|
||||||
def filter_ansi(self,text):
|
|
||||||
|
|
||||||
output = self.fontify(text)
|
|
||||||
return self.header+output+self.footer
|
|
||||||
|
|
||||||
### fontify engine
|
|
||||||
|
|
||||||
def fontify(self,pytext):
|
|
||||||
|
|
||||||
# parse
|
|
||||||
taglist = self.tag(pytext)
|
|
||||||
|
|
||||||
# prepend special 'all' tag:
|
|
||||||
taglist[:0] = [('all',0,len(pytext),None)]
|
|
||||||
|
|
||||||
# prepare splitting
|
|
||||||
splits = []
|
|
||||||
addsplits(splits,pytext,self.formats,taglist)
|
|
||||||
|
|
||||||
# do splitting & inserting
|
|
||||||
splits.sort()
|
|
||||||
l = []
|
|
||||||
li = 0
|
|
||||||
for ri,dummy,insert in splits:
|
|
||||||
if ri > li: l.append(pytext[li:ri])
|
|
||||||
l.append(insert)
|
|
||||||
li = ri
|
|
||||||
if li < len(pytext): l.append(pytext[li:])
|
|
||||||
|
|
||||||
return string.join(l,'')
|
|
||||||
|
|
||||||
def addsplits(splits,text,formats,taglist):
|
|
||||||
# helper for fontify()
|
|
||||||
for id,left,right,sublist in taglist:
|
|
||||||
try:
|
|
||||||
pre,post = formats[id]
|
|
||||||
except KeyError:
|
|
||||||
# sys.stderr.write('Warning: no format for %s specified\n'%repr(id))
|
|
||||||
pre,post = '',''
|
|
||||||
if type(pre) != type(''):
|
|
||||||
pre = pre(text[left:right])
|
|
||||||
if type(post) != type(''):
|
|
||||||
post = post(text[left:right])
|
|
||||||
# len(splits) is a dummy used to make sorting stable
|
|
||||||
splits.append((left,len(splits),pre))
|
|
||||||
if sublist:
|
|
||||||
addsplits(splits,text,formats,sublist)
|
|
||||||
splits.append((right,len(splits),post))
|
|
||||||
|
|
||||||
def write_html_error(titel,text):
|
|
||||||
|
|
||||||
print """\
|
|
||||||
<HTML><HEADER><TITLE>%s</TITLE></HEADER>
|
|
||||||
<BODY>
|
|
||||||
<H2>%s</H2>
|
|
||||||
%s
|
|
||||||
</BODY></HTML>
|
|
||||||
""" % (titel,titel,text)
|
|
||||||
|
|
||||||
def redirect_to(url):
|
|
||||||
|
|
||||||
sys.stdout.write('Content-Type: text/html\r\n')
|
|
||||||
sys.stdout.write('Status: 302\r\n')
|
|
||||||
sys.stdout.write('Location: %s\r\n\r\n' % url)
|
|
||||||
print """
|
|
||||||
<HTML><HEAD>
|
|
||||||
<TITLE>302 Moved Temporarily</TITLE>
|
|
||||||
</HEAD><BODY>
|
|
||||||
<H1>302 Moved Temporarily</H1>
|
|
||||||
The document has moved to <A HREF="%s">%s</A>.<P>
|
|
||||||
</BODY></HTML>
|
|
||||||
""" % (url,url)
|
|
||||||
|
|
||||||
def main(cmdline):
|
|
||||||
|
|
||||||
""" main(cmdline) -- process cmdline as if it were sys.argv
|
|
||||||
"""
|
|
||||||
# parse options/files
|
|
||||||
options = []
|
|
||||||
optvalues = {}
|
|
||||||
for o in cmdline[1:]:
|
|
||||||
if o[0] == '-':
|
|
||||||
if ':' in o:
|
|
||||||
k,v = tuple(string.split(o,':'))
|
|
||||||
optvalues[k] = v
|
|
||||||
options.append(k)
|
|
||||||
else:
|
|
||||||
options.append(o)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
files = cmdline[len(options)+1:]
|
|
||||||
|
|
||||||
# create converting object
|
|
||||||
|
|
||||||
# load fontifier
|
|
||||||
if '-marcs' in options:
|
|
||||||
# use mxTextTool's tagging engine
|
|
||||||
from mxTextTools import tag
|
|
||||||
from mxTextTools.Examples.Python import python_script
|
|
||||||
tagfct = lambda text,tag=tag,pytable=python_script: \
|
|
||||||
tag(text,pytable)[1]
|
|
||||||
print "Py2HTML: using Marc's tagging engine"
|
|
||||||
else:
|
|
||||||
# load Just's
|
|
||||||
try:
|
|
||||||
import PyFontify
|
|
||||||
if PyFontify.__version__ < '0.3': raise ValueError
|
|
||||||
tagfct = PyFontify.fontify
|
|
||||||
except:
|
|
||||||
print """
|
|
||||||
Sorry, but this script needs the PyFontify.py module version 0.3;
|
|
||||||
You can download it from Just's homepage at
|
|
||||||
|
|
||||||
URL: http://starship.skyport.net/crew/just
|
|
||||||
"""
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
if '-format' in options:
|
|
||||||
format = optvalues['-format']
|
|
||||||
else:
|
|
||||||
# use default
|
|
||||||
format = 'html'
|
|
||||||
|
|
||||||
if '-mode' in options:
|
|
||||||
mode = optvalues['-mode']
|
|
||||||
else:
|
|
||||||
# use default
|
|
||||||
mode = 'color'
|
|
||||||
|
|
||||||
c = PrettyPrint(tagfct,format,mode)
|
|
||||||
convert = c.file_filter
|
|
||||||
|
|
||||||
# start working
|
|
||||||
|
|
||||||
if '-title' in options:
|
|
||||||
c.title = optvalues['-title']
|
|
||||||
|
|
||||||
if '-bgcolor' in options:
|
|
||||||
c.bgcolor = optvalues['-bgcolor']
|
|
||||||
|
|
||||||
if '-header' in options:
|
|
||||||
try:
|
|
||||||
f = open(optvalues['-header'])
|
|
||||||
c.header = f.read()
|
|
||||||
f.close()
|
|
||||||
except IOError:
|
|
||||||
if verbose: print 'IOError: header file not found'
|
|
||||||
|
|
||||||
if '-footer' in options:
|
|
||||||
try:
|
|
||||||
f = open(optvalues['-footer'])
|
|
||||||
c.footer = f.read()
|
|
||||||
f.close()
|
|
||||||
except IOError:
|
|
||||||
if verbose: print 'IOError: footer file not found'
|
|
||||||
|
|
||||||
if '-URL' in options:
|
|
||||||
c.replace_URLs = 1
|
|
||||||
|
|
||||||
if '-' in options:
|
|
||||||
convert(sys.stdin,sys.stdout)
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if '-h' in options:
|
|
||||||
print __doc__
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if len(files) == 0:
|
|
||||||
# Turn URL processing on
|
|
||||||
c.replace_URLs = 1
|
|
||||||
# Try CGI processing...
|
|
||||||
import cgi,urllib,urlparse,os
|
|
||||||
form = cgi.FieldStorage()
|
|
||||||
if not form.has_key('script'):
|
|
||||||
# Ok, then try pathinfo
|
|
||||||
if not os.environ.has_key('PATH_INFO'):
|
|
||||||
if INPUT_FORM:
|
|
||||||
redirect_to(INPUT_FORM)
|
|
||||||
else:
|
|
||||||
sys.stdout.write('Content-Type: text/html\r\n\r\n')
|
|
||||||
write_html_error('Missing Parameter',
|
|
||||||
'Missing script=URL field in request')
|
|
||||||
sys.exit(1)
|
|
||||||
url = os.environ['PATH_INFO'][1:] # skip the leading slash
|
|
||||||
else:
|
|
||||||
url = form['script'].value
|
|
||||||
sys.stdout.write('Content-Type: text/html\r\n\r\n')
|
|
||||||
scheme, host, path, params, query, frag = urlparse.urlparse(url)
|
|
||||||
if not host:
|
|
||||||
scheme = 'http'
|
|
||||||
if os.environ.has_key('HTTP_HOST'):
|
|
||||||
host = os.environ['HTTP_HOST']
|
|
||||||
else:
|
|
||||||
host = 'localhost'
|
|
||||||
url = urlparse.urlunparse((scheme, host, path, params, query, frag))
|
|
||||||
#print url; sys.exit()
|
|
||||||
network = urllib.URLopener()
|
|
||||||
try:
|
|
||||||
tempfile,headers = network.retrieve(url)
|
|
||||||
except IOError,reason:
|
|
||||||
write_html_error('Error opening "%s"' % url,
|
|
||||||
'The given URL could not be opened. Reason: %s' %\
|
|
||||||
str(reason))
|
|
||||||
sys.exit(1)
|
|
||||||
f = open(tempfile,'rb')
|
|
||||||
c.title = url
|
|
||||||
c.footer = __cgifooter__
|
|
||||||
convert(f,sys.stdout)
|
|
||||||
f.close()
|
|
||||||
network.close()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if '-stdout' in options:
|
|
||||||
filebreak = '-'*72
|
|
||||||
for f in files:
|
|
||||||
try:
|
|
||||||
if len(files) > 1:
|
|
||||||
print filebreak
|
|
||||||
print 'File:',f
|
|
||||||
print filebreak
|
|
||||||
convert(f,sys.stdout)
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
verbose = ('-v' in options)
|
|
||||||
if verbose:
|
|
||||||
print 'Py2HTML: working on',
|
|
||||||
for f in files:
|
|
||||||
try:
|
|
||||||
if verbose: print f,
|
|
||||||
convert(f,f+'.html')
|
|
||||||
except IOError:
|
|
||||||
if verbose: print '(IOError!)',
|
|
||||||
if verbose:
|
|
||||||
print
|
|
||||||
print 'Done.'
|
|
||||||
|
|
||||||
if __name__=='__main__':
|
|
||||||
main(sys.argv)
|
|
||||||
|
|
||||||
|
|
102
lib/query.py
102
lib/query.py
@@ -1,102 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import time
|
|
||||||
import dbi
|
|
||||||
|
|
||||||
|
|
||||||
## 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 CreateCheckinQuery():
|
|
||||||
return CheckinDatabaseQuery()
|
|
441
lib/rcsparse.py
441
lib/rcsparse.py
@@ -1,441 +0,0 @@
|
|||||||
#
|
|
||||||
# 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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# This software is being maintained as part of the ViewCVS project.
|
|
||||||
# Information is available at:
|
|
||||||
# http://viewcvs.sourceforge.net/
|
|
||||||
#
|
|
||||||
# This file was originally based on portions of the blame.py script by
|
|
||||||
# Curt Hagenlocher.
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
import string
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class _TokenStream:
|
|
||||||
token_term = string.whitespace + ';'
|
|
||||||
|
|
||||||
# the algorithm is about the same speed for any CHUNK_SIZE chosen.
|
|
||||||
# grab a good-sized chunk, but not too large to overwhelm memory.
|
|
||||||
CHUNK_SIZE = 100000
|
|
||||||
|
|
||||||
# CHUNK_SIZE = 5 # for debugging, make the function grind...
|
|
||||||
|
|
||||||
def __init__(self, file):
|
|
||||||
self.rcsfile = file
|
|
||||||
self.idx = 0
|
|
||||||
self.buf = self.rcsfile.read(self.CHUNK_SIZE)
|
|
||||||
if self.buf == '':
|
|
||||||
raise RuntimeError, 'EOF'
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
"Get the next token from the RCS file."
|
|
||||||
|
|
||||||
# Note: we can afford to loop within Python, examining individual
|
|
||||||
# characters. For the whitespace and tokens, the number of iterations
|
|
||||||
# is typically quite small. Thus, a simple iterative loop will beat
|
|
||||||
# out more complex solutions.
|
|
||||||
|
|
||||||
buf = self.buf
|
|
||||||
idx = self.idx
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
if idx == len(buf):
|
|
||||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
|
||||||
if buf == '':
|
|
||||||
# signal EOF by returning None as the token
|
|
||||||
del self.buf # so we fail if get() is called again
|
|
||||||
return None
|
|
||||||
idx = 0
|
|
||||||
|
|
||||||
if buf[idx] not in string.whitespace:
|
|
||||||
break
|
|
||||||
|
|
||||||
idx = idx + 1
|
|
||||||
|
|
||||||
if buf[idx] == ';':
|
|
||||||
self.buf = buf
|
|
||||||
self.idx = idx + 1
|
|
||||||
return ';'
|
|
||||||
|
|
||||||
if buf[idx] != '@':
|
|
||||||
end = idx + 1
|
|
||||||
token = ''
|
|
||||||
while 1:
|
|
||||||
# find token characters in the current buffer
|
|
||||||
while end < len(buf) and buf[end] not in self.token_term:
|
|
||||||
end = end + 1
|
|
||||||
token = token + buf[idx:end]
|
|
||||||
|
|
||||||
if end < len(buf):
|
|
||||||
# we stopped before the end, so we have a full token
|
|
||||||
idx = end
|
|
||||||
break
|
|
||||||
|
|
||||||
# we stopped at the end of the buffer, so we may have a partial token
|
|
||||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
|
||||||
idx = end = 0
|
|
||||||
|
|
||||||
self.buf = buf
|
|
||||||
self.idx = idx
|
|
||||||
return token
|
|
||||||
|
|
||||||
# a "string" which starts with the "@" character. we'll skip it when we
|
|
||||||
# search for content.
|
|
||||||
idx = idx + 1
|
|
||||||
|
|
||||||
chunks = [ ]
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
if idx == len(buf):
|
|
||||||
idx = 0
|
|
||||||
buf = self.rcsfile.read(self.CHUNK_SIZE)
|
|
||||||
if buf == '':
|
|
||||||
raise RuntimeError, 'EOF'
|
|
||||||
i = string.find(buf, '@', idx)
|
|
||||||
if i == -1:
|
|
||||||
chunks.append(buf[idx:])
|
|
||||||
idx = len(buf)
|
|
||||||
continue
|
|
||||||
if i == len(buf) - 1:
|
|
||||||
chunks.append(buf[idx:i])
|
|
||||||
idx = 0
|
|
||||||
buf = '@' + self.rcsfile.read(self.CHUNK_SIZE)
|
|
||||||
if buf == '@':
|
|
||||||
raise RuntimeError, 'EOF'
|
|
||||||
continue
|
|
||||||
if buf[i + 1] == '@':
|
|
||||||
chunks.append(buf[idx:i+1])
|
|
||||||
idx = i + 2
|
|
||||||
continue
|
|
||||||
|
|
||||||
chunks.append(buf[idx:i])
|
|
||||||
|
|
||||||
self.buf = buf
|
|
||||||
self.idx = i + 1
|
|
||||||
|
|
||||||
return string.join(chunks, '')
|
|
||||||
|
|
||||||
# _get = get
|
|
||||||
# def get(self):
|
|
||||||
token = self._get()
|
|
||||||
print 'T:', `token`
|
|
||||||
return token
|
|
||||||
|
|
||||||
def match(self, match):
|
|
||||||
"Try to match the next token from the input buffer."
|
|
||||||
|
|
||||||
token = self.get()
|
|
||||||
if token != match:
|
|
||||||
raise RuntimeError, ('Unexpected parsing error in RCS file.\n' +
|
|
||||||
'Expected token: %s, but saw: %s' % (match, token))
|
|
||||||
|
|
||||||
def unget(self, token):
|
|
||||||
"Put this token back, for the next get() to return."
|
|
||||||
|
|
||||||
# Override the class' .get method with a function which clears the
|
|
||||||
# overridden method then returns the pushed token. Since this function
|
|
||||||
# will not be looked up via the class mechanism, it should be a "normal"
|
|
||||||
# function, meaning it won't have "self" automatically inserted.
|
|
||||||
# Therefore, we need to pass both self and the token thru via defaults.
|
|
||||||
|
|
||||||
# note: we don't put this into the input buffer because it may have been
|
|
||||||
# @-unescaped already.
|
|
||||||
|
|
||||||
def give_it_back(self=self, token=token):
|
|
||||||
del self.get
|
|
||||||
return token
|
|
||||||
|
|
||||||
self.get = give_it_back
|
|
||||||
|
|
||||||
class Parser:
|
|
||||||
|
|
||||||
def parse_rcs_admin(self):
|
|
||||||
while 1:
|
|
||||||
# Read initial token at beginning of line
|
|
||||||
token = self.ts.get()
|
|
||||||
|
|
||||||
# We're done once we reach the description of the RCS tree
|
|
||||||
if token[0] in string.digits:
|
|
||||||
self.ts.unget(token)
|
|
||||||
return
|
|
||||||
|
|
||||||
if token == "head":
|
|
||||||
self.sink.set_head_revision(self.ts.get())
|
|
||||||
self.ts.match(';')
|
|
||||||
elif token == "branch":
|
|
||||||
self.sink.set_principal_branch(self.ts.get())
|
|
||||||
self.ts.match(';')
|
|
||||||
elif token == "symbols":
|
|
||||||
while 1:
|
|
||||||
tag = self.ts.get()
|
|
||||||
if tag == ';':
|
|
||||||
break
|
|
||||||
(tag_name, tag_rev) = string.split(tag, ':')
|
|
||||||
self.sink.define_tag(tag_name, tag_rev)
|
|
||||||
elif token == "comment":
|
|
||||||
self.sink.set_comment(self.ts.get())
|
|
||||||
self.ts.match(';')
|
|
||||||
|
|
||||||
# Ignore all these other fields - We don't care about them. Also chews
|
|
||||||
# up "newphrase".
|
|
||||||
elif token in ("locks", "strict", "expand", "access"):
|
|
||||||
while 1:
|
|
||||||
tag = self.ts.get()
|
|
||||||
if tag == ';':
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
# warn("Unexpected RCS token: $token\n")
|
|
||||||
|
|
||||||
raise RuntimeError, "Unexpected EOF";
|
|
||||||
|
|
||||||
def parse_rcs_tree(self):
|
|
||||||
while 1:
|
|
||||||
revision = self.ts.get()
|
|
||||||
|
|
||||||
# End of RCS tree description ?
|
|
||||||
if revision == 'desc':
|
|
||||||
self.ts.unget(revision)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Parse date
|
|
||||||
self.ts.match('date')
|
|
||||||
date = self.ts.get()
|
|
||||||
self.ts.match(';')
|
|
||||||
|
|
||||||
# Convert date into timestamp
|
|
||||||
date_fields = string.split(date, '.') + ['0', '0', '0']
|
|
||||||
date_fields = map(string.atoi, date_fields)
|
|
||||||
if date_fields[0] < 100:
|
|
||||||
date_fields[0] = date_fields[0] + 1900
|
|
||||||
timestamp = time.mktime(tuple(date_fields))
|
|
||||||
|
|
||||||
# Parse author
|
|
||||||
self.ts.match('author')
|
|
||||||
author = self.ts.get()
|
|
||||||
self.ts.match(';')
|
|
||||||
|
|
||||||
# Parse state
|
|
||||||
self.ts.match('state')
|
|
||||||
state = ''
|
|
||||||
while 1:
|
|
||||||
token = self.ts.get()
|
|
||||||
if token == ';':
|
|
||||||
break
|
|
||||||
state = state + token + ' '
|
|
||||||
state = state[:-1] # toss the trailing space
|
|
||||||
|
|
||||||
# Parse branches
|
|
||||||
self.ts.match('branches')
|
|
||||||
branches = [ ]
|
|
||||||
while 1:
|
|
||||||
token = self.ts.get()
|
|
||||||
if token == ';':
|
|
||||||
break
|
|
||||||
branches.append(token)
|
|
||||||
|
|
||||||
# Parse revision of next delta in chain
|
|
||||||
self.ts.match('next')
|
|
||||||
next = self.ts.get()
|
|
||||||
if next == ';':
|
|
||||||
next = None
|
|
||||||
else:
|
|
||||||
self.ts.match(';')
|
|
||||||
|
|
||||||
# there are some files with extra tags in them. for example:
|
|
||||||
# owner 640;
|
|
||||||
# group 15;
|
|
||||||
# permissions 644;
|
|
||||||
# hardlinks @configure.in@;
|
|
||||||
# this is "newphrase" in RCSFILE(5). we just want to skip over these.
|
|
||||||
while 1:
|
|
||||||
token = self.ts.get()
|
|
||||||
if token == 'desc' or token[0] in string.digits:
|
|
||||||
self.ts.unget(token)
|
|
||||||
break
|
|
||||||
# consume everything up to the semicolon
|
|
||||||
while self.ts.get() != ';':
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.sink.define_revision(revision, timestamp, author, state, branches,
|
|
||||||
next)
|
|
||||||
|
|
||||||
def parse_rcs_description(self):
|
|
||||||
self.ts.match('desc')
|
|
||||||
self.sink.set_description(self.ts.get())
|
|
||||||
|
|
||||||
def parse_rcs_deltatext(self):
|
|
||||||
while 1:
|
|
||||||
revision = self.ts.get()
|
|
||||||
if revision is None:
|
|
||||||
# EOF
|
|
||||||
break
|
|
||||||
self.ts.match('log')
|
|
||||||
log = self.ts.get()
|
|
||||||
### need to add code to chew up "newphrase"
|
|
||||||
self.ts.match('text')
|
|
||||||
text = self.ts.get()
|
|
||||||
self.sink.set_revision_info(revision, log, text)
|
|
||||||
|
|
||||||
def parse(self, file, sink):
|
|
||||||
self.ts = _TokenStream(file)
|
|
||||||
self.sink = sink
|
|
||||||
|
|
||||||
self.parse_rcs_admin()
|
|
||||||
self.parse_rcs_tree()
|
|
||||||
|
|
||||||
# many sinks want to know when the tree has been completed so they can
|
|
||||||
# do some work to prep for the arrival of the deltatext
|
|
||||||
self.sink.tree_completed()
|
|
||||||
|
|
||||||
self.parse_rcs_description()
|
|
||||||
self.parse_rcs_deltatext()
|
|
||||||
|
|
||||||
# easiest for us to tell the sink it is done, rather than worry about
|
|
||||||
# higher level software doing it.
|
|
||||||
self.sink.parse_completed()
|
|
||||||
|
|
||||||
self.ts = self.sink = None
|
|
||||||
|
|
||||||
|
|
||||||
class Sink:
|
|
||||||
def set_head_revision(self, revision):
|
|
||||||
pass
|
|
||||||
def set_principal_branch(self, branch_name):
|
|
||||||
pass
|
|
||||||
def define_tag(self, name, revision):
|
|
||||||
pass
|
|
||||||
def set_comment(self, comment):
|
|
||||||
pass
|
|
||||||
def set_description(self, description):
|
|
||||||
pass
|
|
||||||
def define_revision(self, revision, timestamp, author, state,
|
|
||||||
branches, next):
|
|
||||||
pass
|
|
||||||
def set_revision_info(self, revision, log, text):
|
|
||||||
pass
|
|
||||||
def tree_completed(self):
|
|
||||||
pass
|
|
||||||
def parse_completed(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# TESTING AND DEBUGGING TOOLS
|
|
||||||
#
|
|
||||||
|
|
||||||
class DebugSink:
|
|
||||||
def set_head_revision(self, revision):
|
|
||||||
print 'head:', revision
|
|
||||||
|
|
||||||
def set_principal_branch(self, branch_name):
|
|
||||||
print 'branch:', branch_name
|
|
||||||
|
|
||||||
def define_tag(self, name, revision):
|
|
||||||
print 'tag:', name, '=', revision
|
|
||||||
|
|
||||||
def set_comment(self, comment):
|
|
||||||
print 'comment:', comment
|
|
||||||
|
|
||||||
def set_description(self, description):
|
|
||||||
print 'description:', description
|
|
||||||
|
|
||||||
def define_revision(self, revision, timestamp, author, state,
|
|
||||||
branches, next):
|
|
||||||
print 'revision:', revision
|
|
||||||
print ' timestamp:', timestamp
|
|
||||||
print ' author:', author
|
|
||||||
print ' state:', state
|
|
||||||
print ' branches:', branches
|
|
||||||
print ' next:', next
|
|
||||||
|
|
||||||
def set_revision_info(self, revision, log, text):
|
|
||||||
print 'revision:', revision
|
|
||||||
print ' log:', log
|
|
||||||
print ' text:', text[:100], '...'
|
|
||||||
|
|
||||||
class DumpSink:
|
|
||||||
"""Dump all the parse information directly to stdout.
|
|
||||||
|
|
||||||
The output is relatively unformatted and untagged. It is intended as a
|
|
||||||
raw dump of the data in the RCS file. A copy can be saved, then changes
|
|
||||||
made to the parsing engine, then a comparison of the new output against
|
|
||||||
the old output.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
global sha
|
|
||||||
import sha
|
|
||||||
|
|
||||||
def set_head_revision(self, revision):
|
|
||||||
print revision
|
|
||||||
|
|
||||||
def set_principal_branch(self, branch_name):
|
|
||||||
print branch_name
|
|
||||||
|
|
||||||
def define_tag(self, name, revision):
|
|
||||||
print name, revision
|
|
||||||
|
|
||||||
def set_comment(self, comment):
|
|
||||||
print comment
|
|
||||||
|
|
||||||
def set_description(self, description):
|
|
||||||
print description
|
|
||||||
|
|
||||||
def define_revision(self, revision, timestamp, author, state,
|
|
||||||
branches, next):
|
|
||||||
print revision, timestamp, author, state, branches, next
|
|
||||||
|
|
||||||
def set_revision_info(self, revision, log, text):
|
|
||||||
print revision, sha.new(log).hexdigest(), sha.new(text).hexdigest()
|
|
||||||
|
|
||||||
def tree_completed(self):
|
|
||||||
print 'tree_completed'
|
|
||||||
|
|
||||||
def parse_completed(self):
|
|
||||||
print 'parse_completed'
|
|
||||||
|
|
||||||
def dump_file(fname):
|
|
||||||
Parser().parse(open(fname), DumpSink())
|
|
||||||
|
|
||||||
def time_file(fname):
|
|
||||||
import time
|
|
||||||
p = Parser().parse
|
|
||||||
f = open(fname)
|
|
||||||
s = Sink()
|
|
||||||
t = time.time()
|
|
||||||
p(f, s)
|
|
||||||
t = time.time() - t
|
|
||||||
print t
|
|
||||||
|
|
||||||
def _usage():
|
|
||||||
print 'This is normally a module for importing, but it has a couple'
|
|
||||||
print 'features for testing as an executable script.'
|
|
||||||
print 'USAGE: %s COMMAND filename,v' % sys.argv[0]
|
|
||||||
print ' where COMMAND is one of:'
|
|
||||||
print ' dump: filename is "dumped" to stdout'
|
|
||||||
print ' time: filename is parsed with the time written to stdout'
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
usage()
|
|
||||||
if sys.argv[1] == 'dump':
|
|
||||||
dump_file(sys.argv[2])
|
|
||||||
elif sys.argv[1] == 'time':
|
|
||||||
time_file(sys.argv[2])
|
|
||||||
else:
|
|
||||||
usage()
|
|
350
lib/rlog.py
350
lib/rlog.py
@@ -1,350 +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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
## objects describing all the checkins from a given RCS file; this
|
|
||||||
## parser is fairly optimized, and therefore can be delicate if the
|
|
||||||
## rlog output varies between versions of rlog; I don't know if it does;
|
|
||||||
## to make really fast, I should wrap the C rcslib
|
|
||||||
|
|
||||||
## there's definately not much error checking here; I'll assume things
|
|
||||||
## will go smoothly, and trap errors via exception handlers above this
|
|
||||||
## function
|
|
||||||
|
|
||||||
## exception for this class
|
|
||||||
error = 'rlog error'
|
|
||||||
|
|
||||||
|
|
||||||
class RLogData:
|
|
||||||
"Container object for all data parsed from a 'rlog' output."
|
|
||||||
|
|
||||||
def __init__(self, filename):
|
|
||||||
self.filename = filename
|
|
||||||
self.symbolic_name_hash = {}
|
|
||||||
self.rlog_entry_list = []
|
|
||||||
|
|
||||||
def LookupBranch(self, rlog_entry):
|
|
||||||
index = string.rfind(rlog_entry.revision, '.')
|
|
||||||
branch_revision = rlog_entry.revision[:index]
|
|
||||||
|
|
||||||
try:
|
|
||||||
branch = self.symbolic_name_hash[branch_revision]
|
|
||||||
except KeyError:
|
|
||||||
branch = ''
|
|
||||||
|
|
||||||
return branch
|
|
||||||
|
|
||||||
|
|
||||||
class RLogEntry:
|
|
||||||
## static constants for type of log entry; this will be changed
|
|
||||||
## to strings I guess -JMP
|
|
||||||
CHANGE = 0
|
|
||||||
ADD = 1
|
|
||||||
REMOVE = 2
|
|
||||||
|
|
||||||
## Here's the init function, which isn't needed since this class
|
|
||||||
## is fully initalized by RLogParser when creating a new log entry.
|
|
||||||
## Let's keep this initializer as a description of what is held in
|
|
||||||
## the class, but keep it commented out since it only makes things
|
|
||||||
## slow.
|
|
||||||
##
|
|
||||||
## def __init__(self):
|
|
||||||
## self.revision = ''
|
|
||||||
## self.author = ''
|
|
||||||
## self.branch = ''
|
|
||||||
## self.pluscount = ''
|
|
||||||
## self.minuscount = ''
|
|
||||||
## self.description = ''
|
|
||||||
## self.time = None
|
|
||||||
## self.type = RLogEntry.CHANGE
|
|
||||||
|
|
||||||
|
|
||||||
class RLog:
|
|
||||||
"Provides a alternative file-like interface for running 'rlog'."
|
|
||||||
|
|
||||||
def __init__(self, filename, revision, date):
|
|
||||||
self.filename = self.fix_filename(filename)
|
|
||||||
self.checkout_filename = self.create_checkout_filename(self.filename)
|
|
||||||
self.revision = revision
|
|
||||||
self.date = date
|
|
||||||
|
|
||||||
arg_list = []
|
|
||||||
if self.revision:
|
|
||||||
arg_list.append('-r%s' % (self.revision))
|
|
||||||
if self.date:
|
|
||||||
arg_list.append('-d%s' % (self.date))
|
|
||||||
|
|
||||||
temp = os.path.join(cfg.general.rcs_path, "rlog")
|
|
||||||
self.cmd = '%s %s "%s"' % (temp, string.join(arg_list), self.filename)
|
|
||||||
self.rlog = os.popen(self.cmd, 'r')
|
|
||||||
|
|
||||||
def fix_filename(self, filename):
|
|
||||||
## all RCS files have the ",v" ending
|
|
||||||
if filename[-2:] != ",v":
|
|
||||||
filename = "%s,v" % (filename)
|
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
return filename
|
|
||||||
|
|
||||||
## check the Attic for the RCS file
|
|
||||||
path, basename = os.path.split(filename)
|
|
||||||
filename = os.path.join(path, "Attic", basename)
|
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
|
||||||
return filename
|
|
||||||
|
|
||||||
raise error, "rlog file not found: %s" % (filename)
|
|
||||||
|
|
||||||
def create_checkout_filename(self, filename):
|
|
||||||
## cut off the ",v"
|
|
||||||
checkout_filename = filename[:-2]
|
|
||||||
|
|
||||||
## check if the file is in the Attic
|
|
||||||
path, basename = os.path.split(checkout_filename)
|
|
||||||
if path[-6:] != '/Attic':
|
|
||||||
return checkout_filename
|
|
||||||
|
|
||||||
## remove the "Attic" part of the path
|
|
||||||
checkout_filename = os.path.join(path[:-6], basename)
|
|
||||||
return checkout_filename
|
|
||||||
|
|
||||||
def readline(self):
|
|
||||||
try:
|
|
||||||
line = self.rlog.readline()
|
|
||||||
except AttributeError:
|
|
||||||
self.error()
|
|
||||||
|
|
||||||
if line:
|
|
||||||
return line
|
|
||||||
|
|
||||||
status = self.close()
|
|
||||||
if status:
|
|
||||||
self.error()
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
status = self.rlog.close()
|
|
||||||
self.rlog = None
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
def error(self):
|
|
||||||
raise error, "unexpected rlog exit: %s" % (self.cmd)
|
|
||||||
|
|
||||||
|
|
||||||
## constants used in the output parser
|
|
||||||
|
|
||||||
_rlog_commit_sep = '----------------------------\n'
|
|
||||||
_rlog_end = '=============================================================================\n'
|
|
||||||
|
|
||||||
## regular expression used in the output parser
|
|
||||||
_re_symbolic_name = re.compile("\s+([^:]+):\s+(.+)$")
|
|
||||||
|
|
||||||
_re_revision = re.compile("^revision\s+([0-9.]+).*")
|
|
||||||
|
|
||||||
_re_data_line = re.compile(
|
|
||||||
"^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+"\
|
|
||||||
"author:\s+([^;]+);\s+"\
|
|
||||||
"state:\s+([^;]+);\s+"\
|
|
||||||
"lines:\s+\+(\d+)\s+\-(\d+)$")
|
|
||||||
|
|
||||||
_re_data_line_add = re.compile(
|
|
||||||
"^date:\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);\s+"\
|
|
||||||
"author:\s+([^;]+);\s+"\
|
|
||||||
"state:\s+([^;]+);$")
|
|
||||||
|
|
||||||
class RLogOutputParser:
|
|
||||||
|
|
||||||
def __init__(self, rlog):
|
|
||||||
self.rlog = rlog
|
|
||||||
self.rlog_data = RLogData(rlog.checkout_filename)
|
|
||||||
|
|
||||||
## run the parser
|
|
||||||
self.parse_to_symbolic_names()
|
|
||||||
self.parse_symbolic_names()
|
|
||||||
self.parse_to_description()
|
|
||||||
self.parse_rlog_entries()
|
|
||||||
|
|
||||||
def parse_to_symbolic_names(self):
|
|
||||||
while 1:
|
|
||||||
line = self.rlog.readline()
|
|
||||||
if line[:15] == 'symbolic names:':
|
|
||||||
break
|
|
||||||
|
|
||||||
def parse_symbolic_names(self):
|
|
||||||
## parse all the tags int the branch_hash, it's used later to get
|
|
||||||
## the text names of non-head branches
|
|
||||||
while 1:
|
|
||||||
line = self.rlog.readline()
|
|
||||||
match = _re_symbolic_name.match(line)
|
|
||||||
if not match:
|
|
||||||
break
|
|
||||||
|
|
||||||
(tag, revision) = match.groups()
|
|
||||||
|
|
||||||
## check if the tag represents a branch, in RCS this means
|
|
||||||
## the second-to-last number is a zero
|
|
||||||
index = string.rfind(revision, '.')
|
|
||||||
if revision[index-2:index] == '.0':
|
|
||||||
revision = revision[:index-2] + revision[index:]
|
|
||||||
|
|
||||||
self.rlog_data.symbolic_name_hash[revision] = tag
|
|
||||||
|
|
||||||
def parse_to_description(self):
|
|
||||||
while 1:
|
|
||||||
line = self.rlog.readline()
|
|
||||||
if line[:12] == 'description:':
|
|
||||||
break
|
|
||||||
|
|
||||||
## eat all lines until we reach '-----' seperator
|
|
||||||
while 1:
|
|
||||||
line = self.rlog.readline()
|
|
||||||
if line == _rlog_commit_sep:
|
|
||||||
break
|
|
||||||
|
|
||||||
def parse_rlog_entries(self):
|
|
||||||
while 1:
|
|
||||||
rlog_entry = self.parse_one_rlog_entry()
|
|
||||||
if not rlog_entry:
|
|
||||||
break
|
|
||||||
self.rlog_data.rlog_entry_list.append(rlog_entry)
|
|
||||||
|
|
||||||
def parse_one_rlog_entry(self):
|
|
||||||
## revision line/first line
|
|
||||||
line = self.rlog.readline()
|
|
||||||
# Since FreeBSD's rlog outputs extra "---...---\n" before
|
|
||||||
# "===...===\n", _rlog_end may be occured here.
|
|
||||||
if not line or line == _rlog_end:
|
|
||||||
return None
|
|
||||||
|
|
||||||
## revision
|
|
||||||
match = _re_revision.match(line)
|
|
||||||
(revision,) = match.groups()
|
|
||||||
|
|
||||||
## data line
|
|
||||||
line = self.rlog.readline()
|
|
||||||
match = _re_data_line.match(line)
|
|
||||||
if not match:
|
|
||||||
match = _re_data_line_add.match(line)
|
|
||||||
|
|
||||||
if not match:
|
|
||||||
raise error, "bad rlog parser, no cookie!"
|
|
||||||
|
|
||||||
## retrieve the matched grops as a tuple in hopes
|
|
||||||
## this will be faster (ala profiler)
|
|
||||||
groups = match.groups()
|
|
||||||
|
|
||||||
year = string.atoi(groups[0])
|
|
||||||
month = string.atoi(groups[1])
|
|
||||||
day = string.atoi(groups[2])
|
|
||||||
hour = string.atoi(groups[3])
|
|
||||||
minute = string.atoi(groups[4])
|
|
||||||
second = string.atoi(groups[5])
|
|
||||||
author = groups[6]
|
|
||||||
state = groups[7]
|
|
||||||
|
|
||||||
## very strange; here's the deal: if this is a newly added file,
|
|
||||||
## then there is no plus/minus count count of lines; if there
|
|
||||||
## is, then this could be a "CHANGE" or "REMOVE", you can tell
|
|
||||||
## if the file has been removed by looking if state == 'dead'
|
|
||||||
try:
|
|
||||||
pluscount = groups[8]
|
|
||||||
minuscount = groups[9]
|
|
||||||
except IndexError:
|
|
||||||
pluscount = ''
|
|
||||||
minuscount = ''
|
|
||||||
cmit_type = RLogEntry.ADD
|
|
||||||
else:
|
|
||||||
if state == 'dead':
|
|
||||||
cmit_type = RLogEntry.REMOVE
|
|
||||||
else:
|
|
||||||
cmit_type = RLogEntry.CHANGE
|
|
||||||
|
|
||||||
## branch line: pretty much ignored if it's there
|
|
||||||
desc_line_list = []
|
|
||||||
|
|
||||||
line = self.rlog.readline()
|
|
||||||
if not line[:10] == 'branches: ':
|
|
||||||
desc_line_list.append(string.rstrip(line))
|
|
||||||
|
|
||||||
## suck up description
|
|
||||||
while 1:
|
|
||||||
line = self.rlog.readline()
|
|
||||||
|
|
||||||
## the last line printed out by rlog is '===='...
|
|
||||||
## or '------'... between entries
|
|
||||||
if line == _rlog_commit_sep or line == _rlog_end:
|
|
||||||
break
|
|
||||||
|
|
||||||
## append line to the descripton list
|
|
||||||
desc_line_list.append(string.rstrip(line))
|
|
||||||
|
|
||||||
## compute time using time routines in seconds from epoc GMT
|
|
||||||
## NOTE: mktime's arguments are in local time, and we have
|
|
||||||
## them in GMT from RCS; therefore, we have to manually
|
|
||||||
## subtract out the timezone correction
|
|
||||||
##
|
|
||||||
## XXX: Linux glib2.0.7 bug: it looks like mktime doesn't honor
|
|
||||||
## the '0' flag to force no timezone correction, so we look
|
|
||||||
## at the correction ourself and do the right thing after
|
|
||||||
## mktime mangles the date
|
|
||||||
gmt_time = \
|
|
||||||
time.mktime((year, month, day, hour, minute, second, 0, 0, -1))
|
|
||||||
|
|
||||||
if time.daylight:
|
|
||||||
gmt_time = gmt_time - time.altzone
|
|
||||||
else:
|
|
||||||
gmt_time = gmt_time - time.timezone
|
|
||||||
|
|
||||||
## now create and return the RLogEntry
|
|
||||||
rlog_entry = RLogEntry()
|
|
||||||
rlog_entry.type = cmit_type
|
|
||||||
rlog_entry.revision = revision
|
|
||||||
rlog_entry.author = author
|
|
||||||
rlog_entry.description = string.join(desc_line_list, '\n')
|
|
||||||
rlog_entry.time = gmt_time
|
|
||||||
rlog_entry.pluscount = pluscount
|
|
||||||
rlog_entry.minuscount = minuscount
|
|
||||||
|
|
||||||
return rlog_entry
|
|
||||||
|
|
||||||
|
|
||||||
## entrypoints
|
|
||||||
|
|
||||||
def GetRLogData(path, revision = '', date = ''):
|
|
||||||
rlog = RLog(path, revision, date)
|
|
||||||
rlog_parser = RLogOutputParser(rlog)
|
|
||||||
return rlog_parser.rlog_data
|
|
2526
lib/viewcvs.py
2526
lib/viewcvs.py
File diff suppressed because it is too large
Load Diff
139
tests/timelog.py
139
tests/timelog.py
@@ -1,139 +0,0 @@
|
|||||||
|
|
||||||
import time
|
|
||||||
import string
|
|
||||||
import profile
|
|
||||||
|
|
||||||
import rcsparse
|
|
||||||
import viewcvs
|
|
||||||
|
|
||||||
def lines_changed(delta):
|
|
||||||
idx = 0
|
|
||||||
added = deleted = 0
|
|
||||||
while idx < len(delta):
|
|
||||||
op = delta[idx]
|
|
||||||
i = string.find(delta, ' ', idx + 1)
|
|
||||||
j = string.find(delta, '\n', i + 1)
|
|
||||||
line = int(delta[idx+1:i])
|
|
||||||
count = int(delta[i+1:j])
|
|
||||||
idx = j + 1
|
|
||||||
if op == 'd':
|
|
||||||
deleted = deleted + count
|
|
||||||
else: # 'a' for adding text
|
|
||||||
added = added + count
|
|
||||||
# skip new text
|
|
||||||
while count > 0:
|
|
||||||
nl = string.find(delta, '\n', idx)
|
|
||||||
assert nl > 0, 'missing a newline in the delta in the RCS file'
|
|
||||||
idx = nl + 1
|
|
||||||
count = count - 1
|
|
||||||
return added, deleted
|
|
||||||
|
|
||||||
class FetchSink(rcsparse.Sink):
|
|
||||||
def __init__(self, which_rev=None):
|
|
||||||
self.head = self.branch = ''
|
|
||||||
self.tags = { }
|
|
||||||
self.meta = { }
|
|
||||||
self.revs = [ ]
|
|
||||||
self.base = { }
|
|
||||||
self.entries = { }
|
|
||||||
self.which = which_rev
|
|
||||||
|
|
||||||
def set_head_revision(self, revision):
|
|
||||||
self.head = revision
|
|
||||||
|
|
||||||
def set_principal_branch(self, branch_name):
|
|
||||||
self.branch = branch_name
|
|
||||||
|
|
||||||
def define_tag(self, name, revision):
|
|
||||||
self.tags[name] = revision
|
|
||||||
|
|
||||||
def define_revision(self, revision, timestamp, author, state,
|
|
||||||
branches, next):
|
|
||||||
self.meta[revision] = (timestamp, author, state)
|
|
||||||
self.base[next] = revision
|
|
||||||
for b in branches:
|
|
||||||
self.base[b] = revision
|
|
||||||
|
|
||||||
def set_revision_info(self, revision, log, text):
|
|
||||||
timestamp, author, state = self.meta[revision]
|
|
||||||
entry = viewcvs.LogEntry(revision, int(timestamp) - time.timezone, author,
|
|
||||||
state, None, log)
|
|
||||||
|
|
||||||
# .revs is "order seen" and .entries is for random access
|
|
||||||
self.revs.append(entry)
|
|
||||||
self.entries[revision] = entry
|
|
||||||
|
|
||||||
if revision != self.head:
|
|
||||||
added, deleted = lines_changed(text)
|
|
||||||
if string.count(revision, '.') == 1:
|
|
||||||
# on the trunk. reverse delta.
|
|
||||||
changed = '+%d -%d' % (deleted, added)
|
|
||||||
self.entries[self.base[revision]].changed = changed
|
|
||||||
else:
|
|
||||||
# on a branch. forward delta.
|
|
||||||
changed = '+%d -%d' % (added, deleted)
|
|
||||||
self.entries[revision].changed = changed
|
|
||||||
|
|
||||||
def parse_completed(self):
|
|
||||||
if self.which:
|
|
||||||
self.revs = [ self.entries[self.which] ]
|
|
||||||
|
|
||||||
def fetch_log2(full_name, which_rev=None):
|
|
||||||
sink = FetchSink(which_rev)
|
|
||||||
rcsparse.Parser().parse(open(full_name), sink)
|
|
||||||
return sink.head, sink.branch, sink.tags, sink.revs
|
|
||||||
|
|
||||||
def compare_fetch(full_name, which_rev=None):
|
|
||||||
d1 = viewcvs.fetch_log(full_name, which_rev)
|
|
||||||
d2 = fetch_log2(full_name, which_rev)
|
|
||||||
if d1[:3] != d2[:3]:
|
|
||||||
print 'd1:', d1[:3]
|
|
||||||
print 'd2:', d2[:3]
|
|
||||||
return
|
|
||||||
if len(d1[3]) != len(d2[3]):
|
|
||||||
print 'len(d1[3])=%d len(d2[3])=%d' % (len(d1[3]), len(d2[3]))
|
|
||||||
return
|
|
||||||
def sort_func(e, f):
|
|
||||||
return cmp(e.rev, f.rev)
|
|
||||||
d1[3].sort(sort_func)
|
|
||||||
d2[3].sort(sort_func)
|
|
||||||
import pprint
|
|
||||||
for i in range(len(d1[3])):
|
|
||||||
if vars(d1[3][i]) != vars(d2[3][i]):
|
|
||||||
pprint.pprint((i, vars(d1[3][i]), vars(d2[3][i])))
|
|
||||||
|
|
||||||
def time_fetch(full_name, which_rev=None):
|
|
||||||
t = time.time()
|
|
||||||
viewcvs.fetch_log(full_name, which_rev)
|
|
||||||
t1 = time.time() - t
|
|
||||||
t = time.time()
|
|
||||||
fetch_log2(full_name, which_rev)
|
|
||||||
t2 = time.time() - t
|
|
||||||
print t1, t2
|
|
||||||
|
|
||||||
def profile_fetch(full_name, which_rev=None):
|
|
||||||
p = profile.Profile()
|
|
||||||
def many_calls(*args):
|
|
||||||
for i in xrange(10):
|
|
||||||
apply(fetch_log2, args)
|
|
||||||
p.runcall(many_calls, full_name, which_rev)
|
|
||||||
p.print_stats()
|
|
||||||
|
|
||||||
def varysize(full_name, which_rev=None):
|
|
||||||
def one_run(n, *args):
|
|
||||||
rcsparse._TokenStream.CHUNK_SIZE = n
|
|
||||||
t = time.time()
|
|
||||||
for i in xrange(5):
|
|
||||||
apply(fetch_log2, args)
|
|
||||||
print n, time.time() - t
|
|
||||||
|
|
||||||
#one_run(2020, full_name, which_rev)
|
|
||||||
#one_run(4070, full_name, which_rev)
|
|
||||||
#one_run(8170, full_name, which_rev)
|
|
||||||
#one_run(8192, full_name, which_rev)
|
|
||||||
#one_run(16384, full_name, which_rev)
|
|
||||||
one_run(32740, full_name, which_rev)
|
|
||||||
one_run(65500, full_name, which_rev)
|
|
||||||
one_run(100000, full_name, which_rev)
|
|
||||||
one_run(200000, full_name, which_rev)
|
|
||||||
one_run(500000, full_name, which_rev)
|
|
161
tools/cvsdbadmin
161
tools/cvsdbadmin
@@ -1,161 +0,0 @@
|
|||||||
#!/usr/bin/env 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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# administrative program for CVSdb; this is primarily
|
|
||||||
# used to add/rebuild CVS repositories to the database
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# INSTALL-TIME CONFIGURATION
|
|
||||||
#
|
|
||||||
# These values will be set during the installation process. During
|
|
||||||
# development, they will remain None.
|
|
||||||
#
|
|
||||||
|
|
||||||
LIBRARY_DIR = None
|
|
||||||
CONF_PATHNAME = 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, cvsdbapi
|
|
||||||
|
|
||||||
|
|
||||||
def UpdateFile(db, repository, path):
|
|
||||||
try:
|
|
||||||
commit_list = cvsdbapi.GetUnrecordedCommitList(repository, path)
|
|
||||||
except cvsdbapi.error, e:
|
|
||||||
print '[ERROR] %s' % (e)
|
|
||||||
return
|
|
||||||
|
|
||||||
print '[%s[%d new commits]]' % (path, len(commit_list)),
|
|
||||||
|
|
||||||
## add the commits into the database
|
|
||||||
for commit in commit_list:
|
|
||||||
db.AddCommit(commit)
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
print
|
|
||||||
|
|
||||||
|
|
||||||
def RecurseUpdate(db, repository, directory):
|
|
||||||
for path in os.listdir(directory):
|
|
||||||
path = os.path.join(directory, path)
|
|
||||||
|
|
||||||
if os.path.islink(path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os.path.isdir(path):
|
|
||||||
RecurseUpdate(db, repository, path)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os.path.isfile(path):
|
|
||||||
if path[-2:] == ',v':
|
|
||||||
UpdateFile(db, repository, path)
|
|
||||||
|
|
||||||
|
|
||||||
def CommandUpdate():
|
|
||||||
## connect to the database we are updating
|
|
||||||
db = cvsdbapi.ConnectDatabase()
|
|
||||||
repository = sys.argv[2]
|
|
||||||
RecurseUpdate(db, repository, repository)
|
|
||||||
|
|
||||||
|
|
||||||
def RebuildFile(db, repository, path):
|
|
||||||
try:
|
|
||||||
commit_list = cvsdbapi.GetCommitListFromRCSFile(repository, path)
|
|
||||||
except cvsdbapi.error, e:
|
|
||||||
print '[ERROR] %s' % (e)
|
|
||||||
return
|
|
||||||
|
|
||||||
print '[%s[%d commits]]' % (path, len(commit_list)),
|
|
||||||
|
|
||||||
## add the commits into the database
|
|
||||||
for commit in commit_list:
|
|
||||||
db.AddCommit(commit)
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
print
|
|
||||||
|
|
||||||
|
|
||||||
def RecurseRebuild(db, repository, directory):
|
|
||||||
for path in os.listdir(directory):
|
|
||||||
path = os.path.join(directory, path)
|
|
||||||
|
|
||||||
if os.path.islink(path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os.path.isdir(path):
|
|
||||||
RecurseRebuild(db, repository, path)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os.path.isfile(path):
|
|
||||||
if path[-2:] == ',v':
|
|
||||||
RebuildFile(db, repository, path)
|
|
||||||
|
|
||||||
|
|
||||||
def CommandRebuild():
|
|
||||||
## connect to the database we are updating
|
|
||||||
db = cvsdbapi.ConnectDatabase()
|
|
||||||
repository = sys.argv[2]
|
|
||||||
RecurseRebuild(db, repository, repository)
|
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
|
||||||
print 'Usage: %s <command> [arguments]' % (sys.argv[0])
|
|
||||||
print 'Preforms administrative functions for the CVSdb database'
|
|
||||||
print 'Commands:'
|
|
||||||
print ' rebuild <repository> rebuilds the CVSdb database'
|
|
||||||
print ' for all files in the repository'
|
|
||||||
print ' update <repository> updates the CVSdb database for'
|
|
||||||
print ' all unrecorded commits'
|
|
||||||
print
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
## main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
## check that a command was given
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
usage()
|
|
||||||
|
|
||||||
## set the handler function for the command
|
|
||||||
command = sys.argv[1]
|
|
||||||
if string.lower(command) == 'rebuild':
|
|
||||||
commandFunction = CommandRebuild
|
|
||||||
elif string.lower(command) == 'update':
|
|
||||||
commandFunction = CommandUpdate
|
|
||||||
else:
|
|
||||||
print 'ERROR: unknown command %s' % (command)
|
|
||||||
usage()
|
|
||||||
|
|
||||||
## run command
|
|
||||||
try:
|
|
||||||
commandFunction()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print
|
|
||||||
print '** break **'
|
|
||||||
|
|
||||||
sys.exit(0)
|
|
@@ -1,187 +0,0 @@
|
|||||||
#!/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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# updates SQL database with new commit records
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
#########################################################################
|
|
||||||
#
|
|
||||||
# INSTALL-TIME CONFIGURATION
|
|
||||||
#
|
|
||||||
# These values will be set during the installation process. During
|
|
||||||
# development, they will remain None.
|
|
||||||
#
|
|
||||||
|
|
||||||
LIBRARY_DIR = None
|
|
||||||
CONF_PATHNAME = 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, getopt, re, cvsdbapi
|
|
||||||
|
|
||||||
DEBUG_FLAG = 0
|
|
||||||
|
|
||||||
## pre-compiled regular expressions
|
|
||||||
_re_fileversion = re.compile("([^,]+)\,([^,]+)\,([^,]+)")
|
|
||||||
|
|
||||||
|
|
||||||
## output functions
|
|
||||||
def debug(text):
|
|
||||||
if DEBUG_FLAG:
|
|
||||||
print 'DEBUG(loginfo): %s' % (text)
|
|
||||||
|
|
||||||
def warning(text):
|
|
||||||
print 'WARNING(loginfo): %s' % (text)
|
|
||||||
|
|
||||||
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
|
|
||||||
self.old_version = old_version
|
|
||||||
self.new_version = new_version
|
|
||||||
|
|
||||||
## set the state of this file from the
|
|
||||||
## old_version and new_version information
|
|
||||||
if self.old_version == 'NONE':
|
|
||||||
self.type = self.ADDED
|
|
||||||
elif self.new_version == 'NONE':
|
|
||||||
self.type = self.REMOVED
|
|
||||||
else:
|
|
||||||
self.type = self.CHANGED
|
|
||||||
|
|
||||||
|
|
||||||
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 = 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.type == file_data.CHANGED:
|
|
||||||
commit.SetTypeChange()
|
|
||||||
elif file_data.type == file_data.ADDED:
|
|
||||||
commit.SetTypeAdd()
|
|
||||||
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 cvsdbapi.GetUnrecordedCommitList(repository, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def ProcessLoginfo(repository, stdin_list):
|
|
||||||
## 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
|
|
||||||
list = string.split(stdin_list[0])
|
|
||||||
|
|
||||||
## clean up the directory the following way: we don't want it
|
|
||||||
## to begin with a path seperator, and we don't want it to end
|
|
||||||
## with a path seperator
|
|
||||||
directory = list[0]
|
|
||||||
while directory[0] == os.sep:
|
|
||||||
directory = directory[1:]
|
|
||||||
while directory[-1] == os.sep:
|
|
||||||
directory = directory[:-1]
|
|
||||||
|
|
||||||
## NOTE: SPECIAL HANDLING FOR NEW DIRECTORIES
|
|
||||||
## new directories have the first line form
|
|
||||||
## path/of/dir - New directory
|
|
||||||
if len(list) == 4:
|
|
||||||
if list[1] == '-' and list[2] == 'New' and list[3] == 'directory':
|
|
||||||
debug('new directory')
|
|
||||||
return
|
|
||||||
|
|
||||||
## each file in the file list _should_ be of the form:
|
|
||||||
## file-name,<old-ver>,<new-ver>
|
|
||||||
## a new file has the keyword 'NONE' for old-ver
|
|
||||||
file_data_list = []
|
|
||||||
for item in list[1:]:
|
|
||||||
temp = _re_fileversion.match(item)
|
|
||||||
if not temp:
|
|
||||||
debug('failed match %s' % (item))
|
|
||||||
continue
|
|
||||||
|
|
||||||
filename = temp.group(1)
|
|
||||||
old_version = temp.group(2)
|
|
||||||
new_version = temp.group(3)
|
|
||||||
file_data = FileData(filename, directory, old_version, new_version)
|
|
||||||
file_data_list.append(file_data)
|
|
||||||
|
|
||||||
## convert FileData objects into Commit objects so we can insert them
|
|
||||||
## into the database
|
|
||||||
commit_list = []
|
|
||||||
for file_data in file_data_list:
|
|
||||||
## XXX: this is nasty: in the case of a removed file, we are not
|
|
||||||
## 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.type == file_data.REMOVED:
|
|
||||||
temp = GetUnrecordedCommitList(repository, file_data)
|
|
||||||
commit_list = commit_list + temp
|
|
||||||
else:
|
|
||||||
commit_list.append(CommitFromFileData(repository, file_data))
|
|
||||||
|
|
||||||
## add to the database
|
|
||||||
db = cvsdbapi.ConnectDatabase()
|
|
||||||
db.AddCommitList(commit_list)
|
|
||||||
|
|
||||||
|
|
||||||
## MAIN
|
|
||||||
if __name__ == '__main__':
|
|
||||||
## get the repository from the environment
|
|
||||||
try:
|
|
||||||
repository = os.environ['CVSROOT']
|
|
||||||
except KeyError:
|
|
||||||
error('CVSROOT not in environment')
|
|
||||||
|
|
||||||
## clean up the repository string: remove any trailing path seperater
|
|
||||||
while repository[-1] == os.sep:
|
|
||||||
repository = repository[:-1]
|
|
||||||
|
|
||||||
## read all the lines from stdin
|
|
||||||
stdin_list = []
|
|
||||||
for line in sys.stdin.readlines():
|
|
||||||
stdin_list.append(string.rstrip(line))
|
|
||||||
|
|
||||||
ProcessLoginfo(repository, stdin_list)
|
|
||||||
sys.exit(0)
|
|
@@ -1,146 +0,0 @@
|
|||||||
#!/usr/bin/env 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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# administrative program for CVSdb; creates a clean database in
|
|
||||||
# MySQL 3.22 or later
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
import os, sys, string
|
|
||||||
|
|
||||||
INTRO_TEXT = """\
|
|
||||||
This script creates the database and tables in MySQL used by the ViewCVS
|
|
||||||
checkin database. You will be prompted for: database user, database user
|
|
||||||
password, and database name. This script will use mysql to create the
|
|
||||||
database for you. You will then need to set the appropriate parameters
|
|
||||||
in your viewcvs.conf file under the [cvsdb] section.
|
|
||||||
"""
|
|
||||||
|
|
||||||
DATABASE_SCRIPT="""\
|
|
||||||
DROP DATABASE IF EXISTS <dbname>;
|
|
||||||
CREATE DATABASE <dbname>;
|
|
||||||
|
|
||||||
USE <dbname>;
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS branches;
|
|
||||||
CREATE TABLE branches (
|
|
||||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
|
||||||
branch varchar(64) binary DEFAULT '' NOT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE branch (branch)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS checkins;
|
|
||||||
CREATE TABLE checkins (
|
|
||||||
type enum('Change','Add','Remove'),
|
|
||||||
ci_when datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
|
|
||||||
whoid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
dirid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
fileid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
revision varchar(32) binary DEFAULT '' NOT NULL,
|
|
||||||
stickytag varchar(255) binary DEFAULT '' NOT NULL,
|
|
||||||
branchid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
addedlines int(11) DEFAULT '0' NOT NULL,
|
|
||||||
removedlines int(11) DEFAULT '0' NOT NULL,
|
|
||||||
descid mediumint(9),
|
|
||||||
UNIQUE repositoryid (repositoryid,dirid,fileid,revision),
|
|
||||||
KEY ci_when (ci_when),
|
|
||||||
KEY whoid (whoid),
|
|
||||||
KEY repositoryid_2 (repositoryid),
|
|
||||||
KEY dirid (dirid),
|
|
||||||
KEY fileid (fileid),
|
|
||||||
KEY branchid (branchid)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS descs;
|
|
||||||
CREATE TABLE descs (
|
|
||||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
|
||||||
description text,
|
|
||||||
hash bigint(20) DEFAULT '0' NOT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
KEY hash (hash)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS dirs;
|
|
||||||
CREATE TABLE dirs (
|
|
||||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
|
||||||
dir varchar(128) binary DEFAULT '' NOT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE dir (dir)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS files;
|
|
||||||
CREATE TABLE files (
|
|
||||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
|
||||||
file varchar(128) binary DEFAULT '' NOT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE file (file)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS people;
|
|
||||||
CREATE TABLE people (
|
|
||||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
|
||||||
who varchar(32) binary DEFAULT '' NOT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE who (who)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS repositories;
|
|
||||||
CREATE TABLE repositories (
|
|
||||||
id mediumint(9) DEFAULT '0' NOT NULL auto_increment,
|
|
||||||
repository varchar(64) binary DEFAULT '' NOT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE repository (repository)
|
|
||||||
);
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS tags;
|
|
||||||
CREATE TABLE tags (
|
|
||||||
repositoryid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
branchid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
dirid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
fileid mediumint(9) DEFAULT '0' NOT NULL,
|
|
||||||
revision varchar(32) binary DEFAULT '' NOT NULL,
|
|
||||||
UNIQUE repositoryid (repositoryid,dirid,fileid,branchid,revision),
|
|
||||||
KEY repositoryid_2 (repositoryid),
|
|
||||||
KEY dirid (dirid),
|
|
||||||
KEY fileid (fileid),
|
|
||||||
KEY branchid (branchid)
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print INTRO_TEXT
|
|
||||||
|
|
||||||
user = raw_input("MySQL User: ")
|
|
||||||
passwd = raw_input("MySQL Password: ")
|
|
||||||
dbase = raw_input("ViewCVS Database Name [default: ViewCVS]: ")
|
|
||||||
if not dbase:
|
|
||||||
dbase = "ViewCVS"
|
|
||||||
|
|
||||||
cmd = "{ mysql --user=%s --password=%s ; } 2>&1" % (user, passwd)
|
|
||||||
dscript = string.replace(DATABASE_SCRIPT, "<dbname>", dbase)
|
|
||||||
|
|
||||||
mysql = os.popen(cmd, "w")
|
|
||||||
mysql.write(dscript)
|
|
||||||
status = mysql.close()
|
|
||||||
|
|
||||||
if status:
|
|
||||||
print "[ERROR] the database did not create sucessfully."
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print "Database created successfully."
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# 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/
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# make-release: internal tool for creating ViewCVS releases
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
if [ "x$1" = "x" ]; then
|
|
||||||
echo "USAGE: $0 tagname target-directory"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test -e $2; then
|
|
||||||
echo "ERROR: must remove $2 first."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# grab a copy of the CVS repository
|
|
||||||
echo 'Checking out into:' $2
|
|
||||||
cvs -d :pserver:anonymous@cvs.viewcvs.sourceforge.net:/cvsroot/viewcvs export -r $1 -d $2 viewcvs
|
|
||||||
|
|
||||||
# various shifting, cleanup
|
|
||||||
mv $2/website/license-1.html $2/LICENSE.html
|
|
||||||
rm -r $2/website
|
|
||||||
rm $2/tools/make-release
|
|
||||||
|
|
||||||
echo 'Done.'
|
|
180
viewcvs-install
180
viewcvs-install
@@ -1,180 +0,0 @@
|
|||||||
#!/usr/bin/env 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 script for viewcvs -- temporary?
|
|
||||||
#
|
|
||||||
# ### this will eventually be replaced by autoconf plus tools. an
|
|
||||||
# ### interactive front-end to ./configure may be provided.
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import string
|
|
||||||
import re
|
|
||||||
import traceback
|
|
||||||
import py_compile
|
|
||||||
|
|
||||||
# get access to our library modules
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
|
|
||||||
|
|
||||||
import compat
|
|
||||||
|
|
||||||
|
|
||||||
## installer text
|
|
||||||
INFO_TEXT = """\
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
## installer defaults
|
|
||||||
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 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),
|
|
||||||
|
|
||||||
("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/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/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, 1, 0, 1),
|
|
||||||
("lib/rlog.py", "lib/rlog.py", 0644, 1, 0, 1),
|
|
||||||
("lib/viewcvs.py", "lib/viewcvs.py", 0644, 1, 0, 1),
|
|
||||||
|
|
||||||
("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),
|
|
||||||
|
|
||||||
("html-templates/queryformtemplate.html",
|
|
||||||
"html-templates/queryformtemplate.html", 0644, 0, 1, 0),
|
|
||||||
("html-templates/querytemplate.html",
|
|
||||||
"html-templates/querytemplate.html", 0644, 0, 1, 0),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def Error(text, etype=None, evalue=None):
|
|
||||||
print
|
|
||||||
print "[ERROR] %s" % text
|
|
||||||
if etype:
|
|
||||||
print '[ERROR] ',
|
|
||||||
traceback.print_exception(etype, evalue, None, file=sys.stdout)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def MkDir(path):
|
|
||||||
try:
|
|
||||||
compat.makedirs(path)
|
|
||||||
except os.error, e:
|
|
||||||
if e[0] == 17:
|
|
||||||
# EEXIST: file exists
|
|
||||||
return
|
|
||||||
if e[0] == 13:
|
|
||||||
# EACCES: permission denied
|
|
||||||
Error("You do not have permission to create directory %s" % path)
|
|
||||||
Error("Unknown error creating directory %s" % path, OSError, e)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def SetOnePath(contents, var, value):
|
|
||||||
pattern = re.compile('^' + var + r'\s*=\s*.*$', re.MULTILINE)
|
|
||||||
repl = '%s = "%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 = 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)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
## MAIN
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print INFO_TEXT
|
|
||||||
|
|
||||||
## get the install path
|
|
||||||
temp = raw_input("Installation Path [%s]: " % ROOT_DIR)
|
|
||||||
temp = string.strip(temp)
|
|
||||||
if len(temp):
|
|
||||||
ROOT_DIR = temp
|
|
||||||
|
|
||||||
## install the files
|
|
||||||
print
|
|
||||||
print "Installing ViewCVS to:", ROOT_DIR
|
|
||||||
|
|
||||||
for args in FILE_INFO_LIST:
|
|
||||||
print " ", args[0]
|
|
||||||
apply(InstallFile, args)
|
|
||||||
|
|
||||||
print
|
|
||||||
print "Installation Complete"
|
|
Binary file not shown.
Before Width: | Height: | Size: 755 B |
@@ -5,75 +5,55 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body background="/images/chalk.jpg">
|
<body background="/images/chalk.jpg">
|
||||||
|
<h1>ViewCVS: Viewing CVS Repositories</h1>
|
||||||
<table width="100%" 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 project page</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
The ViewCVS software was inspired by
|
The ViewCVS software was inspired by
|
||||||
<a href="http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/"><i>cvsweb</i></a>
|
<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi"><i>cvsweb</i></a>
|
||||||
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
|
(by <a href="mailto:zeller@think.de">Henner Zeller</a>).
|
||||||
I wanted to make some changes and updates, but cvsweb was
|
I wanted to make some changes and updates, but cvsweb was
|
||||||
implemented in Perl. While I can manage some Perl, cvsweb was
|
implemented in Perl (and rather poorly, IMO). So I undertook the
|
||||||
rather unmaintainable for me. So I undertook the
|
|
||||||
task to convert the software to
|
task to convert the software to
|
||||||
<a href="http://www.python.org/"><i>Python</i></a>. As a result,
|
<a href="http://www.python.org/"><i>Python</i></a>.
|
||||||
I've actually been able to go <em>way</em> beyond the simple
|
|
||||||
changes that I had envisioned.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
ViewCVS can browse directories, change logs, and specific
|
ViewCVS can browse directories, change logs, and specific
|
||||||
revisions of files. It can display diffs between versions and
|
revisions of files. It can display diffs between versions and
|
||||||
show selections of files based on tags or branches. In addition,
|
show selections of files based on tags or branches.
|
||||||
ViewCVS has "annotation" or "blame" support, and the beginnings
|
|
||||||
of Bonsai-like query facilities.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
ViewCVS is currently at version 0.6. It was a port of the cvsweb
|
ViewCVS is currently at version 0.3. It is almost a straight
|
||||||
script, but has had numerous cleanups and other modifications,
|
port of the cvsweb script, but has had numerous cleanups and other
|
||||||
based on some of Python's strengths. There is still some minor
|
modifications, based on some of Python's strengths. There is
|
||||||
"badness" remaining from the Perl code, but I've been working on
|
still some "badness" in there, but I've been working on flushing
|
||||||
flushing that out, while adding new features. Currently, the
|
that out, along with a few new features. The
|
||||||
functionality of ViewCVS surpasses that of cvsweb.
|
functionality of ViewCVS is equal to that of cvsweb, minus the
|
||||||
|
"annotation" support. Annotation requires read/write access to
|
||||||
|
the CVS repository (at this time), and I believe that is a
|
||||||
|
Bad Thing to do. One of my tasks will be eliminating the
|
||||||
|
read/write requirement.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The software is available for download:
|
The software is available for download:
|
||||||
</p>
|
</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<a href="viewcvs-0.6.tar.gz">Version 0.6 of ViewCVS</a>
|
<a href="viewcvs-0.3.tar.gz">Version 0.3 of ViewCVS</a>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<p>
|
<p>
|
||||||
Of course, it is also available through ViewCVS itself:
|
Of course, it is also available through ViewCVS itself:
|
||||||
</p>
|
</p>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/">http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/</a>
|
<a href="/cgi-bin/viewcvs.cgi/gjspy/viewcvs/">http://www.lyra.org/cgi-bin/viewcvs.cgi/gjspy/viewcvs/</a>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
ViewCVS requires <strong>Python 1.5</strong> (which has been out
|
ViewCVS <font color=red>requires <strong>Python
|
||||||
for a couple years and is readily available for your favorite
|
1.5.2</strong></font>.
|
||||||
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>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
ViewCVS has been developed by the <a href="who.html">ViewCVS
|
[ <a href="../">up to Python software</a> ]
|
||||||
Group</a> and is made available under a
|
[ <a href="../../">up to Greg's page</a> ]
|
||||||
<a href="license-1.html">BSD-type license</a>.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
@@ -85,74 +65,56 @@
|
|||||||
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs">ViewCVS
|
<a href="http://mailman.lyra.org/mailman/listinfo/viewcvs">ViewCVS
|
||||||
mailing list</a>.
|
mailing list</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
A <a href="http://mailman.lyra.org/mailman/listinfo/viewcvs-dev">mailing
|
<hr width="75%">
|
||||||
list for ViewCVS developers</a> is also available.
|
<h2>FAQ</h2>
|
||||||
</p>
|
|
||||||
|
<p>Kinda short :-)</p>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<!-- one of (dd dt) -->
|
||||||
|
<dt><strong>I got an <code>AttributeError: urlencode</code></strong></dt>
|
||||||
|
<dd>
|
||||||
|
If you see the following traceback in your browser:
|
||||||
|
<blockquote>
|
||||||
|
<pre>
|
||||||
|
Traceback (innermost last):
|
||||||
|
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2205, in ?
|
||||||
|
main()
|
||||||
|
File "/home/httpd/cgi-bin/viewcvs.cgi", line 2123, in main
|
||||||
|
query_string = sticky_query(query_dict)
|
||||||
|
File "/home/httpd/cgi-bin/viewcvs.cgi", line 444, in sticky_query
|
||||||
|
bare_query = urllib.urlencode(sticky_dict)
|
||||||
|
AttributeError: urlencode
|
||||||
|
</pre>
|
||||||
|
</blockquote>
|
||||||
|
This is indicative of using an earlier version of
|
||||||
|
Python. <code>urllib.urlencode()</code> was introduced in
|
||||||
|
Python 1.5.2.
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
<h2>Additional features over cvsweb</h2>
|
<h2>Additional features over cvsweb</h2>
|
||||||
|
|
||||||
<p>
|
|
||||||
<strong>New to version 0.5:</strong>
|
|
||||||
</p>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>Colorization for Python files</li>
|
||||||
Colorization for many file types via <code>enscript</code>.
|
<li>Better reporting for unreadable files</li>
|
||||||
</li>
|
<li>More robust when given varying <code>rcsdiff</code> or
|
||||||
<li>
|
<code>rlog</code> outputs</li>
|
||||||
Bonsai-like query features.
|
<li>Hard breaks in human-readable diffs</li>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Annotation/blame support against a <strong>read-only</strong>
|
|
||||||
repository.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Configuration on a per-virtual-host basis. This allows you
|
|
||||||
to share the configuration file and ViewCVS installation
|
|
||||||
across virtual hosts, yet still be able to fine-tune the
|
|
||||||
options when necessary.
|
|
||||||
</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
|
|
||||||
<code>rlog</code> outputs.
|
|
||||||
</li>
|
|
||||||
<li>Hard breaks in human-readable diffs.</li>
|
|
||||||
<li>
|
|
||||||
The configuration file is optional (you can change the values
|
|
||||||
right in the CGI script and avoid the config file, if you so
|
|
||||||
choose). The config file syntax is also cleaner, since it is
|
|
||||||
human-manageable rather than source code.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Directories with a large number of files can be viewed.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Security</strong>: ViewCVS only requires read access
|
|
||||||
to the CVS repository (not read/write). With the correct
|
|
||||||
security partitioning, this means that even if ViewCVS were to
|
|
||||||
be subverted, your source code is safe. Further, ViewCVS does
|
|
||||||
not use any <code>system()</code> or <code>popen()</code>
|
|
||||||
calls, which are very susceptible to abuse.
|
|
||||||
<br>
|
|
||||||
<small>(cvsweb had a hole due to a popen() call)</small>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Future features, coming soon:
|
Future features, coming soon:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>
|
||||||
|
Optional, separate configuration file (similar to
|
||||||
|
cvsweb). This will make upgrading easier.
|
||||||
|
</li>
|
||||||
<li>UI streamlining/simplification</li>
|
<li>UI streamlining/simplification</li>
|
||||||
<li>Integration with CVS checkin auto-mail scripts</li>
|
<li>Integration with CVS checkin auto-mail scripts</li>
|
||||||
<li>Tighter integration the query features</li>
|
|
||||||
<li>
|
<li>
|
||||||
<i>Suggestions? Send mail to the
|
<i>Suggestions? Send mail to the
|
||||||
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
|
<a href="mailto:viewcvs@lyra.org">viewcvs@lyra.org</a>
|
||||||
@@ -165,35 +127,20 @@
|
|||||||
Longer term:
|
Longer term:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Annotation ("blame") support (similar to cvsweb)</li>
|
||||||
<li>Integration with an indexer such as LXR</li>
|
<li>Integration with an indexer such as LXR</li>
|
||||||
|
<li>Additional colorizers</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<hr width="75%">
|
<hr width="75%">
|
||||||
<h2>Colorization of files</h2>
|
<h2>Colorization of Python 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. 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 comes with a builtin colorizer for Python
|
|
||||||
source files. This may go away, given the new
|
|
||||||
<code>enscript</code> support...
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
Christophe Pelte suggested this feature: colorize Python source
|
Christophe Pelte suggested this feature: colorize Python source
|
||||||
files using
|
files using
|
||||||
<a href="http://starship.python.net/crew/lemburg/SoftwareDescriptions.html#py2html.py"><i>py2html</i></a>
|
<a href="http://starship.python.net/crew/lemburg/SoftwareDescriptions.html#py2html.py">py2html</a>
|
||||||
(by Marc-Andrew Lemburg, based on
|
(by Marc-Andrew Lemburg, based on
|
||||||
<a href="http://starship.python.net/crew/just/code/PyFontify.py"><i>PyFontify</i></a>
|
<a href="http://starship.python.net/crew/just/code/PyFontify.py">PyFontify</a>
|
||||||
by Just van Rossum). I've added this feature to ViewCVS 0.3,
|
by Just van Rossum). I've added this feature to ViewCVS 0.3,
|
||||||
along with a generalized plugin mechanism for custom coloring other
|
along with a generalized plugin mechanism for custom coloring other
|
||||||
types of files. See the instructions within the viewcvs.cgi for
|
types of files. See the instructions within the viewcvs.cgi for
|
||||||
@@ -205,7 +152,7 @@
|
|||||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
||||||
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
||||||
<!-- hhmts start -->
|
<!-- hhmts start -->
|
||||||
Last modified: Fri May 11 14:22:36 PDT 2001
|
Last modified: Fri Feb 11 07:51:53 PST 2000
|
||||||
<!-- hhmts end -->
|
<!-- hhmts end -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,83 +0,0 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>ViewCVS License Agreement (v1)</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>ViewCVS License Agreement (v1)</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The following text constitutes the license agreement for the
|
|
||||||
<a href="./">ViewCVS</a> software. It
|
|
||||||
is an agreement between
|
|
||||||
<a href="who.html">The ViewCVS
|
|
||||||
Group</a> and the users of ViewCVS.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<small>
|
|
||||||
<em>
|
|
||||||
Note: the copyright years were updated on May 12, 2001. No
|
|
||||||
other changes were made to the license.
|
|
||||||
</em>
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<b>
|
|
||||||
Copyright © 1999-2001 The ViewCVS Group. All rights reserved.
|
|
||||||
</b>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
By using ViewCVS, you agree to the terms and conditions set
|
|
||||||
forth below:
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Redistribution and use in source and binary forms, with or
|
|
||||||
without modification, are permitted provided that the following
|
|
||||||
conditions are met:
|
|
||||||
</p>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<p>
|
|
||||||
Redistributions of source code must retain the above
|
|
||||||
copyright notice, this list of conditions and the following
|
|
||||||
disclaimer.
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<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 May 12 15:53:33 PDT 2001
|
|
||||||
<!-- hhmts end -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>The ViewCVS Group</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body background="/images/chalk.jpg">
|
|
||||||
<h1>The ViewCVS Group</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The ViewCVS Group is an informal group of people working on and
|
|
||||||
developing the ViewCVS package. The current set of members are:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="http://www.lyra.org/greg/"><b>Greg Stein</b></a></li>
|
|
||||||
<li>Jay Painter</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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: Fri May 11 14:10:54 PDT 2001
|
|
||||||
<!-- hhmts end -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user