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

Compare commits

..

1 Commits
1.0.6 ... 1.0.4

Author SHA1 Message Date
cmpilato
8b0bcade3b Tag the 1.0.4 final release.
git-svn-id: http://viewvc.tigris.org/svn/viewvc/tags/1.0.4@1582 8cb11bc2-c004-0410-86c3-e597b4017df7
2007-04-10 06:16:01 +00:00
43 changed files with 484 additions and 881 deletions

26
CHANGES
View File

@@ -1,29 +1,3 @@
Version 1.0.6 (released 16-Sep-2008)
* security fix: ignore arbitrary user-provided MIME types (issue #354)
* fix bug in regexp search filter when used with sticky tag (issue #346)
* fix bug in handling of certain 'co' output (issue #348)
* fix regexp search filter template bug
* fix annotate code syntax error
* fix mod_python import cycle (issue #369)
Version 1.0.5 (released 28-Feb-2008)
* security fix: omit commits of all-forbidden files from query results
* security fix: disallow direct URL navigation to hidden CVSROOT folder
* security fix: strip forbidden paths from revision view
* security fix: don't traverse log history thru forbidden locations
* security fix: honor forbiddenness via diff view path parameters
* new 'forbiddenre' regexp-based path authorization feature
* fix root name conflict resolution inconsistencies (issue #287)
* fix an oversight in the CVS 1.12.9 loginfo-handler support
* fix RSS feed content type to be more specific (issue #306)
* fix entity escaping problems in RSS feed data (issue #238)
* fix bug in tarball generation for remote Subversion repositories
* fix query interface file-count-limiting logic
* fix query results plus/minus count to ignore forbidden files
* fix blame error caused by 'svn' unable to create runtime config dir
Version 1.0.4 (released 10-Apr-2007)
* fix some markup bugs in query views (issue #266)

10
INSTALL
View File

@@ -260,16 +260,6 @@ or if you've got Mod_Python installed you can use METHOD D:
http://<server_name>/viewvc/~checkout~/<module_name>
http://<server_name>/viewvc/<module_name>.tar.gz?view=tar
5) Optional: Protect your ViewVC instance from server-whacking webcrawlers.
As ViewVC is a web-based application which each page containing various
links to other pages and views, you can expect your server's performance
to suffer if a webcrawler finds your ViewVC instance and begins
traversing those links. We highly recommend that you add your ViewVC
location to a site-wide robots.txt file. Visit the Wikipedia page
for Robots.txt (http://en.wikipedia.org/wiki/Robots.txt) for more
information.
UPGRADING VIEWVC
-----------------

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*-python-*-
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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
@@ -77,8 +77,6 @@ def Cvs1Dot12ArgParse(args):
if args[1] == '- New directory':
return None, None
elif args[1] == '- Imported sources':
return None, None
else:
directory = args.pop(0)
files = []

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*-python-*-
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -42,23 +42,9 @@ if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
import sapi
import imp
# Import real ViewVC module
fp, pathname, description = imp.find_module('viewvc', [LIBRARY_DIR])
try:
viewvc = imp.load_module('viewvc', fp, pathname, description)
finally:
if fp:
fp.close()
# Import real ViewVC Query modules
fp, pathname, description = imp.find_module('query', [LIBRARY_DIR])
try:
query = imp.load_module('query', fp, pathname, description)
finally:
if fp:
fp.close()
import viewvc
import query
reload(query) # need reload because initial import loads this stub file
cfg = viewvc.load_config(CONF_PATHNAME)

View File

@@ -42,15 +42,9 @@ if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
import sapi
import imp
import viewvc
reload(viewvc) # need reload because initial import loads this stub file
# Import real ViewVC module
fp, pathname, description = imp.find_module('viewvc', [LIBRARY_DIR])
try:
viewvc = imp.load_module('viewvc', fp, pathname, description)
finally:
if fp:
fp.close()
def index(req):
server = sapi.ModPythonServer(req)

View File

@@ -33,6 +33,7 @@ import time
import math
import cgi
import vclib
import vclib.ccvs.blame
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
@@ -99,7 +100,6 @@ class _item:
def make_html(root, rcs_path):
import vclib.ccvs.blame
bs = vclib.ccvs.blame.BlameSource(os.path.join(root, rcs_path))
count = bs.num_lines
@@ -161,9 +161,9 @@ def make_html(root, rcs_path):
# Close the highlighted section
#if (defined $mark_cmd and mark_cmd != 'begin'):
# chop($output)
# output = output + endOfRow + (startOfRow % row_color)
# inMark = 0
# chop($output)
# output = output + endOfRow + (startOfRow % row_color)
# inMark = 0
print output
print endOfRow + '</table>'

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC
@@ -19,8 +19,6 @@ import os
import string
import ConfigParser
import fnmatch
import re
import vclib
#########################################################################
@@ -41,7 +39,7 @@ import vclib
class Config:
_sections = ('general', 'options', 'cvsdb', 'templates')
_force_multi_value = ('cvs_roots', 'forbidden', 'forbiddenre',
_force_multi_value = ('cvs_roots', 'forbidden',
'svn_roots', 'languages', 'kv_files',
'root_parents')
@@ -153,9 +151,8 @@ class Config:
self.general.svn_path = ''
self.general.mime_types_file = ''
self.general.address = '<a href="mailto:user@insert.your.domain.here">No admin address has been configured</a>'
self.general.forbidden = []
self.general.forbiddenre = []
self.general.kv_files = []
self.general.forbidden = ()
self.general.kv_files = [ ]
self.general.languages = ['en-us']
self.templates.directory = None
@@ -225,65 +222,19 @@ class Config:
self.options.http_expiration_time = 600
self.options.generate_etags = 1
def is_forbidden(self, root, path_parts, pathtype):
# If we don't have a root and path to check, get outta here.
if not (root and path_parts):
def is_forbidden(self, module):
if not module:
return 0
default = 0
for pat in self.general.forbidden:
if pat[0] == '!':
default = 1
if fnmatch.fnmatchcase(module, pat[1:]):
return 0
elif fnmatch.fnmatchcase(module, pat):
return 1
return default
# Give precedence to the new 'forbiddenre' stuff first.
if self.general.forbiddenre:
# Join the root and path-parts together into one path-like thing.
root_and_path = string.join([root] + path_parts, "/")
if pathtype == vclib.DIR:
root_and_path = root_and_path + '/'
# If we still have a list of strings, replace those suckers with
# lists of (compiled_regex, negation_flag)
if type(self.general.forbiddenre[0]) == type(""):
for i in range(len(self.general.forbiddenre)):
pat = self.general.forbiddenre[i]
if pat[0] == '!':
self.general.forbiddenre[i] = (re.compile(pat[1:]), 1)
else:
self.general.forbiddenre[i] = (re.compile(pat), 0)
# Do the forbiddenness test.
default = 0
for (pat, negated) in self.general.forbiddenre:
match = pat.search(root_and_path)
if negated:
default = 1
if match:
return 0
elif match:
return 1
return default
# If no 'forbiddenre' is in use, we check 'forbidden', which only
# looks at the top-most directory.
elif self.general.forbidden:
# A root and a single non-directory path component? That's not
# a module.
if len(path_parts) == 1 and pathtype != vclib.DIR:
return 0
# Do the forbiddenness test.
module = path_parts[0]
default = 0
for pat in self.general.forbidden:
if pat[0] == '!':
default = 1
if fnmatch.fnmatchcase(module, pat[1:]):
return 0
elif fnmatch.fnmatchcase(module, pat):
return 1
return default
# No forbiddenness configuration? Just allow it.
else:
return 0
def _parse_roots(config_name, config_value):
roots = { }

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -200,7 +200,7 @@ Directives
equivalent to "[CALLBACK QUAL_NAME]"
"""
#
# Copyright (C) 2001-2007 Greg Stein. All Rights Reserved.
# Copyright (C) 2001-2005 Greg Stein. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are

View File

@@ -364,11 +364,11 @@ class _pipe:
else:
if self.thread:
self.thread.join()
if type(self.child_pid) == type([]):
if type(self.child_pid) == type([]):
for pid in self.child_pid:
exit = os.waitpid(pid, 0)[1]
return exit
else:
else:
return os.waitpid(self.child_pid, 0)[1]
return None

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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
@@ -25,7 +25,6 @@ import time
import cvsdb
import viewvc
import vclib
import ezt
import debug
import urllib
@@ -283,8 +282,10 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
ob.log = '&nbsp;'
for commit in files:
parts = filter(None, string.split(commit.GetDirectory(), '/'))
if parts and cfg.options.hide_cvsroot and parts[0] == 'CVSROOT':
dir_parts = filter(None, string.split(commit.GetDirectory(), '/'))
if dir_parts \
and ((dir_parts[0] == 'CVSROOT' and cfg.options.hide_cvsroot) \
or cfg.is_forbidden(dir_parts[0])):
continue
ctime = commit.GetTime()
@@ -303,11 +304,6 @@ def build_commit(server, cfg, desc, files, cvsroots, viewvc_link):
file = (directory and directory + "/") + commit.GetFile()
cvsroot_name = cvsroots.get(repository)
## skip forbidden files
if cfg.is_forbidden(cvsroot_name,
filter(None, string.split(file, "/")), vclib.FILE):
continue
## if we couldn't find the cvsroot path configured in the
## viewvc.conf file, then don't make the link
if cvsroot_name:
@@ -349,7 +345,7 @@ def run_query(server, cfg, form_data, viewvc_link):
files = [ ]
cvsroots = {}
rootitems = cfg.general.svn_roots.items() + cfg.general.cvs_roots.items()
rootitems = cfg.general.cvs_roots.items() + cfg.general.svn_roots.items()
for key, value in rootitems:
cvsroots[cvsdb.CleanRepository(value)] = key
@@ -370,13 +366,6 @@ def run_query(server, cfg, form_data, viewvc_link):
commits.append(build_commit(server, cfg, current_desc, files,
cvsroots, viewvc_link))
# Strip out commits that don't have any files attached to them. The
# files probably aren't present because they've been blocked via
# forbiddenness.
def _only_with_files(commit):
return len(commit.files) > 0
commits = filter(_only_with_files, commits)
return commits
def main(server, cfg, viewvc_link):

View File

@@ -534,25 +534,31 @@ def _parse_co_header(fp):
raise COMalformedOutput, "Unable to find filename in co output stream"
filename = match.group(1)
# look through subsequent lines for a revision. we might encounter
# some ignorable or problematic lines along the way.
while 1:
line = fp.readline()
if not line:
break
# look for a revision.
match = _re_co_revision.match(line)
if match:
return filename, match.group(1)
elif _re_co_missing_rev.match(line) or _re_co_side_branches.match(line):
raise COMissingRevision, "Got missing revision error from co output stream"
elif _re_co_warning.match(line):
pass
else:
break
# look for a revision in the second line.
line = fp.readline()
if not line:
raise COMalformedOutput, "Missing second line from co output stream"
match = _re_co_revision.match(line)
if match:
return filename, match.group(1)
elif _re_co_missing_rev.match(line) or _re_co_side_branches.match(line):
raise COMissingRevision, "Got missing revision error from co output stream"
elif _re_co_warning.match(line):
pass
else:
raise COMalformedOutput, "Unable to find revision in co output stream"
# if we get here, the second line wasn't a revision, but it was a
# warning we can ignore. look for a revision in the third line.
line = fp.readline()
if not line:
raise COMalformedOutput, "Missing third line from co output stream"
match = _re_co_revision.match(line)
if match:
return filename, match.group(1)
raise COMalformedOutput, "Unable to find revision in co output stream"
# if your rlog doesn't use 77 '=' characters, then this must change
LOG_END_MARKER = '=' * 77 + '\n'
ENTRY_END_MARKER = '-' * 28 + '\n'

View File

@@ -346,7 +346,7 @@ class CVSParser(rcsparse.Sink):
is_trunk_revision = self.trunk_rev.match(revision) is not None
if is_trunk_revision:
diffs = self.deltatext_split(last_revision)
diffs = self.deltatext_split(last_revision)
# Revisions on the trunk specify deltas that transform a
# revision into an earlier revision, so invert the translation
@@ -379,7 +379,7 @@ class CVSParser(rcsparse.Sink):
# the trunk. They specify deltas that transform a revision
# into a later revision.
adjust = 0
diffs = self.deltatext_split(revision)
diffs = self.deltatext_split(revision)
for command in diffs:
if skip > 0:
skip = skip - 1

View File

@@ -78,7 +78,7 @@ class RCSStopParser(Exception):
#
class _Parser:
stream_class = None # subclasses need to define this
stream_class = None # subclasses need to define this
def parse_rcs_admin(self):
while 1:
@@ -196,7 +196,7 @@ class _Parser:
if token == ';':
break
author = author + token + ' '
author = author[:-1] # toss the trailing space
author = author[:-1] # toss the trailing space
# Parse state
self.ts.match('state')
@@ -206,7 +206,7 @@ class _Parser:
if token == ';':
break
state = state + token + ' '
state = state[:-1] # toss the trailing space
state = state[:-1] # toss the trailing space
# Parse branches
self.ts.match('branches')

View File

@@ -26,7 +26,7 @@ class _TokenStream:
# note: we use a multiple of a standard block size
CHUNK_SIZE = 192 * 512 # about 100k
# CHUNK_SIZE = 5 # for debugging, make the function grind...
# CHUNK_SIZE = 5 # for debugging, make the function grind...
def __init__(self, file):
self.rcsfile = file
@@ -51,7 +51,7 @@ class _TokenStream:
buf = self.rcsfile.read(self.CHUNK_SIZE)
if buf == '':
# signal EOF by returning None as the token
del self.buf # so we fail if get() is called again
del self.buf # so we fail if get() is called again
return None
idx = 0

View File

@@ -25,7 +25,7 @@ _tt = TextTools
_idchar_list = map(chr, range(33, 127)) + map(chr, range(160, 256))
_idchar_list.remove('$')
_idchar_list.remove(',')
#_idchar_list.remove('.') # leave as part of 'num' symbol
#_idchar_list.remove('.') leave as part of 'num' symbol
_idchar_list.remove(':')
_idchar_list.remove(';')
_idchar_list.remove('@')
@@ -41,10 +41,10 @@ _T_STRING_START = 40
_T_STRING_SPAN = 60
_T_STRING_END = 70
_E_COMPLETE = 100 # ended on a complete token
_E_TOKEN = 110 # ended mid-token
_E_STRING_SPAN = 130 # ended within a string
_E_STRING_END = 140 # ended with string-end ('@') (could be mid-@@)
_E_COMPLETE = 100 # ended on a complete token
_E_TOKEN = 110 # ended mid-token
_E_STRING_SPAN = 130 # ended within a string
_E_STRING_END = 140 # ended with string-end ('@') (could be mid-@@)
_SUCCESS = +100
@@ -65,7 +65,7 @@ class _mxTokenStream:
# note: we use a multiple of a standard block size
CHUNK_SIZE = 192 * 512 # about 100k
# CHUNK_SIZE = 5 # for debugging, make the function grind...
# CHUNK_SIZE = 5 # for debugging, make the function grind...
def __init__(self, file):
self.rcsfile = file

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC
@@ -15,11 +15,9 @@
import vclib
import os
import os.path
import stat
import string
import cStringIO
import signal
import shutil
import time
import tempfile
import popen
@@ -501,36 +499,13 @@ class BlameSource:
if os.sep != '/':
rootpath = string.replace(rootpath, os.sep, '/')
# Make a read-only temporary directory for Subversion to use as
# its runtime config dir. (Read-only because that will prevent
# Subversion from fleshing out all the default runtime config
# contents.)
self.config_dir = self._mkdtemp()
os.chmod(self.config_dir, stat.S_IRUSR | stat.S_IXUSR)
url = 'file://' + string.join([rootpath, fs_path], "/")
fp = popen.popen(svn_client_path,
("blame",
"-r%d" % int(rev),
"--non-interactive",
"--config-dir", self.config_dir,
('blame', "-r%d" % int(rev), "--non-interactive",
"%s@%d" % (url, int(rev))),
'rb', 1)
self.fp = fp
def _mkdtemp(self):
### FIXME: When we require Python 2.3, this can go away.
for i in range(10):
dir = tempfile.mktemp()
try:
os.mkdir(dir, 0700)
return dir
except OSError, e:
if e.errno == errno.EEXIST:
continue # try again
raise
raise IOError, (errno.EEXIST, "No usable temporary directory name found")
def __getitem__(self, idx):
if idx == self.idx:
return self.last
@@ -555,13 +530,6 @@ class BlameSource:
self.idx = idx
return item
def __del__(self):
try:
if self.config_dir:
shutil.rmtree(self.config_dir)
except:
pass
class BlameSequencingError(Exception):
pass

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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
@@ -100,12 +100,8 @@ def created_rev(svnrepos, full_name, rev):
kind = ra.svn_ra_check_path(svnrepos.ra_session, full_name, rev,
svnrepos.pool)
if kind == core.svn_node_dir:
retval = ra.svn_ra_get_dir(svnrepos.ra_session, full_name,
rev, svnrepos.pool)
if type(retval) == type([]) and len(retval) == 3:
props = retval[2]
else: # compat with older (broken) bindings
props = retval
props = ra.svn_ra_get_dir(svnrepos.ra_session, full_name,
rev, svnrepos.pool)
return int(props[core.SVN_PROP_ENTRY_COMMITTED_REV])
return core.SVN_INVALID_REVNUM
@@ -180,14 +176,12 @@ class LogCollector:
# Changed paths have leading slashes
changed_paths = paths.keys()
changed_paths.sort(lambda a, b: _compare_paths(a, b))
copyfrom_path = copyfrom_rev = this_path = None
this_path = None
if self.path in changed_paths:
this_path = self.path
change = paths[self.path]
if change.copyfrom_path:
this_path = change.copyfrom_path
copyfrom_path = change.copyfrom_path[1:]
copyfrom_rev = change.copyfrom_rev
for changed_path in changed_paths:
if changed_path != self.path:
# If a parent of our path was copied, our "next previous"
@@ -200,7 +194,7 @@ class LogCollector:
if self.show_all_logs or this_path:
date = _datestr_to_date(date, pool)
entry = Revision(revision, date, author, message, None,
self.path[1:], copyfrom_path, copyfrom_rev)
self.path[1:], None, None)
self.logs.append(entry)
if this_path:
self.path = this_path
@@ -246,7 +240,7 @@ class SelfCleanFP:
self._path = path
self._eof = 0
def read(self, len=None):
def read(self, len):
if len:
chunk = self._fp.read(len)
else:

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC
@@ -14,7 +14,7 @@
#
# -----------------------------------------------------------------------
__version__ = '1.0.6'
__version__ = '1.0.4'
# this comes from our library; measure the startup time
import debug
@@ -160,11 +160,6 @@ class Request:
# validate the parameter
_validate_param(name, values[0])
# only allow the magic ViewVC MIME types to be declared via CGI params
if (name == 'content-type') and \
(not values[0] in (viewcvs_mime_type, alt_mime_type)):
continue
# if we're here, then the parameter is okay
self.query_dict[name] = values[0]
@@ -196,7 +191,7 @@ class Request:
# handle tarball magic suffixes
if self.view_func is download_tarball:
if (self.query_dict.get('parent')):
del path_parts[-1]
del path_parts[-1]
elif path_parts[-1][-7:] == ".tar.gz":
path_parts[-1] = path_parts[-1][:-7]
@@ -288,19 +283,13 @@ class Request:
needs_redirect = 1
if self.repos and self.view_func is not redirect_pathrev:
# If this is an intended-to-be-hidden CVSROOT path, complain.
if cfg.options.hide_cvsroot \
and is_cvsroot_path(self.roottype, path_parts):
raise debug.ViewVCException('%s: unknown location'
% self.where, '404 Not Found')
# Make sure path exists
self.pathrev = pathrev = self.query_dict.get('pathrev')
self.pathtype = _repos_pathtype(self.repos, path_parts, pathrev)
if self.pathtype is None:
# Path doesn't exist, see if it could be an old-style ViewVC URL
# with a fake suffix.
# path doesn't exist, see if it could be an old-style ViewVC URL
# with a fake suffix
result = _strip_suffix('.diff', path_parts, pathrev, vclib.FILE, \
self.repos, view_diff) or \
_strip_suffix('.tar.gz', path_parts, pathrev, vclib.DIR, \
@@ -319,7 +308,7 @@ class Request:
if result:
self.path_parts, self.pathtype, self.view_func = result
self.where = _path_join(self.path_parts)
needs_redirect = 1
needs_redirect = 1
else:
raise debug.ViewVCException('%s: unknown location'
% self.where, '404 Not Found')
@@ -338,11 +327,11 @@ class Request:
self.where = _path_join(attic_parts)
needs_redirect = 1
# If this is a forbidden location, stop now
if cfg.is_forbidden(self.rootname, self.path_parts, self.pathtype):
raise debug.ViewVCException('%s: unknown location' \
% _path_join(self.path_parts),
'404 Not Found')
# If this is a forbidden directory, stop now
if self.path_parts and self.pathtype == vclib.DIR \
and cfg.is_forbidden(self.path_parts[0]):
raise debug.ViewVCException('%s: unknown location' % path_parts[0],
'404 Not Found')
if self.view_func is None:
# view parameter is not set, try looking at pathtype and the
@@ -351,7 +340,7 @@ class Request:
self.view_func = view_roots
elif self.pathtype == vclib.DIR:
# ViewCVS 0.9.2 used to put ?tarball=1 at the end of tarball urls
if self.query_dict.has_key('tarball'):
if self.query_dict.has_key('tarball'):
self.view_func = download_tarball
else:
self.view_func = view_directory
@@ -510,7 +499,7 @@ class Request:
if view_func is download_tarball:
if not where and not cfg.options.root_as_url_component:
url = url + '/' + rootname + '-root'
params['parent'] = '1'
params['parent'] = '1'
url = url + '.tar.gz'
# add trailing slash for a directory
@@ -917,9 +906,6 @@ def is_viewable_image(mime_type):
def is_text(mime_type):
return not mime_type or mime_type[:5] == 'text/'
def is_cvsroot_path(roottype, path_parts):
return roottype == 'cvs' and path_parts and path_parts[0] == 'CVSROOT'
def is_plain_text(mime_type):
return not mime_type or mime_type == 'text/plain'
@@ -993,13 +979,8 @@ def htmlify(html):
html = re.sub(_re_rewrite_email, r'<a href="mailto:\1&#64;\2">\1&#64;\2</a>', html)
return html
def format_log(log, cfg, htmlize=1):
if not log:
return log
if htmlize:
s = htmlify(log[:cfg.options.short_log_len])
else:
s = cgi.escape(log[:cfg.options.short_log_len])
def format_log(log, cfg):
s = htmlify(log[:cfg.options.short_log_len])
if len(log) > cfg.options.short_log_len:
s = s + '...'
return s
@@ -1556,9 +1537,16 @@ def view_directory(request):
cfg.options.hide_attic))
options["cvs_subdirs"] = (cfg.options.show_subdir_lastmod and
cfg.options.show_logs)
file_data = request.repos.listdir(request.path_parts, request.pathrev,
options)
# Filter file list if a regex is specified
search_re = request.query_dict.get('search', '')
if cfg.options.use_re_search and search_re:
file_data = search_files(request.repos, request.path_parts, request.pathrev,
file_data, search_re)
# Retrieve log messages, authors, revision numbers, timestamps
request.repos.dirlogs(request.path_parts, request.pathrev, file_data, options)
@@ -1568,12 +1556,6 @@ def view_directory(request):
sort_file_data(file_data, request.roottype, sortdir, sortby,
cfg.options.sort_group_dirs)
# If a regex is specified, build a compiled form thereof for filtering
searchstr = None
search_re = request.query_dict.get('search', '')
if cfg.options.use_re_search and search_re:
searchstr = re.compile(search_re)
# loop through entries creating rows and changing these values
rows = [ ]
num_displayed = 0
@@ -1605,14 +1587,13 @@ def view_directory(request):
(file.kind == vclib.DIR and 'dir')
row.errors = file.errors
if cfg.is_forbidden(request.rootname, request.path_parts + [file.name],
file.kind):
continue
if file.kind == vclib.DIR:
if cfg.options.hide_cvsroot \
and is_cvsroot_path(request.roottype,
request.path_parts + [file.name]):
if (where == '') and (cfg.is_forbidden(file.name)):
continue
if (request.roottype == 'cvs' and cfg.options.hide_cvsroot
and where == '' and file.name == 'CVSROOT'):
continue
row.view_href = request.get_url(view_func=view_directory,
@@ -1640,17 +1621,10 @@ def view_directory(request):
escape=1)
elif file.kind == vclib.FILE:
if searchstr is not None:
if request.roottype == 'cvs' and (file.errors or file.dead):
continue
if not search_file(request.repos, request.path_parts + [file.name],
request.pathrev, searchstr):
continue
if request.roottype == 'cvs' and file.dead:
num_dead = num_dead + 1
if hideattic:
continue
num_displayed = num_displayed + 1
file_where = where_prefix + file.name
@@ -1917,12 +1891,10 @@ def view_log(request):
# selected revision
selected_rev = request.query_dict.get('r1')
paths_forbidden = {}
entries = [ ]
name_printed = { }
cvs = request.roottype == 'cvs'
for rev in show_revs:
last_one = 0
entry = _item()
entry.rev = rev.string
entry.state = (cvs and rev.dead and 'dead')
@@ -1987,27 +1959,8 @@ def view_log(request):
entry.vendor_branch = None
if rev.filename != request.where:
entry.orig_path = rev.filename
# If this path has been copied, check the copy source for
# forbiddenness. If it's forbidden, we'll a) pretend this is a
# regular add (instead of a copy), and b) stop traversing history.
if rev.copy_path:
if not paths_forbidden.has_key(rev.copy_path):
paths_forbidden[rev.copy_path] = \
cfg.is_forbidden(request.rootname,
_path_parts(rev.copy_path), pathtype)
if paths_forbidden[rev.copy_path]:
entry.prev = None
last_one = 1
else:
entry.copy_path = rev.copy_path
entry.copy_rev = rev.copy_rev
entry.copy_href = request.get_url(view_func=view_log,
where=rev.copy_path,
pathtype=vclib.FILE,
params={'pathrev': rev.copy_rev},
escape=1)
entry.copy_path = rev.copy_path
entry.copy_rev = rev.copy_rev
if entry.orig_path:
entry.orig_href = request.get_url(view_func=view_log,
@@ -2016,6 +1969,14 @@ def view_log(request):
params={'pathrev': rev.string},
escape=1)
if rev.copy_path:
entry.copy_href = request.get_url(view_func=view_log,
where=rev.copy_path,
pathtype=vclib.FILE,
params={'pathrev': rev.copy_rev},
escape=1)
# view/download links
if pathtype is vclib.FILE:
entry.view_href, entry.download_href, entry.download_text_href, \
@@ -2076,8 +2037,6 @@ def view_log(request):
if entry.copy_path:
entry.copy_path = request.server.escape(entry.copy_path)
entries.append(entry)
if last_one:
break
data = common_template_data(request)
data.update({
@@ -2184,7 +2143,8 @@ def view_checkout(request):
# The revision number acts as a strong validator.
if not check_freshness(request, None, revision):
request.server.header(request.mime_type or 'text/plain')
request.server.header(request.query_dict.get('content-type')
or request.mime_type or 'text/plain')
copy_stream(fp)
fp.close()
@@ -2200,7 +2160,7 @@ def view_annotate(request):
diff_url = request.get_url(view_func=view_diff,
params={'r1': None, 'r2': None},
escape=1, partial=1)
escape=1, partial=1)
include_url = request.get_url(view_func=view_log, where='/WHERE/',
pathtype=vclib.FILE, params={}, escape=1)
@@ -2282,23 +2242,58 @@ def view_cvsgraph(request):
request.server.header()
generate_page(request, "graph", data)
def search_file(repos, path_parts, rev, search_re):
"""Return 1 iff the contents of the file at PATH_PARTS in REPOS as
of revision REV matches regular expression SEARCH_RE."""
def search_files(repos, path_parts, rev, files, search_re):
""" Search files in a directory for a regular expression.
Does a check-out of each file in the directory. Only checks for
the first match.
"""
# Pass in search regular expression. We check out
# each file and look for the regular expression. We then return the data
# for all files that match the regex.
# Compile to make sure we do this as fast as possible.
searchstr = re.compile(search_re)
# Will become list of files that have at least one match.
# new_file_list also includes directories.
new_file_list = [ ]
# Loop on every file (and directory)
for file in files:
# Is this a directory? If so, append name to new_file_list
# and move to next file.
if file.kind != vclib.FILE:
new_file_list.append(file)
continue
# Only files at this point
# Shouldn't search binary files, or should we?
# Should allow all text mime types to pass.
if not is_text(guess_mime(file.name)):
continue
# Only text files at this point
# Assign contents of checked out file to fp.
fp = repos.openfile(path_parts + [file.name], rev)[0]
# Read in each line, use re.search to search line.
# If successful, add file to new_file_list and break.
while 1:
line = fp.readline()
if not line:
break
if searchstr.search(line):
new_file_list.append(file)
# close down the pipe (and wait for the child to terminate)
fp.close()
break
return new_file_list
# Read in each line of a checked-out file, and then use re.search to
# search line.
fp = repos.openfile(path_parts, rev)[0]
matches = 0
while 1:
line = fp.readline()
if not line:
break
if search_re.search(line):
matches = 1
fp.close()
break
return matches
def view_doc(request):
"""Serve ViewVC static content locally.
@@ -2567,29 +2562,21 @@ def diff_parse_headers(fp, diff_type, rev1, rev2, sym1=None, sym2=None):
return date1, date2, flag, string.join(header_lines, '')
def _get_svn_location(request, base_rev, rev):
repos = request.repos
try:
parts = _path_parts(vclib.svn.get_location(repos, request.where,
repos._getrev(base_rev),
repos._getrev(rev)))
except vclib.InvalidRevision:
raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request')
except vclib.ItemNotFound:
raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request')
if request.cfg.is_forbidden(request.rootname, parts, vclib.FILE):
raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request')
return parts
def _get_diff_path_parts(request, query_key, rev, base_rev):
if request.query_dict.has_key(query_key):
parts = _path_parts(request.query_dict[query_key])
elif request.roottype == 'svn':
parts = _get_svn_location(request, base_rev, rev)
try:
repos = request.repos
parts = _path_parts(vclib.svn.get_location(repos, request.where,
repos._getrev(base_rev),
repos._getrev(rev)))
except vclib.InvalidRevision:
raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request')
except vclib.ItemNotFound:
raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
'to diff', '400 Bad Request')
else:
parts = request.path_parts
return parts
@@ -2909,11 +2896,6 @@ def generate_tarball(out, request, reldir, stack, dir_mtime=None):
if cvs and (file.rev is None or file.dead):
continue
# Skip forbidden files.
if request.cfg.is_forbidden(request.rootname, rep_path + [file.name],
file.kind):
continue
# If we get here, we've seen at least one valid file in the
# current directory. For CVS, we need to make sure there are
# directory parents to contain it, so we flush the stack.
@@ -2944,15 +2926,12 @@ def generate_tarball(out, request, reldir, stack, dir_mtime=None):
if file.errors or file.kind != vclib.DIR:
continue
# Skip hidden directories (top-level only).
if request.cfg.options.hide_cvsroot \
and is_cvsroot_path(request.roottype, rep_path + [file.name]):
continue
# Skip forbidden subdirs.
if request.cfg.is_forbidden(request.rootname, rep_path + [file.name],
file.kind):
continue
# Skip forbidden/hidden directories (top-level only).
if not rep_path:
if (request.cfg.is_forbidden(file.name)
or (cvs and request.cfg.options.hide_cvsroot
and file.name == 'CVSROOT')):
continue
mtime = request.roottype == 'svn' and file.date or None
generate_tarball(out, request, reldir + [file.name], stack, mtime)
@@ -2997,14 +2976,6 @@ def view_revision(request):
if check_freshness(request, None, str(rev), weak=1):
return
# Strip forbidden changed paths (we allow forbidden copyfrom-paths
# to leak through, though).
def _only_allowed(change):
return not request.cfg.is_forbidden(request.rootname,
_path_parts(change.filename),
change.pathtype)
changes = filter(_only_allowed, changes)
# Handle limit_changes parameter
cfg_limit_changes = request.cfg.options.limit_changes
limit_changes = int(query_dict.get('limit_changes', cfg_limit_changes))
@@ -3030,24 +3001,10 @@ def view_revision(request):
pathtype = (change.pathtype == vclib.FILE and 'file') \
or (change.pathtype == vclib.DIR and 'dir') \
or None
# If this is an add or a replacement, we'll verify that copyfrom
# paths are readable (if this is a copy), and if not claim this
# isn't a copy after all. And if it ain't a copy (now or "after
# all"), we'll clear the text_mods and prop_mods flags.
if (change.action == 'added' or change.action == 'replaced'):
if change.is_copy \
and request.cfg.is_forbidden(request.rootname,
_path_parts(change.base_path),
change.pathtype):
change.is_copy = 0
if change.action == 'added':
change.base_path = None
change.base_rev = None
if not change.is_copy:
change.text_mods = 0
change.prop_mods = 0
if (change.action == 'added' or change.action == 'replaced') \
and not change.is_copy:
change.text_mods = 0
change.prop_mods = 0
view_func = None
if change.pathtype is vclib.FILE:
@@ -3262,23 +3219,27 @@ def prev_rev(rev):
r = r[:-2]
return string.join(r, '.')
def build_commit(request, files, max_files, dir_strip, format):
"""Return a commit object build from the information in FILES, or
None if no allowed files are present in the set. DIR_STRIP is the
path prefix to remove from the commit object's set of files. If
MAX_FILES is non-zero, it is used to limit the number of files
returned in the commit object. FORMAT is the requested output
format of the query request."""
author = files[0].GetAuthor()
date = files[0].GetTime()
def build_commit(request, files, limited_files, dir_strip):
commit = _item(num_files=len(files), files=[])
commit.limited_files = ezt.boolean(limited_files)
desc = files[0].GetDescription()
commit_rev = files[0].GetRevision()
commit.log = htmlify(desc)
commit.short_log = format_log(desc, request.cfg)
commit.author = request.server.escape(files[0].GetAuthor())
commit.rss_date = make_rss_time_string(files[0].GetTime(), request.cfg)
if request.roottype == 'svn':
commit.rev = files[0].GetRevision()
commit.rss_url = '%s://%s%s' % \
(request.server.getenv("HTTPS") == "on" and "https" or "http",
request.server.getenv("HTTP_HOST"),
request.get_url(view_func=view_revision,
params={'revision': commit.rev},
escape=1))
else:
commit.rev = None
commit.rss_url = None
len_strip = len(dir_strip)
commit_files = []
num_allowed = 0
plus_count = 0
minus_count = 0
for f in files:
commit_time = f.GetTime()
@@ -3296,15 +3257,7 @@ def build_commit(request, files, max_files, dir_strip, format):
assert dirname[:len_strip] == dir_strip
assert len(dirname) == len_strip or dirname[len(dir_strip)] == '/'
dirname = dirname[len_strip+1:]
where = dirname and ("%s/%s" % (dirname, filename)) or filename
# skip files in forbidden or hidden modules
path_parts = _path_parts(where)
if request.cfg.is_forbidden(request.rootname, path_parts, vclib.FILE):
continue
if request.cfg.options.hide_cvsroot \
and is_cvsroot_path(request.roottype, path_parts):
continue
filename = dirname and ("%s/%s" % (dirname, filename)) or filename
# In CVS, we can actually look at deleted revisions; in Subversion
# we can't -- we'll look at the previous revision instead.
@@ -3320,14 +3273,14 @@ def build_commit(request, files, max_files, dir_strip, format):
where=dirname, pathtype=vclib.DIR,
params=params, escape=1)
log_href = request.get_url(view_func=view_log,
where=where, pathtype=vclib.FILE,
where=filename, pathtype=vclib.FILE,
params=params, escape=1)
diff_href = view_href = download_href = None
view_href = request.get_url(view_func=view_markup,
where=where, pathtype=vclib.FILE,
where=filename, pathtype=vclib.FILE,
params=params, escape=1)
download_href = request.get_url(view_func=view_checkout,
where=where, pathtype=vclib.FILE,
where=filename, pathtype=vclib.FILE,
params=params, escape=1)
if change_type == 'Change':
diff_href_params = params.copy()
@@ -3337,29 +3290,27 @@ def build_commit(request, files, max_files, dir_strip, format):
'diff_format': None
})
diff_href = request.get_url(view_func=view_diff,
where=where, pathtype=vclib.FILE,
where=filename, pathtype=vclib.FILE,
params=diff_href_params, escape=1)
prefer_markup = ezt.boolean(default_view(guess_mime(filename),
request.cfg) == view_markup)
# Update plus/minus line change count.
plus = int(f.GetPlusCount())
minus = int(f.GetMinusCount())
plus_count = plus_count + plus
minus_count = minus_count + minus
num_allowed = num_allowed + 1
if max_files and num_allowed > max_files:
# skip files in forbidden or hidden modules
dir_parts = filter(None, string.split(dirname, '/'))
if dir_parts \
and ((dir_parts[0] == 'CVSROOT'
and request.cfg.options.hide_cvsroot) \
or request.cfg.is_forbidden(dir_parts[0])):
continue
commit_files.append(_item(date=commit_time,
commit.files.append(_item(date=commit_time,
dir=request.server.escape(dirname),
file=request.server.escape(filename),
file=request.server.escape(f.GetFile()),
author=request.server.escape(f.GetAuthor()),
rev=rev,
branch=f.GetBranch(),
plus=plus,
minus=minus,
plus=int(f.GetPlusCount()),
minus=int(f.GetMinusCount()),
type=change_type,
dir_href=dir_href,
log_href=log_href,
@@ -3367,30 +3318,6 @@ def build_commit(request, files, max_files, dir_strip, format):
download_href=download_href,
prefer_markup=prefer_markup,
diff_href=diff_href))
# No files survived forbiddenness checks? Let's just pretend this
# little commit didn't happen, shall we?
if not len(commit_files):
return None
commit = _item(num_files=len(commit_files), files=commit_files,
plus=plus_count, minus=minus_count)
commit.limited_files = ezt.boolean(num_allowed > len(commit_files))
commit.log = htmlify(desc)
commit.short_log = format_log(desc, request.cfg, format != 'rss')
commit.author = request.server.escape(author)
commit.rss_date = make_rss_time_string(date, request.cfg)
if request.roottype == 'svn':
commit.rev = commit_rev
commit.rss_url = '%s://%s%s' % \
(request.server.getenv("HTTPS") == "on" and "https" or "http",
request.server.getenv("HTTP_HOST"),
request.get_url(view_func=view_revision,
params={'revision': commit.rev},
escape=1))
else:
commit.rev = None
commit.rss_url = None
return commit
def query_backout(request, commits):
@@ -3438,7 +3365,7 @@ def view_query(request):
format = request.query_dict.get('format')
limit = int(request.query_dict.get('limit', 0))
limit_changes = int(request.query_dict.get('limit_changes',
request.cfg.options.limit_changes))
request.cfg.options.limit_changes))
match_types = { 'exact':1, 'like':1, 'glob':1, 'regex':1, 'notregex':1 }
sort_types = { 'date':1, 'author':1, 'file':1 }
@@ -3524,48 +3451,45 @@ def view_query(request):
current_desc = query.commit_list[0].GetDescriptionID()
current_rev = query.commit_list[0].GetRevision()
dir_strip = _path_join(repos_dir)
for commit in query.commit_list:
commit_desc = commit.GetDescriptionID()
commit_rev = commit.GetRevision()
# base modification time on the newest commit
if commit.GetTime() > mod_time:
mod_time = commit.GetTime()
# base modification time on the newest commit ...
if commit.GetTime() > mod_time: mod_time = commit.GetTime()
# form plus/minus totals
plus_count = plus_count + int(commit.GetPlusCount())
minus_count = minus_count + int(commit.GetMinusCount())
# group commits with the same commit message ...
desc = commit.GetDescriptionID()
# For CVS, group commits with the same commit message.
# For Subversion, group them only if they have the same revision number
if request.roottype == 'cvs':
if current_desc == commit_desc:
files.append(commit)
if current_desc == desc:
if not limit_changes or len(files) < limit_changes:
files.append(commit)
else:
limited_files = 1
continue
else:
if current_rev == commit_rev:
files.append(commit)
if current_rev == commit.GetRevision():
if not limit_changes or len(files) < limit_changes:
files.append(commit)
else:
limited_files = 1
continue
# append this grouping
commit_item = build_commit(request, files, limit_changes,
dir_strip, format)
if commit_item:
# update running plus/minus totals
plus_count = plus_count + commit_item.plus
minus_count = minus_count + commit_item.minus
commits.append(commit_item)
# if our current group has any allowed files, append a commit
# with those files.
if len(files):
commits.append(build_commit(request, files, limited_files, dir_strip))
files = [ commit ]
limited_files = 0
current_desc = commit_desc
current_rev = commit_rev
current_desc = desc
current_rev = commit.GetRevision()
# we need to tack on our last commit grouping, if any
commit_item = build_commit(request, files, limit_changes,
dir_strip, format)
if commit_item:
# update running plus/minus totals
plus_count = plus_count + commit_item.plus
minus_count = minus_count + commit_item.minus
commits.append(commit_item)
# we need to tack on our last commit grouping, but, again, only if
# it has allowed files.
if len(files):
commits.append(build_commit(request, files, limited_files, dir_strip))
# only show the branch column if we are querying all branches
# or doing a non-exact branch match on a CVS repository.
@@ -3610,7 +3534,7 @@ def view_query(request):
})
if format == 'rss':
request.server.header("application/rss+xml")
request.server.header("text/xml")
generate_page(request, "rss", data)
else:
request.server.header()
@@ -3640,10 +3564,10 @@ for code, view in _views.items():
def list_roots(cfg):
allroots = { }
for root in cfg.general.svn_roots.keys():
allroots[root] = [cfg.general.svn_roots[root], 'svn']
for root in cfg.general.cvs_roots.keys():
allroots[root] = [cfg.general.cvs_roots[root], 'cvs']
for root in cfg.general.svn_roots.keys():
allroots[root] = [cfg.general.svn_roots[root], 'svn']
return allroots
def load_config(pathname=None, server=None):

View File

@@ -1,6 +1,6 @@
# -*-python-*-
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewVC

View File

@@ -1,53 +0,0 @@
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.

View File

@@ -1,85 +0,0 @@
RELEASE MANAGEMENT
ViewVC rolls releases from release branches associate with each minor
version of the software. For example, the 1.1.0 is rolled from the
1.1.x branch. The same is true for the 1.1.1, 1.1.2, ... releases.
There is a script, `tools/make-release', which creates a release
directory and the various archive files that we distribute. All other
steps required to get a ViewVC release out of the door require manual
execution (currently by C. Michael Pilato). Those steps are as
follows:
Checkout a working copy of the release branch for the release you
intend to roll, and in that working copy, perform the following steps
(X, Y, and Z below represent integral major, minor, and patch version
numbers, and not literal):
1. Review any open bug reports:
http://viewvc.tigris.org/servlets/ProjectIssues
2. Add a new subsection to the file 'docs/upgrading.html' describing
all user visible changes for users of previous releases of ViewVC.
Commit any modifications. NOTE: This step should not be necessary
for patch releases.
3. Verify that copyright years are correct in both the LICENSE.html
file and the source code.
4. Update and commit the 'CHANGES' file.
5. Test, test, test! There is no automatic testsuite available. So
just run with permuting different `viewvc.conf' settings... and
pray. Fix what needs fixin', keeping the CHANGES file in sync
with the branch.
6. At this point, the source code committed to the release branch
should exactly reflect what you wish to distribute and dub "the
release".
7. Edit the file 'lib/viewvc.py' and remove the "-dev" suffix from
__version__. The remainder should be of the form "X.Y.Z", where X,
Y, and Z are positive integers. Do NOT commit this change.
8. Update your working copy to HEAD, and tag the release:
svn update
svn cp -m "Tag the X.Y.Z final release." . \
http://viewvc.tigris.org/svn/viewvc/tags/X.Y.Z
9. Go into an empty directory and run the 'make-release' script:
tools/make-release viewvc-X.Y.Z X.Y.Z
10. Verify the archive files:
- do they have a LICENSE.html file?
- do they have necessary include documentation?
- do they *not* have unnecessary stuff?
- do they install and work correctly?
11. Upload the created archive files (tar.gz and zip) into the Files
and Documents section of the Tigris.org project, and modify the
CHECKSUMS document there accordingly. Also, drop a copy of the
archive files into the root directory of the viewvc.org website
(unversioned).
12. Update the websites (both the viewvc.org/ and www/ ones) to refer
to the new release files.
13. Edit the file 'lib/viewvc.py' again, re-adding the "-dev" suffix
and incrementing the patch number assigned to the __version__
variable, and commit:
svn ci -m "Begin a new release cycle."
14. Edit the Issue Tracker configuration options, adding a new Version
for the just-released one, and a new Milestone for the next patch
(and possibly, minor or major) release. (For the Milestone sort
key, use a packed integer XXYYZZ: 1.0.3 == 10003, 2.11.4 == 21104.)
15. Write an announcement explaining all the cool new features and
post it to the announce@ list, to the project's News area, and to
other places interested in this sort of stuff, such as Freshmeat
(http://www.freshmeat.net).

View File

@@ -9,43 +9,16 @@
<h3>An Exception Has Occurred</h3>
[if-any msg]
<p>[msg]</p>
<p><pre>[msg]</pre></p>
[end]
[if-any status]
<h4>HTTP Response Status</h4>
<p><pre>[status]</pre></p>
<hr />
[end]
[if-any msg][else]
<h4>Python Traceback</h4>
<p><pre>
[stacktrace]
</pre></p>
[end]
[# Here follows a bunch of space characters, present to ensure that
our error message is larger than 512 bytes so that IE's "Friendly
Error Message" won't show. For more information, see
http://oreillynet.com/onjava/blog/2002/09/internet_explorer_subverts_err.html]
</body>
</html>

View File

@@ -1,24 +1,34 @@
[if-any search_re_form]
<hr />
<div>
Show files containing the regular expression:
<form method="get" action="[search_re_action]" style="display: inline;">
<div style="display: inline;">
[search_re_hidden_values]
<input type="text" name="search" value="[search_re]" />
<input type="submit" value="Show" />
</div>
</form>
[if-any search_re]
<form method="get" action="[search_re_action]" style="display: inline;">
<div style="display: inline;">
[search_re_hidden_values]
<input type="submit" value="Show all files" />
</div>
</form>
<hr />
[# this table holds the selectors on the left, and reset on the right ]
<table class="auto">
<tr>
<td>Show files containing the regular expression:</td>
<td>
<form method="get" action="[search_re_action]">
<div>
[search_re_hidden_values]
<input type="text" name="search" value="[search_re]" />
<input type="submit" value="Show" />
</div>
</form>
</td>
</tr>
[if-any search_re]
<tr>
<td>&nbsp;</td>
<td>
<form method="get" action="[search_tag_action]">
<div>
[search_tag_hidden_values]
<input type="submit" value="Show all files" />
</div>
</form>
</td>
</tr>
[end]
</table>
[end]
[end]
</div>
[# if you want to disable tarball generation remove the following: ]
[if-any tarball_href]

View File

@@ -9,8 +9,8 @@
<title>[if-any commits.rev][commits.rev]: [end][[commits.author]] [commits.short_log]</title>
[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>&lt;pre&gt;[format "xml"][commits.log][end]&lt;/pre&gt;</description>
<pubDate>[commits.rss_date]</pubDate>
<description>[commits.log]</description>
</item>[end]
</channel>
</rss>

View File

@@ -1,6 +1,6 @@
#!/bin/sh
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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
@@ -45,44 +45,30 @@ for PLATFORM in unix windows; do
EOL="--native-eol LF"
fi
echo "Beginning build for ${PLATFORM}:"
echo " Exporting source code..."
svn export --quiet ${EOL} http://viewvc.tigris.org/svn/viewvc/${ROOT} ${TARGET}
svn export ${EOL} http://viewvc.tigris.org/svn/viewvc/${ROOT} ${TARGET}
### Various shifting, cleanup.
# Documentation is now also distributed together with the release, but
# we still copy the license file to its traditional place (it is small
# and many files still contain comments refering to this location):
# Remove some not useful directories
for JUNK in elemx \
notes \
tests \
tools \
tparse \
viewcvs.sourceforge.net \
viewvc.org \
www; do
if [ -d ${TARGET}/${JUNK} ]; then
echo " Removing ${TARGET}/${JUNK}..."
rm -r ${TARGET}/${JUNK}
fi
done
rm -r ${TARGET}/{elemx,tests,tools,tparse,viewcvs.sourceforge.net,www}
# Make sure permissions are reasonable:
echo " Normalizing permissions..."
find ${TARGET} -print | xargs chmod uoa+r
find ${TARGET} -type d -print | xargs chmod uoa+x
if test ${PLATFORM} = windows; then
# Create also a ZIP file for those poor souls :-) still using Windows:
echo " Creating ZIP archive..."
zip -qor9 ${TARGET}.zip ${TARGET}
else
# Cut the tarball:
echo " Creating tarball archive..."
tar cf - ${TARGET} | gzip -9 > ${TARGET}.tar.gz
fi
# remove target directory
rm -r ${TARGET}
done
echo "Done."
echo 'Done.'

View File

@@ -0,0 +1,2 @@
*.tar.gz
*.zip

View File

@@ -0,0 +1 @@
RedirectMatch ^(.*)$ http://www.viewvc.org/$1

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 1999-2007 The ViewCVS Group. All Rights Reserved.
# Copyright (C) 1999-2006 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
@@ -75,9 +75,8 @@ if sys.platform == "win32":
## destination path,
## boolean -- prompt before replacing?)
TREE_LIST = [
("lib", "lib", 0),
("templates", "templates", 1),
("templates-contrib", "templates-contrib", 1),
("lib", "lib", 0),
("templates", "templates", 1),
]

View File

@@ -142,9 +142,8 @@ use_rcsparse = 0
address = <a href="mailto:cvs-admin@insert.your.domain.here">No admin address has been configured</a>
#
# This should contain a list of modules (that is, top-level directories within
# repositories) that should not be displayed (by default or by explicit path
# specification).
# This should contain a list of modules in the repository that should not be
# displayed (by default or by explicit path specification).
#
# This configuration can be a simple list of modules, or it can get quite
# complex:
@@ -163,9 +162,8 @@ address = <a href="mailto:cvs-admin@insert.your.domain.here">No admin address ha
#
# Tests are case-sensitive.
#
# NOTE: This is for the hiding of modules within repositories, *not*
# for the hiding of repositories (roots) themselves.
#
forbidden =
# Some examples:
#
# Disallow "example" but allow all others:
@@ -186,40 +184,6 @@ address = <a href="mailto:cvs-admin@insert.your.domain.here">No admin address ha
# Allow "xml", forbid other modules starting with "x", and allow the rest:
# forbidden = !xml, x*, !*
#
forbidden =
#
# This is similar to 'forbidden', but differs in some key ways:
#
# *) Rather than shell-style "glob" expressions, the values in this
# list are regular expressions. You can still prepend a ! character
# to each regular expression to invert its meaning, though.
#
# *) It compares not against modules only, but against paths consisting
# of the repository (or root) name plus the path of the versioned file
# or directory to be tested. For example, to see if the user is
# authorized to see the path "/trunk/www/index.html" in the repository
# whose root name is "svnrepos", this authorizer will test the path
# "svnrepos/trunk/www/index.html" against the list of forbidden regular
# expressions. Directory paths will be terminated by a forward slash.
#
# NOTE: Use of this configuration option will *disable* any configuration of
# the 'forbidden' option -- they cannot be used simultaneously.
#
# Some examples:
#
# Disallow files named "PRIVATE", but allow all others:
# forbiddenre = /PRIVATE$
#
# Allow only the "example1" and "example2" roots and the paths inside them,
# disallowing all others (which can be done in multiple ways):
# forbiddenre = !^example1(/|$), !^example2(/|$)/
# forbiddenre = !^example[12](/|$)
#
# Only allow visibility of HTML files and the directories that hold them:
# forbiddenre = !^.*(/|\.html)$
#
forbiddenre =
#
# This option provides a mechanism for custom key/value pairs to be

View File

@@ -18,7 +18,7 @@
<a href="./download.html">Download</a> |
<a href="./upgrading.html">Upgrading</a> |
<a href="./contributing.html">Contributing</a> |
<a href="http://viewvc.tigris.org/nonav/source/browse/*checkout*/viewvc/trunk/LICENSE.html">License</a> |
<a href="./license-1.html">License</a> |
<a href="./contact.html">Contact</a> |
<a href="./who.html">About</a>
</p>

View File

@@ -18,7 +18,7 @@
<a href="./download.html">Download</a> |
<a href="./upgrading.html">Upgrading</a> |
<a href="./contributing.html">Contributing</a> |
<a href="http://viewvc.tigris.org/nonav/source/browse/*checkout*/viewvc/trunk/LICENSE.html">License</a> |
<a href="./license-1.html">License</a> |
<a href="./contact.html">Contact</a> |
<a href="./who.html">About</a>
</p>

View File

@@ -18,7 +18,7 @@
<a href="./download.html">Download</a> |
<a href="./upgrading.html">Upgrading</a> |
<a href="./contributing.html">Contributing</a> |
<a href="http://viewvc.tigris.org/nonav/source/browse/*checkout*/viewvc/trunk/LICENSE.html">License</a> |
<a href="./license-1.html">License</a> |
<a href="./contact.html">Contact</a> |
<a href="./who.html">About</a>
</p>

View File

@@ -18,7 +18,7 @@
<a href="./download.html">Download</a> |
<a href="./upgrading.html">Upgrading</a> |
<a href="./contributing.html">Contributing</a> |
<a href="http://viewvc.tigris.org/nonav/source/browse/*checkout*/viewvc/trunk/LICENSE.html">License</a> |
<a href="./license-1.html">License</a> |
<a href="./contact.html">Contact</a> |
<a href="./who.html">About</a>
</p>

View File

@@ -1,21 +1,63 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ViewVC: License v1</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="./styles.css"/>
</head>
<body>
<div id="title">
<a href="http://www.viewvc.org/"><img
src="./images/title.jpg" alt="ViewVC: Repository Browsing"/></a>
</div>
<div id="menu">
<p><a href="./index.html">Home</a> |
<a href="http://viewvc.tigris.org/">Project Page</a> |
<a href="./download.html">Download</a> |
<a href="./upgrading.html">Upgrading</a> |
<a href="./contributing.html">Contributing</a> |
<a href="./license-1.html">License</a> |
<a href="./contact.html">Contact</a> |
<a href="./who.html">About</a>
</p>
</div>
<table id="pagetable">
<tr>
<td id="pagecolumn1">
<h4>On this page:</h4>
<ul id="bookmarks">
<li><a href="#sec-license">License</a></li>
</ul>
<hr/>
<address><a href="mailto:&#117&#115&#101&#114&#115&#64&#118&#105&#101&#119&#118&#99&#46&#116&#105&#103&#114&#105&#115&#46&#111&#114&#103">ViewVC Users Group</a></address>
</td>
<td id="pagecolumn2">
<div class="section">
<h2 id="sec-license">License</h2>
<p>The following text constitutes the license agreement for the <a
href="http://www.viewvc.org/">ViewVC</a> software (formerly known
as ViewCVS). It is an agreement between <a
href="http://www.viewvc.org/who.html#sec-viewcvs-group">The ViewCVS
Group</a> and the users of ViewVC.</p>
href="./index.html">ViewVC</a> software (formerly known as
ViewCVS). It is an agreement between <a
href="./who.html#sec-viewcvs-group">The ViewCVS Group</a> and the
users of ViewVC.</p>
<blockquote>
<p style="font-size: 80%;"><em>Note: the copyright years were updated
on May 12, 2001 and September 5, 2002. No other changes were made
to the license.</em></p>
<p><strong>Copyright &copy; 1999-2008 The ViewCVS Group. All rights
<hr/>
<p><strong>Copyright &copy; 1999-2002 The ViewCVS Group. All rights
reserved.</strong></p>
<p>By using ViewVC, you agree to the terms and conditions set forth
@@ -48,18 +90,10 @@
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.</p>
</blockquote>
<hr />
<p>The following changes have occured to this license over time:</p>
<ul>
<li>May 12, 2001 &mdash; copyright years updated</li>
<li>September 5, 2002 &mdash; copyright years updated</li>
<li>March 17, 2006 &mdash; software renamed from "ViewCVS"</li>
<li>April 10, 2007 &mdash; copyright years updated</li>
<li>February 22, 2008 &mdash; copyright years updated</li>
</ul>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -1,95 +0,0 @@
#!/bin/sh
DATE=`date +"%Y%m%d"`
EXPORTDIR="viewvc-${DATE}"
PUBLISH_DIR=/www/viewvc/nightly
# export HEAD of trunk
svn export --quiet http://viewvc.tigris.org/svn/viewvc/trunk ${EXPORTDIR} --username guest --password ""
# use Python to determine the version number, reading it from
# viewvc.__version__
cd $EXPORTDIR/lib
VERSION=`python -c "import viewvc; print viewvc.__version__"`
TARGET=viewvc-${VERSION}-${DATE}
# make a release
cd ../tools
./make-release ${TARGET}
# remove results of last build
rm -rf ${PUBLISH_DIR}/viewvc*.tar.gz ${PUBLISH_DIR}/viewvc*.zip ${PUBLISH_DIR}/index.html
# publish new build
mv ${TARGET}.* ${PUBLISH_DIR}
cd ../..
cat > ${PUBLISH_DIR}/index.html <<EOF
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>ViewVC: Nightly Snapshots</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="../styles.css"/>
</head>
<body>
<div id="title">
<a href="http://www.viewvc.org/"><img
src="../images/title.jpg" alt="ViewVC: Repository Browsing"/></a>
</div>
<div id="menu">
<p><a href="../index.html">Home</a> |
<a href="http://viewvc.tigris.org/">Project Page</a> |
<a href="../download.html">Download</a> |
<a href="../contributing.html">Contributing</a> |
<a href="http://viewvc.tigris.org/nonav/source/browse/*checkout*/viewvc/trunk/LICENSE.html">License</a> |
<a href="../contact.html">Contact</a> |
<a href="../who.html">About</a>
</p>
</div>
<table id="pagetable">
<tr>
<td id="pagecolumn1">
<h4>On this page:</h4>
<ul id="bookmarks">
<li><a href="#sec-snapshots">Snapshots</a></li>
</ul>
<p><a href="http://validator.w3.org/check?uri=referer"><img
src="http://www.w3.org/Icons/valid-xhtml10"
alt="Valid XHTML 1.0 Strict" height="31" width="88" /></a>
</p>
</td>
<td id="pagecolumn2">
<div class="section">
<h2 id="sec-snapshots">Snapshots</h2>
<ul>
EOF
echo "<li><a href=\"${TARGET}.tar.gz\">${TARGET}.tar.gz</a></li>" >> ${PUBLISH_DIR}/index.html
echo "<li><a href=\"${TARGET}.zip\">${TARGET}.zip</a></li>" >> ${PUBLISH_DIR}/index.html
cat >> ${PUBLISH_DIR}/index.html <<EOF
</ul>
</div>
</td>
</tr>
</table>
</body>
</html>
EOF
# more cleanup
rm -rf ${EXPORTDIR}

View File

@@ -1586,20 +1586,10 @@ td {
<td>Boolean</td>
<td>True if files list was cut short due to <tt>limit_changes</tt>.</td>
</tr>
<tr class="varlevel2">
<td class="varname">commits.minus</td>
<td>String</td>
<td>Total number of lines removed from files in this commit.</td>
</tr>
<tr class="varlevel2">
<td class="varname">commits.num_files</td>
<td>String</td>
<td>Total number of files in the <var>commits.files</var> list.</td>
</tr>
<tr class="varlevel2">
<td class="varname">commits.plus</td>
<td>String</td>
<td>Number of lines added to files in this commit.</td>
<td>Number of files in the <var>commits.files</var> list.</td>
</tr>
<tr class="varlevel2">
<td class="varname">commits.rev</td>
@@ -1640,14 +1630,12 @@ td {
<tr class="varlevel1">
<td class="varname">minus_count</td>
<td>String</td>
<td>Total number of lines removed from all files across all returned
commits.</td>
<td>Total number of lines removed in the commit (over all files).</td>
</tr>
<tr class="varlevel1">
<td class="varname">plus_count</td>
<td>String</td>
<td>Total number of lines added to all files across all returned
commits.</td>
<td>Total number of lines added in the commit (over all files).</td>
</tr>
<tr class="varlevel1">
<td class="varname">queryform_href</td>

View File

@@ -89,7 +89,7 @@ th.caption {
<li><a href="#compat-tarball">'<code>tarball=1</code>' Parameter &rArr; '<code>view=tar</code>'</a></li>
<li><a href="#compat-graph">'<code>graph=1</code>' Parameter &rArr; '<code>view=graph</code>'</a></li>
<li><a href="#compat-makeimage">'<code>graph=1&makeimage=1</code>' Parameters &rArr; '<code>view=graphimg</code>'</a></li>
<li><a href="#compat-content_type">'<code>content-type=text/vnd.viewcvs-markup</code>' and '<code>content-type=text/x-cvsweb-markup</code>' Parameters&rArr; '<code>view=markup</code>'
<li><a href="#compat-content_type">'<code>content_type=text/vnd.viewcvs-markup</code>' and '<code>content_type=text/x-cvsweb-markup</code>' Parameters&rArr; '<code>view=markup</code>'
<li><a href="#compat-attic">'<code>Attic/FILE</code>' Paths &rArr; '<code>FILE</code>'</a></li>
</ul>
</div>
@@ -284,6 +284,11 @@ th.caption {
<td>depends</td>
<td><a href="#view-param"><code>view</code> parameter</a>, not needed if the <code>default_file_view</code> configuration variable is set to <code>co</code>, since that makes the checkout view the default view for file paths. Also not needed if the <code>/*checkout*</code> magic prefix or the <code>revision</code> parameter is present.
</tr>
<tr>
<td><code>content-type=<var>TYPE</var></code></td>
<td>optional</td>
<td>MIME type to send with checked out file, default is a guess based on file extension</td>
</tr>
<tr>
<td><code>revision=<var>REVISION</var></code></td>
<td>optional</td>
@@ -1228,8 +1233,8 @@ th.caption {
<h3 id="compat-makeimage">'<code>graph=1&amp;makeimage=1</code>' Parameters &rArr; '<code>view=graphimg</code>'</h3>
<p>A <code>graph=1&amp;makeimage=1</code> parameter is treated like a <code>view=graph</code> parameter. There is currently no redirect when it is encountered, but there could be one in the future.</p>
<h3 id="compat-content_type">'<code>content-type=text/vnd.viewcvs-markup</code>' and '<code>content-type=text/x-cvsweb-markup</code>' Parameters&rArr; '<code>view=markup</code>'; other values ignored</h3>
<p><code>content-type=text/vnd.viewcvs-markup</code> and <code>content-type=text/x-cvsweb-markup</code> parameters are treated like a <code>view=markup</code> parameter. There is currently no redirect when it is encountered, but there could be one in the future. Other values of the <code>content-type</code> parameter, which were used to dictate the MIME type of files displayed in the checkout/download view prior to ViewVC 1.0.6, are ignored.</p>
<h3 id="compat-content_type">'<code>content_type=text/vnd.viewcvs-markup</code>' and '<code>content_type=text/x-cvsweb-markup</code>' Parameters&rArr; '<code>view=markup</code>'</h3>
<p><code>content_type=text/vnd.viewcvs-markup</code> and <code>content_type=text/x-cvsweb-markup</code> parameters are treated like a <code>view=markup</code> parameter. There is currently no redirect when it is encountered, but there could be one in the future.</p>
<h3 id="compat-attic">'<code>Attic/FILE</code>' Paths &rArr; '<code>FILE</code>'</h3>
<p>When ViewVC encounters an invalid repository path whose last or second-to-last component is named <code>Attic</code>, and stripping the component yields a valid path, it will redirect to a URL with that path.</p>

View File

@@ -18,7 +18,7 @@
<a href="./download.html">Download</a> |
<a href="./upgrading.html">Upgrading</a> |
<a href="./contributing.html">Contributing</a> |
<a href="http://viewvc.tigris.org/nonav/source/browse/*checkout*/viewvc/trunk/LICENSE.html">License</a> |
<a href="./license-1.html">License</a> |
<a href="./contact.html">Contact</a> |
<a href="./who.html">About</a>
</p>

70
www/index.html Normal file
View File

@@ -0,0 +1,70 @@
<html>
<head>
<title>ViewVC - Version Control Repository Browser</title>
<!-- Custom stylations to hide the obnoxious project info -->
<style type="text/css">
#projecthome .axial { display: none; }
#apphead h1 { display: none; }
#longdescription { border: none; }
#longdescription h2 { display: none; }
#customcontent h2 { display: block; }
</style>
<!-- End custom stylations -->
</head>
<body>
<div class="app" id="customcontent">
<h1>ViewVC &mdash; Web-based Version Control Repository Browsing</h1>
<div class="h2">
<h2>Latest Release</h2>
<p>The most recent release of ViewVC is: <strong>1.0.0-rc1</strong></p>
</div>
<div class="h2">
<h2>What Is ViewVC?</h2>
<p>ViewVC is a browser interface for CVS and Subversion version
control repositories. It generates templatized HTML to present
navigable directory, revision, and change log listings. It can
display specific versions of files as well as diffs between those
versions. Basically, ViewVC provides the bulk of the report-like
functionality you expect out of your version control tool, but much
more prettily than the average textual command-line program
output.</p>
<p>Here are some of the additional features of ViewVC:</p>
<ul>
<li>Support for filesystem-accessible CVS and Subversion repositories</li>
<li>Individually configurable virtual host support</li>
<li>Line-based annotation/blame display</li>
<li>Revision graph capabilities (<em>CVS only</em>)</li>
<li>Syntax highlighting support</li>
<li>Commit metadata query facilities</li>
<li>Template-driven output generation</li>
<li>Colorized, side-by-side differences</li>
<li>Tarball generation (by tag/branch for CVS, by revision for
Subversion)</li>
<li>Localization support based on the Accept-Language request header</li>
<li>Ability to run either as CGI script or as a standalone
server</li>
<li>Regexp-based file searching</li>
<li>INI-like configuration file (as opposed to requiring actual code
tweaks)</li>
</ul>
<p>For a complete list of changes present in each release, see
ViewVC's <a
href="http://viewvc.tigris.org/source/browse/viewvc/trunk/CHANGES?rev=HEAD"
>CHANGES</a> file.</p>
</div>
</div>
</body>
</html>

30
www/project_tools.html Normal file
View File

@@ -0,0 +1,30 @@
<!-- Overrides the left-nav tool bar on viewvc.tigris.org, a feature
specific to tigris.org's CEE branding. See www/overrides/ in the
look.tigris.org project for details. -->
<!-- dd --><ul>
<li><a href="http://www.viewvc.org/"
>Project website</a></li>
<!-- #################################################### --></ul></dd><dd><ul>
<li><a href="http://viewvc.tigris.org/servlets/ProjectMemberList"
>Membership</a></li>
<!-- #################################################### --></ul></dd><dd><ul>
<li><a href="http://viewvc.tigris.org/servlets/ProjectNewsList"
>Announcements</a></li>
<!-- li><a href="http://viewvc.tigris.org/servlets/ProjectForumView"
>Discussion forums</a></li -->
<li><a href="http://viewvc.tigris.org/servlets/ProjectMailingListList"
>Mailing lists</a></li>
<li><a href="http://viewvc.tigris.org/servlets/ProjectDocumentList"
>Documents &amp; files</a></li>
<li><a href="http://viewvc.tigris.org/source/browse/viewvc/"
>Subversion</a></li>
<li><a href="http://viewvc.tigris.org/issues/buglist.cgi?component=viewvc&amp;issue_status=UNCONFIRMED&amp;issue_status=NEW&amp;issue_status=STARTED&amp;issue_status=REOPENED"
>Issue tracker</a></li>
</ul><!-- /dd -->