~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

and the tutorial patch came back, the very next day

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)
44
 
from bzrlib.revisionspec import RevisionSpec
 
43
from bzrlib.trace import mutter, note, log_error, warning
 
44
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
 
45
from bzrlib.branch import find_branch
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
 
 
81
    It supports integers directly, but everything else it
 
82
    defers for passing to Branch.get_revision_info()
 
83
 
 
84
    >>> _parse_revision_str('234')
 
85
    [234]
 
86
    >>> _parse_revision_str('234..567')
 
87
    [234, 567]
 
88
    >>> _parse_revision_str('..')
 
89
    [None, None]
 
90
    >>> _parse_revision_str('..234')
 
91
    [None, 234]
 
92
    >>> _parse_revision_str('234..')
 
93
    [234, None]
 
94
    >>> _parse_revision_str('234..456..789') # Maybe this should be an error
 
95
    [234, 456, 789]
 
96
    >>> _parse_revision_str('234....789') # Error?
 
97
    [234, None, 789]
 
98
    >>> _parse_revision_str('revid:test@other.com-234234')
 
99
    ['revid:test@other.com-234234']
 
100
    >>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
 
101
    ['revid:test@other.com-234234', 'revid:test@other.com-234235']
 
102
    >>> _parse_revision_str('revid:test@other.com-234234..23')
 
103
    ['revid:test@other.com-234234', 23]
 
104
    >>> _parse_revision_str('date:2005-04-12')
 
105
    ['date:2005-04-12']
 
106
    >>> _parse_revision_str('date:2005-04-12 12:24:33')
 
107
    ['date:2005-04-12 12:24:33']
 
108
    >>> _parse_revision_str('date:2005-04-12T12:24:33')
 
109
    ['date:2005-04-12T12:24:33']
 
110
    >>> _parse_revision_str('date:2005-04-12,12:24:33')
 
111
    ['date:2005-04-12,12:24:33']
 
112
    >>> _parse_revision_str('-5..23')
 
113
    [-5, 23]
 
114
    >>> _parse_revision_str('-5')
 
115
    [-5]
 
116
    >>> _parse_revision_str('123a')
 
117
    ['123a']
 
118
    >>> _parse_revision_str('abc')
 
119
    ['abc']
 
120
    """
 
121
    import re
 
122
    old_format_re = re.compile('\d*:\d*')
 
123
    m = old_format_re.match(revstr)
 
124
    if m:
 
125
        warning('Colon separator for revision numbers is deprecated.'
 
126
                ' Use .. instead')
 
127
        revs = []
 
128
        for rev in revstr.split(':'):
 
129
            if rev:
 
130
                revs.append(int(rev))
 
131
            else:
 
132
                revs.append(None)
 
133
        return revs
 
134
    revs = []
 
135
    for x in revstr.split('..'):
 
136
        if not x:
 
137
            revs.append(None)
 
138
        else:
 
139
            try:
 
140
                revs.append(int(x))
 
141
            except ValueError:
 
142
                revs.append(x)
 
143
    return revs
 
144
 
 
145
 
 
146
def get_merge_type(typestring):
 
147
    """Attempt to find the merge class/factory associated with a string."""
 
148
    from merge import merge_types
 
149
    try:
 
150
        return merge_types[typestring][0]
 
151
    except KeyError:
 
152
        templ = '%s%%7s: %%s' % (' '*12)
 
153
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
154
        type_list = '\n'.join(lines)
 
155
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
156
            (typestring, type_list)
 
157
        raise BzrCommandError(msg)
 
158
    
 
159
 
 
160
def get_merge_type(typestring):
 
161
    """Attempt to find the merge class/factory associated with a string."""
 
162
    from merge import merge_types
 
163
    try:
 
164
        return merge_types[typestring][0]
 
165
    except KeyError:
 
166
        templ = '%s%%7s: %%s' % (' '*12)
 
167
        lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
 
168
        type_list = '\n'.join(lines)
 
169
        msg = "No known merge type %s. Supported types are:\n%s" %\
 
170
            (typestring, type_list)
 
171
        raise BzrCommandError(msg)
 
172
 
 
173
 
82
174
def _builtin_commands():
83
175
    import bzrlib.builtins
84
176
    r = {}
168
260
        List of argument forms, marked with whether they are optional,
169
261
        repeated, etc.
170
262
 
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
263
    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().
 
264
        List of options that may be given for this command.
183
265
 
184
266
    hidden
185
267
        If true, this command isn't advertised.  This is typically
186
268
        for commands intended for expert users.
187
269
    """
