~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-06-27 01:39:26 UTC
  • Revision ID: mbp@sourcefrog.net-20050627013926-49413d5928809350
- import effbot.org http client

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
 
21
21
import bzrlib
22
22
from bzrlib.trace import mutter, note, log_error
23
 
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
 
23
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
24
24
from bzrlib.osutils import quotefn
25
25
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
26
26
     format_date
27
27
 
28
28
 
 
29
plugin_cmds = {}
 
30
 
 
31
 
 
32
def register_command(cmd):
 
33
    "Utility function to help register a command"
 
34
    global plugin_cmds
 
35
    k = cmd.__name__
 
36
    if k.startswith("cmd_"):
 
37
        k_unsquished = _unsquish_command_name(k)
 
38
    else:
 
39
        k_unsquished = k
 
40
    if not plugin_cmds.has_key(k_unsquished):
 
41
        plugin_cmds[k_unsquished] = cmd
 
42
    else:
 
43
        log_error('Two plugins defined the same command: %r' % k)
 
44
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
45
 
 
46
 
29
47
def _squish_command_name(cmd):
30
48
    return 'cmd_' + cmd.replace('-', '_')
31
49
 
68
86
        revs = int(revstr)
69
87
    return revs
70
88
 
71
 
def get_all_cmds():
72
 
    """Return canonical name and class for all registered commands."""
 
89
 
 
90
 
 
91
def _get_cmd_dict(plugins_override=True):
 
92
    d = {}
73
93
    for k, v in globals().iteritems():
74
94
        if k.startswith("cmd_"):
75
 
            yield _unsquish_command_name(k), v
76
 
 
77
 
def get_cmd_class(cmd):
 
95
            d[_unsquish_command_name(k)] = v
 
96
    # If we didn't load plugins, the plugin_cmds dict will be empty
 
97
    if plugins_override:
 
98
        d.update(plugin_cmds)
 
99
    else:
 
100
        d2 = plugin_cmds.copy()
 
101
        d2.update(d)
 
102
        d = d2
 
103
    return d
 
104
 
 
105
    
 
106
def get_all_cmds(plugins_override=True):
 
107
    """Return canonical name and class for all registered commands."""
 
108
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
109
        yield k,v
 
110
 
 
111
 
 
112
def get_cmd_class(cmd, plugins_override=True):
78
113
    """Return the canonical name and command class for a command.
79
114
    """
80
115
    cmd = str(cmd)                      # not unicode
81
116
 
82
117
    # first look up this command under the specified name
 
118
    cmds = _get_cmd_dict(plugins_override=plugins_override)
83
119
    try:
84
 
        return cmd, globals()[_squish_command_name(cmd)]
 
120
        return cmd, cmds[cmd]
85
121
    except KeyError:
86
122
        pass
87
123
 
88
124
    # look for any command which claims this as an alias
89
 
    for cmdname, cmdclass in get_all_cmds():
 
125
    for cmdname, cmdclass in cmds.iteritems():
90
126
        if cmd in cmdclass.aliases:
91
127
            return cmdname, cmdclass
92
128
 
165
201
        import os.path
166
202
        bzrpath = os.environ.get('BZRPATH', '')
167
203
 
168
 
        for dir in bzrpath.split(':'):
 
204
        for dir in bzrpath.split(os.pathsep):
169
205
            path = os.path.join(dir, cmd)
170
206
            if os.path.isfile(path):
171
207
                return ExternalCommand(path)
177
213
    def __init__(self, path):
178
214
        self.path = path
179
215
 
180
 
        # TODO: If either of these fail, we should detect that and
181
 
        # assume that path is not really a bzr plugin after all.
182
 
 
183
216
        pipe = os.popen('%s --bzr-usage' % path, 'r')
184
217
        self.takes_options = pipe.readline().split()
 
218
 
 
219
        for opt in self.takes_options:
 
220
            if not opt in OPTIONS:
 
221
                raise BzrError("Unknown option '%s' returned by external command %s"
 
222
                               % (opt, path))
 
223
 
 
224
        # TODO: Is there any way to check takes_args is valid here?
185
225
        self.takes_args = pipe.readline().split()
186
 
        pipe.close()
 
226
 
 
227
        if pipe.close() is not None:
 
228
            raise BzrError("Failed funning '%s --bzr-usage'" % path)
187
229
 
188
230
        pipe = os.popen('%s --bzr-help' % path, 'r')
