cobalt.py

changeset 72
2266d6d73de3
parent 67
f8cc57c608e2
child 73
d67cc4fbc3f1
equal deleted inserted replaced
67:f8cc57c608e2 72:2266d6d73de3
35 import re 35 import re
36 import urllib 36 import urllib
37 import urllib2 37 import urllib2
38 import hgapi 38 import hgapi
39 import os 39 import os
40 import suds
41 import math 40 import math
42 import json 41 import json
43 from datetime import datetime 42 from datetime import datetime
44 import modulecore as ModuleCore 43 import modulecore as ModuleCore
45 import configfile 44 import configfile
46 from configfile import Config 45 from configfile import Config
47 46 import hgpoll as HgPoll
48 ModuleCore.init_data() 47 import bt as Bt
49 48
50 try:
51 uid = os.geteuid()
52 except:
53 uid = -1
54
55 if uid == 0 and raw_input ('Do you seriously want to run cobalt as root? [y/N] ') != 'y':
56 quit()
57
58 configfile.init()
59 g_admins = Config.get_value ('admins', default=[])
60 g_mynick = Config.get_value ('nickname', default='cobalt')
61 g_BotActive = False 49 g_BotActive = False
62 g_needCommitsTxtRebuild = True 50
63 51 def main():
64 # 52 ModuleCore.init_data()
65 # SOAP stuff 53
66 # 54 try:
67 suds_active = False 55 uid = os.geteuid()
68 56 except:
69 try: 57 uid = -1
70 print 'Initializing MantisBT connection...' 58
71 suds_import = suds.xsd.doctor.Import ('http://schemas.xmlsoap.org/soap/encoding/', 'http://schemas.xmlsoap.org/soap/encoding/') 59 if uid == 0 and raw_input ('Do you seriously want to run cobalt as root? [y/N] ') != 'y':
72 suds_client = suds.client.Client ('https://zandronum.com/tracker/api/soap/mantisconnect.php?wsdl', plugins=[suds.xsd.doctor.ImportDoctor (suds_import)]) 60 quit()
73 suds_active = True 61
74 except Exception as e: 62 configfile.init()
75 print 'Failed to establish MantisBT connection: ' + `e` 63 g_admins = Config.get_value ('admins', default=[])
76 pass 64 g_mynick = Config.get_value ('nickname', default='cobalt')
77 65
78 btannounce_active = False 66 try:
79 btannounce_timeout = 0 67 autoconnects = Config.get_value ('autoconnect', [])
80 68
81 def bt_updatechecktimeout(): 69 if len (autoconnects) == 0:
82 global btannounce_timeout 70 print "Nowhere to connect."
83 btannounce_timeout = time.time() + (Config.get_node ('bt').get_value ('checkinterval', default=5) * 60) 71 quit()
84 72
85 def bt_credentials(): 73 for aconn in autoconnects:
86 bt = Config.get_node ('bt') 74 for conndata in Config.get_nodelist ('connections'):
87 user = bt.get_value ('username', '') 75 if conndata.get_value ('name') == aconn:
88 password = bt.get_value ('password', '') 76 irc_client (conndata, 0)
89 return [user, password] 77 break
90 78 else:
91 if suds_active: 79 raise ValueError ("unknown autoconnect entry %s" % (aconn))
92 sys.stdout.write ('Retrieving latest tracker ticket... ') 80
93 user, password = bt_credentials() 81 g_BotActive = True
94 btannounce_id = suds_client.service.mc_issue_get_biggest_id (user, password, 0) 82 asyncore.loop()
95 btannounce_active = True 83 except KeyboardInterrupt:
96 bt_updatechecktimeout() 84 for client in all_clients:
97 print btannounce_id 85 client.keyboardinterrupt()
98 86 quit()
99 def bt_getissue(ticket):
100 global suds_client
101 user, password = bt_credentials()
102 return suds_client.service.mc_issue_get (user, password, ticket)
103
104 def bt_checklatest():
105 global btannounce_timeout
106 global btannounce_id
107
108 if time.time() >= btannounce_timeout:
109 bt_updatechecktimeout()
110 newid = btannounce_id
111 try:
112 user, password = bt_credentials()
113 newid = suds_client.service.mc_issue_get_biggest_id (user, password, 0)
114 except Exception as e:
115 pass
116
117 while newid > btannounce_id:
118 try:
119 btannounce_id += 1
120 data = bt_getissue (btannounce_id)
121
122 for client in g_clients:
123 client.announce_ticket (data)
124 except Exception as e:
125 pass
126 87
127 # 88 #
128 # irc_client flags 89 # irc_client flags
129 # 90 #
130 CLIF_CONNECTED = (1 << 1) 91 CLIF_CONNECTED = (1 << 1)
131 92
132 # 93 #
133 # List of all clients 94 # List of all clients
134 # 95 #
135 g_clients = [] 96 all_clients = []
136 97
137 class channel (object): 98 class channel (object):
138 name = "" 99 name = ""
139 password = "" 100 password = ""
140 101
143 104
144 # 105 #
145 # Prints a line to log channel(s) 106 # Prints a line to log channel(s)
146 # 107 #
147 def chanlog (line): 108 def chanlog (line):
148 for client in g_clients: 109 for client in all_clients:
149 if not client.flags & CLIF_CONNECTED: 110 if not client.flags & CLIF_CONNECTED:
150 continue 111 continue
151 112
152 for channel in client.channels: 113 for channel in client.channels:
153 if channel.get_value ('logchannel', default=False): 114 if channel.get_value ('logchannel', default=False):
163 for segment in data: 124 for segment in data:
164 for line in segment.splitlines(): 125 for line in segment.splitlines():
165 print line 126 print line
166 chanlog (line) 127 chanlog (line)
167 128
168 for client in g_clients: 129 for client in all_clients:
169 if len(data) > 0: 130 if len(data) > 0:
170 client.exceptdie() 131 client.exceptdie()
171 else: 132 else:
172 client.quit_irc() 133 client.quit_irc()
173 134
190 151
191 # from http://www.daniweb.com/software-development/python/code/260268/restart-your-python-program 152 # from http://www.daniweb.com/software-development/python/code/260268/restart-your-python-program
192 def restart_self(): 153 def restart_self():
193 python = sys.executable 154 python = sys.executable
194 os.execl (python, python, * sys.argv) 155 os.execl (python, python, * sys.argv)
195
196 def make_commits_txt():
197 global g_needCommitsTxtRebuild
198
199 if g_needCommitsTxtRebuild == False:
200 return
201
202 print 'Building commits.txt...'
203 # Update zandronum-everything
204 repo = hgapi.Repo ('zandronum-everything')
205 repo.hg_command ('pull', '../zandronum-sandbox')
206 repo.hg_command ('pull', '../zandronum-sandbox-stable')
207
208 data = repo.hg_command ('log', '--template', '{node} {date|hgdate}\n')
209
210 f = open ('commits.txt', 'w')
211
212 for line in data.split ('\n'):
213 if line == '':
214 continue
215
216 words = line.split (' ')
217 timestamp = int (words[1])
218 f.write ('%s %s\n' % (words[0], datetime.utcfromtimestamp (timestamp).strftime ('%y%m%d-%H%M')))
219 f.close()
220 g_needCommitsTxtRebuild = False
221 #enddef
222
223 ' Check if a repository exists '
224 def check_repo_exists (repo_name, repo_owner):
225 print 'Checking that %s exists...' % repo_name
226 repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name)
227 zanrepo = hgapi.Repo (repo_name)
228
229 try:
230 zanrepo.hg_command ('id', '.')
231 except hgapi.hgapi.HgException:
232 # If the repo does not exist, clone it. zandronum-everything can be spawned off other repos though
233 if repo_name == 'zandronum-everything':
234 if not os.path.exists (repo_name):
235 os.makedirs (repo_name)
236
237 global g_needCommitsTxtRebuild
238 g_needCommitsTxtRebuild = True
239 print 'Init %s' % repo_name
240 zanrepo.hg_command ('init')
241 print 'Cloning zandronum-sandbox into %s' % repo_name
242 zanrepo.hg_command ('pull', '../zandronum-sandbox')
243 print 'Cloning zandronum-sandbox-stable into %s' % repo_name
244 zanrepo.hg_command ('pull', '../zandronum-sandbox-stable')
245 print 'Done'
246 make_commits_txt()
247 return
248 #fi
249
250 try:
251 print 'Cloning %s...' % repo_name
252 zanrepo.hg_clone (repo_url, repo_name)
253 print 'Cloning done.'
254 except Exception as e:
255 print 'Unable to clone %s from %s: %s' % (repo_name, repo_url, str (`e`))
256 quit(1)
257 #tried
258 #enddef
259
260 check_repo_exists ('zandronum', 'Torr_Samaho')
261 check_repo_exists ('zandronum-stable', 'Torr_Samaho')
262 check_repo_exists ('zandronum-sandbox', 'crimsondusk')
263 check_repo_exists ('zandronum-sandbox-stable', 'crimsondusk')
264 check_repo_exists ('zandronum-everything', '')
265
266 repocheck_timeout = (time.time()) + 15
267
268 def get_commit_data (zanrepo, rev, template):
269 return zanrepo.hg_command ('log', '-l', '1', '-r', rev, '--template', template)
270 #enddef
271
272 def decipher_hgapi_error (e):
273 # Blah, hgapi, why must your error messages be so mangled?
274 try:
275 rawmsg = e.message.replace('\n', '').replace('" +','').replace('\t','')
276 errmsg = re.compile (r'.*: tErr: (.*)Out:.*').match (rawmsg).group (1)
277 return [True, errmsg]
278 except:
279 return [False, '']
280 #endtry
281 #enddef
282
283 def bbcodify(commit_diffstat):
284 result=''
285 for line in commit_diffstat.split('\n'):
286 # Add green color-tags for the ++++++++++ stream
287 rex = re.compile (r'^(.*)\|(.*) (\+*)(-*)(.*)$')
288 match = rex.match (line)
289 if match:
290 line = '%s|%s [color=#5F7]%s[/color][color=#F53]%s[/color]%s\n' \
291 % (match.group (1), match.group (2), match.group (3), match.group (4), match.group (5))
292
293 # Tracker doesn't seem to like empty color tags
294 line = line.replace ('[color=#5F7][/color]', '').replace ('[color=#F53][/color]', '')
295 #else:
296 #rex = re.compile (r'^(.*) ([0-9]+) insertions\(\+\), ([0-9]+) deletions\(\-\)$')
297 #match = rex.match (line)
298 #if match:
299 #line = '%s [b][color=green]%s[/color][/b] insertions, [b][color=red]%s[/color][/b] deletions\n' \
300 #% (match.group (1), match.group (2), match.group (3))
301
302 result += line
303 #done
304
305 return result
306 #enddef
307
308 def find_developer_by_email (commit_email):
309 for developer, emails in Config.get_value ('developer_emails', default={}).iteritems():
310 for email in emails:
311 if commit_email == email:
312 return developer
313 #fi
314 #done
315 #done
316
317 return ''
318 #enddef
319
320 ' Retrieves and processes commits for zandronum repositories '
321 ' Ensure both repositories are OK before using this! '
322 def process_zan_repo_updates():
323 for n in ['zandronum-stable', 'zandronum', 'zandronum-sandbox', 'zandronum-sandbox-stable']:
324 process_one_repo (n)
325
326 def process_one_repo (repo_name):
327 global repocheck_timeout
328 global suds_client
329 global g_clients
330
331 hgns = Config.get_node ('hg')
332
333 if not hgns.get_value ('track', default=True):
334 return
335
336 usestable = repo_name == 'zandronum-stable'
337 usesandbox = repo_name == 'zandronum-sandbox' or repo_name == 'zandronum-sandbox-stable'
338 repo_owner = 'Torr_Samaho' if not usesandbox else 'crimsondusk'
339 repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name)
340 num_commits = 0
341 btuser, btpassword = bt_credentials()
342
343 if time.time() < repocheck_timeout:
344 return
345
346 repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60
347 zanrepo = hgapi.Repo (repo_name)
348 commit_data = []
349 delimeter = '@@@@@@@@@@'
350
351 try:
352 data = zanrepo.hg_command ('incoming', '--quiet', '--template',
353 '{node|short} {desc}' + delimeter)
354 except hgapi.hgapi.HgException as e:
355 deciphered = decipher_hgapi_error (e)
356
357 if deciphered[0] and len(deciphered[1]) > 0:
358 chanlog ("error while using hg import on %s: %s" % (repo_name, deciphered[1]))
359 #fi
360
361 return
362 except Exception as e:
363 chanlog ("%s" % `e`)
364 return
365 #tried
366
367 for line in data.split (delimeter):
368 if line == '':
369 continue
370 #fi
371
372 rex = re.compile (r'([^ ]+) (.+)')
373 match = rex.match (line)
374 failed = False
375
376 if not match:
377 chanlog ('malformed hg data: %s' % line)
378 continue
379 #fi
380
381 commit_node = match.group (1)
382 commit_message = match.group (2)
383 commit_data.append ([commit_node, commit_message])
384 #done
385
386 if len (commit_data) > 0:
387 pull_args = [];
388
389 for commit in commit_data:
390 pull_args.append ('-r');
391 pull_args.append (commit[0]);
392 #done
393
394 try:
395 zanrepo.hg_command ('pull', *pull_args)
396
397 # Also pull these commits to the zandronum main repository
398 if usestable:
399 devrepo = hgapi.Repo ('zandronum')
400 devrepo.hg_command ('pull', '../zandronum-stable', *pull_args)
401 #fi
402
403 # Pull everything into sandboxes too
404 if not usesandbox:
405 devrepo = hgapi.Repo ('zandronum-sandbox')
406 devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args)
407
408 devrepo = hgapi.Repo ('zandronum-sandbox-stable')
409 devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args)
410 #fi
411
412 devrepo = hgapi.Repo ('zandronum-everything')
413 devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args)
414
415 global g_needCommitsTxtRebuild
416 g_needCommitsTxtRebuild = True
417 except Exception as e:
418 chanlog ('Warning: unable to pull: %s' % `e`)
419 return
420 #tried
421 #fi
422
423 for commit in commit_data:
424 commit_node = commit[0]
425 commit_message = commit[1]
426
427 try:
428 if usesandbox:
429 commit_author = get_commit_data (zanrepo, commit_node, '{author}')
430 commit_url = '%s/commits/%s' % (repo_url, commit_node)
431 commit_email = ''
432
433 # Remove the email address from the author if possible
434 rex = re.compile (r'^(.+) <([^>]+)>$.*')
435 match = rex.match (commit_author)
436 if match:
437 commit_author = match.group (1)
438 commit_email = match.group (2)
439 #fi
440
441 commit_trackeruser = find_developer_by_email (commit_email)
442 committer = commit_trackeruser if commit_trackeruser != '' else commit_author
443
444 for irc_client in g_clients:
445 for channel in irc_client.cfg['channels']:
446 if 'btprivate' in channel and channel['btprivate'] == True:
447 irc_client.privmsg (channel['name'],
448 "%s: new commit %s by %s: %s"
449 % (repo_name, commit_node, committer, commit_url))
450
451 for line in commit_message.split ('\n'):
452 irc_client.privmsg (channel['name'], line)
453 #fi
454 #done
455 #done
456
457 num_commits += 1
458 continue
459 #fi
460
461 rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$')
462 match = rex.match (commit_message)
463
464 if not match:
465 continue # no "fixes" message in the commit
466 #fi
467
468 ticket_id = int (match.group (2))
469
470 # Acquire additional data
471 moredata = get_commit_data (zanrepo, commit_node,
472 '{author|nonempty}\n{date(date, \'%A %d %B %Y %T\')}').split('\n')
473
474 if len (moredata) != 2:
475 chanlog ('error while processing %s: malformed hg data' % commit_node)
476 continue
477 #fi
478
479 commit_author = moredata[0]
480 commit_date = moredata[1]
481 commit_email = ""
482
483 try:
484 ticket_data = suds_client.service.mc_issue_get (btuser, btpassword, ticket_id)
485 except Exception as e:
486 chanlog ('error while processing %s: %s' % (commit_node, `e`))
487 continue
488 #tried
489
490 # Remove the email address from the author if possible
491 rex = re.compile (r'^(.+) <([^>]+)>$.*')
492 match = rex.match (commit_author)
493 if match:
494 commit_author = match.group (1)
495 commit_email = match.group (2)
496 #fi
497
498 commit_diffstat = zanrepo.hg_command ('diff', '--change', commit_node, '--stat')
499
500 if len(commit_diffstat) > 0:
501 # commit_diffstat = 'Changes in files:\n[code]\n' + commit_diffstat + '\n[/code]'
502 commit_diffstat = 'Changes in files:\n' + bbcodify(commit_diffstat)
503 else:
504 commit_diffstat = 'No changes in files.'
505
506 # Compare the email addresses against known developer usernames
507 commit_trackeruser = find_developer_by_email (commit_email)
508
509 if commit_trackeruser != '':
510 commit_author += ' [%s]' % commit_trackeruser
511 #fi
512
513 message = 'Issue addressed by commit %s: [b][url=%s/commits/%s]%s[/url][/b]' \
514 % (commit_node, repo_url, commit_node, commit_message)
515 message += "\nCommitted by %s on %s\n\n%s" \
516 % (commit_author, commit_date, commit_diffstat)
517
518 need_update = False
519
520 # If not already set, set handler
521 if not 'handler' in ticket_data:
522 ticket_data['handler'] = {'name': commit_trackeruser}
523 need_update = True
524 #fi
525
526 # Find out the status level of the ticket
527 needs_testing_level = 70
528
529 if ticket_data['status']['id'] < needs_testing_level:
530 ticket_data.status['id'] = needs_testing_level
531 need_update = True
532 #fi
533
534 # Set target version if not set
535 if not 'target_version' in ticket_data:
536 ticket_data['target_version'] = '1.4' if repo_name == 'zandronum-stable' else '2.0'
537 need_update = True
538 elif (ticket_data['target_version'] == '2.0' or ticket_data['target_version'] == '2.0-beta') \
539 and repo_name == 'zandronum-stable':
540 # Target version was 2.0 but this was just committed to zandronum-stable, adjust
541 ticket_data['target_version'] = '1.4'
542 need_update = True
543 elif ticket_data['target_version'] == '2.0-beta':
544 # Fix target version from 2.0-beta to 2.0
545 ticket_data['target_version'] = '2.0'
546 need_update = True
547 #fi
548
549 # Announce on IRC
550 for irc_client in g_clients:
551 for channel in irc_client.channels:
552 if channel.get_value ('btannounce', default=True):
553 irc_client.privmsg (channel.get_value ('name'),
554 "%s: commit %s fixes issue %d: %s"
555 % (repo_name, commit_node, ticket_id, commit_message))
556 irc_client.privmsg (channel.get_value ('name'),
557 "Read all about it here: " + irc_client.get_ticket_url (ticket_id))
558 #fi
559 #done
560 #done
561
562 if need_update:
563 # We need to remove the note data, otherwise the ticket notes
564 # will get unnecessary updates. WTF, MantisBT?
565 ticket_data.notes = []
566 suds_client.service.mc_issue_update (btuser, btpassword, ticket_id, ticket_data)
567 #fi
568
569 suds_client.service.mc_issue_note_add (btuser, btpassword, ticket_id, { 'text': message })
570 num_commits += 1
571 except Exception as e:
572 chanlog ('Error while processing %s: %s' % (commit_node, `e`))
573 continue
574 #tried
575 #done
576 #enddef
577 156
578 def plural (a): 157 def plural (a):
579 return '' if a == 1 else 's' 158 return '' if a == 1 else 's'
580 159
581 # 160 #
595 self.desired_name = Config.get_value ('nickname', default='cobalt') 174 self.desired_name = Config.get_value ('nickname', default='cobalt')
596 self.mynick = self.desired_name 175 self.mynick = self.desired_name
597 self.verbose = Config.get_value ('verbose', default=False) 176 self.verbose = Config.get_value ('verbose', default=False)
598 self.commandprefix = Config.get_value ('commandprefix', default='.') 177 self.commandprefix = Config.get_value ('commandprefix', default='.')
599 178
600 g_clients.append (self) 179 all_clients.append (self)
601 asyncore.dispatcher.__init__ (self) 180 asyncore.dispatcher.__init__ (self)
602 self.create_socket (socket.AF_INET, socket.SOCK_STREAM) 181 self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
603 self.connect ((self.host, self.port)) 182 self.connect ((self.host, self.port))
604 183
605 def register_to_irc (self): 184 def register_to_irc (self):
682 261
683 # Check for new issues on the bugtracker 262 # Check for new issues on the bugtracker
684 bt_checklatest() 263 bt_checklatest()
685 264
686 # Check for new commits in the repositories 265 # Check for new commits in the repositories
687 process_zan_repo_updates() 266 HgPoll.poll()
688 267
689 def channel_by_name (self, name): 268 def channel_by_name (self, name):
690 for channel in self.channels: 269 for channel in self.channels:
691 if channel.get_value ('name').upper() == args[0].upper(): 270 if channel.get_value ('name').upper() == args[0].upper():
692 return channel 271 return channel
727 elif http_match: 306 elif http_match:
728 self.get_ticket_data (replyto, http_match.group (2), False) 307 self.get_ticket_data (replyto, http_match.group (2), False)
729 else: 308 else:
730 chanlog ("Recieved bad PRIVMSG: %s" % line) 309 chanlog ("Recieved bad PRIVMSG: %s" % line)
731 310
732 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
733 #
734 # Get the URL for a specified ticket
735 #
736 def get_ticket_url (self, ticket):
737 url = Config.get_node ('bt').get_value ('url')
738 return 'https://%s/view.php?id=%s' % (url, ticket)
739
740 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
741 #
742 # Retrieve a ticket from mantisbt
743 #
744 def get_ticket_data (self, replyto, ticket, withlink):
745 if suds_active == False:
746 return
747
748 data = {}
749 try:
750 data = bt_getissue (ticket)
751 except Exception, e:
752 self.privmsg (replyto, "Failed to get info for issue %s: %s" % (ticket, `e`))
753
754 if data:
755 if data['view_state']['name'] == 'private':
756 allowprivate = False
757
758 for channel in self.channels:
759 if channel.get_value ('name') == replyto and channel.get_value ('btprivate', False):
760 allowprivate = True
761 break
762 #fi
763 #done
764
765 if not allowprivate:
766 self.privmsg (replyto, 'Error: ticket %s is private' % ticket)
767 return
768 #fi
769 #fi
770
771 self.privmsg (replyto, "Issue %s: %s: Reporter: %s, assigned to: %s, status: %s (%s)" % \
772 (ticket, \
773 data.summary, \
774 data.reporter.name if hasattr (data.reporter, 'name') else "<unknown>", \
775 data.handler.name if hasattr (data, 'handler') else "nobody", \
776 data.status.name, \
777 data.resolution.name))
778
779 if withlink:
780 self.privmsg (replyto, "Read all about it here: " + self.get_ticket_url (ticket))
781 #fi
782 #fi
783 #enddef
784
785 def is_admin (self, ident, host): 311 def is_admin (self, ident, host):
786 return ("%s@%s" % (ident, host)) in g_admins 312 return ("%s@%s" % (ident, host)) in g_admins
787 313
788 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 314 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
789 # 315 #
804 for line in lines[1:]: 330 for line in lines[1:]:
805 self.privmsg (replyto, ' ' + line) 331 self.privmsg (replyto, ' ' + line)
806 return 332 return
807 #tried 333 #tried
808 334
809 if command == 'ticket': 335 if command == 'die':
810 if len(args) != 1:
811 raise logical_exception ("usage: .%s <ticket>" % command)
812 self.get_ticket_data (replyto, args[0], True)
813 elif command == 'testannounce':
814 check_admin (sender, ident, host, command)
815 if len(args) != 1:
816 raise logical_exception ("usage: .%s <ticket>" % command)
817 self.announce_ticket (bt_getissue (args[0]))
818 elif command == 'checkhg':
819 check_admin (sender, ident, host, command)
820 global repocheck_timeout
821 repocheck_timeout = 0
822 process_zan_repo_updates()
823 elif command == 'die':
824 check_admin (sender, ident, host, command) 336 check_admin (sender, ident, host, command)
825 quit() 337 quit()
826 elif command == 'convert': 338 elif command == 'convert':
827 if len(args) != 3 or args[1] != 'as': 339 if len(args) != 3 or args[1] != 'as':
828 raise logical_exception ("usage: .%s <value> as <valuetype>" % command) 340 raise logical_exception ("usage: .%s <value> as <valuetype>" % command)
870 self.privmsg (replyto, "couldn't find a definition of \002%s\002" % args[0]) 382 self.privmsg (replyto, "couldn't find a definition of \002%s\002" % args[0])
871 except logical_exception as e: 383 except logical_exception as e:
872 raise e 384 raise e
873 except Exception as e: 385 except Exception as e:
874 raise logical_exception ('Urban dictionary lookup failed: %s' % `e`) 386 raise logical_exception ('Urban dictionary lookup failed: %s' % `e`)
875 elif command == 'hg':
876 check_admin (sender, ident, host, command)
877
878 if len(args) < 2:
879 raise logical_exception ('usage: %s <repo> <command...>' % command)
880
881 try:
882 repo = hgapi.Repo (args[0])
883 result = repo.hg_command (*args[1:])
884 self.privmsg (replyto, result)
885 except hgapi.hgapi.HgException as e:
886 result = decipher_hgapi_error (e)
887
888 if result[0]:
889 self.privmsg (replyto, 'error: %s' % result[1])
890 else:
891 self.privmsg (replyto, 'error: %s' % `e`)
892 #fi
893 #tried
894 elif command == 'changeset' or command == 'cset' or command == 'rev':
895 if len(args) != 1:
896 raise logical_exception ('usage: %s <changeset>' % command)
897
898 repo = hgapi.Repo ('zandronum-everything')
899 data = ""
900 node = args[0]
901
902 # Possibly we're passed a date version instead. Try find the node for this.
903 try:
904 datetime.strptime (args[0], '%y%m%d-%H%M')
905 make_commits_txt()
906 commits_txt = open ('commits.txt', 'r')
907
908 for line in commits_txt:
909 data = line.replace ('\n', '').split (' ')
910 if data[1] == args[0]:
911 node = data[0]
912 break
913 else:
914 self.privmsg (replyto, 'couldn\'t find changset for date %s' % args[0])
915 return
916 #done
917 except ValueError:
918 pass
919 #tried
920
921 # The sandboxes contain all revisions in zandronum and zandronum-stable.
922 # Thus we only need to try find the revision in the sandbox repos.
923 try:
924 repo.hg_command ("log", "-r", node, "--template", " ")
925 except hgapi.hgapi.HgException:
926 self.privmsg (replyto, 'couldn\'t find changeset %s' % (node))
927 return
928 #tried
929
930 try:
931 data = repo.hg_command ("log", "-r", node, "--template",
932 "{node|short}@@@@@@@{desc}@@@@@@@{author}@@@@@@@{diffstat}@@@@@@@{date|hgdate}")
933 data = data.split ('@@@@@@@')
934
935 node = data[0]
936 message = data[1]
937 author = data[2]
938 diffstat = data[3]
939 date = datetime.utcfromtimestamp (int (data[4].split (' ')[0]))
940 delta = datetime.now() - date
941 datestring = ''
942
943 # Remove the email address from the author if possible
944 match = re.compile (r'^(.+) <([^>]+)>$.*').match (author)
945 if match:
946 author = match.group (1)
947 email = match.group (2)
948
949 username = find_developer_by_email (email)
950
951 if username != '':
952 author = username
953
954 if delta.days < 4:
955 if delta.days == 0:
956 if delta.seconds < 60:
957 datestring = 'just now'
958 elif delta.seconds < 3600:
959 minutes = delta.seconds / 60
960 datestring = '%d minute%s ago' % (minutes, plural (minutes))
961 else:
962 hours = delta.seconds / 3600
963 datestring = '%d hour%s ago' % (hours, plural (hours))
964 else:
965 datestring = '%d day%s ago' % (delta.days, plural (delta.days))
966 else:
967 datestring = 'on %s' % (str (date))
968 #fi
969
970 self.privmsg (replyto, 'changeset %s (%s): committed by %s %s (%s)' % \
971 (node, date.strftime ('%y%m%d-%H%M'), author, datestring, diffstat))
972
973 for line in message.split ('\n'):
974 self.privmsg (replyto, ' ' + line)
975 except hgapi.hgapi.HgException as e:
976 result = decipher_hgapi_error (e)
977
978 if result[0]:
979 self.privmsg (replyto, 'error: %s' % result[1])
980 else:
981 self.privmsg (replyto, 'error: %s' % `e`)
982 #tried
983 # else: 387 # else:
984 # raise logical_exception ("unknown command `.%s`" % command) 388 # raise logical_exception ("unknown command `.%s`" % command)
985
986 #
987 # Print a ticket announce to appropriate channels
988 #
989 def announce_ticket (self, data):
990 idstring = "%d" % data.id
991 while len(idstring) < 7:
992 idstring = "0" + idstring
993
994 isprivate = data['view_state']['name'] == 'private'
995 reporter = data['reporter']['name'] if hasattr (data['reporter'], 'name') else '<nobody>'
996
997 for channel in self.channels:
998 if channel.get_value ('btannounce', False):
999 if not isprivate or (channel.get_value ('btprivate', False)):
1000 self.write ("PRIVMSG %s :[%s] New issue %s, reported by %s: %s: %s" % \
1001 (channel['name'], data['project']['name'], idstring, reporter,
1002 data['summary'], self.get_ticket_url (idstring)))
1003 #fi
1004 #fi
1005 #done
1006 389
1007 def handle_error(self): 390 def handle_error(self):
1008 excepterm (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) 391 excepterm (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
1009 392
1010 def restart(self): 393 def restart(self):
1026 self.close_connection ('Caught exception') 409 self.close_connection ('Caught exception')
1027 410
1028 def keyboardinterrupt (self): 411 def keyboardinterrupt (self):
1029 self.close_connection ('KeyboardInterrupt') 412 self.close_connection ('KeyboardInterrupt')
1030 413
1031 # 414 if __name__ == '__main__':
1032 # Main procedure: 415 main()
1033 #
1034 try:
1035 autoconnects = Config.get_value ('autoconnect', [])
1036
1037 if len (autoconnects) == 0:
1038 print "Nowhere to connect."
1039 quit()
1040
1041 for aconn in autoconnects:
1042 for conndata in Config.get_nodelist ('connections'):
1043 if conndata.get_value ('name') == aconn:
1044 irc_client (conndata, 0)
1045 break
1046 else:
1047 raise ValueError ("unknown autoconnect entry %s" % (aconn))
1048
1049 g_BotActive = True
1050 asyncore.loop()
1051 except KeyboardInterrupt:
1052 for client in g_clients:
1053 client.keyboardinterrupt()
1054 quit()

mercurial