/breezy/unstable

To get this branch, use:
bzr branch https://code.breezy-vcs.org/breezy/unstable

« back to all changes in this revision

Viewing changes to breezy/config.py

Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
email=Your Name <your@email.address>
28
28
check_signatures=require|ignore|check-available(default)
29
29
create_signatures=always|never|when-required(default)
30
 
gpg_signing_command=name-of-program
31
30
log_format=name-of-format
32
31
validate_signatures_in_log=true|false(default)
33
32
acceptable_keys=pattern1,pattern2
83
82
from .lazy_import import lazy_import
84
83
lazy_import(globals(), """
85
84
import base64
 
85
import errno
86
86
import fnmatch
87
87
import re
 
88
import stat
88
89
 
89
90
from breezy import (
90
91
    atomicfile,
91
92
    controldir,
92
93
    debug,
93
94
    directory_service,
94
 
    errors,
95
95
    lazy_regex,
96
96
    library_state,
97
97
    lockdir,
107
107
""")
108
108
from . import (
109
109
    commands,
 
110
    errors,
110
111
    hooks,
111
112
    lazy_regex,
112
113
    registry,
154
155
STORE_GLOBAL = 4
155
156
 
156
157
 
 
158
class OptionExpansionLoop(errors.BzrError):
 
159
 
 
160
    _fmt = 'Loop involving %(refs)r while expanding "%(string)s".'
 
161
 
 
162
    def __init__(self, string, refs):
 
163
        self.string = string
 
164
        self.refs = '->'.join(refs)
 
165
 
 
166
 
 
167
class ExpandingUnknownOption(errors.BzrError):
 
168
 
 
169
    _fmt = 'Option "%(name)s" is not defined while expanding "%(string)s".'
 
170
 
 
171
    def __init__(self, name, string):
 
172
        self.name = name
 
173
        self.string = string
 
174
 
 
175
 
 
176
class IllegalOptionName(errors.BzrError):
 
177
 
 
178
    _fmt = 'Option "%(name)s" is not allowed.'
 
179
 
 
180
    def __init__(self, name):
 
181
        self.name = name
 
182
 
 
183
 
 
184
class ConfigContentError(errors.BzrError):
 
185
 
 
186
    _fmt = "Config file %(filename)s is not UTF-8 encoded\n"
 
187
 
 
188
    def __init__(self, filename):
 
189
        self.filename = filename
 
190
 
 
191
 
 
192
class ParseConfigError(errors.BzrError):
 
193
 
 
194
    _fmt = "Error(s) parsing config file %(filename)s:\n%(errors)s"
 
195
 
 
196
    def __init__(self, errors, filename):
 
197
        self.filename = filename
 
198
        self.errors = '\n'.join(e.msg for e in errors)
 
199
 
 
200
 
 
201
class ConfigOptionValueError(errors.BzrError):
 
202
 
 
203
    _fmt = ('Bad value "%(value)s" for option "%(name)s".\n'
 
204
            'See ``brz help %(name)s``')
 
205
 
 
206
    def __init__(self, name, value):
 
207
        errors.BzrError.__init__(self, name=name, value=value)
 
208
 
 
209
 
 
210
class NoEmailInUsername(errors.BzrError):
 
211
 
 
212
    _fmt = "%(username)r does not seem to contain a reasonable email address"
 
213
 
 
214
    def __init__(self, username):
 
215
        self.username = username
 
216
 
 
217
 
 
218
class NoSuchConfig(errors.BzrError):
 
219
 
 
220
    _fmt = ('The "%(config_id)s" configuration does not exist.')
 
221
 
 
222
    def __init__(self, config_id):
 
223
        errors.BzrError.__init__(self, config_id=config_id)
 
224
 
 
225
 
 
226
class NoSuchConfigOption(errors.BzrError):
 
227
 
 
228
    _fmt = ('The "%(option_name)s" configuration option does not exist.')
 
229
 
 
230
    def __init__(self, option_name):
 
231
        errors.BzrError.__init__(self, option_name=option_name)
 
232
 
 
233
 
 
234
class NoWhoami(errors.BzrError):
 
235
 
 
236
    _fmt = ('Unable to determine your name.\n'
 
237
        "Please, set your name with the 'whoami' command.\n"
 
238
        'E.g. brz whoami "Your Name <name@example.com>"')
 
239
 
 
240
 
157
241
def signature_policy_from_unicode(signature_string):
158
242
    """Convert a string to a signing policy."""
159
243
    if signature_string.lower() == 'check-available':
341
425
                else:
342
426
                    name = chunk[1:-1]
343
427
                    if name in _ref_stack:
344
 
                        raise errors.OptionExpansionLoop(string, _ref_stack)
 
428
                        raise OptionExpansionLoop(string, _ref_stack)
345
429
                    _ref_stack.append(name)
346
430
                    value = self._expand_option(name, env, _ref_stack)
347
431
                    if value is None:
348
 
                        raise errors.ExpandingUnknownOption(name, string)
 
432
                        raise ExpandingUnknownOption(name, string)
349
433
                    if isinstance(value, list):
350
434
                        list_value = True
351
435
                        chunks.extend(value)
474
558
        $BRZ_EMAIL can be set to override this, then
475
559
        the concrete policy type is checked, and finally
476
560
        $EMAIL is examined.
477
 
        If no username can be found, errors.NoWhoami exception is raised.
 
561
        If no username can be found, NoWhoami exception is raised.
478
562
        """
479
563
        v = os.environ.get('BRZ_EMAIL')
480
564
        if v:
487
571
        return default_email()
488
572
 
489
573
    def ensure_username(self):
490
 
        """Raise errors.NoWhoami if username is not set.
 
574
        """Raise NoWhoami if username is not set.
491
575
 
492
576
        This method relies on the username() function raising the error.
493
577
        """
665
749
        try:
666
750
            self._parser = ConfigObj(co_input, encoding='utf-8')
667
751
        except configobj.ConfigObjError as e:
668
 
            raise errors.ParseConfigError(e.errors, e.config.filename)
 
752
            raise ParseConfigError(e.errors, e.config.filename)
669
753
        except UnicodeDecodeError:
670
 
            raise errors.ConfigContentError(self.file_name)
 
754
            raise ConfigContentError(self.file_name)
671
755
        # Make sure self.reload() will use the right file name
672
756
        self._parser.filename = self.file_name
673
757
        for hook in OldConfigHooks['load']:
796
880
        else:
797
881
            return None
798
882
 
799
 
    def _gpg_signing_command(self):
800
 
        """See Config.gpg_signing_command."""
801
 
        return self._get_user_option('gpg_signing_command')
802
 
 
803
883
    def _log_format(self):
804
884
        """See Config.log_format."""
805
885
        return self._get_user_option('log_format')
843
923
        try:
844
924
            del section[option_name]
845
925
        except KeyError:
846
 
            raise errors.NoSuchConfigOption(option_name)
 
926
            raise NoSuchConfigOption(option_name)
847
927
        self._write_config_file()
848
928
        for hook in OldConfigHooks['remove']:
849
929
            hook(self, option_name)
1312
1392
    def remove_user_option(self, option_name, section_name=None):
1313
1393
        self._get_branch_data_config().remove_option(option_name, section_name)
1314
1394
 
1315
 
    def _gpg_signing_command(self):
1316
 
        """See Config.gpg_signing_command."""
1317
 
        return self._get_safe_value('_gpg_signing_command')
1318
 
 
1319
1395
    def _post_commit(self):
1320
1396
        """See Config.post_commit."""
1321
1397
        return self._get_safe_value('_post_commit')
1513
1589
        return u'%s <%s>' % (name, email)
1514
1590
    elif email:
1515
1591
        return email
1516
 
    raise errors.NoWhoami()
 
1592
    raise NoWhoami()
1517
1593
 
1518
1594
 
1519
1595
def _auto_user_id():
1598
1674
    """
1599
1675
    name, email = parse_username(e)
1600
1676
    if not email:
1601
 
        raise errors.NoEmailInUsername(e)
 
1677
        raise NoEmailInUsername(e)
1602
1678
    return email
1603
1679
 
1604
1680
 
1643
1719
            self.branch.unlock()
1644
1720
 
1645
1721
 
 
1722
_authentication_config_permission_errors = set()
 
1723
 
 
1724
 
1646
1725
class AuthenticationConfig(object):
1647
1726
    """The authentication configuration file based on a ini file.
1648
1727
 
1655
1734
        if _file is None:
1656
1735
            self._filename = authentication_config_filename()
1657
1736
            self._input = self._filename = authentication_config_filename()
 
1737
            self._check_permissions()
1658
1738
        else:
1659
1739
            # Tests can provide a string as _file
1660
1740
            self._filename = None
1672
1752
            # encoded, but the values in the ConfigObj are always Unicode.
1673
1753
            self._config = ConfigObj(self._input, encoding='utf-8')
1674
1754
        except configobj.ConfigObjError as e:
1675
 
            raise errors.ParseConfigError(e.errors, e.config.filename)
 
1755
            raise ParseConfigError(e.errors, e.config.filename)
1676
1756
        except UnicodeError:
1677
 
            raise errors.ConfigContentError(self._filename)
 
1757
            raise ConfigContentError(self._filename)
1678
1758
        return self._config
1679
1759
 
 
1760
    def _check_permissions(self):
 
1761
        """Check permission of auth file are user read/write able only."""
 
1762
        try:
 
1763
            st = os.stat(self._filename)
 
1764
        except OSError as e:
 
1765
            if e.errno != errno.ENOENT:
 
1766
                trace.mutter('Unable to stat %r: %r', self._filename, e)
 
1767
            return
 
1768
        mode = stat.S_IMODE(st.st_mode)
 
1769
        if ((stat.S_IXOTH | stat.S_IWOTH | stat.S_IROTH | stat.S_IXGRP |
 
1770
             stat.S_IWGRP | stat.S_IRGRP ) & mode):
 
1771
            # Only warn once
 
1772
            if (not self._filename in _authentication_config_permission_errors
 
1773
                and not GlobalConfig().suppress_warning(
 
1774
                    'insecure_permissions')):
 
1775
                trace.warning("The file '%s' has insecure "
 
1776
                        "file permissions. Saved passwords may be accessible "
 
1777
                        "by other users.", self._filename)
 
1778
                _authentication_config_permission_errors.add(self._filename)
 
1779
 
1680
1780
    def _save(self):
1681
1781
        """Save the config file, only tests should use it for now."""
1682
1782
        conf_dir = os.path.dirname(self._filename)
1683
1783
        ensure_config_dir_exists(conf_dir)
1684
 
        f = file(self._filename, 'wb')
 
1784
        fd = os.open(self._filename, os.O_RDWR|os.O_CREAT, 0o600)
1685
1785
        try:
 
1786
            f = os.fdopen(fd, 'wb')
1686
1787
            self._get_config().write(f)
1687
1788
        finally:
1688
1789
            f.close()
2184
2285
            try:
2185
2286
                conf = ConfigObj(f, encoding='utf-8')
2186
2287
            except configobj.ConfigObjError as e:
2187
 
                raise errors.ParseConfigError(e.errors, self._external_url())
 
2288
                raise ParseConfigError(e.errors, self._external_url())
2188
2289
            except UnicodeDecodeError:
2189
 
                raise errors.ConfigContentError(self._external_url())
 
2290
                raise ConfigContentError(self._external_url())
2190
2291
        finally:
2191
2292
            f.close()
2192
2293
        return conf
2302
2403
                trace.warning('Value "%s" is not valid for "%s"',
2303
2404
                              unicode_value, self.name)
2304
2405
            elif self.invalid == 'error':
2305
 
                raise errors.ConfigOptionValueError(self.name, unicode_value)
 
2406
                raise ConfigOptionValueError(self.name, unicode_value)
2306
2407
        return converted
2307
2408
 
2308
2409
    def get_override(self):
2321
2422
        for var in self.default_from_env:
2322
2423
            try:
2323
2424
                # If the env variable is defined, its value is the default one
2324
 
                value = os.environ[var].decode(osutils.get_user_encoding())
 
2425
                value = os.environ[var]
 
2426
                if not PY3:
 
2427
                    value = value.decode(osutils.get_user_encoding())
2325
2428
                break
2326
2429
            except KeyError:
2327
2430
                continue
2500
2603
        :param option_name: The name to validate.
2501
2604
        """
2502
2605
        if _option_ref_re.match('{%s}' % option_name) is None:
2503
 
            raise errors.IllegalOptionName(option_name)
 
2606
            raise IllegalOptionName(option_name)
2504
2607
 
2505
2608
    def register(self, option):
2506
2609
        """Register a new option to its name.
2677
2780
    Option('email', override_from_env=['BRZ_EMAIL'], default=default_email,
2678
2781
           help='The users identity'))
