logmerge.py revision ed5b3d8b3c8077c8c57c8a825cae06e319b0994d
1#! /usr/bin/env python
2
3"""Consolidate a bunch of CVS or RCS logs read from stdin.
4
5Input should be the output of a CVS or RCS logging command, e.g.
6
7    cvs log -rrelease14:
8
9which dumps all log messages from release1.4 upwards (assuming that
10release 1.4 was tagged with tag 'release14').  Note the trailing
11colon!
12
13This collects all the revision records and outputs them sorted by date
14rather than by file, collapsing duplicate revision record, i.e.,
15records with the same message for different files.
16
17The -t option causes it to truncate (discard) the last revision log
18entry; this is useful when using something like the above cvs log
19command, which shows the revisions including the given tag, while you
20probably want everything *since* that tag.
21
22XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
23from their output.
24
25"""
26
27import os, sys, getopt, string, re
28
29sep1 = '='*77 + '\n'                    # file separator
30sep2 = '-'*28 + '\n'                    # revision separator
31
32def main():
33    """Main program"""
34    truncate_last = 0
35    opts, args = getopt.getopt(sys.argv[1:], "-t")
36    for o, a in opts:
37        if o == '-t':
38            truncate_last = 1
39    database = []
40    while 1:
41        chunk = read_chunk(sys.stdin)
42        if not chunk:
43            break
44        records = digest_chunk(chunk)
45        if truncate_last:
46            del records[-1]
47        database[len(database):] = records
48    database.sort()
49    database.reverse()
50    format_output(database)
51
52def read_chunk(fp):
53    """Read a chunk -- data for one file, ending with sep1.
54
55    Split the chunk in parts separated by sep2.
56
57    """
58    chunk = []
59    lines = []
60    while 1:
61        line = fp.readline()
62        if not line:
63            break
64        if line == sep1:
65            if lines:
66                chunk.append(lines)
67            break
68        if line == sep2:
69            if lines:
70                chunk.append(lines)
71                lines = []
72        else:
73            lines.append(line)
74    return chunk
75
76def digest_chunk(chunk):
77    """Digest a chunk -- extrach working file name and revisions"""
78    lines = chunk[0]
79    key = 'Working file:'
80    keylen = len(key)
81    for line in lines:
82        if line[:keylen] == key:
83            working_file = string.strip(line[keylen:])
84            break
85    else:
86        working_file = None
87    records = []
88    for lines in chunk[1:]:
89        revline = lines[0]
90        dateline = lines[1]
91        text = lines[2:]
92        words = string.split(dateline)
93        author = None
94        if len(words) >= 3 and words[0] == 'date:':
95            dateword = words[1]
96            timeword = words[2]
97            if timeword[-1:] == ';':
98                timeword = timeword[:-1]
99            date = dateword + ' ' + timeword
100            if len(words) >= 5 and words[3] == 'author:':
101                author = words[4]
102                if author[-1:] == ';':
103                    author = author[:-1]
104        else:
105            date = None
106            text.insert(0, revline)
107        words = string.split(revline)
108        if len(words) >= 2 and words[0] == 'revision':
109            rev = words[1]
110        else:
111            rev = None
112            text.insert(0, revline)
113        records.append((date, working_file, rev, author, text))
114    return records
115
116def format_output(database):
117    prevtext = None
118    prev = []
119    database.append((None, None, None, None, None)) # Sentinel
120    for (date, working_file, rev, author, text) in database:
121        if text != prevtext:
122            if prev:
123                print sep2,
124                for (p_date, p_working_file, p_rev, p_author) in prev:
125                    print p_date, p_author, p_working_file
126                sys.stdout.writelines(prevtext)
127            prev = []
128        prev.append((date, working_file, rev, author))
129        prevtext = text
130
131main()
132