diff --git a/gateway/cia-smtp.py b/gateway/cia-smtp.py index 4014db7..7cfb595 100755 --- a/gateway/cia-smtp.py +++ b/gateway/cia-smtp.py @@ -1,136 +1,141 @@ #!/usr/bin/python3 # CIA.vc to Irker gateway, SMTP interface # Portions copied from KDE Git Hooks (git.kde.org:repo-management/hooks/hooklib.py) import sys, os, socket, re import configparser import email.parser from lxml import etree from itertools import takewhile try: import simplejson as json except ImportError: import json class Message: def __init__(self, xmlPayload): # Store this for later self._xmlRoot = xmlPayload # These attributes are troublesome - they are only available with Git try: self.project = self._xmlRoot.xpath('/message/source/project')[0].text.strip() self.repository = self._xmlRoot.xpath('/message/source/module')[0].text.strip() self.branch = self._xmlRoot.xpath('/message/source/branch')[0].text.strip() except: self.project = "KDE" self.repository = "SVN" self.branch = "" # Load all the attributes in self.authorName = self._xmlRoot.xpath('/message/body/commit/author')[0].text.strip() self.commitDescription = self._xmlRoot.xpath('/message/body/commit/revision')[0].text.strip() self.commitMessage = self._xmlRoot.xpath('/message/body/commit/log')[0].text.strip() self.commitUrl = self._xmlRoot.xpath('/message/body/commit/url')[0].text.strip() self.affectedFiles = [entry.text for entry in self._xmlRoot.xpath('/message/body/commit/files/file')] # Build a CIA style files/folders affected string self.fileSummary = self.generateFileSummary() # Trim the commit message splittedMessage = self.commitMessage.split('\n') self.commitMessage = '\n'.join(splittedMessage[:5]) def generateFileSummary(self): # Get a list of affected directories (based upon the affected files) affectedDirs = list( set([os.path.dirname(filename) for filename in self.affectedFiles]) ) # Maybe nothing is affected (as is the case in merges)? if len(self.affectedFiles) == 0: return "" # If only a single file is affected, just name that single file explicitly if len(self.affectedFiles) == 1: return self.affectedFiles[0] # We now need the lowest common path to continue, so determine it if len(affectedDirs) == 1: lowest_common_path = affectedDirs[0] else: # This works on path segments rather than char-by-char as os.path.commonprefix does # and hence avoids problems when multiple directories at the same level start with # the same sequence of characters. by_levels = zip( *[p.split(os.path.sep) for p in affectedDirs] ) equal = lambda name: all( n == name[0] for n in name[1:] ) lowest_common_path = os.path.sep.join(x[0] for x in takewhile( equal, by_levels )) # Generate an appropriate message if len(affectedDirs) == 1: return "{0} ({1} files)".format(lowest_common_path, len(self.affectedFiles)) else: return "{0} ({1} files in {2} dirs)".format(lowest_common_path, len(self.affectedFiles), len(affectedDirs)) def summary(self): # If it is SVN we use a different template (otherwise it looks really silly) if self.repository == 'SVN': template = '\x033%(authorName)s\x03 * \x0312r%(commitDescription)s\x03 %(fileSummary)s\n%(commitMessage)s\n%(commitUrl)s' else: template = '\x02%(repository)s\x02 \x035(%(branch)s)\x03 \x0312%(commitDescription)s\x03 * \x033%(authorName)s\x03: %(fileSummary)s\n%(commitMessage)s\n%(commitUrl)s' return template % self.__dict__ # Load our configuration configDefaults = {'project': 'KDE', 'repository': '.*', 'branch': '.*', 'directory': '.*', 'notify_silent': "True"} config = configparser.SafeConfigParser(configDefaults) config.read(sys.argv[1]) # Read the email in emailParser = email.parser.Parser() email = emailParser.parse(sys.stdin) # Ensure it is a CIA.vc formatted mail if email['Subject'] != 'DeliverXML': exit(0) # Load the XML and instantiate the Message instance which will make the information useful xmlRoot = etree.fromstring(email.get_payload(decode=True)) message = Message(xmlRoot) # Is the commit a silenced one? commit_silent = re.search("^(?:CVS|SVN|GIT|SCM).?SILENT$", message.commitMessage, re.MULTILINE) # Determine the channels we want to notify channels = ["irc://chat.freenode.net/commits"] for sectionName in config.sections(): # Try to compile the regexes - if it fails, continue on... try: projectName = re.compile( config.get(sectionName, 'project') ) repoName = re.compile( config.get(sectionName, 'repository') ) branchName = re.compile( config.get(sectionName, 'branch') ) dirName = re.compile( config.get(sectionName, 'directory') ) except: continue if not config.has_option(sectionName, 'channel'): continue # If the commit is marked as silent, and channel is set to not recieve silent commits, skip if not config.getboolean(sectionName, 'notify_silent') and commit_silent: continue if not projectName.match(message.project): continue - if not repoName.match(message.repository): + # repoName could be 'plasma/breeze' while the config regex only looks for 'breeze'; + # make a shortRepoName stripping everything before the slash and match that. + # But it's also possible that repoName is 'websites/edu-kde-org' + # and the regex expects the websites/ part, so we need to try both names. + shortRepoName = re.sub('^.*/', '', repoName) + if not (shortRepoName.match(message.repository) or repoName.match(message.repository)): continue if not branchName.match(message.branch): continue if not any(dirName.match(filename) for filename in message.affectedFiles): continue channelName = config.get(sectionName, 'channel') if channelName not in channels: channels.append(channelName) # Convert our information into what Irker needs, and send it to Irker content = json.dumps({"to": channels, "privmsg": message.summary()}) + "\n" connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connection.connect(("localhost", 6659)) connection.sendall(content.encode('utf-8'))