mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a800c61089 | ||
![]() |
391f7d8237 | ||
![]() |
141cf5ff10 | ||
![]() |
bfe148eb45 | ||
![]() |
6fb28f2198 | ||
![]() |
02cc53d34b | ||
![]() |
5928918da4 | ||
![]() |
2e9f84427c | ||
![]() |
df599031a0 | ||
![]() |
6311c93298 | ||
![]() |
b982cccbcd | ||
![]() |
33b8224714 | ||
![]() |
2f05f570b1 | ||
![]() |
d737657e1d | ||
![]() |
3d5294635e | ||
![]() |
75c3fc2346 | ||
![]() |
80bce159af | ||
![]() |
7ff9b84ee1 | ||
![]() |
b6acd3c114 | ||
![]() |
fa5d40caa3 | ||
![]() |
a9cbd4c6a6 | ||
![]() |
882cdaa46b | ||
![]() |
58e64cb28d | ||
![]() |
779e67653b | ||
![]() |
0381a772b7 | ||
![]() |
048b2c8033 | ||
![]() |
e354ab302c | ||
![]() |
5f931c6cf1 | ||
![]() |
576837351a | ||
![]() |
6084ccf877 |
23
CHANGES
23
CHANGES
@@ -1,9 +1,30 @@
|
||||
Version 1.1.10 (released 15-Mar-2011)
|
||||
|
||||
* fix stack trace in Subversion revision info logic (issue #475, issue #476)
|
||||
|
||||
Version 1.1.9 (released 18-Feb-2011)
|
||||
|
||||
* vcauth universal access determinations (issue #425)
|
||||
* rework svn revision info cache for performance
|
||||
* 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)
|
||||
|
||||
Version 1.1.8 (released 02-Dec-2010)
|
||||
|
||||
* fix slowness triggered by allow_compress=1 configuration (issue #467)
|
||||
* allow use of 'fcrypt' for Windows standalone.py authn support (issue #471)
|
||||
* yield more useful error on directory markup/annotate request (issue #472)
|
||||
|
||||
Version 1.1.7 (released 09-Sep-2010)
|
||||
|
||||
* display Subversion revision properties in the revision view (issue #453)
|
||||
* fix exception in 'standalone.py -r REPOS' when run without a config file
|
||||
* fix standalone.py server root deployments (--script-alias='')
|
||||
* add rudimentary Basic authentication support to standalone.py (issue #49)
|
||||
* add Basic authentication support to standalone.py (Unix only) (issue #49)
|
||||
* fix obscure "unexpected NULL parent pool" Subversion bindings error
|
||||
* enable path info / link display in remote Subversion root revision view
|
||||
* fix vhost name case handling inconsistency (issue #466)
|
||||
|
4
INSTALL
4
INSTALL
@@ -19,7 +19,7 @@ Congratulations on getting this far. :-)
|
||||
|
||||
For CVS Support:
|
||||
|
||||
* Python 1.5.2 or later
|
||||
* Python 1.5.2 or later (sorry, no 3.x support yet)
|
||||
(http://www.python.org/)
|
||||
* RCS, Revision Control System
|
||||
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
|
||||
@@ -30,7 +30,7 @@ Congratulations on getting this far. :-)
|
||||
|
||||
For Subversion Support:
|
||||
|
||||
* Python 2.0 or later
|
||||
* Python 2.0 or later (sorry, no 3.x support yet)
|
||||
(http://www.python.org/)
|
||||
* Subversion, Version Control System, 1.3.1 or later
|
||||
(binary installation and Python bindings)
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
<blockquote>
|
||||
|
||||
<p><strong>Copyright © 1999-2010 The ViewCVS Group. All rights
|
||||
<p><strong>Copyright © 1999-2011 The ViewCVS Group. All rights
|
||||
reserved.</strong></p>
|
||||
|
||||
<p>By using ViewVC, you agree to the terms and conditions set forth
|
||||
@@ -61,6 +61,7 @@
|
||||
<li>February 22, 2008 — copyright years updated</li>
|
||||
<li>March 18, 2009 — copyright years updated</li>
|
||||
<li>March 29, 2010 — copyright years updated</li>
|
||||
<li>February 18, 2011 — copyright years updated</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
@@ -42,7 +42,6 @@ import rfc822
|
||||
import socket
|
||||
import select
|
||||
import base64
|
||||
import crypt
|
||||
import BaseHTTPServer
|
||||
|
||||
if LIBRARY_DIR:
|
||||
@@ -55,6 +54,26 @@ import viewvc
|
||||
import compat; compat.for_standalone()
|
||||
|
||||
|
||||
# The 'crypt' module is only available on Unix platforms. We'll try
|
||||
# to use 'fcrypt' if it's available (for more information, see
|
||||
# http://carey.geek.nz/code/python-fcrypt/).
|
||||
has_crypt = False
|
||||
try:
|
||||
import crypt
|
||||
has_crypt = True
|
||||
def _check_passwd(user_passwd, real_passwd):
|
||||
return real_passwd == crypt.crypt(user_passwd, real_passwd[:2])
|
||||
except ImportError:
|
||||
try:
|
||||
import fcrypt
|
||||
has_crypt = True
|
||||
def _check_passwd(user_passwd, real_passwd):
|
||||
return real_passwd == fcrypt.crypt(user_passwd, real_passwd[:2])
|
||||
except ImportError:
|
||||
def _check_passwd(user_passwd, real_passwd):
|
||||
return False
|
||||
|
||||
|
||||
class Options:
|
||||
port = 49152 # default TCP/IP port used for the server
|
||||
start_gui = 0 # No GUI unless requested.
|
||||
@@ -182,7 +201,7 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
for line in lines:
|
||||
file_user, file_pass = string.split(line.rstrip(), ':', 1)
|
||||
if username == file_user:
|
||||
return file_pass == crypt.crypt(password, file_pass[:2])
|
||||
return _check_passwd(password, file_pass)
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
@@ -693,11 +712,26 @@ def cli(argv):
|
||||
import getopt
|
||||
class BadUsage(Exception): pass
|
||||
|
||||
short_opts = string.join(['c',
|
||||
'd:',
|
||||
'g',
|
||||
'h:',
|
||||
'p:',
|
||||
'r:',
|
||||
's:',
|
||||
], '')
|
||||
long_opts = ['daemon',
|
||||
'config-file=',
|
||||
'gui',
|
||||
'host=',
|
||||
'htpasswd-file=',
|
||||
'port=',
|
||||
'repository=',
|
||||
'script-alias=',
|
||||
]
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], 'gdc:p:r:h:s:',
|
||||
['gui', 'daemon', 'config-file=', 'host=',
|
||||
'port=', 'repository=', 'script-alias=',
|
||||
'htpasswd-file='])
|
||||
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
|
||||
for opt, val in opts:
|
||||
if opt in ('-g', '--gui'):
|
||||
options.start_gui = 1
|
||||
@@ -729,6 +763,13 @@ def cli(argv):
|
||||
if not os.path.isfile(val):
|
||||
raise BadUsage, "'%s' does not appear to be a valid " \
|
||||
"htpasswd file." % (val)
|
||||
if not has_crypt:
|
||||
raise BadUsage, "Unable to locate suitable `crypt' module for use " \
|
||||
"with --htpasswd-file option. If your Python " \
|
||||
"distribution does not include this module (as is " \
|
||||
"the case on many non-Unix platforms), consider " \
|
||||
"installing the `fcrypt' module instead (see " \
|
||||
"http://carey.geek.nz/code/python-fcrypt/)."
|
||||
options.htpasswd_file = val
|
||||
if options.start_gui and options.config_file:
|
||||
raise BadUsage, "--config-file option is not valid in GUI mode."
|
||||
@@ -785,10 +826,11 @@ Options:
|
||||
"cgi-bin/viewvc", then ViewVC will be accessible
|
||||
at "http://%(host)s:%(port)s/cgi-bin/viewvc".
|
||||
[default: %(script_alias)s]
|
||||
|
||||
|
||||
--htpasswd-file=FILE Demand authentication from clients, validating
|
||||
authentication credentials against Apache
|
||||
htpasswd file FILE.
|
||||
authentication credentials against FILE, which is
|
||||
an Apache htpasswd file that employs CRYPT
|
||||
encryption. (Sorry, no DIGEST support yet.)
|
||||
|
||||
--gui (-g) Pop up a graphical interface for serving and
|
||||
testing ViewVC. NOTE: this requires a valid
|
||||
|
@@ -572,6 +572,14 @@
|
||||
##
|
||||
#show_subdir_lastmod = 0
|
||||
|
||||
## show_roots_lastmod: In the root listing view, show the most recent
|
||||
## modifications made to the root. (Subversion roots only.)
|
||||
##
|
||||
## NOTE: Enabling this feature will significantly reduce the
|
||||
## performance of the root listing view.
|
||||
##
|
||||
#show_roots_lastmod = 0
|
||||
|
||||
## show_logs: Show the most recent log entry in directory listings.
|
||||
##
|
||||
#show_logs = 1
|
||||
@@ -649,6 +657,26 @@
|
||||
##
|
||||
#log_pagesize = 0
|
||||
|
||||
## log_pagesextra: Maximum number of extra pages (based on
|
||||
## log_pagesize) of revision log data to fetch and present to the user
|
||||
## as additional options for display. Revision log information
|
||||
## "beyond" this window is still accessible, but must be navigated to
|
||||
## in multiple steps.
|
||||
##
|
||||
## Example:
|
||||
## log_pagesize = 100
|
||||
## log_pagesextra = 3
|
||||
##
|
||||
## For a versioned file with 1000 revisions, the above settings would
|
||||
## present to the user the first 100 of those 1000 revisions, with
|
||||
## links to three additional pages (the 200-299th revisions, 300-399th
|
||||
## revisions, and 400-499th revisions) plus a link to the 500th
|
||||
## revision. Following these links slides the display "window",
|
||||
## showing the requested set of revisions plus links to three
|
||||
## additional pages beyond those, and so on.
|
||||
##
|
||||
#log_pagesextra = 3
|
||||
|
||||
## limit_changes: Maximum number of changed paths shown per commit in
|
||||
## the Subversion revision view and in query results. This is not a
|
||||
## hard limit (the UI provides options to show all changed paths), but
|
||||
|
@@ -2144,6 +2144,32 @@ td {
|
||||
<td>List</td>
|
||||
<td>Set of configured viewable repositories.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.ago</td>
|
||||
<td>String</td>
|
||||
<td>Textual description of the time since <var>roots.date</var>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.author</td>
|
||||
<td>String</td>
|
||||
<td>Username of the last modifier of the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">root.date</td>
|
||||
<td>String</td>
|
||||
<td>Date (in UTC if not otherwise configured) of the last
|
||||
modification of the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.href</td>
|
||||
<td>String</td>
|
||||
<td>URL of root directory view for a configured repository.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.log</td>
|
||||
<td>String</td>
|
||||
<td>Log message of last modification to the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.name</td>
|
||||
<td>String</td>
|
||||
@@ -2157,17 +2183,24 @@ td {
|
||||
configuration can have negative security implications. Use this
|
||||
token at your own risk.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.rev</td>
|
||||
<td>String</td>
|
||||
<td>Youngest revision of the root.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.short_log</td>
|
||||
<td>String</td>
|
||||
<td>Log message of last modification to the root, truncated to
|
||||
contain no more than the number of characters specified by
|
||||
the <code>short_log_len</code> configuration option.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.type</td>
|
||||
<td>String</td>
|
||||
<td>Version control type of a configured repository. Valid
|
||||
values: <tt>cvs</tt>, <tt>svn</tt>.</td>
|
||||
</tr>
|
||||
<tr class="varlevel2">
|
||||
<td class="varname">roots.href</td>
|
||||
<td>String</td>
|
||||
<td>URL of root directory view for a configured repository.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -155,8 +155,7 @@ class Config:
|
||||
self.parser.read(self.conf_path or [])
|
||||
|
||||
for section in self.parser.sections():
|
||||
if self._is_allowed_section(self.parser, section,
|
||||
self._base_sections):
|
||||
if self._is_allowed_section(section, self._base_sections):
|
||||
self._process_section(self.parser, section, section)
|
||||
|
||||
if vhost and self.parser.has_section('vhosts'):
|
||||
@@ -222,7 +221,7 @@ class Config:
|
||||
|
||||
setattr(sc, opt, value)
|
||||
|
||||
def _is_allowed_section(self, parser, section, allowed_sections):
|
||||
def _is_allowed_section(self, section, allowed_sections):
|
||||
"""Return 1 iff SECTION is an allowed section, defined as being
|
||||
explicitly present in the ALLOWED_SECTIONS list or present in the
|
||||
form 'someprefix-*' in that list."""
|
||||
@@ -235,7 +234,7 @@ class Config:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def _is_allowed_override(self, parser, sectype, secspec, section):
|
||||
def _is_allowed_override(self, sectype, secspec, section):
|
||||
"""Test if SECTION is an allowed override section for sections of
|
||||
type SECTYPE ('vhosts' or 'root', currently) and type-specifier
|
||||
SECSPEC (a rootname or vhostname, currently). If it is, return
|
||||
@@ -248,7 +247,7 @@ class Config:
|
||||
if section[:lcv] != cv:
|
||||
return None
|
||||
base_section = section[lcv:]
|
||||
if self._is_allowed_section(parser, base_section,
|
||||
if self._is_allowed_section(base_section,
|
||||
self._allowed_overrides[sectype]):
|
||||
return base_section
|
||||
raise IllegalOverrideSection(sectype, section)
|
||||
@@ -261,8 +260,7 @@ class Config:
|
||||
|
||||
# Overlay any option sections associated with this vhost name.
|
||||
for section in parser.sections():
|
||||
base_section = self._is_allowed_override(parser, 'vhost',
|
||||
canon_vhost, section)
|
||||
base_section = self._is_allowed_override('vhost', canon_vhost, section)
|
||||
if base_section:
|
||||
self._process_section(parser, section, base_section)
|
||||
|
||||
@@ -288,8 +286,7 @@ class Config:
|
||||
return
|
||||
|
||||
for section in self.parser.sections():
|
||||
base_section = self._is_allowed_override(self.parser, 'root',
|
||||
rootname, section)
|
||||
base_section = self._is_allowed_override('root', rootname, section)
|
||||
if base_section:
|
||||
# We can currently only deal with root overlays happening
|
||||
# once, so check that we've not yet done any overlaying of
|
||||
@@ -423,6 +420,7 @@ class Config:
|
||||
self.options.template_dir = "templates"
|
||||
self.options.docroot = None
|
||||
self.options.show_subdir_lastmod = 0
|
||||
self.options.show_roots_lastmod = 0
|
||||
self.options.show_logs = 1
|
||||
self.options.show_log_in_markup = 1
|
||||
self.options.cross_copies = 1
|
||||
@@ -436,6 +434,7 @@ class Config:
|
||||
self.options.use_re_search = 0
|
||||
self.options.dir_pagesize = 0
|
||||
self.options.log_pagesize = 0
|
||||
self.options.log_pagesextra = 3
|
||||
self.options.limit_changes = 100
|
||||
|
||||
self.templates.diff = None
|
||||
|
@@ -29,7 +29,15 @@ class GenericViewVCAuthorizer:
|
||||
def check_root_access(self, rootname):
|
||||
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
|
||||
pass
|
||||
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
"""Return 1 if the associated username is permitted to read every
|
||||
path in the repository at every revision, 0 if the associated
|
||||
username is prohibited from reading any path in the repository, or
|
||||
None if no such determination can be made (perhaps because the
|
||||
cost of making it is too great)."""
|
||||
pass
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
"""Return 1 iff the associated username is permitted to read
|
||||
revision REV of the path PATH_PARTS (of type PATHTYPE) in
|
||||
@@ -44,6 +52,9 @@ class ViewVCAuthorizer(GenericViewVCAuthorizer):
|
||||
"""The uber-permissive authorizer."""
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
return 1
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
return 1
|
||||
|
@@ -23,7 +23,14 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
|
||||
def check_root_access(self, rootname):
|
||||
return 1
|
||||
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
# If there aren't any forbidden paths, we can grant universal read
|
||||
# access. Otherwise, we make no claim.
|
||||
if not self.forbidden:
|
||||
return 1
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
# No path? No problem.
|
||||
if not path_parts:
|
||||
|
@@ -46,6 +46,13 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
def check_root_access(self, rootname):
|
||||
return self._check_root_path_access(rootname)
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
# If there aren't any forbidden regexps, we can grant universal
|
||||
# read access. Otherwise, we make no claim.
|
||||
if not self.forbidden:
|
||||
return 1
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
root_path = rootname
|
||||
if path_parts:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 2006-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 2006-2011 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -54,7 +54,10 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
# option names.
|
||||
cp = ConfigParser()
|
||||
cp.optionxform = lambda x: x
|
||||
cp.read(self.authz_file)
|
||||
try:
|
||||
cp.read(self.authz_file)
|
||||
except:
|
||||
raise debug.ViewVCException("Unable to parse configured authzfile file")
|
||||
|
||||
# Figure out if there are any aliases for the current username
|
||||
aliases = []
|
||||
@@ -221,6 +224,36 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
||||
paths = self._get_paths_for_root(rootname)
|
||||
return (paths is not None) and 1 or 0
|
||||
|
||||
def check_universal_access(self, rootname):
|
||||
paths = self._get_paths_for_root(rootname)
|
||||
if not paths: # None or empty.
|
||||
return 0
|
||||
|
||||
# Search the access determinations. If there's a mix, we can't
|
||||
# claim a universal access determination.
|
||||
found_allow = 0
|
||||
found_deny = 0
|
||||
for access in paths.values():
|
||||
if access:
|
||||
found_allow = 1
|
||||
else:
|
||||
found_deny = 1
|
||||
if found_allow and found_deny:
|
||||
return None
|
||||
|
||||
# We didn't find both allowances and denials, so we must have
|
||||
# found one or the other. Denials only is a universal denial.
|
||||
if found_deny:
|
||||
return 0
|
||||
|
||||
# ... but allowances only is only a universal allowance if read
|
||||
# access is granted to the root directory.
|
||||
if found_allow and paths.has_key('/'):
|
||||
return 1
|
||||
|
||||
# Anything else is indeterminable.
|
||||
return None
|
||||
|
||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||
# Crawl upward from the path represented by PATH_PARTS toward to
|
||||
# the root of the repository, looking for an explicitly grant or
|
||||
|
@@ -40,6 +40,11 @@ class BaseCVSRepository(vclib.Repository):
|
||||
if not vclib.check_root_access(self):
|
||||
raise vclib.ReposNotFound(name)
|
||||
|
||||
def open(self):
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -15,6 +15,7 @@
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import urllib
|
||||
|
||||
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
||||
|
||||
@@ -23,8 +24,20 @@ def canonicalize_rootpath(rootpath):
|
||||
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[-1] == '/' and rootpath[:-1] or rootpath
|
||||
return rootpath.rstrip('/')
|
||||
return os.path.normpath(rootpath)
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -57,7 +57,7 @@ def client_log(url, start_rev, end_rev, log_limit, cross_copies,
|
||||
client.svn_client_log4([url], start_rev, start_rev, end_rev,
|
||||
log_limit, 1, not cross_copies, 0, None,
|
||||
cb_func, ctx)
|
||||
except NameError:
|
||||
except AttributeError:
|
||||
# Wrap old svn_log_message_receiver_t interface with a
|
||||
# svn_log_entry_t one.
|
||||
def cb_convert(paths, revision, author, date, message, pool):
|
||||
@@ -203,6 +203,10 @@ class RemoteSubversionRepository(vclib.Repository):
|
||||
self.youngest = ra.svn_ra_get_latest_revnum(self.ra_session)
|
||||
self._dirent_cache = { }
|
||||
self._revinfo_cache = { }
|
||||
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -204,59 +204,6 @@ class NodeHistory:
|
||||
def __getitem__(self, idx):
|
||||
return self.histories[idx]
|
||||
|
||||
|
||||
def _get_history(svnrepos, path, rev, path_type, limit=0, options={}):
|
||||
if svnrepos.youngest == 0:
|
||||
return []
|
||||
|
||||
rev_paths = []
|
||||
fsroot = svnrepos._getroot(rev)
|
||||
show_all_logs = options.get('svn_show_all_dir_logs', 0)
|
||||
if not show_all_logs:
|
||||
# See if the path is a file or directory.
|
||||
kind = fs.check_path(fsroot, path)
|
||||
if kind is core.svn_node_file:
|
||||
show_all_logs = 1
|
||||
|
||||
# Instantiate a NodeHistory collector object, and use it to collect
|
||||
# history items for PATH@REV.
|
||||
history = NodeHistory(svnrepos.fs_ptr, show_all_logs, limit)
|
||||
try:
|
||||
repos.svn_repos_history(svnrepos.fs_ptr, path, history.add_history,
|
||||
1, rev, options.get('svn_cross_copies', 0))
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
|
||||
raise
|
||||
|
||||
# Now, iterate over those history items, checking for changes of
|
||||
# location, pruning as necessitated by authz rules.
|
||||
for hist_rev, hist_path in history:
|
||||
path_parts = _path_parts(hist_path)
|
||||
if not vclib.check_path_access(svnrepos, path_parts, path_type, hist_rev):
|
||||
break
|
||||
rev_paths.append([hist_rev, hist_path])
|
||||
return rev_paths
|
||||
|
||||
|
||||
def _log_helper(svnrepos, path, rev, lockinfo):
|
||||
rev_root = fs.revision_root(svnrepos.fs_ptr, rev)
|
||||
|
||||
# Was this path@rev the target of a copy?
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
|
||||
|
||||
# Assemble our LogEntry
|
||||
date, author, msg, revprops, changes = svnrepos._revinfo(rev)
|
||||
if fs.is_file(rev_root, path):
|
||||
size = fs.file_length(rev_root, path)
|
||||
else:
|
||||
size = None
|
||||
entry = Revision(rev, date, author, msg, size, lockinfo, path,
|
||||
copyfrom_path and _cleanup_path(copyfrom_path),
|
||||
copyfrom_rev)
|
||||
return entry
|
||||
|
||||
|
||||
def _get_last_history_rev(fsroot, path):
|
||||
history = fs.node_history(fsroot, path)
|
||||
history = fs.history_prev(history, 0)
|
||||
@@ -427,6 +374,10 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
self._fsroots = {}
|
||||
self._revinfo_cache = {}
|
||||
|
||||
# See if a universal read access determination can be made.
|
||||
if self.auth and self.auth.check_universal_access(self.name) == 1:
|
||||
self.auth = None
|
||||
|
||||
def rootname(self):
|
||||
return self.name
|
||||
|
||||
@@ -442,13 +393,8 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
def itemtype(self, path_parts, rev):
|
||||
rev = self._getrev(rev)
|
||||
basepath = self._getpath(path_parts)
|
||||
kind = fs.check_path(self._getroot(rev), basepath)
|
||||
pathtype = None
|
||||
if kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
elif kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
else:
|
||||
pathtype = self._gettype(basepath, rev)
|
||||
if pathtype is None:
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
if not vclib.check_path_access(self, path_parts, pathtype, rev):
|
||||
raise vclib.ItemNotFound(path_parts)
|
||||
@@ -542,20 +488,19 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
# 'limit' parameter here as numeric cut-off for the depth of our
|
||||
# history search.
|
||||
if options.get('svn_latest_log', 0):
|
||||
revision = _log_helper(self, path, rev, lockinfo)
|
||||
revision = self._log_helper(path, rev, lockinfo)
|
||||
if revision:
|
||||
revision.prev = None
|
||||
revs.append(revision)
|
||||
else:
|
||||
history = _get_history(self, path, rev, path_type,
|
||||
first + limit, options)
|
||||
history = self._get_history(path, rev, path_type, first + limit, options)
|
||||
if len(history) < first:
|
||||
history = []
|
||||
if limit:
|
||||
history = history[first:first+limit]
|
||||
|
||||
for hist_rev, hist_path in history:
|
||||
revision = _log_helper(self, hist_path, hist_rev, lockinfo)
|
||||
revision = self._log_helper(hist_path, hist_rev, lockinfo)
|
||||
if revision:
|
||||
# If we have unreadable copyfrom data, obscure it.
|
||||
if revision.copy_path is not None:
|
||||
@@ -583,33 +528,56 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
raise vclib.Error("Path '%s' is not a file." % path)
|
||||
rev = self._getrev(rev)
|
||||
fsroot = self._getroot(rev)
|
||||
history = _get_history(self, path, rev, path_type, 0,
|
||||
{'svn_cross_copies': 1})
|
||||
history = self._get_history(path, rev, path_type, 0,
|
||||
{'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)
|
||||
return source, youngest_rev
|
||||
|
||||
def revinfo(self, rev):
|
||||
return self._revinfo(rev, 1)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r1 = self._getrev(rev1)
|
||||
r2 = self._getrev(rev2)
|
||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
||||
raise vclib.ItemNotFound(path_parts1)
|
||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
|
||||
args = vclib._diff_args(type, options)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
info1 = p1, _date_from_rev(r1), r1
|
||||
info2 = p2, _date_from_rev(r2), r2
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.InvalidRevision
|
||||
raise
|
||||
|
||||
def isexecutable(self, path_parts, rev):
|
||||
props = self.itemprops(path_parts, rev) # does authz-check
|
||||
return props.has_key(core.SVN_PROP_EXECUTABLE)
|
||||
|
||||
##--- helpers ---##
|
||||
|
||||
def _revinfo(self, rev, include_changed_paths=0):
|
||||
"""Internal-use, cache-friendly revision information harvester."""
|
||||
|
||||
def _revinfo_helper(rev, include_changed_paths):
|
||||
# Get the revision property info. (Would use
|
||||
# editor.get_root_props(), but something is broken there...)
|
||||
revprops = fs.revision_proplist(self.fs_ptr, rev)
|
||||
msg, author, date, revprops = _split_revprops(revprops)
|
||||
|
||||
# Optimization: If our caller doesn't care about the changed
|
||||
# paths, and we don't need them to do authz determinations, let's
|
||||
# get outta here.
|
||||
if self.auth is None and not include_changed_paths:
|
||||
return date, author, msg, revprops, None
|
||||
|
||||
# If we get here, then we either need the changed paths because we
|
||||
# were asked for them, or we need them to do authorization checks.
|
||||
# Either way, we need 'em, so let's get 'em.
|
||||
fsroot = self._getroot(rev)
|
||||
|
||||
def _get_changed_paths(fsroot):
|
||||
"""Return a 3-tuple: found_readable, found_unreadable, changed_paths."""
|
||||
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
||||
e_ptr, e_baton = delta.make_editor(editor)
|
||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
||||
@@ -662,7 +630,8 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
if vclib.check_path_access(self, parts, pathtype, rev):
|
||||
if is_copy and change.base_path and (change.base_path != path):
|
||||
parts = _path_parts(change.base_path)
|
||||
if not vclib.check_path_access(self, parts, pathtype, change.base_rev):
|
||||
if not vclib.check_path_access(self, parts, pathtype,
|
||||
change.base_rev):
|
||||
is_copy = 0
|
||||
change.base_path = None
|
||||
change.base_rev = None
|
||||
@@ -674,24 +643,108 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
found_readable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
return found_readable, found_unreadable, changedpaths.values()
|
||||
|
||||
def _get_change_copyinfo(fsroot, path, change):
|
||||
if hasattr(change, 'copyfrom_known') and change.copyfrom_known:
|
||||
copyfrom_path = change.copyfrom_path
|
||||
copyfrom_rev = change.copyfrom_rev
|
||||
else:
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(fsroot, path)
|
||||
return copyfrom_path, copyfrom_rev
|
||||
|
||||
def _simple_auth_check(fsroot):
|
||||
"""Return a 2-tuple: found_readable, found_unreadable."""
|
||||
found_unreadable = found_readable = 0
|
||||
if hasattr(fs, 'paths_changed2'):
|
||||
changes = fs.paths_changed2(fsroot)
|
||||
else:
|
||||
changes = fs.paths_changed(fsroot)
|
||||
paths = changes.keys()
|
||||
for path in paths:
|
||||
change = changes[path]
|
||||
pathtype = None
|
||||
if hasattr(change, 'node_kind'):
|
||||
if change.node_kind == core.svn_node_file:
|
||||
pathtype = vclib.FILE
|
||||
elif change.node_kind == core.svn_node_dir:
|
||||
pathtype = vclib.DIR
|
||||
parts = _path_parts(path)
|
||||
if pathtype is None:
|
||||
# Figure out the pathtype so we can query the authz subsystem.
|
||||
if change.change_kind == fs.path_change_delete:
|
||||
# Deletions are annoying, because they might be underneath
|
||||
# copies (make their previous location non-trivial).
|
||||
prev_parts = parts
|
||||
prev_rev = rev - 1
|
||||
parent_parts = parts[:-1]
|
||||
while parent_parts:
|
||||
parent_path = '/' + self._getpath(parent_parts)
|
||||
parent_change = changes.get(parent_path)
|
||||
if not (parent_change and \
|
||||
(parent_change.change_kind == fs.path_change_add or
|
||||
parent_change.change_kind == fs.path_change_replace)):
|
||||
del(parent_parts[-1])
|
||||
continue
|
||||
copyfrom_path, copyfrom_rev = \
|
||||
_get_change_copyinfo(fsroot, parent_path, parent_change)
|
||||
if copyfrom_path:
|
||||
prev_rev = copyfrom_rev
|
||||
prev_parts = _path_parts(copyfrom_path) + \
|
||||
parts[len(parent_parts):]
|
||||
break
|
||||
del(parent_parts[-1])
|
||||
pathtype = self._gettype(self._getpath(prev_parts), prev_rev)
|
||||
else:
|
||||
pathtype = self._gettype(self._getpath(parts), rev)
|
||||
if vclib.check_path_access(self, parts, pathtype, rev):
|
||||
found_readable = 1
|
||||
copyfrom_path, copyfrom_rev = \
|
||||
_get_change_copyinfo(fsroot, path, change)
|
||||
if copyfrom_path and copyfrom_path != path:
|
||||
parts = _path_parts(copyfrom_path)
|
||||
if not vclib.check_path_access(self, parts, pathtype,
|
||||
copyfrom_rev):
|
||||
found_unreadable = 1
|
||||
else:
|
||||
found_unreadable = 1
|
||||
if found_readable and found_unreadable:
|
||||
break
|
||||
return found_readable, found_unreadable
|
||||
|
||||
def _revinfo_helper(rev, include_changed_paths):
|
||||
# Get the revision property info. (Would use
|
||||
# editor.get_root_props(), but something is broken there...)
|
||||
revprops = fs.revision_proplist(self.fs_ptr, rev)
|
||||
msg, author, date, revprops = _split_revprops(revprops)
|
||||
|
||||
# If our caller doesn't care about changed paths, we must be
|
||||
# here for authz reasons only. That means the minute we've
|
||||
# found both a readable and an unreadable path, we can bail out.
|
||||
if (not include_changed_paths) and found_readable and found_unreadable:
|
||||
return date, author, None, None, None
|
||||
# Optimization: If our caller doesn't care about the changed
|
||||
# paths, and we don't need them to do authz determinations, let's
|
||||
# get outta here.
|
||||
if self.auth is None and not include_changed_paths:
|
||||
return date, author, msg, revprops, None
|
||||
|
||||
# If we get here, then we either need the changed paths because we
|
||||
# were asked for them, or we need them to do authorization checks.
|
||||
#
|
||||
# If we only need them for authorization checks, though, we
|
||||
# won't bother generating fully populated ChangedPath items (the
|
||||
# cost is too great).
|
||||
fsroot = self._getroot(rev)
|
||||
if include_changed_paths:
|
||||
found_readable, found_unreadable, changedpaths = \
|
||||
_get_changed_paths(fsroot)
|
||||
else:
|
||||
changedpaths = None
|
||||
found_readable, found_unreadable = _simple_auth_check(fsroot)
|
||||
|
||||
# Okay, we've process all our paths. Let's filter our metadata,
|
||||
# and return the requested data.
|
||||
# Filter our metadata where necessary, and return the requested data.
|
||||
if found_unreadable:
|
||||
msg = None
|
||||
if not found_readable:
|
||||
author = None
|
||||
date = None
|
||||
if include_changed_paths:
|
||||
return date, author, msg, revprops, changedpaths.values()
|
||||
else:
|
||||
return date, author, msg, revprops, None
|
||||
return date, author, msg, revprops, changedpaths
|
||||
|
||||
# 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
|
||||
@@ -704,40 +757,50 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
self._revinfo_cache[rev] = cached_info
|
||||
return tuple(cached_info)
|
||||
|
||||
def revinfo(self, rev):
|
||||
return self._revinfo(rev, 1)
|
||||
|
||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
||||
p1 = self._getpath(path_parts1)
|
||||
p2 = self._getpath(path_parts2)
|
||||
r1 = self._getrev(rev1)
|
||||
r2 = self._getrev(rev2)
|
||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
||||
raise vclib.ItemNotFound(path_parts1)
|
||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
||||
raise vclib.ItemNotFound(path_parts2)
|
||||
|
||||
args = vclib._diff_args(type, options)
|
||||
def _log_helper(self, path, rev, lockinfo):
|
||||
rev_root = fs.revision_root(self.fs_ptr, rev)
|
||||
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
if fs.is_file(rev_root, path):
|
||||
size = fs.file_length(rev_root, path)
|
||||
else:
|
||||
size = None
|
||||
return Revision(rev, date, author, msg, size, lockinfo, path,
|
||||
copyfrom_path and _cleanup_path(copyfrom_path),
|
||||
copyfrom_rev)
|
||||
|
||||
def _date_from_rev(rev):
|
||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||
return date
|
||||
def _get_history(self, path, rev, path_type, limit=0, options={}):
|
||||
if self.youngest == 0:
|
||||
return []
|
||||
|
||||
rev_paths = []
|
||||
fsroot = self._getroot(rev)
|
||||
show_all_logs = options.get('svn_show_all_dir_logs', 0)
|
||||
if not show_all_logs:
|
||||
# See if the path is a file or directory.
|
||||
kind = fs.check_path(fsroot, path)
|
||||
if kind is core.svn_node_file:
|
||||
show_all_logs = 1
|
||||
|
||||
# Instantiate a NodeHistory collector object, and use it to collect
|
||||
# history items for PATH@REV.
|
||||
history = NodeHistory(self.fs_ptr, show_all_logs, limit)
|
||||
try:
|
||||
temp1 = temp_checkout(self, p1, r1)
|
||||
temp2 = temp_checkout(self, p2, r2)
|
||||
info1 = p1, _date_from_rev(r1), r1
|
||||
info2 = p2, _date_from_rev(r2), r2
|
||||
return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
|
||||
repos.svn_repos_history(self.fs_ptr, path, history.add_history,
|
||||
1, rev, options.get('svn_cross_copies', 0))
|
||||
except core.SubversionException, e:
|
||||
_fix_subversion_exception(e)
|
||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
||||
raise vclib.InvalidRevision
|
||||
raise
|
||||
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
|
||||
raise
|
||||
|
||||
def isexecutable(self, path_parts, rev):
|
||||
props = self.itemprops(path_parts, rev) # does authz-check
|
||||
return props.has_key(core.SVN_PROP_EXECUTABLE)
|
||||
# Now, iterate over those history items, checking for changes of
|
||||
# location, pruning as necessitated by authz rules.
|
||||
for hist_rev, hist_path in history:
|
||||
path_parts = _path_parts(hist_path)
|
||||
if not vclib.check_path_access(self, path_parts, path_type, hist_rev):
|
||||
break
|
||||
rev_paths.append([hist_rev, hist_path])
|
||||
return rev_paths
|
||||
|
||||
def _getpath(self, path_parts):
|
||||
return string.join(path_parts, '/')
|
||||
@@ -760,7 +823,20 @@ class LocalSubversionRepository(vclib.Repository):
|
||||
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev)
|
||||
return r
|
||||
|
||||
##--- custom --##
|
||||
def _gettype(self, path, rev):
|
||||
# Similar to itemtype(), but without the authz check. Returns
|
||||
# None for missing paths.
|
||||
try:
|
||||
kind = fs.check_path(self._getroot(rev), path)
|
||||
except:
|
||||
return None
|
||||
if kind == core.svn_node_dir:
|
||||
return vclib.DIR
|
||||
if kind == core.svn_node_file:
|
||||
return vclib.FILE
|
||||
return None
|
||||
|
||||
##--- custom ---##
|
||||
|
||||
def get_youngest_revision(self):
|
||||
return self.youngest
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*-python-*-
|
||||
#
|
||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
||||
# Copyright (C) 1999-2011 The ViewCVS Group. All Rights Reserved.
|
||||
#
|
||||
# By using this file, you agree to the terms and conditions set forth in
|
||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||
@@ -14,7 +14,7 @@
|
||||
#
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
__version__ = '1.1.7'
|
||||
__version__ = '1.1.10'
|
||||
|
||||
# this comes from our library; measure the startup time
|
||||
import debug
|
||||
@@ -79,10 +79,6 @@ _sticky_vars = [
|
||||
'limit_changes',
|
||||
]
|
||||
|
||||
# number of extra pages of information on either side of the current
|
||||
# page to fetch (see dir_pagesize/log_pagesize configuration option)
|
||||
EXTRA_PAGES = 3
|
||||
|
||||
# for reading/writing between a couple descriptors
|
||||
CHUNK_SIZE = 8192
|
||||
|
||||
@@ -897,25 +893,37 @@ def get_view_template(cfg, view_name, language="en"):
|
||||
|
||||
return template
|
||||
|
||||
def get_writeready_server_file(request, content_type=None, encoding=None):
|
||||
def get_writeready_server_file(request, content_type=None, encoding=None,
|
||||
content_length=None):
|
||||
"""Return a file handle to a response body stream, after outputting
|
||||
any queued special headers (on REQUEST.server) and (optionally) a
|
||||
'Content-Type' header whose value is CONTENT_TYPE and character set
|
||||
is ENCODING. After this is called, it is too late to add new
|
||||
headers to the response."""
|
||||
is ENCODING.
|
||||
|
||||
If CONTENT_LENGTH is provided and compression is not in use, also
|
||||
generate a 'Content-Length' header for this response.
|
||||
|
||||
After this function is called, it is too late to add new headers to
|
||||
the response."""
|
||||
|
||||
if request.gzip_compress_level:
|
||||
request.server.addheader('Content-Encoding', 'gzip')
|
||||
elif content_length is not None:
|
||||
request.server.addheader('Content-Length', content_length)
|
||||
|
||||
if content_type and encoding:
|
||||
request.server.header("%s; charset=%s" % (content_type, encoding))
|
||||
elif content_type:
|
||||
request.server.header(content_type)
|
||||
else:
|
||||
request.server.header()
|
||||
|
||||
if request.gzip_compress_level:
|
||||
fp = gzip.GzipFile('', 'wb', request.gzip_compress_level,
|
||||
request.server.file())
|
||||
else:
|
||||
fp = request.server.file()
|
||||
|
||||
return fp
|
||||
|
||||
def generate_page(request, view_name, data, content_type=None):
|
||||
@@ -1519,6 +1527,21 @@ class MarkupPipeWrapper:
|
||||
if self.posttext:
|
||||
ctx.fp.write(self.posttext)
|
||||
|
||||
_re_rewrite_escaped_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
|
||||
'(://[-a-zA-Z0-9%.~:_/]+)'
|
||||
'((\?|\&amp;|\&|\&)'
|
||||
'([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*'
|
||||
'(#([-a-zA-Z0-9%.~:_]+)?)?)')
|
||||
|
||||
def markup_escaped_urls(s):
|
||||
# Return a copy of S with all URL references -- which are expected
|
||||
# to be already HTML-escaped -- wrapped in <a href=""></a>.
|
||||
def _url_repl(match_obj):
|
||||
url = match_obj.group(0)
|
||||
unescaped_url = string.replace(url, "&amp;", "&")
|
||||
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.
|
||||
@@ -1582,6 +1605,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||
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:
|
||||
@@ -1593,6 +1617,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||
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)
|
||||
@@ -1610,6 +1635,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||
self.line_no = 0
|
||||
def write(self, buf):
|
||||
### FIXME: Don't bank on write() being called once per line
|
||||
buf = markup_escaped_urls(buf)
|
||||
if self.has_blame_data:
|
||||
self.blame_data[self.line_no].text = buf
|
||||
else:
|
||||
@@ -1808,12 +1834,18 @@ def view_markup(request):
|
||||
if 'markup' not in request.cfg.options.allowed_views:
|
||||
raise debug.ViewVCException('Markup view is disabled',
|
||||
'403 Forbidden')
|
||||
if request.pathtype != vclib.FILE:
|
||||
raise debug.ViewVCException('Unsupported feature: markup view on '
|
||||
'directory', '400 Bad Request')
|
||||
markup_or_annotate(request, 0)
|
||||
|
||||
def view_annotate(request):
|
||||
if 'annotate' not in request.cfg.options.allowed_views:
|
||||
raise debug.ViewVCException('Annotation view is disabled',
|
||||
'403 Forbidden')
|
||||
if request.pathtype != vclib.FILE:
|
||||
raise debug.ViewVCException('Unsupported feature: annotate view on '
|
||||
'directory', '400 Bad Request')
|
||||
markup_or_annotate(request, 1)
|
||||
|
||||
def revcmp(rev1, rev2):
|
||||
@@ -1888,9 +1920,16 @@ def view_roots(request):
|
||||
href = request.get_url(view_func=view_directory,
|
||||
where='', pathtype=vclib.DIR,
|
||||
params={'root': rootname}, escape=1)
|
||||
lastmod = allroots[rootname][2]
|
||||
roots.append(_item(name=request.server.escape(rootname),
|
||||
type=allroots[rootname][1],
|
||||
path=allroots[rootname][0],
|
||||
author=lastmod and lastmod.author or None,
|
||||
ago=lastmod and lastmod.ago or None,
|
||||
date=lastmod and lastmod.date or None,
|
||||
log=lastmod and lastmod.log or None,
|
||||
short_log=lastmod and lastmod.short_log or None,
|
||||
rev=lastmod and lastmod.rev or None,
|
||||
href=href))
|
||||
|
||||
data = common_template_data(request)
|
||||
@@ -2215,10 +2254,11 @@ def paging(data, key, pagestart, local_name, pagesize):
|
||||
# Slice
|
||||
return data[key][pagestart:pageend]
|
||||
|
||||
def paging_sws(data, key, pagestart, local_name, pagesize, offset):
|
||||
def paging_sws(data, key, pagestart, local_name, pagesize,
|
||||
extra_pages, offset):
|
||||
"""Implement sliding window-style paging."""
|
||||
# Create the picklist
|
||||
last_requested = pagestart + (EXTRA_PAGES * pagesize)
|
||||
last_requested = pagestart + (extra_pages * pagesize)
|
||||
picklist = data['picklist'] = []
|
||||
has_more = ezt.boolean(0)
|
||||
for i in range(0, len(data[key]), pagesize):
|
||||
@@ -2347,9 +2387,9 @@ def view_log(request):
|
||||
first = last = 0
|
||||
if cfg.options.log_pagesize:
|
||||
log_pagestart = int(request.query_dict.get('log_pagestart', 0))
|
||||
first = log_pagestart - min(log_pagestart,
|
||||
(EXTRA_PAGES * cfg.options.log_pagesize))
|
||||
last = log_pagestart + ((EXTRA_PAGES + 1) * cfg.options.log_pagesize) + 1
|
||||
total = cfg.options.log_pagesextra * cfg.options.log_pagesize
|
||||
first = log_pagestart - min(log_pagestart, total)
|
||||
last = log_pagestart + (total + cfg.options.log_pagesize) + 1
|
||||
show_revs = request.repos.itemlog(request.path_parts, request.pathrev,
|
||||
sortby, first, last - first, options)
|
||||
|
||||
@@ -2610,7 +2650,8 @@ def view_log(request):
|
||||
request.get_form(params={'log_pagestart': None})
|
||||
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, first)
|
||||
'rev', cfg.options.log_pagesize,
|
||||
cfg.options.log_pagesextra, first)
|
||||
|
||||
generate_page(request, "log", data)
|
||||
|
||||
@@ -2751,7 +2792,6 @@ def view_doc(request):
|
||||
raise debug.ViewVCException('Static file "%s" not available (%s)'
|
||||
% (document, str(v)), '404 Not Found')
|
||||
|
||||
request.server.addheader('Content-Length', content_length)
|
||||
if document[-3:] == 'png':
|
||||
mime_type = 'image/png'
|
||||
elif document[-3:] == 'jpg':
|
||||
@@ -2762,7 +2802,8 @@ def view_doc(request):
|
||||
mime_type = 'text/css'
|
||||
else: # assume HTML:
|
||||
mime_type = None
|
||||
copy_stream(fp, get_writeready_server_file(request, mime_type))
|
||||
copy_stream(fp, get_writeready_server_file(request, mime_type,
|
||||
content_length=content_length))
|
||||
fp.close()
|
||||
|
||||
def rcsdiff_date_reformat(date_str, cfg):
|
||||
@@ -4249,11 +4290,26 @@ def list_roots(request):
|
||||
for root in cfg.general.svn_roots.keys():
|
||||
auth = setup_authorizer(cfg, request.username, root)
|
||||
try:
|
||||
vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root], auth,
|
||||
cfg.utilities, cfg.options.svn_config_dir)
|
||||
repos = vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root],
|
||||
auth, cfg.utilities,
|
||||
cfg.options.svn_config_dir)
|
||||
lastmod = None
|
||||
if cfg.options.show_roots_lastmod:
|
||||
try:
|
||||
repos.open()
|
||||
youngest_rev = repos.youngest
|
||||
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)
|
||||
lastmod = _item(ago=ago, author=author, date=date_str, log=log,
|
||||
short_log=short_log, rev=str(youngest_rev))
|
||||
except:
|
||||
lastmod = None
|
||||
except vclib.ReposNotFound:
|
||||
continue
|
||||
allroots[root] = [cfg.general.svn_roots[root], 'svn']
|
||||
allroots[root] = [cfg.general.svn_roots[root], 'svn', lastmod]
|
||||
|
||||
# Add the viewable CVS roots
|
||||
for root in cfg.general.cvs_roots.keys():
|
||||
@@ -4263,7 +4319,7 @@ def list_roots(request):
|
||||
cfg.utilities, cfg.options.use_rcsparse)
|
||||
except vclib.ReposNotFound:
|
||||
continue
|
||||
allroots[root] = [cfg.general.cvs_roots[root], 'cvs']
|
||||
allroots[root] = [cfg.general.cvs_roots[root], 'cvs', None]
|
||||
|
||||
return allroots
|
||||
|
||||
|
@@ -27,7 +27,9 @@ numbers, and not literal):
|
||||
3. Verify that copyright years are correct in both the license-1.html
|
||||
file and the source code.
|
||||
|
||||
4. Update and commit the 'CHANGES' file.
|
||||
4. Update and commit the 'CHANGES' file, using any available crystal
|
||||
balls or other forward-looking devices to take a stab at the
|
||||
release date.
|
||||
|
||||
5. Test, test, test! There is no automatic testsuite available. So
|
||||
just run with permuting different `viewvc.conf' settings... and
|
||||
@@ -83,9 +85,12 @@ numbers, and not literal):
|
||||
|
||||
14. Upload the created archive files (tar.gz and zip) into the Files
|
||||
and Documents section of the Tigris.org project, and modify the
|
||||
CHECKSUMS document there accordingly. Also, drop a copy of the
|
||||
archive files into the root directory of the viewvc.org website
|
||||
(unversioned).
|
||||
CHECKSUMS document there accordingly:
|
||||
|
||||
http://viewvc.tigris.org/servlets/ProjectDocumentList?folderID=6004
|
||||
|
||||
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
|
||||
@@ -102,6 +107,9 @@ numbers, and not literal):
|
||||
(and possibly, minor or major) release. (For the Milestone sort
|
||||
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
|
||||
|
||||
http://viewvc.tigris.org/issues/editversions.cgi?component=viewvc&action=add
|
||||
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).
|
||||
|
@@ -9,6 +9,12 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="vc_header_sort">Name</th>
|
||||
[is cfg.options.show_roots_lastmod "1"]
|
||||
<th class="vc_header">Revision</th>
|
||||
<th class="vc_header">Age</th>
|
||||
<th class="vc_header">Author</th>
|
||||
<th class="vc_header">Log</th>
|
||||
[end]
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -20,6 +26,12 @@
|
||||
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
|
||||
[roots.name]</a>
|
||||
</td>
|
||||
[is cfg.options.show_roots_lastmod "1"]
|
||||
<td style="width:20"> [roots.rev]</td>
|
||||
<td style="width:20"> [roots.ago]</td>
|
||||
<td style="width:20"> [roots.author]</td>
|
||||
<td style="width:20"> [roots.short_log]</td>
|
||||
[end]
|
||||
</tr>
|
||||
[end]
|
||||
</tbody>
|
||||
|
Reference in New Issue
Block a user