/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Robert Collins
  • Date: 2005-10-20 02:52:44 UTC
  • Revision ID: robertc@robertcollins.net-20051020025244-fa1017d19a0ef618
post commit hook, first pass implementation

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.conf 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 _get_user_option(self, option_name):
 
80
        """Template method to provide a user option."""
 
81
        return None
 
82
 
 
83
    def get_user_option(self, option_name):
 
84
        """Get a generic option - no special process, no default."""
 
85
        return self._get_user_option(option_name)
 
86
 
 
87
    def gpg_signing_command(self):
 
88
        """What program should be used to sign signatures?"""
 
89
        result = self._gpg_signing_command()
 
90
        if result is None:
 
91
            result = "gpg"
 
92
        return result
 
93
 
 
94
    def _gpg_signing_command(self):
 
95
        """See gpg_signing_command()."""
 
96
        return None
 
97
 
 
98
    def __init__(self):
 
99
        super(Config, self).__init__()
 
100
 
 
101
    def post_commit(self):
 
102
        """An ordered list of python functions to call.
 
103
 
 
104
        Each function takes branch, rev_id as parameters.
 
105
        """
 
106
        return self._post_commit()
 
107
 
 
108
    def _post_commit(self):
 
109
        """See Config.post_commit."""
 
110
        return None
 
111
 
 
112
    def user_email(self):
 
113
        """Return just the email component of a username."""
 
114
        e = self.username()
 
115
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
116
        if not m:
 
117
            raise BzrError("%r doesn't seem to contain "
 
118
                           "a reasonable email address" % e)
 
119
        return m.group(0)
 
120
 
 
121
    def username(self):
 
122
        """Return email-style username.
 
123
    
 
124
        Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
125
        
 
126
        $BZREMAIL can be set to override this, then
 
127
        the concrete policy type is checked, and finally
 
128
        $EMAIL is examinged.
 
129
        but if none is found, a reasonable default is (hopefully)
 
130
        created.
 
131
    
 
132
        TODO: Check it's reasonably well-formed.
 
133
        """
 
134
        v = os.environ.get('BZREMAIL')
 
135
        if v:
 
136
            return v.decode(bzrlib.user_encoding)
 
137
    
 
138
        v = self._get_user_id()
 
139
        if v:
 
140
            return v
 
141
        
 
142
        v = os.environ.get('EMAIL')
 
143
        if v:
 
144
            return v.decode(bzrlib.user_encoding)
 
145
 
 
146
        name, email = _auto_user_id()
 
147
        if name:
 
148
            return '%s <%s>' % (name, email)
 
149
        else:
 
150
            return email
 
151
 
 
152
    def signature_checking(self):
 
153
        """What is the current policy for signature checking?."""
 
154
        policy = self._get_signature_checking()
 
155
        if policy is not None:
 
156
            return policy
 
157
        return CHECK_IF_POSSIBLE
 
158
 
 
159
    def signature_needed(self):
 
160
        """Is a signature needed when committing ?."""
 
161
        policy = self._get_signature_checking()
 
162
        if policy == CHECK_ALWAYS:
 
163
            return True
 
164
        return False
 
165
 
 
166
 
 
167
class IniBasedConfig(Config):
 
168
    """A configuration policy that draws from ini files."""
 
169
 
 
170
    def _get_parser(self, file=None):
 
171
        if self._parser is not None:
 
172
            return self._parser
 
173
        parser = ConfigParser()
 
174
        if file is not None:
 
175
            parser.readfp(file)
 
176
        else:
 
177
            parser.read([self._get_filename()])
 
178
        self._parser = parser
 
179
        return parser
 
180
 
 
181
    def _get_section(self):
 
182
        """Override this to define the section used by the config."""
 
183
        return "DEFAULT"
 
184
 
 
185
    def _get_signature_checking(self):
 
186
        """See Config._get_signature_checking."""
 
187
        section = self._get_section()
 
188
        if section is None:
 
189
            return None
 
190
        if self._get_parser().has_option(section, 'check_signatures'):
 
191
            return self._string_to_signature_policy(
 
192
                self._get_parser().get(section, 'check_signatures'))
 
193
 
 
194
    def _get_user_id(self):
 
195
        """Get the user id from the 'email' key in the current section."""
 
196
        section = self._get_section()
 
197
        if section is not None:
 
198
            if self._get_parser().has_option(section, 'email'):
 
199
                return self._get_parser().get(section, 'email')
 
200
 
 
201
    def _get_user_option(self, option_name):
 
202
        """See Config._get_user_option."""
 
203
        section = self._get_section()
 
204
        if section is not None:
 
