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)
|
Version 1.1.7 (released 09-Sep-2010)
|
||||||
|
|
||||||
* display Subversion revision properties in the revision view (issue #453)
|
* display Subversion revision properties in the revision view (issue #453)
|
||||||
* fix exception in 'standalone.py -r REPOS' when run without a config file
|
* fix exception in 'standalone.py -r REPOS' when run without a config file
|
||||||
* fix standalone.py server root deployments (--script-alias='')
|
* 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
|
* fix obscure "unexpected NULL parent pool" Subversion bindings error
|
||||||
* enable path info / link display in remote Subversion root revision view
|
* enable path info / link display in remote Subversion root revision view
|
||||||
* fix vhost name case handling inconsistency (issue #466)
|
* fix vhost name case handling inconsistency (issue #466)
|
||||||
|
4
INSTALL
4
INSTALL
@@ -19,7 +19,7 @@ Congratulations on getting this far. :-)
|
|||||||
|
|
||||||
For CVS Support:
|
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/)
|
(http://www.python.org/)
|
||||||
* RCS, Revision Control System
|
* RCS, Revision Control System
|
||||||
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
|
(http://www.cs.purdue.edu/homes/trinkle/RCS/)
|
||||||
@@ -30,7 +30,7 @@ Congratulations on getting this far. :-)
|
|||||||
|
|
||||||
For Subversion Support:
|
For Subversion Support:
|
||||||
|
|
||||||
* Python 2.0 or later
|
* Python 2.0 or later (sorry, no 3.x support yet)
|
||||||
(http://www.python.org/)
|
(http://www.python.org/)
|
||||||
* Subversion, Version Control System, 1.3.1 or later
|
* Subversion, Version Control System, 1.3.1 or later
|
||||||
(binary installation and Python bindings)
|
(binary installation and Python bindings)
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
|
|
||||||
<p><strong>Copyright © 1999-2010 The ViewCVS Group. All rights
|
<p><strong>Copyright © 1999-2011 The ViewCVS Group. All rights
|
||||||
reserved.</strong></p>
|
reserved.</strong></p>
|
||||||
|
|
||||||
<p>By using ViewVC, you agree to the terms and conditions set forth
|
<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>February 22, 2008 — copyright years updated</li>
|
||||||
<li>March 18, 2009 — copyright years updated</li>
|
<li>March 18, 2009 — copyright years updated</li>
|
||||||
<li>March 29, 2010 — copyright years updated</li>
|
<li>March 29, 2010 — copyright years updated</li>
|
||||||
|
<li>February 18, 2011 — copyright years updated</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@@ -42,7 +42,6 @@ import rfc822
|
|||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
import base64
|
import base64
|
||||||
import crypt
|
|
||||||
import BaseHTTPServer
|
import BaseHTTPServer
|
||||||
|
|
||||||
if LIBRARY_DIR:
|
if LIBRARY_DIR:
|
||||||
@@ -55,6 +54,26 @@ import viewvc
|
|||||||
import compat; compat.for_standalone()
|
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:
|
class Options:
|
||||||
port = 49152 # default TCP/IP port used for the server
|
port = 49152 # default TCP/IP port used for the server
|
||||||
start_gui = 0 # No GUI unless requested.
|
start_gui = 0 # No GUI unless requested.
|
||||||
@@ -182,7 +201,7 @@ class ViewVCHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
file_user, file_pass = string.split(line.rstrip(), ':', 1)
|
file_user, file_pass = string.split(line.rstrip(), ':', 1)
|
||||||
if username == file_user:
|
if username == file_user:
|
||||||
return file_pass == crypt.crypt(password, file_pass[:2])
|
return _check_passwd(password, file_pass)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
@@ -693,11 +712,26 @@ def cli(argv):
|
|||||||
import getopt
|
import getopt
|
||||||
class BadUsage(Exception): pass
|
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:
|
try:
|
||||||
opts, args = getopt.getopt(argv[1:], 'gdc:p:r:h:s:',
|
opts, args = getopt.getopt(argv[1:], short_opts, long_opts)
|
||||||
['gui', 'daemon', 'config-file=', 'host=',
|
|
||||||
'port=', 'repository=', 'script-alias=',
|
|
||||||
'htpasswd-file='])
|
|
||||||
for opt, val in opts:
|
for opt, val in opts:
|
||||||
if opt in ('-g', '--gui'):
|
if opt in ('-g', '--gui'):
|
||||||
options.start_gui = 1
|
options.start_gui = 1
|
||||||
@@ -729,6 +763,13 @@ def cli(argv):
|
|||||||
if not os.path.isfile(val):
|
if not os.path.isfile(val):
|
||||||
raise BadUsage, "'%s' does not appear to be a valid " \
|
raise BadUsage, "'%s' does not appear to be a valid " \
|
||||||
"htpasswd file." % (val)
|
"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
|
options.htpasswd_file = val
|
||||||
if options.start_gui and options.config_file:
|
if options.start_gui and options.config_file:
|
||||||
raise BadUsage, "--config-file option is not valid in GUI mode."
|
raise BadUsage, "--config-file option is not valid in GUI mode."
|
||||||
@@ -785,10 +826,11 @@ Options:
|
|||||||
"cgi-bin/viewvc", then ViewVC will be accessible
|
"cgi-bin/viewvc", then ViewVC will be accessible
|
||||||
at "http://%(host)s:%(port)s/cgi-bin/viewvc".
|
at "http://%(host)s:%(port)s/cgi-bin/viewvc".
|
||||||
[default: %(script_alias)s]
|
[default: %(script_alias)s]
|
||||||
|
|
||||||
--htpasswd-file=FILE Demand authentication from clients, validating
|
--htpasswd-file=FILE Demand authentication from clients, validating
|
||||||
authentication credentials against Apache
|
authentication credentials against FILE, which is
|
||||||
htpasswd file FILE.
|
an Apache htpasswd file that employs CRYPT
|
||||||
|
encryption. (Sorry, no DIGEST support yet.)
|
||||||
|
|
||||||
--gui (-g) Pop up a graphical interface for serving and
|
--gui (-g) Pop up a graphical interface for serving and
|
||||||
testing ViewVC. NOTE: this requires a valid
|
testing ViewVC. NOTE: this requires a valid
|
||||||
|
@@ -572,6 +572,14 @@
|
|||||||
##
|
##
|
||||||
#show_subdir_lastmod = 0
|
#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: Show the most recent log entry in directory listings.
|
||||||
##
|
##
|
||||||
#show_logs = 1
|
#show_logs = 1
|
||||||
@@ -649,6 +657,26 @@
|
|||||||
##
|
##
|
||||||
#log_pagesize = 0
|
#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
|
## limit_changes: Maximum number of changed paths shown per commit in
|
||||||
## the Subversion revision view and in query results. This is not a
|
## the Subversion revision view and in query results. This is not a
|
||||||
## hard limit (the UI provides options to show all changed paths), but
|
## hard limit (the UI provides options to show all changed paths), but
|
||||||
|
@@ -2144,6 +2144,32 @@ td {
|
|||||||
<td>List</td>
|
<td>List</td>
|
||||||
<td>Set of configured viewable repositories.</td>
|
<td>Set of configured viewable repositories.</td>
|
||||||
</tr>
|
</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">
|
<tr class="varlevel2">
|
||||||
<td class="varname">roots.name</td>
|
<td class="varname">roots.name</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
@@ -2157,17 +2183,24 @@ td {
|
|||||||
configuration can have negative security implications. Use this
|
configuration can have negative security implications. Use this
|
||||||
token at your own risk.</td>
|
token at your own risk.</td>
|
||||||
</tr>
|
</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">
|
<tr class="varlevel2">
|
||||||
<td class="varname">roots.type</td>
|
<td class="varname">roots.type</td>
|
||||||
<td>String</td>
|
<td>String</td>
|
||||||
<td>Version control type of a configured repository. Valid
|
<td>Version control type of a configured repository. Valid
|
||||||
values: <tt>cvs</tt>, <tt>svn</tt>.</td>
|
values: <tt>cvs</tt>, <tt>svn</tt>.</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-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
|
# 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
|
# 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 [])
|
self.parser.read(self.conf_path or [])
|
||||||
|
|
||||||
for section in self.parser.sections():
|
for section in self.parser.sections():
|
||||||
if self._is_allowed_section(self.parser, section,
|
if self._is_allowed_section(section, self._base_sections):
|
||||||
self._base_sections):
|
|
||||||
self._process_section(self.parser, section, section)
|
self._process_section(self.parser, section, section)
|
||||||
|
|
||||||
if vhost and self.parser.has_section('vhosts'):
|
if vhost and self.parser.has_section('vhosts'):
|
||||||
@@ -222,7 +221,7 @@ class Config:
|
|||||||
|
|
||||||
setattr(sc, opt, value)
|
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
|
"""Return 1 iff SECTION is an allowed section, defined as being
|
||||||
explicitly present in the ALLOWED_SECTIONS list or present in the
|
explicitly present in the ALLOWED_SECTIONS list or present in the
|
||||||
form 'someprefix-*' in that list."""
|
form 'someprefix-*' in that list."""
|
||||||
@@ -235,7 +234,7 @@ class Config:
|
|||||||
return 1
|
return 1
|
||||||
return 0
|
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
|
"""Test if SECTION is an allowed override section for sections of
|
||||||
type SECTYPE ('vhosts' or 'root', currently) and type-specifier
|
type SECTYPE ('vhosts' or 'root', currently) and type-specifier
|
||||||
SECSPEC (a rootname or vhostname, currently). If it is, return
|
SECSPEC (a rootname or vhostname, currently). If it is, return
|
||||||
@@ -248,7 +247,7 @@ class Config:
|
|||||||
if section[:lcv] != cv:
|
if section[:lcv] != cv:
|
||||||
return None
|
return None
|
||||||
base_section = section[lcv:]
|
base_section = section[lcv:]
|
||||||
if self._is_allowed_section(parser, base_section,
|
if self._is_allowed_section(base_section,
|
||||||
self._allowed_overrides[sectype]):
|
self._allowed_overrides[sectype]):
|
||||||
return base_section
|
return base_section
|
||||||
raise IllegalOverrideSection(sectype, section)
|
raise IllegalOverrideSection(sectype, section)
|
||||||
@@ -261,8 +260,7 @@ class Config:
|
|||||||
|
|
||||||
# Overlay any option sections associated with this vhost name.
|
# Overlay any option sections associated with this vhost name.
|
||||||
for section in parser.sections():
|
for section in parser.sections():
|
||||||
base_section = self._is_allowed_override(parser, 'vhost',
|
base_section = self._is_allowed_override('vhost', canon_vhost, section)
|
||||||
canon_vhost, section)
|
|
||||||
if base_section:
|
if base_section:
|
||||||
self._process_section(parser, section, base_section)
|
self._process_section(parser, section, base_section)
|
||||||
|
|
||||||
@@ -288,8 +286,7 @@ class Config:
|
|||||||
return
|
return
|
||||||
|
|
||||||
for section in self.parser.sections():
|
for section in self.parser.sections():
|
||||||
base_section = self._is_allowed_override(self.parser, 'root',
|
base_section = self._is_allowed_override('root', rootname, section)
|
||||||
rootname, section)
|
|
||||||
if base_section:
|
if base_section:
|
||||||
# We can currently only deal with root overlays happening
|
# We can currently only deal with root overlays happening
|
||||||
# once, so check that we've not yet done any overlaying of
|
# 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.template_dir = "templates"
|
||||||
self.options.docroot = None
|
self.options.docroot = None
|
||||||
self.options.show_subdir_lastmod = 0
|
self.options.show_subdir_lastmod = 0
|
||||||
|
self.options.show_roots_lastmod = 0
|
||||||
self.options.show_logs = 1
|
self.options.show_logs = 1
|
||||||
self.options.show_log_in_markup = 1
|
self.options.show_log_in_markup = 1
|
||||||
self.options.cross_copies = 1
|
self.options.cross_copies = 1
|
||||||
@@ -436,6 +434,7 @@ class Config:
|
|||||||
self.options.use_re_search = 0
|
self.options.use_re_search = 0
|
||||||
self.options.dir_pagesize = 0
|
self.options.dir_pagesize = 0
|
||||||
self.options.log_pagesize = 0
|
self.options.log_pagesize = 0
|
||||||
|
self.options.log_pagesextra = 3
|
||||||
self.options.limit_changes = 100
|
self.options.limit_changes = 100
|
||||||
|
|
||||||
self.templates.diff = None
|
self.templates.diff = None
|
||||||
|
@@ -29,7 +29,15 @@ class GenericViewVCAuthorizer:
|
|||||||
def check_root_access(self, rootname):
|
def check_root_access(self, rootname):
|
||||||
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
|
"""Return 1 iff the associated username is permitted to read ROOTNAME."""
|
||||||
pass
|
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):
|
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||||
"""Return 1 iff the associated username is permitted to read
|
"""Return 1 iff the associated username is permitted to read
|
||||||
revision REV of the path PATH_PARTS (of type PATHTYPE) in
|
revision REV of the path PATH_PARTS (of type PATHTYPE) in
|
||||||
@@ -44,6 +52,9 @@ class ViewVCAuthorizer(GenericViewVCAuthorizer):
|
|||||||
"""The uber-permissive authorizer."""
|
"""The uber-permissive authorizer."""
|
||||||
def check_root_access(self, rootname):
|
def check_root_access(self, rootname):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
def check_universal_access(self, rootname):
|
||||||
|
return 1
|
||||||
|
|
||||||
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||||
return 1
|
return 1
|
||||||
|
@@ -23,7 +23,14 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
|||||||
|
|
||||||
def check_root_access(self, rootname):
|
def check_root_access(self, rootname):
|
||||||
return 1
|
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):
|
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||||
# No path? No problem.
|
# No path? No problem.
|
||||||
if not path_parts:
|
if not path_parts:
|
||||||
|
@@ -46,6 +46,13 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
|||||||
def check_root_access(self, rootname):
|
def check_root_access(self, rootname):
|
||||||
return self._check_root_path_access(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):
|
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||||
root_path = rootname
|
root_path = rootname
|
||||||
if path_parts:
|
if path_parts:
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-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
|
# 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
|
# 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.
|
# option names.
|
||||||
cp = ConfigParser()
|
cp = ConfigParser()
|
||||||
cp.optionxform = lambda x: x
|
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
|
# Figure out if there are any aliases for the current username
|
||||||
aliases = []
|
aliases = []
|
||||||
@@ -221,6 +224,36 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
|
|||||||
paths = self._get_paths_for_root(rootname)
|
paths = self._get_paths_for_root(rootname)
|
||||||
return (paths is not None) and 1 or 0
|
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):
|
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
|
||||||
# Crawl upward from the path represented by PATH_PARTS toward to
|
# Crawl upward from the path represented by PATH_PARTS toward to
|
||||||
# the root of the repository, looking for an explicitly grant or
|
# 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):
|
if not vclib.check_root_access(self):
|
||||||
raise vclib.ReposNotFound(name)
|
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):
|
def rootname(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-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
|
# 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
|
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
import urllib
|
||||||
|
|
||||||
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
_re_url = re.compile('^(http|https|file|svn|svn\+[^:]+)://')
|
||||||
|
|
||||||
@@ -23,8 +24,20 @@ def canonicalize_rootpath(rootpath):
|
|||||||
import svn.core
|
import svn.core
|
||||||
return svn.core.svn_path_canonicalize(rootpath)
|
return svn.core.svn_path_canonicalize(rootpath)
|
||||||
except:
|
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):
|
if re.search(_re_url, rootpath):
|
||||||
return rootpath[-1] == '/' and rootpath[:-1] or rootpath
|
return rootpath.rstrip('/')
|
||||||
return os.path.normpath(rootpath)
|
return os.path.normpath(rootpath)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-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
|
# 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
|
# 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,
|
client.svn_client_log4([url], start_rev, start_rev, end_rev,
|
||||||
log_limit, 1, not cross_copies, 0, None,
|
log_limit, 1, not cross_copies, 0, None,
|
||||||
cb_func, ctx)
|
cb_func, ctx)
|
||||||
except NameError:
|
except AttributeError:
|
||||||
# Wrap old svn_log_message_receiver_t interface with a
|
# Wrap old svn_log_message_receiver_t interface with a
|
||||||
# svn_log_entry_t one.
|
# svn_log_entry_t one.
|
||||||
def cb_convert(paths, revision, author, date, message, pool):
|
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.youngest = ra.svn_ra_get_latest_revnum(self.ra_session)
|
||||||
self._dirent_cache = { }
|
self._dirent_cache = { }
|
||||||
self._revinfo_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):
|
def rootname(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-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
|
# 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
|
# 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):
|
def __getitem__(self, idx):
|
||||||
return self.histories[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):
|
def _get_last_history_rev(fsroot, path):
|
||||||
history = fs.node_history(fsroot, path)
|
history = fs.node_history(fsroot, path)
|
||||||
history = fs.history_prev(history, 0)
|
history = fs.history_prev(history, 0)
|
||||||
@@ -427,6 +374,10 @@ class LocalSubversionRepository(vclib.Repository):
|
|||||||
self._fsroots = {}
|
self._fsroots = {}
|
||||||
self._revinfo_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):
|
def rootname(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -442,13 +393,8 @@ class LocalSubversionRepository(vclib.Repository):
|
|||||||
def itemtype(self, path_parts, rev):
|
def itemtype(self, path_parts, rev):
|
||||||
rev = self._getrev(rev)
|
rev = self._getrev(rev)
|
||||||
basepath = self._getpath(path_parts)
|
basepath = self._getpath(path_parts)
|
||||||
kind = fs.check_path(self._getroot(rev), basepath)
|
pathtype = self._gettype(basepath, rev)
|
||||||
pathtype = None
|
if pathtype is None:
|
||||||
if kind == core.svn_node_dir:
|
|
||||||
pathtype = vclib.DIR
|
|
||||||
elif kind == core.svn_node_file:
|
|
||||||
pathtype = vclib.FILE
|
|
||||||
else:
|
|
||||||
raise vclib.ItemNotFound(path_parts)
|
raise vclib.ItemNotFound(path_parts)
|
||||||
if not vclib.check_path_access(self, path_parts, pathtype, rev):
|
if not vclib.check_path_access(self, path_parts, pathtype, rev):
|
||||||
raise vclib.ItemNotFound(path_parts)
|
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
|
# 'limit' parameter here as numeric cut-off for the depth of our
|
||||||
# history search.
|
# history search.
|
||||||
if options.get('svn_latest_log', 0):
|
if options.get('svn_latest_log', 0):
|
||||||
revision = _log_helper(self, path, rev, lockinfo)
|
revision = self._log_helper(path, rev, lockinfo)
|
||||||
if revision:
|
if revision:
|
||||||
revision.prev = None
|
revision.prev = None
|
||||||
revs.append(revision)
|
revs.append(revision)
|
||||||
else:
|
else:
|
||||||
history = _get_history(self, path, rev, path_type,
|
history = self._get_history(path, rev, path_type, first + limit, options)
|
||||||
first + limit, options)
|
|
||||||
if len(history) < first:
|
if len(history) < first:
|
||||||
history = []
|
history = []
|
||||||
if limit:
|
if limit:
|
||||||
history = history[first:first+limit]
|
history = history[first:first+limit]
|
||||||
|
|
||||||
for hist_rev, hist_path in history:
|
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 revision:
|
||||||
# If we have unreadable copyfrom data, obscure it.
|
# If we have unreadable copyfrom data, obscure it.
|
||||||
if revision.copy_path is not None:
|
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)
|
raise vclib.Error("Path '%s' is not a file." % path)
|
||||||
rev = self._getrev(rev)
|
rev = self._getrev(rev)
|
||||||
fsroot = self._getroot(rev)
|
fsroot = self._getroot(rev)
|
||||||
history = _get_history(self, path, rev, path_type, 0,
|
history = self._get_history(path, rev, path_type, 0,
|
||||||
{'svn_cross_copies': 1})
|
{'svn_cross_copies': 1})
|
||||||
youngest_rev, youngest_path = history[0]
|
youngest_rev, youngest_path = history[0]
|
||||||
oldest_rev, oldest_path = history[-1]
|
oldest_rev, oldest_path = history[-1]
|
||||||
source = BlameSource(_rootpath2url(self.rootpath, path),
|
source = BlameSource(_rootpath2url(self.rootpath, path),
|
||||||
youngest_rev, oldest_rev, self.config_dir)
|
youngest_rev, oldest_rev, self.config_dir)
|
||||||
return source, youngest_rev
|
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):
|
def _revinfo(self, rev, include_changed_paths=0):
|
||||||
"""Internal-use, cache-friendly revision information harvester."""
|
"""Internal-use, cache-friendly revision information harvester."""
|
||||||
|
|
||||||
def _revinfo_helper(rev, include_changed_paths):
|
def _get_changed_paths(fsroot):
|
||||||
# Get the revision property info. (Would use
|
"""Return a 3-tuple: found_readable, found_unreadable, changed_paths."""
|
||||||
# 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)
|
|
||||||
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
editor = repos.ChangeCollector(self.fs_ptr, fsroot)
|
||||||
e_ptr, e_baton = delta.make_editor(editor)
|
e_ptr, e_baton = delta.make_editor(editor)
|
||||||
repos.svn_repos_replay(fsroot, e_ptr, e_baton)
|
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 vclib.check_path_access(self, parts, pathtype, rev):
|
||||||
if is_copy and change.base_path and (change.base_path != path):
|
if is_copy and change.base_path and (change.base_path != path):
|
||||||
parts = _path_parts(change.base_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
|
is_copy = 0
|
||||||
change.base_path = None
|
change.base_path = None
|
||||||
change.base_rev = None
|
change.base_rev = None
|
||||||
@@ -674,24 +643,108 @@ class LocalSubversionRepository(vclib.Repository):
|
|||||||
found_readable = 1
|
found_readable = 1
|
||||||
else:
|
else:
|
||||||
found_unreadable = 1
|
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
|
# Optimization: If our caller doesn't care about the changed
|
||||||
# here for authz reasons only. That means the minute we've
|
# paths, and we don't need them to do authz determinations, let's
|
||||||
# found both a readable and an unreadable path, we can bail out.
|
# get outta here.
|
||||||
if (not include_changed_paths) and found_readable and found_unreadable:
|
if self.auth is None and not include_changed_paths:
|
||||||
return date, author, None, None, None
|
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,
|
# Filter our metadata where necessary, and return the requested data.
|
||||||
# and return the requested data.
|
|
||||||
if found_unreadable:
|
if found_unreadable:
|
||||||
msg = None
|
msg = None
|
||||||
if not found_readable:
|
if not found_readable:
|
||||||
author = None
|
author = None
|
||||||
date = None
|
date = None
|
||||||
if include_changed_paths:
|
return date, author, msg, revprops, changedpaths
|
||||||
return date, author, msg, revprops, changedpaths.values()
|
|
||||||
else:
|
|
||||||
return date, author, msg, revprops, None
|
|
||||||
|
|
||||||
# Consult the revinfo cache first. If we don't have cached info,
|
# 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
|
# 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
|
self._revinfo_cache[rev] = cached_info
|
||||||
return tuple(cached_info)
|
return tuple(cached_info)
|
||||||
|
|
||||||
def revinfo(self, rev):
|
def _log_helper(self, path, rev, lockinfo):
|
||||||
return self._revinfo(rev, 1)
|
rev_root = fs.revision_root(self.fs_ptr, rev)
|
||||||
|
copyfrom_rev, copyfrom_path = fs.copied_from(rev_root, path)
|
||||||
def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
|
date, author, msg, revprops, changes = self._revinfo(rev)
|
||||||
p1 = self._getpath(path_parts1)
|
if fs.is_file(rev_root, path):
|
||||||
p2 = self._getpath(path_parts2)
|
size = fs.file_length(rev_root, path)
|
||||||
r1 = self._getrev(rev1)
|
else:
|
||||||
r2 = self._getrev(rev2)
|
size = None
|
||||||
if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
|
return Revision(rev, date, author, msg, size, lockinfo, path,
|
||||||
raise vclib.ItemNotFound(path_parts1)
|
copyfrom_path and _cleanup_path(copyfrom_path),
|
||||||
if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
|
copyfrom_rev)
|
||||||
raise vclib.ItemNotFound(path_parts2)
|
|
||||||
|
|
||||||
args = vclib._diff_args(type, options)
|
|
||||||
|
|
||||||
def _date_from_rev(rev):
|
def _get_history(self, path, rev, path_type, limit=0, options={}):
|
||||||
date, author, msg, revprops, changes = self._revinfo(rev)
|
if self.youngest == 0:
|
||||||
return date
|
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:
|
try:
|
||||||
temp1 = temp_checkout(self, p1, r1)
|
repos.svn_repos_history(self.fs_ptr, path, history.add_history,
|
||||||
temp2 = temp_checkout(self, p2, r2)
|
1, rev, options.get('svn_cross_copies', 0))
|
||||||
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:
|
except core.SubversionException, e:
|
||||||
_fix_subversion_exception(e)
|
_fix_subversion_exception(e)
|
||||||
if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
|
if e.apr_err != _SVN_ERR_CEASE_INVOCATION:
|
||||||
raise vclib.InvalidRevision
|
raise
|
||||||
raise
|
|
||||||
|
|
||||||
def isexecutable(self, path_parts, rev):
|
# Now, iterate over those history items, checking for changes of
|
||||||
props = self.itemprops(path_parts, rev) # does authz-check
|
# location, pruning as necessitated by authz rules.
|
||||||
return props.has_key(core.SVN_PROP_EXECUTABLE)
|
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):
|
def _getpath(self, path_parts):
|
||||||
return string.join(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)
|
r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev)
|
||||||
return r
|
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):
|
def get_youngest_revision(self):
|
||||||
return self.youngest
|
return self.youngest
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-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
|
# 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
|
# 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
|
# this comes from our library; measure the startup time
|
||||||
import debug
|
import debug
|
||||||
@@ -79,10 +79,6 @@ _sticky_vars = [
|
|||||||
'limit_changes',
|
'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
|
# for reading/writing between a couple descriptors
|
||||||
CHUNK_SIZE = 8192
|
CHUNK_SIZE = 8192
|
||||||
|
|
||||||
@@ -897,25 +893,37 @@ def get_view_template(cfg, view_name, language="en"):
|
|||||||
|
|
||||||
return template
|
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
|
"""Return a file handle to a response body stream, after outputting
|
||||||
any queued special headers (on REQUEST.server) and (optionally) a
|
any queued special headers (on REQUEST.server) and (optionally) a
|
||||||
'Content-Type' header whose value is CONTENT_TYPE and character set
|
'Content-Type' header whose value is CONTENT_TYPE and character set
|
||||||
is ENCODING. After this is called, it is too late to add new
|
is ENCODING.
|
||||||
headers to the response."""
|
|
||||||
|
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:
|
if request.gzip_compress_level:
|
||||||
request.server.addheader('Content-Encoding', 'gzip')
|
request.server.addheader('Content-Encoding', 'gzip')
|
||||||
|
elif content_length is not None:
|
||||||
|
request.server.addheader('Content-Length', content_length)
|
||||||
|
|
||||||
if content_type and encoding:
|
if content_type and encoding:
|
||||||
request.server.header("%s; charset=%s" % (content_type, encoding))
|
request.server.header("%s; charset=%s" % (content_type, encoding))
|
||||||
elif content_type:
|
elif content_type:
|
||||||
request.server.header(content_type)
|
request.server.header(content_type)
|
||||||
else:
|
else:
|
||||||
request.server.header()
|
request.server.header()
|
||||||
|
|
||||||
if request.gzip_compress_level:
|
if request.gzip_compress_level:
|
||||||
fp = gzip.GzipFile('', 'wb', request.gzip_compress_level,
|
fp = gzip.GzipFile('', 'wb', request.gzip_compress_level,
|
||||||
request.server.file())
|
request.server.file())
|
||||||
else:
|
else:
|
||||||
fp = request.server.file()
|
fp = request.server.file()
|
||||||
|
|
||||||
return fp
|
return fp
|
||||||
|
|
||||||
def generate_page(request, view_name, data, content_type=None):
|
def generate_page(request, view_name, data, content_type=None):
|
||||||
@@ -1519,6 +1527,21 @@ class MarkupPipeWrapper:
|
|||||||
if self.posttext:
|
if self.posttext:
|
||||||
ctx.fp.write(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,
|
def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
||||||
mime_type, encoding):
|
mime_type, encoding):
|
||||||
# Determine if we should use Pygments to highlight our output.
|
# 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):
|
def __getitem__(self, idx):
|
||||||
item = self.blame_source.__getitem__(idx)
|
item = self.blame_source.__getitem__(idx)
|
||||||
item.text = string.expandtabs(item.text, self.tabsize)
|
item.text = string.expandtabs(item.text, self.tabsize)
|
||||||
|
item.text = markup_escaped_urls(item.text)
|
||||||
return item
|
return item
|
||||||
return BlameSourceTabsizeWrapper(blame_source, cfg.options.tabsize)
|
return BlameSourceTabsizeWrapper(blame_source, cfg.options.tabsize)
|
||||||
else:
|
else:
|
||||||
@@ -1593,6 +1617,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
|||||||
break
|
break
|
||||||
line_no = line_no + 1
|
line_no = line_no + 1
|
||||||
line = sapi.escape(string.expandtabs(line, cfg.options.tabsize))
|
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 = vclib.Annotation(line, line_no, None, None, None, None)
|
||||||
item.diff_href = None
|
item.diff_href = None
|
||||||
lines.append(item)
|
lines.append(item)
|
||||||
@@ -1610,6 +1635,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename,
|
|||||||
self.line_no = 0
|
self.line_no = 0
|
||||||
def write(self, buf):
|
def write(self, buf):
|
||||||
### FIXME: Don't bank on write() being called once per line
|
### FIXME: Don't bank on write() being called once per line
|
||||||
|
buf = markup_escaped_urls(buf)
|
||||||
if self.has_blame_data:
|
if self.has_blame_data:
|
||||||
self.blame_data[self.line_no].text = buf
|
self.blame_data[self.line_no].text = buf
|
||||||
else:
|
else:
|
||||||
@@ -1808,12 +1834,18 @@ def view_markup(request):
|
|||||||
if 'markup' not in request.cfg.options.allowed_views:
|
if 'markup' not in request.cfg.options.allowed_views:
|
||||||
raise debug.ViewVCException('Markup view is disabled',
|
raise debug.ViewVCException('Markup view is disabled',
|
||||||
'403 Forbidden')
|
'403 Forbidden')
|
||||||
|
if request.pathtype != vclib.FILE:
|
||||||
|
raise debug.ViewVCException('Unsupported feature: markup view on '
|
||||||
|
'directory', '400 Bad Request')
|
||||||
markup_or_annotate(request, 0)
|
markup_or_annotate(request, 0)
|
||||||
|
|
||||||
def view_annotate(request):
|
def view_annotate(request):
|
||||||
if 'annotate' not in request.cfg.options.allowed_views:
|
if 'annotate' not in request.cfg.options.allowed_views:
|
||||||
raise debug.ViewVCException('Annotation view is disabled',
|
raise debug.ViewVCException('Annotation view is disabled',
|
||||||
'403 Forbidden')
|
'403 Forbidden')
|
||||||
|
if request.pathtype != vclib.FILE:
|
||||||
|
raise debug.ViewVCException('Unsupported feature: annotate view on '
|
||||||
|
'directory', '400 Bad Request')
|
||||||
markup_or_annotate(request, 1)
|
markup_or_annotate(request, 1)
|
||||||
|
|
||||||
def revcmp(rev1, rev2):
|
def revcmp(rev1, rev2):
|
||||||
@@ -1888,9 +1920,16 @@ def view_roots(request):
|
|||||||
href = request.get_url(view_func=view_directory,
|
href = request.get_url(view_func=view_directory,
|
||||||
where='', pathtype=vclib.DIR,
|
where='', pathtype=vclib.DIR,
|
||||||
params={'root': rootname}, escape=1)
|
params={'root': rootname}, escape=1)
|
||||||
|
lastmod = allroots[rootname][2]
|
||||||
roots.append(_item(name=request.server.escape(rootname),
|
roots.append(_item(name=request.server.escape(rootname),
|
||||||
type=allroots[rootname][1],
|
type=allroots[rootname][1],
|
||||||
path=allroots[rootname][0],
|
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))
|
href=href))
|
||||||
|
|
||||||
data = common_template_data(request)
|
data = common_template_data(request)
|
||||||
@@ -2215,10 +2254,11 @@ def paging(data, key, pagestart, local_name, pagesize):
|
|||||||
# Slice
|
# Slice
|
||||||
return data[key][pagestart:pageend]
|
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."""
|
"""Implement sliding window-style paging."""
|
||||||
# Create the picklist
|
# Create the picklist
|
||||||
last_requested = pagestart + (EXTRA_PAGES * pagesize)
|
last_requested = pagestart + (extra_pages * pagesize)
|
||||||
picklist = data['picklist'] = []
|
picklist = data['picklist'] = []
|
||||||
has_more = ezt.boolean(0)
|
has_more = ezt.boolean(0)
|
||||||
for i in range(0, len(data[key]), pagesize):
|
for i in range(0, len(data[key]), pagesize):
|
||||||
@@ -2347,9 +2387,9 @@ def view_log(request):
|
|||||||
first = last = 0
|
first = last = 0
|
||||||
if cfg.options.log_pagesize:
|
if cfg.options.log_pagesize:
|
||||||
log_pagestart = int(request.query_dict.get('log_pagestart', 0))
|
log_pagestart = int(request.query_dict.get('log_pagestart', 0))
|
||||||
first = log_pagestart - min(log_pagestart,
|
total = cfg.options.log_pagesextra * cfg.options.log_pagesize
|
||||||
(EXTRA_PAGES * cfg.options.log_pagesize))
|
first = log_pagestart - min(log_pagestart, total)
|
||||||
last = log_pagestart + ((EXTRA_PAGES + 1) * cfg.options.log_pagesize) + 1
|
last = log_pagestart + (total + cfg.options.log_pagesize) + 1
|
||||||
show_revs = request.repos.itemlog(request.path_parts, request.pathrev,
|
show_revs = request.repos.itemlog(request.path_parts, request.pathrev,
|
||||||
sortby, first, last - first, options)
|
sortby, first, last - first, options)
|
||||||
|
|
||||||
@@ -2610,7 +2650,8 @@ def view_log(request):
|
|||||||
request.get_form(params={'log_pagestart': None})
|
request.get_form(params={'log_pagestart': None})
|
||||||
data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0))
|
data['log_pagestart'] = int(request.query_dict.get('log_pagestart',0))
|
||||||
data['entries'] = paging_sws(data, 'entries', data['log_pagestart'],
|
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)
|
generate_page(request, "log", data)
|
||||||
|
|
||||||
@@ -2751,7 +2792,6 @@ def view_doc(request):
|
|||||||
raise debug.ViewVCException('Static file "%s" not available (%s)'
|
raise debug.ViewVCException('Static file "%s" not available (%s)'
|
||||||
% (document, str(v)), '404 Not Found')
|
% (document, str(v)), '404 Not Found')
|
||||||
|
|
||||||
request.server.addheader('Content-Length', content_length)
|
|
||||||
if document[-3:] == 'png':
|
if document[-3:] == 'png':
|
||||||
mime_type = 'image/png'
|
mime_type = 'image/png'
|
||||||
elif document[-3:] == 'jpg':
|
elif document[-3:] == 'jpg':
|
||||||
@@ -2762,7 +2802,8 @@ def view_doc(request):
|
|||||||
mime_type = 'text/css'
|
mime_type = 'text/css'
|
||||||
else: # assume HTML:
|
else: # assume HTML:
|
||||||
mime_type = None
|
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()
|
fp.close()
|
||||||
|
|
||||||
def rcsdiff_date_reformat(date_str, cfg):
|
def rcsdiff_date_reformat(date_str, cfg):
|
||||||
@@ -4249,11 +4290,26 @@ def list_roots(request):
|
|||||||
for root in cfg.general.svn_roots.keys():
|
for root in cfg.general.svn_roots.keys():
|
||||||
auth = setup_authorizer(cfg, request.username, root)
|
auth = setup_authorizer(cfg, request.username, root)
|
||||||
try:
|
try:
|
||||||
vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root], auth,
|
repos = vclib.svn.SubversionRepository(root, cfg.general.svn_roots[root],
|
||||||
cfg.utilities, cfg.options.svn_config_dir)
|
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:
|
except vclib.ReposNotFound:
|
||||||
continue
|
continue
|
||||||
allroots[root] = [cfg.general.svn_roots[root], 'svn']
|
allroots[root] = [cfg.general.svn_roots[root], 'svn', lastmod]
|
||||||
|
|
||||||
# Add the viewable CVS roots
|
# Add the viewable CVS roots
|
||||||
for root in cfg.general.cvs_roots.keys():
|
for root in cfg.general.cvs_roots.keys():
|
||||||
@@ -4263,7 +4319,7 @@ def list_roots(request):
|
|||||||
cfg.utilities, cfg.options.use_rcsparse)
|
cfg.utilities, cfg.options.use_rcsparse)
|
||||||
except vclib.ReposNotFound:
|
except vclib.ReposNotFound:
|
||||||
continue
|
continue
|
||||||
allroots[root] = [cfg.general.cvs_roots[root], 'cvs']
|
allroots[root] = [cfg.general.cvs_roots[root], 'cvs', None]
|
||||||
|
|
||||||
return allroots
|
return allroots
|
||||||
|
|
||||||
|
@@ -27,7 +27,9 @@ numbers, and not literal):
|
|||||||
3. Verify that copyright years are correct in both the license-1.html
|
3. Verify that copyright years are correct in both the license-1.html
|
||||||
file and the source code.
|
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
|
5. Test, test, test! There is no automatic testsuite available. So
|
||||||
just run with permuting different `viewvc.conf' settings... and
|
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
|
14. Upload the created archive files (tar.gz and zip) into the Files
|
||||||
and Documents section of the Tigris.org project, and modify the
|
and Documents section of the Tigris.org project, and modify the
|
||||||
CHECKSUMS document there accordingly. Also, drop a copy of the
|
CHECKSUMS document there accordingly:
|
||||||
archive files into the root directory of the viewvc.org website
|
|
||||||
(unversioned).
|
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)
|
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
|
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
|
(and possibly, minor or major) release. (For the Milestone sort
|
||||||
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
|
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
|
18. Send to the announce@ list a message explaining all the cool new
|
||||||
features, and post similar announcements to other places interested
|
features, and post similar announcements to other places interested
|
||||||
in this sort of stuff, such as Freshmeat (http://www.freshmeat.net).
|
in this sort of stuff, such as Freshmeat (http://www.freshmeat.net).
|
||||||
|
@@ -9,6 +9,12 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="vc_header_sort">Name</th>
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
@@ -20,6 +26,12 @@
|
|||||||
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
|
<img src="[docroot]/images/dir.png" alt="" class="vc_icon" />
|
||||||
[roots.name]</a>
|
[roots.name]</a>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
[end]
|
[end]
|
||||||
</tbody>
|
</tbody>
|
||||||
|
Reference in New Issue
Block a user