~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Jelmer Vernooij
  • Date: 2009-02-03 15:48:02 UTC
  • mto: (3978.3.6 branch-bzrdir-inter)
  • mto: This revision was merged to the branch mainline in revision 4250.
  • Revision ID: jelmer@samba.org-20090203154802-niup4xrso35yba65
Move most of push to IterGenericBranchBzrDir.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2007, 2008 Canonical Ltd
2
2
#   Authors: Robert Collins <robert.collins@canonical.com>
 
3
#            and others
3
4
#
4
5
# This program is free software; you can redistribute it and/or modify
5
6
# it under the terms of the GNU General Public License as published by
69
70
import errno
70
71
from fnmatch import fnmatch
71
72
import re
72
 
from StringIO import StringIO
 
73
from cStringIO import StringIO
73
74
 
74
75
import bzrlib
75
76
from bzrlib import (
 
77
    debug,
76
78
    errors,
 
79
    mail_client,
77
80
    osutils,
 
81
    registry,
 
82
    symbol_versioning,
 
83
    trace,
 
84
    ui,
78
85
    urlutils,
 
86
    win32utils,
79
87
    )
80
 
import bzrlib.util.configobj.configobj as configobj
 
88
from bzrlib.util.configobj import configobj
81
89
""")
82
90
 
83
 
from bzrlib.trace import mutter, warning
84
 
 
85
91
 
86
92
CHECK_IF_POSSIBLE=0
87
93
CHECK_ALWAYS=1
93
99
SIGN_NEVER=2
94
100
 
95
101
 
96
 
class ConfigObj(configobj.ConfigObj):
97
 
 
98
 
    def get_bool(self, section, key):
99
 
        return self[section].as_bool(key)
100
 
 
101
 
    def get_value(self, section, name):
102
 
        # Try [] for the old DEFAULT section.
103
 
        if section == "DEFAULT":
104
 
            try:
105
 
                return self[name]
106
 
            except KeyError:
107
 
                pass
108
 
        return self[section][name]
 
102
POLICY_NONE = 0
 
103
POLICY_NORECURSE = 1
 
104
POLICY_APPENDPATH = 2
 
105
 
 
106
_policy_name = {
 
107
    POLICY_NONE: None,
 
108
    POLICY_NORECURSE: 'norecurse',
 
109
    POLICY_APPENDPATH: 'appendpath',
 
110
    }
 
111
_policy_value = {
 
112
    None: POLICY_NONE,
 
113
    'none': POLICY_NONE,
 
114
    'norecurse': POLICY_NORECURSE,
 
115
    'appendpath': POLICY_APPENDPATH,
 
116
    }
 
117
 
 
118
 
 
119
STORE_LOCATION = POLICY_NONE
 
120
STORE_LOCATION_NORECURSE = POLICY_NORECURSE
 
121
STORE_LOCATION_APPENDPATH = POLICY_APPENDPATH
 
122
STORE_BRANCH = 3
 
123
STORE_GLOBAL = 4
 
124
 
 
125
_ConfigObj = None
 
126
def ConfigObj(*args, **kwargs):
 
127
    global _ConfigObj
 
128
    if _ConfigObj is None:
 
129
        class ConfigObj(configobj.ConfigObj):
 
130
 
 
131
            def get_bool(self, section, key):
 
132
                return self[section].as_bool(key)
 
133
 
 
134
            def get_value(self, section, name):
 
135
                # Try [] for the old DEFAULT section.
 
136
                if section == "DEFAULT":
 
137
                    try:
 
138
                        return self[name]
 
139
                    except KeyError:
 
140
                        pass
 
141
                return self[section][name]
 
142
        _ConfigObj = ConfigObj
 
143
    return _ConfigObj(*args, **kwargs)
109
144
 
110
145
 
111
146
class Config(object):
115
150
        """Get the users pop up editor."""
116
151
        raise NotImplementedError
117
152
 
 
153
    def get_mail_client(self):
 
154
        """Get a mail client to use"""
 
155
        selected_client = self.get_user_option('mail_client')
 
156
        _registry = mail_client.mail_client_registry
 
157
        try:
 
158
            mail_client_class = _registry.get(selected_client)
 
159
        except KeyError:
 
160
            raise errors.UnknownMailClient(selected_client)
 
161
        return mail_client_class(self)
 
162
 
118
163
    def _get_signature_checking(self):
119
164
        """Template method to override signature checking policy."""
120
165
 
185
230
        """
186
231
        v = os.environ.get('BZR_EMAIL')
187
232
        if v:
188
 
            return v.decode(bzrlib.user_encoding)
189
 
        v = os.environ.get('BZREMAIL')
190
 
        if v:
191
 
            warning('BZREMAIL is deprecated in favor of BZR_EMAIL. Please update your configuration.')
192
 
            return v.decode(bzrlib.user_encoding)
193
 
    
 
233
            return v.decode(osutils.get_user_encoding())
 
