1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | import sys |
---|
4 | import re |
---|
5 | |
---|
6 | header_re = re.compile(r'^([^:]*): ?(.*)$') |
---|
7 | |
---|
8 | class NodePath: |
---|
9 | def __init__(self, path, headers): |
---|
10 | self.path = path |
---|
11 | self.headers = headers |
---|
12 | |
---|
13 | def dump(self): |
---|
14 | print((' ' * 3) + self.path) |
---|
15 | headers = sorted(self.headers.keys()) |
---|
16 | for header in headers: |
---|
17 | print((' ' * 6) + header + ': ' + self.headers[header]) |
---|
18 | |
---|
19 | |
---|
20 | def dump_revision(rev, nodepaths): |
---|
21 | sys.stderr.write('* Normalizing revision ' + rev + '...') |
---|
22 | print('Revision ' + rev) |
---|
23 | paths = sorted(nodepaths.keys()) |
---|
24 | for path in paths: |
---|
25 | nodepath = nodepaths[path] |
---|
26 | nodepath.dump() |
---|
27 | sys.stderr.write('done\n') |
---|
28 | |
---|
29 | |
---|
30 | |
---|
31 | def parse_header_block(fp): |
---|
32 | headers = {} |
---|
33 | while 1: |
---|
34 | line = fp.readline() |
---|
35 | if line == '': |
---|
36 | return headers, 1 |
---|
37 | line = line.strip() |
---|
38 | if line == '': |
---|
39 | return headers, 0 |
---|
40 | matches = header_re.match(line) |
---|
41 | if not matches: |
---|
42 | raise Exception('Malformed header block') |
---|
43 | headers[matches.group(1)] = matches.group(2) |
---|
44 | |
---|
45 | |
---|
46 | def parse_file(fp): |
---|
47 | nodepaths = {} |
---|
48 | current_rev = None |
---|
49 | |
---|
50 | while 1: |
---|
51 | # Parse a block of headers |
---|
52 | headers, eof = parse_header_block(fp) |
---|
53 | |
---|
54 | # This is a revision header block |
---|
55 | if 'Revision-number' in headers: |
---|
56 | |
---|
57 | # If there was a previous revision, dump it |
---|
58 | if current_rev: |
---|
59 | dump_revision(current_rev, nodepaths) |
---|
60 | |
---|
61 | # Reset the data for this revision |
---|
62 | current_rev = headers['Revision-number'] |
---|
63 | nodepaths = {} |
---|
64 | |
---|
65 | # Skip the contents |
---|
66 | prop_len = headers.get('Prop-content-length', 0) |
---|
67 | fp.read(int(prop_len)) |
---|
68 | |
---|
69 | # This is a node header block |
---|
70 | elif 'Node-path' in headers: |
---|
71 | |
---|
72 | # Make a new NodePath object, and add it to the |
---|
73 | # dictionary thereof |
---|
74 | path = headers['Node-path'] |
---|
75 | node = NodePath(path, headers) |
---|
76 | nodepaths[path] = node |
---|
77 | |
---|
78 | # Skip the content |
---|
79 | text_len = headers.get('Text-content-length', 0) |
---|
80 | prop_len = headers.get('Prop-content-length', 0) |
---|
81 | fp.read(int(text_len) + int(prop_len)) |
---|
82 | |
---|
83 | # Not a revision, not a node -- if we've already seen at least |
---|
84 | # one revision block, we are in an errorful state. |
---|
85 | elif current_rev and len(headers.keys()): |
---|
86 | raise Exception('Header block from outta nowhere') |
---|
87 | |
---|
88 | if eof: |
---|
89 | if current_rev: |
---|
90 | dump_revision(current_rev, nodepaths) |
---|
91 | break |
---|
92 | |
---|
93 | def usage(): |
---|
94 | print('Usage: ' + sys.argv[0] + ' [DUMPFILE]') |
---|
95 | print('') |
---|
96 | print('Reads a Subversion dumpfile from DUMPFILE (or, if not provided,') |
---|
97 | print('from stdin) and normalizes the metadata contained therein,') |
---|
98 | print('printing summarized and sorted information. This is useful for') |
---|
99 | print('generating data about dumpfiles in a diffable fashion.') |
---|
100 | sys.exit(0) |
---|
101 | |
---|
102 | def main(): |
---|
103 | if len(sys.argv) > 1: |
---|
104 | if sys.argv[1] == '--help': |
---|
105 | usage() |
---|
106 | fp = open(sys.argv[1], 'rb') |
---|
107 | else: |
---|
108 | fp = sys.stdin |
---|
109 | parse_file(fp) |
---|
110 | |
---|
111 | |
---|
112 | if __name__ == '__main__': |
---|
113 | main() |
---|
114 | |
---|
115 | |
---|
116 | |
---|
117 | |
---|