Merge lp:~canonical-ca-hackers/tarmac/jenkins into lp:tarmac
- jenkins
- Merge into main
Proposed by
Jonathan Lange
Status: | Rejected |
---|---|
Rejected by: | dobey |
Proposed branch: | lp:~canonical-ca-hackers/tarmac/jenkins |
Merge into: | lp:tarmac |
Diff against target: |
1004 lines (+536/-134) 9 files modified
bin/tarmac (+3/-1) check.py (+89/-0) tarmac/bin/__init__.py (+1/-1) tarmac/bin/commands.py (+333/-81) tarmac/bin/options.py (+6/-0) tarmac/bin/registry.py (+2/-2) tarmac/branch.py (+74/-46) tarmac/plugins/command.py (+4/-3) tarmac/plugins/commit.py (+24/-0) |
To merge this branch: | bzr merge lp:~canonical-ca-hackers/tarmac/jenkins |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Hummer | Pending | ||
Review via email: mp+140437@code.launchpad.net |
Commit message
Description of the change
WIP MP to make the delta visible
To post a comment you must log in.
Unmerged revisions
- 410. By James Westby
-
Merge the check.py file.
- 409. By James Westby
-
Merge sidnei's fix for the timeout thing.
- 408. By James Westby
-
Fix typo.
- 407. By James Westby
-
Fix missing method.
- 406. By James Westby
-
Remove debugging.
- 405. By James Westby
-
Fix typo.
- 404. By James Westby
-
Add the commit plugin that sets [r=reviewer].
- 403. By James Westby
-
Ignore decode errors when decoding streams from executed commands.
- 402. By James Westby
-
Add commands for better jenkins integration.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/tarmac' |
2 | --- bin/tarmac 2010-06-17 17:23:37 +0000 |
3 | +++ bin/tarmac 2012-12-18 13:35:32 +0000 |
4 | @@ -2,5 +2,7 @@ |
5 | # vim:filetype=python |
6 | '''Main tarmac script.''' |
7 | |
8 | +import sys |
9 | + |
10 | from tarmac.bin import main |
11 | -main() |
12 | +sys.exit(main()) |
13 | |
14 | === added file 'check.py' |
15 | --- check.py 1970-01-01 00:00:00 +0000 |
16 | +++ check.py 2012-12-18 13:35:32 +0000 |
17 | @@ -0,0 +1,89 @@ |
18 | +#!/usr/bin/env python |
19 | +import httplib2 |
20 | +import sys |
21 | + |
22 | +from tarmac.bin.commands import TarmacCommand |
23 | +from tarmac.bin.registry import CommandRegistry |
24 | +from tarmac.exceptions import TarmacCommandError |
25 | +from tarmac.log import set_up_debug_logging |
26 | +from tarmac.plugin import load_plugins |
27 | + |
28 | + |
29 | +registry = CommandRegistry() |
30 | + |
31 | + |
32 | +class cmd_check(TarmacCommand): |
33 | + |
34 | + takes_args = ['branch_url?'] |
35 | + |
36 | + def _check_merges(self, branch_url): |
37 | + |
38 | + lp_branch = self.launchpad.branches.getByUrl(url=branch_url) |
39 | + if lp_branch is None: |
40 | + self.logger.info('Not a valid branch: {0}'.format(branch_url)) |
41 | + return |
42 | + |
43 | + proposals = self._get_mergable_proposals_for_branch(lp_branch) |
44 | + |
45 | + if not proposals: |
46 | + self.logger.info( |
47 | + 'No approved proposals found for %(branch_url)s' % { |
48 | + 'branch_url': branch_url}) |
49 | + return 1 |
50 | + return 0 |
51 | + |
52 | + def _get_mergable_proposals_for_branch(self, lp_branch): |
53 | + """Return a list of the mergable proposals for the given branch.""" |
54 | + proposals = [] |
55 | + for entry in lp_branch.landing_candidates: |
56 | + self.logger.debug("Considering merge proposal: {0}".format(entry)) |
57 | + |
58 | + if entry.queue_status != u'Approved': |
59 | + self.logger.debug( |
60 | + " Skipping proposal: status is {0}, not " |
61 | + "'Approved'".format(entry.queue_status)) |
62 | + continue |
63 | + |
64 | + if (not entry.commit_message): |
65 | + self.logger.debug( |
66 | + " Skipping proposal: proposal has no commit message") |
67 | + continue |
68 | + |
69 | + proposals.append(entry) |
70 | + return proposals |
71 | + |
72 | + def run(self, branch_url=None, launchpad=None, **kwargs): |
73 | + for key, value in kwargs.iteritems(): |
74 | + self.config.set('Tarmac', key, value) |
75 | + |
76 | + set_up_debug_logging() |
77 | + |
78 | + self.launchpad = launchpad |
79 | + if self.launchpad is None: |
80 | + self.logger.debug('Loading launchpad object') |
81 | + self.launchpad = self.get_launchpad_object() |
82 | + self.logger.debug('launchpad object loaded') |
83 | + |
84 | + if branch_url: |
85 | + self.logger.debug( |
86 | + '%(branch_url)s specified as branch_url' % { |
87 | + 'branch_url': branch_url}) |
88 | + if not branch_url.startswith('lp:'): |
89 | + raise TarmacCommandError('Branch urls must start with lp:') |
90 | + return self._check_merges(branch_url) |
91 | + else: |
92 | + for branch in self.config.branches: |
93 | + self.logger.debug( |
94 | + 'Merging approved branches against %(branch)s' % { |
95 | + 'branch': branch}) |
96 | + try: |
97 | + return self._check_merges(branch) |
98 | + except Exception, error: |
99 | + self.logger.error( |
100 | + 'An error occurred trying to merge %s: %s', |
101 | + branch, error) |
102 | + raise |
103 | + |
104 | + |
105 | +cmd = cmd_check(registry) |
106 | +sys.exit(cmd.run(sys.argv[1])) |
107 | |
108 | === modified file 'tarmac/bin/__init__.py' |
109 | --- tarmac/bin/__init__.py 2010-09-02 15:18:07 +0000 |
110 | +++ tarmac/bin/__init__.py 2012-12-18 13:35:32 +0000 |
111 | @@ -27,4 +27,4 @@ |
112 | args = sys.argv[1:] |
113 | if not args: |
114 | args = ['help'] |
115 | - registry.run(args) |
116 | + return registry.run(args) |
117 | |
118 | === modified file 'tarmac/bin/commands.py' |
119 | --- tarmac/bin/commands.py 2012-06-29 20:03:55 +0000 |
120 | +++ tarmac/bin/commands.py 2012-12-18 13:35:32 +0000 |
121 | @@ -5,7 +5,7 @@ |
122 | import re |
123 | |
124 | from bzrlib.commands import Command |
125 | -from bzrlib.errors import PointlessMerge |
126 | +from bzrlib.errors import PointlessMerge, LockContention |
127 | from bzrlib.help import help_commands |
128 | from launchpadlib.launchpad import Launchpad |
129 | from launchpadlib.uris import (LPNET_SERVICE_ROOT, |
130 | @@ -134,6 +134,55 @@ |
131 | help_commands(self.outf) |
132 | |
133 | |
134 | +def _get_mergable_proposals_for_branch(lp_branch, logger, imply_commit_message=False): |
135 | + """Return a list of the mergable proposals for the given branch.""" |
136 | + proposals = [] |
137 | + for entry in lp_branch.landing_candidates: |
138 | + logger.debug("Considering merge proposal: {0}".format(entry.web_link)) |
139 | + |
140 | + if entry.queue_status != u'Approved': |
141 | + logger.debug( |
142 | + " Skipping proposal: status is {0}, not " |
143 | + "'Approved'".format(entry.queue_status)) |
144 | + continue |
145 | + |
146 | + if (not imply_commit_message and not entry.commit_message): |
147 | + logger.debug( |
148 | + " Skipping proposal: proposal has no commit message") |
149 | + continue |
150 | + |
151 | + proposals.append(entry) |
152 | + return proposals |
153 | + |
154 | + |
155 | +def _get_reviews(proposal): |
156 | + """Get the set of reviews from the proposal.""" |
157 | + reviews = [] |
158 | + for vote in proposal.votes: |
159 | + if not vote.comment: |
160 | + continue |
161 | + else: |
162 | + reviews.append('%s;%s' % (vote.reviewer.display_name, |
163 | + vote.comment.vote)) |
164 | + |
165 | + if len(reviews) == 0: |
166 | + return None |
167 | + |
168 | + return reviews |
169 | + |
170 | + |
171 | +def setup(logger, debug=False, http_debug=False): |
172 | + if debug: |
173 | + set_up_debug_logging() |
174 | + logger.debug('Debug logging enabled') |
175 | + if http_debug: |
176 | + httplib2.debuglevel = 1 |
177 | + logger.debug('HTTP debugging enabled.') |
178 | + logger.debug('Loading plugins') |
179 | + load_plugins() |
180 | + logger.debug('Plugins loaded') |
181 | + |
182 | + |
183 | class cmd_merge(TarmacCommand): |
184 | '''Automatically merge approved merge proposal branches.''' |
185 | |
186 | @@ -142,7 +191,8 @@ |
187 | takes_options = [ |
188 | options.http_debug_option, |
189 | options.debug_option, |
190 | - options.imply_commit_message_option] |
191 | + options.imply_commit_message_option, |
192 | + options.one_option] |
193 | |
194 | def _do_merges(self, branch_url): |
195 | |
196 | @@ -151,7 +201,8 @@ |
197 | self.logger.info('Not a valid branch: {0}'.format(branch_url)) |
198 | return |
199 | |
200 | - proposals = self._get_mergable_proposals_for_branch(lp_branch) |
201 | + proposals = _get_mergable_proposals_for_branch(lp_branch, self.logger, |
202 | + imply_commit_message=self.config.imply_commit_message) |
203 | |
204 | if not proposals: |
205 | self.logger.info( |
206 | @@ -167,13 +218,10 @@ |
207 | |
208 | try: |
209 | for proposal in proposals: |
210 | - |
211 | + target.cleanup() |
212 | self.logger.debug( |
213 | u'Preparing to merge %(source_branch)s' % { |
214 | - 'source_branch': proposal.source_branch.bzr_identity}) |
215 | - source = Branch.create( |
216 | - proposal.source_branch, self.config, target=target) |
217 | - |
218 | + 'source_branch': proposal.source_branch.web_link}) |
219 | try: |
220 | prerequisite = proposal.prerequisite_branch |
221 | if prerequisite: |
222 | @@ -185,31 +233,37 @@ |
223 | u'No proposals of prerequisite branch.', |
224 | u'No proposals found for merge of %s ' |
225 | u'into %s.' % ( |
226 | - prerequisite.display_name, |
227 | - target.lp_branch.display_name)) |
228 | + prerequisite.web_link, |
229 | + target.lp_branch.web_link)) |
230 | elif len(merges) > 1: |
231 | raise TarmacMergeError( |
232 | u'Too many proposals of prerequisite.', |
233 | u'More than one proposal found for merge ' |
234 | u'of %s into %s, which is not Superseded.' % ( |
235 | - prerequisite.display_name, |
236 | - target.lp_branch.display_name)) |
237 | + prerequisite.web_link, |
238 | + target.lp_branch.web_link)) |
239 | elif len(merges) == 1: |
240 | if merges[0].queue_status != u'Merged': |
241 | raise TarmacMergeError( |
242 | - u'Prerequsiite not yet merged.', |
243 | + u'Prerequisite not yet merged.', |
244 | u'The prerequisite %s has not yet been ' |
245 | u'merged into %s.' % ( |
246 | - prerequisite.display_name, |
247 | - target.lp_branch.display_name)) |
248 | + prerequisite.web_link, |
249 | + target.lp_branch.web_link)) |
250 | |
251 | if not proposal.reviewed_revid: |
252 | raise TarmacMergeError( |
253 | u'No approved revision specified.') |
254 | |
255 | - approved = source.bzr_branch.revision_id_to_revno( |
256 | + |
257 | + source = Branch.create( |
258 | + proposal.source_branch, self.config, target=target) |
259 | + |
260 | + source_bzr_branch = source.get_bzr_branch() |
261 | + approved = source_bzr_branch.revision_id_to_revno( |
262 | str(proposal.reviewed_revid)) |
263 | - tip = source.bzr_branch.revno() |
264 | + tip = source_bzr_branch.revno() |
265 | + |
266 | if tip > approved: |
267 | message = u'Unapproved changes made after approval' |
268 | lp_comment = ( |
269 | @@ -220,12 +274,10 @@ |
270 | |
271 | self.logger.debug( |
272 | 'Merging %(source)s at revision %(revision)s' % { |
273 | - 'source': proposal.source_branch.display_name, |
274 | + 'source': proposal.source_branch.web_link, |
275 | 'revision': proposal.reviewed_revid}) |
276 | |
277 | - target.merge( |
278 | - source, |
279 | - str(proposal.reviewed_revid)) |
280 | + target.merge(source, str(proposal.reviewed_revid)) |
281 | |
282 | self.logger.debug('Firing tarmac_pre_commit hook') |
283 | tarmac_hooks.fire('tarmac_pre_commit', |
284 | @@ -234,8 +286,8 @@ |
285 | except TarmacMergeError, failure: |
286 | self.logger.warn( |
287 | u'Merging %(source)s into %(target)s failed: %(msg)s' % |
288 | - {'source': proposal.source_branch.display_name, |
289 | - 'target': proposal.target_branch.display_name, |
290 | + {'source': proposal.source_branch.web_link, |
291 | + 'target': proposal.target_branch.web_link, |
292 | 'msg': str(failure)}) |
293 | |
294 | subject = u'Re: [Merge] %(source)s into %(target)s' % { |
295 | @@ -247,23 +299,25 @@ |
296 | else: |
297 | comment = str(failure) |
298 | |
299 | - proposal.createComment(subject=subject, |
300 | - content=comment) |
301 | + proposal.createComment(subject=subject, content=comment) |
302 | try: |
303 | proposal.setStatus( |
304 | status=self.config.rejected_branch_status) |
305 | except AttributeError: |
306 | proposal.setStatus(status=u'Needs review') |
307 | proposal.lp_save() |
308 | - target.cleanup() |
309 | + |
310 | + # If we've been asked to only merge one branch, then exit. |
311 | + if self.config.one: |
312 | + return True |
313 | + |
314 | continue |
315 | except PointlessMerge: |
316 | self.logger.warn( |
317 | 'Merging %(source)s into %(target)s would be ' |
318 | 'pointless.' % { |
319 | - 'source': proposal.source_branch.display_name, |
320 | - 'target': proposal.target_branch.display_name}) |
321 | - target.cleanup() |
322 | + 'source': proposal.source_branch.web_link, |
323 | + 'target': proposal.target_branch.web_link}) |
324 | continue |
325 | |
326 | merge_url = get_review_url(proposal) |
327 | @@ -272,16 +326,17 @@ |
328 | commit_message = proposal.commit_message |
329 | if commit_message is None and self.config.imply_commit_message: |
330 | commit_message = proposal.description |
331 | + |
332 | target.commit(commit_message, |
333 | - revprops=revprops, |
334 | - authors=source.authors, |
335 | - reviews=self._get_reviews(proposal)) |
336 | + reviews=_get_reviews(proposal)) |
337 | |
338 | self.logger.debug('Firing tarmac_post_commit hook') |
339 | tarmac_hooks.fire('tarmac_post_commit', |
340 | self, target, source, proposal) |
341 | |
342 | - target.cleanup() |
343 | + # If we've been asked to only merge one branch, then exit. |
344 | + if self.config.one: |
345 | + return True |
346 | |
347 | # This except is here because we need the else and can't have it |
348 | # without an except as well. |
349 | @@ -294,55 +349,13 @@ |
350 | finally: |
351 | target.cleanup() |
352 | |
353 | - def _get_mergable_proposals_for_branch(self, lp_branch): |
354 | - """Return a list of the mergable proposals for the given branch.""" |
355 | - proposals = [] |
356 | - for entry in lp_branch.landing_candidates: |
357 | - self.logger.debug("Considering merge proposal: {0}".format(entry)) |
358 | - |
359 | - if entry.queue_status != u'Approved': |
360 | - self.logger.debug( |
361 | - " Skipping proposal: status is {0}, not " |
362 | - "'Approved'".format(entry.queue_status)) |
363 | - continue |
364 | - |
365 | - if (not self.config.imply_commit_message and |
366 | - not entry.commit_message): |
367 | - self.logger.debug( |
368 | - " Skipping proposal: proposal has no commit message") |
369 | - continue |
370 | - |
371 | - proposals.append(entry) |
372 | - return proposals |
373 | - |
374 | - def _get_reviews(self, proposal): |
375 | - """Get the set of reviews from the proposal.""" |
376 | - reviews = [] |
377 | - for vote in proposal.votes: |
378 | - if not vote.comment: |
379 | - continue |
380 | - else: |
381 | - reviews.append('%s;%s' % (vote.reviewer.display_name, |
382 | - vote.comment.vote)) |
383 | - |
384 | - if len(reviews) == 0: |
385 | - return None |
386 | - |
387 | - return reviews |
388 | |
389 | def run(self, branch_url=None, launchpad=None, **kwargs): |
390 | for key, value in kwargs.iteritems(): |
391 | self.config.set('Tarmac', key, value) |
392 | |
393 | - if self.config.debug: |
394 | - set_up_debug_logging() |
395 | - self.logger.debug('Debug logging enabled') |
396 | - if self.config.http_debug: |
397 | - httplib2.debuglevel = 1 |
398 | - self.logger.debug('HTTP debugging enabled.') |
399 | - self.logger.debug('Loading plugins') |
400 | - load_plugins() |
401 | - self.logger.debug('Plugins loaded') |
402 | + setup(self.logger, debug=self.config.debug, |
403 | + http_debug=self.config.http_debug) |
404 | |
405 | self.launchpad = launchpad |
406 | if self.launchpad is None: |
407 | @@ -351,9 +364,8 @@ |
408 | self.logger.debug('launchpad object loaded') |
409 | |
410 | if branch_url: |
411 | - self.logger.debug( |
412 | - '%(branch_url)s specified as branch_url' % { |
413 | - 'branch_url': branch_url}) |
414 | + self.logger.debug('%(branch_url)s specified as branch_url' % { |
415 | + 'branch_url': branch_url}) |
416 | if not branch_url.startswith('lp:'): |
417 | raise TarmacCommandError('Branch urls must start with lp:') |
418 | self._do_merges(branch_url) |
419 | @@ -364,9 +376,249 @@ |
420 | 'Merging approved branches against %(branch)s' % { |
421 | 'branch': branch}) |
422 | try: |
423 | - self._do_merges(branch) |
424 | + merged = self._do_merges(branch) |
425 | + |
426 | + # If we've been asked to only merge one branch, then exit. |
427 | + if merged and self.config.one: |
428 | + break |
429 | + except LockContention: |
430 | + continue |
431 | except Exception, error: |
432 | self.logger.error( |
433 | 'An error occurred trying to merge %s: %s', |
434 | branch, error) |
435 | raise |
436 | + |
437 | + |
438 | +class cmd_check(TarmacCommand): |
439 | + '''Check whether there are any merge proposals ready to land.''' |
440 | + |
441 | + takes_args = ['branch_url'] |
442 | + takes_options = [ |
443 | + options.http_debug_option, |
444 | + options.debug_option] |
445 | + |
446 | + def _any_merges(self, branch_url): |
447 | + |
448 | + lp_branch = self.launchpad.branches.getByUrl(url=branch_url) |
449 | + if lp_branch is None: |
450 | + self.logger.info('Not a valid branch: {0}'.format(branch_url)) |
451 | + return |
452 | + |
453 | + proposals = _get_mergable_proposals_for_branch(lp_branch, self.logger) |
454 | + |
455 | + if not proposals: |
456 | + self.logger.info( |
457 | + 'No approved proposals found for %(branch_url)s' % { |
458 | + 'branch_url': branch_url}) |
459 | + return False |
460 | + return True |
461 | + |
462 | + def run(self, branch_url, launchpad=None, **kwargs): |
463 | + for key, value in kwargs.iteritems(): |
464 | + self.config.set('Tarmac', key, value) |
465 | + |
466 | + setup(self.logger, debug=self.config.debug, |
467 | + http_debug=self.config.http_debug) |
468 | + |
469 | + self.launchpad = launchpad |
470 | + if self.launchpad is None: |
471 | + self.logger.debug('Loading launchpad object') |
472 | + self.launchpad = self.get_launchpad_object() |
473 | + self.logger.debug('launchpad object loaded') |
474 | + |
475 | + self.logger.debug('%(branch_url)s specified as branch_url' % { |
476 | + 'branch_url': branch_url}) |
477 | + if not branch_url.startswith('lp:'): |
478 | + raise TarmacCommandError('Branch urls must start with lp:') |
479 | + ret = self._any_merges(branch_url) |
480 | + if ret: |
481 | + return 0 |
482 | + return 1 |
483 | + |
484 | + |
485 | +class cmd_merge_one(TarmacCommand): |
486 | + '''Try and merge one branch. |
487 | + |
488 | + Returns 0 if a branch was merged. |
489 | + Return 1 if there were no branches to merge. |
490 | + Returns 2 if the branch failed to merge. |
491 | + ''' |
492 | + |
493 | + takes_args = ['branch_url'] |
494 | + takes_options = [ |
495 | + options.http_debug_option, |
496 | + options.debug_option, |
497 | + options.verify_command_option] |
498 | + |
499 | + def _pick_merge(self, lp_branch, branch_url): |
500 | + proposals = _get_mergable_proposals_for_branch(lp_branch, self.logger) |
501 | + |
502 | + if not proposals: |
503 | + self.logger.info( |
504 | + 'No approved proposals found for %(branch_url)s' % { |
505 | + 'branch_url': branch_url}) |
506 | + return None |
507 | + return proposals[0] |
508 | + |
509 | + def _do_merge(self, lp_branch, proposal, verify_command=None): |
510 | + target = Branch.create(lp_branch, self.config, create_tree=True) |
511 | + |
512 | + if verify_command is not None: |
513 | + setattr(target.config, 'verify_command', verify_command) |
514 | + |
515 | + self.logger.debug('Firing tarmac_pre_merge hook') |
516 | + tarmac_hooks['tarmac_pre_merge'].fire(self, target) |
517 | + |
518 | + try: |
519 | + target.cleanup() |
520 | + self.logger.debug( |
521 | + u'Preparing to merge %(source_branch)s' % { |
522 | + 'source_branch': proposal.source_branch.web_link}) |
523 | + try: |
524 | + prerequisite = proposal.prerequisite_branch |
525 | + if prerequisite: |
526 | + merges = [x for x in prerequisite.landing_targets |
527 | + if x.target_branch == target.lp_branch and |
528 | + x.queue_status != u'Superseded'] |
529 | + if len(merges) == 0: |
530 | + raise TarmacMergeError( |
531 | + u'No proposals of prerequisite branch.', |
532 | + u'No proposals found for merge of %s ' |
533 | + u'into %s.' % ( |
534 | + prerequisite.web_link, |
535 | + target.lp_branch.web_link)) |
536 | + elif len(merges) > 1: |
537 | + raise TarmacMergeError( |
538 | + u'Too many proposals of prerequisite.', |
539 | + u'More than one proposal found for merge ' |
540 | + u'of %s into %s, which is not Superseded.' % ( |
541 | + prerequisite.web_link, |
542 | + target.lp_branch.web_link)) |
543 | + elif len(merges) == 1: |
544 | + if merges[0].queue_status != u'Merged': |
545 | + raise TarmacMergeError( |
546 | + u'Prerequisite not yet merged.', |
547 | + u'The prerequisite %s has not yet been ' |
548 | + u'merged into %s.' % ( |
549 | + prerequisite.web_link, |
550 | + target.lp_branch.web_link)) |
551 | + |
552 | + if not proposal.reviewed_revid: |
553 | + raise TarmacMergeError( |
554 | + u'No approved revision specified.') |
555 | + |
556 | + |
557 | + source = Branch.create( |
558 | + proposal.source_branch, self.config, target=target) |
559 | + |
560 | + approved = source.bzr_branch.revision_id_to_revno( |
561 | + str(proposal.reviewed_revid)) |
562 | + tip = source.bzr_branch.revno() |
563 | + |
564 | + if tip > approved: |
565 | + message = u'Unapproved changes made after approval' |
566 | + lp_comment = ( |
567 | + u'There are additional revisions which have not ' |
568 | + u'been approved in review. Please seek review and ' |
569 | + u'approval of these new revisions.') |
570 | + raise UnapprovedChanges(message, lp_comment) |
571 | + |
572 | + self.logger.debug( |
573 | + 'Merging %(source)s at revision %(revision)s' % { |
574 | + 'source': proposal.source_branch.web_link, |
575 | + 'revision': proposal.reviewed_revid}) |
576 | + |
577 | + target.merge(source, str(proposal.reviewed_revid)) |
578 | + |
579 | + self.logger.debug('Firing tarmac_pre_commit hook') |
580 | + tarmac_hooks['tarmac_pre_commit'].fire( |
581 | + self, target, source, proposal) |
582 | + |
583 | + except TarmacMergeError, failure: |
584 | + self.logger.warn( |
585 | + u'Merging %(source)s into %(target)s failed: %(msg)s' % |
586 | + {'source': proposal.source_branch.web_link, |
587 | + 'target': proposal.target_branch.web_link, |
588 | + 'msg': str(failure)}) |
589 | + |
590 | + subject = u'Re: [Merge] %(source)s into %(target)s' % { |
591 | + "source": proposal.source_branch.display_name, |
592 | + "target": proposal.target_branch.display_name} |
593 | + |
594 | + if failure.comment: |
595 | + comment = failure.comment |
596 | + else: |
597 | + comment = str(failure) |
598 | + |
599 | + proposal.createComment(subject=subject, content=comment) |
600 | + try: |
601 | + proposal.setStatus( |
602 | + status=self.config.rejected_branch_status) |
603 | + except AttributeError: |
604 | + proposal.setStatus(status=u'Needs review') |
605 | + proposal.lp_save() |
606 | + |
607 | + return False |
608 | + except PointlessMerge: |
609 | + self.logger.warn( |
610 | + 'Merging %(source)s into %(target)s would be ' |
611 | + 'pointless.' % { |
612 | + 'source': proposal.source_branch.web_link, |
613 | + 'target': proposal.target_branch.web_link}) |
614 | + return False |
615 | + |
616 | + merge_url = get_review_url(proposal) |
617 | + revprops = {'merge_url': merge_url} |
618 | + |
619 | + commit_message = proposal.commit_message |
620 | + if commit_message is None and self.config.imply_commit_message: |
621 | + commit_message = proposal.description |
622 | + target.commit(commit_message, |
623 | + revprops=revprops, |
624 | + authors=source.authors, |
625 | + reviews=_get_reviews(proposal)) |
626 | + |
627 | + self.logger.debug('Firing tarmac_post_commit hook') |
628 | + tarmac_hooks['tarmac_post_commit'].fire( |
629 | + self, target, source, proposal) |
630 | + return True |
631 | + |
632 | + # This except is here because we need the else and can't have it |
633 | + # without an except as well. |
634 | + except: |
635 | + raise |
636 | + else: |
637 | + self.logger.debug('Firing tarmac_post_merge hook') |
638 | + tarmac_hooks['tarmac_post_merge'].fire(self, target) |
639 | + finally: |
640 | + target.cleanup() |
641 | + |
642 | + def run(self, branch_url, launchpad=None, verify_command=None, **kwargs): |
643 | + for key, value in kwargs.iteritems(): |
644 | + self.config.set('Tarmac', key, value) |
645 | + |
646 | + setup(self.logger, debug=self.config.debug, |
647 | + http_debug=self.config.http_debug) |
648 | + |
649 | + self.launchpad = launchpad |
650 | + if self.launchpad is None: |
651 | + self.logger.debug('Loading launchpad object') |
652 | + self.launchpad = self.get_launchpad_object() |
653 | + self.logger.debug('launchpad object loaded') |
654 | + |
655 | + self.logger.debug('%(branch_url)s specified as branch_url' % { |
656 | + 'branch_url': branch_url}) |
657 | + if not branch_url.startswith('lp:'): |
658 | + raise TarmacCommandError('Branch urls must start with lp:') |
659 | + lp_branch = self.launchpad.branches.getByUrl(url=branch_url) |
660 | + if lp_branch is None: |
661 | + self.logger.info('Not a valid branch: {0}'.format(branch_url)) |
662 | + return 3 |
663 | + proposal = self._pick_merge(lp_branch, branch_url) |
664 | + if proposal is None: |
665 | + return 1 |
666 | + successful = self._do_merge(lp_branch, proposal, verify_command=verify_command) |
667 | + if successful: |
668 | + return 0 |
669 | + return 2 |
670 | |
671 | === modified file 'tarmac/bin/options.py' |
672 | --- tarmac/bin/options.py 2011-01-06 20:20:19 +0000 |
673 | +++ tarmac/bin/options.py 2012-12-18 13:35:32 +0000 |
674 | @@ -15,3 +15,9 @@ |
675 | 'imply-commit-message', |
676 | help=("Use the description as a commit message if the branch " |
677 | "doesn't have a message")) |
678 | +one_option = Option( |
679 | + 'one', short_name='1', |
680 | + help='Merge only one branch and exit.') |
681 | +verify_command_option = Option( |
682 | + 'verify-command', type=str, |
683 | + help='The verify command to run.') |
684 | |
685 | === modified file 'tarmac/bin/registry.py' |
686 | --- tarmac/bin/registry.py 2010-10-25 20:20:18 +0000 |
687 | +++ tarmac/bin/registry.py 2012-12-18 13:35:32 +0000 |
688 | @@ -45,7 +45,7 @@ |
689 | |
690 | def _run(self, args): |
691 | '''Execute the command.''' |
692 | - run_bzr(args) |
693 | + return run_bzr(args) |
694 | |
695 | def install_hooks(self): |
696 | '''Use the bzrlib Command support for running commands.''' |
697 | @@ -57,7 +57,7 @@ |
698 | def run(self, args): |
699 | '''Execute the command.''' |
700 | try: |
701 | - self._run(args) |
702 | + return self._run(args) |
703 | except BzrCommandError, e: |
704 | sys.exit('tarmac: ERROR: ' + str(e)) |
705 | |
706 | |
707 | === modified file 'tarmac/branch.py' |
708 | --- tarmac/branch.py 2012-06-29 20:03:55 +0000 |
709 | +++ tarmac/branch.py 2012-12-18 13:35:32 +0000 |
710 | @@ -37,7 +37,6 @@ |
711 | |
712 | def __init__(self, lp_branch, config=False, target=None): |
713 | self.lp_branch = lp_branch |
714 | - self.bzr_branch = bzr_branch.Branch.open(self.lp_branch.bzr_identity) |
715 | if config: |
716 | self.config = BranchConfig(lp_branch.bzr_identity, config) |
717 | else: |
718 | @@ -45,15 +44,27 @@ |
719 | |
720 | self.target = target |
721 | self.logger = logging.getLogger('tarmac') |
722 | + self.temp_tree_dir = None |
723 | + |
724 | + def get_bzr_branch(self): |
725 | + return bzr_branch.Branch.open(self.lp_branch.bzr_identity) |
726 | + |
727 | + # For backwards compatibility |
728 | + bzr_branch = property(get_bzr_branch) |
729 | + |
730 | + def get_tree(self): |
731 | + if self.temp_tree_dir is not None: |
732 | + return WorkingTree.open(self.temp_tree_dir) |
733 | + if os.path.exists(self.config.tree_dir): |
734 | + return WorkingTree.open(self.config.tree_dir) |
735 | + |
736 | + # For backwards compatibility |
737 | + tree = property(get_tree) |
738 | |
739 | def __del__(self): |
740 | """Do some potenetially necessary cleanup during deletion.""" |
741 | - try: |
742 | - # If we were using a temp directory, then remove it |
743 | + if self.temp_tree_dir is not None: |
744 | shutil.rmtree(self.temp_tree_dir) |
745 | - except AttributeError: |
746 | - # Not using a tempdir |
747 | - pass |
748 | |
749 | @classmethod |
750 | def create(cls, lp_branch, config, create_tree=False, target=None): |
751 | @@ -62,50 +73,59 @@ |
752 | clazz.create_tree() |
753 | return clazz |
754 | |
755 | + |
756 | def create_tree(self): |
757 | '''Create the dir and working tree.''' |
758 | + bzr_branch = self.get_bzr_branch() |
759 | try: |
760 | - self.logger.debug( |
761 | - 'Using tree in %(tree_dir)s' % { |
762 | - 'tree_dir': self.config.tree_dir}) |
763 | - if os.path.exists(self.config.tree_dir): |
764 | - self.tree = WorkingTree.open(self.config.tree_dir) |
765 | - else: |
766 | + tree = self.get_tree() |
767 | + if tree is None: |
768 | self.logger.debug('Tree does not exist. Creating dir') |
769 | # Create the path up to but not including tree_dir if it does |
770 | # not exist. |
771 | parent_dir = os.path.dirname(self.config.tree_dir) |
772 | if not os.path.exists(parent_dir): |
773 | os.makedirs(parent_dir) |
774 | - self.tree = self.bzr_branch.create_checkout( |
775 | - self.config.tree_dir, lightweight=True) |
776 | + tree = bzr_branch.create_checkout( |
777 | + self.config.tree_dir, lightweight=False) |
778 | + self.logger.debug( |
779 | + 'Using tree in %(tree_dir)s' % { |
780 | + 'tree_dir': self.config.tree_dir}) |
781 | except AttributeError: |
782 | # Store this so we can rmtree later |
783 | self.temp_tree_dir = tempfile.mkdtemp() |
784 | self.logger.debug( |
785 | 'Using temp dir at %(tree_dir)s' % { |
786 | 'tree_dir': self.temp_tree_dir}) |
787 | - self.tree = self.bzr_branch.create_checkout(self.temp_tree_dir) |
788 | + tree = bzr_branch.create_checkout(self.temp_tree_dir) |
789 | |
790 | self.cleanup() |
791 | |
792 | def cleanup(self): |
793 | '''Remove the working tree from the temp dir.''' |
794 | - assert self.tree |
795 | - self.tree.revert() |
796 | - for filename in [self.tree.abspath(f) for f in self.unmanaged_files]: |
797 | + tree = self.get_tree() |
798 | + assert tree |
799 | + self.logger.info("Running cleanup in %s." % ( |
800 | + self.lp_branch.bzr_identity)) |
801 | + tree.revert() |
802 | + self.logger.info("Reverted changes in %s." % ( |
803 | + self.lp_branch.bzr_identity)) |
804 | + for filename in [tree.abspath(f) for f in self.unmanaged_files]: |
805 | if os.path.isdir(filename) and not os.path.islink(filename): |
806 | shutil.rmtree(filename) |
807 | else: |
808 | os.remove(filename) |
809 | |
810 | - self.tree.update() |
811 | + self.logger.info("Successfully removed extra files from %s." % ( |
812 | + self.lp_branch.bzr_identity)) |
813 | + tree.update() |
814 | |
815 | def merge(self, branch, revid=None): |
816 | '''Merge from another tarmac.branch.Branch instance.''' |
817 | - assert self.tree |
818 | - conflict_list = self.tree.merge_from_branch( |
819 | - branch.bzr_branch, to_revision=revid) |
820 | + tree = self.get_tree() |
821 | + assert tree |
822 | + conflict_list = tree.merge_from_branch( |
823 | + branch.get_bzr_branch(), to_revision=revid) |
824 | if conflict_list: |
825 | message = u'Conflicts merging branch.' |
826 | lp_comment = ( |
827 | @@ -118,24 +138,29 @@ |
828 | @property |
829 | def unmanaged_files(self): |
830 | """Get the list of ignored and unknown files in the tree.""" |
831 | - self.tree.lock_read() |
832 | - unmanaged = [x for x in self.tree.unknowns()] |
833 | - unmanaged.extend([x[0] for x in self.tree.ignored_files()]) |
834 | - self.tree.unlock() |
835 | + tree = self.get_tree() |
836 | + assert tree |
837 | + tree.lock_read() |
838 | + unmanaged = [x for x in tree.unknowns()] |
839 | + unmanaged.extend([x[0] for x in tree.ignored_files()]) |
840 | + tree.unlock() |
841 | return unmanaged |
842 | |
843 | @property |
844 | def conflicts(self): |
845 | '''Print the conflicts.''' |
846 | - assert self.tree.conflicts() |
847 | + tree = self.get_tree() |
848 | + assert tree |
849 | conflicts = [] |
850 | - for conflict in self.tree.conflicts(): |
851 | + for conflict in tree.conflicts(): |
852 | conflicts.append( |
853 | u'%s in %s' % (conflict.typestring, conflict.path)) |
854 | return '\n'.join(conflicts) |
855 | |
856 | def commit(self, commit_message, revprops=None, **kwargs): |
857 | '''Commit changes.''' |
858 | + tree = self.get_tree() |
859 | + assert tree |
860 | if not revprops: |
861 | revprops = {} |
862 | |
863 | @@ -152,8 +177,8 @@ |
864 | 'review identity or vote.') |
865 | revprops['reviews'] = '\n'.join(reviews) |
866 | |
867 | - self.tree.commit(commit_message, committer='Tarmac', |
868 | - revprops=revprops, authors=authors) |
869 | + tree.commit(commit_message, committer='Tarmac', |
870 | + revprops=revprops, authors=authors) |
871 | |
872 | @property |
873 | def landing_candidates(self): |
874 | @@ -164,18 +189,20 @@ |
875 | def authors(self): |
876 | author_list = [] |
877 | |
878 | + bzr_branch = self.get_bzr_branch() |
879 | if self.target: |
880 | - self.bzr_branch.lock_read() |
881 | - self.target.bzr_branch.lock_read() |
882 | + target_bzr_branch = self.target.get_bzr_branch() |
883 | + bzr_branch.lock_read() |
884 | + target_bzr_branch.lock_read() |
885 | |
886 | - graph = self.bzr_branch.repository.get_graph( |
887 | - self.target.bzr_branch.repository) |
888 | + graph = bzr_branch.repository.get_graph( |
889 | + target_bzr_branch.repository) |
890 | |
891 | unique_ids = graph.find_unique_ancestors( |
892 | - self.bzr_branch.last_revision(), |
893 | - [self.target.bzr_branch.last_revision()]) |
894 | + bzr_branch.last_revision(), |
895 | + [target_bzr_branch.last_revision()]) |
896 | |
897 | - revs = self.bzr_branch.repository.get_revisions(unique_ids) |
898 | + revs = bzr_branch.repository.get_revisions(unique_ids) |
899 | for rev in revs: |
900 | apparent_authors = rev.get_apparent_authors() |
901 | for author in apparent_authors: |
902 | @@ -183,12 +210,12 @@ |
903 | if author not in author_list: |
904 | author_list.append(author) |
905 | |
906 | - self.target.bzr_branch.unlock() |
907 | - self.bzr_branch.unlock() |
908 | + target_bzr_branch.unlock() |
909 | + bzr_branch.unlock() |
910 | else: |
911 | - last_rev = self.bzr_branch.last_revision() |
912 | + last_rev = bzr_branch.last_revision() |
913 | if last_rev != 'null:': |
914 | - rev = self.bzr_branch.repository.get_revision(last_rev) |
915 | + rev = bzr_branch.repository.get_revision(last_rev) |
916 | apparent_authors = rev.get_apparent_authors() |
917 | author_list.extend( |
918 | [a.replace('\n', '') for a in apparent_authors]) |
919 | @@ -200,12 +227,13 @@ |
920 | """Return the list of bugs fixed by the branch.""" |
921 | bugs_list = [] |
922 | |
923 | - self.bzr_branch.lock_read() |
924 | - oldrevid = self.bzr_branch.get_rev_id(self.lp_branch.revision_count) |
925 | - for rev_info in self.bzr_branch.iter_merge_sorted_revisions( |
926 | + bzr_branch = self.get_bzr_branch() |
927 | + bzr_branch.lock_read() |
928 | + oldrevid = bzr_branch.get_rev_id(self.lp_branch.revision_count) |
929 | + for rev_info in bzr_branch.iter_merge_sorted_revisions( |
930 | stop_revision_id=oldrevid): |
931 | try: |
932 | - rev = self.bzr_branch.repository.get_revision(rev_info[0]) |
933 | + rev = bzr_branch.repository.get_revision(rev_info[0]) |
934 | for bug in rev.iter_bugs(): |
935 | if bug[0].startswith('https://launchpad.net/bugs/'): |
936 | bugs_list.append(bug[0].replace( |
937 | @@ -213,5 +241,5 @@ |
938 | except NoSuchRevision: |
939 | continue |
940 | |
941 | - self.bzr_branch.unlock() |
942 | + bzr_branch.unlock() |
943 | return bugs_list |
944 | |
945 | === modified file 'tarmac/plugins/command.py' |
946 | --- tarmac/plugins/command.py 2011-09-02 04:06:25 +0000 |
947 | +++ tarmac/plugins/command.py 2012-12-18 13:35:32 +0000 |
948 | @@ -103,7 +103,8 @@ |
949 | shell=True, |
950 | stdin=subprocess.PIPE, |
951 | stdout=subprocess.PIPE, |
952 | - stderr=subprocess.PIPE) |
953 | + stderr=subprocess.PIPE, |
954 | + preexec_fn=os.setsid) |
955 | proc.stdin.close() |
956 | stdout = tempfile.TemporaryFile() |
957 | stderr = tempfile.TemporaryFile() |
958 | @@ -125,7 +126,7 @@ |
959 | killem(proc.pid, signal.SIGTERM) |
960 | time.sleep(5) |
961 | |
962 | - if proc.poll() is not None: |
963 | + if proc.poll() is None: |
964 | self.logger.debug("SIGTERM did not work. Sending SIGKILL.") |
965 | killem(proc.pid, signal.SIGKILL) |
966 | |
967 | @@ -184,7 +185,7 @@ |
968 | u'%(output)s') % { |
969 | 'source': self.proposal.source_branch.display_name, |
970 | 'target': self.proposal.target_branch.display_name, |
971 | - 'output': u'\n'.join([stdout_value, stderr_value]), |
972 | + 'output': u'\n'.join([stdout_value.decode('utf-8', 'ignore'), stderr_value.decode('utf-8', 'ignore')]), |
973 | } |
974 | raise VerifyCommandFailed(message, comment) |
975 | |
976 | |
977 | === added file 'tarmac/plugins/commit.py' |
978 | --- tarmac/plugins/commit.py 1970-01-01 00:00:00 +0000 |
979 | +++ tarmac/plugins/commit.py 2012-12-18 13:35:32 +0000 |
980 | @@ -0,0 +1,24 @@ |
981 | +import re |
982 | + |
983 | +from tarmac.hooks import tarmac_hooks |
984 | +from tarmac.plugins import TarmacPlugin |
985 | + |
986 | + |
987 | +class CommitMessage(TarmacPlugin): |
988 | + |
989 | + def run(self, command, target, source, proposal): |
990 | + if not re.match(r'^\[.+?\].*$', proposal.commit_message): |
991 | + params = ["[r=%s]" % proposal.reviewer.name] |
992 | + for bug in proposal.source_branch.linked_bugs: |
993 | + params.append("[bug=%s]" % bug.id) |
994 | + |
995 | + proposal.commit_message = ("%s " % ",".join(params) + |
996 | + proposal.commit_message) |
997 | + self.logger.debug("Setting commit_message to %s", |
998 | + proposal.commit_message) |
999 | + else: |
1000 | + self.logger.debug("%s has already right commit message", |
1001 | + proposal) |
1002 | + |
1003 | +tarmac_hooks['tarmac_pre_commit'].hook(CommitMessage(), |
1004 | + 'Commit messsage template editor.') |