Merge lp:~jelmer/bzr/update-feature-flags into lp:bzr

Proposed by Jelmer Vernooij
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
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Review via email: mp+86742@code.launchpad.net

Commit message

Add {Repository,Branch,WorkingTree,BzrDir}.update_feature_flags methods.

Description of the change

Add update_feature_flags methods to the various bzr objects (BzrDir, BzrBranch, RepositoryMetaDir, InventoryWorkingTree).

This allows us to actually use feature flags.

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

> 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()),
104 + self.target_format.as_string())
113 + self.target_format.as_string())

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).

review: Needs Fixing
Revision history for this message
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/branch/tree/dir. When they do that is up to them. In e.g.
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_format.as_string())
> 113 + self.target_format.as_string())
>
> 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

Revision history for this message
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/branch/tree/dir. When they do that is up to them. In e.g.
> 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/feature-flags.txt :)

> > 95 + ('branch-format', self.as_string()),
> > 104 + self.target_format.as_string())
> > 113 + self.target_format.as_string())
> >
> > 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.

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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