class Repository: def __init__(self, hostname, fspath): self.hostname = hostname self.fspath = fspath self.revisions = [Dir(0, prev=None)] def getpath(self, path, rev): result = self.revisions[rev] assert result.rev == rev return result.getpath(path) def getrev(self): return self.revisions[-1].rev class Node: def __init__(self, rev, prev): self.rev = rev self.prev = prev class Dir(Node): def __init__(self, rev, prev): Node.__init__(self, rev, prev) self.entries = {} def modified_copy(self, newrev): result = Dir(newrev, self) result.entries.update(self.entries) return result def getpath(self, path): result = self for p in path.split('/'): if p: result = result.entries[p] return result def getentries(self): result = self.entries.items() result.sort() return result class File(Node): def modified_copy(self, newrev): return File(newrev, self) # ____________________________________________________________ ##def parselogs(repository, lastrev='HEAD', verbose=False): ## import os ## from xml.parsers import expat ## stack = [] ## datalist = [] ## accum = [] ## prio = {'D': '\x001', 'M': '\x002', 'A': '\x003'} ## def start_element(name, attrs): ## stack.append(attrs) ## def char_data(data): ## datalist.append(str(data)) ## def end_element(name): ## attrs = stack.pop() ## s = ''.join(datalist) ## s = str(s).strip() ## if s: ## sortkey = s + prio.get(attrs.get('action', '?'), '') ## accum.append((sortkey, name, attrs, s)) ## del datalist[:] ## if name == 'logentry': ## revnum = int(attrs['revision']) ## assert revnum == len(repository.revisions) ## rev = Rev(revnum) ## rev.entries = repository.revisions[-1].entries.copy() ## new_entries = {} ## def modify(direntry, name): ## prev = direntry.entries[name] ## if prev in new_entries: ## return prev ## else: ## next = Entry(prev) ## if prev.entries is not None: ## next.entries = prev.entries.copy() ## direntry.entries[name] = next ## new_entries[next] = True ## return next ## accum.sort() ## for sortkey, infoname, infoattrs, infos in accum: ## if infoname == 'author': ## rev.author = intern(infos) ## elif infoname == 'date': ## rev.date = infos ## elif infoname == 'path': ## path = infos.split('/') ## direntry = rev ## for p in path[:-1]: ## if p: ## direntry = modify(direntry, p) ## p = path[-1] ## action = infoattrs['action'] ## if action == 'M': ## # modifying an entry called 'p' in 'direntry' ## # special case: ignore changing a property on '/' ## if infos != '/': ## modify(direntry, p) ## elif action == 'A': ## # adding an entry called 'p' in 'direntry' ## if direntry.entries is None: ## direntry.entries = {} ## else: ## assert p not in direntry.entries ## if 'copyfrom-path' in infoattrs: ## prev = repository.getpath( ## str(infoattrs['copyfrom-path']), ## int(infoattrs['copyfrom-rev'])) ## next = Entry(prev) ## if prev.entries is not None: ## next.entries = prev.entries.copy() ## else: ## next = Entry(None) ## direntry.entries[p] = next ## elif action == 'D': ## # deleting an entry called 'p' in 'direntry' ## del direntry.entries[p] ## else: ## raise ValueError, 'action %r' % (action,) ## del accum[:] ## repository.revisions.append(rev) ## if verbose: ## print 'rev', rev.rev ## firstrev = repository.revisions[-1].rev + 1 ## if firstrev > lastrev: ## return ## g = os.popen('svn log -q -v --xml -r%s:%s %s' % ( ## firstrev, lastrev, repository.svnurl), 'r') ## p = expat.ParserCreate() ## p.StartElementHandler = start_element ## p.EndElementHandler = end_element ## p.CharacterDataHandler = char_data ## p.ParseFile(g) ## g.close() def execute(hostname, cmdline): import os, socket if (socket.gethostbyname(socket.gethostname()) != socket.gethostbyname(hostname)): cmdline = 'ssh %s %s' % (hostname, cmdline) print '[*]', cmdline return os.popen(cmdline, 'rb') def dump_and_parse(repository, lastrev='HEAD'): firstrev = repository.getrev() + 1 if lastrev != 'HEAD' and firstrev > lastrev: return cmdline = 'svnadmin dump %s -r%s:%s --incremental --deltas' % ( repository.fspath, firstrev, lastrev) g = execute(repository.hostname, cmdline) revnum = None headers = {} accum = [] prio = {'delete': '\x001', 'change': '\x002', 'add': '\x003', 'replace': '\x004'} def flush(): if revnum is None: return assert revnum == len(repository.revisions) rev = repository.revisions[-1].modified_copy(revnum) new_entries = {} def modify(direntry, name): prev = direntry.entries[name] if prev in new_entries: return prev else: next = prev.modified_copy(revnum) direntry.entries[name] = next new_entries[next] = True return next accum.sort() for sortkey, attrs in accum: nodepath = attrs['Node-path'] path = nodepath.split('/') direntry = rev for p in path[:-1]: direntry = modify(direntry, p) assert isinstance(direntry, Dir) p = path[-1] action = attrs['Node-action'] if action == 'change': # modifying an entry called 'p' in 'direntry' # special case: ignore changing a property on '/' if nodepath != '': modify(direntry, p) elif action == 'add' or action == 'replace': # adding an entry called 'p' in 'direntry' assert (p in direntry.entries) == (action == 'replace') if 'Node-copyfrom-path' in attrs: prev = repository.getpath( attrs['Node-copyfrom-path'], int(attrs['Node-copyfrom-rev'])) if 'Text-content-length' in attrs: next = prev.modified_copy(revnum) new_entries[next] = True else: next = prev elif attrs['Node-kind'] == 'file': next = File(revnum, None) new_entries[next] = True elif attrs['Node-kind'] == 'dir': next = Dir(revnum, None) new_entries[next] = True else: raise ValueError('Node-kind: %s' % attrs['Node-kind']) direntry.entries[p] = next elif action == 'delete': # deleting an entry called 'p' in 'direntry' try: del direntry.entries[p] except KeyError: print 'In revision %s, deleting %r: not found!' % ( revnum, nodepath) import pdb; pdb.set_trace() pass else: raise ValueError('Node-action: %s' % action) del accum[:] repository.revisions.append(rev) while True: line = g.readline() if line == '': break assert line.endswith('\n') line = line[:-1] if line: key, value = line.split(': ', 1) headers[key] = value else: if 'Revision-number' in headers: try: flush() except KeyboardInterrupt: raise except Exception, e: import traceback; traceback.print_exc() import pdb; pdb.set_trace() repository.revisions.append(repository.revisions[-1]) # :-( del accum[:] revnum = int(headers['Revision-number']) if 'Content-length' in headers: g.read(int(headers['Content-length'])) if 'Node-path' in headers: path = headers['Node-path'] sortkey = path + prio[headers['Node-action']] accum.append((sortkey, headers)) headers = {} flush() err = g.close() if err: print 'svnadmin returned with status', err import pdb; pdb.set_trace() pass