Post-merge debug.

Also return some lost files back, document 4intra.net modifications
in CHANGES and README files, remove 'union' authorizer and rewritehtml
as the similar functionality is already provided by the core.

Also fix diffs for non-bash (sh/ash/dash) shells.
custis
Vitaliy Filippov 2013-07-18 20:02:34 +04:00
parent 56c2b61458
commit 983f9c7379
26 changed files with 560 additions and 633 deletions

25
CHANGES
View File

@ -1,3 +1,28 @@
4Intra.net/CUSTIS improvements
* Support for full-text search over file contents, including binary
documents like *.doc and so on using Sphinx Search and Apache Tika
server. Patched Tika with fixes for #TIKA709 and #TIKA964 is highly
recommended:
http://wiki.4intra.net/public/tika-app-1.2-fix-TIKA709-TIKA964.jar
(SHA1 efef722a5e2322f7c2616d096552a48134dc5faa)
* Access right checks in query results.
* Access right checks for repository root directories.
* New query parameters: repository, repo type, revision number.
* Authorizer for CVSnt ACLs.
* InnoDB, additional database indexes and some search query
optimisations.
* Support for specifying path to MySQL UNIX socket.
* Asynchronous hook examples for updating SVN and CVS repos.
* Slightly more correct charset guessing, especially for Russian.
* Support for diffing added/removed files.
* File lists in RSS feeds for 'classic' template.
* Path configuration via a single 'viewvcinstallpath.py' file,
not via editing multiple bin/* files.
* Link to repository list instead of viewvc.org from the logo
* "rcsfile service" support used to workaround command execution
problems (forks) from Apache mod_python.
Version 1.2.0 (released ??-???-????)
* bumped minimum support Python version to 2.4

View File

@ -140,7 +140,7 @@ installation instructions.
default_root
root_as_url_component
rcs_dir
mime_types_file
mime_types_files
There are some other options that are usually nice to change. See
viewvc.conf for more information. ViewVC provides a working,

2
README
View File

@ -1,5 +1,7 @@
ViewVC -- Viewing the content of CVS/SVN repositories with a Webbrowser.
This is the 4Intra.net patched version with some extra features.
Please read the file INSTALL for more information.
And see windows/README for more information on running ViewVC on

View File

@ -363,6 +363,7 @@ class Config:
if section == root_authz_section:
for key, value in self._get_parser_items(self.parser, section):
params[key] = value
params['__config'] = self
return authorizer, params
def get_authorizer_params(self, authorizer=None):
@ -377,6 +378,7 @@ class Config:
sub_config = getattr(self, authz_section)
for attr in dir(sub_config):
params[attr] = getattr(sub_config, attr)
params['__config'] = self
return params
def guesser(self):

View File

@ -14,6 +14,7 @@ import sys
import time
import re
import cgi
import string
import vclib
import dbi
@ -530,7 +531,7 @@ class CheckinDatabase:
' AND dirid=dirs.id AND fileid=files.id' % (commits_table, commits_table, commits_table, ','.join(ids))
)
def CreateSQLQueryString(self, query):
def CreateSQLQueryString(self, query, detect_leftover=0):
commits_table = self.GetCommitsTable()
fields = [
commits_table+".*",
@ -692,7 +693,7 @@ class CheckinDatabase:
revision = rows[docid]['revision']
fp = None
try:
fp, _ = self.request.get_repo(repo).repos.openfile(path, revision)
fp, _ = self.request.get_repo(repo).repos.openfile(path, revision, {})
content = fp.read()
fp.close()
content = self.guesser.utf8(content)
@ -757,13 +758,14 @@ class CheckinDatabase:
rows = self.RunSphinxQuery(query)
else:
# Use regular queries when document content is not searched
rows = self.selectall(self.db, self.CreateSQLQueryString(query))
rows = self.selectall(self.db, self.CreateSQLQueryString(query, 1))
# Check rights
rows = (r for r in rows if self.check_commit_access(
r['repository_name'],
r['dir_name'],
r['file_name'],
r['revision']))
query.SetExecuted()
# Convert rows to commit objects
for row in rows:
@ -1211,15 +1213,13 @@ def CreateCommit():
def CreateCheckinQuery():
return CheckinDatabaseQuery()
def ConnectDatabase(cfg, readonly=0):
if readonly:
user = cfg.cvsdb.readonly_user
passwd = cfg.cvsdb.readonly_passwd
else:
user = cfg.cvsdb.user
passwd = cfg.cvsdb.passwd
db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, user, passwd,
cfg.cvsdb.database_name)
def ConnectDatabase(cfg, request=None, readonly=0):
db = CheckinDatabase(
readonly = readonly,
request = request,
cfg = cfg.cvsdb,
guesser = cfg.guesser(),
)
db.Connect()
return db

View File

@ -32,6 +32,8 @@ server = None
# that character as-is, and sometimes needs to embed escaped values
# into HTML attributes.
def escape(s):
try: s = s.encode('utf8')
except: pass
s = str(s)
s = s.replace('&', '&')
s = s.replace('>', '>')

View File

@ -41,7 +41,7 @@ class GenericViewVCAuthorizer:
pass
##############################################################################
class ViewVCAuthorizer(GenericViewVCAuthorizer):

View File

@ -84,7 +84,7 @@ class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
if not path_parts:
return 1
return self.check(rootname, [], '')
if pathtype == vclib.DIR:
return self.check(rootname, path_parts, '')
f = path_parts[-1]

View File

@ -1,67 +0,0 @@
# -*-python-*-
#
# Copyright (C) 2009 Vitaliy Filippov.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
import vcauth
import vclib
import string
import debug
class ViewVCAuthorizer(vcauth.GenericViewVCAuthorizer):
"""A 'union' authorizer: it makes possible to use different authorizers
for different roots."""
def __init__(self, username, params={}):
self.username = username
self.params = params
self.cfg = params['__config']
self.default = params.get('default', '')
self.byroot = {}
self.authz = {}
union = params.get('union', '')
for i in union.split(','):
if i.find(':') < 0:
continue
(root, auth) = i.split(':', 2)
self.byroot[root.strip()] = auth.strip()
def create_authz(self, rootname):
aname = self.byroot.get(rootname, '') or self.default
if not aname:
return None
if self.authz.get(aname, None):
return self.authz[aname]
import imp
fp = None
try:
try:
fp, path, desc = imp.find_module(aname, vcauth.__path__)
my_auth = imp.load_module('viewvc', fp, path, desc)
except ImportError:
raise
finally:
if fp:
fp.close()
params = self.cfg.get_authorizer_params(aname, rootname)
self.authz[aname] = my_auth.ViewVCAuthorizer(self.username, params)
return self.authz[aname]
def check_root_access(self, rootname):
a = self.create_authz(rootname)
if a:
return a.check_root_access(rootname)
return None
def check_path_access(self, rootname, path_parts, pathtype, rev=None):
a = self.create_authz(rootname)
if a:
return a.check_path_access(rootname, path_parts, pathtype, rev)
return None

View File

@ -392,7 +392,7 @@ class _diff_fp:
args.extend(["-L", self._label(info1), "-L", self._label(info2)])
args.extend([temp1, temp2])
args.insert(0, diff_cmd)
os.system("'"+"' '".join(args)+"' &> "+self.temp3)
os.system("'"+"' '".join(args)+"' > '"+self.temp3+"' 2> '"+self.temp3+"'")
self.fp = open(self.temp3, 'rb')
self.fp.seek(0)

View File

@ -14,7 +14,7 @@
#
# -----------------------------------------------------------------------
__version__ = '1.2-dev-2243'
__version__ = '1.2.svn2905+4intranet-1'
# this comes from our library; measure the startup time
import debug
@ -39,6 +39,7 @@ import types
import urllib
import datetime
import locale
import string
# These modules come from our library (the stub has set up the path)
from common import _item, _RCSDIFF_NO_CHANGES, _RCSDIFF_IS_BINARY, _RCSDIFF_ERROR, TemplateData
@ -152,7 +153,7 @@ class Request:
roottype, rootpath, rootname = locate_root(self.cfg, rootname)
if roottype:
# Setup an Authorizer for this rootname and username
authorizer = setup_authorizer(self.cfg, self.username, self.rootname)
authorizer = setup_authorizer(self.cfg, self.username, rootname)
# Create the repository object
if roottype == 'cvs':
@ -298,7 +299,7 @@ class Request:
debug.t_start('select-repos')
try:
if self.repos.roottype() == 'cvs':
self.rootpath = vclib.ccvs.canonicalize_rootpath(rootpath)
self.rootpath = vclib.ccvs.canonicalize_rootpath(self.rootpath)
self.repos = vclib.ccvs.CVSRepository(self.rootname,
self.rootpath,
self.auth,
@ -308,7 +309,7 @@ class Request:
# $CVSHeader$
os.environ['CVSROOT'] = self.rootpath
elif self.repos.roottype() == 'svn':
self.rootpath = vclib.svn.canonicalize_rootpath(rootpath)
self.rootpath = vclib.svn.canonicalize_rootpath(self.rootpath)
self.repos = vclib.svn.SubversionRepository(self.rootname,
self.rootpath,
self.auth,
@ -898,6 +899,7 @@ def setup_authorizer(cfg, username, rootname=None):
return None
# First, try to load a module with the configured name.
# FIXME FIXME FIXME This hack leads to ALL authorizers having 'viewvc.ViewVCAuthorizer' as their class name
import imp
fp = None
try:
@ -1212,7 +1214,6 @@ _re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
# Matches email addresses
_re_rewrite_email = re.compile('([-a-zA-Z0-9_.\+]+)@'
'(([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})')
_re_rewrites_html = [ [ _re_rewrite_url, r'<a href="\1">\1</a>' ] ]
# Matches revision references
_re_rewrite_svnrevref = re.compile(r'\b(r|rev #?|revision #?)([0-9]+)\b')
@ -1306,9 +1307,6 @@ class ViewVCHtmlFormatter:
trunc_s = mobj.group(1)[:maxlen]
return self._entity_encode(trunc_s), len(trunc_s)
def format_utf8(self, mobj, userdata, maxlen=0):
return userdata(mobj.group(0))
def format_svnrevref(self, mobj, userdata, maxlen=0):
"""Return a 2-tuple containing:
- the text represented by MatchObject MOBJ, formatted as an
@ -1450,11 +1448,15 @@ class LogFormatter:
def get(self, maxlen=0, htmlize=1):
cfg = self.request.cfg
# Prefer the cache.
if self.cache.has_key((maxlen, htmlize)):
return self.cache[(maxlen, htmlize)]
# UTF-8 in CVS messages.
if self.request.roottype == 'cvs':
self.log = self.request.utf8(self.log)
# If we are HTML-izing...
if htmlize:
# ...and we don't yet have ViewVCHtmlFormatter() object tokens...
@ -1462,10 +1464,6 @@ class LogFormatter:
# ... then get them.
lf = ViewVCHtmlFormatter()
# UTF-8 in CVS messages.
if self.request.roottype == 'cvs':
lf.add_formatter('.*', lf.format_utf8, self.request.utf8)
# Rewrite URLs.
lf.add_formatter(_re_rewrite_url, lf.format_url)
@ -1611,6 +1609,7 @@ def common_template_data(request, revision=None, mime_type=None):
'tarball_href' : None,
'up_href' : None,
'username' : request.username,
'env_user_url' : os.environ.get('user_url', ''),
'view' : _view_codes[request.view_func],
'view_href' : None,
'vsn' : __version__,
@ -1866,6 +1865,7 @@ def markup_stream(request, cfg, blame_data, file_lines, filename,
c, encoding = cfg.guesser().guess_charset(content)
if encoding:
file_lines = c.rstrip('\n').split('\n')
file_lines = [ i+'\n' for i in file_lines ]
else:
encoding = 'unknown'
@ -2042,7 +2042,7 @@ def markup_or_annotate(request, is_annotate):
if not mime_type or mime_type == default_mime_type:
try:
fp, revision = request.repos.openfile(path, rev)
fp, revision = request.repos.openfile(path, rev, {})
mime_type = request.cfg.guesser().guess_mime(None, None, fp)
fp.close()
except:
@ -2292,6 +2292,7 @@ def view_roots(request):
# add in the roots for the selection
roots = []
expand_root_parents(request.cfg)
allroots = list_roots(request)
if len(allroots):
rootnames = allroots.keys()
@ -2795,7 +2796,7 @@ def view_log(request):
for rev in show_revs:
entry = _item()
entry.rev = rev.string
entry.state = (cvs and rev.dead and 'dead')
entry.state = (request.roottype == 'cvs' and rev.dead and 'dead')
entry.author = rev.author
entry.changed = rev.changed
entry.date = make_time_string(rev.date, cfg)
@ -4392,7 +4393,8 @@ def validate_query_args(request):
# First, make sure the the XXX_match args have valid values:
arg_match = arg_base + '_match'
arg_match_value = request.query_dict.get(arg_match, 'exact')
if not arg_match_value in ('exact', 'like', 'glob', 'regex', 'notregex'):
if not arg_match_value in ('exact', 'like', 'glob', 'regex', 'notregex') and \
(arg_base != 'comment' or arg_match_value != 'fulltext'):
raise debug.ViewVCException(
'An illegal value was provided for the "%s" parameter.'
% (arg_match),
@ -4442,8 +4444,8 @@ def view_queryform(request):
data = common_template_data(request)
data.merge(TemplateData({
'repos' : request.server.escape(repos),
'repos_match' : request.server.escape(repos_match),
'repos' : request.server.escape(repos or ''),
'repos_match' : request.server.escape(repos_match or ''),
'repos_type' : escaped_query_dict_get('repos_type', ''),
'query_revision' : escaped_query_dict_get('query_revision', ''),
'search_content' : escaped_query_dict_get('search_content', ''),
@ -4855,8 +4857,8 @@ def query_patch(request, commits):
'400 Bad Request')
server_fp.write('Index: %s\n===================================================================\n' % (file))
try:
rdate1, _, _, _ = repos.revinfo(rev1)
rdate2, _, _, _ = repos.revinfo(rev2)
rdate1, _, _, _, _ = repos.revinfo(rev1)
rdate2, _, _, _, _ = repos.revinfo(rev2)
rdate1 = datetime.date.fromtimestamp(rdate1).strftime(' %Y/%m/%d %H:%M:%S')
rdate2 = datetime.date.fromtimestamp(rdate2).strftime(' %Y/%m/%d %H:%M:%S')
except vclib.UnsupportedFeature:
@ -4868,7 +4870,7 @@ def query_patch(request, commits):
p2 = _path_parts(repos.get_location(file, rev2, rev2))
else:
p2 = _path_parts(file)
fd, fr = repos.openfile(p2, rev2)
fd, fr = repos.openfile(p2, rev2, {})
if not fd or rev2 != fr:
raise vclib.ItemNotFound(p2)
if fd:
@ -4886,7 +4888,7 @@ def query_patch(request, commits):
p1 = _path_parts(repos.get_location(p1, rev1, rev1))
else:
p1 = _path_parts(file)
fd, fr = repos.openfile(p1, rev1)
fd, fr = repos.openfile(p1, rev1, {})
if fd:
fd.close()
rev1 = fr
@ -5290,16 +5292,19 @@ def find_root_in_parents(cfg, rootname, roottype):
if repo_type != roottype:
continue
pp = os.path.normpath(pp[:pos].strip())
rootpath = None
if roottype == 'cvs':
rootpath = vclib.ccvs.find_root_in_parent(pp, rootname)
elif roottype == 'svn':
rootpath = vclib.svn.find_root_in_parent(pp, rootname)
if rootpath is not None:
return rootpath
return None
if roottype == 'cvs':
roots = vclib.ccvs.expand_root_parent(pp)
elif roottype == 'svn':
roots = vclib.svn.expand_root_parent(pp)
else:
roots = {}
if roots.has_key(rootname):
return roots[rootname], rootname
for (k, v) in roots.iteritems():
if v == rootname:
return rootname, k
return None, None
def locate_root(cfg, rootname):
"""Return a 3-tuple ROOTTYPE, ROOTPATH, ROOTNAME for configured ROOTNAME.

53
notes/TODO Normal file
View File

@ -0,0 +1,53 @@
PREFACE
-------
This file will go away soon after release 0.8. Please use the SourceForge
tracker to resubmit any of the items listed below, if you think, it is
still an issue:
http://sourceforge.net/tracker/?group_id=18760
Before reporting please check, whether someone else has already done this.
Working patches increase the chance to be included into the next release.
-- PeFu / October 2001
TODO ITEMS
----------
*) add Tamminen Eero's comments on how to make Linux directly execute
the Python script. From email on Feb 19.
[ add other examples, such as my /bin/sh hack or the teeny CGI stub
importing the bulk hack ]
*) insert rcs_path into PATH before calling "rcsdiff". rcsdiff might
use "co" and needs to find it on the path.
*) show the "locked" flag (attach it to the LogEntry objects).
Idea from Russell Gordon <russell@hoopscotch.dhs.org>
*) committing with a specific revision number:
http://mailman.lyra.org/pipermail/viewcvs/2000q1/000008.html
*) add capability similar to cvs2cl.pl:
http://mailman.lyra.org/pipermail/viewcvs/2000q2/000050.html
suggestion from Chris Meyer <cmeyer@gatan.com>.
*) add a tree view of the directory structure (and files?)
*) include a ConfigParser.py to help older Python installations
*) add a check for the rcs programs/paths to viewvc-install. clarify the
dependency on RCS in the docs.
*) have a "check" mode that verifies binaries are available on rcs_path
-> alternately (probably?): use rcsparse rather than external tools
KNOWN BUGS
----------
*) time.timezone seems to not be available on some 1.5.2 installs.
I was unable to verify this. On RedHat and SuSE Linux this bug
is non existant.
*) With old repositories containing many branches, tags or thousands
or revisions, the cvsgraph feature becomes unusable (see INSTALL).
ViewVC can't do much about this, but it might be possible to
investigate the number of branches, tags and revision in advance
and disable the cvsgraph links, if the numbers exceed a certain
treshold.

82
notes/authz-dev-TODO Normal file
View File

@ -0,0 +1,82 @@
Here lie TODO items for the pluggable authz system:
* Subversion uses path privelege to determine visibility of revision
metadata. That logic is pretty Subversion-specific, so it feels like it
belongs outside the vcauth library as just a helper function in viewvc.py
or something. The algorithm is something like this (culled from the
CollabNet implementation, and not expected to work as edited):
# Subversion revision access levels
REVISION_ACCESS_NONE = 0
REVISION_ACCESS_PARTIAL = 1
REVISION_ACCESS_FULL = 2
def check_svn_revision_access(request, rev):
# Check our revision access cache first.
if request.rev_access_cache.has_key(rev):
return request.rev_access_cache[rev]
# Check our cached answer to the question "Does the user have
# an all-access or a not-at-all-access pass?"
if request.full_access is not None:
return request.full_access \
and REVISION_ACCESS_FULL or REVISION_ACCESS_NONE
# Get a list of paths changed in REV.
### FIXME: There outta be a vclib-complaint way to do this,
### as this won't work for vclib.svn_ra.
import svn.fs
rev_root = svn.fs.revision_root(self.repos.fs_ptr, rev)
changes = svn.fs.paths_changed(rev_root)
# Loop over the list of changed paths, asking the access question
# for each one. We'll track whether we've found any readable paths
# as well as any un-readable (non-authorized) paths, and quit
# checking as soon as we know our revision access level.
found_readable = 0
found_unreadable = 0
for path in changes.keys():
parts = _path_parts(path)
kind = request.repos.itemtype(parts, rev)
if kind == vclib.DIR:
access = request.auth.check_dir_access(parts, rev)
elif:
access = request.auth.check_file_access(parts, rev)
if access:
found_readable = 1
else:
found_unreadable = 1
# Optimization: if we've found at least one readable, and one
# unreadable, we needn't ask about any more paths.
if found_readable and found_unreadable:
break
# If there are paths but we can't read any of them, no access is
# granted.
if len(changes) and not found_readable:
request.rev_access_cache[rev] = REVISION_ACCESS_NONE
# If we found at least one unreadable path, partial access is
# granted.
elif found_unreadable:
request.rev_access_cache[rev] = REVISION_ACCESS_PARTIAL
# Finally, if there were no paths at all, or none of the existing
# ones were unreadable, grant full access.
else:
request.rev_access_cache[rev] = REVISION_ACCESS_FULL
return request.rev_access_cache[rev]
The problems are: where does one hang the revision access cache
so that it doesn't survive a given request? On the request, as
shown in the edited code above?
Can we actually get a good interface into the vcauth layer for
asking the all-access / no-access question? Obviously each vcauth
provider can cache that value for itself and use it as it deems
necessary, but ideally the revision access level question will
want to know if said auth provider was able to answer the question
and, if so, what the answer was.
Another off-the-wall idea -- let's just teach Subversion to do
this calculation as part of a libsvn_repos API.

BIN
notes/logo/viewvc-logo.odg Normal file

Binary file not shown.

BIN
notes/logo/viewvc-logo.pdf Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,78 @@
The following is an email from a developer who was integrating bzr
into ViewVC in which he shares some thoughts on how to further
abstract the version control system interactions into first-class APIs
in the vclib module.
Subject: Re: [ViewCVS-dev] difflib module
Date: Wed, 1 Jun 2005 16:59:10 -0800
From: "Johan Rydberg" <jrydberg@gnu.org>
To: "Michael Pilato" <cmpilato@collab.net>
Cc: <viewcvs-dev@lyra.org>
"C. Michael Pilato" <cmpilato@collab.net> writes:
>> I've tried to minimize the changes to the viewcvs.py, but of course
>> there are a few places where some things has to be altered.
>
> Well, if along the way, you have ideas about how to further abstract
> stuff into the vclib/ modules, please post.
I came up with a few as off now;
* Generalize revision counting; svn starts from 0, bzr starts from 1.
Can be done by a constant; request.repos.MIN_REVNO. For CVS I'm not
sure exactly what should be done. Right now this is only used in
view_revision_svn, so it is not a problem in the short term.
* Generalize view_diff;
* Have a repo-method diff(file1, rev1, file2, rev2, args) that returns
(date1, date2, fp). Means human_readbale_diff and raw_diff does not
have to parse dates. Good for VCS that does not have the date in
the diff. [### DONE ###]
* I'm not sure you should require GNU diff. Some VCS may use own
diff mechanisms (bzr uses difflib, _or_ GNU diff when needed.
Monotone uses its own, IIRC.)
* Generalize view_revision ;
* Have a method, revision_info(), which returns (date, author, msg,
changes) much like vclib.svn.get_revision_info. The CVS version
can raise a ViewCVSException. [### DONE ###]
* Establish a convention for renamed/copied files; current should
work good enough (change.base_path, change.base_rev) but action
string must be same for both svn and others.
* request.repos.rev (or .revision) should give the current revision
number of the repo. No need for this (from view_directory):
if request.roottype == 'svn':
revision = str(vclib.svn.created_rev(request.repos, request.where))
If svn needs the full name of the repo, why not give it when the
repo is created?
* request.repos.youngest vs vclib.svn.get_youngest_revision(REPO)
* More object oriented;
* The subversion backend is not really object oriented. viewcfg.py uses
a lot function from vclib.svn, which could instead be methods of the
Repository class. Example:
diffobj = vclib.svn.do_diff(request.repos, p1, int(rev1),
p2, int(rev2), args)
This should be a method of the repository;
diffobj = request.repos.do_diff(p1, rev1, ...)
I have identified the following functions;
- vclib.svn.created_rev
- vclib.svn.get_youngest_revision
- vclib.svn.date_from_rev
- vclib.svn.do_diff
- vclib.svn.get_revision_info

View File

@ -17,7 +17,7 @@
</tr></table>
</div>
<div style="float: right; padding: 5px;"><a href="http://www.viewvc.org/" title="ViewVC Home"><img src="[docroot]/images/viewvc-logo.png" alt="ViewVC logotype" width="240" height="70" /></a></div>
<div style="float: right; padding: 5px;"><a href="[if-any roots_href][roots_href][else]http://www.viewvc.org/[end]" title="ViewVC Home"><img src="[docroot]/images/viewvc-logo.png" alt="ViewVC logotype" width="240" height="70" /></a></div>
<h1>[page_title]</h1>

View File

@ -11,7 +11,43 @@
[if-any commits.rss_url]<link>[commits.rss_url]</link>[end]
<author>[commits.author]</author>
<pubDate>[if-any commits.rss_date][commits.rss_date][else](unknown date)[end]</pubDate>
<description>&#x3C;pre&#x3E;[format "xml"][format "html"][commits.log][end][end]&#x3C;/pre&#x3E;</description>
<description>
[format "xml"]
&lt;pre&gt;[commits.log]&lt;/pre&gt;
&lt;table&gt;
[for commits.files]
&lt;tr&gt;
&lt;td style=&quot;vertical-align: top;&quot;&gt;
[define rev_href][if-any commits.files.prefer_markup][commits.files.view_href][else][if-any commits.files.download_href][commits.files.download_href][end][end][end]
[if-any commits.files.rev][if-any rev_href]&lt;a href=&quot;[rev_href]&quot;&gt;[end][commits.files.rev][if-any rev_href]&lt;/a&gt;[end][else]&amp;nbsp;[end]
&lt;/td&gt;
&lt;td style=&quot;vertical-align: top;&quot;&gt;
&lt;a href=&quot;[commits.files.dir_href]&quot;&gt;[commits.files.dir]/&lt;/a&gt;
&lt;a href=&quot;[commits.files.log_href]&quot;&gt;[commits.files.file]&lt;/a&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: top;&quot;&gt;
[is commits.files.type "Add"]&lt;ins&gt;[end]
[is commits.files.type "Change"]&lt;a href=&quot;[commits.files.diff_href]&quot;&gt;[end]
[is commits.files.type "Remove"]&lt;del&gt;[end]
[commits.files.plus]/[commits.files.minus]
[is commits.files.type "Add"]&lt;/ins&gt;[end]
[is commits.files.type "Change"]&lt;/a&gt;[end]
[is commits.files.type "Remove"]&lt;/del&gt;[end]
&lt;/td&gt;
&lt;/tr&gt;
[end]
[if-any commits.limited_files]
&lt;tr class=&quot;vc_row_[if-index commits even]even[else]odd[end]&quot;&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td colspan=&quot;5&quot;&gt;
&lt;strong&gt;&lt;em&gt;&lt;small&gt;Only first [commits.num_files] files shown.
&lt;a href=&quot;[limit_changes_href]&quot;&gt;Show all files&lt;/a&gt; or
&lt;a href=&quot;[queryform_href]&quot;&gt;adjust limit&lt;/a&gt;.&lt;/small&gt;&lt;/em&gt;&lt;/strong&gt;
&lt;/tr&gt;
[end]
&lt;/table&gt;
[end]
</description>
</item>[end]
</channel>
</rss>

View File

@ -23,7 +23,7 @@
</div>
<div id="vc_logo">
<a href="http://www.viewvc.org/"><img src="[docroot]/images/viewvc-logo.png" alt="ViewVC logotype" width="240" height="70" /></a>
<a href="[if-any roots_href][roots_href][else]http://www.viewvc.org/[end]"><img src="[docroot]/images/viewvc-logo.png" alt="ViewVC logotype" width="240" height="70" /></a>
</div>
<div id="vc_view_selection_group">

View File

@ -1,39 +1,82 @@
[# setup page definitions]
[define page_title]Query on:[end]
[define page_title]Query on /[where][end]
[define help_href][docroot]/help_rootview.html[end]
[# end]
[include "include/header.ezt" "query"]
<p><a href="[dir_href]">
<img src="[docroot]/images/dir.png" class="vc_icon" alt="Directory" />
Browse Directory</a></p>
<form action="[query_action]" method="get">
<div class="vc_query_form">
[for query_hidden_values]<input type="hidden" name="[query_hidden_values.name]" value="[query_hidden_values.value]"/>[end]
<table cellspacing="0" cellpadding="5" class="auto">
[if-any enable_search_content]
<tr>
<th style="text-align:right;vertical-align:top;">Search content:</th>
<td><input type="text" name="search_content" value="[search_content]" size="60" /></td>
</tr>
[end]
<tr>
<th style="text-align:right;vertical-align:top;">Repository:</th>
<td>
<input type="text" name="repos" value="[repos]" /> &nbsp; <b>Repository type:</b> &nbsp;
<select name="repos_type">
<option value="">Any</option>
<option value="cvs" [is repos_type "cvs"]selected[end]>CVS</option>
<option value="svn" [is repos_type "svn"]selected[end]>Subversion</option>
</select>
<br />
<label for="repos_match_exact">
<input type="radio" name="repos_match" id="repos_match_exact"
value="exact" [is repos_match "exact"]checked="checked"[end] />
Exact match
</label>
<label for="repos_match_glob">
<input type="radio" name="repos_match" id="repos_match_glob"
value="glob" [is repos_match "glob"]checked="checked"[end] />
Glob pattern match
</label>
<label for="repos_match_regex">
<input type="radio" name="repos_match" id="repos_match_regex"
value="regex" [is repos_match "regex"]checked="checked"[end] />
Regex match
</label>
<label for="repos_match_notregex">
<input type="radio" name="repos_match" id="repos_match_notregex"
value="notregex" [is repos_match "notregex"]checked="checked"[end] />
Regex doesn't match
</label>
</td>
</tr>
[is roottype "cvs"]
[# For subversion, the branch field is not used ]
<tr>
<th style="text-align:right;vertical-align:top;">Branch:</th>
<td>
<input type="text" name="branch" value="[branch]" />
<input type="text" name="branch" value="[branch]" /><br />
<label for="branch_match_exact">
<input type="radio" name="branch_match" id="branch_match_exact"
value="exact" [is branch_match "exact"]checked="checked"[end] />
exact
Exact match
</label>
<label for="branch_match_glob">
<input type="radio" name="branch_match" id="branch_match_glob"
value="glob" [is branch_match "glob"]checked="checked"[end] />
glob pattern
Glob pattern match
</label>
<label for="branch_match_regex">
<input type="radio" name="branch_match" id="branch_match_regex"
value="regex" [is branch_match "regex"]checked="checked"[end] />
regex
Regex match
</label>
<label for="branch_match_notregex">
<input type="radio" name="branch_match" id="branch_match_notregex"
value="notregex" [is branch_match "notregex"]checked="checked"[end] />
<em>not</em> regex
Regex doesn't match
</label>
</td>
</tr>
@ -41,85 +84,97 @@
<tr>
<th style="text-align:right;vertical-align:top;">Subdirectory:</th>
<td>
<input type="text" name="dir" value="[dir]" />
<em>(You can list multiple directories separated by commas.)</em>
<input type="text" name="dir" value="[dir]" /><br />
(you can list multiple directories separated by commas)
</td>
</tr>
<tr>
<th style="text-align:right;vertical-align:top;">File:</th>
<td>
<input type="text" name="file" value="[file]" />
<input type="text" name="file" value="[file]" /><br />
<label for="file_match_exact">
<input type="radio" name="file_match" id="file_match_exact"
value="exact" [is file_match "exact"]checked="checked"[end] />
exact
Exact match
</label>
<label for="file_match_glob">
<input type="radio" name="file_match" id="file_match_glob"
value="glob" [is file_match "glob"]checked="checked"[end] />
glob pattern
Glob pattern match
</label>
<label for="file_match_regex">
<input type="radio" name="file_match" id="file_match_regex"
value="regex" [is file_match "regex"]checked="checked"[end] />
regex
Regex match
</label>
<label for="file_match_notregex">
<input type="radio" name="file_match" id="file_match_notregex"
value="notregex" [is file_match "notregex"]checked="checked"[end] />
<em>not</em> regex
Regex doesn't match
</label>
</td>
</tr>
<tr>
<th style="text-align:right;vertical-align:top;">Revision number:</th>
<td>
<input type="text" name="query_revision" value="[query_revision]" /><br />
(you can list multiple revision numbers separated by commas)
</td>
</tr>
<tr>
<th style="text-align:right;vertical-align:top;">Who:</th>
<td>
<input type="text" name="who" value="[who]" />
<input type="text" name="who" value="[who]" /><br />
<label for="who_match_exact">
<input type="radio" name="who_match" id="who_match_exact"
value="exact" [is who_match "exact"]checked="checked"[end] />
exact
Exact match
</label>
<label for="who_match_glob">
<input type="radio" name="who_match" id="who_match_glob"
value="glob" [is who_match "glob"]checked="checked"[end] />
glob pattern
Glob pattern match
</label>
<label for="who_match_regex">
<input type="radio" name="who_match" id="who_match_regex"
value="regex" [is who_match "regex"]checked="checked"[end] />
regex
Regex match
</label>
<label for="who_match_notregex">
<input type="radio" name="who_match" id="who_match_notregex"
value="notregex" [is who_match "notregex"]checked="checked"[end] />
<em>not</em> regex
Regex doesn't match
</label>
</td>
</tr>
<tr>
<th style="text-align:right;vertical-align:top;">Comment:</th>
<td>
<input type="text" name="comment" value="[comment]" />
<input type="text" name="comment" value="[comment]" size="40" /><br />
<label for="comment_match_exact">
<input type="radio" name="comment_match" id="comment_match_fulltext"
value="fulltext" [is comment_match "fulltext"]checked=""[end] />
Search full-text
</label>
<label for="comment_match_exact">
<input type="radio" name="comment_match" id="comment_match_exact"
value="exact" [is comment_match "exact"]checked=""[end] />
exact
Exact match
</label>
<label for="comment_match_glob">
<input type="radio" name="comment_match" id="comment_match_glob"
value="glob" [is comment_match "glob"]checked=""[end] />
glob pattern
Glob pattern match
</label>
<label for="comment_match_regex">
<input type="radio" name="comment_match" id="comment_match_regex"
value="regex" [is comment_match "regex"]checked=""[end] />
regex
Regex match
</label>
<label for="comment_match_notregex">
<input type="radio" name="comment_match" id="comment_match_notregex"
value="notregex" [is comment_match "notregex"]checked=""[end] />
<em>not</em> regex
Regex doesn't match
</label>
</td>
</tr>
@ -128,8 +183,10 @@
<td>
<select name="querysort">
<option value="date" [is querysort "date"]selected="selected"[end]>Date</option>
<option value="date_rev" [is querysort "date_rev"]selected="selected"[end]>Date (oldest first)</option>
<option value="author" [is querysort "author"]selected="selected"[end]>Author</option>
<option value="file" [is querysort "file"]selected="selected"[end]>File</option>
<option value="relevance" [is querysort "relevance"]selected="selected"[end]>Relevance</option>
</select>
</td>
</tr>
@ -186,7 +243,8 @@
<td>
Show at most
<input type="text" name="limit_changes" value="[limit_changes]" size="5" />
changed files per commit. <em>(Use 0 to show all files.)</em>
changed files per commit.<br />
(use 0 to show all files)
</td>
</tr>
<tr>

View File

@ -16,8 +16,19 @@
query capabilities, or asking your administrator to raise the
database response size threshold.</p>
[end]
<p><a href="[queryform_href]">Modify query</a></p>
<p><a href="[queryform_href]">Modify query</a>[if-any repos_root] [else]
[if-any repos_type]
[is repos_type "cvs"]| <a href="[querysvn_href]">Look only in SVN</a> | <a href="[queryall_href]">Look in all repos</a> [end]
[is repos_type "svn"]| <a href="[querycvs_href]">Look only in CVS</a> | <a href="[queryall_href]">Look in all repos</a> [end]
[else]
| <a href="[querysvn_href]">Look only in SVN</a> | <a href="[querycvs_href]">Look only in CVS</a>
[end]
[end]</p>
<p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p>
<p>
<a href="[patch_href]">Show a patch built from these changes</a>
[if-any patch_unsecure]<br /><b>CAUTION: selected changes are not contiguous, patch may include differences from other commits.</b>[end]
</p>
<p><strong>+[plus_count]/-[minus_count]</strong> lines changed.</p>

51
tests/testparse.py Normal file
View File

@ -0,0 +1,51 @@
MODULE = '/home/gstein/testing/cvsroot/mod_dav'
OUTPUT = 'rlog-dump'
import sys
sys.path.insert(0, '../lib')
import os
import rlog
def get_files(root):
all_files = [ ]
os.path.walk(root, _collect_files, all_files)
all_files.sort()
return all_files
def _collect_files(all_files, dir, files):
for f in files:
if f[-2:] == ',v':
all_files.append(os.path.join(dir, f))
def get_config():
class _blank:
pass
cfg = _blank()
cfg.general = _blank()
cfg.general.rcs_path = ''
return cfg
def gen_dump(cfg, out_fname, files, func):
out = open(out_fname, 'w')
for f in files:
data = func(cfg, f)
out.write(data.filename + '\n')
tags = data.symbolic_name_hash.keys()
tags.sort()
for t in tags:
out.write('%s:%s\n' % (t, data.symbolic_name_hash[t]))
for e in data.rlog_entry_list:
names = dir(e)
names.sort()
for n in names:
out.write('%s=%s\n' % (n, getattr(e, n)))
def _test():
cfg = get_config()
files = get_files(MODULE)
gen_dump(cfg, OUTPUT + '.old', files, rlog.GetRLogData)
gen_dump(cfg, OUTPUT + '.new', files, rlog.get_data)

53
tests/vclib/co.py Executable file
View File

@ -0,0 +1,53 @@
#!/usr/local/bin/python
import sys, os.path
sys.path.append( os.path.normpath(os.path.join(sys.path[0],"..","..","lib")) )
import vclib.ccvs
import popen
def usage():
print """
co simulation using vclib!!!
python co.py <Path to repository> <(relative) Path to file> <revision>
"""
sys.exit()
def convertpath(s):
a=(s,'')
res=[]
while (a[0]!=''):
a=os.path.split(a[0])
res= [a[1]]+res
return res
def compareco(repo,file,rev):
a=vclib.ccvs.CVSRepository("lucas",repo)
f=a.getfile(convertpath(file)) # example: ["kdelibs","po","Attic","nl.po"]
r=f.tree[rev]
fp1 = r.checkout()
fp2 = popen.popen('co',
('-p'+rev, os.path.join(repo,file) ), 'r')
l1 = fp1.readlines()
l2 = fp2.readlines()
ok=1
for i in range(0,len(l1)-1):
if l1[i] != l2[i+2]:
print " Difference in line %d"% i
print " line from CCVS %s" % l1[i]
print " line from RCS %s" % l2[i+2]
ok=0
return ok
if len(sys.argv)==4:
compareco(sys.argv[1],sys.argv[2],sys.argv[3])
elif len(sys.argv)==3:
a=vclib.ccvs.CVSRepository("lucas",sys.argv[1])
f=a.getfile(convertpath(sys.argv[2])) # example: ["kdelibs","po","Attic","nl.po"]
for rev in f.tree.keys():
print ("revision: %s" % rev),
if compareco(sys.argv[1],sys.argv[2],rev):
print "ok"
else:
print "fail"
else:
usage()

5
tests/vclib/rlog.py Normal file
View File

@ -0,0 +1,5 @@
#!/usr/local/bin/python
import sys, os.path
sys.path.append( os.path.normpath(os.path.join(sys.path[0],"..","..","lib")) )
import vclib.ccvs
import popen

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 1999-2013 The ViewCVS Group. All Rights Reserved.
@ -60,6 +60,8 @@ FILE_INFO_LIST = [
("bin/cvsdbadmin", "bin/cvsdbadmin", 0755, 1, 0, 0),
("bin/svndbadmin", "bin/svndbadmin", 0755, 1, 0, 0),
("bin/make-database", "bin/make-database", 0755, 1, 0, 0),
("bin/svnupdate-async", "bin/svnupdate-async", 0755, 1, 0, 0),
("bin/svnupdate-async.sh", "bin/svnupdate-async.sh", 0755, 1, 0, 0),
("conf/viewvc.conf.dist", "viewvc.conf.dist", 0644, 0, 0, 0),
("conf/viewvc.conf.dist", "viewvc.conf", 0644, 0, 1, 0),
("conf/cvsgraph.conf.dist", "cvsgraph.conf.dist", 0644, 0, 0, 0),
@ -235,492 +237,6 @@ LEGEND
### If we get here, we're creating or overwriting the existing file.
# Read the source file's contents.
try:
contents = open(src_path, "rb").read()
except IOError, e:
error(str(e))
# Ensure the existence of the containing directories.
dst_parent = os.path.dirname(destdir_path)
if not os.path.exists(dst_parent):
try:
compat.makedirs(dst_parent)
print " created %s%s" % (dst_parent, os.sep)
except os.error, e:
if e.errno == 17: # EEXIST: file exists
return
if e.errno == 13: # EACCES: permission denied
error("You do not have permission to create directory %s" \
% (dst_parent))
error("Unknown error creating directory %s" \
% (dst_parent, OSError, e))
# Now, write the file contents to their destination.
try:
exists = os.path.exists(destdir_path)
open(destdir_path, "wb").write(contents)
print " %s %s" \
% (exists and 'replaced ' or 'installed', dst_path)
except IOError, e:
if e.errno == 13:
# EACCES: permission denied
error("You do not have permission to write file %s" % (dst_path))
error("Unknown error writing file %s" % (dst_path, IOError, e))
# Set the files's mode.
os.chmod(destdir_path, mode)
# (Optionally) compile the file.
if compile_it:
py_compile.compile(destdir_path, destdir_path + "c" , dst_path)
def install_tree(src_path, dst_path, prompt_replace):
"""Install a tree whose source is at SRC_PATH (which is relative
to the ViewVC source directory) into the location DST_PATH (which
is relative both to the global ROOT_DIR and DESTDIR settings). If
PROMPT_REPLACE is set (and is not overridden by global setting
CLEAN_MODE), prompt the user for how to deal with already existing
files that differ from the to-be-installed version."""
orig_src_path = src_path
orig_dst_path = dst_path
src_path = _actual_src_path(src_path)
dst_path = os.path.join(ROOT_DIR, string.replace(dst_path, '/', os.sep))
destdir_path = os.path.join(DESTDIR + dst_path)
# Get a list of items in the directory.
files = os.listdir(src_path)
files.sort()
for fname in files:
# Ignore some stuff found in development directories, but not
# intended for installation.
if fname == 'CVS' or fname == '.svn' or fname == '_svn' \
or fname[-4:] == '.pyc' or fname[-5:] == '.orig' \
or fname[-4:] == '.rej' or fname[0] == '.' \
or fname[-1] == '~':
continue
orig_src_child = orig_src_path + '/' + fname
orig_dst_child = orig_dst_path + '/' + fname
# If the item is a subdirectory, recurse. Otherwise, install the file.
if os.path.isdir(os.path.join(src_path, fname)):
install_tree(orig_src_child, orig_dst_child, prompt_replace)
else:
set_paths = 0
compile_it = fname[-3:] == '.py'
install_file(orig_src_child, orig_dst_child, 0644,
set_paths, prompt_replace, compile_it)
# Check for .py and .pyc files that don't belong in installation.
for fname in os.listdir(destdir_path):
if not os.path.isfile(os.path.join(destdir_path, fname)) or \
not ((fname[-3:] == '.py' and fname not in files) or
(fname[-4:] == '.pyc' and fname[:-1] not in files)):
continue
# If we get here, there's cruft.
delete = None
if CLEAN_MODE == 'true':
delete = 1
elif CLEAN_MODE == 'false':
delete = 0
else:
print "File %s does not belong in ViewVC %s." \
% (dst_path, version)
while 1:
temp = raw_input("Do you want to [D]elete it, or [L]eave "
"it as is? ")
temp = string.lower(temp[0])
if temp == "l":
delete = 0
elif temp == "d":
delete = 1
if delete is not None:
break
assert delete is not None
if delete:
print " deleted %s" % (os.path.join(dst_path, fname))
os.unlink(os.path.join(destdir_path, fname))
else:
print " preserved %s" % (os.path.join(dst_path, fname))
def usage_and_exit(errstr=None):
stream = errstr and sys.stderr or sys.stdout
stream.write("""Usage: %s [OPTIONS]
Installs the ViewVC web-based version control repository browser.
Options:
--help, -h, -? Show this usage message and exit.
--prefix=DIR Install ViewVC into the directory DIR. If not provided,
the script will prompt for this information.
--destdir=DIR Use DIR as the DESTDIR. This is generally only used
by package maintainers. If not provided, the script will
prompt for this information.
--clean-mode= If 'true', overwrite existing ViewVC configuration files
found in the target directory, and purge Python modules
from the target directory that aren't part of the ViewVC
distribution. If 'false', do not overwrite configuration
files, and do not purge any files from the target
directory. If not specified, the script will prompt
for the appropriate action on a per-file basis.
""" % (os.path.basename(sys.argv[0])))
if errstr:
stream.write("ERROR: %s\n\n" % (errstr))
sys.exit(1)
else:
sys.exit(0)
if __name__ == "__main__":
# Option parsing.
try:
optlist, args = getopt.getopt(sys.argv[1:], "h?",
['prefix=',
'destdir=',
'clean-mode=',
'help'])
except getopt.GetoptError, e:
usage_and_exit(str(e))
for opt, arg in optlist:
if opt == '--help' or opt == '-h' or opt == '-?':
usage_and_exit()
if opt == '--prefix':
ROOT_DIR = arg
if opt == '--destdir':
DESTDIR = arg
if opt == '--clean-mode':
arg = arg.lower()
if arg not in ('true', 'false'):
usage_and_exit("Invalid value for --overwrite parameter.")
CLEAN_MODE = arg
# Print the header greeting.
print """This is the ViewVC %s installer.
It will allow you to choose the install path for ViewVC. You will now
be asked some installation questions. Defaults are given in square brackets.
Just hit [Enter] if a default is okay.
""" % version
# Prompt for ROOT_DIR if none provided.
if ROOT_DIR is None:
if sys.platform == "win32":
pf = os.getenv("ProgramFiles", "C:\\Program Files")
default = os.path.join(pf, "viewvc-" + version)
else:
default = "/usr/local/viewvc-" + version
temp = string.strip(raw_input("Installation path [%s]: " \
% default))
print
if len(temp):
ROOT_DIR = temp
else:
ROOT_DIR = default
# Prompt for DESTDIR if none provided.
if DESTDIR is None:
default = ''
temp = string.strip(raw_input(
"DESTDIR path (generally only used by package "
"maintainers) [%s]: " \
% default))
print
if len(temp):
DESTDIR = temp
else:
DESTDIR = default
# Install the files.
print "Installing ViewVC to %s%s:" \
% (ROOT_DIR, DESTDIR and " (DESTDIR = %s)" % (DESTDIR) or "")
for args in FILE_INFO_LIST:
apply(install_file, args)
for args in TREE_LIST:
apply(install_tree, args)
# Write LIBRARY_DIR and CONF_PATHNAME into viewvcinstall.py config file
viewvcinstallpath = """#!/usr/bin/python
LIBRARY_DIR = "%s"
CONF_PATHNAME = "%s"
""" % (os.path.join(ROOT_DIR, 'lib'), os.path.join(ROOT_DIR, 'viewvc.conf'))
open(os.path.join(ROOT_DIR, 'bin', 'viewvcinstallpath.py'),'wb').write(viewvcinstallpath)
if sys.platform != 'win32':
for i in ['cgi', 'mod_python']:
os.symlink(os.path.join(ROOT_DIR, 'bin', 'viewvcinstallpath.py'), os.path.join(ROOT_DIR, 'bin', i, 'viewvcinstallpath.py'))
else:
for i in ['asp', 'cgi', 'mod_python']:
open(os.path.join(ROOT_DIR, 'bin', i, 'viewvcinstallpath.py'),'wb').write(viewvcinstallpath)
# Print some final thoughts.
print """
ViewVC file installation complete.
Consult the INSTALL document for detailed information on completing the
installation and configuration of ViewVC on your system. Here's a brief
overview of the remaining steps:
1) Edit the %s file.
2) Either configure an existing web server to run
%s.
Or, copy %s to an
already-configured cgi-bin directory.
Or, use the standalone server provided by this distribution at
%s.
""" % (os.path.join(ROOT_DIR, 'viewvc.conf'),
os.path.join(ROOT_DIR, 'bin', 'cgi', 'viewvc.cgi'),
os.path.join(ROOT_DIR, 'bin', 'cgi', 'viewvc.cgi'),
os.path.join(ROOT_DIR, 'bin', 'standalone.py'))
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC
# distribution or at http://viewvc.org/license-1.html.
#
# For more information, visit http://viewvc.org/
#
# -----------------------------------------------------------------------
#
# Install script for ViewVC
#
# -----------------------------------------------------------------------
import os
import sys
import string
import re
import traceback
import py_compile
import getopt
import StringIO
# Get access to our library modules.
sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), 'lib'))
import compat
import viewvc
import compat_ndiff
version = viewvc.__version__
## Installer defaults.
DESTDIR = None
ROOT_DIR = None
CLEAN_MODE = None
## List of files for installation.
## tuple (source path,
## destination path,
## mode,
## boolean -- search-and-replace?
## boolean -- prompt before replacing?
## boolean -- compile?)
FILE_INFO_LIST = [
("bin/cgi/viewvc.cgi", "bin/cgi/viewvc.cgi", 0755, 1, 0, 0),
("bin/cgi/query.cgi", "bin/cgi/query.cgi", 0755, 1, 0, 0),
("bin/mod_python/viewvc.py", "bin/mod_python/viewvc.py", 0755, 1, 0, 0),
("bin/mod_python/query.py", "bin/mod_python/query.py", 0755, 1, 0, 0),
("bin/mod_python/handler.py", "bin/mod_python/handler.py", 0755, 1, 0, 0),
("bin/mod_python/.htaccess", "bin/mod_python/.htaccess", 0755, 0, 0, 0),
("bin/standalone.py", "bin/standalone.py", 0755, 1, 0, 0),
("bin/loginfo-handler", "bin/loginfo-handler", 0755, 1, 0, 0),
("bin/cvsdbadmin", "bin/cvsdbadmin", 0755, 1, 0, 0),
("bin/svndbadmin", "bin/svndbadmin", 0755, 1, 0, 0),
("bin/make-database", "bin/make-database", 0755, 1, 0, 0),
("bin/svnupdate-async", "bin/svnupdate-async", 0755, 1, 0, 0),
("bin/svnupdate-async.sh", "bin/svnupdate-async.sh", 0755, 1, 0, 0),
("conf/viewvc.conf.dist", "viewvc.conf.dist", 0644, 0, 0, 0),
("conf/viewvc.conf.dist", "viewvc.conf", 0644, 0, 1, 0),
("conf/cvsgraph.conf.dist", "cvsgraph.conf.dist", 0644, 0, 0, 0),
("conf/cvsgraph.conf.dist", "cvsgraph.conf", 0644, 0, 1, 0),
("conf/mimetypes.conf.dist", "mimetypes.conf.dist", 0644, 0, 0, 0),
("conf/mimetypes.conf.dist", "mimetypes.conf", 0644, 0, 1, 0),
]
if sys.platform == "win32":
FILE_INFO_LIST.extend([
("bin/asp/viewvc.asp", "bin/asp/viewvc.asp", 0755, 1, 0, 0),
("bin/asp/query.asp", "bin/asp/query.asp", 0755, 1, 0, 0),
])
## List of directories for installation.
## type (source path,
## destination path,
## boolean -- prompt before replacing?)
TREE_LIST = [
("lib", "lib", 0),
("templates", "templates", 1),
("templates-contrib", "templates-contrib", 1),
]
## List of file extensions we can't show diffs for.
BINARY_FILE_EXTS = [
'.png',
'.gif',
'.jpg',
]
def _escape(str):
"""Callback function for re.sub().
re.escape() is no good because it blindly puts backslashes in
front of anything that is not a number or letter regardless of
whether the resulting sequence will be interpreted."""
return string.replace(str, "\\", "\\\\")
def _actual_src_path(path):
"""Return the real on-disk location of PATH, which is relative to
the ViewVC source directory."""
return os.path.join(os.path.dirname(sys.argv[0]),
string.replace(path, '/', os.sep))
def error(text, etype=None, evalue=None):
"""Print error TEXT to stderr, pretty printing the optional
exception type and value (ETYPE and EVALUE, respective), and then
exit the program with an errorful code."""
sys.stderr.write("\n[ERROR] %s\n" % (text))
if etype:
traceback.print_exception(etype, evalue, None, file=sys.stderr)
sys.exit(1)
def replace_var(contents, var, value):
"""Replace instances of the variable VAR as found in file CONTENTS
with VALUE."""
pattern = re.compile('^' + var + r'\s*=\s*.*$', re.MULTILINE)
repl = '%s = r"%s"' % (var, os.path.join(ROOT_DIR, value))
return re.sub(pattern, _escape(repl), contents)
def replace_paths(contents):
"""Replace all ViewVC path placeholders found in file CONTENTS."""
if contents[:2] == '#!':
shbang = '#!' + sys.executable
contents = re.sub('^#![^\n]*', _escape(shbang), contents)
contents = replace_var(contents, 'LIBRARY_DIR', 'lib')
contents = replace_var(contents, 'CONF_PATHNAME', 'viewvc.conf')
return contents
def install_file(src_path, dst_path, mode, subst_path_vars,
prompt_replace, compile_it):
"""Install a single file whose source is at SRC_PATH (which is
relative to the ViewVC source directory) into the location
DST_PATH (which is relative both to the global ROOT_DIR and
DESTDIR settings), and set the file's MODE. If SUBST_PATH_VARS is
set, substitute path variables in the file's contents. If
PROMPT_REPLACE is set (and is not overridden by global setting
CLEAN_MODE), prompt the user for how to deal with already existing
files that differ from the to-be-installed version. If COMPILE_IT
is set, compile the file as a Python module."""
src_path = _actual_src_path(src_path)
dst_path = os.path.join(ROOT_DIR, string.replace(dst_path, '/', os.sep))
destdir_path = DESTDIR + dst_path
overwrite = None
if not (prompt_replace and os.path.exists(destdir_path)):
# If the file doesn't already exist, or we've been instructed to
# replace it without prompting, then drop in the new file and get
# outta here.
overwrite = 1
else:
# If we're here, then the file already exists, and we've possibly
# got to prompt the user for what to do about that.
# Collect ndiff output from ndiff
sys.stdout = StringIO.StringIO()
compat_ndiff.main([destdir_path, src_path])
ndiff_output = sys.stdout.getvalue()
# Return everything to normal
sys.stdout = sys.__stdout__
# Collect the '+ ' and '- ' lines.
diff_lines = []
looking_at_diff_lines = 0
for line in string.split(ndiff_output, '\n'):
# Print line if it is a difference line
if line[:2] == "+ " or line[:2] == "- " or line[:2] == "? ":
diff_lines.append(line)
looking_at_diff_lines = 1
else:
# Compress lines that are the same to print one blank line
if looking_at_diff_lines:
diff_lines.append("")
looking_at_diff_lines = 0
# If there are no differences, we're done here.
if not diff_lines:
overwrite = 1
else:
# If we get here, there are differences.
if CLEAN_MODE == 'true':
overwrite = 1
elif CLEAN_MODE == 'false':
overwrite = 0
else:
print "File %s exists and is different from source file." \
% (destdir_path)
while 1:
name, ext = os.path.splitext(src_path)
if ext in BINARY_FILE_EXTS:
temp = raw_input("Do you want to [O]verwrite or "
"[D]o not overwrite? ")
else:
temp = raw_input("Do you want to [O]verwrite, [D]o "
"not overwrite, or [V]iew "
"differences? ")
temp = string.lower(temp[0])
if temp == "v" and ext not in BINARY_FILE_EXTS:
print """
---------------------------------------------------------------------------"""
print string.join(diff_lines, '\n') + '\n'
print """
LEGEND
A leading '- ' indicates line to remove from installed file
A leading '+ ' indicates line to add to installed file
A leading '? ' shows intraline differences.
---------------------------------------------------------------------------"""
elif temp == "d":
overwrite = 0
elif temp == "o":
overwrite = 1
if overwrite is not None:
break
assert overwrite is not None
if not overwrite:
print " preserved %s" % (dst_path)
return
### If we get here, we're creating or overwriting the existing file.
# Read the source file's contents.
try:
contents = open(src_path, "rb").read()
@ -922,7 +438,7 @@ Just hit [Enter] if a default is okay.
ROOT_DIR = temp
else:
ROOT_DIR = default
# Prompt for DESTDIR if none provided.
if DESTDIR is None:
default = ''
@ -933,7 +449,7 @@ Just hit [Enter] if a default is okay.
DESTDIR = temp
else:
DESTDIR = default
# Install the files.
print "Installing ViewVC to %s%s:" \
% (ROOT_DIR, DESTDIR and " (DESTDIR = %s)" % (DESTDIR) or "")
@ -941,7 +457,20 @@ Just hit [Enter] if a default is okay.
apply(install_file, args)
for args in TREE_LIST:
apply(install_tree, args)
# Write LIBRARY_DIR and CONF_PATHNAME into viewvcinstall.py config file
viewvcinstallpath = """#!/usr/bin/python
LIBRARY_DIR = "%s"
CONF_PATHNAME = "%s"
""" % (os.path.join(ROOT_DIR, 'lib'), os.path.join(ROOT_DIR, 'viewvc.conf'))
open(os.path.join(ROOT_DIR, 'bin', 'viewvcinstallpath.py'),'wb').write(viewvcinstallpath)
if sys.platform != 'win32':
for i in ['cgi', 'mod_python']:
os.symlink(os.path.join(ROOT_DIR, 'bin', 'viewvcinstallpath.py'), os.path.join(ROOT_DIR, 'bin', i, 'viewvcinstallpath.py'))
else:
for i in ['asp', 'cgi', 'mod_python']:
open(os.path.join(ROOT_DIR, 'bin', i, 'viewvcinstallpath.py'),'wb').write(viewvcinstallpath)
# Print some final thoughts.
print """