/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

More work on roundtrip push support.

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