205
            if self._get_parser().has_option(section, option_name):
 
206
                return self._get_parser().get(section, option_name)
 
207
 
 
208
    def _gpg_signing_command(self):
 
209
        """See Config.gpg_signing_command."""
 
210
        return self._get_user_option('gpg_signing_command')
 
211
 
 
212
    def __init__(self, get_filename):
 
213
        super(IniBasedConfig, self).__init__()
 
214
        self._get_filename = get_filename
 
215
        self._parser = None
 
216
        
 
217
    def _post_commit(self):
 
218
        """See Config.post_commit."""
 
219
        return self._get_user_option('post_commit')
 
220
 
 
221
    def _string_to_signature_policy(self, signature_string):
 
222
        """Convert a string to a signing policy."""
 
223
        if signature_string.lower() == 'check-available':
 
224
            return CHECK_IF_POSSIBLE
 
225
        if signature_string.lower() == 'ignore':
 
226
            return CHECK_NEVER
 
227
        if signature_string.lower() == 'require':
 
228
            return CHECK_ALWAYS
 
229
        raise errors.BzrError("Invalid signatures policy '%s'"
 
230
                              % signature_string)
 
231
 
 
232
 
 
233
class GlobalConfig(IniBasedConfig):
 
234
    """The configuration that should be used for a specific location."""
 
235
 
 
236
    def get_editor(self):
 
237
        if self._get_parser().has_option(self._get_section(), 'editor'):
 
238
            return self._get_parser().get(self._get_section(), 'editor')
 
239
 
 
240
    def __init__(self):
 
241
        super(GlobalConfig, self).__init__(config_filename)
 
242
 
 
243
 
 
244
class LocationConfig(IniBasedConfig):
 
245
    """A configuration object that gives the policy for a location."""
 
246
 
 
247
    def __init__(self, location):
 
248
        super(LocationConfig, self).__init__(branches_config_filename)
 
249
        self._global_config = None
 
250
        self.location = location
 
251
 
 
252
    def _get_global_config(self):
 
253
        if self._global_config is None:
 
254
            self._global_config = GlobalConfig()
 
255
        return self._global_config
 
256
 
 
257
    def _get_section(self):
 
258
        """Get the section we should look in for config items.
 
259
 
 
260
        Returns None if none exists. 
 
261
        TODO: perhaps return a NullSection that thunks through to the 
 
262
              global config.
 
263
        """
 
264
        sections = self._get_parser().sections()
 
265
        location_names = self.location.split('/')
 
266
        if self.location.endswith('/'):
 
267
            del location_names[-1]
 
268
        matches=[]
 
269
        for section in sections:
 
270
            section_names = section.split('/')
 
271
            if section.endswith('/'):
 
272
                del section_names[-1]
 
273
            names = zip(location_names, section_names)
 
274
            matched = True
 
275
            for name in names:
 
276
                if not fnmatch(name[0], name[1]):
 
277
                    matched = False
 
278
                    break
 
279
            if not matched:
 
280
                continue
 
281
            # so, for the common prefix they matched.
 
282
            # if section is longer, no match.
 
283
            if len(section_names) > len(location_names):
 
284
                continue
 
285
            # if path is longer, and recurse is not true, no match
 
286
            if len(section_names) < len(location_names):
 
287
                if (self._get_parser().has_option(section, 'recurse')
 
288
                    and not self._get_parser().getboolean(section, 'recurse')):
 
289
                    continue
 
290
            matches.append((len(section_names), section))
 
291
        if not len(matches):
 
292
            return None
 
293
        matches.sort(reverse=True)
 
294
        return matches[0][1]
 
295
 
 
296
    def _gpg_signing_command(self):
 
297
        """See Config.gpg_signing_command."""
 
298
        command = super(LocationConfig, self)._gpg_signing_command()
 
299
        if command is not None:
 
300
            return command
 
301
        return self._get_global_config()._gpg_signing_command()
 
302
 
 
303
    def _get_user_id(self):
 
304
        user_id = super(LocationConfig, self)._get_user_id()
 
305
        if user_id is not None:
 
306
            return user_id
 
307
        return self._get_global_config()._get_user_id()
 
308
 
 
309
    def _get_user_option(self, option_name):
 
310
        """See Config._get_user_option."""
 
311
        option_value = super(LocationConfig, 
 
312
                             self)._get_user_option(option_name)
 
313
        if option_value is not None:
 
314
            return option_value
 
315
        return self._get_global_config()._get_user_option(option_name)
 
316
 
 
317
    def _get_signature_checking(self):
 
318
        """See Config._get_signature_checking."""
 
319
        check = super(LocationConfig, self)._get_signature_checking()
 
