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-e597b4017df7remotes/tags/1.0.0-rc1
parent
b69d1b42fe
commit
e5588cbba1
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
309
lib/viewcvs.py
309
lib/viewcvs.py
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue