blob: f6766d20e610185da7c07cd1ed4a6907371bfb42 [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 Rossum8b7b3452003-01-07 16:46:24 +000036import os, sys, getopt, re
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 Rossum8b7b3452003-01-07 16:46:24 +0000103 if branch is None:
104 pass
105 elif branch == "HEAD":
106 branch = re.compile(r"^\d+\.\d+$")
107 else:
Guido van Rossumbc01c322002-09-29 04:37:36 +0000108 revisions = {}
109 key = 'symbolic names:\n'
110 found = 0
111 for line in lines:
112 if line == key:
113 found = 1
114 elif found:
115 if line[0] in '\t ':
116 tag, rev = line.split()
117 if tag[-1] == ':':
118 tag = tag[:-1]
119 revisions[tag] = rev
120 else:
121 found = 0
122 rev = revisions.get(branch)
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000123 branch = re.compile(r"^<>$") # <> to force a mismatch by default
Guido van Rossumbc01c322002-09-29 04:37:36 +0000124 if rev:
125 if rev.find('.0.') >= 0:
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000126 rev = rev.replace('.0.', '.')
127 branch = re.compile(r"^" + re.escape(rev) + r"\.\d+$")
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000128 records = []
129 for lines in chunk[1:]:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000130 revline = lines[0]
131 dateline = lines[1]
132 text = lines[2:]
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000133 words = dateline.split()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000134 author = None
135 if len(words) >= 3 and words[0] == 'date:':
136 dateword = words[1]
137 timeword = words[2]
138 if timeword[-1:] == ';':
139 timeword = timeword[:-1]
140 date = dateword + ' ' + timeword
141 if len(words) >= 5 and words[3] == 'author:':
142 author = words[4]
143 if author[-1:] == ';':
144 author = author[:-1]
145 else:
146 date = None
147 text.insert(0, revline)
Walter Dörwaldaaab30e2002-09-11 20:36:02 +0000148 words = revline.split()
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000149 if len(words) >= 2 and words[0] == 'revision':
150 rev = words[1]
151 else:
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000152 # No 'revision' line -- weird...
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000153 rev = None
154 text.insert(0, revline)
Guido van Rossumbc01c322002-09-29 04:37:36 +0000155 if branch:
Guido van Rossum8b7b3452003-01-07 16:46:24 +0000156 if rev is None or not branch.match(rev):
Guido van Rossumbc01c322002-09-29 04:37:36 +0000157 continue
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000158 records.append((date, working_file, rev, author, text))
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000159 return records
Tim Peters70c43782001-01-17 08:48:39 +0000160
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000161def format_output(database):
162 prevtext = None
163 prev = []
Guido van Rossum9971f681997-10-06 21:09:32 +0000164 database.append((None, None, None, None, None)) # Sentinel
165 for (date, working_file, rev, author, text) in database:
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000166 if text != prevtext:
167 if prev:
168 print sep2,
169 for (p_date, p_working_file, p_rev, p_author) in prev:
Guido van Rossumf9e56e12001-04-10 03:31:27 +0000170 print p_date, p_author, p_working_file, p_rev
Guido van Rossumed5b3d81998-03-24 05:30:29 +0000171 sys.stdout.writelines(prevtext)
172 prev = []
173 prev.append((date, working_file, rev, author))
174 prevtext = text
Guido van Rossum6f0cf7e1997-08-14 22:04:00 +0000175
176main()