# # # patch "tracvc/mtn/automate.py" # from [cd549cdc1a713a56ca4dce3e3ad8650c6e2f6d56] # to [8fc528706d3090c199eb3acd578c62e398ca161b] # # patch "tracvc/mtn/backend.py" # from [e5f9250a4a43dfdda760e92974292e442a7a0c9d] # to [30e62c11bf7539ff1af2f7d333e9282e217e6e70] # ============================================================ --- tracvc/mtn/automate.py cd549cdc1a713a56ca4dce3e3ad8650c6e2f6d56 +++ tracvc/mtn/automate.py 8fc528706d3090c199eb3acd578c62e398ca161b @@ -29,7 +29,6 @@ import dummy_threading as _threading import basic_io -MT_BINARY = "/usr/bin/mtn" # fixme: default, make this configurable TAGS_RULE = re.compile(r"^(?P.*?) (?P[0-9a-f]{40}) (?P.*)\n", re.MULTILINE) REVID_RULE = re.compile(r'^[0-9a-f]{40}$') HEAD_RULE = re.compile(r'^h:[a-zA-Z0-9.-]+$') @@ -121,13 +120,14 @@ class MTN: - def __init__(self, db, log, binary = MT_BINARY): + def __init__(self, db, log, binary): self.automate = Automate(db, binary) self.list = List(db, binary) self.log = log self.manifest_cache = {} self.certs_cache = {} self.roots_cache = [] + self.changeset_cache = {} def leaves(self): """Returns a list containing the current leaves.""" @@ -153,6 +153,18 @@ if status == 0: return result.splitlines() else: return [] + def ancestors(self, revs): + """Returns a list of the ancestors of rev.""" + status, result = self.automate.command("ancestors", *revs) + if status == 0: return result.splitlines() + else: return [] + + def toposort(self, revs): + """Sorts revisions topologically.""" + status, result = self.automate.command("toposort", *revs) + if status == 0: return result.splitlines() + else: return [] + def all_revs(self): """Returns a list of all revs in the repository.""" status, result = self.automate.command("select", '') @@ -256,57 +268,59 @@ if status == 0: return result def changeset(self, rev): - """Returns changeset dictionary processed to be consumed by Trac.""" + """Returns a changeset dictionary processed to be consumed by Trac.""" + if rev in self.changeset_cache: + return self.changeset_cache[rev] status, result = self.automate.command("get_revision", rev) - changeset = [] - if status != 0: return changeset - renames = {} - oldrev = None + if status != 0: return [] + changeset = {} + changeset[None] = {} def add_slash(path): return '/' + path - - def append_edit(path, oldrev): - path = add_slash(path) - if path in renames: - (oldpath, oldrev_for_path) = renames[path] - changeset.append((path, 'file', 'edit', oldpath, oldrev_for_path)) - else: - changeset.append((path, 'file', 'edit', path, oldrev)) for stanza in basic_io.get_stanzas(result): entry = basic_io.get_hash_from_stanza(stanza) if entry.has_key('old_revision'): # oldrev is in effect until overwritten by a later stanza oldrev = entry['old_revision'] + renames, changeset[oldrev] = {}, {} elif entry.has_key('add_dir'): path = add_slash(entry['add_dir']) - changeset.append((path, 'dir', 'add', None, None)) + changeset[None][path] = ('dir', 'add', None) elif entry.has_key('add_file'): path = add_slash(entry['add_file']) - changeset.append((path, 'file', 'add', None, None)) + changeset[None][path] = ('file', 'add', None) elif entry.has_key('delete'): path = add_slash(entry['delete']) - changeset.append((None, None, 'delete', path, oldrev)) + changeset[oldrev][None] = (None, 'delete', path) elif entry.has_key('rename'): oldpath = add_slash(entry['rename']) newpath = add_slash(entry['to']) # remember renames for edits - renames[newpath] = (oldpath, oldrev) - changeset.append((newpath, None, 'move', oldpath, oldrev)) + renames[newpath] = oldpath + changeset[oldrev][newpath] = (None, 'move', oldpath) elif entry.has_key('patch'): - append_edit(entry['patch'], oldrev) + path = add_slash(entry['patch']) + if path in renames: + oldpath = renames[path] + changeset[oldrev][path] = ('file', 'edit', oldpath) + else: + changeset[oldrev][path] = ('file', 'edit', path) # fixme: what about 'set' and 'clear'? These are edits, # but not if applied to new files. + if not changeset[None]: + del changeset[None] + self.changeset_cache[rev] = changeset return changeset def branches(self): - """Returns a list of (branch, anyhead) tuples. Caveat: very slow.""" + """Returns a list of (branch, oneoftheheads) tuples. Caveat: very slow.""" branchnames = map(self._u, self.list.get_list("branches").splitlines()) branches = [] for branch in branchnames: - print branch revs = self.heads(branch) - if revs: branches.append((branch, revs[0])) # multiple heads not supported + if revs: + branches.append((branch, revs[0])) # multiple heads not supported return branches def non_merged_branches(self): @@ -324,4 +338,3 @@ for match in TAGS_RULE.finditer(self.list.get_list("tags")): tags.append((self._u(match.group('tag')), match.group('rev'))) return tags - ============================================================ --- tracvc/mtn/backend.py e5f9250a4a43dfdda760e92974292e442a7a0c9d +++ tracvc/mtn/backend.py 30e62c11bf7539ff1af2f7d333e9282e217e6e70 @@ -44,10 +44,16 @@ def get_repository(self, type, path, authname): """Return a monotone repository.""" + + # get options + options = {} + for (key, val) in self.config.options(type): + options[key] = val + # note: we don't use type or authname, therefore we can always # return the same Repository object for the same database path if not path in self.repos: - self.repos[path] = MonotoneRepository(path, self.log) + self.repos[path] = MonotoneRepository(path, self.log, options) return self.repos[path] # IWikiSyntaxProvider methods @@ -81,9 +87,10 @@ class MonotoneRepository(Repository): - def __init__(self, db_path, log): - self.mtn = MTN(db_path, log) - Repository.__init__(self, 'mtn:%s' % db_path, None, log) + def __init__(self, path, log, options): + mtn_binary = options.get('mtn_binary', '/usr/bin/mtn') + self.mtn = MTN(path, log, mtn_binary) + Repository.__init__(self, 'mtn:%s' % path, None, log) def get_changeset(self, rev): """ @@ -97,7 +104,7 @@ Generate Changesets belonging to the given time period (start, stop). """ nodes = self.mtn.leaves() - seen = {} + seen = set() while nodes: current = nodes.pop(0) time = self.mtn.dates(current)[0] @@ -107,7 +114,7 @@ yield MonotoneChangeset(self.mtn, current) for parent in self.mtn.parents(current): if parent not in seen: - seen[parent] = 1 + seen.add(parent) nodes.append(parent) def get_node(self, path, rev=None): @@ -173,6 +180,8 @@ Retrieve all the revisions containing this path (no newer than 'rev'). The result format should be the same as the one of Node.get_history() """ + #for hist in MonotoneNode(self.mtn, rev, path): + # yield hist raise NotImplementedError def normalize_path(self, path): @@ -188,7 +197,7 @@ 'None' is a valid revision value and represents the youngest revision. """ if not rev: - return self.get_youngest_rev() + return self.get_youngest_rev() # fixme: necessary? revs = self.mtn.select(rev) if revs: return revs[0] @@ -257,13 +266,14 @@ if self.isfile: return + def parent(path): + """Returns the name of the directory containing path.""" + path = path and path.rstrip('/') + return path and (path[0:path.rfind('/')] or '/') or None + def ischild(path): - if path.startswith(self.path) and \ - path.rfind('/') <= len(self.path) and \ - path != self.path: - return True - else: - return False + """Returns true, if we are parent of path.""" + return parent(path) == self.path for path in filter(ischild, self.manifest.keys()): yield MonotoneNode(self.mtn, self.rev, path) @@ -279,7 +289,7 @@ """ # fixme!! this is only a stub yield (self.path, self.rev, None) - + def get_properties(self): """ Returns a dictionary containing the properties (meta-data) of the node. @@ -309,13 +319,13 @@ if not self.certs: raise NoSuchChangeset(rev) - # always pick the first self.messages = self.certs.get('changelog', ['-']) self.authors = self.certs.get('author', ['-']) self.dates = mtn.dates(rev) self.branches = self.certs.get('branch', []) self.tags = self.certs.get('tag', []) + # always pick the first message = self.messages[0] author = self.authors[0] date = self.dates[0] @@ -330,7 +340,15 @@ Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or Changeset.MOVE, and kind is one of Node.FILE or Node.DIRECTORY. """ - return self.mtn.changeset(self.rev) + changeset = self.mtn.changeset(self.rev) + for oldrev in changeset: + changes = changeset[oldrev] + for path in changes: + kind, change, opath = changes[path] + if change == 'edit' and path != opath: + # extra entry for the move itself + yield path, kind, 'move', opath, oldrev + yield path, kind, change, opath, oldrev def get_properties(self): """Generator that provides additional metadata for this changeset.