Move all the code that manipulates CVS revision numbers out of viewcvs.py

and into the bincvs module, unfortunately rewriting most of it in the
process. I thought I finished this before, but some code in view_markup and
augment_entry slipped below my radar.

The main difference between the new code and the old code is that the old
code used string representations of revision numbers and manipulated them
with string operations and regular expressions while the new code converts
all revision numbers into tuples of integers and manipulates them in that
form.

This change also restores some minor ViewCVS 0.9.2 behaviors that I
inadvertently changed before:

- "Branch" fields on log pages list "MAIN" tags instead of skipping them
- The "Default branch" field on the top of log pages lists all names for a
  default branch instead of picking one randomly
- Viewing a directory with "only_with_tag=HEAD" shows default branch
  revisions, not just trunk revisions

* lib/vclib/bincvs/__init__.py
  (LogEntry, LogHeader, LogError, TagInfo):
    removed classes

  (Revision):
    new class, replaces LogEntry

  (Tag):
    new class, replaces TagInfo

  (match_rev_tags, add_tag, remove_tag):
    new functions for manipulating revisions and tags

  (_revision_tuple, _tag_tuple, _dict_list_add):
    new internal functions

  (parse_log_header):
    return multiple values directly instead of packing them into a
    LogHeader object

  (parse_log_entry, _sort_tags, BinCVSRepository.openfile):
    use Revision and Tag classes instead of LogEntry and TagInfo classes

  (get_logs):
    fix handling of HEAD tag

  (fetch_log):
    stop returning "head" field from rlog, it's not useful for anything

* lib/viewcvs.py
  (prep_tags, logsort_date_cmp, logsort_rev_cmp)
    tweaked to work with bincvs.Tag and bincvs.Revision objects

  (find_first_rev):
    removed

  (view_markup, augment_entry):
    change to deal with Revision and Tag objects, remove some revision
    number computation

  (read_log):
    don't do revision/tag matching here, instead call match_revs_tags
    update filtering code and some comments

  (view_log_cvs):
    fix default branch code to display all known branch names

* lib/rlog.py
  (GetRLogData):
    update for changed bincvs.fetch_log


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@786 8cb11bc2-c004-0410-86c3-e597b4017df7
remotes/tags/1.0.0-rc1
rey4 2004-01-21 00:50:17 +00:00
parent b69d1b42fe
commit e5588cbba1
3 changed files with 353 additions and 320 deletions

View File

@ -92,9 +92,7 @@ def _get_co_file(v_file):
def GetRLogData(cfg, path, revision=''):
v_file = _get_v_file(path)
head, branch, taginfo, revs = bincvs.fetch_log(cfg.general,
v_file,
revision)
branch, taginfo, revs = bincvs.fetch_log(cfg.general, v_file, revision)
class _blank:
pass
@ -136,7 +134,7 @@ def GetRLogData(cfg, path, revision=''):
else:
new_entry.description = entry.log
new_entry.revision = entry.rev
new_entry.revision = entry.string
new_entry.author = entry.author
new_entry.time = entry.date

View File

