/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/commands.py

  • Committer: Aaron Bentley
  • Date: 2006-02-02 03:12:53 UTC
  • mto: (1534.7.122 bzr.ttransform)
  • mto: This revision was merged to the branch mainline in revision 1558.
  • Revision ID: aaron.bentley@utoronto.ca-20060202031253-fa4f98b03e7cb0d8
Removed more selftest spam

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
# TODO: probably should say which arguments are candidates for glob
 
19
# expansion on windows and do that at the command level.
 
20
 
 
21
# TODO: Help messages for options.
 
22
 
 
23
# TODO: Define arguments by objects, rather than just using names.
 
24
# Those objects can specify the expected type of the argument, which
 
25
# would help with validation and shell completion.
 
26
 
 
27
# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
 
28
# the profile output behind so it can be interactively examined?
 
29
 
 
30
import sys
 
31
import os
 
32
from warnings import warn
 
33
from inspect import getdoc
 
34
import errno
 
35
 
 
36
import bzrlib
 
37
from bzrlib.config import GlobalConfig
 
38
import bzrlib.trace
 
39
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
 
40
from bzrlib.errors import (BzrError, 
 
41
                           BzrCheckError,
 
42
                           BzrCommandError,
 
43
                           BzrOptionError,
 
44
                           NotBranchError,
 
45
                           CommandDefaultSyntax,
 
46
                           )
 
47
from bzrlib.revisionspec import RevisionSpec
 
48
from bzrlib import BZRDIR
 
49
from bzrlib.option import Option
 
50
 
 
51
plugin_cmds = {}
 
52
 
 
53
 
 
54
def register_command(cmd, decorate=False):
 
55
    "Utility function to help register a command"
 
56
    global plugin_cmds
 
57
    k = cmd.__name__
 
58
    if k.startswith("cmd_"):
 
59
        k_unsquished = _unsquish_command_name(k)
 
60
    else:
 
61
        k_unsquished = k
 
62
    if not plugin_cmds.has_key(k_unsquished):
 
63
        plugin_cmds[k_unsquished] = cmd
 
64
        mutter('registered plugin command %s', k_unsquished)      
 
65
        if decorate and k_unsquished in builtin_command_names():
 
66
            return _builtin_commands()[k_unsquished]
 
67
    elif decorate:
 
68
        result = plugin_cmds[k_unsquished]
 
69
        plugin_cmds[k_unsquished] = cmd
 
70
        return result
 
71
    else:
 
72
        log_error('Two plugins defined the same command: %r' % k)
 
73
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
74
 
 
75
 
 
76
def _squish_command_name(cmd):
 
77
    return 'cmd_' + cmd.replace('-', '_')
 
78
 
 
79
 
 
80
def _unsquish_command_name(cmd):
 
81
    assert cmd.startswith("cmd_")
 
82
    return cmd[4:].replace('_','-')
 
83
 
 
84
 
 
85
def _builtin_commands():
 
86
    import bzrlib.builtins
 
87
    r = {}
 
88
    builtins = bzrlib.builtins.__dict__
 
89
    for name in builtins:
 
90
        if name.startswith("cmd_"):
 
91
            real_name = _unsquish_command_name(name)        
 
92
            r[real_name] = builtins[name]
 
93
    return r
 
94
 
 
95
            
 
96
 
 
97
def builtin_command_names():
 
98
    """Return list of builtin command names."""
 
99
    return _builtin_commands().keys()
 
100
    
 
101
 
 
102
def plugin_command_names():
 
103
    return plugin_cmds.keys()
 
104
 
 
105
 
 
106
def _get_cmd_dict(plugins_override=True):
 
107
    """Return name->class mapping for all commands."""
 
108
    d = _builtin_commands()
 
109
    if plugins_override:
 
110
        d.update(plugin_cmds)
 
111
    return d
 
112
 
 
113
    
 
114
def get_all_cmds(plugins_override=True):
 
115
    """Return canonical name and class for all registered commands."""
 
116
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
117
        yield k,v
 
118
 
 
119
 
 
120
def get_cmd_object(cmd_name, plugins_override=True):
 
121
    """Return the canonical name and command class for a command.
 
122
 
 
123
    plugins_override
 
124
        If true, plugin commands can override builtins.
 
125
    """
 
126
    from bzrlib.externalcommand import ExternalCommand
 
127
 
 
128
    cmd_name = str(cmd_name)            # not unicode
 
129
 
 
130
    # first look up this command under the specified name
 
131
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
132
    try:
 
133
        return cmds[cmd_name]()
 
134
    except KeyError:
 
135
        pass
 
136
 
 
137
    # look for any command which claims this as an alias
 
138
    for real_cmd_name, cmd_class in cmds.iteritems():
 
139
        if cmd_name in cmd_class.aliases:
 
140
            return cmd_class()
 
141
 
 
142
    cmd_obj = ExternalCommand.find_command(cmd_name)
 
143
    if cmd_obj:
 
144
        return cmd_obj
 
145
 
 
146
    raise BzrCommandError("unknown command %r" % cmd_name)
 
147
 
 
148
 
 
149
class Command(object):
 
150
    """Base class for commands.
 
151
 
 
152
    Commands are the heart of the command-line bzr interface.
 
153
 
 
154
    The command object mostly handles the mapping of command-line
 
155
    parameters into one or more bzrlib operations, and of the results
 
156
    into textual output.
 
157
 
 
158
    Commands normally don't have any state.  All their arguments are
 
159
    passed in to the run method.  (Subclasses may take a different
 
160
    policy if the behaviour of the instance needs to depend on e.g. a
 
161
    shell plugin and not just its Python class.)
 
162
 
 
163
    The docstring for an actual command should give a single-line
 
164
    summary, then a complete description of the command.  A grammar
 
165
    description will be inserted.
 
166
 
 
167
    aliases
 
168
        Other accepted names for this command.
 
169
 
 
170
    takes_args
 
171
        List of argument forms, marked with whether they are optional,
 
172
        repeated, etc.
 
173
 
 
174
                Examples:
 
175
 
 
176
                ['to_location', 'from_branch?', 'file*']
 
177
 
 
178
                'to_location' is required
 
179
                'from_branch' is optional
 
180
                'file' can be specified 0 or more times
 
181
 
 
182
    takes_options
 
183
        List of options that may be given for this command.  These can
 
184
        be either strings, referring to globally-defined options,
 
185
        or option objects.  Retrieve through options().
 
186
 
 
187
    hidden
 
188
        If true, this command isn't advertised.  This is typically
 
189
        for commands intended for expert users.
 
190
    """
 
191
    aliases = []
 
192
    takes_args = []
 
193
    takes_options = []
 
194
 
 
195
    hidden = False
 
196
    
 
197
    def __init__(self):
 
198
        """Construct an instance of this command."""
 
199
        if self.__doc__ == Command.__doc__:
 
200
            warn("No help message set for %r" % self)
 
201
 
 
202
    def options(self):
 
203
        """Return dict of valid options for this command.
 
204
 
 
205
        Maps from long option name to option object."""
 
206
        r = dict()
 
207
        r['help'] = Option.OPTIONS['help']
 
208
        for o in self.takes_options:
 
209
            if not isinstance(o, Option):
 
210
                o = Option.OPTIONS[o]
 
211
            r[o.name] = o
 
212
        return r
 
213
 
 
214
    def run_argv(self, argv, defaults=None):
 
215
        """Parse command line and run."""
 
216
        if defaults is not None:
 
217
            args, opts = parse_args(self, defaults)
 
218
        else:
 
219
            args = []
 
220
            opts = {}
 
221
        cmd_args, cmd_opts = parse_args(self, argv)
 
222
        args.extend(cmd_args)
 
223
        opts.update(cmd_opts)
 
224
        if 'help' in opts:  # e.g. bzr add --help
 
225
            from bzrlib.help import help_on_command
 
226
            help_on_command(self.name())
 
227
            return 0
 
228
        # XXX: This should be handled by the parser
 
229
        allowed_names = self.options().keys()
 
230
        for oname in opts:
 
231
            if oname not in allowed_names:
 
232
                raise BzrCommandError("option '--%s' is not allowed for command %r"
 
233
                                      % (oname, self.name()))
 
234
        # mix arguments and options into one dictionary
 
235
        cmdargs = _match_argform(self.name(), self.takes_args, args)
 
236
        cmdopts = {}
 
237
        for k, v in opts.items():
 
238
            cmdopts[k.replace('-', '_')] = v
 
239
 
 
240
        all_cmd_args = cmdargs.copy()
 
241
        all_cmd_args.update(cmdopts)
 
242
 
 
243
        return self.run(**all_cmd_args)
 
244
    
 
245
    def run(self):
 
