25
25
# would help with validation and shell completion.
28
# TODO: Help messages for options.
30
# TODO: Define arguments by objects, rather than just using names.
31
# Those objects can specify the expected type of the argument, which
32
# would help with validation and shell completion.
38
from warnings import warn
39
from inspect import getdoc
42
32
import bzrlib.trace
43
33
from bzrlib.trace import mutter, note, log_error, warning
44
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError, NotBranchError
45
from bzrlib.revisionspec import RevisionSpec
34
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
35
from bzrlib.branch import find_branch
46
36
from bzrlib import BZRDIR
76
67
def _parse_revision_str(revstr):
77
68
"""This handles a revision string -> revno.
79
This always returns a list. The list will have one element for
70
This always returns a list. The list will have one element for
72
It supports integers directly, but everything else it
73
defers for passing to Branch.get_revision_info()
82
75
>>> _parse_revision_str('234')
83
[<RevisionSpec_int 234>]
84
77
>>> _parse_revision_str('234..567')
85
[<RevisionSpec_int 234>, <RevisionSpec_int 567>]
86
79
>>> _parse_revision_str('..')
87
[<RevisionSpec None>, <RevisionSpec None>]
88
81
>>> _parse_revision_str('..234')
89
[<RevisionSpec None>, <RevisionSpec_int 234>]
90
83
>>> _parse_revision_str('234..')
91
[<RevisionSpec_int 234>, <RevisionSpec None>]
92
85
>>> _parse_revision_str('234..456..789') # Maybe this should be an error
93
[<RevisionSpec_int 234>, <RevisionSpec_int 456>, <RevisionSpec_int 789>]
94
87
>>> _parse_revision_str('234....789') # Error?
95
[<RevisionSpec_int 234>, <RevisionSpec None>, <RevisionSpec_int 789>]
96
89
>>> _parse_revision_str('revid:test@other.com-234234')
97
[<RevisionSpec_revid revid:test@other.com-234234>]
90
['revid:test@other.com-234234']
98
91
>>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
99
[<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_revid revid:test@other.com-234235>]
92
['revid:test@other.com-234234', 'revid:test@other.com-234235']
100
93
>>> _parse_revision_str('revid:test@other.com-234234..23')
101
[<RevisionSpec_revid revid:test@other.com-234234>, <RevisionSpec_int 23>]
94
['revid:test@other.com-234234', 23]
102
95
>>> _parse_revision_str('date:2005-04-12')
103
[<RevisionSpec_date date:2005-04-12>]
104
97
>>> _parse_revision_str('date:2005-04-12 12:24:33')
105
[<RevisionSpec_date date:2005-04-12 12:24:33>]
98
['date:2005-04-12 12:24:33']
106
99
>>> _parse_revision_str('date:2005-04-12T12:24:33')
107
[<RevisionSpec_date date:2005-04-12T12:24:33>]
100
['date:2005-04-12T12:24:33']
108
101
>>> _parse_revision_str('date:2005-04-12,12:24:33')
109
[<RevisionSpec_date date:2005-04-12,12:24:33>]
102
['date:2005-04-12,12:24:33']
110
103
>>> _parse_revision_str('-5..23')
111
[<RevisionSpec_int -5>, <RevisionSpec_int 23>]
112
105
>>> _parse_revision_str('-5')
113
[<RevisionSpec_int -5>]
114
107
>>> _parse_revision_str('123a')
115
Traceback (most recent call last):
117
BzrError: No namespace registered for string: '123a'
118
109
>>> _parse_revision_str('abc')
119
Traceback (most recent call last):
121
BzrError: No namespace registered for string: 'abc'
124
113
old_format_re = re.compile('\d*:\d*')
125
114
m = old_format_re.match(revstr)
128
116
warning('Colon separator for revision numbers is deprecated.'
129
117
' Use .. instead')
130
119
for rev in revstr.split(':'):
132
revs.append(RevisionSpec(int(rev)))
134
revs.append(RevisionSpec(None))
136
for x in revstr.split('..'):
138
revs.append(RevisionSpec(None))
140
revs.append(RevisionSpec(x))
121
revs.append(int(rev))
126
for x in revstr.split('..'):
144
def _builtin_commands():
137
def get_merge_type(typestring):
138
"""Attempt to find the merge class/factory associated with a string."""
139
from merge import merge_types
141
return merge_types[typestring][0]
143
templ = '%s%%7s: %%s' % (' '*12)
144
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
145
type_list = '\n'.join(lines)
146
msg = "No known merge type %s. Supported types are:\n%s" %\
147
(typestring, type_list)
148
raise BzrCommandError(msg)
151
def get_merge_type(typestring):
152
"""Attempt to find the merge class/factory associated with a string."""
153
from merge import merge_types
155
return merge_types[typestring][0]
157
templ = '%s%%7s: %%s' % (' '*12)
158
lines = [templ % (f[0], f[1][1]) for f in merge_types.iteritems()]
159
type_list = '\n'.join(lines)
160
msg = "No known merge type %s. Supported types are:\n%s" %\
161
(typestring, type_list)
162
raise BzrCommandError(msg)
166
def _get_cmd_dict(plugins_override=True):
145
167
import bzrlib.builtins
147
170
builtins = bzrlib.builtins.__dict__
148
171
for name in builtins:
149
172
if name.startswith("cmd_"):
150
real_name = _unsquish_command_name(name)
151
r[real_name] = builtins[name]
156
def builtin_command_names():
157
"""Return list of builtin command names."""
158
return _builtin_commands().keys()
161
def plugin_command_names():
162
return plugin_cmds.keys()
165
def _get_cmd_dict(plugins_override=True):
166
"""Return name->class mapping for all commands."""
167
d = _builtin_commands()
173
d[_unsquish_command_name(name)] = builtins[name]
174
# If we didn't load plugins, the plugin_cmds dict will be empty
168
175
if plugins_override:
169
176
d.update(plugin_cmds)
178
d2 = plugin_cmds.copy()
179
def get_cmd_object(cmd_name, plugins_override=True):
190
def get_cmd_class(cmd, plugins_override=True):
180
191
"""Return the canonical name and command class for a command.
183
If true, plugin commands can override builtins.
185
from bzrlib.externalcommand import ExternalCommand
187
cmd_name = str(cmd_name) # not unicode
193
cmd = str(cmd) # not unicode
189
195
# first look up this command under the specified name
190
196
cmds = _get_cmd_dict(plugins_override=plugins_override)
197
mutter("all commands: %r", cmds.keys())
192
return cmds[cmd_name]()
199
return cmd, cmds[cmd]
196
203
# look for any command which claims this as an alias
197
for real_cmd_name, cmd_class in cmds.iteritems():
198
if cmd_name in cmd_class.aliases:
201
cmd_obj = ExternalCommand.find_command(cmd_name)
205
raise BzrCommandError("unknown command %r" % cmd_name)
204
for cmdname, cmdclass in cmds.iteritems():
205
if cmd in cmdclass.aliases:
206
return cmdname, cmdclass
208
cmdclass = ExternalCommand.find_command(cmd)
212
raise BzrCommandError("unknown command %r" % cmd)
208
215
class Command(object):
209
216
"""Base class for commands.
211
Commands are the heart of the command-line bzr interface.
213
The command object mostly handles the mapping of command-line
214
parameters into one or more bzrlib operations, and of the results
217
Commands normally don't have any state. All their arguments are
218
passed in to the run method. (Subclasses may take a different
219
policy if the behaviour of the instance needs to depend on e.g. a
220
shell plugin and not just its Python class.)
222
218
The docstring for an actual command should give a single-line
223
219
summary, then a complete description of the command. A grammar
224
220
description will be inserted.
227
Other accepted names for this command.
230
223
List of argument forms, marked with whether they are optional,
248
"""Construct an instance of this command."""
239
def __init__(self, options, arguments):
240
"""Construct and run the command.
242
Sets self.status to the return value of run()."""
243
assert isinstance(options, dict)
244
assert isinstance(arguments, dict)
245
cmdargs = options.copy()
246
cmdargs.update(arguments)
249
247
if self.__doc__ == Command.__doc__:
248
from warnings import warn
250
249
warn("No help message set for %r" % self)
253
def run_argv(self, argv):
254
"""Parse command line and run."""
255
args, opts = parse_args(argv)
257
if 'help' in opts: # e.g. bzr add --help
258
from bzrlib.help import help_on_command
259
help_on_command(self.name())
262
# check options are reasonable
263
allowed = self.takes_options
265
if oname not in allowed:
266
raise BzrCommandError("option '--%s' is not allowed for command %r"
267
% (oname, self.name()))
269
# mix arguments and options into one dictionary
270
cmdargs = _match_argform(self.name(), self.takes_args, args)
272
for k, v in opts.items():
273
cmdopts[k.replace('-', '_')] = v
275
all_cmd_args = cmdargs.copy()
276
all_cmd_args.update(cmdopts)
278
return self.run(**all_cmd_args)
250
self.status = self.run(**cmdargs)
251
if self.status is None:
282
"""Actually run the command.
255
def run(self, *args, **kwargs):
256
"""Override this in sub-classes.
284
258
This is invoked with the options and arguments bound to
285
259
keyword parameters.
287
Return 0 or None if the command was successful, or a non-zero
288
shell error code if not. It's OK for this method to allow
289
an exception to raise up.
261
Return 0 or None if the command was successful, or a shell
291
264
raise NotImplementedError()
295
"""Return help message for this class."""
296
if self.__doc__ is Command.__doc__:
301
return _unsquish_command_name(self.__class__.__name__)
267
class ExternalCommand(Command):
268
"""Class to wrap external commands.
270
We cheat a little here, when get_cmd_class() calls us we actually
271
give it back an object we construct that has the appropriate path,
272
help, options etc for the specified command.
274
When run_bzr() tries to instantiate that 'class' it gets caught by
275
the __call__ method, which we override to call the Command.__init__
276
method. That then calls our run method which is pretty straight
279
The only wrinkle is that we have to map bzr's dictionary of options
280
and arguments back into command line options and arguments for the
284
def find_command(cls, cmd):
286
bzrpath = os.environ.get('BZRPATH', '')
288
for dir in bzrpath.split(os.pathsep):
289
path = os.path.join(dir, cmd)
290
if os.path.isfile(path):
291
return ExternalCommand(path)
295
find_command = classmethod(find_command)
297
def __init__(self, path):
300
pipe = os.popen('%s --bzr-usage' % path, 'r')
301
self.takes_options = pipe.readline().split()
303
for opt in self.takes_options:
304
if not opt in OPTIONS:
305
raise BzrError("Unknown option '%s' returned by external command %s"
308
# TODO: Is there any way to check takes_args is valid here?
309
self.takes_args = pipe.readline().split()
311
if pipe.close() is not None:
312
raise BzrError("Failed funning '%s --bzr-usage'" % path)
314
pipe = os.popen('%s --bzr-help' % path, 'r')
315
self.__doc__ = pipe.read()
316
if pipe.close() is not None:
317
raise BzrError("Failed funning '%s --bzr-help'" % path)
319
def __call__(self, options, arguments):
320
Command.__init__(self, options, arguments)
323
def run(self, **kargs):
330
optname = name.replace('_','-')
332
if OPTIONS.has_key(optname):
334
opts.append('--%s' % optname)
335
if value is not None and value is not True:
336
opts.append(str(value))
338
# it's an arg, or arg list
339
if type(value) is not list:
345
self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
304
350
def parse_spec(spec):
392
440
>>> parse_args('commit --message=biter'.split())
393
441
(['commit'], {'message': u'biter'})
394
442
>>> parse_args('log -r 500'.split())
395
(['log'], {'revision': [<RevisionSpec_int 500>]})
443
(['log'], {'revision': [500]})
396
444
>>> parse_args('log -r500..600'.split())
397
(['log'], {'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
445
(['log'], {'revision': [500, 600]})
398
446
>>> parse_args('log -vr500..600'.split())
399
(['log'], {'verbose': True, 'revision': [<RevisionSpec_int 500>, <RevisionSpec_int 600>]})
400
>>> parse_args('log -rrevno:500..600'.split()) #the r takes an argument
401
(['log'], {'revision': [<RevisionSpec_revno revno:500>, <RevisionSpec_int 600>]})
447
(['log'], {'verbose': True, 'revision': [500, 600]})
448
>>> parse_args('log -rv500..600'.split()) #the r takes an argument
449
(['log'], {'revision': ['v500', 600]})
594
if (not argv) or (argv[0] == '--help'):
617
if not opt_no_plugins:
618
from bzrlib.plugin import load_plugins
621
args, opts = parse_args(argv)
595
624
from bzrlib.help import help
602
if argv[0] == '--version':
631
if 'version' in opts:
603
632
from bzrlib.builtins import show_version
607
if not opt_no_plugins:
608
from bzrlib.plugin import load_plugins
611
cmd = str(argv.pop(0))
613
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
637
from bzrlib.help import help
641
cmd = str(args.pop(0))
643
canonical_cmd, cmd_class = \
644
get_cmd_class(cmd, plugins_override=not opt_builtin)
646
# check options are reasonable
647
allowed = cmd_class.takes_options
649
if oname not in allowed:
650
raise BzrCommandError("option '--%s' is not allowed for command %r"
653
# mix arguments and options into one dictionary
654
cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
656
for k, v in opts.items():
657
cmdopts[k.replace('-', '_')] = v
616
ret = apply_profiled(cmd_obj.run_argv, argv)
660
import hotshot, tempfile
661
pffileno, pfname = tempfile.mkstemp()
663
prof = hotshot.Profile(pfname)
664
ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
668
stats = hotshot.stats.load(pfname)
670
stats.sort_stats('time')
671
## XXX: Might like to write to stderr or the trace file instead but
672
## print_stats seems hardcoded to stdout
673
stats.print_stats(20)
618
ret = cmd_obj.run_argv(argv)
681
return cmd_class(cmdopts, cmdargs).status