# HG changeset patch # User Teemu Piippo # Date 1415555950 -7200 # Node ID 2266d6d73de35c8c1b9754eeabea9520c685a0e3 # Parent f8cc57c608e2e5af58d28124962f28b6616c3efd - commit work done on splitting bt/hg diff -r f8cc57c608e2 -r 2266d6d73de3 bt.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bt.py Sun Nov 09 19:59:10 2014 +0200 @@ -0,0 +1,91 @@ +import suds +import cobalt + +suds_active = False +btannounce_active = False +btannounce_timeout = 0 + +def is_active(): + return suds_active + +def init(): + try: + print 'Initializing MantisBT connection...' + suds_import = suds.xsd.doctor.Import ('http://schemas.xmlsoap.org/soap/encoding/', 'http://schemas.xmlsoap.org/soap/encoding/') + suds_client = suds.client.Client ('https://zandronum.com/tracker/api/soap/mantisconnect.php?wsdl', plugins=[suds.xsd.doctor.ImportDoctor (suds_import)]) + suds_active = True + except Exception as e: + print 'Failed to establish MantisBT connection: ' + `e` + pass + + if suds_active: + sys.stdout.write ('Retrieving latest tracker ticket... ') + user, password = bt_credentials() + btannounce_id = suds_client.service.mc_issue_get_biggest_id (user, password, 0) + btannounce_active = True + bt_updatechecktimeout() + print btannounce_id + +def update_checktimeout(): + global btannounce_timeout + btannounce_timeout = time.time() + (Config.get_node ('bt').get_value ('checkinterval', default=5) * 60) + +def credentials(): + bt = Config.get_node ('bt') + user = bt.get_value ('username', '') + password = bt.get_value ('password', '') + return [user, password] + +def get_issue(ticket): + global suds_client + user, password = bt_credentials() + return suds_client.service.mc_issue_get (user, password, ticket) + +def poll(): + global btannounce_timeout + global btannounce_id + + if time.time() >= btannounce_timeout: + bt_updatechecktimeout() + newid = btannounce_id + try: + user, password = bt_credentials() + newid = suds_client.service.mc_issue_get_biggest_id (user, password, 0) + except Exception as e: + pass + + while newid > btannounce_id: + try: + btannounce_id += 1 + data = bt_getissue (btannounce_id) + + for client in cobalt.all_clients: + announce_new_ticket (client, data) + except Exception as e: + pass + +def get_ticket_url (ticket): + url = Config.get_node ('bt').get_value ('url') + return 'https://%s/view.php?id=%s' % (url, ticket) + + +# +# Print a ticket announce to appropriate channels +# +def announce_new_issue (bot, data): + idstring = "%d" % data.id + while len(idstring) < 7: + idstring = "0" + idstring + + isprivate = data['view_state']['name'] == 'private' + reporter = data['reporter']['name'] if hasattr (data['reporter'], 'name') else '' + + for channel in self.channels: + if channel.get_value ('btannounce', False): + if not isprivate or (channel.get_value ('btprivate', False)): + self.write ("PRIVMSG %s :[%s] New issue %s, reported by %s: %s: %s" % \ + (channel['name'], data['project']['name'], idstring, reporter, + data['summary'], self.get_ticket_url (idstring))) + #fi + #fi + #done diff -r f8cc57c608e2 -r 2266d6d73de3 cobalt.py --- a/cobalt.py Sun Nov 09 19:18:58 2014 +0200 +++ b/cobalt.py Sun Nov 09 19:59:10 2014 +0200 @@ -37,92 +37,53 @@ import urllib2 import hgapi import os -import suds import math import json from datetime import datetime import modulecore as ModuleCore import configfile from configfile import Config - -ModuleCore.init_data() - -try: - uid = os.geteuid() -except: - uid = -1 +import hgpoll as HgPoll +import bt as Bt -if uid == 0 and raw_input ('Do you seriously want to run cobalt as root? [y/N] ') != 'y': - quit() +g_BotActive = False -configfile.init() -g_admins = Config.get_value ('admins', default=[]) -g_mynick = Config.get_value ('nickname', default='cobalt') -g_BotActive = False -g_needCommitsTxtRebuild = True +def main(): + ModuleCore.init_data() -# -# SOAP stuff -# -suds_active = False + try: + uid = os.geteuid() + except: + uid = -1 -try: - print 'Initializing MantisBT connection...' - suds_import = suds.xsd.doctor.Import ('http://schemas.xmlsoap.org/soap/encoding/', 'http://schemas.xmlsoap.org/soap/encoding/') - suds_client = suds.client.Client ('https://zandronum.com/tracker/api/soap/mantisconnect.php?wsdl', plugins=[suds.xsd.doctor.ImportDoctor (suds_import)]) - suds_active = True -except Exception as e: - print 'Failed to establish MantisBT connection: ' + `e` - pass - -btannounce_active = False -btannounce_timeout = 0 - -def bt_updatechecktimeout(): - global btannounce_timeout - btannounce_timeout = time.time() + (Config.get_node ('bt').get_value ('checkinterval', default=5) * 60) + if uid == 0 and raw_input ('Do you seriously want to run cobalt as root? [y/N] ') != 'y': + quit() -def bt_credentials(): - bt = Config.get_node ('bt') - user = bt.get_value ('username', '') - password = bt.get_value ('password', '') - return [user, password] - -if suds_active: - sys.stdout.write ('Retrieving latest tracker ticket... ') - user, password = bt_credentials() - btannounce_id = suds_client.service.mc_issue_get_biggest_id (user, password, 0) - btannounce_active = True - bt_updatechecktimeout() - print btannounce_id - -def bt_getissue(ticket): - global suds_client - user, password = bt_credentials() - return suds_client.service.mc_issue_get (user, password, ticket) - -def bt_checklatest(): - global btannounce_timeout - global btannounce_id - - if time.time() >= btannounce_timeout: - bt_updatechecktimeout() - newid = btannounce_id - try: - user, password = bt_credentials() - newid = suds_client.service.mc_issue_get_biggest_id (user, password, 0) - except Exception as e: - pass - - while newid > btannounce_id: - try: - btannounce_id += 1 - data = bt_getissue (btannounce_id) - - for client in g_clients: - client.announce_ticket (data) - except Exception as e: - pass + configfile.init() + g_admins = Config.get_value ('admins', default=[]) + g_mynick = Config.get_value ('nickname', default='cobalt') + + try: + autoconnects = Config.get_value ('autoconnect', []) + + if len (autoconnects) == 0: + print "Nowhere to connect." + quit() + + for aconn in autoconnects: + for conndata in Config.get_nodelist ('connections'): + if conndata.get_value ('name') == aconn: + irc_client (conndata, 0) + break + else: + raise ValueError ("unknown autoconnect entry %s" % (aconn)) + + g_BotActive = True + asyncore.loop() + except KeyboardInterrupt: + for client in all_clients: + client.keyboardinterrupt() + quit() # # irc_client flags @@ -132,7 +93,7 @@ # # List of all clients # -g_clients = [] +all_clients = [] class channel (object): name = "" @@ -145,7 +106,7 @@ # Prints a line to log channel(s) # def chanlog (line): - for client in g_clients: + for client in all_clients: if not client.flags & CLIF_CONNECTED: continue @@ -165,7 +126,7 @@ print line chanlog (line) - for client in g_clients: + for client in all_clients: if len(data) > 0: client.exceptdie() else: @@ -193,388 +154,6 @@ python = sys.executable os.execl (python, python, * sys.argv) -def make_commits_txt(): - global g_needCommitsTxtRebuild - - if g_needCommitsTxtRebuild == False: - return - - print 'Building commits.txt...' - # Update zandronum-everything - repo = hgapi.Repo ('zandronum-everything') - repo.hg_command ('pull', '../zandronum-sandbox') - repo.hg_command ('pull', '../zandronum-sandbox-stable') - - data = repo.hg_command ('log', '--template', '{node} {date|hgdate}\n') - - f = open ('commits.txt', 'w') - - for line in data.split ('\n'): - if line == '': - continue - - words = line.split (' ') - timestamp = int (words[1]) - f.write ('%s %s\n' % (words[0], datetime.utcfromtimestamp (timestamp).strftime ('%y%m%d-%H%M'))) - f.close() - g_needCommitsTxtRebuild = False -#enddef - -' Check if a repository exists ' -def check_repo_exists (repo_name, repo_owner): - print 'Checking that %s exists...' % repo_name - repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name) - zanrepo = hgapi.Repo (repo_name) - - try: - zanrepo.hg_command ('id', '.') - except hgapi.hgapi.HgException: - # If the repo does not exist, clone it. zandronum-everything can be spawned off other repos though - if repo_name == 'zandronum-everything': - if not os.path.exists (repo_name): - os.makedirs (repo_name) - - global g_needCommitsTxtRebuild - g_needCommitsTxtRebuild = True - print 'Init %s' % repo_name - zanrepo.hg_command ('init') - print 'Cloning zandronum-sandbox into %s' % repo_name - zanrepo.hg_command ('pull', '../zandronum-sandbox') - print 'Cloning zandronum-sandbox-stable into %s' % repo_name - zanrepo.hg_command ('pull', '../zandronum-sandbox-stable') - print 'Done' - make_commits_txt() - return - #fi - - try: - print 'Cloning %s...' % repo_name - zanrepo.hg_clone (repo_url, repo_name) - print 'Cloning done.' - except Exception as e: - print 'Unable to clone %s from %s: %s' % (repo_name, repo_url, str (`e`)) - quit(1) - #tried -#enddef - -check_repo_exists ('zandronum', 'Torr_Samaho') -check_repo_exists ('zandronum-stable', 'Torr_Samaho') -check_repo_exists ('zandronum-sandbox', 'crimsondusk') -check_repo_exists ('zandronum-sandbox-stable', 'crimsondusk') -check_repo_exists ('zandronum-everything', '') - -repocheck_timeout = (time.time()) + 15 - -def get_commit_data (zanrepo, rev, template): - return zanrepo.hg_command ('log', '-l', '1', '-r', rev, '--template', template) -#enddef - -def decipher_hgapi_error (e): - # Blah, hgapi, why must your error messages be so mangled? - try: - rawmsg = e.message.replace('\n', '').replace('" +','').replace('\t','') - errmsg = re.compile (r'.*: tErr: (.*)Out:.*').match (rawmsg).group (1) - return [True, errmsg] - except: - return [False, ''] - #endtry -#enddef - -def bbcodify(commit_diffstat): - result='' - for line in commit_diffstat.split('\n'): - # Add green color-tags for the ++++++++++ stream - rex = re.compile (r'^(.*)\|(.*) (\+*)(-*)(.*)$') - match = rex.match (line) - if match: - line = '%s|%s [color=#5F7]%s[/color][color=#F53]%s[/color]%s\n' \ - % (match.group (1), match.group (2), match.group (3), match.group (4), match.group (5)) - - # Tracker doesn't seem to like empty color tags - line = line.replace ('[color=#5F7][/color]', '').replace ('[color=#F53][/color]', '') - #else: - #rex = re.compile (r'^(.*) ([0-9]+) insertions\(\+\), ([0-9]+) deletions\(\-\)$') - #match = rex.match (line) - #if match: - #line = '%s [b][color=green]%s[/color][/b] insertions, [b][color=red]%s[/color][/b] deletions\n' \ - #% (match.group (1), match.group (2), match.group (3)) - - result += line - #done - - return result -#enddef - -def find_developer_by_email (commit_email): - for developer, emails in Config.get_value ('developer_emails', default={}).iteritems(): - for email in emails: - if commit_email == email: - return developer - #fi - #done - #done - - return '' -#enddef - -' Retrieves and processes commits for zandronum repositories ' -' Ensure both repositories are OK before using this! ' -def process_zan_repo_updates(): - for n in ['zandronum-stable', 'zandronum', 'zandronum-sandbox', 'zandronum-sandbox-stable']: - process_one_repo (n) - -def process_one_repo (repo_name): - global repocheck_timeout - global suds_client - global g_clients - - hgns = Config.get_node ('hg') - - if not hgns.get_value ('track', default=True): - return - - usestable = repo_name == 'zandronum-stable' - usesandbox = repo_name == 'zandronum-sandbox' or repo_name == 'zandronum-sandbox-stable' - repo_owner = 'Torr_Samaho' if not usesandbox else 'crimsondusk' - repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name) - num_commits = 0 - btuser, btpassword = bt_credentials() - - if time.time() < repocheck_timeout: - return - - repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60 - zanrepo = hgapi.Repo (repo_name) - commit_data = [] - delimeter = '@@@@@@@@@@' - - try: - data = zanrepo.hg_command ('incoming', '--quiet', '--template', - '{node|short} {desc}' + delimeter) - except hgapi.hgapi.HgException as e: - deciphered = decipher_hgapi_error (e) - - if deciphered[0] and len(deciphered[1]) > 0: - chanlog ("error while using hg import on %s: %s" % (repo_name, deciphered[1])) - #fi - - return - except Exception as e: - chanlog ("%s" % `e`) - return - #tried - - for line in data.split (delimeter): - if line == '': - continue - #fi - - rex = re.compile (r'([^ ]+) (.+)') - match = rex.match (line) - failed = False - - if not match: - chanlog ('malformed hg data: %s' % line) - continue - #fi - - commit_node = match.group (1) - commit_message = match.group (2) - commit_data.append ([commit_node, commit_message]) - #done - - if len (commit_data) > 0: - pull_args = []; - - for commit in commit_data: - pull_args.append ('-r'); - pull_args.append (commit[0]); - #done - - try: - zanrepo.hg_command ('pull', *pull_args) - - # Also pull these commits to the zandronum main repository - if usestable: - devrepo = hgapi.Repo ('zandronum') - devrepo.hg_command ('pull', '../zandronum-stable', *pull_args) - #fi - - # Pull everything into sandboxes too - if not usesandbox: - devrepo = hgapi.Repo ('zandronum-sandbox') - devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) - - devrepo = hgapi.Repo ('zandronum-sandbox-stable') - devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) - #fi - - devrepo = hgapi.Repo ('zandronum-everything') - devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) - - global g_needCommitsTxtRebuild - g_needCommitsTxtRebuild = True - except Exception as e: - chanlog ('Warning: unable to pull: %s' % `e`) - return - #tried - #fi - - for commit in commit_data: - commit_node = commit[0] - commit_message = commit[1] - - try: - if usesandbox: - commit_author = get_commit_data (zanrepo, commit_node, '{author}') - commit_url = '%s/commits/%s' % (repo_url, commit_node) - commit_email = '' - - # Remove the email address from the author if possible - rex = re.compile (r'^(.+) <([^>]+)>$.*') - match = rex.match (commit_author) - if match: - commit_author = match.group (1) - commit_email = match.group (2) - #fi - - commit_trackeruser = find_developer_by_email (commit_email) - committer = commit_trackeruser if commit_trackeruser != '' else commit_author - - for irc_client in g_clients: - for channel in irc_client.cfg['channels']: - if 'btprivate' in channel and channel['btprivate'] == True: - irc_client.privmsg (channel['name'], - "%s: new commit %s by %s: %s" - % (repo_name, commit_node, committer, commit_url)) - - for line in commit_message.split ('\n'): - irc_client.privmsg (channel['name'], line) - #fi - #done - #done - - num_commits += 1 - continue - #fi - - rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$') - match = rex.match (commit_message) - - if not match: - continue # no "fixes" message in the commit - #fi - - ticket_id = int (match.group (2)) - - # Acquire additional data - moredata = get_commit_data (zanrepo, commit_node, - '{author|nonempty}\n{date(date, \'%A %d %B %Y %T\')}').split('\n') - - if len (moredata) != 2: - chanlog ('error while processing %s: malformed hg data' % commit_node) - continue - #fi - - commit_author = moredata[0] - commit_date = moredata[1] - commit_email = "" - - try: - ticket_data = suds_client.service.mc_issue_get (btuser, btpassword, ticket_id) - except Exception as e: - chanlog ('error while processing %s: %s' % (commit_node, `e`)) - continue - #tried - - # Remove the email address from the author if possible - rex = re.compile (r'^(.+) <([^>]+)>$.*') - match = rex.match (commit_author) - if match: - commit_author = match.group (1) - commit_email = match.group (2) - #fi - - commit_diffstat = zanrepo.hg_command ('diff', '--change', commit_node, '--stat') - - if len(commit_diffstat) > 0: - # commit_diffstat = 'Changes in files:\n[code]\n' + commit_diffstat + '\n[/code]' - commit_diffstat = 'Changes in files:\n' + bbcodify(commit_diffstat) - else: - commit_diffstat = 'No changes in files.' - - # Compare the email addresses against known developer usernames - commit_trackeruser = find_developer_by_email (commit_email) - - if commit_trackeruser != '': - commit_author += ' [%s]' % commit_trackeruser - #fi - - message = 'Issue addressed by commit %s: [b][url=%s/commits/%s]%s[/url][/b]' \ - % (commit_node, repo_url, commit_node, commit_message) - message += "\nCommitted by %s on %s\n\n%s" \ - % (commit_author, commit_date, commit_diffstat) - - need_update = False - - # If not already set, set handler - if not 'handler' in ticket_data: - ticket_data['handler'] = {'name': commit_trackeruser} - need_update = True - #fi - - # Find out the status level of the ticket - needs_testing_level = 70 - - if ticket_data['status']['id'] < needs_testing_level: - ticket_data.status['id'] = needs_testing_level - need_update = True - #fi - - # Set target version if not set - if not 'target_version' in ticket_data: - ticket_data['target_version'] = '1.4' if repo_name == 'zandronum-stable' else '2.0' - need_update = True - elif (ticket_data['target_version'] == '2.0' or ticket_data['target_version'] == '2.0-beta') \ - and repo_name == 'zandronum-stable': - # Target version was 2.0 but this was just committed to zandronum-stable, adjust - ticket_data['target_version'] = '1.4' - need_update = True - elif ticket_data['target_version'] == '2.0-beta': - # Fix target version from 2.0-beta to 2.0 - ticket_data['target_version'] = '2.0' - need_update = True - #fi - - # Announce on IRC - for irc_client in g_clients: - for channel in irc_client.channels: - if channel.get_value ('btannounce', default=True): - irc_client.privmsg (channel.get_value ('name'), - "%s: commit %s fixes issue %d: %s" - % (repo_name, commit_node, ticket_id, commit_message)) - irc_client.privmsg (channel.get_value ('name'), - "Read all about it here: " + irc_client.get_ticket_url (ticket_id)) - #fi - #done - #done - - if need_update: - # We need to remove the note data, otherwise the ticket notes - # will get unnecessary updates. WTF, MantisBT? - ticket_data.notes = [] - suds_client.service.mc_issue_update (btuser, btpassword, ticket_id, ticket_data) - #fi - - suds_client.service.mc_issue_note_add (btuser, btpassword, ticket_id, { 'text': message }) - num_commits += 1 - except Exception as e: - chanlog ('Error while processing %s: %s' % (commit_node, `e`)) - continue - #tried - #done -#enddef - def plural (a): return '' if a == 1 else 's' @@ -597,7 +176,7 @@ self.verbose = Config.get_value ('verbose', default=False) self.commandprefix = Config.get_value ('commandprefix', default='.') - g_clients.append (self) + all_clients.append (self) asyncore.dispatcher.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) self.connect ((self.host, self.port)) @@ -684,7 +263,7 @@ bt_checklatest() # Check for new commits in the repositories - process_zan_repo_updates() + HgPoll.poll() def channel_by_name (self, name): for channel in self.channels: @@ -729,59 +308,6 @@ else: chanlog ("Recieved bad PRIVMSG: %s" % line) - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - # - # Get the URL for a specified ticket - # - def get_ticket_url (self, ticket): - url = Config.get_node ('bt').get_value ('url') - return 'https://%s/view.php?id=%s' % (url, ticket) - - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # - # - # Retrieve a ticket from mantisbt - # - def get_ticket_data (self, replyto, ticket, withlink): - if suds_active == False: - return - - data = {} - try: - data = bt_getissue (ticket) - except Exception, e: - self.privmsg (replyto, "Failed to get info for issue %s: %s" % (ticket, `e`)) - - if data: - if data['view_state']['name'] == 'private': - allowprivate = False - - for channel in self.channels: - if channel.get_value ('name') == replyto and channel.get_value ('btprivate', False): - allowprivate = True - break - #fi - #done - - if not allowprivate: - self.privmsg (replyto, 'Error: ticket %s is private' % ticket) - return - #fi - #fi - - self.privmsg (replyto, "Issue %s: %s: Reporter: %s, assigned to: %s, status: %s (%s)" % \ - (ticket, \ - data.summary, \ - data.reporter.name if hasattr (data.reporter, 'name') else "", \ - data.handler.name if hasattr (data, 'handler') else "nobody", \ - data.status.name, \ - data.resolution.name)) - - if withlink: - self.privmsg (replyto, "Read all about it here: " + self.get_ticket_url (ticket)) - #fi - #fi - #enddef - def is_admin (self, ident, host): return ("%s@%s" % (ident, host)) in g_admins @@ -806,21 +332,7 @@ return #tried - if command == 'ticket': - if len(args) != 1: - raise logical_exception ("usage: .%s " % command) - self.get_ticket_data (replyto, args[0], True) - elif command == 'testannounce': - check_admin (sender, ident, host, command) - if len(args) != 1: - raise logical_exception ("usage: .%s " % command) - self.announce_ticket (bt_getissue (args[0])) - elif command == 'checkhg': - check_admin (sender, ident, host, command) - global repocheck_timeout - repocheck_timeout = 0 - process_zan_repo_updates() - elif command == 'die': + if command == 'die': check_admin (sender, ident, host, command) quit() elif command == 'convert': @@ -872,138 +384,9 @@ raise e except Exception as e: raise logical_exception ('Urban dictionary lookup failed: %s' % `e`) - elif command == 'hg': - check_admin (sender, ident, host, command) - - if len(args) < 2: - raise logical_exception ('usage: %s ' % command) - - try: - repo = hgapi.Repo (args[0]) - result = repo.hg_command (*args[1:]) - self.privmsg (replyto, result) - except hgapi.hgapi.HgException as e: - result = decipher_hgapi_error (e) - - if result[0]: - self.privmsg (replyto, 'error: %s' % result[1]) - else: - self.privmsg (replyto, 'error: %s' % `e`) - #fi - #tried - elif command == 'changeset' or command == 'cset' or command == 'rev': - if len(args) != 1: - raise logical_exception ('usage: %s ' % command) - - repo = hgapi.Repo ('zandronum-everything') - data = "" - node = args[0] - - # Possibly we're passed a date version instead. Try find the node for this. - try: - datetime.strptime (args[0], '%y%m%d-%H%M') - make_commits_txt() - commits_txt = open ('commits.txt', 'r') - - for line in commits_txt: - data = line.replace ('\n', '').split (' ') - if data[1] == args[0]: - node = data[0] - break - else: - self.privmsg (replyto, 'couldn\'t find changset for date %s' % args[0]) - return - #done - except ValueError: - pass - #tried - - # The sandboxes contain all revisions in zandronum and zandronum-stable. - # Thus we only need to try find the revision in the sandbox repos. - try: - repo.hg_command ("log", "-r", node, "--template", " ") - except hgapi.hgapi.HgException: - self.privmsg (replyto, 'couldn\'t find changeset %s' % (node)) - return - #tried - - try: - data = repo.hg_command ("log", "-r", node, "--template", - "{node|short}@@@@@@@{desc}@@@@@@@{author}@@@@@@@{diffstat}@@@@@@@{date|hgdate}") - data = data.split ('@@@@@@@') - - node = data[0] - message = data[1] - author = data[2] - diffstat = data[3] - date = datetime.utcfromtimestamp (int (data[4].split (' ')[0])) - delta = datetime.now() - date - datestring = '' - - # Remove the email address from the author if possible - match = re.compile (r'^(.+) <([^>]+)>$.*').match (author) - if match: - author = match.group (1) - email = match.group (2) - - username = find_developer_by_email (email) - - if username != '': - author = username - - if delta.days < 4: - if delta.days == 0: - if delta.seconds < 60: - datestring = 'just now' - elif delta.seconds < 3600: - minutes = delta.seconds / 60 - datestring = '%d minute%s ago' % (minutes, plural (minutes)) - else: - hours = delta.seconds / 3600 - datestring = '%d hour%s ago' % (hours, plural (hours)) - else: - datestring = '%d day%s ago' % (delta.days, plural (delta.days)) - else: - datestring = 'on %s' % (str (date)) - #fi - - self.privmsg (replyto, 'changeset %s (%s): committed by %s %s (%s)' % \ - (node, date.strftime ('%y%m%d-%H%M'), author, datestring, diffstat)) - - for line in message.split ('\n'): - self.privmsg (replyto, ' ' + line) - except hgapi.hgapi.HgException as e: - result = decipher_hgapi_error (e) - - if result[0]: - self.privmsg (replyto, 'error: %s' % result[1]) - else: - self.privmsg (replyto, 'error: %s' % `e`) - #tried # else: # raise logical_exception ("unknown command `.%s`" % command) - # - # Print a ticket announce to appropriate channels - # - def announce_ticket (self, data): - idstring = "%d" % data.id - while len(idstring) < 7: - idstring = "0" + idstring - - isprivate = data['view_state']['name'] == 'private' - reporter = data['reporter']['name'] if hasattr (data['reporter'], 'name') else '' - - for channel in self.channels: - if channel.get_value ('btannounce', False): - if not isprivate or (channel.get_value ('btprivate', False)): - self.write ("PRIVMSG %s :[%s] New issue %s, reported by %s: %s: %s" % \ - (channel['name'], data['project']['name'], idstring, reporter, - data['summary'], self.get_ticket_url (idstring))) - #fi - #fi - #done - def handle_error(self): excepterm (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) @@ -1028,27 +411,5 @@ def keyboardinterrupt (self): self.close_connection ('KeyboardInterrupt') -# -# Main procedure: -# -try: - autoconnects = Config.get_value ('autoconnect', []) - - if len (autoconnects) == 0: - print "Nowhere to connect." - quit() - - for aconn in autoconnects: - for conndata in Config.get_nodelist ('connections'): - if conndata.get_value ('name') == aconn: - irc_client (conndata, 0) - break - else: - raise ValueError ("unknown autoconnect entry %s" % (aconn)) - - g_BotActive = True - asyncore.loop() -except KeyboardInterrupt: - for client in g_clients: - client.keyboardinterrupt() - quit() +if __name__ == '__main__': + main() \ No newline at end of file diff -r f8cc57c608e2 -r 2266d6d73de3 hgpoll.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgpoll.py Sun Nov 09 19:59:10 2014 +0200 @@ -0,0 +1,388 @@ +impot hgapi +from configfile import Config +g_needCommitsTxtRebuild = True + +def make_commits_txt(): + global g_needCommitsTxtRebuild + + if g_needCommitsTxtRebuild == False: + return + + print 'Building commits.txt...' + # Update zandronum-everything + repo = hgapi.Repo ('zandronum-everything') + repo.hg_command ('pull', '../zandronum-sandbox') + repo.hg_command ('pull', '../zandronum-sandbox-stable') + + data = repo.hg_command ('log', '--template', '{node} {date|hgdate}\n') + + f = open ('commits.txt', 'w') + + for line in data.split ('\n'): + if line == '': + continue + + words = line.split (' ') + timestamp = int (words[1]) + f.write ('%s %s\n' % (words[0], datetime.utcfromtimestamp (timestamp).strftime ('%y%m%d-%H%M'))) + f.close() + g_needCommitsTxtRebuild = False +#enddef + +' Check if a repository exists ' +def check_repo_exists (repo_name, repo_owner): + print 'Checking that %s exists...' % repo_name + repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name) + zanrepo = hgapi.Repo (repo_name) + + try: + zanrepo.hg_command ('id', '.') + except hgapi.hgapi.HgException: + # If the repo does not exist, clone it. zandronum-everything can be spawned off other repos though + if repo_name == 'zandronum-everything': + if not os.path.exists (repo_name): + os.makedirs (repo_name) + + global g_needCommitsTxtRebuild + g_needCommitsTxtRebuild = True + print 'Init %s' % repo_name + zanrepo.hg_command ('init') + print 'Cloning zandronum-sandbox into %s' % repo_name + zanrepo.hg_command ('pull', '../zandronum-sandbox') + print 'Cloning zandronum-sandbox-stable into %s' % repo_name + zanrepo.hg_command ('pull', '../zandronum-sandbox-stable') + print 'Done' + make_commits_txt() + return + #fi + + try: + print 'Cloning %s...' % repo_name + zanrepo.hg_clone (repo_url, repo_name) + print 'Cloning done.' + except Exception as e: + print 'Unable to clone %s from %s: %s' % (repo_name, repo_url, str (`e`)) + quit(1) + #tried +#enddef + +check_repo_exists ('zandronum', 'Torr_Samaho') +check_repo_exists ('zandronum-stable', 'Torr_Samaho') +check_repo_exists ('zandronum-sandbox', 'crimsondusk') +check_repo_exists ('zandronum-sandbox-stable', 'crimsondusk') +check_repo_exists ('zandronum-everything', '') + +repocheck_timeout = (time.time()) + 15 + +def get_commit_data (zanrepo, rev, template): + return zanrepo.hg_command ('log', '-l', '1', '-r', rev, '--template', template) +#enddef + +def decipher_hgapi_error (e): + # Blah, hgapi, why must your error messages be so mangled? + try: + rawmsg = e.message.replace('\n', '').replace('" +','').replace('\t','') + errmsg = re.compile (r'.*: tErr: (.*)Out:.*').match (rawmsg).group (1) + return [True, errmsg] + except: + return [False, ''] + #endtry +#enddef + +def bbcodify(commit_diffstat): + result='' + for line in commit_diffstat.split('\n'): + # Add green color-tags for the ++++++++++ stream + rex = re.compile (r'^(.*)\|(.*) (\+*)(-*)(.*)$') + match = rex.match (line) + if match: + line = '%s|%s [color=#5F7]%s[/color][color=#F53]%s[/color]%s\n' \ + % (match.group (1), match.group (2), match.group (3), match.group (4), match.group (5)) + + # Tracker doesn't seem to like empty color tags + line = line.replace ('[color=#5F7][/color]', '').replace ('[color=#F53][/color]', '') + #else: + #rex = re.compile (r'^(.*) ([0-9]+) insertions\(\+\), ([0-9]+) deletions\(\-\)$') + #match = rex.match (line) + #if match: + #line = '%s [b][color=green]%s[/color][/b] insertions, [b][color=red]%s[/color][/b] deletions\n' \ + #% (match.group (1), match.group (2), match.group (3)) + + result += line + #done + + return result +#enddef + +def find_developer_by_email (commit_email): + for developer, emails in Config.get_value ('developer_emails', default={}).iteritems(): + for email in emails: + if commit_email == email: + return developer + #fi + #done + #done + + return '' +#enddef + +' Retrieves and processes commits for zandronum repositories ' +' Ensure both repositories are OK before using this! ' +def poll(): + for n in ['zandronum-stable', 'zandronum', 'zandronum-sandbox', 'zandronum-sandbox-stable']: + process_one_repo (n) + +def process_one_repo (repo_name): + global repocheck_timeout + global g_clients + + hgns = Config.get_node ('hg') + + if not hgns.get_value ('track', default=True): + return + + usestable = repo_name == 'zandronum-stable' + usesandbox = repo_name == 'zandronum-sandbox' or repo_name == 'zandronum-sandbox-stable' + repo_owner = 'Torr_Samaho' if not usesandbox else 'crimsondusk' + repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name) + num_commits = 0 + btuser, btpassword = bt_credentials() + + if time.time() < repocheck_timeout: + return + + repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60 + zanrepo = hgapi.Repo (repo_name) + commit_data = [] + delimeter = '@@@@@@@@@@' + + try: + data = zanrepo.hg_command ('incoming', '--quiet', '--template', + '{node|short} {desc}' + delimeter) + except hgapi.hgapi.HgException as e: + deciphered = decipher_hgapi_error (e) + + if deciphered[0] and len(deciphered[1]) > 0: + chanlog ("error while using hg import on %s: %s" % (repo_name, deciphered[1])) + #fi + + return + except Exception as e: + chanlog ("%s" % `e`) + return + #tried + + for line in data.split (delimeter): + if line == '': + continue + #fi + + rex = re.compile (r'([^ ]+) (.+)') + match = rex.match (line) + failed = False + + if not match: + chanlog ('malformed hg data: %s' % line) + continue + #fi + + commit_node = match.group (1) + commit_message = match.group (2) + commit_data.append ([commit_node, commit_message]) + #done + + if len (commit_data) > 0: + pull_args = []; + + for commit in commit_data: + pull_args.append ('-r'); + pull_args.append (commit[0]); + #done + + try: + zanrepo.hg_command ('pull', *pull_args) + + # Also pull these commits to the zandronum main repository + if usestable: + devrepo = hgapi.Repo ('zandronum') + devrepo.hg_command ('pull', '../zandronum-stable', *pull_args) + #fi + + # Pull everything into sandboxes too + if not usesandbox: + devrepo = hgapi.Repo ('zandronum-sandbox') + devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) + + devrepo = hgapi.Repo ('zandronum-sandbox-stable') + devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) + #fi + + devrepo = hgapi.Repo ('zandronum-everything') + devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) + + global g_needCommitsTxtRebuild + g_needCommitsTxtRebuild = True + except Exception as e: + chanlog ('Warning: unable to pull: %s' % `e`) + return + #tried + #fi + + for commit in commit_data: + commit_node = commit[0] + commit_message = commit[1] + + try: + if usesandbox: + commit_author = get_commit_data (zanrepo, commit_node, '{author}') + commit_url = '%s/commits/%s' % (repo_url, commit_node) + commit_email = '' + + # Remove the email address from the author if possible + rex = re.compile (r'^(.+) <([^>]+)>$.*') + match = rex.match (commit_author) + if match: + commit_author = match.group (1) + commit_email = match.group (2) + #fi + + commit_trackeruser = find_developer_by_email (commit_email) + committer = commit_trackeruser if commit_trackeruser != '' else commit_author + + for irc_client in g_clients: + for channel in irc_client.cfg['channels']: + if 'btprivate' in channel and channel['btprivate'] == True: + irc_client.privmsg (channel['name'], + "%s: new commit %s by %s: %s" + % (repo_name, commit_node, committer, commit_url)) + + for line in commit_message.split ('\n'): + irc_client.privmsg (channel['name'], line) + #fi + #done + #done + + num_commits += 1 + continue + #fi + + rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$') + match = rex.match (commit_message) + + if not match: + continue # no "fixes" message in the commit + #fi + + ticket_id = int (match.group (2)) + + # Acquire additional data + moredata = get_commit_data (zanrepo, commit_node, + '{author|nonempty}\n{date(date, \'%A %d %B %Y %T\')}').split('\n') + + if len (moredata) != 2: + chanlog ('error while processing %s: malformed hg data' % commit_node) + continue + #fi + + commit_author = moredata[0] + commit_date = moredata[1] + commit_email = "" + + try: + ticket_data = suds_client.service.mc_issue_get (btuser, btpassword, ticket_id) + except Exception as e: + chanlog ('error while processing %s: %s' % (commit_node, `e`)) + continue + #tried + + # Remove the email address from the author if possible + rex = re.compile (r'^(.+) <([^>]+)>$.*') + match = rex.match (commit_author) + if match: + commit_author = match.group (1) + commit_email = match.group (2) + #fi + + commit_diffstat = zanrepo.hg_command ('diff', '--change', commit_node, '--stat') + + if len(commit_diffstat) > 0: + # commit_diffstat = 'Changes in files:\n[code]\n' + commit_diffstat + '\n[/code]' + commit_diffstat = 'Changes in files:\n' + bbcodify(commit_diffstat) + else: + commit_diffstat = 'No changes in files.' + + # Compare the email addresses against known developer usernames + commit_trackeruser = find_developer_by_email (commit_email) + + if commit_trackeruser != '': + commit_author += ' [%s]' % commit_trackeruser + #fi + + message = 'Issue addressed by commit %s: [b][url=%s/commits/%s]%s[/url][/b]' \ + % (commit_node, repo_url, commit_node, commit_message) + message += "\nCommitted by %s on %s\n\n%s" \ + % (commit_author, commit_date, commit_diffstat) + + need_update = False + + # If not already set, set handler + if not 'handler' in ticket_data: + ticket_data['handler'] = {'name': commit_trackeruser} + need_update = True + #fi + + # Find out the status level of the ticket + needs_testing_level = 70 + + if ticket_data['status']['id'] < needs_testing_level: + ticket_data.status['id'] = needs_testing_level + need_update = True + #fi + + # Set target version if not set + if not 'target_version' in ticket_data: + ticket_data['target_version'] = '1.4' if repo_name == 'zandronum-stable' else '2.0' + need_update = True + elif (ticket_data['target_version'] == '2.0' or ticket_data['target_version'] == '2.0-beta') \ + and repo_name == 'zandronum-stable': + # Target version was 2.0 but this was just committed to zandronum-stable, adjust + ticket_data['target_version'] = '1.4' + need_update = True + elif ticket_data['target_version'] == '2.0-beta': + # Fix target version from 2.0-beta to 2.0 + ticket_data['target_version'] = '2.0' + need_update = True + #fi + + # Announce on IRC + for irc_client in g_clients: + for channel in irc_client.channels: + if channel.get_value ('btannounce', default=True): + irc_client.privmsg (channel.get_value ('name'), + "%s: commit %s fixes issue %d: %s" + % (repo_name, commit_node, ticket_id, commit_message)) + irc_client.privmsg (channel.get_value ('name'), + "Read all about it here: " + irc_client.get_ticket_url (ticket_id)) + #fi + #done + #done + + if need_update: + # We need to remove the note data, otherwise the ticket notes + # will get unnecessary updates. WTF, MantisBT? + ticket_data.notes = [] + suds_client.service.mc_issue_update (btuser, btpassword, ticket_id, ticket_data) + #fi + + suds_client.service.mc_issue_note_add (btuser, btpassword, ticket_id, { 'text': message }) + num_commits += 1 + except Exception as e: + chanlog ('Error while processing %s: %s' % (commit_node, `e`)) + continue + #tried + #done +#enddef + +def force_poll(): + repocheck_timeout = 0 + poll() \ No newline at end of file diff -r f8cc57c608e2 -r 2266d6d73de3 mod_bt.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_bt.py Sun Nov 09 19:59:10 2014 +0200 @@ -0,0 +1,71 @@ +import bt as Bt + +ModuleData = { + 'commands': + [ + { + 'name': 'ticket', + 'description': 'Gets ticket info', + 'args': '', + 'level': 'normal', + }, + + { + 'name': 'testannounce', + 'description': 'Tests the ticket announcer', + 'args': ' ', + 'level': 'admin', + }, + ] +} + +def get_ticket_data (bot, replyto, ticket, withlink): + if suds_active == False: + return + + data = {} + try: + data = bt_getissue (ticket) + except Exception, e: + bot.privmsg (replyto, "Failed to get info for issue %s: %s" % (ticket, `e`)) + + if data: + if data['view_state']['name'] == 'private': + allowprivate = False + + for channel in bot.channels: + if channel.get_value ('name') == replyto and channel.get_value ('btprivate', False): + allowprivate = True + break + #fi + #done + + if not allowprivate: + bot.privmsg (replyto, 'Error: ticket %s is private' % ticket) + return + #fi + #fi + + bot.privmsg (replyto, "Issue %s: %s: Reporter: %s, assigned to: %s, status: %s (%s)" % \ + (ticket, \ + data.summary, \ + data.reporter.name if hasattr (data.reporter, 'name') else "", \ + data.handler.name if hasattr (data, 'handler') else "nobody", \ + data.status.name, \ + data.resolution.name)) + + if withlink: + bot.privmsg (replyto, "Read all about it here: " + bot.get_ticket_url (ticket)) + #fi + #fi +#enddef + +def cmd_ticket (bot, args, **rest): + Bt.get_ticket_data (bot, replyto, args['ticket'], True) + +def cmd_testannounce (bot, args, **rest); +elif command == 'testannounce': + check_admin (sender, ident, host, command) + if len(args) != 1: + raise logical_exception ("usage: .%s " % command) + self.announce_ticket (bt_getissue (args[0])) \ No newline at end of file diff -r f8cc57c608e2 -r 2266d6d73de3 mod_hgpoll.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_hgpoll.py Sun Nov 09 19:59:10 2014 +0200 @@ -0,0 +1,131 @@ +from hgapi import hgapi, Repo +import hgpoll as HgPoll + +ModuleData = { + 'commands': + [ + { + 'name': 'checkhg', + 'description': 'Polls the zandronum repositories for updates', + 'args': None, + 'level': 'admin', + }, + { + 'name': 'cset', + 'description': 'Yields changeset information (use a hash or date as key)', + 'args': '', + 'level': 'normal', + }, + { + 'name': 'hg', + 'description': 'Executes a hg command', + 'args': ' ', + 'level': 'admin', + } + ] +} + +def cmd_checkhg (bot, **rest): + HgPoll.force_poll() + +def cmd_cset (bot, args, **rest) + repo = Repo ('zandronum-everything') + data = "" + node = args['key'] + + # Possibly we're passed a date version instead. Try find the node for this. + try: + datetime.strptime (args['key'], '%y%m%d-%H%M') + make_commits_txt() + commits_txt = open ('commits.txt', 'r') + + for line in commits_txt: + data = line.replace ('\n', '').split (' ') + if data[1] == args['key']: + node = data[0] + break + else: + bot.privmsg (replyto, 'couldn\'t find changset for date %s' % args['key']) + return + #done + except ValueError: + pass + #tried + + # The sandboxes contain all revisions in zandronum and zandronum-stable. + # Thus we only need to try find the revision in the sandbox repos. + try: + repo.hg_command ("log", "-r", node, "--template", " ") + except hgapi.HgException: + bot.privmsg (replyto, 'couldn\'t find changeset %s' % (node)) + return + #tried + + try: + data = repo.hg_command ("log", "-r", node, "--template", + "{node|short}@@@@@@@{desc}@@@@@@@{author}@@@@@@@{diffstat}@@@@@@@{date|hgdate}") + data = data.split ('@@@@@@@') + + node = data[0] + message = data[1] + author = data[2] + diffstat = data[3] + date = datetime.utcfromtimestamp (int (data[4].split (' ')[0])) + delta = datetime.now() - date + datestring = '' + + # Remove the email address from the author if possible + match = re.compile (r'^(.+) <([^>]+)>$.*').match (author) + if match: + author = match.group (1) + email = match.group (2) + + username = find_developer_by_email (email) + + if username != '': + author = username + + if delta.days < 4: + if delta.days == 0: + if delta.seconds < 60: + datestring = 'just now' + elif delta.seconds < 3600: + minutes = delta.seconds / 60 + datestring = '%d minute%s ago' % (minutes, plural (minutes)) + else: + hours = delta.seconds / 3600 + datestring = '%d hour%s ago' % (hours, plural (hours)) + else: + datestring = '%d day%s ago' % (delta.days, plural (delta.days)) + else: + datestring = 'on %s' % (str (date)) + #fi + + bot.privmsg (replyto, 'changeset %s (%s): committed by %s %s (%s)' % \ + (node, date.strftime ('%y%m%d-%H%M'), author, datestring, diffstat)) + + for line in message.split ('\n'): + bot.privmsg (replyto, ' ' + line) + except hgapi.HgException as e: + result = HgPoll.decipher_hgapi_error (e) + + if result[0]: + bot.privmsg (replyto, 'error: %s' % result[1]) + else: + bot.privmsg (replyto, 'error: %s' % `e`) + #tried + +def cmd_hg (bot, args, **rest): + try: + repo = hgapi.Repo (args['repo']) + result = repo.hg_command (*args['command']) + self.privmsg (replyto, result) + except hgapi.hgapi.HgException as e: + result = HgPoll.decipher_hgapi_error (e) + + if result[0]: + self.privmsg (replyto, 'error: %s' % result[1]) + else: + self.privmsg (replyto, 'error: %s' % `e`) + #fi + #tried \ No newline at end of file