# Written by Bram Cohen and Ross Cohen # see LICENSE.txt for license information from Codeville.bencode import bdecode, bencode import binascii from Codeville.db import db from merge import find_conflict, find_conflict_multiple_safe, MergeError, replay from os import path from sets import Set from sha import sha import struct from time import localtime, strftime from xml.sax import saxutils import zlib roothandle = '\xdfp\xce\xbaNt5\xf9\xa1\xabd\xf2\xc4\x87\xc5\xd1\x0bI\x8d\xb4' rootnode = '\xb3L\xac\x1f\x98B\x15\\\x8c\t0&\xd7m\xecK\xdd\n\x81\xc4' class HistoryError(Exception): pass def dmerge(da, db): dc = {}.fromkeys(da) dc.update({}.fromkeys(db)) return dc.keys() def rename_conflict_check(linfo, rinfo): if linfo['name'] == rinfo['name']: try: if linfo['parent'] == rinfo['parent']: return ('local', dmerge(linfo['rename point'], rinfo['rename point'])) except KeyError: assert linfo['rename point'] == rinfo['rename point'] == [rootnode] return ('local', linfo['rename point']) for i in rinfo['rename point']: if i in linfo['points']: return ('local', linfo['rename point']) for i in linfo['rename point']: if i in rinfo['points']: return ('remote', rinfo['rename point']) return ('conflict', None) def _name_use_count(co, state, point, func, txn): cursor = co.allnamesdb.cursor(txn=txn) lookup = state['parent'] + state['name'] try: key, value = cursor.set(lookup) except (db.DBNotFoundError, TypeError): return [] named = [] while key == lookup: vinfo = func(co, value, point, txn) if vinfo is not None: if not vinfo.has_key('delete') and vinfo['parent'] == state['parent'] and vinfo['name'] == state['name']: named.append(value) # next_dup() is broken foo = cursor.next() if foo is None: break key, value = foo cursor.close() return named def name_use_count(co, state, point, txn): return _name_use_count(co, state, point, __handle_name_at_point, txn) def _children_count(co, handle, point, func, txn): cursor = co.allnamesdb.cursor(txn=txn) try: foo = cursor.set_range(handle) except (db.DBNotFoundError, TypeError): foo = None children = {} while foo is not None: key, value = foo parent = key[:20] if parent != handle: break vinfo = func(co, value, point, txn) if vinfo is not None: if not vinfo.has_key('delete') and vinfo['parent'] == handle: children[value] = None foo = cursor.next() cursor.close() return children.keys() def children_count(co, handle, point, txn): return _children_count(co, handle, point, __handle_name_at_point, txn) def parent_loop_check(co, handle, point, txn): hseen = {} while handle != roothandle: if hseen.has_key(handle): return handle hseen[handle] = 1 change = handle_last_modified(co, co.names, handle, point, txn) if change is None: return handle handle = _handle_name_at_point(co, handle, change, txn)['parent'] return None def is_ancestor(co, ancestor, point, txn): if point is None: return 0 return _is_ancestor(co, ancestor, [point], txn) def _is_ancestor(co, ancestor, points, txn): #ainfo = bdecode(co.branchmapdb.get(ancestor, txn=txn)) ainfo = db_get(co, co.branchmapdb, ancestor, txn) points = points[:] state = {} while len(points): pnext = points.pop() if pnext == ancestor: return 1 if state.has_key(pnext): continue state[pnext] = 1 #pinfo = bdecode(co.branchmapdb.get(pnext, txn=txn)) pinfo = db_get(co, co.branchmapdb, pnext, txn) if pinfo['generation'] <= ainfo['generation']: continue points.extend(pinfo['precursors']) return 0 def read_diff(co, handle, point, txn): hinfo = bdecode(co.contents.dagdb.get(handle + point, txn=txn)) hfile = open(path.join(co.cpath, binascii.hexlify(handle)), 'rb') diff = _read_diff(hinfo, hfile) hfile.close() return diff def _read_diff(index, hfile): if not index.has_key('handle'): return None try: hfile.seek(index['handle']['offset']) except IOError: print index raise return hfile.read(index['handle']['length']) def write_diff(co, handle, diff, txn): cdagdb = co.contents.dagdb try: fend = struct.unpack(' c_%s [style=%s]' % (short_id(co, pre), sid, style) style = "dashed" points.extend([point for point in info['precursors']]) print '}' def pretty_print_dag(co, handle, heads): print 'digraph {' points = [] for point in heads: head = handle_last_modified(co, co.contents, handle, point, None) if head is not None: points.append(head) cdagdb = co.contents.dagdb cache = {} while len(points): point = points.pop() if cache.has_key(point): continue cache[point] = 1 sid = short_id(co, point) print 'c_%s [label="%s"]' % (sid, sid) info = bdecode(cdagdb.get(handle + point)) for pre, foo in info['precursors']: print 'c_%s -> c_%s' % (sid, short_id(co, pre)) points.extend([point for point, index in info['precursors']]) print '}' def _update_mini_dag(co, changedbs, helper, handles, cset, txn): indexdb = changedbs.indexdb dagdb = changedbs.dagdb pres = cset['precursors'] point = cset['point'] #bminfo = bdecode(co.branchmapdb.get(point, txn=txn)) bminfo = db_get(co, co.branchmapdb, point, txn) bnum = struct.pack('>II', bminfo['branch'], bminfo['branchnum']) clean_merges = [] for handle in handles: precursors = simplify_precursors(co, handle, changedbs, pres, txn)[0] mdinfo = {'handle': {}} if cset['handles'].has_key(handle): mdinfo['handle'] = helper(co, handle, point, cset['handles'][handle], txn) if mdinfo['handle'] == {}: del mdinfo['handle'] if len(precursors) > 1: clean_merges.append(handle) mdinfo['precursors'] = precursors if precursors == []: assert cset['handles'][handle].has_key('add') dagdb.put(handle + point, bencode(mdinfo), txn=txn) indexdb.put(handle + bnum, point, txn=txn) if len(clean_merges) > 0: changedbs.mergedb.put(point, bencode(clean_merges), txn=txn) return def simplify_precursors(co, handle, changedbs, pres, txn): # map big DAG precursors to little DAG dagdb = changedbs.dagdb precursors, indices = [], [] for i in xrange(len(pres)): last = handle_last_modified(co, changedbs, handle, pres[i], txn) if last is None: continue # XXX: this is correct, but breaks old history if 0: pinfo = bdecode(dagdb.get(handle + last, txn=txn)) if not pinfo.has_key('handle') and len(pinfo['precursors']) == 1: last = handle_last_modified(co, changedbs, handle, pinfo['precursors'][0][0], txn) if last is None: continue precursors.append(last) indices.append(i) # second pass to eliminate precursors which are ancestors of others retval = [] for i in xrange(len(precursors)): pre = precursors.pop(0) index = indices.pop(0) if _is_ancestor(co, pre, precursors, txn): continue precursors.append(pre) retval.append((pre, index)) return retval, len(pres) def _update_helper_content(co, handle, point, hinfo, txn): oinfo = {} if hinfo.has_key('hash'): if co.contents.dagdb.has_key(handle + point, txn): dinfo = bdecode(co.contents.dagdb.get(handle + point, txn=txn)) oinfo['offset'] = dinfo['handle']['offset'] oinfo['length'] = dinfo['handle']['length'] else: # XXX: ugly in general oinfo['offset'] = -1 if hinfo.has_key('add'): oinfo['add'] = 1 elif hinfo.has_key('delete'): oinfo['delete'] = 1 return oinfo def _update_helper_name(co, handle, point, hinfo, txn): oinfo = {} if hinfo.has_key('name'): oinfo['name'] = hinfo['name'] try: oinfo['parent'] = hinfo['parent'] co.allnamesdb.put(hinfo['parent'] + hinfo['name'], handle, flags=db.DB_NODUPDATA, txn=txn) except KeyError: assert handle == roothandle assert hinfo.has_key('add') except db.DBKeyExistError: pass if hinfo.has_key('add'): oinfo['add'] = 1 elif hinfo.has_key('delete'): oinfo['delete'] = 1 return oinfo def changes_in_branch(co, lpoints, bpoints, txn, cache=None): points = bpoints[:] seen, changes = {}, [] while len(points): pnext = points.pop() if seen.has_key(pnext): continue seen[pnext] = 1 if _is_ancestor(co, pnext, lpoints, txn): continue if cache: if cache.has_key(pnext): pinfo = cache[pnext] else: pinfo = bdecode(co.lcrepo.get(pnext, txn=txn)) cache[pnext] = pinfo else: pinfo = bdecode(co.lcrepo.get(pnext, txn=txn)) points.extend(pinfo['precursors']) changes.append(pnext) return changes def handles_in_branch(co, lpoints, bpoints, txn, cache=None, deleted_modified=False): points = bpoints[:] seen, named, modified, deleted = {}, {}, {}, {} while len(points): pnext = points.pop() if seen.has_key(pnext): continue seen[pnext] = 1 if _is_ancestor(co, pnext, lpoints, txn): continue if cache: if cache.has_key(pnext): pinfo = cache[pnext] else: pinfo = bdecode(co.lcrepo.get(pnext, txn=txn)) cache[pnext] = pinfo else: pinfo = bdecode(co.lcrepo.get(pnext, txn=txn)) for handle, hinfo in pinfo['handles'].items(): if hinfo.has_key('name'): named[handle] = 1 if hinfo.has_key('hash'): modified[handle] = 1 if hinfo.has_key('delete'): named[handle] = 1 if not deleted_modified: deleted[handle] = 1 # XXX: afaik, this is only an issue for ambiguous clean merges, which # don't happen with name operations. requires more thought. if co.contents.mergedb.has_key(pnext, txn): clean_merges = bdecode(co.contents.mergedb.get(pnext, txn=txn)) for handle in clean_merges: modified[handle] = 1 # XXX: check for deletes? points.extend(pinfo['precursors']) for handle in deleted.keys(): if modified.has_key(handle): del modified[handle] return (named.keys(), modified.keys()) def handle_last_modified(co, changedbs, handle, change, txn): indexdb = changedbs.indexdb dagdb = changedbs.dagdb if dagdb.has_key(handle + change, txn): return change #hinfo = bdecode(co.branchmapdb.get(change, txn=txn)) hinfo = db_get(co, co.branchmapdb, change, txn) bbranch, bnum = int(hinfo['branch']), hinfo['branchnum'] retval = None cursor = indexdb.cursor(txn=txn) while 1: branchpoint = struct.pack('>20sII', handle, bbranch, bnum) try: foo = cursor.set_range(branchpoint) except db.DBNotFoundError: foo = None if foo is None: foo = cursor.last() if foo is None: break key, value = foo else: key, value = foo if key != branchpoint: try: foo = cursor.prev_nodup() except db.DBNotFound: foo = None if foo is None: break key, value = foo rhandle, rbranch, rnum = struct.unpack('>20sII', key) if rhandle == handle and rbranch == bbranch: retval = value break #pinfo = bdecode(co.branchdb.get(bbranch, txn=txn)) pinfo = db_get(co, co.branchdb, bbranch, txn) try: bbranch, bnum = int(pinfo['parent']), pinfo['parentnum'] except KeyError: #_print_indexdb(co, indexdb, handle, txn) break cursor.close() return retval def _print_indexdb(co, indexdb, handle, txn): print 'indexdb' cursor = indexdb.cursor(txn=txn) foo = cursor.set_range(handle) while foo != None: key, value = foo nhandle, branch, num = struct.unpack('<20sII', key) if handle != nhandle: break print "%d, %d" % (branch, num) foo = cursor.next() cursor.close() def handle_name_at_point(co, handle, point, txn, dochecks=0): change = handle_last_modified(co, co.names, handle, point, txn) if change is None: return None co.name_cache = {} return _handle_name_at_point(co, handle, change, txn, dochecks=dochecks) def __handle_name_at_point(co, handle, point, txn, dochecks=0): key = handle + point if not dochecks and co.name_cache.has_key(key): return co.name_cache[key] change = handle_last_modified(co, co.names, handle, point, txn) if change is None: return None return _handle_name_at_point(co, handle, change, txn, dochecks=dochecks) def _handle_name_from_precursors(precursors, resolved): state = {} for pre in precursors: if state == {}: state = pre.copy() continue if pre.has_key('delete'): return pre outcome, rename_point = rename_conflict_check(state, pre) if not resolved and outcome == 'conflict': raise HistoryError, 'double name conflict' elif outcome == 'remote': state['name'] = pre['name'] state['parent'] = pre['parent'] state['rename point'] = rename_point state['points'] = dmerge(state['points'], pre['points']) return state def _handle_name_at_point(co, handle, point, txn, dochecks=0): def walk_precursors(cset, dochecks): precursors, points = [], [point] for pre, index in cset['precursors']: foo = _handle_name_at_point(co, handle, pre, txn, dochecks=dochecks) if foo is None: continue points = dmerge(points, foo['points']) precursors.append(foo) return precursors, points cset = bdecode(co.names.dagdb.get(handle + point, txn=txn)) if not cset.has_key('handle'): precursors, points = walk_precursors(cset, dochecks) state = _handle_name_from_precursors(precursors, 0) elif cset['handle'].has_key('delete'): precursors, points = walk_precursors(cset, dochecks) state = _handle_name_from_precursors(precursors, 1) state['delete'] = 1 else: precursors, points = walk_precursors(cset, dochecks) state = {} state['name'] = cset['handle']['name'] try: state['parent'] = cset['handle']['parent'] except KeyError: assert handle == roothandle assert cset['handle'].has_key('add') state['rename point'] = [point] state['points'] = points if dochecks == 0: return state co.name_cache[handle + point] = state if state['name'] == '' and handle != roothandle: raise HistoryError, 'illegal name' if state['name'] == '.' or state['name'] == '..': raise HistoryError, 'illegal name' if state.has_key('delete'): if len(children_count(co, handle, point, txn)): raise HistoryError, 'non-empty directory can\'t be deleted' return state staticinfo = db_get(co, co.staticdb, handle, txn) if staticinfo['type'] == 'dir': try: if parent_loop_check(co, state['parent'], point, txn): raise HistoryError, 'parent loop' except KeyError: pass try: #parentinfo = bdecode(co.staticdb.get(state['parent'], txn=txn)) parentinfo = db_get(co, co.staticdb, state['parent'], txn) if parentinfo['type'] != 'dir': raise HistoryError, 'parent not a directory' parentstate = __handle_name_at_point(co, state['parent'], point, txn) if parentstate is None: raise HistoryError, 'parent not in repository' if parentstate.has_key('delete'): raise HistoryError, 'file committed with deleted parent' if len(name_use_count(co, state, point, txn)) != 1: raise HistoryError, 'name already in use' if state['name'] == 'CVILLE': raise HistoryError, 'illegal name' except KeyError: assert handle == roothandle return state def fullpath_at_point(co, handle, point, txn=None): name = None while handle != roothandle: pinfo = handle_name_at_point(co, handle, point, txn) if pinfo is None: return None if name is None: name = pinfo['name'] else: name = path.join(pinfo['name'], name) handle = pinfo['parent'] return name def _mini_dag_refcount(co, handle, point, txn, cache=None, info_cache=None): assert info_cache is not None if cache is None: cache = {} points = [point] while len(points): point = points.pop() if cache.has_key(point): cache[point]['refcount'] += 1 continue cache[point] = {'refcount': 1} pinfo = bdecode(co.contents.dagdb.get(handle + point, txn=txn)) info_cache[point] = pinfo for p, i in pinfo['precursors']: points.append(p) return cache def handle_contents_at_point(co, handle, point, txn, dcache=None): if dcache is None: dcache = {} #staticinfo = bdecode(co.staticdb.get(handle, txn=txn)) staticinfo = db_get(co, co.staticdb, handle, txn) if staticinfo['type'] != 'file': raise ValueError, 'no contents for non-file' change = handle_last_modified(co, co.contents, handle, point, txn) if change is None: return None hcache = {} cache = _mini_dag_refcount(co, handle, change, txn, info_cache=hcache) hfile = open(path.join(co.cpath, binascii.hexlify(handle)), 'rb') points = [change] while len(points): point = points[-1] # we may have already done this one if cache.has_key(point) and cache[point].has_key('info'): points.pop() continue # cache this, since we visit most nodes twice if hcache.has_key(point): hinfo = hcache[point] else: hinfo = bdecode(co.contents.dagdb.get(handle + point, txn=txn)) hcache[point] = hinfo # check if we've got the precursors dirty = False for pre, foo in hinfo['precursors']: if not cache[pre].has_key('info'): dirty = True points.append(pre) if dirty: continue points.pop() # read the diff if dcache.has_key(point): diff = dcache[point] else: diff = _read_diff(hinfo, hfile) if diff is not None: diff = bdecode(zlib.decompress(diff)) # put together the precursor list and decrement refcounts precursors = [] for pre, foo in hinfo['precursors']: precursors.append(cache[pre]['info']) cache[pre]['refcount'] -= 1 if cache[pre]['refcount'] == 0: del cache[pre] # finally, get the contents cache[point]['info'] = _handle_contents_at_point(point, hinfo, precursors, diff) hfile.close() cache[change]['info']['type'] = staticinfo['type'] return cache[change]['info'] def _handle_contents_at_point(point, hinfo, precursors, diff): state = {} points = [] for pre in precursors: points = dmerge(points, pre['points']) state['points'] = points matches = [] if diff is not None: for pre, index in hinfo['precursors']: matches.append(diff['matches'][index]) if diff.has_key('add'): if precursors != []: raise HistoryError, 'handle already exists' elif precursors == []: raise HistoryError, 'cannot modify non-existent file' if diff.has_key('delete'): state['delete'] = diff['delete'] if not state.has_key('delete'): fpre = [] for pre in precursors: fpre.append((pre['lines'], pre['line points'], pre['points'])) if diff is not None: try: lines, line_points = replay(fpre, matches, diff['newlines'], point) except MergeError, msg: raise HistoryError, 'merge error: ' + str(msg) except KeyError: raise HistoryError, 'malformed change' points.append(point) else: lines, line_points, points = find_conflict_multiple_safe(fpre) if lines is None: # XXX: this is a pretty gross hack if len(fpre) == 2: s0 = Set(fpre[0][2]) s1 = Set(fpre[1][2]) if s0 == s1: raise HistoryError, 'merge error' elif s0.issubset(s1): lines, line_points, points = fpre[1] elif s0.issuperset(s1): lines, line_points, points = fpre[0] else: raise HistoryError, 'merge error' else: raise HistoryError, 'merge error' state['lines'] = lines state['line points'] = line_points return state def print_file_with_points(pre): def _print_points(line_points): ps = [] for line_point in line_points: ps.append(binascii.hexlify(line_point)) return ', '.join(ps) lines, line_points, points = pre out = [_print_points(line_points[0])] for i in xrange(len(lines)): out.append(lines[i]) out.append(_print_points(line_points[i+1])) return '\n'.join(out) from merge import _find_conflict def print_conflict(co, fpre): p1, p2 = fpre[0], fpre[1] olines, oline_points = _find_conflict(fpre[0][0], fpre[0][1], fpre[0][2], fpre[1][0], fpre[1][1], fpre[1][2]) ls = [] offset = [0, 0] for i in xrange(len(olines)): l = olines[i] if type(l) is str: offset[0] += 1 offset[1] += 1 continue print '@@ -%d,%d +%d,%d @@' % (offset[0], len(l[0]), offset[0], len(l[1])) offset[0] += len(l[0]) offset[1] += len(l[1]) lp = oline_points[i] ls.append('<<<<<<< local') ps = ', '.join([short_id(co, p) for p in lp[0][0]]) ls.append(ps) for j in xrange(len(l[0])): ls.append(l[0][j]) ps = ', '.join([short_id(co, p) for p in lp[0][j+1]]) ls.append(ps) ls.append('=======') ps = ', '.join([short_id(co, p) for p in lp[1][0]]) ls.append(ps) for j in xrange(len(l[1])): ls.append(l[1][j]) ps = ', '.join([short_id(co, p) for p in lp[1][j+1]]) ls.append(ps) ls.append('>>>>>>> remote') return '\n'.join(ls) def rebuild_from_points(co, points, txn): co.changesdb.truncate(txn) co.branchdb.truncate(txn) co.branchmapdb.truncate(txn) co.names.indexdb.truncate(txn) co.names.dagdb.truncate(txn) co.names.mergedb.truncate(txn) co.contents.indexdb.truncate(txn) co.contents.mergedb.truncate(txn) # we don't truncate the cdagdb because it contains the offsets and lengths # for the diffs in the files, which we can't recreate. the sync below will # read those parameters out and rewrite the cdagdb, anyway. co.linforepo.put('branchmax', bencode(0), txn=txn) cdagdb = co.contents.dagdb for key, value in cdagdb.items(txn): if len(key) != 40: continue if not bdecode(value).has_key('handle'): cdagdb.delete(key, txn=txn) for point in points: sync_history(co, point, txn) def clean_merge_point(info): if info['handles'] != {}: return 0 if len(info['precursors']) != 2: return 0 if info.has_key('comment'): return 0 return 1 def short_id(co, point): apoint = binascii.hexlify(point) length = 3 key = apoint[:length] cursor = co.changesdb.cursor() while key.startswith(apoint[:length]) and length < len(apoint): length += 1 try: key, value = cursor.set_range(apoint[:length]) except (db.DBNotFoundError, TypeError): break if key != apoint: continue foo = cursor.next() if foo is None: break key, value = foo cursor.close() return apoint[:length] class ChangeNotKnown(Exception): pass class ChangeNotUnique(Exception): pass class ChangeBadRepository(Exception): pass def long_id(co, point): if point.startswith('cdv://'): key = repo_head(co, tuple_to_server(server_to_tuple(point))) if key is None: raise ChangeBadRepository, point return key cursor = co.changesdb.cursor() try: key, value = cursor.set_range(point) except (db.DBNotFoundError, TypeError): cursor.close() raise ChangeNotKnown, point if not key.startswith(point): cursor.close() raise ChangeNotKnown, point try: key2, value = cursor.next() except (db.DBNotFoundError, TypeError): cursor.close() return binascii.unhexlify(key) if key2.startswith(point): keys = [key] while key2.startswith(point): keys.append(key2) try: key2, value = cursor.next() except (db.DBNotFoundError, TypeError): break cursor.close() raise ChangeNotUnique, (point, keys) cursor.close() return binascii.unhexlify(key) def repo_head(co, repo): if repo is None: return None repo = tuple_to_server(server_to_tuple(repo)) return co.linforepo.get(repo) def server_to_tuple(server_string): if server_string.startswith('cdv://'): server_string = server_string[6:] temp = server_string.split('/', 1) if len(temp) != 2 or temp[1] == '': repo = None else: repo = temp[1] if repo[-1] == '/': repo = repo[:-1] temp = temp[0].split(':', 1) if len(temp) == 1: port = 6601 else: try: port = int(temp[1]) except ValueError: return None server_string = temp[0] return (temp[0], port, repo) def tuple_to_server(tuple): return 'cdv://%s:%d/%s' % tuple def print_change(co, change, changeset): escape = saxutils.escape output = [] output.append("") output.append("" + binascii.hexlify(change) + "") output.append("" + short_id(co, change) + "") output.append("" + escape(changeset['user']) + "") try: output.append("" + escape(changeset['comment']) + "") except KeyError: pass output.append("" + str(changeset['time']) + "") output.append("" + strftime('%a %b %d %H:%M:%S %Y %Z', localtime(changeset['time'])) + "") adds, deletes, renames, mods = {}, {}, {}, {} for handle, value in changeset['handles'].items(): if value.has_key('add'): adds[handle] = value['add'] else: if value.has_key('delete'): deletes[handle] = 1 if value.has_key('name'): renames[handle] = 1 if value.has_key('hash'): mods[handle] = 1 output.append("") for handle, info in adds.items(): output.append("") output.append("" + escape(fullpath_at_point(co, handle, change)) + "") output.append("" + info['type'] + "") output.append("") output.append("") output.append("") for handle in deletes.keys(): output.append("") output.append("" + escape(fullpath_at_point(co, handle, change)) + "") output.append("") output.append("") output.append("") for handle in renames.keys(): output.append("") output.append("" + escape(fullpath_at_point(co, handle, change)) + "") output.append("") output.append("") output.append("") for handle in mods.keys(): output.append("") output.append("" + escape(fullpath_at_point(co, handle, change)) + "") output.append("") output.append("") output.append("") return output def dump_changeinfo(co, change, repo=None): output = [] changeset = bdecode(co.lcrepo.get(change)) if not clean_merge_point(changeset): raise ValueError output.append("") if repo: output.append("" + saxutils.escape(repo) + "") output.append("" + binascii.hexlify(change) + "") output.append("" + short_id(co, change) + "") output.append("" + saxutils.escape(changeset['user']) + "") output.append("" + str(changeset['time']) + "") for change in changes_in_branch(co, [changeset['precursors'][0]], changeset['precursors'][1:], None): changeset = bdecode(co.lcrepo.get(change)) if not clean_merge_point(changeset): output.extend(print_change(co, change, changeset)) output.append("\n") return '\n'.join(output) def db_get(co, cdb, key, txn): try: cache = co.db_cache[db] except KeyError: cache = co.db_cache[db] = {} if cache.has_key(key): return cache[key] cache[key] = bdecode(cdb.get(key, txn=txn)) #try: # return cache[key] #except KeyError: # cache[key] = bdecode(cdb.get(key, txn=txn)) return cache[key] def db_put(co, cdb, key, value, txn): cdb.put(key, bencode(value), txn=txn) try: cache = co.db_cache[db] except KeyError: cache = co.db_cache[db] = {} cache[key] = value try: import psyco psyco.bind(_is_ancestor, 0) except ImportError: pass