Merge lp:~vila/bzr/832036-stack-registry into lp:bzr

Proposed by Vincent Ladeuil
Status: Rejected
Rejected by: Vincent Ladeuil
Proposed branch: lp:~vila/bzr/832036-stack-registry
Merge into: lp:bzr
Diff against target: 563 lines (+201/-89)
18 files modified
bzrlib/bzrdir.py (+1/-1)
bzrlib/config.py (+103/-57)
bzrlib/debug.py (+1/-1)
bzrlib/dirstate.py (+3/-4)
bzrlib/i18n.py (+1/-1)
bzrlib/library_state.py (+2/-3)
bzrlib/lockdir.py (+1/-1)
bzrlib/msgeditor.py (+1/-1)
bzrlib/osutils.py (+1/-1)
bzrlib/repofmt/pack_repo.py (+1/-1)
bzrlib/tests/per_workingtree/test_workingtree.py (+2/-2)
bzrlib/tests/test_config.py (+54/-7)
bzrlib/tests/test_debug.py (+1/-1)
bzrlib/tests/test_selftest.py (+1/-1)
bzrlib/ui/__init__.py (+1/-1)
bzrlib/workingtree_4.py (+1/-1)
doc/developers/configuration.txt (+23/-5)
doc/en/release-notes/bzr-2.5.txt (+3/-0)
To merge this branch: bzr merge lp:~vila/bzr/832036-stack-registry
Reviewer Review Type Date Requested Status
Martin Pool Needs Information
Jelmer Vernooij (community) Needs Information
Review via email: mp+74366@code.launchpad.net

Description of the change

This introduces a stack registry (config.stacks) needed to:
- get a single point where stacks are obtained,
- allow plugins to override/enrich the stacks or add their own ones.

The registry really provides stack *factories*, each factory
defining its own context which callers should provide via the
parameters.

I've replaced the *Stack classes with their moral equivalent as
factories (suggested by Martin in a previous review).

I first implemented the registry so that the callers use:

  - c = config.GlobalStack()
  + c = config.stacks.get('bazaar')()

  - return config.BranchStack(b)
  + return config.stacks.get('branch')(b)

But I then switched to:

  - c = config.stacks.get('bazaar')()
  + c = config.stacks.get('bazaar')

  - return config.stacks.get('branch')(b)
  + return config.stacks.get('branch', b)

which avoid the additional () and should be easier to use and let
the caller focus on getting a stack, not on implementation
details (i.e. *how* the stack is built is of little interest to
the caller as long as he get the right stack).

There is still one piece missing here: a way to centralize access
to the stores so we can guarantee unique reads/writes. This
registry will make it easier to pass some info to the factories
to get these stores.

I'm not sold on the factory signatures, Martin mentioned using a
dict (instead of args in the current implementation), that may be
better. In fact, turning these args into a dict will allow
injecting the store access means to all factories (i.e. 'stores'
or 'possible_stores' object can be passed from the registry in
this dict and will provide a single access point to get Store
objects shared across all stacks but this is probably better
addressed as a separate mp)...

I've fixed a small bug in the OptionRegistry where the option
help was acquired at registration, only to be ignored in
get_help(). The same attribute is used in both cases so there is
no point in acquiring it at registration.

Known issues not addressed here but IMHO not strictly required to
consider bug #832036 fixed:

* composing specific stacks should be easier and the mandatory
  parts should be reusable (option values from the command-line
  should be respected by all stacks (or not ? do we really want
  to allow this feature for 'control-only' which defines only
  'default_stack_on' and ignore 'locations.conf' for example ?)).

* the stack registry is not yet plugged into the library state (I
  need some teddy bear to discuss how this should work though).

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Can you explain a bit what the stacks make possible, and what use cases they cover? I don't really understand why this all needs to be in a registry rather than e.g. just having the relevant context objects provide their own stack (Branch.get_config_stack()?), like we do for config at the moment.

review: Needs Information
Revision history for this message
Vincent Ladeuil (vila) wrote :