320
        if check is not None:
 
321
            return check
 
322
        return self._get_global_config()._get_signature_checking()
 
323
 
 
324
    def _post_commit(self):
 
325
        """See Config.post_commit."""
 
326
        hook = self._get_user_option('post_commit')
 
327
        if hook is not None:
 
328
            return hook
 
329
        return self._get_global_config()._post_commit()
 
330
 
 
331
 
 
332
class BranchConfig(Config):
 
333
    """A configuration object giving the policy for a branch."""
 
334
 
 
335
    def _get_location_config(self):
 
336
        if self._location_config is None:
 
337
            self._location_config = LocationConfig(self.branch.base)
 
338
        return self._location_config
 
339
 
 
340
    def _get_user_id(self):
 
341
        """Return the full user id for the branch.
 
342
    
 
343
        e.g. "John Hacker <jhacker@foo.org>"
 
344
        This is looked up in the email controlfile for the branch.
 
345
        """
 
346
        try:
 
347
            return (self.branch.controlfile("email", "r") 
 
348
                    .read()
 
349
                    .decode(bzrlib.user_encoding)
 
350
                    .rstrip("\r\n"))
 
351
        except errors.NoSuchFile, e:
 
352
            pass
 
353
        
 
354
        return self._get_location_config()._get_user_id()
 
355
 
 
356
    def _get_signature_checking(self):
 
357
        """See Config._get_signature_checking."""
 
358
        return self._get_location_config()._get_signature_checking()
 
359
 
 
360
    def _get_user_option(self, option_name):
 
361
        """See Config._get_user_option."""
 
362
        return self._get_location_config()._get_user_option(option_name)
 
363
 
 
364
    def _gpg_signing_command(self):
 
365
        """See Config.gpg_signing_command."""
 
366
        return self._get_location_config()._gpg_signing_command()
 
367
        
 
368
    def __init__(self, branch):
 
369
        super(BranchConfig, self).__init__()
 
370
        self._location_config = None
 
371
        self.branch = branch
 
372
 
 
373
    def _post_commit(self):
 
374
        """See Config.post_commit."""
 
375
        return self._get_location_config()._post_commit()
 
376
 
 
377
 
 
378
def config_dir():
 
379
    """Return per-user configuration directory.
 
380
 
 
381
    By default this is ~/.bazaar/
 
382
    
 
383
    TODO: Global option --config-dir to override this.
 
384
    """
 
385
    return os.path.join(os.path.expanduser("~"), ".bazaar")
 
386
 
 
387
 
 
388
def config_filename():
 
389
    """Return per-user configuration ini file filename."""
 
390
    return os.path.join(config_dir(), 'bazaar.conf')
 
391
 
 
392
 
 
393
def branches_config_filename():
 
394
    """Return per-user configuration ini file filename."""
 
395
    return os.path.join(config_dir(), 'branches.conf')
 
396
 
 
397
 
 
398
def _auto_user_id():
 
399
    """Calculate automatic user identification.
 
400
 
 
401
    Returns (realname, email).
 
402
 
 
403
    Only used when none is set in the environment or the id file.
 
404
 
 
405
    This previously used the FQDN as the default domain, but that can
 
406
    be very slow on machines where DNS is broken.  So now we simply
 
407
    use the hostname.
 
408
    """
 
409
    import socket
 
410
 
 
411
    # XXX: Any good way to get real user name on win32?
 
412
 
 
413
    try:
 
414
        import pwd
 
415
        uid = os.getuid()
 
416
        w = pwd.getpwuid(uid)
 
417
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
418
        username = w.pw_name.decode(bzrlib.user_encoding)
 
419
        comma = gecos.find(',')
 
420
        if comma == -1:
 
421
            realname = gecos
 
422
        else:
 
423
            realname = gecos[:comma]
 
424
        if not realname:
 
425
            realname = username
 
426
 
 
427
    except ImportError:
 
428
        import getpass
 
429
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
430
 
 
431
    return realname, (username + '@' + socket.gethostname())
 
432
 
 
433
 
 
434
def extract_email_address(e):
 
435
    """Return just the address part of an email string.
 
436
    
 
437
    That is just the user@domain part, nothing else. 
 
438
    This part is required to contain only ascii characters.
 
439
    If it can't be extracted, raises an error.
 
440
    
 
441
    >>> extract_email_address('Jane Tester <jane@test.com>')
 
442
    "jane@test.com"
 
443
    """
 
444
    m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
445
    if not m:
 
446
        raise BzrError("%r doesn't seem to contain "
 
447
                       "a reasonable email address" % e)
 
448
    return m.group(0)