~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Aaron Bentley
  • Date: 2005-10-03 16:53:39 UTC
  • mto: (1185.25.1)
  • mto: This revision was merged to the branch mainline in revision 1419.
  • Revision ID: abentley@panoramicfeedback.com-20051003165339-9ee4d484477fd164
Ignored user-installed plugins

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
# Those objects can specify the expected type of the argument, which
25
25
# would help with validation and shell completion.
26
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?
 
27
 
 
28
# TODO: Help messages for options.
 
29
 
 
30
# TODO: Define arguments by objects, rather than just using names.
 
31
# Those objects can specify the expected type of the argument, which
 
32
# would help with validation and shell completion.
 
33
 
 
34
 
29
35
 
30
36
import sys
31
37
import os
32
38
from warnings import warn
33
39
from inspect import getdoc
34
 
import errno
35
40
 
36
41
import bzrlib
37
42
import bzrlib.trace
38
 
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
39
 
from bzrlib.errors import (BzrError, 
40
 
                           BzrCheckError,
41
 
                           BzrCommandError,
42
 
                           BzrOptionError,
43
 
                           NotBranchError)
 
43
from bzrlib.trace import mutter, note, log_error, warning
 
44
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
44
45
from bzrlib.revisionspec import RevisionSpec
45
46
from bzrlib import BZRDIR
46
 
from bzrlib.option import Option
47
47
 
48
48
plugin_cmds = {}
49
49
 
50
50
 
51
 
def register_command(cmd, decorate=False):
 
51
def register_command(cmd):
52
52
    "Utility function to help register a command"
53
53
    global plugin_cmds
54
54
    k = cmd.__name__
59
59
    if not plugin_cmds.has_key(k_unsquished):
60
60
        plugin_cmds[k_unsquished] = cmd
61
61
        mutter('registered plugin command %s', k_unsquished)      
62
 
        if decorate and k_unsquished in builtin_command_names():
63
 
            return _builtin_commands()[k_unsquished]
64
 
    elif decorate:
65
 
        result = plugin_cmds[k_unsquished]
66
 
        plugin_cmds[k_unsquished] = cmd
67
 
        return result
68
62
    else:
69
63
        log_error('Two plugins defined the same command: %r' % k)
70
64
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
79
73
    return cmd[4:].replace('_','-')
80
74
 
81
75
 
 
76
def _parse_revision_str(revstr):
 
77
    """This handles a revision string -> revno.
 
78
 
 
79
    This always returns a list.  The list will have one element for
 
80
    each revision.
 
81
 
 
82
    >>> _parse_revision_str('234')
 
83
    [<RevisionSpec_int 234>]
 
84
    >>> _parse_revision_str('234..567')
 
85
    [<RevisionSpec_int 234>, <RevisionSpec_int 567>]
 
86
    >>> _parse_revision_str('..')
 
87
    [<RevisionSpec None>, <RevisionSpec None>]
 
88
    >>> _parse_revision_str('..234')
 
89
    [<RevisionSpec None>, <RevisionSpec_int 234>]
 
90
    >>> _parse_revision_str('234..')
 
91
    [<RevisionSpec_int 234>, <RevisionSpec None>]
 
92
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
 
93
    [<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
 
94
    >>> _parse_revision_str('234....789') # Error?
 
95
    [<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
 
96
    >>> _parse_revision_str('revid:test@other.com-234234')
 
97
    [<RevisionSpec_revid revid:test@other.com-234234>]
 
98
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
 
99
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
 
100
    >>> _parse_revision_str('revid:test@other.com-234234..23')
 
101
    [<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
 
102
    >>> _parse_revision_str('date:2005-04-12')
 
103
    [<RevisionSpec_date date:2005-04-12>]
 
104
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
 
105
    [<RevisionSpec_date date:2005-04-12 12:24:33>]
 
106
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
 
107
    [<RevisionSpec_date date:2005-04-12T12:24:33>]
 
108
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
 
109
    [<RevisionSpec_date date:2005-04-12,12:24:33>]
 
110
    >>> _parse_revision_str('-5..23')
 
111
    [<RevisionSpec_int -5>, <RevisionSpec_int 23>]
 
112
    >>> _parse_revision_str('-5')
 
113
    [<RevisionSpec_int -5>]
 
114
    >>> _parse_revision_str('123a')
 
115
    Traceback (most recent call last):
 
116
      ...
 
117
    BzrError: No namespace registered for string: '123a'
 
118
    >>> _parse_revision_str('abc')
 
119
    Traceback (most recent call last):
 
120
      ...
 
121
    BzrError: No namespace registered for string: 'abc'
 
122
    """
 