234
 
194
235
        v = self._get_user_id()
195
236
        if v:
196
237
            return v
197
 
        
 
238
 
198
239
        v = os.environ.get('EMAIL')
199
240
        if v:
200
 
            return v.decode(bzrlib.user_encoding)
 
241
            return v.decode(osutils.get_user_encoding())
201
242
 
202
243
        name, email = _auto_user_id()
203
244
        if name:
225
266
        if policy is None:
226
267
            policy = self._get_signature_checking()
227
268
            if policy is not None:
228
 
                warning("Please use create_signatures, not check_signatures "
229
 
                        "to set signing policy.")
 
269
                trace.warning("Please use create_signatures,"
 
270
                              " not check_signatures to set signing policy.")
230
271
            if policy == CHECK_ALWAYS:
231
272
                return True
232
273
        elif policy == SIGN_ALWAYS:
245
286
    def _get_nickname(self):
246
287
        return None
247
288
 
 
289
    def get_bzr_remote_path(self):
 
290
        try:
 
291
            return os.environ['BZR_REMOTE_PATH']
 
292
        except KeyError:
 
293
            path = self.get_user_option("bzr_remote_path")
 
294
            if path is None:
 
295
                path = 'bzr'
 
296
            return path
 
297
 
248
298
 
249
299
class IniBasedConfig(Config):
250
300
    """A configuration policy that draws from ini files."""
278
328
        """Override this to define the section used by the config."""
279
329
        return "DEFAULT"
280
330
 
 
331
    def _get_option_policy(self, section, option_name):
 
332
        """Return the policy for the given (section, option_name) pair."""
 
333
        return POLICY_NONE
 
334
 
281
335
    def _get_signature_checking(self):
282
336
        """See Config._get_signature_checking."""
283
337
        policy = self._get_user_option('check_signatures')
298
352
        """See Config._get_user_option."""
299
353
        for (section, extra_path) in self._get_matching_sections():
300
354
            try:
301
 
                return self._get_parser().get_value(section, option_name)
 
355
                value = self._get_parser().get_value(section, option_name)
302
356
            except KeyError:
303
 
                pass
 
357
                continue
 
358
            policy = self._get_option_policy(section, option_name)
 
359
            if policy == POLICY_NONE:
 
360
                return value
 
361
            elif policy == POLICY_NORECURSE:
 
362
                # norecurse items only apply to the exact path
 
363
                if extra_path:
 
364
                    continue
 
365
                else:
 
366
                    return value
 
367
            elif policy == POLICY_APPENDPATH:
 
368
                if extra_path:
 
369
                    value = urlutils.join(value, extra_path)
 
370
                return value
 
371
            else:
 
372
                raise AssertionError('Unexpected config policy %r' % policy)
304
373
        else:
305
374
            return None
306
375
 
365
434
 
366
435
    def set_user_option(self, option, value):
367
436
        """Save option and its value in the configuration."""
 
437
        self._set_option(option, value, 'DEFAULT')
 
438
 
 
439
    def get_aliases(self):
 
440
        """Return the aliases section."""
 
441
        if 'ALIASES' in self._get_parser():
 
442
            return self._get_parser()['ALIASES']
 
443
        else:
 
444
            return {}
 
445
 
 
446
    def set_alias(self, alias_name, alias_command):
 
447
        """Save the alias in the configuration."""
 
448
        self._set_option(alias_name, alias_command, 'ALIASES')
 
449
 
 
450
    def unset_alias(self, alias_name):
 
451
        """Unset an existing alias."""
 
452
        aliases = self._get_parser().get('ALIASES')
 
453
        if not aliases or alias_name not in aliases:
 
454
            raise errors.NoSuchAlias(alias_name)
 
455
        del aliases[alias_name]
 
456
        self._write_config_file()
 
457
 
 
458
    def _set_option(self, option, value, section):
368
459
        # FIXME: RBC 20051029 This should refresh the parser and also take a
369
460
        # file lock on bazaar.conf.
370
461
        conf_dir = os.path.dirname(self._get_filename())
371
462
        ensure_config_dir_exists(conf_dir)
372
 
        if 'DEFAULT' not in self._get_parser():
373
 
            self._get_parser()['DEFAULT'] = {}
374
 
        self._get_parser()['DEFAULT'][option] = value
 
463
        self._get_parser().setdefault(section, {})[option] = value
 
464
        self._write_config_file()
 
465
 
 
466
    def _write_config_file(self):
375
467
        f = open(self._get_filename(), 'wb')
376
468
        self._get_parser().write(f)
377
469
        f.close()
382
474
 
383
475
    def __init__(self, location):
384
476
        name_generator = locations_config_filename
