| |
1 impot hgapi |
| |
2 from configfile import Config |
| |
3 g_needCommitsTxtRebuild = True |
| |
4 |
| |
5 def make_commits_txt(): |
| |
6 global g_needCommitsTxtRebuild |
| |
7 |
| |
8 if g_needCommitsTxtRebuild == False: |
| |
9 return |
| |
10 |
| |
11 print 'Building commits.txt...' |
| |
12 # Update zandronum-everything |
| |
13 repo = hgapi.Repo ('zandronum-everything') |
| |
14 repo.hg_command ('pull', '../zandronum-sandbox') |
| |
15 repo.hg_command ('pull', '../zandronum-sandbox-stable') |
| |
16 |
| |
17 data = repo.hg_command ('log', '--template', '{node} {date|hgdate}\n') |
| |
18 |
| |
19 f = open ('commits.txt', 'w') |
| |
20 |
| |
21 for line in data.split ('\n'): |
| |
22 if line == '': |
| |
23 continue |
| |
24 |
| |
25 words = line.split (' ') |
| |
26 timestamp = int (words[1]) |
| |
27 f.write ('%s %s\n' % (words[0], datetime.utcfromtimestamp (timestamp).strftime ('%y%m%d-%H%M'))) |
| |
28 f.close() |
| |
29 g_needCommitsTxtRebuild = False |
| |
30 #enddef |
| |
31 |
| |
32 ' Check if a repository exists ' |
| |
33 def check_repo_exists (repo_name, repo_owner): |
| |
34 print 'Checking that %s exists...' % repo_name |
| |
35 repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name) |
| |
36 zanrepo = hgapi.Repo (repo_name) |
| |
37 |
| |
38 try: |
| |
39 zanrepo.hg_command ('id', '.') |
| |
40 except hgapi.hgapi.HgException: |
| |
41 # If the repo does not exist, clone it. zandronum-everything can be spawned off other repos though |
| |
42 if repo_name == 'zandronum-everything': |
| |
43 if not os.path.exists (repo_name): |
| |
44 os.makedirs (repo_name) |
| |
45 |
| |
46 global g_needCommitsTxtRebuild |
| |
47 g_needCommitsTxtRebuild = True |
| |
48 print 'Init %s' % repo_name |
| |
49 zanrepo.hg_command ('init') |
| |
50 print 'Cloning zandronum-sandbox into %s' % repo_name |
| |
51 zanrepo.hg_command ('pull', '../zandronum-sandbox') |
| |
52 print 'Cloning zandronum-sandbox-stable into %s' % repo_name |
| |
53 zanrepo.hg_command ('pull', '../zandronum-sandbox-stable') |
| |
54 print 'Done' |
| |
55 make_commits_txt() |
| |
56 return |
| |
57 #fi |
| |
58 |
| |
59 try: |
| |
60 print 'Cloning %s...' % repo_name |
| |
61 zanrepo.hg_clone (repo_url, repo_name) |
| |
62 print 'Cloning done.' |
| |
63 except Exception as e: |
| |
64 print 'Unable to clone %s from %s: %s' % (repo_name, repo_url, str (`e`)) |
| |
65 quit(1) |
| |
66 #tried |
| |
67 #enddef |
| |
68 |
| |
69 check_repo_exists ('zandronum', 'Torr_Samaho') |
| |
70 check_repo_exists ('zandronum-stable', 'Torr_Samaho') |
| |
71 check_repo_exists ('zandronum-sandbox', 'crimsondusk') |
| |
72 check_repo_exists ('zandronum-sandbox-stable', 'crimsondusk') |
| |
73 check_repo_exists ('zandronum-everything', '') |
| |
74 |
| |
75 repocheck_timeout = (time.time()) + 15 |
| |
76 |
| |
77 def get_commit_data (zanrepo, rev, template): |
| |
78 return zanrepo.hg_command ('log', '-l', '1', '-r', rev, '--template', template) |
| |
79 #enddef |
| |
80 |
| |
81 def decipher_hgapi_error (e): |
| |
82 # Blah, hgapi, why must your error messages be so mangled? |
| |
83 try: |
| |
84 rawmsg = e.message.replace('\n', '').replace('" +','').replace('\t','') |
| |
85 errmsg = re.compile (r'.*: tErr: (.*)Out:.*').match (rawmsg).group (1) |
| |
86 return [True, errmsg] |
| |
87 except: |
| |
88 return [False, ''] |
| |
89 #endtry |
| |
90 #enddef |
| |
91 |
| |
92 def bbcodify(commit_diffstat): |
| |
93 result='' |
| |
94 for line in commit_diffstat.split('\n'): |
| |
95 # Add green color-tags for the ++++++++++ stream |
| |
96 rex = re.compile (r'^(.*)\|(.*) (\+*)(-*)(.*)$') |
| |
97 match = rex.match (line) |
| |
98 if match: |
| |
99 line = '%s|%s [color=#5F7]%s[/color][color=#F53]%s[/color]%s\n' \ |
| |
100 % (match.group (1), match.group (2), match.group (3), match.group (4), match.group (5)) |
| |
101 |
| |
102 # Tracker doesn't seem to like empty color tags |
| |
103 line = line.replace ('[color=#5F7][/color]', '').replace ('[color=#F53][/color]', '') |
| |
104 #else: |
| |
105 #rex = re.compile (r'^(.*) ([0-9]+) insertions\(\+\), ([0-9]+) deletions\(\-\)$') |
| |
106 #match = rex.match (line) |
| |
107 #if match: |
| |
108 #line = '%s [b][color=green]%s[/color][/b] insertions, [b][color=red]%s[/color][/b] deletions\n' \ |
| |
109 #% (match.group (1), match.group (2), match.group (3)) |
| |
110 |
| |
111 result += line |
| |
112 #done |
| |
113 |
| |
114 return result |
| |
115 #enddef |
| |
116 |
| |
117 def find_developer_by_email (commit_email): |
| |
118 for developer, emails in Config.get_value ('developer_emails', default={}).iteritems(): |
| |
119 for email in emails: |
| |
120 if commit_email == email: |
| |
121 return developer |
| |
122 #fi |
| |
123 #done |
| |
124 #done |
| |
125 |
| |
126 return '' |
| |
127 #enddef |
| |
128 |
| |
129 ' Retrieves and processes commits for zandronum repositories ' |
| |
130 ' Ensure both repositories are OK before using this! ' |
| |
131 def poll(): |
| |
132 for n in ['zandronum-stable', 'zandronum', 'zandronum-sandbox', 'zandronum-sandbox-stable']: |
| |
133 process_one_repo (n) |
| |
134 |
| |
135 def process_one_repo (repo_name): |
| |
136 global repocheck_timeout |
| |
137 global g_clients |
| |
138 |
| |
139 hgns = Config.get_node ('hg') |
| |
140 |
| |
141 if not hgns.get_value ('track', default=True): |
| |
142 return |
| |
143 |
| |
144 usestable = repo_name == 'zandronum-stable' |
| |
145 usesandbox = repo_name == 'zandronum-sandbox' or repo_name == 'zandronum-sandbox-stable' |
| |
146 repo_owner = 'Torr_Samaho' if not usesandbox else 'crimsondusk' |
| |
147 repo_url = 'https://bitbucket.org/%s/%s' % (repo_owner, repo_name) |
| |
148 num_commits = 0 |
| |
149 btuser, btpassword = bt_credentials() |
| |
150 |
| |
151 if time.time() < repocheck_timeout: |
| |
152 return |
| |
153 |
| |
154 repocheck_timeout = time.time() + hgns.get_value ('checkinterval', default=15) * 60 |
| |
155 zanrepo = hgapi.Repo (repo_name) |
| |
156 commit_data = [] |
| |
157 delimeter = '@@@@@@@@@@' |
| |
158 |
| |
159 try: |
| |
160 data = zanrepo.hg_command ('incoming', '--quiet', '--template', |
| |
161 '{node|short} {desc}' + delimeter) |
| |
162 except hgapi.hgapi.HgException as e: |
| |
163 deciphered = decipher_hgapi_error (e) |
| |
164 |
| |
165 if deciphered[0] and len(deciphered[1]) > 0: |
| |
166 chanlog ("error while using hg import on %s: %s" % (repo_name, deciphered[1])) |
| |
167 #fi |
| |
168 |
| |
169 return |
| |
170 except Exception as e: |
| |
171 chanlog ("%s" % `e`) |
| |
172 return |
| |
173 #tried |
| |
174 |
| |
175 for line in data.split (delimeter): |
| |
176 if line == '': |
| |
177 continue |
| |
178 #fi |
| |
179 |
| |
180 rex = re.compile (r'([^ ]+) (.+)') |
| |
181 match = rex.match (line) |
| |
182 failed = False |
| |
183 |
| |
184 if not match: |
| |
185 chanlog ('malformed hg data: %s' % line) |
| |
186 continue |
| |
187 #fi |
| |
188 |
| |
189 commit_node = match.group (1) |
| |
190 commit_message = match.group (2) |
| |
191 commit_data.append ([commit_node, commit_message]) |
| |
192 #done |
| |
193 |
| |
194 if len (commit_data) > 0: |
| |
195 pull_args = []; |
| |
196 |
| |
197 for commit in commit_data: |
| |
198 pull_args.append ('-r'); |
| |
199 pull_args.append (commit[0]); |
| |
200 #done |
| |
201 |
| |
202 try: |
| |
203 zanrepo.hg_command ('pull', *pull_args) |
| |
204 |
| |
205 # Also pull these commits to the zandronum main repository |
| |
206 if usestable: |
| |
207 devrepo = hgapi.Repo ('zandronum') |
| |
208 devrepo.hg_command ('pull', '../zandronum-stable', *pull_args) |
| |
209 #fi |
| |
210 |
| |
211 # Pull everything into sandboxes too |
| |
212 if not usesandbox: |
| |
213 devrepo = hgapi.Repo ('zandronum-sandbox') |
| |
214 devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) |
| |
215 |
| |
216 devrepo = hgapi.Repo ('zandronum-sandbox-stable') |
| |
217 devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) |
| |
218 #fi |
| |
219 |
| |
220 devrepo = hgapi.Repo ('zandronum-everything') |
| |
221 devrepo.hg_command ('pull', '../%s' % repo_name, *pull_args) |
| |
222 |
| |
223 global g_needCommitsTxtRebuild |
| |
224 g_needCommitsTxtRebuild = True |
| |
225 except Exception as e: |
| |
226 chanlog ('Warning: unable to pull: %s' % `e`) |
| |
227 return |
| |
228 #tried |
| |
229 #fi |
| |
230 |
| |
231 for commit in commit_data: |
| |
232 commit_node = commit[0] |
| |
233 commit_message = commit[1] |
| |
234 |
| |
235 try: |
| |
236 if usesandbox: |
| |
237 commit_author = get_commit_data (zanrepo, commit_node, '{author}') |
| |
238 commit_url = '%s/commits/%s' % (repo_url, commit_node) |
| |
239 commit_email = '' |
| |
240 |
| |
241 # Remove the email address from the author if possible |
| |
242 rex = re.compile (r'^(.+) <([^>]+)>$.*') |
| |
243 match = rex.match (commit_author) |
| |
244 if match: |
| |
245 commit_author = match.group (1) |
| |
246 commit_email = match.group (2) |
| |
247 #fi |
| |
248 |
| |
249 commit_trackeruser = find_developer_by_email (commit_email) |
| |
250 committer = commit_trackeruser if commit_trackeruser != '' else commit_author |
| |
251 |
| |
252 for irc_client in g_clients: |
| |
253 for channel in irc_client.cfg['channels']: |
| |
254 if 'btprivate' in channel and channel['btprivate'] == True: |
| |
255 irc_client.privmsg (channel['name'], |
| |
256 "%s: new commit %s by %s: %s" |
| |
257 % (repo_name, commit_node, committer, commit_url)) |
| |
258 |
| |
259 for line in commit_message.split ('\n'): |
| |
260 irc_client.privmsg (channel['name'], line) |
| |
261 #fi |
| |
262 #done |
| |
263 #done |
| |
264 |
| |
265 num_commits += 1 |
| |
266 continue |
| |
267 #fi |
| |
268 |
| |
269 rex = re.compile (r'^.*(fixes|resolves|addresses|should fix) ([0-9]+).*$') |
| |
270 match = rex.match (commit_message) |
| |
271 |
| |
272 if not match: |
| |
273 continue # no "fixes" message in the commit |
| |
274 #fi |
| |
275 |
| |
276 ticket_id = int (match.group (2)) |
| |
277 |
| |
278 # Acquire additional data |
| |
279 moredata = get_commit_data (zanrepo, commit_node, |
| |
280 '{author|nonempty}\n{date(date, \'%A %d %B %Y %T\')}').split('\n') |
| |
281 |
| |
282 if len (moredata) != 2: |
| |
283 chanlog ('error while processing %s: malformed hg data' % commit_node) |
| |
284 continue |
| |
285 #fi |
| |
286 |
| |
287 commit_author = moredata[0] |
| |
288 commit_date = moredata[1] |
| |
289 commit_email = "" |
| |
290 |
| |
291 try: |
| |
292 ticket_data = suds_client.service.mc_issue_get (btuser, btpassword, ticket_id) |
| |
293 except Exception as e: |
| |
294 chanlog ('error while processing %s: %s' % (commit_node, `e`)) |
| |
295 continue |
| |
296 #tried |
| |
297 |
| |
298 # Remove the email address from the author if possible |
| |
299 rex = re.compile (r'^(.+) <([^>]+)>$.*') |
| |
300 match = rex.match (commit_author) |
| |
301 if match: |
| |
302 commit_author = match.group (1) |
| |
303 commit_email = match.group (2) |
| |
304 #fi |
| |
305 |
| |
306 commit_diffstat = zanrepo.hg_command ('diff', '--change', commit_node, '--stat') |
| |
307 |
| |
308 if len(commit_diffstat) > 0: |
| |
309 # commit_diffstat = 'Changes in files:\n[code]\n' + commit_diffstat + '\n[/code]' |
| |
310 commit_diffstat = 'Changes in files:\n' + bbcodify(commit_diffstat) |
| |
311 else: |
| |
312 commit_diffstat = 'No changes in files.' |
| |
313 |
| |
314 # Compare the email addresses against known developer usernames |
| |
315 commit_trackeruser = find_developer_by_email (commit_email) |
| |
316 |
| |
317 if commit_trackeruser != '': |
| |
318 commit_author += ' [%s]' % commit_trackeruser |
| |
319 #fi |
| |
320 |
| |
321 message = 'Issue addressed by commit %s: [b][url=%s/commits/%s]%s[/url][/b]' \ |
| |
322 % (commit_node, repo_url, commit_node, commit_message) |
| |
323 message += "\nCommitted by %s on %s\n\n%s" \ |
| |
324 % (commit_author, commit_date, commit_diffstat) |
| |
325 |
| |
326 need_update = False |
| |
327 |
| |
328 # If not already set, set handler |
| |
329 if not 'handler' in ticket_data: |
| |
330 ticket_data['handler'] = {'name': commit_trackeruser} |
| |
331 need_update = True |
| |
332 #fi |
| |
333 |
| |
334 # Find out the status level of the ticket |
| |
335 needs_testing_level = 70 |
| |
336 |
| |
337 if ticket_data['status']['id'] < needs_testing_level: |
| |
338 ticket_data.status['id'] = needs_testing_level |
| |
339 need_update = True |
| |
340 #fi |
| |
341 |
| |
342 # Set target version if not set |
| |
343 if not 'target_version' in ticket_data: |
| |
344 ticket_data['target_version'] = '1.4' if repo_name == 'zandronum-stable' else '2.0' |
| |
345 need_update = True |
| |
346 elif (ticket_data['target_version'] == '2.0' or ticket_data['target_version'] == '2.0-beta') \ |
| |
347 and repo_name == 'zandronum-stable': |
| |
348 # Target version was 2.0 but this was just committed to zandronum-stable, adjust |
| |
349 ticket_data['target_version'] = '1.4' |
| |
350 need_update = True |
| |
351 elif ticket_data['target_version'] == '2.0-beta': |
| |
352 # Fix target version from 2.0-beta to 2.0 |
| |
353 ticket_data['target_version'] = '2.0' |
| |
354 need_update = True |
| |
355 #fi |
| |
356 |
| |
357 # Announce on IRC |
| |
358 for irc_client in g_clients: |
| |
359 for channel in irc_client.channels: |
| |
360 if channel.get_value ('btannounce', default=True): |
| |
361 irc_client.privmsg (channel.get_value ('name'), |
| |
362 "%s: commit %s fixes issue %d: %s" |
| |
363 % (repo_name, commit_node, ticket_id, commit_message)) |
| |
364 irc_client.privmsg (channel.get_value ('name'), |
| |
365 "Read all about it here: " + irc_client.get_ticket_url (ticket_id)) |
| |
366 #fi |
| |
367 #done |
| |
368 #done |
| |
369 |
| |
370 if need_update: |
| |
371 # We need to remove the note data, otherwise the ticket notes |
| |
372 # will get unnecessary updates. WTF, MantisBT? |
| |
373 ticket_data.notes = [] |
| |
374 suds_client.service.mc_issue_update (btuser, btpassword, ticket_id, ticket_data) |
| |
375 #fi |
| |
376 |
| |
377 suds_client.service.mc_issue_note_add (btuser, btpassword, ticket_id, { 'text': message }) |
| |
378 num_commits += 1 |
| |
379 except Exception as e: |
| |
380 chanlog ('Error while processing %s: %s' % (commit_node, `e`)) |
| |
381 continue |
| |
382 #tried |
| |
383 #done |
| |
384 #enddef |
| |
385 |
| |
386 def force_poll(): |
| |
387 repocheck_timeout = 0 |
| |
388 poll() |