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

Compare commits

..

1 Commits
V0_7 ... V0_5

Author SHA1 Message Date
(no author)
99216ae1da This commit was manufactured by cvs2svn to create tag 'V0_5'.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/V0_5@140 8cb11bc2-c004-0410-86c3-e597b4017df7
2000-07-02 00:44:05 +00:00
28 changed files with 2719 additions and 3714 deletions

View File

@@ -74,8 +74,6 @@ 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
@@ -174,7 +172,7 @@ If you've trouble to make viewcvs.cgi work:
o check the ViewCVS home page:
http://viewcvs.sourceforge.net/
http://www.lyra.org/viewcvs/
o review the ViewCVS mailing list archive to see if somebody else had
the same problem, and it was solved:

25
TODO
View File

@@ -14,26 +14,25 @@ TODO ITEMS
*) committing with a specific revision number:
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
*) add an "allowed" modules. process the forbidden modules first. any
match will throw it out immediately. if an allowed is present, then
disallow any module unless it is present in the allowed list.
*) provide a virtual host capability. consider multiple hosts which
map a particular Location to the same directory (i.e. the same
ViewCVS script and config file). provide for a way to have global
options plus vhost-specific options.
*) 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
----------
*) 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
keyword, then the diff output chokes.

View File

@@ -1,22 +1,22 @@
#!/usr/bin/python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
# CGI script to process and display queries to CVSdb
#
# This script is part of the ViewCVS package. More information can be
# found at http://viewcvs.sourceforge.net/.
# found at http://www.lyra.org/viewcvs/.
#
# -----------------------------------------------------------------------
#
@@ -43,164 +43,49 @@ else:
#########################################################################
import os
import string
import cgi
import time
import cvsdbapi
import os, string, cgi, time, 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 HTMLHeader():
print "Content-type: text/html"
print
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)
query.SetRepository(temp)
if form.has_key("branch"):
temp = form["branch"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetBranch(str, cmd)
query.SetBranch(temp)
if form.has_key("directory"):
temp = form["directory"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetDirectory(str, cmd)
query.SetDirectory(temp)
if form.has_key("file"):
temp = form["file"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetFile(str, cmd)
query.SetFile(temp)
if form.has_key("who"):
temp = form["who"].value
for cmd, str in listparse_string(temp):
cmd = decode_command(cmd)
query.SetAuthor(str, cmd)
query.SetAuthor(temp)
if form.has_key("sortby"):
temp = form["sortby"].value
if temp == "date":
query.SetSortMethod("date")
query.SetSortMethod(query.SORT_DATE)
elif temp == "author":
query.SetSortMethod("author")
query.SetSortMethod(query.SORT_AUTHOR)
else:
query.SetSortMethod("file")
query.SetSortMethod(query.SORT_FILE)
if form.has_key("date"):
temp = form["date"].value
@@ -297,7 +182,7 @@ class HTMLTemplate:
def Main():
print "Content-type: text/html\n\n"
HTMLHeader()
template_path = os.path.join(HTML_TEMPLATE_DIR, "querytemplate.html")
template = HTMLTemplate(template_path)

View File

@@ -1,15 +1,15 @@
#!/usr/bin/python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
# Configuration file for ViewCVS
#
# Information on ViewCVS is located at the following web site:
# http://viewcvs.sourceforge.net/
# http://www.lyra.org/viewcvs/
#
#---------------------------------------------------------------------------
@@ -240,7 +240,7 @@ long_intro =
</p>
<p>
This script
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
@@ -252,7 +252,7 @@ long_intro =
Licence</a>.
If you would like to use this CGI script on your own web server and
CVS tree, see Greg's
<a href="http://viewcvs.sourceforge.net/">ViewCVS distribution
<a href="http://www.lyra.org/viewcvs/">ViewCVS distribution
site</a>.
Please send any suggestions, comments, etc. to
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
@@ -262,9 +262,9 @@ 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://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://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet
another CVS tutorial (a little old, but nice)</a><br>
@@ -434,12 +434,6 @@ enscript_path =
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

View File

@@ -1,16 +1,16 @@
#!/usr/local/bin/python
# -*-python-*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 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.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -21,7 +21,7 @@
#
# This software is being maintained as part of the ViewCVS project.
# Information is available at:
# http://viewcvs.sourceforge.net/
# http://www.lyra.org/viewcvs/
#
# This file is based on the cvsblame.pl portion of the Bonsai CVS tool,
# developed by Steve Lamm for Netscape Communications Corporation. More
@@ -40,10 +40,17 @@ import re
import time
import math
import cgi
import rcsparse
class CVSParser(rcsparse.Sink):
path_sep = os.path.normpath('/')[-1]
class CVSParser:
# Precompiled regular expressions
nonws_token = re.compile('^([^;@][^;\\s]*)\\s*')
semic_token = re.compile('^;\\s*')
rcsen_token = re.compile('^@([^@]*)')
undo_escape = re.compile('@@')
single_at = re.compile('([^@]|^)@([^@]|$)')
rcs_tree = re.compile('^\\d')
trunk_rev = re.compile('^[0-9]+\\.[0-9]+$')
last_branch = re.compile('(.*)\\.[0-9]+')
is_branch = re.compile('(.*)\\.0\\.([0-9]+)')
@@ -56,6 +63,9 @@ class CVSParser(rcsparse.Sink):
self.Reset()
def Reset(self):
self.line_buffer = ''
self.rcsfile = None
self.debug = 0
self.last_revision = {}
self.prev_revision = {}
self.revision_date = {}
@@ -63,6 +73,7 @@ class CVSParser(rcsparse.Sink):
self.revision_branches = {}
self.next_delta = {}
self.prev_delta = {}
self.feof = 0
self.tag_revision = {}
self.revision_symbolic_name = {}
self.timestamp = {}
@@ -74,6 +85,69 @@ class CVSParser(rcsparse.Sink):
self.lines_added = {}
self.lines_removed = {}
# Get the next token from the RCS file
def get_token(self):
# Erase all-whitespace lines
while len(self.line_buffer) == 0:
self.line_buffer = self.rcsfile.readline()
if self.line_buffer == '':
raise RuntimeError, 'EOF'
self.line_buffer = string.lstrip(self.line_buffer)
# A string of non-whitespace characters is a token
match = self.nonws_token.match(self.line_buffer)
if match:
self.line_buffer = self.nonws_token.sub('', self.line_buffer)
return match.group(1)
# ...and so is a single semicolon
if self.semic_token.match(self.line_buffer):
self.line_buffer = self.semic_token.sub('', self.line_buffer)
return ';'
# ...or an RCS-encoded string that starts with an @ character
match = self.rcsen_token.match(self.line_buffer)
self.line_buffer = self.rcsen_token.sub('', self.line_buffer)
token = match.group(1)
# Detect single @ character used to close RCS-encoded string
while string.find(self.line_buffer, '@') < 0 or not self.single_at.search(self.line_buffer):
token = token + self.line_buffer
self.line_buffer = self.rcsfile.readline()
if self.line_buffer == '':
raise RuntimeError, 'EOF'
# Retain the remainder of the line after the terminating @ character
i = string.rindex(self.line_buffer, '@')
token = token + self.line_buffer[:i]
self.line_buffer = self.line_buffer[i+1:]
# Undo escape-coding of @ characters.
token = self.undo_escape.sub('@', token)
# Digest any extra blank lines
while len(self.line_buffer) == 0 or self.line_buffer == '\n':
self.line_buffer = self.rcsfile.readline()
if self.line_buffer == '':
self.feof = 1
break
if token[-1:] == '\n':
token = token[:-1]
return token
# Try to match the next token from the input buffer
def match_token(self, match):
token = self.get_token()
if token != match:
raise RuntimeError, ('Unexpected parsing error in RCS file.\n' +
'Expected token: %s, but saw: %s' % (match, token))
# Push RCS token back into the input buffer.
def unget_token(self, token):
self.line_buffer = token + " " + self.line_buffer
# 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.
@@ -108,13 +182,6 @@ class CVSParser(rcsparse.Sink):
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):
@@ -128,12 +195,12 @@ class CVSParser(rcsparse.Sink):
path.reverse()
path = path[1:] # Get rid of head revision
text = self.deltatext_split(self.head_revision)
text = string.split(self.revision_deltatext[self.head_revision], '\n')
# Iterate, applying deltas to previous revision
for revision in path:
adjust = 0
diffs = self.deltatext_split(revision)
diffs = string.split(self.revision_deltatext[revision], '\n')
self.lines_added[revision] = 0
self.lines_removed[revision] = 0
lines_added_now = 0
@@ -168,25 +235,49 @@ class CVSParser(rcsparse.Sink):
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
return text
def set_head_revision(self, revision):
self.head_revision = revision
def parse_rcs_admin(self):
while 1:
# Read initial token at beginning of line
token = self.get_token()
def set_principal_branch(self, branch_name):
self.principal_branch = branch_name
# We're done once we reach the description of the RCS tree
if self.rcs_tree.match(token):
self.unget_token(token)
return
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
# print "token:", token
### actually, this is a bit bogus... a rev can have multiple names
self.revision_symbolic_name[revision] = name
if token == "head":
self.head_revision = self.get_token()
self.get_token() # Eat semicolon
elif token == "branch":
self.principal_branch = self.get_token()
self.get_token() # Eat semicolon
elif token == "symbols":
# Create an associate array that maps from tag name to
# revision number and vice-versa.
while 1:
tag = self.get_token()
if tag == ';':
break
(tag_name, tag_rev) = string.split(tag, ':')
self.tag_revision[tag_name] = tag_rev
self.revision_symbolic_name[tag_rev] = tag_name
elif token == "comment":
self.file_description = self.get_token()
self.get_token() # Eat semicolon
def set_comment(self, comment):
self.file_description = comment
# Ignore all these other fields - We don't care about them.
elif token in ("locks", "strict", "expand", "access"):
while 1:
tag = self.get_token()
if tag == ';':
break
else:
pass
# warn("Unexpected RCS token: $token\n")
def set_description(self, description):
self.rcs_file_description = description
raise RuntimeError, "Unexpected EOF";
# Construct dicts that represent the topology of the RCS tree
# and other arrays that contain info about individual revisions.
@@ -210,47 +301,91 @@ class CVSParser(rcsparse.Sink):
# 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
def parse_rcs_tree(self):
while 1:
revision = self.get_token()
# 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
# End of RCS tree description ?
if revision == 'desc':
self.unget_token(revision)
return
# 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
self.tag_revision[revision] = revision
branch = self.last_branch.match(revision).group(1)
self.last_revision[branch] = revision
# Parse date
self.match_token('date')
date = self.get_token()
self.revision_date[revision] = date
self.match_token(';')
# 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
self.timestamp[revision] = time.mktime(date_fields)
# 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)
# Parse author
self.match_token('author')
author = self.get_token()
self.revision_author[revision] = author
self.match_token(';')
# Parse state
self.match_token('state')
while self.get_token() != ';':
pass
# Parse branches
self.match_token('branches')
branches = ''
while 1:
token = self.get_token()
if token == ';':
break
self.prev_revision[token] = revision
self.prev_delta[token] = revision
branches = branches + token + ' '
self.revision_branches[revision] = branches
# Parse revision of next delta in chain
self.match_token('next')
next = ''
token = self.get_token()
if token != ';':
next = token
self.get_token() # Eat semicolon
self.next_delta[revision] = next
self.prev_delta[next] = revision
if is_trunk_revision:
self.prev_revision[revision] = next
else:
self.prev_revision[next] = revision
if self.debug >= 3:
print "<pre>revision =", revision
print "date = ", date
print "author = ", author
print "branches = ", branches
print "next = ", next + "</pre>\n"
def parse_rcs_description(self):
self.match_token('desc')
self.rcs_file_description = self.get_token()
# Construct associative arrays containing info about individual revisions.
#
@@ -263,9 +398,36 @@ class CVSParser(rcsparse.Sink):
# 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_rcs_deltatext(self):
while not self.feof:
revision = self.get_token()
if self.debug >= 3:
print "Reading delta for revision:", revision
self.match_token('log')
self.revision_log[revision] = self.get_token()
self.match_token('text')
self.revision_deltatext[revision] = self.get_token()
def parse_rcs_file(self):
if self.debug >= 2:
print "Reading RCS admin..."
self.parse_rcs_admin()
if self.debug >= 2:
print "Reading RCS revision tree topology..."
self.parse_rcs_tree()
if self.debug >= 3:
print "<pre>Keys:\n"
for i in self.tag_revision.keys():
print "yoyuo %s: %s" % (i, self.tag_revision[i])
print "</pre>"
self.parse_rcs_description()
if self.debug >= 2:
print "Reading RCS revision deltas..."
self.parse_rcs_deltatext()
if self.debug >= 2:
print "Done reading RCS file..."
def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
# Args in: opt_rev - requested revision
@@ -276,13 +438,13 @@ class CVSParser(rcsparse.Sink):
# CheckHidden(rcs_pathname);
try:
rcsfile = open(rcs_pathname, 'r')
self.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()
self.parse_rcs_file()
self.rcsfile.close()
if opt_rev in [None, '', 'HEAD']:
# Explicitly specified topmost revision in tree
@@ -308,14 +470,14 @@ class CVSParser(rcsparse.Sink):
# first revision.
line_count = 0
if self.revision_deltatext.get(self.head_revision):
tmp_array = self.deltatext_split(self.head_revision)
tmp_array = string.split(self.revision_deltatext[self.head_revision], '\n')
line_count = len(tmp_array)
skip = 0
rev = self.prev_revision.get(self.head_revision)
while rev:
diffs = self.deltatext_split(rev)
diffs = string.split(self.revision_deltatext[rev], '\n')
for command in diffs:
dmatch = self.d_command.match(command)
amatch = self.a_command.match(command)
@@ -360,7 +522,7 @@ class CVSParser(rcsparse.Sink):
is_trunk_revision = self.trunk_rev.match(revision) is not None
if is_trunk_revision:
diffs = self.deltatext_split(last_revision)
diffs = string.split(self.revision_deltatext[last_revision], '\n')
# Revisions on the trunk specify deltas that transform a
# revision into an earlier revision, so invert the translation
@@ -393,7 +555,7 @@ class CVSParser(rcsparse.Sink):
# the trunk. They specify deltas that transform a revision
# into a later revision.
adjust = 0
diffs = self.deltatext_split(revision)
diffs = string.split(self.revision_deltatext[revision], '\n')
for command in diffs:
if skip > 0:
skip = skip - 1
@@ -427,24 +589,21 @@ class CVSParser(rcsparse.Sink):
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
re_filename = re.compile('(.*[\\\\/])?(.+)')
def link_includes(text, root, rcs_path, sticky = None):
def link_includes(text, root, rcs_path):
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'))
file = os.path.normpath('%s%s%s%s%s,v' % (root, path_sep, trial_root, path_sep, incfile))
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)
(match.group(1), match.group(2),
os.path.join(rel_path, incfile), incfile)
return text
def make_html(root, rcs_path, opt_rev = None, sticky = None):
filename = os.path.join(root, rcs_path)
def make_html(root, rcs_path, opt_rev = None):
filename = root + path_sep + rcs_path
parser = CVSParser()
revision = parser.parse_cvs_file(filename, opt_rev)
count = len(parser.revision_map)
@@ -490,7 +649,7 @@ def make_html(root, rcs_path, opt_rev = None, sticky = None):
# Add a link to traverse to included files
if 1: # opt_includes
thisline = link_includes(thisline, root, file_head, sticky)
thisline = link_includes(thisline, root, file_head)
output = ''
@@ -526,11 +685,9 @@ def make_html(root, rcs_path, opt_rev = None, sticky = None):
if parser.prev_revision.get(revision):
fname = file_tail[:-2] # strip the ",v"
url = '%s.diff?r1=%s&amp;r2=%s' % \
(fname, parser.prev_revision[revision], revision)
if sticky:
url = url + '&' + sticky
output = output + ' <a href="%s"' % (url, )
### need the sticky options! need cvsroot if not-default
output = output + ' <a href="%s.diff?r1=%s&amp;r2=%s"' % \
(fname, parser.prev_revision[revision], revision)
if 0: # use_layers
output = output + " onmouseover='return log(event,\"%s\",\"%s\");'" % (
parser.prev_revision[revision], revision)

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -38,22 +38,21 @@ except AttributeError:
keyvalue = [ ]
for key, value in dict.items():
keyvalue.append(quote(key) + '=' + quote(str(value)))
return string.join(keyvalue, '&')
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)
return tuple(map(int, matches)) + (0, 1, -1)
cvs_strptime.__doc__ = 'Parse a CVS-style date/time value.'
#
# os.makedirs() is new to Python 1.5.2

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -189,7 +189,6 @@ class Config:
self.options.use_enscript = 0
self.options.enscript_path = ''
self.options.disable_enscript_lang = ()
self.options.allow_tar = 0
self.text.long_intro = """\
<p>
@@ -206,7 +205,7 @@ class Config:
</p>
<p>
This script
(<a href="http://viewcvs.sourceforge.net/">ViewCVS</a>)
(<a href="http://www.lyra.org/viewcvs/">ViewCVS</a>)
has been written by Greg Stein
&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
based on the
@@ -217,7 +216,7 @@ class Config:
<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
<a href="http://www.lyra.org/viewcvs/">ViewCVS distribution
site</a>.
Please send any suggestions, comments, etc. to
<a href="mailto:gstein@lyra.org">Greg Stein</a>.
@@ -229,9 +228,9 @@ class Config:
<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://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://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>
@@ -273,10 +272,3 @@ class _sub_config:
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
+ ']*=\)\(.*\)$')

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -25,20 +25,14 @@ CONF_PATHNAME = None
#########################################################################
import os
import database
import query
import rlog
import commit
import config
import os, database, rlog, commit, config
## error
error = 'cvsdbapi error'
## database
CreateCheckinDatabase = database.CreateCheckinDatabase
CreateCheckinQuery = query.CreateCheckinQuery
CreateCheckinQuery = database.CreateCheckinQuery
## rlog
GetRLogData = rlog.GetRLogData

View File

@@ -1,24 +1,25 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
import os
import sys
import string
import time
import os, sys, string, time
## imports from the database API; we re-assign the namespace here so it
## is easier to switch databases
import MySQLdb
DBI = MySQLdb
import dbi
from commit import CreateCommit, PrintCommit
@@ -26,19 +27,17 @@ from commit import CreateCommit, PrintCommit
## 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")'
sqlRepository = '(checkins.repositoryid=repositories.id AND repositories.repository="%s")'
sqlBranch = '(checkins.branchid=branches.id AND branches.branch="%s")'
sqlDirectory = '(checkins.dirid=dirs.id AND dirs.dir LIKE "%s%%")'
sqlFile = '(checkins.fileid=files.id AND files.file="%s")'
sqlFromDate ='(checkins.ci_when>="%s")'
sqlToDate = '(checkins.ci_when<="%s")'
sqlAuthor = '(checkins.whoid=people.id AND people.who="%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")'
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
@@ -59,8 +58,7 @@ class CheckinDatabase:
self.dbDescriptionIDCache = {}
def Connect(self):
self.dbConn = dbi.connect(
self.dbHost, self.dbUser, self.dbPasswd, self.dbDatabase)
self.dbConn = self.SQLConnect()
def SQLGetID(self, table, field, identifier, auto_set):
sql = 'SELECT id FROM %s x WHERE x.%s="%s"' % (
@@ -241,7 +239,7 @@ class CheckinDatabase:
## module to do the conversion
temp = time.localtime(commit.GetTime())
dbCI_When = dbi.Timestamp(
dbCI_When = DBI.Timestamp(
temp[0], temp[1], temp[2], temp[3], temp[4], temp[5])
dbWhoID = self.GetAuthorID(commit.GetAuthor())
@@ -264,73 +262,45 @@ class CheckinDatabase:
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")
tableList.append('files')
condList.append(sqlExcludeVersionFiles)
if query.repository:
tableList.append('repositories')
condList.append(sqlRepository % (query.repository))
if len(query.repository_list):
tableList.append("repositories")
condList.append(
self.SQLQueryListString(sqlRepository, query.repository_list))
if query.branch:
tableList.append('branches')
condList.append(sqlBranch % (query.branch))
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
if query.author:
tableList.append('people')
condList.append(sqlAuthor % (query.author))
## exclude duplicates from the table list
for table in tableList[:]:
while tableList.count(table) > 1:
tableList.remove(table)
if query.directory:
tableList.append('dirs')
condList.append(sqlDirectory % (query.directory))
if query.file:
#tableList.append('files')
condList.append(sqlFile % (query.file))
if query.sort == query.SORT_DATE:
order_by = sqlSortByDate
elif query.sort == query.SORT_AUTHOR:
order_by = sqlSortByAuthor
elif query.sort == query.SORT_FILE:
order_by = sqlSortByFile
sql = sqlBase % (
string.join(tableList, ', '),
@@ -401,8 +371,106 @@ class CheckinDatabase:
return None
return commit
class MySQLCheckinDatabase(CheckinDatabase):
def SQLConnect(self):
return MySQLdb.connect(
host = self.dbHost,
user = self.dbUser,
passwd = self.dbPasswd,
db = self.dbDatabase)
## CheckinDatabaseQueryData is a object which contains the search parameters
## for a query to the CheckinDatabase
class CheckinDatabaseQuery:
SORT_DATE = 0
SORT_AUTHOR = 1
SORT_FILE = 2
SORT_CHANGESIZE = 3
def __init__(self):
## repository to query
self.repository = None
## branch
self.branch = None
## directory to seach
self.directory = None
## file to search for
self.file = None
## sorting method
self.sort = CheckinDatabaseQuery.SORT_DATE;
## author to search for
self.author = None
## 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):
self.repository = repository
def SetBranch(self, branch):
self.branch = branch
def SetDirectory(self, directory):
self.directory = directory
def SetFile(self, file):
self.file = file
def SetSortMethod(self, sort):
self.sort = sort
def SetAuthor(self, author):
self.author = author
def SetFromDateObject(self, date):
self.from_date = date
def SetToDateObject(self, date):
self.to_date = date
def SetFromDateHoursAgo(self, hours_ago):
ticks = time.time() - (3600 * hours_ago)
self.from_date = DBI.TimestampFromTicks(ticks)
def SetFromDateDaysAgo(self, days_ago):
ticks = time.time() - (86400 * days_ago)
self.from_date = DBI.TimestampFromTicks(ticks)
def SetToDateDaysAgo(self, days_ago):
ticks = time.time() - (86400 * days_ago)
self.to_date = DBI.TimestampFromTicks(ticks)
def AddCommit(self, commit):
self.commit_list.append(commit)
if self.commit_cb:
self.commit_cb(commit)
def SetCommitCB(self, callback):
self.commit_cb = callback
## entrypoints
def CreateCheckinDatabase(host, user, passwd, database):
return CheckinDatabase(host, user, passwd, database)
return MySQLCheckinDatabase(host, user, passwd, database)
def CreateCheckinQuery():
return CheckinDatabaseQuery()

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

View File

@@ -1,13 +1,13 @@
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -22,13 +22,8 @@
#
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:
@@ -50,7 +45,6 @@ def popen(cmd, args, mode, capture_err=1):
# 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:
@@ -61,7 +55,6 @@ def popen(cmd, args, mode, capture_err=1):
# 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)
@@ -71,91 +64,11 @@ def popen(cmd, args, mode, capture_err=1):
os.close(w)
# the stdin/stdout/stderr are all set up. exec the target
try:
os.execvp(cmd, (cmd,) + tuple(args))
except:
pass
os.execvp(cmd, (cmd,) + tuple(args))
# 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."
@@ -164,20 +77,10 @@ class _pipe:
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
self.file.close()
self.file = None
return os.waitpid(self.child_pid, 0)[1] or None
def __getattr__(self, name):
return getattr(self.file, name)

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

View File

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

View File

@@ -1,14 +1,14 @@
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -245,9 +245,7 @@ class RLogOutputParser:
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:
if not line:
return None
## revision

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

View File

@@ -1,15 +1,15 @@
#!/usr/bin/python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#

View File

@@ -1,14 +1,14 @@
#!/bin/sh
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -18,22 +18,22 @@
#
if [ "x$1" = "x" ]; then
echo "USAGE: $0 tagname target-directory"
echo "USAGE: $0 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 (export requires a tag, so just use 'co')
rm -r $1
echo 'Checking out into:' $1
cvs -d /home/cvsroot co -d $1 -kv -P -R viewcvs
# 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
echo 'Cleaning up...'
# clean out the CVS crap
find $1 -name CVS | xargs rm -r
# various shifting, cleanup
mv $2/website/license-1.html $2/LICENSE.html
rm -r $2/website
rm $2/tools/make-release
mv $1/website/license-1.html $1/LICENSE.html
rm -r $1/website
rm $1/tools/make-release
echo 'Done.'

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 2000-2001 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2000 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://viewcvs.sourceforge.net/license-1.html.
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# Contact information:
# Greg Stein, PO Box 760, Palo Alto, CA, 94302
# gstein@lyra.org, http://viewcvs.sourceforge.net/
# gstein@lyra.org, http://www.lyra.org/viewcvs/
#
# -----------------------------------------------------------------------
#
@@ -44,7 +44,7 @@ is okay.
"""
## installer defaults
ROOT_DIR = "/usr/local/viewcvs-dev"
ROOT_DIR = "/usr/local/viewcvs"
## list of files for installation
@@ -66,13 +66,9 @@ FILE_INFO_LIST = [
("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),
@@ -97,11 +93,11 @@ def Error(text, etype=None, evalue=None):
def MkDir(path):
try:
compat.makedirs(path)
except os.error, e:
if e[0] == 17:
except OSError, e:
if e.errno == 17:
# EEXIST: file exists
return
if e[0] == 13:
if e.errno == 13:
# EACCES: permission denied
Error("You do not have permission to create directory %s" % path)
Error("Unknown error creating directory %s" % path, OSError, e)
@@ -147,7 +143,7 @@ def InstallFile(src_path, dest_path, mode, set_python_paths, prompt_replace,
try:
open(dest_path, "w").write(contents)
except IOError, e:
if e[0] == 13:
if e.errno == 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

View File

@@ -5,33 +5,16 @@
</head>
<body background="/images/chalk.jpg">
<table width="100&#37;" cellspacing=5>
<tr>
<td>
<h1>ViewCVS: Viewing CVS Repositories</h1>
</td>
<td align=center valign=top bgcolor="white" width="1%">
<b>Quickstart:</b>
<a href="viewcvs-0.6.tar.gz">download</a>
</td>
<td width="1%"><a href="http://sourceforge.net/"><img border=0
src="http://sourceforge.net/sflogo.php?group_id=18760&type=1"></a><br><a href="http://sourceforge.net/projects/viewcvs/">ViewCVS&nbsp;project&nbsp;page</a>
</td>
</tr>
</table>
<h1>ViewCVS: Viewing CVS Repositories</h1>
<p>
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>).
I wanted to make some changes and updates, but cvsweb was
implemented in Perl. While I can manage some Perl, cvsweb was
rather unmaintainable for me. So I undertook the
implemented in Perl (and rather poorly, IMO). So I undertook the
task to convert the software to
<a href="http://www.python.org/"><i>Python</i></a>. As a result,
I've actually been able to go <em>way</em> beyond the simple
changes that I had envisioned.
<a href="http://www.python.org/"><i>Python</i></a>.
</p>
<p>
ViewCVS can browse directories, change logs, and specific
@@ -41,39 +24,35 @@
of Bonsai-like query facilities.
</p>
<p>
ViewCVS is currently at version 0.6. It was a port of the cvsweb
ViewCVS is currently at version 0.5. It was a port of the cvsweb
script, but has had numerous cleanups and other modifications,
based on some of Python's strengths. There is still some minor
"badness" remaining from the Perl code, but I've been working on
flushing that out, while adding new features. Currently, the
functionality of ViewCVS surpasses that of cvsweb.
"badness" in there, but I've been working on flushing that out,
while adding new features. Currently, the functionality of ViewCVS
surpasses that of cvsweb.
</p>
<p>
The software is available for download:
</p>
<blockquote>
<a href="viewcvs-0.6.tar.gz">Version 0.6 of ViewCVS</a>
<a href="viewcvs-0.5.tar.gz">Version 0.5 of ViewCVS</a>
</blockquote>
<p>
Of course, it is also available through ViewCVS itself:
</p>
<blockquote>
<a href="http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/">http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/viewcvs/</a>
<a href="/cgi-bin/viewcvs.cgi/viewcvs/">http://www.lyra.org/cgi-bin/viewcvs.cgi/viewcvs/</a>
</blockquote>
<p>
ViewCVS requires <strong>Python 1.5</strong> (which has been out
for a couple years and is readily available for your favorite
operating system). If you choose to use the SQL Checkin Database
feature, then you must use <strong>Python 1.5.2</strong> and the
<a href="http://dustman.net/andy/python/MySQLdb"><i>MySQLdb
module</i></a> installed.
operating system).
</p>
<p>
ViewCVS has been developed by the <a href="who.html">ViewCVS
Group</a> and is made available under a
<a href="license-1.html">BSD-type license</a>.
[ <a href="/greg/">Greg's page</a> ]
[ <a href="/greg/python/">other Python software</a> ]
</p>
<hr width="75&#37;">
@@ -93,32 +72,8 @@
<hr width="75&#37;">
<h2>Additional features over cvsweb</h2>
<p>
<strong>New to version 0.5:</strong>
</p>
<ul>
<li>
Colorization for many file types via <code>enscript</code>.
</li>
<li>
Bonsai-like query features.
</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>Colorization for many file types via <code>enscript</code>.</li>
<li>Better reporting for unreadable files.</li>
<li>
More robust when given varying <code>rcsdiff</code> or
@@ -134,15 +89,16 @@
<li>
Directories with a large number of files can be viewed.
</li>
<li>Bonsai-like query features.</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>
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>
@@ -205,7 +161,7 @@
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
<!-- hhmts start -->
Last modified: Fri May 11 14:22:36 PDT 2001
Last modified: Fri May 12 03:53:51 PDT 2000
<!-- hhmts end -->
</body>
</html>

View File

@@ -14,20 +14,12 @@
<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 &copy; 1999-2001 The ViewCVS Group. All rights reserved.
Copyright &copy; 1999-2000 The ViewCVS Group. All rights reserved.
</b>
</p>
<p>
@@ -77,7 +69,7 @@
<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
Last modified: Mon May 8 19:28:37 PDT 2000
<!-- hhmts end -->
</body>
</html>

View File

@@ -12,7 +12,7 @@
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><a href="/greg/"><b>Greg Stein</b></a></li>
<li>Jay Painter</li>
</ul>
@@ -26,7 +26,7 @@
<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
Last modified: Mon May 8 19:14:45 PDT 2000
<!-- hhmts end -->
</body>
</html>