385
 
        if (not os.path.exists(name_generator()) and 
 
477
        if (not os.path.exists(name_generator()) and
386
478
                os.path.exists(branches_config_filename())):
387
479
            if sys.platform == 'win32':
388
 
                warning('Please rename %s to %s' 
389
 
                         % (branches_config_filename(),
390
 
                            locations_config_filename()))
 
480
                trace.warning('Please rename %s to %s'
 
481
                              % (branches_config_filename(),
 
482
                                 locations_config_filename()))
391
483
            else:
392
 
                warning('Please rename ~/.bazaar/branches.conf'
393
 
                        ' to ~/.bazaar/locations.conf')
 
484
                trace.warning('Please rename ~/.bazaar/branches.conf'
 
485
                              ' to ~/.bazaar/locations.conf')
394
486
            name_generator = branches_config_filename
395
487
        super(LocationConfig, self).__init__(name_generator)
396
488
        # local file locations are looked up by local path, rather than
431
523
            # if section is longer, no match.
432
524
            if len(section_names) > len(location_names):
433
525
                continue
434
 
            # if path is longer, and recurse is not true, no match
435
 
            if len(section_names) < len(location_names):
436
 
                try:
437
 
                    if not self._get_parser()[section].as_bool('recurse'):
438
 
                        continue
439
 
                except KeyError:
440
 
                    pass
441
526
            matches.append((len(section_names), section,
442
527
                            '/'.join(location_names[len(section_names):])))
443
528
        matches.sort(reverse=True)
452
537
                pass
453
538
        return sections
454
539
 
455
 
    def set_user_option(self, option, value):
 
540
    def _get_option_policy(self, section, option_name):
 
541
        """Return the policy for the given (section, option_name) pair."""
 
542
        # check for the old 'recurse=False' flag
 
543
        try:
 
544
            recurse = self._get_parser()[section].as_bool('recurse')
 
545
        except KeyError:
 
546
            recurse = True
 
547
        if not recurse:
 
548
            return POLICY_NORECURSE
 
549
 
 
550
        policy_key = option_name + ':policy'
 
551
        try:
 
552
            policy_name = self._get_parser()[section][policy_key]
 
553
        except KeyError:
 
554
            policy_name = None
 
555
 
 
556
        return _policy_value[policy_name]
 
557
 
 
558
    def _set_option_policy(self, section, option_name, option_policy):
 
559
        """Set the policy for the given option name in the given section."""
 
560
        # The old recurse=False option affects all options in the
 
561
        # section.  To handle multiple policies in the section, we
 
562
        # need to convert it to a policy_norecurse key.
 
563
        try:
 
564
            recurse = self._get_parser()[section].as_bool('recurse')
 
565
        except KeyError:
 
566
            pass
 
567
        else:
 
568
            symbol_versioning.warn(
 
569
                'The recurse option is deprecated as of 0.14.  '
 
570
                'The section "%s" has been converted to use policies.'
 
571
                % section,
 
572
                DeprecationWarning)
 
573
            del self._get_parser()[section]['recurse']
 
574
            if not recurse:
 
575
                for key in self._get_parser()[section].keys():
 
576
                    if not key.endswith(':policy'):
 
577
                        self._get_parser()[section][key +
 
578
                                                    ':policy'] = 'norecurse'
 
579
 
 
580
        policy_key = option_name + ':policy'
 
581
        policy_name = _policy_name[option_policy]
 
582
        if policy_name is not None:
 
583
            self._get_parser()[section][policy_key] = policy_name
 
584
        else:
 
585
            if policy_key in self._get_parser()[section]:
 
586
                del self._get_parser()[section][policy_key]
 
587
 
 
588
    def set_user_option(self, option, value, store=STORE_LOCATION):
456
589
        """Save option and its value in the configuration."""
 
590
        if store not in [STORE_LOCATION,
 
591
                         STORE_LOCATION_NORECURSE,
 
592
                         STORE_LOCATION_APPENDPATH]:
 
593
            raise ValueError('bad storage policy %r for %r' %
 
594
                (store, option))
457
595
        # FIXME: RBC 20051029 This should refresh the parser and also take a
458
596
        # file lock on locations.conf.
459
597
        conf_dir = os.path.dirname(self._get_filename())
467
605
        elif location + '/' in self._get_parser():
468
606
            location = location + '/'
469
607
        self._get_parser()[location][option]=value
 
608
        # the allowed values of store match the config policies
 
609
        self._set_option_policy(location, option, store)
470
610
        self._get_parser().write(file(self._get_filename(), 'wb'))
471
611
 
472
612
 
518
658
    def _get_user_id(self):
519
659
        """Return the full user id for the branch.
520
660
    
521
 
        e.g. "John Hacker <jhacker@foo.org>"
 
661
        e.g. "John Hacker <jhacker@example.com>"
522
662
        This is looked up in the email controlfile for the branch.
523
663
        """
524
664
        try:
525
 
            return (self.branch.control_files.get_utf8("email") 
526
 
                    .read()
527
 
                    .decode(bzrlib.user_encoding)
 
665
            return (self.branch._transport.get_bytes("email")
 
666
                    .decode(osutils.get_user_encoding())
528
667
                    .rstrip("\r\n"))
529
668
        except errors.NoSuchFile, e:
530
669
            pass
547
686
                return value
548
687
        return None
549
688
 
550
 
    def set_user_option(self, name, value, local=False):
551
 
        if local is True:
552
 
            self._get_location_config().set_user_option(name, value)
553
 
        else:
 
689
    def set_user_option(self, name, value, store=STORE_BRANCH,
 
690
        warn_masked=False):
 
691
        if store == STORE_BRANCH:
554
692
            self._get_branch_data_config().set_option(value, name)
 
693
        elif store == STORE_GLOBAL:
 
694
            self._get_global_config().set_user_option(name, value)
 
695
        else:
 
696
            self._get_location_config().set_user_option(name, value, store)
 
697
        if not warn_masked:
 
698
            return
 
699
        if store in (STORE_GLOBAL, STORE_BRANCH):
 
700
            mask_value = self._get_location_config().get_user_option(name)
 
701
            if mask_value is not None:
 
702
                trace.warning('Value "%s" is masked by "%s" from'
 
703
                              ' locations.conf', value, mask_value)
 
704
            else:
 
705
                if store == STORE_GLOBAL:
 
706
                    branch_config = self._get_branch_data_config()
 
707
                    mask_value = branch_config.get_user_option(name)
 
708
                    if mask_value is not None:
 
709
                        trace.warning('Value "%s" is masked by "%s" from'
 
710
                                      ' branch.conf', value, mask_value)
555
711
 
556
712
 
557
713
    def _gpg_signing_command(self):
576
732
        value = self._get_explicit_nickname()
577
733
        if value is not None:
578
734
            return value
579
 
        return self.branch.base.split('/')[-2]
 
735
        return urlutils.unescape(self.branch.base.split('/')[-2])
580
736
 
581
737
    def has_explicit_nickname(self):
582
738
        """Return true if a nickname has been explicitly assigned."""
602
758
        if sys.platform == 'win32':
603
759
            parent_dir = os.path.dirname(path)
604
760
            if not os.path.isdir(parent_dir):
605
 
                mutter('creating config parent directory: %r', parent_dir)
 
761
                trace.mutter('creating config parent directory: %r', parent_dir)
606
762
            os.mkdir(parent_dir)
607
 
        mutter('creating config directory: %r', path)
 
763
        trace.mutter('creating config directory: %r', path)
608
764
        os.mkdir(path)
609
765
 
610
766
 
618
774
    base = os.environ.get('BZR_HOME', None)
619
775
    if sys.platform == 'win32':
620
776
        if base is None:
621
 
            base = os.environ.get('APPDATA', None)
 
777
            base = win32utils.get_appdata_location_unicode()
622
778
        if base is None:
623
779
            base = os.environ.get('HOME', None)
624
780
        if base is None:
625
 
            raise errors.BzrError('You must have one of BZR_HOME, APPDATA, or HOME set')
 
781
            raise errors.BzrError('You must have one of BZR_HOME, APPDATA,'
 
782
                                  ' or HOME set')
626
783
        return osutils.pathjoin(base, 'bazaar', '2.0')
627
784
    else:
628
785
        # cygwin, linux, and darwin all have a $HOME directory
646
803
    return osutils.pathjoin(config_dir(), 'locations.conf')
647
804
 
648
805
 
 
806
def authentication_config_filename():
 
807
    """Return per-user authentication ini file filename."""
 
808
    return osutils.pathjoin(config_dir(), 'authentication.conf')
 
809
 
 
810
 
649
811
def user_ignore_config_filename():
650
812
    """Return the user default ignore filename"""
651
813
    return osutils.pathjoin(config_dir(), 'ignore')
664
826
    """
665
827
    import socket
666
828
 
667
 
    # XXX: Any good way to get real user name on win32?
 
829
    if sys.platform == 'win32':
 
830
        name = win32utils.get_user_name_unicode()
 
831
        if name is None:
 
832
            raise errors.BzrError("Cannot autodetect user name.\n"
 
833
                                  "Please, set your name with command like:\n"
 
834
                                  'bzr whoami "Your Name <name@domain.com>"')
 
835
        host = win32utils.get_host_name_unicode()
 
836
        if host is None:
 
837
            host = socket.gethostname()
 
838
        return name, (name + '@' + host)
668
839
 
669
840
    try:
670
841
        import pwd
671
842
        uid = os.getuid()
672
 
        w = pwd.getpwuid(uid)
 
843
        try:
 
844
            w = pwd.getpwuid(uid)
 
845
        except KeyError:
 
846
            raise errors.BzrCommandError('Unable to determine your name.  '
 
847
                'Please use "bzr whoami" to set it.')
673
848
 
674
849
        # we try utf-8 first, because on many variants (like Linux),
675
850
        # /etc/passwd "should" be in utf-8, and because it's unlikely to give
680
855
            encoding = 'utf-8'
681
856
        except UnicodeError:
682
857
            try:
683
 
                gecos = w.pw_gecos.decode(bzrlib.user_encoding)
684
 
                encoding = bzrlib.user_encoding
 
858
                encoding = osutils.get_user_encoding()
 
859
                gecos = w.pw_gecos.decode(encoding)
685
860
            except UnicodeError:
686
861
                raise errors.BzrCommandError('Unable to determine your name.  '
687
862
                   'Use "bzr whoami" to set it.')
702
877
    except ImportError:
703
878
        import getpass
704
879
        try:
705
 
            realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
880
            user_encoding = osutils.get_user_encoding()
 
881
            realname = username = getpass.getuser().decode(user_encoding)
706
882
        except UnicodeDecodeError:
707
883
            raise errors.BzrError("Can't decode username as %s." % \
708
 
                    bzrlib.user_encoding)
 
884
                    user_encoding)
709
885
 
710
886
    return realname, (username + '@' + socket.gethostname())
711
887
 
712
888
 
 
889
def parse_username(username):
 
890
    """Parse e-mail username and return a (name, address) tuple."""
 
891
    match = re.match(r'(.*?)\s*<?([\w+.-]+@[\w+.-]+)>?', username)
 
892
    if match is None:
 
893
        return (username, '')
 
894
    else:
 
895
        return (match.group(1), match.group(2))
 
896
 
 
897
 
713
898
def extract_email_address(e):
714
899
    """Return just the address part of an email string.
715
 
    
 
900
 
716
901
    That is just the user@domain part, nothing else. 
717
902
    This part is required to contain only ascii characters.
718
903
    If it can't be extracted, raises an error.
719
 
    
 
904
 
720
905
    >>> extract_email_address('Jane Tester <jane@test.com>')
721
906
    "jane@test.com"
722
907
    """
723
 
    m = re.search(r'[\w+.-]+@[\w+.-]+', e)
724
 
    if not m:
 
908
    name, email = parse_username(e)
 
909
    if not email:
725
910
        raise errors.NoEmailInUsername(e)
726
 
    return m.group(0)
 
911
    return email
727
912
 
728
913
 
729
914
class TreeConfig(IniBasedConfig):
730
915
    """Branch configuration data associated with its contents, not location"""
 
916
 
 
917
    # XXX: Really needs a better name, as this is not part of the tree! -- mbp 20080507
 
918
 
731
919
    def __init__(self, branch):
 
920
        # XXX: Really this should be asking the branch for its configuration
 
921
        # data, rather than relying on a Transport, so that it can work 
 
922
        # more cleanly with a RemoteBranch that has no transport.
 
923
        self._config = TransportConfig(branch._transport, 'branch.conf')
732
924
        self.branch = branch
733
925
 
734
926
    def _get_parser(self, file=None):
735
927
        if file is not None:
736
928
            return IniBasedConfig._get_parser(file)
737
 
        return self._get_config()
738
 
 
739
 
    def _get_config(self):
740
 
        try:
741
 
            obj = ConfigObj(self.branch.control_files.get('branch.conf'), 
742
 
                            encoding='utf-8')
743
 
        except errors.NoSuchFile:
744
 
            obj = ConfigObj(encoding='utf=8')
745
 
        return obj
 
929
        return self._config._get_configobj()
746
930
 
747
931
    def get_option(self, name, section=None, default=None):
748
932
        self.branch.lock_read()
749
933
        try:
750
 
            obj = self._get_config()
751
 
            try:
752
 
                if section is not None:
753
 
                    obj[section]
754
 
                result = obj[name]
755
 
            except KeyError:
756
 
                result = default
 
934
            return self._config.get_option(name, section, default)
757
935
        finally:
758
936
            self.branch.unlock()
759
937
        return result
762
940
        """Set a per-branch configuration option"""
763
941
        self.branch.lock_write()
764
942
        try:
765
 
            cfg_obj = self._get_config()
766
 
            if section is None:
767
 
                obj = cfg_obj
768
 
            else:
769
 
                try:
770
 
                    obj = cfg_obj[section]
771
 
                except KeyError:
772
 
                    cfg_obj[section] = {}
773
 
                    obj = cfg_obj[section]
774
 
            obj[name] = value
775
 
            out_file = StringIO()
776
 
            cfg_obj.write(out_file)
777
 
            out_file.seek(0)
778
 
            self.branch.control_files.put('branch.conf', out_file)
 
943
            self._config.set_option(value, name, section)
779
944
        finally:
780
945
            self.branch.unlock()
 
946
 
 
947
 
 
948
class AuthenticationConfig(object):
 
949
    """The authentication configuration file based on a ini file.
 
950
 
 
951
    Implements the authentication.conf file described in
 
952
    doc/developers/authentication-ring.txt.
 
953
    """
 
954
 
 
955
    def __init__(self, _file=None):
 
956
        self._config = None # The ConfigObj
 
957
        if _file is None:
 
958
            self._filename = authentication_config_filename()
 
959
            self._input = self._filename = authentication_config_filename()
 
960
        else:
 
961
            # Tests can provide a string as _file
 
962
            self._filename = None
 
963
            self._input = _file
 
964
 
 
965
    def _get_config(self):
 
966
        if self._config is not None:
 
967
            return self._config
 
968
        try:
 
969
            # FIXME: Should we validate something here ? Includes: empty
 
970
            # sections are useless, at least one of
 
971
            # user/password/password_encoding should be defined, etc.
 
972
 
 
973
            # Note: the encoding below declares that the file itself is utf-8
 
974
            # encoded, but the values in the ConfigObj are always Unicode.
 
975
            self._config = ConfigObj(self._input, encoding='utf-8')
 
976
        except configobj.ConfigObjError, e:
 
977
            raise errors.ParseConfigError(e.errors, e.config.filename)
 
978
        return self._config
 
979
 
 
980
    def _save(self):
 
981
        """Save the config file, only tests should use it for now."""
 
982
        conf_dir = os.path.dirname(self._filename)
 
983
        ensure_config_dir_exists(conf_dir)
 
984
        self._get_config().write(file(self._filename, 'wb'))
 
985
 
 
986
    def _set_option(self, section_name, option_name, value):
 
987
        """Set an authentication configuration option"""
 
988
        conf = self._get_config()
 
989
        section = conf.get(section_name)
 
990
        if section is None:
 
991
            conf[section] = {}
 
992
            section = conf[section]
 
993
        section[option_name] = value
 
994
        self._save()
 
995
 
 
996
    def get_credentials(self, scheme, host, port=None, user=None, path=None):
 
997
        """Returns the matching credentials from authentication.conf file.
 
998
 
 
999
        :param scheme: protocol
 
1000
 
 
1001
        :param host: the server address
 
1002
 
 
1003
        :param port: the associated port (optional)
 
1004
 
 
1005
        :param user: login (optional)
 
1006
 
 
1007
        :param path: the absolute path on the server (optional)
 
1008
 
 
1009
        :return: A dict containing the matching credentials or None.
 
1010
           This includes:
 
1011
           - name: the section name of the credentials in the
 
1012
             authentication.conf file,
 
1013
           - user: can't de different from the provided user if any,
 
1014
           - password: the decoded password, could be None if the credential
 
1015
             defines only the user
 
1016
           - verify_certificates: https specific, True if the server
 
1017
             certificate should be verified, False otherwise.
 
1018
        """
 
1019
        credentials = None
 
1020
        for auth_def_name, auth_def in self._get_config().items():
 
1021
            if type(auth_def) is not configobj.Section:
 
1022
                raise ValueError("%s defined outside a section" % auth_def_name)
 
1023
 
 
1024
            a_scheme, a_host, a_user, a_path = map(
 
1025
                auth_def.get, ['scheme', 'host', 'user', 'path'])
 
1026
 
 
1027
            try:
 
1028
                a_port = auth_def.as_int('port')
 
1029
            except KeyError:
 
1030
                a_port = None
 
1031
            except ValueError:
 
1032
                raise ValueError("'port' not numeric in %s" % auth_def_name)
 
1033
            try:
 
1034
                a_verify_certificates = auth_def.as_bool('verify_certificates')
 
1035
            except KeyError:
 
1036
                a_verify_certificates = True
 
1037
            except ValueError:
 
1038
                raise ValueError(
 
1039
                    "'verify_certificates' not boolean in %s" % auth_def_name)
 
1040
 
 
1041
            # Attempt matching
 
1042
            if a_scheme is not None and scheme != a_scheme:
 
1043
                continue
 
1044
            if a_host is not None:
 
1045
                if not (host == a_host
 
1046
                        or (a_host.startswith('.') and host.endswith(a_host))):
 
1047
                    continue
 
1048
            if a_port is not None and port != a_port:
 
1049
                continue
 
1050
            if (a_path is not None and path is not None
 
1051
                and not path.startswith(a_path)):
 
1052
                continue
 
1053
            if (a_user is not None and user is not None
 
1054
                and a_user != user):
 
1055
                # Never contradict the caller about the user to be used
 
1056
                continue
 
1057
            if a_user is None:
 
1058
                # Can't find a user
 
1059
                continue
 
1060
            credentials = dict(name=auth_def_name,
 
1061
                               user=a_user,
 
1062
                               password=auth_def.get('password', None),
 
1063
                               verify_certificates=a_verify_certificates)
 
1064
            self.decode_password(credentials,
 
1065
                                 auth_def.get('password_encoding', None))
 
1066
            if 'auth' in debug.debug_flags:
 
1067
                trace.mutter("Using authentication section: %r", auth_def_name)
 
1068
            break
 
1069
 
 
1070
        return credentials
 
1071
 
 
1072
    def set_credentials(self, name, host, user, scheme=None, password=None,
 
1073
                        port=None, path=None, verify_certificates=None):
 
1074
        """Set authentication credentials for a host.
 
1075
 
 
1076
        Any existing credentials with matching scheme, host, port and path
 
1077
        will be deleted, regardless of name.
 
1078
 
 
1079
        :param name: An arbitrary name to describe this set of credentials.
 
1080
        :param host: Name of the host that accepts these credentials.
 
1081
        :param user: The username portion of these credentials.
 
1082
        :param scheme: The URL scheme (e.g. ssh, http) the credentials apply
 
1083
            to.
 
1084
        :param password: Password portion of these credentials.
 
1085
        :param port: The IP port on the host that these credentials apply to.
 
1086
        :param path: A filesystem path on the host that these credentials
 
1087
            apply to.
 
1088
        :param verify_certificates: On https, verify server certificates if
 
1089
            True.
 
1090
        """
 
1091
        values = {'host': host, 'user': user}
 
1092
        if password is not None:
 
1093
            values['password'] = password
 
1094
        if scheme is not None:
 
1095
            values['scheme'] = scheme
 
1096
        if port is not None:
 
1097
            values['port'] = '%d' % port
 
1098
        if path is not None:
 
1099
            values['path'] = path
 
1100
        if verify_certificates is not None:
 
1101
            values['verify_certificates'] = str(verify_certificates)
 
1102
        config = self._get_config()
 
1103
        for_deletion = []
 
1104
        for section, existing_values in config.items():
 
1105
            for key in ('scheme', 'host', 'port', 'path'):
 
1106
                if existing_values.get(key) != values.get(key):
 
1107
                    break
 
1108
            else:
 
1109
                del config[section]
 
1110
        config.update({name: values})
 
1111
        self._save()
 
1112
 
 
1113
    def get_user(self, scheme, host, port=None,
 
1114
                 realm=None, path=None, prompt=None):
 
1115
        """Get a user from authentication file.
 
1116
 
 
1117
        :param scheme: protocol
 
1118
 
 
1119
        :param host: the server address
 
1120
 
 
1121
        :param port: the associated port (optional)
 
1122
 
 
1123
        :param realm: the realm sent by the server (optional)
 
1124
 
 
1125
        :param path: the absolute path on the server (optional)
 
1126
 
 
1127
        :return: The found user.
 
1128
        """
 
1129
        credentials = self.get_credentials(scheme, host, port, user=None,
 
1130
                                           path=path)
 
1131
        if credentials is not None:
 
1132
            user = credentials['user']
 
1133
        else:
 
1134
            user = None
 
1135
        return user
 
1136
 
 
1137
    def get_password(self, scheme, host, user, port=None,
 
1138
                     realm=None, path=None, prompt=None):
 
1139
        """Get a password from authentication file or prompt the user for one.
 
1140
 
 
1141
        :param scheme: protocol
 
1142
 
 
1143
        :param host: the server address
 
1144
 
 
1145
        :param port: the associated port (optional)
 
1146
 
 
1147
        :param user: login
 
1148
 
 
1149
        :param realm: the realm sent by the server (optional)
 
1150
 
 
1151
        :param path: the absolute path on the server (optional)
 
1152
 
 
1153
        :return: The found password or the one entered by the user.
 
1154
        """
 
1155
        credentials = self.get_credentials(scheme, host, port, user, path)
 
1156
        if credentials is not None:
 
1157
            password = credentials['password']
 
1158
            if password is not None and scheme is 'ssh':
 
1159
                trace.warning('password ignored in section [%s],'
 
1160
                              ' use an ssh agent instead'
 
1161
                              % credentials['name'])
 
1162
                password = None
 
1163
        else:
 
1164
            password = None
 
1165
        # Prompt user only if we could't find a password
 
1166
        if password is None:
 
1167
            if prompt is None:
 
1168
                # Create a default prompt suitable for most cases
 
1169
                prompt = '%s' % scheme.upper() + ' %(user)s@%(host)s password'
 
1170
            # Special handling for optional fields in the prompt
 
1171
            if port is not None:
 
1172
                prompt_host = '%s:%d' % (host, port)
 
1173
            else:
 
1174
                prompt_host = host
 
1175
            password = ui.ui_factory.get_password(prompt,
 
1176
                                                  host=prompt_host, user=user)
 
1177
        return password
 
1178
 
 
1179
    def decode_password(self, credentials, encoding):
 
1180
        try:
 
1181
            cs = credential_store_registry.get_credential_store(encoding)
 
1182
        except KeyError:
 
1183
            raise ValueError('%r is not a known password_encoding' % encoding)
 
1184
        credentials['password'] = cs.decode_password(credentials)
 
1185
        return credentials
 
1186
 
 
1187
 
 
1188
class CredentialStoreRegistry(registry.Registry):
 
1189
    """A class that registers credential stores.
 
1190
 
 
1191
    A credential store provides access to credentials via the password_encoding
 
1192
    field in authentication.conf sections.
 
1193
 
 
1194
    Except for stores provided by bzr itself,most stores are expected to be
 
1195
    provided by plugins that will therefore use
 
1196
    register_lazy(password_encoding, module_name, member_name, help=help,
 
1197
    info=info) to install themselves.
 
1198
    """
 
1199
 
 
1200
    def get_credential_store(self, encoding=None):
 
1201
        cs = self.get(encoding)
 
1202
        if callable(cs):
 
1203
            cs = cs()
 
1204
        return cs
 
1205
 
 
1206
 
 
1207
credential_store_registry = CredentialStoreRegistry()
 
1208
 
 
1209
 
 
1210
class CredentialStore(object):
 
1211
    """An abstract class to implement storage for credentials"""
 
1212
 
 
1213
    def decode_password(self, credentials):
 
1214
        """Returns a password for the provided credentials in clear text."""
 
1215
        raise NotImplementedError(self.decode_password)
 
1216
 
 
1217
 
 
1218
class PlainTextCredentialStore(CredentialStore):
 
1219
    """Plain text credential store for the authentication.conf file."""
 
1220
 
 
1221
    def decode_password(self, credentials):
 
1222
        """See CredentialStore.decode_password."""
 
1223
        return credentials['password']
 
1224
 
 
1225
 
 
1226
credential_store_registry.register('plain', PlainTextCredentialStore,
 
1227
                                   help=PlainTextCredentialStore.__doc__)
 
1228
credential_store_registry.default_key = 'plain'
 
1229
 
 
1230
 
 
1231
class BzrDirConfig(object):
 
1232
 
 
1233
    def __init__(self, transport):
 
1234
        self._config = TransportConfig(transport, 'control.conf')
 
1235
 
 
1236
    def set_default_stack_on(self, value):
 
1237
        """Set the default stacking location.
 
1238
 
 
1239
        It may be set to a location, or None.
 
1240
 
 
1241
        This policy affects all branches contained by this bzrdir, except for
 
1242
        those under repositories.
 
1243
        """
 
1244
        if value is None:
 
1245
            self._config.set_option('', 'default_stack_on')
 
1246
        else:
 
1247
            self._config.set_option(value, 'default_stack_on')
 
1248
 
 
1249
    def get_default_stack_on(self):
 
1250
        """Return the default stacking location.
 
1251
 
 
1252
        This will either be a location, or None.
 
1253
 
 
1254
        This policy affects all branches contained by this bzrdir, except for
 
1255
        those under repositories.
 
1256
        """
 
1257
        value = self._config.get_option('default_stack_on')
 
1258
        if value == '':
 
1259
            value = None
 
1260
        return value
 
1261
 
 
1262
 
 
1263
class TransportConfig(object):
 
1264
    """A Config that reads/writes a config file on a Transport.
 
1265
 
 
1266
    It is a low-level object that considers config data to be name/value pairs
 
1267
    that may be associated with a section.  Assigning meaning to the these
 
1268
    values is done at higher levels like TreeConfig.
 
1269
    """
 
1270
 
 
1271
    def __init__(self, transport, filename):
 
1272
        self._transport = transport
 
1273
        self._filename = filename
 
1274
 
 
1275
    def get_option(self, name, section=None, default=None):
 
1276
        """Return the value associated with a named option.
 
1277
 
 
1278
        :param name: The name of the value
 
1279
        :param section: The section the option is in (if any)
 
1280
        :param default: The value to return if the value is not set
 
1281
        :return: The value or default value
 
1282
        """
 
1283
        configobj = self._get_configobj()
 
1284
        if section is None:
 
1285
            section_obj = configobj
 
1286
        else:
 
1287
            try:
 
1288
                section_obj = configobj[section]
 
1289
            except KeyError:
 
1290
                return default
 
1291
        return section_obj.get(name, default)
 
1292
 
 
1293
    def set_option(self, value, name, section=None):
 
1294
        """Set the value associated with a named option.
 
1295
 
 
1296
        :param value: The value to set
 
1297
        :param name: The name of the value to set
 
1298
        :param section: The section the option is in (if any)
 
1299
        """
 
1300
        configobj = self._get_configobj()
 
1301
        if section is None:
 
1302
            configobj[name] = value
 
1303
        else:
 
1304
            configobj.setdefault(section, {})[name] = value
 
1305
        self._set_configobj(configobj)
 
1306
 
 
1307
    def _get_configobj(self):
 
1308
        try:
 
1309
            return ConfigObj(self._transport.get(self._filename),
 
1310
                             encoding='utf-8')
 
1311
        except errors.NoSuchFile:
 
1312
            return ConfigObj(encoding='utf-8')
 
1313
 
 
1314
    def _set_configobj(self, configobj):
 
1315
        out_file = StringIO()
 
1316
        configobj.write(out_file)
 
1317
        out_file.seek(0)
 
1318
        self._transport.put_file(self._filename, out_file)