|  | #!/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. | 
|  | # | 
|  | # | 
|  | # | 
|  | # This is a pretty-printer for subversion BDB repository databases. | 
|  | # | 
|  |  | 
|  | import sys, os, re, codecs, textwrap | 
|  | import skel, svnfs | 
|  |  | 
|  | # Parse arguments | 
|  | if len(sys.argv) == 2: | 
|  | dbhome = os.path.join(sys.argv[1], 'db') | 
|  | if not os.path.exists(dbhome): | 
|  | sys.stderr.write("%s: '%s' is not a valid svn repository\n" % | 
|  | (sys.argv[0], dbhome)) | 
|  | sys.exit(1) | 
|  | else: | 
|  | sys.stderr.write("Usage: %s <svn-repository>\n" % sys.argv[0]) | 
|  | sys.exit(1) | 
|  |  | 
|  | # Helper Classes | 
|  | class RepositoryProblem(Exception): | 
|  | pass | 
|  |  | 
|  | # Helper Functions | 
|  | def ok(bool, comment): | 
|  | if not bool: | 
|  | raise RepositoryProblem(text) | 
|  |  | 
|  | # Helper Data | 
|  | opmap = { | 
|  | 'add': 'A', | 
|  | 'modify': 'M', | 
|  | 'delete': 'D', | 
|  | 'replace': 'R', | 
|  | 'reset': 'X', | 
|  | } | 
|  |  | 
|  | # Analysis Modules | 
|  | def am_uuid(ctx): | 
|  | "uuids" | 
|  | db = ctx.uuids_db | 
|  | ok(list(db.keys()) == [1], 'uuid Table Structure') | 
|  | ok(re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', | 
|  | db[1]), 'UUID format') | 
|  | print("Repos UUID: %s" % db[1]) | 
|  |  | 
|  | def am_revisions(ctx): | 
|  | "revisions" | 
|  | cur = ctx.revs_db.cursor() | 
|  | try: | 
|  | rec = cur.first() | 
|  | ctx.txn2rev = txn2rev = {} | 
|  | prevrevnum = -1 | 
|  | while rec: | 
|  | rev = skel.Rev(rec[1]) | 
|  | revnum = rec[0] - 1 | 
|  | print("r%d: txn %s%s" % (revnum, rev.txn, | 
|  | (rev.txn not in ctx.txns_db) and "*** MISSING TXN ***" or "")) | 
|  | ok(rev.txn not in txn2rev, 'Multiple revs bound to same txn') | 
|  | txn2rev[rev.txn] = revnum | 
|  | rec = cur.next() | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  | def am_changes(ctx): | 
|  | "changes" | 
|  | cur = ctx.changes_db.cursor() | 
|  | try: | 
|  | current_txnid_len = 0 | 
|  | maximum_txnid_len = 0 | 
|  | while current_txnid_len <= maximum_txnid_len: | 
|  | current_txnid_len += 1 | 
|  | rec = cur.first() | 
|  | prevtxn = None | 
|  | while rec: | 
|  | if len(rec[0]) != current_txnid_len: | 
|  | rec = cur.next() | 
|  | continue | 
|  | ch = skel.Change(rec[1]) | 
|  | lead = "txn %s:" % rec[0] | 
|  | if prevtxn == rec[0]: | 
|  | lead = " " * len(lead) | 
|  | print("%s %s %s %s %s %s%s" % (lead, opmap[ch.kind], ch.path, ch.node, | 
|  | ch.textmod and "T" or "-", ch.propmod and "P" or "-", | 
|  | (ch.node not in ctx.nodes_db) \ | 
|  | and "*** MISSING NODE ***" or "")) | 
|  | prevtxn = rec[0] | 
|  | if len(rec[0]) > maximum_txnid_len: | 
|  | maximum_txnid_len = len(rec[0]) | 
|  | rec = cur.next() | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  | def am_copies(ctx): | 
|  | "copies" | 
|  | cur = ctx.copies_db.cursor() | 
|  | try: | 
|  | print("next-key: %s" % ctx.copies_db['next-key']) | 
|  | rec = cur.first() | 
|  | while rec: | 
|  | if rec[0] != 'next-key': | 
|  | cp = skel.Copy(rec[1]) | 
|  | destnode = ctx.nodes_db.get(cp.destnode) | 
|  | if not destnode: | 
|  | destpath = "*** MISSING NODE ***" | 
|  | else: | 
|  | destpath = skel.Node(destnode).createpath | 
|  | print("cpy %s: %s %s @txn %s to %s (%s)" % (rec[0], | 
|  | {'copy':'C','soft-copy':'S'}[cp.kind], cp.srcpath or "-", | 
|  | cp.srctxn or "-", cp.destnode, destpath)) | 
|  | rec = cur.next() | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  | def am_txns(ctx): | 
|  | "transactions" | 
|  | cur = ctx.txns_db.cursor() | 
|  | try: | 
|  | print("next-key: %s" % ctx.txns_db['next-key']) | 
|  | length = 1 | 
|  | found_some = True | 
|  | while found_some: | 
|  | found_some = False | 
|  | rec = cur.first() | 
|  | while rec: | 
|  | if rec[0] != 'next-key' and len(rec[0]) == length: | 
|  | found_some = True | 
|  | txn = skel.Txn(rec[1]) | 
|  | if txn.kind == "committed": | 
|  | label = "r%s" % txn.rev | 
|  | ok(ctx.txn2rev[rec[0]] == int(txn.rev), 'Txn->rev not <-txn') | 
|  | else: | 
|  | label = "%s based-on %s" % (txn.kind, txn.basenode) | 
|  | print("txn %s: %s root-node %s props %d copies %s" % (rec[0], | 
|  | label, txn.rootnode, len(txn.proplist) / 2, ",".join(txn.copies))) | 
|  | rec = cur.next() | 
|  | length += 1 | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  | def am_nodes(ctx): | 
|  | "nodes" | 
|  | cur = ctx.nodes_db.cursor() | 
|  | try: | 
|  | print("next-key: %s" % ctx.txns_db['next-key']) | 
|  | rec = cur.first() | 
|  | data = {} | 
|  | while rec: | 
|  | if rec[0] == 'next-key': | 
|  | rec = cur.next() | 
|  | continue | 
|  | nd = skel.Node(rec[1]) | 
|  | nid,cid,tid = rec[0].split(".") | 
|  | data[tid.rjust(20)+nd.createpath] = (rec[0], nd) | 
|  | rec = cur.next() | 
|  | k = sorted(data.keys()) | 
|  | reptype = {"fulltext":"F", "delta":"D"} | 
|  | for i in k: | 
|  | nd = data[i][1] | 
|  | prkind = drkind = " " | 
|  | if nd.proprep: | 
|  | try: | 
|  | rep = skel.Rep(ctx.reps_db[nd.proprep]) | 
|  | prkind = reptype[rep.kind] | 
|  | if nd.proprep in ctx.bad_reps: | 
|  | prkind += " *** BAD ***" | 
|  | except KeyError: | 
|  | prkind = "*** MISSING ***" | 
|  | if nd.datarep: | 
|  | try: | 
|  | rep = skel.Rep(ctx.reps_db[nd.datarep]) | 
|  | drkind = reptype[rep.kind] | 
|  | if nd.datarep in ctx.bad_reps: | 
|  | drkind += " *** BAD ***" | 
|  | except KeyError: | 
|  | drkind = "*** MISSING ***" | 
|  | stringdata = "%s: %s %s pred %s count %s prop %s %s data %s %s edit %s" \ | 
|  | % ( data[i][0], {"file":"F", "dir":"D"}[nd.kind], nd.createpath, | 
|  | nd.prednode or "-", nd.predcount, prkind, nd.proprep or "-", | 
|  | drkind, nd.datarep or "-", nd.editrep or "-") | 
|  | if nd.createpath == "/": | 
|  | print("") | 
|  | print(stringdata) | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  | def get_string(ctx, id): | 
|  | try: | 
|  | return ctx.get_whole_string(id) | 
|  | except DbNotFoundError: | 
|  | return "*** MISSING STRING ***" | 
|  |  | 
|  | def am_reps(ctx): | 
|  | "representations" | 
|  | ctx.bad_reps = {} | 
|  | cur = ctx.reps_db.cursor() | 
|  | try: | 
|  | print("next-key: %s" % ctx.txns_db['next-key']) | 
|  | rec = cur.first() | 
|  | while rec: | 
|  | if rec[0] != 'next-key': | 
|  | rep = skel.Rep(rec[1]) | 
|  | lead = "rep %s: txn %s: %s %s " % (rec[0], rep.txn, rep.cksumtype, | 
|  | codecs.getencoder('hex_codec')(rep.cksum)[0]) | 
|  | if rep.kind == "fulltext": | 
|  | note = "" | 
|  | if rep.str not in ctx.strings_db: | 
|  | note = " *MISS*" | 
|  | ctx.bad_reps[rec[0]] = None | 
|  | print(lead+("fulltext str %s%s" % (rep.str, note))) | 
|  | if ctx.verbose: | 
|  | print(textwrap.fill(get_string(ctx, rep.str), initial_indent="  ", | 
|  | subsequent_indent="  ", width=78)) | 
|  | elif rep.kind == "delta": | 
|  | print(lead+("delta of %s window%s" % (len(rep.windows), | 
|  | len(rep.windows) != 1 and "s" or ""))) | 
|  | for window in rep.windows: | 
|  | noterep = notestr = "" | 
|  | if window.vs_rep not in ctx.reps_db: | 
|  | noterep = " *MISS*" | 
|  | ctx.bad_reps[rec[0]] = None | 
|  | if window.str not in ctx.strings_db: | 
|  | notestr = " *MISS*" | 
|  | ctx.bad_reps[rec[0]] = None | 
|  | print("\toff %s len %s vs-rep %s%s str %s%s" % (window.offset, | 
|  | window.size, window.vs_rep, noterep, window.str, notestr)) | 
|  | else: | 
|  | print(lead+"*** UNKNOWN REPRESENTATION TYPE ***") | 
|  | rec = cur.next() | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  |  | 
|  | def am_stringsize(ctx): | 
|  | "string size" | 
|  | if not ctx.verbose: | 
|  | return | 
|  | cur = ctx.strings_db.cursor() | 
|  | try: | 
|  | rec = cur.first() | 
|  | size = 0 | 
|  | while rec: | 
|  | size = size + len(rec[1] or "") | 
|  | rec = cur.next() | 
|  | print("%s %s %s" % (size, size/1024.0, size/1024.0/1024.0)) | 
|  | finally: | 
|  | cur.close() | 
|  |  | 
|  | modules = ( | 
|  | am_uuid, | 
|  | am_revisions, | 
|  | am_changes, | 
|  | am_copies, | 
|  | am_txns, | 
|  | am_reps, | 
|  | am_nodes, | 
|  | # Takes too long: am_stringsize, | 
|  | ) | 
|  |  | 
|  | def main(): | 
|  | print("Repository View for '%s'" % dbhome) | 
|  | print("") | 
|  | ctx = svnfs.Ctx(dbhome, readonly=1) | 
|  | # Stash process state in a library data structure. Yuck! | 
|  | ctx.verbose = 0 | 
|  | try: | 
|  | for am in modules: | 
|  | print("MODULE: %s" % am.__doc__) | 
|  | am(ctx) | 
|  | print("") | 
|  | finally: | 
|  | ctx.close() | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |