~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Robert Collins
  • Date: 2006-02-11 11:58:06 UTC
  • mto: (1534.1.22 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060211115806-732dabc1e35714ed
Give format3 working trees their own last-revision marker.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
2
 
#
 
1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
# TODO: probably should say which arguments are candidates for glob
19
19
# expansion on windows and do that at the command level.
20
20
 
 
21
# TODO: Help messages for options.
 
22
 
21
23
# TODO: Define arguments by objects, rather than just using names.
22
24
# Those objects can specify the expected type of the argument, which
23
 
# would help with validation and shell completion.  They could also provide
24
 
# help/explanation for that argument in a structured way.
25
 
 
26
 
# TODO: Specific "examples" property on commands for consistent formatting.
 
25
# would help with validation and shell completion.
27
26
 
28
27
# TODO: "--profile=cum", to change sort order.  Is there any value in leaving
29
28
# the profile output behind so it can be interactively examined?
30
29
 
31
 
import codecs
32
 
import errno
 
30
import sys
33
31
import os
34
32
from warnings import warn
35
 
import sys
 
33
from inspect import getdoc
 
34
import errno
36
35
 
37
36
import bzrlib
38
 
import bzrlib.errors as errors
39
 
from bzrlib.errors import (BzrError,
 
37
import bzrlib.trace
 
38
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
 
39
from bzrlib.errors import (BzrError, 
 
40
                           BzrCheckError,
40
41
                           BzrCommandError,
41
 
                           BzrCheckError,
 
42
                           BzrOptionError,
42
43
                           NotBranchError)
43
 
from bzrlib import option
 
44
from bzrlib.revisionspec import RevisionSpec
 
45
from bzrlib import BZRDIR
44
46
from bzrlib.option import Option
45
 
import bzrlib.osutils
46
 
from bzrlib.revisionspec import RevisionSpec
47
 
from bzrlib.symbol_versioning import (deprecated_method, zero_eight)
48
 
import bzrlib.trace
49
 
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
50
47
 
51
48
plugin_cmds = {}
52
49
 
53
50
 
54
51
def register_command(cmd, decorate=False):
55
 
    """Utility function to help register a command
56
 
 
57
 
    :param cmd: Command subclass to register
58
 
    :param decorate: If true, allow overriding an existing command
59
 
        of the same name; the old command is returned by this function.
60
 
        Otherwise it is an error to try to override an existing command.
61
 
    """
 
52
    "Utility function to help register a command"
62
53
    global plugin_cmds
63
54
    k = cmd.__name__
64
55
    if k.startswith("cmd_"):
67
58
        k_unsquished = k
68
59
    if not plugin_cmds.has_key(k_unsquished):
69
60
        plugin_cmds[k_unsquished] = cmd
70
 
        mutter('registered plugin command %s', k_unsquished)
 
61
        mutter('registered plugin command %s', k_unsquished)      
71
62
        if decorate and k_unsquished in builtin_command_names():
72
63
            return _builtin_commands()[k_unsquished]
73
64
    elif decorate:
94
85
    builtins = bzrlib.builtins.__dict__
95
86
    for name in builtins:
96
87
        if name.startswith("cmd_"):
97
 
            real_name = _unsquish_command_name(name)
 
88
            real_name = _unsquish_command_name(name)        
98
89
            r[real_name] = builtins[name]
99
90
    return r
 
91
 
100
92
            
101
93
 
102
94
def builtin_command_names():
148
140
    if cmd_obj:
149
141
        return cmd_obj
150
142
 
151
 
    raise BzrCommandError('unknown command "%s"' % cmd_name)
 
143
    raise BzrCommandError("unknown command %r" % cmd_name)
152
144
 
153
145
 
154
146
class Command(object):
192
184
    hidden
193
185
        If true, this command isn't advertised.  This is typically
194
186
        for commands intended for expert users.
195
 
 
196
 
    encoding_type
197
 
        Command objects will get a 'outf' attribute, which has been
198
 
        setup to properly handle encoding of unicode strings.
199
 
        encoding_type determines what will happen when characters cannot
200
 
        be encoded
201
 
            strict - abort if we cannot decode
202
 
            replace - put in a bogus character (typically '?')
203
 
            exact - do not encode sys.stdout
204
 
 
205
187
    """
206
188
    aliases = []
207
189
    takes_args = []
208
190
    takes_options = []
209
 
    encoding_type = 'strict'
210
191
 
211
192
    hidden = False
212
193
    
222
203
        r = dict()
223
204
        r['help'] = Option.OPTIONS['help']
224
205
        for o in self.takes_options:
225
 
            if isinstance(o, basestring):
 
206
            if not isinstance(o, Option):
226
207
                o = Option.OPTIONS[o]
227
208
            r[o.name] = o
228
209
        return r
229
210
 
230
 
    def _setup_outf(self):
231
 
        """Return a file linked to stdout, which has proper encoding."""
232
 
        assert self.encoding_type in ['strict', 'exact', 'replace']
233
 
 
234
 
        # Originally I was using self.stdout, but that looks
235
 
        # *way* too much like sys.stdout
236
 
        if self.encoding_type == 'exact':
237
 
            self.outf = sys.stdout
238
 
            return
239
 
 
240
 
        output_encoding = bzrlib.osutils.get_terminal_encoding()
241
 
 
242
 
        # use 'replace' so that we don't abort if trying to write out
243
 
        # in e.g. the default C locale.
244
 
        self.outf = codecs.getwriter(output_encoding)(sys.stdout, errors=self.encoding_type)
245
 
        # For whatever reason codecs.getwriter() does not advertise its encoding
246
 
        # it just returns the encoding of the wrapped file, which is completely
247
 
        # bogus. So set the attribute, so we can find the correct encoding later.
248
 
        self.outf.encoding = output_encoding
249
 
 
250
 
    @deprecated_method(zero_eight)
251
211
    def run_argv(self, argv):
252
 
        """Parse command line and run.
253
 
        
254
 
        See run_argv_aliases for the 0.8 and beyond api.
255
 
        """
256
 
        return self.run_argv_aliases(argv)
257
 
 
258
 
    def run_argv_aliases(self, argv, alias_argv=None):
259
 
        """Parse the command line and run with extra aliases in alias_argv."""
260
 
        if argv is None:
261
 
            warn("Passing None for [] is deprecated from bzrlib 0.10", 
262
 
                 DeprecationWarning, stacklevel=2)
263
 
            argv = []
264
 
        args, opts = parse_args(self, argv, alias_argv)
 
212
        """Parse command line and run."""
 
213
        args, opts = parse_args(self, argv)
265
214
        if 'help' in opts:  # e.g. bzr add --help
266
215
            from bzrlib.help import help_on_command
267
216
            help_on_command(self.name())
268
217
            return 0
 
218
        # XXX: This should be handled by the parser
 
219
        allowed_names = self.options().keys()
 
220
        for oname in opts:
 
221
            if oname not in allowed_names:
 
222
                raise BzrCommandError("option '--%s' is not allowed for command %r"
 
223
                                      % (oname, self.name()))
269
224
        # mix arguments and options into one dictionary
270
225
        cmdargs = _match_argform(self.name(), self.takes_args, args)
271
226
        cmdopts = {}
275
230
        all_cmd_args = cmdargs.copy()
276
231
        all_cmd_args.update(cmdopts)
277
232
 
278
 
        self._setup_outf()
279
 
 
280
233
        return self.run(**all_cmd_args)
281
234
    
282
235
    def run(self):
289
242
        shell error code if not.  It's OK for this method to allow
290
243
        an exception to raise up.
291
244
        """
292
 
        raise NotImplementedError('no implementation of command %r' 
293
 
                                  % self.name())
 
245
        raise NotImplementedError()
 
246
 
294
247
 
295
248
    def help(self):
296
249
        """Return help message for this class."""
297
 
        from inspect import getdoc
298
250
        if self.__doc__ is Command.__doc__:
299
251
            return None
300
252
        return getdoc(self)
302
254
    def name(self):
303
255
        return _unsquish_command_name(self.__class__.__name__)
304
256
 
305
 
    def plugin_name(self):
306
 
        """Get the name of the plugin that provides this command.
307
 
 
308
 
        :return: The name of the plugin or None if the command is builtin.
309
 
        """
310
 
        mod_parts = self.__module__.split('.')
311
 
        if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
312
 
            return mod_parts[2]
313
 
        else:
314
 
            return None
315
 
 
316
257
 
317
258
def parse_spec(spec):
318
259
    """
345
286
        parsed = [spec, None]
346
287
    return parsed
347
288
 
348
 
def parse_args(command, argv, alias_argv=None):
 
289
def parse_args(command, argv):
349
290
    """Parse command line.
350
291
    
351
292
    Arguments and options are parsed at this level before being passed
353
294
    lookup table, something about the available options, what optargs
354
295
    they take, and which commands will accept them.
355
296
    """
356
 
    # TODO: make it a method of the Command?
357
 
    parser = option.get_optparser(command.options())
358
 
    if alias_argv is not None:
359
 
        args = alias_argv + argv
360
 
    else:
361
 
        args = argv
362
 
 
363
 
    options, args = parser.parse_args(args)
364
 
    opts = dict([(k, v) for k, v in options.__dict__.iteritems() if 
365
 
                 v is not option.OptionParser.DEFAULT_VALUE])
 
297
    # TODO: chop up this beast; make it a method of the Command
 
298
    args = []
 
299
    opts = {}
 
300
 
 
301
    cmd_options = command.options()
 
302
    argsover = False
 
303
    while argv:
 
304
        a = argv.pop(0)
 
305
        if argsover:
 
306
            args.append(a)
 
307
            continue
 
308
        elif a == '--':
 
309
            # We've received a standalone -- No more flags
 
310
            argsover = True
 
311
            continue
 
312
        if a[0] == '-':
 
313
            # option names must not be unicode
 
314
            a = str(a)
 
315
            optarg = None
 
316
            if a[1] == '-':
 
317
                mutter("  got option %r", a)
 
318
                if '=' in a:
 
319
                    optname, optarg = a[2:].split('=', 1)
 
320
                else:
 
321
                    optname = a[2:]
 
322
                if optname not in cmd_options:
 
323
                    raise BzrOptionError('unknown long option %r for command %s'
 
324
                        % (a, command.name()))
 
325
            else:
 
326
                shortopt = a[1:]
 
327
                if shortopt in Option.SHORT_OPTIONS:
 
328
                    # Multi-character options must have a space to delimit
 
329
                    # their value
 
330
                    # ^^^ what does this mean? mbp 20051014
 
331
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
332
                else:
 
333
                    # Single character short options, can be chained,
 
334
                    # and have their value appended to their name
 
335
                    shortopt = a[1:2]
 
336
                    if shortopt not in Option.SHORT_OPTIONS:
 
337
                        # We didn't find the multi-character name, and we
 
338
                        # didn't find the single char name
 
339
                        raise BzrError('unknown short option %r' % a)
 
340
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
341
 
 
342
                    if a[2:]:
 
343
                        # There are extra things on this option
 
344
                        # see if it is the value, or if it is another
 
345
                        # short option
 
346
                        optargfn = Option.OPTIONS[optname].type
 
347
                        if optargfn is None:
 
348
                            # This option does not take an argument, so the
 
349
                            # next entry is another short option, pack it back
 
350
                            # into the list
 
351
                            argv.insert(0, '-' + a[2:])
 
352
                        else:
 
353
                            # This option takes an argument, so pack it
 
354
                            # into the array
 
355
                            optarg = a[2:]
 
356
            
 
357
                if optname not in cmd_options:
 
358
                    raise BzrOptionError('unknown short option %r for command'
 
359
                        ' %s' % (shortopt, command.name()))
 
360
            if optname in opts:
 
361
                # XXX: Do we ever want to support this, e.g. for -r?
 
362
                raise BzrError('repeated option %r' % a)
 
363
                
 
364
            option_obj = cmd_options[optname]
 
365
            optargfn = option_obj.type
 
366
            if optargfn:
 
367
                if optarg == None:
 
368
                    if not argv:
 
369
                        raise BzrError('option %r needs an argument' % a)
 
370
                    else:
 
371
                        optarg = argv.pop(0)
 
372
                opts[optname] = optargfn(optarg)
 
373
            else:
 
374
                if optarg != None:
 
375
                    raise BzrError('option %r takes no argument' % optname)
 
376
                opts[optname] = True
 
377
        else:
 
378
            args.append(a)
366
379
    return args, opts
367
380
 
368
381
 
393
406
                raise BzrCommandError("command %r needs one or more %s"
394
407
                        % (cmd, argname.upper()))
395
408
            argdict[argname + '_list'] = args[:-1]
396
 
            args[:-1] = []
 
409
            args[:-1] = []                
397
410
        else:
398
411
            # just a plain arg
399
412
            argname = ap
434
447
        os.remove(pfname)
435
448
 
436
449
 
437
 
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
 
450
def apply_lsprofiled(the_callable, *args, **kwargs):
438
451
    from bzrlib.lsprof import profile
439
 
    import cPickle
440
 
    ret, stats = profile(the_callable, *args, **kwargs)
 
452
    ret,stats = profile(the_callable,*args,**kwargs)
441
453
    stats.sort()
442
 
    if filename is None:
443
 
        stats.pprint()
444
 
    else:
445
 
        stats.freeze()
446
 
        cPickle.dump(stats, open(filename, 'w'), 2)
447
 
        print 'Profile data written to %r.' % filename
 
454
    stats.pprint()
448
455
    return ret
449
456
 
450
 
 
451
 
def get_alias(cmd):
452
 
    """Return an expanded alias, or None if no alias exists"""
453
 
    import bzrlib.config
454
 
    alias = bzrlib.config.GlobalConfig().get_alias(cmd)
455
 
    if (alias):
456
 
        return alias.split(' ')
457
 
    return None
458
 
 
459
 
 
460
457
def run_bzr(argv):
461
458
    """Execute a command.
462
459
 
465
462
    
466
463
    argv
467
464
       The command-line arguments, without the program name from argv[0]
468
 
       These should already be decoded. All library/test code calling
469
 
       run_bzr should be passing valid strings (don't need decoding).
470
465
    
471
466
    Returns a command status or raises an exception.
472
467
 
476
471
    --no-plugins
477
472
        Do not load plugin modules at all
478
473
 
479
 
    --no-aliases
480
 
        Do not allow aliases
481
 
 
482
474
    --builtin
483
475
        Only use builtin commands.  (Plugins are still allowed to change
484
476
        other behaviour.)
489
481
    --lsprof
490
482
        Run under the Python lsprof profiler.
491
483
    """
492
 
    argv = list(argv)
 
484
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
493
485
 
494
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin =  \
495
 
                opt_no_aliases = False
496
 
    opt_lsprof_file = None
 
486
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
497
487
 
498
488
    # --no-plugins is handled specially at a very early stage. We need
499
489
    # to load plugins before doing other command parsing so that they
500
490
    # can override commands, but this needs to happen first.
501
491
 
502
 
    argv_copy = []
503
 
    i = 0
504
 
    while i < len(argv):
505
 
        a = argv[i]
 
492
    for a in argv:
506
493
        if a == '--profile':
507
494
            opt_profile = True
508
495
        elif a == '--lsprof':
509
496
            opt_lsprof = True
510
 
        elif a == '--lsprof-file':
511
 
            opt_lsprof = True
512
 
            opt_lsprof_file = argv[i + 1]
513
 
            i += 1
514
497
        elif a == '--no-plugins':
515
498
            opt_no_plugins = True
516
 
        elif a == '--no-aliases':
517
 
            opt_no_aliases = True
518
499
        elif a == '--builtin':
519
500
            opt_builtin = True
520
501
        elif a in ('--quiet', '-q'):
521
502
            be_quiet()
522
503
        else:
523
 
            argv_copy.append(a)
524
 
        i += 1
 
504
            continue
 
505
        argv.remove(a)
525
506
 
526
 
    argv = argv_copy
527
 
    if (not argv):
528
 
        from bzrlib.builtins import cmd_help
529
 
        cmd_help().run_argv_aliases([])
 
507
    if (not argv) or (argv[0] == '--help'):
 
508
        from bzrlib.help import help
 
509
        if len(argv) > 1:
 
510
            help(argv[1])
 
511
        else:
 
512
            help()
530
513
        return 0
531
514
 
532
515
    if argv[0] == '--version':
533
 
        from bzrlib.version import show_version
 
516
        from bzrlib.builtins import show_version
534
517
        show_version()
535
518
        return 0
536
519
        
541
524
        from bzrlib.plugin import disable_plugins
542
525
        disable_plugins()
543
526
 
544
 
    alias_argv = None
545
 
 
546
 
    if not opt_no_aliases:
547
 
        alias_argv = get_alias(argv[0])
548
 
        if alias_argv:
549
 
            alias_argv = [a.decode(bzrlib.user_encoding) for a in alias_argv]
550
 
            argv[0] = alias_argv.pop(0)
551
 
 
552
527
    cmd = str(argv.pop(0))
553
528
 
554
529
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
555
 
    if not getattr(cmd_obj.run_argv, 'is_deprecated', False):
556
 
        run = cmd_obj.run_argv
557
 
        run_argv = [argv]
558
 
    else:
559
 
        run = cmd_obj.run_argv_aliases
560
 
        run_argv = [argv, alias_argv]
561
530
 
562
531
    try:
563
532
        if opt_lsprof:
564
 
            ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
 
533
            ret = apply_lsprofiled(cmd_obj.run_argv, argv)
565
534
        elif opt_profile:
566
 
            ret = apply_profiled(run, *run_argv)
 
535
            ret = apply_profiled(cmd_obj.run_argv, argv)
567
536
        else:
568
 
            ret = run(*run_argv)
 
537
            ret = cmd_obj.run_argv(argv)
569
538
        return ret or 0
570
539
    finally:
571
540
        # reset, in case we may do other commands later within the same process
582
551
            if not hasattr(e, 'errno'):
583
552
                raise
584
553
            if e.errno != errno.EPIPE:
585
 
                # Win32 raises IOError with errno=0 on a broken pipe
586
 
                if sys.platform != 'win32' or e.errno != 0:
587
 
                    raise
 
554
                raise
588
555
            pass
589
556
        except KeyboardInterrupt:
590
557
            pass
594
561
def main(argv):
595
562
    import bzrlib.ui
596
563
    from bzrlib.ui.text import TextUIFactory
 
564
    ## bzrlib.trace.enable_default_logging()
 
565
    bzrlib.trace.log_startup(argv)
597
566
    bzrlib.ui.ui_factory = TextUIFactory()
598
 
    argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
599
 
    ret = run_bzr_catch_errors(argv)
 
567
    ret = run_bzr_catch_errors(argv[1:])
600
568
    mutter("return code %d", ret)
601
569
    return ret
602
570
 
603
571
 
604
572
def run_bzr_catch_errors(argv):
605
573
    try:
606
 
        return run_bzr(argv)
607
 
        # do this here inside the exception wrappers to catch EPIPE
608
 
        sys.stdout.flush()
 
574
        try:
 
575
            return run_bzr(argv)
 
576
        finally:
 
577
            # do this here inside the exception wrappers to catch EPIPE
 
578
            sys.stdout.flush()
609
579
    except Exception, e:
610
580
        # used to handle AssertionError and KeyboardInterrupt
611
581
        # specially here, but hopefully they're handled ok by the logger now
612
 
        bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
613
 
        if os.environ.get('BZR_PDB'):
614
 
            print '**** entering debugger'
615
 
            import pdb
616
 
            pdb.post_mortem(sys.exc_traceback)
617
 
        return 3
 
582
        import errno
 
583
        if (isinstance(e, IOError) 
 
584
            and hasattr(e, 'errno')
 
585
            and e.errno == errno.EPIPE):
 
586
            bzrlib.trace.note('broken pipe')
 
587
            return 3
 
588
        else:
 
589
            bzrlib.trace.log_exception()
 
590
            if os.environ.get('BZR_PDB'):
 
591
                print '**** entering debugger'
 
592
                import pdb
 
593
                pdb.post_mortem(sys.exc_traceback)
 
594
            return 3
618
595
 
619
596
if __name__ == '__main__':
620
597
    sys.exit(main(sys.argv))