~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Martin Pool
  • Date: 2005-09-06 07:26:13 UTC
  • Revision ID: mbp@sourcefrog.net-20050906072613-1a4a18769aaaa3eb
- add xml round-trip test for revisions

- fix up __eq__ method for Revision

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
 
 
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
 
82
167
def _builtin_commands():
83
168
    import bzrlib.builtins
84
169
    r = {}
168
253
        List of argument forms, marked with whether they are optional,
169
254
        repeated, etc.
170
255
 
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
256
    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().
 
257
        List of options that may be given for this command.
183
258
 
184
259
    hidden
185
260
        If true, this command isn't advertised.  This is typically
186
261
        for commands intended for expert users.
187
262
    """
188
263
    aliases = []
 
264
    
189
265
    takes_args = []
190
266
    takes_options = []
191
267
 
196
272
        if self.__doc__ == Command.__doc__:
197
273
            warn("No help message set for %r" % self)
198
274
 
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
275
 
211
276
    def run_argv(self, argv):
212
277
        """Parse command line and run."""
213
 
        args, opts = parse_args(self, argv)
 
278
        args, opts = parse_args(argv)
 
279
 
214
280
        if 'help' in opts:  # e.g. bzr add --help
215
281
            from bzrlib.help import help_on_command
216
282
            help_on_command(self.name())
217
283
            return 0
218
 
        # XXX: This should be handled by the parser
219
 
        allowed_names = self.options().keys()
 
284
 
 
285
        # check options are reasonable
 
286
        allowed = self.takes_options
220
287
        for oname in opts:
221
 
            if oname not in allowed_names:
 
288
            if oname not in allowed:
222
289
                raise BzrCommandError("option '--%s' is not allowed for command %r"
223
290
                                      % (oname, self.name()))
 
291
 
224
292
        # mix arguments and options into one dictionary
225
293
        cmdargs = _match_argform(self.name(), self.takes_args, args)
226
294
        cmdopts = {}
231
299
        all_cmd_args.update(cmdopts)
232
300
 
233
301
        return self.run(**all_cmd_args)
 
302
 
234
303
    
235
304
    def run(self):
236
305
        """Actually run the command.
286
355
        parsed = [spec, None]
287
356
    return parsed
288
357
 
289
 
def parse_args(command, argv):
 
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):
290
402
    """Parse command line.
291
403
    
292
404
    Arguments and options are parsed at this level before being passed
293
405
    down to specific command handlers.  This routine knows, from a
294
406
    lookup table, something about the available options, what optargs
295
407
    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]})
296
427
    """
297
 
    # TODO: chop up this beast; make it a method of the Command
298
428
    args = []
299
429
    opts = {}
300
430
 
301
 
    cmd_options = command.options()
302
431
    argsover = False
303
432
    while argv:
304
433
        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] == '-':
 
434
        if not argsover and a[0] == '-':
313
435
            # option names must not be unicode
314
436
            a = str(a)
315
437
            optarg = None
316
438
            if a[1] == '-':
317
 
                mutter("  got option %r", a)
 
439
                if a == '--':
 
440
                    # We've received a standalone -- No more flags
 
441
                    argsover = True
 
442
                    continue
 
443
                mutter("  got option %r" % a)
318
444
                if '=' in a:
319
445
                    optname, optarg = a[2:].split('=', 1)
320
446
                else:
321
447
                    optname = a[2:]
322
 
                if optname not in cmd_options:
323
 
                    raise BzrOptionError('unknown long option %r for command %s'
324
 
                        % (a, command.name()))
 
448
                if optname not in OPTIONS:
 
449
                    raise BzrError('unknown long option %r' % a)
325
450
            else:
326
451
                shortopt = a[1:]
327
 
                if shortopt in Option.SHORT_OPTIONS:
 
452
                if shortopt in SHORT_OPTIONS:
328
453
                    # Multi-character options must have a space to delimit
329
454
                    # their value
330
 
                    # ^^^ what does this mean? mbp 20051014
331
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
455
                    optname = SHORT_OPTIONS[shortopt]
332
456
                else:
333
457
                    # Single character short options, can be chained,
334
458
                    # and have their value appended to their name
335
459
                    shortopt = a[1:2]
336
 
                    if shortopt not in Option.SHORT_OPTIONS:
 
460
                    if shortopt not in SHORT_OPTIONS:
337
461
                        # We didn't find the multi-character name, and we
338
462
                        # didn't find the single char name
339
463
                        raise BzrError('unknown short option %r' % a)
340
 
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
464
                    optname = SHORT_OPTIONS[shortopt]
341
465
 
342
466
                    if a[2:]:
343
467
                        # There are extra things on this option
