Merge lp:~abentley/launchpad/unnecessary-upgrade into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 13765
Proposed branch: lp:~abentley/launchpad/unnecessary-upgrade
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/upgrade-not-branch-error
Diff against target: 416 lines (+151/-23)
8 files modified
lib/lp/code/browser/branch.py (+5/-1)
lib/lp/code/browser/tests/test_branch.py (+26/-4)
lib/lp/code/errors.py (+34/-0)
lib/lp/code/interfaces/branch.py (+6/-0)
lib/lp/code/model/branch.py (+20/-7)
lib/lp/code/model/branchjob.py (+1/-4)
lib/lp/code/model/tests/test_branch.py (+52/-4)
lib/lp/code/model/tests/test_branchjob.py (+7/-3)
To merge this branch: bzr merge lp:~abentley/launchpad/unnecessary-upgrade
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Review via email: mp+72480@code.launchpad.net

Commit message

Nice error if branch cannot be upgraded.

Description of the change

= Summary =
Fix bug #823850: AssertionError raised upgrading a branch that doesn't need upgrade

== Proposed fix ==
Produce a notification explaining why the branch could not be upgraded.

== Pre-implementation notes ==
None

== Implementation details ==
Extracted the Branch.needs_upgrade logic to Branch.checkUpgrade, so that we can raise exceptions based on the precise checks.

Fixed lint.

== Tests ==
bin/test -t checkUpgrade -t test_upgrade_branch_action_cannot_upgrade

== Demo and Q/A ==
Upgrade a branch, and then immediately request another upgrade. You should get a nice error notification.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/errors.py
  lib/lp/code/browser/branch.py
  lib/lp/code/browser/tests/test_branch.py
  lib/lp/code/interfaces/branch.py
  lib/lp/code/model/branch.py
  lib/lp/code/model/branchjob.py
  lib/lp/code/model/tests/test_branch.py

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

