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
19
import sys, os, time, os.path
23
from bzrlib.trace import mutter, note, log_error
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 _squish_command_name(cmd):
33
return 'cmd_' + cmd.replace('-', '_')
36
def _unsquish_command_name(cmd):
37
assert cmd.startswith("cmd_")
38
return cmd[4:].replace('_','-')
41
"""Return canonical name and class for all registered commands."""
42
for k, v in globals().iteritems():
43
if k.startswith("cmd_"):
44
yield _unsquish_command_name(k), v
46
def get_cmd_class(cmd):
47
"""Return the canonical name and command class for a command.
49
cmd = str(cmd) # not unicode
51
# first look up this command under the specified name
53
return cmd, globals()[_squish_command_name(cmd)]
57
# look for any command which claims this as an alias
58
for cmdname, cmdclass in get_all_cmds():
59
if cmd in cmdclass.aliases:
60
return cmdname, cmdclass
62
raise BzrCommandError("unknown command %r" % cmd)
66
"""Base class for commands.
68
The docstring for an actual command should give a single-line
69
summary, then a complete description of the command. A grammar
70
description will be inserted.
73
List of argument forms, marked with whether they are optional,
77
List of options that may be given for this command.
80
If true, this command isn't advertised.
89
def __init__(self, options, arguments):
90
"""Construct and run the command.
92
Sets self.status to the return value of run()."""
93
assert isinstance(options, dict)
94
assert isinstance(arguments, dict)
95
cmdargs = options.copy()
96
cmdargs.update(arguments)
97
assert self.__doc__ != Command.__doc__, \
98
("No help message set for %r" % self)
99
self.status = self.run(**cmdargs)
103
"""Override this in sub-classes.
105
This is invoked with the options and arguments bound to
108
Return 0 or None if the command was successful, or a shell
115
class cmd_status(Command):
116
"""Display status summary.
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.
122
takes_args = ['file*']
123
takes_options = ['all']
124
aliases = ['st', 'stat']
126
def run(self, all=False, file_list=None):
127
#import bzrlib.status
128
#bzrlib.status.tree_status(Branch('.'))
129
Branch('.').show_status(show_all=all, file_list=file_list)
132
class cmd_cat_revision(Command):
133
"""Write out metadata for a revision."""
136
takes_args = ['revision_id']
138
def run(self, revision_id):
139
Branch('.').get_revision(revision_id).write_xml(sys.stdout)
142
class cmd_revno(Command):
143
"""Show current revision number.
145
This is equal to the number of revisions on this branch."""
147
print Branch('.').revno()
150
class cmd_add(Command):
151
"""Add specified files or directories.
153
In non-recursive mode, all the named items are added, regardless
154
of whether they were previously ignored. A warning is given if
155
any of the named files are already versioned.
157
In recursive mode (the default), files are treated the same way
158
but the behaviour for directories is different. Directories that
159
are already versioned do not give a warning. All directories,
160
whether already versioned or not, are searched for files or
161
subdirectories that are neither versioned or ignored, and these
162
are added. This search proceeds recursively into versioned
165
Therefore simply saying 'bzr add .' will version all files that
166
are currently unknown.
168
TODO: Perhaps adding a file whose directly is not versioned should
169
recursively add that parent, rather than giving an error?
171
takes_args = ['file+']
172
takes_options = ['verbose']
174
def run(self, file_list, verbose=False):
175
bzrlib.add.smart_add(file_list, verbose)
178
class cmd_relpath(Command):
179
"""Show path of a file relative to root"""
180
takes_args = ['filename']
182
def run(self, filename):
183
print Branch(filename).relpath(filename)
187
class cmd_inventory(Command):
188
"""Show inventory of the current working copy or a revision."""
189
takes_options = ['revision']
191
def run(self, revision=None):
194
inv = b.read_working_inventory()
196
inv = b.get_revision_inventory(b.lookup_revision(revision))
198
for path, entry in inv.iter_entries():
199
print '%-50s %s' % (entry.file_id, path)
202
class cmd_move(Command):
203
"""Move files to a different directory.
208
The destination must be a versioned directory in the same branch.
210
takes_args = ['source$', 'dest']
211
def run(self, source_list, dest):
214
b.move([b.relpath(s) for s in source_list], b.relpath(dest))
217
class cmd_rename(Command):
218
"""Change the name of an entry.
221
bzr rename frob.c frobber.c
222
bzr rename src/frob.c lib/frob.c
224
It is an error if the destination name exists.
226
See also the 'move' command, which moves files into a different
227
directory without changing their name.
229
TODO: Some way to rename multiple files without invoking bzr for each
231
takes_args = ['from_name', 'to_name']
233
def run(self, from_name, to_name):
235
b.rename_one(b.relpath(from_name), b.relpath(to_name))
239
class cmd_renames(Command):
240
"""Show list of renamed files.
242
TODO: Option to show renames between two historical versions.
244
TODO: Only show renames under dir, rather than in the whole branch.
246
takes_args = ['dir?']
248
def run(self, dir='.'):
250
old_inv = b.basis_tree().inventory
251
new_inv = b.read_working_inventory()
253
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
255
for old_name, new_name in renames:
256
print "%s => %s" % (old_name, new_name)
259
class cmd_info(Command):
260
"""Show statistical information for this branch"""
263
info.show_info(Branch('.'))
266
class cmd_remove(Command):
267
"""Make a file unversioned.
269
This makes bzr stop tracking changes to a versioned file. It does
270
not delete the working copy.
272
takes_args = ['file+']
273
takes_options = ['verbose']
275
def run(self, file_list, verbose=False):
276
b = Branch(file_list[0])
277
b.remove([b.relpath(f) for f in file_list], verbose=verbose)
280
class cmd_file_id(Command):
281
"""Print file_id of a particular file or directory.
283
The file_id is assigned when the file is first added and remains the
284
same through all revisions where the file exists, even when it is
288
takes_args = ['filename']
289
def run(self, filename):
291
i = b.inventory.path2id(b.relpath(filename))
293
bailout("%r is not a versioned file" % filename)
298
class cmd_file_path(Command):
299
"""Print path of file_ids to a file or directory.
301
This prints one line for each directory down to the target,
302
starting at the branch root."""
304
takes_args = ['filename']
305
def run(self, filename):
308
fid = inv.path2id(b.relpath(filename))
310
bailout("%r is not a versioned file" % filename)
311
for fip in inv.get_idpath(fid):
315
class cmd_revision_history(Command):
316
"""Display list of revision ids on this branch."""
318
for patchid in Branch('.').revision_history():
322
class cmd_directories(Command):
323
"""Display list of versioned directories in this branch."""
325
for name, ie in Branch('.').read_working_inventory().directories():
332
class cmd_init(Command):
333
"""Make a directory into a versioned branch.
335
Use this to create an empty branch, or before importing an
338
Recipe for importing a tree of files:
343
bzr commit -m 'imported project'
346
Branch('.', init=True)
349
class cmd_diff(Command):
350
"""Show differences in working tree.
352
If files are listed, only the changes in those files are listed.
353
Otherwise, all changes for the tree are listed.
355
TODO: Given two revision arguments, show the difference between them.
357
TODO: Allow diff across branches.
359
TODO: Option to use external diff command; could be GNU diff, wdiff,
362
TODO: Python difflib is not exactly the same as unidiff; should
363
either fix it up or prefer to use an external diff.
365
TODO: If a directory is given, diff everything under that.
367
TODO: Selected-file diff is inefficient and doesn't show you
370
TODO: This probably handles non-Unix newlines poorly.
373
takes_args = ['file*']
374
takes_options = ['revision']
377
def run(self, revision=None, file_list=None):
378
from bzrlib.diff import show_diff
380
show_diff(Branch('.'), revision, file_list)
383
class cmd_deleted(Command):
384
"""List files deleted in the working tree.
386
TODO: Show files deleted since a previous revision, or between two revisions.
388
def run(self, show_ids=False):
391
new = b.working_tree()
393
## TODO: Much more efficient way to do this: read in new
394
## directories with readdir, rather than stating each one. Same
395
## level of effort but possibly much less IO. (Or possibly not,
396
## if the directories are very large...)
398
for path, ie in old.inventory.iter_entries():
399
if not new.has_id(ie.file_id):
401
print '%-50s %s' % (path, ie.file_id)
405
class cmd_root(Command):
406
"""Show the tree root directory.
408
The root is the nearest enclosing directory with a .bzr control
410
takes_args = ['filename?']
411
def run(self, filename=None):
412
"""Print the branch root."""
413
print bzrlib.branch.find_branch_root(filename)
417
class cmd_log(Command):
418
"""Show log of this branch.
420
TODO: Option to limit range.
422
TODO: Perhaps show most-recent first with an option for last.
424
takes_args = ['filename?']
425
takes_options = ['timezone', 'verbose', 'show-ids']
426
def run(self, filename=None, timezone='original', verbose=False, show_ids=False):
427
b = Branch((filename or '.'), lock_mode='r')
429
filename = b.relpath(filename)
430
bzrlib.show_log(b, filename,
431
show_timezone=timezone,
437
class cmd_touching_revisions(Command):
438
"""Return revision-ids which affected a particular file."""
440
takes_args = ["filename"]
441
def run(self, filename):
442
b = Branch(filename, lock_mode='r')
443
inv = b.read_working_inventory()
444
file_id = inv.path2id(b.relpath(filename))
445
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
446
print "%6d %s" % (revno, what)
449
class cmd_ls(Command):
450
"""List files in a tree.
452
TODO: Take a revision or remote path and list that tree instead.
455
def run(self, revision=None, verbose=False):
458
tree = b.working_tree()
460
tree = b.revision_tree(b.lookup_revision(revision))
462
for fp, fc, kind, fid in tree.list_files():
464
if kind == 'directory':
471
print '%-8s %s%s' % (fc, fp, kindch)
477
class cmd_unknowns(Command):
478
"""List unknown files"""
480
for f in Branch('.').unknowns():
485
class cmd_ignore(Command):
486
"""Ignore a command or pattern"""
487
takes_args = ['name_pattern']
489
def run(self, name_pattern):
492
# XXX: This will fail if it's a hardlink; should use an AtomicFile class.
493
f = open(b.abspath('.bzrignore'), 'at')
494
f.write(name_pattern + '\n')
497
inv = b.working_tree().inventory
498
if inv.path2id('.bzrignore'):
499
mutter('.bzrignore is already versioned')
501
mutter('need to make new .bzrignore file versioned')
502
b.add(['.bzrignore'])
506
class cmd_ignored(Command):
507
"""List ignored files and the patterns that matched them."""
509
tree = Branch('.').working_tree()
510
for path, file_class, kind, file_id in tree.list_files():
511
if file_class != 'I':
513
## XXX: Slightly inefficient since this was already calculated
514
pat = tree.is_ignored(path)
515
print '%-50s %s' % (path, pat)
518
class cmd_lookup_revision(Command):
519
"""Lookup the revision-id from a revision-number
522
bzr lookup-revision 33
525
takes_args = ['revno']
527
def run(self, revno):
531
raise BzrCommandError("not a valid revision-number: %r" % revno)
533
print Branch('.').lookup_revision(revno)
536
class cmd_export(Command):
537
"""Export past revision to destination directory.
539
If no revision is specified this exports the last committed revision."""
540
takes_args = ['dest']
541
takes_options = ['revision']
542
def run(self, dest, revision=None):
545
rh = b.revision_history()[-1]
547
rh = b.lookup_revision(int(revision))
548
t = b.revision_tree(rh)
552
class cmd_cat(Command):
553
"""Write a file's text from a previous revision."""
555
takes_options = ['revision']
556
takes_args = ['filename']
558
def run(self, filename, revision=None):
560
raise BzrCommandError("bzr cat requires a revision number")
562
b.print_file(b.relpath(filename), int(revision))
565
class cmd_local_time_offset(Command):
566
"""Show the offset in seconds from GMT to local time."""
569
print bzrlib.osutils.local_time_offset()
573
class cmd_commit(Command):
574
"""Commit changes into a new revision.
576
TODO: Commit only selected files.
578
TODO: Run hooks on tree to-be-committed, and after commit.
580
TODO: Strict commit that fails if there are unknown or deleted files.
582
takes_options = ['message', 'file', 'verbose']
583
aliases = ['ci', 'checkin']
585
def run(self, message=None, file=None, verbose=False):
586
## Warning: shadows builtin file()
587
if not message and not file:
588
raise BzrCommandError("please specify a commit message",
589
["use either --message or --file"])
590
elif message and file:
591
raise BzrCommandError("please specify either --message or --file")
595
message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
597
Branch('.').commit(message, verbose=verbose)
600
class cmd_check(Command):
601
"""Validate consistency of branch history.
603
This command checks various invariants about the branch storage to
604
detect data corruption or bzr bugs.
606
takes_args = ['dir?']
607
def run(self, dir='.'):
609
bzrlib.check.check(Branch(dir, find_root=False))
613
class cmd_whoami(Command):
614
"""Show bzr user id."""
615
takes_options = ['email']
617
def run(self, email=False):
619
print bzrlib.osutils.user_email()
621
print bzrlib.osutils.username()
624
class cmd_selftest(Command):
625
"""Run internal test suite"""
628
failures, tests = 0, 0
630
import doctest, bzrlib.store, bzrlib.tests
631
bzrlib.trace.verbose = False
633
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \
634
bzrlib.tree, bzrlib.tests, bzrlib.commands, bzrlib.add:
635
mf, mt = doctest.testmod(m)
638
print '%-40s %3d tests' % (m.__name__, mt),
640
print '%3d FAILED!' % mf
644
print '%-40s %3d tests' % ('total', tests),
646
print '%3d FAILED!' % failures
652
class cmd_version(Command):
653
"""Show version of bzr"""
658
print "bzr (bazaar-ng) %s" % bzrlib.__version__
659
print bzrlib.__copyright__
660
print "http://bazaar-ng.org/"
662
print "bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and"
663
print "you may use, modify and redistribute it under the terms of the GNU"
664
print "General Public License version 2 or later."
667
class cmd_rocks(Command):
668
"""Statement of optimism."""
671
print "it sure does!"
674
class cmd_assert_fail(Command):
675
"""Test reporting of assertion failures"""
678
assert False, "always fails"
681
class cmd_help(Command):
682
"""Show help on a command or other topic.
684
For a list of all available commands, say 'bzr help commands'."""
685
takes_args = ['topic?']
688
def run(self, topic=None):
693
######################################################################
697
# list of all available options; the rhs can be either None for an
698
# option that takes no argument, or a constructor function that checks
722
def parse_args(argv):
723
"""Parse command line.
725
Arguments and options are parsed at this level before being passed
726
down to specific command handlers. This routine knows, from a
727
lookup table, something about the available options, what optargs
728
they take, and which commands will accept them.
730
>>> parse_args('--help'.split())
732
>>> parse_args('--version'.split())
733
([], {'version': True})
734
>>> parse_args('status --all'.split())
735
(['status'], {'all': True})
736
>>> parse_args('commit --message=biter'.split())
737
(['commit'], {'message': u'biter'})
742
# TODO: Maybe handle '--' to end options?
747
# option names must not be unicode
751
mutter(" got option %r" % a)
753
optname, optarg = a[2:].split('=', 1)
756
if optname not in OPTIONS:
757
bailout('unknown long option %r' % a)
760
if shortopt not in SHORT_OPTIONS:
761
bailout('unknown short option %r' % a)
762
optname = SHORT_OPTIONS[shortopt]
765
# XXX: Do we ever want to support this, e.g. for -r?
766
bailout('repeated option %r' % a)
768
optargfn = OPTIONS[optname]
772
bailout('option %r needs an argument' % a)
775
opts[optname] = optargfn(optarg)
778
bailout('option %r takes no argument' % optname)
788
def _match_argform(cmd, takes_args, args):
791
# step through args and takes_args, allowing appropriate 0-many matches
792
for ap in takes_args:
796
argdict[argname] = args.pop(0)
797
elif ap[-1] == '*': # all remaining arguments
799
argdict[argname + '_list'] = args[:]
802
argdict[argname + '_list'] = None
805
raise BzrCommandError("command %r needs one or more %s"
806
% (cmd, argname.upper()))
808
argdict[argname + '_list'] = args[:]
810
elif ap[-1] == '$': # all but one
812
raise BzrCommandError("command %r needs one or more %s"
813
% (cmd, argname.upper()))
814
argdict[argname + '_list'] = args[:-1]
820
raise BzrCommandError("command %r requires argument %s"
821
% (cmd, argname.upper()))
823
argdict[argname] = args.pop(0)
826
raise BzrCommandError("extra argument to command %s: %s"
834
"""Execute a command.
836
This is similar to main(), but without all the trappings for
837
logging and error handling.
839
argv = [a.decode(bzrlib.user_encoding) for a in argv]
842
args, opts = parse_args(argv[1:])
850
elif 'version' in opts:
853
cmd = str(args.pop(0))
855
log_error('usage: bzr COMMAND')
856
log_error(' try "bzr help"')
859
canonical_cmd, cmd_class = get_cmd_class(cmd)
862
if 'profile' in opts:
868
# check options are reasonable
869
allowed = cmd_class.takes_options
871
if oname not in allowed:
872
raise BzrCommandError("option '--%s' is not allowed for command %r"
875
# mix arguments and options into one dictionary
876
cmdargs = _match_argform(cmd, cmd_class.takes_args, args)
878
for k, v in opts.items():
879
cmdopts[k.replace('-', '_')] = v
882
import hotshot, tempfile
883
pffileno, pfname = tempfile.mkstemp()
885
prof = hotshot.Profile(pfname)
886
ret = prof.runcall(cmd_class, cmdopts, cmdargs) or 0
890
stats = hotshot.stats.load(pfname)
892
stats.sort_stats('time')
893
## XXX: Might like to write to stderr or the trace file instead but
894
## print_stats seems hardcoded to stdout
895
stats.print_stats(20)
903
cmdobj = cmd_class(cmdopts, cmdargs).status
906
def _report_exception(summary, quiet=False):
908
log_error('bzr: ' + summary)
909
bzrlib.trace.log_exception()
912
tb = sys.exc_info()[2]
913
exinfo = traceback.extract_tb(tb)
915
sys.stderr.write(' at %s:%d in %s()\n' % exinfo[-1][:3])
916
sys.stderr.write(' see ~/.bzr.log for debug information\n')
923
bzrlib.open_tracefile(argv)
930
# do this here inside the exception wrappers to catch EPIPE
933
quiet = isinstance(e, (BzrCommandError))
934
_report_exception('error: ' + e.args[0], quiet=quiet)
937
# some explanation or hints
940
except AssertionError, e:
941
msg = 'assertion failed'
944
_report_exception(msg)
946
except KeyboardInterrupt, e:
947
_report_exception('interrupted', quiet=True)
951
if isinstance(e, IOError) and e.errno == errno.EPIPE:
955
msg = str(e).rstrip('\n')
956
_report_exception(msg, quiet)
959
bzrlib.trace.close_trace()
962
if __name__ == '__main__':
963
sys.exit(main(sys.argv))