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 |
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 # |
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): |