246
        """Actually run the command.
 
247
 
 
248
        This is invoked with the options and arguments bound to
 
249
        keyword parameters.
 
250
 
 
251
        Return 0 or None if the command was successful, or a non-zero
 
252
        shell error code if not.  It's OK for this method to allow
 
253
        an exception to raise up.
 
254
        """
 
255
        raise NotImplementedError()
 
256
 
 
257
 
 
258
    def help(self):
 
259
        """Return help message for this class."""
 
260
        if self.__doc__ is Command.__doc__:
 
261
            return None
 
262
        return getdoc(self)
 
263
 
 
264
    def name(self):
 
265
        return _unsquish_command_name(self.__class__.__name__)
 
266
 
 
267
 
 
268
def parse_spec(spec):
 
269
    """
 
270
    >>> parse_spec(None)
 
271
    [None, None]
 
272
    >>> parse_spec("./")
 
273
    ['./', None]
 
274
    >>> parse_spec("../@")
 
275
    ['..', -1]
 
276
    >>> parse_spec("../f/@35")
 
277
    ['../f', 35]
 
278
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
279
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
 
280
    """
 
281
    if spec is None:
 
282
        return [None, None]
 
283
    if '/@' in spec:
 
284
        parsed = spec.split('/@')
 
285
        assert len(parsed) == 2
 
286
        if parsed[1] == "":
 
287
            parsed[1] = -1
 
288
        else:
 
289
            try:
 
290
                parsed[1] = int(parsed[1])
 
291
            except ValueError:
 
292
                pass # We can allow stuff like ./@revid:blahblahblah
 
293
            else:
 
294
                assert parsed[1] >=0
 
295
    else:
 
296
        parsed = [spec, None]
 
297
    return parsed
 
298
 
 
299
def parse_args(command, argv):
 
300
    """Parse command line.
 
301
    
 
302
    Arguments and options are parsed at this level before being passed
 
303
    down to specific command handlers.  This routine knows, from a
 
304
    lookup table, something about the available options, what optargs
 
305
    they take, and which commands will accept them.
 
306
    """
 
307
    # TODO: chop up this beast; make it a method of the Command
 
308
    args = []
 
309
    opts = {}
 
310
 
 
311
    cmd_options = command.options()
 
312
    argsover = False
 
313
    while argv:
 
314
        a = argv.pop(0)
 
315
        if argsover:
 
316
            args.append(a)
 
317
            continue
 
318
        elif a == '--':
 
319
            # We've received a standalone -- No more flags
 
320
            argsover = True
 
321
            continue
 
322
        if a[0] == '-':
 
323
            # option names must not be unicode
 
324
            a = str(a)
 
325
            optarg = None
 
326
            if a[1] == '-':
 
327
                mutter("  got option %r", a)
 
328
                if '=' in a:
 
329
                    optname, optarg = a[2:].split('=', 1)
 
330
                else:
 
331
                    optname = a[2:]
 
332
                if optname not in cmd_options:
 
333
                    raise BzrOptionError('unknown long option %r for command %s'
 
334
                        % (a, command.name()))
 
335
            else:
 
336
                shortopt = a[1:]
 
337
                if shortopt in Option.SHORT_OPTIONS:
 
338
                    # Multi-character options must have a space to delimit
 
339
                    # their value
 
340
                    # ^^^ what does this mean? mbp 20051014
 
341
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
342
                else:
 
343
                    # Single character short options, can be chained,
 
344
                    # and have their value appended to their name
 
345
                    shortopt = a[1:2]
 
346
                    if shortopt not in Option.SHORT_OPTIONS:
 
347
                        # We didn't find the multi-character name, and we
 
348
                        # didn't find the single char name
 
349
                        raise BzrError('unknown short option %r' % a)
 
350
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
351
 
 
352
                    if a[2:]:
 
353
                        # There are extra things on this option
 
354
                        # see if it is the value, or if it is another
 
355
                        # short option
 
356
                        optargfn = Option.OPTIONS[optname].type
 
357
                        if optargfn is None:
 
358
                            # This option does not take an argument, so the
 
359
                            # next entry is another short option, pack it back
 
360
                            # into the list
 
361
                            argv.insert(0, '-' + a[2:])
 
362
                        else:
 
363
                            # This option takes an argument, so pack it
 
364
                            # into the array
 
365
                            optarg = a[2:]
 
366
            
 
367
                if optname not in cmd_options:
 
368
                    raise BzrOptionError('unknown short option %r for command'
 
369
                        ' %s' % (shortopt, command.name()))
 
370
            if optname in opts:
 
371
                # XXX: Do we ever want to support this, e.g. for -r?
 
