~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-09-16 09:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050916095623-ca0dff452934f21f
- make progress bar more tolerant of out-of-range values

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
 
29
28
 
30
29
import sys
31
30
import os
32
31
from warnings import warn
33
32
from inspect import getdoc
34
 
import errno
35
33
 
36
34
import bzrlib
37
35
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
 
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
45
39
from bzrlib import BZRDIR
46
 
from bzrlib.option import Option
47
40
 
48
41
plugin_cmds = {}
49
42
 
50
43
 
51
 
def register_command(cmd, decorate=False):
 
44
def register_command(cmd):
52
45
    "Utility function to help register a command"
53
46
    global plugin_cmds
54
47
    k = cmd.__name__
59
52
    if not plugin_cmds.has_key(k_unsquished):
60
53
        plugin_cmds[k_unsquished] = cmd
61
54
        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
55
    else:
69
56
        log_error('Two plugins defined the same command: %r' % k)
70
57
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
79
66
    return cmd[4:].replace('_','-')
80
67
 
81
68
 
 
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
 
82
153
def _builtin_commands():
83
154
    import bzrlib.builtins
84
155
    r = {}
168
239
        List of argument forms, marked with whether they are optional,
169
240
        repeated, etc.
170
241
 
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
242
    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().
 
243
        List of options that may be given for this command.
183
244
 
184
245
    hidden
185
246
        If true, this command isn't advertised.  This is typically
186
247
        for commands intended for expert users.
187
248
    """
188
249
    aliases = []
 
250
    
189
251
    takes_args = []
190
252
    takes_options = []
191
253
 
196
258
        if self.__doc__ == Command.__doc__:
197
259
            warn("No help message set for %r" % self)
198
260
 
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
261
 
211
262
    def run_argv(self, argv):
212
263
        """Parse command line and run."""
213
 
        args, opts = parse_args(self, argv)
 
264
        args, opts = parse_args(argv)
 
265
 
214
266
        if 'help' in opts:  # e.g. bzr add --help
215
267
            from bzrlib.help import help_on_command
216
268
            help_on_command(self.name())
217
269
            return 0
218
 
        # XXX: This should be handled by the parser
219
 
        allowed_names = self.options().keys()
 
270
 
 
271
        # check options are reasonable
 
272
        allowed = self.takes_options
220
273
        for oname in opts:
221
 
            if oname not in allowed_names:
 
274
            if oname not in allowed:
222
275
                raise BzrCommandError("option '--%s' is not allowed for command %r"
223
276
                                      % (oname, self.name()))
 
277
 
224
278
        # mix arguments and options into one dictionary
225
279
        cmdargs = _match_argform(self.name(), self.takes_args, args)
226
280
        cmdopts = {}
231
285
        all_cmd_args.update(cmdopts)
232
286
 
233
287
        return self.run(**all_cmd_args)
 
288
 
234
289
    
235
290
    def run(self):
236
291
        """Actually run the command.
286
341
        parsed = [spec, None]
287
342
    return parsed
288
343
 
289
 
def parse_args(command, argv):
 
344
 
 
345
 
 
346
 
 
347
# list of all available options; the rhs can be either None for an
 
348
# option that takes no argument, or a constructor function that checks
 
349
# the type.
 
350
OPTIONS = {
 
351
    'all':                    None,
 
352
    'diff-options':           str,
 
353
    'help':                   None,
 
354
    'file':                   unicode,
 
355
    'force':                  None,
 
356
    'format':                 unicode,
 
357
    'forward':                None,
 
358
    'message':                unicode,
 
359
    'no-recurse':             None,
 
360
    'profile':                None,
 
361
    'revision':               _parse_revision_str,
 
362
    'short':                  None,
 
363
    'show-ids':               None,
 
364
    'timezone':               str,
 
365
    'verbose':                None,
 
366
    'version':                None,
 
367
    'email':                  None,
 
368
    'unchanged':              None,
 
369
    'update':                 None,
 
370
    'long':                   None,
 
371
    'root':                   str,
 
372
    'no-backup':              None,
 
373
    'merge-type':             get_merge_type,
 
374
    'pattern':                str,
 
375
    }
 
376
 
 
377
SHORT_OPTIONS = {
 
378
    'F':                      'file', 
 
379
    'h':                      'help',
 
380
    'm':                      'message',
 
381
    'r':                      'revision',
 
382
    'v':                      'verbose',
 
383
    'l':                      'long',
 
384
}
 
