~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Robert Collins
  • Date: 2005-10-17 11:56:54 UTC
  • mfrom: (1185.16.59)
  • Revision ID: robertc@robertcollins.net-20051017115654-662239e1587524a8
mergeĀ fromĀ martin.

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