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
=== modified file 'bzrlib/bzrdir.py'
--- bzrlib/bzrdir.py 2011-08-17 09:27:29 +0000
+++ bzrlib/bzrdir.py 2011-09-07 09:07:25 +0000
@@ -2288,7 +2288,7 @@
2288 help='Same as 2a.')2288 help='Same as 2a.')
22892289
2290# The current format that is made on 'bzr init'.2290# The current format that is made on 'bzr init'.
2291format_name = config.GlobalStack().get('default_format')2291format_name = config.stacks.get('bazaar').get('default_format')
2292controldir.format_registry.set_default(format_name)2292controldir.format_registry.set_default(format_name)
22932293
2294# XXX 2010-08-20 JRV: There is still a lot of code relying on2294# XXX 2010-08-20 JRV: There is still a lot of code relying on
22952295
=== modified file 'bzrlib/config.py'
--- bzrlib/config.py 2011-09-06 11:53:48 +0000
+++ bzrlib/config.py 2011-09-07 09:07:25 +0000
@@ -414,7 +414,7 @@
414 # add) the final ','414 # add) the final ','
415 l = [l]415 l = [l]
416 return l416 return l
417 417
418 def get_user_option_as_int_from_SI(self, option_name, default=None):418 def get_user_option_as_int_from_SI(self, option_name, default=None):
419 """Get a generic option from a human readable size in SI units, e.g 10MB419 """Get a generic option from a human readable size in SI units, e.g 10MB
420 420
@@ -452,7 +452,6 @@
452 except TypeError:452 except TypeError:
453 val = default453 val = default
454 return val454 return val
455
456455
457 def gpg_signing_command(self):456 def gpg_signing_command(self):
458 """What program should be used to sign signatures?"""457 """What program should be used to sign signatures?"""
@@ -2468,8 +2467,7 @@
24682467
2469 :param option: The option to register. Its name is used as the key.2468 :param option: The option to register. Its name is used as the key.
2470 """2469 """
2471 super(OptionRegistry, self).register(option.name, option,2470 super(OptionRegistry, self).register(option.name, option)
2472 help=option.help)
24732471
2474 def register_lazy(self, key, module_name, member_name):2472 def register_lazy(self, key, module_name, member_name):
2475 """Register a new option to be loaded on request.2473 """Register a new option to be loaded on request.
@@ -3214,63 +3212,111 @@
3214 self.store.save()3212 self.store.save()
32153213
32163214
3217class GlobalStack(_CompatibleStack):3215def global_stack():
3218 """Global options only stack."""3216 """Create a stack for the global options."""
32193217 # Get a GlobalStore
3220 def __init__(self):3218 gstore = GlobalStore()
3221 # Get a GlobalStore3219 stack = _CompatibleStack([gstore.get_sections], gstore)
3222 gstore = GlobalStore()3220 return stack
3223 super(GlobalStack, self).__init__([gstore.get_sections], gstore)3221
32243222
32253223def location_stack(location):
3226class LocationStack(_CompatibleStack):3224 """Create a stack for the options applying to a location.
3227 """Per-location options falling back to global options stack."""3225
32283226 This includes the matching sections from LocationStore and falls back to
3229 def __init__(self, location):3227 the section in the GlobalStore.
3230 """Make a new stack for a location and global configuration.3228
3231 3229 :param location: A URL prefix to match sections from the LocationStore.
3232 :param location: A URL prefix to """3230 """
3233 lstore = LocationStore()3231 lstore = LocationStore()
3234 matcher = LocationMatcher(lstore, location)3232 matcher = LocationMatcher(lstore, location)
3235 gstore = GlobalStore()3233 gstore = GlobalStore()
3236 super(LocationStack, self).__init__(3234 stack = _CompatibleStack([matcher.get_sections, gstore.get_sections],
3237 [matcher.get_sections, gstore.get_sections], lstore)3235 lstore)
32383236 return stack
32393237
3240class BranchStack(_CompatibleStack):3238
3241 """Per-location options falling back to branch then global options stack."""3239def branch_stack(branch):
32423240 """Create a stack for the branch options.
3243 def __init__(self, branch):3241
3244 bstore = BranchStore(branch)3242 This includes the section in the BranchStore, falls back the sections in
3245 lstore = LocationStore()3243 LocationStore (using the branch URL) and finally to the section in the
3246 matcher = LocationMatcher(lstore, branch.base)3244 GlobalStore.
3247 gstore = GlobalStore()3245
3248 super(BranchStack, self).__init__(3246 :param branch: The related branch object.
3249 [matcher.get_sections, bstore.get_sections, gstore.get_sections],3247 """
3250 bstore)3248 bstore = BranchStore(branch)
3251 self.branch = branch3249 lstore = LocationStore()
32523250 matcher = LocationMatcher(lstore, branch.base)
32533251 gstore = GlobalStore()
3254class RemoteControlStack(_CompatibleStack):3252 stack = _CompatibleStack(
3255 """Remote control-only options stack."""3253 [matcher.get_sections, bstore.get_sections, gstore.get_sections],
32563254 bstore)
3257 def __init__(self, bzrdir):3255 return stack
3258 cstore = ControlStore(bzrdir)3256
3259 super(RemoteControlStack, self).__init__(3257# Neeed for 'default_stack_on location' which is the *only* option defined in
3258# 'control.conf'. This stack is not yet used in bzrdir.py -- vila 2011-09-06
3259def control_only_stack(bzrdir):
3260 """Create a stack for the control options *only*.
3261
3262 This includes only the section in the ControlStore.
3263
3264 :param bzrdir: The related bzrdir object.
3265 """
3266 cstore = ControlStore(bzrdir)
3267 stack = _CompatibleStack(
3260 [cstore.get_sections],3268 [cstore.get_sections],
3261 cstore)3269 cstore)
3262 self.bzrdir = bzrdir3270 return stack
32633271
32643272
3265class RemoteBranchStack(_CompatibleStack):3273# Needed for 'stacked_on_location' *only* which use a hack that could be
3266 """Remote branch-only options stack."""3274# removed when each config file is read only once. This stack is not yet used
32673275# in branch.py -- vila 2011-09-06
3268 def __init__(self, branch):3276def branch_only_stack(branch):
3269 bstore = BranchStore(branch)3277 """Create a stack for the branch options *only*.
3270 super(RemoteBranchStack, self).__init__(3278
3279 This includes only the section in the BranchStore.
3280
3281 :param branch: The related branch object.
3282 """
3283 bstore = BranchStore(branch)
3284 stack = _CompatibleStack(
3271 [bstore.get_sections],3285 [bstore.get_sections],
3272 bstore)3286 bstore)
3273 self.branch = branch3287 return stack
3288
3289
3290class StackRegistry(registry.Registry):
3291 """Registry of Stack factories.
3292
3293 This registry is queried for a given key and provides a factory that accept
3294 a context and build the appropriate Stack for this context.
3295 """
3296
3297 def get(self, key, *args, **kwargs):
3298 return self._get_factory(key)(*args, **kwargs)
3299
3300 def _get_factory(self, key):
3301 return super(StackRegistry, self).get(key)
3302
3303 def get_help(self, key=None):
3304 """Get the help text associated with the given key"""
3305 factory = self._get_factory(key)
3306 the_help = factory.__doc__
3307 if callable(the_help):
3308 return the_help(self, key)
3309 return the_help
3310
3311
3312stacks = StackRegistry()
3313stacks.register('bazaar', global_stack)
3314stacks.register('locations', location_stack)
3315stacks.register('branch', branch_stack)
3316
3317# Not used yet, may change in the future, don't use -- vila 2011-09-06
3318stacks.register('control-only', control_only_stack)
3319stacks.register('branch-only', branch_only_stack)
32743320
32753321
3276class cmd_config(commands.Command):3322class cmd_config(commands.Command):
32773323
=== modified file 'bzrlib/debug.py'
--- bzrlib/debug.py 2011-08-20 07:49:15 +0000
+++ bzrlib/debug.py 2011-09-07 09:07:25 +0000
@@ -32,7 +32,7 @@
3232
33 from bzrlib import config33 from bzrlib import config
3434
35 c = config.GlobalStack()35 c = config.stacks.get('bazaar')
36 for f in c.get('debug_flags'):36 for f in c.get('debug_flags'):
37 debug_flags.add(f)37 debug_flags.add(f)
3838
3939
=== modified file 'bzrlib/dirstate.py'
--- bzrlib/dirstate.py 2011-07-25 07:11:56 +0000
+++ bzrlib/dirstate.py 2011-09-07 09:07:25 +0000
@@ -450,12 +450,11 @@
450 self._known_hash_changes = set()450 self._known_hash_changes = set()
451 # How many hash changed entries can we have without saving451 # How many hash changed entries can we have without saving
452 self._worth_saving_limit = worth_saving_limit452 self._worth_saving_limit = worth_saving_limit
453 self._config_stack = config.LocationStack(urlutils.local_path_to_url(453 self._config_stack = config.stacks.get(
454 path))454 'locations', urlutils.local_path_to_url(path))
455455
456 def __repr__(self):456 def __repr__(self):
457 return "%s(%r)" % \457 return "%s(%r)" % (self.__class__.__name__, self._filename)
458 (self.__class__.__name__, self._filename)
459458
460 def _mark_modified(self, hash_changed_entries=None, header_modified=False):459 def _mark_modified(self, hash_changed_entries=None, header_modified=False):
461 """Mark this dirstate as modified.460 """Mark this dirstate as modified.
462461
=== modified file 'bzrlib/i18n.py'
--- bzrlib/i18n.py 2011-08-31 10:56:58 +0000
+++ bzrlib/i18n.py 2011-09-07 09:07:25 +0000
@@ -136,7 +136,7 @@
136def _get_current_locale():136def _get_current_locale():
137 if not os.environ.get('LANGUAGE'):137 if not os.environ.get('LANGUAGE'):
138 from bzrlib import config138 from bzrlib import config
139 lang = config.GlobalStack().get('language')139 lang = config.stacks.get('bazaar').get('language')
140 if lang:140 if lang:
141 os.environ['LANGUAGE'] = lang141 os.environ['LANGUAGE'] = lang
142 return lang142 return lang
143143
=== modified file 'bzrlib/library_state.py'
--- bzrlib/library_state.py 2011-03-29 09:50:04 +0000
+++ bzrlib/library_state.py 2011-09-07 09:07:25 +0000
@@ -69,8 +69,7 @@
69 return self # This is bound to the 'as' clause in a with statement.69 return self # This is bound to the 'as' clause in a with statement.
7070
71 def _start(self):71 def _start(self):
72 """Do all initialization.72 """Do all initialization."""
73 """
74 # NB: This function tweaks so much global state it's hard to test it in73 # NB: This function tweaks so much global state it's hard to test it in
75 # isolation within the same interpreter. It's not reached on normal74 # isolation within the same interpreter. It's not reached on normal
76 # in-process run_bzr calls. If it's broken, we expect that75 # in-process run_bzr calls. If it's broken, we expect that
@@ -108,4 +107,4 @@
108 bzrlib.ui.ui_factory = self._orig_ui107 bzrlib.ui.ui_factory = self._orig_ui
109 global global_state108 global global_state
110 global_state = self.saved_state109 global_state = self.saved_state
111 return False # propogate exceptions.110 return False # propagate exceptions.
112111
=== modified file 'bzrlib/lockdir.py'
--- bzrlib/lockdir.py 2011-08-12 12:11:44 +0000
+++ bzrlib/lockdir.py 2011-09-07 09:07:25 +0000
@@ -708,7 +708,7 @@
708 # XXX: This really should also use the locationconfig at least, but708 # XXX: This really should also use the locationconfig at least, but
709 # that seems a bit hard to hook up at the moment. -- mbp 20110329709 # that seems a bit hard to hook up at the moment. -- mbp 20110329
710 # FIXME: The above is still true ;) -- vila 20110811710 # FIXME: The above is still true ;) -- vila 20110811
711 return config.GlobalStack()711 return config.stacks.get('bazaar')
712712
713713
714class LockHeldInfo(object):714class LockHeldInfo(object):
715715
=== modified file 'bzrlib/msgeditor.py'
--- bzrlib/msgeditor.py 2011-07-23 16:33:38 +0000
+++ bzrlib/msgeditor.py 2011-09-07 09:07:25 +0000
@@ -41,7 +41,7 @@
41 except KeyError:41 except KeyError:
42 pass42 pass
4343
44 e = config.GlobalStack().get('editor')44 e = config.stacks.get('bazaar').get('editor')
45 if e is not None:45 if e is not None:
46 yield e, config.config_filename()46 yield e, config.config_filename()
4747
4848
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2011-08-24 23:55:03 +0000
+++ bzrlib/osutils.py 2011-09-07 09:07:25 +0000
@@ -986,7 +986,7 @@
986def report_extension_load_failures():986def report_extension_load_failures():
987 if not _extension_load_failures:987 if not _extension_load_failures:
988 return988 return
989 if config.GlobalStack().get('ignore_missing_extensions'):989 if config.stacks.get('bazaar').get('ignore_missing_extensions'):
990 return990 return
991 # the warnings framework should by default show this only once991 # the warnings framework should by default show this only once
992 from bzrlib.trace import warning992 from bzrlib.trace import warning
993993
=== modified file 'bzrlib/repofmt/pack_repo.py'
--- bzrlib/repofmt/pack_repo.py 2011-07-25 07:51:35 +0000
+++ bzrlib/repofmt/pack_repo.py 2011-09-07 09:07:25 +0000
@@ -831,7 +831,7 @@
831 set(all_combined).difference([combined_idx]))831 set(all_combined).difference([combined_idx]))
832 # resumed packs832 # resumed packs
833 self._resumed_packs = []833 self._resumed_packs = []
834 self.config_stack = config.LocationStack(self.transport.base)834 self.config_stack = config.stacks.get('locations', self.transport.base)
835835
836 def __repr__(self):836 def __repr__(self):
837 return '%s(%r)' % (self.__class__.__name__, self.repo)837 return '%s(%r)' % (self.__class__.__name__, self.repo)
838838
=== modified file 'bzrlib/tests/per_workingtree/test_workingtree.py'
--- bzrlib/tests/per_workingtree/test_workingtree.py 2011-08-31 22:52:43 +0000
+++ bzrlib/tests/per_workingtree/test_workingtree.py 2011-09-07 09:07:25 +0000
@@ -1161,7 +1161,7 @@
11611161
1162 def test_set_in_branch(self):1162 def test_set_in_branch(self):
1163 wt = self.make_wt_with_worth_saving_limit()1163 wt = self.make_wt_with_worth_saving_limit()
1164 conf = config.BranchStack(wt.branch)1164 conf = config.stacks.get('branch', wt.branch)
1165 conf.set('bzr.workingtree.worth_saving_limit', '20')1165 conf.set('bzr.workingtree.worth_saving_limit', '20')
1166 self.assertEqual(20, wt._worth_saving_limit())1166 self.assertEqual(20, wt._worth_saving_limit())
1167 ds = wt.current_dirstate()1167 ds = wt.current_dirstate()
@@ -1169,7 +1169,7 @@
11691169
1170 def test_invalid(self):1170 def test_invalid(self):
1171 wt = self.make_wt_with_worth_saving_limit()1171 wt = self.make_wt_with_worth_saving_limit()
1172 conf = config.BranchStack(wt.branch)1172 conf = config.stacks.get('branch', wt.branch)
1173 conf.set('bzr.workingtree.worth_saving_limit', 'a')1173 conf.set('bzr.workingtree.worth_saving_limit', 'a')
1174 # If the config entry is invalid, default to 101174 # If the config entry is invalid, default to 10
1175 warnings = []1175 warnings = []
11761176
=== modified file 'bzrlib/tests/test_config.py'
--- bzrlib/tests/test_config.py 2011-09-06 11:53:48 +0000
+++ bzrlib/tests/test_config.py 2011-09-07 09:07:25 +0000
@@ -132,15 +132,15 @@
132132
133133
134config.test_stack_builder_registry.register(134config.test_stack_builder_registry.register(
135 'bazaar', lambda test: config.GlobalStack())135 'bazaar', lambda test: config.stacks.get('bazaar'))
136config.test_stack_builder_registry.register(136config.test_stack_builder_registry.register(
137 'location', lambda test: config.LocationStack('.'))137 'location', lambda test: config.stacks.get('locations', '.'))
138138
139139
140def build_branch_stack(test):140def build_branch_stack(test):
141 build_backing_branch(test, 'branch')141 build_backing_branch(test, 'branch')
142 b = branch.Branch.open('branch')142 b = branch.Branch.open('branch')
143 return config.BranchStack(b)143 return config.stacks.get('branch', b)
144config.test_stack_builder_registry.register('branch', build_branch_stack)144config.test_stack_builder_registry.register('branch', build_branch_stack)
145145
146146
@@ -151,7 +151,7 @@
151 server_class) = transport_remote.get_test_permutations()[0]151 server_class) = transport_remote.get_test_permutations()[0]
152 build_backing_branch(test, 'branch', transport_class, server_class)152 build_backing_branch(test, 'branch', transport_class, server_class)
153 b = branch.Branch.open(test.get_url('branch'))153 b = branch.Branch.open(test.get_url('branch'))
154 return config.RemoteBranchStack(b)154 return config.stacks.get('branch', b)
155config.test_stack_builder_registry.register('remote_branch',155config.test_stack_builder_registry.register('remote_branch',
156 build_remote_branch_stack)156 build_remote_branch_stack)
157157
@@ -164,7 +164,7 @@
164 # creating a dedicated helper to create only the bzrdir164 # creating a dedicated helper to create only the bzrdir
165 build_backing_branch(test, 'branch', transport_class, server_class)165 build_backing_branch(test, 'branch', transport_class, server_class)
166 b = branch.Branch.open(test.get_url('branch'))166 b = branch.Branch.open(test.get_url('branch'))
167 return config.RemoteControlStack(b.bzrdir)167 return config.stacks.get('control-only', b.bzrdir)
168config.test_stack_builder_registry.register('remote_control',168config.test_stack_builder_registry.register('remote_control',
169 build_remote_control_stack)169 build_remote_control_stack)
170170
@@ -2529,7 +2529,6 @@
25292529
2530 def test_help_is_set(self):2530 def test_help_is_set(self):
2531 option_help = self.registry.get_help(self.option_name)2531 option_help = self.registry.get_help(self.option_name)
2532 self.assertNotEquals(None, option_help)
2533 # Come on, think about the user, he really wants to know what the2532 # Come on, think about the user, he really wants to know what the
2534 # option is about2533 # option is about
2535 self.assertIsNot(None, option_help)2534 self.assertIsNot(None, option_help)
@@ -3181,6 +3180,54 @@
3181 self.assertEquals(expected_location, matcher.location)3180 self.assertEquals(expected_location, matcher.location)
31823181
31833182
3183class TestStackRegistry(tests.TestCase):
3184
3185 def setUp(self):
3186 super(TestStackRegistry, self).setUp()
3187 self.registry = config.StackRegistry()
3188
3189 def test_register_remove(self):
3190 self.registry.register('test', config.Stack)
3191 self.assertIs(config.Stack, self.registry._get_factory('test'))
3192 self.registry.remove('test')
3193 self.assertRaises(KeyError, self.registry._get_factory, 'test')
3194
3195 lazy_stack = config.Stack
3196
3197 def test_register_lazy_remove(self):
3198 self.registry.register_lazy('test', self.__module__,
3199 'TestStackRegistry.lazy_stack')
3200 self.assertIs(self.lazy_stack, self.registry._get_factory('test'))
3201 self.registry.remove('test')
3202 self.assertRaises(KeyError, self.registry._get_factory, 'test')
3203
3204 def test_register_override(self):
3205 self.registry.register('test', config.Stack)
3206 self.assertIs(config.Stack, self.registry._get_factory('test'))
3207 self.registry.register('test', self.lazy_stack, override_existing=True)
3208 self.assertIs(self.lazy_stack, self.registry._get_factory('test'))
3209
3210
3211class TestRegisteredStacks(tests.TestCase):
3212
3213 scenarios = [(key, {'stack_name': key, 'stack_factory': factory})
3214 for key, factory in config.stacks.iteritems()]
3215
3216 def setUp(self):
3217 super(TestRegisteredStacks, self).setUp()
3218 self.registry = config.stacks
3219
3220 def test_factory_callable(self):
3221 self.assertTrue(callable(self.stack_factory))
3222
3223 def test_help_is_set(self):
3224 stack_help = self.registry.get_help(self.stack_name)
3225 # Come on, think about the user, he really wants to know what the
3226 # stack is about
3227 self.assertIsNot(None, stack_help)
3228 self.assertNotEquals('', stack_help)
3229
3230
3184class TestStackGet(tests.TestCase):3231class TestStackGet(tests.TestCase):
31853232
3186 # FIXME: This should be parametrized for all known Stack or dedicated3233 # FIXME: This should be parametrized for all known Stack or dedicated
@@ -3506,7 +3553,7 @@
3506 string = ''3553 string = ''
3507 # Since we don't save the config we won't strictly require to inherit3554 # Since we don't save the config we won't strictly require to inherit
3508 # from TestCaseInTempDir, but an error occurs so quickly...3555 # from TestCaseInTempDir, but an error occurs so quickly...
3509 c = config.LocationStack(location)3556 c = config.stacks.get('locations', location)
3510 c.store._load_from_string(string)3557 c.store._load_from_string(string)
3511 return c3558 return c
35123559
35133560
=== modified file 'bzrlib/tests/test_debug.py'
--- bzrlib/tests/test_debug.py 2011-08-12 16:25:56 +0000
+++ bzrlib/tests/test_debug.py 2011-09-07 09:07:25 +0000
@@ -33,7 +33,7 @@
33 self.assertDebugFlags(['hpss'], 'debug_flags = hpss\n')33 self.assertDebugFlags(['hpss'], 'debug_flags = hpss\n')
3434
35 def assertDebugFlags(self, expected_flags, conf_bytes):35 def assertDebugFlags(self, expected_flags, conf_bytes):
36 conf = config.GlobalStack()36 conf = config.stacks.get('bazaar')
37 conf.store._load_from_string(conf_bytes)37 conf.store._load_from_string(conf_bytes)
38 conf.store.save()38 conf.store.save()
39 self.overrideAttr(debug, 'debug_flags', set())39 self.overrideAttr(debug, 'debug_flags', set())
4040
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2011-08-19 22:34:02 +0000
+++ bzrlib/tests/test_selftest.py 2011-09-07 09:07:25 +0000
@@ -3162,7 +3162,7 @@
3162 result.stopTestRun()3162 result.stopTestRun()
3163 self.assertEqual(result._tests_leaking_threads_count, 0)3163 self.assertEqual(result._tests_leaking_threads_count, 0)
3164 self.assertEqual(result.leaks, [])3164 self.assertEqual(result.leaks, [])
3165 3165
3166 def test_thread_leak(self):3166 def test_thread_leak(self):
3167 """Ensure a thread that outlives the running of a test is reported3167 """Ensure a thread that outlives the running of a test is reported
31683168
31693169
=== modified file 'bzrlib/ui/__init__.py'
--- bzrlib/ui/__init__.py 2011-08-08 15:57:57 +0000
+++ bzrlib/ui/__init__.py 2011-09-07 09:07:25 +0000
@@ -248,7 +248,7 @@
248 """248 """
249 # XXX: is the caller supposed to close the resulting object?249 # XXX: is the caller supposed to close the resulting object?
250 if encoding is None:250 if encoding is None:
251 encoding = config.GlobalStack().get('output_encoding')251 encoding = config.stacks.get('bazaar').get('output_encoding')
252 if encoding is None:252 if encoding is None:
253 encoding = osutils.get_terminal_encoding(trace=True)253 encoding = osutils.get_terminal_encoding(trace=True)
254 if encoding_type is None:254 if encoding_type is None:
255255
=== modified file 'bzrlib/workingtree_4.py'
--- bzrlib/workingtree_4.py 2011-08-12 14:47:42 +0000
+++ bzrlib/workingtree_4.py 2011-09-07 09:07:25 +0000
@@ -251,7 +251,7 @@
251 :return: an integer. -1 means never save.251 :return: an integer. -1 means never save.
252 """252 """
253 # FIXME: We want a WorkingTreeStack here -- vila 20110812253 # FIXME: We want a WorkingTreeStack here -- vila 20110812
254 conf = config.BranchStack(self.branch)254 conf = config.stacks.get('branch', self.branch)
255 return conf.get('bzr.workingtree.worth_saving_limit')255 return conf.get('bzr.workingtree.worth_saving_limit')
256256
257 def filter_unversioned_files(self, paths):257 def filter_unversioned_files(self, paths):
258258
=== modified file 'doc/developers/configuration.txt'
--- doc/developers/configuration.txt 2011-08-19 14:10:32 +0000
+++ doc/developers/configuration.txt 2011-09-07 09:07:25 +0000
@@ -122,9 +122,27 @@
122is one, then the *user* should be able to decide and in this case a new122is one, then the *user* should be able to decide and in this case a new
123``Stack`` can be created cheaply.123``Stack`` can be created cheaply.
124124
125StackRegistry
126-------------
127
128All config stacks should be obtained from ``config.stacks`` which provides
129stack factories for various contexts.
130
131Each factory is registered under a name so plugins can override them as they
132see fit.
133
134Each factory defines its own context as a set of parameters that should be
135provided to build a stack.
136
125Different stacks can be created for different purposes, the existing137Different stacks can be created for different purposes, the existing
126``GlobalStack``, ``LocationStack`` and ``BranchStack`` can be used as basis138``global_stack``, ``location_stack``, ``branch_stack`` and so on can be used
127or examples. These classes are the only ones that should be used in code,139as basis or examples.
128``Stores`` can be used to build them but shouldn't be used otherwise, ditto140
129for sections. Again, the associated tests could and should be used against the141Such factory methods can use ``Stores``, ``SectionMatcher`` or ``Sections``
130created stacks.142objects to build their stack (they are the only intended consumer) but these
143objects shouldn't be used otherwise.
144
145There are parameterized tests intended to exercise these factories and their
146resulting stacks (see ``config.test_stack_builder_registry`` and the tests
147that use them in ``bzrlib/tests/test_config.py``). Using them should help
148implementing such factories.
131149
=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- doc/en/release-notes/bzr-2.5.txt 2011-09-06 13:35:07 +0000
+++ doc/en/release-notes/bzr-2.5.txt 2011-09-07 09:07:25 +0000
@@ -92,6 +92,9 @@
92* bzr now ships with translations for command help. (Jonathan92* bzr now ships with translations for command help. (Jonathan
93 Riddell, INADA Naoki, #83941)93 Riddell, INADA Naoki, #83941)
9494
95* The ``config.stacks`` registry provides a way to get a config stack
96 factory for various contexts. (Vincent Ladeuil, #832036)
97
95Improvements98Improvements
96************99************
97100