1
# Copyright (C) 2004, 2005 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: Help messages for options.
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.
33
from bzrlib.trace import mutter, note, log_error, warning
34
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
35
from bzrlib.branch import find_branch
36
from bzrlib import BZRDIR
42
def register_command(cmd):
43
"Utility function to help register a command"
46
if k.startswith("cmd_"):
47
k_unsquished = _unsquish_command_name(k)
50
if not plugin_cmds.has_key(k_unsquished):
51
plugin_cmds[k_unsquished] = cmd
52
mutter('registered plugin command %s', k_unsquished)
54
log_error('Two plugins defined the same command: %r' % k)
55
log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
58
def _squish_command_name(cmd):
59
return 'cmd_' + cmd.replace('-', '_')
62
def _unsquish_command_name(cmd):
63
assert cmd.startswith("cmd_")
64
return cmd[4:].replace('_','-')
67
def _parse_revision_str(revstr):
68
"""This handles a revision string -> revno.
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()
75
>>> _parse_revision_str('234')
77
>>> _parse_revision_str('234..567')
79
>>> _parse_revision_str('..')
81
>>> _parse_revision_str('..234')
83
>>> _parse_revision_str('234..')
85
>>> _parse_revision_str('234..456..789') # Maybe this should be an error
87
>>> _parse_revision_str('234....789') # Error?
89
>>> _parse_revision_str('revid:test@other.com-234234')
90
['revid:test@other.com-234234']
91
>>> _parse_revision_str('revid:test@other.com-234234..revid:test@other.com-234235')
92
['revid:test@other.com-234234', 'revid:test@other.com-234235']
93
>>> _parse_revision_str('revid:test@other.com-234234..23')
94
['revid:test@other.com-234234', 23]
95
>>> _parse_revision_str('date:2005-04-12')
97
>>> _parse_revision_str('date:2005-04-12 12:24:33')
98
['date:2005-04-12 12:24:33']
99
>>> _parse_revision_str('date:2005-04-12T12:24:33')
100
['date:2005-04-12T12:24:33']
101
>>> _parse_revision_str('date:2005-04-12,12:24:33')
102
['date:2005-04-12,12:24:33']
103
>>> _parse_revision_str('-5..23')
105
>>> _parse_revision_str('-5')
107
>>> _parse_revision_str('123a')
109
>>> _parse_revision_str('abc')
113
old_format_re = re.compile('\d*:\d*')
114
m = old_format_re.match(revstr)
116
warning('Colon separator for revision numbers is deprecated.'
119
for rev in revstr.split(':'):
121
revs.append(int(rev))
126
for x in revstr.split('..'):
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):
167
import bzrlib.builtins
170
builtins = bzrlib.builtins.__dict__
171
for name in builtins:
172
if name.startswith("cmd_"):
173
d[_unsquish_command_name(name)] = builtins[name]
174
# If we didn't load plugins, the plugin_cmds dict will be empty
176
d.update(plugin_cmds)
178
d2 = plugin_cmds.copy()
184
def get_all_cmds(plugins_override=True):
185
"""Return canonical name and class for all registered commands."""
186
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
190
def get_cmd_class(cmd, plugins_override=True):
191
"""Return the canonical name and command class for a command.
193
cmd = str(cmd) # not unicode
195
# first look up this command under the specified name
196
cmds = _get_cmd_dict(plugins_override=plugins_override)
197
mutter("all commands: %r", cmds.keys())
199
return cmd, cmds[cmd]
203
# look for any command which claims this as an alias
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)
215
class Command(object):
216
"""Base class for commands.
218
The docstring for an actual command should give a single-line
219
summary, then a complete description of the command. A grammar
220
description will be inserted.
223
List of argument forms, marked with whether they are optional,
227
List of options that may be given for this command.
230
If true, this command isn't advertised.
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)
247
if self.__doc__ == Command.__doc__:
248
from warnings import warn
249
warn("No help message set for %r" % self)
250
self.status = self.run(**cmdargs)
251
if self.status is None:
255
def run(self, *args, **kwargs):
256
"""Override this in sub-classes.
258
This is invoked with the options and arguments bound to
261
Return 0 or None if the command was successful, or a shell
264
raise NotImplementedError()
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)
350
def parse_spec(spec):
356
>>> parse_spec("../@")
358
>>> parse_spec("../f/@35")
360
>>> parse_spec('./@revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67')
361
['.', 'revid:john@arbash-meinel.com-20050711044610-3ca0327c6a222f67']
366
parsed = spec.split('/@')
367
assert len(parsed) == 2
372
parsed[1] = int(parsed[1])
374
pass # We can allow stuff like ./@revid:blahblahblah
378
parsed = [spec, None]
384
# list of all available options; the rhs can be either None for an
385
# option that takes no argument, or a constructor function that checks
398
'revision': _parse_revision_str,
410
'merge-type': get_merge_type,
424
def parse_args(argv):
425
"""Parse command line.
427
Arguments and options are parsed at this level before being passed
428
down to specific command handlers. This routine knows, from a
429
lookup table, something about the available options, what optargs
430
they take, and which commands will accept them.
432
>>> parse_args('--help'.split())
434
>>> parse_args('help -- --invalidcmd'.split())
435
(['help', '--invalidcmd'], {})
436
>>> parse_args('--version'.split())
437
([], {'version': True})
438
>>> parse_args('status --all'.split())
439
(['status'], {'all': True})
440
>>> parse_args('commit --message=biter'.split())
441
(['commit'], {'message': u'biter'})
442
>>> parse_args('log -r 500'.split())
443
(['log'], {'revision': [500]})
444
>>> parse_args('log -r500..600'.split())
445
(['log'], {'revision': [500, 600]})
446
>>> parse_args('log -vr500..600'.split())
447
(['log'], {'verbose': True, 'revision': [500, 600]})
448
>>> parse_args('log -rv500..600'.split()) #the r takes an argument
449
(['log'], {'revision': ['v500', 600]})
457
if not argsover and a[0] == '-':
458
# option names must not be unicode
463
# We've received a standalone -- No more flags
466
mutter(" got option %r" % a)
468
optname, optarg = a[2:].split('=', 1)
471
if optname not in OPTIONS:
472
raise BzrError('unknown long option %r' % a)
475
if shortopt in SHORT_OPTIONS:
476
# Multi-character options must have a space to delimit
478
optname = SHORT_OPTIONS[shortopt]
480
# Single character short options, can be chained,
481
# and have their value appended to their name
483
if shortopt not in SHORT_OPTIONS:
484
# We didn't find the multi-character name, and we
485
# didn't find the single char name
486
raise BzrError('unknown short option %r' % a)
487
optname = SHORT_OPTIONS[shortopt]
490
# There are extra things on this option
491
# see if it is the value, or if it is another
493
optargfn = OPTIONS[optname]
495
# This option does not take an argument, so the
496
# next entry is another short option, pack it back
498
argv.insert(0, '-' + a[2:])
500
# This option takes an argument, so pack it
505
# XXX: Do we ever want to support this, e.g. for -r?
506
raise BzrError('repeated option %r' % a)
508
optargfn = OPTIONS[optname]
512
raise BzrError('option %r needs an argument' % a)
515
opts[optname] = optargfn(optarg)
518
raise BzrError('option %r takes no argument' % optname)
528
def _match_argform(cmd, takes_args, args):
531
# step through args and takes_args, allowing appropriate 0-many matches
532
for ap in takes_args:
536
argdict[argname] = args.pop(0)
537
elif ap[-1] == '*': # all remaining arguments
539
argdict[argname + '_list'] = args[:]
542
argdict[argname + '_list'] = None
545
raise BzrCommandError("command %r needs one or more %s"
546
% (cmd, argname.upper()))
548
argdict[argname + '_list'] = args[:]
550
elif ap[-1] == '$': # all but one
552
raise BzrCommandError("command %r needs one or more %s"
553
% (cmd, argname.upper()))
554
argdict[argname + '_list'] = args[:-1]
560
raise BzrCommandError("command %r requires argument %s"
561
% (cmd, argname.upper()))
563
argdict[argname] = args.pop(0)
566
raise BzrCommandError("extra argument to command %s: %s"
574
"""Execute a command.
576
This is similar to main(), but without all the trappings for
577
logging and error handling.
580
The command-line arguments, without the program name from argv[0]
582
Returns a command status or raises an exception.
584
Special master options: these must come before the command because
585
they control how the command is interpreted.
588
Do not load plugin modules at all
591
Only use builtin commands. (Plugins are still allowed to change
595
Run under the Python profiler.
598
argv = [a.decode(bzrlib.user_encoding) for a in argv]
600
opt_profile = opt_no_plugins = opt_builtin = False
602
# --no-plugins is handled specially at a very early stage. We need
603
# to load plugins before doing other command parsing so that they
604
# can override commands, but this needs to happen first.
609
elif a == '--no-plugins':
610
opt_no_plugins = True
611
elif a == '--builtin':
617
if not opt_no_plugins:
618
from bzrlib.plugin import load_plugins
621
args, opts = parse_args(argv)
624
from bzrlib.help import help
631
if 'version' in opts:
632
from bzrlib.builtins import show_version
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
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)
681
return cmd_class(cmdopts, cmdargs).status
686
bzrlib.trace.log_startup(argv)
687
bzrlib.ui.ui_factory = bzrlib.ui.TextUIFactory()
691
return run_bzr(argv[1:])
693
# do this here inside the exception wrappers to catch EPIPE
695
except BzrCommandError, e:
696
# command line syntax error, etc
700
bzrlib.trace.log_exception()
702
except AssertionError, e:
703
bzrlib.trace.log_exception('assertion failed: ' + str(e))
705
except KeyboardInterrupt, e:
706
bzrlib.trace.note('interrupted')
710
if (isinstance(e, IOError)
711
and hasattr(e, 'errno')
712
and e.errno == errno.EPIPE):
713
bzrlib.trace.note('broken pipe')
716
bzrlib.trace.log_exception()
720
if __name__ == '__main__':
721
sys.exit(main(sys.argv))