1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | Tools/scripts/logmerge.py
#! /usr/bin/env python """Consolidate a bunch of CVS or RCS logs read from stdin. Input should be the output of a CVS or RCS logging command, e.g. cvs log -rrelease14: which dumps all log messages from release1.4 upwards (assuming that release 1.4 was tagged with tag 'release14'). Note the trailing colon! This collects all the revision records and outputs them sorted by date rather than by file, collapsing duplicate revision record, i.e., records with the same message for different files. The -t option causes it to truncate (discard) the last revision log entry; this is useful when using something like the above cvs log command, which shows the revisions including the given tag, while you probably want everything *since* that tag. The -r option reverses the output (oldest first; the default is oldest last). The -b tag option restricts the output to *only* checkin messages belonging to the given branch tag. The form -b HEAD restricts the output to checkin messages belonging to the CVS head (trunk). (It produces some output if tag is a non-branch tag, but this output is not very useful.) -h prints this message and exits. XXX This code was created by reverse engineering CVS 1.9 and RCS 5.7 from their output. """ import sys, errno, getopt, re sep1 = '='*77 + '\n' # file separator sep2 = '-'*28 + '\n' # revision separator def main(): """Main program""" truncate_last = 0 reverse = 0 branch = None opts, args = getopt.getopt(sys.argv[1:], "trb:h") for o, a in opts: if o == '-t': truncate_last = 1 elif o == '-r': reverse = 1 elif o == '-b': branch = a elif o == '-h': print __doc__ sys.exit(0) database = [] while 1: chunk = read_chunk(sys.stdin) if not chunk: break records = digest_chunk(chunk, branch) if truncate_last: del records[-1] database[len(database):] = records database.sort() if not reverse: database.reverse() format_output(database) def read_chunk(fp): """Read a chunk -- data for one file, ending with sep1. Split the chunk in parts separated by sep2. """ chunk = [] lines = [] while 1: line = fp.readline() if not line: break if line == sep1: if lines: chunk.append(lines) break if line == sep2: if lines: chunk.append(lines) lines = [] else: lines.append(line) return chunk def digest_chunk(chunk, branch=None): """Digest a chunk -- extract working file name and revisions""" lines = chunk[0] key = 'Working file:' keylen = len(key) for line in lines: if line[:keylen] == key: working_file = line[keylen:].strip() break else: working_file = None if branch is None: pass elif branch == "HEAD": branch = re.compile(r"^\d+\.\d+$") else: revisions = {} key = 'symbolic names:\n' found = 0 for line in lines: if line == key: found = 1 elif found: if line[0] in '\t ': tag, rev = line.split() if tag[-1] == ':': tag = tag[:-1] revisions[tag] = rev else: found = 0 rev = revisions.get(branch) branch = re.compile(r"^<>$") # <> to force a mismatch by default if rev: if rev.find('.0.') >= 0: rev = rev.replace('.0.', '.') branch = re.compile(r"^" + re.escape(rev) + r"\.\d+$") records = [] for lines in chunk[1:]: revline = lines[0] dateline = lines[1] text = lines[2:] words = dateline.split() author = None if len(words) >= 3 and words[0] == 'date:': dateword = words[1] timeword = words[2] if timeword[-1:] == ';': timeword = timeword[:-1] date = dateword + ' ' + timeword if len(words) >= 5 and words[3] == 'author:': author = words[4] if author[-1:] == ';': author = author[:-1] else: date = None text.insert(0, revline) words = revline.split() if len(words) >= 2 and words[0] == 'revision': rev = words[1] else: # No 'revision' line -- weird... rev = None text.insert(0, revline) if branch: if rev is None or not branch.match(rev): continue records.append((date, working_file, rev, author, text)) return records def format_output(database): prevtext = None prev = [] database.append((None, None, None, None, None)) # Sentinel for (date, working_file, rev, author, text) in database: if text != prevtext: if prev: print sep2, for (p_date, p_working_file, p_rev, p_author) in prev: print p_date, p_author, p_working_file, p_rev sys.stdout.writelines(prevtext) prev = [] prev.append((date, working_file, rev, author)) prevtext = text if __name__ == '__main__': try: main() except IOError, e: if e.errno != errno.EPIPE: raise |