385
 
 
386
 
 
387
def parse_args(argv):
290
388
    """Parse command line.
291
389
    
292
390
    Arguments and options are parsed at this level before being passed
293
391
    down to specific command handlers.  This routine knows, from a
294
392
    lookup table, something about the available options, what optargs
295
393
    they take, and which commands will accept them.
 
394
 
 
395
    >>> parse_args('--help'.split())
 
396
    ([], {'help': True})
 
397
    >>> parse_args('help -- --invalidcmd'.split())
 
398
    (['help', '--invalidcmd'], {})
 
399
    >>> parse_args('--version'.split())
 
400
    ([], {'version': True})
 
401
    >>> parse_args('status --all'.split())
 
402
    (['status'], {'all': True})
 
403
    >>> parse_args('commit --message=biter'.split())
 
404
    (['commit'], {'message': u'biter'})
 
405
    >>> parse_args('log -r 500'.split())
 
406
    (['log'], {'revision': [500]})
 
407
    >>> parse_args('log -r500..600'.split())
 
408
    (['log'], {'revision': [500, 600]})
 
409
    >>> parse_args('log -vr500..600'.split())
 
410
    (['log'], {'verbose': True, 'revision': [500, 600]})
 
411
    >>> parse_args('log -rv500..600'.split()) #the r takes an argument
 
412
    (['log'], {'revision': ['v500', 600]})
296
413
    """
297
 
    # TODO: chop up this beast; make it a method of the Command
298
414
    args = []
299
415
    opts = {}
300
416
 
301
 
    cmd_options = command.options()
302
417
    argsover = False
303
418
    while argv:
304
419
        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] == '-':
 
420
        if not argsover and a[0] == '-':
313
421
            # option names must not be unicode
314
422
            a = str(a)
315
423
            optarg = None
316
424
            if a[1] == '-':
317
 
                mutter("  got option %r", a)
 
425
                if a == '--':
 
426
                    # We've received a standalone -- No more flags
 
427
                    argsover = True
 
428
                    continue
 
429
                mutter("  got option %r" % a)
318
430
                if '=' in a:
319
431
                    optname, optarg = a[2:].split('=', 1)
320
432
                else:
321
433
                    optname = a[2:]
322
 
                if optname not in cmd_options:
323
 
                    raise BzrOptionError('unknown long option %r for command %s'
324
 
                        % (a, command.name()))
 
434
                if optname not in OPTIONS:
 
435
                    raise BzrError('unknown long option %r' % a)
325
436
            else:
326
437
                shortopt = a[1:]
327
 
                if shortopt in Option.SHORT_OPTIONS:
 
438
                if shortopt in SHORT_OPTIONS:
328
439
                    # Multi-character options must have a space to delimit
329
440
                    # their value
330
 
                    # ^^^ what does this mean? mbp 20051014
331
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
441
                    optname = SHORT_OPTIONS[shortopt]
332
442
                else:
333
443
                    # Single character short options, can be chained,
334
444
                    # and have their value appended to their name
335
445
                    shortopt = a[1:2]
336
 
                    if shortopt not in Option.SHORT_OPTIONS:
 
446
                    if shortopt not in SHORT_OPTIONS:
337
447
                        # We didn't find the multi-character name, and we
338
448
                        # didn't find the single char name
339
449
                        raise BzrError('unknown short option %r' % a)
340
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
450
                    optname = SHORT_OPTIONS[shortopt]
341
451
 
342
452
                    if a[2:]:
343
453
                        # There are extra things on this option
344
454
                        # see if it is the value, or if it is another
345
455
                        # short option
346
 
                        optargfn = Option.OPTIONS[optname].type
 
456
                        optargfn = OPTIONS[optname]
347
457
                        if optargfn is None:
348
458
                            # This option does not take an argument, so the
349
459
                            # next entry is another short option, pack it back
354
464
                            # into the array
355
465
                            optarg = a[2:]
356
466
            
357
 
                if optname not in cmd_options:
358
 
                    raise BzrOptionError('unknown short option %r for command'
359
 
                        ' %s' % (shortopt, command.name()))
360
467
            if optname in opts:
361
468
                # XXX: Do we ever want to support this, e.g. for -r?
362
469
                raise BzrError('repeated option %r' % a)
363
470
                
364
 
            option_obj = cmd_options[optname]
365
 
            optargfn = option_obj.type
 
471
            optargfn = OPTIONS[optname]
366
472
            if optargfn:
367
473
                if optarg == None:
368
474
                    if not argv:
376
482
                opts[optname] = True
377
483
        else:
378
484
            args.append(a)
 
485
 
379
486
    return args, opts
380
487
 
381
488
 
 
489
 
 
490
 
382
491
def _match_argform(cmd, takes_args, args):
383
492
    argdict = {}
384
493
 
427
536
def apply_profiled(the_callable, *args, **kwargs):
428
537
    import hotshot
429
538
    import tempfile
430
 
    import hotshot.stats
431
539
    pffileno, pfname = tempfile.mkstemp()
432
540
    try:
433
541
        prof = hotshot.Profile(pfname)
435
543
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
436
544
        finally:
437
545
            prof.close()
 
546
 
 
547
        import hotshot.stats
438
548
        stats = hotshot.stats.load(pfname)
439
 
        stats.strip_dirs()
440
 
        stats.sort_stats('cum')   # 'time'
 
549
        #stats.strip_dirs()
 
550
        stats.sort_stats('time')
441
551
        ## XXX: Might like to write to stderr or the trace file instead but
442
552
        ## print_stats seems hardcoded to stdout
443
553
        stats.print_stats(20)
 
