Merge lp:~dpb/tarmac/better-prereq-branches into lp:tarmac

Proposed by David Britton
Status: Merged
Approved by: dobey
Approved revision: 428
Merged at revision: 426
Proposed branch: lp:~dpb/tarmac/better-prereq-branches
Merge into: lp:tarmac
Diff against target: 347 lines (+162/-23)
5 files modified
.bzrignore (+1/-0)
docs/introduction.txt (+15/-1)
tarmac/bin/commands.py (+43/-13)
tarmac/tests/__init__.py (+1/-0)
tarmac/tests/test_commands.py (+102/-9)
To merge this branch: bzr merge lp:~dpb/tarmac/better-prereq-branches
Reviewer Review Type Date Requested Status
dobey Approve
Review via email: mp+197969@code.launchpad.net

Commit message

Better prerequisite handling.

Description of the change

Tarmac understands the launchpad concept of prerequisite branches. The
following conditions are supported when evaluating a candidate merge
proposal (MP).

 * Multiple prerequisite non-superseded MPs => error
 * Prerequiste branch but no associated MP => error
 * Exactly one prerequisite non-superseded unmerged MP => skip
 * Exactly one prerequisite non-superseded *Merged* MP => proceed

Additionally, the tarmac queue is processed taking into account prerequisite
branches. That is, dependent branches are processed *after* their prerequisites.

To post a comment you must log in.
Revision history for this message
dobey (dobey) wrote :

32 + * Multiple prerequisite non-superseded MPs => error

How does one declare multiple prerequisite branches in Launchpad?

161 +++ tarmac/tests/test_commands.py 2013-12-05 22:45:14 +0000

Many of the changes in here seem unrelated to the prerequisite work, and seems to be just you changing some of the names for the third branch in several of the tests. How are these related?

The bug you filed is a duplicate of https://bugs.launchpad.net/tarmac/+bug/900731 and I've marked it as such already. Changing the --fixes here too would be great, if possible.

Also, you need to commit using an e-mail address that is associated with your Launchpad account. Your shortened canonical.com address does not appear to be associated to your account, and so Launchpad cannot link to your account, nor will tarmac be able to find you via the Launchpad API from the e-mail address in the commit.

review: Needs Fixing
426. By David Britton

remove renaming of 'branch3'

Revision history for this message
David Britton (dpb) wrote :

> 32 + * Multiple prerequisite non-superseded MPs => error
>
> How does one declare multiple prerequisite branches in Launchpad?
>

I don't know, but I didn't change or add this code path. You can see it at line 231 of tarmac/bin/commands.py. I just documented what would happen if this condition was hit.

What would you like me to do?

> 161 +++ tarmac/tests/test_commands.py 2013-12-05 22:45:14 +0000
>
> Many of the changes in here seem unrelated to the prerequisite work, and seems
> to be just you changing some of the names for the third branch in several of
> the tests. How are these related?
>

I reverted since you asked. They were to help me track down which test was failing before adding the 'unique_name' property. The error message coming out of bzrlib was not helpful and just contained 'branch3' which made it hard to see which test failed.

> The bug you filed is a duplicate of
> https://bugs.launchpad.net/tarmac/+bug/900731 and I've marked it as such
> already. Changing the --fixes here too would be great, if possible.

OK, I'll hopefully add '--fixes' to this commit

> Also, you need to commit using an e-mail address that is associated with your
> Launchpad account. Your shortened canonical.com address does not appear to be
> associated to your account, and so Launchpad cannot link to your account, nor
> will tarmac be able to find you via the Launchpad API from the e-mail address
> in the commit.

I added it.

427. By David Britton

Adding lp:900731 to the list of fixes

Revision history for this message
dobey (dobey) wrote :

> > 32 + * Multiple prerequisite non-superseded MPs => error
> >
> > How does one declare multiple prerequisite branches in Launchpad?
> >
>
> I don't know, but I didn't change or add this code path. You can see it at
> line 231 of tarmac/bin/commands.py. I just documented what would happen if
> this condition was hit.
>
> What would you like me to do?

