| #!/usr/bin/env python |
| # |
| # 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(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, |
| (not ctx.txns_db.has_key(rev.txn)) and "*** MISSING TXN ***" or "") |
| ok(not txn2rev.has_key(rev.txn), '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 "-", |
| (not ctx.nodes_db.has_key(ch.node)) \ |
| 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 = data.keys() |
| k.sort() |
| 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 ctx.bad_reps.has_key(nd.proprep): |
| prkind += " *** BAD ***" |
| except KeyError: |
| prkind = "*** MISSING ***" |
| if nd.datarep: |
| try: |
| rep = skel.Rep(ctx.reps_db[nd.datarep]) |
| drkind = reptype[rep.kind] |
| if ctx.bad_reps.has_key(nd.datarep): |
| 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 not ctx.strings_db.has_key(rep.str): |
| 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 not ctx.reps_db.has_key(window.vs_rep): |
| noterep = " *MISS*" |
| ctx.bad_reps[rec[0]] = None |
| if not ctx.strings_db.has_key(window.str): |
| 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 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() |