Merge lp:~abentley/launchpad/upgrade-badly-stacked into lp:launchpad

Proposed by Aaron Bentley on 2012-08-22
Status: Merged
Approved by: Aaron Bentley on 2012-08-22
Approved revision: no longer in the source branch.
Merged at revision: 15852
Proposed branch: lp:~abentley/launchpad/upgrade-badly-stacked
Merge into: lp:launchpad
Diff against target: 293 lines (+83/-34)
5 files modified
lib/lp/codehosting/safe_open.py (+9/-8)
lib/lp/codehosting/tests/helpers.py (+11/-0)
lib/lp/codehosting/tests/test_safe_open.py (+43/-22)
lib/lp/codehosting/tests/test_upgrade.py (+16/-3)
lib/lp/codehosting/upgrade.py (+4/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/upgrade-badly-stacked
Reviewer Review Type Date Requested Status
Deryck Hodge (community) 2012-08-22 Approve on 2012-08-22
Review via email: mp+120854@code.launchpad.net

Commit Message

Allow upgrader to fix cross-format stacking.

Description of the Change

= Summary =
Fix bug #1039558: upgrade_all_branches script fails on cross-format stacking

== Proposed fix ==
Implement and use ignore_fallbacks parameter to safe_open.

== Pre-implementation notes ==
None

== LOC Rationale ==
I have a LOC credit of 1860. Also, once we run this script, we can delete all the associated code.

== Implementation details ==
bzr's upgrade functionality was designed to support upgrading a branch that was stacked on different-format branch, since that is inevitable. (Upgrade either a stacked or stacked-on branch, and you leave the other branch in that situation.) The ignore_fallbacks flag on Branch.open is designed to support such upgrades, but we weren't using it. It wasn't exposed on BzrBranch.getBzrBranch, but since that's a short method, it was simpler to just use safe_open directly.

As a driveby, I fixed old stacking-related tests to forgo specifying a format. Evidently, these tests were written before the default format supported stacking (i.e. before 2a became the default). Now, it's just confusing to specify a format.

I extracted force_stacked_on from testSelfStackedBranch, so that we have a standardized, resuable way of forcing stacking.

== Tests ==
bin/test -m 'test_upgrade|test_safe_open'

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/codehosting/tests/helpers.py
  lib/lp/codehosting/tests/test_upgrade.py
  lib/lp/codehosting/tests/test_safe_open.py
  lib/lp/codehosting/safe_open.py
  lib/lp/codehosting/upgrade.py

To post a comment you must log in.
Deryck Hodge (deryck) wrote :

Looks good, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/codehosting/safe_open.py'
2--- lib/lp/codehosting/safe_open.py 2012-06-29 08:40:05 +0000
3+++ lib/lp/codehosting/safe_open.py 2012-08-22 19:10:27 +0000
4@@ -326,7 +326,7 @@
5 redirected)
6 return format.open(transport)
7
8- def open(self, url):
9+ def open(self, url, ignore_fallbacks=False):
10 """Open the Bazaar branch at url, first checking for safety.
11
12 What safety means is defined by a subclasses `followReference` and
13@@ -334,20 +334,21 @@
14 """
15 url = self.checkAndFollowBranchReference(url)
16
17- def open_branch(url):
18+ def open_branch(url, ignore_fallbacks):
19 dir = self._open_dir(url)
20- return dir.open_branch()
21+ return dir.open_branch(ignore_fallbacks=ignore_fallbacks)
22 return self.runWithTransformFallbackLocationHookInstalled(
23- open_branch, url)
24-
25-
26-def safe_open(allowed_scheme, url):
27+ open_branch, url, ignore_fallbacks)
28+
29+
30+def safe_open(allowed_scheme, url, ignore_fallbacks=False):
31 """Open the branch at `url`, only accessing URLs on `allowed_scheme`.
32
33 :raises BadUrl: An attempt was made to open a URL that was not on
34 `allowed_scheme`.
35 """
36- return SafeBranchOpener(SingleSchemePolicy(allowed_scheme)).open(url)
37+ return SafeBranchOpener(SingleSchemePolicy(allowed_scheme)).open(url,
38+ ignore_fallbacks=ignore_fallbacks)
39
40
41 SafeBranchOpener.install_hook()
42
43=== modified file 'lib/lp/codehosting/tests/helpers.py'
44--- lib/lp/codehosting/tests/helpers.py 2012-01-01 02:58:52 +0000
45+++ lib/lp/codehosting/tests/helpers.py 2012-08-22 19:10:27 +0000
46@@ -10,6 +10,7 @@
47 'CodeHostingTestProviderAdapter',
48 'create_branch_with_one_revision',
49 'deferToThread',
50+ 'force_stacked_on_url',
51 'LoomTestMixin',
52 'make_bazaar_branch_and_tree',
53 'TestResultWrapper',
54@@ -187,6 +188,16 @@
55 return tree
56
57
58+def force_stacked_on_url(branch, url):
59+ """Set the stacked_on url of a branch without standard error-checking.
60+
61+ Bazaar 1.17 and up make it harder to create branches with invalid
62+ stacking. It's still worth testing that we don't blow up in the face of
63+ them, so this function lets us create them anyway.
64+ """
65+ branch.get_config().set_user_option('stacked_on_location', url)
66+
67+
68 class TestResultWrapper:
69 """A wrapper for `TestResult` that knows about bzrlib's `TestSkipped`."""
70
71
72=== modified file 'lib/lp/codehosting/tests/test_safe_open.py'
73--- lib/lp/codehosting/tests/test_safe_open.py 2011-12-15 10:09:59 +0000
74+++ lib/lp/codehosting/tests/test_safe_open.py 2012-08-22 19:10:27 +0000
75@@ -32,6 +32,9 @@
76 SafeBranchOpener,
77 WhitelistPolicy,
78 )
79+from lp.codehosting.tests.helpers import (
80+ force_stacked_on_url,
81+ )
82 from lp.testing import TestCase
83
84
85@@ -195,8 +198,8 @@
86 def testAllowedURL(self):
87 # checkSource does not raise an exception for branches stacked on
88 # branches with allowed URLs.
89- stacked_on_branch = self.make_branch('base-branch', format='1.6')
90- stacked_branch = self.make_branch('stacked-branch', format='1.6')
91+ stacked_on_branch = self.make_branch('base-branch')
92+ stacked_branch = self.make_branch('stacked-branch')
93 stacked_branch.set_stacked_on_url(stacked_on_branch.base)
94 opener = self.makeBranchOpener(
95 [stacked_branch.base, stacked_on_branch.base])
96@@ -215,8 +218,8 @@
97 def testAllowedRelativeURL(self):
98 # checkSource passes on absolute urls to checkOneURL, even if the
99 # value of stacked_on_location in the config is set to a relative URL.
100- stacked_on_branch = self.make_branch('base-branch', format='1.6')
101- stacked_branch = self.make_branch('stacked-branch', format='1.6')
102+ stacked_on_branch = self.make_branch('base-branch')
103+ stacked_branch = self.make_branch('stacked-branch')
104 stacked_branch.set_stacked_on_url('../base-branch')
105 opener = self.makeBranchOpener(
106 [stacked_branch.base, stacked_on_branch.base])
107@@ -229,10 +232,10 @@
108 def testAllowedRelativeNested(self):
109 # Relative URLs are resolved relative to the stacked branch.
110 self.get_transport().mkdir('subdir')
111- a = self.make_branch('subdir/a', format='1.6')
112- b = self.make_branch('b', format='1.6')
113+ a = self.make_branch('subdir/a')
114+ b = self.make_branch('b')
115 b.set_stacked_on_url('../subdir/a')
116- c = self.make_branch('subdir/c', format='1.6')
117+ c = self.make_branch('subdir/c')
118 c.set_stacked_on_url('../../b')
119 opener = self.makeBranchOpener([c.base, b.base, a.base])
120 # This doesn't raise an exception.
121@@ -241,8 +244,8 @@
122 def testForbiddenURL(self):
123 # checkSource raises a BadUrl exception if a branch is stacked on a
124 # branch with a forbidden URL.
125- stacked_on_branch = self.make_branch('base-branch', format='1.6')
126- stacked_branch = self.make_branch('stacked-branch', format='1.6')
127+ stacked_on_branch = self.make_branch('base-branch')
128+ stacked_branch = self.make_branch('stacked-branch')
129 stacked_branch.set_stacked_on_url(stacked_on_branch.base)
130 opener = self.makeBranchOpener([stacked_branch.base])
131 self.assertRaises(BadUrl, opener.open, stacked_branch.base)
132@@ -250,10 +253,10 @@
133 def testForbiddenURLNested(self):
134 # checkSource raises a BadUrl exception if a branch is stacked on a
135 # branch that is in turn stacked on a branch with a forbidden URL.
136- a = self.make_branch('a', format='1.6')
137- b = self.make_branch('b', format='1.6')
138+ a = self.make_branch('a')
139+ b = self.make_branch('b')
140 b.set_stacked_on_url(a.base)
141- c = self.make_branch('c', format='1.6')
142+ c = self.make_branch('c')
143 c.set_stacked_on_url(b.base)
144 opener = self.makeBranchOpener([c.base, b.base])
145 self.assertRaises(BadUrl, opener.open, c.base)
146@@ -261,11 +264,8 @@
147 def testSelfStackedBranch(self):
148 # checkSource raises StackingLoopError if a branch is stacked on
149 # itself. This avoids infinite recursion errors.
150- a = self.make_branch('a', format='1.6')
151- # Bazaar 1.17 and up make it harder to create branches like this.
152- # It's still worth testing that we don't blow up in the face of them,
153- # so we grovel around a bit to create one anyway.
154- a.get_config().set_user_option('stacked_on_location', a.base)
155+ a = self.make_branch('a')
156+ force_stacked_on_url(a, a.base)
157 opener = self.makeBranchOpener([a.base])
158 self.assertRaises(BranchLoopError, opener.open, a.base)
159
160@@ -273,8 +273,8 @@
161 # checkSource raises StackingLoopError if a branch is stacked in such
162 # a way so that it is ultimately stacked on itself. e.g. a stacked on
163 # b stacked on a.
164- a = self.make_branch('a', format='1.6')
165- b = self.make_branch('b', format='1.6')
166+ a = self.make_branch('a')
167+ b = self.make_branch('b')
168 a.set_stacked_on_url(b.base)
169 b.set_stacked_on_url(a.base)
170 opener = self.makeBranchOpener([a.base, b.base])
171@@ -283,8 +283,8 @@
172
173 def testCustomOpener(self):
174 # A custom function for opening a control dir can be specified.
175- a = self.make_branch('a', format='2a')
176- b = self.make_branch('b', format='2a')
177+ a = self.make_branch('a')
178+ b = self.make_branch('b')
179 b.set_stacked_on_url(a.base)
180
181 TrackingProber.seen_urls = []
182@@ -296,7 +296,7 @@
183
184 def testCustomOpenerWithBranchReference(self):
185 # A custom function for opening a control dir can be specified.
186- a = self.make_branch('a', format='2a')
187+ a = self.make_branch('a')
188 b_dir = self.make_bzrdir('b')
189 b = BranchReferenceFormat().initialize(b_dir, target_branch=a)
190 TrackingProber.seen_urls = []
191@@ -306,6 +306,20 @@
192 self.assertEquals(
193 set(TrackingProber.seen_urls), set([b.base, a.base]))
194
195+ def test_ignore_fallbacks(self):
196+ """"Cross-format stacking doesn't error with ignore_fallbacks."""
197+ stacked, stacked_on = make_cross_format_stacked(self)
198+ opener = self.makeBranchOpener([stacked.base, stacked_on.base])
199+ opener.open(stacked.base, ignore_fallbacks=True)
200+
201+
202+def make_cross_format_stacked(test_case):
203+ test_case.get_transport().mkdir('inside')
204+ stacked = test_case.make_branch('inside/stacked', format='1.6')
205+ stacked_on = test_case.make_branch('inside/stacked-on', format='2a')
206+ force_stacked_on_url(stacked, stacked_on.base)
207+ return stacked, stacked_on
208+
209
210 class TestSafeOpen(TestCaseWithTransport):
211 """Tests for `safe_open`."""
212@@ -361,3 +375,10 @@
213 self.get_url('outside/stacked-on'))
214 self.assertRaises(
215 BadUrl, safe_open, scheme, get_chrooted_url('stacked'))
216+
217+ def test_ignore_fallbacks(self):
218+ """"Cross-format stacking doesn't error with ignore_fallbacks."""
219+ scheme, get_chrooted_url = self.get_chrooted_scheme('inside')
220+ stacked, stacked_on = make_cross_format_stacked(self)
221+ force_stacked_on_url(stacked, get_chrooted_url('stacked-on'))
222+ safe_open(scheme, get_chrooted_url('stacked'), ignore_fallbacks=True)
223
224=== modified file 'lib/lp/codehosting/tests/test_upgrade.py'
225--- lib/lp/codehosting/tests/test_upgrade.py 2012-06-14 05:18:22 +0000
226+++ lib/lp/codehosting/tests/test_upgrade.py 2012-08-22 19:10:27 +0000
227@@ -29,6 +29,7 @@
228 )
229 from lp.codehosting.bzrutils import read_locked
230 from lp.codehosting.upgrade import Upgrader
231+from lp.codehosting.tests.helpers import force_stacked_on_url
232 from lp.services.config import config
233 from lp.testing import TestCaseWithFactory
234 from lp.testing.layers import ZopelessDatabaseLayer
235@@ -55,16 +56,19 @@
236 bzr_branch = tree.branch
237 return self.getUpgrader(bzr_branch, branch)
238
239+ def getTargetDir(self, bzr_branch):
240+ return self.useFixture(TempDir(
241+ rootdir=dirname(config.codehosting.mirrored_branches_root))).path
242+
243 def getUpgrader(self, bzr_branch, branch):
244 """Return an upgrader for the specified branches.
245
246 :param bzr_branch: the bzr branch to use.
247 :param branch: The DB branch to use.
248 """
249- target_dir = self.useFixture(TempDir(
250- rootdir=dirname(config.codehosting.mirrored_branches_root))).path
251 return Upgrader(
252- branch, target_dir, logging.getLogger(), bzr_branch)
253+ branch, self.getTargetDir(bzr_branch), logging.getLogger(),
254+ bzr_branch)
255
256 def addTreeReference(self, tree):
257 """Add a tree reference to a tree and commit.
258@@ -270,3 +274,12 @@
259 upgraded.repository._format.__class__)
260 self.assertEqual(
261 'foo', upgraded.repository.get_revision('prepare-commit').message)
262+
263+ def test_invalid_stacking(self):
264+ """Upgrade tolerates branches stacked on different-format branches."""
265+ self.useBzrBranches(direct_database=True)
266+ target, target_tree = self.create_branch_and_tree(format='1.6')
267+ trunk, trunk_tree = self.create_branch_and_tree(format='2a')
268+ force_stacked_on_url(target_tree.branch, trunk_tree.branch.base)
269+ Upgrader(target, self.getTargetDir(target_tree.branch),
270+ logging.getLogger())
271
272=== modified file 'lib/lp/codehosting/upgrade.py'
273--- lib/lp/codehosting/upgrade.py 2012-03-22 23:21:24 +0000
274+++ lib/lp/codehosting/upgrade.py 2012-08-22 19:10:27 +0000
275@@ -36,6 +36,7 @@
276 )
277 from lp.code.model.branch import Branch
278 from lp.codehosting.bzrutils import read_locked
279+from lp.codehosting.safe_open import safe_open
280 from lp.codehosting.vfs.branchfs import get_real_branch_path
281 from lp.services.database.lpstorm import IStore
282
283@@ -51,7 +52,9 @@
284 self.branch = branch
285 self.bzr_branch = bzr_branch
286 if self.bzr_branch is None:
287- self.bzr_branch = self.branch.getBzrBranch()
288+ self.bzr_branch = safe_open('lp-internal',
289+ self.branch.getInternalBzrUrl(),
290+ ignore_fallbacks=True)
291 self.target_dir = target_dir
292 self.target_subdir = os.path.join(
293 self.target_dir, str(self.branch.id))