188
270
    aliases = []
 
271
    
189
272
    takes_args = []
190
273
    takes_options = []
191
274
 
196
279
        if self.__doc__ == Command.__doc__:
197
280
            warn("No help message set for %r" % self)
198
281
 
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
282
 
211
283
    def run_argv(self, argv):
212
284
        """Parse command line and run."""
213
 
        args, opts = parse_args(self, argv)
 
285
        args, opts = parse_args(argv)
 
286
 
214
287
        if 'help' in opts:  # e.g. bzr add --help
215
288
            from bzrlib.help import help_on_command
216
289
            help_on_command(self.name())
217
290
            return 0
218
 
        # XXX: This should be handled by the parser
219
 
        allowed_names = self.options().keys()
 
291
 
 
292
        # check options are reasonable
 
293
        allowed = self.takes_options
220
294
        for oname in opts:
221
 
            if oname not in allowed_names:
 
295
            if oname not in allowed:
222
296
                raise BzrCommandError("option '--%s' is not allowed for command %r"
223
297
                                      % (oname, self.name()))
 
298
 
224
299
        # mix arguments and options into one dictionary
225
300
        cmdargs = _match_argform(self.name(), self.takes_args, args)
226
301
        cmdopts = {}
231
306
        all_cmd_args.update(cmdopts)
232
307
 
233
308
        return self.run(**all_cmd_args)
 
309
 
234
310
    
235
311
    def run(self):
236
312
        """Actually run the command.
286
362
        parsed = [spec, None]
287
363
    return parsed
288
364
 
289
 
def parse_args(command, argv):
 
365
 
 
366
# list of all available options; the rhs can be either None for an
 
367
# option that takes no argument, or a constructor function that checks
 
368
# the type.
 
369
OPTIONS = {
 
370
    'all':                    None,
 
371
    'diff-options':           str,
 
372
    'help':                   None,
 
373
    'file':                   unicode,
 
374
    'force':                  None,
 
375
    'format':                 unicode,
 
376
    'forward':                None,
 
377
    'message':                unicode,
 
378
    'no-recurse':             None,
 
379
    'profile':                None,
 
380
    'revision':               _parse_revision_str,
 
381
    'short':                  None,
 
382
    'show-ids':               None,
 
383
    'timezone':               str,
 
384
    'verbose':                None,
 
385
    'version':                None,
 
386
    'email':                  None,
 
387
    'unchanged':              None,
 
388
    'update':                 None,
 
389
    'long':                   None,
 
390
    'root':                   str,
 
391
    'no-backup':              None,
 
392
    'merge-type':             get_merge_type,
 
393
    'pattern':                str,
 
394
    }
 
395
 
 
396
SHORT_OPTIONS = {
 
397
    'F':                      'file', 
 
398
    'h':                      'help',
 
399
    'm':                      'message',
 
400
    'r':                      'revision',
 
401
    'v':                      'verbose',
 
402
    'l':                      'long',
 
403
}
 
404
 
 
405
 
 
406
def parse_args(argv):
290
407
    """Parse command line.
291
408
    
292
409
    Arguments and options are parsed at this level before being passed
293
410
    down to specific command handlers.  This routine knows, from a
294
411
    lookup table, something about the available options, what optargs
295
412
    they take, and which commands will accept them.
 
413
 
 
414
    >>> parse_args('--help'.split())
 
415
    ([], {'help': True})
 
416
    >>> parse_args('help -- --invalidcmd'.split())
 
417
    (['help', '--invalidcmd'], {})
 
418
    >>> parse_args('--version'.split())
 
419
    ([], {'version': True})
 
