1
# Copyright (C) 2006, 2008 Canonical Ltd
1
# Copyright (C) 2004, 2005 by Martin Pool
2
# Copyright (C) 2005 by Canonical Ltd
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
5
7
# the Free Software Foundation; either version 2 of the License, or
6
8
# (at your option) any later version.
8
10
# This program is distributed in the hope that it will be useful,
9
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
13
# GNU General Public License for more details.
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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?
34
from bzrlib.lazy_import import lazy_import
35
lazy_import(globals(), """
38
from warnings import warn
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
"""Bazaar-NG -- a free distributed version-control tool
22
**WARNING: THIS IS AN UNSTABLE DEVELOPMENT VERSION**
24
* Metadata format is not stable yet -- you may need to
25
discard history in the future.
27
* Many commands unimplemented or partially implemented.
29
* Space-inefficient storage.
31
* No merge operators yet.
38
Show software version/licence/non-warranty.
40
Start versioning the current directory
44
Show revision history.
47
bzr move FROM... DESTDIR
48
Move one or more files to a different directory.
50
Show changes from last revision to working copy.
51
bzr commit -m 'MESSAGE'
52
Store current state as new revision.
53
bzr export REVNO DESTINATION
54
Export the branch state at a previous version.
56
Show summary of pending changes.
58
Make a file not versioned.
60
Show statistics about this branch.
62
Verify history is stored safely.
63
(for more type 'bzr help commands')
69
import sys, os, time, types, shutil, tempfile, fnmatch, difflib, os.path
71
from pprint import pprint
74
from inspect import getdoc
51
from bzrlib import registry
53
from bzrlib.hooks import HookPoint, Hooks
54
from bzrlib.option import Option
57
class CommandInfo(object):
58
"""Information about a command."""
60
def __init__(self, aliases):
61
"""The list of aliases for the command."""
62
self.aliases = aliases
65
def from_command(klass, command):
66
"""Factory to construct a CommandInfo from a command."""
67
return klass(command.aliases)
70
class CommandRegistry(registry.Registry):
73
def _get_name(command_name):
74
if command_name.startswith("cmd_"):
75
return _unsquish_command_name(command_name)
79
def register(self, cmd, decorate=False):
80
"""Utility function to help register a command
82
:param cmd: Command subclass to register
83
:param decorate: If true, allow overriding an existing command
84
of the same name; the old command is returned by this function.
85
Otherwise it is an error to try to override an existing command.
88
k_unsquished = self._get_name(k)
90
previous = self.get(k_unsquished)
92
previous = _builtin_commands().get(k_unsquished)
93
info = CommandInfo.from_command(cmd)
95
registry.Registry.register(self, k_unsquished, cmd,
96
override_existing=decorate, info=info)
98
trace.log_error('Two plugins defined the same command: %r' % k)
99
trace.log_error('Not loading the one in %r' %
100
sys.modules[cmd.__module__])
101
trace.log_error('Previously this command was registered from %r' %
102
sys.modules[previous.__module__])
105
def register_lazy(self, command_name, aliases, module_name):
106
"""Register a command without loading its module.
108
:param command_name: The primary name of the command.
109
:param aliases: A list of aliases for the command.
110
:module_name: The module that the command lives in.
112
key = self._get_name(command_name)
113
registry.Registry.register_lazy(self, key, module_name, command_name,
114
info=CommandInfo(aliases))
117
plugin_cmds = CommandRegistry()
120
def register_command(cmd, decorate=False):
122
return plugin_cmds.register(cmd, decorate)
125
def _squish_command_name(cmd):
126
return 'cmd_' + cmd.replace('-', '_')
129
def _unsquish_command_name(cmd):
130
return cmd[4:].replace('_','-')
133
def _builtin_commands():
134
import bzrlib.builtins
136
builtins = bzrlib.builtins.__dict__
137
for name in builtins:
138
if name.startswith("cmd_"):
139
real_name = _unsquish_command_name(name)
140
r[real_name] = builtins[name]
144
def builtin_command_names():
145
"""Return list of builtin command names."""
146
return _builtin_commands().keys()
149
def plugin_command_names():
150
return plugin_cmds.keys()
153
def _get_cmd_dict(plugins_override=True):
154
"""Return name->class mapping for all commands."""
155
d = _builtin_commands()
157
d.update(plugin_cmds.iteritems())
161
def get_all_cmds(plugins_override=True):
162
"""Return canonical name and class for all registered commands."""
163
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
167
def get_cmd_object(cmd_name, plugins_override=True):
168
"""Return the canonical name and command class for a command.
171
If true, plugin commands can override builtins.
174
cmd = _get_cmd_object(cmd_name, plugins_override)
175
# Allow plugins to extend commands
176
for hook in Command.hooks['extend_command']:
180
raise errors.BzrCommandError('unknown command "%s"' % cmd_name)
183
def _get_cmd_object(cmd_name, plugins_override=True):
184
"""Worker for get_cmd_object which raises KeyError rather than BzrCommandError."""
185
from bzrlib.externalcommand import ExternalCommand
187
# We want only 'ascii' command names, but the user may have typed
188
# in a Unicode name. In that case, they should just get a
189
# 'command not found' error later.
190
# In the future, we may actually support Unicode command names.
192
# first look up this command under the specified name
195
return plugin_cmds.get(cmd_name)()
198
cmds = _get_cmd_dict(plugins_override=False)
200
return cmds[cmd_name]()
204
for key in plugin_cmds.keys():
205
info = plugin_cmds.get_info(key)
206
if cmd_name in info.aliases:
207
return plugin_cmds.get(key)()
208
# look for any command which claims this as an alias
209
for real_cmd_name, cmd_class in cmds.iteritems():
210
if cmd_name in cmd_class.aliases:
213
cmd_obj = ExternalCommand.find_command(cmd_name)
217
# look for plugins that provide this command but aren't installed
218
for provider in command_providers_registry:
220
plugin_metadata = provider.plugin_for_command(cmd_name)
221
except errors.NoPluginAvailable:
224
raise errors.CommandAvailableInPlugin(cmd_name,
225
plugin_metadata, provider)
229
class Command(object):
230
"""Base class for commands.
232
Commands are the heart of the command-line bzr interface.
234
The command object mostly handles the mapping of command-line
235
parameters into one or more bzrlib operations, and of the results
238
Commands normally don't have any state. All their arguments are
239
passed in to the run method. (Subclasses may take a different
240
policy if the behaviour of the instance needs to depend on e.g. a
241
shell plugin and not just its Python class.)
243
The docstring for an actual command should give a single-line
244
summary, then a complete description of the command. A grammar
245
description will be inserted.
248
Other accepted names for this command.
251
List of argument forms, marked with whether they are optional,
256
['to_location', 'from_branch?', 'file*']
258
'to_location' is required
259
'from_branch' is optional
260
'file' can be specified 0 or more times
263
List of options that may be given for this command. These can
264
be either strings, referring to globally-defined options,
265
or option objects. Retrieve through options().
268
If true, this command isn't advertised. This is typically
269
for commands intended for expert users.
272
Command objects will get a 'outf' attribute, which has been
273
setup to properly handle encoding of unicode strings.
274
encoding_type determines what will happen when characters cannot
276
strict - abort if we cannot decode
277
replace - put in a bogus character (typically '?')
278
exact - do not encode sys.stdout
280
NOTE: by default on Windows, sys.stdout is opened as a text
281
stream, therefore LF line-endings are converted to CRLF.
282
When a command uses encoding_type = 'exact', then
283
sys.stdout is forced to be a binary stream, and line-endings
286
:cvar hooks: An instance of CommandHooks.
291
encoding_type = 'strict'
296
"""Construct an instance of this command."""
297
if self.__doc__ == Command.__doc__:
298
warn("No help message set for %r" % self)
299
# List of standard options directly supported
300
self.supported_std_options = []
302
def _maybe_expand_globs(self, file_list):
303
"""Glob expand file_list if the platform does not do that itself.
305
:return: A possibly empty list of unicode paths.
307
Introduced in bzrlib 0.18.
311
if sys.platform == 'win32':
312
file_list = win32utils.glob_expand(file_list)
313
return list(file_list)
316
"""Return single-line grammar for this command.
318
Only describes arguments, not options.
320
s = 'bzr ' + self.name() + ' '
321
for aname in self.takes_args:
322
aname = aname.upper()
323
if aname[-1] in ['$', '+']:
324
aname = aname[:-1] + '...'
325
elif aname[-1] == '?':
326
aname = '[' + aname[:-1] + ']'
327
elif aname[-1] == '*':
328
aname = '[' + aname[:-1] + '...]'
330
s = s[:-1] # remove last space
333
def get_help_text(self, additional_see_also=None, plain=True,
334
see_also_as_links=False, verbose=True):
335
"""Return a text string with help for this command.
337
:param additional_see_also: Additional help topics to be
339
:param plain: if False, raw help (reStructuredText) is
340
returned instead of plain text.
341
:param see_also_as_links: if True, convert items in 'See also'
342
list to internal links (used by bzr_man rstx generator)
343
:param verbose: if True, display the full help, otherwise
344
leave out the descriptive sections and just display
345
usage help (e.g. Purpose, Usage, Options) with a
346
message explaining how to obtain full help.
350
raise NotImplementedError("sorry, no detailed help yet for %r" % self.name())
352
# Extract the summary (purpose) and sections out from the text
353
purpose,sections,order = self._get_help_parts(doc)
355
# If a custom usage section was provided, use it
356
if sections.has_key('Usage'):
357
usage = sections.pop('Usage')
359
usage = self._usage()
361
# The header is the purpose and usage
363
result += ':Purpose: %s\n' % purpose
364
if usage.find('\n') >= 0:
365
result += ':Usage:\n%s\n' % usage
367
result += ':Usage: %s\n' % usage
371
options = option.get_optparser(self.options()).format_option_help()
372
if options.startswith('Options:'):
373
result += ':' + options
374
elif options.startswith('options:'):
375
# Python 2.4 version of optparse
376
result += ':Options:' + options[len('options:'):]
77
from bzrlib.store import ImmutableStore
78
from bzrlib.trace import mutter, note, log_error
79
from bzrlib.errors import bailout, BzrError, BzrCheckError
80
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
81
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
82
from bzrlib.revision import Revision
83
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
86
BZR_DIFF_FORMAT = "## Bazaar-NG diff, format 0 ##\n"
87
BZR_PATCHNAME_FORMAT = 'cset:sha1:%s'
89
## standard representation
90
NONE_STRING = '(none)'
94
## TODO: Perhaps a different version of inventory commands that
95
## returns iterators...
97
## TODO: Perhaps an AtomicFile class that writes to a temporary file and then renames.
99
## TODO: Some kind of locking on branches. Perhaps there should be a
100
## parameter to the branch object saying whether we want a read or
101
## write lock; release it from destructor. Perhaps don't even need a
102
## read lock to look at immutable objects?
104
## TODO: Perhaps make UUIDs predictable in test mode to make it easier
105
## to compare output?
107
## TODO: Some kind of global code to generate the right Branch object
108
## to work on. Almost, but not quite all, commands need one, and it
109
## can be taken either from their parameters or their working
124
def get_cmd_handler(cmd):
127
cmd = cmd_aliases.get(cmd, cmd)
130
cmd_handler = globals()['cmd_' + cmd.replace('-', '_')]
132
raise BzrError("unknown command %r" % cmd)
134
return cmd, cmd_handler
138
def cmd_status(all=False):
139
"""Display status summary.
141
For each file there is a single line giving its file state and name.
142
The name is that in the current revision unless it is deleted or
143
missing, in which case the old name is shown.
145
#import bzrlib.status
146
#bzrlib.status.tree_status(Branch('.'))
147
Branch('.').show_status(show_all=all)
151
######################################################################
153
def cmd_get_revision(revision_id):
154
Branch('.').get_revision(revision_id).write_xml(sys.stdout)
157
def cmd_get_file_text(text_id):
158
"""Get contents of a file by hash."""
159
sf = Branch('.').text_store[text_id]
160
pumpfile(sf, sys.stdout)
164
######################################################################
169
"""Show number of revisions on this branch"""
170
print Branch('.').revno()
174
def cmd_add(file_list, verbose=False):
175
"""Add specified files or directories.
177
In non-recursive mode, all the named items are added, regardless
178
of whether they were previously ignored. A warning is given if
179
any of the named files are already versioned.
181
In recursive mode (the default), files are treated the same way
182
but the behaviour for directories is different. Directories that
183
are already versioned do not give a warning. All directories,
184
whether already versioned or not, are searched for files or
185
subdirectories that are neither versioned or ignored, and these
186
are added. This search proceeds recursively into versioned
189
Therefore simply saying 'bzr add .' will version all files that
190
are currently unknown.
192
TODO: Perhaps adding a file whose directly is not versioned should
193
recursively add that parent, rather than giving an error?
195
bzrlib.add.smart_add(file_list, verbose)
198
def cmd_relpath(filename):
199
"""Show path of file relative to root"""
200
print Branch(filename).relpath(filename)
204
def cmd_inventory(revision=None):
205
"""Show inventory of the current working copy."""
206
## TODO: Also optionally show a previous inventory
207
## TODO: Format options
210
inv = b.read_working_inventory()
212
inv = b.get_revision_inventory(b.lookup_revision(revision))
214
for path, entry in inv.iter_entries():
215
print '%-50s %s' % (entry.file_id, path)
219
# TODO: Maybe a 'mv' command that has the combined move/rename
220
# special behaviour of Unix?
222
def cmd_move(source_list, dest):
225
b.move([b.relpath(s) for s in source_list], b.relpath(dest))
229
def cmd_rename(from_name, to_name):
230
"""Change the name of an entry.
232
usage: bzr rename FROM_NAME TO_NAME
235
bzr rename frob.c frobber.c
236
bzr rename src/frob.c lib/frob.c
238
It is an error if the destination name exists.
240
See also the 'move' command, which moves files into a different
241
directory without changing their name.
243
TODO: Some way to rename multiple files without invoking bzr for each
246
b.rename_one(b.relpath(from_name), b.relpath(to_name))
251
def cmd_renames(dir='.'):
252
"""Show list of renamed files.
254
usage: bzr renames [BRANCH]
256
TODO: Option to show renames between two historical versions.
258
TODO: Only show renames under dir, rather than in the whole branch.
261
old_inv = b.basis_tree().inventory
262
new_inv = b.read_working_inventory()
264
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
266
for old_name, new_name in renames:
267
print "%s => %s" % (old_name, new_name)
272
"""info: Show statistical information for this branch
276
info.show_info(Branch('.'))
280
def cmd_remove(file_list, verbose=False):
281
b = Branch(file_list[0])
282
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
286
def cmd_file_id(filename):
287
"""Print file_id of a particular file or directory.
289
usage: bzr file-id FILE
291
The file_id is assigned when the file is first added and remains the
292
same through all revisions where the file exists, even when it is
296
i = b.inventory.path2id(b.relpath(filename))
298
bailout("%r is not a versioned file" % filename)
303
def cmd_file_id_path(filename):
304
"""Print path of file_ids to a file or directory.
306
usage: bzr file-id-path FILE
308
This prints one line for each directory down to the target,
309
starting at the branch root."""
312
fid = inv.path2id(b.relpath(filename))
314
bailout("%r is not a versioned file" % filename)
315
for fip in inv.get_idpath(fid):
319
def cmd_revision_history():
320
for patchid in Branch('.').revision_history():
324
def cmd_directories():
325
for name, ie in Branch('.').read_working_inventory().directories():
333
for name, ie in Branch('.').working_tree().missing():
338
# TODO: Check we're not already in a working directory? At the
339
# moment you'll get an ugly error.
341
# TODO: What if we're in a subdirectory of a branch? Would like
342
# to allow that, but then the parent may need to understand that
343
# the children have disappeared, or should they be versioned in
346
# TODO: Take an argument/option for branch name.
347
Branch('.', init=True)
350
def cmd_diff(revision=None, file_list=None):
351
"""bzr diff: Show differences in working tree.
353
usage: bzr diff [-r REV] [FILE...]
356
Show changes since REV, rather than predecessor.
358
If files are listed, only the changes in those files are listed.
359
Otherwise, all changes for the tree are listed.
361
TODO: Given two revision arguments, show the difference between them.
363
TODO: Allow diff across branches.
365
TODO: Option to use external diff command; could be GNU diff, wdiff,
368
TODO: Python difflib is not exactly the same as unidiff; should
369
either fix it up or prefer to use an external diff.
371
TODO: If a directory is given, diff everything under that.
373
TODO: Selected-file diff is inefficient and doesn't show you
376
TODO: This probably handles non-Unix newlines poorly.
379
## TODO: Shouldn't be in the cmd function.
384
old_tree = b.basis_tree()
386
old_tree = b.revision_tree(b.lookup_revision(revision))
388
new_tree = b.working_tree()
390
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
394
DEVNULL = '/dev/null'
395
# Windows users, don't panic about this filename -- it is a
396
# special signal to GNU patch that the file should be created or
397
# deleted respectively.
399
# TODO: Generation of pseudo-diffs for added/deleted files could
400
# be usefully made into a much faster special case.
402
# TODO: Better to return them in sorted order I think.
405
file_list = [b.relpath(f) for f in file_list]
407
# FIXME: If given a file list, compare only those files rather
408
# than comparing everything and then throwing stuff away.
410
for file_state, fid, old_name, new_name, kind in bzrlib.diff_trees(old_tree, new_tree):
412
if file_list and (new_name not in file_list):
415
# Don't show this by default; maybe do it if an option is passed
416
# idlabel = ' {%s}' % fid
419
# FIXME: Something about the diff format makes patch unhappy
420
# with newly-added files.
422
def diffit(oldlines, newlines, **kw):
424
# FIXME: difflib is wrong if there is no trailing newline.
425
# The syntax used by patch seems to be "\ No newline at
426
# end of file" following the last diff line from that
427
# file. This is not trivial to insert into the
428
# unified_diff output and it might be better to just fix
429
# or replace that function.
431
# In the meantime we at least make sure the patch isn't
435
# Special workaround for Python2.3, where difflib fails if
436
# both sequences are empty.
437
if not oldlines and not newlines:
442
if oldlines and (oldlines[-1][-1] != '\n'):
445
if newlines and (newlines[-1][-1] != '\n'):
449
ud = difflib.unified_diff(oldlines, newlines, **kw)
450
sys.stdout.writelines(ud)
452
print "\\ No newline at end of file"
453
sys.stdout.write('\n')
455
if file_state in ['.', '?', 'I']:
457
elif file_state == 'A':
458
print '*** added %s %r' % (kind, new_name)
461
new_tree.get_file(fid).readlines(),
463
tofile=new_label + new_name + idlabel)
464
elif file_state == 'D':
465
assert isinstance(old_name, types.StringTypes)
466
print '*** deleted %s %r' % (kind, old_name)
468
diffit(old_tree.get_file(fid).readlines(), [],
469
fromfile=old_label + old_name + idlabel,
471
elif file_state in ['M', 'R']:
472
if file_state == 'M':
473
assert kind == 'file'
474
assert old_name == new_name
475
print '*** modified %s %r' % (kind, new_name)
476
elif file_state == 'R':
477
print '*** renamed %s %r => %r' % (kind, old_name, new_name)
480
diffit(old_tree.get_file(fid).readlines(),
481
new_tree.get_file(fid).readlines(),
482
fromfile=old_label + old_name + idlabel,
483
tofile=new_label + new_name)
485
bailout("can't represent state %s {%s}" % (file_state, fid))
489
def cmd_deleted(show_ids=False):
490
"""List files deleted in the working tree.
492
TODO: Show files deleted since a previous revision, or between two revisions.
496
new = b.working_tree()
498
## TODO: Much more efficient way to do this: read in new
499
## directories with readdir, rather than stating each one. Same
500
## level of effort but possibly much less IO. (Or possibly not,
501
## if the directories are very large...)
503
for path, ie in old.inventory.iter_entries():
504
if not new.has_id(ie.file_id):
506
print '%-50s %s' % (path, ie.file_id)
512
def cmd_parse_inventory():
515
cElementTree.ElementTree().parse(file('.bzr/inventory'))
519
def cmd_load_inventory():
520
"""Load inventory for timing purposes"""
521
Branch('.').basis_tree().inventory
524
def cmd_dump_inventory():
525
Branch('.').read_working_inventory().write_xml(sys.stdout)
528
def cmd_dump_new_inventory():
529
import bzrlib.newinventory
530
inv = Branch('.').basis_tree().inventory
531
bzrlib.newinventory.write_inventory(inv, sys.stdout)
534
def cmd_load_new_inventory():
535
import bzrlib.newinventory
536
bzrlib.newinventory.read_new_inventory(sys.stdin)
539
def cmd_dump_slacker_inventory():
540
import bzrlib.newinventory
541
inv = Branch('.').basis_tree().inventory
542
bzrlib.newinventory.write_slacker_inventory(inv, sys.stdout)
546
def cmd_dump_text_inventory():
547
import bzrlib.textinv
548
inv = Branch('.').basis_tree().inventory
549
bzrlib.textinv.write_text_inventory(inv, sys.stdout)
552
def cmd_load_text_inventory():
553
import bzrlib.textinv
554
inv = bzrlib.textinv.read_text_inventory(sys.stdin)
555
print 'loaded %d entries' % len(inv)
559
def cmd_root(filename=None):
560
"""Print the branch root."""
561
print bzrlib.branch.find_branch_root(filename)
564
def cmd_log(timezone='original', verbose=False):
565
"""Show log of this branch.
567
TODO: Options for utc; to show ids; to limit range; etc.
569
Branch('.').write_log(show_timezone=timezone, verbose=verbose)
572
def cmd_ls(revision=None, verbose=False):
573
"""List files in a tree.
575
TODO: Take a revision or remote path and list that tree instead.
579
tree = b.working_tree()
581
tree = b.revision_tree(b.lookup_revision(revision))
583
for fp, fc, kind, fid in tree.list_files():
382
# Add the description, indenting it 2 spaces
383
# to match the indentation of the options
384
if sections.has_key(None):
385
text = sections.pop(None)
386
text = '\n '.join(text.splitlines())
387
result += ':%s:\n %s\n\n' % ('Description',text)
389
# Add the custom sections (e.g. Examples). Note that there's no need
390
# to indent these as they must be indented already in the source.
393
if sections.has_key(label):
394
result += ':%s:\n%s\n' % (label,sections[label])
397
result += ("See bzr help %s for more details and examples.\n\n"
400
# Add the aliases, source (plug-in) and see also links, if any
402
result += ':Aliases: '
403
result += ', '.join(self.aliases) + '\n'
404
plugin_name = self.plugin_name()
405
if plugin_name is not None:
406
result += ':From: plugin "%s"\n' % plugin_name
407
see_also = self.get_see_also(additional_see_also)
409
if not plain and see_also_as_links:
411
for item in see_also:
413
# topics doesn't have an independent section
414
# so don't create a real link
415
see_also_links.append(item)
417
# Use a reST link for this entry
418
see_also_links.append("`%s`_" % (item,))
419
see_also = see_also_links
420
result += ':See also: '
421
result += ', '.join(see_also) + '\n'
423
# If this will be rendered as plain text, convert it
425
import bzrlib.help_topics
426
result = bzrlib.help_topics.help_as_plain_text(result)
430
def _get_help_parts(text):
431
"""Split help text into a summary and named sections.
433
:return: (summary,sections,order) where summary is the top line and
434
sections is a dictionary of the rest indexed by section name.
435
order is the order the section appear in the text.
436
A section starts with a heading line of the form ":xxx:".
437
Indented text on following lines is the section value.
438
All text found outside a named section is assigned to the
439
default section which is given the key of None.
441
def save_section(sections, order, label, section):
443
if sections.has_key(label):
444
sections[label] += '\n' + section
447
sections[label] = section
449
lines = text.rstrip().splitlines()
450
summary = lines.pop(0)
453
label,section = None,''
455
if line.startswith(':') and line.endswith(':') and len(line) > 2:
456
save_section(sections, order, label, section)
457
label,section = line[1:-1],''
458
elif (label is not None) and len(line) > 1 and not line[0].isspace():
459
save_section(sections, order, label, section)
460
label,section = None,line
585
if kind == 'directory':
463
section += '\n' + line
466
save_section(sections, order, label, section)
467
return summary, sections, order
469
def get_help_topic(self):
470
"""Return the commands help topic - its name."""
473
def get_see_also(self, additional_terms=None):
474
"""Return a list of help topics that are related to this command.
476
The list is derived from the content of the _see_also attribute. Any
477
duplicates are removed and the result is in lexical order.
478
:param additional_terms: Additional help topics to cross-reference.
479
:return: A list of help topics.
481
see_also = set(getattr(self, '_see_also', []))
483
see_also.update(additional_terms)
484
return sorted(see_also)
487
"""Return dict of valid options for this command.
489
Maps from long option name to option object."""
490
r = Option.STD_OPTIONS.copy()
492
for o in self.takes_options:
493
if isinstance(o, basestring):
494
o = option.Option.OPTIONS[o]
496
if o.name in std_names:
497
self.supported_std_options.append(o.name)
500
def _setup_outf(self):
501
"""Return a file linked to stdout, which has proper encoding."""
502
# Originally I was using self.stdout, but that looks
503
# *way* too much like sys.stdout
504
if self.encoding_type == 'exact':
505
# force sys.stdout to be binary stream on win32
506
if sys.platform == 'win32':
507
fileno = getattr(sys.stdout, 'fileno', None)
510
msvcrt.setmode(fileno(), os.O_BINARY)
511
self.outf = sys.stdout
514
output_encoding = osutils.get_terminal_encoding()
516
self.outf = codecs.getwriter(output_encoding)(sys.stdout,
517
errors=self.encoding_type)
518
# For whatever reason codecs.getwriter() does not advertise its encoding
519
# it just returns the encoding of the wrapped file, which is completely
520
# bogus. So set the attribute, so we can find the correct encoding later.
521
self.outf.encoding = output_encoding
523
def run_argv_aliases(self, argv, alias_argv=None):
524
"""Parse the command line and run with extra aliases in alias_argv."""
526
warn("Passing None for [] is deprecated from bzrlib 0.10",
527
DeprecationWarning, stacklevel=2)
529
args, opts = parse_args(self, argv, alias_argv)
531
# Process the standard options
532
if 'help' in opts: # e.g. bzr add --help
533
sys.stdout.write(self.get_help_text())
535
if 'usage' in opts: # e.g. bzr add --usage
536
sys.stdout.write(self.get_help_text(verbose=False))
538
trace.set_verbosity_level(option._verbosity_level)
539
if 'verbose' in self.supported_std_options:
540
opts['verbose'] = trace.is_verbose()
541
elif opts.has_key('verbose'):
543
if 'quiet' in self.supported_std_options:
544
opts['quiet'] = trace.is_quiet()
545
elif opts.has_key('quiet'):
548
# mix arguments and options into one dictionary
549
cmdargs = _match_argform(self.name(), self.takes_args, args)
551
for k, v in opts.items():
552
cmdopts[k.replace('-', '_')] = v
554
all_cmd_args = cmdargs.copy()
555
all_cmd_args.update(cmdopts)
559
return self.run(**all_cmd_args)
562
"""Actually run the command.
564
This is invoked with the options and arguments bound to
567
Return 0 or None if the command was successful, or a non-zero
568
shell error code if not. It's OK for this method to allow
569
an exception to raise up.
571
raise NotImplementedError('no implementation of command %r'
575
"""Return help message for this class."""
576
from inspect import getdoc
577
if self.__doc__ is Command.__doc__:
582
return _unsquish_command_name(self.__class__.__name__)
584
def plugin_name(self):
585
"""Get the name of the plugin that provides this command.
587
:return: The name of the plugin or None if the command is builtin.
589
mod_parts = self.__module__.split('.')
590
if len(mod_parts) >= 3 and mod_parts[1] == 'plugins':
596
class CommandHooks(Hooks):
597
"""Hooks related to Command object creation/enumeration."""
600
"""Create the default hooks.
602
These are all empty initially, because by default nothing should get
606
self.create_hook(HookPoint('extend_command',
607
"Called after creating a command object to allow modifications "
608
"such as adding or removing options, docs etc. Called with the "
609
"new bzrlib.commands.Command object.", (1, 13), None))
611
Command.hooks = CommandHooks()
614
def parse_args(command, argv, alias_argv=None):
592
print '%-8s %s%s' % (fc, fp, kindch)
599
"""List unknown files"""
600
for f in Branch('.').unknowns():
605
def cmd_ignore(name_pattern):
606
"""Ignore a command or pattern"""
610
# XXX: This will fail if it's a hardlink; should use an AtomicFile class.
611
f = open(b.abspath('.bzrignore'), 'at')
612
f.write(name_pattern + '\n')
615
inv = b.working_tree().inventory
616
if inv.path2id('.bzrignore'):
617
mutter('.bzrignore is already versioned')
619
mutter('need to make new .bzrignore file versioned')
620
b.add(['.bzrignore'])
625
"""List ignored files and the patterns that matched them.
627
tree = Branch('.').working_tree()
628
for path, file_class, kind, file_id in tree.list_files():
629
if file_class != 'I':
631
## XXX: Slightly inefficient since this was already calculated
632
pat = tree.is_ignored(path)
633
print '%-50s %s' % (path, pat)
636
def cmd_lookup_revision(revno):
640
bailout("usage: lookup-revision REVNO",
641
["REVNO is a non-negative revision number for this branch"])
643
print Branch('.').lookup_revision(revno) or NONE_STRING
647
def cmd_export(revno, dest):
648
"""Export past revision to destination directory."""
650
rh = b.lookup_revision(int(revno))
651
t = b.revision_tree(rh)
654
def cmd_cat(revision, filename):
655
"""Print file to stdout."""
657
b.print_file(b.relpath(filename), int(revision))
660
######################################################################
661
# internal/test commands
665
"""Print a newly-generated UUID."""
666
print bzrlib.osutils.uuid()
670
def cmd_local_time_offset():
671
print bzrlib.osutils.local_time_offset()
675
def cmd_commit(message=None, verbose=False):
676
"""Commit changes to a new revision.
679
Description of changes in this revision; free form text.
680
It is recommended that the first line be a single-sentence
683
Show status of changed files,
685
TODO: Commit only selected files.
687
TODO: Run hooks on tree to-be-committed, and after commit.
689
TODO: Strict commit that fails if there are unknown or deleted files.
693
bailout("please specify a commit message")
694
Branch('.').commit(message, verbose=verbose)
697
def cmd_check(dir='.'):
698
"""check: Consistency check of branch history.
700
usage: bzr check [-v] [BRANCH]
703
--verbose, -v Show progress of checking.
705
This command checks various invariants about the branch storage to
706
detect data corruption or bzr bugs.
709
bzrlib.check.check(Branch(dir, find_root=False))
712
def cmd_is(pred, *rest):
713
"""Test whether PREDICATE is true."""
715
cmd_handler = globals()['assert_' + pred.replace('-', '_')]
717
bailout("unknown predicate: %s" % quotefn(pred))
721
except BzrCheckError:
722
# by default we don't print the message so that this can
723
# be used from shell scripts without producing noise
727
def cmd_whoami(email=False):
733
--email Show only the email address.
737
print bzrlib.osutils.user_email()
739
print bzrlib.osutils.username()
742
def cmd_gen_revision_id():
743
print bzrlib.branch._gen_revision_id(time.time())
747
"""Run internal test suite"""
748
## -v, if present, is seen by doctest; the argument is just here
749
## so our parser doesn't complain
751
## TODO: --verbose option
753
failures, tests = 0, 0
755
import doctest, bzrlib.store, bzrlib.tests
756
bzrlib.trace.verbose = False
758
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
759
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
760
mf, mt = doctest.testmod(m)
763
print '%-40s %3d tests' % (m.__name__, mt),
765
print '%3d FAILED!' % mf
769
print '%-40s %3d tests' % ('total', tests),
771
print '%3d FAILED!' % failures
778
cmd_doctest = cmd_selftest
781
######################################################################
785
def cmd_help(topic=None):
788
elif topic == 'commands':
791
# otherwise, maybe the name of a command?
792
topic, cmdfn = get_cmd_handler(topic)
796
bailout("sorry, no detailed help yet for %r" % topic)
802
"""List all commands"""
804
for k in globals().keys():
805
if k.startswith('cmd_'):
806
accu.append(k[4:].replace('_','-'))
808
print "bzr commands: "
811
print "note: some of these commands are internal-use or obsolete"
812
# TODO: Some kind of marker for internal-use commands?
813
# TODO: Show aliases?
819
print "bzr (bazaar-ng) %s" % bzrlib.__version__
820
print bzrlib.__copyright__
821
print "http://bazaar-ng.org/"
824
"""bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and
825
you may use, modify and redistribute it under the terms of the GNU
826
General Public License version 2 or later."""
830
"""Statement of optimism."""
831
print "it sure does!"
835
######################################################################
839
# list of all available options; the rhs can be either None for an
840
# option that takes no argument, or a constructor function that checks
861
# List of options that apply to particular commands; commands not
866
'commit': ['message', 'verbose'],
867
'deleted': ['show-ids'],
868
'diff': ['revision'],
869
'inventory': ['revision'],
870
'log': ['timezone', 'verbose'],
871
'ls': ['revision', 'verbose'],
872
'remove': ['verbose'],
883
'export': ['revno', 'dest'],
884
'file-id': ['filename'],
885
'file-id-path': ['filename'],
886
'get-file-text': ['text_id'],
887
'get-inventory': ['inventory_id'],
888
'get-revision': ['revision_id'],
889
'get-revision-inventory': ['revision_id'],
891
'ignore': ['name_pattern'],
894
'lookup-revision': ['revno'],
895
'move': ['source$', 'dest'],
896
'relpath': ['filename'],
898
'rename': ['from_name', 'to_name'],
900
'root': ['filename?'],
905
def parse_args(argv):
615
906
"""Parse command line.
617
908
Arguments and options are parsed at this level before being passed
618
909
down to specific command handlers. This routine knows, from a
619
910
lookup table, something about the available options, what optargs
620
911
they take, and which commands will accept them.
913
>>> parse_args('--help'.split())
915
>>> parse_args('--version'.split())
916
([], {'version': True})
917
>>> parse_args('status --all'.split())
918
(['status'], {'all': True})
919
>>> parse_args('commit --message=biter'.split())
920
(['commit'], {'message': u'biter'})
622
# TODO: make it a method of the Command?
623
parser = option.get_optparser(command.options())
624
if alias_argv is not None:
625
args = alias_argv + argv
629
options, args = parser.parse_args(args)
630
opts = dict([(k, v) for k, v in options.__dict__.iteritems() if
631
v is not option.OptionParser.DEFAULT_VALUE])
925
# TODO: Maybe handle '--' to end options?
930
# option names must not be unicode
934
mutter(" got option %r" % a)
936
optname, optarg = a[2:].split('=', 1)
939
if optname not in OPTIONS:
940
bailout('unknown long option %r' % a)
943
if shortopt not in SHORT_OPTIONS:
944
bailout('unknown short option %r' % a)
945
optname = SHORT_OPTIONS[shortopt]
948
# XXX: Do we ever want to support this, e.g. for -r?
949
bailout('repeated option %r' % a)
951
optargfn = OPTIONS[optname]
955
bailout('option %r needs an argument' % a)
958
opts[optname] = optargfn(optarg)
961
bailout('option %r takes no argument' % optname)
632
966
return args, opts
635
def _match_argform(cmd, takes_args, args):
970
def _match_args(cmd, args):
971
"""Check non-option arguments match required pattern.
973
>>> _match_args('status', ['asdasdsadasd'])
974
Traceback (most recent call last):
976
BzrError: ("extra arguments to command status: ['asdasdsadasd']", [])
977
>>> _match_args('add', ['asdasdsadasd'])
978
{'file_list': ['asdasdsadasd']}
979
>>> _match_args('add', 'abc def gj'.split())
980
{'file_list': ['abc', 'def', 'gj']}
982
# match argument pattern
983
argform = cmd_args.get(cmd, [])
985
# TODO: Need a way to express 'cp SRC... DEST', where it matches
638
# step through args and takes_args, allowing appropriate 0-many matches
639
for ap in takes_args:
988
# step through args and argform, allowing appropriate 0-many matches
640
990
argname = ap[:-1]
641
991
if ap[-1] == '?':
649
999
argdict[argname + '_list'] = None
650
1000
elif ap[-1] == '+':
652
raise errors.BzrCommandError("command %r needs one or more %s"
653
% (cmd, argname.upper()))
1002
bailout("command %r needs one or more %s"
1003
% (cmd, argname.upper()))
655
1005
argdict[argname + '_list'] = args[:]
657
1007
elif ap[-1] == '$': # all but one
658
1008
if len(args) < 2:
659
raise errors.BzrCommandError("command %r needs one or more %s"
660
% (cmd, argname.upper()))
1009
bailout("command %r needs one or more %s"
1010
% (cmd, argname.upper()))
661
1011
argdict[argname + '_list'] = args[:-1]
664
1014
# just a plain arg
667
raise errors.BzrCommandError("command %r requires argument %s"
668
% (cmd, argname.upper()))
1017
bailout("command %r requires argument %s"
1018
% (cmd, argname.upper()))
670
1020
argdict[argname] = args.pop(0)
673
raise errors.BzrCommandError("extra argument to command %s: %s"
1023
bailout("extra arguments to command %s: %r"
678
def apply_coveraged(dirname, the_callable, *args, **kwargs):
679
# Cannot use "import trace", as that would import bzrlib.trace instead of
680
# the standard library's trace.
681
trace = __import__('trace')
683
tracer = trace.Trace(count=1, trace=0)
684
sys.settrace(tracer.globaltrace)
687
return exception_to_return_code(the_callable, *args, **kwargs)
690
results = tracer.results()
691
results.write_results(show_missing=1, summary=False,
695
def apply_profiled(the_callable, *args, **kwargs):
699
pffileno, pfname = tempfile.mkstemp()
701
prof = hotshot.Profile(pfname)
703
ret = prof.runcall(exception_to_return_code, the_callable, *args,
707
stats = hotshot.stats.load(pfname)
709
stats.sort_stats('cum') # 'time'
710
## XXX: Might like to write to stderr or the trace file instead but
711
## print_stats seems hardcoded to stdout
712
stats.print_stats(20)
719
def exception_to_return_code(the_callable, *args, **kwargs):
720
"""UI level helper for profiling and coverage.
722
This transforms exceptions into a return value of 3. As such its only
723
relevant to the UI layer, and should never be called where catching
724
exceptions may be desirable.
727
return the_callable(*args, **kwargs)
728
except (KeyboardInterrupt, Exception), e:
729
# used to handle AssertionError and KeyboardInterrupt
730
# specially here, but hopefully they're handled ok by the logger now
731
exc_info = sys.exc_info()
732
exitcode = trace.report_exception(exc_info, sys.stderr)
733
if os.environ.get('BZR_PDB'):
734
print '**** entering debugger'
737
if sys.version_info[:2] < (2, 6):
739
# pdb.post_mortem(tb)
740
# but because pdb.post_mortem gives bad results for tracebacks
741
# from inside generators, we do it manually.
742
# (http://bugs.python.org/issue4150, fixed in Python 2.6)
744
# Setup pdb on the traceback
747
p.setup(tb.tb_frame, tb)
748
# Point the debugger at the deepest frame of the stack
749
p.curindex = len(p.stack) - 1
750
p.curframe = p.stack[p.curindex][0]
751
# Start the pdb prompt.
752
p.print_stack_entry(p.stack[p.curindex])
760
def apply_lsprofiled(filename, the_callable, *args, **kwargs):
761
from bzrlib.lsprof import profile
762
ret, stats = profile(exception_to_return_code, the_callable, *args, **kwargs)
768
trace.note('Profile data written to "%s".', filename)
772
def shlex_split_unicode(unsplit):
774
return [u.decode('utf-8') for u in shlex.split(unsplit.encode('utf-8'))]
777
def get_alias(cmd, config=None):
778
"""Return an expanded alias, or None if no alias exists.
781
Command to be checked for an alias.
783
Used to specify an alternative config to use,
784
which is especially useful for testing.
785
If it is unspecified, the global config will be used.
789
config = bzrlib.config.GlobalConfig()
790
alias = config.get_alias(cmd)
792
return shlex_split_unicode(alias)
796
1030
def run_bzr(argv):
797
1031
"""Execute a command.
800
The command-line arguments, without the program name from argv[0]
801
These should already be decoded. All library/test code calling
802
run_bzr should be passing valid strings (don't need decoding).
804
Returns a command status or raises an exception.
806
Special master options: these must come before the command because
807
they control how the command is interpreted.
810
Do not load plugin modules at all
816
Only use builtin commands. (Plugins are still allowed to change
820
Run under the Python hotshot profiler.
823
Run under the Python lsprof profiler.
826
Generate line coverage report in the specified directory.
1033
This is similar to main(), but without all the trappings for
1034
logging and error handling.
829
trace.mutter("bzr arguments: %r", argv)
831
opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
832
opt_no_aliases = False
833
opt_lsprof_file = opt_coverage_dir = None
835
# --no-plugins is handled specially at a very early stage. We need
836
# to load plugins before doing other command parsing so that they
837
# can override commands, but this needs to happen first.
845
elif a == '--lsprof':
847
elif a == '--lsprof-file':
849
opt_lsprof_file = argv[i + 1]
851
elif a == '--no-plugins':
852
opt_no_plugins = True
853
elif a == '--no-aliases':
854
opt_no_aliases = True
855
elif a == '--builtin':
857
elif a == '--coverage':
858
opt_coverage_dir = argv[i + 1]
860
elif a.startswith('-D'):
861
debug.debug_flags.add(a[2:])
866
debug.set_debug_flags_from_config()
870
from bzrlib.builtins import cmd_help
871
cmd_help().run_argv_aliases([])
874
if argv[0] == '--version':
875
from bzrlib.builtins import cmd_version
876
cmd_version().run_argv_aliases([])
879
if not opt_no_plugins:
880
from bzrlib.plugin import load_plugins
883
from bzrlib.plugin import disable_plugins
888
if not opt_no_aliases:
889
alias_argv = get_alias(argv[0])
891
user_encoding = osutils.get_user_encoding()
892
alias_argv = [a.decode(user_encoding) for a in alias_argv]
893
argv[0] = alias_argv.pop(0)
896
# We want only 'ascii' command names, but the user may have typed
897
# in a Unicode name. In that case, they should just get a
898
# 'command not found' error later.
900
cmd_obj = get_cmd_object(cmd, plugins_override=not opt_builtin)
901
run = cmd_obj.run_argv_aliases
902
run_argv = [argv, alias_argv]
1037
argv = [a.decode(bzrlib.user_encoding) for a in argv]
905
# We can be called recursively (tests for example), but we don't want
906
# the verbosity level to propagate.
907
saved_verbosity_level = option._verbosity_level
908
option._verbosity_level = 0
912
'--coverage ignored, because --lsprof is in use.')
913
ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
917
'--coverage ignored, because --profile is in use.')
918
ret = apply_profiled(run, *run_argv)
919
elif opt_coverage_dir:
920
ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
923
if 'memory' in debug.debug_flags:
924
trace.debug_memory('Process status after command:', short=False)
927
# reset, in case we may do other commands later within the same
928
# process. Commands that want to execute sub-commands must propagate
929
# --verbose in their own way.
930
option._verbosity_level = saved_verbosity_level
933
def display_command(func):
934
"""Decorator that suppresses pipe/interrupt errors."""
935
def ignore_pipe(*args, **kwargs):
1040
args, opts = parse_args(argv[1:])
1042
# TODO: pass down other arguments in case they asked for
1043
# help on a command name?
1049
elif 'version' in opts:
1052
cmd = str(args.pop(0))
1054
log_error('usage: bzr COMMAND')
1055
log_error(' try "bzr help"')
1058
canonical_cmd, cmd_handler = get_cmd_handler(cmd)
1061
if 'profile' in opts:
1067
# check options are reasonable
1068
allowed = cmd_options.get(canonical_cmd, [])
1070
if oname not in allowed:
1071
bailout("option %r is not allowed for command %r"
1074
# TODO: give an error if there are any mandatory options which are
1075
# not specified? Or maybe there shouldn't be any "mandatory
1076
# options" (it is an oxymoron)
1078
# mix arguments and options into one dictionary
1079
cmdargs = _match_args(canonical_cmd, args)
1080
for k, v in opts.items():
1081
cmdargs[k.replace('-', '_')] = v
1085
pffileno, pfname = tempfile.mkstemp()
937
result = func(*args, **kwargs)
941
if getattr(e, 'errno', None) is None:
943
if e.errno != errno.EPIPE:
944
# Win32 raises IOError with errno=0 on a broken pipe
945
if sys.platform != 'win32' or (e.errno not in (0, errno.EINVAL)):
948
except KeyboardInterrupt:
1087
prof = hotshot.Profile(pfname)
1088
ret = prof.runcall(cmd_handler, **cmdargs) or 0
1091
import hotshot.stats
1092
stats = hotshot.stats.load(pfname)
1094
stats.sort_stats('time')
1095
## XXX: Might like to write to stderr or the trace file instead but
1096
## print_stats seems hardcoded to stdout
1097
stats.print_stats(20)
1105
return cmd_handler(**cmdargs) or 0
1109
def _report_exception(e, summary):
1111
log_error('bzr: ' + summary)
1112
bzrlib.trace.log_exception(e)
1113
tb = sys.exc_info()[2]
1114
exinfo = traceback.extract_tb(tb)
1116
sys.stderr.write(' at %s:%d in %s()\n' % exinfo[-1][:3])
1117
sys.stderr.write(' see ~/.bzr.log for debug information\n')
1120
def cmd_assert_fail():
1121
assert False, "always fails"
955
bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(
956
sys.stdin, sys.stdout, sys.stderr)
958
# Is this a final release version? If so, we should suppress warnings
959
if bzrlib.version_info[3] == 'final':
960
from bzrlib import symbol_versioning
961
symbol_versioning.suppress_deprecation_warnings(override=False)
963
user_encoding = osutils.get_user_encoding()
964
argv = [a.decode(user_encoding) for a in argv[1:]]
965
except UnicodeDecodeError:
966
raise errors.BzrError(("Parameter '%r' is unsupported by the current "
968
ret = run_bzr_catch_errors(argv)
969
trace.mutter("return code %d", ret)
973
def run_bzr_catch_errors(argv):
974
"""Run a bzr command with parameters as described by argv.
976
This function assumed that that UI layer is setup, that symbol deprecations
977
are already applied, and that unicode decoding has already been performed on argv.
979
return exception_to_return_code(run_bzr, argv)
982
def run_bzr_catch_user_errors(argv):
983
"""Run bzr and report user errors, but let internal errors propagate.
985
This is used for the test suite, and might be useful for other programs
986
that want to wrap the commandline interface.
991
if (isinstance(e, (OSError, IOError))
992
or not getattr(e, 'internal_error', True)):
993
trace.report_exception(sys.exc_info(), sys.stderr)
999
class HelpCommandIndex(object):
1000
"""A index for bzr help that returns commands."""
1003
self.prefix = 'commands/'
1005
def get_topics(self, topic):
1006
"""Search for topic amongst commands.
1008
:param topic: A topic to search for.
1009
:return: A list which is either empty or contains a single
1012
if topic and topic.startswith(self.prefix):
1013
topic = topic[len(self.prefix):]
1125
bzrlib.trace.create_tracefile(argv)
1015
cmd = _get_cmd_object(topic)
1022
class Provider(object):
1023
'''Generic class to be overriden by plugins'''
1025
def plugin_for_command(self, cmd_name):
1026
'''Takes a command and returns the information for that plugin
1028
:return: A dictionary with all the available information
1029
for the requested plugin
1031
raise NotImplementedError
1034
class ProvidersRegistry(registry.Registry):
1035
'''This registry exists to allow other providers to exist'''
1038
for key, provider in self.iteritems():
1041
command_providers_registry = ProvidersRegistry()
1132
_report_exception(e, 'error: ' + e.args[0])
1135
# some explanation or hints
1138
except AssertionError, e:
1139
msg = 'assertion failed'
1141
msg += ': ' + str(e)
1142
_report_exception(e, msg)
1143
except Exception, e:
1144
_report_exception(e, 'exception: %s' % str(e).rstrip('\n'))
1147
bzrlib.trace.close_trace()
1149
## TODO: Trap AssertionError
1151
# TODO: Maybe nicer handling of IOError especially for broken pipe.
1044
1155
if __name__ == '__main__':