blob: 977324d4bf43045dc86acaf9993660ba70caf893 [file] [log] [blame]
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +00001#! /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
Guido van Rossum9971f681997-10-06 21:09:32 +00007 cvs log -rrelease14:
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +00008
9which dumps all log messages from release1.4 upwards (assuming that
Guido van Rossum9971f681997-10-06 21:09:32 +000010release 1.4 was tagged with tag 'release14'). Note the trailing
11colon!
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000012
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
Guido van Rossumbc01c322002-09-29 04:37:36 +000022The -r option reverses the output (oldest first; the default is oldest
23last).
24
25The -b tag option restricts the output to *only* checkin messages
26belonging to the given branch tag. The form -b HEAD restricts the
27output to checkin messages belonging to the CVS head (trunk). (It
28produces some output if tag is a non-branch tag, but this output is
29not very useful.)
30
Barry Warsaw334df6c2003-04-20 00:59:24 +000031-h prints this message and exits.
32
Guido van Rossum9971f681997-10-06 21:09:32 +000033XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
34from their output.
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000035"""
36
Guido van Rossum8b7b3452003-01-07 16:46:24 +000037import os, sys, getopt, re
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000038
Guido van Rossumed5b3d81998-03-24 05:30:29 +000039sep1 = '='*77 + '\n' # file separator
40sep2 = '-'*28 + '\n' # revision separator
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000041
42def main():
43 """Main program"""
44 truncate_last = 0
Guido van Rossumd9628782000-02-14 21:41:50 +000045 reverse = 0
Guido van Rossumbc01c322002-09-29 04:37:36 +000046 branch = None
Barry Warsaw334df6c2003-04-20 00:59:24 +000047 opts, args = getopt.getopt(sys.argv[1:], "trb:h")
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000048 for o, a in opts:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000049 if o == '-t':
50 truncate_last = 1
Guido van Rossumd9628782000-02-14 21:41:50 +000051 elif o == '-r':
52 reverse = 1
Guido van Rossumbc01c322002-09-29 04:37:36 +000053 elif o == '-b':
54 branch = a
Barry Warsaw334df6c2003-04-20 00:59:24 +000055 elif o == '-h':
56 print __doc__
57 sys.exit(0)
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000058 database = []
59 while 1:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000060 chunk = read_chunk(sys.stdin)
61 if not chunk:
62 break
Guido van Rossumbc01c322002-09-29 04:37:36 +000063 records = digest_chunk(chunk, branch)
Guido van Rossumed5b3d81998-03-24 05:30:29 +000064 if truncate_last:
65 del records[-1]
66 database[len(database):] = records
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000067 database.sort()
Guido van Rossumd9628782000-02-14 21:41:50 +000068 if not reverse:
69 database.reverse()
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000070 format_output(database)
71
72def read_chunk(fp):
73 """Read a chunk -- data for one file, ending with sep1.
74
75 Split the chunk in parts separated by sep2.
76
77 """
78 chunk = []
79 lines = []
80 while 1:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000081 line = fp.readline()
82 if not line:
83 break
84 if line == sep1:
85 if lines:
86 chunk.append(lines)
87 break
88 if line == sep2:
89 if lines:
90 chunk.append(lines)
91 lines = []
92 else:
93 lines.append(line)
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000094 return chunk
95
Guido van Rossumbc01c322002-09-29 04:37:36 +000096def digest_chunk(chunk, branch=None):
97 """Digest a chunk -- extract working file name and revisions"""
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000098 lines = chunk[0]
99 key = 'Working file:'
100 keylen = len(key)
101 for line in lines:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000102 if line[:keylen] == key:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000103 working_file = line[keylen:].strip()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000104 break
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000105 else:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000106 working_file = None
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000107 if branch is None:
108 pass
109 elif branch == "HEAD":
110 branch = re.compile(r"^\d+\.\d+$")
111 else:
Guido van Rossumbc01c322002-09-29 04:37:36 +0000112 revisions = {}
113 key = 'symbolic names:\n'
114 found = 0
115 for line in lines:
116 if line == key:
117 found = 1
118 elif found:
119 if line[0] in '\t ':
120 tag, rev = line.split()
121 if tag[-1] == ':':
122 tag = tag[:-1]
123 revisions[tag] = rev
124 else:
125 found = 0
126 rev = revisions.get(branch)
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000127 branch = re.compile(r"^<>$") # <> to force a mismatch by default
Guido van Rossumbc01c322002-09-29 04:37:36 +0000128 if rev:
129 if rev.find('.0.') >= 0:
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000130 rev = rev.replace('.0.', '.')
131 branch = re.compile(r"^" + re.escape(rev) + r"\.\d+$")
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000132 records = []
133 for lines in chunk[1:]:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000134 revline = lines[0]
135 dateline = lines[1]
136 text = lines[2:]
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000137 words = dateline.split()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000138 author = None
139 if len(words) >= 3 and words[0] == 'date:':
140 dateword = words[1]
141 timeword = words[2]
142 if timeword[-1:] == ';':
143 timeword = timeword[:-1]
144 date = dateword + ' ' + timeword
145 if len(words) >= 5 and words[3] == 'author:':
146 author = words[4]
147 if author[-1:] == ';':
148 author = author[:-1]
149 else:
150 date = None
151 text.insert(0, revline)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000152 words = revline.split()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000153 if len(words) >= 2 and words[0] == 'revision':
154 rev = words[1]
155 else:
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000156 # No 'revision' line -- weird...
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000157 rev = None
158 text.insert(0, revline)
Guido van Rossumbc01c322002-09-29 04:37:36 +0000159 if branch:
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000160 if rev is None or not branch.match(rev):
Guido van Rossumbc01c322002-09-29 04:37:36 +0000161 continue
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000162 records.append((date, working_file, rev, author, text))
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000163 return records
Tim Peters70c43782001-01-17 08:48:39 +0000164
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000165def format_output(database):
166 prevtext = None
167 prev = []
Guido van Rossum9971f681997-10-06 21:09:32 +0000168 database.append((None, None, None, None, None)) # Sentinel
169 for (date, working_file, rev, author, text) in database:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000170 if text != prevtext:
171 if prev:
172 print sep2,
173 for (p_date, p_working_file, p_rev, p_author) in prev:
Guido van Rossumf9e56e12001-04-10 03:31:27 +0000174 print p_date, p_author, p_working_file, p_rev
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000175 sys.stdout.writelines(prevtext)
176 prev = []
177 prev.append((date, working_file, rev, author))
178 prevtext = text
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000179
180main()