123
    import re
 
124
    old_format_re = re.compile('\d*:\d*')
 
125
    m = old_format_re.match(revstr)
 
126
    revs = []
 
127
    if m:
 
128
        warning('Colon separator for revision numbers is deprecated.'
 
129
                ' Use .. instead')
 
130
        for rev in revstr.split(':'):
 
131
            if rev:
 
132
                revs.append(RevisionSpec(int(rev)))
 
133
            else:
 
134
                revs.append(RevisionSpec(None))
 
135
    else:
 
136
        for x in revstr.split('..'):
 
137
            if not x:
 
138
                revs.append(RevisionSpec(None))
 
139
            else:
 
140
                revs.append(RevisionSpec(x))
 
141
    return revs
 
142
 
 
143
 
82
144
def _builtin_commands():
83
145
    import bzrlib.builtins
84
146
    r = {}
168
230
        List of argument forms, marked with whether they are optional,
169
231
        repeated, etc.
170
232
 
171
 
                Examples:
172
 
 
173
 
                ['to_location', 'from_branch?', 'file*']
174
 
 
175
 
                'to_location' is required
176
 
                'from_branch' is optional
177
 
                'file' can be specified 0 or more times
178
 
 
179
233
    takes_options
180
 
        List of options that may be given for this command.  These can
181
 
        be either strings, referring to globally-defined options,
182
 
        or option objects.  Retrieve through options().
 
234
        List of options that may be given for this command.
183
235
 
184
236
    hidden
185
237
        If true, this command isn't advertised.  This is typically
186
238
        for commands intended for expert users.
187
239
    """
188
240
    aliases = []
 
241
    
189
242
    takes_args = []
190
243
    takes_options = []
191
244
 
196
249
        if self.__doc__ == Command.__doc__:
197
250
            warn("No help message set for %r" % self)
198
251
 
199
 
    def options(self):
200
 
        """Return dict of valid options for this command.
201
 
 
202
 
        Maps from long option name to option object."""
203
 
        r = dict()
204
 
        r['help'] = Option.OPTIONS['help']
205
 
        for o in self.takes_options:
206
 
            if not isinstance(o, Option):
207
 
                o = Option.OPTIONS[o]
208
 
            r[o.name] = o
209
 
        return r
210
252
 
211
253
    def run_argv(self, argv):
212
254
        """Parse command line and run."""
213
 
        args, opts = parse_args(self, argv)
 
255
        args, opts = parse_args(argv)
 
256
 
214
257
        if 'help' in opts:  # e.g. bzr add --help
215
258
            from bzrlib.help import help_on_command
216
259
            help_on_command(self.name())
217
260
            return 0
218
 
        # XXX: This should be handled by the parser
219
 
        allowed_names = self.options().keys()
 
261
 
 
262
        # check options are reasonable
 
263
        allowed = self.takes_options
220
264
        for oname in opts:
221
 
            if oname not in allowed_names:
 
265
            if oname not in allowed:
222
266
                raise BzrCommandError("option '--%s' is not allowed for command %r"
223
267
                                      % (oname, self.name()))
 
268
 
224
269
        # mix arguments and options into one dictionary
225
270
        cmdargs = _match_argform(self.name(), self.takes_args, args)
226
271
        cmdopts = {}
231
276
        all_cmd_args.update(cmdopts)
232
277
 
233
278
        return self.run(**all_cmd_args)
 
279
 
234
280
    
235
281
    def run(self):
236
282
        """Actually run the command.
286
332
        parsed = [spec, None]
287
333
    return parsed
288
334
 
289
 
def parse_args(command, argv):
 
