# # # patch "README" # from [edf0d00b38304aec3850810fe01b4e4a422fe748] # to [2e3b139ee66a3c3aff269a7c7fe539883d10a056] # # patch "tracvc/mtn/automate.py" # from [03b65e719913b77116bd86fc7e2845b2afb39b8b] # to [dc3b66c7286e7f39c8dbb07f671ef6cdb0a28066] # # patch "tracvc/mtn/backend.py" # from [15d7b0d4adfc2ff8e9f70890c07b6acf8b7d15c6] # to [9218c82bd3917f1d436f66e145a0249ddc585a5c] # ============================================================ --- README edf0d00b38304aec3850810fe01b4e4a422fe748 +++ README 2e3b139ee66a3c3aff269a7c7fe539883d10a056 @@ -6,7 +6,9 @@ The plugin is currently neither stable nor optimized for high performance. Use at your own risk! Some things work, others don't. + Feedback is welcome! + Prerequisites * A Monotone 0.26 binary. Currently, it is expected in @@ -35,9 +37,10 @@ Known Problems/Missing Features - * Revisions are always printed as complete 40-char string. + * SECURITY: The monotone select functionality is exposed. Selectors + are susceptible for SQL injections. Needs to be fixed. - * Wiki-Link support for tags/branches/... is missing. + * Revisions are almost always printed as complete 40-char string. * Changeset displays don't show attr changes. ============================================================ --- tracvc/mtn/automate.py 03b65e719913b77116bd86fc7e2845b2afb39b8b +++ tracvc/mtn/automate.py dc3b66c7286e7f39c8dbb07f671ef6cdb0a28066 @@ -22,10 +22,15 @@ """ -import os, basic_io, time, calendar +import os, re, time, calendar +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) + + class Connection: """Starts monotone and communicates with it through a pipe.""" @@ -33,7 +38,7 @@ (self.to_child, self.from_child) = os.popen2( "%s --db=%s %s" % (binary, db, cmd), "b") - def read(self, maxlen): + def read(self, maxlen = -1): return self.from_child.read(maxlen) def write(self, data): @@ -43,6 +48,17 @@ return self.to_child.flush() +class List: + """General interface to the 'list' command.""" + def __init__(self, db, binary): + self.db = db + self.binary = binary + + def get_list(self, what): + conn = Connection(self.db, "list " + what, self.binary) + return conn.read() + + class Automate: """General interface to the 'automate stdio' command.""" @@ -93,6 +109,7 @@ def __init__(self, db, log, binary = MT_BINARY): self.automate = Automate(db, binary) + self.list = List(db, binary) self.log = log self.manifest_cache = {} self.certs_cache = {} @@ -104,6 +121,12 @@ if status == 0: return result.splitlines() else: return [] + def heads(self, name): + """Returns a list containing the head revs of branch 'name'.""" + status, result = self.automate.command("heads", name) + if status == 0: return result.splitlines() + else: return [] + def children(self, rev): """Returns a list of the children of rev.""" status, result = self.automate.command("children", rev) @@ -168,13 +191,19 @@ self.manifest_cache[rev] = manifest return manifest + def _u(self, x): + """ + Convert x from utf-8 to unicode, thereby replacing unknown + characters with the unicode replace char + """ + return unicode(x,"utf-8", "replace") + def certs(self, rev): """ Returns a dictionary of certs for rev. There might be more than one cert of the same name, so their values are collected in a list. """ - #print "asking for cert for rev", revb if rev in self.certs_cache: return self.certs_cache[rev] status, result = self.automate.command("certs", rev) @@ -182,8 +211,8 @@ if status != 0: return certs for stanza in basic_io.get_stanzas(result): cert = basic_io.get_hash_from_stanza(stanza) - name = unicode(cert['name'], 'utf-8', 'replace') - value = unicode(cert['value'], 'utf-8', 'replace') + name = self._u(cert['name']) + value = self._u(cert['value']) if not name in certs: certs[name] = [value] else: @@ -243,3 +272,29 @@ changeset.append((path, 'file', 'edit', path, oldrev)) # fixme: add clear and set return changeset + + def branches(self): + """Returns a list of (branch, anyhead) 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 + return branches + + def non_merged_branches(self): + """Returns a list of (branch, rev) tuples for all leave revs.""" + leaves = [] + for leave in self.leaves(): + branches = self.certs(leave)['branch'] + for branch in branches: + leaves.append((branch, leave)) + return leaves + + def tags(self): + """Returns a list of tags and their revs.""" + tags = [] + 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 15d7b0d4adfc2ff8e9f70890c07b6acf8b7d15c6 +++ tracvc/mtn/backend.py 9218c82bd3917f1d436f66e145a0249ddc585a5c @@ -61,14 +61,13 @@ """Add the cset namespace.""" yield('cset', self._format_link) yield('chgset', self._format_link) - yield('branch', self._format_link) # returns head of the branch + yield('branch', self._format_link) # branch head yield('revtag', self._format_link) def _format_link(self, formatter, ns, rev, label): """Format a changeset link.""" - # fixme: add support for selectors repos = self.env.get_repository() - if ns == 'branch': rev = "h:/b:" + rev + if ns == 'branch': rev = "h:" + rev elif ns == 'revtag': rev = "t:" + rev rev = repos.normalize_rev(rev) try: @@ -204,7 +203,21 @@ """ raise NotImplementedError - + def get_tags(self, rev): + """Generate a list of known tags, as (name, rev) pairs. + `rev` might be needed in order to retrieve the tags, + but in general it's best to produce all known tags. + """ + return self.mtn.tags() + + def get_branches(self, rev): + """Generate a list of known branches, as (name, rev) pairs. + `rev` might be needed in order to retrieve the branches, + but in general it's best to produce all known branches. + """ + return self.mtn.non_merged_branches() + + class MonotoneNode(Node): def __init__(self, mtn, rev, path):