Merge lp:~jml/tarmac/break-up-do-merges into lp:~ubuntuone-hackers/tarmac/trunk
- break-up-do-merges
- Merge into trunk
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Merged at revision: | 420 |
Proposed branch: | lp:~jml/tarmac/break-up-do-merges |
Merge into: | lp:~ubuntuone-hackers/tarmac/trunk |
Diff against target: |
462 lines (+200/-195) 2 files modified
tarmac/bin/commands.py (+194/-191) tarmac/tests/test_commands.py (+6/-4) |
To merge this branch: | bzr merge lp:~jml/tarmac/break-up-do-merges |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email: mp+140894@code.launchpad.net |
Commit message
Description of the change
Builds on james_w's branch of the same name, applying my review comments.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'tarmac/bin/commands.py' |
2 | --- tarmac/bin/commands.py 2012-12-19 15:54:44 +0000 |
3 | +++ tarmac/bin/commands.py 2012-12-20 12:57:59 +0000 |
4 | @@ -134,6 +134,182 @@ |
5 | help_commands(self.outf) |
6 | |
7 | |
8 | +def _get_mergable_proposals_for_branch(lp_branch, logger, imply_commit_message=False): |
9 | + """Return a list of the mergable proposals for the given branch.""" |
10 | + proposals = [] |
11 | + for entry in lp_branch.landing_candidates: |
12 | + logger.debug("Considering merge proposal: {0}".format(entry.web_link)) |
13 | + |
14 | + if entry.queue_status != u'Approved': |
15 | + logger.debug( |
16 | + " Skipping proposal: status is {0}, not " |
17 | + "'Approved'".format(entry.queue_status)) |
18 | + continue |
19 | + |
20 | + if (not imply_commit_message and not entry.commit_message): |
21 | + logger.debug( |
22 | + " Skipping proposal: proposal has no commit message") |
23 | + continue |
24 | + |
25 | + proposals.append(entry) |
26 | + return proposals |
27 | + |
28 | + |
29 | +def _get_reviews(proposal): |
30 | + """Get the set of reviews from the proposal.""" |
31 | + votes = [vote for vote in proposal.votes if vote.comment] |
32 | + if not votes: |
33 | + return None |
34 | + return [ |
35 | + '%s;%s' % (vote.reviewer.display_name, vote.comment.vote) |
36 | + for vote in votes] |
37 | + |
38 | + |
39 | +def set_up(logger, debug=False, http_debug=False): |
40 | + if debug: |
41 | + set_up_debug_logging() |
42 | + logger.debug('Debug logging enabled') |
43 | + if http_debug: |
44 | + httplib2.debuglevel = 1 |
45 | + logger.debug('HTTP debugging enabled.') |
46 | + logger.debug('Loading plugins') |
47 | + load_plugins() |
48 | + logger.debug('Plugins loaded') |
49 | + |
50 | + |
51 | +def merge_proposals(target, proposals, logger, config, command): |
52 | + logger.debug('Firing tarmac_pre_merge hook') |
53 | + tarmac_hooks.fire('tarmac_pre_merge', |
54 | + command, target) |
55 | + try: |
56 | + statuses = [ |
57 | + merge_proposal(target, proposal, logger, config, command) |
58 | + for proposal in proposals] |
59 | + logger.debug('Firing tarmac_post_merge hook') |
60 | + tarmac_hooks.fire('tarmac_post_merge', |
61 | + command, target) |
62 | + finally: |
63 | + target.cleanup() |
64 | + return statuses |
65 | + |
66 | + |
67 | +def merge_proposal(target, proposal, logger, config, command): |
68 | + target.cleanup() |
69 | + logger.debug( |
70 | + u'Preparing to merge %(source_branch)s' % { |
71 | + 'source_branch': proposal.source_branch.web_link}) |
72 | + try: |
73 | + prerequisite = proposal.prerequisite_branch |
74 | + if prerequisite: |
75 | + merges = [x for x in prerequisite.landing_targets |
76 | + if x.target_branch == target.lp_branch and |
77 | + x.queue_status != u'Superseded'] |
78 | + if len(merges) == 0: |
79 | + raise TarmacMergeError( |
80 | + u'No proposals of prerequisite branch.', |
81 | + u'No proposals found for merge of %s ' |
82 | + u'into %s.' % ( |
83 | + prerequisite.web_link, |
84 | + target.lp_branch.web_link)) |
85 | + elif len(merges) > 1: |
86 | + raise TarmacMergeError( |
87 | + u'Too many proposals of prerequisite.', |
88 | + u'More than one proposal found for merge ' |
89 | + u'of %s into %s, which is not Superseded.' % ( |
90 | + prerequisite.web_link, |
91 | + target.lp_branch.web_link)) |
92 | + elif len(merges) == 1: |
93 | + if merges[0].queue_status != u'Merged': |
94 | + raise TarmacMergeError( |
95 | + u'Prerequisite not yet merged.', |
96 | + u'The prerequisite %s has not yet been ' |
97 | + u'merged into %s.' % ( |
98 | + prerequisite.web_link, |
99 | + target.lp_branch.web_link)) |
100 | + |
101 | + if not proposal.reviewed_revid: |
102 | + raise TarmacMergeError( |
103 | + u'No approved revision specified.') |
104 | + |
105 | + |
106 | + source = Branch.create( |
107 | + proposal.source_branch, config, target=target) |
108 | + |
109 | + source_bzr_branch = source.get_bzr_branch() |
110 | + approved = source_bzr_branch.revision_id_to_revno( |
111 | + str(proposal.reviewed_revid)) |
112 | + tip = source_bzr_branch.revno() |
113 | + |
114 | + if tip > approved: |
115 | + message = u'Unapproved changes made after approval' |
116 | + lp_comment = ( |
117 | + u'There are additional revisions which have not ' |
118 | + u'been approved in review. Please seek review and ' |
119 | + u'approval of these new revisions.') |
120 | + raise UnapprovedChanges(message, lp_comment) |
121 | + |
122 | + logger.debug( |
123 | + 'Merging %(source)s at revision %(revision)s' % { |
124 | + 'source': proposal.source_branch.web_link, |
125 | + 'revision': proposal.reviewed_revid}) |
126 | + |
127 | + target.merge(source, str(proposal.reviewed_revid)) |
128 | + |
129 | + logger.debug('Firing tarmac_pre_commit hook') |
130 | + tarmac_hooks.fire('tarmac_pre_commit', |
131 | + command, target, source, proposal) |
132 | + |
133 | + except TarmacMergeError, failure: |
134 | + logger.warn( |
135 | + u'Merging %(source)s into %(target)s failed: %(msg)s' % |
136 | + {'source': proposal.source_branch.web_link, |
137 | + 'target': proposal.target_branch.web_link, |
138 | + 'msg': str(failure)}) |
139 | + |
140 | + subject = u'Re: [Merge] %(source)s into %(target)s' % { |
141 | + "source": proposal.source_branch.display_name, |
142 | + "target": proposal.target_branch.display_name} |
143 | + |
144 | + if failure.comment: |
145 | + comment = failure.comment |
146 | + else: |
147 | + comment = str(failure) |
148 | + |
149 | + proposal.createComment(subject=subject, content=comment) |
150 | + try: |
151 | + proposal.setStatus( |
152 | + status=config.rejected_branch_status) |
153 | + except AttributeError: |
154 | + proposal.setStatus(status=u'Needs review') |
155 | + proposal.lp_save() |
156 | + return False |
157 | + |
158 | + except PointlessMerge: |
159 | + logger.warn( |
160 | + 'Merging %(source)s into %(target)s would be ' |
161 | + 'pointless.' % { |
162 | + 'source': proposal.source_branch.web_link, |
163 | + 'target': proposal.target_branch.web_link}) |
164 | + return False |
165 | + |
166 | + merge_url = get_review_url(proposal) |
167 | + revprops = {'merge_url': merge_url} |
168 | + |
169 | + commit_message = proposal.commit_message |
170 | + if commit_message is None and config.imply_commit_message: |
171 | + commit_message = proposal.description |
172 | + |
173 | + target.commit(commit_message, |
174 | + revprops=revprops, |
175 | + authors=source.authors, |
176 | + reviews=_get_reviews(proposal)) |
177 | + |
178 | + logger.debug('Firing tarmac_post_commit hook') |
179 | + tarmac_hooks.fire('tarmac_post_commit', |
180 | + command, target, source, proposal) |
181 | + return True |
182 | + |
183 | + |
184 | class cmd_merge(TarmacCommand): |
185 | '''Automatically merge approved merge proposal branches.''' |
186 | |
187 | @@ -143,7 +319,8 @@ |
188 | options.http_debug_option, |
189 | options.debug_option, |
190 | options.imply_commit_message_option, |
191 | - options.one_option] |
192 | + options.one_option, |
193 | + ] |
194 | |
195 | def _do_merges(self, branch_url): |
196 | |
197 | @@ -152,204 +329,29 @@ |
198 | self.logger.info('Not a valid branch: {0}'.format(branch_url)) |
199 | return |
200 | |
201 | - proposals = self._get_mergable_proposals_for_branch(lp_branch) |
202 | + proposals = _get_mergable_proposals_for_branch(lp_branch, self.logger, |
203 | + imply_commit_message=self.config.imply_commit_message) |
204 | |
205 | if not proposals: |
206 | self.logger.info( |
207 | 'No approved proposals found for %(branch_url)s' % { |
208 | 'branch_url': branch_url}) |
209 | - return |
210 | + return [] |
211 | + |
212 | + if self.config.one: |
213 | + proposals = proposals[0] |
214 | |
215 | target = Branch.create(lp_branch, self.config, create_tree=True) |
216 | - |
217 | - self.logger.debug('Firing tarmac_pre_merge hook') |
218 | - tarmac_hooks.fire('tarmac_pre_merge', |
219 | - self, target) |
220 | - |
221 | - try: |
222 | - for proposal in proposals: |
223 | - target.cleanup() |
224 | - self.logger.debug( |
225 | - u'Preparing to merge %(source_branch)s' % { |
226 | - 'source_branch': proposal.source_branch.web_link}) |
227 | - try: |
228 | - prerequisite = proposal.prerequisite_branch |
229 | - if prerequisite: |
230 | - merges = [x for x in prerequisite.landing_targets |
231 | - if x.target_branch == target.lp_branch and |
232 | - x.queue_status != u'Superseded'] |
233 | - if len(merges) == 0: |
234 | - raise TarmacMergeError( |
235 | - u'No proposals of prerequisite branch.', |
236 | - u'No proposals found for merge of %s ' |
237 | - u'into %s.' % ( |
238 | - prerequisite.web_link, |
239 | - target.lp_branch.web_link)) |
240 | - elif len(merges) > 1: |
241 | - raise TarmacMergeError( |
242 | - u'Too many proposals of prerequisite.', |
243 | - u'More than one proposal found for merge ' |
244 | - u'of %s into %s, which is not Superseded.' % ( |
245 | - prerequisite.web_link, |
246 | - target.lp_branch.web_link)) |
247 | - elif len(merges) == 1: |
248 | - if merges[0].queue_status != u'Merged': |
249 | - raise TarmacMergeError( |
250 | - u'Prerequisite not yet merged.', |
251 | - u'The prerequisite %s has not yet been ' |
252 | - u'merged into %s.' % ( |
253 | - prerequisite.web_link, |
254 | - target.lp_branch.web_link)) |
255 | - |
256 | - if not proposal.reviewed_revid: |
257 | - raise TarmacMergeError( |
258 | - u'No approved revision specified.') |
259 | - |
260 | - |
261 | - source = Branch.create( |
262 | - proposal.source_branch, self.config, target=target) |
263 | - |
264 | - source_bzr_branch = source.get_bzr_branch() |
265 | - approved = source_bzr_branch.revision_id_to_revno( |
266 | - str(proposal.reviewed_revid)) |
267 | - tip = source_bzr_branch.revno() |
268 | - |
269 | - if tip > approved: |
270 | - message = u'Unapproved changes made after approval' |
271 | - lp_comment = ( |
272 | - u'There are additional revisions which have not ' |
273 | - u'been approved in review. Please seek review and ' |
274 | - u'approval of these new revisions.') |
275 | - raise UnapprovedChanges(message, lp_comment) |
276 | - |
277 | - self.logger.debug( |
278 | - 'Merging %(source)s at revision %(revision)s' % { |
279 | - 'source': proposal.source_branch.web_link, |
280 | - 'revision': proposal.reviewed_revid}) |
281 | - |
282 | - target.merge(source, str(proposal.reviewed_revid)) |
283 | - |
284 | - self.logger.debug('Firing tarmac_pre_commit hook') |
285 | - tarmac_hooks.fire('tarmac_pre_commit', |
286 | - self, target, source, proposal) |
287 | - |
288 | - except TarmacMergeError, failure: |
289 | - self.logger.warn( |
290 | - u'Merging %(source)s into %(target)s failed: %(msg)s' % |
291 | - {'source': proposal.source_branch.web_link, |
292 | - 'target': proposal.target_branch.web_link, |
293 | - 'msg': str(failure)}) |
294 | - |
295 | - subject = u'Re: [Merge] %(source)s into %(target)s' % { |
296 | - "source": proposal.source_branch.display_name, |
297 | - "target": proposal.target_branch.display_name} |
298 | - |
299 | - if failure.comment: |
300 | - comment = failure.comment |
301 | - else: |
302 | - comment = str(failure) |
303 | - |
304 | - proposal.createComment(subject=subject, content=comment) |
305 | - try: |
306 | - proposal.setStatus( |
307 | - status=self.config.rejected_branch_status) |
308 | - except AttributeError: |
309 | - proposal.setStatus(status=u'Needs review') |
310 | - proposal.lp_save() |
311 | - |
312 | - # If we've been asked to only merge one branch, then exit. |
313 | - if self.config.one: |
314 | - return True |
315 | - |
316 | - continue |
317 | - except PointlessMerge: |
318 | - self.logger.warn( |
319 | - 'Merging %(source)s into %(target)s would be ' |
320 | - 'pointless.' % { |
321 | - 'source': proposal.source_branch.web_link, |
322 | - 'target': proposal.target_branch.web_link}) |
323 | - continue |
324 | - |
325 | - merge_url = get_review_url(proposal) |
326 | - revprops = {'merge_url': merge_url} |
327 | - |
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 | - |
337 | - self.logger.debug('Firing tarmac_post_commit hook') |
338 | - tarmac_hooks.fire('tarmac_post_commit', |
339 | - self, target, source, proposal) |
340 | - |
341 | - # If we've been asked to only merge one branch, then exit. |
342 | - if self.config.one: |
343 | - return True |
344 | - |
345 | - # This except is here because we need the else and can't have it |
346 | - # without an except as well. |
347 | - except: |
348 | - raise |
349 | - else: |
350 | - self.logger.debug('Firing tarmac_post_merge hook') |
351 | - tarmac_hooks.fire('tarmac_post_merge', |
352 | - self, target) |
353 | - finally: |
354 | - target.cleanup() |
355 | - |
356 | - def _get_mergable_proposals_for_branch(self, lp_branch): |
357 | - """Return a list of the mergable proposals for the given branch.""" |
358 | - proposals = [] |
359 | - for entry in lp_branch.landing_candidates: |
360 | - self.logger.debug("Considering merge proposal: {0}".format(entry.web_link)) |
361 | - |
362 | - if entry.queue_status != u'Approved': |
363 | - self.logger.debug( |
364 | - " Skipping proposal: status is {0}, not " |
365 | - "'Approved'".format(entry.queue_status)) |
366 | - continue |
367 | - |
368 | - if (not self.config.imply_commit_message and |
369 | - not entry.commit_message): |
370 | - self.logger.debug( |
371 | - " Skipping proposal: proposal has no commit message") |
372 | - continue |
373 | - |
374 | - proposals.append(entry) |
375 | - return proposals |
376 | - |
377 | - def _get_reviews(self, proposal): |
378 | - """Get the set of reviews from the proposal.""" |
379 | - reviews = [] |
380 | - for vote in proposal.votes: |
381 | - if not vote.comment: |
382 | - continue |
383 | - else: |
384 | - reviews.append('%s;%s' % (vote.reviewer.display_name, |
385 | - vote.comment.vote)) |
386 | - |
387 | - if len(reviews) == 0: |
388 | - return None |
389 | - |
390 | - return reviews |
391 | + statuses = merge_proposals(target, proposals, self.logger, self.config, self) |
392 | + return statuses |
393 | + |
394 | |
395 | def run(self, branch_url=None, launchpad=None, **kwargs): |
396 | for key, value in kwargs.iteritems(): |
397 | self.config.set('Tarmac', key, value) |
398 | |
399 | - if self.config.debug: |
400 | - set_up_debug_logging() |
401 | - self.logger.debug('Debug logging enabled') |
402 | - if self.config.http_debug: |
403 | - httplib2.debuglevel = 1 |
404 | - self.logger.debug('HTTP debugging enabled.') |
405 | - self.logger.debug('Loading plugins') |
406 | - load_plugins() |
407 | - self.logger.debug('Plugins loaded') |
408 | + set_up(self.logger, debug=self.config.debug, |
409 | + http_debug=self.config.http_debug) |
410 | |
411 | self.launchpad = launchpad |
412 | if self.launchpad is None: |
413 | @@ -357,23 +359,24 @@ |
414 | self.launchpad = self.get_launchpad_object() |
415 | self.logger.debug('launchpad object loaded') |
416 | |
417 | + statuses = [] |
418 | + |
419 | if branch_url: |
420 | self.logger.debug('%(branch_url)s specified as branch_url' % { |
421 | 'branch_url': branch_url}) |
422 | if not branch_url.startswith('lp:'): |
423 | raise TarmacCommandError('Branch urls must start with lp:') |
424 | - self._do_merges(branch_url) |
425 | - |
426 | + statuses.extend(self._do_merges(branch_url)) |
427 | else: |
428 | for branch in self.config.branches: |
429 | self.logger.debug( |
430 | 'Merging approved branches against %(branch)s' % { |
431 | 'branch': branch}) |
432 | try: |
433 | - merged = self._do_merges(branch) |
434 | + statuses.extend(self._do_merges(branch)) |
435 | |
436 | # If we've been asked to only merge one branch, then exit. |
437 | - if merged and self.config.one: |
438 | + if statuses and self.config.one: |
439 | break |
440 | except LockContention: |
441 | continue |
442 | |
443 | === modified file 'tarmac/tests/test_commands.py' |
444 | --- tarmac/tests/test_commands.py 2012-12-20 11:08:10 +0000 |
445 | +++ tarmac/tests/test_commands.py 2012-12-20 12:57:59 +0000 |
446 | @@ -198,10 +198,12 @@ |
447 | |
448 | def test_get_reviews(self): |
449 | """Test that the _get_reviews method gives the right lists.""" |
450 | - self.assertEqual(self.command._get_reviews(self.proposals[0]), |
451 | - [u'Reviewer;Needs Fixing']) |
452 | - self.assertEqual(self.command._get_reviews(self.proposals[1]), |
453 | - [u'Reviewer;Approve', u'Reviewer2;Abstain']) |
454 | + self.assertEqual( |
455 | + commands._get_reviews(self.proposals[0]), |
456 | + [u'Reviewer;Needs Fixing']) |
457 | + self.assertEqual( |
458 | + commands._get_reviews(self.proposals[1]), |
459 | + [u'Reviewer;Approve', u'Reviewer2;Abstain']) |
460 | |
461 | def test_run_merge_url_substitution(self): |
462 | """Test that the merge urls get substituted correctly.""" |