Skip to content

Commit

Permalink
porting vclib/ccvs into Python 3
Browse files Browse the repository at this point in the history
  * now ViewVC works for CVS on Python 3, if use_rcsparse is False
  * if use_rcsparse, it cannot work correctly for checkout non text file.
   (to fix it, make rcsparse to parse bytes stream. Currently it works
    for text stream only, for corner-cutting porting for Python 3)
  • Loading branch information
futatuki committed Mar 13, 2020
1 parent 8344fee commit eef060e
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 113 deletions.
4 changes: 2 additions & 2 deletions lib/vclib/ccvs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def find_root_in_parent(parent_path, rootname):
def CVSRepository(name, rootpath, authorizer, utilities, use_rcsparse):
rootpath = canonicalize_rootpath(rootpath)
if use_rcsparse:
import ccvs
from . import ccvs
return ccvs.CCVSRepository(name, rootpath, authorizer, utilities)
else:
import bincvs
from . import bincvs
return bincvs.BinCVSRepository(name, rootpath, authorizer, utilities)
84 changes: 59 additions & 25 deletions lib/vclib/ccvs/bincvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@
import re
import time
import calendar
import subprocess

# ViewVC libs
import popen
import vclib.ccvs

if sys.version_info[0] >= 3:
PY3 = True
import functools
# Python 3: workaround for cmp()
def cmp(a, b):
return (a > b) - (a < b)
else:
PY3 = False

def _path_join(path_parts):
return '/'.join(path_parts)

Expand Down Expand Up @@ -123,7 +132,7 @@ def listdir(self, path_parts, rev, options):
return data

def _getpath(self, path_parts):
return apply(os.path.join, (self.rootpath,) + tuple(path_parts))
return os.path.join(*((self.rootpath,) + tuple(path_parts)))

def _atticpath(self, path_parts):
return path_parts[:-1] + ['Attic'] + path_parts[-1:]
Expand Down Expand Up @@ -317,9 +326,15 @@ def itemlog(self, path_parts, rev, sortby, first, limit, options):

options['cvs_tags'] = tags
if sortby == vclib.SORTBY_DATE:
filtered_revs.sort(_logsort_date_cmp)
if PY3:
filtered_revs.sort(key=functools.cmp_to_key(_logsort_date_cmp))
else:
filtered_revs.sort(_logsort_date_cmp)
elif sortby == vclib.SORTBY_REV:
filtered_revs.sort(_logsort_rev_cmp)
if PY3:
filtered_revs.sort(key=functools.cmp_to_key(_logsort_rev_cmp))
else:
filtered_revs.sort(_logsort_rev_cmp)

if len(filtered_revs) < first:
return []
Expand All @@ -328,14 +343,21 @@ def itemlog(self, path_parts, rev, sortby, first, limit, options):
return filtered_revs

def rcs_popen(self, rcs_cmd, rcs_args, mode, capture_err=1):
# as we use this function as "r" mode only, we don't care stdin
# to communicate child process.
if self.utilities.cvsnt:
cmd = self.utilities.cvsnt
args = ['rcsfile', rcs_cmd]
args.extend(list(rcs_args))
else:
cmd = os.path.join(self.utilities.rcs_dir, rcs_cmd)
args = rcs_args
return popen.popen(cmd, args, mode, capture_err)
prc = subprocess.Popen([cmd] + list(args), bufsize = -1,
stdout=subprocess.PIPE,
stderr=(subprocess.STDOUT if capture_err else None),
universal_newlines=('t' in mode),
close_fds=(sys.platform != "win32"))
return prc.stdout

def annotate(self, path_parts, rev=None, include_text=False):
if self.itemtype(path_parts, rev) != vclib.FILE: # does auth-check
Expand Down Expand Up @@ -366,7 +388,7 @@ def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):

rcsfile = self.rcsfile(path_parts1, 1)
if path_parts1 != path_parts2:
raise NotImplementedError, "cannot diff across paths in cvs"
raise NotImplementedError("cannot diff across paths in cvs")
args.extend(['-r' + rev1, '-r' + rev2, rcsfile])

fp = self.rcs_popen('rcsdiff', args, 'rt')
Expand Down Expand Up @@ -487,7 +509,7 @@ def _match_revs_tags(revlist, taglist):

# loop through revisions, setting properties and storing state in "history"
for rev in revlist:
depth = len(rev.number) / 2 - 1
depth = len(rev.number) // 2 - 1