189
231
        self.__doc__ = pipe.read()
190
 
        pipe.close()
 
232
        if pipe.close() is not None:
 
233
            raise BzrError("Failed funning '%s --bzr-help'" % path)
191
234
 
192
235
    def __call__(self, options, arguments):
193
236
        Command.__init__(self, options, arguments)
200
243
        keys = kargs.keys()
201
244
        keys.sort()
202
245
        for name in keys:
 
246
            optname = name.replace('_','-')
203
247
            value = kargs[name]
204
 
            if OPTIONS.has_key(name):
 
248
            if OPTIONS.has_key(optname):
205
249
                # it's an option
206
 
                opts.append('--%s' % name)
 
250
                opts.append('--%s' % optname)
207
251
                if value is not None and value is not True:
208
252
                    opts.append(str(value))
209
253
            else:
319
363
        bzrlib.add.smart_add(file_list, verbose, not no_recurse)
320
364
 
321
365
 
 
366
 
 
367
class cmd_mkdir(Command):
 
368
    """Create a new versioned directory.
 
369
 
 
370
    This is equivalent to creating the directory and then adding it.
 
371
    """
 
372
    takes_args = ['dir+']
 
373
 
 
374
    def run(self, dir_list):
 
375
        import os
 
376
        import bzrlib.branch
 
377
        
 
378
        b = None
 
379
        
 
380
        for d in dir_list:
 
381
            os.mkdir(d)
 
382
            if not b:
 
383
                b = bzrlib.branch.Branch(d)
 
384
            b.add([d], verbose=True)
 
385
 
 
386
 
322
387
class cmd_relpath(Command):
323
388
    """Show path of a file relative to root"""
324
389
    takes_args = ['filename']
384
449
 
385
450
 
386
451
 
 
452
 
 
453
 
 
454
class cmd_pull(Command):
 
455
    """Pull any changes from another branch into the current one.
 
456
 
 
457
    If the location is omitted, the last-used location will be used.
 
458
    Both the revision history and the working directory will be
 
459
    updated.
 
460
 
 
461
    This command only works on branches that have not diverged.  Branches are
 
462
    considered diverged if both branches have had commits without first
 
463
    pulling from the other.
 
464
 
 
465
    If branches have diverged, you can use 'bzr merge' to pull the text changes
 
466
    from one into the other.
 
467
    """
 
468
    takes_args = ['location?']
 
469
 
 
470
    def run(self, location=None):
 
471
        from bzrlib.merge import merge
 
472
        import tempfile
 
473
        from shutil import rmtree
 
474
        import errno
 
475
        
 
476
        br_to = Branch('.')
 
477
        stored_loc = None
 
478
        try:
 
479
            stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
 
480
        except IOError, e:
 
481
            if e.errno != errno.ENOENT:
 
482
                raise
 
483
        if location is None:
 
484
            if stored_loc is None:
 
485
                raise BzrCommandError("No pull location known or specified.")
 
486
            else:
 
487
                print "Using last location: %s" % stored_loc
 
488
                location = stored_loc
 
489
        cache_root = tempfile.mkdtemp()
 
490
        try:
 
491
            from branch import find_cached_branch, DivergedBranches
 
492
            br_from = find_cached_branch(location, cache_root)
 
493
            location = pull_loc(br_from)
 
494
            old_revno = br_to.revno()
 
495
            try:
 
496
                br_to.update_revisions(br_from)
 
497
            except DivergedBranches:
 
498
                raise BzrCommandError("These branches have diverged."
 
499
                    "  Try merge.")
 
500
                
 
501
            merge(('.', -1), ('.', old_revno), check_clean=False)
 
502
            if location != stored_loc:
 
503
                br_to.controlfile("x-pull", "wb").write(location + "\n")
 
504
        finally:
 
505
            rmtree(cache_root)
 
506
 
 
507
 
 
508
 
 
509
class cmd_branch(Command):
 
510
    """Create a new copy of a branch.
 
511
 
 
512
    If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
 
513
    be used.  In other words, "branch ../foo/bar" will attempt to create ./bar.
 
514
 
 
515
    To retrieve the branch as of a particular revision, supply the --revision
 
516
    parameter, as in "branch foo/bar -r 5".
 
517
    """
 
518
    takes_args = ['from_location', 'to_location?']
 
519
    takes_options = ['revision']
 
