blob: 5409a70ec04fb506d139232fb38d1af6b86cb31c [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
Guido van Rossum9971f681997-10-06 21:09:32 +000031XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
32from their output.
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000033
34"""
35
Guido van Rossumbc01c322002-09-29 04:37:36 +000036import os, sys, getopt
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000037
Guido van Rossumed5b3d81998-03-24 05:30:29 +000038sep1 = '='*77 + '\n' # file separator
39sep2 = '-'*28 + '\n' # revision separator
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000040
41def main():
42 """Main program"""
43 truncate_last = 0
Guido van Rossumd9628782000-02-14 21:41:50 +000044 reverse = 0
Guido van Rossumbc01c322002-09-29 04:37:36 +000045 branch = None
46 opts, args = getopt.getopt(sys.argv[1:], "trb:")
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000047 for o, a in opts:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000048 if o == '-t':
49 truncate_last = 1
Guido van Rossumd9628782000-02-14 21:41:50 +000050 elif o == '-r':
51 reverse = 1
Guido van Rossumbc01c322002-09-29 04:37:36 +000052 elif o == '-b':
53 branch = a
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000054 database = []
55 while 1:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000056 chunk = read_chunk(sys.stdin)
57 if not chunk:
58 break
Guido van Rossumbc01c322002-09-29 04:37:36 +000059 records = digest_chunk(chunk, branch)
Guido van Rossumed5b3d81998-03-24 05:30:29 +000060 if truncate_last:
61 del records[-1]
62 database[len(database):] = records
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000063 database.sort()
Guido van Rossumd9628782000-02-14 21:41:50 +000064 if not reverse:
65 database.reverse()
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000066 format_output(database)
67
68def read_chunk(fp):
69 """Read a chunk -- data for one file, ending with sep1.
70
71 Split the chunk in parts separated by sep2.
72
73 """
74 chunk = []
75 lines = []
76 while 1:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000077 line = fp.readline()
78 if not line:
79 break
80 if line == sep1:
81 if lines:
82 chunk.append(lines)
83 break
84 if line == sep2:
85 if lines:
86 chunk.append(lines)
87 lines = []
88 else:
89 lines.append(line)
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000090 return chunk
91
Guido van Rossumbc01c322002-09-29 04:37:36 +000092def digest_chunk(chunk, branch=None):
93 """Digest a chunk -- extract working file name and revisions"""
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +000094 lines = chunk[0]
95 key = 'Working file:'
96 keylen = len(key)
97 for line in lines:
Guido van Rossumed5b3d81998-03-24 05:30:29 +000098 if line[:keylen] == key:
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000099 working_file = line[keylen:].strip()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000100 break
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000101 else:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000102 working_file = None
Guido van Rossumbc01c322002-09-29 04:37:36 +0000103 if branch and branch != "HEAD":
104 revisions = {}
105 key = 'symbolic names:\n'
106 found = 0
107 for line in lines:
108 if line == key:
109 found = 1
110 elif found:
111 if line[0] in '\t ':
112 tag, rev = line.split()
113 if tag[-1] == ':':
114 tag = tag[:-1]
115 revisions[tag] = rev
116 else:
117 found = 0
118 rev = revisions.get(branch)
119 if rev:
120 if rev.find('.0.') >= 0:
121 rev = rev.replace('.0.', '.') + '.'
122 branch = rev or "<>" # <> to force a mismatch
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000123 records = []
124 for lines in chunk[1:]:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000125 revline = lines[0]
126 dateline = lines[1]
127 text = lines[2:]
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000128 words = dateline.split()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000129 author = None
130 if len(words) >= 3 and words[0] == 'date:':
131 dateword = words[1]
132 timeword = words[2]
133 if timeword[-1:] == ';':
134 timeword = timeword[:-1]
135 date = dateword + ' ' + timeword
136 if len(words) >= 5 and words[3] == 'author:':
137 author = words[4]
138 if author[-1:] == ';':
139 author = author[:-1]
140 else:
141 date = None
142 text.insert(0, revline)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000143 words = revline.split()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000144 if len(words) >= 2 and words[0] == 'revision':
145 rev = words[1]
146 else:
147 rev = None
148 text.insert(0, revline)
Guido van Rossumbc01c322002-09-29 04:37:36 +0000149 if branch:
150 if branch == "HEAD":
151 if rev is not None and rev.count('.') > 1:
152 continue
153 elif rev is None or not rev.startswith(branch):
154 continue
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000155 records.append((date, working_file, rev, author, text))
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000156 return records
Tim Peters70c43782001-01-17 08:48:39 +0000157
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000158def format_output(database):
159 prevtext = None
160 prev = []
Guido van Rossum9971f681997-10-06 21:09:32 +0000161 database.append((None, None, None, None, None)) # Sentinel
162 for (date, working_file, rev, author, text) in database:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000163 if text != prevtext:
164 if prev:
165 print sep2,
166 for (p_date, p_working_file, p_rev, p_author) in prev:
Guido van Rossumf9e56e12001-04-10 03:31:27 +0000167 print p_date, p_author, p_working_file, p_rev
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000168 sys.stdout.writelines(prevtext)
169 prev = []
170 prev.append((date, working_file, rev, author))
171 prevtext = text
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000172
173main()