/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/commands.py

  • Committer: Robert Collins
  • Date: 2006-02-24 23:13:20 UTC
  • mto: (1587.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 1588.
  • Revision ID: robertc@robertcollins.net-20060224231320-dbaf879d3070bfd7
Replace the slow topo_sort routine with a much faster one for non trivial datasets.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004, 2005 by Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
# TODO: probably should say which arguments are candidates for glob
 
19
# expansion on windows and do that at the command level.
 
20
 
 
21
# TODO: Help messages for options.
 
22
 
 
23
# TODO: Define arguments by objects, rather than just using names.
 
24
# Those objects can specify the expected type of the argument, which
 
25
# would help with validation and shell completion.
 
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?
 
29
 
 
30
import sys
 
31
import os
 
32
from warnings import warn
 
33
from inspect import getdoc
 
34
import errno
 
35
 
 
36
import bzrlib
 
37
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
 
45
from bzrlib.option import Option
 
46
 
 
47
plugin_cmds = {}
 
48
 
 
49
 
 
50
def register_command(cmd, decorate=False):
 
51
    "Utility function to help register a command"
 
52
    global plugin_cmds
 
53
    k = cmd.__name__
 
54
    if k.startswith("cmd_"):
 
55
        k_unsquished = _unsquish_command_name(k)
 
56
    else:
 
57
        k_unsquished = k
 
58
    if not plugin_cmds.has_key(k_unsquished):
 
59
        plugin_cmds[k_unsquished] = cmd
 
60
        mutter('registered plugin command %s', k_unsquished)      
 
61
        if decorate and k_unsquished in builtin_command_names():
 
62
            return _builtin_commands()[k_unsquished]
 
63
    elif decorate:
 
64
        result = plugin_cmds[k_unsquished]
 
65
        plugin_cmds[k_unsquished] = cmd
 
66
        return result
 
67
    else:
 
68
        log_error('Two plugins defined the same command: %r' % k)
 
69
        log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
 
70
 
 
71
 
 
72
def _squish_command_name(cmd):
 
73
    return 'cmd_' + cmd.replace('-', '_')
 
74
 
 
75
 
 
76
def _unsquish_command_name(cmd):
 
77
    assert cmd.startswith("cmd_")
 
78
    return cmd[4:].replace('_','-')
 
79
 
 
80
 
 
81
def _builtin_commands():
 
82
    import bzrlib.builtins
 
83
    r = {}
 
84
    builtins = bzrlib.builtins.__dict__
 
85
    for name in builtins:
 
86
        if name.startswith("cmd_"):
 
87
            real_name = _unsquish_command_name(name)        
 
88
            r[real_name] = builtins[name]
 
89
    return r
 
90
 
 
91
            
 
92
 
 
93
def builtin_command_names():
 
94
    """Return list of builtin command names."""
 
95
    return _builtin_commands().keys()
 
96
    
 
97
 
 
98
def plugin_command_names():
 
99
    return plugin_cmds.keys()
 
100
 
 
101
 
 
102
def _get_cmd_dict(plugins_override=True):
 
103
    """Return name->class mapping for all commands."""
 
104
    d = _builtin_commands()
 
105
    if plugins_override:
 
106
        d.update(plugin_cmds)
 
107
    return d
 
108
 
 
109
    
 
110
def get_all_cmds(plugins_override=True):
 
111
    """Return canonical name and class for all registered commands."""
 
112
    for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
 
113
        yield k,v
 
114
 
 
115
 
 
116
def get_cmd_object(cmd_name, plugins_override=True):
 
117
    """Return the canonical name and command class for a command.
 
118
 
 
119
    plugins_override
 
120
        If true, plugin commands can override builtins.
 
121
    """
 
122
    from bzrlib.externalcommand import ExternalCommand
 
123
 
 
124
    cmd_name = str(cmd_name)            # not unicode
 
125
 
 
126
    # first look up this command under the specified name
 
127
    cmds = _get_cmd_dict(plugins_override=plugins_override)
 
128
    try:
 
129
        return cmds[cmd_name]()
 
130
    except KeyError:
 
131
        pass
 
132
 
 
133
    # look for any command which claims this as an alias
 
134
    for real_cmd_name, cmd_class in cmds.iteritems():
 
135
        if cmd_name in cmd_class.aliases:
 
136
            return cmd_class()
 
137
 
 
138
    cmd_obj = ExternalCommand.find_command(cmd_name)
 
139
    if cmd_obj:
 
140
        return cmd_obj
 
141
 
 
142
    raise BzrCommandError("unknown command %r" % cmd_name)
 
143
 
 
144
 
 
145
class Command(object):
 
146
    """Base class for commands.
 
147
 
 
148
    Commands are the heart of the command-line bzr interface.
 
149
 
 
150
    The command object mostly handles the mapping of command-line
 
151
    parameters into one or more bzrlib operations, and of the results
 
152
    into textual output.
 
153
 
 
154
    Commands normally don't have any state.  All their arguments are
 
155
    passed in to the run method.  (Subclasses may take a different
 
156
    policy if the behaviour of the instance needs to depend on e.g. a
 
157
    shell plugin and not just its Python class.)
 
158
 
 
159
    The docstring for an actual command should give a single-line
 
160
    summary, then a complete description of the command.  A grammar
 
161
    description will be inserted.
 
162
 
 
163
    aliases
 
164
        Other accepted names for this command.
 
165
 
 
166
    takes_args
 
167
        List of argument forms, marked with whether they are optional,
 
168
        repeated, etc.
 
169
 
 
170
                Examples:
 
171
 
 
172
                ['to_location', 'from_branch?', 'file*']
 
173
 
 
174
                'to_location' is required
 
175
                'from_branch' is optional
 
176
                'file' can be specified 0 or more times
 
177
 
 
178
    takes_options
 
179
        List of options that may be given for this command.  These can
 
180
        be either strings, referring to globally-defined options,
 
181
        or option objects.  Retrieve through options().
 
182
 
 
183
    hidden
 
184
        If true, this command isn't advertised.  This is typically
 
185
        for commands intended for expert users.
 
186
    """
 
187
    aliases = []
 
188
    takes_args = []
 
189
    takes_options = []
 
190
 
 
191
    hidden = False
 
192
    
 
193
    def __init__(self):
 
194
        """Construct an instance of this command."""
 
195
        if self.__doc__ == Command.__doc__:
 
196
            warn("No help message set for %r" % self)
 
197
 
 
198
    def options(self):
 
199
        """Return dict of valid options for this command.
 
200
 
 
201
        Maps from long option name to option object."""
 
202
        r = dict()
 
203
        r['help'] = Option.OPTIONS['help']
 
204
        for o in self.takes_options:
 
205
            if not isinstance(o, Option):
 
206
                o = Option.OPTIONS[o]
 
207
            r[o.name] = o
 
208
        return r
 
209
 
 
210
    def run_argv(self, argv):
 
211
        """Parse command line and run."""
 
212
        args, opts = parse_args(self, argv)
 
213
        if 'help' in opts:  # e.g. bzr add --help
 
214
            from bzrlib.help import help_on_command
 
215
            help_on_command(self.name())
 
216
            return 0
 
217
        # XXX: This should be handled by the parser
 
218
        allowed_names = self.options().keys()
 
219
        for oname in opts:
 
220
            if oname not in allowed_names:
 
221
                raise BzrCommandError("option '--%s' is not allowed for command %r"
 
222
                                      % (oname, self.name()))
 
223
        # mix arguments and options into one dictionary
 
224
        cmdargs = _match_argform(self.name(), self.takes_args, args)
 
225
        cmdopts = {}
 
226
        for k, v in opts.items():
 
227
            cmdopts[k.replace('-', '_')] = v
 
228
 
 
229
        all_cmd_args = cmdargs.copy()
 
230
        all_cmd_args.update(cmdopts)
 
231
 
 
232
        return self.run(**all_cmd_args)
 
233
    
 
234
    def run(self):
 
235
        """Actually run the command.
 
236
 
 
237
        This is invoked with the options and arguments bound to
 
238
        keyword parameters.
 
239
 
 
240
        Return 0 or None if the command was successful, or a non-zero
 
241
        shell error code if not.  It's OK for this method to allow
 
242
        an exception to raise up.
 
243
        """
 
244
        raise NotImplementedError()
 
245
 
 
246
 
 
247
    def help(self):
 
248
        """Return help message for this class."""
 
249
        if self.__doc__ is Command.__doc__:
 
250
            return None
 
251
        return getdoc(self)
 
252
 
 
253
    def name(self):
 
254
        return _unsquish_command_name(self.__class__.__name__)
 
255
 
 
256
 
 
257
def parse_spec(spec):
 
258
    """
 
259
    >>> parse_spec(None)
 
260
    [None, None]
 
261
    >>> parse_spec("./")
 
262
    ['./', None]
 
263
    >>> parse_spec("../@")
 
264
    ['..', -1]
 
265
    >>> parse_spec("../f/@35")
 
266
    ['../f', 35]
 
267
    >>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
 
268
    ['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
 
269
    """
 
270
    if spec is None:
 
271
        return [None, None]
 
272
    if '/@' in spec:
 
273
        parsed = spec.split('/@')
 
274
        assert len(parsed) == 2
 
275
        if parsed[1] == "":
 
276
            parsed[1] = -1
 
277
        else:
 
278
            try:
 
279
                parsed[1] = int(parsed[1])
 
280
            except ValueError:
 
281
                pass # We can allow stuff like ./@revid:blahblahblah
 
282
            else:
 
283
                assert parsed[1] >=0
 
284
    else:
 
285
        parsed = [spec, None]
 
286
    return parsed
 
287
 
 
288
def parse_args(command, argv):
 
289
    """Parse command line.
 
290
    
 
291
    Arguments and options are parsed at this level before being passed
 
292
    down to specific command handlers.  This routine knows, from a
 
293
    lookup table, something about the available options, what optargs
 
294
    they take, and which commands will accept them.
 
295
    """
 
296
    # TODO: chop up this beast; make it a method of the Command
 
297
    args = []
 
298
    opts = {}
 
299
 
 
300
    cmd_options = command.options()
 
301
    argsover = False
 
302
    while argv:
 
303
        a = argv.pop(0)
 
304
        if argsover:
 
305
            args.append(a)
 
306
            continue
 
307
        elif a == '--':
 
308
            # We've received a standalone -- No more flags
 
309
            argsover = True
 
310
            continue
 
311
        if a[0] == '-':
 
312
            # option names must not be unicode
 
313
            a = str(a)
 
314
            optarg = None
 
315
            if a[1] == '-':
 
316
                mutter("  got option %r", a)
 
317
                if '=' in a:
 
318
                    optname, optarg = a[2:].split('=', 1)
 
319
                else:
 
320
                    optname = a[2:]
 
321
                if optname not in cmd_options:
 
322
                    raise BzrOptionError('unknown long option %r for command %s'
 
323
                        % (a, command.name()))
 
324
            else:
 
325
                shortopt = a[1:]
 
326
                if shortopt in Option.SHORT_OPTIONS:
 
327
                    # Multi-character options must have a space to delimit
 
328
                    # their value
 
329
                    # ^^^ what does this mean? mbp 20051014
 
330
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
331
                else:
 
332
                    # Single character short options, can be chained,
 
333
                    # and have their value appended to their name
 
334
                    shortopt = a[1:2]
 
335
                    if shortopt not in Option.SHORT_OPTIONS:
 
336
                        # We didn't find the multi-character name, and we
 
337
                        # didn't find the single char name
 
338
                        raise BzrError('unknown short option %r' % a)
 
339
                    optname = Option.SHORT_OPTIONS[shortopt].name
 
340
 
 
341
                    if a[2:]:
 
342
                        # There are extra things on this option
 
343
                        # see if it is the value, or if it is another
 
344
                        # short option
 
345
                        optargfn = Option.OPTIONS[optname].type
 
346
                        if optargfn is None:
 
347
                            # This option does not take an argument, so the
 
348
                            # next entry is another short option, pack it back
 
349
                            # into the list
 
350
                            argv.insert(0, '-' + a[2:])
 
351
                        else:
 
352
                            # This option takes an argument, so pack it
 
353
                            # into the array
 
354
                            optarg = a[2:]
 
355
            
 
356
                if optname not in cmd_options:
 
357
                    raise BzrOptionError('unknown short option %r for command'
 
358
                        ' %s' % (shortopt, command.name()))
 
359
            if optname in opts:
 
360
                # XXX: Do we ever want to support this, e.g. for -r?
 
361
                raise BzrError('repeated option %r' % a)
 
362
                
 
363
            option_obj = cmd_options[optname]
 
364
            optargfn = option_obj.type
 
365
            if optargfn:
 
366
                if optarg == None:
 
367
                    if not argv:
 
368
                        raise BzrError('option %r needs an argument' % a)
 
369
                    else:
 
370
                        optarg = argv.pop(0)
 
371
                opts[optname] = optargfn(optarg)
 
372
            else:
 
373
                if optarg != None:
 
374
                    raise BzrError('option %r takes no argument' % optname)
 
375
                opts[optname] = True
 
376
        else:
 
377
            args.append(a)
 
378
    return args, opts
 
379
 
 
380
 
 
381
def _match_argform(cmd, takes_args, args):
 
382
    argdict = {}
 
383
 
 
384
    # step through args and takes_args, allowing appropriate 0-many matches
 
385
    for ap in takes_args:
 
386
        argname = ap[:-1]
 
387
        if ap[-1] == '?':
 
388
            if args:
 
389
                argdict[argname] = args.pop(0)
 
390
        elif ap[-1] == '*': # all remaining arguments
 
391
            if args:
 
392
                argdict[argname + '_list'] = args[:]
 
393
                args = []
 
394
            else:
 
395
                argdict[argname + '_list'] = None
 
396
        elif ap[-1] == '+':
 
397
            if not args:
 
398
                raise BzrCommandError("command %r needs one or more %s"
 
399
                        % (cmd, argname.upper()))
 
400
            else:
 
401
                argdict[argname + '_list'] = args[:]
 
402
                args = []
 
403
        elif ap[-1] == '$': # all but one
 
404
            if len(args) < 2:
 
405
                raise BzrCommandError("command %r needs one or more %s"
 
406
                        % (cmd, argname.upper()))
 
407
            argdict[argname + '_list'] = args[:-1]
 
408
            args[:-1] = []                
 
409
        else:
 
410
            # just a plain arg
 
411
            argname = ap
 
412
            if not args:
 
413
                raise BzrCommandError("command %r requires argument %s"
 
414
                        % (cmd, argname.upper()))
 
415
            else:
 
416
                argdict[argname] = args.pop(0)
 
417
            
 
418
    if args:
 
419
        raise BzrCommandError("extra argument to command %s: %s"
 
420
                              % (cmd, args[0]))
 
421
 
 
422
    return argdict
 
423
 
 
424
 
 
425
 
 
426
def apply_profiled(the_callable, *args, **kwargs):
 
427
    import hotshot
 
428
    import tempfile
 
429
    import hotshot.stats
 
430
    pffileno, pfname = tempfile.mkstemp()
 
431
    try:
 
432
        prof = hotshot.Profile(pfname)
 
433
        try:
 
434
            ret = prof.runcall(the_callable, *args, **kwargs) or 0
 
435
        finally:
 
436
            prof.close()
 
437
        stats = hotshot.stats.load(pfname)
 
438
        stats.strip_dirs()
 
439
        stats.sort_stats('cum')   # 'time'
 
440
        ## XXX: Might like to write to stderr or the trace file instead but
 
441
        ## print_stats seems hardcoded to stdout
 
442
        stats.print_stats(20)
 
443
        return ret
 
444
    finally:
 
445
        os.close(pffileno)
 
446
        os.remove(pfname)
 
447
 
 
448
 
 
449
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
 
450
    from bzrlib.lsprof import profile
 
451
    import cPickle
 
452
    ret, stats = profile(the_callable, *args, **kwargs)
 
453
    stats.sort()
 
454
    if filename is None:
 
455
        stats.pprint()
 
456
    else:
 
457
        stats.freeze()
 
458
        cPickle.dump(stats, open(filename, 'w'), 2)
 
459
        print 'Profile data written to %r.' % filename
 
460
    return ret
 
461
 
 
462
def run_bzr(argv):
 
463
    """Execute a command.
 
464
 
 
465
    This is similar to main(), but without all the trappings for
 
466
    logging and error handling.  
 
467
    
 
468
    argv
 
469
       The command-line arguments, without the program name from argv[0]
 
470
    
 
471
    Returns a command status or raises an exception.
 
472
 
 
473
    Special master options: these must come before the command because
 
474
    they control how the command is interpreted.
 
475
 
 
476
    --no-plugins
 
477
        Do not load plugin modules at all
 
478
 
 
479
    --builtin
 
480
        Only use builtin commands.  (Plugins are still allowed to change
 
481
        other behaviour.)
 
482
 
 
483
    --profile
 
484
        Run under the Python hotshot profiler.
 
485
 
 
486
    --lsprof
 
487
        Run under the Python lsprof profiler.
 
488
    """
 
489
    argv = [a.decode(bzrlib.user_encoding) for a in argv]
 
490
 
 
491
    opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = False
 
492
    opt_lsprof_file = None
 
493
 
 
494
    # --no-plugins is handled specially at a very early stage. We need
 
495
    # to load plugins before doing other command parsing so that they
 
496
    # can override commands, but this needs to happen first.
 
497
 
 
498
    argv_copy = []
 
499
    i = 0
 
500
    while i < len(argv):
 
501
        a = argv[i]
 
502
        if a == '--profile':
 
503
            opt_profile = True
 
504
        elif a == '--lsprof':
 
505
            opt_lsprof = True
 
506
        elif a == '--lsprof-file':
 
507
            opt_lsprof_file = argv[i + 1]
 
508
            i += 1
 
509
        elif a == '--no-plugins':
 
510
            opt_no_plugins = True
 
511
        elif a == '--builtin':
 
512
            opt_builtin = True
 
513
        elif a in ('--quiet', '-q'):
 
514
            be_quiet()
 
515
        else:
 
516
            argv_copy.append(a)
 
517
        i += 1
 
518
 
 
519
    argv = argv_copy
 
520
    if (not argv) or (argv[0] == '--help'):
 
521
        from bzrlib.help import help
 
522
        if len(argv) > 1:
 
523
            help(argv[1])
 
524
        else:
 
525
            help()
 
526
        return 0
 
527
 
 
528
    if argv[0] == '--version':
 
529
        from bzrlib.builtins import show_version
 
530
        show_version()
 
531
        return 0
 
532
        
 
533
    if not opt_no_plugins:
 
534
        from bzrlib.plugin import load_plugins
 
535
        load_plugins()
 
536
    else:
 
537
        from bzrlib.plugin import disable_plugins
 
538
        disable_plugins()
 
539
 
 
540
    cmd = str(argv.pop(0))
 
541
 
 
542
    cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
 
543
 
 
544
    try:
 
545
        if opt_lsprof:
 
546
            ret = apply_lsprofiled(opt_lsprof_file, cmd_obj.run_argv, argv)
 
547
        elif opt_profile:
 
548
            ret = apply_profiled(cmd_obj.run_argv, argv)
 
549
        else:
 
550
            ret = cmd_obj.run_argv(argv)
 
551
        return ret or 0
 
552
    finally:
 
553
        # reset, in case we may do other commands later within the same process
 
554
        be_quiet(False)
 
555
 
 
556
def display_command(func):
 
557
    """Decorator that suppresses pipe/interrupt errors."""
 
558
    def ignore_pipe(*args, **kwargs):
 
559
        try:
 
560
            result = func(*args, **kwargs)
 
561
            sys.stdout.flush()
 
562
            return result
 
563
        except IOError, e:
 
564
            if not hasattr(e, 'errno'):
 
565
                raise
 
566
            if e.errno != errno.EPIPE:
 
567
                raise
 
568
            pass
 
569
        except KeyboardInterrupt:
 
570
            pass
 
571
    return ignore_pipe
 
572
 
 
573
 
 
574
def main(argv):
 
575
    import bzrlib.ui
 
576
    from bzrlib.ui.text import TextUIFactory
 
577
    ## bzrlib.trace.enable_default_logging()
 
578
    bzrlib.trace.log_startup(argv)
 
579
    bzrlib.ui.ui_factory = TextUIFactory()
 
580
    ret = run_bzr_catch_errors(argv[1:])
 
581
    mutter("return code %d", ret)
 
582
    return ret
 
583
 
 
584
 
 
585
def run_bzr_catch_errors(argv):
 
586
    try:
 
587
        try:
 
588
            return run_bzr(argv)
 
589
        finally:
 
590
            # do this here inside the exception wrappers to catch EPIPE
 
591
            sys.stdout.flush()
 
592
    except Exception, e:
 
593
        # used to handle AssertionError and KeyboardInterrupt
 
594
        # specially here, but hopefully they're handled ok by the logger now
 
595
        import errno
 
596
        if (isinstance(e, IOError) 
 
597
            and hasattr(e, 'errno')
 
598
            and e.errno == errno.EPIPE):
 
599
            bzrlib.trace.note('broken pipe')
 
600
            return 3
 
601
        else:
 
602
            bzrlib.trace.log_exception()
 
603
            if os.environ.get('BZR_PDB'):
 
604
                print '**** entering debugger'
 
605
                import pdb
 
606
                pdb.post_mortem(sys.exc_traceback)
 
607
            return 3
 
608
 
 
609
if __name__ == '__main__':
 
610
    sys.exit(main(sys.argv))