520
 
 
521
    def run(self, from_location, to_location=None, revision=None):
 
522
        import errno
 
523
        from bzrlib.merge import merge
 
524
        from branch import find_cached_branch, DivergedBranches, NoSuchRevision
 
525
        from shutil import rmtree
 
526
        from meta_store import CachedStore
 
527
        import tempfile
 
528
        cache_root = tempfile.mkdtemp()
 
529
        try:
 
530
            try:
 
531
                br_from = find_cached_branch(from_location, cache_root)
 
532
            except OSError, e:
 
533
                if e.errno == errno.ENOENT:
 
534
                    raise BzrCommandError('Source location "%s" does not'
 
535
                                          ' exist.' % to_location)
 
536
                else:
 
537
                    raise
 
538
 
 
539
            if to_location is None:
 
540
                to_location = os.path.basename(from_location.rstrip("/\\"))
 
541
 
 
542
            try:
 
543
                os.mkdir(to_location)
 
544
            except OSError, e:
 
545
                if e.errno == errno.EEXIST:
 
546
                    raise BzrCommandError('Target directory "%s" already'
 
547
                                          ' exists.' % to_location)
 
548
                if e.errno == errno.ENOENT:
 
549
                    raise BzrCommandError('Parent of "%s" does not exist.' %
 
550
                                          to_location)
 
551
                else:
 
552
                    raise
 
553
            br_to = Branch(to_location, init=True)
 
554
 
 
555
            try:
 
556
                br_to.update_revisions(br_from, stop_revision=revision)
 
557
            except NoSuchRevision:
 
558
                rmtree(to_location)
 
559
                msg = "The branch %s has no revision %d." % (from_location,
 
560
                                                             revision)
 
561
                raise BzrCommandError(msg)
 
562
            merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
563
                  check_clean=False, ignore_zero=True)
 
564
            from_location = pull_loc(br_from)
 
565
            br_to.controlfile("x-pull", "wb").write(from_location + "\n")
 
566
        finally:
 
567
            rmtree(cache_root)
 
568
 
 
569
 
 
570
def pull_loc(branch):
 
571
    # TODO: Should perhaps just make attribute be 'base' in
 
572
    # RemoteBranch and Branch?
 
573
    if hasattr(branch, "baseurl"):
 
574
        return branch.baseurl
 
575
    else:
 
576
        return branch.base
 
577
 
 
578
 
 
579
 
387
580
class cmd_renames(Command):
388
581
    """Show list of renamed files.
389
582
 
443
636
        b = Branch(filename)
444
637
        i = b.inventory.path2id(b.relpath(filename))
445
638
        if i == None:
446
 
            bailout("%r is not a versioned file" % filename)
 
639
            raise BzrError("%r is not a versioned file" % filename)
447
640
        else:
448
641
            print i
449
642
 
460
653
        inv = b.inventory
461
654
        fid = inv.path2id(b.relpath(filename))
462
655
        if fid == None:
463
 
            bailout("%r is not a versioned file" % filename)
 
656
            raise BzrError("%r is not a versioned file" % filename)
464
657
        for fip in inv.get_idpath(fid):
465
658
            print fip
466
659
 
526
719
    
527
720
    takes_args = ['file*']
528
721
    takes_options = ['revision', 'diff-options']
529
 
    aliases = ['di']
 
722
    aliases = ['di', 'dif']
530
723
 
531
724
    def run(self, revision=None, file_list=None, diff_options=None):
532
725
        from bzrlib.diff import show_diff
734
927
 
735
928
 
736
929
class cmd_unknowns(Command):
737
 
    """List unknown files"""
 
930
    """List unknown files."""
738
931
    def run(self):
739
932
        for f in Branch('.').unknowns():
740
933
            print quotefn(f)
742
935
 
743
936
 
744
937
class cmd_ignore(Command):
745
 
    """Ignore a command or pattern
 
938
    """Ignore a command or pattern.
746
939
 
747
940
    To remove patterns from the ignore list, edit the .bzrignore file.
748
941
 
834
1027
class cmd_export(Command):
835
1028
    """Export past revision to destination directory.
836
1029
 
837
 
    If no revision is specified this exports the last committed revision."""
 
1030
    If no revision is specified this exports the last committed revision.
 
1031
 
 
1032
    Format may be an "exporter" name, such as tar, tgz, tbz2.  If none is
 
1033
    given, exports to a directory (equivalent to --format=dir)."""
 
