Merge lp:~jelmer/bzr/update-feature-flags into lp:bzr
- update-feature-flags
- Merge into bzr.dev
Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merged at revision: | 6408 |
Proposed branch: | lp:~jelmer/bzr/update-feature-flags |
Merge into: | lp:bzr |
Diff against target: |
367 lines (+97/-27) 12 files modified
bzrlib/branch.py (+10/-0) bzrlib/bzrdir.py (+35/-8) bzrlib/repository.py (+10/-0) bzrlib/tests/blackbox/test_annotate.py (+1/-1) bzrlib/tests/blackbox/test_commit.py (+1/-1) bzrlib/tests/test_branch.py (+4/-3) bzrlib/tests/test_bzrdir.py (+4/-3) bzrlib/tests/test_remote.py (+10/-6) bzrlib/tests/test_repository.py (+1/-3) bzrlib/tests/test_workingtree.py (+1/-2) bzrlib/workingtree.py (+10/-0) doc/developers/feature-flags.txt (+10/-0) |
To merge this branch: | bzr merge lp:~jelmer/bzr/update-feature-flags |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vincent Ladeuil | Approve | ||
Review via email: mp+86742@code.launchpad.net |
Commit message
Add {Repository,
Description of the change
Add update_
This allows us to actually use feature flags.
Jelmer Vernooij (jelmer) wrote : | # |
On 12/23/2011 08:18 AM, Vincent Ladeuil wrote:
> Review: Needs Fixing
>
>> This allows us to actually use feature flags.
>
> The code seems fine, but there is no doc to explain the sentence above :)
>
> How should this be used ? Via hooks ? Directly ? Some example or at least
> some guidance sounds necessary at this point.
That's intentional because there is no right way. :-)
Plugins would call these methods when they want to enable features on a
repository/
bzr-search I imagine it will set the feature flag when the user runs
"bzr index" for the first time. In bzr-git it would automatically set it
when the user fetches into the repository with bzr-git, etc.
Where should I be documenting this?
> 95 + ('branch-format', self.as_string()),
> 104 + self.target_
> 113 + self.target_
>
> Strictly speaking this should have been part of your previous proposal,
> right ? I'm mostly concerned about whether this needs to be backported and
> why no tests caught them (I suspect it's because no test activate features
> but it's slightly worrying that other occurrences could have been
> forgotten).
This is for upgrades dealing with features properly. I'll pull it out
into a searate branch with tests.
Cheers,
Jelmer
Vincent Ladeuil (vila) wrote : | # |
> > How should this be used ? Via hooks ? Directly ? Some example or at least
> > some guidance sounds necessary at this point.
> That's intentional because there is no right way. :-)
>
> Plugins would call these methods when they want to enable features on a
> repository/
> bzr-search I imagine it will set the feature flag when the user runs
> "bzr index" for the first time. In bzr-git it would automatically set it
> when the user fetches into the repository with bzr-git, etc.
Right, thanks for the clarification. Indeed, these examples make it quite clearer how they should be used.
>
> Where should I be documenting this?
Right into doc/developers/
> > 95 + ('branch-format', self.as_string()),
> > 104 + self.target_
> > 113 + self.target_
> >
> > Strictly speaking this should have been part of your previous proposal,
> > right ? I'm mostly concerned about whether this needs to be backported and
> > why no tests caught them (I suspect it's because no test activate features
> > but it's slightly worrying that other occurrences could have been
> > forgotten).
> This is for upgrades dealing with features properly.
Ha ok.
> I'll pull it out into a searate branch with tests.
Cool.
With some doc added based on your answer above, I'm fine with this landing, up to you to ask for a review about it or from someone else.
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Preview Diff
1 | === modified file 'bzrlib/branch.py' |
2 | --- bzrlib/branch.py 2011-12-22 18:52:58 +0000 |
3 | +++ bzrlib/branch.py 2011-12-30 15:29:25 +0000 |
4 | @@ -2721,6 +2721,16 @@ |
5 | self._transport.put_bytes('last-revision', out_string, |
6 | mode=self.bzrdir._get_file_mode()) |
7 | |
8 | + @needs_write_lock |
9 | + def update_feature_flags(self, updated_flags): |
10 | + """Update the feature flags for this branch. |
11 | + |
12 | + :param updated_flags: Dictionary mapping feature names to necessities |
13 | + A necessity can be None to indicate the feature should be removed |
14 | + """ |
15 | + self._format._update_feature_flags(updated_flags) |
16 | + self.control_transport.put_bytes('format', self._format.as_string()) |
17 | + |
18 | |
19 | class FullHistoryBzrBranch(BzrBranch): |
20 | """Bzr branch which contains the full revision history.""" |
21 | |
22 | === modified file 'bzrlib/bzrdir.py' |
23 | --- bzrlib/bzrdir.py 2011-12-22 15:33:16 +0000 |
24 | +++ bzrlib/bzrdir.py 2011-12-30 15:29:25 +0000 |
25 | @@ -786,6 +786,19 @@ |
26 | def __repr__(self): |
27 | return "<%s at %r>" % (self.__class__.__name__, self.user_url) |
28 | |
29 | + def update_feature_flags(self, updated_flags): |
30 | + """Update the features required by this bzrdir. |
31 | + |
32 | + :param updated_flags: Dictionary mapping feature names to necessities |
33 | + A necessity can be None to indicate the feature should be removed |
34 | + """ |
35 | + self.control_files.lock_write() |
36 | + try: |
37 | + self._format._update_feature_flags(updated_flags) |
38 | + self.transport.put_bytes('branch-format', self._format.as_string()) |
39 | + finally: |
40 | + self.control_files.unlock() |
41 | + |
42 | |
43 | class BzrDirMeta1(BzrDir): |
44 | """A .bzr meta version 1 control object. |
45 | @@ -796,6 +809,11 @@ |
46 | present within a BzrDir. |
47 | """ |
48 | |
49 | + def __init__(self, _transport, _format): |
50 | + super(BzrDirMeta1, self).__init__(_transport, _format) |
51 | + self.control_files = lockable_files.LockableFiles(self.control_transport, |
52 | + self._format._lock_file_name, self._format._lock_class) |
53 | + |
54 | def can_convert_format(self): |
55 | """See BzrDir.can_convert_format().""" |
56 | return True |
57 | @@ -993,11 +1011,6 @@ |
58 | BzrDirMeta1. |
59 | """ |
60 | |
61 | - def __init__(self, _transport, _format): |
62 | - super(BzrDirMeta1Colo, self).__init__(_transport, _format) |
63 | - self.control_files = lockable_files.LockableFiles(self.control_transport, |
64 | - self._format._lock_file_name, self._format._lock_class) |
65 | - |
66 | def _get_branch_path(self, name): |
67 | """Obtain the branch path to use. |
68 | |
69 | @@ -1213,6 +1226,20 @@ |
70 | return (self.__class__ is other.__class__ and |
71 | self.features == other.features) |
72 | |
73 | + def _update_feature_flags(self, updated_flags): |
74 | + """Update the feature flags in this format. |
75 | + |
76 | + :param updated_flags: Updated feature flags |
77 | + """ |
78 | + for name, necessity in updated_flags.iteritems(): |
79 | + if necessity is None: |
80 | + try: |
81 | + del self.features[name] |
82 | + except KeyError: |
83 | + pass |
84 | + else: |
85 | + self.features[name] = necessity |
86 | + |
87 | |
88 | class BzrProber(controldir.Prober): |
89 | """Prober for formats that use a .bzr/ control directory.""" |
90 | @@ -1451,7 +1478,7 @@ |
91 | "This is a Bazaar control directory.\n" |
92 | "Do not change any files in this directory.\n" |
93 | "See http://bazaar.canonical.com/ for more information about Bazaar.\n"), |
94 | - ('branch-format', self.get_format_string()), |
95 | + ('branch-format', self.as_string()), |
96 | ] |
97 | # NB: no need to escape relative paths that are url safe. |
98 | control_files = lockable_files.LockableFiles(bzrdir_transport, |
99 | @@ -1866,7 +1893,7 @@ |
100 | def convert(self, to_convert, pb): |
101 | """See Converter.convert().""" |
102 | to_convert.transport.put_bytes('branch-format', |
103 | - self.target_format.get_format_string()) |
104 | + self.target_format.as_string()) |
105 | return BzrDir.open_from_transport(to_convert.root_transport) |
106 | |
107 | |
108 | @@ -1892,7 +1919,7 @@ |
109 | finally: |
110 | to_convert.control_files.unlock() |
111 | to_convert.transport.put_bytes('branch-format', |
112 | - self.target_format.get_format_string()) |
113 | + self.target_format.as_string()) |
114 | return BzrDir.open_from_transport(to_convert.root_transport) |
115 | |
116 | |
117 | |
118 | === modified file 'bzrlib/repository.py' |
119 | --- bzrlib/repository.py 2011-12-19 19:15:58 +0000 |
120 | +++ bzrlib/repository.py 2011-12-30 15:29:25 +0000 |
121 | @@ -1293,6 +1293,16 @@ |
122 | """Returns the policy for making working trees on new branches.""" |
123 | return not self._transport.has('no-working-trees') |
124 | |
125 | + @needs_write_lock |
126 | + def update_feature_flags(self, updated_flags): |
127 | + """Update the feature flags for this branch. |
128 | + |
129 | + :param updated_flags: Dictionary mapping feature names to necessities |
130 | + A necessity can be None to indicate the feature should be removed |
131 | + """ |
132 | + self._format._update_feature_flags(updated_flags) |
133 | + self.control_transport.put_bytes('format', self._format.as_string()) |
134 | + |
135 | |
136 | class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry): |
137 | """Repository format registry.""" |
138 | |
139 | === modified file 'bzrlib/tests/blackbox/test_annotate.py' |
140 | --- bzrlib/tests/blackbox/test_annotate.py 2011-12-14 21:57:37 +0000 |
141 | +++ bzrlib/tests/blackbox/test_annotate.py 2011-12-30 15:29:25 +0000 |
142 | @@ -326,7 +326,7 @@ |
143 | # being too low. If rpc_count increases, more network roundtrips have |
144 | # become necessary for this use case. Please do not adjust this number |
145 | # upwards without agreement from bzr's network support maintainers. |
146 | - self.assertLength(15, self.hpss_calls) |
147 | + self.assertLength(16, self.hpss_calls) |
148 | self.assertLength(1, self.hpss_connections) |
149 | self.expectFailure("annotate accesses inventories, which require VFS access", |
150 | self.assertThat, self.hpss_calls, ContainsNoVfsCalls) |
151 | |
152 | === modified file 'bzrlib/tests/blackbox/test_commit.py' |
153 | --- bzrlib/tests/blackbox/test_commit.py 2011-12-30 14:19:53 +0000 |
154 | +++ bzrlib/tests/blackbox/test_commit.py 2011-12-30 15:29:25 +0000 |
155 | @@ -889,7 +889,7 @@ |
156 | # being too low. If rpc_count increases, more network roundtrips have |
157 | # become necessary for this use case. Please do not adjust this number |
158 | # upwards without agreement from bzr's network support maintainers. |
159 | - self.assertLength(213, self.hpss_calls) |
160 | + self.assertLength(214, self.hpss_calls) |
161 | self.assertLength(2, self.hpss_connections) |
162 | self.expectFailure("commit still uses VFS calls", |
163 | self.assertThat, self.hpss_calls, ContainsNoVfsCalls) |
164 | |
165 | === modified file 'bzrlib/tests/test_branch.py' |
166 | --- bzrlib/tests/test_branch.py 2011-12-22 18:52:58 +0000 |
167 | +++ bzrlib/tests/test_branch.py 2011-12-30 15:29:25 +0000 |
168 | @@ -219,12 +219,13 @@ |
169 | |
170 | def test_find_format_with_features(self): |
171 | tree = self.make_branch_and_tree('.', format='2a') |
172 | - tree.branch.control_transport.put_bytes('format', |
173 | - tree.branch._format.get_format_string() + |
174 | - "optional name\n") |
175 | + tree.branch.update_feature_flags({"name": "optional"}) |
176 | found_format = _mod_branch.BranchFormatMetadir.find_format(tree.bzrdir) |
177 | self.assertIsInstance(found_format, _mod_branch.BranchFormatMetadir) |
178 | self.assertEquals(found_format.features.get("name"), "optional") |
179 | + tree.branch.update_feature_flags({"name": None}) |
180 | + branch = _mod_branch.Branch.open('.') |
181 | + self.assertEquals(branch._format.features, {}) |
182 | |
183 | def test_register_unregister_format(self): |
184 | # Test the deprecated format registration functions |
185 | |
186 | === modified file 'bzrlib/tests/test_bzrdir.py' |
187 | --- bzrlib/tests/test_bzrdir.py 2011-12-22 15:33:16 +0000 |
188 | +++ bzrlib/tests/test_bzrdir.py 2011-12-30 15:29:25 +0000 |
189 | @@ -1027,14 +1027,15 @@ |
190 | |
191 | def test_with_features(self): |
192 | tree = self.make_branch_and_tree('tree', format='2a') |
193 | - tree.bzrdir.control_transport.put_bytes( |
194 | - 'branch-format', |
195 | - tree.bzrdir._format.get_format_string() + "required bar\n") |
196 | + tree.bzrdir.update_feature_flags({"bar": "required"}) |
197 | self.assertRaises(errors.MissingFeature, bzrdir.BzrDir.open, 'tree') |
198 | bzrdir.BzrDirMetaFormat1.register_feature('bar') |
199 | self.addCleanup(bzrdir.BzrDirMetaFormat1.unregister_feature, 'bar') |
200 | dir = bzrdir.BzrDir.open('tree') |
201 | self.assertEquals("required", dir._format.features.get("bar")) |
202 | + tree.bzrdir.update_feature_flags({"bar": None, "nonexistant": None}) |
203 | + dir = bzrdir.BzrDir.open('tree') |
204 | + self.assertEquals({}, dir._format.features) |
205 | |
206 | def test_needs_conversion_different_working_tree(self): |
207 | # meta1dirs need an conversion if any element is not the default. |
208 | |
209 | === modified file 'bzrlib/tests/test_remote.py' |
210 | --- bzrlib/tests/test_remote.py 2011-12-15 12:59:56 +0000 |
211 | +++ bzrlib/tests/test_remote.py 2011-12-30 15:29:25 +0000 |
212 | @@ -933,6 +933,7 @@ |
213 | # name. |
214 | client.add_success_response_with_body( |
215 | "Bazaar-NG meta directory, format 1\n", 'ok') |
216 | + client.add_success_response('stat', '0', '65535') |
217 | client.add_success_response_with_body( |
218 | reference_format.get_format_string(), 'ok') |
219 | # PackRepository wants to do a stat |
220 | @@ -947,6 +948,7 @@ |
221 | ('call', 'BzrDir.find_repositoryV2', ('quack/',)), |
222 | ('call', 'BzrDir.find_repository', ('quack/',)), |
223 | ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)), |
224 | + ('call', 'stat', ('/quack/.bzr',)), |
225 | ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)), |
226 | ('call', 'stat', ('/quack/.bzr/repository',)), |
227 | ], |
228 | @@ -966,6 +968,7 @@ |
229 | # name. |
230 | client.add_success_response_with_body( |
231 | "Bazaar-NG meta directory, format 1\n", 'ok') |
232 | + client.add_success_response('stat', '0', '65535') |
233 | client.add_success_response_with_body( |
234 | reference_format.get_format_string(), 'ok') |
235 | # PackRepository wants to do a stat |
236 | @@ -979,6 +982,7 @@ |
237 | [('call', 'BzrDir.find_repositoryV3', ('quack/',)), |
238 | ('call', 'BzrDir.find_repositoryV2', ('quack/',)), |
239 | ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)), |
240 | + ('call', 'stat', ('/quack/.bzr',)), |
241 | ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)), |
242 | ('call', 'stat', ('/quack/.bzr/repository',)), |
243 | ], |
244 | @@ -1270,7 +1274,7 @@ |
245 | verb = 'Branch.set_parent_location' |
246 | self.disable_verb(verb) |
247 | branch.set_parent('http://foo/') |
248 | - self.assertLength(12, self.hpss_calls) |
249 | + self.assertLength(13, self.hpss_calls) |
250 | |
251 | |
252 | class TestBranchGetTagsBytes(RemoteBranchTestCase): |
253 | @@ -1998,7 +2002,7 @@ |
254 | self.addCleanup(branch.unlock) |
255 | self.reset_smart_call_log() |
256 | branch._get_config().set_option('value', 'name') |
257 | - self.assertLength(10, self.hpss_calls) |
258 | + self.assertLength(11, self.hpss_calls) |
259 | self.assertEqual('value', branch._get_config().get_option('name')) |
260 | |
261 | def test_backwards_compat_set_option_with_dict(self): |
262 | @@ -2012,7 +2016,7 @@ |
263 | config = branch._get_config() |
264 | value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'} |
265 | config.set_option(value_dict, 'name') |
266 | - self.assertLength(10, self.hpss_calls) |
267 | + self.assertLength(11, self.hpss_calls) |
268 | self.assertEqual(value_dict, branch._get_config().get_option('name')) |
269 | |
270 | |
271 | @@ -2138,7 +2142,7 @@ |
272 | self.reset_smart_call_log() |
273 | self.assertEquals((0, ), |
274 | branch.revision_id_to_dotted_revno('null:')) |
275 | - self.assertLength(7, self.hpss_calls) |
276 | + self.assertLength(8, self.hpss_calls) |
277 | |
278 | |
279 | class TestBzrDirGetSetConfig(RemoteBzrDirTestCase): |
280 | @@ -2160,7 +2164,7 @@ |
281 | self.reset_smart_call_log() |
282 | config = bzrdir.get_config() |
283 | config.set_default_stack_on('/') |
284 | - self.assertLength(3, self.hpss_calls) |
285 | + self.assertLength(4, self.hpss_calls) |
286 | |
287 | def test_backwards_compat_get_option(self): |
288 | self.setup_smart_server_with_call_log() |
289 | @@ -2170,7 +2174,7 @@ |
290 | self.reset_smart_call_log() |
291 | self.assertEqual(None, |
292 | bzrdir._get_config().get_option('default_stack_on')) |
293 | - self.assertLength(3, self.hpss_calls) |
294 | + self.assertLength(4, self.hpss_calls) |
295 | |
296 | |
297 | class TestTransportIsReadonly(tests.TestCase): |
298 | |
299 | === modified file 'bzrlib/tests/test_repository.py' |
300 | --- bzrlib/tests/test_repository.py 2011-12-22 16:21:25 +0000 |
301 | +++ bzrlib/tests/test_repository.py 2011-12-30 15:29:25 +0000 |
302 | @@ -166,9 +166,7 @@ |
303 | |
304 | def test_find_format_with_features(self): |
305 | tree = self.make_branch_and_tree('.', format='2a') |
306 | - tree.branch.repository.control_transport.put_bytes('format', |
307 | - tree.branch.repository._format.get_format_string() + |
308 | - "necessity name\n") |
309 | + tree.branch.repository.update_feature_flags({"name": "necessity"}) |
310 | found_format = repository.RepositoryFormatMetaDir.find_format(tree.bzrdir) |
311 | self.assertIsInstance(found_format, repository.RepositoryFormatMetaDir) |
312 | self.assertEquals(found_format.features.get("name"), "necessity") |
313 | |
314 | === modified file 'bzrlib/tests/test_workingtree.py' |
315 | --- bzrlib/tests/test_workingtree.py 2011-12-22 17:30:20 +0000 |
316 | +++ bzrlib/tests/test_workingtree.py 2011-12-30 15:29:25 +0000 |
317 | @@ -245,8 +245,7 @@ |
318 | |
319 | def test_find_format_with_features(self): |
320 | tree = self.make_branch_and_tree('.', format='2a') |
321 | - tree.control_transport.put_bytes('format', |
322 | - tree._format.get_format_string() + "necessity name\n") |
323 | + tree.update_feature_flags({"name": "necessity"}) |
324 | found_format = workingtree.WorkingTreeFormatMetaDir.find_format( |
325 | tree.bzrdir) |
326 | self.assertIsInstance(found_format, workingtree.WorkingTreeFormat) |
327 | |
328 | === modified file 'bzrlib/workingtree.py' |
329 | --- bzrlib/workingtree.py 2011-12-19 19:15:58 +0000 |
330 | +++ bzrlib/workingtree.py 2011-12-30 15:29:25 +0000 |
331 | @@ -2977,6 +2977,16 @@ |
332 | if dir[2] == _directory: |
333 | pending.append(dir) |
334 | |
335 | + @needs_write_lock |
336 | + def update_feature_flags(self, updated_flags): |
337 | + """Update the feature flags for this branch. |
338 | + |
339 | + :param updated_flags: Dictionary mapping feature names to necessities |
340 | + A necessity can be None to indicate the feature should be removed |
341 | + """ |
342 | + self._format._update_feature_flags(updated_flags) |
343 | + self.control_transport.put_bytes('format', self._format.as_string()) |
344 | + |
345 | |
346 | class WorkingTreeFormatRegistry(controldir.ControlComponentFormatRegistry): |
347 | """Registry for working tree formats.""" |
348 | |
349 | === modified file 'doc/developers/feature-flags.txt' |
350 | --- doc/developers/feature-flags.txt 2011-12-22 14:25:34 +0000 |
351 | +++ doc/developers/feature-flags.txt 2011-12-30 15:29:25 +0000 |
352 | @@ -129,6 +129,16 @@ |
353 | * BzrFormat.features.set(name, necessity) |
354 | * BzrFormat.features.get(name) -> necessity |
355 | |
356 | +Enabling features |
357 | +----------------- |
358 | + |
359 | +Features are enabled through the ``update_feature_flags`` method on |
360 | +``Repository``, ``Branch``, ``WorkingTree`` and ``BzrDir``. |
361 | + |
362 | +These methods are called by whatever needs to enable features. |
363 | +When they do that is up to them - e.g. bzr-search would enable its |
364 | +feature when ``bzr index`` is first run. |
365 | + |
366 | See also |
367 | -------- |
368 |
> This allows us to actually use feature flags.
The code seems fine, but there is no doc to explain the sentence above :)
How should this be used ? Via hooks ? Directly ? Some example or at least
some guidance sounds necessary at this point.
95 + ('branch-format', self.as_string()), format. as_string( )) format. as_string( ))
104 + self.target_
113 + self.target_
Strictly speaking this should have been part of your previous proposal,
right ? I'm mostly concerned about whether this needs to be backported and
why no tests caught them (I suspect it's because no test activate features
but it's slightly worrying that other occurrences could have been
forgotten).