Merge lp:~abentley/launchpad/upgrade-not-branch-error into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 13756
Proposed branch: lp:~abentley/launchpad/upgrade-not-branch-error
Merge into: lp:launchpad
Diff against target: 447 lines (+95/-40)
10 files modified
database/schema/security.cfg (+2/-0)
lib/lp/code/browser/branch.py (+2/-3)
lib/lp/code/interfaces/branch.py (+1/-1)
lib/lp/code/model/branch.py (+2/-2)
lib/lp/code/model/branchjob.py (+18/-3)
lib/lp/code/model/tests/test_branch.py (+7/-8)
lib/lp/code/model/tests/test_branchjob.py (+46/-17)
lib/lp/code/scripts/tests/test_upgrade_branches.py (+2/-2)
lib/lp/scripts/tests/test_garbo.py (+5/-3)
lib/lp/testing/__init__.py (+10/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/upgrade-not-branch-error
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+72242@code.launchpad.net

Commit message

NotBranch while upgrading branch is user error, not oops.

Description of the change

= Summary =
Fix bug #823485: NotBranchError raised by upgrade_branches.py script

== Proposed fix ==
Handle NotBranchError as a user error and email to user.

== Pre-implementation notes ==
None

== Implementation details ==
The error notification should go to the user who requested the upgrade, but
until now, the requester was not recorded. Now, BranchUpgradeJob.create
requires a requester, and various tests are updated to match.

== Tests ==
bin/test -t test_branch -t test_branchjob -t test_upgrade_branches -t test_garbo

== Demo and Q/A ==
Create a branch. Request an upgrade. Immediately delete the contents of the
branch (but don't delete the branch from the database.)

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/browser/branch.py
  lib/lp/code/interfaces/branch.py
  lib/lp/code/model/tests/test_branchjob.py
  database/schema/security.cfg
  lib/lp/testing/__init__.py
  lib/lp/scripts/tests/test_garbo.py
  lib/lp/code/model/branch.py
  lib/lp/code/scripts/tests/test_upgrade_branches.py
  lib/lp/code/model/tests/test_branch.py
  lib/lp/code/model/branchjob.py

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2011-08-17 19:51:38 +0000
3+++ database/schema/security.cfg 2011-08-22 18:13:31 +0000
4@@ -1903,7 +1903,9 @@
5 groups=script
6 public.branch = SELECT, UPDATE
7 public.branchjob = SELECT, INSERT
8+public.emailaddress = SELECT
9 public.job = SELECT, INSERT, UPDATE
10+public.person = SELECT
11 type=user
12
13 [send-branch-mail]
14
15=== modified file 'lib/lp/code/browser/branch.py'
16--- lib/lp/code/browser/branch.py 2011-07-21 02:18:54 +0000
17+++ lib/lp/code/browser/branch.py 2011-08-22 18:13:31 +0000
18@@ -47,7 +47,6 @@
19 )
20 from lazr.uri import URI
21 import pytz
22-import simplejson
23 from zope.app.form.browser import TextAreaWidget
24 from zope.component import (
25 getUtility,
26@@ -838,7 +837,7 @@
27 def mirror_of_ssh(self):
28 """True if this a mirror branch with an sftp or bzr+ssh URL."""
29 if not self.context.url:
30- return False # not a mirror branch
31+ return False # not a mirror branch
32 uri = URI(self.context.url)
33 return uri.scheme in ('sftp', 'bzr+ssh')
34
35@@ -994,7 +993,7 @@
36
37 @action('Upgrade', name='upgrade_branch')
38 def upgrade_branch_action(self, action, data):
39- self.context.requestUpgrade()
40+ self.context.requestUpgrade(self.user)
41
42
43 class BranchEditView(BranchEditFormView, BranchNameValidationMixin):
44
45=== modified file 'lib/lp/code/interfaces/branch.py'
46--- lib/lp/code/interfaces/branch.py 2011-08-17 10:01:10 +0000
47+++ lib/lp/code/interfaces/branch.py 2011-08-22 18:13:31 +0000
48@@ -1063,7 +1063,7 @@
49 adapted to an IBranchTarget.
50 """
51
52- def requestUpgrade():
53+ def requestUpgrade(requester):
54 """Create an IBranchUpgradeJob to upgrade this branch."""
55
56 def branchChanged(stacked_on_url, last_revision_id, control_format,
57
58=== modified file 'lib/lp/code/model/branch.py'
59--- lib/lp/code/model/branch.py 2011-08-17 20:21:01 +0000
60+++ lib/lp/code/model/branch.py 2011-08-22 18:13:31 +0000
61@@ -1152,10 +1152,10 @@
62 BranchJob.job_type == BranchJobType.UPGRADE_BRANCH)
63 return jobs.count() > 0
64
65- def requestUpgrade(self):
66+ def requestUpgrade(self, requester):
67 """See `IBranch`."""
68 from lp.code.interfaces.branchjob import IBranchUpgradeJobSource
69- return getUtility(IBranchUpgradeJobSource).create(self)
70+ return getUtility(IBranchUpgradeJobSource).create(self, requester)
71
72 def _checkBranchVisibleByUser(self, user):
73 """Is *this* branch visible by the user.
74
75=== modified file 'lib/lp/code/model/branchjob.py'
76--- lib/lp/code/model/branchjob.py 2011-07-18 20:46:27 +0000
77+++ lib/lp/code/model/branchjob.py 2011-08-22 18:13:31 +0000
78@@ -21,7 +21,10 @@
79
80 from bzrlib.branch import Branch as BzrBranch
81 from bzrlib.diff import show_diff_trees
82-from bzrlib.errors import NoSuchFile
83+from bzrlib.errors import (
84+ NoSuchFile,
85+ NotBranchError,
86+ )
87 from bzrlib.log import (
88 log_formatter,
89 show_log,
90@@ -107,6 +110,7 @@
91 from lp.services.job.interfaces.job import JobStatus
92 from lp.services.job.model.job import Job
93 from lp.services.job.runner import BaseRunnableJob
94+from lp.services.mail.sendmail import format_address_for_person
95 from lp.translations.interfaces.translationimportqueue import (
96 ITranslationImportQueue,
97 )
98@@ -275,6 +279,11 @@
99 vars.append(('branch_name', self.context.branch.unique_name))
100 return vars
101
102+ def getErrorRecipients(self):
103+ if self.requester is None:
104+ return []
105+ return [format_address_for_person(self.requester)]
106+
107
108 class BranchDiffJob(BranchJobDerived):
109 """A Job that calculates the a diff related to a Branch."""
110@@ -361,14 +370,20 @@
111 classProvides(IBranchUpgradeJobSource)
112 class_job_type = BranchJobType.UPGRADE_BRANCH
113
114+ user_error_types = (NotBranchError,)
115+
116+ def getOperationDescription(self):
117+ return 'upgrading a branch'
118+
119 @classmethod
120- def create(cls, branch):
121+ def create(cls, branch, requester):
122 """See `IBranchUpgradeJobSource`."""
123 if not branch.needs_upgrading:
124 raise AssertionError('Branch does not need upgrading.')
125 if branch.upgrade_pending:
126 raise AssertionError('Branch already has upgrade pending.')
127- branch_job = BranchJob(branch, BranchJobType.UPGRADE_BRANCH, {})
128+ branch_job = BranchJob(
129+ branch, BranchJobType.UPGRADE_BRANCH, {}, requester=requester)
130 return cls(branch_job)
131
132 @staticmethod
133
134=== modified file 'lib/lp/code/model/tests/test_branch.py'
135--- lib/lp/code/model/tests/test_branch.py 2011-08-09 14:53:07 +0000
136+++ lib/lp/code/model/tests/test_branch.py 2011-08-22 18:13:31 +0000
137@@ -617,7 +617,7 @@
138 owner = removeSecurityProxy(branch).owner
139 login_person(owner)
140 self.addCleanup(logout)
141- branch.requestUpgrade()
142+ branch.requestUpgrade(branch.owner)
143
144 self.assertFalse(branch.needs_upgrading)
145
146@@ -682,7 +682,7 @@
147 owner = removeSecurityProxy(branch).owner
148 login_person(owner)
149 self.addCleanup(logout)
150- job = removeSecurityProxy(branch.requestUpgrade())
151+ job = removeSecurityProxy(branch.requestUpgrade(branch.owner))
152
153 jobs = list(getUtility(IBranchUpgradeJobSource).iterReady())
154 self.assertEqual(
155@@ -698,7 +698,7 @@
156 owner = removeSecurityProxy(branch).owner
157 login_person(owner)
158 self.addCleanup(logout)
159- self.assertRaises(AssertionError, branch.requestUpgrade)
160+ self.assertRaises(AssertionError, branch.requestUpgrade, branch.owner)
161
162 def test_requestUpgrade_upgrade_pending(self):
163 # If there is a pending upgrade already requested, requestUpgrade
164@@ -708,9 +708,9 @@
165 owner = removeSecurityProxy(branch).owner
166 login_person(owner)
167 self.addCleanup(logout)
168- branch.requestUpgrade()
169+ branch.requestUpgrade(branch.owner)
170
171- self.assertRaises(AssertionError, branch.requestUpgrade)
172+ self.assertRaises(AssertionError, branch.requestUpgrade, branch.owner)
173
174 def test_upgradePending(self):
175 # If there is a BranchUpgradeJob pending for the branch, return True.
176@@ -719,7 +719,7 @@
177 owner = removeSecurityProxy(branch).owner
178 login_person(owner)
179 self.addCleanup(logout)
180- branch.requestUpgrade()
181+ branch.requestUpgrade(branch.owner)
182
183 self.assertTrue(branch.upgrade_pending)
184
185@@ -737,7 +737,7 @@
186 owner = removeSecurityProxy(branch).owner
187 login_person(owner)
188 self.addCleanup(logout)
189- branch_job = removeSecurityProxy(branch.requestUpgrade())
190+ branch_job = removeSecurityProxy(branch.requestUpgrade(branch.owner))
191 branch_job.job.start()
192 branch_job.job.complete()
193
194@@ -1430,7 +1430,6 @@
195 package.distribution.owner,
196 package.development_version.setBranch,
197 pocket, branch, package.distribution.owner)
198- series_set = getUtility(IFindOfficialBranchLinks)
199 self.assertEqual(
200 {package: ('alter',
201 _('Branch is officially linked to a source package.'))},
202
203=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
204--- lib/lp/code/model/tests/test_branchjob.py 2011-08-12 11:37:08 +0000
205+++ lib/lp/code/model/tests/test_branchjob.py 2011-08-22 18:13:31 +0000
206@@ -15,7 +15,10 @@
207 Branch,
208 BzrBranchFormat7,
209 )
210-from bzrlib.bzrdir import BzrDirMetaFormat1
211+from bzrlib.bzrdir import (
212+ BzrDir,
213+ BzrDirMetaFormat1,
214+ )
215 from bzrlib.repofmt.pack_repo import RepositoryFormatKnitPack6
216 from bzrlib.revision import NULL_REVISION
217 from bzrlib.transport import get_transport
218@@ -77,6 +80,7 @@
219 from lp.scripts.helpers import TransactionFreeOperation
220 from lp.services.job.interfaces.job import JobStatus
221 from lp.services.job.model.job import Job
222+from lp.services.job.runner import JobRunner
223 from lp.services.osutils import override_environ
224 from lp.testing import TestCaseWithFactory
225 from lp.testing.mail_helpers import pop_notifications
226@@ -291,20 +295,18 @@
227 branch = self.factory.makeAnyBranch(
228 branch_format=BranchFormat.BZR_BRANCH_5,
229 repository_format=RepositoryFormat.BZR_REPOSITORY_4)
230- job = BranchUpgradeJob.create(branch)
231+ job = BranchUpgradeJob.create(branch, self.factory.makePerson())
232 verifyObject(IBranchUpgradeJob, job)
233
234 def test_upgrades_branch(self):
235 """Ensure that a branch with an outdated format is upgraded."""
236 self.useBzrBranches(direct_database=True)
237- db_branch, tree = self.create_branch_and_tree(format='knit')
238- db_branch.branch_format = BranchFormat.BZR_BRANCH_5
239- db_branch.repository_format = RepositoryFormat.BZR_KNIT_1
240+ db_branch, tree = self.create_knit()
241 self.assertEqual(
242 tree.branch.repository._format.get_format_string(),
243 'Bazaar-NG Knit Repository Format 1')
244
245- job = BranchUpgradeJob.create(db_branch)
246+ job = BranchUpgradeJob.create(db_branch, self.factory.makePerson())
247 self.becomeDbUser(config.upgrade_branches.dbuser)
248 with TransactionFreeOperation.require():
249 job.run()
250@@ -321,15 +323,21 @@
251 branch = self.factory.makeAnyBranch(
252 branch_format=BranchFormat.BZR_BRANCH_7,
253 repository_format=RepositoryFormat.BZR_CHK_2A)
254- self.assertRaises(AssertionError, BranchUpgradeJob.create, branch)
255+ self.assertRaises(
256+ AssertionError, BranchUpgradeJob.create, branch,
257+ self.factory.makePerson())
258+
259+ def create_knit(self):
260+ db_branch, tree = self.create_branch_and_tree(format='knit')
261+ db_branch.branch_format = BranchFormat.BZR_BRANCH_5
262+ db_branch.repository_format = RepositoryFormat.BZR_KNIT_1
263+ return db_branch, tree
264
265 def test_existing_bzr_backup(self):
266 # If the target branch already has a backup.bzr dir, the upgrade copy
267 # should remove it.
268 self.useBzrBranches(direct_database=True)
269- db_branch, tree = self.create_branch_and_tree(format='knit')
270- db_branch.branch_format = BranchFormat.BZR_BRANCH_5
271- db_branch.repository_format = RepositoryFormat.BZR_KNIT_1
272+ db_branch, tree = self.create_knit()
273
274 # Add a fake backup.bzr dir
275 source_branch_transport = get_transport(db_branch.getInternalBzrUrl())
276@@ -337,7 +345,7 @@
277 source_branch_transport.clone('.bzr').copy_tree_to_transport(
278 source_branch_transport.clone('backup.bzr'))
279
280- job = BranchUpgradeJob.create(db_branch)
281+ job = BranchUpgradeJob.create(db_branch, self.factory.makePerson())
282 self.becomeDbUser(config.upgrade_branches.dbuser)
283 job.run()
284
285@@ -355,6 +363,27 @@
286 branch.branchChanged('', 'new-id', None, None, None)
287 Store.of(branch).flush()
288
289+ def test_not_branch_error(self):
290+ self.useBzrBranches(direct_database=True)
291+ db_branch, tree = self.create_branch_and_tree()
292+ branch2 = BzrDir.create_branch_convenience('.')
293+ tree.branch.set_stacked_on_url(branch2.base)
294+ branch2.bzrdir.destroy_branch()
295+ # Create BranchUpgradeJob manually, because we're trying to upgrade a
296+ # branch that doesn't need upgrading.
297+ requester = self.factory.makePerson()
298+ branch_job = BranchJob(
299+ db_branch, BranchJobType.UPGRADE_BRANCH, {}, requester=requester)
300+ job = BranchUpgradeJob(branch_job)
301+ self.becomeDbUser(config.upgrade_branches.dbuser)
302+ runner = JobRunner([job])
303+ with self.noOops():
304+ runner.runJobHandleError(job)
305+ (mail,) = pop_notifications()
306+ self.assertEqual(
307+ 'Launchpad error while upgrading a branch', mail['subject'])
308+ self.assertIn('Not a branch', mail.get_payload(decode=True))
309+
310
311 class TestRevisionMailJob(TestCaseWithFactory):
312 """Tests for BranchDiffJob."""
313@@ -898,7 +927,7 @@
314 self.layer.switchDbUser('branchscanner')
315 self.updateDBRevisions(db_branch, tree.branch)
316 expected = (
317- u"-"*60 + '\n'
318+ u"-" * 60 + '\n'
319 "revno: 1" '\n'
320 "committer: Joe Bloggs <joe@example.com>" '\n'
321 "branch nick: %s" '\n'
322@@ -912,7 +941,7 @@
323 job.getRevisionMessage(first_revision, 1), expected)
324
325 expected_message = (
326- u"-"*60 + '\n'
327+ u"-" * 60 + '\n'
328 "revno: 2" '\n'
329 "committer: Joe Bloggs <joe@example.com>" '\n'
330 "branch nick: %s" '\n'
331@@ -988,7 +1017,7 @@
332 super(TestRosettaUploadJob, self).setUp()
333 self.series = None
334
335- def _makeBranchWithTreeAndFile(self, file_name, file_content = None):
336+ def _makeBranchWithTreeAndFile(self, file_name, file_content=None):
337 return self._makeBranchWithTreeAndFiles(((file_name, file_content), ))
338
339 def _makeBranchWithTreeAndFiles(self, files):
340@@ -1035,7 +1064,7 @@
341 try:
342 file_content = file_pair[1]
343 if file_content is None:
344- raise IndexError # Same as if missing.
345+ raise IndexError # Same as if missing.
346 except IndexError:
347 file_content = self.factory.getUniqueString()
348 dname = os.path.dirname(file_name)
349@@ -1060,7 +1089,7 @@
350 self.series.branch = self.branch
351 self.series.translations_autoimport_mode = mode
352
353- def _runJobWithFile(self, import_mode, file_name, file_content = None):
354+ def _runJobWithFile(self, import_mode, file_name, file_content=None):
355 return self._runJobWithFiles(
356 import_mode, ((file_name, file_content), ))
357
358@@ -1293,7 +1322,7 @@
359 # iterReady does not return jobs for branches where last_scanned_id
360 # and last_mirror_id are different.
361 self._makeBranchWithTreeAndFiles([])
362- self.branch.last_scanned_id = NULL_REVISION # Was not scanned yet.
363+ self.branch.last_scanned_id = NULL_REVISION # Was not scanned yet.
364 self._makeProductSeries(
365 TranslationsBranchImportMode.IMPORT_TEMPLATES)
366 # Put the job in ready state.
367
368=== modified file 'lib/lp/code/scripts/tests/test_upgrade_branches.py'
369--- lib/lp/code/scripts/tests/test_upgrade_branches.py 2010-10-04 19:50:45 +0000
370+++ lib/lp/code/scripts/tests/test_upgrade_branches.py 2011-08-22 18:13:31 +0000
371@@ -34,7 +34,7 @@
372 target_tree.branch.repository._format.get_format_string(),
373 'Bazaar-NG Knit Repository Format 1')
374
375- BranchUpgradeJob.create(target)
376+ BranchUpgradeJob.create(target, self.factory.makePerson())
377 transaction.commit()
378
379 retcode, stdout, stderr = run_script(
380@@ -62,7 +62,7 @@
381 target_tree.branch.repository._format.get_format_string(),
382 'Bazaar-NG Knit Repository Format 1')
383
384- BranchUpgradeJob.create(target)
385+ BranchUpgradeJob.create(target, self.factory.makePerson())
386 transaction.commit()
387
388 retcode, stdout, stderr = run_script(
389
390=== modified file 'lib/lp/scripts/tests/test_garbo.py'
391--- lib/lp/scripts/tests/test_garbo.py 2011-08-17 10:43:26 +0000
392+++ lib/lp/scripts/tests/test_garbo.py 2011-08-22 18:13:31 +0000
393@@ -848,7 +848,8 @@
394 db_branch.branch_format = BranchFormat.BZR_BRANCH_5
395 db_branch.repository_format = RepositoryFormat.BZR_KNIT_1
396 Store.of(db_branch).flush()
397- branch_job = BranchUpgradeJob.create(db_branch)
398+ branch_job = BranchUpgradeJob.create(
399+ db_branch, self.factory.makePerson())
400 branch_job.job.date_finished = THIRTY_DAYS_AGO
401
402 self.assertEqual(
403@@ -876,13 +877,14 @@
404 branch_format=BranchFormat.BZR_BRANCH_5,
405 repository_format=RepositoryFormat.BZR_KNIT_1)
406
407- branch_job = BranchUpgradeJob.create(db_branch)
408+ branch_job = BranchUpgradeJob.create(
409+ db_branch, self.factory.makePerson())
410 branch_job.job.date_finished = THIRTY_DAYS_AGO
411
412 db_branch2 = self.factory.makeAnyBranch(
413 branch_format=BranchFormat.BZR_BRANCH_5,
414 repository_format=RepositoryFormat.BZR_KNIT_1)
415- BranchUpgradeJob.create(db_branch2)
416+ BranchUpgradeJob.create(db_branch2, self.factory.makePerson())
417
418 self.runDaily()
419
420
421=== modified file 'lib/lp/testing/__init__.py'
422--- lib/lp/testing/__init__.py 2011-08-15 09:21:48 +0000
423+++ lib/lp/testing/__init__.py 2011-08-22 18:13:31 +0000
424@@ -437,13 +437,22 @@
425 'Events were generated: %s.' % event_list)
426 return result
427
428+ @contextmanager
429+ def noOops(self):
430+ oops = errorlog.globalErrorUtility.getLastOopsReport()
431+ try:
432+ yield
433+ finally:
434+ self.assertNoNewOops(oops)
435+
436 def assertNoNewOops(self, old_oops):
437 """Assert that no oops has been recorded since old_oops."""
438 oops = errorlog.globalErrorUtility.getLastOopsReport()
439 if old_oops is None:
440 self.assertIs(None, oops)
441 else:
442- self.assertEqual(oops.id, old_oops.id)
443+ self.assertTrue(
444+ oops.id == old_oops.id, 'Oops recorded: %s' % oops.id)
445
446 def assertSqlAttributeEqualsDate(self, sql_object, attribute_name, date):
447 """Fail unless the value of the attribute is equal to the date.