diff --git a/lib/vclib/bincvs/__init__.py b/lib/vclib/bincvs/__init__.py
index 35f9fd3e..ade26d15 100644
--- a/lib/vclib/bincvs/__init__.py
+++ b/lib/vclib/bincvs/__init__.py
@@ -109,50 +109,70 @@ class BinCVSRepository(CVSRepository):
CVSRepository.__init__(self, name, rootpath)
self.rcs_paths = rcs_paths
+ def _get_tip_revision(self, rcs_file, rev=None):
+ """Get the (basically) youngest revision (filtered by REV)."""
+ args = rcs_file,
+ fp = self.rcs_popen('rlog', args, 'rt', 0)
+ filename, default_branch, tags, msg, eof = _parse_log_header(fp)
+ revs = []
+ while not eof:
+ revision, eof = _parse_log_entry(fp)
+ if revision:
+ revs.append(revision)
+ revs = _file_log(revs, tags, default_branch, rev)
+ if revs:
+ return revs[-1]
+ return None
+
def openfile(self, path_parts, rev=None):
if not rev or rev == 'HEAD' or rev == 'MAIN':
rev_flag = '-p'
else:
rev_flag = '-p' + rev
-
full_name = self.rcsfile(path_parts, root=1, v=0)
- fp = self.rcs_popen('co', (rev_flag, full_name), 'rb')
+ used_rlog = 0
+ tip_rev = None # used only if we have to fallback to using rlog
- filename, revision = _parse_co_header(fp)
+ fp = self.rcs_popen('co', (rev_flag, full_name), 'rb')
+ try:
+ filename, revision = _parse_co_header(fp)
+ except COMissingRevision:
+ # We got a "revision X.Y.Z absent" error from co. This could be
+ # because we were asked to find a tip of a branch, which co
+ # doesn't seem to handle. So we do rlog-gy stuff to figure out
+ # which revision the tip of the branch currently maps to.
+ ### TODO: Only do this when 'rev' is a branch symbol name?
+ if not used_rlog:
+ tip_rev = self._get_tip_revision(full_name + ',v', rev)
+ used_rlog = 1
+ if not tip_rev:
+ raise vclib.Error("Unable to find valid revision")
+ fp = self.rcs_popen('co', ('-p' + tip_rev.string, full_name), 'rb')
+ filename, revision = _parse_co_header(fp)
+
if filename is None:
# CVSNT's co exits without any output if a dead revision is requested.
# Bug at http://www.cvsnt.org/cgi-bin/bugzilla/show_bug.cgi?id=190
# As a workaround, we invoke rlog to find the first non-dead revision
- # that precedes it and check out that revision instead
- args = full_name + ',v',
- fp = self.rcs_popen('rlog', args, 'rt', 0)
- filename, default_branch, tags, msg, eof = _parse_log_header(fp)
-
- # Retrieve revision objects
- revs = []
- while not eof:
- revision, eof = _parse_log_entry(fp)
- if revision:
- revs.append(revision)
-
- revs = _file_log(revs, tags, default_branch, rev)
-
- # if we find a good revision, invoke co again, otherwise error out
- if revs and revs[-1].undead:
- rev_flag = '-p' + revs[-1].undead.string
- fp = self.rcs_popen('co', (rev_flag, full_name), 'rb')
- filename, revision = _parse_co_header(fp)
- else:
- raise vclib.Error("CVSNT co workaround could not find non-dead "
- "revision preceding \"%s\"" % rev)
+ # that precedes it and check out that revision instead. Of course,
+ # if we've already invoked rlog above, we just reuse its output.
+ if not used_rlog:
+ tip_rev = self._get_tip_revision(full_name + ',v', rev)
+ used_rlog = 1
+ if not (tip_rev and tip_rev.undead):
+ raise vclib.Error(
+ 'Could not find non-dead revision preceding "%s"' % rev)
+ fp = self.rcs_popen('co', ('-p' + tip_rev.undead.string,
+ full_name), 'rb')
+ filename, revision = _parse_co_header(fp)
if filename is None:
- raise vclib.Error('Missing output from co.
fname="%s".' % full_name)
+ raise vclib.Error('Missing output from co (filename = "%s")' % full_name)
if not _paths_eq(filename, full_name):
raise vclib.Error(
- 'The filename from co did not match. Found "%s". Wanted "%s"
'
+ 'The filename from co ("%s") did not match (expected "%s")'
% (filename, full_name))
return fp, revision
@@ -487,9 +507,16 @@ def _dict_list_add(dict, idx, elem):
# ======================================================================
# Functions for parsing output from RCS utilities
+
+class COMalformedOutput(vclib.Error):
+ pass
+class COMissingRevision(vclib.Error):
+ pass
+
### suck up other warnings in _re_co_warning?
_re_co_filename = re.compile(r'^(.*),v\s+-->\s+standard output\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_revision = re.compile(r'^revision\s+([\d\.]+)\s*\n$')
def _parse_co_header(fp):
@@ -509,50 +536,40 @@ def _parse_co_header(fp):
#co: INSTALL,v: warning: Unknown phrases like `permissions ...;' are present.
# parse the output header
- filename = revision = None
+ filename = None
+ # look for a filename in the first line (if there is a first line).
line = fp.readline()
if not line:
return None, None
-
match = _re_co_filename.match(line)
if not match:
- raise vclib.Error(
- 'First line of co output is not the filename.
'
- 'Line was: %s' % (line))
+ raise COMalformedOutput, "Unable to find filename in co output stream"
filename = match.group(1)
+ # look for a revision in the second line.
line = fp.readline()
if not line:
- raise vclib.Error(
- 'Missing second line of output from co.
'
- 'fname="%s".' % (filename))
+ raise COMalformedOutput, "Missing second line from co output stream"
match = _re_co_revision.match(line)
- if not match:
- match = _re_co_warning.match(line)
- if not match:
- raise vclib.Error(
- 'Second line of co output is not the revision.
'
- 'Line was: %s
'
- 'fname="%s".' % (line, filename))
-
- # second line was a warning. ignore it and move along.
- line = fp.readline()
- if not line:
- raise vclib.Error(
- 'Missing third line of output from co (after a warning).
'
- 'fname="%s".' % (filename))
- match = _re_co_revision.match(line)
- if not match:
- raise vclib.Error(
- 'Third line of co output is not the revision.
'
- 'Line was: %s
'
- 'fname="%s".' % (line, filename))
-
- # one of the above cases matches the revision. grab it.
- revision = match.group(1)
-
- return filename, revision
+ if match:
+ return filename, match.group(1)
+ elif _re_co_missing_rev.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