19
import sys, os, time, os.path
22
23
from bzrlib.trace import mutter, note, log_error
23
from bzrlib.errors import BzrError, BzrCheckError, BzrCommandError
24
from bzrlib.osutils import quotefn
25
from bzrlib import Branch, Inventory, InventoryEntry, BZRDIR, \
24
from bzrlib.errors import bailout, BzrError, BzrCheckError, BzrCommandError
25
from bzrlib.osutils import quotefn, pumpfile, isdir, isfile
26
from bzrlib.tree import RevisionTree, EmptyTree, WorkingTree, Tree
27
from bzrlib.revision import Revision
28
from bzrlib import Branch, Inventory, InventoryEntry, ScratchBranch, BZRDIR, \
32
def register_command(cmd):
33
"Utility function to help register a command"
36
if k.startswith("cmd_"):
37
k_unsquished = _unsquish_command_name(k)
40
if not plugin_cmds.has_key(k_unsquished):
41
plugin_cmds[k_unsquished] = cmd
43
log_error('Two plugins defined the same command: %r' % k)
44
log_error('Not loading the one in %r' % sys.modules[cmd.__module__])
47
32
def _squish_command_name(cmd):
48
33
return 'cmd_' + cmd.replace('-', '_')
52
37
assert cmd.startswith("cmd_")
53
38
return cmd[4:].replace('_','-')
55
def _parse_revision_str(revstr):
56
"""This handles a revision string -> revno.
58
There are several possibilities:
61
'234:345' -> [234, 345]
65
In the future we will also support:
66
'uuid:blah-blah-blah' -> ?
67
'hash:blahblahblah' -> ?
71
if revstr.find(':') != -1:
72
revs = revstr.split(':')
74
raise ValueError('More than 2 pieces not supported for --revision: %r' % revstr)
79
revs[0] = int(revs[0])
84
revs[1] = int(revs[1])
91
def _get_cmd_dict(plugins_override=True):
41
"""Return canonical name and class for all registered commands."""
93
42
for k, v in globals().iteritems():
94
43
if k.startswith("cmd_"):
95
d[_unsquish_command_name(k)] = v
96
# If we didn't load plugins, the plugin_cmds dict will be empty
100
d2 = plugin_cmds.copy()
106
def get_all_cmds(plugins_override=True):
107
"""Return canonical name and class for all registered commands."""
108
for k, v in _get_cmd_dict(plugins_override=plugins_override).iteritems():
112
def get_cmd_class(cmd, plugins_override=True):
44
yield _unsquish_command_name(k), v
46
def get_cmd_class(cmd):
113
47
"""Return the canonical name and command class for a command.
115
49
cmd = str(cmd) # not unicode
117
51
# first look up this command under the specified name
118
cmds = _get_cmd_dict(plugins_override=plugins_override)
120
return cmd, cmds[cmd]
53
return cmd, globals()[_squish_command_name(cmd)]
124
57
# look for any command which claims this as an alias
125
for cmdname, cmdclass in cmds.iteritems():
58
for cmdname, cmdclass in get_all_cmds():
126
59
if cmd in cmdclass.aliases:
127
60
return cmdname, cmdclass
129
cmdclass = ExternalCommand.find_command(cmd)
133
raise BzrCommandError("unknown command %r" % cmd)
136
class Command(object):
62
raise BzrCommandError("unknown command %r" % cmd)
137
66
"""Base class for commands.
139
68
The docstring for an actual command should give a single-line
185
class ExternalCommand(Command):
186
"""Class to wrap external commands.
188
We cheat a little here, when get_cmd_class() calls us we actually give it back
189
an object we construct that has the appropriate path, help, options etc for the
192
When run_bzr() tries to instantiate that 'class' it gets caught by the __call__
193
method, which we override to call the Command.__init__ method. That then calls
194
our run method which is pretty straight forward.
196
The only wrinkle is that we have to map bzr's dictionary of options and arguments
197
back into command line options and arguments for the script.
200
def find_command(cls, cmd):
202
bzrpath = os.environ.get('BZRPATH', '')
204
for dir in bzrpath.split(os.pathsep):
205
path = os.path.join(dir, cmd)
206
if os.path.isfile(path):
207
return ExternalCommand(path)
211
find_command = classmethod(find_command)
213
def __init__(self, path):
216
pipe = os.popen('%s --bzr-usage' % path, 'r')
217
self.takes_options = pipe.readline().split()
219
for opt in self.takes_options:
220
if not opt in OPTIONS:
221
raise BzrError("Unknown option '%s' returned by external command %s"
224
# TODO: Is there any way to check takes_args is valid here?
225
self.takes_args = pipe.readline().split()
227
if pipe.close() is not None:
228
raise BzrError("Failed funning '%s --bzr-usage'" % path)
230
pipe = os.popen('%s --bzr-help' % path, 'r')
231
self.__doc__ = pipe.read()
232
if pipe.close() is not None:
233
raise BzrError("Failed funning '%s --bzr-help'" % path)
235
def __call__(self, options, arguments):
236
Command.__init__(self, options, arguments)
239
def run(self, **kargs):
246
optname = name.replace('_','-')
248
if OPTIONS.has_key(optname):
250
opts.append('--%s' % optname)
251
if value is not None and value is not True:
252
opts.append(str(value))
254
# it's an arg, or arg list
255
if type(value) is not list:
261
self.status = os.spawnv(os.P_WAIT, self.path, [self.path] + opts + args)
265
115
class cmd_status(Command):
266
116
"""Display status summary.
268
This reports on versioned and unknown files, reporting them
269
grouped by state. Possible states are:
272
Versioned in the working copy but not in the previous revision.
275
Versioned in the previous revision but removed or deleted
279
Path of this file changed from the previous revision;
280
the text may also have changed. This includes files whose
281
parent directory was renamed.
284
Text has changed since the previous revision.
287
Nothing about this file has changed since the previous revision.
288
Only shown with --all.
291
Not versioned and not matching an ignore pattern.
293
To see ignored files use 'bzr ignored'. For details in the
294
changes to file texts, use 'bzr diff'.
296
If no arguments are specified, the status of the entire working
297
directory is shown. Otherwise, only the status of the specified
298
files or directories is reported. If a directory is given, status
299
is reported for everything inside that directory.
118
For each file there is a single line giving its file state and name.
119
The name is that in the current revision unless it is deleted or
120
missing, in which case the old name is shown.
301
takes_args = ['file*']
302
takes_options = ['all', 'show-ids']
122
takes_options = ['all']
303
123
aliases = ['st', 'stat']
305
def run(self, all=False, show_ids=False, file_list=None):
307
b = Branch(file_list[0])
308
file_list = [b.relpath(x) for x in file_list]
309
# special case: only one path was given and it's the root
311
if file_list == ['']:
316
status.show_status(b, show_unchanged=all, show_ids=show_ids,
317
specific_files=file_list)
125
def run(self, all=False):
126
#import bzrlib.status
127
#bzrlib.status.tree_status(Branch('.'))
128
Branch('.').show_status(show_all=all)
320
131
class cmd_cat_revision(Command):
357
168
recursively add that parent, rather than giving an error?
359
170
takes_args = ['file+']
360
takes_options = ['verbose', 'no-recurse']
171
takes_options = ['verbose']
362
def run(self, file_list, verbose=False, no_recurse=False):
363
bzrlib.add.smart_add(file_list, verbose, not no_recurse)
367
class cmd_mkdir(Command):
368
"""Create a new versioned directory.
370
This is equivalent to creating the directory and then adding it.
372
takes_args = ['dir+']
374
def run(self, dir_list):
383
b = bzrlib.branch.Branch(d)
384
b.add([d], verbose=True)
387
class cmd_relpath(Command):
173
def run(self, file_list, verbose=False):
174
bzrlib.add.smart_add(file_list, verbose)
177
def Relpath(Command):
388
178
"""Show path of a file relative to root"""
389
takes_args = ['filename']
179
takes_args = ('filename')
392
def run(self, filename):
393
print Branch(filename).relpath(filename)
182
print Branch(self.args['filename']).relpath(filename)
397
186
class cmd_inventory(Command):
398
187
"""Show inventory of the current working copy or a revision."""
399
takes_options = ['revision', 'show-ids']
188
takes_options = ['revision']
401
def run(self, revision=None, show_ids=False):
190
def run(self, revision=None):
403
192
if revision == None:
404
193
inv = b.read_working_inventory()
406
195
inv = b.get_revision_inventory(b.lookup_revision(revision))
408
for path, entry in inv.entries():
410
print '%-50s %s' % (path, entry.file_id)
197
for path, entry in inv.iter_entries():
198
print '%-50s %s' % (entry.file_id, path)
415
201
class cmd_move(Command):
454
class cmd_pull(Command):
455
"""Pull any changes from another branch into the current one.
457
If the location is omitted, the last-used location will be used.
458
Both the revision history and the working directory will be
461
This command only works on branches that have not diverged. Branches are
462
considered diverged if both branches have had commits without first
463
pulling from the other.
465
If branches have diverged, you can use 'bzr merge' to pull the text changes
466
from one into the other.
468
takes_args = ['location?']
470
def run(self, location=None):
471
from bzrlib.merge import merge
477
stored_loc = br_to.controlfile("x-pull", "rb").read().rstrip('\n')
479
if errno == errno.ENOENT:
482
if stored_loc is None:
483
raise BzrCommandError("No pull location known or specified.")
485
print "Using last location: %s" % stored_loc
486
location = stored_loc
487
from branch import find_branch, DivergedBranches
488
br_from = find_branch(location)
489
location = pull_loc(br_from)
490
old_revno = br_to.revno()
492
br_to.update_revisions(br_from)
493
except DivergedBranches:
494
raise BzrCommandError("These branches have diverged. Try merge.")
496
merge(('.', -1), ('.', old_revno), check_clean=False)
497
if location != stored_loc:
498
br_to.controlfile("x-pull", "wb").write(location + "\n")
502
class cmd_branch(Command):
503
"""Create a new copy of a branch.
505
If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
506
be used. In other words, "branch ../foo/bar" will attempt to create ./bar.
508
To retrieve the branch as of a particular revision, supply the --revision
509
parameter, as in "branch foo/bar -r 5".
511
takes_args = ['from_location', 'to_location?']
512
takes_options = ['revision']
514
def run(self, from_location, to_location=None, revision=None):
516
from bzrlib.merge import merge
517
from branch import find_branch, DivergedBranches, NoSuchRevision
518
from shutil import rmtree
520
br_from = find_branch(from_location)
522
if e.errno == errno.ENOENT:
523
raise BzrCommandError('Source location "%s" does not exist.' %
528
if to_location is None:
529
to_location = os.path.basename(from_location.rstrip("/\\"))
532
os.mkdir(to_location)
534
if e.errno == errno.EEXIST:
535
raise BzrCommandError('Target directory "%s" already exists.' %
537
if e.errno == errno.ENOENT:
538
raise BzrCommandError('Parent of "%s" does not exist.' %
542
br_to = Branch(to_location, init=True)
545
br_to.update_revisions(br_from, stop_revision=revision)
546
except NoSuchRevision:
548
msg = "The branch %s has no revision %d." % (from_location,
550
raise BzrCommandError(msg)
551
merge((to_location, -1), (to_location, 0), this_dir=to_location,
552
check_clean=False, ignore_zero=True)
553
from_location = pull_loc(br_from)
554
br_to.controlfile("x-pull", "wb").write(from_location + "\n")
557
def pull_loc(branch):
558
# TODO: Should perhaps just make attribute be 'base' in
559
# RemoteBranch and Branch?
560
if hasattr(branch, "baseurl"):
561
return branch.baseurl
567
238
class cmd_renames(Command):
568
239
"""Show list of renamed files.
707
372
takes_args = ['file*']
708
takes_options = ['revision', 'diff-options']
709
aliases = ['di', 'dif']
373
takes_options = ['revision']
711
def run(self, revision=None, file_list=None, diff_options=None):
376
def run(self, revision=None, file_list=None):
712
377
from bzrlib.diff import show_diff
713
from bzrlib import find_branch
716
b = find_branch(file_list[0])
717
file_list = [b.relpath(f) for f in file_list]
718
if file_list == ['']:
719
# just pointing to top-of-tree
724
show_diff(b, revision, specific_files=file_list,
725
external_diff_options=diff_options)
379
show_diff(Branch('.'), revision, file_list)
731
382
class cmd_deleted(Command):
754
class cmd_modified(Command):
755
"""List files modified in working tree."""
760
inv = b.read_working_inventory()
761
sc = statcache.update_cache(b, inv)
762
basis = b.basis_tree()
763
basis_inv = basis.inventory
765
# We used to do this through iter_entries(), but that's slow
766
# when most of the files are unmodified, as is usually the
767
# case. So instead we iterate by inventory entry, and only
768
# calculate paths as necessary.
770
for file_id in basis_inv:
771
cacheentry = sc.get(file_id)
772
if not cacheentry: # deleted
774
ie = basis_inv[file_id]
775
if cacheentry[statcache.SC_SHA1] != ie.text_sha1:
776
path = inv.id2path(file_id)
781
class cmd_added(Command):
782
"""List files added in working tree."""
786
wt = b.working_tree()
787
basis_inv = b.basis_tree().inventory
790
if file_id in basis_inv:
792
path = inv.id2path(file_id)
793
if not os.access(b.abspath(path), os.F_OK):
799
404
class cmd_root(Command):
800
405
"""Show the tree root directory.
804
409
takes_args = ['filename?']
805
410
def run(self, filename=None):
806
411
"""Print the branch root."""
807
from branch import find_branch
808
b = find_branch(filename)
809
print getattr(b, 'base', None) or getattr(b, 'baseurl')
412
print bzrlib.branch.find_branch_root(filename)
812
416
class cmd_log(Command):
813
417
"""Show log of this branch.
815
To request a range of logs, you can use the command -r begin:end
816
-r revision requests a specific revision, -r :end or -r begin: are
819
TODO: Make --revision support uuid: and hash: [future tag:] notation.
419
TODO: Options to show ids; to limit range; etc.
823
takes_args = ['filename?']
824
takes_options = ['forward', 'timezone', 'verbose', 'show-ids', 'revision']
826
def run(self, filename=None, timezone='original',
831
from bzrlib import show_log, find_branch
834
direction = (forward and 'forward') or 'reverse'
837
b = find_branch(filename)
838
fp = b.relpath(filename)
840
file_id = b.read_working_inventory().path2id(fp)
842
file_id = None # points to branch root
848
revision = [None, None]
849
elif isinstance(revision, int):
850
revision = [revision, revision]
855
assert len(revision) == 2
857
mutter('encoding log as %r' % bzrlib.user_encoding)
859
# use 'replace' so that we don't abort if trying to write out
860
# in e.g. the default C locale.
861
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
864
show_timezone=timezone,
869
start_revision=revision[0],
870
end_revision=revision[1])
874
class cmd_touching_revisions(Command):
875
"""Return revision-ids which affected a particular file.
877
A more user-friendly interface is "bzr log FILE"."""
879
takes_args = ["filename"]
880
def run(self, filename):
882
inv = b.read_working_inventory()
883
file_id = inv.path2id(b.relpath(filename))
884
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
885
print "%6d %s" % (revno, what)
421
takes_options = ['timezone', 'verbose']
422
def run(self, timezone='original', verbose=False):
423
Branch('.', lock_mode='r').write_log(show_timezone=timezone, verbose=verbose)
888
426
class cmd_ls(Command):
924
462
class cmd_ignore(Command):
925
"""Ignore a command or pattern.
927
To remove patterns from the ignore list, edit the .bzrignore file.
929
If the pattern contains a slash, it is compared to the whole path
930
from the branch root. Otherwise, it is comapred to only the last
931
component of the path.
933
Ignore patterns are case-insensitive on case-insensitive systems.
935
Note: wildcards must be quoted from the shell on Unix.
938
bzr ignore ./Makefile
463
"""Ignore a command or pattern"""
941
464
takes_args = ['name_pattern']
943
466
def run(self, name_pattern):
944
from bzrlib.atomicfile import AtomicFile
948
ifn = b.abspath('.bzrignore')
950
if os.path.exists(ifn):
953
igns = f.read().decode('utf-8')
959
# TODO: If the file already uses crlf-style termination, maybe
960
# we should use that for the newly added lines?
962
if igns and igns[-1] != '\n':
964
igns += name_pattern + '\n'
967
f = AtomicFile(ifn, 'wt')
968
f.write(igns.encode('utf-8'))
469
# XXX: This will fail if it's a hardlink; should use an AtomicFile class.
470
f = open(b.abspath('.bzrignore'), 'at')
471
f.write(name_pattern + '\n')
973
474
inv = b.working_tree().inventory
974
475
if inv.path2id('.bzrignore'):
1014
513
class cmd_export(Command):
1015
514
"""Export past revision to destination directory.
1017
If no revision is specified this exports the last committed revision.
1019
Format may be an "exporter" name, such as tar, tgz, tbz2. If none is
1020
given, exports to a directory (equivalent to --format=dir)."""
1021
# TODO: list known exporters
516
If no revision is specified this exports the last committed revision."""
1022
517
takes_args = ['dest']
1023
takes_options = ['revision', 'format']
1024
def run(self, dest, revision=None, format='dir'):
518
takes_options = ['revision']
519
def run(self, dest, revno=None):
1026
if revision == None:
1027
rh = b.revision_history()[-1]
522
rh = b.revision_history[-1]
1029
rh = b.lookup_revision(int(revision))
524
rh = b.lookup_revision(int(revno))
1030
525
t = b.revision_tree(rh)
1031
t.export(dest, format)
1034
529
class cmd_cat(Command):
1055
550
class cmd_commit(Command):
1056
551
"""Commit changes into a new revision.
1058
If selected files are specified, only changes to those files are
1059
committed. If a directory is specified then its contents are also
1062
A selected-file commit may fail in some cases where the committed
1063
tree would be invalid, such as trying to commit a file in a
1064
newly-added directory that is not itself committed.
553
TODO: Commit only selected files.
1066
555
TODO: Run hooks on tree to-be-committed, and after commit.
1068
557
TODO: Strict commit that fails if there are unknown or deleted files.
1070
takes_args = ['selected*']
1071
takes_options = ['message', 'file', 'verbose']
559
takes_options = ['message', 'verbose']
1072
560
aliases = ['ci', 'checkin']
1074
def run(self, message=None, file=None, verbose=True, selected_list=None):
1075
from bzrlib.commit import commit
1076
from bzrlib.osutils import get_text_message
1078
## Warning: shadows builtin file()
1079
if not message and not file:
1082
catcher = cStringIO.StringIO()
1083
sys.stdout = catcher
1084
cmd_status({"file_list":selected_list}, {})
1085
info = catcher.getvalue()
1087
message = get_text_message(info)
1090
raise BzrCommandError("please specify a commit message",
1091
["use either --message or --file"])
1092
elif message and file:
1093
raise BzrCommandError("please specify either --message or --file")
1097
message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1100
commit(b, message, verbose=verbose, specific_files=selected_list)
562
def run(self, message=None, verbose=False):
564
raise BzrCommandError("please specify a commit message")
565
Branch('.').commit(message, verbose=verbose)
1103
568
class cmd_check(Command):
1146
593
"""Run internal test suite"""
1149
from bzrlib.selftest import selftest
1150
return int(not selftest())
596
failures, tests = 0, 0
598
import doctest, bzrlib.store, bzrlib.tests
599
bzrlib.trace.verbose = False
601
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
602
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
603
mf, mt = doctest.testmod(m)
606
print '%-40s %3d tests' % (m.__name__, mt),
608
print '%3d FAILED!' % mf
612
print '%-40s %3d tests' % ('total', tests),
614
print '%3d FAILED!' % failures
1153
620
class cmd_version(Command):
1154
"""Show version of bzr."""
621
"""Show version of bzr"""
1158
625
def show_version():
1159
626
print "bzr (bazaar-ng) %s" % bzrlib.__version__
1160
# is bzrlib itself in a branch?
1161
bzrrev = bzrlib.get_bzr_revision()
1163
print " (bzr checkout, revision %d {%s})" % bzrrev
1164
627
print bzrlib.__copyright__
1165
628
print "http://bazaar-ng.org/"
1176
639
print "it sure does!"
1178
def parse_spec(spec):
1180
>>> parse_spec(None)
1182
>>> parse_spec("./")
1184
>>> parse_spec("../@")
1186
>>> parse_spec("../f/@35")
1192
parsed = spec.split('/@')
1193
assert len(parsed) == 2
1197
parsed[1] = int(parsed[1])
1198
assert parsed[1] >=0
1200
parsed = [spec, None]
1205
class cmd_merge(Command):
1206
"""Perform a three-way merge of trees.
1208
The SPEC parameters are working tree or revision specifiers. Working trees
1209
are specified using standard paths or urls. No component of a directory
1210
path may begin with '@'.
1212
Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
1214
Revisions are specified using a dirname/@revno pair, where dirname is the
1215
branch directory and revno is the revision within that branch. If no revno
1216
is specified, the latest revision is used.
1218
Revision examples: './@127', 'foo/@', '../@1'
1220
The OTHER_SPEC parameter is required. If the BASE_SPEC parameter is
1221
not supplied, the common ancestor of OTHER_SPEC the current branch is used
1224
merge refuses to run if there are any uncommitted changes, unless
1227
takes_args = ['other_spec', 'base_spec?']
1228
takes_options = ['force']
1230
def run(self, other_spec, base_spec=None, force=False):
1231
from bzrlib.merge import merge
1232
merge(parse_spec(other_spec), parse_spec(base_spec),
1233
check_clean=(not force))
1237
class cmd_revert(Command):
1238
"""Restore selected files from a previous revision.
1240
takes_args = ['file+']
1241
def run(self, file_list):
1242
from bzrlib.branch import find_branch
1247
b = find_branch(file_list[0])
1249
b.revert([b.relpath(f) for f in file_list])
1252
class cmd_merge_revert(Command):
1253
"""Reverse all changes since the last commit.
1255
Only versioned files are affected.
1257
TODO: Store backups of any files that will be reverted, so
1258
that the revert can be undone.
1260
takes_options = ['revision']
1262
def run(self, revision=-1):
1263
from bzrlib.merge import merge
1264
merge(('.', revision), parse_spec('.'),
1269
642
class cmd_assert_fail(Command):
1270
643
"""Test reporting of assertion failures"""
1355
701
(['status'], {'all': True})
1356
702
>>> parse_args('commit --message=biter'.split())
1357
703
(['commit'], {'message': u'biter'})
1358
>>> parse_args('log -r 500'.split())
1359
(['log'], {'revision': 500})
1360
>>> parse_args('log -r500:600'.split())
1361
(['log'], {'revision': [500, 600]})
1362
>>> parse_args('log -vr500:600'.split())
1363
(['log'], {'verbose': True, 'revision': [500, 600]})
1364
>>> parse_args('log -rv500:600'.split()) #the r takes an argument
1365
Traceback (most recent call last):
1367
ValueError: invalid literal for int(): v500
1386
722
if optname not in OPTIONS:
1387
raise BzrError('unknown long option %r' % a)
723
bailout('unknown long option %r' % a)
1389
725
shortopt = a[1:]
1390
if shortopt in SHORT_OPTIONS:
1391
# Multi-character options must have a space to delimit
1393
optname = SHORT_OPTIONS[shortopt]
1395
# Single character short options, can be chained,
1396
# and have their value appended to their name
1398
if shortopt not in SHORT_OPTIONS:
1399
# We didn't find the multi-character name, and we
1400
# didn't find the single char name
1401
raise BzrError('unknown short option %r' % a)
1402
optname = SHORT_OPTIONS[shortopt]
1405
# There are extra things on this option
1406
# see if it is the value, or if it is another
1408
optargfn = OPTIONS[optname]
1409
if optargfn is None:
1410
# This option does not take an argument, so the
1411
# next entry is another short option, pack it back
1413
argv.insert(0, '-' + a[2:])
1415
# This option takes an argument, so pack it
726
if shortopt not in SHORT_OPTIONS:
727
bailout('unknown short option %r' % a)
728
optname = SHORT_OPTIONS[shortopt]
1419
730
if optname in opts:
1420
731
# XXX: Do we ever want to support this, e.g. for -r?
1421
raise BzrError('repeated option %r' % a)
732
bailout('repeated option %r' % a)
1423
734
optargfn = OPTIONS[optname]
1425
736
if optarg == None:
1427
raise BzrError('option %r needs an argument' % a)
738
bailout('option %r needs an argument' % a)
1429
740
optarg = argv.pop(0)
1430
741
opts[optname] = optargfn(optarg)
1432
743
if optarg != None:
1433
raise BzrError('option %r takes no argument' % optname)
744
bailout('option %r takes no argument' % optname)
1434
745
opts[optname] = True
1487
def _parse_master_args(argv):
1488
"""Parse the arguments that always go with the original command.
1489
These are things like bzr --no-plugins, etc.
1491
There are now 2 types of option flags. Ones that come *before* the command,
1492
and ones that come *after* the command.
1493
Ones coming *before* the command are applied against all possible commands.
1494
And are generally applied before plugins are loaded.
1496
The current list are:
1497
--builtin Allow plugins to load, but don't let them override builtin commands,
1498
they will still be allowed if they do not override a builtin.
1499
--no-plugins Don't load any plugins. This lets you get back to official source
1501
--profile Enable the hotspot profile before running the command.
1502
For backwards compatibility, this is also a non-master option.
1503
--version Spit out the version of bzr that is running and exit.
1504
This is also a non-master option.
1505
--help Run help and exit, also a non-master option (I think that should stay, though)
1507
>>> argv, opts = _parse_master_args(['bzr', '--test'])
1508
Traceback (most recent call last):
1510
BzrCommandError: Invalid master option: 'test'
1511
>>> argv, opts = _parse_master_args(['bzr', '--version', 'command'])
1514
>>> print opts['version']
1516
>>> argv, opts = _parse_master_args(['bzr', '--profile', 'command', '--more-options'])
1518
['command', '--more-options']
1519
>>> print opts['profile']
1521
>>> argv, opts = _parse_master_args(['bzr', '--no-plugins', 'command'])
1524
>>> print opts['no-plugins']
1526
>>> print opts['profile']
1528
>>> argv, opts = _parse_master_args(['bzr', 'command', '--profile'])
1530
['command', '--profile']
1531
>>> print opts['profile']
1534
master_opts = {'builtin':False,
1541
# This is the point where we could hook into argv[0] to determine
1542
# what front-end is supposed to be run
1543
# For now, we are just ignoring it.
1544
cmd_name = argv.pop(0)
1546
if arg[:2] != '--': # at the first non-option, we return the rest
1548
arg = arg[2:] # Remove '--'
1549
if arg not in master_opts:
1550
# We could say that this is not an error, that we should
1551
# just let it be handled by the main section instead
1552
raise BzrCommandError('Invalid master option: %r' % arg)
1553
argv.pop(0) # We are consuming this entry
1554
master_opts[arg] = True
1555
return argv, master_opts
1559
799
def run_bzr(argv):
1560
800
"""Execute a command.
1565
805
argv = [a.decode(bzrlib.user_encoding) for a in argv]
1568
# some options like --builtin and --no-plugins have special effects
1569
argv, master_opts = _parse_master_args(argv)
1570
if not master_opts['no-plugins']:
1571
bzrlib.load_plugins()
1573
args, opts = parse_args(argv)
1575
if master_opts['help']:
1576
from bzrlib.help import help
808
args, opts = parse_args(argv[1:])
1583
809
if 'help' in opts:
1584
from bzrlib.help import help
1590
816
elif 'version' in opts:
1593
elif args and args[0] == 'builtin':
1594
include_plugins=False
1596
819
cmd = str(args.pop(0))
1597
820
except IndexError:
821
log_error('usage: bzr COMMAND')
822
log_error(' try "bzr help"')
1603
plugins_override = not (master_opts['builtin'])
1604
canonical_cmd, cmd_class = get_cmd_class(cmd, plugins_override=plugins_override)
1606
profile = master_opts['profile']
1607
# For backwards compatibility, I would rather stick with --profile being a
1608
# master/global option
825
canonical_cmd, cmd_class = get_cmd_class(cmd)
1609
828
if 'profile' in opts:
1611
830
del opts['profile']
1613
834
# check options are reasonable
1614
835
allowed = cmd_class.takes_options
1615
836
for oname in opts:
1616
837
if oname not in allowed:
1617
raise BzrCommandError("option '--%s' is not allowed for command %r"
838
raise BzrCommandError("option %r is not allowed for command %r"
1620
841
# mix arguments and options into one dictionary