|
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() |