344
468
                        # see if it is the value, or if it is another
345
469
                        # short option
346
 
                        optargfn = Option.OPTIONS[optname].type
 
470
                        optargfn = OPTIONS[optname]
347
471
                        if optargfn is None:
348
472
                            # This option does not take an argument, so the
349
473
                            # next entry is another short option, pack it back
354
478
                            # into the array
355
479
                            optarg = a[2:]
356
480
            
357
 
                if optname not in cmd_options:
358
 
                    raise BzrOptionError('unknown short option %r for command'
359
 
                        ' %s' % (shortopt, command.name()))
360
481
            if optname in opts:
361
482
                # XXX: Do we ever want to support this, e.g. for -r?
362
483
                raise BzrError('repeated option %r' % a)
363
484
                
364
 
            option_obj = cmd_options[optname]
365
 
            optargfn = option_obj.type
 
485
            optargfn = OPTIONS[optname]
366
486
            if optargfn:
367
487
                if optarg == None:
368
488
                    if not argv:
376
496
                opts[optname] = True
377
497
        else:
378
498
            args.append(a)
 
499
 
379
500
    return args, opts
380
501
 
381
502
 
 
503
 
 
504
 
382
505
def _match_argform(cmd, takes_args, args):
383
506
    argdict = {}
384
507
 
427
550
def apply_profiled(the_callable, *args, **kwargs):
428
551
    import hotshot
429
552
    import tempfile
430
 
    import hotshot.stats
431
553
    pffileno, pfname = tempfile.mkstemp()
432
554
    try:
433
555
        prof = hotshot.Profile(pfname)
435
557
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
436
558
        finally:
437
559
            prof.close()
 
560
 
 
561
        import hotshot.stats
438
562
        stats = hotshot.stats.load(pfname)
439
 
        stats.strip_dirs()
440
 
        stats.sort_stats('cum')   # 'time'
 
563
        #stats.strip_dirs()
 
564
        stats.sort_stats('time')
441
565
        ## XXX: Might like to write to stderr or the trace file instead but
442
566
        ## print_stats seems hardcoded to stdout
443
567
        stats.print_stats(20)
 
568
 
444
569
        return ret
445
570
    finally:
446
571
        os.close(pffileno)
447
572
        os.remove(pfname)
448
573
 
449
574
 
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
575
def run_bzr(argv):
458
576
    """Execute a command.
459
577
 
476
594
        other behaviour.)
477
595
 
478
596
    --profile
479
 
        Run under the Python hotshot profiler.
480
 
 
481
 
    --lsprof
482
 
        Run under the Python lsprof profiler.
 
597
        Run under the Python profiler.
483
598
    """
 
599
    
484
600
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
485
601
 
486
 
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
 
602
    opt_profile = opt_no_plugins = opt_builtin = False
487
603
 
488
604
    # --no-plugins is handled specially at a very early stage. We need
489
605
    # to load plugins before doing other command parsing so that they
492
608
    for a in argv:
493
609
        if a == '--profile':
494
610
            opt_profile = True
495
 
        elif a == '--lsprof':
496
 
            opt_lsprof = True
497
611
        elif a == '--no-plugins':
498
612
            opt_no_plugins = True
499
613
        elif a == '--builtin':
500
614
            opt_builtin = True
501
 
        elif a in ('--quiet', '-q'):
502
 
            be_quiet()
503
615
        else:
504
 
            continue
 
616
            break
505
617
        argv.remove(a)
506
618
 
507
619
    if (not argv) or (argv[0] == '--help'):
525
637
 
526
638
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
527
639
 
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
 
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
556
645
 
557
646
 
558
647
def main(argv):
559
648
    import bzrlib.ui
560
 
    from bzrlib.ui.text import TextUIFactory
561
 
    ## bzrlib.trace.enable_default_logging()
562
649
    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):
 
650
    bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
 
651
 
570
652
    try:
571
653
        try:
572
 
            return run_bzr(argv)
 
654
            return run_bzr(argv[1:])
573
655
        finally:
574
656
            # do this here inside the exception wrappers to catch EPIPE
575
657
            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
576
671
    except Exception, e:
577
 
        # used to handle AssertionError and KeyboardInterrupt
578
 
        # specially here, but hopefully they're handled ok by the logger now
579
672
        import errno
580
673
        if (isinstance(e, IOError) 
581
674
            and hasattr(e, 'errno')
582
675
            and e.errno == errno.EPIPE):
583
676
            bzrlib.trace.note('broken pipe')
584
 
            return 3
 
677
            return 2
585
678
        else:
586
679
            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
 
680
            return 2
 
681
 
592
682
 
593
683
if __name__ == '__main__':
594
684
    sys.exit(main(sys.argv))