mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
739c51dd7c | ||
![]() |
167a2a041b | ||
![]() |
c4483d0501 | ||
![]() |
d620bc13e7 | ||
![]() |
e3756ae365 | ||
![]() |
77ad38cbdb | ||
![]() |
7feecdd512 | ||
![]() |
a087c06a88 | ||
![]() |
acce6a556d | ||
![]() |
4670019d3a | ||
![]() |
9ce7372e1a | ||
![]() |
e1959ac2e5 | ||
![]() |
cb38ccc929 | ||
![]() |
f2b82132c2 | ||
![]() |
5caf3a4437 | ||
![]() |
06ee4df42d | ||
![]() |
0a6b13145e | ||
![]() |
65d3568c92 | ||
![]() |
de5b147a6f | ||
![]() |
eb6b575701 | ||
![]() |
8d82b6f0d6 | ||
![]() |
5a51470cbc | ||
![]() |
acc0783468 | ||
![]() |
c91325d40a | ||
![]() |
ca1bd67b5d | ||
![]() |
f1e7ef42d6 | ||
![]() |
8b7eae7f14 | ||
![]() |
6f3d9a3a00 | ||
![]() |
1d7307c09b | ||
![]() |
22d1e72c66 | ||
![]() |
0e7d4061ed | ||
![]() |
56dd2dcf28 | ||
![]() |
5932f24a68 | ||
![]() |
161421a20f | ||
![]() |
2a297c5361 | ||
![]() |
f43117b10f | ||
![]() |
a025237f7e | ||
![]() |
228db2fadb | ||
![]() |
0cd26cc79f | ||
![]() |
b808d5d1e8 | ||
![]() |
f843c054b7 | ||
![]() |
cf06a971e8 | ||
![]() |
bc8f3bdd4f | ||
![]() |
a6fcab67b0 | ||
![]() |
4069208316 | ||
![]() |
cfaa30b40f |
36
CHANGES
36
CHANGES
@@ -1,3 +1,38 @@
|
||||
Version 1.1.17 (released 25-Oct-2012)
|
||||
|
||||
* fix exception caused by uninitialized variable usage (issue #516)
|
||||
|
||||
Version 1.1.16 (released 24-Oct-2012)
|
||||
|
||||
* security fix: escape "extra" diff info to avoid XSS attack (issue #515)
|
||||
* add 'binary_mime_types' configuration option and handling (issue #510)
|
||||
* fix 'select for diffs' persistence across log pages (issue #512)
|
||||
* remove lock status and filesize check on directories in remote SVN views
|
||||
* fix bogus 'Annotation of' page title for non-annotated view (issue #514)
|
||||
|
||||
Version 1.1.15 (released 22-Jun-2012)
|
||||
|
||||
* security fix: complete authz support for remote SVN views (issue #353)
|
||||
* security fix: log msg leak in SVN revision view with unreadable copy source
|
||||
* fix several instances of incorrect information in remote SVN views
|
||||
* increase performance of some revision metadata lookups in remote SVN views
|
||||
* fix RSS feed regression introduced in 1.1.14
|
||||
|
||||
Version 1.1.14 (released 12-Jun-2012)
|
||||
|
||||
* fix annotation of svn files with non-URI-safe paths (issue #504)
|
||||
* handle file:/// Subversion rootpaths as local roots (issue #446)
|
||||
* fix bug caused by trying to case-normalize anon usernames (issue #505)
|
||||
* speed up log handling by reusing tokenization results (issue #506)
|
||||
* add support for custom review log markup rules (issue #429)
|
||||
|
||||
Version 1.1.13 (released 23-Jan-2012)
|
||||
|
||||
* fix svndbadmin failure on deleted paths under Subversion 1.7 (issue #499)
|
||||
* fix annotation of files in svn roots with non-URI-safe paths
|
||||
* fix stray annotation warning in markup display of images
|
||||
* more gracefully handle attempts to display binary content (issue #501)
|
||||
|
||||
Version 1.1.12 (released 03-Nov-2011)
|
||||
|
||||
* fix path display in patch and certain diff views (issue #485)
|
||||
@@ -31,7 +66,6 @@ Version 1.1.9 (released 18-Feb-2011)
|
||||
* make revision log "extra pages" count configurable
|
||||
* fix Subversion 1.4.x revision log compatibility code regression
|
||||
* display sanitized error when authzfile is malformed
|
||||
* handle file:/// Subversion rootpaths as local roots (issue #446)
|
||||
* restore markup of URLs in file contents (issue #455)
|
||||
* optionally display last-committed metadata in roots view (issue #457)
|
||||
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
<blockquote>
|
||||
|
||||
<p><strong>Copyright © 1999-2011 The ViewCVS Group. All rights
|
||||
<p><strong>Copyright © 1999-2012 The ViewCVS Group. All rights
|
||||
reserved.</strong></p>
|
||||
|
||||
<p>By using ViewVC, you agree to the terms and conditions set forth
|
||||
@@ -62,6 +62,7 @@
|
||||
<li>March 18, 2009 — copyright years updated</li>
|
||||
<li>March 29, 2010 — copyright years updated</li>
|
||||
<li>February 18, 2011 — copyright years updated</li>
|
||||
<li>January 23, 2012 — copyright years updated</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2004-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2004-2012 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2004-2007 James Henstridge
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
@@ -153,7 +153,7 @@ class SvnRev:
|
||||
fsroot = self._get_root_for_rev(rev)
|
||||
|
||||
# find changes in the revision
|
||||
editor = svn.repos.RevisionChangeCollector(repo.fs, rev)
|
||||
editor = svn.repos.ChangeCollector(repo.fs, fsroot)
|
||||
e_ptr, e_baton = svn.delta.make_editor(editor)
|
||||
svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
|
||||
@@ -164,21 +164,33 @@ class SvnRev:
|
||||
continue
|
||||
|
||||
# deal with the change types we handle
|
||||
action = None
|
||||
base_root = None
|
||||
base_path = change.base_path
|
||||
if change.base_path:
|
||||
base_root = self._get_root_for_rev(change.base_rev)
|
||||
|
||||
if not change.path:
|
||||
|
||||
# figure out what kind of change this is, and get a diff
|
||||
# object for it. note that prior to 1.4 Subversion's
|
||||
# bindings didn't give us change.action, but that's okay
|
||||
# because back then deleted paths always had a change.path
|
||||
# of None.
|
||||
if hasattr(change, 'action') \
|
||||
and change.action == svn.repos.CHANGE_ACTION_DELETE:
|
||||
action = 'remove'
|
||||
elif not change.path:
|
||||
action = 'remove'
|
||||
elif change.added:
|
||||
action = 'add'
|
||||
else:
|
||||
action = 'change'
|
||||
|
||||
diffobj = svn.fs.FileDiff(base_root and base_root or None,
|
||||
base_root and change.base_path or None,
|
||||
change.path and fsroot or None,
|
||||
change.path and change.path or None)
|
||||
if action == 'remove':
|
||||
diffobj = svn.fs.FileDiff(base_root, base_path, None, None)
|
||||
else:
|
||||
diffobj = svn.fs.FileDiff(base_root, base_path,
|
||||
fsroot, change.path)
|
||||
|
||||
diff_fp = diffobj.get_pipe()
|
||||
plus, minus = _get_diff_counts(diff_fp)
|
||||
self.changes.append((path, action, plus, minus))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -86,8 +86,8 @@
|
||||
|
||||
## cvs_roots: Specifies each of the CVS roots on your system and
|
||||
## assigns names to them. Each root should be given by a "name: path"
|
||||
## value. Multiple roots should be separated by commas and can be
|
||||
## placed on separate lines.
|
||||
## value (where the path is an absolute filesystem path). Multiple roots
|
||||
## should be separated by commas and can be placed on separate lines.
|
||||
##
|
||||
## Example:
|
||||
## cvs_roots = cvsroot: /opt/cvs/repos1,
|
||||
@@ -97,8 +97,13 @@
|
||||
|
||||
## svn_roots: Specifies each of the Subversion roots (repositories) on
|
||||
## your system and assigns names to them. Each root should be given by
|
||||
## a "name: path" value. Multiple roots should be separated by commas
|
||||
## and can be placed on separate lines.
|
||||
## a "name: path" value (where the path is an absolute filesystem path).
|
||||
## Multiple roots should be separated by commas and can be placed on
|
||||
## separate lines.
|
||||
##
|
||||
## NOTE: ViewVC offers *experimental* support for displaying remote
|
||||
## Subversion repositories. Simply use the repository's URL instead
|
||||
## of a local filesystem path when defining the root.
|
||||
##
|
||||
## Example:
|
||||
## svn_roots = svnrepos: /opt/svn/,
|
||||
@@ -109,7 +114,8 @@
|
||||
## root_parents: Specifies a list of directories under which any
|
||||
## number of repositories may reside. You can specify multiple root
|
||||
## parents separated by commas or new lines, each of which is of the
|
||||
## form "path: type" (where type is either "cvs" or "svn").
|
||||
## form "path: type" (where the type is either "cvs" or "svn", and
|
||||
## the path is an absolute filesystem path).
|
||||
##
|
||||
## Rather than force you to add a new entry to 'cvs_roots' or
|
||||
## 'svn_roots' each time you create a new repository, ViewVC rewards
|
||||
@@ -385,6 +391,24 @@
|
||||
##
|
||||
#allowed_views = annotate, diff, markup, roots
|
||||
|
||||
## Comma-delimited list of MIME content types (with support for fnmatch-
|
||||
## style glob characters) which are considered not-human-readable and for
|
||||
## which ViewVC will neither generate links to, nor support the direct
|
||||
## display of, non-checkout views which carry the file's content (the
|
||||
## 'markup', 'annotate', 'diff', and 'patch' views).
|
||||
##
|
||||
## NOTE: Handling of this option is given priority over ViewVC's
|
||||
## longstanding support for showing web-friendly file formats -- even
|
||||
## binary ones such as "image/jpeg" and "image/gif" -- in the 'markup'
|
||||
## view. Thus, if you add "image/*" to this list, 'markup'-view
|
||||
## display of JPEG, GIF, and PNG images will be disabled.
|
||||
##
|
||||
## Example:
|
||||
## binary_mime_types = application/octet-stream, image/*, application/pdf,
|
||||
## application/vnd*, application/msword, audio/*
|
||||
#
|
||||
#binary_mime_types =
|
||||
|
||||
## authorizer: The name of the ViewVC authorizer plugin to use when
|
||||
## authorizing access to repository contents. This value must be the
|
||||
## name of a Python module addressable as vcauth.MODULENAME (most
|
||||
@@ -427,6 +451,26 @@
|
||||
##
|
||||
#mangle_email_addresses = 0
|
||||
|
||||
## custom_log_formatting: Specifies mappings of regular expressions to
|
||||
## substitution format strings used to URL-ize strings found in
|
||||
## revision log messages. Multiple mappings (specified as
|
||||
## REGEXP:FORMATSTRING) may be defined, separated by commas.
|
||||
##
|
||||
## NOTE: Due to a limitation of the configuration format, commas may
|
||||
## not be used in the regular expression portion of each mapping.
|
||||
## Commas "in the raw" may not be used in the format string portion,
|
||||
## either, but you can probably use the URI-encoded form of the comma
|
||||
## ("%2C") instead with no ill side-effects. If you must specify a
|
||||
## colon character in either the regular expression or the format
|
||||
## string, escape it with a preceding backslash ("\:").
|
||||
##
|
||||
## Example:
|
||||
## custom_log_formatting =
|
||||
## artf[0-9]+ : http://example.com/tracker?id=\0,
|
||||
## issue ([0-9]+) : http://example.com/bug?id=\1&opts=full%2csortby=id
|
||||
##
|
||||
#custom_log_formatting =
|
||||
|
||||
## default_file_view: "log", "co", or "markup"
|
||||
## Controls whether the default view for file URLs is a checkout view or
|
||||
## a log view. "log" is the default for backwards compatibility with old
|
||||
@@ -535,7 +579,7 @@
|
||||
## (Only works well for C source files, otherwise diff's heuristic falls short.)
|
||||
## ('-p' option to diff)
|
||||
##
|
||||
#hr_funout = 0
|
||||
#hr_funout = 1
|
||||
|
||||
## hr_ignore_white: Ignore whitespace (indendation and stuff) for human
|
||||
## readable diffs.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -74,7 +74,8 @@ class HTMLBlameSource:
|
||||
self.path_parts = path_parts
|
||||
self.diff_url = diff_url
|
||||
self.include_url = include_url
|
||||
self.annotation, self.revision = self.repos.annotate(path_parts, opt_rev)
|
||||
self.annotation, self.revision = self.repos.annotate(path_parts, opt_rev,
|
||||
True)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
item = self.annotation.__getitem__(idx)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -112,6 +112,8 @@ class Config:
|
||||
_force_multi_value = (
|
||||
# Configuration values with multiple, comma-separated values.
|
||||
'allowed_views',
|
||||
'binary_mime_types',
|
||||
'custom_log_formatting',
|
||||
'cvs_roots',
|
||||
'kv_files',
|
||||
'languages',
|
||||
@@ -398,7 +400,9 @@ class Config:
|
||||
self.options.allowed_views = ['annotate', 'diff', 'markup', 'roots']
|
||||
self.options.authorizer = None
|
||||
self.options.mangle_email_addresses = 0
|
||||
self.options.custom_log_formatting = []
|
||||
self.options.default_file_view = "log"
|
||||
self.options.binary_mime_types = []
|
||||
self.options.http_expiration_time = 600
|
||||
self.options.generate_etags = 1
|
||||
self.options.svn_ignore_mimetype = 0
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2008-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2008-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2012 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
|
||||
@@ -34,9 +34,9 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
# See if the admin wants us to do case normalization of usernames.
|
||||
self.force_username_case = params.get('force_username_case')
|
||||
if self.force_username_case == "upper":
|
||||
self.username = username.upper()
|
||||
self.username = username and string.upper(username) or username
|
||||
elif self.force_username_case == "lower":
|
||||
self.username = username.lower()
|
||||
self.username = username and string.lower(username) or username
|
||||
elif not self.force_username_case:
|
||||
self.username = username
|
||||
else:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -170,12 +170,19 @@ class Repository:
|
||||
Return value is a python file object
|
||||
"""
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
"""Return a list of annotate file content lines and a revision.
|
||||
def annotate(self, path_parts, rev, include_text=False):
|
||||
"""Return a list of Annotation object, sorted by their
|
||||
"line_number" components, which describe the lines of given
|
||||
version of a file.
|
||||
|
||||
The result is a list of Annotation objects, sorted by their
|
||||
line_number components.
|
||||
"""
|
||||
The file path is specified as a list of components, relative to
|
||||
the root of the repository. e.g. ["subdir1", "subdir2", "filename"]
|
||||
|
||||
rev is the revision of the item to return information about.
|
||||
|
||||
If include_text is true, populate the Annotation objects' "text"
|
||||
members with the corresponding line of file content; otherwise,
|
||||
leave that member set to None."""
|
||||
|
||||
def revinfo(self, rev):
|
||||
"""Return information about a global revision
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -14,6 +14,7 @@ import os.path
|
||||
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
assert os.path.isabs(rootpath)
|
||||
return os.path.normpath(rootpath)
|
||||
|
||||
|
||||
@@ -22,6 +23,7 @@ def expand_root_parent(parent_path):
|
||||
# "CVSROOT/config" is added the set of returned roots. Or, if the
|
||||
# PARENT_PATH itself contains a child "CVSROOT/config", then all its
|
||||
# subdirectories are returned as roots.
|
||||
assert os.path.isabs(parent_path)
|
||||
roots = {}
|
||||
subpaths = os.listdir(parent_path)
|
||||
cvsroot = os.path.exists(os.path.join(parent_path, "CVSROOT", "config"))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -332,13 +332,13 @@ class BinCVSRepository(BaseCVSRepository):
|
||||
args = rcs_args
|
||||
return popen.popen(cmd, args, mode, capture_err)
|
||||
|
||||
def annotate(self, path_parts, rev=None):
|
||||
def annotate(self, path_parts, rev=None, include_text=False):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
|
||||
from vclib.ccvs import blame
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev, include_text)
|
||||
return source, source.revision
|
||||
|
||||
def revinfo(self, rev):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -414,7 +414,7 @@ class CVSParser(rcsparse.Sink):
|
||||
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, rcs_file, opt_rev=None):
|
||||
def __init__(self, rcs_file, opt_rev=None, include_text=False):
|
||||
# Parse the CVS file
|
||||
parser = CVSParser()
|
||||
revision = parser.parse_cvs_file(rcs_file, opt_rev)
|
||||
@@ -428,6 +428,7 @@ class BlameSource:
|
||||
self.lines = lines
|
||||
self.num_lines = count
|
||||
self.parser = parser
|
||||
self.include_text = include_text
|
||||
|
||||
# keep track of where we are during an iteration
|
||||
self.idx = -1
|
||||
@@ -447,6 +448,8 @@ class BlameSource:
|
||||
line_number = idx + 1
|
||||
author = self.parser.revision_author[rev]
|
||||
thisline = self.lines[idx]
|
||||
if not self.include_text:
|
||||
thisline = None
|
||||
### TODO: Put a real date in here.
|
||||
item = vclib.Annotation(thisline, line_number, rev, prev_rev, author, None)
|
||||
self.last = item
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -142,11 +142,11 @@ class CCVSRepository(BaseCVSRepository):
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2,
|
||||
self.utilities.diff or 'diff', diff_args)
|
||||
|
||||
def annotate(self, path_parts, rev=None):
|
||||
def annotate(self, path_parts, rev=None, include_text=False):
|
||||
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a file."
|
||||
% (string.join(path_parts, "/")))
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
|
||||
source = blame.BlameSource(self.rcsfile(path_parts, 1), rev, include_text)
|
||||
return source, source.revision
|
||||
|
||||
def revinfo(self, rev):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -19,26 +19,46 @@ import urllib
|
||||
|
||||
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
def _canonicalize_path(path):
|
||||
import svn.core
|
||||
try:
|
||||
import svn.core
|
||||
return svn.core.svn_path_canonicalize(rootpath)
|
||||
except:
|
||||
if os.name == 'posix':
|
||||
rootpath_lower = rootpath.lower()
|
||||
if rootpath_lower in ['file://localhost',
|
||||
'file://localhost/',
|
||||
'file://',
|
||||
'file:///'
|
||||
]:
|
||||
return '/'
|
||||
if rootpath_lower.startswith('file://localhost/'):
|
||||
return os.path.normpath(urllib.unquote(rootpath[16:]))
|
||||
elif rootpath_lower.startswith('file:///'):
|
||||
return os.path.normpath(urllib.unquote(rootpath[7:]))
|
||||
if re.search(_re_url, rootpath):
|
||||
return rootpath.rstrip('/')
|
||||
return os.path.normpath(rootpath)
|
||||
return svn.core.svn_path_canonicalize(path)
|
||||
except AttributeError: # svn_path_canonicalize() appeared in 1.4.0 bindings
|
||||
# There's so much more that we *could* do here, but if we're
|
||||
# here at all its because there's a really old Subversion in
|
||||
# place, and those older Subversion versions cared quite a bit
|
||||
# less about the specifics of path canonicalization.
|
||||
if re.search(_re_url, path):
|
||||
return path.rstrip('/')
|
||||
else:
|
||||
return os.path.normpath(path)
|
||||
|
||||
|
||||
def canonicalize_rootpath(rootpath):
|
||||
# Try to canonicalize the rootpath using Subversion semantics.
|
||||
rootpath = _canonicalize_path(rootpath)
|
||||
|
||||
# ViewVC's support for local repositories is more complete and more
|
||||
# performant than its support for remote ones, so if we're on a
|
||||
# Unix-y system and we have a file:/// URL, convert it to a local
|
||||
# path instead.
|
||||
if os.name == 'posix':
|
||||
rootpath_lower = rootpath.lower()
|
||||
if rootpath_lower in ['file://localhost',
|
||||
'file://localhost/',
|
||||
'file://',
|
||||
'file:///'
|
||||
]:
|
||||
return '/'
|
||||
if rootpath_lower.startswith('file://localhost/'):
|
||||
rootpath = os.path.normpath(urllib.unquote(rootpath[16:]))
|
||||
elif rootpath_lower.startswith('file:///'):
|
||||
rootpath = os.path.normpath(urllib.unquote(rootpath[7:]))
|
||||
|
||||
# Ensure that we have an absolute path (or URL), and return.
|
||||
if not re.search(_re_url, rootpath):
|
||||
assert os.path.isabs(rootpath)
|
||||
return rootpath
|
||||
|
||||
|
||||
def expand_root_parent(parent_path):
|
||||
@@ -48,6 +68,7 @@ def expand_root_parent(parent_path):
|
||||
else:
|
||||
# Any subdirectories of PARENT_PATH which themselves have a child
|
||||
# "format" are returned as roots.
|
||||
assert os.path.isabs(parent_path)
|
||||
subpaths = os.listdir(parent_path)
|
||||
for rootname in subpaths:
|
||||
rootpath = os.path.join(parent_path, rootname)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -20,7 +20,10 @@ import re
|
||||
import tempfile
|
||||
import time
|
||||
import urllib
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, _compare_paths, _path_parts, _cleanup_path, _rev2optrev, _fix_subversion_exception, _split_revprops
|
||||
from svn_repos import Revision, SVNChangedPath, _datestr_to_date, \
|
||||
_compare_paths, _path_parts, _cleanup_path, \
|
||||
_rev2optrev, _fix_subversion_exception, \
|
||||
_split_revprops, _canonicalize_path
|
||||
from svn import core, delta, client, wc, ra
|
||||
|
||||
|
||||
@@ -51,12 +54,14 @@ def get_directory_props(ra_session, path, rev):
|
||||
props = ra.svn_ra_get_dir(ra_session, path, rev)
|
||||
return props
|
||||
|
||||
def client_log(url, start_rev, end_rev, log_limit, cross_copies,
|
||||
cb_func, ctx):
|
||||
def client_log(url, start_rev, end_rev, log_limit, include_changes,
|
||||
cross_copies, cb_func, ctx):
|
||||
include_changes = include_changes and 1 or 0
|
||||
cross_copies = cross_copies and 1 or 0
|
||||
try:
|
||||
client.svn_client_log4([url], start_rev, start_rev, end_rev,
|
||||
log_limit, 1, not cross_copies, 0, None,
|
||||
cb_func, ctx)
|
||||
log_limit, include_changes, not cross_copies,
|
||||
0, None, cb_func, ctx)
|
||||
except AttributeError:
|
||||
# Wrap old svn_log_message_receiver_t interface with a
|
||||
# svn_log_entry_t one.
|
||||
@@ -72,15 +77,14 @@ def client_log(url, start_rev, end_rev, log_limit, cross_copies,
|
||||
}
|
||||
cb_func(log_entry, pool)
|
||||
client.svn_client_log2([url], start_rev, end_rev, log_limit,
|
||||
1, not cross_copies, cb_convert, ctx)
|
||||
include_changes, not cross_copies, cb_convert, ctx)
|
||||
|
||||
### END COMPATABILITY CODE ###
|
||||
|
||||
|
||||
class LogCollector:
|
||||
### TODO: Make this thing authz-aware
|
||||
|
||||
def __init__(self, path, show_all_logs, lockinfo):
|
||||
def __init__(self, path, show_all_logs, lockinfo, access_check_func):
|
||||
# This class uses leading slashes for paths internally
|
||||
if not path:
|
||||
self.path = '/'
|
||||
@@ -89,8 +93,12 @@ class LogCollector:
|
||||
self.logs = []
|
||||
self.show_all_logs = show_all_logs
|
||||
self.lockinfo = lockinfo
|
||||
self.access_check_func = access_check_func
|
||||
self.done = False
|
||||
|
||||
def add_log(self, log_entry, pool):
|
||||
if self.done:
|
||||
return
|
||||
paths = log_entry.changed_paths
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
@@ -114,9 +122,13 @@ class LogCollector:
|
||||
if change.copyfrom_path:
|
||||
this_path = change.copyfrom_path + self.path[len(changed_path):]
|
||||
if self.show_all_logs or this_path:
|
||||
entry = Revision(revision, date, author, msg, None, self.lockinfo,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
if self.access_check_func is None \
|
||||
or self.access_check_func(self.path[1:], revision):
|
||||
entry = Revision(revision, date, author, msg, None, self.lockinfo,
|
||||
self.path[1:], None, None)
|
||||
self.logs.append(entry)
|
||||
else:
|
||||
self.done = True
|
||||
if this_path:
|
||||
self.path = this_path
|
||||
|
||||
@@ -252,14 +264,15 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
### rev here should be the last history revision of the URL
|
||||
client.svn_client_cat(core.Stream(stream), url, _rev2optrev(rev), self.ctx)
|
||||
core.svn_stream_close(stream)
|
||||
return SelfCleanFP(tmp_file), self._get_last_history_rev(path_parts, rev)
|
||||
lh_rev, c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
return SelfCleanFP(tmp_file), lh_rev
|
||||
|
||||
def listdir(self, path_parts, rev, options):
|
||||
path = self._getpath(path_parts)
|
||||
if self.itemtype(path_parts, rev) != vclib.DIR: # does auth-check
|
||||
raise vclib.Error("Path '%s' is not a directory." % path)
|
||||
rev = self._getrev(rev)
|
||||
entries = [ ]
|
||||
entries = []
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for name in dirents.keys():
|
||||
entry = dirents[name]
|
||||
@@ -267,8 +280,9 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
kind = vclib.DIR
|
||||
elif entry.kind == core.svn_node_file:
|
||||
kind = vclib.FILE
|
||||
if vclib.check_path_access(self, path_parts + [name], kind, rev):
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
else:
|
||||
kind = None
|
||||
entries.append(vclib.DirEntry(name, kind))
|
||||
return entries
|
||||
|
||||
def dirlogs(self, path_parts, rev, entries, options):
|
||||
@@ -279,11 +293,13 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
dirents, locks = self._get_dirents(path, rev)
|
||||
for entry in entries:
|
||||
entry_path_parts = path_parts + [entry.name]
|
||||
if not vclib.check_path_access(self, entry_path_parts, entry.kind, rev):
|
||||
dirent = dirents.get(entry.name, None)
|
||||
# dirents is authz-sanitized, so ensure the entry is found therein.
|
||||
if dirent is None:
|
||||
continue
|
||||
dirent = dirents[entry.name]
|
||||
# Get authz-sanitized revision metadata.
|
||||
entry.date, entry.author, entry.log, revprops, changes = \
|
||||
self.revinfo(dirent.created_rev)
|
||||
self._revinfo(dirent.created_rev)
|
||||
entry.rev = str(dirent.created_rev)
|
||||
entry.size = dirent.size
|
||||
entry.lockinfo = None
|
||||
@@ -297,28 +313,51 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
|
||||
# Use ls3 to fetch the lock status for this item.
|
||||
lockinfo = None
|
||||
basename = path_parts and path_parts[-1] or ""
|
||||
dirents, locks = list_directory(url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
if locks.has_key(basename):
|
||||
lockinfo = locks[basename].owner
|
||||
# If this is a file, fetch the lock status and size (as of REV)
|
||||
# for this item.
|
||||
lockinfo = size_in_rev = None
|
||||
if path_type == vclib.FILE:
|
||||
basename = path_parts[-1]
|
||||
list_url = self._geturl(self._getpath(path_parts[:-1]))
|
||||
dirents, locks = list_directory(list_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
if locks.has_key(basename):
|
||||
lockinfo = locks[basename].owner
|
||||
if dirents.has_key(basename):
|
||||
size_in_rev = dirents[basename].size
|
||||
|
||||
# Special handling for the 'svn_latest_log' scenario.
|
||||
### FIXME: Don't like this hack. We should just introduce
|
||||
### something more direct in the vclib API.
|
||||
if options.get('svn_latest_log', 0):
|
||||
dir_lh_rev, dir_c_rev = self._get_last_history_rev(path_parts, rev)
|
||||
date, author, log, revprops, changes = self._revinfo(dir_lh_rev)
|
||||
return [vclib.Revision(dir_lh_rev, str(dir_lh_rev), date, author,
|
||||
None, log, size_in_rev, lockinfo)]
|
||||
|
||||
def _access_checker(check_path, check_rev):
|
||||
return vclib.check_path_access(self, _path_parts(check_path),
|
||||
path_type, check_rev)
|
||||
|
||||
# It's okay if we're told to not show all logs on a file -- all
|
||||
# the revisions should match correctly anyway.
|
||||
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0), lockinfo)
|
||||
lc = LogCollector(path, options.get('svn_show_all_dir_logs', 0),
|
||||
lockinfo, _access_checker)
|
||||
|
||||
cross_copies = options.get('svn_cross_copies', 0)
|
||||
log_limit = 0
|
||||
if limit:
|
||||
log_limit = first + limit
|
||||
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit,
|
||||
client_log(url, _rev2optrev(rev), _rev2optrev(1), log_limit, 1,
|
||||
cross_copies, lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
revs.sort()
|
||||
prev = None
|
||||
for rev in revs:
|
||||
# Swap out revision info with stuff from the cache (which is
|
||||
# authz-sanitized).
|
||||
rev.date, rev.author, rev.log, revprops, changes \
|
||||
= self._revinfo(rev.number)
|
||||
rev.prev = prev
|
||||
prev = rev
|
||||
revs.reverse()
|
||||
@@ -338,13 +377,25 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
return pairs and pairs[0][1] or {}
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
def annotate(self, path_parts, rev, include_text=False):
|
||||
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)
|
||||
rev = self._getrev(rev)
|
||||
url = self._geturl(path)
|
||||
|
||||
# Examine logs for the file to determine the oldest revision we are
|
||||
# permitted to see.
|
||||
log_options = {
|
||||
'svn_cross_copies' : 1,
|
||||
'svn_show_all_dir_logs' : 1,
|
||||
}
|
||||
revs = self.itemlog(path_parts, rev, vclib.SORTBY_REV, 0, 0, log_options)
|
||||
oldest_rev = revs[-1].number
|
||||
|
||||
# Now calculate the annotation data. Note that we'll not
|
||||
# inherently trust the provided author and date, because authz
|
||||
# rules might necessitate that we strip that information out.
|
||||
blame_data = []
|
||||
|
||||
def _blame_cb(line_no, revision, author, date,
|
||||
@@ -352,21 +403,27 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
prev_rev = None
|
||||
if revision > 1:
|
||||
prev_rev = revision - 1
|
||||
blame_data.append(vclib.Annotation(line, line_no+1, revision, prev_rev,
|
||||
author, None))
|
||||
|
||||
client.svn_client_blame(url, _rev2optrev(1), _rev2optrev(rev),
|
||||
_blame_cb, self.ctx)
|
||||
|
||||
# If we have an invalid revision, clear the date and author
|
||||
# values. Otherwise, if we have authz filtering to do, use the
|
||||
# revinfo cache to do so.
|
||||
if revision < 0:
|
||||
date = author = None
|
||||
elif self.auth:
|
||||
date, author, msg, revprops, changes = self._revinfo(revision)
|
||||
|
||||
# Strip text if the caller doesn't want it.
|
||||
if not include_text:
|
||||
line = None
|
||||
blame_data.append(vclib.Annotation(line, line_no + 1, revision, prev_rev,
|
||||
author, date))
|
||||
|
||||
client.blame2(url, _rev2optrev(rev), _rev2optrev(oldest_rev),
|
||||
_rev2optrev(rev), _blame_cb, self.ctx)
|
||||
return blame_data, rev
|
||||
|
||||
def revinfo(self, rev):
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info:
|
||||
cached_info = self._revinfo_raw(rev)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return tuple(cached_info)
|
||||
return self._revinfo(rev, 1)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
@@ -381,7 +438,7 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, revprops, changes = self.revinfo(rev)
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
@@ -420,49 +477,84 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
def _geturl(self, path=None):
|
||||
if not path:
|
||||
return self.rootpath
|
||||
return self.rootpath + '/' + urllib.quote(path, "/*~")
|
||||
path = self.rootpath + '/' + urllib.quote(path)
|
||||
return _canonicalize_path(path)
|
||||
|
||||
def _get_dirents(self, path, rev):
|
||||
"""Return a 2-type of dirents and locks, possibly reading/writing
|
||||
from a local cache of that information."""
|
||||
from a local cache of that information. This functions performs
|
||||
authz checks, stripping out unreadable dirents."""
|
||||
|
||||
dir_url = self._geturl(path)
|
||||
path_parts = _path_parts(path)
|
||||
if path:
|
||||
key = str(rev) + '/' + path
|
||||
else:
|
||||
key = str(rev)
|
||||
|
||||
# Ensure that the cache gets filled...
|
||||
dirents_locks = self._dirent_cache.get(key)
|
||||
if not dirents_locks:
|
||||
dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
tmp_dirents, locks = list_directory(dir_url, _rev2optrev(rev),
|
||||
_rev2optrev(rev), 0, self.ctx)
|
||||
dirents = {}
|
||||
for name, dirent in tmp_dirents.items():
|
||||
dirent_parts = path_parts + [name]
|
||||
kind = dirent.kind
|
||||
if (kind == core.svn_node_dir or kind == core.svn_node_file) \
|
||||
and vclib.check_path_access(self, dirent_parts,
|
||||
kind == core.svn_node_dir \
|
||||
and vclib.DIR or vclib.FILE, rev):
|
||||
lh_rev, c_rev = self._get_last_history_rev(dirent_parts, rev)
|
||||
dirent.created_rev = lh_rev
|
||||
dirents[name] = dirent
|
||||
dirents_locks = [dirents, locks]
|
||||
self._dirent_cache[key] = dirents_locks
|
||||
|
||||
# ...then return the goodies from the cache.
|
||||
return dirents_locks[0], dirents_locks[1]
|
||||
|
||||
def _get_last_history_rev(self, path_parts, rev):
|
||||
"""Return the a 2-tuple which contains:
|
||||
- the last interesting revision equal to or older than REV in
|
||||
the history of PATH_PARTS.
|
||||
- the created_rev of of PATH_PARTS as of REV."""
|
||||
|
||||
path = self._getpath(path_parts)
|
||||
url = self._geturl(self._getpath(path_parts))
|
||||
optrev = _rev2optrev(rev)
|
||||
|
||||
# Get the last-changed-rev.
|
||||
revisions = []
|
||||
def _info_cb(path, info, pool, retval=revisions):
|
||||
revisions.append(info.last_changed_rev)
|
||||
client.svn_client_info(url, optrev, optrev, _info_cb, 0, self.ctx)
|
||||
return revisions[0]
|
||||
|
||||
def _revinfo_raw(self, rev):
|
||||
# return 5-tuple (date, author, msg, revprops, changes)
|
||||
optrev = _rev2optrev(rev)
|
||||
revs = []
|
||||
last_changed_rev = revisions[0]
|
||||
|
||||
# Now, this object might not have been directly edited since the
|
||||
# last-changed-rev, but it might have been the child of a copy.
|
||||
# To determine this, we'll run a potentially no-op log between
|
||||
# LAST_CHANGED_REV and REV.
|
||||
lc = LogCollector(path, 1, None, None)
|
||||
client_log(url, optrev, _rev2optrev(last_changed_rev), 1, 1, 0,
|
||||
lc.add_log, self.ctx)
|
||||
revs = lc.logs
|
||||
if revs:
|
||||
revs.sort()
|
||||
return revs[0].number, last_changed_rev
|
||||
else:
|
||||
return last_changed_rev, last_changed_rev
|
||||
|
||||
def _revinfo_fetch(self, rev, include_changed_paths=0):
|
||||
need_changes = include_changed_paths or self.auth
|
||||
revs = []
|
||||
|
||||
def _log_cb(log_entry, pool, retval=revs):
|
||||
### Subversion 1.5 and earlier didn't offer the 'changed_paths2'
|
||||
### hash, and in Subversion 1.6, it's offered but broken.
|
||||
try:
|
||||
changed_paths = log_entry.changed_paths2
|
||||
paths = (changed_paths or {}).keys()
|
||||
except:
|
||||
changed_paths = log_entry.changed_paths
|
||||
paths = (changed_paths or {}).keys()
|
||||
paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
# If Subversion happens to call us more than once, we choose not
|
||||
# to care.
|
||||
if retval:
|
||||
return
|
||||
|
||||
revision = log_entry.revision
|
||||
msg, author, date, revprops = _split_revprops(log_entry.revprops)
|
||||
action_map = { 'D' : vclib.DELETED,
|
||||
@@ -470,22 +562,42 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
'R' : vclib.REPLACED,
|
||||
'M' : vclib.MODIFIED,
|
||||
}
|
||||
|
||||
# Easy out: if we won't use the changed-path info, just return a
|
||||
# changes-less tuple.
|
||||
if not need_changes:
|
||||
return revs.append([date, author, msg, revprops, None])
|
||||
|
||||
# Subversion 1.5 and earlier didn't offer the 'changed_paths2'
|
||||
# hash, and in Subversion 1.6, it's offered but broken.
|
||||
try:
|
||||
changed_paths = log_entry.changed_paths2
|
||||
paths = (changed_paths or {}).keys()
|
||||
except:
|
||||
changed_paths = log_entry.changed_paths
|
||||
paths = (changed_paths or {}).keys()
|
||||
paths.sort(lambda a, b: _compare_paths(a, b))
|
||||
|
||||
# If we get this far, our caller needs changed-paths, or we need
|
||||
# them for authz-related sanitization.
|
||||
changes = []
|
||||
found_readable = found_unreadable = 0
|
||||
for path in paths:
|
||||
change = changed_paths[path]
|
||||
### svn_log_changed_path_t (which we might get instead of the
|
||||
### svn_log_changed_path2_t we'd prefer) doesn't have the
|
||||
### 'node_kind' member.
|
||||
|
||||
# svn_log_changed_path_t (which we might get instead of the
|
||||
# svn_log_changed_path2_t we'd prefer) doesn't have the
|
||||
# 'node_kind' member.
|
||||
pathtype = None
|
||||
if hasattr(change, 'node_kind'):
|
||||
if change.node_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif change.node_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
### svn_log_changed_path2_t only has the 'text_modified' and
|
||||
### 'props_modified' bits in Subversion 1.7 and beyond. And
|
||||
### svn_log_changed_path_t is without.
|
||||
|
||||
# svn_log_changed_path2_t only has the 'text_modified' and
|
||||
# 'props_modified' bits in Subversion 1.7 and beyond. And
|
||||
# svn_log_changed_path_t is without.
|
||||
text_modified = props_modified = 0
|
||||
if hasattr(change, 'text_modified'):
|
||||
if change.text_modified == core.svn_tristate_true:
|
||||
@@ -493,9 +605,10 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
if hasattr(change, 'props_modified'):
|
||||
if change.props_modified == core.svn_tristate_true:
|
||||
props_modified = 1
|
||||
### Wrong, diddily wrong wrong wrong. Can you say,
|
||||
### "Manufacturing data left and right because it hurts to
|
||||
### figure out the right stuff?"
|
||||
|
||||
# Wrong, diddily wrong wrong wrong. Can you say,
|
||||
# "Manufacturing data left and right because it hurts to
|
||||
# figure out the right stuff?"
|
||||
action = action_map.get(change.action, vclib.MODIFIED)
|
||||
if change.copyfrom_path and change.copyfrom_rev:
|
||||
is_copy = 1
|
||||
@@ -509,15 +622,16 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
base_path = path
|
||||
base_rev = revision - 1
|
||||
|
||||
### Check authz rules (we lie about the path type)
|
||||
# Check authz rules (sadly, we have to lie about the path type)
|
||||
parts = _path_parts(path)
|
||||
if vclib.check_path_access(self, parts, vclib.FILE, revision):
|
||||
if is_copy and base_path and (base_path != path):
|
||||
parts = _path_parts(base_path)
|
||||
if vclib.check_path_access(self, parts, vclib.FILE, base_rev):
|
||||
if not vclib.check_path_access(self, parts, vclib.FILE, base_rev):
|
||||
is_copy = 0
|
||||
base_path = None
|
||||
base_rev = None
|
||||
found_unreadable = 1
|
||||
changes.append(SVNChangedPath(path, revision, pathtype, base_path,
|
||||
base_rev, action, is_copy,
|
||||
text_modified, props_modified))
|
||||
@@ -525,16 +639,45 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
else:
|
||||
found_unreadable = 1
|
||||
|
||||
# If our caller doesn't want changed-path stuff, and we have
|
||||
# the info we need to make an authz determination already,
|
||||
# quit this loop and get on with it.
|
||||
if (not include_changed_paths) and found_unreadable and found_readable:
|
||||
break
|
||||
|
||||
# Filter unreadable information.
|
||||
if found_unreadable:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
revs.append([date, author, msg, revprops, changes])
|
||||
|
||||
client_log(self.rootpath, optrev, optrev, 1, 0, _log_cb, self.ctx)
|
||||
# Drop unrequested changes.
|
||||
if not include_changed_paths:
|
||||
changes = None
|
||||
|
||||
# Add this revision information to the "return" array.
|
||||
retval.append([date, author, msg, revprops, changes])
|
||||
|
||||
optrev = _rev2optrev(rev)
|
||||
client_log(self.rootpath, optrev, optrev, 1, need_changes, 0,
|
||||
_log_cb, self.ctx)
|
||||
return tuple(revs[0])
|
||||
|
||||
def _revinfo(self, rev, include_changed_paths=0):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
# Consult the revinfo cache first. If we don't have cached info,
|
||||
# or our caller wants changed paths and we don't have those for
|
||||
# this revision, go do the real work.
|
||||
rev = self._getrev(rev)
|
||||
cached_info = self._revinfo_cache.get(rev)
|
||||
if not cached_info \
|
||||
or (include_changed_paths and cached_info[4] is None):
|
||||
cached_info = self._revinfo_fetch(rev, include_changed_paths)
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return cached_info
|
||||
|
||||
##--- custom --##
|
||||
|
||||
def get_youngest_revision(self):
|
||||
@@ -552,23 +695,16 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
old_path = results[old_rev]
|
||||
except KeyError:
|
||||
raise vclib.ItemNotFound(path)
|
||||
|
||||
return _cleanup_path(old_path)
|
||||
old_path = _cleanup_path(old_path)
|
||||
old_path_parts = _path_parts(old_path)
|
||||
# Check access (lying about path types)
|
||||
if not vclib.check_path_access(self, old_path_parts, vclib.FILE, old_rev):
|
||||
raise vclib.ItemNotFound(path)
|
||||
return old_path
|
||||
|
||||
def created_rev(self, path, rev):
|
||||
# NOTE: We can't use svn_client_propget here because the
|
||||
# interfaces in that layer strip out the properties not meant for
|
||||
# human consumption (such as svn:entry:committed-rev, which we are
|
||||
# using here to get the created revision of PATH@REV).
|
||||
kind = ra.svn_ra_check_path(self.ra_session, path, rev)
|
||||
if kind == core.svn_node_none:
|
||||
raise vclib.ItemNotFound(_path_parts(path))
|
||||
elif kind == core.svn_node_dir:
|
||||
props = get_directory_props(self.ra_session, path, rev)
|
||||
elif kind == core.svn_node_file:
|
||||
fetched_rev, props = ra.svn_ra_get_file(self.ra_session, path, rev, None)
|
||||
return int(props.get(core.SVN_PROP_ENTRY_COMMITTED_REV,
|
||||
SVN_INVALID_REVNUM))
|
||||
lh_rev, c_rev = self._get_last_history_rev(_path_parts(path), rev)
|
||||
return lh_rev
|
||||
|
||||
def last_rev(self, path, peg_revision, limit_revision=None):
|
||||
"""Given PATH, known to exist in PEG_REVISION, find the youngest
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -22,6 +22,7 @@ import time
|
||||
import tempfile
|
||||
import popen
|
||||
import re
|
||||
import urllib
|
||||
from svn import fs, repos, core, client, delta
|
||||
|
||||
|
||||
@@ -42,7 +43,14 @@ def _fix_subversion_exception(e):
|
||||
e.apr_err = e[1]
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = e[0]
|
||||
|
||||
|
||||
### Pre-1.4 Subversion doesn't have svn_path_canonicalize()
|
||||
def _canonicalize_path(path):
|
||||
try:
|
||||
return core.svn_path_canonicalize(path)
|
||||
except AttributeError:
|
||||
return path
|
||||
|
||||
def _allow_all(root, path, pool):
|
||||
"""Generic authz_read_func that permits access to all paths"""
|
||||
return 1
|
||||
@@ -108,11 +116,16 @@ def _rev2optrev(rev):
|
||||
|
||||
def _rootpath2url(rootpath, path):
|
||||
rootpath = os.path.abspath(rootpath)
|
||||
if rootpath and rootpath[0] != '/':
|
||||
rootpath = '/' + rootpath
|
||||
drive, rootpath = os.path.splitdrive(rootpath)
|
||||
if os.sep != '/':
|
||||
rootpath = string.replace(rootpath, os.sep, '/')
|
||||
return 'file://' + string.join([rootpath, path], "/")
|
||||
rootpath = urllib.quote(rootpath)
|
||||
path = urllib.quote(path)
|
||||
if drive:
|
||||
url = 'file:///' + drive + rootpath + '/' + path
|
||||
else:
|
||||
url = 'file://' + rootpath + '/' + path
|
||||
return _canonicalize_path(url)
|
||||
|
||||
|
||||
# Given a dictionary REVPROPS of revision properties, pull special
|
||||
@@ -282,10 +295,11 @@ class FileContentsPipe:
|
||||
|
||||
|
||||
class BlameSource:
|
||||
def __init__(self, local_url, rev, first_rev, config_dir):
|
||||
def __init__(self, local_url, rev, first_rev, include_text, config_dir):
|
||||
self.idx = -1
|
||||
self.first_rev = first_rev
|
||||
self.blame_data = []
|
||||
self.include_text = include_text
|
||||
|
||||
ctx = client.svn_client_create_context()
|
||||
core.svn_config_ensure(config_dir)
|
||||
@@ -306,6 +320,8 @@ class BlameSource:
|
||||
prev_rev = None
|
||||
if rev > self.first_rev:
|
||||
prev_rev = rev - 1
|
||||
if not self.include_text:
|
||||
text = None
|
||||
self.blame_data.append(vclib.Annotation(text, line_no + 1, rev,
|
||||
prev_rev, author, None))
|
||||
|
||||
@@ -521,7 +537,7 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
fsroot = self._getroot(rev)
|
||||
return fs.node_proplist(fsroot, path)
|
||||
|
||||
def annotate(self, path_parts, rev):
|
||||
def annotate(self, path_parts, rev, include_text=False):
|
||||
path = self._getpath(path_parts)
|
||||
path_type = self.itemtype(path_parts, rev) # does auth-check
|
||||
if path_type != vclib.FILE:
|
||||
@@ -532,8 +548,8 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
{'svn_cross_copies': 1})
|
||||
youngest_rev, youngest_path = history[0]
|
||||
oldest_rev, oldest_path = history[-1]
|
||||
source = BlameSource(_rootpath2url(self.rootpath, path),
|
||||
youngest_rev, oldest_rev, self.config_dir)
|
||||
source = BlameSource(_rootpath2url(self.rootpath, path), youngest_rev,
|
||||
oldest_rev, include_text, self.config_dir)
|
||||
return source, youngest_rev
|
||||
|
||||
def revinfo(self, rev):
|
||||
@@ -635,6 +651,7 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
is_copy = 0
|
||||
change.base_path = None
|
||||
change.base_rev = None
|
||||
found_unreadable = 1
|
||||
changedpaths[path] = SVNChangedPath(path, rev, pathtype,
|
||||
change.base_path,
|
||||
change.base_rev, action,
|
||||
|
461
lib/viewvc.py
461
lib/viewvc.py
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
@@ -14,7 +14,7 @@
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
__version__ = '1.1.12'
|
||||
__version__ = '1.1.17'
|
||||
|
||||
# this comes from our library; measure the startup time
|
||||
import debug
|
||||
@@ -24,6 +24,7 @@ debug.t_start('imports')
|
||||
# standard modules that we know are in the path or builtin
|
||||
import sys
|
||||
import os
|
||||
import fnmatch
|
||||
import gzip
|
||||
import mimetypes
|
||||
import re
|
||||
@@ -1017,6 +1018,15 @@ def default_view(mime_type, cfg):
|
||||
return view_markup
|
||||
return view_checkout
|
||||
|
||||
def is_binary_file_mime_type(mime_type, cfg):
|
||||
"""Return True iff MIME_TYPE is set and matches one of the binary
|
||||
file mime type patterns in CFG."""
|
||||
if mime_type:
|
||||
for pattern in cfg.options.binary_mime_types:
|
||||
if fnmatch.fnmatch(mime_type, pattern):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1):
|
||||
"""Return an object holding common hrefs and a viewability flag used
|
||||
for various views of FILENAME at revision REV whose MIME type is
|
||||
@@ -1077,7 +1087,12 @@ def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1):
|
||||
params={'revision': rev},
|
||||
escape=1)
|
||||
|
||||
prefer_markup = default_view(mime_type, request.cfg) == view_markup
|
||||
is_binary_file = is_binary_file_mime_type(mime_type, request.cfg)
|
||||
if is_binary_file:
|
||||
download_text_href = annotate_href = view_href = None
|
||||
prefer_markup = False
|
||||
else:
|
||||
prefer_markup = default_view(mime_type, request.cfg) == view_markup
|
||||
|
||||
return _item(view_href=view_href,
|
||||
download_href=download_href,
|
||||
@@ -1099,6 +1114,29 @@ _re_rewrite_email = re.compile('([-a-zA-Z0-9_.\+]+)@'
|
||||
# Matches revision references
|
||||
_re_rewrite_svnrevref = re.compile(r'\b(r|rev #?|revision #?)([0-9]+)\b')
|
||||
|
||||
class ViewVCHtmlFormatterTokens:
|
||||
def __init__(self, tokens):
|
||||
self.tokens = tokens
|
||||
|
||||
def get_result(self, maxlen=0):
|
||||
"""Format the tokens per the registered set of formatters, and
|
||||
limited to MAXLEN visible characters (or unlimited if MAXLEN is
|
||||
0). Return a 3-tuple containing the formatted result string, the
|
||||
number of visible characters in the result string, and a boolean
|
||||
flag indicating whether or not S was truncated."""
|
||||
out = ''
|
||||
out_len = 0
|
||||
for token in self.tokens:
|
||||
chunk, chunk_len = token.converter(token.match, token.userdata, maxlen)
|
||||
out = out + chunk
|
||||
out_len = out_len + chunk_len
|
||||
if maxlen:
|
||||
maxlen = maxlen - chunk_len
|
||||
if maxlen <= 0:
|
||||
return out, out_len, 1
|
||||
return out, out_len, 0
|
||||
|
||||
|
||||
class ViewVCHtmlFormatter:
|
||||
"""Format a string as HTML-encoded output with customizable markup
|
||||
rules, for example turning strings that look like URLs into anchor links.
|
||||
@@ -1184,6 +1222,30 @@ class ViewVCHtmlFormatter:
|
||||
sapi.escape(trunc_s)), \
|
||||
len(trunc_s)
|
||||
|
||||
def format_custom_url(self, mobj, userdata, maxlen=0):
|
||||
"""Return a 2-tuple containing:
|
||||
- the text represented by MatchObject MOBJ, formatted as an
|
||||
linkified URL created by substituting match groups 0-9 into
|
||||
USERDATA (which is a format string that uses \N to
|
||||
represent the substitution locations) and with no more than
|
||||
MAXLEN characters in the non-HTML-tag portions. If MAXLEN
|
||||
is 0, there is no maximum.
|
||||
- the number of characters returned.
|
||||
"""
|
||||
format = userdata
|
||||
text = mobj.group(0)
|
||||
url = format
|
||||
for i in range(9):
|
||||
try:
|
||||
repl = mobj.group(i)
|
||||
except:
|
||||
repl = ''
|
||||
url = url.replace('\%d' % (i), repl)
|
||||
trunc_s = maxlen and text[:maxlen] or text
|
||||
return '<a href="%s">%s</a>' % (sapi.escape(url),
|
||||
sapi.escape(trunc_s)), \
|
||||
len(trunc_s)
|
||||
|
||||
def format_text(self, s, unused, maxlen=0):
|
||||
"""Return a 2-tuple containing:
|
||||
- the text S, HTML-escaped, containing no more than MAXLEN
|
||||
@@ -1216,20 +1278,14 @@ class ViewVCHtmlFormatter:
|
||||
"""
|
||||
out = ''
|
||||
out_len = 0
|
||||
for token in self._tokenize_text(s):
|
||||
chunk, chunk_len = token.converter(token.match, token.userdata, maxlen)
|
||||
out = out + chunk
|
||||
out_len = out_len + chunk_len
|
||||
if maxlen:
|
||||
maxlen = maxlen - chunk_len
|
||||
if maxlen <= 0:
|
||||
return out, out_len, 1
|
||||
return out, out_len, 0
|
||||
tokens = self.tokenize_text(s)
|
||||
return tokens.get_result()
|
||||
|
||||
def _entity_encode(self, s):
|
||||
return string.join(map(lambda x: '&#%d;' % (ord(x)), s), '')
|
||||
|
||||
def _tokenize_text(self, s):
|
||||
def tokenize_text(self, s):
|
||||
"""Return a ViewVCHtmlFormatterTokens object containing the tokens
|
||||
created when parsing the string S. Callers can use that object's
|
||||
get_result() function to retrieve HTML-formatted text.
|
||||
"""
|
||||
tokens = []
|
||||
# We could just have a "while s:" here instead of "for line: while
|
||||
# line:", but for really large log messages with heavy
|
||||
@@ -1276,36 +1332,80 @@ class ViewVCHtmlFormatter:
|
||||
converter=self.format_text,
|
||||
userdata=None))
|
||||
line = ''
|
||||
return tokens
|
||||
return ViewVCHtmlFormatterTokens(tokens)
|
||||
|
||||
def _entity_encode(self, s):
|
||||
return string.join(map(lambda x: '&#%d;' % (ord(x)), s), '')
|
||||
|
||||
|
||||
def format_log(request, log, maxlen=0, htmlize=1):
|
||||
if not log:
|
||||
return log
|
||||
class LogFormatter:
|
||||
def __init__(self, request, log):
|
||||
self.request = request
|
||||
self.log = log or ''
|
||||
self.tokens = None
|
||||
self.cache = {} # (maxlen, htmlize) => resulting_log
|
||||
|
||||
cfg = request.cfg
|
||||
if htmlize:
|
||||
lf = ViewVCHtmlFormatter()
|
||||
lf.add_formatter(_re_rewrite_url, lf.format_url)
|
||||
if request.roottype == 'svn':
|
||||
def revision_to_url(rev):
|
||||
return request.get_url(view_func=view_revision,
|
||||
params={'revision': rev},
|
||||
escape=1)
|
||||
lf.add_formatter(_re_rewrite_svnrevref, lf.format_svnrevref,
|
||||
revision_to_url)
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_truncated)
|
||||
elif cfg.options.mangle_email_addresses == 1:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_obfuscated)
|
||||
def get(self, maxlen=0, htmlize=1):
|
||||
cfg = self.request.cfg
|
||||
|
||||
# Prefer the cache.
|
||||
if self.cache.has_key((maxlen, htmlize)):
|
||||
return self.cache[(maxlen, htmlize)]
|
||||
|
||||
# If we are HTML-izing...
|
||||
if htmlize:
|
||||
# ...and we don't yet have ViewVCHtmlFormatter() object tokens...
|
||||
if not self.tokens:
|
||||
# ... then get them.
|
||||
lf = ViewVCHtmlFormatter()
|
||||
|
||||
# Rewrite URLs.
|
||||
lf.add_formatter(_re_rewrite_url, lf.format_url)
|
||||
|
||||
# Rewrite Subversion revision references.
|
||||
if self.request.roottype == 'svn':
|
||||
def revision_to_url(rev):
|
||||
return self.request.get_url(view_func=view_revision,
|
||||
params={'revision': rev},
|
||||
escape=1)
|
||||
lf.add_formatter(_re_rewrite_svnrevref, lf.format_svnrevref,
|
||||
revision_to_url)
|
||||
|
||||
# Rewrite email addresses.
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_truncated)
|
||||
elif cfg.options.mangle_email_addresses == 1:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email_obfuscated)
|
||||
else:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email)
|
||||
|
||||
# Add custom rewrite handling per configuration.
|
||||
for rule in cfg.options.custom_log_formatting:
|
||||
rule = rule.replace('\\:', '\x01')
|
||||
regexp, format = map(lambda x: x.strip(), rule.split(':', 1))
|
||||
regexp = regexp.replace('\x01', ':')
|
||||
format = format.replace('\x01', ':')
|
||||
lf.add_formatter(re.compile(regexp), lf.format_custom_url, format)
|
||||
|
||||
# Tokenize the log message.
|
||||
self.tokens = lf.tokenize_text(self.log)
|
||||
|
||||
# Use our formatter to ... you know ... format.
|
||||
log, log_len, truncated = self.tokens.get_result(maxlen)
|
||||
result_log = log + (truncated and '…' or '')
|
||||
|
||||
# But if we're not HTML-izing...
|
||||
else:
|
||||
lf.add_formatter(_re_rewrite_email, lf.format_email)
|
||||
log, log_len, truncated = lf.get_result(log, maxlen)
|
||||
return log + (truncated and '…' or '')
|
||||
else:
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
log = re.sub(_re_rewrite_email, r'\1@...', log)
|
||||
return maxlen and log[:maxlen] or log
|
||||
# ...then do much more simplistic transformations as necessary.
|
||||
log = self.log
|
||||
if cfg.options.mangle_email_addresses == 2:
|
||||
log = re.sub(_re_rewrite_email, r'\1@...', log)
|
||||
result_log = maxlen and log[:maxlen] or log
|
||||
|
||||
# In either case, populate the cache and return the results.
|
||||
self.cache[(maxlen, htmlize)] = result_log
|
||||
return result_log
|
||||
|
||||
|
||||
_time_desc = {
|
||||
1 : 'second',
|
||||
@@ -1547,28 +1647,29 @@ def markup_escaped_urls(s):
|
||||
return "<a href=\"%s\">%s</a>" % (unescaped_url, url)
|
||||
return re.sub(_re_rewrite_escaped_url, _url_repl, s)
|
||||
|
||||
def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||
mime_type, encoding):
|
||||
# Determine if we should use Pygments to highlight our output.
|
||||
# Reasons not to include a) being told not to by the configuration,
|
||||
# b) not being able to import the Pygments modules, and c) Pygments
|
||||
# not having a lexer for our file's format.
|
||||
blame_source = []
|
||||
if blame_data:
|
||||
for i in blame_data:
|
||||
i.text = sapi.escape(i.text)
|
||||
i.diff_href = None
|
||||
if i.prev_rev:
|
||||
i.diff_href = request.get_url(view_func=view_diff,
|
||||
params={'r1': i.prev_rev,
|
||||
'r2': i.rev},
|
||||
escape=1, partial=1)
|
||||
blame_source.append(i)
|
||||
blame_data = blame_source
|
||||
lexer = None
|
||||
use_pygments = cfg.options.enable_syntax_coloration
|
||||
first_line = None
|
||||
try:
|
||||
|
||||
def markup_stream(request, cfg, blame_data, file_lines, filename,
|
||||
mime_type, encoding, colorize):
|
||||
"""Return the contents of a versioned file as a list of
|
||||
vclib.Annotation objects, each representing one line of the file's
|
||||
contents. Use BLAME_DATA as the annotation information for the file
|
||||
if provided. Use FILE_LINES as the lines of file content text
|
||||
themselves. MIME_TYPE is the MIME content type of the file;
|
||||
ENCODING is its character encoding. If COLORIZE is true, attempt to
|
||||
apply syntax coloration to the file contents, and use the
|
||||
HTML-marked-up results as the text in the return vclib.Annotation
|
||||
objects."""
|
||||
|
||||
# Nothing to mark up? So be it.
|
||||
if not file_lines:
|
||||
return []
|
||||
|
||||
# Determine if we should (and can) use Pygments to highlight our
|
||||
# output. Reasons not to include a) being told not to by the
|
||||
# configuration, b) not being able to import the Pygments modules,
|
||||
# and c) Pygments not having a lexer for our file's format.
|
||||
pygments_lexer = None
|
||||
if colorize:
|
||||
from pygments import highlight
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import ClassNotFound, \
|
||||
@@ -1584,59 +1685,50 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||
encoding = 'chardet'
|
||||
except (SyntaxError, ImportError):
|
||||
pass
|
||||
try:
|
||||
lexer = get_lexer_for_mimetype(mime_type,
|
||||
encoding=encoding,
|
||||
tabsize=cfg.options.tabsize,
|
||||
stripnl=False)
|
||||
except ClassNotFound:
|
||||
try:
|
||||
lexer = get_lexer_for_filename(filename,
|
||||
encoding=encoding,
|
||||
tabsize=cfg.options.tabsize,
|
||||
stripnl=False)
|
||||
except ClassNotFound:
|
||||
try:
|
||||
first_line = fp.readline()
|
||||
lexer = guess_lexer(first_line)
|
||||
use_pygments = 1
|
||||
except ClassNotFound:
|
||||
use_pygments = 0
|
||||
except ImportError:
|
||||
use_pygments = 0
|
||||
|
||||
# If we aren't going to be highlighting anything, just return the
|
||||
# BLAME_SOURCE. If there's no blame_source, we'll generate a fake
|
||||
# one from the file contents we fetch with PATH and REV.
|
||||
if not use_pygments:
|
||||
if blame_source:
|
||||
class BlameSourceTabsizeWrapper:
|
||||
def __init__(self, blame_source, tabsize):
|
||||
self.blame_source = blame_source
|
||||
self.tabsize = cfg.options.tabsize
|
||||
def __getitem__(self, idx):
|
||||
item = self.blame_source.__getitem__(idx)
|
||||
item.text = string.expandtabs(item.text, self.tabsize)
|
||||
item.text = markup_escaped_urls(item.text)
|
||||
return item
|
||||
return BlameSourceTabsizeWrapper(blame_source, cfg.options.tabsize)
|
||||
else:
|
||||
lines = []
|
||||
line_no = 0
|
||||
while 1:
|
||||
if first_line is not None:
|
||||
line = first_line
|
||||
first_line = None
|
||||
else:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
line_no = line_no + 1
|
||||
line = sapi.escape(string.expandtabs(line, cfg.options.tabsize))
|
||||
line = markup_escaped_urls(line)
|
||||
item = vclib.Annotation(line, line_no, None, None, None, None)
|
||||
item.diff_href = None
|
||||
lines.append(item)
|
||||
# First, see if there's a Pygments lexer associated with MIME_TYPE.
|
||||
if mime_type:
|
||||
try:
|
||||
pygments_lexer = get_lexer_for_mimetype(mime_type,
|
||||
encoding=encoding,
|
||||
tabsize=cfg.options.tabsize,
|
||||
stripnl=False)
|
||||
except ClassNotFound:
|
||||
pygments_lexer = None
|
||||
|
||||
# If we've no lexer thus far, try to find one based on the FILENAME.
|
||||
if not pygments_lexer:
|
||||
try:
|
||||
pygments_lexer = get_lexer_for_filename(filename,
|
||||
encoding=encoding,
|
||||
tabsize=cfg.options.tabsize,
|
||||
stripnl=False)
|
||||
except ClassNotFound:
|
||||
pygments_lexer = None
|
||||
|
||||
# Still no lexer? If we've reason to believe this is a text
|
||||
# file, try to guess the lexer based on the file's content.
|
||||
if not pygments_lexer and is_text(mime_type) and file_lines:
|
||||
try:
|
||||
pygments_lexer = guess_lexer(file_lines[0])
|
||||
except ClassNotFound:
|
||||
pygments_lexer = None
|
||||
|
||||
# If we aren't highlighting, just return an amalgamation of the
|
||||
# BLAME_DATA (if any) and the FILE_LINES.
|
||||
if not pygments_lexer:
|
||||
lines = []
|
||||
for i in range(len(file_lines)):
|
||||
line = file_lines[i]
|
||||
line = sapi.escape(string.expandtabs(line, cfg.options.tabsize))
|
||||
line = markup_escaped_urls(line)
|
||||
if blame_data:
|
||||
blame_item = blame_data[i]
|
||||
blame_item.text = line
|
||||
else:
|
||||
blame_item = vclib.Annotation(line, i + 1, None, None, None, None)
|
||||
blame_item.diff_href = None
|
||||
lines.append(blame_item)
|
||||
return lines
|
||||
|
||||
# If we get here, we're highlighting something.
|
||||
@@ -1660,8 +1752,9 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||
item.diff_href = None
|
||||
self.blame_data.append(item)
|
||||
self.line_no = self.line_no + 1
|
||||
ps = PygmentsSink(blame_source)
|
||||
highlight((first_line or '') + fp.read(), lexer,
|
||||
|
||||
ps = PygmentsSink(blame_data)
|
||||
highlight(string.join(file_lines, ''), pygments_lexer,
|
||||
HtmlFormatter(nowrap=True,
|
||||
classprefix="pygments-",
|
||||
encoding='utf-8'), ps)
|
||||
@@ -1700,7 +1793,8 @@ def get_itemprops(request, path_parts, rev):
|
||||
propnames.sort()
|
||||
props = []
|
||||
for name in propnames:
|
||||
value = format_log(request, itemprops[name])
|
||||
lf = LogFormatter(request, itemprops[name])
|
||||
value = lf.get(maxlen=0, htmlize=1)
|
||||
undisplayable = ezt.boolean(0)
|
||||
# skip non-utf8 property names
|
||||
try:
|
||||
@@ -1752,6 +1846,11 @@ def markup_or_annotate(request, is_annotate):
|
||||
revision = None
|
||||
mime_type, encoding = calculate_mime_type(request, path, rev)
|
||||
|
||||
# Is this display blocked by 'binary_mime_types' configuration?
|
||||
if is_binary_file_mime_type(mime_type, cfg):
|
||||
raise debug.ViewVCException('Display of binary file content disabled '
|
||||
'by configuration', '403 Forbidden')
|
||||
|
||||
# Is this a viewable image type?
|
||||
if is_viewable_image(mime_type) \
|
||||
and 'co' in cfg.options.allowed_views:
|
||||
@@ -1759,34 +1858,71 @@ def markup_or_annotate(request, is_annotate):
|
||||
fp.close()
|
||||
if check_freshness(request, None, revision, weak=1):
|
||||
return
|
||||
annotation = 'binary'
|
||||
if is_annotate:
|
||||
annotation = 'binary'
|
||||
image_src_href = request.get_url(view_func=view_checkout,
|
||||
params={'revision': rev}, escape=1)
|
||||
|
||||
# Not a viewable image.
|
||||
else:
|
||||
blame_source = None
|
||||
blame_data = None
|
||||
|
||||
# If this was an annotation request, try to annotate this file.
|
||||
# If something goes wrong, that's okay -- we'll gracefully revert
|
||||
# to a plain markup display.
|
||||
if is_annotate:
|
||||
# Try to annotate this file, but don't croak if we fail.
|
||||
try:
|
||||
blame_source, revision = request.repos.annotate(path, rev)
|
||||
annotation = 'annotated'
|
||||
blame_source, revision = request.repos.annotate(path, rev, False)
|
||||
if check_freshness(request, None, revision, weak=1):
|
||||
return
|
||||
# Create BLAME_DATA list from BLAME_SOURCE, adding diff_href
|
||||
# items to each relevant "line".
|
||||
blame_data = []
|
||||
for item in blame_source:
|
||||
item.diff_href = None
|
||||
if item.prev_rev:
|
||||
item.diff_href = request.get_url(view_func=view_diff,
|
||||
params={'r1': item.prev_rev,
|
||||
'r2': item.rev},
|
||||
escape=1, partial=1)
|
||||
blame_data.append(item)
|
||||
annotation = 'annotated'
|
||||
except vclib.NonTextualFileContents:
|
||||
annotation = 'binary'
|
||||
except:
|
||||
annotation = 'error'
|
||||
|
||||
# Grab the file contents.
|
||||
fp, revision = request.repos.openfile(path, rev, {'cvs_oldkeywords' : 1})
|
||||
if check_freshness(request, None, revision, weak=1):
|
||||
fp.close()
|
||||
return
|
||||
lines = markup_stream_pygments(request, cfg, blame_source, fp,
|
||||
path[-1], mime_type, encoding)
|
||||
file_lines = fp.readlines()
|
||||
fp.close()
|
||||
|
||||
data = common_template_data(request, revision)
|
||||
# Do we have a differing number of file content lines and
|
||||
# annotation items? That's no good. Call it an error and don't
|
||||
# bother attempting the annotation display.
|
||||
if blame_data and (len(file_lines) != len(blame_data)):
|
||||
annotation = 'error'
|
||||
blame_data = None
|
||||
|
||||
# Try to markup the file contents/annotation. If we get an error
|
||||
# and we were colorizing the stream, try once more without the
|
||||
# colorization enabled.
|
||||
colorize = cfg.options.enable_syntax_coloration
|
||||
try:
|
||||
lines = markup_stream(request, cfg, blame_data, file_lines,
|
||||
path[-1], mime_type, encoding, colorize)
|
||||
except:
|
||||
if colorize:
|
||||
lines = markup_stream(request, cfg, blame_data, file_lines,
|
||||
path[-1], mime_type, encoding, False)
|
||||
else:
|
||||
raise debug.ViewVCException('Error displaying file contents',
|
||||
'500 Internal Server Error')
|
||||
|
||||
data = common_template_data(request, revision, mime_type)
|
||||
data.merge(ezt.TemplateData({
|
||||
'mime_type' : mime_type,
|
||||
'log' : None,
|
||||
@@ -1810,14 +1946,19 @@ def markup_or_annotate(request, is_annotate):
|
||||
}))
|
||||
|
||||
if cfg.options.show_log_in_markup:
|
||||
options = {'svn_latest_log': 1} ### FIXME: No longer needed?
|
||||
options = {
|
||||
'svn_latest_log': 1, ### FIXME: Use of this magical value is uncool.
|
||||
'svn_cross_copies': 1,
|
||||
}
|
||||
revs = request.repos.itemlog(path, revision, vclib.SORTBY_REV,
|
||||
0, 1, options)
|
||||
entry = revs[-1]
|
||||
lf = LogFormatter(request, entry.log)
|
||||
|
||||
data['date'] = make_time_string(entry.date, cfg)
|
||||
data['author'] = entry.author
|
||||
data['changed'] = entry.changed
|
||||
data['log'] = format_log(request, entry.log)
|
||||
data['log'] = lf.get(maxlen=0, htmlize=1)
|
||||
data['size'] = entry.size
|
||||
|
||||
if entry.date is not None:
|
||||
@@ -2053,9 +2194,11 @@ def view_directory(request):
|
||||
row.date = make_time_string(file.date, cfg)
|
||||
row.ago = html_time(request, file.date)
|
||||
if cfg.options.show_logs:
|
||||
row.log = format_log(request, file.log)
|
||||
row.short_log = format_log(request, file.log,
|
||||
maxlen=cfg.options.short_log_len)
|
||||
debug.t_start("dirview_logformat")
|
||||
lf = LogFormatter(request, file.log)
|
||||
row.log = lf.get(maxlen=0, htmlize=1)
|
||||
row.short_log = lf.get(maxlen=cfg.options.short_log_len, htmlize=1)
|
||||
debug.t_end("dirview_logformat")
|
||||
row.lockinfo = file.lockinfo
|
||||
row.anchor = request.server.escape(file.name)
|
||||
row.name = request.server.escape(file.name)
|
||||
@@ -2408,6 +2551,7 @@ def view_log(request):
|
||||
sortby = vclib.SORTBY_DEFAULT
|
||||
|
||||
first = last = 0
|
||||
log_pagestart = None
|
||||
if cfg.options.log_pagesize:
|
||||
log_pagestart = int(request.query_dict.get('log_pagestart', 0))
|
||||
total = cfg.options.log_pagesextra * cfg.options.log_pagesize
|
||||
@@ -2432,7 +2576,6 @@ def view_log(request):
|
||||
entry.ago = None
|
||||
if rev.date is not None:
|
||||
entry.ago = html_time(request, rev.date, 1)
|
||||
entry.log = format_log(request, rev.log or '')
|
||||
entry.size = rev.size
|
||||
entry.lockinfo = rev.lockinfo
|
||||
entry.branch_point = None
|
||||
@@ -2440,6 +2583,9 @@ def view_log(request):
|
||||
entry.orig_path = None
|
||||
entry.copy_path = None
|
||||
|
||||
lf = LogFormatter(request, rev.log or '')
|
||||
entry.log = lf.get(maxlen=0, htmlize=1)
|
||||
|
||||
entry.view_href = None
|
||||
entry.download_href = None
|
||||
entry.download_text_href = None
|
||||
@@ -2529,7 +2675,8 @@ def view_log(request):
|
||||
if selected_rev != entry.rev:
|
||||
entry.sel_for_diff_href = \
|
||||
request.get_url(view_func=view_log,
|
||||
params={'r1': entry.rev},
|
||||
params={'r1': entry.rev,
|
||||
'log_pagestart': log_pagestart},
|
||||
escape=1)
|
||||
if entry.prev is not None:
|
||||
entry.diff_to_prev_href = \
|
||||
@@ -2670,7 +2817,9 @@ def view_log(request):
|
||||
|
||||
if cfg.options.log_pagesize:
|
||||
data['log_paging_action'], data['log_paging_hidden_values'] = \
|
||||
request.get_form(params={'log_pagestart': None})
|
||||
request.get_form(params={'log_pagestart': None,
|
||||
'r1': selected_rev,
|
||||
})
|
||||
data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0))
|
||||
data['entries'] = paging_sws(data, 'entries', data['log_pagestart'],
|
||||
'rev', cfg.options.log_pagesize,
|
||||
@@ -2936,7 +3085,7 @@ class DiffSource:
|
||||
return _item(type='header',
|
||||
line_info_left=match.group(1),
|
||||
line_info_right=match.group(2),
|
||||
line_info_extra=match.group(3))
|
||||
line_info_extra=self._format_text(match.group(3)))
|
||||
|
||||
if line[0] == '\\':
|
||||
# \ No newline at end of file
|
||||
@@ -3151,6 +3300,13 @@ def view_patch(request):
|
||||
query_dict = request.query_dict
|
||||
p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request)
|
||||
|
||||
mime_type1, encoding1 = calculate_mime_type(request, p1, rev1)
|
||||
mime_type2, encoding2 = calculate_mime_type(request, p2, rev2)
|
||||
if is_binary_file_mime_type(mime_type1, cfg) or \
|
||||
is_binary_file_mime_type(mime_type2, cfg):
|
||||
raise debug.ViewVCException('Display of binary file content disabled '
|
||||
'by configuration', '403 Forbidden')
|
||||
|
||||
# In the absence of a format dictation in the CGI params, we'll let
|
||||
# use the configured diff format, allowing 'c' to mean 'c' and
|
||||
# anything else to mean 'u'.
|
||||
@@ -3191,6 +3347,13 @@ def view_diff(request):
|
||||
query_dict = request.query_dict
|
||||
p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request)
|
||||
|
||||
mime_type1, encoding1 = calculate_mime_type(request, p1, rev1)
|
||||
mime_type2, encoding2 = calculate_mime_type(request, p2, rev2)
|
||||
if is_binary_file_mime_type(mime_type1, cfg) or \
|
||||
is_binary_file_mime_type(mime_type2, cfg):
|
||||
raise debug.ViewVCException('Display of binary file content disabled '
|
||||
'by configuration', '403 Forbidden')
|
||||
|
||||
# since templates are in use and subversion allows changes to the dates,
|
||||
# we can't provide a strong etag
|
||||
if check_freshness(request, None, '%s-%s' % (rev1, rev2), weak=1):
|
||||
@@ -3288,7 +3451,8 @@ def view_diff(request):
|
||||
fvi = get_file_view_info(request, path_left, rev1)
|
||||
left = _item(date=make_time_string(log_entry1.date, cfg),
|
||||
author=log_entry1.author,
|
||||
log=format_log(request, log_entry1.log),
|
||||
log=LogFormatter(request,
|
||||
log_entry1.log).get(maxlen=0, htmlize=1),
|
||||
size=log_entry1.size,
|
||||
ago=ago1,
|
||||
path=path_left,
|
||||
@@ -3304,7 +3468,8 @@ def view_diff(request):
|
||||
fvi = get_file_view_info(request, path_right, rev2)
|
||||
right = _item(date=make_time_string(log_entry2.date, cfg),
|
||||
author=log_entry2.author,
|
||||
log=format_log(request, log_entry2.log),
|
||||
log=LogFormatter(request,
|
||||
log_entry2.log).get(maxlen=0, htmlize=1),
|
||||
size=log_entry2.size,
|
||||
ago=ago2,
|
||||
path=path_right,
|
||||
@@ -3552,7 +3717,8 @@ def view_revision(request):
|
||||
propnames.sort()
|
||||
props = []
|
||||
for name in propnames:
|
||||
value = format_log(request, revprops[name])
|
||||
lf = LogFormatter(request, revprops[name])
|
||||
value = lf.get(maxlen=0, htmlize=1)
|
||||
undisplayable = ezt.boolean(0)
|
||||
# skip non-utf8 property names
|
||||
try:
|
||||
@@ -3675,13 +3841,14 @@ def view_revision(request):
|
||||
escape=1)
|
||||
jump_rev_action, jump_rev_hidden_values = \
|
||||
request.get_form(params={'revision': None})
|
||||
|
||||
|
||||
lf = LogFormatter(request, msg)
|
||||
data = common_template_data(request)
|
||||
data.merge(ezt.TemplateData({
|
||||
'rev' : str(rev),
|
||||
'author' : author,
|
||||
'date' : date_str,
|
||||
'log' : format_log(request, msg),
|
||||
'log' : lf.get(maxlen=0, htmlize=1),
|
||||
'properties' : props,
|
||||
'ago' : date is not None and html_time(request, date, 1) or None,
|
||||
'changes' : changes,
|
||||
@@ -4039,9 +4206,10 @@ def build_commit(request, files, max_files, dir_strip, format):
|
||||
commit.log = None
|
||||
commit.short_log = None
|
||||
else:
|
||||
commit.log = format_log(request, desc, 0, format != 'rss')
|
||||
commit.short_log = format_log(request, desc, cfg.options.short_log_len,
|
||||
format != 'rss')
|
||||
lf = LogFormatter(request, desc)
|
||||
htmlize = (format != 'rss')
|
||||
commit.log = lf.get(maxlen=0, htmlize=htmlize)
|
||||
commit.short_log = lf.get(maxlen=cfg.options.short_log_len, htmlize=htmlize)
|
||||
commit.author = request.server.escape(author)
|
||||
commit.rss_date = make_rss_time_string(date, request.cfg)
|
||||
if request.roottype == 'svn':
|
||||
@@ -4335,8 +4503,9 @@ def list_roots(request):
|
||||
date, author, msg, revprops, changes = repos.revinfo(youngest_rev)
|
||||
date_str = make_time_string(date, cfg)
|
||||
ago = html_time(request, date)
|
||||
log = format_log(request, msg)
|
||||
short_log = format_log(request, msg, maxlen=cfg.options.short_log_len)
|
||||
lf = LogFormatter(request, msg)
|
||||
log = lf.get(maxlen=0, htmlize=1)
|
||||
short_log = lf.get(maxlen=cfg.options.short_log_len, htmlize=1)
|
||||
lastmod = _item(ago=ago, author=author, date=date_str, log=log,
|
||||
short_log=short_log, rev=str(youngest_rev))
|
||||
except:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -92,13 +92,15 @@ numbers, and not literal):
|
||||
Also, drop a copy of the archive files into the root directory of
|
||||
the viewvc.org website (unversioned).
|
||||
|
||||
15. On trunk, update the websites (both the viewvc.org/ and www/ ones)
|
||||
to refer to the new release files, and copy the CHANGES for the
|
||||
new release into trunk's CHANGES file.
|
||||
15. Update the Tigris.org website (^/trunk/www/index.html) to refer to
|
||||
the new release files and commit.
|
||||
|
||||
16. Edit the file 'lib/viewvc.py' again, incrementing the patch number
|
||||
assigned to the __version__ variable. Add a new empty block in
|
||||
the branch's CHANGES file. Commit your changes:
|
||||
svn ci -m "Bump latest advertised release."
|
||||
|
||||
16. Back on the release branch, edit the file 'lib/viewvc.py' again,
|
||||
incrementing the patch number assigned to the __version__
|
||||
variable. Add a new empty block in the branch's CHANGES file.
|
||||
Commit your changes:
|
||||
|
||||
svn ci -m "Begin a new release cycle."
|
||||
|
||||
@@ -111,5 +113,13 @@ numbers, and not literal):
|
||||
http://viewvc.tigris.org/issues/editmilestones.cgi?component=viewvc&action=add
|
||||
|
||||
18. Send to the announce@ list a message explaining all the cool new
|
||||
features, and post similar announcements to other places interested
|
||||
in this sort of stuff, such as Freshmeat (http://www.freshmeat.net).
|
||||
features.
|
||||
|
||||
http://viewvc.tigris.org/ds/viewForumSummary.do?dsForumId=4253
|
||||
|
||||
19. Post a new release notification at Freecode.
|
||||
|
||||
https://freecode.com/projects/viewvc/releases/new
|
||||
|
||||
20. Merge CHANGES for this release into the CHANGES file for newer
|
||||
release lines and commit.
|
||||
|
@@ -9,7 +9,11 @@
|
||||
[# ------------------------------------------------------------------------- ]
|
||||
|
||||
[# setup page definitions]
|
||||
[define page_title]Contents of /[where][end]
|
||||
[is annotation "annotated"]
|
||||
[define page_title]Annotation of /[where][end]
|
||||
[else]
|
||||
[define page_title]Contents of /[where][end]
|
||||
[end]
|
||||
[define help_href][docroot]/help_rootview.html[end]
|
||||
[# end]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: python -*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2012 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
|
||||
|
Reference in New Issue
Block a user