2218
2072
del configobj[option_name]
2220
2074
del configobj[section_name][option_name]
2221
for hook in OldConfigHooks['remove']:
2222
hook(self, option_name)
2223
2075
self._set_configobj(configobj)
2225
2077
def _get_config_file(self):
2227
f = StringIO(self._transport.get_bytes(self._filename))
2228
for hook in OldConfigHooks['load']:
2079
return StringIO(self._transport.get_bytes(self._filename))
2231
2080
except errors.NoSuchFile:
2232
2081
return StringIO()
2234
def _external_url(self):
2235
return urlutils.join(self._transport.external_url(), self._filename)
2237
2083
def _get_configobj(self):
2238
2084
f = self._get_config_file()
2241
conf = ConfigObj(f, encoding='utf-8')
2242
except configobj.ConfigObjError, e:
2243
raise errors.ParseConfigError(e.errors, self._external_url())
2244
except UnicodeDecodeError:
2245
raise errors.ConfigContentError(self._external_url())
2086
return ConfigObj(f, encoding='utf-8')
2250
2090
def _set_configobj(self, configobj):
2251
2091
out_file = StringIO()
2252
2092
configobj.write(out_file)
2253
2093
out_file.seek(0)
2254
2094
self._transport.put_file(self._filename, out_file)
2255
for hook in OldConfigHooks['save']:
2259
class Option(object):
2260
"""An option definition.
2262
The option *values* are stored in config files and found in sections.
2264
Here we define various properties about the option itself, its default
2265
value, in which config files it can be stored, etc (TBC).
2268
def __init__(self, name, default=None):
2270
self.default = default
2272
def get_default(self):
2278
option_registry = registry.Registry()
2281
option_registry.register(
2282
'editor', Option('editor'),
2283
help='The command called to launch an editor to enter a message.')
2286
class Section(object):
2287
"""A section defines a dict of option name => value.
2289
This is merely a read-only dict which can add some knowledge about the
2290
options. It is *not* a python dict object though and doesn't try to mimic
2294
def __init__(self, section_id, options):
2295
self.id = section_id
2296
# We re-use the dict-like object received
2297
self.options = options
2299
def get(self, name, default=None):
2300
return self.options.get(name, default)
2303
# Mostly for debugging use
2304
return "<config.%s id=%s>" % (self.__class__.__name__, self.id)
2307
_NewlyCreatedOption = object()
2308
"""Was the option created during the MutableSection lifetime"""
2311
class MutableSection(Section):
2312
"""A section allowing changes and keeping track of the original values."""
2314
def __init__(self, section_id, options):
2315
super(MutableSection, self).__init__(section_id, options)
2318
def set(self, name, value):
2319
if name not in self.options:
2320
# This is a new option
2321
self.orig[name] = _NewlyCreatedOption
2322
elif name not in self.orig:
2323
self.orig[name] = self.get(name, None)
2324
self.options[name] = value
2326
def remove(self, name):
2327
if name not in self.orig:
2328
self.orig[name] = self.get(name, None)
2329
del self.options[name]
2332
class Store(object):
2333
"""Abstract interface to persistent storage for configuration options."""
2335
readonly_section_class = Section
2336
mutable_section_class = MutableSection
2338
def is_loaded(self):
2339
"""Returns True if the Store has been loaded.
2341
This is used to implement lazy loading and ensure the persistent
2342
storage is queried only when needed.
2344
raise NotImplementedError(self.is_loaded)
2347
"""Loads the Store from persistent storage."""
2348
raise NotImplementedError(self.load)
2350
def _load_from_string(self, bytes):
2351
"""Create a store from a string in configobj syntax.
2353
:param bytes: A string representing the file content.
2355
raise NotImplementedError(self._load_from_string)
2358
"""Unloads the Store.
2360
This should make is_loaded() return False. This is used when the caller
2361
knows that the persistent storage has changed or may have change since
2364
raise NotImplementedError(self.unload)
2367
"""Saves the Store to persistent storage."""
2368
raise NotImplementedError(self.save)
2370
def external_url(self):
2371
raise NotImplementedError(self.external_url)
2373
def get_sections(self):
2374
"""Returns an ordered iterable of existing sections.
2376
:returns: An iterable of (name, dict).
2378
raise NotImplementedError(self.get_sections)
2380
def get_mutable_section(self, section_name=None):
2381
"""Returns the specified mutable section.
2383
:param section_name: The section identifier
2385
raise NotImplementedError(self.get_mutable_section)
2388
# Mostly for debugging use
2389
return "<config.%s(%s)>" % (self.__class__.__name__,
2390
self.external_url())
2393
class IniFileStore(Store):
2394
"""A config Store using ConfigObj for storage.
2396
:ivar transport: The transport object where the config file is located.
2398
:ivar file_name: The config file basename in the transport directory.
2400
:ivar _config_obj: Private member to hold the ConfigObj instance used to
2401
serialize/deserialize the config file.
2404
def __init__(self, transport, file_name):
2405
"""A config Store using ConfigObj for storage.
2407
:param transport: The transport object where the config file is located.
2409
:param file_name: The config file basename in the transport directory.
2411
super(IniFileStore, self).__init__()
2412
self.transport = transport
2413
self.file_name = file_name
2414
self._config_obj = None
2416
def is_loaded(self):
2417
return self._config_obj != None
2420
self._config_obj = None
2423
"""Load the store from the associated file."""
2424
if self.is_loaded():
2426
content = self.transport.get_bytes(self.file_name)
2427
self._load_from_string(content)
2428
for hook in ConfigHooks['load']:
2431
def _load_from_string(self, bytes):
2432
"""Create a config store from a string.
2434
:param bytes: A string representing the file content.
2436
if self.is_loaded():
2437
raise AssertionError('Already loaded: %r' % (self._config_obj,))
2438
co_input = StringIO(bytes)
2440
# The config files are always stored utf8-encoded
2441
self._config_obj = ConfigObj(co_input, encoding='utf-8')
2442
except configobj.ConfigObjError, e:
2443
self._config_obj = None
2444
raise errors.ParseConfigError(e.errors, self.external_url())
2445
except UnicodeDecodeError:
2446
raise errors.ConfigContentError(self.external_url())
2449
if not self.is_loaded():
2453
self._config_obj.write(out)
2454
self.transport.put_bytes(self.file_name, out.getvalue())
2455
for hook in ConfigHooks['save']:
2458
def external_url(self):
2459
# FIXME: external_url should really accepts an optional relpath
2460
# parameter (bug #750169) :-/ -- vila 2011-04-04
2461
# The following will do in the interim but maybe we don't want to
2462
# expose a path here but rather a config ID and its associated
2463
# object </hand wawe>.
2464
return urlutils.join(self.transport.external_url(), self.file_name)
2466
def get_sections(self):
2467
"""Get the configobj section in the file order.
2469
:returns: An iterable of (name, dict).
2471
# We need a loaded store
2474
except errors.NoSuchFile:
2475
# If the file doesn't exist, there is no sections
2477
cobj = self._config_obj
2479
yield self.readonly_section_class(None, cobj)
2480
for section_name in cobj.sections:
2481
yield self.readonly_section_class(section_name, cobj[section_name])
2483
def get_mutable_section(self, section_name=None):
2484
# We need a loaded store
2487
except errors.NoSuchFile:
2488
# The file doesn't exist, let's pretend it was empty
2489
self._load_from_string('')
2490
if section_name is None:
2491
section = self._config_obj
2493
section = self._config_obj.setdefault(section_name, {})
2494
return self.mutable_section_class(section_name, section)
2497
# Note that LockableConfigObjStore inherits from ConfigObjStore because we need
2498
# unlockable stores for use with objects that can already ensure the locking
2499
# (think branches). If different stores (not based on ConfigObj) are created,
2500
# they may face the same issue.
2503
class LockableIniFileStore(IniFileStore):
2504
"""A ConfigObjStore using locks on save to ensure store integrity."""
2506
def __init__(self, transport, file_name, lock_dir_name=None):
2507
"""A config Store using ConfigObj for storage.
2509
:param transport: The transport object where the config file is located.
2511
:param file_name: The config file basename in the transport directory.
2513
if lock_dir_name is None:
2514
lock_dir_name = 'lock'
2515
self.lock_dir_name = lock_dir_name
2516
super(LockableIniFileStore, self).__init__(transport, file_name)
2517
self._lock = lockdir.LockDir(self.transport, self.lock_dir_name)
2519
def lock_write(self, token=None):
2520
"""Takes a write lock in the directory containing the config file.
2522
If the directory doesn't exist it is created.
2524
# FIXME: This doesn't check the ownership of the created directories as
2525
# ensure_config_dir_exists does. It should if the transport is local
2526
# -- vila 2011-04-06
2527
self.transport.create_prefix()
2528
return self._lock.lock_write(token)
2533
def break_lock(self):
2534
self._lock.break_lock()
2538
# We need to be able to override the undecorated implementation
2539
self.save_without_locking()
2541
def save_without_locking(self):
2542
super(LockableIniFileStore, self).save()
2545
# FIXME: global, bazaar, shouldn't that be 'user' instead or even
2546
# 'user_defaults' as opposed to 'user_overrides', 'system_defaults'
2547
# (/etc/bzr/bazaar.conf) and 'system_overrides' ? -- vila 2011-04-05
2549
# FIXME: Moreover, we shouldn't need classes for these stores either, factory
2550
# functions or a registry will make it easier and clearer for tests, focusing
2551
# on the relevant parts of the API that needs testing -- vila 20110503 (based
2552
# on a poolie's remark)
2553
class GlobalStore(LockableIniFileStore):
2555
def __init__(self, possible_transports=None):
2556
t = transport.get_transport(config_dir(),
2557
possible_transports=possible_transports)
2558
super(GlobalStore, self).__init__(t, 'bazaar.conf')
2561
class LocationStore(LockableIniFileStore):
2563
def __init__(self, possible_transports=None):
2564
t = transport.get_transport(config_dir(),
2565
possible_transports=possible_transports)
2566
super(LocationStore, self).__init__(t, 'locations.conf')
2569
class BranchStore(IniFileStore):
2571
def __init__(self, branch):
2572
super(BranchStore, self).__init__(branch.control_transport,
2574
self.branch = branch
2576
def lock_write(self, token=None):
2577
return self.branch.lock_write(token)
2580
return self.branch.unlock()
2584
# We need to be able to override the undecorated implementation
2585
self.save_without_locking()
2587
def save_without_locking(self):
2588
super(BranchStore, self).save()
2591
class SectionMatcher(object):
2592
"""Select sections into a given Store.
2594
This intended to be used to postpone getting an iterable of sections from a
2598
def __init__(self, store):
2601
def get_sections(self):
2602
# This is where we require loading the store so we can see all defined
2604
sections = self.store.get_sections()
2605
# Walk the revisions in the order provided
2610
def match(self, secion):
2611
raise NotImplementedError(self.match)
2614
class LocationSection(Section):
2616
def __init__(self, section, length, extra_path):
2617
super(LocationSection, self).__init__(section.id, section.options)
2618
self.length = length
2619
self.extra_path = extra_path
2621
def get(self, name, default=None):
2622
value = super(LocationSection, self).get(name, default)
2623
if value is not None:
2624
policy_name = self.get(name + ':policy', None)
2625
policy = _policy_value.get(policy_name, POLICY_NONE)
2626
if policy == POLICY_APPENDPATH:
2627
value = urlutils.join(value, self.extra_path)
2631
class LocationMatcher(SectionMatcher):
2633
def __init__(self, store, location):
2634
super(LocationMatcher, self).__init__(store)
2635
if location.startswith('file://'):
2636
location = urlutils.local_path_from_url(location)
2637
self.location = location
2639
def _get_matching_sections(self):
2640
"""Get all sections matching ``location``."""
2641
# We slightly diverge from LocalConfig here by allowing the no-name
2642
# section as the most generic one and the lower priority.
2643
no_name_section = None
2645
# Filter out the no_name_section so _iter_for_location_by_parts can be
2646
# used (it assumes all sections have a name).
2647
for section in self.store.get_sections():
2648
if section.id is None:
2649
no_name_section = section
2651
sections.append(section)
2652
# Unfortunately _iter_for_location_by_parts deals with section names so
2653
# we have to resync.
2654
filtered_sections = _iter_for_location_by_parts(
2655
[s.id for s in sections], self.location)
2656
iter_sections = iter(sections)
2657
matching_sections = []
2658
if no_name_section is not None:
2659
matching_sections.append(
2660
LocationSection(no_name_section, 0, self.location))
2661
for section_id, extra_path, length in filtered_sections:
2662
# a section id is unique for a given store so it's safe to iterate
2664
section = iter_sections.next()
2665
if section_id == section.id:
2666
matching_sections.append(
2667
LocationSection(section, length, extra_path))
2668
return matching_sections
2670
def get_sections(self):
2671
# Override the default implementation as we want to change the order
2672
matching_sections = self._get_matching_sections()
2673
# We want the longest (aka more specific) locations first
2674
sections = sorted(matching_sections,
2675
key=lambda section: (section.length, section.id),
2677
# Sections mentioning 'ignore_parents' restrict the selection
2678
for section in sections:
2679
# FIXME: We really want to use as_bool below -- vila 2011-04-07
2680
ignore = section.get('ignore_parents', None)
2681
if ignore is not None:
2682
ignore = ui.bool_from_string(ignore)
2685
# Finally, we have a valid section
2689
class Stack(object):
2690
"""A stack of configurations where an option can be defined"""
2692
def __init__(self, sections_def, store=None, mutable_section_name=None):
2693
"""Creates a stack of sections with an optional store for changes.
2695
:param sections_def: A list of Section or callables that returns an
2696
iterable of Section. This defines the Sections for the Stack and
2697
can be called repeatedly if needed.
2699
:param store: The optional Store where modifications will be
2700
recorded. If none is specified, no modifications can be done.
2702
:param mutable_section_name: The name of the MutableSection where
2703
changes are recorded. This requires the ``store`` parameter to be
2706
self.sections_def = sections_def
2708
self.mutable_section_name = mutable_section_name
2710
def get(self, name):
2711
"""Return the *first* option value found in the sections.
2713
This is where we guarantee that sections coming from Store are loaded
2714
lazily: the loading is delayed until we need to either check that an
2715
option exists or get its value, which in turn may require to discover
2716
in which sections it can be defined. Both of these (section and option
2717
existence) require loading the store (even partially).
2719
# FIXME: No caching of options nor sections yet -- vila 20110503
2721
# Ensuring lazy loading is achieved by delaying section matching (which
2722
# implies querying the persistent storage) until it can't be avoided
2723
# anymore by using callables to describe (possibly empty) section
2725
for section_or_callable in self.sections_def:
2726
# Each section can expand to multiple ones when a callable is used
2727
if callable(section_or_callable):
2728
sections = section_or_callable()
2730
sections = [section_or_callable]
2731
for section in sections:
2732
value = section.get(name)
2733
if value is not None:
2735
if value is not None:
2738
# If the option is registered, it may provide a default value
2740
opt = option_registry.get(name)
2745
value = opt.get_default()
2746
for hook in ConfigHooks['get']:
2747
hook(self, name, value)
2750
def _get_mutable_section(self):
2751
"""Get the MutableSection for the Stack.
2753
This is where we guarantee that the mutable section is lazily loaded:
2754
this means we won't load the corresponding store before setting a value
2755
or deleting an option. In practice the store will often be loaded but
2756
this allows helps catching some programming errors.
2758
section = self.store.get_mutable_section(self.mutable_section_name)
2761
def set(self, name, value):
2762
"""Set a new value for the option."""
2763
section = self._get_mutable_section()
2764
section.set(name, value)
2765
for hook in ConfigHooks['set']:
2766
hook(self, name, value)
2768
def remove(self, name):
2769
"""Remove an existing option."""
2770
section = self._get_mutable_section()
2771
section.remove(name)
2772
for hook in ConfigHooks['remove']:
2776
# Mostly for debugging use
2777
return "<config.%s(%s)>" % (self.__class__.__name__, id(self))
2780
class _CompatibleStack(Stack):
2781
"""Place holder for compatibility with previous design.
2783
This is intended to ease the transition from the Config-based design to the
2784
Stack-based design and should not be used nor relied upon by plugins.
2786
One assumption made here is that the daughter classes will all use Stores
2787
derived from LockableIniFileStore).
2789
It implements set() by re-loading the store before applying the
2790
modification and saving it.
2792
The long term plan being to implement a single write by store to save
2793
all modifications, this class should not be used in the interim.
2796
def set(self, name, value):
2799
super(_CompatibleStack, self).set(name, value)
2800
# Force a write to persistent storage
2804
class GlobalStack(_CompatibleStack):
2808
gstore = GlobalStore()
2809
super(GlobalStack, self).__init__([gstore.get_sections], gstore)
2812
class LocationStack(_CompatibleStack):
2814
def __init__(self, location):
2815
lstore = LocationStore()
2816
matcher = LocationMatcher(lstore, location)
2817
gstore = GlobalStore()
2818
super(LocationStack, self).__init__(
2819
[matcher.get_sections, gstore.get_sections], lstore)
2821
class BranchStack(_CompatibleStack):
2823
def __init__(self, branch):
2824
bstore = BranchStore(branch)
2825
lstore = LocationStore()
2826
matcher = LocationMatcher(lstore, branch.base)
2827
gstore = GlobalStore()
2828
super(BranchStack, self).__init__(
2829
[matcher.get_sections, bstore.get_sections, gstore.get_sections],
2831
self.branch = branch
2834
2097
class cmd_config(commands.Command):