> Can you explain a bit what the stacks make possible, and what use cases they
> cover?

Pfew, quite a lot already, see
http://doc.bazaar.canonical.com/devnotes/configuration.html for
the full story ;)

But talking about already available features: they are associated
with an optional MutableStore and a given section where the
modification can occur. They are also associated with a list of
sections across multiple stores where option values can be
defined. Each stack can define its own rules about selection and
ordering for these sections.

As such, they already support the old GlobalConfig and
BranchConfig (except for the point you raised about bzr-svn
injecting its own branch config to allow subversion.conf sections
to be supported).

They can also support more config files/contexts depending on
what we decide:

- config stacks for repositories and working trees,
- system-wide config file,
- defaults and overrides config files (the later being
  locations.conf today, the former existing for UUIDs svn repos
  via subversion.conf only today),
- etc

I didn't push for more features nor make the existing ones
covering more than what was needed for the migration, but they
already offer a greater flexibility than the existing ones.

> I don't really understand why this all needs to be in a registry rather
> than e.g. just having the relevant context objects provide their own stack
> (Branch.get_config_stack()?), like we do for config at the moment.

Several reasons:

- the actual design is a mix of transport/local files APIs, that
  makes it hard to have a consistent behavior for all config
  files (some existing classes don't even provide a way to remove
  an option for example).

- creating config objects on the fly, makes it hard to share the
  some config files as far as IOs is concerned (which means you
  generally need to read bazaar.conf, locations.conf and
  branch.conf each time you want to query the config for a given
  option),

- using classes instead of a registry didn't scale well in the
  past in other areas.

But in the end, using a registry mean you can implement
Branch.get_config_stack() by getting the stack from the registry ;)

Revision history for this message
Vincent Ladeuil (vila) wrote :

Let's try another way to answer.

> Can you explain a bit what the stacks make possible, and what
> use cases they cover?

Stacks allow a more uniform way to declare where option values
could be find for a given context:

* an ordered list of config files should be queried,

* for each config file a set of sections (depending on the
  context) should be selected,

* a single (optional) config file a single section in this file
  should be used to *modify* option values. If this file/section is
  not provided, the stack is read-only.

* additionally some global behaviors can be taken into account

** option values can be overridden from the command-line

** a given config file should be read and written only once for a
   given bzr operation.

> I don't really understand why this all needs to be in a registry rather
> than e.g. just having the relevant context objects provide their own stack
> (Branch.get_config_stack()?), like we do for config at the moment.

The actual implementation creates a new config object for every
option query.

The global behaviors mentioned above require that all stores
(config files) are handled in a central place to be able to share
them between the various stacks.

Using a stack registry gives a single place to control these
global behaviors without forcing all stack implementors to care
about it.

@jelmer: Did that answer your questions ?

Revision history for this message
Vincent Ladeuil (vila) wrote :

ping ?

Revision history for this message
Martin Pool (mbp) wrote :

I'm fine I think Jelmer has a reasonable point that just asking eg the Branch for its configuration seems like a pretty nice api for normal use. It also gives an easy place to hold a reference to the configuration object so that it can be reused.

So, why wouldn't that work?
 * it's quite conceivable we need to ask about the configuration that would apply to a branch, before the branch exists? but, really, you can't ask about data from the branch.conf, until there's at least a nascent branch.
 * we need a way to ask about say locations that don't have any clear owner. perhaps they could go on the library state.

As vila says, we can always provide this on top of some lower-level code that actually constructs the stacks.

I think stacks are a good way to say that the configuration relevant to, say, a branch is in order the location, the branch.conf, the global config.

I think the main thing I would question about this is why we actually have a registry rather than just the factory functions. Registries are a way of handling lookup by name and dynamic lazy loading, neither of which seem especially relevant here as far as I can tell. I would just tell people to call eg branch_stack(), or more to the point have Branch.get_config_stack() call that.

review: Needs Information
Revision history for this message
Martin Pool (mbp) wrote :

I'm -1 on this until I understand what it actually improves.