1034
    # TODO: list known exporters
838
1035
    takes_args = ['dest']
839
 
    takes_options = ['revision']
840
 
    def run(self, dest, revision=None):
 
1036
    takes_options = ['revision', 'format']
 
1037
    def run(self, dest, revision=None, format='dir'):
841
1038
        b = Branch('.')
842
1039
        if revision == None:
843
1040
            rh = b.revision_history()[-1]
844
1041
        else:
845
1042
            rh = b.lookup_revision(int(revision))
846
1043
        t = b.revision_tree(rh)
847
 
        t.export(dest)
 
1044
        t.export(dest, format)
848
1045
 
849
1046
 
850
1047
class cmd_cat(Command):
889
1086
 
890
1087
    def run(self, message=None, file=None, verbose=True, selected_list=None):
891
1088
        from bzrlib.commit import commit
 
1089
        from bzrlib.osutils import get_text_message
892
1090
 
893
1091
        ## Warning: shadows builtin file()
894
1092
        if not message and not file:
895
 
            raise BzrCommandError("please specify a commit message",
896
 
                                  ["use either --message or --file"])
 
1093
            import cStringIO
 
1094
            stdout = sys.stdout
 
1095
            catcher = cStringIO.StringIO()
 
1096
            sys.stdout = catcher
 
1097
            cmd_status({"file_list":selected_list}, {})
 
1098
            info = catcher.getvalue()
 
1099
            sys.stdout = stdout
 
1100
            message = get_text_message(info)
 
1101
            
 
1102
            if message is None:
 
1103
                raise BzrCommandError("please specify a commit message",
 
1104
                                      ["use either --message or --file"])
897
1105
        elif message and file:
898
1106
            raise BzrCommandError("please specify either --message or --file")
899
1107
        
910
1118
 
911
1119
    This command checks various invariants about the branch storage to
912
1120
    detect data corruption or bzr bugs.
 
1121
 
 
1122
    If given the --update flag, it will update some optional fields
 
1123
    to help ensure data consistency.
913
1124
    """
914
1125
    takes_args = ['dir?']
 
1126
 
915
1127
    def run(self, dir='.'):
916
1128
        import bzrlib.check
917
1129
        bzrlib.check.check(Branch(dir))
918
1130
 
919
1131
 
920
1132
 
 
1133
class cmd_upgrade(Command):
 
1134
    """Upgrade branch storage to current format.
 
1135
 
 
1136
    This should normally be used only after the check command tells
 
1137
    you to run it.
 
1138
    """
 
1139
    takes_args = ['dir?']
 
1140
 
 
1141
    def run(self, dir='.'):
 
1142
        from bzrlib.upgrade import upgrade
 
1143
        upgrade(Branch(dir))
 
1144
 
 
1145
 
 
1146
 
921
1147
class cmd_whoami(Command):
922
1148
    """Show bzr user id."""
923
1149
    takes_options = ['email']
934
1160
    hidden = True
935
1161
    def run(self):
936
1162
        from bzrlib.selftest import selftest
937
 
        if selftest():
938
 
            return 0
939
 
        else:
940
 
            return 1
941
 
 
 
1163
        return int(not selftest())
942
1164
 
943
1165
 
944
1166
class cmd_version(Command):
945
 
    """Show version of bzr"""
 
1167
    """Show version of bzr."""
946
1168
    def run(self):
947
1169
        show_version()
948
1170
 
967
1189
        print "it sure does!"
968
1190
 
969
1191
def parse_spec(spec):
 
1192
    """
 
1193
    >>> parse_spec(None)
 
1194
    [None, None]
 
1195
    >>> parse_spec("./")
 
1196
    ['./', None]
 
1197
    >>> parse_spec("../@")
 
1198
    ['..', -1]
 
1199
    >>> parse_spec("../f/@35")
 
1200
    ['../f', 35]
 
1201
    """
 
1202
    if spec is None:
 
1203
        return [None, None]
970
1204
    if '/@' in spec:
971
1205
        parsed = spec.split('/@')
972
1206
        assert len(parsed) == 2
979
1213
        parsed = [spec, None]
980
1214
    return parsed
981
1215
 
 
1216
 
 
1217
 
982
1218
class cmd_merge(Command):
983
 
    """Perform a three-way merge of trees."""
984
 
    takes_args = ['other_spec', 'base_spec']
985
 
 
986
 
    def run(self, other_spec, base_spec):
987
 
        from bzrlib.merge import merge
988
 
        merge(parse_spec(other_spec), parse_spec(base_spec))
 
1219
    """Perform a three-way merge of trees.
 
