~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Aaron Bentley
  • Date: 2005-10-18 18:48:27 UTC
  • mto: (1185.25.1)
  • mto: This revision was merged to the branch mainline in revision 1474.
  • Revision ID: abentley@panoramicfeedback.com-20051018184827-2cc69376beb1cdf3
Switched to ConfigObj

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
#   Authors: Robert Collins <robert.collins@canonical.com>
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
"""Configuration that affects the behaviour of Bazaar.
 
19
 
 
20
Currently this configuration resides in ~/.bazaar/bazaar.conf
 
21
and ~/.bazaar/branches.conf, which is written to by bzr.
 
22
 
 
23
In bazaar.config the following options may be set:
 
24
[DEFAULT]
 
25
editor=name-of-program
 
26
email=Your Name <your@email.address>
 
27
check_signatures=require|ignore|check-available(default)
 
28
create_signatures=always|never|when-required(default)
 
29
 
 
30
in branches.conf, you specify the url of a branch and options for it.
 
31
Wildcards may be used - * and ? as normal in shell completion. Options
 
32
set in both bazaar.conf and branches.conf are overriden by the branches.conf
 
33
setting.
 
34
[/home/robertc/source]
 
35
recurse=False|True(default)
 
36
email= as above
 
37
check_signatures= as abive 
 
38
create_signatures= as above.
 
39
 
 
40
explanation of options
 
41
----------------------
 
42
editor - this option sets the pop up editor to use during commits.
 
43
email - this option sets the user id bzr will use when committing.
 
44
check_signatures - this option controls whether bzr will require good gpg
 
45
                   signatures, ignore them, or check them if they are 
 
46
                   present.
 
47
create_signatures - this option controls whether bzr will always create 
 
48
                    gpg signatures, never create them, or create them if the
 
49
                    branch is configured to require them.
 
50
                    NB: This option is planned, but not implemented yet.
 
51
"""
 
52
 
 
53
from util.configobj.configobj import ConfigObj, ConfigObjError
 
54
import os
 
55
from fnmatch import fnmatch
 
56
import errno
 
57
import re
 
58
 
 
59
import bzrlib
 
60
import bzrlib.errors as errors
 
61
 
 
62
 
 
63
CHECK_IF_POSSIBLE=0
 
64
CHECK_ALWAYS=1
 
65
CHECK_NEVER=2
 
66
 
 
67
 
 
68
class Config(object):
 
69
    """A configuration policy - what username, editor, gpg needs etc."""
 
70
 
 
71
    def get_editor(self):
 
72
        """Get the users pop up editor."""
 
73
        raise NotImplementedError
 
74
 
 
75
    def _get_signature_checking(self):
 
76
        """Template method to override signature checking policy."""
 
77
 
 
78
    def __init__(self):
 
79
        super(Config, self).__init__()
 
80
 
 
81
    def user_email(self):
 
82
        """Return just the email component of a username."""
 
83
        e = self.username()
 
84
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
85
        if not m:
 
86
            raise BzrError("%r doesn't seem to contain "
 
87
                           "a reasonable email address" % e)
 
88
        return m.group(0)
 
89
 
 
90
    def username(self):
 
91
        """Return email-style username.
 
92
    
 
93
        Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
94
        
 
95
        $BZREMAIL can be set to override this, then
 
96
        the concrete policy type is checked, and finally
 
97
        $EMAIL is examinged.
 
98
        but if none is found, a reasonable default is (hopefully)
 
99
        created.
 
100
    
 
101
        TODO: Check it's reasonably well-formed.
 
102
        """
 
103
        v = os.environ.get('BZREMAIL')
 
104
        if v:
 
105
            return v.decode(bzrlib.user_encoding)
 
106
    
 
107
        v = self._get_user_id()
 
108
        if v:
 
109
            return v
 
110
        
 
111
        v = os.environ.get('EMAIL')
 
112
        if v:
 
113
            return v.decode(bzrlib.user_encoding)
 
114
 
 
115
        name, email = _auto_user_id()
 
116
        if name:
 
117
            return '%s <%s>' % (name, email)
 
118
        else:
 
119
            return email
 
120
 
 
121
    def signature_checking(self):
 
122
        """What is the current policy for signature checking?."""
 
123
        policy = self._get_signature_checking()
 
124
        if policy is not None:
 
125
            return policy
 
126
        return CHECK_IF_POSSIBLE
 
127
 
 
128
    def signature_needed(self):
 
129
        """Is a signature needed when committing ?."""
 
130
        policy = self._get_signature_checking()
 
131
        if policy == CHECK_ALWAYS:
 
132
            return True
 
133
        return False
 
134
 
 
135
 
 
136
class IniBasedConfig(Config):
 