# set "prev" and "next" properties
rev.prev = rev.next = None
Expand Down Expand Up @@ -574,7 +596,7 @@ def _revision_tuple(revision_string):
def _tag_tuple(revision_string):
"""convert a revision number or branch number into a tuple of integers"""
if revision_string:
t = map(int, revision_string.split('.'))
t = [int(x) for x in revision_string.split('.')]
l = len(t)
if l == 1:
return ()
Expand Down Expand Up @@ -603,20 +625,29 @@ class COMissingRevision(vclib.Error):
pass

### suck up other warnings in _re_co_warning?
_re_co_filename = re.compile(r'^(.*),v\s+-->\s+(?:(?:standard output)|(?:stdout))\s*\n?$')
_re_co_warning = re.compile(r'^.*co: .*,v: warning: Unknown phrases like .*\n$')
_re_co_missing_rev = re.compile(r'^.*co: .*,v: revision.*absent\n$')
_re_co_side_branches = re.compile(r'^.*co: .*,v: no side branches present for [\d\.]+\n$')
_re_co_revision = re.compile(r'^revision\s+([\d\.]+)\s*\n$')
if PY3:
_re_co_filename = re.compile(br'^(.*),v\s+-->\s+(?:(?:standard output)|(?:stdout))\s*\n?$')
_re_co_warning = re.compile(br'^.*co: .*,v: warning: Unknown phrases like .*\n$')
_re_co_missing_rev = re.compile(br'^.*co: .*,v: revision.*absent\n$')
_re_co_side_branches = re.compile(br'^.*co: .*,v: no side branches present for [\d\.]+\n$')
_re_co_revision = re.compile(br'^revision\s+([\d\.]+)\s*\n$')
else:
_re_co_filename = re.compile(r'^(.*),v\s+-->\s+(?:(?:standard output)|(?:stdout))\s*\n?$')
_re_co_warning = re.compile(r'^.*co: .*,v: warning: Unknown phrases like .*\n$')
_re_co_missing_rev = re.compile(r'^.*co: .*,v: revision.*absent\n$')
_re_co_side_branches = re.compile(r'^.*co: .*,v: no side branches present for [\d\.]+\n$')
_re_co_revision = re.compile(r'^revision\s+([\d\.]+)\s*\n$')

def _parse_co_header(fp):
def _parse_co_header(fp, encoding='utf-8'):
"""Parse RCS co header.
fp is a file (pipe) opened for reading the co standard error stream.
Returns: (filename, revision) or (None, None) if output is empty
"""

# Python 3: in this context, fp is raw mode.

# header from co:
#
#/home/cvsroot/mod_dav/dav_shared_stub.c,v --> standard output
Expand All @@ -634,7 +665,7 @@ def _parse_co_header(fp):
return None, None
match = _re_co_filename.match(line)
if not match:
raise COMalformedOutput, "Unable to find filename in co output stream"
raise COMalformedOutput("Unable to find filename in co output stream")
filename = match.group(1)

# look through subsequent lines for a revision. we might encounter
Expand All @@ -646,15 +677,18 @@ def _parse_co_header(fp):
# look for a revision.
match = _re_co_revision.match(line)
if match:
return filename, match.group(1)
if PY3:
return filename.decode(encoding, 'surrogateescape'), match.group(1).decode(encoding, 'surrogateescape')
else:
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"
raise COMissingRevision("Got missing revision error from co output stream")
elif _re_co_warning.match(line):
pass
else:
break

raise COMalformedOutput, "Unable to find revision in co output stream"
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'
Expand Down Expand Up @@ -719,15 +753,15 @@ def _parse_log_header(fp):

if state == 1:
if line[0] == '\t':
[ tag, rev ] = map(lambda x: x.strip(), line.split(':'))
[ tag, rev ] = [x.strip() for x in line.split(':')]
taginfo[tag] = rev
else:
# oops. this line isn't tag info. stop parsing tags.
state = 0

if state == 2:
if line[0] == '\t':
[ locker, rev ] = map(lambda x: x.strip(), line.split(':'))
[ locker, rev ] = [x.strip() for x in line.split(':')]
lockinfo[rev] = locker
else:
# oops. this line isn't lock info. stop parsing tags.
Expand Down Expand Up @@ -846,7 +880,7 @@ def _parse_log_entry(fp):
if (tm[0] - 1900) < 70:
tm[0] = tm[0] + 100
if tm[0] < EPOCH:
raise ValueError, 'invalid year'
raise ValueError('invalid year')
date = calendar.timegm(tm)