1220
    
 
1221
    The SPEC parameters are working tree or revision specifiers.  Working trees
 
1222
    are specified using standard paths or urls.  No component of a directory
 
1223
    path may begin with '@'.
 
1224
    
 
1225
    Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
 
1226
 
 
1227
    Revisions are specified using a dirname/@revno pair, where dirname is the
 
1228
    branch directory and revno is the revision within that branch.  If no revno
 
1229
    is specified, the latest revision is used.
 
1230
 
 
1231
    Revision examples: './@127', 'foo/@', '../@1'
 
1232
 
 
1233
    The OTHER_SPEC parameter is required.  If the BASE_SPEC parameter is
 
1234
    not supplied, the common ancestor of OTHER_SPEC the current branch is used
 
1235
    as the BASE.
 
1236
 
 
1237
    merge refuses to run if there are any uncommitted changes, unless
 
1238
    --force is given.
 
1239
    """
 
1240
    takes_args = ['other_spec', 'base_spec?']
 
1241
    takes_options = ['force']
 
1242
 
 
1243
    def run(self, other_spec, base_spec=None, force=False):
 
1244
        from bzrlib.merge import merge
 
1245
        merge(parse_spec(other_spec), parse_spec(base_spec),
 
1246
              check_clean=(not force))
 
1247
 
 
1248
 
 
1249
 
 
1250
class cmd_revert(Command):
 
1251
    """Restore selected files from a previous revision.
 
1252
    """
 
1253
    takes_args = ['file+']
 
1254
    def run(self, file_list):
 
1255
        from bzrlib.branch import find_branch
 
1256
        
 
1257
        if not file_list:
 
1258
            file_list = ['.']
 
1259
            
 
1260
        b = find_branch(file_list[0])
 
1261
 
 
1262
        b.revert([b.relpath(f) for f in file_list])
 
1263
 
 
1264
 
 
1265
class cmd_merge_revert(Command):
 
1266
    """Reverse all changes since the last commit.
 
1267
 
 
1268
    Only versioned files are affected.
 
1269
 
 
1270
    TODO: Store backups of any files that will be reverted, so
 
1271
          that the revert can be undone.          
 
1272
    """
 
1273
    takes_options = ['revision']
 
1274
 
 
1275
    def run(self, revision=-1):
 
1276
        from bzrlib.merge import merge
 
1277
        merge(('.', revision), parse_spec('.'),
 
1278
              check_clean=False,
 
1279
              ignore_zero=True)
 
1280
 
989
1281
 
990
1282
class cmd_assert_fail(Command):
991
1283
    """Test reporting of assertion failures"""
1018
1310
 
1019
1311
 
1020
1312
 
 
1313
class cmd_plugins(Command):
 
1314
    """List plugins"""
 
1315
    hidden = True
 
1316
    def run(self):
 
1317
        import bzrlib.plugin
 
1318
        from pprint import pprint
 
1319
        pprint(bzrlib.plugin.all_plugins)
 
1320
 
 
1321
 
 
1322
 
1021
1323
# list of all available options; the rhs can be either None for an
1022
1324
# option that takes no argument, or a constructor function that checks
1023
1325
# the type.
1026
1328
    'diff-options':           str,
1027
1329
    'help':                   None,
1028
1330
    'file':                   unicode,
 
1331
    'force':                  None,
 
1332
    'format':                 unicode,
1029
1333
    'forward':                None,
1030
1334
    'message':                unicode,
1031
1335
    'no-recurse':             None,
1036
1340
    'verbose':                None,
1037
1341
    'version':                None,
1038
1342
    'email':                  None,
 
1343
    'update':                 None,
1039
1344
    }
1040
1345
 
1041
1346
SHORT_OPTIONS = {
1063
1368
    (['status'], {'all': True})
1064
1369
    >>> parse_args('commit --message=biter'.split())
1065
1370
    (['commit'], {'message': u'biter'})
 
1371
    >>> parse_args('log -r 500'.split())
 
1372
    (['log'], {'revision': 500})
 
1373
    >>> parse_args('log -r500:600'.split())
 
1374
    (['log'], {'revision': [500, 600]})
 
1375
    >>> parse_args('log -vr500:600'.split())
 
1376
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
1377
    >>> parse_args('log -rv500:600'.split()) #the r takes an argument
 
1378
    Traceback (most recent call last):
 
1379
    ...
 
1380
    ValueError: invalid literal for int(): v500
1066
1381
    """