372
                raise BzrError('repeated option %r' % a)
 
373
                
 
374
            option_obj = cmd_options[optname]
 
375
            optargfn = option_obj.type
 
376
            if optargfn:
 
377
                if optarg == None:
 
378
                    if not argv:
 
379
                        raise BzrError('option %r needs an argument' % a)
 
380
                    else:
 
381
                        optarg = argv.pop(0)
 
382
                opts[optname] = optargfn(optarg)
 
383
            else:
 
384
                if optarg != None:
 
385
                    raise BzrError('option %r takes no argument' % optname)
 
386
                opts[optname] = True
 
387
        else:
 
388
            args.append(a)
 
389
    return args, opts
 
390
 
 
391
 
 
392
def _match_argform(cmd, takes_args, args):
 
393
    argdict = {}
 
394
 
 
395
    # step through args and takes_args, allowing appropriate 0-many matches
 
396
    for ap in takes_args:
 
397
        argname = ap[:-1]
 
398
        if ap[-1] == '?':
 
399
            if args:
 
400
                argdict[argname] = args.pop(0)
 
401
        elif ap[-1] == '*': # all remaining arguments
 
402
            if args:
 
403
                argdict[argname + '_list'] = args[:]
 
404
                args = []
 
405
            else:
 
406
                argdict[argname + '_list'] = None
 
407
        elif ap[-1] == '+':
 
408
            if not args:
 
409
                raise BzrCommandError("command %r needs one or more %s"
 
410
                        % (cmd, argname.upper()))
 
411
            else:
 
412
                argdict[argname + '_list'] = args[:]
 
413
                args = []
 
414
        elif ap[-1] == '$': # all but one
 
415
            if len(args) < 2:
 
416
                raise BzrCommandError("command %r needs one or more %s"
 
417
                        % (cmd, argname.upper()))
 
418
            argdict[argname + '_list'] = args[:-1]
 
419
            args[:-1] = []                
 
420
        else:
 
421
            # just a plain arg
 
422
            argname = ap
 
423
            if not args:
 
424
                raise BzrCommandError("command %r requires argument %s"
 
425
                        % (cmd, argname.upper()))
 
426
            else:
 
427
                argdict[argname] = args.pop(0)
 
428
            
 
429
    if args:
 
430
        raise BzrCommandError("extra argument to command %s: %s"
 
431
                              % (cmd, args[0]))
 
432
 
 
433
    return argdict
 
434
 
 
435
 
 
436
 
 
437
def apply_profiled(the_callable, *args, **kwargs):
 
438
    import hotshot
 
439
    import tempfile
 
440
    import hotshot.stats
 
441
    pffileno, pfname = tempfile.mkstemp()
 
442
    try:
 
443
        prof = hotshot.Profile(pfname)
 
444
        try:
 
445
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
 
446
        finally:
 
447
            prof.close()
 
448
        stats = hotshot.stats.load(pfname)
 
449
        stats.strip_dirs()
 
450
        stats.sort_stats('cum')   # 'time'
 
451
        ## XXX: Might like to write to stderr or the trace file instead but
 
452
        ## print_stats seems hardcoded to stdout
 
453
        stats.print_stats(20)
 
454
        return ret
 
455
    finally:
 
456
        os.close(pffileno)
 
457
        os.remove(pfname)
 
458
 
 
459
 
 
460
def apply_lsprofiled(the_callable, *args, **kwargs):
 
461
    from bzrlib.lsprof import profile
 
462
    ret,stats = profile(the_callable,*args,**kwargs)
 
463
    stats.sort()
 
464
    stats.pprint()
 
465
    return ret
 
466
 
 
467
def run_bzr(argv):
 
468
    """Execute a command.
 
469
 
 
470
    This is similar to main(), but without all the trappings for
 
471
    logging and error handling.  
 
472
    
 
473
    argv
 
474
       The command-line arguments, without the program name from argv[0]
 
475
    
 
476
    Returns a command status or raises an exception.
 
477
 
 
478
    Special master options: these must come before the command because
 
479
    they control how the command is interpreted.
 
480
 
 
481
    --no-plugins
 
482
        Do not load plugin modules at all
 
483
 
 
484
    --builtin
 
485
        Only use builtin commands.  (Plugins are still allowed to change
 
486
        other behaviour.)
 
487
 
 
488
    --profile
 
489
        Run under the Python hotshot profiler.
 
490
 
 
491
    --lsprof
 
492
        Run under the Python lsprof profiler.
 
493
    """
 
