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
import bzrlib.errors as errors
31
from bzrlib.errors import (BzrError, BzrCheckError, BzrCommandError,
32
NotBranchError, DivergedBranches, NotConflicted,
33
NoSuchFile, NoWorkingTree, FileInWrongBranch)
34
from bzrlib.option import Option
35
from bzrlib.revisionspec import RevisionSpec
37
from bzrlib.trace import mutter, note, log_error, warning
38
from bzrlib.workingtree import WorkingTree
41
def branch_files(file_list, default_branch='.'):
43
return inner_branch_files(file_list, default_branch)
44
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.
265
It is possible to limit the output to a particular entry
266
type using the --kind option. For example; --kind file.
268
takes_options = ['revision', 'show-ids', 'kind']
271
def run(self, revision=None, show_ids=False, kind=None):
272
if kind and kind not in ['file', 'directory', 'symlink']:
273
raise BzrCommandError('invalid kind specified')
274
b = Branch.open_containing('.')[0]
276
inv = b.working_tree().read_working_inventory()
278
if len(revision) > 1:
279
raise BzrCommandError('bzr inventory --revision takes'
280
' exactly one revision identifier')
281
inv = b.get_revision_inventory(revision[0].in_history(b).rev_id)
283
for path, entry in inv.entries():
284
if kind and kind != entry.kind:
287
print '%-50s %s' % (path, entry.file_id)
292
class cmd_move(Command):
293
"""Move files to a different directory.
298
The destination must be a versioned directory in the same branch.
300
takes_args = ['source$', 'dest']
301
def run(self, source_list, dest):
302
b, source_list = branch_files(source_list)
304
# TODO: glob expansion on windows?
305
tree = WorkingTree(b.base, b)
306
b.move(source_list, tree.relpath(dest))
309
class cmd_rename(Command):
310
"""Change the name of an entry.
313
bzr rename frob.c frobber.c
314
bzr rename src/frob.c lib/frob.c
316
It is an error if the destination name exists.
318
See also the 'move' command, which moves files into a different
319
directory without changing their name.
321
# TODO: Some way to rename multiple files without invoking
322
# bzr for each one?"""
323
takes_args = ['from_name', 'to_name']
325
def run(self, from_name, to_name):
326
b, (from_name, to_name) = branch_files((from_name, to_name))
327
b.rename_one(from_name, to_name)
330
class cmd_mv(Command):
331
"""Move or rename a file.
334
bzr mv OLDNAME NEWNAME
335
bzr mv SOURCE... DESTINATION
337
If the last argument is a versioned directory, all the other names
338
are moved into it. Otherwise, there must be exactly two arguments
339
and the file is changed to a new name, which must not already exist.
341
Files cannot be moved between branches.
343
takes_args = ['names*']
344
def run(self, names_list):
345
if len(names_list) < 2:
346
raise BzrCommandError("missing file argument")
347
b, rel_names = branch_files(names_list)
349
if os.path.isdir(names_list[-1]):
350
# move into existing directory
351
for pair in b.move(rel_names[:-1], rel_names[-1]):
352
print "%s => %s" % pair
354
if len(names_list) != 2:
355
raise BzrCommandError('to mv multiple files the destination '
356
'must be a versioned directory')
357
b.rename_one(rel_names[0], rel_names[1])
358
print "%s => %s" % (rel_names[0], rel_names[1])
361
class cmd_pull(Command):
362
"""Pull any changes from another branch into the current one.
364
If there is no default location set, the first pull will set it. After
365
that, you can omit the location to use the default. To change the
366
default, use --remember.
368
This command only works on branches that have not diverged. Branches are
369
considered diverged if both branches have had commits without first
370
pulling from the other.
372
If branches have diverged, you can use 'bzr merge' to pull the text changes
373
from one into the other. Once one branch has merged, the other should
374
be able to pull it again.
376
If you want to forget your local changes and just update your branch to
377
match the remote one, use --overwrite.
379
takes_options = ['remember', 'overwrite', 'verbose']
380
takes_args = ['location?']
382
def run(self, location=None, remember=False, overwrite=False, verbose=False):
383
from bzrlib.merge import merge
384
from shutil import rmtree
387
br_to = Branch.open_containing('.')[0]
388
stored_loc = br_to.get_parent()
390
if stored_loc is None:
391
raise BzrCommandError("No pull location known or specified.")
393
print "Using saved location: %s" % stored_loc
394
location = stored_loc
395
br_from = Branch.open(location)
397
old_rh = br_to.revision_history()
398
br_to.working_tree().pull(br_from, overwrite)
399
except DivergedBranches:
400
raise BzrCommandError("These branches have diverged."
402
if br_to.get_parent() is None or remember:
403
br_to.set_parent(location)
406
new_rh = br_to.revision_history()
409
from bzrlib.log import show_changed_revisions
410
show_changed_revisions(br_to, old_rh, new_rh)
413
class cmd_push(Command):
414
"""Push this branch into another branch.
416
The remote branch will not have its working tree populated because this
417
is both expensive, and may not be supported on the remote file system.
419
Some smart servers or protocols *may* put the working tree in place.
421
If there is no default push location set, the first push will set it.
422
After that, you can omit the location to use the default. To change the
423
default, use --remember.
425
This command only works on branches that have not diverged. Branches are
426
considered diverged if the branch being pushed to is not an older version
429
If branches have diverged, you can use 'bzr push --overwrite' to replace
430
the other branch completely.
432
If you want to ensure you have the different changes in the other branch,
433
do a merge (see bzr help merge) from the other branch, and commit that
434
before doing a 'push --overwrite'.
436
takes_options = ['remember', 'overwrite',
437
Option('create-prefix',
438
help='Create the path leading up to the branch '
439
'if it does not already exist')]
440
takes_args = ['location?']
442
def run(self, location=None, remember=False, overwrite=False,
443
create_prefix=False, verbose=False):
445
from shutil import rmtree
446
from bzrlib.transport import get_transport
448
br_from = Branch.open_containing('.')[0]
449
stored_loc = br_from.get_push_location()
451
if stored_loc is None:
452
raise BzrCommandError("No push location known or specified.")
454
print "Using saved location: %s" % stored_loc
455
location = stored_loc
457
br_to = Branch.open(location)
458
except NotBranchError:
460
transport = get_transport(location).clone('..')
461
if not create_prefix:
463
transport.mkdir(transport.relpath(location))
465
raise BzrCommandError("Parent directory of %s "
466
"does not exist." % location)
468
current = transport.base
469
needed = [(transport, transport.relpath(location))]
472
transport, relpath = needed[-1]
473
transport.mkdir(relpath)
476
new_transport = transport.clone('..')
477
needed.append((new_transport,
478
new_transport.relpath(transport.base)))
479
if new_transport.base == transport.base:
480
raise BzrCommandError("Could not creeate "
484
br_to = Branch.initialize(location)
486
old_rh = br_to.revision_history()
487
br_to.pull(br_from, overwrite)
488
except DivergedBranches:
489
raise BzrCommandError("These branches have diverged."
490
" Try a merge then push with overwrite.")
491
if br_from.get_push_location() is None or remember:
492
br_from.set_push_location(location)
495
new_rh = br_to.revision_history()
498
from bzrlib.log import show_changed_revisions
499
show_changed_revisions(br_to, old_rh, new_rh)
501
class cmd_branch(Command):
502
"""Create a new copy of a branch.
504
If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
505
be used. In other words, "branch ../foo/bar" will attempt to create ./bar.
507
To retrieve the branch as of a particular revision, supply the --revision
508
parameter, as in "branch foo/bar -r 5".
510
--basis is to speed up branching from remote branches. When specified, it
511
copies all the file-contents, inventory and revision data from the basis
512
branch before copying anything from the remote branch.
514
takes_args = ['from_location', 'to_location?']
515
takes_options = ['revision', 'basis']
516
aliases = ['get', 'clone']
518
def run(self, from_location, to_location=None, revision=None, basis=None):
519
from bzrlib.clone import copy_branch
521
from shutil import rmtree
524
elif len(revision) > 1:
525
raise BzrCommandError(
526
'bzr branch --revision takes exactly 1 revision value')
528
br_from = Branch.open(from_location)
530
if e.errno == errno.ENOENT:
531
raise BzrCommandError('Source location "%s" does not'
532
' exist.' % to_location)
537
if basis is not None:
538
basis_branch = Branch.open_containing(basis)[0]
541
if len(revision) == 1 and revision[0] is not None:
542
revision_id = revision[0].in_history(br_from)[1]
545
if to_location is None:
546
to_location = os.path.basename(from_location.rstrip("/\\"))
549
name = os.path.basename(to_location) + '\n'
551
os.mkdir(to_location)
553
if e.errno == errno.EEXIST:
554
raise BzrCommandError('Target directory "%s" already'
555
' exists.' % to_location)
556
if e.errno == errno.ENOENT:
557
raise BzrCommandError('Parent of "%s" does not exist.' %
562
copy_branch(br_from, to_location, revision_id, basis_branch)
563
except bzrlib.errors.NoSuchRevision:
565
msg = "The branch %s has no revision %s." % (from_location, revision[0])
566
raise BzrCommandError(msg)
567
except bzrlib.errors.UnlistableBranch:
569
msg = "The branch %s cannot be used as a --basis"
570
raise BzrCommandError(msg)
572
branch = Branch.open(to_location)
573
name = StringIO(name)
574
branch.put_controlfile('branch-name', name)
579
class cmd_renames(Command):
580
"""Show list of renamed files.
582
# TODO: Option to show renames between two historical versions.
584
# TODO: Only show renames under dir, rather than in the whole branch.
585
takes_args = ['dir?']
588
def run(self, dir='.'):
589
b = Branch.open_containing(dir)[0]
590
old_inv = b.basis_tree().inventory
591
new_inv = b.working_tree().read_working_inventory()
593
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
595
for old_name, new_name in renames:
596
print "%s => %s" % (old_name, new_name)
599
class cmd_info(Command):
600
"""Show statistical information about a branch."""
601
takes_args = ['branch?']
604
def run(self, branch=None):
606
b = Branch.open_containing(branch)[0]
610
class cmd_remove(Command):
611
"""Make a file unversioned.
613
This makes bzr stop tracking changes to a versioned file. It does
614
not delete the working copy.
616
takes_args = ['file+']
617
takes_options = ['verbose']
620
def run(self, file_list, verbose=False):
621
b, file_list = branch_files(file_list)
622
tree = b.working_tree()
623
tree.remove(file_list, verbose=verbose)
626
class cmd_file_id(Command):
627
"""Print file_id of a particular file or directory.
629
The file_id is assigned when the file is first added and remains the
630
same through all revisions where the file exists, even when it is
634
takes_args = ['filename']
636
def run(self, filename):
637
b, relpath = Branch.open_containing(filename)
638
i = b.working_tree().inventory.path2id(relpath)
640
raise BzrError("%r is not a versioned file" % filename)
645
class cmd_file_path(Command):
646
"""Print path of file_ids to a file or directory.
648
This prints one line for each directory down to the target,
649
starting at the branch root."""
651
takes_args = ['filename']
653
def run(self, filename):
654
b, relpath = Branch.open_containing(filename)
656
fid = inv.path2id(relpath)
658
raise BzrError("%r is not a versioned file" % filename)
659
for fip in inv.get_idpath(fid):
663
class cmd_revision_history(Command):
664
"""Display list of revision ids on this branch."""
668
for patchid in Branch.open_containing('.')[0].revision_history():
672
class cmd_ancestry(Command):
673
"""List all revisions merged into this branch."""
677
b = Branch.open_containing('.')[0]
678
for revision_id in b.get_ancestry(b.last_revision()):
682
class cmd_init(Command):
683
"""Make a directory into a versioned branch.
685
Use this to create an empty branch, or before importing an
688
Recipe for importing a tree of files:
693
bzr commit -m 'imported project'
695
takes_args = ['location?']
696
def run(self, location=None):
697
from bzrlib.branch import Branch
701
# The path has to exist to initialize a
702
# branch inside of it.
703
# Just using os.mkdir, since I don't
704
# believe that we want to create a bunch of
705
# locations if the user supplies an extended path
706
if not os.path.exists(location):
708
Branch.initialize(location)
711
class cmd_diff(Command):
712
"""Show differences in working tree.
714
If files are listed, only the changes in those files are listed.
715
Otherwise, all changes for the tree are listed.
722
# TODO: Allow diff across branches.
723
# TODO: Option to use external diff command; could be GNU diff, wdiff,
724
# or a graphical diff.
726
# TODO: Python difflib is not exactly the same as unidiff; should
727
# either fix it up or prefer to use an external diff.
729
# TODO: If a directory is given, diff everything under that.
731
# TODO: Selected-file diff is inefficient and doesn't show you
734
# TODO: This probably handles non-Unix newlines poorly.
736
takes_args = ['file*']
737
takes_options = ['revision', 'diff-options']
738
aliases = ['di', 'dif']
741
def run(self, revision=None, file_list=None, diff_options=None):
742
from bzrlib.diff import show_diff
744
b, file_list = inner_branch_files(file_list)
746
except FileInWrongBranch:
747
if len(file_list) != 2:
748
raise BzrCommandError("Files are in different branches")
750
b, file1 = Branch.open_containing(file_list[0])
751
b2, file2 = Branch.open_containing(file_list[1])
752
if file1 != "" or file2 != "":
753
raise BzrCommandError("Files are in different branches")
755
if revision is not None:
757
raise BzrCommandError("Can't specify -r with two branches")
758
if len(revision) == 1:
759
return show_diff(b, revision[0], specific_files=file_list,
760
external_diff_options=diff_options)
761
elif len(revision) == 2:
762
return show_diff(b, revision[0], specific_files=file_list,
763
external_diff_options=diff_options,
764
revision2=revision[1])
766
raise BzrCommandError('bzr diff --revision takes exactly one or two revision identifiers')
768
return show_diff(b, None, specific_files=file_list,
769
external_diff_options=diff_options, b2=b2)
772
class cmd_deleted(Command):
773
"""List files deleted in the working tree.
775
# TODO: Show files deleted since a previous revision, or
776
# between two revisions.
777
# TODO: Much more efficient way to do this: read in new
778
# directories with readdir, rather than stating each one. Same
779
# level of effort but possibly much less IO. (Or possibly not,
780
# if the directories are very large...)
782
def run(self, show_ids=False):
783
b = Branch.open_containing('.')[0]
785
new = b.working_tree()
786
for path, ie in old.inventory.iter_entries():
787
if not new.has_id(ie.file_id):
789
print '%-50s %s' % (path, ie.file_id)
794
class cmd_modified(Command):
795
"""List files modified in working tree."""
799
from bzrlib.delta import compare_trees
801
b = Branch.open_containing('.')[0]
802
td = compare_trees(b.basis_tree(), b.working_tree())
804
for path, id, kind, text_modified, meta_modified in td.modified:
809
class cmd_added(Command):
810
"""List files added in working tree."""
814
b = Branch.open_containing('.')[0]
815
wt = b.working_tree()
816
basis_inv = b.basis_tree().inventory
819
if file_id in basis_inv:
821
path = inv.id2path(file_id)
822
if not os.access(b.abspath(path), os.F_OK):
828
class cmd_root(Command):
829
"""Show the tree root directory.
831
The root is the nearest enclosing directory with a .bzr control
833
takes_args = ['filename?']
835
def run(self, filename=None):
836
"""Print the branch root."""
837
b = Branch.open_containing(filename)[0]
841
class cmd_log(Command):
842
"""Show log of this branch.
844
To request a range of logs, you can use the command -r begin..end
845
-r revision requests a specific revision, -r ..end or -r begin.. are
849
# TODO: Make --revision support uuid: and hash: [future tag:] notation.
851
takes_args = ['filename?']
852
takes_options = [Option('forward',
853
help='show from oldest to newest'),
854
'timezone', 'verbose',
855
'show-ids', 'revision',
856
Option('line', help='format with one line per revision'),
859
help='show revisions whose message matches this regexp',
861
Option('short', help='use moderately short format'),
864
def run(self, filename=None, timezone='original',
873
from bzrlib.log import log_formatter, show_log
875
assert message is None or isinstance(message, basestring), \
876
"invalid message argument %r" % message
877
direction = (forward and 'forward') or 'reverse'
880
b, fp = Branch.open_containing(filename)
883
inv = b.working_tree().read_working_inventory()
884
except NoWorkingTree:
885
inv = b.get_inventory(b.last_revision())
886
file_id = inv.path2id(fp)
888
file_id = None # points to branch root
890
b, relpath = Branch.open_containing('.')
896
elif len(revision) == 1:
897
rev1 = rev2 = revision[0].in_history(b).revno
898
elif len(revision) == 2:
899
rev1 = revision[0].in_history(b).revno
900
rev2 = revision[1].in_history(b).revno
902
raise BzrCommandError('bzr log --revision takes one or two values.')
904
# By this point, the revision numbers are converted to the +ve
905
# form if they were supplied in the -ve form, so we can do
906
# this comparison in relative safety
908
(rev2, rev1) = (rev1, rev2)
910
mutter('encoding log as %r', bzrlib.user_encoding)
912
# use 'replace' so that we don't abort if trying to write out
913
# in e.g. the default C locale.
914
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
921
lf = log_formatter(log_format,
924
show_timezone=timezone)
937
class cmd_touching_revisions(Command):
938
"""Return revision-ids which affected a particular file.
940
A more user-friendly interface is "bzr log FILE"."""
942
takes_args = ["filename"]
944
def run(self, filename):
945
b, relpath = Branch.open_containing(filename)[0]
946
inv = b.working_tree().read_working_inventory()
947
file_id = inv.path2id(relpath)
948
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
949
print "%6d %s" % (revno, what)
952
class cmd_ls(Command):
953
"""List files in a tree.
955
# TODO: Take a revision or remote path and list that tree instead.
957
takes_options = ['verbose', 'revision',
958
Option('non-recursive',
959
help='don\'t recurse into sub-directories'),
961
help='Print all paths from the root of the branch.'),
962
Option('unknown', help='Print unknown files'),
963
Option('versioned', help='Print versioned files'),
964
Option('ignored', help='Print ignored files'),
966
Option('null', help='Null separate the files'),
969
def run(self, revision=None, verbose=False,
970
non_recursive=False, from_root=False,
971
unknown=False, versioned=False, ignored=False,
975
raise BzrCommandError('Cannot set both --verbose and --null')
976
all = not (unknown or versioned or ignored)
978
selection = {'I':ignored, '?':unknown, 'V':versioned}
980
b, relpath = Branch.open_containing('.')
986
tree = b.working_tree()
988
tree = b.revision_tree(revision[0].in_history(b).rev_id)
989
for fp, fc, kind, fid, entry in tree.list_files():
990
if fp.startswith(relpath):
991
fp = fp[len(relpath):]
992
if non_recursive and '/' in fp:
994
if not all and not selection[fc]:
997
kindch = entry.kind_character()
998
print '%-8s %s%s' % (fc, fp, kindch)
1000
sys.stdout.write(fp)
1001
sys.stdout.write('\0')
1008
class cmd_unknowns(Command):
1009
"""List unknown files."""
1012
from bzrlib.osutils import quotefn
1013
for f in Branch.open_containing('.')[0].unknowns():
1018
class cmd_ignore(Command):
1019
"""Ignore a command or pattern.
1021
To remove patterns from the ignore list, edit the .bzrignore file.
1023
If the pattern contains a slash, it is compared to the whole path
1024
from the branch root. Otherwise, it is compared to only the last
1025
component of the path. To match a file only in the root directory,
1028
Ignore patterns are case-insensitive on case-insensitive systems.
1030
Note: wildcards must be quoted from the shell on Unix.
1033
bzr ignore ./Makefile
1034
bzr ignore '*.class'
1036
# TODO: Complain if the filename is absolute
1037
takes_args = ['name_pattern']
1039
def run(self, name_pattern):
1040
from bzrlib.atomicfile import AtomicFile
1043
b, relpath = Branch.open_containing('.')
1044
ifn = b.abspath('.bzrignore')
1046
if os.path.exists(ifn):
1049
igns = f.read().decode('utf-8')
1055
# TODO: If the file already uses crlf-style termination, maybe
1056
# we should use that for the newly added lines?
1058
if igns and igns[-1] != '\n':
1060
igns += name_pattern + '\n'
1063
f = AtomicFile(ifn, 'wt')
1064
f.write(igns.encode('utf-8'))
1069
inv = b.working_tree().inventory
1070
if inv.path2id('.bzrignore'):
1071
mutter('.bzrignore is already versioned')
1073
mutter('need to make new .bzrignore file versioned')
1074
b.add(['.bzrignore'])
1078
class cmd_ignored(Command):
1079
"""List ignored files and the patterns that matched them.
1081
See also: bzr ignore"""
1084
tree = Branch.open_containing('.')[0].working_tree()
1085
for path, file_class, kind, file_id, entry in tree.list_files():
1086
if file_class != 'I':
1088
## XXX: Slightly inefficient since this was already calculated
1089
pat = tree.is_ignored(path)
1090
print '%-50s %s' % (path, pat)
1093
class cmd_lookup_revision(Command):
1094
"""Lookup the revision-id from a revision-number
1097
bzr lookup-revision 33
1100
takes_args = ['revno']
1103
def run(self, revno):
1107
raise BzrCommandError("not a valid revision-number: %r" % revno)
1109
print Branch.open_containing('.')[0].get_rev_id(revno)
1112
class cmd_export(Command):
1113
"""Export past revision to destination directory.
1115
If no revision is specified this exports the last committed revision.
1117
Format may be an "exporter" name, such as tar, tgz, tbz2. If none is
1118
given, try to find the format with the extension. If no extension
1119
is found exports to a directory (equivalent to --format=dir).
1121
Root may be the top directory for tar, tgz and tbz2 formats. If none
1122
is given, the top directory will be the root name of the file.
1124
Note: export of tree with non-ascii filenames to zip is not supported.
1126
Supported formats Autodetected by extension
1127
----------------- -------------------------
1130
tbz2 .tar.bz2, .tbz2
1134
takes_args = ['dest']
1135
takes_options = ['revision', 'format', 'root']
1136
def run(self, dest, revision=None, format=None, root=None):
1138
from bzrlib.export import export
1139
b = Branch.open_containing('.')[0]
1140
if revision is None:
1141
rev_id = b.last_revision()
1143
if len(revision) != 1:
1144
raise BzrError('bzr export --revision takes exactly 1 argument')
1145
rev_id = revision[0].in_history(b).rev_id
1146
t = b.revision_tree(rev_id)
1148
export(t, dest, format, root)
1149
except errors.NoSuchExportFormat, e:
1150
raise BzrCommandError('Unsupported export format: %s' % e.format)
1153
class cmd_cat(Command):
1154
"""Write a file's text from a previous revision."""
1156
takes_options = ['revision']
1157
takes_args = ['filename']
1160
def run(self, filename, revision=None):
1161
if revision is None:
1162
raise BzrCommandError("bzr cat requires a revision number")
1163
elif len(revision) != 1:
1164
raise BzrCommandError("bzr cat --revision takes exactly one number")
1165
b, relpath = Branch.open_containing(filename)
1166
b.print_file(relpath, revision[0].in_history(b).revno)
1169
class cmd_local_time_offset(Command):
1170
"""Show the offset in seconds from GMT to local time."""
1174
print bzrlib.osutils.local_time_offset()
1178
class cmd_commit(Command):
1179
"""Commit changes into a new revision.
1181
If no arguments are given, the entire tree is committed.
1183
If selected files are specified, only changes to those files are
1184
committed. If a directory is specified then the directory and everything
1185
within it is committed.
1187
A selected-file commit may fail in some cases where the committed
1188
tree would be invalid, such as trying to commit a file in a
1189
newly-added directory that is not itself committed.
1191
# TODO: Run hooks on tree to-be-committed, and after commit.
1193
# TODO: Strict commit that fails if there are deleted files.
1194
# (what does "deleted files" mean ??)
1196
# TODO: Give better message for -s, --summary, used by tla people
1198
# XXX: verbose currently does nothing
1200
takes_args = ['selected*']
1201
takes_options = ['message', 'verbose',
1203
help='commit even if nothing has changed'),
1204
Option('file', type=str,
1206
help='file containing commit message'),
1208
help="refuse to commit if there are unknown "
1209
"files in the working tree."),
1211
aliases = ['ci', 'checkin']
1213
def run(self, message=None, file=None, verbose=True, selected_list=None,
1214
unchanged=False, strict=False):
1215
from bzrlib.errors import (PointlessCommit, ConflictsInTree,
1217
from bzrlib.msgeditor import edit_commit_message
1218
from bzrlib.status import show_status
1219
from cStringIO import StringIO
1221
b, selected_list = branch_files(selected_list)
1222
if message is None and not file:
1223
catcher = StringIO()
1224
show_status(b, specific_files=selected_list,
1226
message = edit_commit_message(catcher.getvalue())
1229
raise BzrCommandError("please specify a commit message"
1230
" with either --message or --file")
1231
elif message and file:
1232
raise BzrCommandError("please specify either --message or --file")
1236
message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
1239
raise BzrCommandError("empty commit message specified")
1242
b.working_tree().commit(message, specific_files=selected_list,
1243
allow_pointless=unchanged, strict=strict)
1244
except PointlessCommit:
1245
# FIXME: This should really happen before the file is read in;
1246
# perhaps prepare the commit; get the message; then actually commit
1247
raise BzrCommandError("no changes to commit",
1248
["use --unchanged to commit anyhow"])
1249
except ConflictsInTree:
1250
raise BzrCommandError("Conflicts detected in working tree. "
1251
'Use "bzr conflicts" to list, "bzr resolve FILE" to resolve.')
1252
except StrictCommitFailed:
1253
raise BzrCommandError("Commit refused because there are unknown "
1254
"files in the working tree.")
1257
class cmd_check(Command):
1258
"""Validate consistency of branch history.
1260
This command checks various invariants about the branch storage to
1261
detect data corruption or bzr bugs.
1263
takes_args = ['dir?']
1264
takes_options = ['verbose']
1266
def run(self, dir='.', verbose=False):
1267
from bzrlib.check import check
1268
check(Branch.open_containing(dir)[0], verbose)
1271
class cmd_scan_cache(Command):
1274
from bzrlib.hashcache import HashCache
1280
print '%6d stats' % c.stat_count
1281
print '%6d in hashcache' % len(c._cache)
1282
print '%6d files removed from cache' % c.removed_count
1283
print '%6d hashes updated' % c.update_count
1284
print '%6d files changed too recently to cache' % c.danger_count
1291
class cmd_upgrade(Command):
1292
"""Upgrade branch storage to current format.
1294
The check command or bzr developers may sometimes advise you to run
1297
This version of this command upgrades from the full-text storage
1298
used by bzr 0.0.8 and earlier to the weave format (v5).
1300
takes_args = ['dir?']
1302
def run(self, dir='.'):
1303
from bzrlib.upgrade import upgrade
1307
class cmd_whoami(Command):
1308
"""Show bzr user id."""
1309
takes_options = ['email']
1312
def run(self, email=False):
1314
b = bzrlib.branch.Branch.open_containing('.')[0]
1315
config = bzrlib.config.BranchConfig(b)
1316
except NotBranchError:
1317
config = bzrlib.config.GlobalConfig()
1320
print config.user_email()
1322
print config.username()
1324
class cmd_nick(Command):
1326
Print or set the branch nickname.
1327
If unset, the tree root directory name is used as the nickname
1328
To print the current nickname, execute with no argument.
1330
takes_args = ['nickname?']
1331
def run(self, nickname=None):
1332
branch = Branch.open_containing('.')[0]
1333
if nickname is None:
1334
self.printme(branch)
1336
branch.nick = nickname
1339
def printme(self, branch):
1342
class cmd_selftest(Command):
1343
"""Run internal test suite.
1345
This creates temporary test directories in the working directory,
1346
but not existing data is affected. These directories are deleted
1347
if the tests pass, or left behind to help in debugging if they
1348
fail and --keep-output is specified.
1350
If arguments are given, they are regular expressions that say
1351
which tests should run.
1353
# TODO: --list should give a list of all available tests
1355
takes_args = ['testspecs*']
1356
takes_options = ['verbose',
1357
Option('one', help='stop when one test fails'),
1358
Option('keep-output',
1359
help='keep output directories when tests fail')
1362
def run(self, testspecs_list=None, verbose=False, one=False,
1365
from bzrlib.selftest import selftest
1366
# we don't want progress meters from the tests to go to the
1367
# real output; and we don't want log messages cluttering up
1369
save_ui = bzrlib.ui.ui_factory
1370
bzrlib.trace.info('running tests...')
1372
bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1373
if testspecs_list is not None:
1374
pattern = '|'.join(testspecs_list)
1377
result = selftest(verbose=verbose,
1379
stop_on_failure=one,
1380
keep_output=keep_output)
1382
bzrlib.trace.info('tests passed')
1384
bzrlib.trace.info('tests failed')
1385
return int(not result)
1387
bzrlib.ui.ui_factory = save_ui
1391
print "bzr (bazaar-ng) %s" % bzrlib.__version__
1392
# is bzrlib itself in a branch?
1393
bzrrev = bzrlib.get_bzr_revision()
1395
print " (bzr checkout, revision %d {%s})" % bzrrev
1396
print bzrlib.__copyright__
1397
print "http://bazaar-ng.org/"
1399
print "bzr comes with ABSOLUTELY NO WARRANTY. bzr is free software, and"
1400
print "you may use, modify and redistribute it under the terms of the GNU"
1401
print "General Public License version 2 or later."
1404
class cmd_version(Command):
1405
"""Show version of bzr."""
1410
class cmd_rocks(Command):
1411
"""Statement of optimism."""
1415
print "it sure does!"
1418
class cmd_find_merge_base(Command):
1419
"""Find and print a base revision for merging two branches.
1421
# TODO: Options to specify revisions on either side, as if
1422
# merging only part of the history.
1423
takes_args = ['branch', 'other']
1427
def run(self, branch, other):
1428
from bzrlib.revision import common_ancestor, MultipleRevisionSources
1430
branch1 = Branch.open_containing(branch)[0]
1431
branch2 = Branch.open_containing(other)[0]
1433
history_1 = branch1.revision_history()
1434
history_2 = branch2.revision_history()
1436
last1 = branch1.last_revision()
1437
last2 = branch2.last_revision()
1439
source = MultipleRevisionSources(branch1, branch2)
1441
base_rev_id = common_ancestor(last1, last2, source)
1443
print 'merge base is revision %s' % base_rev_id
1447
if base_revno is None:
1448
raise bzrlib.errors.UnrelatedBranches()
1450
print ' r%-6d in %s' % (base_revno, branch)
1452
other_revno = branch2.revision_id_to_revno(base_revid)
1454
print ' r%-6d in %s' % (other_revno, other)
1458
class cmd_merge(Command):
1459
"""Perform a three-way merge.
1461
The branch is the branch you will merge from. By default, it will
1462
merge the latest revision. If you specify a revision, that
1463
revision will be merged. If you specify two revisions, the first
1464
will be used as a BASE, and the second one as OTHER. Revision
1465
numbers are always relative to the specified branch.
1467
By default bzr will try to merge in all new work from the other
1468
branch, automatically determining an appropriate base. If this
1469
fails, you may need to give an explicit base.
1473
To merge the latest revision from bzr.dev
1474
bzr merge ../bzr.dev
1476
To merge changes up to and including revision 82 from bzr.dev
1477
bzr merge -r 82 ../bzr.dev
1479
To merge the changes introduced by 82, without previous changes:
1480
bzr merge -r 81..82 ../bzr.dev
1482
merge refuses to run if there are any uncommitted changes, unless
1485
takes_args = ['branch?']
1486
takes_options = ['revision', 'force', 'merge-type', 'reprocess',
1487
Option('show-base', help="Show base revision text in "
1490
def run(self, branch=None, revision=None, force=False, merge_type=None,
1491
show_base=False, reprocess=False):
1492
from bzrlib.merge import merge
1493
from bzrlib.merge_core import ApplyMerge3
1494
if merge_type is None:
1495
merge_type = ApplyMerge3
1497
branch = Branch.open_containing('.')[0].get_parent()
1499
raise BzrCommandError("No merge location known or specified.")
1501
print "Using saved location: %s" % branch
1502
if revision is None or len(revision) < 1:
1504
other = [branch, -1]
1506
if len(revision) == 1:
1508
other_branch = Branch.open_containing(branch)[0]
1509
revno = revision[0].in_history(other_branch).revno
1510
other = [branch, revno]
1512
assert len(revision) == 2
1513
if None in revision:
1514
raise BzrCommandError(
1515
"Merge doesn't permit that revision specifier.")
1516
b = Branch.open_containing(branch)[0]
1518
base = [branch, revision[0].in_history(b).revno]
1519
other = [branch, revision[1].in_history(b).revno]
1522
conflict_count = merge(other, base, check_clean=(not force),
1523
merge_type=merge_type, reprocess=reprocess,
1524
show_base=show_base)
1525
if conflict_count != 0:
1529
except bzrlib.errors.AmbiguousBase, e:
1530
m = ("sorry, bzr can't determine the right merge base yet\n"
1531
"candidates are:\n "
1532
+ "\n ".join(e.bases)
1534
"please specify an explicit base with -r,\n"
1535
"and (if you want) report this to the bzr developers\n")
1539
class cmd_remerge(Command):
1542
takes_args = ['file*']
1543
takes_options = ['merge-type', 'reprocess',
1544
Option('show-base', help="Show base revision text in "
1547
def run(self, file_list=None, merge_type=None, show_base=False,
1549
from bzrlib.merge import merge_inner, transform_tree
1550
from bzrlib.merge_core import ApplyMerge3
1551
if merge_type is None:
1552
merge_type = ApplyMerge3
1553
b, file_list = branch_files(file_list)
1556
pending_merges = b.working_tree().pending_merges()
1557
if len(pending_merges) != 1:
1558
raise BzrCommandError("Sorry, remerge only works after normal"
1559
+ " merges. Not cherrypicking or"
1561
this_tree = b.working_tree()
1562
base_revision = common_ancestor(b.last_revision(),
1563
pending_merges[0], b)
1564
base_tree = b.revision_tree(base_revision)
1565
other_tree = b.revision_tree(pending_merges[0])
1566
interesting_ids = None
1567
if file_list is not None:
1568
interesting_ids = set()
1569
for filename in file_list:
1570
file_id = this_tree.path2id(filename)
1571
interesting_ids.add(file_id)
1572
if this_tree.kind(file_id) != "directory":
1575
for name, ie in this_tree.inventory.iter_entries(file_id):
1576
interesting_ids.add(ie.file_id)
1577
transform_tree(this_tree, b.basis_tree(), interesting_ids)
1578
if file_list is None:
1579
restore_files = list(this_tree.iter_conflicts())
1581
restore_files = file_list
1582
for filename in restore_files:
1584
restore(this_tree.abspath(filename))
1585
except NotConflicted:
1587
conflicts = merge_inner(b, other_tree, base_tree,
1588
interesting_ids = interesting_ids,
1589
other_rev_id=pending_merges[0],
1590
merge_type=merge_type,
1591
show_base=show_base,
1592
reprocess=reprocess)
1600
class cmd_revert(Command):
1601
"""Reverse all changes since the last commit.
1603
Only versioned files are affected. Specify filenames to revert only
1604
those files. By default, any files that are changed will be backed up
1605
first. Backup files have a '~' appended to their name.
1607
takes_options = ['revision', 'no-backup']
1608
takes_args = ['file*']
1609
aliases = ['merge-revert']
1611
def run(self, revision=None, no_backup=False, file_list=None):
1612
from bzrlib.merge import merge_inner
1613
from bzrlib.commands import parse_spec
1614
if file_list is not None:
1615
if len(file_list) == 0:
1616
raise BzrCommandError("No files specified")
1619
if revision is None:
1621
b = Branch.open_containing('.')[0]
1622
rev_id = b.last_revision()
1623
elif len(revision) != 1:
1624
raise BzrCommandError('bzr revert --revision takes exactly 1 argument')
1626
b, file_list = branch_files(file_list)
1627
rev_id = revision[0].in_history(b).rev_id
1628
b.working_tree().revert(file_list, b.revision_tree(rev_id),
1632
class cmd_assert_fail(Command):
1633
"""Test reporting of assertion failures"""
1636
assert False, "always fails"
1639
class cmd_help(Command):
1640
"""Show help on a command or other topic.
1642
For a list of all available commands, say 'bzr help commands'."""
1643
takes_options = ['long']
1644
takes_args = ['topic?']
1648
def run(self, topic=None, long=False):
1650
if topic is None and long:
1655
class cmd_shell_complete(Command):
1656
"""Show appropriate completions for context.
1658
For a list of all available commands, say 'bzr shell-complete'."""
1659
takes_args = ['context?']
1664
def run(self, context=None):
1665
import shellcomplete
1666
shellcomplete.shellcomplete(context)
1669
class cmd_fetch(Command):
1670
"""Copy in history from another branch but don't merge it.
1672
This is an internal method used for pull and merge."""
1674
takes_args = ['from_branch', 'to_branch']
1675
def run(self, from_branch, to_branch):
1676
from bzrlib.fetch import Fetcher
1677
from bzrlib.branch import Branch
1678
from_b = Branch.open(from_branch)
1679
to_b = Branch.open(to_branch)
1684
Fetcher(to_b, from_b)
1691
class cmd_missing(Command):
1692
"""What is missing in this branch relative to other branch.
1694
# TODO: rewrite this in terms of ancestry so that it shows only
1697
takes_args = ['remote?']
1698
aliases = ['mis', 'miss']
1699
# We don't have to add quiet to the list, because
1700
# unknown options are parsed as booleans
1701
takes_options = ['verbose', 'quiet']
1704
def run(self, remote=None, verbose=False, quiet=False):
1705
from bzrlib.errors import BzrCommandError
1706
from bzrlib.missing import show_missing
1708
if verbose and quiet:
1709
raise BzrCommandError('Cannot pass both quiet and verbose')
1711
b = Branch.open_containing('.')[0]
1712
parent = b.get_parent()
1715
raise BzrCommandError("No missing location known or specified.")
1718
print "Using last location: %s" % parent
1720
elif parent is None:
1721
# We only update parent if it did not exist, missing
1722
# should not change the parent
1723
b.set_parent(remote)
1724
br_remote = Branch.open_containing(remote)[0]
1725
return show_missing(b, br_remote, verbose=verbose, quiet=quiet)
1728
class cmd_plugins(Command):
1733
import bzrlib.plugin
1734
from inspect import getdoc
1735
for plugin in bzrlib.plugin.all_plugins:
1736
if hasattr(plugin, '__path__'):
1737
print plugin.__path__[0]
1738
elif hasattr(plugin, '__file__'):
1739
print plugin.__file__
1745
print '\t', d.split('\n')[0]
1748
class cmd_testament(Command):
1749
"""Show testament (signing-form) of a revision."""
1750
takes_options = ['revision', 'long']
1751
takes_args = ['branch?']
1753
def run(self, branch='.', revision=None, long=False):
1754
from bzrlib.testament import Testament
1755
b = Branch.open_containing(branch)[0]
1758
if revision is None:
1759
rev_id = b.last_revision()
1761
rev_id = revision[0].in_history(b).rev_id
1762
t = Testament.from_revision(b, rev_id)
1764
sys.stdout.writelines(t.as_text_lines())
1766
sys.stdout.write(t.as_short_text())
1771
class cmd_annotate(Command):
1772
"""Show the origin of each line in a file.
1774
This prints out the given file with an annotation on the left side
1775
indicating which revision, author and date introduced the change.
1777
If the origin is the same for a run of consecutive lines, it is
1778
shown only at the top, unless the --all option is given.
1780
# TODO: annotate directories; showing when each file was last changed
1781
# TODO: annotate a previous version of a file
1782
# TODO: if the working copy is modified, show annotations on that
1783
# with new uncommitted lines marked
1784
aliases = ['blame', 'praise']
1785
takes_args = ['filename']
1786
takes_options = [Option('all', help='show annotations on all lines'),
1787
Option('long', help='show date in annotations'),
1791
def run(self, filename, all=False, long=False):
1792
from bzrlib.annotate import annotate_file
1793
b, relpath = Branch.open_containing(filename)
1796
tree = WorkingTree(b.base, b)
1797
tree = b.revision_tree(b.last_revision())
1798
file_id = tree.inventory.path2id(relpath)
1799
file_version = tree.inventory[file_id].revision
1800
annotate_file(b, file_version, file_id, long, all, sys.stdout)
1805
class cmd_re_sign(Command):
1806
"""Create a digital signature for an existing revision."""
1807
# TODO be able to replace existing ones.
1809
hidden = True # is this right ?
1810
takes_args = ['revision_id?']
1811
takes_options = ['revision']
1813
def run(self, revision_id=None, revision=None):
1814
import bzrlib.config as config
1815
import bzrlib.gpg as gpg
1816
if revision_id is not None and revision is not None:
1817
raise BzrCommandError('You can only supply one of revision_id or --revision')
1818
if revision_id is None and revision is None:
1819
raise BzrCommandError('You must supply either --revision or a revision_id')
1820
b = Branch.open_containing('.')[0]
1821
gpg_strategy = gpg.GPGStrategy(config.BranchConfig(b))
1822
if revision_id is not None:
1823
b.sign_revision(revision_id, gpg_strategy)
1824
elif revision is not None:
1825
if len(revision) == 1:
1826
revno, rev_id = revision[0].in_history(b)
1827
b.sign_revision(rev_id, gpg_strategy)
1828
elif len(revision) == 2:
1829
# are they both on rh- if so we can walk between them
1830
# might be nice to have a range helper for arbitrary
1831
# revision paths. hmm.
1832
from_revno, from_revid = revision[0].in_history(b)
1833
to_revno, to_revid = revision[1].in_history(b)
1834
if to_revid is None:
1835
to_revno = b.revno()
1836
if from_revno is None or to_revno is None:
1837
raise BzrCommandError('Cannot sign a range of non-revision-history revisions')
1838
for revno in range(from_revno, to_revno + 1):
1839
b.sign_revision(b.get_rev_id(revno), gpg_strategy)
1841
raise BzrCommandError('Please supply either one revision, or a range.')
1844
# these get imported and then picked up by the scan for cmd_*
1845
# TODO: Some more consistent way to split command definitions across files;
1846
# we do need to load at least some information about them to know of
1848
from bzrlib.conflicts import cmd_resolve, cmd_conflicts, restore