mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
99216ae1da |
2
INSTALL
2
INSTALL
@@ -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
|
||||
|
18
TODO
18
TODO
@@ -14,19 +14,19 @@ 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
|
||||
|
||||
|
||||
KNOWN BUGS
|
||||
----------
|
||||
|
143
cgi/query.cgi
143
cgi/query.cgi
@@ -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)
|
||||
|
@@ -21,12 +21,12 @@
|
||||
# derived from software by Bill Fenner, with additional modifications by
|
||||
# Henrik Nordstrom and Ken Coar). The cvsweb distribution can be found
|
||||
# on Zeller's site:
|
||||
# http://stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/
|
||||
# http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
__version__ = '0.6'
|
||||
__version__ = '0.5'
|
||||
|
||||
#########################################################################
|
||||
#
|
||||
@@ -69,7 +69,7 @@ import popen
|
||||
|
||||
#########################################################################
|
||||
|
||||
checkout_magic_path = '~checkout~'
|
||||
checkout_magic_path = '~checkout~/'
|
||||
viewcvs_mime_type = 'text/vnd.viewcvs-markup'
|
||||
|
||||
# put here the variables we need in order to hold our state - they will be
|
||||
@@ -113,17 +113,15 @@ class Request:
|
||||
# clean it up. this removes duplicate '/' characters and any that may
|
||||
# exist at the front or end of the path.
|
||||
parts = filter(None, string.split(where, '/'))
|
||||
where = string.join(parts, '/')
|
||||
|
||||
# does it have the magic checkout prefix?
|
||||
if parts and parts[0] == checkout_magic_path:
|
||||
if where[:len(checkout_magic_path)] == checkout_magic_path:
|
||||
self.has_checkout_magic = 1
|
||||
del parts[0]
|
||||
where = where[len(checkout_magic_path):]
|
||||
else:
|
||||
self.has_checkout_magic = 0
|
||||
|
||||
# put it back together
|
||||
where = string.join(parts, '/')
|
||||
|
||||
script_name = os.environ['SCRIPT_NAME'] ### clean this up?
|
||||
if where:
|
||||
url = script_name + '/' + urllib.quote(where)
|
||||
@@ -276,7 +274,7 @@ def toggle_query(query_dict, which, value=None):
|
||||
return '?' + query
|
||||
return ''
|
||||
|
||||
def clickable_path(request, path, leaf_is_link, leaf_is_file, drop_leaf):
|
||||
def clickable_path(request, path, leaf_is_link, drop_leaf):
|
||||
if path == '/':
|
||||
# this should never happen - chooseCVSRoot() is
|
||||
# intended to do this
|
||||
@@ -288,19 +286,13 @@ def clickable_path(request, path, leaf_is_link, leaf_is_file, drop_leaf):
|
||||
if drop_leaf:
|
||||
del parts[-1]
|
||||
leaf_is_link = 1
|
||||
leaf_is_file = 0
|
||||
where = ''
|
||||
for i in range(len(parts)):
|
||||
where = where + '/' + parts[i]
|
||||
is_leaf = i == len(parts) - 1
|
||||
if not is_leaf or leaf_is_link:
|
||||
if is_leaf and leaf_is_file:
|
||||
slash = ''
|
||||
else:
|
||||
slash = '/'
|
||||
if i < len(parts) - 1 or leaf_is_link:
|
||||
### should we be encoding/quoting the URL stuff? (probably...)
|
||||
s = s + ' / <a href="%s%s%s%s#dirlist">%s</a>' % \
|
||||
(request.script_name, where, slash, request.qmark_query, parts[i])
|
||||
s = s + ' / <a href="%s%s/%s#dirlist">%s</a>' % \
|
||||
(request.script_name, where, request.qmark_query, parts[i])
|
||||
else:
|
||||
s = s + ' / ' + parts[i]
|
||||
|
||||
@@ -342,7 +334,7 @@ def html_log(log):
|
||||
|
||||
def download_url(request, url, revision, mime_type):
|
||||
if cfg.options.checkout_magic and mime_type != viewcvs_mime_type:
|
||||
url = '%s/%s/%s/%s' % \
|
||||
url = '%s/%s%s/%s' % \
|
||||
(request.script_name, checkout_magic_path,
|
||||
os.path.dirname(request.where), url)
|
||||
|
||||
@@ -455,7 +447,7 @@ def navigate_header(request, swhere, path, filename, rev, title):
|
||||
'%s%s#rev%s' % (swhere, request.qmark_query, rev)),
|
||||
html_icon('file'))
|
||||
print '<td align=right>%s <b>Up to %s</b></td>' % \
|
||||
(html_icon('dir'), clickable_path(request, path, 1, 0, 0))
|
||||
(html_icon('dir'), clickable_path(request, path, 1, 0))
|
||||
print '</tr></table>'
|
||||
|
||||
def markup_stream_default(fp):
|
||||
@@ -492,20 +484,15 @@ def markup_stream_python(fp):
|
||||
|
||||
def markup_stream_enscript(lang, fp):
|
||||
sys.stdout.flush()
|
||||
enscript = popen.pipe_cmds([(cfg.options.enscript_path + 'enscript',
|
||||
'--color', '-W', 'html', '-E' + lang, '-o',
|
||||
'-', '-'),
|
||||
('sed', '-n', '/^<PRE>$/,/<\\/PRE>$/p')])
|
||||
|
||||
cmd = "%senscript --color -W html -E%s -o - - 2> /dev/null " \
|
||||
"| sed -n '/^<PRE>$/,/<\\/PRE>$/p'" % \
|
||||
(cfg.options.enscript_path, lang,)
|
||||
enscript = os.popen(cmd, "w")
|
||||
while 1:
|
||||
chunk = fp.read(CHUNK_SIZE)
|
||||
if not chunk:
|
||||
if fp.eof() is None:
|
||||
time.sleep(1)
|
||||
continue
|
||||
break
|
||||
enscript.write(chunk)
|
||||
|
||||
enscript.close()
|
||||
|
||||
markup_streamers = {
|
||||
@@ -588,7 +575,7 @@ def markup_stream(request, fp, revision, mime_type):
|
||||
navigate_header(request, request.url, pathname, filename, revision, 'view')
|
||||
print '<hr noshade>'
|
||||
print '<table width="100%"><tr><td bgcolor="%s">' % cfg.colors.markup_log
|
||||
print 'File:', clickable_path(request, where, 1, 1, 0), '</b>'
|
||||
print 'File:', clickable_path(request, where, 1, 0), '</b>'
|
||||
download_link(request, file_url, revision, '(download)')
|
||||
if not request.default_text_plain:
|
||||
download_link(request, file_url, revision, '(as text)', 'text/plain')
|
||||
@@ -625,9 +612,6 @@ def markup_stream(request, fp, revision, mime_type):
|
||||
markup_stream_enscript(lang, fp)
|
||||
else:
|
||||
markup_stream_default(fp)
|
||||
status = fp.close()
|
||||
if status:
|
||||
raise error, 'pipe error status: %d' % status
|
||||
html_footer()
|
||||
|
||||
def get_file_data(full_name):
|
||||
@@ -940,7 +924,7 @@ def process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags):
|
||||
def get_logs(full_name, files, view_tag):
|
||||
|
||||
if len(files) == 0:
|
||||
return { }, [ ]
|
||||
return { }, { }
|
||||
|
||||
fileinfo = { }
|
||||
alltags = { # all the tags seen in the files of this dir
|
||||
@@ -977,9 +961,9 @@ def get_logs(full_name, files, view_tag):
|
||||
###
|
||||
### more work for later...
|
||||
|
||||
status = rlog.close()
|
||||
if status:
|
||||
raise 'error during rlog: '+hex(status)
|
||||
if rlog.close():
|
||||
### what to do?
|
||||
pass
|
||||
|
||||
return fileinfo, alltags.keys()
|
||||
|
||||
@@ -1024,7 +1008,7 @@ def view_directory(request):
|
||||
if not hideattic:
|
||||
try:
|
||||
attic_files = os.listdir(full_name + '/Attic')
|
||||
except os.error:
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
### filter for just RCS files?
|
||||
@@ -1062,8 +1046,7 @@ def view_directory(request):
|
||||
#choose_cvsroot()
|
||||
pass
|
||||
else:
|
||||
print '<p>Current directory: <b>', \
|
||||
clickable_path(request, where, 0, 0, 0), '</b>'
|
||||
print '<p>Current directory: <b>', clickable_path(request, where, 0, 0), '</b>'
|
||||
if view_tag:
|
||||
print '<p>Current tag: <b>', view_tag, '</b>'
|
||||
|
||||
@@ -1275,7 +1258,7 @@ def view_directory(request):
|
||||
print '</table>'
|
||||
|
||||
if num_files and not num_displayed:
|
||||
print '<p><b>NOTE:</b> There are %d files, but none match the current ' \
|
||||
print '<p><b>NOTE:</b> There are %d files, but none match the current' \
|
||||
'tag (%s)' % (num_files, view_tag)
|
||||
if unreadable:
|
||||
print '<hr size=1 noshade><b>NOTE:</b> One or more files were ' \
|
||||
@@ -1665,20 +1648,12 @@ def view_log(request):
|
||||
html_header('CVS log for %s' % where)
|
||||
|
||||
up_where = re.sub(_re_up_path, '', where)
|
||||
|
||||
### whoops. this sometimes/always? does not have the ",v"
|
||||
assert full_name[-2:] != ',v', 'please report this error to viewcvs@lyra.org'
|
||||
#filename = os.path.basename(full_name[:-2]) # drop the ",v"
|
||||
filename = os.path.basename(full_name)
|
||||
|
||||
### try: "./" + query + "#" + filename
|
||||
filename = os.path.basename(full_name[:-2]) # drop the ",v"
|
||||
back_url = request.script_name + '/' + urllib.quote(up_where) + \
|
||||
request.qmark_query
|
||||
|
||||
print html_link(html_icon('back'), back_url + '#' + filename)
|
||||
|
||||
### use drop_leaf
|
||||
print '<b>Up to %s</b><p>' % clickable_path(request, up_where, 1, 0, 0)
|
||||
print '<b>Up to %s</b><p>' % clickable_path(request, up_where, 1, 0)
|
||||
print '<a href="#diff">Request diff between arbitrary revisions</a>'
|
||||
print '<hr noshade>'
|
||||
|
||||
@@ -2247,9 +2222,9 @@ def main():
|
||||
|
||||
try:
|
||||
main()
|
||||
except SystemExit, e:
|
||||
# don't catch SystemExit (caused by sys.exit()). propagate the exit code
|
||||
sys.exit(e[0])
|
||||
except SystemExit:
|
||||
# don't stop on a SystemExit (caused by sys.exit())
|
||||
pass
|
||||
except:
|
||||
info = sys.exc_info()
|
||||
html_header('Python Exception Occurred')
|
||||
|
@@ -45,15 +45,14 @@ except AttributeError:
|
||||
#
|
||||
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
|
||||
|
@@ -272,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
|
||||
+ ']*=\)\(.*\)$')
|
||||
|
@@ -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
|
||||
|
214
lib/database.py
214
lib/database.py
@@ -13,12 +13,13 @@
|
||||
# -----------------------------------------------------------------------
|
||||
#
|
||||
|
||||
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()
|
||||
|
105
lib/popen.py
105
lib/popen.py
@@ -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)
|
||||
|
@@ -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,11 +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/rlog.py", "lib/rlog.py", 0644, 1, 0, 1),
|
||||
("lib/query.py", "lib/query.py", 0644, 1, 0, 1),
|
||||
|
||||
("tools/loginfo-handler", "loginfo-handler", 0755, 1, 0, 0),
|
||||
("tools/cvsdbadmin", "cvsdbadmin", 0755, 1, 0, 0),
|
||||
@@ -95,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)
|
||||
@@ -145,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)
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
<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 (and rather poorly, IMO). So I undertook the
|
||||
@@ -24,18 +24,18 @@
|
||||
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:
|
||||
@@ -47,16 +47,12 @@
|
||||
<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%">
|
||||
@@ -76,32 +72,8 @@
|
||||
<hr width="75%">
|
||||
<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
|
||||
@@ -117,6 +89,17 @@
|
||||
<li>
|
||||
Directories with a large number of files can be viewed.
|
||||
</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>
|
||||
@@ -174,19 +157,11 @@
|
||||
you want to use this feature.
|
||||
</p>
|
||||
|
||||
<hr width="75%">
|
||||
<h3>Other links</h3>
|
||||
|
||||
<p>
|
||||
[ <a href="/greg/">Greg's page</a> ]
|
||||
[ <a href="/greg/python/">other Python software</a> ]
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
<address><a href="mailto:gstein@lyra.org">Greg Stein</a></address>
|
||||
<!-- Created: Fri Dec 3 02:51:37 PST 1999 -->
|
||||
<!-- hhmts start -->
|
||||
Last modified: Tue Jul 25 05:14:15 PDT 2000
|
||||
Last modified: Fri May 12 03:53:51 PDT 2000
|
||||
<!-- hhmts end -->
|
||||
</body>
|
||||
</html>
|
||||
|
Reference in New Issue
Block a user