2679
2782
option_registry.register(
2680
 
    Option('gpg_signing_command',
2681
 
           default='gpg',
2682
 
           help="""\
2683
 
Program to use use for creating signatures.
2684
 
 
2685
 
This should support at least the -u and --clearsign options.
2686
 
"""))
2687
 
option_registry.register(
2688
2783
    Option('gpg_signing_key',
2689
2784
           default=None,
2690
2785
           help="""\
2693
2788
This defaults to the first key associated with the users email.
2694
2789
"""))
2695
2790
option_registry.register(
2696
 
    Option('ignore_missing_extensions', default=False,
2697
 
           from_unicode=bool_from_store,
2698
 
           help='''\
2699
 
Control the missing extensions warning display.
2700
 
 
2701
 
The warning will not be emitted if set to True.
2702
 
'''))
2703
 
option_registry.register(
2704
2791
    Option('language',
2705
2792
           help='Language to translate messages into.'))
2706
2793
option_registry.register(
3149
3236
                                         list_values=False)
3150
3237
        except configobj.ConfigObjError as e:
3151
3238
            self._config_obj = None
3152
 
            raise errors.ParseConfigError(e.errors, self.external_url())
 
3239
            raise ParseConfigError(e.errors, self.external_url())
3153
3240
        except UnicodeDecodeError:
3154
 
            raise errors.ConfigContentError(self.external_url())
 
3241
            raise ConfigContentError(self.external_url())
3155
3242
 
3156
3243
    def save_changes(self):
3157
3244
        if not self.is_loaded():
3200
3287
            self.load()
3201
3288
        except errors.NoSuchFile:
3202
3289
            # The file doesn't exist, let's pretend it was empty
3203
 
            self._load_from_string('')
 
3290
            self._load_from_string(b'')
3204
3291
        if section_id in self.dirty_sections:
3205
3292
            # We already created a mutable section for this id
3206
3293
            return self.dirty_sections[section_id]
3713
3800
                    expanded = True
3714
3801
                    name = chunk[1:-1]
3715
3802
                    if name in _refs:
3716
 
                        raise errors.OptionExpansionLoop(string, _refs)
 
3803
                        raise OptionExpansionLoop(string, _refs)
3717
3804
                    _refs.append(name)
3718
3805
                    value = self._expand_option(name, env, _refs)
3719
3806
                    if value is None:
3720
 
                        raise errors.ExpandingUnknownOption(name, string)
 
3807
                        raise ExpandingUnknownOption(name, string)
3721
3808
                    chunks.append(value)
3722
3809
                    _refs.pop()
3723
3810
            result = ''.join(chunks)
4108
4195
                if write_access:
4109
4196
                    self.add_cleanup(br.lock_write().unlock)
4110
4197
                return br.get_config_stack()
4111
 
            raise errors.NoSuchConfig(scope)
 
4198
            raise NoSuchConfig(scope)
4112
4199
        else:
4113
4200
            try:
4114
4201
                (_, br, _) = (
4133
4220
            value = self._quote_multiline(value)
4134
4221
            self.outf.write('%s\n' % (value,))
4135
4222
        else:
4136
 
            raise errors.NoSuchConfigOption(name)
 
4223
            raise NoSuchConfigOption(name)
4137
4224
 
4138
4225
    def _show_matching_options(self, name, directory, scope):
4139
4226
        name = lazy_regex.lazy_compile(name)
4178
4265
            # Explicitly save the changes
4179
4266
            conf.store.save_changes()
4180
4267
        except KeyError:
4181
 
            raise errors.NoSuchConfigOption(name)
 
4268
            raise NoSuchConfigOption(name)
4182
4269
 
4183
4270
 
4184
4271
# Test registries