17
17
"""builtin bzr commands"""
23
from shutil import rmtree
25
27
import bzrlib.branch
26
28
from bzrlib.branch import Branch
27
29
import bzrlib.bzrdir as bzrdir
30
from bzrlib.bundle import read_bundle_from_url
28
31
from bzrlib.bundle.read_bundle import BundleReader
29
from bzrlib.bundle.apply_bundle import merge_bundle
32
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
30
33
from bzrlib.commands import Command, display_command
31
34
import bzrlib.errors as errors
32
35
from bzrlib.errors import (BzrError, BzrCheckError, BzrCommandError,
33
36
NotBranchError, DivergedBranches, NotConflicted,
34
37
NoSuchFile, NoWorkingTree, FileInWrongBranch,
35
NotVersionedError, BadBundle)
38
NotVersionedError, NotABundle)
36
39
from bzrlib.log import show_one_log
37
40
from bzrlib.merge import Merge3Merger
38
41
from bzrlib.option import Option
39
43
from bzrlib.progress import DummyProgress, ProgressPhase
40
44
from bzrlib.revision import common_ancestor
41
45
from bzrlib.revisionspec import RevisionSpec
43
47
from bzrlib.trace import mutter, note, log_error, warning, is_quiet
44
48
from bzrlib.transport.local import LocalTransport
50
import bzrlib.urlutils as urlutils
46
51
from bzrlib.workingtree import WorkingTree
150
151
# TODO: --no-recurse, --recurse options
152
153
takes_args = ['file*']
153
takes_options = ['all', 'show-ids', 'revision']
154
takes_options = ['show-ids', 'revision']
154
155
aliases = ['st', 'stat']
157
encoding_type = 'replace'
157
def run(self, all=False, show_ids=False, file_list=None, revision=None):
158
tree, file_list = tree_files(file_list)
160
def run(self, show_ids=False, file_list=None, revision=None):
160
161
from bzrlib.status import show_tree_status
161
show_tree_status(tree, show_unchanged=all, show_ids=show_ids,
162
specific_files=file_list, revision=revision)
163
tree, file_list = tree_files(file_list)
165
show_tree_status(tree, show_ids=show_ids,
166
specific_files=file_list, revision=revision,
165
170
class cmd_cat_revision(Command):
173
178
takes_args = ['revision_id?']
174
179
takes_options = ['revision']
180
# cat-revision is more for frontends so should be exact
177
184
def run(self, revision_id=None, revision=None):
181
188
if revision_id is None and revision is None:
182
189
raise BzrCommandError('You must supply either --revision or a revision_id')
183
190
b = WorkingTree.open_containing(u'.')[0].branch
192
# TODO: jam 20060112 should cat-revision always output utf-8?
184
193
if revision_id is not None:
185
sys.stdout.write(b.repository.get_revision_xml(revision_id))
194
self.outf.write(b.repository.get_revision_xml(revision_id).decode('utf-8'))
186
195
elif revision is not None:
187
196
for rev in revision:
189
198
raise BzrCommandError('You cannot specify a NULL revision.')
190
199
revno, rev_id = rev.in_history(b)
191
sys.stdout.write(b.repository.get_revision_xml(rev_id))
200
self.outf.write(b.repository.get_revision_xml(rev_id).decode('utf-8'))
194
203
class cmd_revno(Command):
195
204
"""Show current revision number.
197
This is equal to the number of revisions on this branch."""
206
This is equal to the number of revisions on this branch.
198
209
takes_args = ['location?']
200
212
def run(self, location=u'.'):
201
print Branch.open_containing(location)[0].revno()
213
self.outf.write(str(Branch.open_containing(location)[0].revno()))
214
self.outf.write('\n')
204
217
class cmd_revision_info(Command):
250
264
Adding a file whose parent directory is not versioned will
251
265
implicitly add the parent, and so on up to the root. This means
252
you should never need to explictly add a directory, they'll just
266
you should never need to explicitly add a directory, they'll just
253
267
get added when you add a file in the directory.
255
269
--dry-run will show which files would be added, but not actually
258
272
takes_args = ['file*']
259
273
takes_options = ['no-recurse', 'dry-run', 'verbose']
274
encoding_type = 'replace'
261
276
def run(self, file_list, no_recurse=False, dry_run=False, verbose=False):
262
277
import bzrlib.add
266
# This is pointless, but I'd rather not raise an error
267
action = bzrlib.add.add_action_null
269
action = bzrlib.add.add_action_print
271
action = bzrlib.add.add_action_add
273
action = bzrlib.add.add_action_add_and_print
279
action = bzrlib.add.AddAction(to_file=self.outf,
280
should_print=(not is_quiet()))
275
282
added, ignored = bzrlib.add.smart_add(file_list, not no_recurse,
283
action=action, save=not dry_run)
277
284
if len(ignored) > 0:
279
286
for glob in sorted(ignored.keys()):
280
287
for path in ignored[glob]:
281
print "ignored %s matching \"%s\"" % (path, glob)
288
self.outf.write("ignored %s matching \"%s\"\n"
284
292
for glob, paths in ignored.items():
285
293
match_len += len(paths)
286
print "ignored %d file(s)." % match_len
287
print "If you wish to add some of these files, please add them"\
294
self.outf.write("ignored %d file(s).\n" % match_len)
295
self.outf.write("If you wish to add some of these files,"
296
" please add them by name.\n")
291
299
class cmd_mkdir(Command):
294
302
This is equivalent to creating the directory and then adding it.
296
305
takes_args = ['dir+']
306
encoding_type = 'replace'
298
308
def run(self, dir_list):
299
309
for d in dir_list:
301
311
wt, dd = WorkingTree.open_containing(d)
313
self.outf.write('added %s\n' % d)
306
316
class cmd_relpath(Command):
307
317
"""Show path of a file relative to root"""
308
319
takes_args = ['filename']
312
323
def run(self, filename):
324
# TODO: jam 20050106 Can relpath return a munged path if
325
# sys.stdout encoding cannot represent it?
313
326
tree, relpath = WorkingTree.open_containing(filename)
327
self.outf.write(relpath)
328
self.outf.write('\n')
317
331
class cmd_inventory(Command):
359
375
Files cannot be moved between branches.
361
378
takes_args = ['names*']
362
379
aliases = ['move', 'rename']
380
encoding_type = 'replace'
364
382
def run(self, names_list):
365
383
if len(names_list) < 2:
369
387
if os.path.isdir(names_list[-1]):
370
388
# move into existing directory
371
389
for pair in tree.move(rel_names[:-1], rel_names[-1]):
372
print "%s => %s" % pair
390
self.outf.write("%s => %s\n" % pair)
374
392
if len(names_list) != 2:
375
393
raise BzrCommandError('to mv multiple files the destination '
376
394
'must be a versioned directory')
377
395
tree.rename_one(rel_names[0], rel_names[1])
378
print "%s => %s" % (rel_names[0], rel_names[1])
396
self.outf.write("%s => %s\n" % (rel_names[0], rel_names[1]))
381
399
class cmd_pull(Command):
400
418
that, you can omit the location to use the default. To change the
401
419
default, use --remember.
403
422
takes_options = ['remember', 'overwrite', 'revision', 'verbose']
404
423
takes_args = ['location?']
424
encoding_type = 'replace'
406
426
def run(self, location=None, remember=False, overwrite=False, revision=None, verbose=False):
407
427
# FIXME: too much stuff is in the command class
410
430
branch_to = tree_to.branch
411
431
except NoWorkingTree:
413
branch_to = Branch.open_containing(u'.')[0]
433
branch_to = Branch.open_containing(u'.')[0]
436
if location is not None:
438
reader = read_bundle_from_url(location)
440
pass # Continue on considering this url a Branch
414
442
stored_loc = branch_to.get_parent()
415
443
if location is None:
416
444
if stored_loc is None:
417
445
raise BzrCommandError("No pull location known or specified.")
419
print "Using saved location: %s" % stored_loc
447
display_url = urlutils.unescape_for_display(stored_loc,
449
self.outf.write("Using saved location: %s\n" % display_url)
420
450
location = stored_loc
422
if branch_to.get_parent() is None or remember:
423
branch_to.set_parent(location)
425
branch_from = Branch.open(location)
453
if reader is not None:
454
install_bundle(branch_to.repository, reader)
455
branch_from = branch_to
457
branch_from = Branch.open(location)
459
if branch_to.get_parent() is None or remember:
460
branch_to.set_parent(branch_from.base)
427
463
if revision is None:
464
if reader is not None:
465
rev_id = reader.info.target
429
466
elif len(revision) == 1:
430
467
rev_id = revision[0].in_history(branch_from).rev_id
443
480
if old_rh != new_rh:
444
481
# Something changed
445
482
from bzrlib.log import show_changed_revisions
446
show_changed_revisions(branch_to, old_rh, new_rh)
483
show_changed_revisions(branch_to, old_rh, new_rh,
449
487
class cmd_push(Command):
470
508
After that, you can omit the location to use the default. To change the
471
509
default, use --remember.
473
takes_options = ['remember', 'overwrite',
512
takes_options = ['remember', 'overwrite', 'verbose',
474
513
Option('create-prefix',
475
514
help='Create the path leading up to the branch '
476
515
'if it does not already exist')]
477
516
takes_args = ['location?']
517
encoding_type = 'replace'
479
519
def run(self, location=None, remember=False, overwrite=False,
480
520
create_prefix=False, verbose=False):
488
528
if stored_loc is None:
489
529
raise BzrCommandError("No push location known or specified.")
491
print "Using saved location: %s" % stored_loc
531
display_url = urlutils.unescape_for_display(stored_loc,
533
self.outf.write("Using saved location: %s" % display_url)
492
534
location = stored_loc
536
transport = get_transport(location)
537
location_url = transport.base
493
538
if br_from.get_push_location() is None or remember:
494
br_from.set_push_location(location)
539
br_from.set_push_location(location_url)
496
dir_to = bzrlib.bzrdir.BzrDir.open(location)
543
dir_to = bzrlib.bzrdir.BzrDir.open(location_url)
497
544
br_to = dir_to.open_branch()
498
545
except NotBranchError:
499
546
# create a branch.
500
transport = get_transport(location).clone('..')
547
transport = transport.clone('..')
501
548
if not create_prefix:
503
transport.mkdir(transport.relpath(location))
550
relurl = transport.relpath(location_url)
551
mutter('creating directory %s => %s', location_url, relurl)
552
transport.mkdir(relurl)
504
553
except NoSuchFile:
505
554
raise BzrCommandError("Parent directory of %s "
506
555
"does not exist." % location)
508
557
current = transport.base
509
needed = [(transport, transport.relpath(location))]
558
needed = [(transport, transport.relpath(location_url))]
512
561
transport, relpath = needed[-1]
519
568
if new_transport.base == transport.base:
520
569
raise BzrCommandError("Could not create "
522
dir_to = br_from.bzrdir.clone(location,
571
dir_to = br_from.bzrdir.clone(location_url,
523
572
revision_id=br_from.last_revision())
524
573
br_to = dir_to.open_branch()
525
574
count = len(br_to.revision_history())
546
595
if old_rh != new_rh:
547
596
# Something changed
548
597
from bzrlib.log import show_changed_revisions
549
show_changed_revisions(br_to, old_rh, new_rh)
598
show_changed_revisions(br_to, old_rh, new_rh,
552
602
class cmd_branch(Command):
567
617
aliases = ['get', 'clone']
569
619
def run(self, from_location, to_location=None, revision=None, basis=None):
620
from bzrlib.transport import get_transport
570
621
from bzrlib.osutils import rmtree
571
622
if revision is None:
572
623
revision = [None]
601
652
name = os.path.basename(to_location) + '\n'
654
to_transport = get_transport(to_location)
603
os.mkdir(to_location)
605
if e.errno == errno.EEXIST:
606
raise BzrCommandError('Target directory "%s" already'
607
' exists.' % to_location)
608
if e.errno == errno.ENOENT:
609
raise BzrCommandError('Parent of "%s" does not exist.' %
656
to_transport.mkdir('.')
657
except bzrlib.errors.FileExists:
658
raise BzrCommandError('Target directory "%s" already'
659
' exists.' % to_location)
660
except bzrlib.errors.NoSuchFile:
661
raise BzrCommandError('Parent of "%s" does not exist.' %
614
664
# preserve whatever source format we have.
615
dir = br_from.bzrdir.sprout(to_location, revision_id, basis_dir)
665
dir = br_from.bzrdir.sprout(to_transport.base,
666
revision_id, basis_dir)
616
667
branch = dir.open_branch()
617
668
except bzrlib.errors.NoSuchRevision:
669
to_transport.delete_tree('.')
619
670
msg = "The branch %s has no revision %s." % (from_location, revision[0])
620
671
raise BzrCommandError(msg)
621
672
except bzrlib.errors.UnlistableBranch:
735
785
renames = list(bzrlib.tree.find_renames(old_inv, new_inv))
737
787
for old_name, new_name in renames:
738
print "%s => %s" % (old_name, new_name)
788
self.outf.write("%s => %s\n" % (old_name, new_name))
741
791
class cmd_update(Command):
804
854
takes_args = ['file*']
805
855
takes_options = ['verbose', Option('new', help='remove newly-added files')]
857
encoding_type = 'replace'
808
859
def run(self, file_list, verbose=False, new=False):
809
860
tree, file_list = tree_files(file_list)
818
869
file_list = sorted([f[0] for f in added[0]], reverse=True)
819
870
if len(file_list) == 0:
820
871
raise BzrCommandError('No matching files.')
821
tree.remove(file_list, verbose=verbose)
872
tree.remove(file_list, verbose=verbose, to_file=self.outf)
824
875
class cmd_file_id(Command):
838
891
raise BzrError("%r is not a versioned file" % filename)
893
self.outf.write(i + '\n')
843
896
class cmd_file_path(Command):
844
897
"""Print path of file_ids to a file or directory.
846
899
This prints one line for each directory down to the target,
847
starting at the branch root."""
900
starting at the branch root.
849
904
takes_args = ['filename']
851
907
def run(self, filename):
852
908
tree, relpath = WorkingTree.open_containing(filename)
887
943
class cmd_revision_history(Command):
888
"""Display list of revision ids on this branch."""
944
"""Display the list of revision ids on a branch."""
945
takes_args = ['location?']
892
branch = WorkingTree.open_containing(u'.')[0].branch
893
for patchid in branch.revision_history():
950
def run(self, location="."):
951
branch = Branch.open_containing(location)[0]
952
for revid in branch.revision_history():
953
self.outf.write(revid)
954
self.outf.write('\n')
897
957
class cmd_ancestry(Command):
898
958
"""List all revisions merged into this branch."""
959
takes_args = ['location?']
902
tree = WorkingTree.open_containing(u'.')[0]
904
# FIXME. should be tree.last_revision
905
revision_ids = b.repository.get_ancestry(b.last_revision())
964
def run(self, location="."):
966
wt = WorkingTree.open_containing(location)[0]
967
except errors.NoWorkingTree:
968
b = Branch.open(location)
969
last_revision = b.last_revision()
972
last_revision = wt.last_revision()
974
revision_ids = b.repository.get_ancestry(last_revision)
906
975
assert revision_ids[0] == None
907
976
revision_ids.pop(0)
908
977
for revision_id in revision_ids:
978
self.outf.write(revision_id + '\n')
912
981
class cmd_init(Command):
1037
1106
takes_args = ['file*']
1038
1107
takes_options = ['revision', 'diff-options', 'prefix']
1039
1108
aliases = ['di', 'dif']
1109
encoding_type = 'exact'
1041
1111
@display_command
1042
1112
def run(self, revision=None, file_list=None, diff_options=None,
1103
1173
# directories with readdir, rather than stating each one. Same
1104
1174
# level of effort but possibly much less IO. (Or possibly not,
1105
1175
# if the directories are very large...)
1176
takes_options = ['show-ids']
1106
1178
@display_command
1107
1179
def run(self, show_ids=False):
1108
1180
tree = WorkingTree.open_containing(u'.')[0]
1109
1181
old = tree.basis_tree()
1110
1182
for path, ie in old.inventory.iter_entries():
1111
1183
if not tree.has_id(ie.file_id):
1184
self.outf.write(path)
1113
print '%-50s %s' % (path, ie.file_id)
1186
self.outf.write(' ')
1187
self.outf.write(ie.file_id)
1188
self.outf.write('\n')
1118
1191
class cmd_modified(Command):
1126
1199
td = compare_trees(tree.basis_tree(), tree)
1128
1201
for path, id, kind, text_modified, meta_modified in td.modified:
1202
self.outf.write(path + '\n')
1133
1205
class cmd_added(Command):
1144
1216
path = inv.id2path(file_id)
1145
1217
if not os.access(bzrlib.osutils.abspath(path), os.F_OK):
1219
self.outf.write(path + '\n')
1151
1222
class cmd_root(Command):
1152
1223
"""Show the tree root directory.
1158
1229
def run(self, filename=None):
1159
1230
"""Print the branch root."""
1160
1231
tree = WorkingTree.open_containing(filename)[0]
1232
self.outf.write(tree.basedir + '\n')
1164
1235
class cmd_log(Command):
1206
1279
from bzrlib.log import log_formatter, show_log
1208
1280
assert message is None or isinstance(message, basestring), \
1209
1281
"invalid message argument %r" % message
1210
1282
direction = (forward and 'forward') or 'reverse'
1256
1328
if rev1 > rev2:
1257
1329
(rev2, rev1) = (rev1, rev2)
1259
mutter('encoding log as %r', bzrlib.user_encoding)
1261
# use 'replace' so that we don't abort if trying to write out
1262
# in e.g. the default C locale.
1263
outf = codecs.getwriter(bzrlib.user_encoding)(sys.stdout, errors='replace')
1265
1331
if (log_format == None):
1266
1332
default = bzrlib.config.BranchConfig(b).log_format()
1267
1333
log_format = get_log_format(long=long, short=short, line=line, default=default)
1269
1334
lf = log_formatter(log_format,
1270
1335
show_ids=show_ids,
1272
1337
show_timezone=timezone)
1295
1360
class cmd_touching_revisions(Command):
1296
1361
"""Return revision-ids which affected a particular file.
1298
A more user-friendly interface is "bzr log FILE"."""
1363
A more user-friendly interface is "bzr log FILE".
1300
1367
takes_args = ["filename"]
1301
1369
@display_command
1302
1370
def run(self, filename):
1303
1371
tree, relpath = WorkingTree.open_containing(filename)
1305
1373
inv = tree.read_working_inventory()
1306
1374
file_id = inv.path2id(relpath)
1307
1375
for revno, revision_id, what in bzrlib.log.find_touching_revisions(b, file_id):
1308
print "%6d %s" % (revno, what)
1376
self.outf.write("%6d %s\n" % (revno, what))
1311
1379
class cmd_ls(Command):
1344
1412
if revision is not None:
1345
1413
tree = tree.branch.repository.revision_tree(
1346
1414
revision[0].in_history(tree.branch).rev_id)
1347
1416
for fp, fc, kind, fid, entry in tree.list_files():
1348
1417
if fp.startswith(relpath):
1349
1418
fp = fp[len(relpath):]
1355
1424
kindch = entry.kind_character()
1356
print '%-8s %s%s' % (fc, fp, kindch)
1425
self.outf.write('%-8s %s%s\n' % (fc, fp, kindch))
1358
sys.stdout.write(fp)
1359
sys.stdout.write('\0')
1427
self.outf.write(fp + '\0')
1430
self.outf.write(fp + '\n')
1365
1433
class cmd_unknowns(Command):
1591
1659
from bzrlib.msgeditor import edit_commit_message, \
1592
1660
make_commit_message_template
1593
1661
from tempfile import TemporaryFile
1596
1663
# TODO: Need a blackbox test for invoking the external editor; may be
1597
1664
# slightly problematic to run this cross-platform.
1994
2060
if merge_type is None:
1995
2061
merge_type = Merge3Merger
1998
2063
tree = WorkingTree.open_containing(u'.')[0]
2000
if branch is not None:
2001
reader = BundleReader(file(branch, 'rb'))
2005
if e.errno not in (errno.ENOENT, errno.EISDIR):
2010
if reader is not None:
2011
conflicts = merge_bundle(reader, tree, not force, merge_type,
2012
reprocess, show_base)
2018
stored_loc = tree.branch.get_parent()
2020
if stored_loc is None:
2021
raise BzrCommandError("No merge branch known or specified.")
2023
print "Using saved branch: %s" % stored_loc
2026
if tree.branch.get_parent() is None or remember:
2027
tree.branch.set_parent(branch)
2065
if branch is not None:
2067
reader = read_bundle_from_url(branch)
2069
pass # Continue on considering this url a Branch
2071
conflicts = merge_bundle(reader, tree, not force, merge_type,
2072
reprocess, show_base)
2078
branch = self._get_remembered_parent(tree, branch, 'Merging from')
2029
2080
if revision is None or len(revision) < 1:
2030
2081
base = [None, None]
2041
2092
if None in revision:
2042
2093
raise BzrCommandError(
2043
2094
"Merge doesn't permit that revision specifier.")
2044
b, path = Branch.open_containing(branch)
2046
base = [branch, revision[0].in_history(b).revno]
2047
other = [branch, revision[1].in_history(b).revno]
2095
other_branch, path = Branch.open_containing(branch)
2097
base = [branch, revision[0].in_history(other_branch).revno]
2098
other = [branch, revision[1].in_history(other_branch).revno]
2100
if tree.branch.get_parent() is None or remember:
2101
tree.branch.set_parent(other_branch.base)
2049
2104
interesting_files = [path]
2072
2127
"and (if you want) report this to the bzr developers\n")
2130
# TODO: move up to common parent; this isn't merge-specific anymore.
2131
def _get_remembered_parent(self, tree, supplied_location, verb_string):
2132
"""Use tree.branch's parent if none was supplied.
2134
Report if the remembered location was used.
2136
if supplied_location is not None:
2137
return supplied_location
2138
stored_location = tree.branch.get_parent()
2139
mutter("%s", stored_location)
2140
if stored_location is None:
2141
raise BzrCommandError("No location specified or remembered")
2142
display_url = urlutils.unescape_for_display(stored_location, self.outf.encoding)
2143
self.outf.write("%s remembered location %s\n" % (verb_string, display_url))
2144
return stored_location
2076
2147
class cmd_remerge(Command):
2077
2148
"""Redo a merge.
2502
2573
# TODO: jam 20060108 Add an option to allow uncommit to remove
2503
# unreferenced information in 'branch-as-repostory' branches.
2574
# unreferenced information in 'branch-as-repository' branches.
2504
2575
# TODO: jam 20060108 Add the ability for uncommit to remove unreferenced
2505
2576
# information in shared branches as well.
2506
2577
takes_options = ['verbose', 'revision',