495 lines
15 KiB
Python
495 lines
15 KiB
Python
#!/usr/bin/env python
|
|
# -*-python-*-
|
|
#
|
|
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved.
|
|
#
|
|
# By using this file, you agree to the terms and conditions set forth in
|
|
# the LICENSE.html file which can be found at the top level of the ViewVC
|
|
# distribution or at http://viewvc.org/license-1.html.
|
|
#
|
|
# For more information, visit http://viewvc.org/
|
|
#
|
|
# -----------------------------------------------------------------------
|
|
#
|
|
# CGI script to process and display queries to CVSdb
|
|
#
|
|
# This script is part of the ViewVC package. More information can be
|
|
# found at http://viewvc.org
|
|
#
|
|
# -----------------------------------------------------------------------
|
|
|
|
import os
|
|
import sys
|
|
import string
|
|
import time
|
|
|
|
from common import _item, TemplateData
|
|
import cvsdb
|
|
import viewvc
|
|
import ezt
|
|
import debug
|
|
import urllib
|
|
import fnmatch
|
|
|
|
class FormData:
|
|
def __init__(self, form):
|
|
self.valid = 0
|
|
|
|
self.repository = ""
|
|
self.branch = ""
|
|
self.directory = ""
|
|
self.file = ""
|
|
self.who = ""
|
|
self.sortby = ""
|
|
self.textquery = ""
|
|
self.date = ""
|
|
self.hours = 0
|
|
|
|
self.decode_thyself(form)
|
|
|
|
def decode_thyself(self, form):
|
|
try:
|
|
self.textquery = string.strip(form["textquery"].value)
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
try:
|
|
self.repository = form["repository"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
try:
|
|
self.branch = form["branch"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
try:
|
|
self.directory = form["directory"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
try:
|
|
self.file = form["file"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
try:
|
|
self.who = form["who"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
try:
|
|
self.sortby = form["sortby"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
|
|
try:
|
|
self.date = form["date"].value.strip()
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
|
|
try:
|
|
self.hours = int(form["hours"].value)
|
|
except KeyError:
|
|
pass
|
|
except TypeError:
|
|
pass
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
self.valid = 1
|
|
|
|
## 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 = temp.rstrip()
|
|
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 form_to_cvsdb_query(cfg, form_data):
|
|
query = cvsdb.CreateCheckinQuery()
|
|
query.SetLimit(cfg.cvsdb.row_limit)
|
|
|
|
if form_data.repository:
|
|
for cmd, str in listparse_string(form_data.repository):
|
|
cmd = decode_command(cmd)
|
|
query.SetRepository(str, cmd)
|
|
|
|
if form_data.branch:
|
|
for cmd, str in listparse_string(form_data.branch):
|
|
cmd = decode_command(cmd)
|
|
query.SetBranch(str, cmd)
|
|
|
|
if form_data.directory:
|
|
for cmd, str in listparse_string(form_data.directory):
|
|
cmd = decode_command(cmd)
|
|
query.SetDirectory(str, cmd)
|
|
|
|
if form_data.file:
|
|
for cmd, str in listparse_string(form_data.file):
|
|
cmd = decode_command(cmd)
|
|
query.SetFile(str, cmd)
|
|
|
|
if form_data.who:
|
|
for cmd, str in listparse_string(form_data.who):
|
|
cmd = decode_command(cmd)
|
|
query.SetAuthor(str, cmd)
|
|
|
|
if form_data.textquery:
|
|
query.SetTextQuery(form_data.textquery)
|
|
|
|
if form_data.sortby == "author":
|
|
query.SetSortMethod("author")
|
|
elif form_data.sortby == "file":
|
|
query.SetSortMethod("file")
|
|
elif form_data.sortby == "relevance" and form_data.textquery:
|
|
query.SetSortMethod("relevance")
|
|
else:
|
|
query.SetSortMethod("date")
|
|
|
|
if form_data.date:
|
|
if form_data.date == "hours" and form_data.hours:
|
|
query.SetFromDateHoursAgo(form_data.hours)
|
|
elif form_data.date == "day":
|
|
query.SetFromDateDaysAgo(1)
|
|
elif form_data.date == "week":
|
|
query.SetFromDateDaysAgo(7)
|
|
elif form_data.date == "month":
|
|
query.SetFromDateDaysAgo(31)
|
|
|
|
return query
|
|
|
|
def prev_rev(rev):
|
|
'''Returns a string representing the previous revision of the argument.'''
|
|
r = rev.split('.')
|
|
# decrement final revision component
|
|
r[-1] = str(int(r[-1]) - 1)
|
|
# prune if we pass the beginning of the branch
|
|
if len(r) > 2 and r[-1] == '0':
|
|
r = r[:-2]
|
|
return '.'.join(r)
|
|
|
|
def is_forbidden(cfg, cvsroot_name, module):
|
|
'''Return 1 if MODULE in CVSROOT_NAME is forbidden; return 0 otherwise.'''
|
|
|
|
# CVSROOT_NAME might be None here if the data comes from an
|
|
# unconfigured root. This interfaces doesn't care that the root
|
|
# isn't configured, but if that's the case, it will consult only
|
|
# the base and per-vhost configuration for authorizer and
|
|
# authorizer parameters.
|
|
if cvsroot_name:
|
|
cfg = cfg.get_root_config(cvsroot_name)
|
|
authorizer = cfg.options.authorizer
|
|
params = cfg.get_authorizer_params()
|
|
|
|
# If CVSROOT_NAME isn't configured to use an authorizer, nothing
|
|
# is forbidden. If it's configured to use something other than
|
|
# the 'forbidden' authorizer, complain. Otherwise, check for
|
|
# forbiddenness per the PARAMS as expected.
|
|
if not authorizer:
|
|
return 0
|
|
if authorizer != 'forbidden':
|
|
raise Exception("The 'forbidden' authorizer is the only one supported "
|
|
"by this interface. The '%s' root is configured to "
|
|
"use a different one." % (cvsroot_name))
|
|
forbidden = params.get('forbidden', '')
|
|
forbidden = map(lambda x: x.strip(), filter(None, forbidden.split(',')))
|
|
default = 0
|
|
for pat in forbidden:
|
|
if pat[0] == '!':
|
|
default = 1
|
|
if fnmatch.fnmatchcase(module, pat[1:]):
|
|
return 0
|
|
elif fnmatch.fnmatchcase(module, pat):
|
|
return 1
|
|
return default
|
|
|
|
def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
|
|
ob = _item(num_files=len(files), files=[])
|
|
ob.log = desc and server.escape(desc).replace('\n', '<br />') or ' '
|
|
|
|
for commit in files:
|
|
repository = commit.GetRepository()
|
|
directory = commit.GetDirectory()
|
|
cvsroot_name = cvsroots.get(repository)
|
|
|
|
## find the module name (if any)
|
|
try:
|
|
module = filter(None, directory.split('/'))[0]
|
|
except IndexError:
|
|
module = None
|
|
|
|
## skip commits we aren't supposed to show
|
|
if module and ((module == 'CVSROOT' and cfg.options.hide_cvsroot) \
|
|
or is_forbidden(cfg, cvsroot_name, module)):
|
|
continue
|
|
|
|
ctime = commit.GetTime()
|
|
if not ctime:
|
|
ctime = " "
|
|
else:
|
|
if (cfg.options.use_localtime):
|
|
ctime = time.strftime("%y/%m/%d %H:%M %Z", time.localtime(ctime))
|
|
else:
|
|
ctime = time.strftime("%y/%m/%d %H:%M", time.gmtime(ctime)) \
|
|
+ ' UTC'
|
|
|
|
## make the file link
|
|
try:
|
|
file = (directory and directory + "/") + commit.GetFile()
|
|
except:
|
|
raise Exception, str([directory, commit.GetFile()])
|
|
|
|
## If we couldn't find the cvsroot path configured in the
|
|
## viewvc.conf file, or we don't have a VIEWVC_LINK, then
|
|
## don't make the link.
|
|
if cvsroot_name and viewvc_link:
|
|
flink = '[%s] <a href="%s/%s?root=%s">%s</a>' % (
|
|
cvsroot_name, viewvc_link, urllib.quote(file),
|
|
cvsroot_name, file)
|
|
if commit.GetType() == commit.CHANGE:
|
|
dlink = '%s/%s?root=%s&view=diff&r1=%s&r2=%s' % (
|
|
viewvc_link, urllib.quote(file), cvsroot_name,
|
|
prev_rev(commit.GetRevision()), commit.GetRevision())
|
|
else:
|
|
dlink = None
|
|
else:
|
|
flink = '[%s] %s' % (repository, file)
|
|
dlink = None
|
|
|
|
ob.relevance = commit.GetRelevance()
|
|
ob.plus += int(commit.GetPlusCount())
|
|
ob.minus += int(commit.GetMinusCount())
|
|
ob.files.append(_item(date=ctime,
|
|
author=commit.GetAuthor(),
|
|
link=flink,
|
|
rev=commit.GetRevision(),
|
|
branch=commit.GetBranch(),
|
|
plus=int(commit.GetPlusCount()),
|
|
minus=int(commit.GetMinusCount()),
|
|
type=commit.GetTypeString(),
|
|
difflink=dlink,
|
|
))
|
|
|
|
return ob
|
|
|
|
def run_query(server, cfg, db, form_data, viewvc_link):
|
|
query = form_to_cvsdb_query(cfg, form_data)
|
|
db.RunQuery(query)
|
|
|
|
commit_list = query.GetCommitList()
|
|
if not commit_list:
|
|
return [ ], 0
|
|
|
|
row_limit_reached = query.GetLimitReached()
|
|
|
|
commits = [ ]
|
|
files = [ ]
|
|
|
|
cvsroots = {}
|
|
viewvc.expand_root_parents(cfg)
|
|
rootitems = cfg.general.svn_roots.items() + cfg.general.cvs_roots.items()
|
|
for key, value in rootitems:
|
|
cvsroots[cvsdb.CleanRepository(value)] = key
|
|
|
|
current_desc = commit_list[0].GetDescription()
|
|
for commit in commit_list:
|
|
desc = commit.GetDescription()
|
|
if current_desc == desc:
|
|
files.append(commit)
|
|
continue
|
|
|
|
commits.append(build_commit(server, cfg, current_desc, files,
|
|
cvsroots, viewvc_link))
|
|
|
|
files = [ commit ]
|
|
current_desc = desc
|
|
|
|
## add the last file group to the commit list
|
|
commits.append(build_commit(server, cfg, current_desc, files,
|
|
cvsroots, viewvc_link))
|
|
|
|
# Strip out commits that don't have any files attached to them. The
|
|
# files probably aren't present because they've been blocked via
|
|
# forbiddenness.
|
|
def _only_with_files(commit):
|
|
return len(commit.files) > 0
|
|
commits = filter(_only_with_files, commits)
|
|
|
|
return commits, row_limit_reached
|
|
|
|
def main(server, cfg, viewvc_link):
|
|
try:
|
|
|
|
form = server.FieldStorage()
|
|
form_data = FormData(form)
|
|
|
|
db = cvsdb.ConnectDatabaseReadOnly(cfg, None)
|
|
if form_data.valid:
|
|
commits, row_limit_reached = run_query(server, cfg, db, form_data, viewvc_link)
|
|
query = None
|
|
else:
|
|
commits = [ ]
|
|
row_limit_reached = 0
|
|
query = 'skipped'
|
|
|
|
docroot = cfg.options.docroot
|
|
if docroot is None and viewvc_link:
|
|
docroot = viewvc_link + '/' + viewvc.docroot_magic_path
|
|
|
|
data = TemplateData({
|
|
'cfg' : cfg,
|
|
'address' : cfg.general.address,
|
|
'vsn' : viewvc.__version__,
|
|
'textquery' : server.escape(form_data.textquery),
|
|
'repository' : server.escape(form_data.repository),
|
|
'branch' : server.escape(form_data.branch),
|
|
'directory' : server.escape(form_data.directory),
|
|
'file' : server.escape(form_data.file),
|
|
'who' : server.escape(form_data.who),
|
|
'docroot' : docroot,
|
|
'sortby' : form_data.sortby,
|
|
'date' : form_data.date,
|
|
'query' : query,
|
|
'row_limit_reached' : ezt.boolean(row_limit_reached),
|
|
'commits' : commits,
|
|
'num_commits' : len(commits),
|
|
'rss_href' : None,
|
|
'repositories' : db.GetRepositoryList(),
|
|
'hours' : form_data.hours and form_data.hours or 2,
|
|
})
|
|
|
|
# generate the page
|
|
server.header()
|
|
template = viewvc.get_view_template(cfg, "query")
|
|
template.generate(server.file(), data)
|
|
|
|
except SystemExit, e:
|
|
pass
|
|
except:
|
|
exc_info = debug.GetExceptionData()
|
|
server.header(status=exc_info['status'])
|
|
debug.PrintException(server, exc_info)
|