~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 21:57:32 UTC
  • mto: This revision was merged to the branch mainline in revision 1462.
  • Revision ID: robertc@robertcollins.net-20051017215732-08f487800e726748
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
 
"""Configuration that affects the behaviour of Bazaar."""
 
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
"""
19
53
 
20
54
from ConfigParser import ConfigParser
21
55
import os
 
56
from fnmatch import fnmatch
22
57
import errno
23
58
import re
24
59
 
25
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
26
326
 
27
327
 
28
328
def config_dir():
40
340
    return os.path.join(config_dir(), 'bazaar.conf')
41
341
 
42
342
 
43
 
def _get_config_parser(file=None):
44
 
    parser = ConfigParser()
45
 
    if file is not None:
46
 
        parser.readfp(file)
47
 
    else:
48
 
        parser.read([config_filename()])
49
 
    return parser
50
 
 
51
 
 
52
 
def get_editor(parser=None):
53
 
    if parser is None:
54
 
        parser = _get_config_parser()
55
 
    if parser.has_option('DEFAULT', 'editor'):
56
 
        return parser.get('DEFAULT', 'editor')
57
 
 
58
 
 
59
 
def _get_user_id(branch=None, parser = None):
60
 
    """Return the full user id from a file or environment variable.
61
 
 
62
 
    e.g. "John Hacker <jhacker@foo.org>"
63
 
 
64
 
    branch
65
 
        A branch to use for a per-branch configuration, or None.
66
 
 
67
 
    The following are searched in order:
68
 
 
69
 
    1. $BZREMAIL
70
 
    2. .bzr/email for this branch.
71
 
    3. ~/.bzr.conf/email
72
 
    4. $EMAIL
73
 
    """
74
 
    v = os.environ.get('BZREMAIL')
75
 
    if v:
76
 
        return v.decode(bzrlib.user_encoding)
77
 
 
78
 
    if branch:
79
 
        try:
80
 
            return (branch.controlfile("email", "r") 
81
 
                    .read()
82
 
                    .decode(bzrlib.user_encoding)
83
 
                    .rstrip("\r\n"))
84
 
        except IOError, e:
85
 
            if e.errno != errno.ENOENT:
86
 
                raise
87
 
        except BzrError, e:
88
 
            pass
89
 
    
90
 
    if parser is None:
91
 
        parser = _get_config_parser()
92
 
    if parser.has_option('DEFAULT', 'email'):
93
 
        email = parser.get('DEFAULT', 'email')
94
 
        if email is not None:
95
 
            return email
96
 
 
97
 
    v = os.environ.get('EMAIL')
98
 
    if v:
99
 
        return v.decode(bzrlib.user_encoding)
100
 
    else:    
101
 
        return None
 
343
def branches_config_filename():
 
344
    """Return per-user configuration ini file filename."""
 
345
    return os.path.join(config_dir(), 'branches.conf')
102
346
 
103
347
 
104
348
def _auto_user_id():
137
381
    return realname, (username + '@' + socket.gethostname())
138
382
 
139
383
 
140
 
def username(branch):
141
 
    """Return email-style username.
142
 
 
143
 
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
144
 
 
145
 
    TODO: Check it's reasonably well-formed.
 
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"
146
393
    """
147
 
    v = _get_user_id(branch)
148
 
    if v:
149
 
        return v
150
 
    
151
 
    name, email = _auto_user_id()
152
 
    if name:
153
 
        return '%s <%s>' % (name, email)
154
 
    else:
155
 
        return email
156
 
 
157
 
 
158
 
def user_email(branch):
159
 
    """Return just the email component of a username."""
160
 
    e = _get_user_id(branch)
161
 
    if e:
162
 
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
163
 
        if not m:
164
 
            raise BzrError("%r doesn't seem to contain "
165
 
                           "a reasonable email address" % e)
166
 
        return m.group(0)
167
 
 
168
 
    return _auto_user_id()[1]
169
 
 
170
 
 
 
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)