494
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
495
 
 
496
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
 
497
    opt_no_defaults = False
 
498
 
 
499
    # --no-plugins is handled specially at a very early stage. We need
 
500
    # to load plugins before doing other command parsing so that they
 
501
    # can override commands, but this needs to happen first.
 
502
 
 
503
    for a in argv[:]:
 
504
        if a == '--profile':
 
505
            opt_profile = True
 
506
        elif a == '--lsprof':
 
507
            opt_lsprof = True
 
508
        elif a == '--no-plugins':
 
509
            opt_no_plugins = True
 
510
        elif a == '--builtin':
 
511
            opt_builtin = True
 
512
        elif a in ('--quiet', '-q'):
 
513
            be_quiet()
 
514
        elif a in ('--no-defaults',):
 
515
            opt_no_defaults = True
 
516
        else:
 
517
            continue
 
518
        argv.remove(a)
 
519
 
 
520
    if (not argv) or (argv[0] == '--help'):
 
521
        from bzrlib.help import help
 
522
        if len(argv) > 1:
 
523
            help(argv[1])
 
524
        else:
 
525
            help()
 
526
        return 0
 
527
 
 
528
    if argv[0] == '--version':
 
529
        from bzrlib.builtins import show_version
 
530
        show_version()
 
531
        return 0
 
532
        
 
533
    if not opt_no_plugins:
 
534
        from bzrlib.plugin import load_plugins
 
535
        load_plugins()
 
536
 
 
537
    cmd = str(argv.pop(0))
 
538
 
 
539
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
 
540
    if opt_no_defaults is True:
 
541
        cmd_defaults = []
 
542
    else:
 
543
        cmd_defaults = GlobalConfig().get_command_defaults(cmd_obj.name())
 
544
    try:
 
545
        if opt_lsprof:
 
546
            ret = apply_lsprofiled(cmd_obj.run_argv, argv, cmd_defaults)
 
547
        elif opt_profile:
 
548
            ret = apply_profiled(cmd_obj.run_argv, argv, cmd_defaults)
 
549
        else:
 
550
            ret = cmd_obj.run_argv(argv, cmd_defaults)
 
551
        return ret or 0
 
552
    finally:
 
553
        # reset, in case we may do other commands later within the same process
 
554
        be_quiet(False)
 
555
 
 
556
def display_command(func):
 
557
    """Decorator that suppresses pipe/interrupt errors."""
 
558
    def ignore_pipe(*args, **kwargs):
 
559
        try:
 
560
            result = func(*args, **kwargs)
 
561
            sys.stdout.flush()
 
562
            return result
 
563
        except IOError, e:
 
564
            if not hasattr(e, 'errno'):
 
565
                raise
 
566
            if e.errno != errno.EPIPE:
 
567
                raise
 
568
            pass
 
569
        except KeyboardInterrupt:
 
570
            pass
 
571
    return ignore_pipe
 
572
 
 
573
 
 
574
def main(argv):
 
575
    import bzrlib.ui
 
576
    from bzrlib.ui.text import TextUIFactory
 
577
    ## bzrlib.trace.enable_default_logging()
 
578
    bzrlib.trace.log_startup(argv)
 
579
    bzrlib.ui.ui_factory = TextUIFactory()
 
580
    ret = run_bzr_catch_errors(argv[1:])
 
581
    mutter("return code %d", ret)
 
582
    return ret
 
583
 
 
584
 
 
585
def run_bzr_catch_errors(argv):
 
586
    try:
 
587
        try:
 
588
            return run_bzr(argv)
 
589
        finally:
 
590
            # do this here inside the exception wrappers to catch EPIPE
 
591
            sys.stdout.flush()
 
592
    except Exception, e:
 
593
        # used to handle AssertionError and KeyboardInterrupt
 
594
        # specially here, but hopefully they're handled ok by the logger now
 
595
        import errno
 
596
        if (isinstance(e, IOError) 
 
597
            and hasattr(e, 'errno')
 
598
            and e.errno == errno.EPIPE):
 
599
            bzrlib.trace.note('broken pipe')
 
600
            return 3
 
601
        else:
 
602
            bzrlib.trace.log_exception()
 
603
            if os.environ.get('BZR_PDB'):
 
604
                print '**** entering debugger'
 
605
                import pdb
 
606
                pdb.post_mortem(sys.exc_traceback)
 
607
            return 3
 
608
 
 
609
if __name__ == '__main__':
 
610
    sys.exit(main(sys.argv))