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
17
# DO NOT change this to cStringIO - it results in control files
19
# FIXIT! (Only deal with byte streams OR unicode at any one layer.)
21
from StringIO import StringIO
26
from bzrlib import BZRDIR
27
from bzrlib.commands import Command, display_command
28
from bzrlib.branch import Branch
29
from bzrlib.revision import common_ancestor
30
from bzrlib.errors import (BzrError, BzrCheckError, BzrCommandError,
31
NotBranchError, DivergedBranches, NotConflicted,
32
NoSuchFile, NoWorkingTree, FileInWrongBranch)
33
from bzrlib.option import Option
34
from bzrlib.revisionspec import RevisionSpec
36
from bzrlib.trace import mutter, note, log_error, warning
37
from bzrlib.workingtree import WorkingTree
40
def branch_files(file_list, default_branch='.'):
42
return inner_branch_files(file_list, default_branch)
43
except FileInWrongBranch, e:
45
raise BzrCommandError("%s is not in the same branch as %s" %
46
(e.path, file_list[0]))
48
def inner_branch_files(file_list, default_branch='.'):
50
Return a branch and list of branch-relative paths.
51
If supplied file_list is empty or None, the branch default will be used,
52
and returned file_list will match the original.
54
if file_list is None or len(file_list) == 0:
55
return Branch.open_containing(default_branch)[0], file_list
56
b = Branch.open_containing(file_list[0])[0]
58
# note that if this is a remote branch, we would want
59
# relpath against the transport. RBC 20051018
60
# Most branch ops can't meaningfully operate on files in remote branches;
61
# the above comment was in cmd_status. ADHB 20051026
62
tree = WorkingTree(b.base, b)
64
for filename in file_list:
66
new_list.append(tree.relpath(filename))
67
except NotBranchError:
68
raise FileInWrongBranch(b, filename)
72
# TODO: Make sure no commands unconditionally use the working directory as a
73
# branch. If a filename argument is used, the first of them should be used to
74
# specify the branch. (Perhaps this can be factored out into some kind of
75
# Argument class, representing a file in a branch, where the first occurrence
78
class cmd_status(Command):
79
"""Display status summary.
81
This reports on versioned and unknown files, reporting them
82
grouped by state. Possible states are:
85
Versioned in the working copy but not in the previous revision.
88
Versioned in the previous revision but removed or deleted
92
Path of this file changed from the previous revision;
93
the text may also have changed. This includes files whose
94
parent directory was renamed.
97
Text has changed since the previous revision.
100
Nothing about this file has changed since the previous revision.
101
Only shown with --all.
104
Not versioned and not matching an ignore pattern.
106
To see ignored files use 'bzr ignored'. For details in the
107
changes to file texts, use 'bzr diff'.
109
If no arguments are specified, the status of the entire working
110
directory is shown. Otherwise, only the status of the specified
111
files or directories is reported. If a directory is given, status
112
is reported for everything inside that directory.
114
If a revision argument is given, the status is calculated against
115
that revision, or between two revisions if two are provided.
118
# TODO: --no-recurse, --recurse options
120
takes_args = ['file*']
121
takes_options = ['all', 'show-ids', 'revision']
122
aliases = ['st', 'stat']
125
def run(self, all=False, show_ids=False, file_list=None, revision=None):
126
b, file_list = branch_files(file_list)
128
from bzrlib.status import show_status
129
show_status(b, show_unchanged=all, show_ids=show_ids,
130
specific_files=file_list, revision=revision)
133
class cmd_cat_revision(Command):
134
"""Write out metadata for a revision.
136
The revision to print can either be specified by a specific
137
revision identifier, or you can use --revision.
141
takes_args = ['revision_id?']
142
takes_options = ['revision']
145
def run(self, revision_id=None, revision=None):
147
if revision_id is not None and revision is not None:
148
raise BzrCommandError('You can only supply one of revision_id or --revision')
149
if revision_id is None and revision is None:
150
raise BzrCommandError('You must supply either --revision or a revision_id')
151
b = Branch.open_containing('.')[0]
152
if revision_id is not None:
153
sys.stdout.write(b.get_revision_xml_file(revision_id).read())
154
elif revision is not None:
157
raise BzrCommandError('You cannot specify a NULL revision.')
158
revno, rev_id = rev.in_history(b)
159
sys.stdout.write(b.get_revision_xml_file(rev_id).read())
162
class cmd_revno(Command):
163
"""Show current revision number.
165
This is equal to the number of revisions on this branch."""
168
print Branch.open_containing('.')[0].revno()
171
class cmd_revision_info(Command):
172
"""Show revision number and revision id for a given revision identifier.
175
takes_args = ['revision_info*']
176
takes_options = ['revision']
178
def run(self, revision=None, revision_info_list=[]):
181
if revision is not None:
182
revs.extend(revision)
183
if revision_info_list is not None:
184
for rev in revision_info_list:
185
revs.append(RevisionSpec(rev))
187
raise BzrCommandError('You must supply a revision identifier')
189
b = Branch.open_containing('.')[0]
192
revinfo = rev.in_history(b)
193
if revinfo.revno is None:
194
print ' %s' % revinfo.rev_id
196
print '%4d %s' % (revinfo.revno, revinfo.rev_id)
199
class cmd_add(Command):
200
"""Add specified files or directories.
202
In non-recursive mode, all the named items are added, regardless
203
of whether they were previously ignored. A warning is given if
204
any of the named files are already versioned.
206
In recursive mode (the default), files are treated the same way
207
but the behaviour for directories is different. Directories that
208
are already versioned do not give a warning. All directories,
209
whether already versioned or not, are searched for files or
210
subdirectories that are neither versioned or ignored, and these
211
are added. This search proceeds recursively into versioned
212
directories. If no names are given '.' is assumed.
214
Therefore simply saying 'bzr add' will version all files that
215
are currently unknown.
217
Adding a file whose parent directory is not versioned will
218
implicitly add the parent, and so on up to the root. This means
219
you should never need to explictly add a directory, they'll just
220
get added when you add a file in the directory.
222
takes_args = ['file*']
223
takes_options = ['no-recurse', 'quiet']
225
def run(self, file_list, no_recurse=False, quiet=False):
226
from bzrlib.add import smart_add, add_reporter_print, add_reporter_null
228
reporter = add_reporter_null
230
reporter = add_reporter_print
231
smart_add(file_list, not no_recurse, reporter)
234
class cmd_mkdir(Command):
235
"""Create a new versioned directory.
237
This is equivalent to creating the directory and then adding it.
239
takes_args = ['dir+']
241
def run(self, dir_list):
246
b, dd = Branch.open_containing(d)
251
class cmd_relpath(Command):
252
"""Show path of a file relative to root"""
253
takes_args = ['filename']
257
def run(self, filename):
258
branch, relpath = Branch.open_containing(filename)
262
class cmd_inventory(Command):
263
"""Show inventory of the current working copy or a revision."""
264
takes_options = ['revision', 'show-ids']
267
def run(self, revision=None, show_ids=False):
268
b = Branch.open_containing('.')[0]
270
inv = b.working_tree().read_working_inventory()
272
if len(revision) > 1:
273
raise BzrCommandError('bzr inventory --revision takes'
274
' exactly one revision identifier')
275
inv = b.get_revision_inventory(revision[0].in_history(b).rev_id)
277
for path, entry in inv.entries():
279
print '%-50s %s' % (path, entry.file_id)
284
class cmd_move(Command):
285
"""Move files to a different directory.
290
The destination must be a versioned directory in the same branch.
292
takes_args = ['source$', 'dest']
293
def run(self, source_list, dest):
294
b, source_list = branch_files(source_list)
296
# TODO: glob expansion on windows?
297
tree = WorkingTree(b.base, b)
298
b.move(source_list, tree.relpath(dest))
301
class cmd_rename(Command):
302
"""Change the name of an entry.
305
bzr rename frob.c frobber.c
306
bzr rename src/frob.c lib/frob.c
308
It is an error if the destination name exists.
310
See also the 'move' command, which moves files into a different
311
directory without changing their name.
313
# TODO: Some way to rename multiple files without invoking
314
# bzr for each one?"""
315
takes_args = ['from_name', 'to_name']
317
def run(self, from_name, to_name):
318
b, (from_name, to_name) = branch_files((from_name, to_name))
319
b.rename_one(from_name, to_name)
322
class cmd_mv(Command):
323
"""Move or rename a file.
326
bzr mv OLDNAME NEWNAME
327
bzr mv SOURCE... DESTINATION
329
If the last argument is a versioned directory, all the other names
330
are moved into it. Otherwise, there must be exactly two arguments
331
and the file is changed to a new name, which must not already exist.
333
Files cannot be moved between branches.
335
takes_args = ['names*']
336
def run(self, names_list):
337
if len(names_list) < 2:
338
raise BzrCommandError("missing file argument")
339
b, rel_names = branch_files(names_list)
341
if os.path.isdir(names_list[-1]):
342
# move into existing directory
343
for pair in b.move(rel_names[:-1], rel_names[-1]):
344
print "%s => %s" % pair
346
if len(names_list) != 2:
347
raise BzrCommandError('to mv multiple files the destination '
348
'must be a versioned directory')
349
b.rename_one(rel_names[0], rel_names[1])
350
print "%s => %s" % (rel_names[0], rel_names[1])
353
class cmd_pull(Command):
354
"""Pull any changes from another branch into the current one.
356
If there is no default location set, the first pull will set it. After
357
that, you can omit the location to use the default. To change the
358
default, use --remember.
360
This command only works on branches that have not diverged. Branches are
361
considered diverged if both branches have had commits without first
362
pulling from the other.
364
If branches have diverged, you can use 'bzr merge' to pull the text changes
365
from one into the other. Once one branch has merged, the other should
366
be able to pull it again.
368
If you want to forget your local changes and just update your branch to
369
match the remote one, use --overwrite.
371
takes_options = ['remember', 'overwrite', 'verbose']
372
takes_args = ['location?']
374
def run(self, location=None, remember=False, overwrite=False, verbose=False):
375
from bzrlib.merge import merge
376
from shutil import rmtree
379
br_to = Branch.open_containing('.')[0]
380
stored_loc = br_to.get_parent()
382
if stored_loc is None:
383
raise BzrCommandError("No pull location known or specified.")
385
print "Using saved location: %s" % stored_loc
386
location = stored_loc
387
br_from = Branch.open(location)
389
old_rh = br_to.revision_history()
390
count = br_to.working_tree().pull(br_from, overwrite)
391
except DivergedBranches:
392
raise BzrCommandError("These branches have diverged."
394
if br_to.get_parent() is None or remember:
395
br_to.set_parent(location)
396
note('%d revision(s) pulled.' % (count,))
399
new_rh = br_to.revision_history()
402
from bzrlib.log import show_changed_revisions
403
show_changed_revisions(br_to, old_rh, new_rh)
406
class cmd_push(Command):
407
"""Push this branch into another branch.
409
The remote branch will not have its working tree populated because this
410
is both expensive, and may not be supported on the remote file system.
412
Some smart servers or protocols *may* put the working tree in place.
414
If there is no default push location set, the first push will set it.
415
After that, you can omit the location to use the default. To change the
416
default, use --remember.
418
This command only works on branches that have not diverged. Branches are
419
considered diverged if the branch being pushed to is not an older version
422
If branches have diverged, you can use 'bzr push --overwrite' to replace
423
the other branch completely.
425
If you want to ensure you have the different changes in the other branch,
426
do a merge (see bzr help merge) from the other branch, and commit that
427
before doing a 'push --overwrite'.
429
takes_options = ['remember', 'overwrite',
430
Option('create-prefix',
431
help='Create the path leading up to the branch '
432
'if it does not already exist')]
433
takes_args = ['location?']
435
def run(self, location=None, remember=False, overwrite=False,
436
create_prefix=False, verbose=False):
438
from shutil import rmtree
439
from bzrlib.transport import get_transport
441
br_from = Branch.open_containing('.')[0]
442
stored_loc = br_from.get_push_location()
444
if stored_loc is None:
445
raise BzrCommandError("No push location known or specified.")
447
print "Using saved location: %s" % stored_loc
448
location = stored_loc
450
br_to = Branch.open(location)
451
except NotBranchError:
453
transport = get_transport(location).clone('..')
454
if not create_prefix:
456
transport.mkdir(transport.relpath(location))
458
raise BzrCommandError("Parent directory of %s "
459
"does not exist." % location)
461
current = transport.base
462
needed = [(transport, transport.relpath(location))]
465
transport, relpath = needed[-1]
466
transport.mkdir(relpath)
469
new_transport = transport.clone('..')
470
needed.append((new_transport,
471
new_transport.relpath(transport.base)))
472
if new_transport.base == transport.base:
473
raise BzrCommandError("Could not creeate "
477
br_to = Branch.initialize(location)
479
old_rh = br_to.revision_history()
480
count = br_to.pull(br_from, overwrite)
481
except DivergedBranches:
482
raise BzrCommandError("These branches have diverged."
483
" Try a merge then push with overwrite.")
484
if br_from.get_push_location() is None or remember:
485
br_from.set_push_location(location)
486
note('%d revision(s) pushed.' % (count,))
489
new_rh = br_to.revision_history()
492
from bzrlib.log import show_changed_revisions
493
show_changed_revisions(br_to, old_rh, new_rh)
495
class cmd_branch(Command):
496
"""Create a new copy of a branch.
498
If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
499
be used. In other words, "branch ../foo/bar" will attempt to create ./bar.
501
To retrieve the branch as of a particular revision, supply the --revision
502
parameter, as in "branch foo/bar -r 5".
504
--basis is to speed up branching from remote branches. When specified, it
505
copies all the file-contents, inventory and revision data from the basis
506
branch before copying anything from the remote branch.
508
takes_args = ['from_location', 'to_location?']
509
takes_options = ['revision', 'basis']
510
aliases = ['get', 'clone']
512
def run(self, from_location, to_location=None, revision=None, basis=None):
513
from bzrlib.clone import copy_branch
515
from shutil import rmtree
518
elif len(revision) > 1:
519
raise BzrCommandError(
520
'bzr branch --revision takes exactly 1 revision value')
522
br_from = Branch.open(from_location)
524
if e.errno == errno.ENOENT:
525
raise BzrCommandError('Source location "%s" does not'
526
' exist.' % to_location)
531
if basis is not None:
532
basis_branch = Branch.open_containing(basis)[0]
535
if len(revision) == 1 and revision[0] is not None:
536
revision_id = revision[0].in_history(br_from)[1]
539
if to_location is None:
540
to_location = os.path.basename(from_location.rstrip("/\\"))
543
name = os.path.basename(to_location) + '\n'
545
os.mkdir(to_location)
547
if e.errno == errno.EEXIST:
548
raise BzrCommandError('Target directory "%s" already'
549
' exists.' % to_location)
550
if e.errno == errno.ENOENT:
551
raise BzrCommandError('Parent of "%s" does not exist.' %
556
copy_branch(br_from, to_location, revision_id, basis_branch)
557
except bzrlib.errors.NoSuchRevision:
559
msg = "The branch %s has no revision %s." % (from_location, revision[0])
560
raise BzrCommandError(msg)
561
except bzrlib.errors.UnlistableBranch:
563
msg = "The branch %s cannot be used as a --basis"
564
raise BzrCommandError(msg)
565
branch = Branch.open(to_location)
567
name = StringIO(name)
568
branch.put_controlfile('branch-name', name)
569
note('Branched %d revision(s).' % branch.revno())
574
class cmd_renames(Command):
575
"""Show list of renamed files.
577
# TODO: Option to show renames between two historical versions.
579
# TODO: Only show renames under dir, rather than in the whole branch.
580
takes_args = ['dir?']
583
def run(self, dir='.'):
584
b = Branch.open_containing(dir)[0]
585
old_inv = b.basis_tree().inventory
586
new_inv = b.working_tree().read_working_inventory()
588
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
590
for old_name, new_name in renames:
591
print "%s => %s" % (old_name, new_name)
594
class cmd_info(Command):
595
"""Show statistical information about a branch."""
596
takes_args = ['branch?']
599
def run(self, branch=None):
601
b = Branch.open_containing(branch)[0]
605
class cmd_remove(Command):
606
"""Make a file unversioned.
608
This makes bzr stop tracking changes to a versioned file. It does
609
not delete the working copy.
611
takes_args = ['file+']
612
takes_options = ['verbose']
615
def run(self, file_list, verbose=False):
616
b, file_list = branch_files(file_list)
617
tree = b.working_tree()
618
tree.remove(file_list, verbose=verbose)
621
class cmd_file_id(Command):
622
"""Print file_id of a particular file or directory.
624
The file_id is assigned when the file is first added and remains the
625
same through all revisions where the file exists, even when it is
629
takes_args = ['filename']
631
def run(self, filename):
632
b, relpath = Branch.open_containing(filename)
633
i = b.working_tree().inventory.path2id(relpath)
635
raise BzrError("%r is not a versioned file" % filename)
640
class cmd_file_path(Command):
641
"""Print path of file_ids to a file or directory.
643
This prints one line for each directory down to the target,
644
starting at the branch root."""
646
takes_args = ['filename']
648
def run(self, filename):
649
b, relpath = Branch.open_containing(filename)
651
fid = inv.path2id(relpath)
653
raise BzrError("%r is not a versioned file" % filename)
654
for fip in inv.get_idpath(fid):
658
class cmd_revision_history(Command):
659
"""Display list of revision ids on this branch."""
663
for patchid in Branch.open_containing('.')[0].revision_history():
667
class cmd_ancestry(Command):
668
"""List all revisions merged into this branch."""
672
b = Branch.open_containing('.')[0]
673
for revision_id in b.get_ancestry(b.last_revision()):
677
class cmd_directories(Command):
678
"""Display list of versioned directories in this branch."""
681
for name, ie in (Branch.open_containing('.')[0].working_tree().
682
read_working_inventory().directories()):
689
class cmd_init(Command):
690
"""Make a directory into a versioned branch.
692
Use this to create an empty branch, or before importing an
695
Recipe for importing a tree of files:
700
bzr commit -m 'imported project'
702
takes_args = ['location?']
703
def run(self, location=None):
704
from bzrlib.branch import Branch
708
# The path has to exist to initialize a
709
# branch inside of it.
710
# Just using os.mkdir, since I don't
711
# believe that we want to create a bunch of
712
# locations if the user supplies an extended path
713
if not os.path.exists(location):
715
Branch.initialize(location)
718
class cmd_diff(Command):
719
"""Show differences in working tree.
721
If files are listed, only the changes in those files are listed.
722
Otherwise, all changes for the tree are listed.
729
# TODO: Allow diff across branches.
730
# TODO: Option to use external diff command; could be GNU diff, wdiff,
731
# or a graphical diff.
733
# TODO: Python difflib is not exactly the same as unidiff; should
734
# either fix it up or prefer to use an external diff.
736
# TODO: If a directory is given, diff everything under that.
738
# TODO: Selected-file diff is inefficient and doesn't show you
741
# TODO: This probably handles non-Unix newlines poorly.
743
takes_args = ['file*']
744
takes_options = ['revision', 'diff-options']
745
aliases = ['di', 'dif']
748
def run(self, revision=None, file_list=None, diff_options=None):
749
from bzrlib.diff import show_diff
751
b, file_list = inner_branch_files(file_list)
753
except FileInWrongBranch:
754
if len(file_list) != 2:
755
raise BzrCommandError("Files are in different branches")
757
b, file1 = Branch.open_containing(file_list[0])
758
b2, file2 = Branch.open_containing(file_list[1])
759
if file1 != "" or file2 != "":
760
raise BzrCommandError("Files are in different branches")
762
if revision is not None:
764
raise BzrCommandError("Can't specify -r with two branches")
765
if len(revision) == 1:
766
return show_diff(b, revision[0], specific_files=file_list,
767
external_diff_options=diff_options)
768
elif len(revision) == 2:
769
return show_diff(b, revision[0], specific_files=file_list,
770
external_diff_options=diff_options,
771
revision2=revision[1])
773
raise BzrCommandError('bzr diff --revision takes exactly one or two revision identifiers')
775
return show_diff(b, None, specific_files=file_list,
776
external_diff_options=diff_options, b2=b2)
779
class cmd_deleted(Command):
780
"""List files deleted in the working tree.
782
# TODO: Show files deleted since a previous revision, or
783
# between two revisions.
784
# TODO: Much more efficient way to do this: read in new
785
# directories with readdir, rather than stating each one. Same
786
# level of effort but possibly much less IO. (Or possibly not,
787
# if the directories are very large...)
789
def run(self, show_ids=False):
790
b = Branch.open_containing('.')[0]
792
new = b.working_tree()
793
for path, ie in old.inventory.iter_entries():
794
if not new.has_id(ie.file_id):
796
print '%-50s %s' % (path, ie.file_id)
801
class cmd_modified(Command):
802
"""List files modified in working tree."""
806
from bzrlib.delta import compare_trees
808
b = Branch.open_containing('.')[0]
809
td = compare_trees(b.basis_tree(), b.working_tree())
811
for path, id, kind, text_modified, meta_modified in td.modified:
816
class cmd_added(Command):
817
"""List files added in working tree."""
821
b = Branch.open_containing('.')[0]
822
wt = b.working_tree()
823
basis_inv = b.basis_tree().inventory
826
if file_id in basis_inv:
828
path = inv.id2path(file_id)
829
if not os.access(b.abspath(path), os.F_OK):
835
class cmd_root(Command):
836
"""Show the tree root directory.
838
The root is the nearest enclosing directory with a .bzr control
840
takes_args = ['filename?']
842
def run(self, filename=None):
843
"""Print the branch root."""
844
b = Branch.open_containing(filename)[0]
848
class cmd_log(Command):
849
"""Show log of this branch.
851
To request a range of logs, you can use the command -r begin..end
852
-r revision requests a specific revision, -r ..end or -r begin.. are
856
# TODO: Make --revision support uuid: and hash: [future tag:] notation.
858
takes_args = ['filename?']
859
takes_options = [Option('forward',
860
help='show from oldest to newest'),
861
'timezone', 'verbose',
862
'show-ids', 'revision',
863
Option('line', help='format with one line per revision'),
866
help='show revisions whose message matches this regexp',
868
Option('short', help='use moderately short format'),
871
def run(self, filename=None, timezone='original',
880
from bzrlib.log import log_formatter, show_log
882
assert message is None or isinstance(message, basestring), \
883
"invalid message argument %r" % message
884
direction = (forward and 'forward') or 'reverse'
887
b, fp = Branch.open_containing(filename)
890
inv = b.working_tree().read_working_inventory()
891
except NoWorkingTree:
892
inv = b.get_inventory(b.last_revision())
893
file_id = inv.path2id(fp)
895
file_id = None # points to branch root
897
b, relpath = Branch.open_containing('.')
903
elif len(revision) == 1:
904
rev1 = rev2 = revision[0].in_history(b).revno
905
elif len(revision) == 2:
906
rev1 = revision[0].in_history(b).revno
907
rev2 = revision[1].in_history(b).revno
909
raise BzrCommandError('bzr log --revision takes one or two values.')
911
# By this point, the revision numbers are converted to the +ve
912
# form if they were supplied in the -ve form, so we can do
913
# this comparison in relative safety
915
(rev2, rev1) = (rev1, rev2)
917
mutter('encoding log as %r', bzrlib.user_encoding)
919
# use 'replace' so that we don't abort if trying to write out
920
# in e.g. the default C locale.
921
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
928
lf = log_formatter(log_format,
931
show_timezone=timezone)
944
class cmd_touching_revisions(Command):
945
"""Return revision-ids which affected a particular file.
947
A more user-friendly interface is "bzr log FILE"."""
949
takes_args = ["filename"]
951
def run(self, filename):
952
b, relpath = Branch.open_containing(filename)[0]
953
inv = b.working_tree().read_working_inventory()
954
file_id = inv.path2id(relpath)
955
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
956
print "%6d %s" % (revno, what)
959
class cmd_ls(Command):
960
"""List files in a tree.
962
# TODO: Take a revision or remote path and list that tree instead.
964
takes_options = ['verbose', 'revision',
965
Option('non-recursive',
966
help='don\'t recurse into sub-directories'),
968
help='Print all paths from the root of the branch.'),
969
Option('unknown', help='Print unknown files'),
970
Option('versioned', help='Print versioned files'),
971
Option('ignored', help='Print ignored files'),
973
Option('null', help='Null separate the files'),
976
def run(self, revision=None, verbose=False,
977
non_recursive=False, from_root=False,
978
unknown=False, versioned=False, ignored=False,
982
raise BzrCommandError('Cannot set both --verbose and --null')
983
all = not (unknown or versioned or ignored)
985
selection = {'I':ignored, '?':unknown, 'V':versioned}
987
b, relpath = Branch.open_containing('.')
993
tree = b.working_tree()
995
tree = b.revision_tree(revision[0].in_history(b).rev_id)
996
for fp, fc, kind, fid, entry in tree.list_files():
997
if fp.startswith(relpath):
998
fp = fp[len(relpath):]
999
if non_recursive and '/' in fp:
1001
if not all and not selection[fc]:
1004
kindch = entry.kind_character()
1005
print '%-8s %s%s' % (fc, fp, kindch)
1007
sys.stdout.write(fp)
1008
sys.stdout.write('\0')
1015
class cmd_unknowns(Command):
1016
"""List unknown files."""
1019
from bzrlib.osutils import quotefn
1020
for f in Branch.open_containing('.')[0].unknowns():
1025
class cmd_ignore(Command):
1026
"""Ignore a command or pattern.
1028
To remove patterns from the ignore list, edit the .bzrignore file.
1030
If the pattern contains a slash, it is compared to the whole path
1031
from the branch root. Otherwise, it is compared to only the last
1032
component of the path. To match a file only in the root directory,
1035
Ignore patterns are case-insensitive on case-insensitive systems.
1037
Note: wildcards must be quoted from the shell on Unix.
1040
bzr ignore ./Makefile
1041
bzr ignore '*.class'
1043
# TODO: Complain if the filename is absolute
1044
takes_args = ['name_pattern']
1046
def run(self, name_pattern):
1047
from bzrlib.atomicfile import AtomicFile
1050
b, relpath = Branch.open_containing('.')
1051
ifn = b.abspath('.bzrignore')
1053
if os.path.exists(ifn):
1056
igns = f.read().decode('utf-8')
1062
# TODO: If the file already uses crlf-style termination, maybe
1063
# we should use that for the newly added lines?
1065
if igns and igns[-1] != '\n':
1067
igns += name_pattern + '\n'
1070
f = AtomicFile(ifn, 'wt')
1071
f.write(igns.encode('utf-8'))
1076
inv = b.working_tree().inventory
1077
if inv.path2id('.bzrignore'):
1078
mutter('.bzrignore is already versioned')
1080
mutter('need to make new .bzrignore file versioned')
1081
b.add(['.bzrignore'])
1085
class cmd_ignored(Command):
1086
"""List ignored files and the patterns that matched them.
1088
See also: bzr ignore"""
1091
tree = Branch.open_containing('.')[0].working_tree()
1092
for path, file_class, kind, file_id, entry in tree.list_files():
1093
if file_class != 'I':
1095
## XXX: Slightly inefficient since this was already calculated
1096
pat = tree.is_ignored(path)
1097
print '%-50s %s' % (path, pat)
1100
class cmd_lookup_revision(Command):
1101
"""Lookup the revision-id from a revision-number
1104
bzr lookup-revision 33
1107
takes_args = ['revno']
1110
def run(self, revno):
1114
raise BzrCommandError("not a valid revision-number: %r" % revno)
1116
print Branch.open_containing('.')[0].get_rev_id(revno)
1119
class cmd_export(Command):
1120
"""Export past revision to destination directory.
1122
If no revision is specified this exports the last committed revision.
1124
Format may be an "exporter" name, such as tar, tgz, tbz2. If none is
1125
given, try to find the format with the extension. If no extension
1126
is found exports to a directory (equivalent to --format=dir).
1128
Root may be the top directory for tar, tgz and tbz2 formats. If none
1129
is given, the top directory will be the root name of the file."""
1130
# TODO: list known exporters
1131
takes_args = ['dest']
1132
takes_options = ['revision', 'format', 'root']
1133
def run(self, dest, revision=None, format=None, root=None):
1135
b = Branch.open_containing('.')[0]
1136
if revision is None:
1137
rev_id = b.last_revision()
1139
if len(revision) != 1:
1140
raise BzrError('bzr export --revision takes exactly 1 argument')
1141
rev_id = revision[0].in_history(b).rev_id
1142
t = b.revision_tree(rev_id)
1143
arg_root, ext = os.path.splitext(os.path.basename(dest))
1144
if ext in ('.gz', '.bz2'):
1145
new_root, new_ext = os.path.splitext(arg_root)
1146
if new_ext == '.tar':
1152
if ext in (".tar",):
1154
elif ext in (".tar.gz", ".tgz"):
1156
elif ext in (".tar.bz2", ".tbz2"):
1160
t.export(dest, format, root)
1163
class cmd_cat(Command):
1164
"""Write a file's text from a previous revision."""
1166
takes_options = ['revision']
1167
takes_args = ['filename']
1170
def run(self, filename, revision=None):
1171
if revision is None:
1172
raise BzrCommandError("bzr cat requires a revision number")
1173
elif len(revision) != 1:
1174
raise BzrCommandError("bzr cat --revision takes exactly one number")
1175
b, relpath = Branch.open_containing(filename)
1176
b.print_file(relpath, revision[0].in_history(b).revno)
1179
class cmd_local_time_offset(Command):
1180
"""Show the offset in seconds from GMT to local time."""
1184
print bzrlib.osutils.local_time_offset()
1188
class cmd_commit(Command):
1189
"""Commit changes into a new revision.
1191
If no arguments are given, the entire tree is committed.
1193
If selected files are specified, only changes to those files are
1194
committed. If a directory is specified then the directory and everything
1195
within it is committed.
1197
A selected-file commit may fail in some cases where the committed
1198
tree would be invalid, such as trying to commit a file in a
1199
newly-added directory that is not itself committed.
1201
# TODO: Run hooks on tree to-be-committed, and after commit.
1203
# TODO: Strict commit that fails if there are deleted files.
1204
# (what does "deleted files" mean ??)
1206
# TODO: Give better message for -s, --summary, used by tla people
1208
# XXX: verbose currently does nothing
1210
takes_args = ['selected*']
1211
takes_options = ['message', 'verbose',
1213
help='commit even if nothing has changed'),
1214
Option('file', type=str,
1216
help='file containing commit message'),
1218
help="refuse to commit if there are unknown "
1219
"files in the working tree."),
1221
aliases = ['ci', 'checkin']
1223
def run(self, message=None, file=None, verbose=True, selected_list=None,
1224
unchanged=False, strict=False):
1225
from bzrlib.errors import (PointlessCommit, ConflictsInTree,
1227
from bzrlib.msgeditor import edit_commit_message
1228
from bzrlib.status import show_status
1229
from cStringIO import StringIO
1231
b, selected_list = branch_files(selected_list)
1232
if message is None and not file:
1233
catcher = StringIO()
1234
show_status(b, specific_files=selected_list,
1236
message = edit_commit_message(catcher.getvalue())
1239
raise BzrCommandError("please specify a commit message"
1240
" with either --message or --file")
1241
elif message and file:
1242
raise BzrCommandError("please specify either --message or --file")
1246
message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1249
raise BzrCommandError("empty commit message specified")
1252
b.working_tree().commit(message, specific_files=selected_list,
1253
allow_pointless=unchanged, strict=strict)
1254
except PointlessCommit:
1255
# FIXME: This should really happen before the file is read in;
1256
# perhaps prepare the commit; get the message; then actually commit
1257
raise BzrCommandError("no changes to commit",
1258
["use --unchanged to commit anyhow"])
1259
except ConflictsInTree:
1260
raise BzrCommandError("Conflicts detected in working tree. "
1261
'Use "bzr conflicts" to list, "bzr resolve FILE" to resolve.')
1262
except StrictCommitFailed:
1263
raise BzrCommandError("Commit refused because there are unknown "
1264
"files in the working tree.")
1265
note('Committed revision %d.' % (b.revno(),))
1268
class cmd_check(Command):
1269
"""Validate consistency of branch history.
1271
This command checks various invariants about the branch storage to
1272
detect data corruption or bzr bugs.
1274
takes_args = ['dir?']
1275
takes_options = ['verbose']
1277
def run(self, dir='.', verbose=False):
1278
from bzrlib.check import check
1279
check(Branch.open_containing(dir)[0], verbose)
1282
class cmd_scan_cache(Command):
1285
from bzrlib.hashcache import HashCache
1291
print '%6d stats' % c.stat_count
1292
print '%6d in hashcache' % len(c._cache)
1293
print '%6d files removed from cache' % c.removed_count
1294
print '%6d hashes updated' % c.update_count
1295
print '%6d files changed too recently to cache' % c.danger_count
1302
class cmd_upgrade(Command):
1303
"""Upgrade branch storage to current format.
1305
The check command or bzr developers may sometimes advise you to run
1308
This version of this command upgrades from the full-text storage
1309
used by bzr 0.0.8 and earlier to the weave format (v5).
1311
takes_args = ['dir?']
1313
def run(self, dir='.'):
1314
from bzrlib.upgrade import upgrade
1318
class cmd_whoami(Command):
1319
"""Show bzr user id."""
1320
takes_options = ['email']
1323
def run(self, email=False):
1325
b = bzrlib.branch.Branch.open_containing('.')[0]
1326
config = bzrlib.config.BranchConfig(b)
1327
except NotBranchError:
1328
config = bzrlib.config.GlobalConfig()
1331
print config.user_email()
1333
print config.username()
1335
class cmd_nick(Command):
1337
Print or set the branch nickname.
1338
If unset, the tree root directory name is used as the nickname
1339
To print the current nickname, execute with no argument.
1341
takes_args = ['nickname?']
1342
def run(self, nickname=None):
1343
branch = Branch.open_containing('.')[0]
1344
if nickname is None:
1345
self.printme(branch)
1347
branch.nick = nickname
1350
def printme(self, branch):
1353
class cmd_selftest(Command):
1354
"""Run internal test suite.
1356
This creates temporary test directories in the working directory,
1357
but not existing data is affected. These directories are deleted
1358
if the tests pass, or left behind to help in debugging if they
1359
fail and --keep-output is specified.
1361
If arguments are given, they are regular expressions that say
1362
which tests should run.
1364
# TODO: --list should give a list of all available tests
1366
takes_args = ['testspecs*']
1367
takes_options = ['verbose',
1368
Option('one', help='stop when one test fails'),
1369
Option('keep-output',
1370
help='keep output directories when tests fail')
1373
def run(self, testspecs_list=None, verbose=False, one=False,
1376
from bzrlib.selftest import selftest
1377
# we don't want progress meters from the tests to go to the
1378
# real output; and we don't want log messages cluttering up
1380
save_ui = bzrlib.ui.ui_factory
1381
bzrlib.trace.info('running tests...')
1383
bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1384
if testspecs_list is not None:
1385
pattern = '|'.join(testspecs_list)
1388
result = selftest(verbose=verbose,
1390
stop_on_failure=one,
1391
keep_output=keep_output)
1393
bzrlib.trace.info('tests passed')
1395
bzrlib.trace.info('tests failed')
1396
return int(not result)
1398
bzrlib.ui.ui_factory = save_ui
1402
print "bzr (bazaar-ng) %s" % bzrlib.__version__
1403
# is bzrlib itself in a branch?
1404
bzrrev = bzrlib.get_bzr_revision()
1406
print " (bzr checkout, revision %d {%s})" % bzrrev
1407
print bzrlib.__copyright__
1408
print "http://bazaar-ng.org/"
1410
print "bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and"
1411
print "you may use, modify and redistribute it under the terms of the GNU"
1412
print "General Public License version 2 or later."
1415
class cmd_version(Command):
1416
"""Show version of bzr."""
1421
class cmd_rocks(Command):
1422
"""Statement of optimism."""
1426
print "it sure does!"
1429
class cmd_find_merge_base(Command):
1430
"""Find and print a base revision for merging two branches.
1432
# TODO: Options to specify revisions on either side, as if
1433
# merging only part of the history.
1434
takes_args = ['branch', 'other']
1438
def run(self, branch, other):
1439
from bzrlib.revision import common_ancestor, MultipleRevisionSources
1441
branch1 = Branch.open_containing(branch)[0]
1442
branch2 = Branch.open_containing(other)[0]
1444
history_1 = branch1.revision_history()
1445
history_2 = branch2.revision_history()
1447
last1 = branch1.last_revision()
1448
last2 = branch2.last_revision()
1450
source = MultipleRevisionSources(branch1, branch2)
1452
base_rev_id = common_ancestor(last1, last2, source)
1454
print 'merge base is revision %s' % base_rev_id
1458
if base_revno is None:
1459
raise bzrlib.errors.UnrelatedBranches()
1461
print ' r%-6d in %s' % (base_revno, branch)
1463
other_revno = branch2.revision_id_to_revno(base_revid)
1465
print ' r%-6d in %s' % (other_revno, other)
1469
class cmd_merge(Command):
1470
"""Perform a three-way merge.
1472
The branch is the branch you will merge from. By default, it will
1473
merge the latest revision. If you specify a revision, that
1474
revision will be merged. If you specify two revisions, the first
1475
will be used as a BASE, and the second one as OTHER. Revision
1476
numbers are always relative to the specified branch.
1478
By default bzr will try to merge in all new work from the other
1479
branch, automatically determining an appropriate base. If this
1480
fails, you may need to give an explicit base.
1484
To merge the latest revision from bzr.dev
1485
bzr merge ../bzr.dev
1487
To merge changes up to and including revision 82 from bzr.dev
1488
bzr merge -r 82 ../bzr.dev
1490
To merge the changes introduced by 82, without previous changes:
1491
bzr merge -r 81..82 ../bzr.dev
1493
merge refuses to run if there are any uncommitted changes, unless
1496
takes_args = ['branch?']
1497
takes_options = ['revision', 'force', 'merge-type', 'reprocess',
1498
Option('show-base', help="Show base revision text in "
1501
def run(self, branch=None, revision=None, force=False, merge_type=None,
1502
show_base=False, reprocess=False):
1503
from bzrlib.merge import merge
1504
from bzrlib.merge_core import ApplyMerge3
1505
if merge_type is None:
1506
merge_type = ApplyMerge3
1508
branch = Branch.open_containing('.')[0].get_parent()
1510
raise BzrCommandError("No merge location known or specified.")
1512
print "Using saved location: %s" % branch
1513
if revision is None or len(revision) < 1:
1515
other = [branch, -1]
1517
if len(revision) == 1:
1519
other_branch = Branch.open_containing(branch)[0]
1520
revno = revision[0].in_history(other_branch).revno
1521
other = [branch, revno]
1523
assert len(revision) == 2
1524
if None in revision:
1525
raise BzrCommandError(
1526
"Merge doesn't permit that revision specifier.")
1527
b = Branch.open_containing(branch)[0]
1529
base = [branch, revision[0].in_history(b).revno]
1530
other = [branch, revision[1].in_history(b).revno]
1533
conflict_count = merge(other, base, check_clean=(not force),
1534
merge_type=merge_type, reprocess=reprocess,
1535
show_base=show_base)
1536
if conflict_count != 0:
1540
except bzrlib.errors.AmbiguousBase, e:
1541
m = ("sorry, bzr can't determine the right merge base yet\n"
1542
"candidates are:\n "
1543
+ "\n ".join(e.bases)
1545
"please specify an explicit base with -r,\n"
1546
"and (if you want) report this to the bzr developers\n")
1550
class cmd_remerge(Command):
1553
takes_args = ['file*']
1554
takes_options = ['merge-type', 'reprocess',
1555
Option('show-base', help="Show base revision text in "
1558
def run(self, file_list=None, merge_type=None, show_base=False,
1560
from bzrlib.merge import merge_inner, transform_tree
1561
from bzrlib.merge_core import ApplyMerge3
1562
if merge_type is None:
1563
merge_type = ApplyMerge3
1564
b, file_list = branch_files(file_list)
1567
pending_merges = b.working_tree().pending_merges()
1568
if len(pending_merges) != 1:
1569
raise BzrCommandError("Sorry, remerge only works after normal"
1570
+ " merges. Not cherrypicking or"
1572
this_tree = b.working_tree()
1573
base_revision = common_ancestor(b.last_revision(),
1574
pending_merges[0], b)
1575
base_tree = b.revision_tree(base_revision)
1576
other_tree = b.revision_tree(pending_merges[0])
1577
interesting_ids = None
1578
if file_list is not None:
1579
interesting_ids = set()
1580
for filename in file_list:
1581
file_id = this_tree.path2id(filename)
1582
interesting_ids.add(file_id)
1583
if this_tree.kind(file_id) != "directory":
1586
for name, ie in this_tree.inventory.iter_entries(file_id):
1587
interesting_ids.add(ie.file_id)
1588
transform_tree(this_tree, b.basis_tree(), interesting_ids)
1589
if file_list is None:
1590
restore_files = list(this_tree.iter_conflicts())
1592
restore_files = file_list
1593
for filename in restore_files:
1595
restore(this_tree.abspath(filename))
1596
except NotConflicted:
1598
conflicts = merge_inner(b, other_tree, base_tree,
1599
interesting_ids = interesting_ids,
1600
other_rev_id=pending_merges[0],
1601
merge_type=merge_type,
1602
show_base=show_base,
1603
reprocess=reprocess)
1611
class cmd_revert(Command):
1612
"""Reverse all changes since the last commit.
1614
Only versioned files are affected. Specify filenames to revert only
1615
those files. By default, any files that are changed will be backed up
1616
first. Backup files have a '~' appended to their name.
1618
takes_options = ['revision', 'no-backup']
1619
takes_args = ['file*']
1620
aliases = ['merge-revert']
1622
def run(self, revision=None, no_backup=False, file_list=None):
1623
from bzrlib.merge import merge_inner
1624
from bzrlib.commands import parse_spec
1625
if file_list is not None:
1626
if len(file_list) == 0:
1627
raise BzrCommandError("No files specified")
1630
if revision is None:
1632
b = Branch.open_containing('.')[0]
1633
rev_id = b.last_revision()
1634
elif len(revision) != 1:
1635
raise BzrCommandError('bzr revert --revision takes exactly 1 argument')
1637
b, file_list = branch_files(file_list)
1638
rev_id = revision[0].in_history(b).rev_id
1639
b.working_tree().revert(file_list, b.revision_tree(rev_id),
1643
class cmd_assert_fail(Command):
1644
"""Test reporting of assertion failures"""
1647
assert False, "always fails"
1650
class cmd_help(Command):
1651
"""Show help on a command or other topic.
1653
For a list of all available commands, say 'bzr help commands'."""
1654
takes_options = ['long']
1655
takes_args = ['topic?']
1659
def run(self, topic=None, long=False):
1661
if topic is None and long:
1666
class cmd_shell_complete(Command):
1667
"""Show appropriate completions for context.
1669
For a list of all available commands, say 'bzr shell-complete'."""
1670
takes_args = ['context?']
1675
def run(self, context=None):
1676
import shellcomplete
1677
shellcomplete.shellcomplete(context)
1680
class cmd_fetch(Command):
1681
"""Copy in history from another branch but don't merge it.
1683
This is an internal method used for pull and merge."""
1685
takes_args = ['from_branch', 'to_branch']
1686
def run(self, from_branch, to_branch):
1687
from bzrlib.fetch import Fetcher
1688
from bzrlib.branch import Branch
1689
from_b = Branch.open(from_branch)
1690
to_b = Branch.open(to_branch)
1695
Fetcher(to_b, from_b)
1702
class cmd_missing(Command):
1703
"""What is missing in this branch relative to other branch.
1705
# TODO: rewrite this in terms of ancestry so that it shows only
1708
takes_args = ['remote?']
1709
aliases = ['mis', 'miss']
1710
# We don't have to add quiet to the list, because
1711
# unknown options are parsed as booleans
1712
takes_options = ['verbose', 'quiet']
1715
def run(self, remote=None, verbose=False, quiet=False):
1716
from bzrlib.errors import BzrCommandError
1717
from bzrlib.missing import show_missing
1719
if verbose and quiet:
1720
raise BzrCommandError('Cannot pass both quiet and verbose')
1722
b = Branch.open_containing('.')[0]
1723
parent = b.get_parent()
1726
raise BzrCommandError("No missing location known or specified.")
1729
print "Using last location: %s" % parent
1731
elif parent is None:
1732
# We only update parent if it did not exist, missing
1733
# should not change the parent
1734
b.set_parent(remote)
1735
br_remote = Branch.open_containing(remote)[0]
1736
return show_missing(b, br_remote, verbose=verbose, quiet=quiet)
1739
class cmd_plugins(Command):
1744
import bzrlib.plugin
1745
from inspect import getdoc
1746
for plugin in bzrlib.plugin.all_plugins:
1747
if hasattr(plugin, '__path__'):
1748
print plugin.__path__[0]
1749
elif hasattr(plugin, '__file__'):
1750
print plugin.__file__
1756
print '\t', d.split('\n')[0]
1759
class cmd_testament(Command):
1760
"""Show testament (signing-form) of a revision."""
1761
takes_options = ['revision', 'long']
1762
takes_args = ['branch?']
1764
def run(self, branch='.', revision=None, long=False):
1765
from bzrlib.testament import Testament
1766
b = Branch.open_containing(branch)[0]
1769
if revision is None:
1770
rev_id = b.last_revision()
1772
rev_id = revision[0].in_history(b).rev_id
1773
t = Testament.from_revision(b, rev_id)
1775
sys.stdout.writelines(t.as_text_lines())
1777
sys.stdout.write(t.as_short_text())
1782
class cmd_annotate(Command):
1783
"""Show the origin of each line in a file.
1785
This prints out the given file with an annotation on the left side
1786
indicating which revision, author and date introduced the change.
1788
If the origin is the same for a run of consecutive lines, it is
1789
shown only at the top, unless the --all option is given.
1791
# TODO: annotate directories; showing when each file was last changed
1792
# TODO: annotate a previous version of a file
1793
# TODO: if the working copy is modified, show annotations on that
1794
# with new uncommitted lines marked
1795
aliases = ['blame', 'praise']
1796
takes_args = ['filename']
1797
takes_options = [Option('all', help='show annotations on all lines'),
1798
Option('long', help='show date in annotations'),
1802
def run(self, filename, all=False, long=False):
1803
from bzrlib.annotate import annotate_file
1804
b, relpath = Branch.open_containing(filename)
1807
tree = WorkingTree(b.base, b)
1808
tree = b.revision_tree(b.last_revision())
1809
file_id = tree.inventory.path2id(relpath)
1810
file_version = tree.inventory[file_id].revision
1811
annotate_file(b, file_version, file_id, long, all, sys.stdout)
1816
class cmd_re_sign(Command):
1817
"""Create a digital signature for an existing revision."""
1818
# TODO be able to replace existing ones.
1820
hidden = True # is this right ?
1821
takes_args = ['revision_id?']
1822
takes_options = ['revision']
1824
def run(self, revision_id=None, revision=None):
1825
import bzrlib.config as config
1826
import bzrlib.gpg as gpg
1827
if revision_id is not None and revision is not None:
1828
raise BzrCommandError('You can only supply one of revision_id or --revision')
1829
if revision_id is None and revision is None:
1830
raise BzrCommandError('You must supply either --revision or a revision_id')
1831
b = Branch.open_containing('.')[0]
1832
gpg_strategy = gpg.GPGStrategy(config.BranchConfig(b))
1833
if revision_id is not None:
1834
b.sign_revision(revision_id, gpg_strategy)
1835
elif revision is not None:
1836
if len(revision) == 1:
1837
revno, rev_id = revision[0].in_history(b)
1838
b.sign_revision(rev_id, gpg_strategy)
1839
elif len(revision) == 2:
1840
# are they both on rh- if so we can walk between them
1841
# might be nice to have a range helper for arbitrary
1842
# revision paths. hmm.
1843
from_revno, from_revid = revision[0].in_history(b)
1844
to_revno, to_revid = revision[1].in_history(b)
1845
if to_revid is None:
1846
to_revno = b.revno()
1847
if from_revno is None or to_revno is None:
1848
raise BzrCommandError('Cannot sign a range of non-revision-history revisions')
1849
for revno in range(from_revno, to_revno + 1):
1850
b.sign_revision(b.get_rev_id(revno), gpg_strategy)
1852
raise BzrCommandError('Please supply either one revision, or a range.')
1855
# these get imported and then picked up by the scan for cmd_*
1856
# TODO: Some more consistent way to split command definitions across files;
1857
# we do need to load at least some information about them to know of
1859
from bzrlib.conflicts import cmd_resolve, cmd_conflicts, restore