335
 
 
336
# list of all available options; the rhs can be either None for an
 
337
# option that takes no argument, or a constructor function that checks
 
338
# the type.
 
339
OPTIONS = {
 
340
    'all':                    None,
 
341
    'basis':                  str,
 
342
    'diff-options':           str,
 
343
    'help':                   None,
 
344
    'file':                   unicode,
 
345
    'force':                  None,
 
346
    'format':                 unicode,
 
347
    'forward':                None,
 
348
    'message':                unicode,
 
349
    'no-recurse':             None,
 
350
    'profile':                None,
 
351
    'revision':               _parse_revision_str,
 
352
    'short':                  None,
 
353
    'show-ids':               None,
 
354
    'timezone':               str,
 
355
    'verbose':                None,
 
356
    'version':                None,
 
357
    'email':                  None,
 
358
    'unchanged':              None,
 
359
    'update':                 None,
 
360
    'long':                   None,
 
361
    'root':                   str,
 
362
    'no-backup':              None,
 
363
    'pattern':                str,
 
364
    }
 
365
 
 
366
SHORT_OPTIONS = {
 
367
    'F':                      'file', 
 
368
    'h':                      'help',
 
369
    'm':                      'message',
 
370
    'r':                      'revision',
 
371
    'v':                      'verbose',
 
372
    'l':                      'long',
 
373
}
 
374
 
 
375
 
 
376
def parse_args(argv):
290
377
    """Parse command line.
291
378
    
292
379
    Arguments and options are parsed at this level before being passed
293
380
    down to specific command handlers.  This routine knows, from a
294
381
    lookup table, something about the available options, what optargs
295
382
    they take, and which commands will accept them.
 
383
 
 
384
    >>> parse_args('--help'.split())
 
385
    ([], {'help': True})
 
386
    >>> parse_args('help -- --invalidcmd'.split())
 
387
    (['help', '--invalidcmd'], {})
 
388
    >>> parse_args('--version'.split())
 
389
    ([], {'version': True})
 
390
    >>> parse_args('status --all'.split())
 
391
    (['status'], {'all': True})
 
392
    >>> parse_args('commit --message=biter'.split())
 
393
    (['commit'], {'message': u'biter'})
 
394
    >>> parse_args('log -r 500'.split())
 
395
    (['log'], {'revision': [<RevisionSpec_int 500>]})
 
396
    >>> parse_args('log -r500..600'.split())
 
397
    (['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
 
398
    >>> parse_args('log -vr500..600'.split())
 
399
    (['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
 
400
    >>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
 
401
    (['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
296
402
    """
297
 
    # TODO: chop up this beast; make it a method of the Command
298
403
    args = []
299
404
    opts = {}
300
405
 
301
 
    cmd_options = command.options()
302
406
    argsover = False
303
407
    while argv:
304
408
        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] == '-':
 
409
        if not argsover and a[0] == '-':
313
410
            # option names must not be unicode
314
411
            a = str(a)
315
412
            optarg = None
316
413
            if a[1] == '-':
317
 
                mutter("  got option %r", a)
 
414
                if a == '--':
 
415
                    # We've received a standalone -- No more flags
 
416
                    argsover = True
 
417
                    continue
 
418
                mutter("  got option %r" % a)
318
419
                if '=' in a:
319
420
                    optname, optarg = a[2:].split('=', 1)
320
421
                else:
321
422
                    optname = a[2:]
322
 
                if optname not in cmd_options:
323
 
                    raise BzrOptionError('unknown long option %r for command %s'
324
 
                        % (a, command.name()))
 
423
                if optname not in OPTIONS:
 
424
                    raise BzrError('unknown long option %r' % a)
325
425
            else:
326
426
                shortopt = a[1:]
327
 
                if shortopt in Option.SHORT_OPTIONS:
 
427
                if shortopt in SHORT_OPTIONS:
328
428
                    # Multi-character options must have a space to delimit
329
429
                    # their value
330
 
                    # ^^^ what does this mean? mbp 20051014
331
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
430
                    optname = SHORT_OPTIONS[shortopt]
332
431
                else:
333
432
                    # Single character short options, can be chained,