137
    """A configuration policy that draws from ini files."""
 
138
 
 
139
    def _get_parser(self, file=None):
 
140
        if file is None:
 
141
            input = self._get_filename()
 
142
        else:
 
143
            input = file
 
144
        if self._parser is None:
 
145
            try:
 
146
                self._parser = ConfigObj(input)
 
147
            except ConfigObjError, e:
 
148
                raise errors.ParseConfigError(e.errors, e.config.filename)
 
149
        return self._parser
 
150
 
 
151
    def _get_section(self):
 
152
        """Override this to define the section used by the config."""
 
153
        return "DEFAULT"
 
154
 
 
155
    def _config_val(self, name, section=None):
 
156
        if section is None:
 
157
            section = self._get_section()
 
158
        if section is None:
 
159
            raise KeyError(name)
 
160
        # Throw KeyError if name or section not present
 
161
        if section == "DEFAULT":
 
162
            try:
 
163
                return self._get_parser()[name]
 
164
            except KeyError:
 
165
                pass
 
166
        return self._get_parser()[section][name]
 
167
 
 
168
    def _config_bool(self, name, section=None):
 
169
        val = self._config_val(name, section).lower()
 
170
        if val in ('1', 'yes', 'true', 'on'):
 
171
            return True
 
172
        elif val in ('0', 'no', 'false', 'off'):
 
173
            return False
 
174
        else:
 
175
            raise ValueError("Value %r is not boolean" % val)
 
176
 
 
177
        
 
178
 
 
179
    def _get_signature_checking(self):
 
180
        """See Config._get_signature_checking."""
 
181
        try:
 
182
            policy = self._config_val('check_signatures')
 
183
        except KeyError:
 
184
            return None
 
185
        return self._string_to_signature_policy(policy)
 
186
 
 
187
    def _get_user_id(self):
 
188
        """Get the user id from the 'email' key in the current section."""
 
189
        try:
 
190
            return self._config_val('email')
 
191
        except KeyError:
 
192
            pass
 
193
 
 
194
    def __init__(self, get_filename):
 
195
        super(IniBasedConfig, self).__init__()
 
196
        self._get_filename = get_filename
 
197
        self._parser = None
 
198
 
 
199
    def _string_to_signature_policy(self, signature_string):
 
200
        """Convert a string to a signing policy."""
 
201
        if signature_string.lower() == 'check-available':
 
202
            return CHECK_IF_POSSIBLE
 
203
        if signature_string.lower() == 'ignore':
 
204
            return CHECK_NEVER
 
205
        if signature_string.lower() == 'require':
 
206
            return CHECK_ALWAYS
 
207
        raise errors.BzrError("Invalid signatures policy '%s'"
 
208
                              % signature_string)
 
209
 
 
210
 
 
211
class GlobalConfig(IniBasedConfig):
 
212
    """The configuration that should be used for a specific location."""
 
213
 
 
214
    def get_editor(self):
 
215
        try:
 
216
            return self._config_val('editor')
 
217
        except KeyError:
 
218
            pass
 
219
 
 
220
    def __init__(self):
 
221
        super(GlobalConfig, self).__init__(config_filename)
 
222
 
 
223
 
 
224
class LocationConfig(IniBasedConfig):
 
225
    """A configuration object that gives the policy for a location."""
 
226
 
 
227
    def __init__(self, location):
 
228
        super(LocationConfig, self).__init__(branches_config_filename)
 
229
        self._global_config = None
 
230
        self.location = location
 
231
 
 
232
    def _get_global_config(self):
 
233
        if self._global_config is None:
 
234
            self._global_config = GlobalConfig()
 
235
        return self._global_config
 
236
 
 
237
    def _get_section(self):
 
238
        """Get the section we should look in for config items.
 
239
 
 
240
        Returns None if none exists. 
 
241
        TODO: perhaps return a NullSection that thunks through to the 
 
242
              global config.
 
243
        """
 
244
        sections = self._get_parser()
 
245
        location_names = self.location.split('/')
 
246
        if self.location.endswith('/'):
 
247
            del location_names[-1]
 
248
        matches=[]
 
249
        for section in sections:
 
250
            section_names = section.split('/')
 
251
            if section.endswith('/'):
 
252
                del section_names[-1]
 
253
            names = zip(location_names, section_names)
 
254
            matched = True
 
255
            for name in names:
 
256
                if not fnmatch(name[0], name[1]):
 
257
                    matched = False
 
258
                    break
 
259
            if not matched:
 
260
                continue
 
261
            # so, for the common prefix they matched.
 
262
            # if section is longer, no match.
 
263
            if len(section_names) > len(location_names):
 
264
                continue
 