420
    >>> parse_args('status --all'.split())
 
421
    (['status'], {'all': True})
 
422
    >>> parse_args('commit --message=biter'.split())
 
423
    (['commit'], {'message': u'biter'})
 
424
    >>> parse_args('log -r 500'.split())
 
425
    (['log'], {'revision': [500]})
 
426
    >>> parse_args('log -r500..600'.split())
 
427
    (['log'], {'revision': [500, 600]})
 
428
    >>> parse_args('log -vr500..600'.split())
 
429
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
430
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
431
    (['log'], {'revision': ['v500', 600]})
296
432
    """
297
 
    # TODO: chop up this beast; make it a method of the Command
298
433
    args = []
299
434
    opts = {}
300
435
 
301
 
    cmd_options = command.options()
302
436
    argsover = False
303
437
    while argv:
304
438
        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] == '-':
 
439
        if not argsover and a[0] == '-':
313
440
            # option names must not be unicode
314
441
            a = str(a)
315
442
            optarg = None
316
443
            if a[1] == '-':
317
 
                mutter("  got option %r", a)
 
444
                if a == '--':
 
445
                    # We've received a standalone -- No more flags
 
446
                    argsover = True
 
447
                    continue
 
448
                mutter("  got option %r" % a)
318
449
                if '=' in a:
319
450
                    optname, optarg = a[2:].split('=', 1)
320
451
                else:
321
452
                    optname = a[2:]
322
 
                if optname not in cmd_options:
323
 
                    raise BzrOptionError('unknown long option %r for command %s'
324
 
                        % (a, command.name()))
 
453
                if optname not in OPTIONS:
 
454
                    raise BzrError('unknown long option %r' % a)
325
455
            else:
326
456
                shortopt = a[1:]
327
 
                if shortopt in Option.SHORT_OPTIONS:
 
457
                if shortopt in SHORT_OPTIONS:
328
458
                    # Multi-character options must have a space to delimit
329
459
                    # their value
330
 
                    # ^^^ what does this mean? mbp 20051014
331
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
460
                    optname = SHORT_OPTIONS[shortopt]
332
461
                else:
333
462
                    # Single character short options, can be chained,
334
463
                    # and have their value appended to their name
335
464
                    shortopt = a[1:2]
336
 
                    if shortopt not in Option.SHORT_OPTIONS:
 
465
                    if shortopt not in SHORT_OPTIONS:
337
466
                        # We didn't find the multi-character name, and we
338
467
                        # didn't find the single char name
339
468
                        raise BzrError('unknown short option %r' % a)
340
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
469
                    optname = SHORT_OPTIONS[shortopt]
341
470
 
342
471
                    if a[2:]:
343
472
                        # There are extra things on this option
344
473
                        # see if it is the value, or if it is another
345
474
                        # short option
346
 
                        optargfn = Option.OPTIONS[optname].type
 
475
                        optargfn = OPTIONS[optname]
347
476
                        if optargfn is None:
348
477
                            # This option does not take an argument, so the
349
478
                            # next entry is another short option, pack it back
354
483
                            # into the array
355
484
                            optarg = a[2:]
356
485
            
357
 
                if optname not in cmd_options:
358
 
                    raise BzrOptionError('unknown short option %r for command'
359
 
                        ' %s' % (shortopt, command.name()))
360
486
            if optname in opts:
361
487
                # XXX: Do we ever want to support this, e.g. for -r?
362
488
                raise BzrError('repeated option %r' % a)
363
489
                
364
 
            option_obj = cmd_options[optname]
365
 
            optargfn = option_obj.type
 
490
            optargfn = OPTIONS[optname]
366
491
            if optargfn:
367
492
                if optarg == None:
368
493
                    if not argv:
376
501
                opts[optname] = True
377
502
        else:
378
503
            args.append(a)
 
504
 
379
505
    return args, opts
380
506
 
381
507
 
 
508
 
 
509
 
382
510
def _match_argform(cmd, takes_args, args):
383
511
    argdict = {}
384
512
 
427
555
def apply_profiled(the_callable, *args, **kwargs):
428
556
    import hotshot
429
557
    import tempfile
430
 
    import hotshot.stats
431
558
    pffileno, pfname = tempfile.mkstemp()
432
559
    try:
433
560
        prof = hotshot.Profile(pfname)
435
562
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
436
563
        finally:
437
564
            prof.close()
 
565
 
 
566
        import hotshot.stats
438
567
        stats = hotshot.stats.load(pfname)
439
 
        stats.strip_dirs()
440
 
        stats.sort_stats('cum')   # 'time'
 
568
        #stats.strip_dirs()
 
569
        stats.sort_stats('time')
441
570
        ## XXX: Might like to write to stderr or the trace file instead but
442
571
        ## print_stats seems hardcoded to stdout
443
572
        stats.print_stats(20)
 
573
 
444
574
        return ret
445
575
    finally:
446
576
        os.close(pffileno)
447
577
        os.remove(pfname)
448
578
 
449
579
 
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
580
def run_bzr(argv):
458
581
    """Execute a command.
