"""Subversion interface.""" from __future__ import absolute_import import re import attr import blinker import svn.remote import svn.common from . import attrs_extra task_logged = blinker.NamedSignal('task_logged') task_marker_re = re.compile(r'\[(?PT\d+)\]') def obtain(server_location): """Returns a Connection object for the given server location.""" return svn.remote.RemoteClient(server_location) @attr.s class CommitLogObserver(object): svn_client = attr.ib(validator=attr.validators.instance_of(svn.remote.RemoteClient)) last_seen_revision = attr.ib(default=0, validator=attr.validators.instance_of(int)) _log = attrs_extra.log('%s.CommitLogObserver' % __name__) def fetch_and_observe(self): """Obtains task IDs from SVN logs.""" self._log.debug('%s: fetch_and_observe()', self) try: svn_log = self.svn_client.log_default(revision_from=self.last_seen_revision + 1) for log_entry in svn_log: self._log.debug('- %r', log_entry) # assumption: revisions are always logged in strictly increasing order. self.last_seen_revision = log_entry.revision self._parse_log_entry(log_entry) except svn.common.SvnCommandError: # The SVN library just raises a SvnCommandError when something goes wrong, # without any structured indication of the error. There isn't much else # we can do, except to log the error and return. self._log.exception('Error calling self.svn_client.log_default()') return def _parse_log_entry(self, log_entry): """Parses the commit log to see if there are any task markers.""" # Log entries can be None. if not log_entry.msg: return # Parse the commit log to see if there are any task markers. lines = log_entry.msg.split('\n', 1) first_line = lines[0] for match in task_marker_re.finditer(first_line): task_id = match.group('task_id') # Send a Blinker signal for each observed task identifier. task_logged.send(self, task_id=task_id, log_entry=log_entry)