334
433
                    # and have their value appended to their name
335
434
                    shortopt = a[1:2]
336
 
                    if shortopt not in Option.SHORT_OPTIONS:
 
435
                    if shortopt not in SHORT_OPTIONS:
337
436
                        # We didn't find the multi-character name, and we
338
437
                        # didn't find the single char name
339
438
                        raise BzrError('unknown short option %r' % a)
340
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
439
                    optname = SHORT_OPTIONS[shortopt]
341
440
 
342
441
                    if a[2:]:
343
442
                        # There are extra things on this option
344
443
                        # see if it is the value, or if it is another
345
444
                        # short option
346
 
                        optargfn = Option.OPTIONS[optname].type
 
445
                        optargfn = OPTIONS[optname]
347
446
                        if optargfn is None:
348
447
                            # This option does not take an argument, so the
349
448
                            # next entry is another short option, pack it back
354
453
                            # into the array
355
454
                            optarg = a[2:]
356
455
            
357
 
                if optname not in cmd_options:
358
 
                    raise BzrOptionError('unknown short option %r for command'
359
 
                        ' %s' % (shortopt, command.name()))
360
456
            if optname in opts:
361
457
                # XXX: Do we ever want to support this, e.g. for -r?
362
458
                raise BzrError('repeated option %r' % a)
363
459
                
364
 
            option_obj = cmd_options[optname]
365
 
            optargfn = option_obj.type
 
460
            optargfn = OPTIONS[optname]
366
461
            if optargfn:
367
462
                if optarg == None:
368
463
                    if not argv:
376
471
                opts[optname] = True
377
472
        else:
378
473
            args.append(a)
 
474
 
379
475
    return args, opts
380
476
 
381
477
 
 
478
 
 
479
 
382
480
def _match_argform(cmd, takes_args, args):
383
481
    argdict = {}
384
482
 
427
525
def apply_profiled(the_callable, *args, **kwargs):
428
526
    import hotshot
429
527
    import tempfile
430
 
    import hotshot.stats
431
528
    pffileno, pfname = tempfile.mkstemp()
432
529
    try:
433
530
        prof = hotshot.Profile(pfname)
435
532
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
436
533
        finally:
437
534
            prof.close()
 
535
 
 
536
        import hotshot.stats
438
537
        stats = hotshot.stats.load(pfname)
439
 
        stats.strip_dirs()
440
 
        stats.sort_stats('cum')   # 'time'
 
538
        #stats.strip_dirs()
 
539
        stats.sort_stats('time')
441
540
        ## XXX: Might like to write to stderr or the trace file instead but
442
541
        ## print_stats seems hardcoded to stdout
443
542
        stats.print_stats(20)
 
543
 
444
544
        return ret
445
545
    finally:
446
546
        os.close(pffileno)
447
547
        os.remove(pfname)
448
548
 
449
549
 
450
 
def apply_lsprofiled(the_callable, *args, **kwargs):
451
 
    from bzrlib.lsprof import profile
452
 
    ret,stats = profile(the_callable,*args,**kwargs)
453
 
    stats.sort()
454
 
    stats.pprint()
455
 
    return ret
456
 
 
457
550
def run_bzr(argv):
458
551
    """Execute a command.
459
552
 
476
569
        other behaviour.)
477
570
 
478
571
    --profile
479
 
        Run under the Python hotshot profiler.
480
 
 
481
 
    --lsprof
482
 
        Run under the Python lsprof profiler.
 
572
        Run under the Python profiler.
483
573
    """
 
574
    
484
575
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
485
576
 
486
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
 
577
    opt_profile = opt_no_plugins = opt_builtin = False
487
578
 
488
579
    # --no-plugins is handled specially at a very early stage. We need
489
580
    # to load plugins before doing other command parsing so that they
492
583
    for a in argv:
493
584
        if a == '--profile':
494
585
            opt_profile = True
495
 
        elif a == '--lsprof':
496
 
            opt_lsprof = True
497
586
        elif a == '--no-plugins':
498
587
            opt_no_plugins = True
499
588
        elif a == '--builtin':
500
589
            opt_builtin = True
