Allow comment characters (#) to be escaped:
[python/dscho.git] / Tools / scripts / logmerge.py
blobc26df30690cb631b44453356f297276090a4ca07
1 #! /usr/bin/env python
3 """Consolidate a bunch of CVS or RCS logs read from stdin.
5 Input should be the output of a CVS or RCS logging command, e.g.
7 cvs log -rrelease14:
9 which dumps all log messages from release1.4 upwards (assuming that
10 release 1.4 was tagged with tag 'release14'). Note the trailing
11 colon!
13 This collects all the revision records and outputs them sorted by date
14 rather than by file, collapsing duplicate revision record, i.e.,
15 records with the same message for different files.
17 The -t option causes it to truncate (discard) the last revision log
18 entry; this is useful when using something like the above cvs log
19 command, which shows the revisions including the given tag, while you
20 probably want everything *since* that tag.
22 XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
23 from their output.
25 """
27 import os, sys, getopt, string, re
29 sep1 = '='*77 + '\n' # file separator
30 sep2 = '-'*28 + '\n' # revision separator
32 def 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)
52 def read_chunk(fp):
53 """Read a chunk -- data for one file, ending with sep1.
55 Split the chunk in parts separated by sep2.
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
76 def 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
116 def 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
131 main()