Looks good. Thanks for fixing this!

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branch.py'
2--- lib/lp/code/browser/branch.py 2011-08-22 18:45:27 +0000
3+++ lib/lp/code/browser/branch.py 2011-08-22 18:45:28 +0000
4@@ -131,6 +131,7 @@
5 from lp.code.errors import (
6 BranchCreationForbidden,
7 BranchExists,
8+ CannotUpgradeBranch,
9 CodeImportAlreadyRequested,
10 CodeImportAlreadyRunning,
11 CodeImportNotInReviewedState,
12@@ -993,7 +994,10 @@
13
14 @action('Upgrade', name='upgrade_branch')
15 def upgrade_branch_action(self, action, data):
16- self.context.requestUpgrade(self.user)
17+ try:
18+ self.context.requestUpgrade(self.user)
19+ except CannotUpgradeBranch as e:
20+ self.request.response.addErrorNotification(e)
21
22
23 class BranchEditView(BranchEditFormView, BranchNameValidationMixin):
24
25=== modified file 'lib/lp/code/browser/tests/test_branch.py'
26--- lib/lp/code/browser/tests/test_branch.py 2011-07-21 02:18:54 +0000
27+++ lib/lp/code/browser/tests/test_branch.py 2011-08-22 18:45:28 +0000
28@@ -7,12 +7,10 @@
29
30 from datetime import (
31 datetime,
32- timedelta,
33 )
34 from textwrap import dedent
35
36 import pytz
37-import simplejson
38 from zope.security.proxy import removeSecurityProxy
39
40 from canonical.config import config
41@@ -41,7 +39,11 @@
42 BranchView,
43 )
44 from lp.code.browser.branchlisting import PersonOwnedBranchesView
45-from lp.code.bzr import ControlFormat
46+from lp.code.bzr import (
47+ BranchFormat,
48+ ControlFormat,
49+ RepositoryFormat,
50+ )
51 from lp.code.enums import (
52 BranchLifecycleStatus,
53 BranchType,
54@@ -400,7 +402,7 @@
55
56 def _add_revisions(self, branch, nr_revisions=1):
57 revisions = []
58- for seq in range(1, nr_revisions+1):
59+ for seq in range(1, nr_revisions + 1):
60 revision = self.factory.makeRevision(
61 author="Eric the Viking <eric@vikings-r-us.example.com>",
62 log_body=(
63@@ -806,3 +808,23 @@
64 'Some Product.'))
65 with person_logged_in(person):
66 self.assertEquals(person, branch.owner)
67+
68+
69+class TestBranchUpgradeView(TestCaseWithFactory):
70+
71+ layer = LaunchpadFunctionalLayer
72+
73+ def test_upgrade_branch_action_cannot_upgrade(self):
74+ # A nice error is displayed if a branch cannot be upgraded.
75+ branch = self.factory.makePersonalBranch(
76+ branch_format=BranchFormat.BZR_BRANCH_6,
77+ repository_format=RepositoryFormat.BZR_CHK_2A)
78+ login_person(branch.owner)
79+ self.addCleanup(logout)
80+ branch.requestUpgrade(branch.owner)
81+ view = create_initialized_view(branch, '+upgrade')
82+ view.upgrade_branch_action.success({})
83+ self.assertEqual(1, len(view.request.notifications))
84+ self.assertEqual(
85+ 'An upgrade is already in progress for branch %s.' %
86+ branch.bzr_identity, view.request.notifications[0].message)
87
88=== modified file 'lib/lp/code/errors.py'
89--- lib/lp/code/errors.py 2011-06-23 21:09:20 +0000
90+++ lib/lp/code/errors.py 2011-08-22 18:45:28 +0000
91@@ -5,6 +5,7 @@
92
93 __metaclass__ = type
94 __all__ = [
95+ 'AlreadyLatestFormat',
96 'BadBranchMergeProposalSearchContext',
97 'BadStateTransition',
98 'BranchCannotBePrivate',
99@@ -20,6 +21,8 @@
100 'BuildNotAllowedForDistro',
101 'BranchMergeProposalExists',
102 'CannotDeleteBranch',
103+ 'CannotUpgradeBranch',
104+ 'CannotUpgradeNonHosted',
105 'CannotHaveLinkedBranch',
106 'CodeImportAlreadyRequested',
107 'CodeImportAlreadyRunning',
108@@ -36,6 +39,7 @@
109 'TooManyBuilds',
110 'TooNewRecipeFormat',
111 'UnknownBranchTypeError',
112+ 'UpgradePending',
113 'UserHasExistingReview',
114 'UserNotBranchReviewer',
115 'WrongBranchMergeProposal',
116@@ -167,6 +171,36 @@
117 _msg_template = "%s cannot have linked branches."
118
119
120+class CannotUpgradeBranch(Exception):
121+ """"Made for subclassing."""
122+
123+ def __init__(self, branch):
124+ super(CannotUpgradeBranch, self).__init__(
125+ self._msg_template % branch.bzr_identity)
126+ self.branch = branch
127+
128+
129+class AlreadyLatestFormat(CannotUpgradeBranch):
130+ """Raised on attempt to upgrade a branch already in the latest format."""
131+
132+ _msg_template = (
133+ 'Branch %s is in the latest format, so it cannot be upgraded.')
134+
135+
136+class CannotUpgradeNonHosted(CannotUpgradeBranch):
137+
138+ """Raised on attempt to upgrade a non-Hosted branch."""
139+
140+ _msg_template = 'Cannot upgrade non-hosted branch %s'
141+
142+
143+class UpgradePending(CannotUpgradeBranch):
144+
145+ """Raised on attempt to upgrade a branch already in the latest format."""
146+
147+ _msg_template = 'An upgrade is already in progress for branch %s.'
148+
149+
150 class ClaimReviewFailed(Exception):
151 """The user cannot claim the pending review."""
152
153
154=== modified file 'lib/lp/code/interfaces/branch.py'
155--- lib/lp/code/interfaces/branch.py 2011-08-22 18:45:27 +0000
156+++ lib/lp/code/interfaces/branch.py 2011-08-22 18:45:28 +0000
157@@ -944,6 +944,12 @@
158 :return: A list of tuples like (date, count).
159 """
160
161+ def checkUpgrade():
162+ """Check whether an upgrade should be performed, and raise if not.
163+
164+ :raises: a `CannotUpgradeBranch`, or a subclass.
165+ """
166+
167 needs_upgrading = Attribute("Whether the branch needs to be upgraded.")
168 upgrade_pending = Attribute(
169 "Whether a branch has had an upgrade requested.")
170
171=== modified file 'lib/lp/code/model/branch.py'
172--- lib/lp/code/model/branch.py 2011-08-22 18:45:27 +0000
173+++ lib/lp/code/model/branch.py 2011-08-22 18:45:28 +0000
174@@ -87,14 +87,18 @@
175 BranchType,
176 )
177 from lp.code.errors import (
178+ AlreadyLatestFormat,
179 BranchCannotBePrivate,
180 BranchCannotBePublic,
181 BranchMergeProposalExists,
182 BranchTargetError,
183 BranchTypeError,
184 CannotDeleteBranch,
185+ CannotUpgradeBranch,
186+ CannotUpgradeNonHosted,
187 InvalidBranchMergeProposal,
188 InvalidMergeQueueConfig,
189+ UpgradePending,
190 )
191 from lp.code.event.branchmergeproposal import (
192 BranchMergeProposalNeedsReviewEvent,
193@@ -1127,16 +1131,25 @@
194 DateTrunc(u'day', Revision.revision_date))
195 return sorted(results)
196
197- @property
198- def needs_upgrading(self):
199- """See `IBranch`."""
200+ def checkUpgrade(self):
201 if self.branch_type is not BranchType.HOSTED:
202- return False
203+ raise CannotUpgradeNonHosted(self)
204 if self.upgrade_pending:
205- return False
206- return not (
207+ raise UpgradePending(self)
208+ if (
209 self.branch_format in CURRENT_BRANCH_FORMATS and
210- self.repository_format in CURRENT_REPOSITORY_FORMATS)
211+ self.repository_format in CURRENT_REPOSITORY_FORMATS):
212+ raise AlreadyLatestFormat(self)
213+
214+ @property
215+ def needs_upgrading(self):
216+ """See `IBranch`."""
217+ try:
218+ self.checkUpgrade()
219+ except CannotUpgradeBranch:
220+ return False
221+ else:
222+ return True
223
224 @property
225 def upgrade_pending(self):
226
227=== modified file 'lib/lp/code/model/branchjob.py'
228--- lib/lp/code/model/branchjob.py 2011-08-22 18:45:27 +0000
229+++ lib/lp/code/model/branchjob.py 2011-08-22 18:45:28 +0000
230@@ -378,10 +378,7 @@
231 @classmethod
232 def create(cls, branch, requester):
233 """See `IBranchUpgradeJobSource`."""
234- if not branch.needs_upgrading:
235- raise AssertionError('Branch does not need upgrading.')
236- if branch.upgrade_pending:
237- raise AssertionError('Branch already has upgrade pending.')
238+ branch.checkUpgrade()
239 branch_job = BranchJob(
240 branch, BranchJobType.UPGRADE_BRANCH, {}, requester=requester)
241 return cls(branch_job)
242
243=== modified file 'lib/lp/code/model/tests/test_branch.py'
244--- lib/lp/code/model/tests/test_branch.py 2011-08-22 18:45:27 +0000
245+++ lib/lp/code/model/tests/test_branch.py 2011-08-22 18:45:28 +0000
246@@ -19,6 +19,7 @@
247 import simplejson
248 from sqlobject import SQLObjectNotFound
249 from storm.locals import Store
250+from testtools import ExpectedException
251 import transaction
252 from zope.component import getUtility
253 from zope.security.proxy import removeSecurityProxy
254@@ -56,14 +57,17 @@
255 CodeReviewNotificationLevel,
256 )
257 from lp.code.errors import (
258+ AlreadyLatestFormat,
259 BranchCannotBePrivate,
260 BranchCannotBePublic,
261 BranchCreatorNotMemberOfOwnerTeam,
262 BranchCreatorNotOwner,
263 BranchTargetError,
264 CannotDeleteBranch,
265+ CannotUpgradeNonHosted,
266 InvalidBranchMergeProposal,
267 InvalidMergeQueueConfig,
268+ UpgradePending,
269 )
270 from lp.code.interfaces.branch import (
271 DEFAULT_BRANCH_STATUS_IN_LISTING,
272@@ -586,6 +590,14 @@
273 branch = self.factory.makePersonalBranch()
274 self.assertFalse(branch.needs_upgrading)
275
276+ def test_checkUpgrade_empty_formats(self):
277+ branch = self.factory.makePersonalBranch()
278+ with ExpectedException(
279+ AlreadyLatestFormat,
280+ 'Branch lp://dev/~person-name.*junk/branch.* is in the latest'
281+ ' format, so it cannot be upgraded.'):
282+ branch.checkUpgrade()
283+
284 def test_needsUpgrade_mirrored_branch(self):
285 branch = self.factory.makeBranch(
286 branch_type=BranchType.MIRRORED,
287@@ -593,6 +605,16 @@
288 repository_format=RepositoryFormat.BZR_REPOSITORY_4)
289 self.assertFalse(branch.needs_upgrading)
290
291+ def test_checkUpgrade_mirrored_branch(self):
292+ branch = self.factory.makeBranch(
293+ branch_type=BranchType.MIRRORED,
294+ branch_format=BranchFormat.BZR_BRANCH_6,
295+ repository_format=RepositoryFormat.BZR_REPOSITORY_4)
296+ with ExpectedException(
297+ CannotUpgradeNonHosted,
298+ 'Cannot upgrade non-hosted branch %s' % branch.bzr_identity):
299+ branch.checkUpgrade()
300+
301 def test_needsUpgrade_remote_branch(self):
302 branch = self.factory.makeBranch(
303 branch_type=BranchType.REMOTE,
304@@ -621,6 +643,20 @@
305
306 self.assertFalse(branch.needs_upgrading)
307
308+ def test_checkUpgrade_already_requested(self):
309+ branch = self.factory.makePersonalBranch(
310+ branch_format=BranchFormat.BZR_BRANCH_6,
311+ repository_format=RepositoryFormat.BZR_CHK_2A)
312+ owner = removeSecurityProxy(branch).owner
313+ login_person(owner)
314+ self.addCleanup(logout)
315+ branch.requestUpgrade(branch.owner)
316+ with ExpectedException(
317+ UpgradePending,
318+ 'An upgrade is already in progress for branch'
319+ ' lp://dev/~person-name.*junk/branch.*.'):
320+ branch.checkUpgrade()
321+
322 def test_needsUpgrading_branch_format_unrecognized(self):
323 # A branch has a needs_upgrading attribute that returns whether or not
324 # a branch needs to be upgraded or not. If the format is
325@@ -639,6 +675,17 @@
326 repository_format=RepositoryFormat.BZR_CHK_2A)
327 self.assertFalse(branch.needs_upgrading)
328
329+ def test_checkUpgrade_branch_format_upgrade_not_needed(self):
330+ # If a branch is up-to-date, checkUpgrade raises AlreadyLatestFormat
331+ branch = self.factory.makePersonalBranch(
332+ branch_format=BranchFormat.BZR_BRANCH_8,
333+ repository_format=RepositoryFormat.BZR_CHK_2A)
334+ with ExpectedException(
335+ AlreadyLatestFormat,
336+ 'Branch lp://dev/~person-name.*junk/branch.* is in the latest'
337+ ' format, so it cannot be upgraded.'):
338+ branch.checkUpgrade()
339+
340 def test_needsUpgrading_branch_format_upgrade_needed(self):
341 # A branch has a needs_upgrading attribute that returns whether or not
342 # a branch needs to be upgraded or not. If a branch doesn't support
343@@ -691,18 +738,19 @@
344
345 def test_requestUpgrade_no_upgrade_needed(self):
346 # If a branch doesn't need to be upgraded, requestUpgrade raises an
347- # AssertionError.
348+ # AlreadyLatestFormat.
349 branch = self.factory.makeAnyBranch(
350 branch_format=BranchFormat.BZR_BRANCH_8,
351 repository_format=RepositoryFormat.BZR_CHK_2A)
352 owner = removeSecurityProxy(branch).owner
353 login_person(owner)
354 self.addCleanup(logout)
355- self.assertRaises(AssertionError, branch.requestUpgrade, branch.owner)
356+ self.assertRaises(
357+ AlreadyLatestFormat, branch.requestUpgrade, branch.owner)
358
359 def test_requestUpgrade_upgrade_pending(self):
360 # If there is a pending upgrade already requested, requestUpgrade
361- # raises an AssertionError.
362+ # raises an UpgradePending.
363 branch = self.factory.makeAnyBranch(
364 branch_format=BranchFormat.BZR_BRANCH_6)
365 owner = removeSecurityProxy(branch).owner
366@@ -710,7 +758,7 @@
367 self.addCleanup(logout)
368 branch.requestUpgrade(branch.owner)
369
370- self.assertRaises(AssertionError, branch.requestUpgrade, branch.owner)
371+ self.assertRaises(UpgradePending, branch.requestUpgrade, branch.owner)
372
373 def test_upgradePending(self):
374 # If there is a BranchUpgradeJob pending for the branch, return True.
375
376=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
377--- lib/lp/code/model/tests/test_branchjob.py 2011-08-22 18:45:27 +0000
378+++ lib/lp/code/model/tests/test_branchjob.py 2011-08-22 18:45:28 +0000
379@@ -52,6 +52,7 @@
380 BranchSubscriptionNotificationLevel,
381 CodeReviewNotificationLevel,
382 )
383+from lp.code.errors import AlreadyLatestFormat
384 from lp.code.interfaces.branchjob import (
385 IBranchDiffJob,
386 IBranchJob,
387@@ -307,6 +308,8 @@
388 'Bazaar-NG Knit Repository Format 1')
389
390 job = BranchUpgradeJob.create(db_branch, self.factory.makePerson())
391+
392+ dbuser = config.launchpad.dbuser
393 self.becomeDbUser(config.upgrade_branches.dbuser)
394 with TransactionFreeOperation.require():
395 job.run()
396@@ -315,16 +318,17 @@
397 new_branch.repository._format.get_format_string(),
398 'Bazaar repository format 2a (needs bzr 1.16 or later)\n')
399
400+ self.becomeDbUser(dbuser)
401 self.assertFalse(db_branch.needs_upgrading)
402
403 def test_needs_no_upgrading(self):
404- # Branch upgrade job creation should raise an AssertionError if the
405- # branch does not need to be upgraded.
406+ # Branch upgrade job creation should raise an AlreadyLatestFormat if
407+ # the branch does not need to be upgraded.
408 branch = self.factory.makeAnyBranch(
409 branch_format=BranchFormat.BZR_BRANCH_7,
410 repository_format=RepositoryFormat.BZR_CHK_2A)
411 self.assertRaises(
412- AssertionError, BranchUpgradeJob.create, branch,
413+ AlreadyLatestFormat, BranchUpgradeJob.create, branch,
414 self.factory.makePerson())
415
416 def create_knit(self):