~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Robert Collins
  • Date: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

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