1067
1382
    args = []
1068
1383
    opts = {}
1082
1397
                else:
1083
1398
                    optname = a[2:]
1084
1399
                if optname not in OPTIONS:
1085
 
                    bailout('unknown long option %r' % a)
 
1400
                    raise BzrError('unknown long option %r' % a)
1086
1401
            else:
1087
1402
                shortopt = a[1:]
1088
 
                if shortopt not in SHORT_OPTIONS:
1089
 
                    bailout('unknown short option %r' % a)
1090
 
                optname = SHORT_OPTIONS[shortopt]
 
1403
                if shortopt in SHORT_OPTIONS:
 
1404
                    # Multi-character options must have a space to delimit
 
1405
                    # their value
 
1406
                    optname = SHORT_OPTIONS[shortopt]
 
1407
                else:
 
1408
                    # Single character short options, can be chained,
 
1409
                    # and have their value appended to their name
 
1410
                    shortopt = a[1:2]
 
1411
                    if shortopt not in SHORT_OPTIONS:
 
1412
                        # We didn't find the multi-character name, and we
 
1413
                        # didn't find the single char name
 
1414
                        raise BzrError('unknown short option %r' % a)
 
1415
                    optname = SHORT_OPTIONS[shortopt]
 
1416
 
 
1417
                    if a[2:]:
 
1418
                        # There are extra things on this option
 
1419
                        # see if it is the value, or if it is another
 
1420
                        # short option
 
1421
                        optargfn = OPTIONS[optname]
 
1422
                        if optargfn is None:
 
1423
                            # This option does not take an argument, so the
 
1424
                            # next entry is another short option, pack it back
 
1425
                            # into the list
 
1426
                            argv.insert(0, '-' + a[2:])
 
1427
                        else:
 
1428
                            # This option takes an argument, so pack it
 
1429
                            # into the array
 
1430
                            optarg = a[2:]
1091
1431
            
1092
1432
            if optname in opts:
1093
1433
                # XXX: Do we ever want to support this, e.g. for -r?
1094
 
                bailout('repeated option %r' % a)
 
1434
                raise BzrError('repeated option %r' % a)
1095
1435
                
1096
1436
            optargfn = OPTIONS[optname]
1097
1437
            if optargfn:
1098
1438
                if optarg == None:
1099
1439
                    if not argv:
1100
 
                        bailout('option %r needs an argument' % a)
 
1440
                        raise BzrError('option %r needs an argument' % a)
1101
1441
                    else:
1102
1442
                        optarg = argv.pop(0)
1103
1443
                opts[optname] = optargfn(optarg)
1104
1444
            else:
1105
1445
                if optarg != None:
1106
 
                    bailout('option %r takes no argument' % optname)
 
1446
                    raise BzrError('option %r takes no argument' % optname)
1107
1447
                opts[optname] = True
1108
1448
        else:
1109
1449
            args.append(a)
1157
1497
    return argdict
1158
1498
 
1159
1499
 
 
1500
def _parse_master_args(argv):
 
