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.
-
- XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
- from their output.
-
- """
-
- import os, sys, getopt, string, re
-
- sep1 = '='*77 + '\n' # file separator
- sep2 = '-'*28 + '\n' # revision separator
-
- def main():
- """Main program"""
- truncate_last = 0
- reverse = 0
- opts, args = getopt.getopt(sys.argv[1:], "tr")
- for o, a in opts:
- if o == '-t':
- truncate_last = 1
- elif o == '-r':
- reverse = 1
- database = []
- while 1:
- chunk = read_chunk(sys.stdin)
- if not chunk:
- break
- records = digest_chunk(chunk)
- 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):
- """Digest a chunk -- extrach working file name and revisions"""
- lines = chunk[0]
- key = 'Working file:'
- keylen = len(key)
- for line in lines:
- if line[:keylen] == key:
- working_file = string.strip(line[keylen:])
- break
- else:
- working_file = None
- records = []
- for lines in chunk[1:]:
- revline = lines[0]
- dateline = lines[1]
- text = lines[2:]
- words = string.split(dateline)
- 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 = string.split(revline)
- if len(words) >= 2 and words[0] == 'revision':
- rev = words[1]
- else:
- rev = None
- text.insert(0, revline)
- 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
- sys.stdout.writelines(prevtext)
- prev = []
- prev.append((date, working_file, rev, author))
- prevtext = text
-
- main()
-