265
            # if path is longer, and recurse is not true, no match
 
266
            if len(section_names) < len(location_names):
 
267
                try:
 
268
                    if not self._config_bool('recurse', section=section):
 
269
                        continue
 
270
                except KeyError:
 
271
                    pass
 
272
            matches.append((len(section_names), section))
 
273
        if not len(matches):
 
274
            return None
 
275
        matches.sort(reverse=True)
 
276
        return matches[0][1]
 
277
 
 
278
    def _get_user_id(self):
 
279
        user_id = super(LocationConfig, self)._get_user_id()
 
280
        if user_id is not None:
 
281
            return user_id
 
282
        return self._get_global_config()._get_user_id()
 
283
 
 
284
    def _get_signature_checking(self):
 
285
        """See Config._get_signature_checking."""
 
286
        check = super(LocationConfig, self)._get_signature_checking()
 
287
        if check is not None:
 
288
            return check
 
289
        return self._get_global_config()._get_signature_checking()
 
290
 
 
291
 
 
292
class BranchConfig(Config):
 
293
    """A configuration object giving the policy for a branch."""
 
294
 
 
295
    def _get_location_config(self):
 
296
        if self._location_config is None:
 
297
            self._location_config = LocationConfig(self.branch.base)
 
298
        return self._location_config
 
299
 
 
300
    def _get_user_id(self):
 
301
        """Return the full user id for the branch.
 
302
    
 
303
        e.g. "John Hacker <jhacker@foo.org>"
 
304
        This is looked up in the email controlfile for the branch.
 
305
        """
 
306
        try:
 
307
            return (self.branch.controlfile("email", "r") 
 
308
                    .read()
 
309
                    .decode(bzrlib.user_encoding)
 
310
                    .rstrip("\r\n"))
 
311
        except errors.NoSuchFile, e:
 
312
            pass
 
313
        
 
314
        return self._get_location_config()._get_user_id()
 
315
 
 
316
    def _get_signature_checking(self):
 
317
        """See Config._get_signature_checking."""
 
318
        return self._get_location_config()._get_signature_checking()
 
319
 
 
320
    def __init__(self, branch):
 
321
        super(BranchConfig, self).__init__()
 
322
        self._location_config = None
 
323
        self.branch = branch
 
324
 
 
325
 
 
326
def config_dir():
 
327
    """Return per-user configuration directory.
 
328
 
 
329
    By default this is ~/.bazaar/
 
330
    
 
331
    TODO: Global option --config-dir to override this.
 
332
    """
 
333
    return os.path.join(os.path.expanduser("~"), ".bazaar")
 
334
 
 
335
 
 
336
def config_filename():
 
337
    """Return per-user configuration ini file filename."""
 
338
    return os.path.join(config_dir(), 'bazaar.conf')
 
339
 
 
340
 
 
341
def branches_config_filename():
 
342
    """Return per-user configuration ini file filename."""
 
343
    return os.path.join(config_dir(), 'branches.conf')
 
344
 
 
345
 
 
346
def _auto_user_id():
 
347
    """Calculate automatic user identification.
 
348
 
 
349
    Returns (realname, email).
 
350
 
 
351
    Only used when none is set in the environment or the id file.
 
352
 
 
353
    This previously used the FQDN as the default domain, but that can
 
354
    be very slow on machines where DNS is broken.  So now we simply
 
355
    use the hostname.
 
356
    """
 
357
    import socket
 
358
 
 
359
    # XXX: Any good way to get real user name on win32?
 
360
 
 
361
    try:
 
362
        import pwd
 
363
        uid = os.getuid()
 
364
        w = pwd.getpwuid(uid)
 
365
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
366
        username = w.pw_name.decode(bzrlib.user_encoding)
 
367
        comma = gecos.find(',')
 
368
        if comma == -1:
 
369
            realname = gecos
 
370
        else:
 
371
            realname = gecos[:comma]
 
372
        if not realname:
 
373
            realname = username
 
374
 
 
375
    except ImportError:
 
376
        import getpass
 
377
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
378
 
 
379
    return realname, (username + '@' + socket.gethostname())
 
380
 
 
381
 
 
382
def extract_email_address(e):
 
383
    """Return just the address part of an email string.
 
384
    
 
385
    That is just the user@domain part, nothing else. 
 
386
    This part is required to contain only ascii characters.
 
387
    If it can't be extracted, raises an error.
 
388
    
 
389
    >>> extract_email_address('Jane Tester <jane@test.com>')
 
390
    "jane@test.com"
 
391
    """
 
392
    m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
393
    if not m:
 
394
        raise BzrError("%r doesn't seem to contain "
 
395
                       "a reasonable email address" % e)
 
396
    return m.group(0)