# # # patch "tracvc/mtn/automate.py" # from [dc3b66c7286e7f39c8dbb07f671ef6cdb0a28066] # to [a0a5a0fa90e6b72944f93064851ab6d28cea49f4] # # patch "tracvc/mtn/backend.py" # from [9218c82bd3917f1d436f66e145a0249ddc585a5c] # to [5256840759d369117cfb6c04143fe1c35f3192b8] # ============================================================ --- tracvc/mtn/automate.py dc3b66c7286e7f39c8dbb07f671ef6cdb0a28066 +++ tracvc/mtn/automate.py a0a5a0fa90e6b72944f93064851ab6d28cea49f4 @@ -23,14 +23,16 @@ """ import os, re, time, calendar +try: + import threading as _threading +except ImportError: + 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) +REV_RULE = re.compile('^[0-9a-f]{40}$') - - class Connection: """Starts monotone and communicates with it through a pipe.""" @@ -50,6 +52,7 @@ class List: """General interface to the 'list' command.""" + def __init__(self, db, binary): self.db = db self.binary = binary @@ -64,7 +67,8 @@ def __init__(self, db, binary): self.conn = Connection(db, "automate stdio", binary) - + self.lock = _threading.Lock() + def _read_until_colon(self): result = '' while True: @@ -73,8 +77,9 @@ result += char return result - # we must not block, so be careful to read only what's expected def _read_packet(self): + # we must not block, so be careful to read only what's + # expected cmd_nr = self._read_until_colon() status = int(self._read_until_colon()) cont = self._read_until_colon() @@ -83,6 +88,7 @@ return status, cont, val def _get_result(self): + """Read and concatenate the result packages.""" result, cont = ('', '') while cont != 'l': status, cont, val = self._read_packet() @@ -90,6 +96,7 @@ return status, result def _write_cmd(self, cmd, args): + """Assemble command and args and send it to mtn.""" def lstring(str): return "%d:%s" % (len(str), str) cmdstring = "l" + lstring(cmd) @@ -101,8 +108,13 @@ def command(self, cmd, *args): """Send a command to mtn. Returns a tuple (status, result).""" + # critical region: only one thread may send a command and read + # back the result at a time + self.lock.acquire() self._write_cmd(cmd, args) - return self._get_result() + status, result = self._get_result() + self.lock.release() + return status, result class MTN: @@ -160,7 +172,6 @@ def select(self, selector): """Returns a list of revisions selected by the selector.""" - #self.log.debug('select: %s', selector) status, result = self.automate.command("select", selector) if status == 0: return result.splitlines() else: return [] ============================================================ --- tracvc/mtn/backend.py 9218c82bd3917f1d436f66e145a0249ddc585a5c +++ tracvc/mtn/backend.py 5256840759d369117cfb6c04143fe1c35f3192b8 @@ -28,9 +28,7 @@ from trac.core import * from cStringIO import StringIO from automate import MTN -import re -REV = re.compile('^[0-9a-f]{40}$') class MonotoneConnector(Component): @@ -119,15 +117,20 @@ node at that revision is returned, otherwise the latest version of the node is returned. """ - return MonotoneNode(self.mtn, rev, path) + # Note: in an mtn repository, there might be many file + # hierarchies, so it makes no sense to ask for the latest + # version of a path. + return MonotoneNode(self.mtn, rev or self.get_youngest_rev(), path) def get_oldest_rev(self): """ Return the oldest revision stored in the repository. Here: Return the oldest root. """ - revs = dict([(self.mtn.dates(rev)[0], rev) for rev in self.mtn.roots()]) - return revs[sorted(revs.keys())[0]] + roots = dict([(self.mtn.dates(rev)[0], rev) for rev in self.mtn.roots()]) + dates = roots.keys() + dates.sort() + return roots[dates[0]] def get_youngest_rev(self): """ @@ -135,7 +138,9 @@ Here: Return the youngest leave. """ leaves = dict([(self.mtn.dates(rev)[-1], rev) for rev in self.mtn.leaves()]) - return leaves[sorted(leaves.keys())[-1]] + dates = leaves.keys() + dates.sort() + return leaves[dates[-1]] def previous_rev(self, rev): """ @@ -182,9 +187,11 @@ Return a canonical representation of a revision in the repos. 'None' is a valid revision value and represents the youngest revision. """ - if REV.match(rev): return rev - revs = self.mtn.select(rev) - return revs and revs[0] or self.get_youngest_rev() + if rev: + revs = self.mtn.select(rev) + if revs: + return revs[0] + return self.get_youngest_rev() def short_rev(self, rev): """