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