554
 
444
555
        return ret
445
556
    finally:
446
557
        os.close(pffileno)
447
558
        os.remove(pfname)
448
559
 
449
560
 
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
561
def run_bzr(argv):
458
562
    """Execute a command.
459
563
 
476
580
        other behaviour.)
477
581
 
478
582
    --profile
479
 
        Run under the Python hotshot profiler.
480
 
 
481
 
    --lsprof
482
 
        Run under the Python lsprof profiler.
 
583
        Run under the Python profiler.
483
584
    """
 
585
    
484
586
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
485
587
 
486
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
 
588
    opt_profile = opt_no_plugins = opt_builtin = False
487
589
 
488
590
    # --no-plugins is handled specially at a very early stage. We need
489
591
    # to load plugins before doing other command parsing so that they
492
594
    for a in argv:
493
595
        if a == '--profile':
494
596
            opt_profile = True
495
 
        elif a == '--lsprof':
496
 
            opt_lsprof = True
497
597
        elif a == '--no-plugins':
498
598
            opt_no_plugins = True
499
599
        elif a == '--builtin':
500
600
            opt_builtin = True
501
 
        elif a in ('--quiet', '-q'):
502
 
            be_quiet()
503
601
        else:
504
 
            continue
 
602
            break
505
603
        argv.remove(a)
506
604
 
507
605
    if (not argv) or (argv[0] == '--help'):
520
618
    if not opt_no_plugins:
521
619
        from bzrlib.plugin import load_plugins
522
620
        load_plugins()
523
 
    else:
524
 
        from bzrlib.plugin import disable_plugins
525
 
        disable_plugins()
526
621
 
527
622
    cmd = str(argv.pop(0))
528
623
 
529
624
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
530
625
 
531
 
    try:
532
 
        if opt_lsprof:
533
 
            ret = apply_lsprofiled(cmd_obj.run_argv, argv)
534
 
        elif opt_profile:
535
 
            ret = apply_profiled(cmd_obj.run_argv, argv)
536
 
        else:
537
 
            ret = cmd_obj.run_argv(argv)
538
 
        return ret or 0
539
 
    finally:
540
 
        # reset, in case we may do other commands later within the same process
541
 
        be_quiet(False)
542
 
 
543
 
def display_command(func):
544
 
    """Decorator that suppresses pipe/interrupt errors."""
545
 
    def ignore_pipe(*args, **kwargs):
546
 
        try:
547
 
            result = func(*args, **kwargs)
548
 
            sys.stdout.flush()
549
 
            return result
550
 
        except IOError, e:
551
 
            if not hasattr(e, 'errno'):
552
 
                raise
553
 
            if e.errno != errno.EPIPE:
554
 
                raise
555
 
            pass
556
 
        except KeyboardInterrupt:
557
 
            pass
558
 
    return ignore_pipe
 
626
    if opt_profile:
 
627
        ret = apply_profiled(cmd_obj.run_argv, argv)
 
628
    else:
 
629
        ret = cmd_obj.run_argv(argv)
 
630
    return ret or 0
559
631
 
560
632
 
561
633
def main(argv):
562
634
    import bzrlib.ui
563
 
    from bzrlib.ui.text import TextUIFactory
564
 
    ## bzrlib.trace.enable_default_logging()
565
635
    bzrlib.trace.log_startup(argv)
566
 
    bzrlib.ui.ui_factory = TextUIFactory()
567
 
    ret = run_bzr_catch_errors(argv[1:])
568
 
    mutter("return code %d", ret)
569
 
    return ret
570
 
 
571
 
 
572
 
def run_bzr_catch_errors(argv):
 
636
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
637
 
573
638
    try:
574
639
        try:
575
 
            return run_bzr(argv)
 
640
            return run_bzr(argv[1:])
576
641
        finally:
577
642
            # do this here inside the exception wrappers to catch EPIPE
578
643
            sys.stdout.flush()
 
644
    except BzrCommandError, e:
 
645
        # command line syntax error, etc
 
646
        log_error(str(e))
 
647
        return 1
 
648
    except BzrError, e:
 
649
        bzrlib.trace.log_exception()
 
650
        return 1
 
651
    except AssertionError, e:
 
652
        bzrlib.trace.log_exception('assertion failed: ' + str(e))
 
653
        return 3
 
654
    except KeyboardInterrupt, e:
 
655
        bzrlib.trace.note('interrupted')
 
656
        return 2
579
657
    except Exception, e:
580
 
        # used to handle AssertionError and KeyboardInterrupt
581
 
        # specially here, but hopefully they're handled ok by the logger now
582
658
        import errno
583
659
        if (isinstance(e, IOError) 
584
660
            and hasattr(e, 'errno')
585
661
            and e.errno == errno.EPIPE):
586
662
            bzrlib.trace.note('broken pipe')
587
 
            return 3
 
663
            return 2
588
664
        else:
589
665
            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
 
666
            return 2
 
667
 
595
668
 
596
669
if __name__ == '__main__':
597
670
    sys.exit(main(sys.argv))