1
# Copyright (C) 2006 by Canonical Ltd
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.
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.
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
18
# TODO: probably should say which arguments are candidates for glob
19
# expansion on windows and do that at the command level.
21
# TODO: Define arguments by objects, rather than just using names.
22
# Those objects can specify the expected type of the argument, which
23
# would help with validation and shell completion. They could also provide
24
# help/explanation for that argument in a structured way.
26
# TODO: Specific "examples" property on commands for consistent formatting.
28
# TODO: "--profile=cum", to change sort order. Is there any value in leaving
29
# the profile output behind so it can be interactively examined?
33
from warnings import warn
38
import bzrlib.errors as errors
39
from bzrlib.errors import (BzrError,
43
from bzrlib.option import Option
44
from bzrlib.revisionspec import RevisionSpec
45
from bzrlib.symbol_versioning import *
47
from bzrlib.trace import mutter, note, log_error, warning, be_quiet
52
def register_command(cmd, decorate=False):
53
"""Utility function to help register a command
55
:param cmd: Command subclass to register
56
:param decorate: If true, allow overriding an existing command
57
of the same name; the old command is returned by this function.
58
Otherwise it is an error to try to override an existing command.
62
if k.startswith("cmd_"):
63
k_unsquished = _unsquish_command_name(k)
66
if not plugin_cmds.has_key(k_unsquished):
67
plugin_cmds[k_unsquished] = cmd
68
mutter('registered plugin command %s', k_unsquished)
69
if decorate and k_unsquished in builtin_command_names():
70
return _builtin_commands()[k_unsquished]
72
result = plugin_cmds[k_unsquished]
73
plugin_cmds[k_unsquished] = cmd
76
log_error('Two plugins defined the same command: %r' % k)
77
log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
80
def _squish_command_name(cmd):
81
return 'cmd_' + cmd.replace('-', '_')
84
def _unsquish_command_name(cmd):
85
assert cmd.startswith("cmd_")
86
return cmd[4:].replace('_','-')
89
def _builtin_commands():
90
import bzrlib.builtins
92
builtins = bzrlib.builtins.__dict__
94
if name.startswith("cmd_"):
95
real_name = _unsquish_command_name(name)
96
r[real_name] = builtins[name]
100
def builtin_command_names():
101
"""Return list of builtin command names."""
102
return _builtin_commands().keys()
105
def plugin_command_names():
106
return plugin_cmds.keys()
109
def _get_cmd_dict(plugins_override=True):
110
"""Return name->class mapping for all commands."""
111
d = _builtin_commands()
113
d.update(plugin_cmds)
117
def get_all_cmds(plugins_override=True):
118
"""Return canonical name and class for all registered commands."""
119
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
123
def get_cmd_object(cmd_name, plugins_override=True):
124
"""Return the canonical name and command class for a command.
127
If true, plugin commands can override builtins.
129
from bzrlib.externalcommand import ExternalCommand
131
cmd_name = str(cmd_name) # not unicode
133
# first look up this command under the specified name
134
cmds = _get_cmd_dict(plugins_override=plugins_override)
136
return cmds[cmd_name]()
140
# look for any command which claims this as an alias
141
for real_cmd_name, cmd_class in cmds.iteritems():
142
if cmd_name in cmd_class.aliases:
145
cmd_obj = ExternalCommand.find_command(cmd_name)
149
raise BzrCommandError('unknown command "%s"' % cmd_name)
152
class Command(object):
153
"""Base class for commands.
155
Commands are the heart of the command-line bzr interface.
157
The command object mostly handles the mapping of command-line
158
parameters into one or more bzrlib operations, and of the results
161
Commands normally don't have any state. All their arguments are
162
passed in to the run method. (Subclasses may take a different
163
policy if the behaviour of the instance needs to depend on e.g. a
164
shell plugin and not just its Python class.)
166
The docstring for an actual command should give a single-line
167
summary, then a complete description of the command. A grammar
168
description will be inserted.
171
Other accepted names for this command.
174
List of argument forms, marked with whether they are optional,
179
['to_location', 'from_branch?', 'file*']
181
'to_location' is required
182
'from_branch' is optional
183
'file' can be specified 0 or more times
186
List of options that may be given for this command. These can
187
be either strings, referring to globally-defined options,
188
or option objects. Retrieve through options().
191
If true, this command isn't advertised. This is typically
192
for commands intended for expert users.
195
Command objects will get a 'outf' attribute, which has been
196
setup to properly handle encoding of unicode strings.
197
encoding_type determines what will happen when characters cannot
199
strict - abort if we cannot decode
200
replace - put in a bogus character (typically '?')
201
exact - do not encode sys.stdout
207
encoding_type = 'strict'
212
"""Construct an instance of this command."""
213
if self.__doc__ == Command.__doc__:
214
warn("No help message set for %r" % self)
217
"""Return dict of valid options for this command.
219
Maps from long option name to option object."""
221
r['help'] = Option.OPTIONS['help']
222
for o in self.takes_options:
223
if not isinstance(o, Option):
224
o = Option.OPTIONS[o]
228
def _setup_outf(self):
229
"""Return a file linked to stdout, which has proper encoding."""
230
assert self.encoding_type in ['strict', 'exact', 'replace']
232
# Originally I was using self.stdout, but that looks
233
# *way* too much like sys.stdout
234
if self.encoding_type == 'exact':
235
self.outf = sys.stdout
238
output_encoding = getattr(sys.stdout, 'encoding', None)
239
if not output_encoding:
240
input_encoding = getattr(sys.stdin, 'encoding', None)
241
if not input_encoding:
242
output_encoding = bzrlib.user_encoding
243
mutter('encoding stdout as bzrlib.user_encoding %r', output_encoding)
245
output_encoding = input_encoding
246
mutter('encoding stdout as sys.stdin encoding %r', output_encoding)
248
mutter('encoding stdout as sys.stdout encoding %r', output_encoding)
250
# use 'replace' so that we don't abort if trying to write out
251
# in e.g. the default C locale.
252
self.outf = codecs.getwriter(output_encoding)(sys.stdout, errors=self.encoding_type)
253
# For whatever reason codecs.getwriter() does not advertise its encoding
254
# it just returns the encoding of the wrapped file, which is completely
255
# bogus. So set the attribute, so we can find the correct encoding later.
256
self.outf.encoding = output_encoding
258
@deprecated_method(zero_eight)
259
def run_argv(self, argv):
260
"""Parse command line and run.
262
See run_argv_aliases for the 0.8 and beyond api.
264
return self.run_argv_aliases(argv)
266
def run_argv_aliases(self, argv, alias_argv=None):
267
"""Parse the command line and run with extra aliases in alias_argv."""
268
args, opts = parse_args(self, argv, alias_argv)
269
if 'help' in opts: # e.g. bzr add --help
270
from bzrlib.help import help_on_command
271
help_on_command(self.name())
273
# XXX: This should be handled by the parser
274
allowed_names = self.options().keys()
276
if oname not in allowed_names:
277
raise BzrOptionError("option '--%s' is not allowed for"
278
" command %r" % (oname, self.name()))
279
# mix arguments and options into one dictionary
280
cmdargs = _match_argform(self.name(), self.takes_args, args)
282
for k, v in opts.items():
283
cmdopts[k.replace('-', '_')] = v
285
all_cmd_args = cmdargs.copy()
286
all_cmd_args.update(cmdopts)
290
return self.run(**all_cmd_args)
293
"""Actually run the command.
295
This is invoked with the options and arguments bound to
298
Return 0 or None if the command was successful, or a non-zero
299
shell error code if not. It's OK for this method to allow
300
an exception to raise up.
302
raise NotImplementedError('no implementation of command %r'
306
"""Return help message for this class."""
307
from inspect import getdoc
308
if self.__doc__ is Command.__doc__:
313
return _unsquish_command_name(self.__class__.__name__)
315
def plugin_name(self):
316
"""Get the name of the plugin that provides this command.
318
:return: The name of the plugin or None if the command is builtin.
320
mod_parts = self.__module__.split('.')
321
if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
327
def parse_spec(spec):
333
>>> parse_spec("../@")
335
>>> parse_spec("../f/@35")
337
>>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
338
['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
343
parsed = spec.split('/@')
344
assert len(parsed) == 2
349
parsed[1] = int(parsed[1])
351
pass # We can allow stuff like ./@revid:blahblahblah
355
parsed = [spec, None]
358
def parse_args(command, argv, alias_argv=None):
359
"""Parse command line.
361
Arguments and options are parsed at this level before being passed
362
down to specific command handlers. This routine knows, from a
363
lookup table, something about the available options, what optargs
364
they take, and which commands will accept them.
366
# TODO: chop up this beast; make it a method of the Command
371
cmd_options = command.options()
373
proc_aliasarg = True # Are we processing alias_argv now?
374
for proc_argv in alias_argv, argv:
381
# We've received a standalone -- No more flags
385
# option names must not be unicode
389
mutter(" got option %r", a)
391
optname, optarg = a[2:].split('=', 1)
394
if optname not in cmd_options:
395
raise BzrCommandError('unknown option "%s"' % a)
398
if shortopt in Option.SHORT_OPTIONS:
399
# Multi-character options must have a space to delimit
401
# ^^^ what does this mean? mbp 20051014
402
optname = Option.SHORT_OPTIONS[shortopt].name
404
# Single character short options, can be chained,
405
# and have their value appended to their name
407
if shortopt not in Option.SHORT_OPTIONS:
408
# We didn't find the multi-character name, and we
409
# didn't find the single char name
410
raise BzrCommandError('unknown option "%s"' % a)
411
optname = Option.SHORT_OPTIONS[shortopt].name
414
# There are extra things on this option
415
# see if it is the value, or if it is another
417
optargfn = Option.OPTIONS[optname].type
419
# This option does not take an argument, so the
420
# next entry is another short option, pack it
422
proc_argv.insert(0, '-' + a[2:])
424
# This option takes an argument, so pack it
427
if optname not in cmd_options:
428
raise BzrCommandError('unknown option "%s"' % shortopt)
430
# XXX: Do we ever want to support this, e.g. for -r?
432
raise BzrCommandError('repeated option %r' % a)
433
elif optname in alias_opts:
434
# Replace what's in the alias with what's in the real
436
del alias_opts[optname]
438
proc_argv.insert(0, a)
441
raise BzrCommandError('repeated option %r' % a)
443
option_obj = cmd_options[optname]
444
optargfn = option_obj.type
448
raise BzrCommandError('option %r needs an argument' % a)
450
optarg = proc_argv.pop(0)
451
opts[optname] = optargfn(optarg)
453
alias_opts[optname] = optargfn(optarg)
456
raise BzrCommandError('option %r takes no argument' % optname)
459
alias_opts[optname] = True
462
proc_aliasarg = False # Done with alias argv
466
def _match_argform(cmd, takes_args, args):
469
# step through args and takes_args, allowing appropriate 0-many matches
470
for ap in takes_args:
474
argdict[argname] = args.pop(0)
475
elif ap[-1] == '*': # all remaining arguments
477
argdict[argname + '_list'] = args[:]
480
argdict[argname + '_list'] = None
483
raise BzrCommandError("command %r needs one or more %s"
484
% (cmd, argname.upper()))
486
argdict[argname + '_list'] = args[:]
488
elif ap[-1] == '$': # all but one
490
raise BzrCommandError("command %r needs one or more %s"
491
% (cmd, argname.upper()))
492
argdict[argname + '_list'] = args[:-1]
498
raise BzrCommandError("command %r requires argument %s"
499
% (cmd, argname.upper()))
501
argdict[argname] = args.pop(0)
504
raise BzrCommandError("extra argument to command %s: %s"
511
def apply_profiled(the_callable, *args, **kwargs):
515
pffileno, pfname = tempfile.mkstemp()
517
prof = hotshot.Profile(pfname)
519
ret = prof.runcall(the_callable, *args, **kwargs) or 0
522
stats = hotshot.stats.load(pfname)
524
stats.sort_stats('cum') # 'time'
525
## XXX: Might like to write to stderr or the trace file instead but
526
## print_stats seems hardcoded to stdout
527
stats.print_stats(20)
534
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
535
from bzrlib.lsprof import profile
537
ret, stats = profile(the_callable, *args, **kwargs)
543
cPickle.dump(stats, open(filename, 'w'), 2)
544
print 'Profile data written to %r.' % filename
549
"""Return an expanded alias, or None if no alias exists"""
551
alias = bzrlib.config.GlobalConfig().get_alias(cmd)
553
return alias.split(' ')
558
"""Execute a command.
560
This is similar to main(), but without all the trappings for
561
logging and error handling.
564
The command-line arguments, without the program name from argv[0]
565
These should already be decoded. All library/test code calling
566
run_bzr should be passing valid strings (don't need decoding).
568
Returns a command status or raises an exception.
570
Special master options: these must come before the command because
571
they control how the command is interpreted.
574
Do not load plugin modules at all
580
Only use builtin commands. (Plugins are still allowed to change
584
Run under the Python hotshot profiler.
587
Run under the Python lsprof profiler.
591
opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
592
opt_no_aliases = False
593
opt_lsprof_file = None
595
# --no-plugins is handled specially at a very early stage. We need
596
# to load plugins before doing other command parsing so that they
597
# can override commands, but this needs to happen first.
605
elif a == '--lsprof':
607
elif a == '--lsprof-file':
608
opt_lsprof_file = argv[i + 1]
610
elif a == '--no-plugins':
611
opt_no_plugins = True
612
elif a == '--no-aliases':
613
opt_no_aliases = True
614
elif a == '--builtin':
616
elif a in ('--quiet', '-q'):
624
from bzrlib.builtins import cmd_help
625
cmd_help().run_argv_aliases([])
628
if argv[0] == '--version':
629
from bzrlib.builtins import show_version
633
if not opt_no_plugins:
634
from bzrlib.plugin import load_plugins
637
from bzrlib.plugin import disable_plugins
642
if not opt_no_aliases:
643
alias_argv = get_alias(argv[0])
645
alias_argv = [a.decode(bzrlib.user_encoding) for a in alias_argv]
646
argv[0] = alias_argv.pop(0)
648
cmd = str(argv.pop(0))
650
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
651
if not getattr(cmd_obj.run_argv, 'is_deprecated', False):
652
run = cmd_obj.run_argv
655
run = cmd_obj.run_argv_aliases
656
run_argv = [argv, alias_argv]
660
ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
662
ret = apply_profiled(run, *run_argv)
667
# reset, in case we may do other commands later within the same process
670
def display_command(func):
671
"""Decorator that suppresses pipe/interrupt errors."""
672
def ignore_pipe(*args, **kwargs):
674
result = func(*args, **kwargs)
678
if not hasattr(e, 'errno'):
680
if e.errno != errno.EPIPE:
683
except KeyboardInterrupt:
690
from bzrlib.ui.text import TextUIFactory
691
## bzrlib.trace.enable_default_logging()
692
bzrlib.ui.ui_factory = TextUIFactory()
694
argv = [a.decode(bzrlib.user_encoding) for a in argv[1:]]
695
ret = run_bzr_catch_errors(argv)
696
mutter("return code %d", ret)
700
def run_bzr_catch_errors(argv):
705
# do this here inside the exception wrappers to catch EPIPE
708
# used to handle AssertionError and KeyboardInterrupt
709
# specially here, but hopefully they're handled ok by the logger now
710
bzrlib.trace.report_exception(sys.exc_info(), sys.stderr)
711
if os.environ.get('BZR_PDB'):
712
print '**** entering debugger'
714
pdb.post_mortem(sys.exc_traceback)
717
if __name__ == '__main__':
718
sys.exit(main(sys.argv))