459
582
 
476
599
        other behaviour.)
477
600
 
478
601
    --profile
479
 
        Run under the Python hotshot profiler.
480
 
 
481
 
    --lsprof
482
 
        Run under the Python lsprof profiler.
 
602
        Run under the Python profiler.
483
603
    """
 
604
    
484
605
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
485
606
 
486
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
 
607
    opt_profile = opt_no_plugins = opt_builtin = False
487
608
 
488
609
    # --no-plugins is handled specially at a very early stage. We need
489
610
    # to load plugins before doing other command parsing so that they
492
613
    for a in argv:
493
614
        if a == '--profile':
494
615
            opt_profile = True
495
 
        elif a == '--lsprof':
496
 
            opt_lsprof = True
497
616
        elif a == '--no-plugins':
498
617
            opt_no_plugins = True
499
618
        elif a == '--builtin':
500
619
            opt_builtin = True
501
 
        elif a in ('--quiet', '-q'):
502
 
            be_quiet()
503
620
        else:
504
 
            continue
 
621
            break
505
622
        argv.remove(a)
506
623
 
507
624
    if (not argv) or (argv[0] == '--help'):
525
642
 
526
643
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
527
644
 
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
 
645
    if opt_profile:
 
646
        ret = apply_profiled(cmd_obj.run_argv, argv)
 
647
    else:
 
648
        ret = cmd_obj.run_argv(argv)
 
649
    return ret or 0
556
650
 
557
651
 
558
652
def main(argv):
559
653
    import bzrlib.ui
560
 
    from bzrlib.ui.text import TextUIFactory
561
 
    ## bzrlib.trace.enable_default_logging()
562
654
    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
567
 
 
568
 
 
569
 
def run_bzr_catch_errors(argv):
 
655
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
656
 
570
657
    try:
571
658
        try:
572
 
            return run_bzr(argv)
 
659
            return run_bzr(argv[1:])
573
660
        finally:
574
661
            # do this here inside the exception wrappers to catch EPIPE
575
662
            sys.stdout.flush()
 
663
    except BzrCommandError, e:
 
664
        # command line syntax error, etc
 
665
        log_error(str(e))
 
666
        return 1
 
667
    except BzrError, e:
 
668
        bzrlib.trace.log_exception()
 
669
        return 1
 
670
    except AssertionError, e:
 
671
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
 
672
        return 3
 
673
    except KeyboardInterrupt, e:
 
674
        bzrlib.trace.note('interrupted')
 
675
        return 2
576
676
    except Exception, e:
577
 
        # used to handle AssertionError and KeyboardInterrupt
578
 
        # specially here, but hopefully they're handled ok by the logger now
579
677
        import errno
580
678
        if (isinstance(e, IOError) 
581
679
            and hasattr(e, 'errno')
582
680
            and e.errno == errno.EPIPE):
583
681
            bzrlib.trace.note('broken pipe')
584
 
            return 3
 
682
            return 2
585
683
        else:
586
684
            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
 
685
            return 2
 
686
 
592
687
 
593
688
if __name__ == '__main__':
594
689
    sys.exit(main(sys.argv))