return Revision(rev, date,
Expand Down Expand Up @@ -890,7 +924,7 @@ def _file_log(revs, taginfo, lockinfo, cur_branch, filter):
# Create tag objects
for name, num in taginfo.items():
taginfo[name] = Tag(name, num)
tags = taginfo.values()
tags = list(taginfo.values())

# Set view_tag to a Tag object in order to filter results. We can filter by
# revision number or branch number
Expand Down Expand Up @@ -991,7 +1025,7 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
# we'll search the output for the appropriate revision
# fetch the latest revision on the default branch
args.append('-r')
args.extend(map(lambda x: x.path, chunk))
args.extend([x.path for x in chunk])
rlog = repos.rcs_popen('rlog', args, 'rt')

# consume each file found in the resulting log
Expand Down Expand Up @@ -1041,7 +1075,7 @@ def _get_logs(repos, dir_path_parts, entries, view_tag, get_dirs):
tag = None
if view_tag == 'MAIN' or view_tag == 'HEAD':
tag = Tag(None, default_branch)
elif taginfo.has_key(view_tag):
elif view_tag in taginfo:
tag = Tag(None, taginfo[view_tag])
elif view_tag and (eof != _EOF_FILE):
# the tag wasn't found, so skip this file (unless we already
Expand Down Expand Up @@ -1150,7 +1184,7 @@ def _check_path(path):
def _check_path(pathname):
try:
info = os.stat(pathname)
except os.error, e:
except os.error as e:
return None, ["stat error: %s" % e]

kind = None
Expand Down
20 changes: 10 additions & 10 deletions lib/vclib/ccvs/blame.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import re
import time
import math
import rcsparse
from . import rcsparse
import vclib

class CVSParser(rcsparse.Sink):
Expand Down Expand Up @@ -151,7 +151,7 @@ def extract_revision(self, revision):
add_lines_remaining = count
lines_added_now = lines_added_now + count
else:
raise RuntimeError, 'Error parsing diff commands'
raise RuntimeError('Error parsing diff commands')

self.lines_added[revision] = self.lines_added[revision] + lines_added_now
self.lines_removed[revision] = self.lines_removed[revision] + lines_removed_now
Expand Down Expand Up @@ -262,10 +262,10 @@ def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):

# CheckHidden(rcs_pathname)
try:
rcsfile = open(rcs_pathname, 'rb')
rcsfile = open(rcs_pathname, 'r')
except:
raise RuntimeError, ('error: %s appeared to be under CVS control, ' +
'but the RCS file is inaccessible.') % rcs_pathname
raise RuntimeError(('error: %s appeared to be under CVS control, ' +
'but the RCS file is inaccessible.') % rcs_pathname)

rcsparse.parse(rcsfile, self)
rcsfile.close()
Expand All @@ -277,7 +277,7 @@ def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
# Symbolic tag or specific revision number specified.
revision = self.map_tag_to_revision(opt_rev)
if revision == '':
raise RuntimeError, 'error: -r: No such revision: ' + opt_rev
raise RuntimeError('error: -r: No such revision: ' + opt_rev)

# The primordial revision is not always 1.1! Go find it.
primordial = revision
Expand Down Expand Up @@ -320,7 +320,7 @@ def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
skip = count
line_count = line_count + count
else:
raise RuntimeError, 'error: illegal RCS file'
raise RuntimeError('error: illegal RCS file')

rev = self.prev_revision.get(rev)

Expand Down Expand Up @@ -372,7 +372,7 @@ def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
del self.revision_map[start_line:start_line + count]
skip = count
else:
raise RuntimeError, 'Error parsing diff commands'
raise RuntimeError('Error parsing diff commands')

else:
# Revisions on a branch are arranged backwards from those on
Expand Down Expand Up @@ -405,7 +405,7 @@ def parse_cvs_file(self, rcs_pathname, opt_rev = None, opt_m_timestamp = None):
temp + self.revision_map[start_line + adjust:])
adjust = adjust + skip
else:
raise RuntimeError, 'Error parsing diff commands'
raise RuntimeError('Error parsing diff commands')

last_revision = revision

Expand All @@ -420,7 +420,7 @@ def __init__(self, rcs_file, opt_rev=None, include_text=False):
count = len(parser.revision_map)
lines = parser.extract_revision(revision)
if len(lines) != count:
raise RuntimeError, 'Internal consistency error'
raise RuntimeError('Internal consistency error')

# set up some state variables
self.revision = revision
Expand Down
Loading

0 comments on commit eef060e

Please sign in to comment.