| #!/usr/bin/env python | |
| # | |
| # | |
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| # | |
| # | |
| import sys | |
| import re | |
| header_re = re.compile(r'^([^:]*): ?(.*)$') | |
| class NodePath: | |
| def __init__(self, path, headers): | |
| self.path = path | |
| self.headers = headers | |
| def dump(self): | |
| print((' ' * 3) + self.path) | |
| headers = sorted(self.headers.keys()) | |
| for header in headers: | |
| print((' ' * 6) + header + ': ' + self.headers[header]) | |
| def dump_revision(rev, nodepaths): | |
| sys.stderr.write('* Normalizing revision ' + rev + '...') | |
| print('Revision ' + rev) | |
| paths = sorted(nodepaths.keys()) | |
| for path in paths: | |
| nodepath = nodepaths[path] | |
| nodepath.dump() | |
| sys.stderr.write('done\n') | |
| def parse_header_block(fp): | |
| headers = {} | |
| while True: | |
| line = fp.readline() | |
| if line == '': | |
| return headers, 1 | |
| line = line.strip() | |
| if line == '': | |
| return headers, 0 | |
| matches = header_re.match(line) | |
| if not matches: | |
| raise Exception('Malformed header block') | |
| headers[matches.group(1)] = matches.group(2) | |
| def parse_file(fp): | |
| nodepaths = {} | |
| current_rev = None | |
| while True: | |
| # Parse a block of headers | |
| headers, eof = parse_header_block(fp) | |
| # This is a revision header block | |
| if 'Revision-number' in headers: | |
| # If there was a previous revision, dump it | |
| if current_rev: | |
| dump_revision(current_rev, nodepaths) | |
| # Reset the data for this revision | |
| current_rev = headers['Revision-number'] | |
| nodepaths = {} | |
| # Skip the contents | |
| prop_len = headers.get('Prop-content-length', 0) | |
| fp.read(int(prop_len)) | |
| # This is a node header block | |
| elif 'Node-path' in headers: | |
| # Make a new NodePath object, and add it to the | |
| # dictionary thereof | |
| path = headers['Node-path'] | |
| node = NodePath(path, headers) | |
| nodepaths[path] = node | |
| # Skip the content | |
| text_len = headers.get('Text-content-length', 0) | |
| prop_len = headers.get('Prop-content-length', 0) | |
| fp.read(int(text_len) + int(prop_len)) | |
| # Not a revision, not a node -- if we've already seen at least | |
| # one revision block, we are in an errorful state. | |
| elif current_rev and len(headers.keys()): | |
| raise Exception('Header block from outta nowhere') | |
| if eof: | |
| if current_rev: | |
| dump_revision(current_rev, nodepaths) | |
| break | |
| def usage(): | |
| print('Usage: ' + sys.argv[0] + ' [DUMPFILE]') | |
| print('') | |
| print('Reads a Subversion dumpfile from DUMPFILE (or, if not provided,') | |
| print('from stdin) and normalizes the metadata contained therein,') | |
| print('printing summarized and sorted information. This is useful for') | |
| print('generating data about dumpfiles in a diffable fashion.') | |
| sys.exit(0) | |
| def main(): | |
| if len(sys.argv) > 1: | |
| if sys.argv[1] == '--help': | |
| usage() | |
| fp = open(sys.argv[1], 'rb') | |
| else: | |
| fp = sys.stdin | |
| parse_file(fp) | |
| if __name__ == '__main__': | |
| main() | |