501
 
        elif a in ('--quiet', '-q'):
502
 
            be_quiet()
503
590
        else:
504
 
            continue
 
591
            break
505
592
        argv.remove(a)
506
593
 
507
594
    if (not argv) or (argv[0] == '--help'):
525
612
 
526
613
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
527
614
 
528
 
    try:
529
 
        if opt_lsprof:
530
 
            ret = apply_lsprofiled(cmd_obj.run_argv, argv)
531
 
        elif opt_profile:
532
 
            ret = apply_profiled(cmd_obj.run_argv, argv)
533
 
        else:
534
 
            ret = cmd_obj.run_argv(argv)
535
 
        return ret or 0
536
 
    finally:
537
 
        # reset, in case we may do other commands later within the same process
538
 
        be_quiet(False)
539
 
 
540
 
def display_command(func):
541
 
    """Decorator that suppresses pipe/interrupt errors."""
542
 
    def ignore_pipe(*args, **kwargs):
543
 
        try:
544
 
            result = func(*args, **kwargs)
545
 
            sys.stdout.flush()
546
 
            return result
547
 
        except IOError, e:
548
 
            if not hasattr(e, 'errno'):
549
 
                raise
550
 
            if e.errno != errno.EPIPE:
551
 
                raise
552
 
            pass
553
 
        except KeyboardInterrupt:
554
 
            pass
555
 
    return ignore_pipe
 
615
    if opt_profile:
 
616
        ret = apply_profiled(cmd_obj.run_argv, argv)
 
617
    else:
 
618
        ret = cmd_obj.run_argv(argv)
 
619
    return ret or 0
556
620
 
557
621
 
558
622
def main(argv):
559
623
    import bzrlib.ui
560
 
    from bzrlib.ui.text import TextUIFactory
561
 
    ## bzrlib.trace.enable_default_logging()
562
624
    bzrlib.trace.log_startup(argv)
563
 
    bzrlib.ui.ui_factory = TextUIFactory()
564
 
    ret = run_bzr_catch_errors(argv[1:])
565
 
    mutter("return code %d", ret)
566
 
    return ret
 
625
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
626
 
 
627
    return run_bzr_catch_errors(argv[1:])
567
628
 
568
629
 
569
630
def run_bzr_catch_errors(argv):
570
631
    try:
571
632
        try:
572
 
            return run_bzr(argv)
573
 
        finally:
574
 
            # do this here inside the exception wrappers to catch EPIPE
575
 
            sys.stdout.flush()
 
633
            try:
 
634
                return run_bzr(argv)
 
635
            finally:
 
636
                # do this here inside the exception wrappers to catch EPIPE
 
637
                sys.stdout.flush()
 
638
        #wrap common errors as CommandErrors.
 
639
        except (NotBranchError,), e:
 
640
            raise BzrCommandError(str(e))
 
641
    except BzrCommandError, e:
 
642
        # command line syntax error, etc
 
643
        log_error(str(e))
 
644
        return 1
 
645
    except BzrError, e:
 
646
        bzrlib.trace.log_exception()
 
647
        return 1
 
648
    except AssertionError, e:
 
649
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
 
650
        return 3
 
651
    except KeyboardInterrupt, e:
 
652
        bzrlib.trace.log_exception('interrupted')
 
653
        return 2
576
654
    except Exception, e:
577
 
        # used to handle AssertionError and KeyboardInterrupt
578
 
        # specially here, but hopefully they're handled ok by the logger now
579
655
        import errno
580
656
        if (isinstance(e, IOError) 
581
657
            and hasattr(e, 'errno')
582
658
            and e.errno == errno.EPIPE):
583
659
            bzrlib.trace.note('broken pipe')
584
 
            return 3
 
660
            return 2
585
661
        else:
586
662
            bzrlib.trace.log_exception()
587
 
            if os.environ.get('BZR_PDB'):
588
 
                print '**** entering debugger'
589
 
                import pdb
590
 
                pdb.post_mortem(sys.exc_traceback)
591
 
            return 3
 
663
            return 2
 
664
 
592
665
 
593
666
if __name__ == '__main__':
594
667
    sys.exit(main(sys.argv))