I think you've misunderstood the code. That code is to deal with the case when a prerequisite branch has multiple proposals (ie, if someone re-proposed the branch and there is a superseded proposal, and such). These are not multiple prerequisites. The comments and documentation need to be clear about what the code is actually doing.

Revision history for this message
David Britton (dpb) wrote :

I'll copy it here again:

"Multiple prerequisite non-superseded MPs"

What is wrong with that statement? I didn't say multiple prerequisite
branches. I said multiple prerequisite MPs (merge proposals). What would
you like me to say to clarify it?

Also, there is no comment about this. It's just documentation in a Readme,
unless I'm missing something.

On Sun, Feb 9, 2014 at 12:27 PM, Rodney Dawes <email address hidden>wrote:

> > > 32 + * Multiple prerequisite non-superseded MPs => error
> > >
> > > How does one declare multiple prerequisite branches in Launchpad?
> > >
> >
> > I don't know, but I didn't change or add this code path. You can see it
> at
> > line 231 of tarmac/bin/commands.py. I just documented what would happen
> if
> > this condition was hit.
> >
> > What would you like me to do?
>
> I think you've misunderstood the code. That code is to deal with the case
> when a prerequisite branch has multiple proposals (ie, if someone
> re-proposed the branch and there is a superseded proposal, and such). These
> are not multiple prerequisites. The comments and documentation need to be
> clear about what the code is actually doing.
> --
>
> https://code.launchpad.net/~davidpbritton/tarmac/better-prereq-branches/+merge/197969
> You are the owner of lp:~davidpbritton/tarmac/better-prereq-branches.
>

--
David Britton <email address hidden>

428. By David Britton

Clean up wording around prerequisite branches

Revision history for this message
David Britton (dpb) wrote :

I committed a rewording change, I hope it's more clear. I think calling an MP "prerequisite" was what was leading to the confusion. (hopefully)