If there was a bug saying "plugins want to do X and can only do it by monkeypatching" that would be a good reason to add something like a registry (though it wouldn't necessarily be urgent.) A bug like 832036 isn't very helpful because it specifies a purported solution not a problem.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> Registries are a way of handling lookup by name and dynamic lazy loading, neither of which seem especially relevant here as far as I can tell.

Of course they are relevant, one important target is to never load a config file before it's strictly required which is what lazy loading is about no ?

I'm not saying that's the *onlY* possible solution but it builds on known concepts and code so I see little point in reinventing a wheel here.

Anyway, the discussion on IRC went ahead so enough on that.

Unmerged revisions

6129. By Vincent Ladeuil

Implement a config stack registry and use it.

6128. By Vincent Ladeuil

Make config.stacks.get() build the stack and accept context parameters but _get_factory for tests and unforeseen uses.

6127. By Vincent Ladeuil

Introduce a stack factory registry and use it instead of the *Stack classses (now deleted).

6126. By Vincent Ladeuil

Implements a stack registry with basic tests.

6125. By Canonical.com Patch Queue Manager <email address hidden>

(jelmer) Mention the number of tags that was pushed/pull,
 if any. (Jelmer Vernooij)

6124. By Canonical.com Patch Queue Manager <email address hidden>

(jelmer) Add a ``bugtracker`` configuration option. (Jelmer Vernooij)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/bzrdir.py'
2--- bzrlib/bzrdir.py 2011-08-17 09:27:29 +0000
3+++ bzrlib/bzrdir.py 2011-09-07 09:07:25 +0000
4@@ -2288,7 +2288,7 @@
5 help='Same as 2a.')
6
7 # The current format that is made on 'bzr init'.
8-format_name = config.GlobalStack().get('default_format')
9+format_name = config.stacks.get('bazaar').get('default_format')
10 controldir.format_registry.set_default(format_name)
11
12 # XXX 2010-08-20 JRV: There is still a lot of code relying on
13
14=== modified file 'bzrlib/config.py'
15--- bzrlib/config.py 2011-09-06 11:53:48 +0000
16+++ bzrlib/config.py 2011-09-07 09:07:25 +0000
17@@ -414,7 +414,7 @@
18 # add) the final ','
19 l = [l]
20 return l
21-
22+
23 def get_user_option_as_int_from_SI(self, option_name, default=None):
24 """Get a generic option from a human readable size in SI units, e.g 10MB
25
26@@ -452,7 +452,6 @@
27 except TypeError:
28 val = default
29 return val
30-
31
32 def gpg_signing_command(self):
33 """What program should be used to sign signatures?"""
34@@ -2468,8 +2467,7 @@
35
36 :param option: The option to register. Its name is used as the key.
37 """
38- super(OptionRegistry, self).register(option.name, option,
39- help=option.help)
40+ super(OptionRegistry, self).register(option.name, option)
41
42 def register_lazy(self, key, module_name, member_name):
43 """Register a new option to be loaded on request.
44@@ -3214,63 +3212,111 @@
45 self.store.save()
46
47
48-class GlobalStack(_CompatibleStack):
49- """Global options only stack."""
50-
51- def __init__(self):
52- # Get a GlobalStore
53- gstore = GlobalStore()
54- super(GlobalStack, self).__init__([gstore.get_sections], gstore)
55-
56-
57-class LocationStack(_CompatibleStack):
58- """Per-location options falling back to global options stack."""
59-
60- def __init__(self, location):
61- """Make a new stack for a location and global configuration.
62-
63- :param location: A URL prefix to """
64- lstore = LocationStore()
65- matcher = LocationMatcher(lstore, location)
66- gstore = GlobalStore()
67- super(LocationStack, self).__init__(
68- [matcher.get_sections, gstore.get_sections], lstore)
69-
70-
71-class BranchStack(_CompatibleStack):
72- """Per-location options falling back to branch then global options stack."""
73-
74- def __init__(self, branch):
75- bstore = BranchStore(branch)
76- lstore = LocationStore()
77- matcher = LocationMatcher(lstore, branch.base)
78- gstore = GlobalStore()
79- super(BranchStack, self).__init__(
80- [matcher.get_sections, bstore.get_sections, gstore.get_sections],
81- bstore)
82- self.branch = branch
83-
84-
85-class RemoteControlStack(_CompatibleStack):
86- """Remote control-only options stack."""
87-
88- def __init__(self, bzrdir):
89- cstore = ControlStore(bzrdir)
90- super(RemoteControlStack, self).__init__(
91+def global_stack():
92+ """Create a stack for the global options."""
93+ # Get a GlobalStore
94+ gstore = GlobalStore()
95+ stack = _CompatibleStack([gstore.get_sections], gstore)
96+ return stack
97+
98+
99+def location_stack(location):
100+ """Create a stack for the options applying to a location.
101+
102+ This includes the matching sections from LocationStore and falls back to
103+ the section in the GlobalStore.
104+
105+ :param location: A URL prefix to match sections from the LocationStore.
106+ """
107+ lstore = LocationStore()
108+ matcher = LocationMatcher(lstore, location)
109+ gstore = GlobalStore()
110+ stack = _CompatibleStack([matcher.get_sections, gstore.get_sections],
111+ lstore)
112+ return stack
113+
114+
115+def branch_stack(branch):
116+ """Create a stack for the branch options.
117+
118+ This includes the section in the BranchStore, falls back the sections in
119+ LocationStore (using the branch URL) and finally to the section in the
120+ GlobalStore.
121+
122+ :param branch: The related branch object.
123+ """
124+ bstore = BranchStore(branch)
125+ lstore = LocationStore()
126+ matcher = LocationMatcher(lstore, branch.base)
127+ gstore = GlobalStore()
128+ stack = _CompatibleStack(
129+ [matcher.get_sections, bstore.get_sections, gstore.get_sections],
130+ bstore)
131+ return stack
132+
133+# Neeed for 'default_stack_on location' which is the *only* option defined in
134+# 'control.conf'. This stack is not yet used in bzrdir.py -- vila 2011-09-06
135+def control_only_stack(bzrdir):
136+ """Create a stack for the control options *only*.
137+
138+ This includes only the section in the ControlStore.
139+
140+ :param bzrdir: The related bzrdir object.
141+ """
142+ cstore = ControlStore(bzrdir)
143+ stack = _CompatibleStack(
144 [cstore.get_sections],
145 cstore)
146- self.bzrdir = bzrdir
147-
148-
149-class RemoteBranchStack(_CompatibleStack):
150- """Remote branch-only options stack."""
151-
152- def __init__(self, branch):
153- bstore = BranchStore(branch)
154- super(RemoteBranchStack, self).__init__(
155+ return stack
156+
157+
158+# Needed for 'stacked_on_location' *only* which use a hack that could be
159+# removed when each config file is read only once. This stack is not yet used
160+# in branch.py -- vila 2011-09-06
161+def branch_only_stack(branch):
162+ """Create a stack for the branch options *only*.
163+
164+ This includes only the section in the BranchStore.
165+
166+ :param branch: The related branch object.
167+ """
168+ bstore = BranchStore(branch)
169+ stack = _CompatibleStack(
170 [bstore.get_sections],
171 bstore)
172- self.branch = branch
173+ return stack
174+
175+
176+class StackRegistry(registry.Registry):
177+ """Registry of Stack factories.
178+
179+ This registry is queried for a given key and provides a factory that accept
180+ a context and build the appropriate Stack for this context.
181+ """
182+
183+ def get(self, key, *args, **kwargs):
184+ return self._get_factory(key)(*args, **kwargs)
185+
186+ def _get_factory(self, key):
187+ return super(StackRegistry, self).get(key)
188+
189+ def get_help(self, key=None):
190+ """Get the help text associated with the given key"""
191+ factory = self._get_factory(key)
192+ the_help = factory.__doc__
193+ if callable(the_help):
194+ return the_help(self, key)
195+ return the_help
196+
197+
198+stacks = StackRegistry()
199+stacks.register('bazaar', global_stack)
200+stacks.register('locations', location_stack)
201+stacks.register('branch', branch_stack)
202+
203+# Not used yet, may change in the future, don't use -- vila 2011-09-06
204+stacks.register('control-only', control_only_stack)
205+stacks.register('branch-only', branch_only_stack)
206
207
208 class cmd_config(commands.Command):
209
210=== modified file 'bzrlib/debug.py'
211--- bzrlib/debug.py 2011-08-20 07:49:15 +0000
212+++ bzrlib/debug.py 2011-09-07 09:07:25 +0000
213@@ -32,7 +32,7 @@
214
215 from bzrlib import config
216
217- c = config.GlobalStack()
218+ c = config.stacks.get('bazaar')
219 for f in c.get('debug_flags'):
220 debug_flags.add(f)
221
222
223=== modified file 'bzrlib/dirstate.py'
224--- bzrlib/dirstate.py 2011-07-25 07:11:56 +0000
225+++ bzrlib/dirstate.py 2011-09-07 09:07:25 +0000
226@@ -450,12 +450,11 @@
227 self._known_hash_changes = set()
228 # How many hash changed entries can we have without saving
229 self._worth_saving_limit = worth_saving_limit
230- self._config_stack = config.LocationStack(urlutils.local_path_to_url(
231- path))
232+ self._config_stack = config.stacks.get(
233+ 'locations', urlutils.local_path_to_url(path))
234
235 def __repr__(self):
236- return "%s(%r)" % \
237- (self.__class__.__name__, self._filename)
238+ return "%s(%r)" % (self.__class__.__name__, self._filename)
239
240 def _mark_modified(self, hash_changed_entries=None, header_modified=False):
241 """Mark this dirstate as modified.
242
243=== modified file 'bzrlib/i18n.py'
244--- bzrlib/i18n.py 2011-08-31 10:56:58 +0000
245+++ bzrlib/i18n.py 2011-09-07 09:07:25 +0000
246@@ -136,7 +136,7 @@
247 def _get_current_locale():
248 if not os.environ.get('LANGUAGE'):
249 from bzrlib import config
250- lang = config.GlobalStack().get('language')
251+ lang = config.stacks.get('bazaar').get('language')
252 if lang:
253 os.environ['LANGUAGE'] = lang
254 return lang
255
256=== modified file 'bzrlib/library_state.py'
257--- bzrlib/library_state.py 2011-03-29 09:50:04 +0000
258+++ bzrlib/library_state.py 2011-09-07 09:07:25 +0000
259@@ -69,8 +69,7 @@
260 return self # This is bound to the 'as' clause in a with statement.
261
262 def _start(self):
263- """Do all initialization.
264- """
265+ """Do all initialization."""
266 # NB: This function tweaks so much global state it's hard to test it in
267 # isolation within the same interpreter. It's not reached on normal
268 # in-process run_bzr calls. If it's broken, we expect that
269@@ -108,4 +107,4 @@
270 bzrlib.ui.ui_factory = self._orig_ui
271 global global_state
272 global_state = self.saved_state
273- return False # propogate exceptions.
274+ return False # propagate exceptions.
275
276=== modified file 'bzrlib/lockdir.py'
277--- bzrlib/lockdir.py 2011-08-12 12:11:44 +0000
278+++ bzrlib/lockdir.py 2011-09-07 09:07:25 +0000
279@@ -708,7 +708,7 @@
280 # XXX: This really should also use the locationconfig at least, but
281 # that seems a bit hard to hook up at the moment. -- mbp 20110329
282 # FIXME: The above is still true ;) -- vila 20110811
283- return config.GlobalStack()
284+ return config.stacks.get('bazaar')
285
286
287 class LockHeldInfo(object):
288
289=== modified file 'bzrlib/msgeditor.py'
290--- bzrlib/msgeditor.py 2011-07-23 16:33:38 +0000
291+++ bzrlib/msgeditor.py 2011-09-07 09:07:25 +0000
292@@ -41,7 +41,7 @@
293 except KeyError:
294 pass
295
296- e = config.GlobalStack().get('editor')
297+ e = config.stacks.get('bazaar').get('editor')
298 if e is not None:
299 yield e, config.config_filename()
300
301
302=== modified file 'bzrlib/osutils.py'
303--- bzrlib/osutils.py 2011-08-24 23:55:03 +0000
304+++ bzrlib/osutils.py 2011-09-07 09:07:25 +0000
305@@ -986,7 +986,7 @@
306 def report_extension_load_failures():
307 if not _extension_load_failures:
308 return
309- if config.GlobalStack().get('ignore_missing_extensions'):
310+ if config.stacks.get('bazaar').get('ignore_missing_extensions'):
311 return
312 # the warnings framework should by default show this only once
313 from bzrlib.trace import warning
314
315=== modified file 'bzrlib/repofmt/pack_repo.py'
316--- bzrlib/repofmt/pack_repo.py 2011-07-25 07:51:35 +0000
317+++ bzrlib/repofmt/pack_repo.py 2011-09-07 09:07:25 +0000
318@@ -831,7 +831,7 @@
319 set(all_combined).difference([combined_idx]))
320 # resumed packs
321 self._resumed_packs = []
322- self.config_stack = config.LocationStack(self.transport.base)
323+ self.config_stack = config.stacks.get('locations', self.transport.base)
324
325 def __repr__(self):
326 return '%s(%r)' % (self.__class__.__name__, self.repo)
327
328=== modified file 'bzrlib/tests/per_workingtree/test_workingtree.py'
329--- bzrlib/tests/per_workingtree/test_workingtree.py 2011-08-31 22:52:43 +0000
330+++ bzrlib/tests/per_workingtree/test_workingtree.py 2011-09-07 09:07:25 +0000
331@@ -1161,7 +1161,7 @@
332
333 def test_set_in_branch(self):
334 wt = self.make_wt_with_worth_saving_limit()
335- conf = config.BranchStack(wt.branch)
336+ conf = config.stacks.get('branch', wt.branch)
337 conf.set('bzr.workingtree.worth_saving_limit', '20')
338 self.assertEqual(20, wt._worth_saving_limit())
339 ds = wt.current_dirstate()
340@@ -1169,7 +1169,7 @@
341
342 def test_invalid(self):
343 wt = self.make_wt_with_worth_saving_limit()
344- conf = config.BranchStack(wt.branch)
345+ conf = config.stacks.get('branch', wt.branch)
346 conf.set('bzr.workingtree.worth_saving_limit', 'a')
347 # If the config entry is invalid, default to 10
348 warnings = []
349
350=== modified file 'bzrlib/tests/test_config.py'
351--- bzrlib/tests/test_config.py 2011-09-06 11:53:48 +0000
352+++ bzrlib/tests/test_config.py 2011-09-07 09:07:25 +0000
353@@ -132,15 +132,15 @@
354
355
356 config.test_stack_builder_registry.register(
357- 'bazaar', lambda test: config.GlobalStack())
358+ 'bazaar', lambda test: config.stacks.get('bazaar'))
359 config.test_stack_builder_registry.register(
360- 'location', lambda test: config.LocationStack('.'))
361+ 'location', lambda test: config.stacks.get('locations', '.'))
362
363
364 def build_branch_stack(test):
365 build_backing_branch(test, 'branch')
366 b = branch.Branch.open('branch')
367- return config.BranchStack(b)
368+ return config.stacks.get('branch', b)
369 config.test_stack_builder_registry.register('branch', build_branch_stack)
370
371
372@@ -151,7 +151,7 @@
373 server_class) = transport_remote.get_test_permutations()[0]
374 build_backing_branch(test, 'branch', transport_class, server_class)
375 b = branch.Branch.open(test.get_url('branch'))
376- return config.RemoteBranchStack(b)
377+ return config.stacks.get('branch', b)
378 config.test_stack_builder_registry.register('remote_branch',
379 build_remote_branch_stack)
380
381@@ -164,7 +164,7 @@
382 # creating a dedicated helper to create only the bzrdir
383 build_backing_branch(test, 'branch', transport_class, server_class)
384 b = branch.Branch.open(test.get_url('branch'))
385- return config.RemoteControlStack(b.bzrdir)
386+ return config.stacks.get('control-only', b.bzrdir)
387 config.test_stack_builder_registry.register('remote_control',
388 build_remote_control_stack)
389
390@@ -2529,7 +2529,6 @@
391
392 def test_help_is_set(self):
393 option_help = self.registry.get_help(self.option_name)
394- self.assertNotEquals(None, option_help)
395 # Come on, think about the user, he really wants to know what the
396 # option is about
397 self.assertIsNot(None, option_help)
398@@ -3181,6 +3180,54 @@
399 self.assertEquals(expected_location, matcher.location)
400
401
402+class TestStackRegistry(tests.TestCase):
403+
404+ def setUp(self):
405+ super(TestStackRegistry, self).setUp()
406+ self.registry = config.StackRegistry()
407+
408+ def test_register_remove(self):
409+ self.registry.register('test', config.Stack)
410+ self.assertIs(config.Stack, self.registry._get_factory('test'))
411+ self.registry.remove('test')
412+ self.assertRaises(KeyError, self.registry._get_factory, 'test')
413+
414+ lazy_stack = config.Stack
415+
416+ def test_register_lazy_remove(self):
417+ self.registry.register_lazy('test', self.__module__,
418+ 'TestStackRegistry.lazy_stack')
419+ self.assertIs(self.lazy_stack, self.registry._get_factory('test'))
420+ self.registry.remove('test')
421+ self.assertRaises(KeyError, self.registry._get_factory, 'test')
422+
423+ def test_register_override(self):
424+ self.registry.register('test', config.Stack)
425+ self.assertIs(config.Stack, self.registry._get_factory('test'))
426+ self.registry.register('test', self.lazy_stack, override_existing=True)
427+ self.assertIs(self.lazy_stack, self.registry._get_factory('test'))
428+
429+
430+class TestRegisteredStacks(tests.TestCase):
431+
432+ scenarios = [(key, {'stack_name': key, 'stack_factory': factory})
433+ for key, factory in config.stacks.iteritems()]
434+
435+ def setUp(self):
436+ super(TestRegisteredStacks, self).setUp()
437+ self.registry = config.stacks
438+
439+ def test_factory_callable(self):
440+ self.assertTrue(callable(self.stack_factory))
441+
442+ def test_help_is_set(self):
443+ stack_help = self.registry.get_help(self.stack_name)
444+ # Come on, think about the user, he really wants to know what the
445+ # stack is about
446+ self.assertIsNot(None, stack_help)
447+ self.assertNotEquals('', stack_help)
448+
449+
450 class TestStackGet(tests.TestCase):
451
452 # FIXME: This should be parametrized for all known Stack or dedicated
453@@ -3506,7 +3553,7 @@
454 string = ''
455 # Since we don't save the config we won't strictly require to inherit
456 # from TestCaseInTempDir, but an error occurs so quickly...
457- c = config.LocationStack(location)
458+ c = config.stacks.get('locations', location)
459 c.store._load_from_string(string)
460 return c
461
462
463=== modified file 'bzrlib/tests/test_debug.py'
464--- bzrlib/tests/test_debug.py 2011-08-12 16:25:56 +0000
465+++ bzrlib/tests/test_debug.py 2011-09-07 09:07:25 +0000
466@@ -33,7 +33,7 @@
467 self.assertDebugFlags(['hpss'], 'debug_flags = hpss\n')
468
469 def assertDebugFlags(self, expected_flags, conf_bytes):
470- conf = config.GlobalStack()
471+ conf = config.stacks.get('bazaar')
472 conf.store._load_from_string(conf_bytes)
473 conf.store.save()
474 self.overrideAttr(debug, 'debug_flags', set())
475
476=== modified file 'bzrlib/tests/test_selftest.py'
477--- bzrlib/tests/test_selftest.py 2011-08-19 22:34:02 +0000
478+++ bzrlib/tests/test_selftest.py 2011-09-07 09:07:25 +0000
479@@ -3162,7 +3162,7 @@
480 result.stopTestRun()
481 self.assertEqual(result._tests_leaking_threads_count, 0)
482 self.assertEqual(result.leaks, [])
483-
484+
485 def test_thread_leak(self):
486 """Ensure a thread that outlives the running of a test is reported
487
488
489=== modified file 'bzrlib/ui/__init__.py'
490--- bzrlib/ui/__init__.py 2011-08-08 15:57:57 +0000
491+++ bzrlib/ui/__init__.py 2011-09-07 09:07:25 +0000
492@@ -248,7 +248,7 @@
493 """
494 # XXX: is the caller supposed to close the resulting object?
495 if encoding is None:
496- encoding = config.GlobalStack().get('output_encoding')
497+ encoding = config.stacks.get('bazaar').get('output_encoding')
498 if encoding is None:
499 encoding = osutils.get_terminal_encoding(trace=True)
500 if encoding_type is None:
501
502=== modified file 'bzrlib/workingtree_4.py'
503--- bzrlib/workingtree_4.py 2011-08-12 14:47:42 +0000
504+++ bzrlib/workingtree_4.py 2011-09-07 09:07:25 +0000
505@@ -251,7 +251,7 @@
506 :return: an integer. -1 means never save.
507 """
508 # FIXME: We want a WorkingTreeStack here -- vila 20110812
509- conf = config.BranchStack(self.branch)
510+ conf = config.stacks.get('branch', self.branch)
511 return conf.get('bzr.workingtree.worth_saving_limit')
512
513 def filter_unversioned_files(self, paths):
514
515=== modified file 'doc/developers/configuration.txt'
516--- doc/developers/configuration.txt 2011-08-19 14:10:32 +0000
517+++ doc/developers/configuration.txt 2011-09-07 09:07:25 +0000
518@@ -122,9 +122,27 @@
519 is one, then the *user* should be able to decide and in this case a new
520 ``Stack`` can be created cheaply.
521
522+StackRegistry
523+-------------
524+
525+All config stacks should be obtained from ``config.stacks`` which provides
526+stack factories for various contexts.
527+
528+Each factory is registered under a name so plugins can override them as they
529+see fit.
530+
531+Each factory defines its own context as a set of parameters that should be
532+provided to build a stack.
533+
534 Different stacks can be created for different purposes, the existing
535-``GlobalStack``, ``LocationStack`` and ``BranchStack`` can be used as basis
536-or examples. These classes are the only ones that should be used in code,
537-``Stores`` can be used to build them but shouldn't be used otherwise, ditto
538-for sections. Again, the associated tests could and should be used against the
539-created stacks.
540+``global_stack``, ``location_stack``, ``branch_stack`` and so on can be used
541+as basis or examples.
542+
543+Such factory methods can use ``Stores``, ``SectionMatcher`` or ``Sections``
544+objects to build their stack (they are the only intended consumer) but these
545+objects shouldn't be used otherwise.
546+
547+There are parameterized tests intended to exercise these factories and their
548+resulting stacks (see ``config.test_stack_builder_registry`` and the tests
549+that use them in ``bzrlib/tests/test_config.py``). Using them should help
550+implementing such factories.
551
552=== modified file 'doc/en/release-notes/bzr-2.5.txt'
553--- doc/en/release-notes/bzr-2.5.txt 2011-09-06 13:35:07 +0000
554+++ doc/en/release-notes/bzr-2.5.txt 2011-09-07 09:07:25 +0000
555@@ -92,6 +92,9 @@
556 * bzr now ships with translations for command help. (Jonathan
557 Riddell, INADA Naoki, #83941)
558
559+* The ``config.stacks`` registry provides a way to get a config stack
560+ factory for various contexts. (Vincent Ladeuil, #832036)
561+
562 Improvements
563 ************
564