blob: 43dcc21a59aba0994fde7b207a9eb60eadbb1214 [file] [log] [blame]
#!/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()