Revision history for this message
dobey (dobey) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2010-07-24 13:04:41 +0000
+++ .bzrignore 2014-02-09 23:07:13 +0000
@@ -4,3 +4,4 @@
4dist4dist
5TODO5TODO
6debug*6debug*
7tarmac.egg-info
78
=== modified file 'docs/introduction.txt'
--- docs/introduction.txt 2013-11-06 19:35:08 +0000
+++ docs/introduction.txt 2014-02-09 23:07:13 +0000
@@ -6,7 +6,6 @@
6merge, it should really just magically be merged. That's what Tarmac does, and6merge, it should really just magically be merged. That's what Tarmac does, and
7it does it well.7it does it well.
88
9
10How it works9How it works
11============10============
1211
@@ -20,6 +19,21 @@
20branch has landed. This opens up all sorts of branch landing options that can19branch has landed. This opens up all sorts of branch landing options that can
21happen behind the scenes, while you can safely do more exciting work.20happen behind the scenes, while you can safely do more exciting work.
2221
22Prerequisite Branches
23=====================
24
25Tarmac understands the launchpad concept of prerequisite branches. The
26following conditions are supported when evaluating a candidate merge
27proposal (MP).
28
29 * Prerequisite branch without an associated MP => error
30 * Prerequisite branch with multiple non-superseded MPs => error
31 * Prerequisite branch with exactly one non-superseded unmerged MP => skip
32 * Prerequisite branch with exactly one non-superseded *Merged* MP => proceed
33
34Additionally, the tarmac queue is processed taking into account prerequisite
35branches. That is, dependent branches are processed *after* their
36prerequisites.
2337
24Configuration38Configuration
25=============39=============
2640
=== modified file 'tarmac/bin/commands.py'
--- tarmac/bin/commands.py 2013-11-07 17:28:53 +0000
+++ tarmac/bin/commands.py 2014-02-09 23:07:13 +0000
@@ -25,6 +25,17 @@
25from tarmac.utility import get_review_url25from tarmac.utility import get_review_url
2626
2727
28def _compare_proposals(a, b):
29 """Helper to sort proposals based on a prerequisite branch"""
30 if a.prerequisite_branch is not None:
31 if a.prerequisite_branch.unique_name == b.source_branch.unique_name:
32 return 1
33 if b.prerequisite_branch is not None:
34 if b.prerequisite_branch.unique_name == a.source_branch.unique_name:
35 return -1
36 return 0
37
38
28class TarmacCommand(Command):39class TarmacCommand(Command):
29 '''A command class.'''40 '''A command class.'''
3041
@@ -210,9 +221,7 @@
210 try:221 try:
211 prerequisite = proposal.prerequisite_branch222 prerequisite = proposal.prerequisite_branch
212 if prerequisite:223 if prerequisite:
213 merges = [x for x in prerequisite.landing_targets224 merges = self._get_prerequisite_proposals(proposal)
214 if x.target_branch == target.lp_branch and
215 x.queue_status != u'Superseded']
216 if len(merges) == 0:225 if len(merges) == 0:
217 raise TarmacMergeError(226 raise TarmacMergeError(
218 u'No proposals of prerequisite branch.',227 u'No proposals of prerequisite branch.',
@@ -227,14 +236,6 @@
227 u'of %s into %s, which is not Superseded.' % (236 u'of %s into %s, which is not Superseded.' % (
228 prerequisite.web_link,237 prerequisite.web_link,
229 target.lp_branch.web_link))238 target.lp_branch.web_link))
230 elif len(merges) == 1:
231 if merges[0].queue_status != u'Merged':
232 raise TarmacMergeError(
233 u'Prerequisite not yet merged.',
234 u'The prerequisite %s has not yet been '
235 u'merged into %s.' % (
236 prerequisite.web_link,
237 target.lp_branch.web_link))
238239
239 if not proposal.reviewed_revid:240 if not proposal.reviewed_revid:
240 raise TarmacMergeError(241 raise TarmacMergeError(
@@ -325,10 +326,16 @@
325 target.cleanup()326 target.cleanup()
326327
327 def _get_mergable_proposals_for_branch(self, lp_branch):328 def _get_mergable_proposals_for_branch(self, lp_branch):
328 """Return a list of the mergable proposals for the given branch."""329 """
330 Return a list of the mergable proposals for the given branch. The
331 list returned will be in the order that they should be processed.
332 """
329 proposals = []333 proposals = []
330 for entry in lp_branch.landing_candidates:334 sorted_proposals = sorted(
335 lp_branch.landing_candidates, cmp=_compare_proposals)
336 for entry in sorted_proposals:
331 self.logger.debug("Considering merge proposal: {0}".format(entry.web_link))337 self.logger.debug("Considering merge proposal: {0}".format(entry.web_link))
338 prereqs = self._get_prerequisite_proposals(entry)
332339
333 if entry.queue_status != u'Approved':340 if entry.queue_status != u'Approved':
334 self.logger.debug(341 self.logger.debug(
@@ -342,9 +349,31 @@
342 " Skipping proposal: proposal has no commit message")349 " Skipping proposal: proposal has no commit message")
343 continue350 continue
344351
352 if len(prereqs) == 1 and prereqs[0].queue_status != u'Merged':
353 # N.B.: The case of a MP with more than one prereq MP open
354 # will be caught as a merge error.
355 self.logger.debug(
356 " Skipping proposal: prerequisite not yet merged")
357 continue
358
345 proposals.append(entry)359 proposals.append(entry)
346 return proposals360 return proposals
347361
362 def _get_prerequisite_proposals(self, proposal):
363 """
364 Given a proposal, return all prerequisite
365 proposals that are not in the superseded state. There ideally
366 should be one and only one here (or zero), but sometimes there
367 are not, depending on developer habits
368 """
369 prerequisite = proposal.prerequisite_branch
370 target_branch = proposal.target_branch
371 if not prerequisite or not prerequisite.landing_targets:
372 return []
373 return [x for x in prerequisite.landing_targets
374 if x.target_branch.unique_name == target_branch.unique_name and
375 x.queue_status != u'Superseded']
376
348 def _get_reviews(self, proposal):377 def _get_reviews(self, proposal):
349 """Get the set of reviews from the proposal."""378 """Get the set of reviews from the proposal."""
350 reviews = []379 reviews = []
@@ -405,3 +434,4 @@
405 'An error occurred trying to merge %s: %s',434 'An error occurred trying to merge %s: %s',
406 branch, error)435 branch, error)
407 raise436 raise
437
408438
=== modified file 'tarmac/tests/__init__.py'
--- tarmac/tests/__init__.py 2013-10-31 19:09:08 +0000
+++ tarmac/tests/__init__.py 2014-02-09 23:07:13 +0000
@@ -56,6 +56,7 @@
56 self.revision_count = 056 self.revision_count = 0
57 self.bzr_identity = 'lp:%s' % os.path.basename(self.tree_dir)57 self.bzr_identity = 'lp:%s' % os.path.basename(self.tree_dir)
58 self.web_link = self.bzr_identity58 self.web_link = self.bzr_identity
59 self.unique_name = self.bzr_identity
59 self.project = MockLPProject()60 self.project = MockLPProject()
6061
6162
6263
=== modified file 'tarmac/tests/test_commands.py'
--- tarmac/tests/test_commands.py 2013-11-07 17:28:53 +0000
+++ tarmac/tests/test_commands.py 2014-02-09 23:07:13 +0000
@@ -128,13 +128,16 @@
128 display_name=self.branch2.lp_branch.bzr_identity,128 display_name=self.branch2.lp_branch.bzr_identity,
129 web_link=self.branch2.lp_branch.bzr_identity,129 web_link=self.branch2.lp_branch.bzr_identity,
130 name='source',130 name='source',
131 unique_name='source',
131 revision_count=self.branch2.lp_branch.revision_count,132 revision_count=self.branch2.lp_branch.revision_count,
132 landing_candidates=[]),133 landing_candidates=[],
134 landing_targets=[]),
133 Thing(135 Thing(
134 bzr_identity=self.branch1.lp_branch.bzr_identity,136 bzr_identity=self.branch1.lp_branch.bzr_identity,
135 display_name=self.branch1.lp_branch.bzr_identity,137 display_name=self.branch1.lp_branch.bzr_identity,
136 web_link=self.branch1.lp_branch.bzr_identity,138 web_link=self.branch1.lp_branch.bzr_identity,
137 name='target',139 name='target',
140 unique_name='target',
138 revision_count=self.branch1.lp_branch.revision_count,141 revision_count=self.branch1.lp_branch.revision_count,
139 landing_candidates=None)]142 landing_candidates=None)]
140 self.proposals = [Thing(143 self.proposals = [Thing(
@@ -171,6 +174,8 @@
171 comment=Thing(vote=u'Abstain'),174 comment=Thing(vote=u'Abstain'),
172 reviewer=Thing(display_name=u'Reviewer2'))])]175 reviewer=Thing(display_name=u'Reviewer2'))])]
173 self.branches[1].landing_candidates = self.proposals176 self.branches[1].landing_candidates = self.proposals
177 self.branches[0].landing_targets = [
178 self.proposals[0], self.proposals[1]]
174179
175 self.launchpad = Thing(branches=Thing(getByUrl=self.getBranchByUrl),180 self.launchpad = Thing(branches=Thing(getByUrl=self.getBranchByUrl),
176 me=Thing(display_name='Tarmac'))181 me=Thing(display_name='Tarmac'))
@@ -180,6 +185,42 @@
180185
181 self.command = registry._get_command(commands.cmd_merge, 'merge')186 self.command = registry._get_command(commands.cmd_merge, 'merge')
182187
188 def addProposal(self, name, prerequisite_branch=None):
189 """Create a 3rd branch with a proposal"""
190 # Create a 3rd branch we'll use to test with
191 branch3_dir = os.path.join(self.TEST_ROOT, name)
192 mock3 = MockLPBranch(branch3_dir, source_branch=self.branch1.lp_branch)
193 branch3 = Branch.create(mock3, self.config, create_tree=True,
194 target=self.branch1)
195 branch3.commit('Prerequisite commit.')
196 branch3.lp_branch.revision_count += 1
197
198 # Set up an approved proposal for the branch (prereq on branches[0])
199 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity
200 branch3.lp_branch.name = name
201 branch3.lp_branch.unique_name = '~user/branch/' + name
202 branch3.lp_branch.landing_candidates = []
203 b3_proposal = Thing(
204 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
205 web_link=u'http://edge.launchpad.net/proposal3',
206 queue_status=u'Approved',
207 commit_message=u'Commitable.',
208 source_branch=branch3.lp_branch,
209 target_branch=self.branches[1],
210 prerequisite_branch=prerequisite_branch,
211 createComment=self.createComment,
212 setStatus=self.lp_save,
213 lp_save=self.lp_save,
214 reviewed_revid=None,
215 votes=[Thing(
216 comment=Thing(vote=u'Approve'),
217 reviewer=Thing(display_name=u'Reviewer'))])
218
219 branch3.lp_branch.landing_targets = [b3_proposal]
220 self.proposals.append(b3_proposal)
221 self.branches.append(branch3.lp_branch)
222
223
183 def lp_save(self, *args, **kwargs):224 def lp_save(self, *args, **kwargs):
184 """Do nothing here."""225 """Do nothing here."""
185 pass226 pass
@@ -248,8 +289,8 @@
248 self.assertEqual(last_rev.properties.get('merge_url', None),289 self.assertEqual(last_rev.properties.get('merge_url', None),
249 u'http://code.launchpad.net/proposal0')290 u'http://code.launchpad.net/proposal0')
250291
251 def test_run_merge_with_unmerged_prerequisite_fails(self):292 def test_run_merge_with_unmerged_prerequisite_skips(self):
252 """Test that mereging a branch with an unmerged prerequisite fails."""293 """Test that mereging a branch with an unmerged prerequisite skips."""
253 # Create a 3rd prerequisite branch we'll use to test with294 # Create a 3rd prerequisite branch we'll use to test with
254 branch3_dir = os.path.join(self.TEST_ROOT, 'branch3')295 branch3_dir = os.path.join(self.TEST_ROOT, 'branch3')
255 mock3 = MockLPBranch(branch3_dir, source_branch=self.branch1.lp_branch)296 mock3 = MockLPBranch(branch3_dir, source_branch=self.branch1.lp_branch)
@@ -267,6 +308,7 @@
267 # Set up an unapproved proposal for the prerequisite308 # Set up an unapproved proposal for the prerequisite
268 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity309 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity
269 branch3.lp_branch.name = 'branch3'310 branch3.lp_branch.name = 'branch3'
311 branch3.unique_name = 'branch3'
270 branch3.lp_branch.landing_candidates = []312 branch3.lp_branch.landing_candidates = []
271 b3_proposal = Thing(313 b3_proposal = Thing(
272 self_link=u'http://api.edge.launchpad.net/devel/proposal3',314 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
@@ -290,11 +332,8 @@
290 self.proposals[1].prerequisite_branch = branch3.lp_branch332 self.proposals[1].prerequisite_branch = branch3.lp_branch
291 self.proposals[1].reviewed_revid = \333 self.proposals[1].reviewed_revid = \
292 self.branch2.bzr_branch.last_revision()334 self.branch2.bzr_branch.last_revision()
293 self.command.run(launchpad=self.launchpad)335 self.assertEqual(self.command.run(launchpad=self.launchpad), None)
294 shutil.rmtree(branch3_dir)336 shutil.rmtree(branch3_dir)
295 self.assertEqual(self.error.comment,
296 u'The prerequisite lp:branch3 has not yet been '
297 u'merged into lp:branch1.')
298337
299 def test_run_merge_with_unproposed_prerequisite_fails(self):338 def test_run_merge_with_unproposed_prerequisite_fails(self):
300 """Test that mereging a branch with an unmerged prerequisite fails."""339 """Test that mereging a branch with an unmerged prerequisite fails."""
@@ -315,6 +354,7 @@
315 # Set up an unapproved proposal for the prerequisite354 # Set up an unapproved proposal for the prerequisite
316 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity355 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity
317 branch3.lp_branch.name = 'branch3'356 branch3.lp_branch.name = 'branch3'
357 branch3.lp_branch.unique_name = 'branch3'
318 branch3.lp_branch.landing_candidates = []358 branch3.lp_branch.landing_candidates = []
319 b3_proposal = Thing(359 b3_proposal = Thing(
320 self_link=u'http://api.edge.launchpad.net/devel/proposal3',360 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
@@ -363,6 +403,7 @@
363 # Set up an unapproved proposal for the prerequisite403 # Set up an unapproved proposal for the prerequisite
364 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity404 branch3.lp_branch.display_name = branch3.lp_branch.bzr_identity
365 branch3.lp_branch.name = 'branch3'405 branch3.lp_branch.name = 'branch3'
406 branch3.lp_branch.unique_name = 'branch3'
366 branch3.lp_branch.landing_candidates = []407 branch3.lp_branch.landing_candidates = []
367 b3_proposal = Thing(408 b3_proposal = Thing(
368 self_link=u'http://api.edge.launchpad.net/devel/proposal3',409 self_link=u'http://api.edge.launchpad.net/devel/proposal3',
@@ -394,8 +435,8 @@
394 shutil.rmtree(branch3_dir)435 shutil.rmtree(branch3_dir)
395 self.assertEqual(self.error.comment,436 self.assertEqual(self.error.comment,
396 u'More than one proposal found for merge of '437 u'More than one proposal found for merge of '
397 u'lp:branch3 into lp:branch1, which is not '438 u'lp:branch3 into lp:branch1, '
398 u'Superseded.')439 u'which is not Superseded.')
399440
400 @patch('bzrlib.workingtree.WorkingTree.open')441 @patch('bzrlib.workingtree.WorkingTree.open')
401 def test_run_merge_with_invalid_workingtree(self, mocked):442 def test_run_merge_with_invalid_workingtree(self, mocked):
@@ -407,3 +448,55 @@
407 self.command.run(launchpad=self.launchpad)448 self.command.run(launchpad=self.launchpad)
408 self.assertEqual(self.error.comment,449 self.assertEqual(self.error.comment,
409 invalid_tree_comment)450 invalid_tree_comment)
451
452 def test__compare_proposals(self):
453 """
454 _compare_proposals is meant to be a sort routine comparison function
455 """
456 self.addProposal("compare_proposals", self.branches[0])
457 self.assertEqual(
458 commands._compare_proposals(self.proposals[0], self.proposals[1]),
459 0)
460 self.assertEqual(
461 commands._compare_proposals(self.proposals[0], self.proposals[2]),
462 -1)
463 self.assertEqual(
464 commands._compare_proposals(self.proposals[2], self.proposals[0]),
465 1)
466
467 def test__get_mergable_proposals_for_branch_are_sorted(self):
468 """
469 Mergable proposals should be in sorted order (prereqs should come
470 first in the list)
471 """
472 self.addProposal("sorted_test", self.branches[0])
473 proposals = self.command._get_mergable_proposals_for_branch(
474 self.branches[1])
475 self.assertEqual(len(proposals), 2)
476 self.assertTrue(proposals[1].source_branch.name == "sorted_test")
477
478 def test__get_mergable_proposals_for_branch_prereq_unmerged(self):
479 """
480 skip mergable proposals that have specified a prereq,
481 but that prereq is not merged yet. This is edge-casey, since
482 we now process the queue in an order that makes sense for
483 prereqs
484 """
485 self.addProposal("unmerged")
486 self.proposals[0].prerequisite_branch = self.branches[2]
487 self.proposals[1].prerequisite_branch = self.branches[2]
488 proposals = self.command._get_mergable_proposals_for_branch(
489 self.branches[1])
490 self.assertEqual(len(proposals), 1)
491 self.assertEqual(proposals[0].source_branch.name, "unmerged")
492
493 def test__get_prerequisite_proposals_no_prerequisites(self):
494 """proposals[0] does not have a prerequisite branch listed"""
495 proposals = self.command._get_prerequisite_proposals(self.proposals[0])
496 self.assertEqual(len(proposals), 0)
497
498 def test__get_prerequisite_proposals_one_prerequisite(self):
499 """Branches[0] (source) has two open MPs against it"""
500 self.addProposal("one_prerequisite", self.branches[0])
501 proposals = self.command._get_prerequisite_proposals(self.proposals[2])
502 self.assertEqual(len(proposals), 2)

Subscribers

People subscribed via source and target branches