@ -30,6 +30,197 @@ import time
import compat
import popen
class Revision:
def __init__(self, revstr, date, author, state, changed, log):
self.number = _revision_tuple(revstr)
self.string = revstr
self.date = date
self.author = author
self.state = state
self.changed = changed
self.log = log
def __cmp__(self, other):
return cmp(self.number, other.number)
class Tag:
def __init__(self, name, revstr):
self.name = name
self.number = _tag_tuple(revstr)
self.is_branch = len(self.number) % 2 == 1 or not self.number
def match_revs_tags(revlist, taglist):
"""Match up a list of Revision objects with a list of Tag objects
Sets the following properties on each Revision in revlist:
"tags"
list of non-branch tags which refer to this revision
example: if revision is 1.2.3.4, tags is a list of all 1.2.3.4 tags
"branches"
list of branch tags which refer to this revision's branch
example: if revision is 1.2.3.4, branches is a list of all 1.2.3 tags
"branch_points"
list of branch tags which branch off of this revision
example: if revision is 1.2, it's a list of tags like 1.2.3 and 1.2.4
"prev"
reference to the previous revision, possibly None
example: if revision is 1.2.3.4, prev is 1.2.3.3
"next"
reference to next revision, possibly None
example: if revision is 1.2.3.4, next is 1.2.3.5
"parent"
reference to revision this one branches off of, possibly None
example: if revision is 1.2.3.4, parent is 1.2
"branch_number"
tuple representing branch number or empty tuple if on trunk
example: if revision is 1.2.3.4, branch_number is (1, 2, 3)
Each tag in taglist gets these properties set:
"co_rev"
reference to revision that would be retrieved if tag were checked out
"branch_rev"
reference to revision branched off of, only set for branch tags
example: if tag is 1.2.3, branch_rev points to 1.2 revision
"aliases"
list of tags that have the same number
This function assumes it will be passed a complete, feasible sequence of
revisions. If an invalid sequence is passed it will return garbage or throw
exceptions.
"""
# map of branch numbers to lists of corresponding branch Tags
branch_dict = {}
# map of revision numbers to lists of non-branch Tags
tag_dict = {}
# map of revision numbers to lists of branch Tags
branch_point_dict = {}
# toss tags into "branch_dict", "tag_dict", and "branch_point_dict"
# set "aliases" property and default "co_rev" and "branch_rev" values
for tag in taglist:
tag.co_rev = None
if tag.is_branch:
tag.branch_rev = None
_dict_list_add(branch_point_dict, tag.number[:-1], tag)
tag.aliases = _dict_list_add(branch_dict, tag.number, tag)
else:
tag.aliases = _dict_list_add(tag_dict, tag.number, tag)
# sort the revisions so the loop below can work properly
revlist.sort()
# array of the most recently encountered revision objects indexed by depth
history = []
# loop through revisions, setting properties and storing state in "history"
for rev in revlist:
depth = len(rev.number) / 2 - 1
# set "prev" and "next" properties
rev.prev = rev.next = None
if depth < len(history):
prev = history[depth]
if depth == 0 or rev.number[:-1] == prev.number[:-1]:
rev.prev = prev
prev.next = rev
# set "parent"
if depth > 0:
assert history[depth-1].number == rev.number[:-2]
rev.parent = history[depth-1]
else:
rev.parent = None
# set "tags" and "branch_points"
rev.tags = tag_dict.get(rev.number, [])
rev.branch_points = branch_point_dict.get(rev.number, [])
# set "branches" and "branch_number"
if rev.prev:
rev.branches = rev.prev.branches
rev.branch_number = rev.prev.branch_number
else:
rev.branch_number = rev.parent and rev.number[:-1] or ()
try:
rev.branches = branch_dict[rev.branch_number]
except KeyError:
rev.branches = []
# set "co_rev" and "branch_rev"
for tag in rev.tags:
tag.co_rev = rev
for tag in rev.branch_points:
tag.co_rev = rev
tag.branch_rev = rev
# This loop only needs to be run for revisions at the heads of branches,
# but for the simplicity's sake, it actually runs for every revision on
# a branch. The later revisions overwrite values set by the earlier ones.
for branch in rev.branches:
branch.co_rev = rev
# end of outer loop, store most recent revision in "history" array
if depth < len(history):
history[depth] = rev
else:
assert depth == len(history)
history.append(rev)
def add_tag(tag_name, revision):
"""Create a new tag object and associate it with a revision"""
tag = Tag(tag_name, revision.string)
revision.tags.append(tag)
tag.co_rev = revision
tag.aliases = revision.tags
return tag
def remove_tag(tag):
"""Remove a tag's associations"""
tag.aliases.remove(tag)
if tag.is_branch and tag.branch_rev:
tag.branch_rev.branch_points.remove(tag)
def _revision_tuple(revision_string):
"""convert a revision number into a tuple of integers"""
t = tuple(map(int, string.split(revision_string, '.')))
if len(t) % 2 == 0:
return t
raise ValueError
def _tag_tuple(revision_string):
"""convert a revision number or branch number into a tuple of integers"""
if revision_string:
t = map(int, string.split(revision_string, '.'))
l = len(t)
if l == 1:
raise ValueError
if l > 2 and t[-2] == 0 and l % 2 == 0:
del t[-2]
return tuple(t)
return ()
def _dict_list_add(dict, idx, elem):
try:
list = dict[idx]
except KeyError:
list = dict[idx] = [elem]
else:
list.append(elem)
return list
# if your rlog doesn't use 77 '=' characters, then this must change
LOG_END_MARKER = '=' * 77 + '\n'
ENTRY_END_MARKER = '-' * 28 + '\n'
@ -40,94 +231,6 @@ _EOF_ERROR = 'error message found' # rlog issued an error
_FILE_HAD_ERROR = 'could not read file'
class LogHeader:
"Hold state from the header portion of an 'rlog' output."
def __init__(self, filename, head=None, branch=None, taginfo=None):
self.filename = filename
self.head = head
self.branch = branch
self.taginfo = taginfo
class LogEntry:
"Hold state for each revision entry in an 'rlog' output."
def __init__(self, rev, date, author, state, changed, log):
self.rev = rev
self.date = date
self.author = author
self.state = state
self.changed = changed
self.log = log
class LogError:
"Represent an entry that had an (unknown) error."
pass
# match a revision number
_re_revision = re.compile(r'^\d+\.\d+(?:\.\d+\.\d+)*$')
# match a branch number with optional 0
_re_branch = re.compile(r'^(?P<base>(?:\d+\.\d+)(?:\.\d+\.\d+)*)'
r'(?:\.0)?\.(?P<branch>\d+)$')
class TagInfo:
def __init__(self, number):
if number == '': # number has special value used to refer to the trunk
self._rev = number
self._branch = ''
return
match = _re_branch.match(number)
if match: # number refers to a branch
self._rev = match.group('base')
self._branch = self._rev + '.' + match.group('branch')
return
match = _re_revision.match(number)
if match: # number refers to a revision
self._rev = number
self._branch = ''
return
raise vclib.InvalidRevision(number)
def is_trunk(self):
"true if this is a trunk tag (i.e. MAIN when the file has no default branch)"
return not self._rev
def is_branch(self):
"true if this is a branch tag"
return not self._rev or self._branch
def branches_at(self):
"return revision number that this branch branches off of"
return self._branch and self._rev or None
def matches_rev(self, number):
"true if the tag either specifies or branches off the revision"
return number == self._rev
def holds_rev(self, number):
"true if specified revision number is on this branch"
if self._rev:
if self._branch: # tag refers to branch
p = string.rfind(number, '.')
if p < 0:
raise vclib.InvalidRevision(number)
return number[:p] == self._branch
else: # tag refers to a revision
return 0
else: # tag refers to the trunk
return string.count(number, '.') == 1
def number(self):
return self._branch or self._rev
_re_lineno = re.compile(r'\:\d+$')
def parse_log_header(fp):
@ -141,6 +244,8 @@ def parse_log_header(fp):
If there is no revision information (e.g. the "-h" switch was passed to
rlog), then fp will consumed the file separator line on exit.
Returns: filename, default branch, tag dictionary, and eof flag
"""
filename = head = branch = ""
taginfo = { } # tag name => number
@ -197,7 +302,7 @@ def parse_log_header(fp):
# looks like a filename
filename = line[6:idx]
return LogHeader(filename), _EOF_ERROR
return filename, branch, taginfo, _EOF_ERROR
elif line[-28:] == ": No such file or directory\n":
# For some reason the windows version of rlog omits the "rlog: "
# prefix for first error message when the standard error stream
@ -206,10 +311,10 @@ def parse_log_header(fp):
# This is just a special case to prevent an especially common
# error message from being lost when this happens
filename = line[:-28]
return LogHeader(filename), _EOF_ERROR
return filename, branch, taginfo, _EOF_ERROR
# dunno what this is
return LogHeader(filename, head, branch, taginfo), eof
return filename, branch, taginfo, eof
_re_log_info = re.compile(r'^date:\s+([^;]+);'
r'\s+author:\s+([^;]+);'
@ -225,8 +330,7 @@ def parse_log_entry(fp):
On exit, fp will have consumed the log separator line (dashes) or the
end-of-file marker (equals).
Returns: revision, date (time_t secs), author, state, lines changed,
the log text, and eof flag (see _EOF_*)
Returns: Revision object and eof flag (see _EOF_*)
"""
rev = None
line = fp.readline()
@ -274,7 +378,7 @@ def parse_log_entry(fp):
tm = compat.cvs_strptime(match.group(1))
date = compat.timegm(tm)
return LogEntry(rev, date,
return Revision(rev, date,
# author, state, lines changed
match.group(2), match.group(3), match.group(5),
log), eof
@ -314,14 +418,13 @@ def _sort_tags(alltags):
plain_tags = []
for tag in alltagnames:
rev = alltags[tag]
if TagInfo(rev).is_branch():
if Tag(None, rev).is_branch:
branch_tags.append(tag)
else:
plain_tags.append(tag)
return branch_tags, plain_tags
def get_logs(repos, path_parts, entries, view_tag, get_dirs=0):
have_logs = 0
alltags = { # all the tags seen in the files of this dir
'MAIN' : '',
'HEAD' : '1.1'
@ -372,7 +475,7 @@ def get_logs(repos, path_parts, entries, view_tag, get_dirs=0):
# consume each file found in the resulting log
for file in chunk:
header, eof = parse_log_header(rlog)
filename, default_branch, taginfo, eof = parse_log_header(rlog)
if eof == _EOF_LOG:
# the rlog output ended early. this happens on errors that rlog thinks
@ -394,12 +497,12 @@ def get_logs(repos, path_parts, entries, view_tag, get_dirs=0):
raise vclib.Error('Rlog output ended early. Expected RCS file "%s"'
% file.path)
# check path_ends_in instead of file == header.filename because of
# check path_ends_in instead of file.path == filename because of
# cvsnt's rlog, which only outputs the base filename
# http://www.cvsnt.org/cgi-bin/bugzilla/show_bug.cgi?id=188
if not (header.filename and path_ends_in(file.path, header.filename)):
if not (filename and path_ends_in(file.path, filename)):
raise vclib.Error('Error parsing rlog output. Expected RCS file "%s"'
', found "%s"' % (file.path, header.filename))
', found "%s"' % (file.path, filename))
# an error was found regarding this file
if eof == _EOF_ERROR:
@ -411,24 +514,22 @@ def get_logs(repos, path_parts, entries, view_tag, get_dirs=0):
if eof:
continue
if view_tag == 'MAIN':
view_tag_info = TagInfo(header.branch)
elif view_tag == 'HEAD':
view_tag_info = TagInfo(header.head)
elif header.taginfo.has_key(view_tag):
view_tag_info = TagInfo(header.taginfo[view_tag])
if view_tag == 'MAIN' or view_tag == 'HEAD':
tag = Tag(None, default_branch)
elif taginfo.has_key(view_tag):
tag = Tag(None, taginfo[view_tag])
elif view_tag:
# the tag wasn't found, so skip this file
skip_file(rlog)
continue
else:
view_tag_info = None
tag = None
# we don't care about the specific values -- just the keys and whether
# the values point to branches or revisions. this the fastest way to
# merge the set of keys and keep values that allow us to make the
# distinction between branch tags and normal tags
alltags.update(header.taginfo)
alltags.update(taginfo)
# read all of the log entries until we find the revision we want
wanted_entry = None
@ -441,19 +542,18 @@ def get_logs(repos, path_parts, entries, view_tag, get_dirs=0):
# parsing error
break
rev = entry.rev
# A perfect match is a revision on the branch being viewed or
# a revision having the tag being viewed or any revision
# when nothing is being viewed. When there's a perfect match
# we set the wanted_entry value and break out of the loop.
# An imperfect match is a revision at the branch point of the
# An imperfect match is a revision at the branch point of a
# branch being viewed. When there's an imperfect match we
# also set the wanted_entry value but keep looping in case
# something better comes along.
perfect = not view_tag_info or view_tag_info.holds_rev(rev)
if perfect or view_tag_info.matches_rev(rev):
perfect = perfect or not view_tag_info.is_branch()
perfect = not tag or entry.number == tag.number or \
(len(entry.number) == 2 and not tag.number) or \
entry.number[:-1] == tag.number
if perfect or entry.number[-2:] == tag.number[:-1]:
wanted_entry = entry
if perfect:
break
@ -464,8 +564,7 @@ def get_logs(repos, path_parts, entries, view_tag, get_dirs=0):
break
if wanted_entry:
have_logs = 1
file.rev = wanted_entry.rev
file.rev = wanted_entry.string
file.date = wanted_entry.date
file.author = wanted_entry.author
file.state = wanted_entry.state
@ -484,14 +583,11 @@ def fetch_log(rcs_paths, full_name, which_rev=None):
args = (full_name,)
rlog = rcs_popen(rcs_paths, 'rlog', args, 'rt', 0)
header, eof = parse_log_header(rlog)
head = header.head
branch = header.branch
taginfo = header.taginfo
filename, branch, taginfo, eof = parse_log_header(rlog)
if eof:
# no log entries or a parsing failure
return head, branch, taginfo, [ ]
return branch, taginfo, [ ]
revs = [ ]
while 1:
@ -502,7 +598,7 @@ def fetch_log(rcs_paths, full_name, which_rev=None):
if eof:
break
return head, branch, taginfo, revs
return branch, taginfo, revs
if sys.platform == "win32":
@ -596,7 +692,7 @@ class BinCVSRepository(vclib.Repository):
raise vclib.ItemNotFound(path_parts)
def openfile(self, path_parts, rev=None):
if not rev or rev == 'HEAD':
if not rev or rev == 'HEAD' or rev == 'MAIN':
rev_flag = '-p'
else:
rev_flag = '-p' + rev
@ -624,15 +720,13 @@ class BinCVSRepository(vclib.Repository):
# As a workaround, we invoke rlog to find the first non-dead revision
# that precedes it and check out that revision instead
rlog = rcs_popen(self.rcs_paths, 'rlog', (full_name,), 'rt', 0)
header, eof = parse_log_header(rlog)
filename, default_branch, taginfo, eof = parse_log_header(rlog)
# interpret rev parameter using header information
header.taginfo['HEAD'] = header.head
header.taginfo['MAIN'] = header.branch
t = TagInfo(header.taginfo.get(rev, rev))
is_branch = t.is_branch()
t = t.number()
revtuple = t and map(int, string.split(t, '.')) or []
taginfo['HEAD'] = taginfo['MAIN'] = default_branch
t = Tag(None, taginfo.get(rev, rev))
is_branch = t.is_branch
revtuple = t.number
# build up list containing revtuple and all non-dead revisions
revs = [revtuple]
@ -640,7 +734,7 @@ class BinCVSRepository(vclib.Repository):
entry, eof = parse_log_entry(rlog)
if entry and entry.state == "Exp":
# valid revision info
revs.append(map(int, string.split(entry.rev, '.')))
revs.append(entry.number)
# sort the list of revision numbers in descending lexicographic order
revs.sort()
@ -658,7 +752,7 @@ class BinCVSRepository(vclib.Repository):
elif is_branch:
if len(corev) == len(revtuple)+1 and revtuple == corev[:-1]:
break
if len(corev) == len(revtuple)+2 and revtuple == []:
if len(corev) == len(revtuple)+2 and revtuple == ():
break
else:
corev = None

View File

@ -621,7 +621,7 @@ def prep_tags(request, tags):
links = [ ]
for tag in tags:
links.append(_item(name=tag, href=url+tag))
links.append(_item(name=tag.name, href=url+tag.name))
return links
def is_viewable_image(mime_type):
@ -970,18 +970,14 @@ def view_markup(request):
if cfg.options.show_log_in_markup:
if request.roottype == 'cvs':
show_revs, rev_map, rev_order, taginfo, rev2tag, \
cur_branch, branch_points, branch_names = read_log(full_name)
entry = rev_map[revision]
idx = string.rfind(revision, '.')
branch = revision[:idx]
entry.date_str = make_time_string(entry.date)
revs, taginfo = read_log(full_name, revision, None)
entry = revs[-1]
branch = entry.branch_number
data.update({
'roottype' : 'cvs',
'date_str' : entry.date_str,
'date_str' : make_time_string(entry.date),
'ago' : html_time(request, entry.date, 1),
'author' : entry.author,
'branches' : None,
@ -991,26 +987,15 @@ def view_markup(request):
'log' : htmlify(entry.log),
'size' : None,
'state' : entry.state,
'vendor_branch' : ezt.boolean(_re_is_vendor_branch.match(revision)),
'vendor_branch' : ezt.boolean(branch and branch[2] % 2 == 1),
'branches' : string.join(map(lambda x: x.name, entry.branches), ', '),
'tags' : string.join(map(lambda x: x.name, entry.tags), ', '),
'branch_points': string.join(map(lambda x: x.name,
entry.branch_points), ', ')
})
if rev2tag.has_key(branch):
data['branches'] = string.join(rev2tag[branch], ', ')
if rev2tag.has_key(revision):
data['tags'] = string.join(rev2tag[revision], ', ')
if branch_points.has_key(revision):
data['branch_points'] = string.join(branch_points[revision], ', ')
prev_rev = string.split(revision, '.')
while 1:
if prev_rev[-1] == '0': # .0 can be caused by 'commit -r X.Y.Z.0'
prev_rev = prev_rev[:-2] # X.Y.Z.0 becomes X.Y.Z
else:
prev_rev[-1] = str(int(prev_rev[-1]) - 1)
prev = string.join(prev_rev, '.')
if rev_map.has_key(prev) or prev == '':
break
data['prev'] = prev
prev = entry.prev or entry.parent
data['prev'] = prev and prev.string
elif request.roottype == 'svn':
alltags, logs = vclib.svn.fetch_log(request.repos, where)
this_rev = int(revision)
@ -1395,93 +1380,74 @@ def paging(data, key, pagestart, local_name):
def logsort_date_cmp(rev1, rev2):
# sort on date; secondary on revision number
return -cmp(rev1.date, rev2.date) or -revcmp(rev1.rev, rev2.rev)
return -cmp(rev1.date, rev2.date) or -cmp(rev1.number, rev2.number)
def logsort_rev_cmp(rev1, rev2):
# sort highest revision first
return -revcmp(rev1.rev, rev2.rev)
return -cmp(rev1.number, rev2.number)
def find_first_rev(taginfo, revs):
"Find first revision that matches a normal tag or is on a branch tag"
for rev in revs:
if taginfo.matches_rev(rev) or taginfo.holds_rev(rev):
return rev
return None
def read_log(full_name, filter, logsort):
# Retrieve log info
cur_branch, taginfo, revs = bincvs.fetch_log(cfg.general, full_name)
def read_log(full_name, which_rev=None, view_tag=None, logsort='cvs'):
head, cur_branch, taginfo, revs = bincvs.fetch_log(cfg.general,
full_name, which_rev)
# Add artificial ViewCVS tag MAIN. If the file has a default branch, then
# MAIN acts like a branch tag pointing to that branch. Otherwise MAIN acts
# like a branch tag that points to the trunk. (Note: A default branch is
# just a branch number specified in an RCS file that tells CVS and RCS
# what branch to use for checkout and update operations by default, when
# there's no revision argument or sticky branch to override it. Default
# branches get set by "cvs import" to point to newly created vendor
# branches. Sometimes they are also set manually with "cvs admin -b")
taginfo['MAIN'] = cur_branch
rev_order = map(lambda entry: entry.rev, revs)
rev_order.sort(revcmp)
rev_order.reverse()
# Create tag objects
for name, num in taginfo.items():
taginfo[name] = bincvs.Tag(name, num)
tags = taginfo.values()
# HEAD is an artificial tag which is simply the highest tag number on the
# main branch, unless there is a branch tag in the RCS file in which case
# it's the highest revision on that branch. Find it by looking through
# rev_order; it is the first commit listed on the appropriate branch.
# This is not neccesary the same revision as marked as head in the RCS file.
### Why are we defining our own HEAD instead of just using the revision
### marked as head? What's wrong with: taginfo['HEAD'] = bincvs.TagInfo(head)
taginfo['MAIN'] = bincvs.TagInfo(cur_branch)
taginfo['HEAD'] = find_first_rev(taginfo['MAIN'], rev_order)
# map revision numbers to tag names
rev2tag = { }
# names of symbols at each branch point
branch_points = { }
branch_names = [ ]
# Now that we know all of the revision numbers, we can associate
# absolute revision numbers with all of the symbolic names, and
# pass them to the form so that the same association doesn't have
# to be built then.
items = taginfo.items()
items.sort()
items.reverse()
for name, tag in items:
if not isinstance(tag, bincvs.TagInfo):
taginfo[name] = tag = bincvs.TagInfo(tag)
number = tag.number()
if tag.is_branch():
branch_names.append(name)
if number == cur_branch:
default_branch = name
if not tag.is_trunk():
rev = tag.branches_at()
if branch_points.has_key(rev):
branch_points[rev].append(name)
else:
branch_points[rev] = [ name ]
# revision number you'd get if you checked out this branch
tag.co_rev = find_first_rev(tag, rev_order)
# Set view_tag to a Tag object in order to filter results. We can filter by
# revision number or branch number
if filter:
try:
view_tag = bincvs.Tag(None, filter)
except ValueError:
view_tag = None
else:
tag.co_rev = number
tags.append(view_tag)
if rev2tag.has_key(number):
rev2tag[number].append(name)
else:
rev2tag[number] = [ name ]
# Match up tags and revisions
bincvs.match_revs_tags(revs, tags)
if view_tag:
tag = taginfo.get(view_tag)
if not tag:
raise debug.ViewcvsException('Tag %s not defined.' % view_tag,
'404 Tag Not Found')
# Add artificial ViewCVS tag HEAD, which acts like a non-branch tag pointing
# at the latest revision on the MAIN branch. The HEAD revision doesn't have
# anything to do with the "head" revision number specified in the RCS file
# and in rlog output. HEAD refers to the revision that the CVS and RCS co
# commands will check out by default, whereas the "head" field just refers
# to the highest revision on the trunk.
taginfo['HEAD'] = bincvs.add_tag('HEAD', taginfo['MAIN'].co_rev)
# Determine what revisions to return
if filter:
# If view_tag isn't set, it means filter is not a valid revision or
# branch number. Check taginfo to see if filter is set to a valid tag
# name. If so, filter by that tag, otherwise raise an error.
if not view_tag:
try:
view_tag = taginfo[filter]
except KeyError:
raise debug.ViewcvsException('Invalid tag or revision number "%s"'
% filter)
show_revs = [ ]
for entry in revs:
rev = entry.rev
if tag.matches_rev(rev) or tag.holds_rev(rev):
show_revs.append(entry)
if view_tag.is_branch:
for rev in revs:
if rev.branch_number == view_tag.number or rev is view_tag.branch_rev:
show_revs.append(rev)
elif view_tag.co_rev:
show_revs.append(view_tag.co_rev)
# get rid of the view_tag if it was only created for filtering
if view_tag.name is None:
bincvs.remove_tag(view_tag)
else:
show_revs = revs
@ -1493,110 +1459,69 @@ def read_log(full_name, which_rev=None, view_tag=None, logsort='cvs'):
# no sorting
pass
# build a map of revision number to entry information
rev_map = { }
for entry in revs:
rev_map[entry.rev] = entry
return show_revs, taginfo
### some of this return stuff doesn't make a lot of sense...
return show_revs, rev_map, rev_order, taginfo, rev2tag, \
default_branch, branch_points, branch_names
_re_is_vendor_branch = re.compile(r'^1\.1\.1\.\d+$')
def augment_entry(entry, request, rev_map, rev2tag, branch_points,
rev_order, extended, name_printed):
def augment_entry(entry, rev, request, name_printed, extended):
"Augment the entry with additional, computed data from the log output."
query_dict = request.query_dict
rev = entry.rev
idx = string.rfind(rev, '.')
branch = rev[:idx]
branch = rev.branch_number
entry.vendor_branch = ezt.boolean(_re_is_vendor_branch.match(rev))
entry.vendor_branch = ezt.boolean(branch and branch[2] % 2 == 1)
entry.date_str = make_time_string(entry.date)
entry.date_str = make_time_string(rev.date)
entry.ago = html_time(request, entry.date, 1)
entry.ago = html_time(request, rev.date, 1)
entry.branches = prep_tags(request, rev2tag.get(branch, [ ]))
entry.tags = prep_tags(request, rev2tag.get(rev, [ ]))
entry.branch_points = prep_tags(request, branch_points.get(rev, [ ]))
entry.branches = prep_tags(request, rev.branches)
entry.tags = prep_tags(request, rev.tags)
entry.branch_points = prep_tags(request, rev.branch_points)
prev_rev = string.split(rev, '.')
while 1:
if prev_rev[-1] == '0': # .0 can be caused by 'commit -r X.Y.Z.0'
prev_rev = prev_rev[:-2] # X.Y.Z.0 becomes X.Y.Z
else:
prev_rev[-1] = str(int(prev_rev[-1]) - 1)
prev = string.join(prev_rev, '.')
if rev_map.has_key(prev) or prev == '':
break
entry.prev = prev
prev = rev.prev or rev.parent
entry.prev = prev and prev.string
### maybe just overwrite entry.log?
entry.html_log = htmlify(entry.log)
entry.html_log = htmlify(rev.log)
if extended:
entry.tag_names = rev2tag.get(rev, [ ])
if rev2tag.has_key(branch) and not name_printed.has_key(branch):
entry.branch_names = rev2tag.get(branch)
entry.tag_names = map(lambda x: x.name, rev.tags)
if branch and not name_printed.has_key(branch):
entry.branch_names = map(lambda x: x.name, rev.branches)
name_printed[branch] = 1
else:
entry.branch_names = [ ]
entry.href = request.get_url(view_func=view_checkout, params={'rev': rev})
entry.href = request.get_url(view_func=view_checkout,
params={'rev': rev.string})
entry.view_href = request.get_url(view_func=view_markup,
params={'rev': rev})
params={'rev': rev.string})
entry.text_href = request.get_url(view_func=view_checkout,
params={'content-type': 'text/plain',
'rev': rev})
'rev': rev.string})
entry.annotate_href = request.get_url(view_func=view_annotate,
params={'annotate': rev})
params={'annotate': rev.string})
# figure out some target revisions for performing diffs
entry.branch_point = None
entry.next_main = None
idx = string.rfind(branch, '.')
if idx != -1:
branch_point = branch[:idx]
if not entry.vendor_branch \
and branch_point != rev and branch_point != prev:
entry.branch_point = branch_point
if rev.parent and rev.parent is not prev and not entry.vendor_branch:
entry.branch_point = rev.parent.string
# if it's on a branch (and not a vendor branch), then diff against the
# next revision of the higher branch (e.g. change is committed and
# brought over to -stable)
if string.count(rev, '.') > 1 and not entry.vendor_branch:
# locate this rev in the ordered list of revisions
i = rev_order.index(rev)
# create a rev that can be compared component-wise
c_rev = string.split(rev, '.')
while i:
next = rev_order[i - 1]
c_work = string.split(next, '.')
if len(c_work) < len(c_rev):
# found something not on the branch
entry.next_main = next
break
# this is a higher version on the same branch; the lower one (rev)
if rev.parent and rev.parent.next and not entry.vendor_branch:
if not rev.next:
# this is the highest version on the branch; a lower one
# shouldn't have a diff against the "next main branch"
if c_work[:-1] == c_rev[:len(c_work) - 1]:
break
i = i - 1
entry.next_main = rev.parent.next.string
# the template could do all these comparisons itself, but let's help
# it out.
r1 = query_dict.get('r1')
if r1 and r1 != rev and r1 != prev and r1 != entry.branch_point \
if r1 and r1 != entry.rev and r1 != entry.prev and r1 != entry.branch_point \
and r1 != entry.next_main:
entry.to_selected = 'yes'
else:
@ -1747,9 +1672,7 @@ def view_log_cvs(request, data, logsort):
view_tag = query_dict.get('only_with_tag')
show_revs, rev_map, rev_order, taginfo, rev2tag, \
cur_branch, branch_points, branch_names = \
read_log(full_name, None, view_tag, logsort)
show_revs, taginfo = read_log(full_name, view_tag, logsort)
up_where = get_up_path(request, where, int(query_dict.get('hideattic',
cfg.options.hide_attic)))
@ -1761,15 +1684,23 @@ def view_log_cvs(request, data, logsort):
where=up_where, params={}),
'filename' : filename,
'view_tag' : view_tag,
'entries' : show_revs, ### rename the show_rev local to entries?
})
if cfg.options.use_cvsgraph:
data['graph_href'] = request.get_url(view_func=view_cvsgraph, params={})
if cur_branch:
data['branch'] = cur_branch
main = taginfo.get('MAIN')
if main:
# Default branch may have multiple names so we list them
branches = []
for branch in main.aliases:
# Don't list MAIN unless there are no other names
if branch is not main or len(main.aliases) == 1:
branches.append(branch.name)
### this formatting should be moved into the ezt template
data['branch'] = string.join(branches, ', ')
### I don't like this URL construction stuff. the value
### for head_abs_href vs head_href is a bit bogus: why decide to
@ -1782,11 +1713,17 @@ def view_log_cvs(request, data, logsort):
else:
data['head_href'] = request.get_url(view_func=view_checkout, params={})
data['entries'] = entries = [ ]
name_printed = { }
for entry in show_revs:
for rev in show_revs:
entry = _item(rev = rev.string,
state = rev.state,
author = rev.author,
changed = rev.changed)
# augment the entry with (extended=1) info.
augment_entry(entry, request, rev_map, rev2tag, branch_points,
rev_order, 1, name_printed)
augment_entry(entry, rev, request, name_printed, 1)
entries.append(entry)
tagitems = taginfo.items()
tagitems.sort()
@ -1795,20 +1732,24 @@ def view_log_cvs(request, data, logsort):
data['tags'] = tags = [ ]
for tag, rev in tagitems:
if rev.co_rev:
tags.append(_item(rev=rev.co_rev, name=tag))
tags.append(_item(rev=rev.co_rev.string, name=tag))
if query_dict.has_key('r1'):
diff_rev = query_dict['r1']
else:
diff_rev = show_revs[-1].rev
diff_rev = show_revs[-1].string
data['tr1'] = diff_rev
if query_dict.has_key('r2'):
diff_rev = query_dict['r2']
else:
diff_rev = show_revs[0].rev
diff_rev = show_revs[0].string
data['tr2'] = diff_rev
branch_names = []
for tag in taginfo.values():
if tag.is_branch:
branch_names.append(tag.name)
branch_names.sort()
branch_names.reverse()
data['branch_names'] = branch_names