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

Compare commits

...

17 Commits

Author SHA1 Message Date
cmpilato
1f399bbd6d Tag the 1.1.11 final release.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.1.11@2573 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-17 12:27:03 +00:00
cmpilato
e54399a169 Let's try to roll 1.1.11 today, shall we?
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2572 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-17 12:23:20 +00:00
cmpilato
4cc0db75be Merge from trunk r2569 and r2570, which just updated some copyright years.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2571 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-17 12:21:27 +00:00
cmpilato
74a9cbb2a0 Minor wording, spelling, and ordering tweaks.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2568 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-17 11:49:31 +00:00
cmpilato
390e337a8e * bin/standalone.py
(main): Was cli().  Caller(s) updated.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2567 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-12 19:01:48 +00:00
cmpilato
ecdac77d5f Merge from trunk r2565, whose modified-per-conflict-resolution log
message might read something like this:

   Add '--help' option to standalone.py.
   
   * bin/standalone.py
     (usage, badusage): New functions.
     (cli): Separate the option parsing from the option validation.  Add
       support for a '--help' option.  On bad input, now print a suggestion
       to run the script with '--help' for usage hints.

Also:

* CHANGES
  Note this change.

git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2566 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-12 19:01:05 +00:00
cmpilato
79158c2ee7 Merge from trunk r2559-2563, which is a bunch of tweaks to the
standalone.py usage message and options handling.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2564 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-12 13:50:05 +00:00
cmpilato
96fdfbdbcf Merge from trunk r2557, whose log message read like so:
Fix issue #444 ("ViewVC stumbles over revisionless ,v files when
   sticky tag is specified").
   
   * lib/vclib/ccvs/bincvs.py
     (_get_logs): Only try to skip the "rest of the file" if there's
       reason to believe there's a "rest of the file".  While here,
       ensure that the 'tag' variable is defined in all cases (though it
       shouldn't matter).

Also:

* CHANGES
  Record this change.

git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2558 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-05-05 14:39:22 +00:00
cmpilato
361538da21 Merge from trunk r2555, whose log message read like so:
Fix issue #483 ("Error skipping newphrases when parsing RCS data").
   
   * lib/vclib/ccvs/rcsparse/common.py
     (_Parser.parse_rcs_admin): Chew up newphrases found while parsing
       the admin block.
   
   Patch by: Giovanni Pellicciotta <giovanni.pellicciotta@anubex.com>

Also:

* CHANGES
  Note this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2556 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-04-21 16:06:23 +00:00
cmpilato
6250d4134b Merge some spelling fixes from trunk's r2553 here.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2554 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-04-20 15:02:08 +00:00
cmpilato
8589949521 Merge from trunk r2550 and r2551, whose combined log messages might
read like so:

   Fix (to the degree that I believe is reasonable at this time) issue
   #433 ("queries return only partial results").  When a database query
   is artificially limited by the 'row_limit' setting, inform the user
   that the returned data is incomplete.
   
   * lib/cvsdb.py
     (CheckinDatabase.GetCommitsTable): New helper.
     (CheckinDatabase.AddCommit, CheckinDatabase.CheckCommit,
      CheckinDatabase.sql_delete): Use new GetCommitsTable() helper
       instead of hard-coding the version-specific selection of the
       commits table.
     (CheckinDatabase.CreateSQLQueryString): Add 'detect_leftover'
       parameter, used internally to check for a reached query limit.
       Also, use new GetCommitsTable() helper.
     (CheckinDatabase.RunQuery): Update call to CreateSQLQueryString(),
       and check for leftover query response rows.  If any are found, set
       the appropriate flag on the query object.
     (CheckinDatabaseQuery.__init__): Set initial values for new
       'executed' and 'limit_reached' members.
     (CheckinDatabaseQuery.SetExecuted,
      CheckinDatabaseQuery.SetLimitReached,
      CheckinDatabaseQuery.GetLimitReached,
      CheckinDatabaseQuery.GetCommitList): New functions.
   
   * lib/viewvc.py
     (view_query): Use query.GetCommitList() now instead of poking into
       the query object directly.  Also, check query.GetLimitReached(),
       reporting the findings through the data dictionary (via a new
       'row_limit_reached' item) to the templates.
   
   * lib/query.py
     (run_query): Use query.GetCommitList() now instead of poking into
       the query object directly.  Now return a 2-tuple of commits and a
       limit-reached flag.
     (main): Update expectations of run_query() call.  Populate
       'row_limit_reached' data dictionary item.
   
   * templates/query_results.ezt,
   * templates/query.ezt
     Display a warning if the query results are incomplete.
   
   * templates/docroot/styles.css
     (.vc_warning): New style definition.
   
   * docs/template-authoring-guide.html
     Document the new 'row_limit_reached' template item.
   
   * conf/viewvc.conf.dist
     (row_limit, rss_row_limit): Make it clear what exactly is getting
       limited here.

Also:

* CHANGES:
  Note this change.

git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2552 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-04-20 14:56:39 +00:00
cmpilato
f12e262fa5 Merge from trunk r2547, whose log message read like so:
Try to make some sense of the various CVSdb-related limitation
   mechanisms, namely by removing the largely redundant "global" limit
   and allowing the per-query row limit (which already exist, too) to do
   its work.
   
   While here, remove a poorly conceived (but thankfully unhighlighted)
   mechanism for overriding the administrative limit on database rows
   which was accessible via URL CGI params.
   
   * lib/viewvc.py
     (_legal_params): Remove 'limit' as a legal parameter.
     (view_query): No longer allow an undocumented URL parameter to
       override the admin-declared SQL row limit.  That should have never
       been allowed!
   
   * lib/cvsdb.py
     (CheckinDatabase.__init__): Remove 'row_limit' parameter and
       associated self._row_limit member.
     (CheckinDatabase.CreateSQLQueryString): No longer fuss with
       self._row_limit.  Let the individual query carry the row limit.
     (ConnectDatabase): Update call to CheckinDatabase().
   
   * lib/query.py
     (form_to_cvsdb_query): Now accept 'cfg' parameter, and set the
       query's row limit from the configured defaults.
     (run_query): Update call to form_to_cvsdb_query().
   
   * docs/url-reference.html
     Remove reference to the 'limit' parameter.

Also:

* CHANGES
  Note this change, referring to it as a 'security fix' due to the
  ramifications of allowing folks to query your potentially monstrous
  database while ignoring your configured response set limits.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2548 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-04-19 20:44:05 +00:00
cmpilato
f379070697 Merge from trunk r2545, whose log message read something like this:
Fix (I think...) issue #479 ("annotate a file, which uses
   CVS-keywords").  This changes causes the checkout of CVS file
   contents -- when used as part of the markup/annotate logic -- to not
   expand keywords.  This helps it to be consistent with the results of
   the annotate information query.
   
   * lib/vclib/__init__.py,
   * lib/vclib/svn/svn_ra.py,
   * lib/vclib/svn/svn_repos.py
   * lib/vclib/ccvs/ccvs.py
     (openfile): Add 'options' parameter (unused).  Callers updated.
   
   * lib/vclib/ccvs/bincvs.py
     (openfile): Add 'options' parameter, and look for a 'cvs_oldkeywords'
       option to govern the use of -kkv or -ko in the 'co' command.
       Callers updated.
   
   * lib/viewvc.py
     (markup_or_annotate): Pass 'cvs_oldkeywords' option to repos.openfile().

Also:

* CHANGES
  Note this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2546 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-04-01 17:02:07 +00:00
cmpilato
d47fc0ff3b Merge from trunk r2543 (with some tweaks to use the 'string' module
functions), whose log message looks like so:

  Fix issue #477 ("Large log messages trigger excessive memory
  consumption").
  
  * lib/viewvc.py
    (ViewVCHtmlFormatter._tokenize_text): Switch to a line-based
      approach.  This provides a *vast* improvement in performance and
      memory usage, especially for large log messages with many
      mark-up-able regions.

Also:

* CHANGES
  Note this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2544 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-04-01 14:43:01 +00:00
cmpilato
f2b6f0ba86 Merge from trunk r2541, whose log message read like so:
Finish issue #478 ("Client-facing unhandled exceptions, traceback
   dumped") by raising a cleaner error message when asked to display a
   "checkout" of a non-file.
   
   * lib/viewvc.py
     (view_checkout): If the target isn't a file, raise a
       ViewVCException().

Also:

* CHANGES
  Note this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2542 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-03-29 16:41:09 +00:00
cmpilato
ddbe150be1 Merge from trunk r2538, which did a little something like this:
* bin/standalone.py
     (main): Fix broken option handling: -d expected an argument, -c
       didn't and was doubly associated with both --config-file and
       --htpasswd-file.

Also:

* CHANGES
  Note this change.


git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2539 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-03-15 20:42:42 +00:00
cmpilato
6b5ed7c857 Begin a new release cycle.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/branches/1.1.x@2537 8cb11bc2-c004-0410-86c3-e597b4017df7
2011-03-15 16:37:38 +00:00
21 changed files with 373 additions and 229 deletions

12
CHANGES
View File

@@ -1,3 +1,15 @@
Version 1.1.11 (released 17-May-2011)
* security fix: remove user-reachable override of cvsdb row limit
* fix broken standalone.py -c and -d options handling
* add --help option to standalone.py
* fix stack trace when asked to checkout a directory (issue #478)
* improve memory usage and speed of revision log markup (issue #477)
* fix broken annotation view in CVS keyword-bearing files (issue #479)
* warn users when query results are incomplete (issue #443)
* avoid parsing errors on RCS newphrases in the admin section (issue #483)
* make rlog parsing code more robust in certain error cases (issue #444)
Version 1.1.10 (released 15-Mar-2011)
* fix stack trace in Subversion revision info logic (issue #475, issue #476)

View File

@@ -76,8 +76,6 @@ except ImportError:
class Options:
port = 49152 # default TCP/IP port used for the server
start_gui = 0 # No GUI unless requested.
daemon = 0 # stay in the foreground by default
repositories = {} # use default repositories specified in config
host = sys.platform == 'mac' and '127.0.0.1' or 'localhost'
script_alias = 'viewvc'
@@ -707,13 +705,66 @@ def gui(host, port):
# --- command-line interface: ----------------------------------------------
def cli(argv):
def usage():
clean_options = Options()
cmd = os.path.basename(sys.argv[0])
port = clean_options.port
host = clean_options.host
script_alias = clean_options.script_alias
sys.stderr.write("""Usage: %(cmd)s [OPTIONS]
Run a simple, standalone HTTP server configured to serve up ViewVC requests.
Options:
--config-file=FILE (-c) Read configuration options from FILE. If not
specified, ViewVC will look for a configuration
file in its installation tree, falling back to
built-in default values. (Not valid in GUI mode.)
--daemon (-d) Background the server process.
--gui (-g) Pop up a graphical configuration interface.
Requires a valid X11 display connection.
--help Show this usage message and exit.
--host=HOSTNAME (-h) Listen on HOSTNAME. Required for access from a
remote machine. [default: %(host)s]
--htpasswd-file=FILE Authenticate incoming requests, validating against
against FILE, which is an Apache HTTP Server
htpasswd file. (CRYPT only; no DIGEST support.)
--port=PORT (-p) Listen on PORT. [default: %(port)d]
--repository=PATH (-r) Serve the Subversion or CVS repository located
at PATH. This option may be used more than once.
--script-alias=PATH (-s) Use PATH as the virtual script location (similar
to Apache HTTP Server's ScriptAlias directive).
For example, "--script-alias=repo/view" will serve
ViewVC at "http://HOSTNAME:PORT/repo/view".
[default: %(script_alias)s]
""" % locals())
sys.exit(0)
def badusage(errstr):
cmd = os.path.basename(sys.argv[0])
sys.stderr.write("ERROR: %s\n\n"
"Try '%s --help' for detailed usage information.\n"
% (errstr, cmd))
sys.exit(1)
def main(argv):
"""Command-line interface (looks at argv to decide what to do)."""
import getopt
class BadUsage(Exception): pass
short_opts = string.join(['c',
'd:',
short_opts = string.join(['c:',
'd',
'g',
'h:',
'p:',
@@ -723,6 +774,7 @@ def cli(argv):
long_opts = ['daemon',
'config-file=',
'gui',
'help',
'host=',
'htpasswd-file=',
'port=',
@@ -730,113 +782,98 @@ def cli(argv):
'script-alias=',
]
try:
opt_daemon = 0
opt_gui = 0
opt_host = None
opt_port = None
opt_htpasswd_file = None
opt_config_file = None
opt_script_alias = None
opt_repositories = []
# Parse command-line options.
try:
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
for opt, val in opts:
if opt in ('-g', '--gui'):
options.start_gui = 1
elif opt in ('-r', '--repository'):
if options.repositories: # option may be used more than once:
num = len(options.repositories.keys())+1
symbolic_name = "Repository"+str(num)
options.repositories[symbolic_name] = val
else:
options.repositories["Development"] = val
elif opt in ('-d', '--daemon'):
options.daemon = 1
elif opt in ('-p', '--port'):
try:
options.port = int(val)
except ValueError:
raise BadUsage, "Port '%s' is not a valid port number" % (val)
elif opt in ('-h', '--host'):
options.host = val
elif opt in ('-s', '--script-alias'):
options.script_alias = \
string.join(filter(None, string.split(val, '/')), '/')
elif opt in ('-c', '--config-file'):
if not os.path.isfile(val):
raise BadUsage, "'%s' does not appear to be a valid " \
"configuration file." % (val)
options.config_file = val
elif opt in ('-c', '--htpasswd-file'):
if not os.path.isfile(val):
raise BadUsage, "'%s' does not appear to be a valid " \
"htpasswd file." % (val)
if not has_crypt:
raise BadUsage, "Unable to locate suitable `crypt' module for use " \
"with --htpasswd-file option. If your Python " \
"distribution does not include this module (as is " \
"the case on many non-Unix platforms), consider " \
"installing the `fcrypt' module instead (see " \
"http://carey.geek.nz/code/python-fcrypt/)."
options.htpasswd_file = val
if options.start_gui and options.config_file:
raise BadUsage, "--config-file option is not valid in GUI mode."
if not options.start_gui and not options.port:
raise BadUsage, "You must supply a valid port, or run in GUI mode."
if options.daemon:
pid = os.fork()
if pid != 0:
sys.exit()
if options.start_gui:
gui(options.host, options.port)
return
elif options.port:
def ready(server):
print 'server ready at %s%s' % (server.url, options.script_alias)
serve(options.host, options.port, ready)
return
except (getopt.error, BadUsage), err:
cmd = os.path.basename(sys.argv[0])
port = options.port
host = options.host
script_alias = options.script_alias
if str(err):
sys.stderr.write("ERROR: %s\n\n" % (str(err)))
sys.stderr.write("""Usage: %(cmd)s [OPTIONS]
if opt in ['--help']:
usage()
elif opt in ['-g', '--gui']:
opt_gui = 1
elif opt in ['-r', '--repository']: # may be used more than once
opt_repositories.append(val)
elif opt in ['-d', '--daemon']:
opt_daemon = 1
elif opt in ['-p', '--port']:
opt_port = val
elif opt in ['-h', '--host']:
opt_host = val
elif opt in ['-s', '--script-alias']:
opt_script_alias = val
elif opt in ['-c', '--config-file']:
opt_config_file = val
elif opt in ['--htpasswd-file']:
opt_htpasswd_file = val
except getopt.error, err:
badusage(str(err))
Run a simple, standalone HTTP server configured to serve up ViewVC
requests.
# Validate options that need validating.
class BadUsage(Exception): pass
try:
if opt_port is not None:
try:
options.port = int(opt_port)
except ValueError:
raise BadUsage("Port '%s' is not a valid port number" % (opt_port))
if not options.port:
raise BadUsage("You must supply a valid port.")
if opt_htpasswd_file is not None:
if not os.path.isfile(opt_htpasswd_file):
raise BadUsage("'%s' does not appear to be a valid htpasswd file."
% (opt_htpasswd_file))
if not has_crypt:
raise BadUsage("Unable to locate suitable `crypt' module for use "
"with --htpasswd-file option. If your Python "
"distribution does not include this module (as is "
"the case on many non-Unix platforms), consider "
"installing the `fcrypt' module instead (see "
"http://carey.geek.nz/code/python-fcrypt/).")
options.htpasswd_file = opt_htpasswd_file
if opt_config_file is not None:
if not os.path.isfile(opt_config_file):
raise BadUsage("'%s' does not appear to be a valid configuration file."
% (opt_config_file))
options.config_file = opt_config_file
if opt_host is not None:
options.host = opt_host
if opt_script_alias is not None:
options.script_alias = string.join(filter(None,
string.split(opt_script_alias,
'/')),
'/')
for repository in opt_repositories:
if not options.repositories.has_key('Development'):
rootname = 'Development'
else:
rootname = 'Repository%d' % (len(options.repositories.keys()) + 1)
options.repositories[rootname] = repository
except BadUsage, err:
badusage(str(err))
# Fork if we're in daemon mode.
if opt_daemon:
pid = os.fork()
if pid != 0:
sys.exit()
Options:
# Finaly, start the server.
if opt_gui:
gui(options.host, options.port)
else:
def ready(server):
print 'server ready at %s%s' % (server.url, options.script_alias)
serve(options.host, options.port, ready)
--config-file=PATH (-c) Use the file at PATH as the ViewVC configuration
file. If not specified, ViewVC will try to use
the configuration file in its installation tree;
otherwise, built-in default values are used.
(Not valid in GUI mode.)
--daemon (-d) Background the server process.
--host=HOST (-h) Start the server listening on HOST. You need
to provide the hostname if you want to
access the standalone server from a remote
machine. [default: %(host)s]
--port=PORT (-p) Start the server on the given PORT.
[default: %(port)d]
--repository=PATH (-r) Serve up the Subversion or CVS repository located
at PATH. This option may be used more than once.
--script-alias=PATH (-s) Specify the ScriptAlias, the artificial path
location that at which ViewVC appears to be
located. For example, if your ScriptAlias is
"cgi-bin/viewvc", then ViewVC will be accessible
at "http://%(host)s:%(port)s/cgi-bin/viewvc".
[default: %(script_alias)s]
--htpasswd-file=FILE Demand authentication from clients, validating
authentication credentials against FILE, which is
an Apache htpasswd file that employs CRYPT
encryption. (Sorry, no DIGEST support yet.)
--gui (-g) Pop up a graphical interface for serving and
testing ViewVC. NOTE: this requires a valid
X11 display connection.
""" % locals())
if __name__ == '__main__':
options = Options()
cli(sys.argv)
main(sys.argv)

View File

@@ -797,6 +797,14 @@
## row_limit: Maximum number of rows returned by a given normal query
## to the database.
##
## NOTE: This limits the amount of data provided to ViewVC by the
## database. It is from this already-reduced data set that ViewVC
## builds the query response it presents to the user, which may or may
## not include still more limiting via the query form's 'limit'
## parameter. In other words, there is no value which the user can use
## in the query form's 'limit' parameter which will cause more data to
## be returned by the database for ViewVC to process.
##
#row_limit = 1000
## rss_row_limit: Maximum number of rows returned by a given query to
@@ -804,6 +812,9 @@
## that RSS readers tend to poll regularly for new data, you might want
## to keep this set to a conservative number.)
##
## See also the `NOTE' for the 'row_limit' option, which applies here
## as well.
##
#rss_row_limit = 100
## check_database_for_root: Check if the repository is found in the

View File

@@ -1821,6 +1821,14 @@ td {
<td>Indicates how query results are being sorted. Possible values:
<tt>date</tt>, <tt>author</tt>, and <tt>file</tt>.</td>
</tr>
<tr class="varlevel1">
<td class="varname">row_limit_reached</td>
<td>Boolean</td>
<td>Indicates whether the internal database row limit threshold (set
via the <code>cvsdb.row_limit</code>
and <code>cvsdb.rss_row_limit</code> configuration options) was
reached by the query.</td>
</tr>
<tr class="varlevel1">
<td class="varname">show_branch</td>
<td>Boolean</td>

View File

@@ -1192,13 +1192,6 @@ th.caption {
commands to back out changes instead showing a normal query result
page</td>
</tr>
<tr>
<td><code>limit=<var>LIMIT</var></code></td>
<td>optional</td>
<td>maximum number of file-revisions to process during a
query. Default is value of <code>row_limit</code> configuration
option</td>
</tr>
<tr>
<td><code>limit_changes=<var>LIMIT_CHANGES</var></code></td>
<td>optional</td>

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2011 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
@@ -38,13 +38,12 @@ error = "cvsdb error"
## complient database interface
class CheckinDatabase:
def __init__(self, host, port, user, passwd, database, row_limit):
def __init__(self, host, port, user, passwd, database):
self._host = host
self._port = port
self._user = user
self._passwd = passwd
self._database = database
self._row_limit = row_limit
self._version = None
## database lookup caches
@@ -169,6 +168,9 @@ class CheckinDatabase:
return list
def GetCommitsTable(self):
return self._version >= 1 and 'commits' or 'checkins'
def GetTableList(self):
sql = "SHOW TABLES"
cursor = self.db.cursor()
@@ -309,8 +311,7 @@ class CheckinDatabase:
minus_count = commit.GetMinusCount() or '0'
description_id = self.GetDescriptionID(commit.GetDescription())
commits_table = self._version >= 1 and 'commits' or 'checkins'
sql = "REPLACE INTO %s" % (commits_table)
sql = "REPLACE INTO %s" % (self.GetCommitsTable())
sql = sql + \
" (type,ci_when,whoid,repositoryid,dirid,fileid,revision,"\
" stickytag,branchid,addedlines,removedlines,descid)"\
@@ -363,8 +364,8 @@ class CheckinDatabase:
return "(%s)" % (string.join(sqlList, " OR "))
def CreateSQLQueryString(self, query):
commits_table = self._version >= 1 and 'commits' or 'checkins'
def CreateSQLQueryString(self, query, detect_leftover=0):
commits_table = self.GetCommitsTable()
tableList = [(commits_table, None)]
condList = []
@@ -444,13 +445,14 @@ class CheckinDatabase:
conditions = string.join(joinConds + condList, " AND ")
conditions = conditions and "WHERE %s" % conditions
## limit the number of rows requested or we could really slam
## a server with a large database
## apply the query's row limit, if any (so we avoid really
## slamming a server with a large database)
limit = ""
if query.limit:
limit = "LIMIT %s" % (str(query.limit))
elif self._row_limit:
limit = "LIMIT %s" % (str(self._row_limit))
if detect_leftover:
limit = "LIMIT %s" % (str(query.limit + 1))
else:
limit = "LIMIT %s" % (str(query.limit))
sql = "SELECT %s.* FROM %s %s %s %s" \
% (commits_table, tables, conditions, order_by, limit)
@@ -458,14 +460,20 @@ class CheckinDatabase:
return sql
def RunQuery(self, query):
sql = self.CreateSQLQueryString(query)
sql = self.CreateSQLQueryString(query, 1)
cursor = self.db.cursor()
cursor.execute(sql)
query.SetExecuted()
row_count = 0
while 1:
row = cursor.fetchone()
if not row:
break
row_count = row_count + 1
if query.limit and (row_count > query.limit):
query.SetLimitReached()
break
(dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
@@ -504,13 +512,12 @@ class CheckinDatabase:
if file_id == None:
return None
commits_table = self._version >= 1 and 'commits' or 'checkins'
sql = "SELECT * FROM %s WHERE "\
" repositoryid=%%s "\
" AND dirid=%%s"\
" AND fileid=%%s"\
" AND revision=%%s"\
% (commits_table)
% (self.GetCommitsTable())
sql_args = (repository_id, dir_id, file_id, commit.GetRevision())
cursor = self.db.cursor()
@@ -527,10 +534,9 @@ class CheckinDatabase:
def sql_delete(self, table, key, value, keep_fkey = None):
sql = "DELETE FROM %s WHERE %s=%%s" % (table, key)
sql_args = (value, )
commits_table = self._version >= 1 and 'commits' or 'checkins'
if keep_fkey:
sql += " AND %s NOT IN (SELECT %s FROM %s WHERE %s = %%s)" \
% (key, keep_fkey, commits_table, keep_fkey)
% (key, keep_fkey, self.GetCommitsTable(), keep_fkey)
sql_args = (value, value)
cursor = self.db.cursor()
cursor.execute(sql, sql_args)
@@ -769,8 +775,9 @@ class QueryEntry:
self.data = data
self.match = match
## CheckinDatabaseQueryData is a object which contains the search parameters
## for a query to the CheckinDatabase
## CheckinDatabaseQuery is an object which contains the search
## parameters for a query to the Checkin Database and -- after the
## query is executed -- the data returned by the query.
class CheckinDatabaseQuery:
def __init__(self):
## sorting
@@ -790,7 +797,8 @@ class CheckinDatabaseQuery:
## limit on number of rows to return
self.limit = None
self.limit_reached = 0
## list of commits -- filled in by CVS query
self.commit_list = []
@@ -798,6 +806,9 @@ class CheckinDatabaseQuery:
## are added
self.commit_cb = None
## has this query been run?
self.executed = 0
def SetRepository(self, repository, match = "exact"):
self.repository_list.append(QueryEntry(repository, match))
@@ -843,6 +854,20 @@ class CheckinDatabaseQuery:
def AddCommit(self, commit):
self.commit_list.append(commit)
def SetExecuted(self):
self.executed = 1
def SetLimitReached(self):
self.limit_reached = 1
def GetLimitReached(self):
assert self.executed
return self.limit_reached
def GetCommitList(self):
assert self.executed
return self.commit_list
##
## entrypoints
@@ -861,7 +886,7 @@ def ConnectDatabase(cfg, readonly=0):
user = cfg.cvsdb.user
passwd = cfg.cvsdb.passwd
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd,
cfg.cvsdb.database_name, cfg.cvsdb.row_limit)
cfg.cvsdb.database_name)
db.Connect()
return db

View File

@@ -217,8 +217,9 @@ def decode_command(cmd):
else:
return "exact"
def form_to_cvsdb_query(form_data):
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):
@@ -377,12 +378,15 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
return ob
def run_query(server, cfg, form_data, viewvc_link):
query = form_to_cvsdb_query(form_data)
query = form_to_cvsdb_query(cfg, form_data)
db = cvsdb.ConnectDatabaseReadOnly(cfg)
db.RunQuery(query)
if not query.commit_list:
return [ ]
commit_list = query.GetCommitList()
if not commit_list:
return [ ], 0
row_limit_reached = query.GetLimitReached()
commits = [ ]
files = [ ]
@@ -393,8 +397,8 @@ def run_query(server, cfg, form_data, viewvc_link):
for key, value in rootitems:
cvsroots[cvsdb.CleanRepository(value)] = key
current_desc = query.commit_list[0].GetDescription()
for commit in query.commit_list:
current_desc = commit_list[0].GetDescription()
for commit in commit_list:
desc = commit.GetDescription()
if current_desc == desc:
files.append(commit)
@@ -417,7 +421,7 @@ def run_query(server, cfg, form_data, viewvc_link):
return len(commit.files) > 0
commits = filter(_only_with_files, commits)
return commits
return commits, row_limit_reached
def main(server, cfg, viewvc_link):
try:
@@ -426,10 +430,12 @@ def main(server, cfg, viewvc_link):
form_data = FormData(form)
if form_data.valid:
commits = run_query(server, cfg, form_data, viewvc_link)
commits, row_limit_reached = run_query(server, cfg,
form_data, viewvc_link)
query = None
else:
commits = [ ]
row_limit_reached = 0
query = 'skipped'
docroot = cfg.options.docroot
@@ -449,6 +455,7 @@ def main(server, cfg, viewvc_link):
'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,

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2006-2010 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

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2006-2010 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

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 2008-2010 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

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2011 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
@@ -76,7 +76,7 @@ class Repository:
"""
pass
def openfile(self, path_parts, rev):
def openfile(self, path_parts, rev, options):
"""Open a file object to read file contents at a given path and revision.
The return value is a 2-tuple of containg the file object and revision
@@ -86,6 +86,8 @@ class Repository:
of the repository. e.g. ["subdir1", "subdir2", "filename"]
rev is the revision of the file to check out
options is a dictionary of implementation specific options
"""
def listdir(self, path_parts, rev, options):

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2011 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
@@ -167,7 +167,14 @@ class BinCVSRepository(BaseCVSRepository):
return revs[-1]
return None
def openfile(self, path_parts, rev):
def openfile(self, path_parts, rev, options):
"""see vclib.Repository.openfile docstring
Option values recognized by this implementation:
cvs_oldkeywords
boolean. true to use the original keyword substitution values.
"""
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file."
% (string.join(path_parts, "/")))
@@ -175,12 +182,14 @@ class BinCVSRepository(BaseCVSRepository):
rev_flag = '-p'
else:
rev_flag = '-p' + rev
if options.get('cvs_oldkeywords', 0):
kv_flag = '-ko'
else:
kv_flag = '-kkv'
full_name = self.rcsfile(path_parts, root=1, v=0)
used_rlog = 0
tip_rev = None # used only if we have to fallback to using rlog
fp = self.rcs_popen('co', (rev_flag, full_name), 'rb')
fp = self.rcs_popen('co', (kv_flag, rev_flag, full_name), 'rb')
try:
filename, revision = _parse_co_header(fp)
except COMissingRevision:
@@ -1027,16 +1036,16 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
file.errors.append("rlog error: %s" % msg)
continue
tag = None
if view_tag == 'MAIN' or view_tag == 'HEAD':
tag = Tag(None, default_branch)
elif taginfo.has_key(view_tag):
tag = Tag(None, taginfo[view_tag])
elif view_tag:
# the tag wasn't found, so skip this file
elif view_tag and (eof != _EOF_FILE):
# the tag wasn't found, so skip this file (unless we already
# know there's nothing left of it to read)
_skip_file(rlog)
eof = 1
else:
tag = None
eof = _EOF_FILE
# we don't care about the specific values -- just the keys and whether
# the values point to branches or revisions. this the fastest way to

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2011 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

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2011 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
@@ -127,9 +127,9 @@ class CCVSRepository(BaseCVSRepository):
% (string.join(path_parts2, "/")))
temp1 = tempfile.mktemp()
open(temp1, 'wb').write(self.openfile(path_parts1, rev1)[0].getvalue())
open(temp1, 'wb').write(self.openfile(path_parts1, rev1, {})[0].getvalue())
temp2 = tempfile.mktemp()
open(temp2, 'wb').write(self.openfile(path_parts2, rev2)[0].getvalue())
open(temp2, 'wb').write(self.openfile(path_parts2, rev2, {})[0].getvalue())
r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
@@ -152,7 +152,7 @@ class CCVSRepository(BaseCVSRepository):
def revinfo(self, rev):
raise vclib.UnsupportedFeature
def openfile(self, path_parts, rev=None):
def openfile(self, path_parts, rev, options):
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file."
% (string.join(path_parts, "/")))

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2011 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
@@ -194,7 +194,8 @@ class _Parser:
else:
# Chew up "newphrase"
# warn("Unexpected RCS token: $token\n")
pass
while self.ts.get() != ';':
pass
else:
if f is None:
self.ts.unget(token)

View File

@@ -241,7 +241,7 @@ class RemoteSubversionRepository(vclib.Repository):
raise vclib.ItemNotFound(path_parts)
return pathtype
def openfile(self, path_parts, rev):
def openfile(self, path_parts, rev, options):
path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path)

View File

@@ -400,7 +400,7 @@ class LocalSubversionRepository(vclib.Repository):
raise vclib.ItemNotFound(path_parts)
return pathtype
def openfile(self, path_parts, rev):
def openfile(self, path_parts, rev, options):
path = self._getpath(path_parts)
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
raise vclib.Error("Path '%s' is not a file." % path)

View File

@@ -14,7 +14,7 @@
#
# -----------------------------------------------------------------------
__version__ = '1.1.10-dev'
__version__ = '1.1.11'
# this comes from our library; measure the startup time
import debug
@@ -703,7 +703,6 @@ _legal_params = {
'mindate' : _re_validate_datetime,
'maxdate' : _re_validate_datetime,
'format' : _re_validate_alpha,
'limit' : _re_validate_number,
# for redirect_pathrev
'orig_path' : None,
@@ -1131,7 +1130,7 @@ class ViewVCHtmlFormatter:
linkified email address, with no more than MAXLEN characters
in the non-HTML-tag bits. If MAXLEN is 0, there is no maximum.
- the number of non-HTML-tag characters returned.
"""
"""
s = mobj.group(0)
trunc_s = maxlen and s[:maxlen] or s
return '<a href="mailto:%s">%s</a>' % (urllib.quote(s),
@@ -1232,45 +1231,51 @@ class ViewVCHtmlFormatter:
def _tokenize_text(self, s):
tokens = []
while s:
best_match = best_conv = best_userdata = None
for test in self._formatters:
match = test[0].search(s)
# If we find and match and (a) its our first one, or (b) it
# matches text earlier than our previous best match, or (c) it
# matches text at the same location as our previous best match
# but extends to cover more text than that match, then this is
# our new best match.
#
# Implied here is that when multiple formatters match exactly
# the same text, the first formatter in the registration list wins.
if match \
and ((best_match is None) \
or (match.start() < best_match.start())
or ((match.start() == best_match.start()) \
and (match.end() > best_match.end()))):
best_match = match
best_conv = test[1]
best_userdata = test[2]
# If we found a match...
if best_match:
# ... add any non-matching stuff first, then the matching bit.
start = best_match.start()
end = best_match.end()
if start > 0:
tokens.append(_item(match=s[:start],
# We could just have a "while s:" here instead of "for line: while
# line:", but for really large log messages with heavy
# tokenization, the cost in both performance and memory
# consumption of the approach taken was atrocious.
for line in string.split(string.replace(s, '\r\n', '\n'), '\n'):
line = line + '\n'
while line:
best_match = best_conv = best_userdata = None
for test in self._formatters:
match = test[0].search(line)
# If we find and match and (a) its our first one, or (b) it
# matches text earlier than our previous best match, or (c) it
# matches text at the same location as our previous best match
# but extends to cover more text than that match, then this is
# our new best match.
#
# Implied here is that when multiple formatters match exactly
# the same text, the first formatter in the registration list wins.
if match \
and ((best_match is None) \
or (match.start() < best_match.start())
or ((match.start() == best_match.start()) \
and (match.end() > best_match.end()))):
best_match = match
best_conv = test[1]
best_userdata = test[2]
# If we found a match...
if best_match:
# ... add any non-matching stuff first, then the matching bit.
start = best_match.start()
end = best_match.end()
if start > 0:
tokens.append(_item(match=line[:start],
converter=self.format_text,
userdata=None))
tokens.append(_item(match=best_match,
converter=best_conv,
userdata=best_userdata))
line = line[end:]
else:
# Otherwise, just add the rest of the string.
tokens.append(_item(match=line,
converter=self.format_text,
userdata=None))
tokens.append(_item(match=best_match,
converter=best_conv,
userdata=best_userdata))
s = s[end:]
else:
# Otherwise, just add the rest of the string.
tokens.append(_item(match=s,
converter=self.format_text,
userdata=None))
s = ''
line = ''
return tokens
@@ -1739,7 +1744,7 @@ def markup_or_annotate(request, is_annotate):
# Is this a viewable image type?
if is_viewable_image(mime_type) \
and 'co' in cfg.options.allowed_views:
fp, revision = request.repos.openfile(path, rev)
fp, revision = request.repos.openfile(path, rev, {})
fp.close()
if check_freshness(request, None, revision, weak=1):
return
@@ -1762,7 +1767,7 @@ def markup_or_annotate(request, is_annotate):
except:
annotation = 'error'
fp, revision = request.repos.openfile(path, rev)
fp, revision = request.repos.openfile(path, rev, {'cvs_oldkeywords' : 1})
if check_freshness(request, None, revision, weak=1):
fp.close()
return
@@ -2662,9 +2667,12 @@ def view_checkout(request):
if 'co' not in cfg.options.allowed_views:
raise debug.ViewVCException('Checkout view is disabled',
'403 Forbidden')
if request.pathtype != vclib.FILE:
raise debug.ViewVCException('Unsupported feature: checkout view on '
'directory', '400 Bad Request')
path, rev = _orig_path(request)
fp, revision = request.repos.openfile(path, rev)
fp, revision = request.repos.openfile(path, rev, {})
# The revision number acts as a strong validator.
if not check_freshness(request, None, revision):
@@ -2750,7 +2758,7 @@ def search_file(repos, path_parts, rev, search_re):
# Read in each line of a checked-out file, and then use re.search to
# search line.
fp = repos.openfile(path_parts, rev)[0]
fp = repos.openfile(path_parts, rev, {})[0]
matches = 0
while 1:
line = fp.readline()
@@ -3210,13 +3218,13 @@ def view_diff(request):
if (cfg.options.hr_intraline and idiff
and ((human_readable and idiff.sidebyside)
or (not human_readable and diff_type == vclib.UNIFIED))):
f1 = request.repos.openfile(p1, rev1)[0]
f1 = request.repos.openfile(p1, rev1, {})[0]
try:
lines_left = f1.readlines()
finally:
f1.close()
f2 = request.repos.openfile(p2, rev2)[0]
f2 = request.repos.openfile(p2, rev2, {})[0]
try:
lines_right = f2.readlines()
finally:
@@ -3429,7 +3437,7 @@ def generate_tarball(out, request, reldir, stack, dir_mtime=None):
### FIXME: Read the whole file into memory? Bad... better to do
### 2 passes.
fp = request.repos.openfile(rep_path + [file.name], request.pathrev)[0]
fp = request.repos.openfile(rep_path + [file.name], request.pathrev, {})[0]
contents = fp.read()
fp.close()
@@ -4078,7 +4086,6 @@ def view_query(request):
mindate = request.query_dict.get('mindate', '')
maxdate = request.query_dict.get('maxdate', '')
format = request.query_dict.get('format')
limit = int(request.query_dict.get('limit', 0))
limit_changes = int(request.query_dict.get('limit_changes',
cfg.options.limit_changes))
@@ -4148,29 +4155,32 @@ def view_query(request):
query.SetFromDateObject(mindate)
if maxdate is not None:
query.SetToDateObject(maxdate)
if limit:
query.SetLimit(limit)
elif format == 'rss':
# Set the admin-defined (via configuration) row limits. This is to avoid
# slamming the database server with a monster query.
if format == 'rss':
query.SetLimit(cfg.cvsdb.rss_row_limit)
else:
query.SetLimit(cfg.cvsdb.row_limit)
# run the query
db.RunQuery(query)
sql = request.server.escape(db.CreateSQLQueryString(query))
commit_list = query.GetCommitList()
row_limit_reached = query.GetLimitReached()
# gather commits
commits = []
plus_count = 0
minus_count = 0
mod_time = -1
if query.commit_list:
if commit_list:
files = []
limited_files = 0
current_desc = query.commit_list[0].GetDescriptionID()
current_rev = query.commit_list[0].GetRevision()
current_desc = commit_list[0].GetDescriptionID()
current_rev = commit_list[0].GetRevision()
dir_strip = _path_join(repos_dir)
for commit in query.commit_list:
for commit in commit_list:
commit_desc = commit.GetDescriptionID()
commit_rev = commit.GetRevision()
@@ -4239,7 +4249,7 @@ def view_query(request):
data = common_template_data(request)
data.merge(ezt.TemplateData({
'sql': sql,
'sql': request.server.escape(db.CreateSQLQueryString(query)),
'english_query': english_query(request),
'queryform_href': request.get_url(view_func=view_queryform, escape=1),
'backout_href': backout_href,
@@ -4248,6 +4258,7 @@ def view_query(request):
'show_branch': show_branch,
'querysort': querysort,
'commits': commits,
'row_limit_reached' : ezt.boolean(row_limit_reached),
'limit_changes': limit_changes,
'limit_changes_href': limit_changes_href,
'rss_link_href': request.get_url(view_func=view_query,

View File

@@ -269,3 +269,14 @@ table.vc_idiff tbody th {
.vc_query_form {
background-color: #e6e6e6;
}
/*** Warning! ***/
.vc_warning {
border-width: 1px 2px 2px 2px;
border-color: black;
border-style: solid;
background-color: red;
color: white;
padding: 0.5em;
}

View File

@@ -158,7 +158,15 @@
[is query "skipped"]
[else]
<p><strong>[num_commits]</strong> matches found.</p>
[if-any row_limit_reached]
<p class="vc_warning">WARNING: These query results have been
artificially limited by an administrative threshold value and do
<em>not</em> represent the entirety of the data set which matches
the query. Consider modifying your query to be more specific</a>,
using your version control tool's query capabilities, or asking
your administrator to raise the database response size
threshold.</p>
[end]
[if-any commits]
<table cellspacing="0" cellpadding="2">
<thead>

View File

@@ -7,6 +7,15 @@
<p><strong>[english_query]</strong></p>
[# <!-- {sql} --> ]
[if-any row_limit_reached]
<p class="vc_warning">WARNING: These query results have been
artificially limited by an administrative threshold value and do
<em>not</em> represent the entirety of the data set which matches
the query. Consider <a href="[queryform_href]">modifying your
query to be more specific</a>, using your version control tool's
query capabilities, or asking your administrator to raise the
database response size threshold.</p>
[end]
<p><a href="[queryform_href]">Modify query</a></p>
<p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p>