]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/python/Tools/scripts/logmerge.py
/sys/lib/dist/mkfile: test for .git directory
[plan9front.git] / sys / src / cmd / python / Tools / scripts / logmerge.py
1 #! /usr/bin/env python
2
3 """Consolidate a bunch of CVS or RCS logs read from stdin.
4
5 Input should be the output of a CVS or RCS logging command, e.g.
6
7     cvs log -rrelease14:
8
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!
12
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.
16
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.
21
22 The -r option reverses the output (oldest first; the default is oldest
23 last).
24
25 The -b tag option restricts the output to *only* checkin messages
26 belonging to the given branch tag.  The form -b HEAD restricts the
27 output to checkin messages belonging to the CVS head (trunk).  (It
28 produces some output if tag is a non-branch tag, but this output is
29 not very useful.)
30
31 -h prints this message and exits.
32
33 XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7
34 from their output.
35 """
36
37 import os, sys, errno, getopt, re
38
39 sep1 = '='*77 + '\n'                    # file separator
40 sep2 = '-'*28 + '\n'                    # revision separator
41
42 def main():
43     """Main program"""
44     truncate_last = 0
45     reverse = 0
46     branch = None
47     opts, args = getopt.getopt(sys.argv[1:], "trb:h")
48     for o, a in opts:
49         if o == '-t':
50             truncate_last = 1
51         elif o == '-r':
52             reverse = 1
53         elif o == '-b':
54             branch = a
55         elif o == '-h':
56             print __doc__
57             sys.exit(0)
58     database = []
59     while 1:
60         chunk = read_chunk(sys.stdin)
61         if not chunk:
62             break
63         records = digest_chunk(chunk, branch)
64         if truncate_last:
65             del records[-1]
66         database[len(database):] = records
67     database.sort()
68     if not reverse:
69         database.reverse()
70     format_output(database)
71
72 def 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:
81         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)
94     return chunk
95
96 def digest_chunk(chunk, branch=None):
97     """Digest a chunk -- extract working file name and revisions"""
98     lines = chunk[0]
99     key = 'Working file:'
100     keylen = len(key)
101     for line in lines:
102         if line[:keylen] == key:
103             working_file = line[keylen:].strip()
104             break
105     else:
106         working_file = None
107     if branch is None:
108         pass
109     elif branch == "HEAD":
110         branch = re.compile(r"^\d+\.\d+$")
111     else:
112         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)
127         branch = re.compile(r"^<>$") # <> to force a mismatch by default
128         if rev:
129             if rev.find('.0.') >= 0:
130                 rev = rev.replace('.0.', '.')
131                 branch = re.compile(r"^" + re.escape(rev) + r"\.\d+$")
132     records = []
133     for lines in chunk[1:]:
134         revline = lines[0]
135         dateline = lines[1]
136         text = lines[2:]
137         words = dateline.split()
138         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)
152         words = revline.split()
153         if len(words) >= 2 and words[0] == 'revision':
154             rev = words[1]
155         else:
156             # No 'revision' line -- weird...
157             rev = None
158             text.insert(0, revline)
159         if branch:
160             if rev is None or not branch.match(rev):
161                 continue
162         records.append((date, working_file, rev, author, text))
163     return records
164
165 def format_output(database):
166     prevtext = None
167     prev = []
168     database.append((None, None, None, None, None)) # Sentinel
169     for (date, working_file, rev, author, text) in database:
170         if text != prevtext:
171             if prev:
172                 print sep2,
173                 for (p_date, p_working_file, p_rev, p_author) in prev:
174                     print p_date, p_author, p_working_file, p_rev
175                 sys.stdout.writelines(prevtext)
176             prev = []
177         prev.append((date, working_file, rev, author))
178         prevtext = text
179
180 if __name__ == '__main__':
181     try:
182         main()
183     except IOError, e:
184         if e.errno != errno.EPIPE:
185             raise