home *** CD-ROM | disk | FTP | other *** search
- #! /usr/bin/env python
-
- """Consolidate a bunch of CVS or RCS logs read from stdin.
-
- Input should be the output of a CVS or RCS logging command, e.g.
-
- cvs log -rrelease14:
-
- which dumps all log messages from release1.4 upwards (assuming that
- release 1.4 was tagged with tag 'release14'). Note the trailing
- colon!
-
- This collects all the revision records and outputs them sorted by date
- rather than by file, collapsing duplicate revision record, i.e.,
- records with the same message for different files.
-
- The -t option causes it to truncate (discard) the last revision log
- entry; this is useful when using something like the above cvs log
- command, which shows the revisions including the given tag, while you
- probably want everything *since* that tag.
-
- The -r option reverses the output (oldest first; the default is oldest
- last).
-
- The -b tag option restricts the output to *only* checkin messages
- belonging to the given branch tag. The form -b HEAD restricts the
- output to checkin messages belonging to the CVS head (trunk). (It
- produces some output if tag is a non-branch tag, but this output is
- not very useful.)
-
- -h prints this message and exits.
-
- XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
- from their output.
- """
-
- import os, sys, getopt, re
-
- sep1 = '='*77 + '\n' # file separator
- sep2 = '-'*28 + '\n' # revision separator
-
- def main():
- """Main program"""
- truncate_last = 0
- reverse = 0
- branch = None
- opts, args = getopt.getopt(sys.argv[1:], "trb:h")
- for o, a in opts:
- if o == '-t':
- truncate_last = 1
- elif o == '-r':
- reverse = 1
- elif o == '-b':
- branch = a
- elif o == '-h':
- print __doc__
- sys.exit(0)
- database = []
- while 1:
- chunk = read_chunk(sys.stdin)
- if not chunk:
- break
- records = digest_chunk(chunk, branch)
- if truncate_last:
- del records[-1]
- database[len(database):] = records
- database.sort()
- if not reverse:
- database.reverse()
- format_output(database)
-
- def read_chunk(fp):
- """Read a chunk -- data for one file, ending with sep1.
-
- Split the chunk in parts separated by sep2.
-
- """
- chunk = []
- lines = []
- while 1:
- line = fp.readline()
- if not line:
- break
- if line == sep1:
- if lines:
- chunk.append(lines)
- break
- if line == sep2:
- if lines:
- chunk.append(lines)
- lines = []
- else:
- lines.append(line)
- return chunk
-
- def digest_chunk(chunk, branch=None):
- """Digest a chunk -- extract working file name and revisions"""
- lines = chunk[0]
- key = 'Working file:'
- keylen = len(key)
- for line in lines:
- if line[:keylen] == key:
- working_file = line[keylen:].strip()
- break
- else:
- working_file = None
- if branch is None:
- pass
- elif branch == "HEAD":
- branch = re.compile(r"^\d+\.\d+$")
- else:
- revisions = {}
- key = 'symbolic names:\n'
- found = 0
- for line in lines:
- if line == key:
- found = 1
- elif found:
- if line[0] in '\t ':
- tag, rev = line.split()
- if tag[-1] == ':':
- tag = tag[:-1]
- revisions[tag] = rev
- else:
- found = 0
- rev = revisions.get(branch)
- branch = re.compile(r"^<>$") # <> to force a mismatch by default
- if rev:
- if rev.find('.0.') >= 0:
- rev = rev.replace('.0.', '.')
- branch = re.compile(r"^" + re.escape(rev) + r"\.\d+$")
- records = []
- for lines in chunk[1:]:
- revline = lines[0]
- dateline = lines[1]
- text = lines[2:]
- words = dateline.split()
- author = None
- if len(words) >= 3 and words[0] == 'date:':
- dateword = words[1]
- timeword = words[2]
- if timeword[-1:] == ';':
- timeword = timeword[:-1]
- date = dateword + ' ' + timeword
- if len(words) >= 5 and words[3] == 'author:':
- author = words[4]
- if author[-1:] == ';':
- author = author[:-1]
- else:
- date = None
- text.insert(0, revline)
- words = revline.split()
- if len(words) >= 2 and words[0] == 'revision':
- rev = words[1]
- else:
- # No 'revision' line -- weird...
- rev = None
- text.insert(0, revline)
- if branch:
- if rev is None or not branch.match(rev):
- continue
- records.append((date, working_file, rev, author, text))
- return records
-
- def format_output(database):
- prevtext = None
- prev = []
- database.append((None, None, None, None, None)) # Sentinel
- for (date, working_file, rev, author, text) in database:
- if text != prevtext:
- if prev:
- print sep2,
- for (p_date, p_working_file, p_rev, p_author) in prev:
- print p_date, p_author, p_working_file, p_rev
- sys.stdout.writelines(prevtext)
- prev = []
- prev.append((date, working_file, rev, author))
- prevtext = text
-
- main()
-