1501
    """Parse the arguments that always go with the original command.
 
1502
    These are things like bzr --no-plugins, etc.
 
1503
 
 
1504
    There are now 2 types of option flags. Ones that come *before* the command,
 
1505
    and ones that come *after* the command.
 
1506
    Ones coming *before* the command are applied against all possible commands.
 
1507
    And are generally applied before plugins are loaded.
 
1508
 
 
1509
    The current list are:
 
1510
        --builtin   Allow plugins to load, but don't let them override builtin commands,
 
1511
                    they will still be allowed if they do not override a builtin.
 
1512
        --no-plugins    Don't load any plugins. This lets you get back to official source
 
1513
                        behavior.
 
1514
        --profile   Enable the hotspot profile before running the command.
 
1515
                    For backwards compatibility, this is also a non-master option.
 
1516
        --version   Spit out the version of bzr that is running and exit.
 
1517
                    This is also a non-master option.
 
1518
        --help      Run help and exit, also a non-master option (I think that should stay, though)
 
1519
 
 
1520
    >>> argv, opts = _parse_master_args(['bzr', '--test'])
 
1521
    Traceback (most recent call last):
 
1522
    ...
 
1523
    BzrCommandError: Invalid master option: 'test'
 
1524
    >>> argv, opts = _parse_master_args(['bzr', '--version', 'command'])
 
1525
    >>> print argv
 
1526
    ['command']
 
1527
    >>> print opts['version']
 
1528
    True
 
1529
    >>> argv, opts = _parse_master_args(['bzr', '--profile', 'command', '--more-options'])
 
1530
    >>> print argv
 
1531
    ['command', '--more-options']
 
1532
    >>> print opts['profile']
 
1533
    True
 
1534
    >>> argv, opts = _parse_master_args(['bzr', '--no-plugins', 'command'])
 
1535
    >>> print argv
 
1536
    ['command']
 
1537
    >>> print opts['no-plugins']
 
1538
    True
 
1539
    >>> print opts['profile']
 
1540
    False
 
1541
    >>> argv, opts = _parse_master_args(['bzr', 'command', '--profile'])
 
1542
    >>> print argv
 
1543
    ['command', '--profile']
 
1544
    >>> print opts['profile']
 
1545
    False
 
1546
    """
 
1547
    master_opts = {'builtin':False,
 
1548
        'no-plugins':False,
 
1549
        'version':False,
 
1550
        'profile':False,
 
1551
        'help':False
 
1552
    }
 
1553
 
 
1554
    # This is the point where we could hook into argv[0] to determine
 
1555
    # what front-end is supposed to be run
 
1556
    # For now, we are just ignoring it.
 
1557
    cmd_name = argv.pop(0)
 
1558
    for arg in argv[:]:
 
1559
        if arg[:2] != '--': # at the first non-option, we return the rest
 
1560
            break
 
1561
        arg = arg[2:] # Remove '--'
 
1562
        if arg not in master_opts:
 
1563
            # We could say that this is not an error, that we should
 
1564
            # just let it be handled by the main section instead
 
1565
            raise BzrCommandError('Invalid master option: %r' % arg)
 
1566
        argv.pop(0) # We are consuming this entry
 
1567
        master_opts[arg] = True
 
1568
    return argv, master_opts
 
1569
 
 
1570
 
1160
1571
 
1161
1572
def run_bzr(argv):
1162
1573
    """Execute a command.
1167
1578
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
1168
1579
    
1169
1580
    try:
1170
 
        args, opts = parse_args(argv[1:])
 
1581
        # some options like --builtin and --no-plugins have special effects
 
1582
        argv, master_opts = _parse_master_args(argv)
 
1583
        if not master_opts['no-plugins']:
 
1584
            bzrlib.load_plugins()
 
1585
 
 
1586
        args, opts = parse_args(argv)
 
1587
 
 
1588
        if master_opts['help']:
 
1589
            from bzrlib.help import help
 
1590
            if argv:
 
1591
                help(argv[0])
 
1592
            else:
 
1593
                help()
 
1594
            return 0            
 
1595
            
1171
1596
        if 'help' in opts:
1172
 
            import help
 
1597
            from bzrlib.help import help
1173
1598
            if args:
1174
 
                help.help(args[0])
 
1599
                help(args[0])
1175
1600
            else:
1176
 
                help.help()
 
1601
                help()
1177
1602
            return 0
1178
1603
        elif 'version' in opts:
1179
1604
            show_version()
1180
1605
            return 0
 
1606
        elif args and args[0] == 'builtin':
 
1607
            include_plugins=False
 
1608
            args = args[1:]
1181
1609
        cmd = str(args.pop(0))
1182
1610
    except IndexError:
1183
1611
        import help
1185
1613
        return 1
1186
1614
          
1187
1615
 
1188
 
    canonical_cmd, cmd_class = get_cmd_class(cmd)
 
1616
    plugins_override = not (master_opts['builtin'])
 
1617
    canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
1189
1618
 
1190
 
    # global option
 
1619
    profile = master_opts['profile']
 
1620
    # For backwards compatibility, I would rather stick with --profile being a
 
1621
    # master/global option
1191
1622
    if 'profile' in opts:
1192
1623
        profile = True
1193
1624
        del opts['profile']
1194
 
    else:
1195
 
        profile = False
1196
1625
 
1197
1626
    # check options are reasonable
1198
1627
    allowed = cmd_class.takes_options