logmerge.py 3.85 KB
Newer Older
1 2 3 4 5 6
#! /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.

7
    cvs log -rrelease14:
8 9

which dumps all log messages from release1.4 upwards (assuming that
10 11
release 1.4 was tagged with tag 'release14').  Note the trailing
colon!
12 13 14 15 16 17 18 19 20 21

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.

22 23
XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
from their output.
24 25 26 27 28

"""

import os, sys, getopt, string, re

Guido van Rossum's avatar
Guido van Rossum committed
29 30
sep1 = '='*77 + '\n'                    # file separator
sep2 = '-'*28 + '\n'                    # revision separator
31 32 33 34

def main():
    """Main program"""
    truncate_last = 0
Guido van Rossum's avatar
Guido van Rossum committed
35 36
    reverse = 0
    opts, args = getopt.getopt(sys.argv[1:], "tr")
37
    for o, a in opts:
Guido van Rossum's avatar
Guido van Rossum committed
38 39
        if o == '-t':
            truncate_last = 1
Guido van Rossum's avatar
Guido van Rossum committed
40 41
        elif o == '-r':
            reverse = 1
42 43
    database = []
    while 1:
Guido van Rossum's avatar
Guido van Rossum committed
44 45 46 47 48 49 50
        chunk = read_chunk(sys.stdin)
        if not chunk:
            break
        records = digest_chunk(chunk)
        if truncate_last:
            del records[-1]
        database[len(database):] = records
51
    database.sort()
Guido van Rossum's avatar
Guido van Rossum committed
52 53
    if not reverse:
        database.reverse()
54 55 56 57 58 59 60 61 62 63 64
    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:
Guido van Rossum's avatar
Guido van Rossum committed
65 66 67 68 69 70 71 72 73 74 75 76 77
        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)
78 79 80 81 82 83 84 85
    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:
Guido van Rossum's avatar
Guido van Rossum committed
86 87 88
        if line[:keylen] == key:
            working_file = string.strip(line[keylen:])
            break
89
    else:
Guido van Rossum's avatar
Guido van Rossum committed
90
        working_file = None
91 92
    records = []
    for lines in chunk[1:]:
Guido van Rossum's avatar
Guido van Rossum committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
        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))
118
    return records
Tim Peters's avatar
Tim Peters committed
119

120 121 122
def format_output(database):
    prevtext = None
    prev = []
123 124
    database.append((None, None, None, None, None)) # Sentinel
    for (date, working_file, rev, author, text) in database:
Guido van Rossum's avatar
Guido van Rossum committed
125 126 127 128 129 130 131 132 133
        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
134 135

main()