1
# Copyright (C) 2005-2012 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""builtin brz commands"""
30
from . import lazy_import
31
lazy_import.lazy_import(globals(), """
36
branch as _mod_branch,
42
config as _mod_config,
49
mergeable as _mod_mergeable,
54
revision as _mod_revision,
63
from breezy.branch import Branch
64
from breezy.conflicts import ConflictList
65
from breezy.transport import memory
66
from breezy.smtp_connection import SMTPConnection
67
from breezy.workingtree import WorkingTree
68
from breezy.i18n import gettext, ngettext
71
from .commands import (
73
builtin_command_registry,
83
from .revisionspec import (
87
from .trace import mutter, note, warning, is_quiet, get_verbosity_level
90
def _get_branch_location(control_dir, possible_transports=None):
91
"""Return location of branch for this control dir."""
93
target = control_dir.get_branch_reference()
94
except errors.NotBranchError:
95
return control_dir.root_transport.base
96
if target is not None:
98
this_branch = control_dir.open_branch(
99
possible_transports=possible_transports)
100
# This may be a heavy checkout, where we want the master branch
101
master_location = this_branch.get_bound_location()
102
if master_location is not None:
103
return master_location
104
# If not, use a local sibling
105
return this_branch.base
108
def _is_colocated(control_dir, possible_transports=None):
109
"""Check if the branch in control_dir is colocated.
111
:param control_dir: Control directory
112
:return: Tuple with boolean indicating whether the branch is colocated
113
and the full URL to the actual branch
115
# This path is meant to be relative to the existing branch
116
this_url = _get_branch_location(
117
control_dir, possible_transports=possible_transports)
118
# Perhaps the target control dir supports colocated branches?
120
root = controldir.ControlDir.open(
121
this_url, possible_transports=possible_transports)
122
except errors.NotBranchError:
123
return (False, this_url)
126
control_dir.open_workingtree()
127
except (errors.NoWorkingTree, errors.NotLocalUrl):
128
return (False, this_url)
131
root._format.colocated_branches and
132
control_dir.control_url == root.control_url,
136
def lookup_new_sibling_branch(control_dir, location, possible_transports=None):
137
"""Lookup the location for a new sibling branch.
139
:param control_dir: Control directory to find sibling branches from
140
:param location: Name of the new branch
141
:return: Full location to the new branch
143
location = directory_service.directories.dereference(location)
144
if '/' not in location and '\\' not in location:
145
(colocated, this_url) = _is_colocated(control_dir, possible_transports)
148
return urlutils.join_segment_parameters(
149
this_url, {"branch": urlutils.escape(location)})
151
return urlutils.join(this_url, '..', urlutils.escape(location))
155
def open_sibling_branch(control_dir, location, possible_transports=None):
156
"""Open a branch, possibly a sibling of another.
158
:param control_dir: Control directory relative to which to lookup the
160
:param location: Location to look up
161
:return: branch to open
164
# Perhaps it's a colocated branch?
165
return control_dir.open_branch(
166
location, possible_transports=possible_transports)
167
except (errors.NotBranchError, errors.NoColocatedBranchSupport):
168
this_url = _get_branch_location(control_dir)
171
this_url, '..', urlutils.escape(location)))
174
def open_nearby_branch(near=None, location=None, possible_transports=None):
175
"""Open a nearby branch.
177
:param near: Optional location of container from which to open branch
178
:param location: Location of the branch
179
:return: Branch instance
186
location, possible_transports=possible_transports)
187
except errors.NotBranchError:
189
cdir = controldir.ControlDir.open(
190
near, possible_transports=possible_transports)
191
return open_sibling_branch(
192
cdir, location, possible_transports=possible_transports)
195
def iter_sibling_branches(control_dir, possible_transports=None):
196
"""Iterate over the siblings of a branch.
198
:param control_dir: Control directory for which to look up the siblings
199
:return: Iterator over tuples with branch name and branch object
202
reference = control_dir.get_branch_reference()
203
except errors.NotBranchError:
205
if reference is not None:
207
ref_branch = Branch.open(
208
reference, possible_transports=possible_transports)
209
except errors.NotBranchError:
213
if ref_branch is None or ref_branch.name:
214
if ref_branch is not None:
215
control_dir = ref_branch.controldir
216
for name, branch in control_dir.get_branches().items():
219
repo = ref_branch.controldir.find_repository()
220
for branch in repo.find_branches(using=True):
221
name = urlutils.relative_url(
222
repo.user_url, branch.user_url).rstrip("/")
226
def tree_files_for_add(file_list):
228
Return a tree and list of absolute paths from a file list.
230
Similar to tree_files, but add handles files a bit differently, so it a
231
custom implementation. In particular, MutableTreeTree.smart_add expects
232
absolute paths, which it immediately converts to relative paths.
234
# FIXME Would be nice to just return the relative paths like
235
# internal_tree_files does, but there are a large number of unit tests
236
# that assume the current interface to mutabletree.smart_add
238
tree, relpath = WorkingTree.open_containing(file_list[0])
239
if tree.supports_views():
240
view_files = tree.views.lookup_view()
242
for filename in file_list:
243
if not osutils.is_inside_any(view_files, filename):
244
raise views.FileOutsideView(filename, view_files)
245
file_list = file_list[:]
246
file_list[0] = tree.abspath(relpath)
248
tree = WorkingTree.open_containing(u'.')[0]
249
if tree.supports_views():
250
view_files = tree.views.lookup_view()
252
file_list = view_files
253
view_str = views.view_display_str(view_files)
254
note(gettext("Ignoring files outside view. View is %s"),
256
return tree, file_list
259
def _get_one_revision(command_name, revisions):
260
if revisions is None:
262
if len(revisions) != 1:
263
raise errors.CommandError(gettext(
264
'brz %s --revision takes exactly one revision identifier') % (
269
def _get_one_revision_tree(command_name, revisions, branch=None, tree=None):
270
"""Get a revision tree. Not suitable for commands that change the tree.
272
Specifically, the basis tree in dirstate trees is coupled to the dirstate
273
and doing a commit/uncommit/pull will at best fail due to changing the
276
If tree is passed in, it should be already locked, for lifetime management
277
of the trees internal cached state.
281
if revisions is None:
283
rev_tree = tree.basis_tree()
285
rev_tree = branch.basis_tree()
287
revision = _get_one_revision(command_name, revisions)
288
rev_tree = revision.as_tree(branch)
292
def _get_view_info_for_change_reporter(tree):
293
"""Get the view information from a tree for change reporting."""
296
current_view = tree.views.get_view_info()[0]
297
if current_view is not None:
298
view_info = (current_view, tree.views.lookup_view())
299
except views.ViewsNotSupported:
304
def _open_directory_or_containing_tree_or_branch(filename, directory):
305
"""Open the tree or branch containing the specified file, unless
306
the --directory option is used to specify a different branch."""
307
if directory is not None:
308
return (None, Branch.open(directory), filename)
309
return controldir.ControlDir.open_containing_tree_or_branch(filename)
312
# TODO: Make sure no commands unconditionally use the working directory as a
313
# branch. If a filename argument is used, the first of them should be used to
314
# specify the branch. (Perhaps this can be factored out into some kind of
315
# Argument class, representing a file in a branch, where the first occurrence
318
class cmd_status(Command):
319
__doc__ = """Display status summary.
321
This reports on versioned and unknown files, reporting them
322
grouped by state. Possible states are:
325
Versioned in the working copy but not in the previous revision.
328
Versioned in the previous revision but removed or deleted
332
Path of this file changed from the previous revision;
333
the text may also have changed. This includes files whose
334
parent directory was renamed.
337
Text has changed since the previous revision.
340
File kind has been changed (e.g. from file to directory).
343
Not versioned and not matching an ignore pattern.
345
Additionally for directories, symlinks and files with a changed
346
executable bit, Breezy indicates their type using a trailing
347
character: '/', '@' or '*' respectively. These decorations can be
348
disabled using the '--no-classify' option.
350
To see ignored files use 'brz ignored'. For details on the
351
changes to file texts, use 'brz diff'.
353
Note that --short or -S gives status flags for each item, similar
354
to Subversion's status command. To get output similar to svn -q,
357
If no arguments are specified, the status of the entire working
358
directory is shown. Otherwise, only the status of the specified
359
files or directories is reported. If a directory is given, status
360
is reported for everything inside that directory.
362
Before merges are committed, the pending merge tip revisions are
363
shown. To see all pending merge revisions, use the -v option.
364
To skip the display of pending merge information altogether, use
365
the no-pending option or specify a file/directory.
367
To compare the working directory to a specific revision, pass a
368
single revision to the revision argument.
370
To see which files have changed in a specific revision, or between
371
two revisions, pass a revision range to the revision argument.
372
This will produce the same results as calling 'brz diff --summarize'.
375
# TODO: --no-recurse/-N, --recurse options
377
takes_args = ['file*']
378
takes_options = ['show-ids', 'revision', 'change', 'verbose',
379
Option('short', help='Use short status indicators.',
381
Option('versioned', help='Only show versioned files.',
383
Option('no-pending', help='Don\'t show pending merges.'),
384
Option('no-classify',
385
help='Do not mark object type using indicator.'),
387
aliases = ['st', 'stat']
389
encoding_type = 'replace'
390
_see_also = ['diff', 'revert', 'status-flags']
393
def run(self, show_ids=False, file_list=None, revision=None, short=False,
394
versioned=False, no_pending=False, verbose=False,
396
from .status import show_tree_status
398
if revision and len(revision) > 2:
399
raise errors.CommandError(
400
gettext('brz status --revision takes exactly'
401
' one or two revision specifiers'))
403
tree, relfile_list = WorkingTree.open_containing_paths(file_list)
404
# Avoid asking for specific files when that is not needed.
405
if relfile_list == ['']:
407
# Don't disable pending merges for full trees other than '.'.
408
if file_list == ['.']:
410
# A specific path within a tree was given.
411
elif relfile_list is not None:
413
show_tree_status(tree, show_ids=show_ids,
414
specific_files=relfile_list, revision=revision,
415
to_file=self.outf, short=short, versioned=versioned,
416
show_pending=(not no_pending), verbose=verbose,
417
classify=not no_classify)
420
class cmd_cat_revision(Command):
421
__doc__ = """Write out metadata for a revision.
423
The revision to print can either be specified by a specific
424
revision identifier, or you can use --revision.
428
takes_args = ['revision_id?']
429
takes_options = ['directory', 'revision']
430
# cat-revision is more for frontends so should be exact
433
def print_revision(self, revisions, revid):
434
stream = revisions.get_record_stream([(revid,)], 'unordered', True)
435
record = next(stream)
436
if record.storage_kind == 'absent':
437
raise errors.NoSuchRevision(revisions, revid)
438
revtext = record.get_bytes_as('fulltext')
439
self.outf.write(revtext.decode('utf-8'))
442
def run(self, revision_id=None, revision=None, directory=u'.'):
443
if revision_id is not None and revision is not None:
444
raise errors.CommandError(gettext('You can only supply one of'
445
' revision_id or --revision'))
446
if revision_id is None and revision is None:
447
raise errors.CommandError(
448
gettext('You must supply either --revision or a revision_id'))
450
b = controldir.ControlDir.open_containing_tree_or_branch(directory)[1]
452
revisions = getattr(b.repository, "revisions", None)
453
if revisions is None:
454
raise errors.CommandError(
455
gettext('Repository %r does not support '
456
'access to raw revision texts') % b.repository)
458
with b.repository.lock_read():
459
# TODO: jam 20060112 should cat-revision always output utf-8?
460
if revision_id is not None:
461
revision_id = cache_utf8.encode(revision_id)
463
self.print_revision(revisions, revision_id)
464
except errors.NoSuchRevision:
466
"The repository {0} contains no revision {1}.").format(
467
b.repository.base, revision_id.decode('utf-8'))
468
raise errors.CommandError(msg)
469
elif revision is not None:
472
raise errors.CommandError(
473
gettext('You cannot specify a NULL revision.'))
474
rev_id = rev.as_revision_id(b)
475
self.print_revision(revisions, rev_id)
478
class cmd_remove_tree(Command):
479
__doc__ = """Remove the working tree from a given branch/checkout.
481
Since a lightweight checkout is little more than a working tree
482
this will refuse to run against one.
484
To re-create the working tree, use "brz checkout".
486
_see_also = ['checkout', 'working-trees']
487
takes_args = ['location*']
490
help='Remove the working tree even if it has '
491
'uncommitted or shelved changes.'),
494
def run(self, location_list, force=False):
495
if not location_list:
496
location_list = ['.']
498
for location in location_list:
499
d = controldir.ControlDir.open(location)
502
working = d.open_workingtree()
503
except errors.NoWorkingTree:
504
raise errors.CommandError(
505
gettext("No working tree to remove"))
506
except errors.NotLocalUrl:
507
raise errors.CommandError(
508
gettext("You cannot remove the working tree"
509
" of a remote path"))
511
if (working.has_changes()):
512
raise errors.UncommittedChanges(working)
513
if working.get_shelf_manager().last_shelf() is not None:
514
raise errors.ShelvedChanges(working)
516
if working.user_url != working.branch.user_url:
517
raise errors.CommandError(
518
gettext("You cannot remove the working tree"
519
" from a lightweight checkout"))
521
d.destroy_workingtree()
524
class cmd_repair_workingtree(Command):
525
__doc__ = """Reset the working tree state file.
527
This is not meant to be used normally, but more as a way to recover from
528
filesystem corruption, etc. This rebuilds the working inventory back to a
529
'known good' state. Any new modifications (adding a file, renaming, etc)
530
will be lost, though modified files will still be detected as such.
532
Most users will want something more like "brz revert" or "brz update"
533
unless the state file has become corrupted.
535
By default this attempts to recover the current state by looking at the
536
headers of the state file. If the state file is too corrupted to even do
537
that, you can supply --revision to force the state of the tree.
541
'revision', 'directory',
543
help='Reset the tree even if it doesn\'t appear to be'
548
def run(self, revision=None, directory='.', force=False):
549
tree, _ = WorkingTree.open_containing(directory)
550
self.enter_context(tree.lock_tree_write())
554
except errors.BzrError:
555
pass # There seems to be a real error here, so we'll reset
558
raise errors.CommandError(gettext(
559
'The tree does not appear to be corrupt. You probably'
560
' want "brz revert" instead. Use "--force" if you are'
561
' sure you want to reset the working tree.'))
565
revision_ids = [r.as_revision_id(tree.branch) for r in revision]
567
tree.reset_state(revision_ids)
568
except errors.BzrError:
569
if revision_ids is None:
570
extra = gettext(', the header appears corrupt, try passing '
571
'-r -1 to set the state to the last commit')
574
raise errors.CommandError(
575
gettext('failed to reset the tree state{0}').format(extra))
578
class cmd_revno(Command):
579
__doc__ = """Show current revision number.
581
This is equal to the number of revisions on this branch.
585
takes_args = ['location?']
587
Option('tree', help='Show revno of working tree.'),
592
def run(self, tree=False, location=u'.', revision=None):
593
if revision is not None and tree:
594
raise errors.CommandError(
595
gettext("--tree and --revision can not be used together"))
599
wt = WorkingTree.open_containing(location)[0]
600
self.enter_context(wt.lock_read())
601
except (errors.NoWorkingTree, errors.NotLocalUrl):
602
raise errors.NoWorkingTree(location)
604
revid = wt.last_revision()
606
b = Branch.open_containing(location)[0]
607
self.enter_context(b.lock_read())
609
if len(revision) != 1:
610
raise errors.CommandError(gettext(
611
"Revision numbers only make sense for single "
612
"revisions, not ranges"))
613
revid = revision[0].as_revision_id(b)
615
revid = b.last_revision()
617
revno_t = b.revision_id_to_dotted_revno(revid)
618
except (errors.NoSuchRevision, errors.GhostRevisionsHaveNoRevno):
620
revno = ".".join(str(n) for n in revno_t)
622
self.outf.write(revno + '\n')
625
class cmd_revision_info(Command):
626
__doc__ = """Show revision number and revision id for a given revision identifier.
629
takes_args = ['revision_info*']
632
custom_help('directory', help='Branch to examine, '
633
'rather than the one containing the working directory.'),
634
Option('tree', help='Show revno of working tree.'),
638
def run(self, revision=None, directory=u'.', tree=False,
639
revision_info_list=[]):
642
wt = WorkingTree.open_containing(directory)[0]
644
self.enter_context(wt.lock_read())
645
except (errors.NoWorkingTree, errors.NotLocalUrl):
647
b = Branch.open_containing(directory)[0]
648
self.enter_context(b.lock_read())
650
if revision is not None:
651
revision_ids.extend(rev.as_revision_id(b) for rev in revision)
652
if revision_info_list is not None:
653
for rev_str in revision_info_list:
654
rev_spec = RevisionSpec.from_string(rev_str)
655
revision_ids.append(rev_spec.as_revision_id(b))
656
# No arguments supplied, default to the last revision
657
if len(revision_ids) == 0:
660
raise errors.NoWorkingTree(directory)
661
revision_ids.append(wt.last_revision())
663
revision_ids.append(b.last_revision())
667
for revision_id in revision_ids:
669
dotted_revno = b.revision_id_to_dotted_revno(revision_id)
670
revno = '.'.join(str(i) for i in dotted_revno)
671
except errors.NoSuchRevision:
673
maxlen = max(maxlen, len(revno))
674
revinfos.append((revno, revision_id))
677
for revno, revid in revinfos:
679
'%*s %s\n' % (maxlen, revno, revid.decode('utf-8')))
682
class cmd_add(Command):
683
__doc__ = """Add specified files or directories.
685
In non-recursive mode, all the named items are added, regardless
686
of whether they were previously ignored. A warning is given if
687
any of the named files are already versioned.
689
In recursive mode (the default), files are treated the same way
690
but the behaviour for directories is different. Directories that
691
are already versioned do not give a warning. All directories,
692
whether already versioned or not, are searched for files or
693
subdirectories that are neither versioned or ignored, and these
694
are added. This search proceeds recursively into versioned
695
directories. If no names are given '.' is assumed.
697
A warning will be printed when nested trees are encountered,
698
unless they are explicitly ignored.
700
Therefore simply saying 'brz add' will version all files that
701
are currently unknown.
703
Adding a file whose parent directory is not versioned will
704
implicitly add the parent, and so on up to the root. This means
705
you should never need to explicitly add a directory, they'll just
706
get added when you add a file in the directory.
708
--dry-run will show which files would be added, but not actually
711
--file-ids-from will try to use the file ids from the supplied path.
712
It looks up ids trying to find a matching parent directory with the
713
same filename, and then by pure path. This option is rarely needed
714
but can be useful when adding the same logical file into two
715
branches that will be merged later (without showing the two different
716
adds as a conflict). It is also useful when merging another project
717
into a subdirectory of this one.
719
Any files matching patterns in the ignore list will not be added
720
unless they are explicitly mentioned.
722
In recursive mode, files larger than the configuration option
723
add.maximum_file_size will be skipped. Named items are never skipped due
726
takes_args = ['file*']
729
help="Don't recursively add the contents of directories.",
732
help="Show what would be done, but don't actually do "
735
Option('file-ids-from',
737
help='Lookup file ids from this tree.'),
739
encoding_type = 'replace'
740
_see_also = ['remove', 'ignore']
742
def run(self, file_list, no_recurse=False, dry_run=False, verbose=False,
745
tree, file_list = tree_files_for_add(file_list)
747
if file_ids_from is not None and not tree.supports_setting_file_ids():
749
gettext('Ignoring --file-ids-from, since the tree does not '
750
'support setting file ids.'))
754
if file_ids_from is not None:
756
base_tree, base_path = WorkingTree.open_containing(
758
except errors.NoWorkingTree:
759
base_branch, base_path = Branch.open_containing(
761
base_tree = base_branch.basis_tree()
763
action = breezy.add.AddFromBaseAction(
764
base_tree, base_path, to_file=self.outf,
765
should_print=(not is_quiet()))
767
action = breezy.add.AddWithSkipLargeAction(
768
to_file=self.outf, should_print=(not is_quiet()))
771
self.enter_context(base_tree.lock_read())
772
added, ignored = tree.smart_add(
773
file_list, not no_recurse, action=action, save=not dry_run)
777
for glob in sorted(ignored):
778
for path in ignored[glob]:
780
gettext("ignored {0} matching \"{1}\"\n").format(
784
class cmd_mkdir(Command):
785
__doc__ = """Create a new versioned directory.
787
This is equivalent to creating the directory and then adding it.
790
takes_args = ['dir+']
794
help='No error if existing, make parent directories as needed.',
798
encoding_type = 'replace'
801
def add_file_with_parents(cls, wt, relpath):
802
if wt.is_versioned(relpath):
804
cls.add_file_with_parents(wt, osutils.dirname(relpath))
808
def add_file_single(cls, wt, relpath):
811
def run(self, dir_list, parents=False):
813
add_file = self.add_file_with_parents
815
add_file = self.add_file_single
817
wt, relpath = WorkingTree.open_containing(dir)
822
if e.errno != errno.EEXIST:
826
add_file(wt, relpath)
828
self.outf.write(gettext('added %s\n') % dir)
831
class cmd_relpath(Command):
832
__doc__ = """Show path of a file relative to root"""
834
takes_args = ['filename']
838
def run(self, filename):
839
# TODO: jam 20050106 Can relpath return a munged path if
840
# sys.stdout encoding cannot represent it?
841
tree, relpath = WorkingTree.open_containing(filename)
842
self.outf.write(relpath)
843
self.outf.write('\n')
846
class cmd_inventory(Command):
847
__doc__ = """Show inventory of the current working copy or a revision.
849
It is possible to limit the output to a particular entry
850
type using the --kind option. For example: --kind file.
852
It is also possible to restrict the list of files to a specific
853
set. For example: brz inventory --show-ids this/file
861
Option('include-root',
862
help='Include the entry for the root of the tree, if any.'),
864
help='List entries of a particular kind: file, directory, '
868
takes_args = ['file*']
871
def run(self, revision=None, show_ids=False, kind=None, include_root=False,
873
if kind and kind not in ['file', 'directory', 'symlink']:
874
raise errors.CommandError(
875
gettext('invalid kind %r specified') % (kind,))
877
revision = _get_one_revision('inventory', revision)
878
work_tree, file_list = WorkingTree.open_containing_paths(file_list)
879
self.enter_context(work_tree.lock_read())
880
if revision is not None:
881
tree = revision.as_tree(work_tree.branch)
883
extra_trees = [work_tree]
884
self.enter_context(tree.lock_read())
889
self.enter_context(tree.lock_read())
890
if file_list is not None:
891
paths = tree.find_related_paths_across_trees(
892
file_list, extra_trees, require_versioned=True)
893
# find_ids_across_trees may include some paths that don't
895
entries = tree.iter_entries_by_dir(specific_files=paths)
897
entries = tree.iter_entries_by_dir()
899
for path, entry in sorted(entries):
900
if kind and kind != entry.kind:
902
if path == "" and not include_root:
905
self.outf.write('%-50s %s\n' % (
906
path, entry.file_id.decode('utf-8')))
908
self.outf.write(path)
909
self.outf.write('\n')
912
class cmd_cp(Command):
913
__doc__ = """Copy a file.
916
brz cp OLDNAME NEWNAME
918
brz cp SOURCE... DESTINATION
920
If the last argument is a versioned directory, all the other names
921
are copied into it. Otherwise, there must be exactly two arguments
922
and the file is copied to a new name.
924
Files cannot be copied between branches. Only files can be copied
928
takes_args = ['names*']
931
encoding_type = 'replace'
933
def run(self, names_list):
934
if names_list is None:
936
if len(names_list) < 2:
937
raise errors.CommandError(gettext("missing file argument"))
938
tree, rel_names = WorkingTree.open_containing_paths(
939
names_list, canonicalize=False)
940
for file_name in rel_names[0:-1]:
942
raise errors.CommandError(
943
gettext("can not copy root of branch"))
944
self.enter_context(tree.lock_tree_write())
945
into_existing = osutils.isdir(names_list[-1])
946
if not into_existing:
948
(src, dst) = rel_names
950
raise errors.CommandError(
951
gettext('to copy multiple files the'
952
' destination must be a versioned'
957
(n, osutils.joinpath([rel_names[-1], osutils.basename(n)]))
958
for n in rel_names[:-1]]
960
for src, dst in pairs:
962
src_kind = tree.stored_kind(src)
963
except errors.NoSuchFile:
964
raise errors.CommandError(
965
gettext('Could not copy %s => %s: %s is not versioned.')
968
raise errors.CommandError(
969
gettext('Could not copy %s => %s . %s is not versioned\\.'
971
if src_kind == 'directory':
972
raise errors.CommandError(
973
gettext('Could not copy %s => %s . %s is a directory.'
975
dst_parent = osutils.split(dst)[0]
978
dst_parent_kind = tree.stored_kind(dst_parent)
979
except errors.NoSuchFile:
980
raise errors.CommandError(
981
gettext('Could not copy %s => %s: %s is not versioned.')
982
% (src, dst, dst_parent))
983
if dst_parent_kind != 'directory':
984
raise errors.CommandError(
985
gettext('Could not copy to %s: %s is not a directory.')
986
% (dst_parent, dst_parent))
988
tree.copy_one(src, dst)
991
class cmd_mv(Command):
992
__doc__ = """Move or rename a file.
995
brz mv OLDNAME NEWNAME
997
brz mv SOURCE... DESTINATION
999
If the last argument is a versioned directory, all the other names
1000
are moved into it. Otherwise, there must be exactly two arguments
1001
and the file is changed to a new name.
1003
If OLDNAME does not exist on the filesystem but is versioned and
1004
NEWNAME does exist on the filesystem but is not versioned, mv
1005
assumes that the file has been manually moved and only updates
1006
its internal inventory to reflect that change.
1007
The same is valid when moving many SOURCE files to a DESTINATION.
1009
Files cannot be moved between branches.
1012
takes_args = ['names*']
1013
takes_options = [Option("after", help="Move only the brz identifier"
1014
" of the file, because the file has already been moved."),
1015
Option('auto', help='Automatically guess renames.'),
1017
'dry-run', help='Avoid making changes when guessing renames.'),
1019
aliases = ['move', 'rename']
1020
encoding_type = 'replace'
1022
def run(self, names_list, after=False, auto=False, dry_run=False):
1024
return self.run_auto(names_list, after, dry_run)
1026
raise errors.CommandError(gettext('--dry-run requires --auto.'))
1027
if names_list is None:
1029
if len(names_list) < 2:
1030
raise errors.CommandError(gettext("missing file argument"))
1031
tree, rel_names = WorkingTree.open_containing_paths(
1032
names_list, canonicalize=False)
1033
for file_name in rel_names[0:-1]:
1035
raise errors.CommandError(
1036
gettext("can not move root of branch"))
1037
self.enter_context(tree.lock_tree_write())
1038
self._run(tree, names_list, rel_names, after)
1040
def run_auto(self, names_list, after, dry_run):
1041
if names_list is not None and len(names_list) > 1:
1042
raise errors.CommandError(
1043
gettext('Only one path may be specified to --auto.'))
1045
raise errors.CommandError(
1046
gettext('--after cannot be specified with --auto.'))
1047
work_tree, file_list = WorkingTree.open_containing_paths(
1048
names_list, default_directory='.')
1049
self.enter_context(work_tree.lock_tree_write())
1050
rename_map.RenameMap.guess_renames(
1051
work_tree.basis_tree(), work_tree, dry_run)
1053
def _run(self, tree, names_list, rel_names, after):
1054
into_existing = osutils.isdir(names_list[-1])
1055
if into_existing and len(names_list) == 2:
1057
# a. case-insensitive filesystem and change case of dir
1058
# b. move directory after the fact (if the source used to be
1059
# a directory, but now doesn't exist in the working tree
1060
# and the target is an existing directory, just rename it)
1061
if (not tree.case_sensitive
1062
and rel_names[0].lower() == rel_names[1].lower()):
1063
into_existing = False
1065
# 'fix' the case of a potential 'from'
1066
from_path = tree.get_canonical_path(rel_names[0])
1067
if (not osutils.lexists(names_list[0]) and
1068
tree.is_versioned(from_path) and
1069
tree.stored_kind(from_path) == "directory"):
1070
into_existing = False
1073
# move into existing directory
1074
# All entries reference existing inventory items, so fix them up
1075
# for cicp file-systems.
1076
rel_names = list(tree.get_canonical_paths(rel_names))
1077
for src, dest in tree.move(rel_names[:-1], rel_names[-1], after=after):
1079
self.outf.write("%s => %s\n" % (src, dest))
1081
if len(names_list) != 2:
1082
raise errors.CommandError(gettext('to mv multiple files the'
1083
' destination must be a versioned'
1086
# for cicp file-systems: the src references an existing inventory
1088
src = tree.get_canonical_path(rel_names[0])
1089
# Find the canonical version of the destination: In all cases, the
1090
# parent of the target must be in the inventory, so we fetch the
1091
# canonical version from there (we do not always *use* the
1092
# canonicalized tail portion - we may be attempting to rename the
1094
canon_dest = tree.get_canonical_path(rel_names[1])
1095
dest_parent = osutils.dirname(canon_dest)
1096
spec_tail = osutils.basename(rel_names[1])
1097
# For a CICP file-system, we need to avoid creating 2 inventory
1098
# entries that differ only by case. So regardless of the case
1099
# we *want* to use (ie, specified by the user or the file-system),
1100
# we must always choose to use the case of any existing inventory
1101
# items. The only exception to this is when we are attempting a
1102
# case-only rename (ie, canonical versions of src and dest are
1104
dest_id = tree.path2id(canon_dest)
1105
if dest_id is None or tree.path2id(src) == dest_id:
1106
# No existing item we care about, so work out what case we
1107
# are actually going to use.
1109
# If 'after' is specified, the tail must refer to a file on disk.
1111
dest_parent_fq = osutils.pathjoin(
1112
tree.basedir, dest_parent)
1114
# pathjoin with an empty tail adds a slash, which breaks
1116
dest_parent_fq = tree.basedir
1118
dest_tail = osutils.canonical_relpath(
1120
osutils.pathjoin(dest_parent_fq, spec_tail))
1122
# not 'after', so case as specified is used
1123
dest_tail = spec_tail
1125
# Use the existing item so 'mv' fails with AlreadyVersioned.
1126
dest_tail = os.path.basename(canon_dest)
1127
dest = osutils.pathjoin(dest_parent, dest_tail)
1128
mutter("attempting to move %s => %s", src, dest)
1129
tree.rename_one(src, dest, after=after)
1131
self.outf.write("%s => %s\n" % (src, dest))
1134
class cmd_pull(Command):
1135
__doc__ = """Turn this branch into a mirror of another branch.
1137
By default, this command only works on branches that have not diverged.
1138
Branches are considered diverged if the destination branch's most recent
1139
commit is one that has not been merged (directly or indirectly) into the
1142
If branches have diverged, you can use 'brz merge' to integrate the changes
1143
from one into the other. Once one branch has merged, the other should
1144
be able to pull it again.
1146
If you want to replace your local changes and just want your branch to
1147
match the remote one, use pull --overwrite. This will work even if the two
1148
branches have diverged.
1150
If there is no default location set, the first pull will set it (use
1151
--no-remember to avoid setting it). After that, you can omit the
1152
location to use the default. To change the default, use --remember. The
1153
value will only be saved if the remote location can be accessed.
1155
The --verbose option will display the revisions pulled using the log_format
1156
configuration option. You can use a different format by overriding it with
1157
-Olog_format=<other_format>.
1159
Note: The location can be specified either in the form of a branch,
1160
or in the form of a path to a file containing a merge directive generated
1164
_see_also = ['push', 'update', 'status-flags', 'send']
1165
takes_options = ['remember', 'overwrite', 'revision',
1166
custom_help('verbose',
1167
help='Show logs of pulled revisions.'),
1168
custom_help('directory',
1169
help='Branch to pull into, '
1170
'rather than the one containing the working directory.'),
1172
help="Perform a local pull in a bound "
1173
"branch. Local pulls are not applied to "
1174
"the master branch."
1177
help="Show base revision text in conflicts."),
1178
Option('overwrite-tags',
1179
help="Overwrite tags only."),
1181
takes_args = ['location?']
1182
encoding_type = 'replace'
1184
def run(self, location=None, remember=None, overwrite=False,
1185
revision=None, verbose=False,
1186
directory=None, local=False,
1187
show_base=False, overwrite_tags=False):
1190
overwrite = ["history", "tags"]
1191
elif overwrite_tags:
1192
overwrite = ["tags"]
1195
# FIXME: too much stuff is in the command class
1198
if directory is None:
1201
tree_to = WorkingTree.open_containing(directory)[0]
1202
branch_to = tree_to.branch
1203
self.enter_context(tree_to.lock_write())
1204
except errors.NoWorkingTree:
1206
branch_to = Branch.open_containing(directory)[0]
1207
self.enter_context(branch_to.lock_write())
1209
warning(gettext("No working tree, ignoring --show-base"))
1211
if local and not branch_to.get_bound_location():
1212
raise errors.LocalRequiresBoundBranch()
1214
possible_transports = []
1215
if location is not None:
1217
mergeable = _mod_mergeable.read_mergeable_from_url(
1218
location, possible_transports=possible_transports)
1219
except errors.NotABundle:
1222
stored_loc = branch_to.get_parent()
1223
if location is None:
1224
if stored_loc is None:
1225
raise errors.CommandError(gettext("No pull location known or"
1228
display_url = urlutils.unescape_for_display(stored_loc,
1232
gettext("Using saved parent location: %s\n") % display_url)
1233
location = stored_loc
1235
revision = _get_one_revision('pull', revision)
1236
if mergeable is not None:
1237
if revision is not None:
1238
raise errors.CommandError(gettext(
1239
'Cannot use -r with merge directives or bundles'))
1240
mergeable.install_revisions(branch_to.repository)
1241
base_revision_id, revision_id, verified = \
1242
mergeable.get_merge_request(branch_to.repository)
1243
branch_from = branch_to
1245
branch_from = Branch.open(location,
1246
possible_transports=possible_transports)
1247
self.enter_context(branch_from.lock_read())
1248
# Remembers if asked explicitly or no previous location is set
1250
or (remember is None and branch_to.get_parent() is None)):
1251
# FIXME: This shouldn't be done before the pull
1252
# succeeds... -- vila 2012-01-02
1253
branch_to.set_parent(branch_from.base)
1255
if revision is not None:
1256
revision_id = revision.as_revision_id(branch_from)
1258
if tree_to is not None:
1259
view_info = _get_view_info_for_change_reporter(tree_to)
1260
change_reporter = delta._ChangeReporter(
1261
unversioned_filter=tree_to.is_ignored,
1262
view_info=view_info)
1263
result = tree_to.pull(
1264
branch_from, overwrite, revision_id, change_reporter,
1265
local=local, show_base=show_base)
1267
result = branch_to.pull(
1268
branch_from, overwrite, revision_id, local=local)
1270
result.report(self.outf)
1271
if verbose and result.old_revid != result.new_revid:
1272
log.show_branch_change(
1273
branch_to, self.outf, result.old_revno,
1275
if getattr(result, 'tag_conflicts', None):
1281
class cmd_push(Command):
1282
__doc__ = """Update a mirror of this branch.
1284
The target branch will not have its working tree populated because this
1285
is both expensive, and is not supported on remote file systems.
1287
Some smart servers or protocols *may* put the working tree in place in
1290
This command only works on branches that have not diverged. Branches are
1291
considered diverged if the destination branch's most recent commit is one
1292
that has not been merged (directly or indirectly) by the source branch.
1294
If branches have diverged, you can use 'brz push --overwrite' to replace
1295
the other branch completely, discarding its unmerged changes.
1297
If you want to ensure you have the different changes in the other branch,
1298
do a merge (see brz help merge) from the other branch, and commit that.
1299
After that you will be able to do a push without '--overwrite'.
1301
If there is no default push location set, the first push will set it (use
1302
--no-remember to avoid setting it). After that, you can omit the
1303
location to use the default. To change the default, use --remember. The
1304
value will only be saved if the remote location can be accessed.
1306
The --verbose option will display the revisions pushed using the log_format
1307
configuration option. You can use a different format by overriding it with
1308
-Olog_format=<other_format>.
1311
_see_also = ['pull', 'update', 'working-trees']
1312
takes_options = ['remember', 'overwrite', 'verbose', 'revision',
1313
Option('create-prefix',
1314
help='Create the path leading up to the branch '
1315
'if it does not already exist.'),
1316
custom_help('directory',
1317
help='Branch to push from, '
1318
'rather than the one containing the working directory.'),
1319
Option('use-existing-dir',
1320
help='By default push will fail if the target'
1321
' directory exists, but does not already'
1322
' have a control directory. This flag will'
1323
' allow push to proceed.'),
1325
help='Create a stacked branch that references the public location '
1326
'of the parent branch.'),
1327
Option('stacked-on',
1328
help='Create a stacked branch that refers to another branch '
1329
'for the commit history. Only the work not present in the '
1330
'referenced branch is included in the branch created.',
1333
help='Refuse to push if there are uncommitted changes in'
1334
' the working tree, --no-strict disables the check.'),
1336
help="Don't populate the working tree, even for protocols"
1337
" that support it."),
1338
Option('overwrite-tags',
1339
help="Overwrite tags only."),
1340
Option('lossy', help="Allow lossy push, i.e. dropping metadata "
1341
"that can't be represented in the target.")
1343
takes_args = ['location?']
1344
encoding_type = 'replace'
1346
def run(self, location=None, remember=None, overwrite=False,
1347
create_prefix=False, verbose=False, revision=None,
1348
use_existing_dir=False, directory=None, stacked_on=None,
1349
stacked=False, strict=None, no_tree=False,
1350
overwrite_tags=False, lossy=False):
1351
from .location import location_to_url
1352
from .push import _show_push_branch
1355
overwrite = ["history", "tags"]
1356
elif overwrite_tags:
1357
overwrite = ["tags"]
1361
if directory is None:
1363
# Get the source branch
1365
_unused) = controldir.ControlDir.open_containing_tree_or_branch(directory)
1366
# Get the tip's revision_id
1367
revision = _get_one_revision('push', revision)
1368
if revision is not None:
1369
revision_id = revision.in_history(br_from).rev_id
1372
if tree is not None and revision_id is None:
1373
tree.check_changed_or_out_of_date(
1374
strict, 'push_strict',
1375
more_error='Use --no-strict to force the push.',
1376
more_warning='Uncommitted changes will not be pushed.')
1377
# Get the stacked_on branch, if any
1378
if stacked_on is not None:
1379
stacked_on = location_to_url(stacked_on, 'read')
1380
stacked_on = urlutils.normalize_url(stacked_on)
1382
parent_url = br_from.get_parent()
1384
parent = Branch.open(parent_url)
1385
stacked_on = parent.get_public_branch()
1387
# I considered excluding non-http url's here, thus forcing
1388
# 'public' branches only, but that only works for some
1389
# users, so it's best to just depend on the user spotting an
1390
# error by the feedback given to them. RBC 20080227.
1391
stacked_on = parent_url
1393
raise errors.CommandError(gettext(
1394
"Could not determine branch to refer to."))
1396
# Get the destination location
1397
if location is None:
1398
stored_loc = br_from.get_push_location()
1399
if stored_loc is None:
1400
parent_loc = br_from.get_parent()
1402
raise errors.CommandError(gettext(
1403
"No push location known or specified. To push to the "
1404
"parent branch (at %s), use 'brz push :parent'." %
1405
urlutils.unescape_for_display(parent_loc,
1406
self.outf.encoding)))
1408
raise errors.CommandError(gettext(
1409
"No push location known or specified."))
1411
display_url = urlutils.unescape_for_display(stored_loc,
1413
note(gettext("Using saved push location: %s") % display_url)
1414
location = stored_loc
1416
_show_push_branch(br_from, revision_id, location, self.outf,
1417
verbose=verbose, overwrite=overwrite, remember=remember,
1418
stacked_on=stacked_on, create_prefix=create_prefix,
1419
use_existing_dir=use_existing_dir, no_tree=no_tree,
1423
class cmd_branch(Command):
1424
__doc__ = """Create a new branch that is a copy of an existing branch.
1426
If the TO_LOCATION is omitted, the last component of the FROM_LOCATION will
1427
be used. In other words, "branch ../foo/bar" will attempt to create ./bar.
1428
If the FROM_LOCATION has no / or path separator embedded, the TO_LOCATION
1429
is derived from the FROM_LOCATION by stripping a leading scheme or drive
1430
identifier, if any. For example, "branch lp:foo-bar" will attempt to
1433
To retrieve the branch as of a particular revision, supply the --revision
1434
parameter, as in "branch foo/bar -r 5".
1438
_see_also = ['checkout']
1439
takes_args = ['from_location', 'to_location?']
1440
takes_options = ['revision',
1442
'hardlink', help='Hard-link working tree files where possible.'),
1443
Option('files-from', type=str,
1444
help="Get file contents from this tree."),
1446
help="Create a branch without a working-tree."),
1448
help="Switch the checkout in the current directory "
1449
"to the new branch."),
1451
help='Create a stacked branch referring to the source branch. '
1452
'The new branch will depend on the availability of the source '
1453
'branch for all operations.'),
1454
Option('standalone',
1455
help='Do not use a shared repository, even if available.'),
1456
Option('use-existing-dir',
1457
help='By default branch will fail if the target'
1458
' directory exists, but does not already'
1459
' have a control directory. This flag will'
1460
' allow branch to proceed.'),
1462
help="Bind new branch to from location."),
1463
Option('no-recurse-nested',
1464
help='Do not recursively check out nested trees.'),
1465
Option('colocated-branch', short_name='b',
1466
type=str, help='Name of colocated branch to sprout.'),
1469
def run(self, from_location, to_location=None, revision=None,
1470
hardlink=False, stacked=False, standalone=False, no_tree=False,
1471
use_existing_dir=False, switch=False, bind=False,
1472
files_from=None, no_recurse_nested=False, colocated_branch=None):
1473
from breezy import switch as _mod_switch
1474
accelerator_tree, br_from = controldir.ControlDir.open_tree_or_branch(
1475
from_location, name=colocated_branch)
1476
if no_recurse_nested:
1480
if not (hardlink or files_from):
1481
# accelerator_tree is usually slower because you have to read N
1482
# files (no readahead, lots of seeks, etc), but allow the user to
1483
# explicitly request it
1484
accelerator_tree = None
1485
if files_from is not None and files_from != from_location:
1486
accelerator_tree = WorkingTree.open(files_from)
1487
revision = _get_one_revision('branch', revision)
1488
self.enter_context(br_from.lock_read())
1489
if revision is not None:
1490
revision_id = revision.as_revision_id(br_from)
1492
# FIXME - wt.last_revision, fallback to branch, fall back to
1493
# None or perhaps NULL_REVISION to mean copy nothing
1495
revision_id = br_from.last_revision()
1496
if to_location is None:
1497
to_location = urlutils.derive_to_location(from_location)
1498
to_transport = transport.get_transport(to_location, purpose='write')
1500
to_transport.mkdir('.')
1501
except errors.FileExists:
1503
to_dir = controldir.ControlDir.open_from_transport(
1505
except errors.NotBranchError:
1506
if not use_existing_dir:
1507
raise errors.CommandError(gettext('Target directory "%s" '
1508
'already exists.') % to_location)
1513
to_dir.open_branch()
1514
except errors.NotBranchError:
1517
raise errors.AlreadyBranchError(to_location)
1518
except errors.NoSuchFile:
1519
raise errors.CommandError(gettext('Parent of "%s" does not exist.')
1525
# preserve whatever source format we have.
1526
to_dir = br_from.controldir.sprout(
1527
to_transport.base, revision_id,
1528
possible_transports=[to_transport],
1529
accelerator_tree=accelerator_tree, hardlink=hardlink,
1530
stacked=stacked, force_new_repo=standalone,
1531
create_tree_if_local=not no_tree, source_branch=br_from,
1533
branch = to_dir.open_branch(
1534
possible_transports=[
1535
br_from.controldir.root_transport, to_transport])
1536
except errors.NoSuchRevision:
1537
to_transport.delete_tree('.')
1538
msg = gettext("The branch {0} has no revision {1}.").format(
1539
from_location, revision)
1540
raise errors.CommandError(msg)
1543
to_repo = to_dir.open_repository()
1544
except errors.NoRepositoryPresent:
1545
to_repo = to_dir.create_repository()
1546
to_repo.fetch(br_from.repository, revision_id=revision_id)
1547
branch = br_from.sprout(
1548
to_dir, revision_id=revision_id)
1549
br_from.tags.merge_to(branch.tags)
1551
# If the source branch is stacked, the new branch may
1552
# be stacked whether we asked for that explicitly or not.
1553
# We therefore need a try/except here and not just 'if stacked:'
1555
note(gettext('Created new stacked branch referring to %s.') %
1556
branch.get_stacked_on_url())
1557
except (errors.NotStacked, _mod_branch.UnstackableBranchFormat,
1558
errors.UnstackableRepositoryFormat) as e:
1559
revno = branch.revno()
1560
if revno is not None:
1561
note(ngettext('Branched %d revision.',
1562
'Branched %d revisions.',
1563
branch.revno()) % revno)
1565
note(gettext('Created new branch.'))
1567
# Bind to the parent
1568
parent_branch = Branch.open(from_location)
1569
branch.bind(parent_branch)
1570
note(gettext('New branch bound to %s') % from_location)
1572
# Switch to the new branch
1573
wt, _ = WorkingTree.open_containing('.')
1574
_mod_switch.switch(wt.controldir, branch)
1575
note(gettext('Switched to branch: %s'),
1576
urlutils.unescape_for_display(branch.base, 'utf-8'))
1579
class cmd_branches(Command):
1580
__doc__ = """List the branches available at the current location.
1582
This command will print the names of all the branches at the current
1586
takes_args = ['location?']
1588
Option('recursive', short_name='R',
1589
help='Recursively scan for branches rather than '
1590
'just looking in the specified location.')]
1592
def run(self, location=".", recursive=False):
1594
t = transport.get_transport(location, purpose='read')
1595
if not t.listable():
1596
raise errors.CommandError(
1597
"Can't scan this type of location.")
1598
for b in controldir.ControlDir.find_branches(t):
1599
self.outf.write("%s\n" % urlutils.unescape_for_display(
1600
urlutils.relative_url(t.base, b.base),
1601
self.outf.encoding).rstrip("/"))
1603
dir = controldir.ControlDir.open_containing(location)[0]
1605
active_branch = dir.open_branch(name="")
1606
except errors.NotBranchError:
1607
active_branch = None
1609
for name, branch in iter_sibling_branches(dir):
1612
active = (active_branch is not None and
1613
active_branch.user_url == branch.user_url)
1614
names[name] = active
1615
# Only mention the current branch explicitly if it's not
1616
# one of the colocated branches
1617
if not any(names.values()) and active_branch is not None:
1618
self.outf.write("* %s\n" % gettext("(default)"))
1619
for name in sorted(names):
1620
active = names[name]
1625
self.outf.write("%s %s\n" % (prefix, name))
1628
class cmd_checkout(Command):
1629
__doc__ = """Create a new checkout of an existing branch.
1631
If BRANCH_LOCATION is omitted, checkout will reconstitute a working tree
1632
for the branch found in '.'. This is useful if you have removed the working
1633
tree or if it was never created - i.e. if you pushed the branch to its
1634
current location using SFTP.
1636
If the TO_LOCATION is omitted, the last component of the BRANCH_LOCATION
1637
will be used. In other words, "checkout ../foo/bar" will attempt to create
1638
./bar. If the BRANCH_LOCATION has no / or path separator embedded, the
1639
TO_LOCATION is derived from the BRANCH_LOCATION by stripping a leading
1640
scheme or drive identifier, if any. For example, "checkout lp:foo-bar" will
1641
attempt to create ./foo-bar.
1643
To retrieve the branch as of a particular revision, supply the --revision
1644
parameter, as in "checkout foo/bar -r 5". Note that this will be
1645
immediately out of date [so you cannot commit] but it may be useful (i.e.
1646
to examine old code.)
1649
_see_also = ['checkouts', 'branch', 'working-trees', 'remove-tree']
1650
takes_args = ['branch_location?', 'to_location?']
1651
takes_options = ['revision',
1652
Option('lightweight',
1653
help="Perform a lightweight checkout. Lightweight "
1654
"checkouts depend on access to the branch for "
1655
"every operation. Normal checkouts can perform "
1656
"common operations like diff and status without "
1657
"such access, and also support local commits."
1659
Option('files-from', type=str,
1660
help="Get file contents from this tree."),
1662
help='Hard-link working tree files where possible.'
1667
def run(self, branch_location=None, to_location=None, revision=None,
1668
lightweight=False, files_from=None, hardlink=False):
1669
if branch_location is None:
1670
branch_location = osutils.getcwd()
1671
to_location = branch_location
1672
accelerator_tree, source = controldir.ControlDir.open_tree_or_branch(
1674
if not (hardlink or files_from):
1675
# accelerator_tree is usually slower because you have to read N
1676
# files (no readahead, lots of seeks, etc), but allow the user to
1677
# explicitly request it
1678
accelerator_tree = None
1679
revision = _get_one_revision('checkout', revision)
1680
if files_from is not None and files_from != branch_location:
1681
accelerator_tree = WorkingTree.open(files_from)
1682
if revision is not None:
1683
revision_id = revision.as_revision_id(source)
1686
if to_location is None:
1687
to_location = urlutils.derive_to_location(branch_location)
1688
# if the source and to_location are the same,
1689
# and there is no working tree,
1690
# then reconstitute a branch
1691
if osutils.abspath(to_location) == osutils.abspath(branch_location):
1693
source.controldir.open_workingtree()
1694
except errors.NoWorkingTree:
1695
source.controldir.create_workingtree(revision_id)
1697
source.create_checkout(to_location, revision_id, lightweight,
1698
accelerator_tree, hardlink)
1701
class cmd_clone(Command):
1702
__doc__ = """Clone a control directory.
1705
takes_args = ['from_location', 'to_location?']
1706
takes_options = ['revision',
1707
Option('no-recurse-nested',
1708
help='Do not recursively check out nested trees.'),
1711
def run(self, from_location, to_location=None, revision=None, no_recurse_nested=False):
1712
accelerator_tree, br_from = controldir.ControlDir.open_tree_or_branch(
1714
if no_recurse_nested:
1718
revision = _get_one_revision('branch', revision)
1719
self.enter_context(br_from.lock_read())
1720
if revision is not None:
1721
revision_id = revision.as_revision_id(br_from)
1723
# FIXME - wt.last_revision, fallback to branch, fall back to
1724
# None or perhaps NULL_REVISION to mean copy nothing
1726
revision_id = br_from.last_revision()
1727
if to_location is None:
1728
to_location = urlutils.derive_to_location(from_location)
1729
target_controldir = br_from.controldir.clone(to_location, revision_id=revision_id)
1730
note(gettext('Created new control directory.'))
1733
class cmd_renames(Command):
1734
__doc__ = """Show list of renamed files.
1736
# TODO: Option to show renames between two historical versions.
1738
# TODO: Only show renames under dir, rather than in the whole branch.
1739
_see_also = ['status']
1740
takes_args = ['dir?']
1743
def run(self, dir=u'.'):
1744
tree = WorkingTree.open_containing(dir)[0]
1745
self.enter_context(tree.lock_read())
1746
old_tree = tree.basis_tree()
1747
self.enter_context(old_tree.lock_read())
1749
iterator = tree.iter_changes(old_tree, include_unchanged=True)
1750
for change in iterator:
1751
if change.path[0] == change.path[1]:
1753
if None in change.path:
1755
renames.append(change.path)
1757
for old_name, new_name in renames:
1758
self.outf.write("%s => %s\n" % (old_name, new_name))
1761
class cmd_update(Command):
1762
__doc__ = """Update a working tree to a new revision.
1764
This will perform a merge of the destination revision (the tip of the
1765
branch, or the specified revision) into the working tree, and then make
1766
that revision the basis revision for the working tree.
1768
You can use this to visit an older revision, or to update a working tree
1769
that is out of date from its branch.
1771
If there are any uncommitted changes in the tree, they will be carried
1772
across and remain as uncommitted changes after the update. To discard
1773
these changes, use 'brz revert'. The uncommitted changes may conflict
1774
with the changes brought in by the change in basis revision.
1776
If the tree's branch is bound to a master branch, brz will also update
1777
the branch from the master.
1779
You cannot update just a single file or directory, because each Breezy
1780
working tree has just a single basis revision. If you want to restore a
1781
file that has been removed locally, use 'brz revert' instead of 'brz
1782
update'. If you want to restore a file to its state in a previous
1783
revision, use 'brz revert' with a '-r' option, or use 'brz cat' to write
1784
out the old content of that file to a new location.
1786
The 'dir' argument, if given, must be the location of the root of a
1787
working tree to update. By default, the working tree that contains the
1788
current working directory is used.
1791
_see_also = ['pull', 'working-trees', 'status-flags']
1792
takes_args = ['dir?']
1793
takes_options = ['revision',
1795
help="Show base revision text in conflicts."),
1799
def run(self, dir=None, revision=None, show_base=None):
1800
if revision is not None and len(revision) != 1:
1801
raise errors.CommandError(gettext(
1802
"brz update --revision takes exactly one revision"))
1804
tree = WorkingTree.open_containing('.')[0]
1806
tree, relpath = WorkingTree.open_containing(dir)
1809
raise errors.CommandError(gettext(
1810
"brz update can only update a whole tree, "
1811
"not a file or subdirectory"))
1812
branch = tree.branch
1813
possible_transports = []
1814
master = branch.get_master_branch(
1815
possible_transports=possible_transports)
1816
if master is not None:
1817
branch_location = master.base
1818
self.enter_context(tree.lock_write())
1820
branch_location = tree.branch.base
1821
self.enter_context(tree.lock_tree_write())
1822
# get rid of the final '/' and be ready for display
1823
branch_location = urlutils.unescape_for_display(
1824
branch_location.rstrip('/'),
1826
existing_pending_merges = tree.get_parent_ids()[1:]
1830
# may need to fetch data into a heavyweight checkout
1831
# XXX: this may take some time, maybe we should display a
1833
old_tip = branch.update(possible_transports)
1834
if revision is not None:
1835
revision_id = revision[0].as_revision_id(branch)
1837
revision_id = branch.last_revision()
1838
if revision_id == _mod_revision.ensure_null(tree.last_revision()):
1839
revno = branch.revision_id_to_dotted_revno(revision_id)
1840
note(gettext("Tree is up to date at revision {0} of branch {1}"
1841
).format('.'.join(map(str, revno)), branch_location))
1843
view_info = _get_view_info_for_change_reporter(tree)
1844
change_reporter = delta._ChangeReporter(
1845
unversioned_filter=tree.is_ignored,
1846
view_info=view_info)
1848
conflicts = tree.update(
1850
possible_transports=possible_transports,
1851
revision=revision_id,
1853
show_base=show_base)
1854
except errors.NoSuchRevision as e:
1855
raise errors.CommandError(gettext(
1856
"branch has no revision %s\n"
1857
"brz update --revision only works"
1858
" for a revision in the branch history")
1860
revno = tree.branch.revision_id_to_dotted_revno(
1861
_mod_revision.ensure_null(tree.last_revision()))
1862
note(gettext('Updated to revision {0} of branch {1}').format(
1863
'.'.join(map(str, revno)), branch_location))
1864
parent_ids = tree.get_parent_ids()
1865
if parent_ids[1:] and parent_ids[1:] != existing_pending_merges:
1866
note(gettext('Your local commits will now show as pending merges with '
1867
"'brz status', and can be committed with 'brz commit'."))
1874
class cmd_info(Command):
1875
__doc__ = """Show information about a working tree, branch or repository.
1877
This command will show all known locations and formats associated to the
1878
tree, branch or repository.
1880
In verbose mode, statistical information is included with each report.
1881
To see extended statistic information, use a verbosity level of 2 or
1882
higher by specifying the verbose option multiple times, e.g. -vv.
1884
Branches and working trees will also report any missing revisions.
1888
Display information on the format and related locations:
1892
Display the above together with extended format information and
1893
basic statistics (like the number of files in the working tree and
1894
number of revisions in the branch and repository):
1898
Display the above together with number of committers to the branch:
1902
_see_also = ['revno', 'working-trees', 'repositories']
1903
takes_args = ['location?']
1904
takes_options = ['verbose']
1905
encoding_type = 'replace'
1908
def run(self, location=None, verbose=False):
1910
noise_level = get_verbosity_level()
1913
from .info import show_bzrdir_info
1914
show_bzrdir_info(controldir.ControlDir.open_containing(location)[0],
1915
verbose=noise_level, outfile=self.outf)
1918
class cmd_remove(Command):
1919
__doc__ = """Remove files or directories.
1921
This makes Breezy stop tracking changes to the specified files. Breezy will
1922
delete them if they can easily be recovered using revert otherwise they
1923
will be backed up (adding an extension of the form .~#~). If no options or
1924
parameters are given Breezy will scan for files that are being tracked by
1925
Breezy but missing in your tree and stop tracking them for you.
1927
takes_args = ['file*']
1928
takes_options = ['verbose',
1930
'new', help='Only remove files that have never been committed.'),
1931
RegistryOption.from_kwargs('file-deletion-strategy',
1932
'The file deletion mode to be used.',
1933
title='Deletion Strategy', value_switches=True, enum_switch=False,
1934
safe='Backup changed files (default).',
1935
keep='Delete from brz but leave the working copy.',
1936
no_backup='Don\'t backup changed files.'),
1938
aliases = ['rm', 'del']
1939
encoding_type = 'replace'
1941
def run(self, file_list, verbose=False, new=False,
1942
file_deletion_strategy='safe'):
1944
tree, file_list = WorkingTree.open_containing_paths(file_list)
1946
if file_list is not None:
1947
file_list = [f for f in file_list]
1949
self.enter_context(tree.lock_write())
1950
# Heuristics should probably all move into tree.remove_smart or
1953
added = tree.changes_from(tree.basis_tree(),
1954
specific_files=file_list).added
1955
file_list = sorted([f.path[1] for f in added], reverse=True)
1956
if len(file_list) == 0:
1957
raise errors.CommandError(gettext('No matching files.'))
1958
elif file_list is None:
1959
# missing files show up in iter_changes(basis) as
1960
# versioned-with-no-kind.
1962
for change in tree.iter_changes(tree.basis_tree()):
1963
# Find paths in the working tree that have no kind:
1964
if change.path[1] is not None and change.kind[1] is None:
1965
missing.append(change.path[1])
1966
file_list = sorted(missing, reverse=True)
1967
file_deletion_strategy = 'keep'
1968
tree.remove(file_list, verbose=verbose, to_file=self.outf,
1969
keep_files=file_deletion_strategy == 'keep',
1970
force=(file_deletion_strategy == 'no-backup'))
1973
class cmd_reconcile(Command):
1974
__doc__ = """Reconcile brz metadata in a branch.
1976
This can correct data mismatches that may have been caused by
1977
previous ghost operations or brz upgrades. You should only
1978
need to run this command if 'brz check' or a brz developer
1979
advises you to run it.
1981
If a second branch is provided, cross-branch reconciliation is
1982
also attempted, which will check that data like the tree root
1983
id which was not present in very early brz versions is represented
1984
correctly in both branches.
1986
At the same time it is run it may recompress data resulting in
1987
a potential saving in disk space or performance gain.
1989
The branch *MUST* be on a listable system such as local disk or sftp.
1992
_see_also = ['check']
1993
takes_args = ['branch?']
1995
Option('canonicalize-chks',
1996
help='Make sure CHKs are in canonical form (repairs '
2001
def run(self, branch=".", canonicalize_chks=False):
2002
from .reconcile import reconcile
2003
dir = controldir.ControlDir.open(branch)
2004
reconcile(dir, canonicalize_chks=canonicalize_chks)
2007
class cmd_revision_history(Command):
2008
__doc__ = """Display the list of revision ids on a branch."""
2011
takes_args = ['location?']
2016
def run(self, location="."):
2017
branch = Branch.open_containing(location)[0]
2018
self.enter_context(branch.lock_read())
2019
graph = branch.repository.get_graph()
2020
history = list(graph.iter_lefthand_ancestry(branch.last_revision(),
2021
[_mod_revision.NULL_REVISION]))
2022
for revid in reversed(history):
2023
self.outf.write(revid)
2024
self.outf.write('\n')
2027
class cmd_ancestry(Command):
2028
__doc__ = """List all revisions merged into this branch."""
2030
_see_also = ['log', 'revision-history']
2031
takes_args = ['location?']
2036
def run(self, location="."):
2038
wt = WorkingTree.open_containing(location)[0]
2039
except errors.NoWorkingTree:
2040
b = Branch.open(location)
2041
last_revision = b.last_revision()
2044
last_revision = wt.last_revision()
2046
self.enter_context(b.repository.lock_read())
2047
graph = b.repository.get_graph()
2048
revisions = [revid for revid, parents in
2049
graph.iter_ancestry([last_revision])]
2050
for revision_id in reversed(revisions):
2051
if _mod_revision.is_null(revision_id):
2053
self.outf.write(revision_id.decode('utf-8') + '\n')
2056
class cmd_init(Command):
2057
__doc__ = """Make a directory into a versioned branch.
2059
Use this to create an empty branch, or before importing an
2062
If there is a repository in a parent directory of the location, then
2063
the history of the branch will be stored in the repository. Otherwise
2064
init creates a standalone branch which carries its own history
2065
in the .bzr directory.
2067
If there is already a branch at the location but it has no working tree,
2068
the tree can be populated with 'brz checkout'.
2070
Recipe for importing a tree of files::
2076
brz commit -m "imported project"
2079
_see_also = ['init-shared-repository', 'branch', 'checkout']
2080
takes_args = ['location?']
2082
Option('create-prefix',
2083
help='Create the path leading up to the branch '
2084
'if it does not already exist.'),
2085
RegistryOption('format',
2086
help='Specify a format for this branch. '
2087
'See "help formats" for a full list.',
2088
lazy_registry=('breezy.controldir', 'format_registry'),
2089
converter=lambda name: controldir.format_registry.make_controldir(
2091
value_switches=True,
2092
title="Branch format",
2094
Option('append-revisions-only',
2095
help='Never change revnos or the existing log.'
2096
' Append revisions to it only.'),
2098
'Create a branch without a working tree.')
2101
def run(self, location=None, format=None, append_revisions_only=False,
2102
create_prefix=False, no_tree=False):
2104
format = controldir.format_registry.make_controldir('default')
2105
if location is None:
2108
to_transport = transport.get_transport(location, purpose='write')
2110
# The path has to exist to initialize a
2111
# branch inside of it.
2112
# Just using os.mkdir, since I don't
2113
# believe that we want to create a bunch of
2114
# locations if the user supplies an extended path
2116
to_transport.ensure_base()
2117
except errors.NoSuchFile:
2118
if not create_prefix:
2119
raise errors.CommandError(gettext("Parent directory of %s"
2121
"\nYou may supply --create-prefix to create all"
2122
" leading parent directories.")
2124
to_transport.create_prefix()
2127
a_controldir = controldir.ControlDir.open_from_transport(
2129
except errors.NotBranchError:
2130
# really a NotBzrDir error...
2131
create_branch = controldir.ControlDir.create_branch_convenience
2133
force_new_tree = False
2135
force_new_tree = None
2136
branch = create_branch(to_transport.base, format=format,
2137
possible_transports=[to_transport],
2138
force_new_tree=force_new_tree)
2139
a_controldir = branch.controldir
2141
from .transport.local import LocalTransport
2142
if a_controldir.has_branch():
2143
if (isinstance(to_transport, LocalTransport)
2144
and not a_controldir.has_workingtree()):
2145
raise errors.BranchExistsWithoutWorkingTree(location)
2146
raise errors.AlreadyBranchError(location)
2147
branch = a_controldir.create_branch()
2148
if not no_tree and not a_controldir.has_workingtree():
2149
a_controldir.create_workingtree()
2150
if append_revisions_only:
2152
branch.set_append_revisions_only(True)
2153
except errors.UpgradeRequired:
2154
raise errors.CommandError(gettext('This branch format cannot be set'
2155
' to append-revisions-only. Try --default.'))
2157
from .info import describe_layout, describe_format
2159
tree = a_controldir.open_workingtree(recommend_upgrade=False)
2160
except (errors.NoWorkingTree, errors.NotLocalUrl):
2162
repository = branch.repository
2163
layout = describe_layout(repository, branch, tree).lower()
2164
format = describe_format(a_controldir, repository, branch, tree)
2165
self.outf.write(gettext("Created a {0} (format: {1})\n").format(
2167
if repository.is_shared():
2168
# XXX: maybe this can be refactored into transport.path_or_url()
2169
url = repository.controldir.root_transport.external_url()
2171
url = urlutils.local_path_from_url(url)
2172
except urlutils.InvalidURL:
2174
self.outf.write(gettext("Using shared repository: %s\n") % url)
2177
class cmd_init_shared_repository(Command):
2178
__doc__ = """Create a shared repository for branches to share storage space.
2180
New branches created under the repository directory will store their
2181
revisions in the repository, not in the branch directory. For branches
2182
with shared history, this reduces the amount of storage needed and
2183
speeds up the creation of new branches.
2185
If the --no-trees option is given then the branches in the repository
2186
will not have working trees by default. They will still exist as
2187
directories on disk, but they will not have separate copies of the
2188
files at a certain revision. This can be useful for repositories that
2189
store branches which are interacted with through checkouts or remote
2190
branches, such as on a server.
2193
Create a shared repository holding just branches::
2195
brz init-shared-repo --no-trees repo
2198
Make a lightweight checkout elsewhere::
2200
brz checkout --lightweight repo/trunk trunk-checkout
2205
_see_also = ['init', 'branch', 'checkout', 'repositories']
2206
takes_args = ["location"]
2207
takes_options = [RegistryOption('format',
2208
help='Specify a format for this repository. See'
2209
' "brz help formats" for details.',
2211
'breezy.controldir', 'format_registry'),
2212
converter=lambda name: controldir.format_registry.make_controldir(
2214
value_switches=True, title='Repository format'),
2216
help='Branches in the repository will default to'
2217
' not having a working tree.'),
2219
aliases = ["init-shared-repo", "init-repo"]
2221
def run(self, location, format=None, no_trees=False):
2223
format = controldir.format_registry.make_controldir('default')
2225
if location is None:
2228
to_transport = transport.get_transport(location, purpose='write')
2230
if format.fixed_components:
2231
repo_format_name = None
2233
repo_format_name = format.repository_format.get_format_string()
2235
(repo, newdir, require_stacking, repository_policy) = (
2236
format.initialize_on_transport_ex(to_transport,
2237
create_prefix=True, make_working_trees=not no_trees,
2238
shared_repo=True, force_new_repo=True,
2239
use_existing_dir=True,
2240
repo_format_name=repo_format_name))
2242
from .info import show_bzrdir_info
2243
show_bzrdir_info(newdir, verbose=0, outfile=self.outf)
2246
class cmd_diff(Command):
2247
__doc__ = """Show differences in the working tree, between revisions or branches.
2249
If no arguments are given, all changes for the current tree are listed.
2250
If files are given, only the changes in those files are listed.
2251
Remote and multiple branches can be compared by using the --old and
2252
--new options. If not provided, the default for both is derived from
2253
the first argument, if any, or the current tree if no arguments are
2256
"brz diff -p1" is equivalent to "brz diff --prefix old/:new/", and
2257
produces patches suitable for "patch -p1".
2259
Note that when using the -r argument with a range of revisions, the
2260
differences are computed between the two specified revisions. That
2261
is, the command does not show the changes introduced by the first
2262
revision in the range. This differs from the interpretation of
2263
revision ranges used by "brz log" which includes the first revision
2268
2 - unrepresentable changes
2273
Shows the difference in the working tree versus the last commit::
2277
Difference between the working tree and revision 1::
2281
Difference between revision 3 and revision 1::
2285
Difference between revision 3 and revision 1 for branch xxx::
2289
The changes introduced by revision 2 (equivalent to -r1..2)::
2293
To see the changes introduced by revision X::
2297
Note that in the case of a merge, the -c option shows the changes
2298
compared to the left hand parent. To see the changes against
2299
another parent, use::
2301
brz diff -r<chosen_parent>..X
2303
The changes between the current revision and the previous revision
2304
(equivalent to -c-1 and -r-2..-1)
2308
Show just the differences for file NEWS::
2312
Show the differences in working tree xxx for file NEWS::
2316
Show the differences from branch xxx to this working tree:
2320
Show the differences between two branches for file NEWS::
2322
brz diff --old xxx --new yyy NEWS
2324
Same as 'brz diff' but prefix paths with old/ and new/::
2326
brz diff --prefix old/:new/
2328
Show the differences using a custom diff program with options::
2330
brz diff --using /usr/bin/diff --diff-options -wu
2332
_see_also = ['status']
2333
takes_args = ['file*']
2335
Option('diff-options', type=str,
2336
help='Pass these options to the external diff program.'),
2337
Option('prefix', type=str,
2339
help='Set prefixes added to old and new filenames, as '
2340
'two values separated by a colon. (eg "old/:new/").'),
2342
help='Branch/tree to compare from.',
2346
help='Branch/tree to compare to.',
2352
help='Use this command to compare files.',
2355
RegistryOption('format',
2357
help='Diff format to use.',
2358
lazy_registry=('breezy.diff', 'format_registry'),
2359
title='Diff format'),
2361
help='How many lines of context to show.',
2364
RegistryOption.from_kwargs(
2366
help='Color mode to use.',
2367
title='Color Mode', value_switches=False, enum_switch=True,
2368
never='Never colorize output.',
2369
auto='Only colorize output if terminal supports it and STDOUT is a'
2371
always='Always colorize output (default).'),
2374
help=('Warn if trailing whitespace or spurious changes have been'
2378
aliases = ['di', 'dif']
2379
encoding_type = 'exact'
2382
def run(self, revision=None, file_list=None, diff_options=None,
2383
prefix=None, old=None, new=None, using=None, format=None,
2384
context=None, color='never'):
2385
from .diff import (get_trees_and_branches_to_diff_locked,
2392
elif prefix == u'1' or prefix is None:
2395
elif u':' in prefix:
2396
old_label, new_label = prefix.split(u":")
2398
raise errors.CommandError(gettext(
2399
'--prefix expects two values separated by a colon'
2400
' (eg "old/:new/")'))
2402
if revision and len(revision) > 2:
2403
raise errors.CommandError(gettext('brz diff --revision takes exactly'
2404
' one or two revision specifiers'))
2406
if using is not None and format is not None:
2407
raise errors.CommandError(gettext(
2408
'{0} and {1} are mutually exclusive').format(
2409
'--using', '--format'))
2411
(old_tree, new_tree,
2412
old_branch, new_branch,
2413
specific_files, extra_trees) = get_trees_and_branches_to_diff_locked(
2414
file_list, revision, old, new, self._exit_stack, apply_view=True)
2415
# GNU diff on Windows uses ANSI encoding for filenames
2416
path_encoding = osutils.get_diff_header_encoding()
2419
from .terminal import has_ansi_colors
2420
if has_ansi_colors():
2424
if 'always' == color:
2425
from .colordiff import DiffWriter
2426
outf = DiffWriter(outf)
2427
return show_diff_trees(old_tree, new_tree, outf,
2428
specific_files=specific_files,
2429
external_diff_options=diff_options,
2430
old_label=old_label, new_label=new_label,
2431
extra_trees=extra_trees,
2432
path_encoding=path_encoding,
2433
using=using, context=context,
2437
class cmd_deleted(Command):
2438
__doc__ = """List files deleted in the working tree.
2440
# TODO: Show files deleted since a previous revision, or
2441
# between two revisions.
2442
# TODO: Much more efficient way to do this: read in new
2443
# directories with readdir, rather than stating each one. Same
2444
# level of effort but possibly much less IO. (Or possibly not,
2445
# if the directories are very large...)
2446
_see_also = ['status', 'ls']
2447
takes_options = ['directory', 'show-ids']
2450
def run(self, show_ids=False, directory=u'.'):
2451
tree = WorkingTree.open_containing(directory)[0]
2452
self.enter_context(tree.lock_read())
2453
old = tree.basis_tree()
2454
self.enter_context(old.lock_read())
2455
delta = tree.changes_from(old)
2456
for change in delta.removed:
2457
self.outf.write(change.path[0])
2459
self.outf.write(' ')
2460
self.outf.write(change.file_id)
2461
self.outf.write('\n')
2464
class cmd_modified(Command):
2465
__doc__ = """List files modified in working tree.
2469
_see_also = ['status', 'ls']
2470
takes_options = ['directory', 'null']
2473
def run(self, null=False, directory=u'.'):
2474
tree = WorkingTree.open_containing(directory)[0]
2475
self.enter_context(tree.lock_read())
2476
td = tree.changes_from(tree.basis_tree())
2478
for change in td.modified:
2480
self.outf.write(change.path[1] + '\0')
2482
self.outf.write(osutils.quotefn(change.path[1]) + '\n')
2485
class cmd_added(Command):
2486
__doc__ = """List files added in working tree.
2490
_see_also = ['status', 'ls']
2491
takes_options = ['directory', 'null']
2494
def run(self, null=False, directory=u'.'):
2495
wt = WorkingTree.open_containing(directory)[0]
2496
self.enter_context(wt.lock_read())
2497
basis = wt.basis_tree()
2498
self.enter_context(basis.lock_read())
2499
for path in wt.all_versioned_paths():
2500
if basis.has_filename(path):
2504
if not os.access(osutils.pathjoin(wt.basedir, path), os.F_OK):
2507
self.outf.write(path + '\0')
2509
self.outf.write(osutils.quotefn(path) + '\n')
2512
class cmd_root(Command):
2513
__doc__ = """Show the tree root directory.
2515
The root is the nearest enclosing directory with a control
2518
takes_args = ['filename?']
2521
def run(self, filename=None):
2522
"""Print the branch root."""
2523
tree = WorkingTree.open_containing(filename)[0]
2524
self.outf.write(tree.basedir + '\n')
2527
def _parse_limit(limitstring):
2529
return int(limitstring)
2531
msg = gettext("The limit argument must be an integer.")
2532
raise errors.CommandError(msg)
2535
def _parse_levels(s):
2539
msg = gettext("The levels argument must be an integer.")
2540
raise errors.CommandError(msg)
2543
class cmd_log(Command):
2544
__doc__ = """Show historical log for a branch or subset of a branch.
2546
log is brz's default tool for exploring the history of a branch.
2547
The branch to use is taken from the first parameter. If no parameters
2548
are given, the branch containing the working directory is logged.
2549
Here are some simple examples::
2551
brz log log the current branch
2552
brz log foo.py log a file in its branch
2553
brz log http://server/branch log a branch on a server
2555
The filtering, ordering and information shown for each revision can
2556
be controlled as explained below. By default, all revisions are
2557
shown sorted (topologically) so that newer revisions appear before
2558
older ones and descendants always appear before ancestors. If displayed,
2559
merged revisions are shown indented under the revision in which they
2564
The log format controls how information about each revision is
2565
displayed. The standard log formats are called ``long``, ``short``
2566
and ``line``. The default is long. See ``brz help log-formats``
2567
for more details on log formats.
2569
The following options can be used to control what information is
2572
-l N display a maximum of N revisions
2573
-n N display N levels of revisions (0 for all, 1 for collapsed)
2574
-v display a status summary (delta) for each revision
2575
-p display a diff (patch) for each revision
2576
--show-ids display revision-ids (and file-ids), not just revnos
2578
Note that the default number of levels to display is a function of the
2579
log format. If the -n option is not used, the standard log formats show
2580
just the top level (mainline).
2582
Status summaries are shown using status flags like A, M, etc. To see
2583
the changes explained using words like ``added`` and ``modified``
2584
instead, use the -vv option.
2588
To display revisions from oldest to newest, use the --forward option.
2589
In most cases, using this option will have little impact on the total
2590
time taken to produce a log, though --forward does not incrementally
2591
display revisions like --reverse does when it can.
2593
:Revision filtering:
2595
The -r option can be used to specify what revision or range of revisions
2596
to filter against. The various forms are shown below::
2598
-rX display revision X
2599
-rX.. display revision X and later
2600
-r..Y display up to and including revision Y
2601
-rX..Y display from X to Y inclusive
2603
See ``brz help revisionspec`` for details on how to specify X and Y.
2604
Some common examples are given below::
2606
-r-1 show just the tip
2607
-r-10.. show the last 10 mainline revisions
2608
-rsubmit:.. show what's new on this branch
2609
-rancestor:path.. show changes since the common ancestor of this
2610
branch and the one at location path
2611
-rdate:yesterday.. show changes since yesterday
2613
When logging a range of revisions using -rX..Y, log starts at
2614
revision Y and searches back in history through the primary
2615
("left-hand") parents until it finds X. When logging just the
2616
top level (using -n1), an error is reported if X is not found
2617
along the way. If multi-level logging is used (-n0), X may be
2618
a nested merge revision and the log will be truncated accordingly.
2622
If parameters are given and the first one is not a branch, the log
2623
will be filtered to show only those revisions that changed the
2624
nominated files or directories.
2626
Filenames are interpreted within their historical context. To log a
2627
deleted file, specify a revision range so that the file existed at
2628
the end or start of the range.
2630
Historical context is also important when interpreting pathnames of
2631
renamed files/directories. Consider the following example:
2633
* revision 1: add tutorial.txt
2634
* revision 2: modify tutorial.txt
2635
* revision 3: rename tutorial.txt to guide.txt; add tutorial.txt
2639
* ``brz log guide.txt`` will log the file added in revision 1
2641
* ``brz log tutorial.txt`` will log the new file added in revision 3
2643
* ``brz log -r2 -p tutorial.txt`` will show the changes made to
2644
the original file in revision 2.
2646
* ``brz log -r2 -p guide.txt`` will display an error message as there
2647
was no file called guide.txt in revision 2.
2649
Renames are always followed by log. By design, there is no need to
2650
explicitly ask for this (and no way to stop logging a file back
2651
until it was last renamed).
2655
The --match option can be used for finding revisions that match a
2656
regular expression in a commit message, committer, author or bug.
2657
Specifying the option several times will match any of the supplied
2658
expressions. --match-author, --match-bugs, --match-committer and
2659
--match-message can be used to only match a specific field.
2663
GUI tools and IDEs are often better at exploring history than command
2664
line tools: you may prefer qlog or viz from qbzr or bzr-gtk, the
2665
bzr-explorer shell, or the Loggerhead web interface. See the Bazaar
2666
Plugin Guide <http://doc.bazaar.canonical.com/plugins/en/> and
2667
<http://wiki.bazaar.canonical.com/IDEIntegration>.
2669
You may find it useful to add the aliases below to ``breezy.conf``::
2673
top = log -l10 --line
2676
``brz tip`` will then show the latest revision while ``brz top``
2677
will show the last 10 mainline revisions. To see the details of a
2678
particular revision X, ``brz show -rX``.
2680
If you are interested in looking deeper into a particular merge X,
2681
use ``brz log -n0 -rX``.
2683
``brz log -v`` on a branch with lots of history is currently
2684
very slow. A fix for this issue is currently under development.
2685
With or without that fix, it is recommended that a revision range
2686
be given when using the -v option.
2688
brz has a generic full-text matching plugin, brz-search, that can be
2689
used to find revisions matching user names, commit messages, etc.
2690
Among other features, this plugin can find all revisions containing
2691
a list of words but not others.
2693
When exploring non-mainline history on large projects with deep
2694
history, the performance of log can be greatly improved by installing
2695
the historycache plugin. This plugin buffers historical information
2696
trading disk space for faster speed.
2698
takes_args = ['file*']
2699
_see_also = ['log-formats', 'revisionspec']
2702
help='Show from oldest to newest.'),
2704
custom_help('verbose',
2705
help='Show files changed in each revision.'),
2709
type=breezy.option._parse_revision_str,
2711
help='Show just the specified revision.'
2712
' See also "help revisionspec".'),
2714
RegistryOption('authors',
2715
'What names to list as authors - first, all or committer.',
2718
'breezy.log', 'author_list_registry'),
2722
help='Number of levels to display - 0 for all, 1 for flat.',
2724
type=_parse_levels),
2726
help='Show revisions whose message matches this '
2727
'regular expression.',
2732
help='Limit the output to the first N revisions.',
2737
help='Show changes made in each revision as a patch.'),
2738
Option('include-merged',
2739
help='Show merged revisions like --levels 0 does.'),
2740
Option('include-merges', hidden=True,
2741
help='Historical alias for --include-merged.'),
2742
Option('omit-merges',
2743
help='Do not report commits with more than one parent.'),
2744
Option('exclude-common-ancestry',
2745
help='Display only the revisions that are not part'
2746
' of both ancestries (require -rX..Y).'
2748
Option('signatures',
2749
help='Show digital signature validity.'),
2752
help='Show revisions whose properties match this '
2755
ListOption('match-message',
2756
help='Show revisions whose message matches this '
2759
ListOption('match-committer',
2760
help='Show revisions whose committer matches this '
2763
ListOption('match-author',
2764
help='Show revisions whose authors match this '
2767
ListOption('match-bugs',
2768
help='Show revisions whose bugs match this '
2772
encoding_type = 'replace'
2775
def run(self, file_list=None, timezone='original',
2786
include_merged=None,
2788
exclude_common_ancestry=False,
2792
match_committer=None,
2799
make_log_request_dict,
2800
_get_info_for_log_files,
2802
direction = (forward and 'forward') or 'reverse'
2803
if include_merged is None:
2804
include_merged = False
2805
if (exclude_common_ancestry
2806
and (revision is None or len(revision) != 2)):
2807
raise errors.CommandError(gettext(
2808
'--exclude-common-ancestry requires -r with two revisions'))
2813
raise errors.CommandError(gettext(
2814
'{0} and {1} are mutually exclusive').format(
2815
'--levels', '--include-merged'))
2817
if change is not None:
2819
raise errors.RangeInChangeOption()
2820
if revision is not None:
2821
raise errors.CommandError(gettext(
2822
'{0} and {1} are mutually exclusive').format(
2823
'--revision', '--change'))
2828
filter_by_dir = False
2830
# find the file ids to log and check for directory filtering
2831
b, file_info_list, rev1, rev2 = _get_info_for_log_files(
2832
revision, file_list, self._exit_stack)
2833
for relpath, file_id, kind in file_info_list:
2835
raise errors.CommandError(gettext(
2836
"Path unknown at end or start of revision range: %s") %
2838
# If the relpath is the top of the tree, we log everything
2843
file_ids.append(file_id)
2844
filter_by_dir = filter_by_dir or (
2845
kind in ['directory', 'tree-reference'])
2848
# FIXME ? log the current subdir only RBC 20060203
2849
if revision is not None \
2850
and len(revision) > 0 and revision[0].get_branch():
2851
location = revision[0].get_branch()
2854
dir, relpath = controldir.ControlDir.open_containing(location)
2855
b = dir.open_branch()
2856
self.enter_context(b.lock_read())
2857
rev1, rev2 = _get_revision_range(revision, b, self.name())
2859
if b.get_config_stack().get('validate_signatures_in_log'):
2863
if not gpg.GPGStrategy.verify_signatures_available():
2864
raise errors.GpgmeNotInstalled(None)
2866
# Decide on the type of delta & diff filtering to use
2867
# TODO: add an --all-files option to make this configurable & consistent
2875
diff_type = 'partial'
2879
# Build the log formatter
2880
if log_format is None:
2881
log_format = log.log_formatter_registry.get_default(b)
2882
# Make a non-encoding output to include the diffs - bug 328007
2883
unencoded_output = ui.ui_factory.make_output_stream(
2884
encoding_type='exact')
2885
lf = log_format(show_ids=show_ids, to_file=self.outf,
2886
to_exact_file=unencoded_output,
2887
show_timezone=timezone,
2888
delta_format=get_verbosity_level(),
2890
show_advice=levels is None,
2891
author_list_handler=authors)
2893
# Choose the algorithm for doing the logging. It's annoying
2894
# having multiple code paths like this but necessary until
2895
# the underlying repository format is faster at generating
2896
# deltas or can provide everything we need from the indices.
2897
# The default algorithm - match-using-deltas - works for
2898
# multiple files and directories and is faster for small
2899
# amounts of history (200 revisions say). However, it's too
2900
# slow for logging a single file in a repository with deep
2901
# history, i.e. > 10K revisions. In the spirit of "do no
2902
# evil when adding features", we continue to use the
2903
# original algorithm - per-file-graph - for the "single
2904
# file that isn't a directory without showing a delta" case.
2905
partial_history = revision and b.repository._format.supports_chks
2906
match_using_deltas = (len(file_ids) != 1 or filter_by_dir
2907
or delta_type or partial_history)
2911
match_dict[''] = match
2913
match_dict['message'] = match_message
2915
match_dict['committer'] = match_committer
2917
match_dict['author'] = match_author
2919
match_dict['bugs'] = match_bugs
2921
# Build the LogRequest and execute it
2922
if len(file_ids) == 0:
2924
rqst = make_log_request_dict(
2925
direction=direction, specific_fileids=file_ids,
2926
start_revision=rev1, end_revision=rev2, limit=limit,
2927
message_search=message, delta_type=delta_type,
2928
diff_type=diff_type, _match_using_deltas=match_using_deltas,
2929
exclude_common_ancestry=exclude_common_ancestry, match=match_dict,
2930
signature=signatures, omit_merges=omit_merges,
2932
Logger(b, rqst).show(lf)
2935
def _get_revision_range(revisionspec_list, branch, command_name):
2936
"""Take the input of a revision option and turn it into a revision range.
2938
It returns RevisionInfo objects which can be used to obtain the rev_id's
2939
of the desired revisions. It does some user input validations.
2941
if revisionspec_list is None:
2944
elif len(revisionspec_list) == 1:
2945
rev1 = rev2 = revisionspec_list[0].in_history(branch)
2946
elif len(revisionspec_list) == 2:
2947
start_spec = revisionspec_list[0]
2948
end_spec = revisionspec_list[1]
2949
if end_spec.get_branch() != start_spec.get_branch():
2950
# b is taken from revision[0].get_branch(), and
2951
# show_log will use its revision_history. Having
2952
# different branches will lead to weird behaviors.
2953
raise errors.CommandError(gettext(
2954
"brz %s doesn't accept two revisions in different"
2955
" branches.") % command_name)
2956
if start_spec.spec is None:
2957
# Avoid loading all the history.
2958
rev1 = RevisionInfo(branch, None, None)
2960
rev1 = start_spec.in_history(branch)
2961
# Avoid loading all of history when we know a missing
2962
# end of range means the last revision ...
2963
if end_spec.spec is None:
2964
last_revno, last_revision_id = branch.last_revision_info()
2965
rev2 = RevisionInfo(branch, last_revno, last_revision_id)
2967
rev2 = end_spec.in_history(branch)
2969
raise errors.CommandError(gettext(
2970
'brz %s --revision takes one or two values.') % command_name)
2974
def _revision_range_to_revid_range(revision_range):
2977
if revision_range[0] is not None:
2978
rev_id1 = revision_range[0].rev_id
2979
if revision_range[1] is not None:
2980
rev_id2 = revision_range[1].rev_id
2981
return rev_id1, rev_id2
2984
def get_log_format(long=False, short=False, line=False, default='long'):
2985
log_format = default
2989
log_format = 'short'
2995
class cmd_touching_revisions(Command):
2996
__doc__ = """Return revision-ids which affected a particular file.
2998
A more user-friendly interface is "brz log FILE".
3002
takes_args = ["filename"]
3005
def run(self, filename):
3006
tree, relpath = WorkingTree.open_containing(filename)
3007
with tree.lock_read():
3008
touching_revs = log.find_touching_revisions(
3009
tree.branch.repository, tree.branch.last_revision(), tree, relpath)
3010
for revno, revision_id, what in reversed(list(touching_revs)):
3011
self.outf.write("%6d %s\n" % (revno, what))
3014
class cmd_ls(Command):
3015
__doc__ = """List files in a tree.
3018
_see_also = ['status', 'cat']
3019
takes_args = ['path?']
3023
Option('recursive', short_name='R',
3024
help='Recurse into subdirectories.'),
3026
help='Print paths relative to the root of the branch.'),
3027
Option('unknown', short_name='u',
3028
help='Print unknown files.'),
3029
Option('versioned', help='Print versioned files.',
3031
Option('ignored', short_name='i',
3032
help='Print ignored files.'),
3033
Option('kind', short_name='k',
3034
help=('List entries of a particular kind: file, '
3035
'directory, symlink, tree-reference.'),
3043
def run(self, revision=None, verbose=False,
3044
recursive=False, from_root=False,
3045
unknown=False, versioned=False, ignored=False,
3046
null=False, kind=None, show_ids=False, path=None, directory=None):
3048
if kind and kind not in ('file', 'directory', 'symlink', 'tree-reference'):
3049
raise errors.CommandError(gettext('invalid kind specified'))
3051
if verbose and null:
3052
raise errors.CommandError(
3053
gettext('Cannot set both --verbose and --null'))
3054
all = not (unknown or versioned or ignored)
3056
selection = {'I': ignored, '?': unknown, 'V': versioned}
3062
raise errors.CommandError(gettext('cannot specify both --from-root'
3065
tree, branch, relpath = \
3066
_open_directory_or_containing_tree_or_branch(fs_path, directory)
3068
# Calculate the prefix to use
3072
prefix = relpath + '/'
3073
elif fs_path != '.' and not fs_path.endswith('/'):
3074
prefix = fs_path + '/'
3076
if revision is not None or tree is None:
3077
tree = _get_one_revision_tree('ls', revision, branch=branch)
3080
if isinstance(tree, WorkingTree) and tree.supports_views():
3081
view_files = tree.views.lookup_view()
3084
view_str = views.view_display_str(view_files)
3085
note(gettext("Ignoring files outside view. View is %s") % view_str)
3087
self.enter_context(tree.lock_read())
3088
for fp, fc, fkind, entry in tree.list_files(
3089
include_root=False, from_dir=relpath, recursive=recursive):
3090
# Apply additional masking
3091
if not all and not selection[fc]:
3093
if kind is not None and fkind != kind:
3098
fullpath = osutils.pathjoin(relpath, fp)
3101
views.check_path_in_view(tree, fullpath)
3102
except views.FileOutsideView:
3107
fp = osutils.pathjoin(prefix, fp)
3108
kindch = entry.kind_character()
3109
outstring = fp + kindch
3110
ui.ui_factory.clear_term()
3112
outstring = '%-8s %s' % (fc, outstring)
3113
if show_ids and getattr(entry, 'file_id', None) is not None:
3114
outstring = "%-50s %s" % (outstring, entry.file_id.decode('utf-8'))
3115
self.outf.write(outstring + '\n')
3117
self.outf.write(fp + '\0')
3119
if getattr(entry, 'file_id', None) is not None:
3120
self.outf.write(entry.file_id.decode('utf-8'))
3121
self.outf.write('\0')
3125
if getattr(entry, 'file_id', None) is not None:
3126
my_id = entry.file_id.decode('utf-8')
3129
self.outf.write('%-50s %s\n' % (outstring, my_id))
3131
self.outf.write(outstring + '\n')
3134
class cmd_unknowns(Command):
3135
__doc__ = """List unknown files.
3140
takes_options = ['directory']
3143
def run(self, directory=u'.'):
3144
for f in WorkingTree.open_containing(directory)[0].unknowns():
3145
self.outf.write(osutils.quotefn(f) + '\n')
3148
class cmd_ignore(Command):
3149
__doc__ = """Ignore specified files or patterns.
3151
See ``brz help patterns`` for details on the syntax of patterns.
3153
If a .bzrignore file does not exist, the ignore command
3154
will create one and add the specified files or patterns to the newly
3155
created file. The ignore command will also automatically add the
3156
.bzrignore file to be versioned. Creating a .bzrignore file without
3157
the use of the ignore command will require an explicit add command.
3159
To remove patterns from the ignore list, edit the .bzrignore file.
3160
After adding, editing or deleting that file either indirectly by
3161
using this command or directly by using an editor, be sure to commit
3164
Breezy also supports a global ignore file ~/.config/breezy/ignore. On
3165
Windows the global ignore file can be found in the application data
3167
C:\\Documents and Settings\\<user>\\Application Data\\Breezy\\3.0\\ignore.
3168
Global ignores are not touched by this command. The global ignore file
3169
can be edited directly using an editor.
3171
Patterns prefixed with '!' are exceptions to ignore patterns and take
3172
precedence over regular ignores. Such exceptions are used to specify
3173
files that should be versioned which would otherwise be ignored.
3175
Patterns prefixed with '!!' act as regular ignore patterns, but have
3176
precedence over the '!' exception patterns.
3180
* Ignore patterns containing shell wildcards must be quoted from
3183
* Ignore patterns starting with "#" act as comments in the ignore file.
3184
To ignore patterns that begin with that character, use the "RE:" prefix.
3187
Ignore the top level Makefile::
3189
brz ignore ./Makefile
3191
Ignore .class files in all directories...::
3193
brz ignore "*.class"
3195
...but do not ignore "special.class"::
3197
brz ignore "!special.class"
3199
Ignore files whose name begins with the "#" character::
3203
Ignore .o files under the lib directory::
3205
brz ignore "lib/**/*.o"
3207
Ignore .o files under the lib directory::
3209
brz ignore "RE:lib/.*\\.o"
3211
Ignore everything but the "debian" toplevel directory::
3213
brz ignore "RE:(?!debian/).*"
3215
Ignore everything except the "local" toplevel directory,
3216
but always ignore autosave files ending in ~, even under local/::
3219
brz ignore "!./local"
3223
_see_also = ['status', 'ignored', 'patterns']
3224
takes_args = ['name_pattern*']
3225
takes_options = ['directory',
3226
Option('default-rules',
3227
help='Display the default ignore rules that brz uses.')
3230
def run(self, name_pattern_list=None, default_rules=None,
3232
from breezy import ignores
3233
if default_rules is not None:
3234
# dump the default rules and exit
3235
for pattern in ignores.USER_DEFAULTS:
3236
self.outf.write("%s\n" % pattern)
3238
if not name_pattern_list:
3239
raise errors.CommandError(gettext("ignore requires at least one "
3240
"NAME_PATTERN or --default-rules."))
3241
name_pattern_list = [globbing.normalize_pattern(p)
3242
for p in name_pattern_list]
3244
bad_patterns_count = 0
3245
for p in name_pattern_list:
3246
if not globbing.Globster.is_pattern_valid(p):
3247
bad_patterns_count += 1
3248
bad_patterns += ('\n %s' % p)
3250
msg = (ngettext('Invalid ignore pattern found. %s',
3251
'Invalid ignore patterns found. %s',
3252
bad_patterns_count) % bad_patterns)
3253
ui.ui_factory.show_error(msg)
3254
raise lazy_regex.InvalidPattern('')
3255
for name_pattern in name_pattern_list:
3256
if (name_pattern[0] == '/' or
3257
(len(name_pattern) > 1 and name_pattern[1] == ':')):
3258
raise errors.CommandError(gettext(
3259
"NAME_PATTERN should not be an absolute path"))
3260
tree, relpath = WorkingTree.open_containing(directory)
3261
ignores.tree_ignores_add_patterns(tree, name_pattern_list)
3262
ignored = globbing.Globster(name_pattern_list)
3264
self.enter_context(tree.lock_read())
3265
for filename, fc, fkind, entry in tree.list_files():
3266
id = getattr(entry, 'file_id', None)
3268
if ignored.match(filename):
3269
matches.append(filename)
3270
if len(matches) > 0:
3271
self.outf.write(gettext("Warning: the following files are version "
3272
"controlled and match your ignore pattern:\n%s"
3273
"\nThese files will continue to be version controlled"
3274
" unless you 'brz remove' them.\n") % ("\n".join(matches),))
3277
class cmd_ignored(Command):
3278
__doc__ = """List ignored files and the patterns that matched them.
3280
List all the ignored files and the ignore pattern that caused the file to
3283
Alternatively, to list just the files::
3288
encoding_type = 'replace'
3289
_see_also = ['ignore', 'ls']
3290
takes_options = ['directory']
3293
def run(self, directory=u'.'):
3294
tree = WorkingTree.open_containing(directory)[0]
3295
self.enter_context(tree.lock_read())
3296
for path, file_class, kind, entry in tree.list_files():
3297
if file_class != 'I':
3299
# XXX: Slightly inefficient since this was already calculated
3300
pat = tree.is_ignored(path)
3301
self.outf.write('%-50s %s\n' % (path, pat))
3304
class cmd_lookup_revision(Command):
3305
__doc__ = """Lookup the revision-id from a revision-number
3308
brz lookup-revision 33
3311
takes_args = ['revno']
3312
takes_options = ['directory']
3315
def run(self, revno, directory=u'.'):
3319
raise errors.CommandError(gettext("not a valid revision-number: %r")
3321
revid = WorkingTree.open_containing(
3322
directory)[0].branch.get_rev_id(revno)
3323
self.outf.write("%s\n" % revid.decode('utf-8'))
3326
class cmd_export(Command):
3327
__doc__ = """Export current or past revision to a destination directory or archive.
3329
If no revision is specified this exports the last committed revision.
3331
Format may be an "exporter" name, such as tar, tgz, tbz2. If none is
3332
given, try to find the format with the extension. If no extension
3333
is found exports to a directory (equivalent to --format=dir).
3335
If root is supplied, it will be used as the root directory inside
3336
container formats (tar, zip, etc). If it is not supplied it will default
3337
to the exported filename. The root option has no effect for 'dir' format.
3339
If branch is omitted then the branch containing the current working
3340
directory will be used.
3342
Note: Export of tree with non-ASCII filenames to zip is not supported.
3344
================= =========================
3345
Supported formats Autodetected by extension
3346
================= =========================
3349
tbz2 .tar.bz2, .tbz2
3352
================= =========================
3355
encoding_type = 'exact'
3356
takes_args = ['dest', 'branch_or_subdir?']
3357
takes_options = ['directory',
3359
help="Type of file to export to.",
3362
Option('filters', help='Apply content filters to export the '
3363
'convenient form.'),
3366
help="Name of the root directory inside the exported file."),
3367
Option('per-file-timestamps',
3368
help='Set modification time of files to that of the last '
3369
'revision in which it was changed.'),
3370
Option('uncommitted',
3371
help='Export the working tree contents rather than that of the '
3375
def run(self, dest, branch_or_subdir=None, revision=None, format=None,
3376
root=None, filters=False, per_file_timestamps=False, uncommitted=False,
3378
from .export import export, guess_format, get_root_name
3380
if branch_or_subdir is None:
3381
branch_or_subdir = directory
3383
(tree, b, subdir) = controldir.ControlDir.open_containing_tree_or_branch(
3385
if tree is not None:
3386
self.enter_context(tree.lock_read())
3390
raise errors.CommandError(
3391
gettext("--uncommitted requires a working tree"))
3394
export_tree = _get_one_revision_tree(
3395
'export', revision, branch=b,
3399
format = guess_format(dest)
3402
root = get_root_name(dest)
3404
if not per_file_timestamps:
3405
force_mtime = time.time()
3410
from breezy.filter_tree import ContentFilterTree
3411
export_tree = ContentFilterTree(
3412
export_tree, export_tree._content_filter_stack)
3415
export(export_tree, dest, format, root, subdir,
3416
per_file_timestamps=per_file_timestamps)
3417
except errors.NoSuchExportFormat as e:
3418
raise errors.CommandError(
3419
gettext('Unsupported export format: %s') % e.format)
3422
class cmd_cat(Command):
3423
__doc__ = """Write the contents of a file as of a given revision to standard output.
3425
If no revision is nominated, the last revision is used.
3427
Note: Take care to redirect standard output when using this command on a
3432
takes_options = ['directory',
3433
Option('name-from-revision',
3434
help='The path name in the old tree.'),
3435
Option('filters', help='Apply content filters to display the '
3436
'convenience form.'),
3439
takes_args = ['filename']
3440
encoding_type = 'exact'
3443
def run(self, filename, revision=None, name_from_revision=False,
3444
filters=False, directory=None):
3445
if revision is not None and len(revision) != 1:
3446
raise errors.CommandError(gettext("brz cat --revision takes exactly"
3447
" one revision specifier"))
3448
tree, branch, relpath = \
3449
_open_directory_or_containing_tree_or_branch(filename, directory)
3450
self.enter_context(branch.lock_read())
3451
return self._run(tree, branch, relpath, filename, revision,
3452
name_from_revision, filters)
3454
def _run(self, tree, b, relpath, filename, revision, name_from_revision,
3458
tree = b.basis_tree()
3459
rev_tree = _get_one_revision_tree('cat', revision, branch=b)
3460
self.enter_context(rev_tree.lock_read())
3462
if name_from_revision:
3463
# Try in revision if requested
3464
if not rev_tree.is_versioned(relpath):
3465
raise errors.CommandError(gettext(
3466
"{0!r} is not present in revision {1}").format(
3467
filename, rev_tree.get_revision_id()))
3468
rev_tree_path = relpath
3471
rev_tree_path = _mod_tree.find_previous_path(
3472
tree, rev_tree, relpath)
3473
except errors.NoSuchFile:
3474
rev_tree_path = None
3476
if rev_tree_path is None:
3477
# Path didn't exist in working tree
3478
if not rev_tree.is_versioned(relpath):
3479
raise errors.CommandError(gettext(
3480
"{0!r} is not present in revision {1}").format(
3481
filename, rev_tree.get_revision_id()))
3483
# Fall back to the same path in the basis tree, if present.
3484
rev_tree_path = relpath
3487
from .filter_tree import ContentFilterTree
3488
filter_tree = ContentFilterTree(
3489
rev_tree, rev_tree._content_filter_stack)
3490
fileobj = filter_tree.get_file(rev_tree_path)
3492
fileobj = rev_tree.get_file(rev_tree_path)
3493
shutil.copyfileobj(fileobj, self.outf)
3497
class cmd_local_time_offset(Command):
3498
__doc__ = """Show the offset in seconds from GMT to local time."""
3503
self.outf.write("%s\n" % osutils.local_time_offset())
3506
class cmd_commit(Command):
3507
__doc__ = """Commit changes into a new revision.
3509
An explanatory message needs to be given for each commit. This is
3510
often done by using the --message option (getting the message from the
3511
command line) or by using the --file option (getting the message from
3512
a file). If neither of these options is given, an editor is opened for
3513
the user to enter the message. To see the changed files in the
3514
boilerplate text loaded into the editor, use the --show-diff option.
3516
By default, the entire tree is committed and the person doing the
3517
commit is assumed to be the author. These defaults can be overridden
3522
If selected files are specified, only changes to those files are
3523
committed. If a directory is specified then the directory and
3524
everything within it is committed.
3526
When excludes are given, they take precedence over selected files.
3527
For example, to commit only changes within foo, but not changes
3530
brz commit foo -x foo/bar
3532
A selective commit after a merge is not yet supported.
3536
If the author of the change is not the same person as the committer,
3537
you can specify the author's name using the --author option. The
3538
name should be in the same format as a committer-id, e.g.
3539
"John Doe <jdoe@example.com>". If there is more than one author of
3540
the change you can specify the option multiple times, once for each
3545
A common mistake is to forget to add a new file or directory before
3546
running the commit command. The --strict option checks for unknown
3547
files and aborts the commit if any are found. More advanced pre-commit
3548
checks can be implemented by defining hooks. See ``brz help hooks``
3553
If you accidentally commit the wrong changes or make a spelling
3554
mistake in the commit message say, you can use the uncommit command
3555
to undo it. See ``brz help uncommit`` for details.
3557
Hooks can also be configured to run after a commit. This allows you
3558
to trigger updates to external systems like bug trackers. The --fixes
3559
option can be used to record the association between a revision and
3560
one or more bugs. See ``brz help bugs`` for details.
3563
_see_also = ['add', 'bugs', 'hooks', 'uncommit']
3564
takes_args = ['selected*']
3567
'exclude', type=str, short_name='x',
3568
help="Do not consider changes made to a given path."),
3569
Option('message', type=str,
3571
help="Description of the new revision."),
3574
help='Commit even if nothing has changed.'),
3575
Option('file', type=str,
3578
help='Take commit message from this file.'),
3580
help="Refuse to commit if there are unknown "
3581
"files in the working tree."),
3582
Option('commit-time', type=str,
3583
help="Manually set a commit time using commit date "
3584
"format, e.g. '2009-10-10 08:00:00 +0100'."),
3587
help="Link to a related bug. (see \"brz help bugs\")."),
3590
help="Mark a bug as being fixed by this revision "
3591
"(see \"brz help bugs\")."),
3594
help="Set the author's name, if it's different "
3595
"from the committer."),
3597
help="Perform a local commit in a bound "
3598
"branch. Local commits are not pushed to "
3599
"the master branch until a normal commit "
3602
Option('show-diff', short_name='p',
3603
help='When no message is supplied, show the diff along'
3604
' with the status summary in the message editor.'),
3606
help='When committing to a foreign version control '
3607
'system do not push data that can not be natively '
3609
aliases = ['ci', 'checkin']
3611
def _iter_bug_urls(self, bugs, branch, status):
3612
default_bugtracker = None
3613
# Configure the properties for bug fixing attributes.
3615
tokens = bug.split(':')
3616
if len(tokens) == 1:
3617
if default_bugtracker is None:
3618
branch_config = branch.get_config_stack()
3619
default_bugtracker = branch_config.get(
3621
if default_bugtracker is None:
3622
raise errors.CommandError(gettext(
3623
"No tracker specified for bug %s. Use the form "
3624
"'tracker:id' or specify a default bug tracker "
3625
"using the `bugtracker` option.\nSee "
3626
"\"brz help bugs\" for more information on this "
3627
"feature. Commit refused.") % bug)
3628
tag = default_bugtracker
3630
elif len(tokens) != 2:
3631
raise errors.CommandError(gettext(
3632
"Invalid bug %s. Must be in the form of 'tracker:id'. "
3633
"See \"brz help bugs\" for more information on this "
3634
"feature.\nCommit refused.") % bug)
3636
tag, bug_id = tokens
3638
yield bugtracker.get_bug_url(tag, branch, bug_id), status
3639
except bugtracker.UnknownBugTrackerAbbreviation:
3640
raise errors.CommandError(gettext(
3641
'Unrecognized bug %s. Commit refused.') % bug)
3642
except bugtracker.MalformedBugIdentifier as e:
3643
raise errors.CommandError(gettext(
3644
u"%s\nCommit refused.") % (e,))
3646
def run(self, message=None, file=None, verbose=False, selected_list=None,
3647
unchanged=False, strict=False, local=False, fixes=None, bugs=None,
3648
author=None, show_diff=False, exclude=None, commit_time=None,
3651
from .commit import (
3654
from .errors import (
3658
from .msgeditor import (
3659
edit_commit_message_encoded,
3660
generate_commit_message_template,
3661
make_commit_message_template_encoded,
3665
commit_stamp = offset = None
3666
if commit_time is not None:
3668
commit_stamp, offset = timestamp.parse_patch_date(commit_time)
3669
except ValueError as e:
3670
raise errors.CommandError(gettext(
3671
"Could not parse --commit-time: " + str(e)))
3675
tree, selected_list = WorkingTree.open_containing_paths(selected_list)
3676
if selected_list == ['']:
3677
# workaround - commit of root of tree should be exactly the same
3678
# as just default commit in that tree, and succeed even though
3679
# selected-file merge commit is not done yet
3686
bug_property = bugtracker.encode_fixes_bug_urls(
3688
self._iter_bug_urls(bugs, tree.branch, bugtracker.RELATED),
3689
self._iter_bug_urls(fixes, tree.branch, bugtracker.FIXED)))
3691
properties[u'bugs'] = bug_property
3693
if local and not tree.branch.get_bound_location():
3694
raise errors.LocalRequiresBoundBranch()
3696
if message is not None:
3698
file_exists = osutils.lexists(message)
3699
except UnicodeError:
3700
# The commit message contains unicode characters that can't be
3701
# represented in the filesystem encoding, so that can't be a
3706
'The commit message is a file name: "%(f)s".\n'
3707
'(use --file "%(f)s" to take commit message from that file)'
3709
ui.ui_factory.show_warning(warning_msg)
3711
message = message.replace('\r\n', '\n')
3712
message = message.replace('\r', '\n')
3714
raise errors.CommandError(gettext(
3715
"please specify either --message or --file"))
3717
def get_message(commit_obj):
3718
"""Callback to get commit message"""
3720
with open(file, 'rb') as f:
3721
my_message = f.read().decode(osutils.get_user_encoding())
3722
elif message is not None:
3723
my_message = message
3725
# No message supplied: make one up.
3726
# text is the status of the tree
3727
text = make_commit_message_template_encoded(tree,
3728
selected_list, diff=show_diff,
3729
output_encoding=osutils.get_user_encoding())
3730
# start_message is the template generated from hooks
3731
# XXX: Warning - looks like hooks return unicode,
3732
# make_commit_message_template_encoded returns user encoding.
3733
# We probably want to be using edit_commit_message instead to
3735
my_message = set_commit_message(commit_obj)
3736
if my_message is None:
3737
start_message = generate_commit_message_template(
3739
if start_message is not None:
3740
start_message = start_message.encode(
3741
osutils.get_user_encoding())
3742
my_message = edit_commit_message_encoded(text,
3743
start_message=start_message)
3744
if my_message is None:
3745
raise errors.CommandError(gettext("please specify a commit"
3746
" message with either --message or --file"))
3747
if my_message == "":
3748
raise errors.CommandError(gettext("Empty commit message specified."
3749
" Please specify a commit message with either"
3750
" --message or --file or leave a blank message"
3751
" with --message \"\"."))
3754
# The API permits a commit with a filter of [] to mean 'select nothing'
3755
# but the command line should not do that.
3756
if not selected_list:
3757
selected_list = None
3759
tree.commit(message_callback=get_message,
3760
specific_files=selected_list,
3761
allow_pointless=unchanged, strict=strict, local=local,
3762
reporter=None, verbose=verbose, revprops=properties,
3763
authors=author, timestamp=commit_stamp,
3765
exclude=tree.safe_relpath_files(exclude),
3767
except PointlessCommit:
3768
raise errors.CommandError(gettext("No changes to commit."
3769
" Please 'brz add' the files you want to commit, or use"
3770
" --unchanged to force an empty commit."))
3771
except ConflictsInTree:
3772
raise errors.CommandError(gettext('Conflicts detected in working '
3773
'tree. Use "brz conflicts" to list, "brz resolve FILE" to'
3775
except StrictCommitFailed:
3776
raise errors.CommandError(gettext("Commit refused because there are"
3777
" unknown files in the working tree."))
3778
except errors.BoundBranchOutOfDate as e:
3779
e.extra_help = (gettext("\n"
3780
'To commit to master branch, run update and then commit.\n'
3781
'You can also pass --local to commit to continue working '
3786
class cmd_check(Command):
3787
__doc__ = """Validate working tree structure, branch consistency and repository history.
3789
This command checks various invariants about branch and repository storage
3790
to detect data corruption or brz bugs.
3792
The working tree and branch checks will only give output if a problem is
3793
detected. The output fields of the repository check are:
3796
This is just the number of revisions checked. It doesn't
3800
This is just the number of versionedfiles checked. It
3801
doesn't indicate a problem.
3803
unreferenced ancestors
3804
Texts that are ancestors of other texts, but
3805
are not properly referenced by the revision ancestry. This is a
3806
subtle problem that Breezy can work around.
3809
This is the total number of unique file contents
3810
seen in the checked revisions. It does not indicate a problem.
3813
This is the total number of repeated texts seen
3814
in the checked revisions. Texts can be repeated when their file
3815
entries are modified, but the file contents are not. It does not
3818
If no restrictions are specified, all data that is found at the given
3819
location will be checked.
3823
Check the tree and branch at 'foo'::
3825
brz check --tree --branch foo
3827
Check only the repository at 'bar'::
3829
brz check --repo bar
3831
Check everything at 'baz'::
3836
_see_also = ['reconcile']
3837
takes_args = ['path?']
3838
takes_options = ['verbose',
3839
Option('branch', help="Check the branch related to the"
3840
" current directory."),
3841
Option('repo', help="Check the repository related to the"
3842
" current directory."),
3843
Option('tree', help="Check the working tree related to"
3844
" the current directory.")]
3846
def run(self, path=None, verbose=False, branch=False, repo=False,
3848
from .check import check_dwim
3851
if not branch and not repo and not tree:
3852
branch = repo = tree = True
3853
check_dwim(path, verbose, do_branch=branch, do_repo=repo, do_tree=tree)
3856
class cmd_upgrade(Command):
3857
__doc__ = """Upgrade a repository, branch or working tree to a newer format.
3859
When the default format has changed after a major new release of
3860
Bazaar/Breezy, you may be informed during certain operations that you
3861
should upgrade. Upgrading to a newer format may improve performance
3862
or make new features available. It may however limit interoperability
3863
with older repositories or with older versions of Bazaar or Breezy.
3865
If you wish to upgrade to a particular format rather than the
3866
current default, that can be specified using the --format option.
3867
As a consequence, you can use the upgrade command this way to
3868
"downgrade" to an earlier format, though some conversions are
3869
a one way process (e.g. changing from the 1.x default to the
3870
2.x default) so downgrading is not always possible.
3872
A backup.bzr.~#~ directory is created at the start of the conversion
3873
process (where # is a number). By default, this is left there on
3874
completion. If the conversion fails, delete the new .bzr directory
3875
and rename this one back in its place. Use the --clean option to ask
3876
for the backup.bzr directory to be removed on successful conversion.
3877
Alternatively, you can delete it by hand if everything looks good
3880
If the location given is a shared repository, dependent branches
3881
are also converted provided the repository converts successfully.
3882
If the conversion of a branch fails, remaining branches are still
3885
For more information on upgrades, see the Breezy Upgrade Guide,
3886
https://www.breezy-vcs.org/doc/en/upgrade-guide/.
3889
_see_also = ['check', 'reconcile', 'formats']
3890
takes_args = ['url?']
3892
RegistryOption('format',
3893
help='Upgrade to a specific format. See "brz help'
3894
' formats" for details.',
3895
lazy_registry=('breezy.controldir', 'format_registry'),
3896
converter=lambda name: controldir.format_registry.make_controldir(
3898
value_switches=True, title='Branch format'),
3900
help='Remove the backup.bzr directory if successful.'),
3902
help="Show what would be done, but don't actually do anything."),
3905
def run(self, url='.', format=None, clean=False, dry_run=False):
3906
from .upgrade import upgrade
3907
exceptions = upgrade(url, format, clean_up=clean, dry_run=dry_run)
3909
if len(exceptions) == 1:
3910
# Compatibility with historical behavior
3916
class cmd_whoami(Command):
3917
__doc__ = """Show or set brz user id.
3920
Show the email of the current user::
3924
Set the current user::
3926
brz whoami "Frank Chu <fchu@example.com>"
3928
takes_options = ['directory',
3930
help='Display email address only.'),
3932
help='Set identity for the current branch instead of '
3935
takes_args = ['name?']
3936
encoding_type = 'replace'
3939
def run(self, email=False, branch=False, name=None, directory=None):
3941
if directory is None:
3942
# use branch if we're inside one; otherwise global config
3944
c = Branch.open_containing(u'.')[0].get_config_stack()
3945
except errors.NotBranchError:
3946
c = _mod_config.GlobalStack()
3948
c = Branch.open(directory).get_config_stack()
3949
identity = c.get('email')
3951
self.outf.write(_mod_config.extract_email_address(identity)
3954
self.outf.write(identity + '\n')
3958
raise errors.CommandError(gettext("--email can only be used to display existing "
3961
# display a warning if an email address isn't included in the given name.
3963
_mod_config.extract_email_address(name)
3964
except _mod_config.NoEmailInUsername:
3965
warning('"%s" does not seem to contain an email address. '
3966
'This is allowed, but not recommended.', name)
3968
# use global config unless --branch given
3970
if directory is None:
3971
c = Branch.open_containing(u'.')[0].get_config_stack()
3973
b = Branch.open(directory)
3974
self.enter_context(b.lock_write())
3975
c = b.get_config_stack()
3977
c = _mod_config.GlobalStack()
3978
c.set('email', name)
3981
class cmd_nick(Command):
3982
__doc__ = """Print or set the branch nickname.
3984
If unset, the colocated branch name is used for colocated branches, and
3985
the branch directory name is used for other branches. To print the
3986
current nickname, execute with no argument.
3988
Bound branches use the nickname of its master branch unless it is set
3992
_see_also = ['info']
3993
takes_args = ['nickname?']
3994
takes_options = ['directory']
3996
def run(self, nickname=None, directory=u'.'):
3997
branch = Branch.open_containing(directory)[0]
3998
if nickname is None:
3999
self.printme(branch)
4001
branch.nick = nickname
4004
def printme(self, branch):
4005
self.outf.write('%s\n' % branch.nick)
4008
class cmd_alias(Command):
4009
__doc__ = """Set/unset and display aliases.
4012
Show the current aliases::
4016
Show the alias specified for 'll'::
4020
Set an alias for 'll'::
4022
brz alias ll="log --line -r-10..-1"
4024
To remove an alias for 'll'::
4026
brz alias --remove ll
4029
takes_args = ['name?']
4031
Option('remove', help='Remove the alias.'),
4034
def run(self, name=None, remove=False):
4036
self.remove_alias(name)
4038
self.print_aliases()
4040
equal_pos = name.find('=')
4042
self.print_alias(name)
4044
self.set_alias(name[:equal_pos], name[equal_pos + 1:])
4046
def remove_alias(self, alias_name):
4047
if alias_name is None:
4048
raise errors.CommandError(gettext(
4049
'brz alias --remove expects an alias to remove.'))
4050
# If alias is not found, print something like:
4051
# unalias: foo: not found
4052
c = _mod_config.GlobalConfig()
4053
c.unset_alias(alias_name)
4056
def print_aliases(self):
4057
"""Print out the defined aliases in a similar format to bash."""
4058
aliases = _mod_config.GlobalConfig().get_aliases()
4059
for key, value in sorted(aliases.items()):
4060
self.outf.write('brz alias %s="%s"\n' % (key, value))
4063
def print_alias(self, alias_name):
4064
from .commands import get_alias
4065
alias = get_alias(alias_name)
4067
self.outf.write("brz alias: %s: not found\n" % alias_name)
4070
'brz alias %s="%s"\n' % (alias_name, ' '.join(alias)))
4072
def set_alias(self, alias_name, alias_command):
4073
"""Save the alias in the global config."""
4074
c = _mod_config.GlobalConfig()
4075
c.set_alias(alias_name, alias_command)
4078
class cmd_selftest(Command):
4079
__doc__ = """Run internal test suite.
4081
If arguments are given, they are regular expressions that say which tests
4082
should run. Tests matching any expression are run, and other tests are
4085
Alternatively if --first is given, matching tests are run first and then
4086
all other tests are run. This is useful if you have been working in a
4087
particular area, but want to make sure nothing else was broken.
4089
If --exclude is given, tests that match that regular expression are
4090
excluded, regardless of whether they match --first or not.
4092
To help catch accidential dependencies between tests, the --randomize
4093
option is useful. In most cases, the argument used is the word 'now'.
4094
Note that the seed used for the random number generator is displayed
4095
when this option is used. The seed can be explicitly passed as the
4096
argument to this option if required. This enables reproduction of the
4097
actual ordering used if and when an order sensitive problem is encountered.
4099
If --list-only is given, the tests that would be run are listed. This is
4100
useful when combined with --first, --exclude and/or --randomize to
4101
understand their impact. The test harness reports "Listed nn tests in ..."
4102
instead of "Ran nn tests in ..." when list mode is enabled.
4104
If the global option '--no-plugins' is given, plugins are not loaded
4105
before running the selftests. This has two effects: features provided or
4106
modified by plugins will not be tested, and tests provided by plugins will
4109
Tests that need working space on disk use a common temporary directory,
4110
typically inside $TMPDIR or /tmp.
4112
If you set BRZ_TEST_PDB=1 when running selftest, failing tests will drop
4113
into a pdb postmortem session.
4115
The --coverage=DIRNAME global option produces a report with covered code
4119
Run only tests relating to 'ignore'::
4123
Disable plugins and list tests as they're run::
4125
brz --no-plugins selftest -v
4127
# NB: this is used from the class without creating an instance, which is
4128
# why it does not have a self parameter.
4130
def get_transport_type(typestring):
4131
"""Parse and return a transport specifier."""
4132
if typestring == "sftp":
4133
from .tests import stub_sftp
4134
return stub_sftp.SFTPAbsoluteServer
4135
elif typestring == "memory":
4136
from .tests import test_server
4137
return memory.MemoryServer
4138
elif typestring == "fakenfs":
4139
from .tests import test_server
4140
return test_server.FakeNFSServer
4141
msg = "No known transport type %s. Supported types are: sftp\n" %\
4143
raise errors.CommandError(msg)
4146
takes_args = ['testspecs*']
4147
takes_options = ['verbose',
4149
help='Stop when one test fails.',
4153
help='Use a different transport by default '
4154
'throughout the test suite.',
4155
type=get_transport_type),
4157
help='Run the benchmarks rather than selftests.',
4159
Option('lsprof-timed',
4160
help='Generate lsprof output for benchmarked'
4161
' sections of code.'),
4162
Option('lsprof-tests',
4163
help='Generate lsprof output for each test.'),
4165
help='Run all tests, but run specified tests first.',
4169
help='List the tests instead of running them.'),
4170
RegistryOption('parallel',
4171
help="Run the test suite in parallel.",
4173
'breezy.tests', 'parallel_registry'),
4174
value_switches=False,
4176
Option('randomize', type=str, argname="SEED",
4177
help='Randomize the order of tests using the given'
4178
' seed or "now" for the current time.'),
4179
ListOption('exclude', type=str, argname="PATTERN",
4181
help='Exclude tests that match this regular'
4184
help='Output test progress via subunit v1.'),
4186
help='Output test progress via subunit v2.'),
4187
Option('strict', help='Fail on missing dependencies or '
4189
Option('load-list', type=str, argname='TESTLISTFILE',
4190
help='Load a test id list from a text file.'),
4191
ListOption('debugflag', type=str, short_name='E',
4192
help='Turn on a selftest debug flag.'),
4193
ListOption('starting-with', type=str, argname='TESTID',
4194
param_name='starting_with', short_name='s',
4195
help='Load only the tests starting with TESTID.'),
4197
help="By default we disable fsync and fdatasync"
4198
" while running the test suite.")
4200
encoding_type = 'replace'
4203
Command.__init__(self)
4204
self.additional_selftest_args = {}
4206
def run(self, testspecs_list=None, verbose=False, one=False,
4207
transport=None, benchmark=None,
4209
first=False, list_only=False,
4210
randomize=None, exclude=None, strict=False,
4211
load_list=None, debugflag=None, starting_with=None, subunit1=False,
4212
subunit2=False, parallel=None, lsprof_tests=False, sync=False):
4214
# During selftest, disallow proxying, as it can cause severe
4215
# performance penalties and is only needed for thread
4216
# safety. The selftest command is assumed to not use threads
4217
# too heavily. The call should be as early as possible, as
4218
# error reporting for past duplicate imports won't have useful
4220
if sys.version_info[0] < 3:
4221
# TODO(pad.lv/1696545): Allow proxying on Python 3, since
4222
# disallowing it currently leads to failures in many places.
4223
lazy_import.disallow_proxying()
4227
except ImportError as e:
4228
raise errors.CommandError("tests not available. Install the "
4229
"breezy tests to run the breezy testsuite.")
4231
if testspecs_list is not None:
4232
pattern = '|'.join(testspecs_list)
4237
from .tests import SubUnitBzrRunnerv1
4239
raise errors.CommandError(gettext(
4240
"subunit not available. subunit needs to be installed "
4241
"to use --subunit."))
4242
self.additional_selftest_args['runner_class'] = SubUnitBzrRunnerv1
4243
# On Windows, disable automatic conversion of '\n' to '\r\n' in
4244
# stdout, which would corrupt the subunit stream.
4245
# FIXME: This has been fixed in subunit trunk (>0.0.5) so the
4246
# following code can be deleted when it's sufficiently deployed
4247
# -- vila/mgz 20100514
4248
if (sys.platform == "win32"
4249
and getattr(sys.stdout, 'fileno', None) is not None):
4251
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
4254
from .tests import SubUnitBzrRunnerv2
4256
raise errors.CommandError(gettext(
4257
"subunit not available. subunit "
4258
"needs to be installed to use --subunit2."))
4259
self.additional_selftest_args['runner_class'] = SubUnitBzrRunnerv2
4262
self.additional_selftest_args.setdefault(
4263
'suite_decorators', []).append(parallel)
4265
raise errors.CommandError(gettext(
4266
"--benchmark is no longer supported from brz 2.2; "
4267
"use bzr-usertest instead"))
4268
test_suite_factory = None
4270
exclude_pattern = None
4272
exclude_pattern = '(' + '|'.join(exclude) + ')'
4274
self._disable_fsync()
4275
selftest_kwargs = {"verbose": verbose,
4277
"stop_on_failure": one,
4278
"transport": transport,
4279
"test_suite_factory": test_suite_factory,
4280
"lsprof_timed": lsprof_timed,
4281
"lsprof_tests": lsprof_tests,
4282
"matching_tests_first": first,
4283
"list_only": list_only,
4284
"random_seed": randomize,
4285
"exclude_pattern": exclude_pattern,
4287
"load_list": load_list,
4288
"debug_flags": debugflag,
4289
"starting_with": starting_with
4291
selftest_kwargs.update(self.additional_selftest_args)
4293
# Make deprecation warnings visible, unless -Werror is set
4294
cleanup = symbol_versioning.activate_deprecation_warnings(
4297
result = tests.selftest(**selftest_kwargs)
4300
return int(not result)
4302
def _disable_fsync(self):
4303
"""Change the 'os' functionality to not synchronize."""
4304
self._orig_fsync = getattr(os, 'fsync', None)
4305
if self._orig_fsync is not None:
4306
os.fsync = lambda filedes: None
4307
self._orig_fdatasync = getattr(os, 'fdatasync', None)
4308
if self._orig_fdatasync is not None:
4309
os.fdatasync = lambda filedes: None
4312
class cmd_version(Command):
4313
__doc__ = """Show version of brz."""
4315
encoding_type = 'replace'
4317
Option("short", help="Print just the version number."),
4321
def run(self, short=False):
4322
from .version import show_version
4324
self.outf.write(breezy.version_string + '\n')
4326
show_version(to_file=self.outf)
4329
class cmd_rocks(Command):
4330
__doc__ = """Statement of optimism."""
4336
self.outf.write(gettext("It sure does!\n"))
4339
class cmd_find_merge_base(Command):
4340
__doc__ = """Find and print a base revision for merging two branches."""
4341
# TODO: Options to specify revisions on either side, as if
4342
# merging only part of the history.
4343
takes_args = ['branch', 'other']
4347
def run(self, branch, other):
4348
from .revision import ensure_null
4350
branch1 = Branch.open_containing(branch)[0]
4351
branch2 = Branch.open_containing(other)[0]
4352
self.enter_context(branch1.lock_read())
4353
self.enter_context(branch2.lock_read())
4354
last1 = ensure_null(branch1.last_revision())
4355
last2 = ensure_null(branch2.last_revision())
4357
graph = branch1.repository.get_graph(branch2.repository)
4358
base_rev_id = graph.find_unique_lca(last1, last2)
4360
self.outf.write(gettext('merge base is revision %s\n') %
4361
base_rev_id.decode('utf-8'))
4364
class cmd_merge(Command):
4365
__doc__ = """Perform a three-way merge.
4367
The source of the merge can be specified either in the form of a branch,
4368
or in the form of a path to a file containing a merge directive generated
4369
with brz send. If neither is specified, the default is the upstream branch
4370
or the branch most recently merged using --remember. The source of the
4371
merge may also be specified in the form of a path to a file in another
4372
branch: in this case, only the modifications to that file are merged into
4373
the current working tree.
4375
When merging from a branch, by default brz will try to merge in all new
4376
work from the other branch, automatically determining an appropriate base
4377
revision. If this fails, you may need to give an explicit base.
4379
To pick a different ending revision, pass "--revision OTHER". brz will
4380
try to merge in all new work up to and including revision OTHER.
4382
If you specify two values, "--revision BASE..OTHER", only revisions BASE
4383
through OTHER, excluding BASE but including OTHER, will be merged. If this
4384
causes some revisions to be skipped, i.e. if the destination branch does
4385
not already contain revision BASE, such a merge is commonly referred to as
4386
a "cherrypick". Unlike a normal merge, Breezy does not currently track
4387
cherrypicks. The changes look like a normal commit, and the history of the
4388
changes from the other branch is not stored in the commit.
4390
Revision numbers are always relative to the source branch.
4392
Merge will do its best to combine the changes in two branches, but there
4393
are some kinds of problems only a human can fix. When it encounters those,
4394
it will mark a conflict. A conflict means that you need to fix something,
4395
before you can commit.
4397
Use brz resolve when you have fixed a problem. See also brz conflicts.
4399
If there is no default branch set, the first merge will set it (use
4400
--no-remember to avoid setting it). After that, you can omit the branch
4401
to use the default. To change the default, use --remember. The value will
4402
only be saved if the remote location can be accessed.
4404
The results of the merge are placed into the destination working
4405
directory, where they can be reviewed (with brz diff), tested, and then
4406
committed to record the result of the merge.
4408
merge refuses to run if there are any uncommitted changes, unless
4409
--force is given. If --force is given, then the changes from the source
4410
will be merged with the current working tree, including any uncommitted
4411
changes in the tree. The --force option can also be used to create a
4412
merge revision which has more than two parents.
4414
If one would like to merge changes from the working tree of the other
4415
branch without merging any committed revisions, the --uncommitted option
4418
To select only some changes to merge, use "merge -i", which will prompt
4419
you to apply each diff hunk and file change, similar to "shelve".
4422
To merge all new revisions from brz.dev::
4424
brz merge ../brz.dev
4426
To merge changes up to and including revision 82 from brz.dev::
4428
brz merge -r 82 ../brz.dev
4430
To merge the changes introduced by 82, without previous changes::
4432
brz merge -r 81..82 ../brz.dev
4434
To apply a merge directive contained in /tmp/merge::
4436
brz merge /tmp/merge
4438
To create a merge revision with three parents from two branches
4439
feature1a and feature1b:
4441
brz merge ../feature1a
4442
brz merge ../feature1b --force
4443
brz commit -m 'revision with three parents'
4446
encoding_type = 'exact'
4447
_see_also = ['update', 'remerge', 'status-flags', 'send']
4448
takes_args = ['location?']
4453
help='Merge even if the destination tree has uncommitted changes.'),
4457
Option('show-base', help="Show base revision text in "
4459
Option('uncommitted', help='Apply uncommitted changes'
4460
' from a working copy, instead of branch changes.'),
4461
Option('pull', help='If the destination is already'
4462
' completely merged into the source, pull from the'
4463
' source rather than merging. When this happens,'
4464
' you do not need to commit the result.'),
4465
custom_help('directory',
4466
help='Branch to merge into, '
4467
'rather than the one containing the working directory.'),
4468
Option('preview', help='Instead of merging, show a diff of the'
4470
Option('interactive', help='Select changes interactively.',
4474
def run(self, location=None, revision=None, force=False,
4475
merge_type=None, show_base=False, reprocess=None, remember=None,
4476
uncommitted=False, pull=False,
4481
if merge_type is None:
4482
merge_type = _mod_merge.Merge3Merger
4484
if directory is None:
4486
possible_transports = []
4488
allow_pending = True
4489
verified = 'inapplicable'
4491
tree = WorkingTree.open_containing(directory)[0]
4492
if tree.branch.last_revision() == _mod_revision.NULL_REVISION:
4493
raise errors.CommandError(gettext('Merging into empty branches not currently supported, '
4494
'https://bugs.launchpad.net/bzr/+bug/308562'))
4496
# die as quickly as possible if there are uncommitted changes
4498
if tree.has_changes():
4499
raise errors.UncommittedChanges(tree)
4501
view_info = _get_view_info_for_change_reporter(tree)
4502
change_reporter = delta._ChangeReporter(
4503
unversioned_filter=tree.is_ignored, view_info=view_info)
4504
pb = ui.ui_factory.nested_progress_bar()
4505
self.enter_context(pb)
4506
self.enter_context(tree.lock_write())
4507
if location is not None:
4509
mergeable = _mod_mergeable.read_mergeable_from_url(
4510
location, possible_transports=possible_transports)
4511
except errors.NotABundle:
4515
raise errors.CommandError(gettext('Cannot use --uncommitted'
4516
' with bundles or merge directives.'))
4518
if revision is not None:
4519
raise errors.CommandError(gettext(
4520
'Cannot use -r with merge directives or bundles'))
4521
merger, verified = _mod_merge.Merger.from_mergeable(tree,
4524
if merger is None and uncommitted:
4525
if revision is not None and len(revision) > 0:
4526
raise errors.CommandError(gettext('Cannot use --uncommitted and'
4527
' --revision at the same time.'))
4528
merger = self.get_merger_from_uncommitted(tree, location, None)
4529
allow_pending = False
4532
merger, allow_pending = self._get_merger_from_branch(tree,
4533
location, revision, remember, possible_transports, None)
4535
merger.merge_type = merge_type
4536
merger.reprocess = reprocess
4537
merger.show_base = show_base
4538
self.sanity_check_merger(merger)
4539
if (merger.base_rev_id == merger.other_rev_id and
4540
merger.other_rev_id is not None):
4541
# check if location is a nonexistent file (and not a branch) to
4542
# disambiguate the 'Nothing to do'
4543
if merger.interesting_files:
4544
if not merger.other_tree.has_filename(
4545
merger.interesting_files[0]):
4546
note(gettext("merger: ") + str(merger))
4547
raise errors.PathsDoNotExist([location])
4548
note(gettext('Nothing to do.'))
4550
if pull and not preview:
4551
if merger.interesting_files is not None:
4552
raise errors.CommandError(
4553
gettext('Cannot pull individual files'))
4554
if (merger.base_rev_id == tree.last_revision()):
4555
result = tree.pull(merger.other_branch, False,
4556
merger.other_rev_id)
4557
result.report(self.outf)
4559
if merger.this_basis is None:
4560
raise errors.CommandError(gettext(
4561
"This branch has no commits."
4562
" (perhaps you would prefer 'brz pull')"))
4564
return self._do_preview(merger)
4566
return self._do_interactive(merger)
4568
return self._do_merge(merger, change_reporter, allow_pending,
4571
def _get_preview(self, merger):
4572
tree_merger = merger.make_merger()
4573
tt = tree_merger.make_preview_transform()
4574
self.enter_context(tt)
4575
result_tree = tt.get_preview_tree()
4578
def _do_preview(self, merger):
4579
from .diff import show_diff_trees
4580
result_tree = self._get_preview(merger)
4581
path_encoding = osutils.get_diff_header_encoding()
4582
show_diff_trees(merger.this_tree, result_tree, self.outf,
4583
old_label='', new_label='',
4584
path_encoding=path_encoding)
4586
def _do_merge(self, merger, change_reporter, allow_pending, verified):
4587
merger.change_reporter = change_reporter
4588
conflict_count = merger.do_merge()
4590
merger.set_pending()
4591
if verified == 'failed':
4592
warning('Preview patch does not match changes')
4593
if conflict_count != 0:
4598
def _do_interactive(self, merger):
4599
"""Perform an interactive merge.
4601
This works by generating a preview tree of the merge, then using
4602
Shelver to selectively remove the differences between the working tree
4603
and the preview tree.
4605
from . import shelf_ui
4606
result_tree = self._get_preview(merger)
4607
writer = breezy.option.diff_writer_registry.get()
4608
shelver = shelf_ui.Shelver(merger.this_tree, result_tree, destroy=True,
4609
reporter=shelf_ui.ApplyReporter(),
4610
diff_writer=writer(self.outf))
4616
def sanity_check_merger(self, merger):
4617
if (merger.show_base and
4618
merger.merge_type is not _mod_merge.Merge3Merger):
4619
raise errors.CommandError(gettext("Show-base is not supported for this"
4620
" merge type. %s") % merger.merge_type)
4621
if merger.reprocess is None:
4622
if merger.show_base:
4623
merger.reprocess = False
4625
# Use reprocess if the merger supports it
4626
merger.reprocess = merger.merge_type.supports_reprocess
4627
if merger.reprocess and not merger.merge_type.supports_reprocess:
4628
raise errors.CommandError(gettext("Conflict reduction is not supported"
4629
" for merge type %s.") %
4631
if merger.reprocess and merger.show_base:
4632
raise errors.CommandError(gettext("Cannot do conflict reduction and"
4635
if (merger.merge_type.requires_file_merge_plan and
4636
(not getattr(merger.this_tree, 'plan_file_merge', None) or
4637
not getattr(merger.other_tree, 'plan_file_merge', None) or
4638
(merger.base_tree is not None and
4639
not getattr(merger.base_tree, 'plan_file_merge', None)))):
4640
raise errors.CommandError(
4641
gettext('Plan file merge unsupported: '
4642
'Merge type incompatible with tree formats.'))
4644
def _get_merger_from_branch(self, tree, location, revision, remember,
4645
possible_transports, pb):
4646
"""Produce a merger from a location, assuming it refers to a branch."""
4647
# find the branch locations
4648
other_loc, user_location = self._select_branch_location(tree, location,
4650
if revision is not None and len(revision) == 2:
4651
base_loc, _unused = self._select_branch_location(tree,
4652
location, revision, 0)
4654
base_loc = other_loc
4656
other_branch, other_path = Branch.open_containing(other_loc,
4657
possible_transports)
4658
if base_loc == other_loc:
4659
base_branch = other_branch
4661
base_branch, base_path = Branch.open_containing(base_loc,
4662
possible_transports)
4663
# Find the revision ids
4664
other_revision_id = None
4665
base_revision_id = None
4666
if revision is not None:
4667
if len(revision) >= 1:
4668
other_revision_id = revision[-1].as_revision_id(other_branch)
4669
if len(revision) == 2:
4670
base_revision_id = revision[0].as_revision_id(base_branch)
4671
if other_revision_id is None:
4672
other_revision_id = _mod_revision.ensure_null(
4673
other_branch.last_revision())
4674
# Remember where we merge from. We need to remember if:
4675
# - user specify a location (and we don't merge from the parent
4677
# - user ask to remember or there is no previous location set to merge
4678
# from and user didn't ask to *not* remember
4679
if (user_location is not None
4681
(remember is None and
4682
tree.branch.get_submit_branch() is None)))):
4683
tree.branch.set_submit_branch(other_branch.base)
4684
# Merge tags (but don't set them in the master branch yet, the user
4685
# might revert this merge). Commit will propagate them.
4686
other_branch.tags.merge_to(tree.branch.tags, ignore_master=True)
4687
merger = _mod_merge.Merger.from_revision_ids(tree,
4688
other_revision_id, base_revision_id, other_branch, base_branch)
4689
if other_path != '':
4690
allow_pending = False
4691
merger.interesting_files = [other_path]
4693
allow_pending = True
4694
return merger, allow_pending
4696
def get_merger_from_uncommitted(self, tree, location, pb):
4697
"""Get a merger for uncommitted changes.
4699
:param tree: The tree the merger should apply to.
4700
:param location: The location containing uncommitted changes.
4701
:param pb: The progress bar to use for showing progress.
4703
location = self._select_branch_location(tree, location)[0]
4704
other_tree, other_path = WorkingTree.open_containing(location)
4705
merger = _mod_merge.Merger.from_uncommitted(tree, other_tree, pb)
4706
if other_path != '':
4707
merger.interesting_files = [other_path]
4710
def _select_branch_location(self, tree, user_location, revision=None,
4712
"""Select a branch location, according to possible inputs.
4714
If provided, branches from ``revision`` are preferred. (Both
4715
``revision`` and ``index`` must be supplied.)
4717
Otherwise, the ``location`` parameter is used. If it is None, then the
4718
``submit`` or ``parent`` location is used, and a note is printed.
4720
:param tree: The working tree to select a branch for merging into
4721
:param location: The location entered by the user
4722
:param revision: The revision parameter to the command
4723
:param index: The index to use for the revision parameter. Negative
4724
indices are permitted.
4725
:return: (selected_location, user_location). The default location
4726
will be the user-entered location.
4728
if (revision is not None and index is not None
4729
and revision[index] is not None):
4730
branch = revision[index].get_branch()
4731
if branch is not None:
4732
return branch, branch
4733
if user_location is None:
4734
location = self._get_remembered(tree, 'Merging from')
4736
location = user_location
4737
return location, user_location
4739
def _get_remembered(self, tree, verb_string):
4740
"""Use tree.branch's parent if none was supplied.
4742
Report if the remembered location was used.
4744
stored_location = tree.branch.get_submit_branch()
4745
stored_location_type = "submit"
4746
if stored_location is None:
4747
stored_location = tree.branch.get_parent()
4748
stored_location_type = "parent"
4749
mutter("%s", stored_location)
4750
if stored_location is None:
4751
raise errors.CommandError(
4752
gettext("No location specified or remembered"))
4753
display_url = urlutils.unescape_for_display(stored_location, 'utf-8')
4754
note(gettext("{0} remembered {1} location {2}").format(verb_string,
4755
stored_location_type, display_url))
4756
return stored_location
4759
class cmd_remerge(Command):
4760
__doc__ = """Redo a merge.
4762
Use this if you want to try a different merge technique while resolving
4763
conflicts. Some merge techniques are better than others, and remerge
4764
lets you try different ones on different files.
4766
The options for remerge have the same meaning and defaults as the ones for
4767
merge. The difference is that remerge can (only) be run when there is a
4768
pending merge, and it lets you specify particular files.
4771
Re-do the merge of all conflicted files, and show the base text in
4772
conflict regions, in addition to the usual THIS and OTHER texts::
4774
brz remerge --show-base
4776
Re-do the merge of "foobar", using the weave merge algorithm, with
4777
additional processing to reduce the size of conflict regions::
4779
brz remerge --merge-type weave --reprocess foobar
4781
takes_args = ['file*']
4786
help="Show base revision text in conflicts."),
4789
def run(self, file_list=None, merge_type=None, show_base=False,
4791
from .conflicts import restore
4792
if merge_type is None:
4793
merge_type = _mod_merge.Merge3Merger
4794
tree, file_list = WorkingTree.open_containing_paths(file_list)
4795
self.enter_context(tree.lock_write())
4796
parents = tree.get_parent_ids()
4797
if len(parents) != 2:
4798
raise errors.CommandError(
4799
gettext("Sorry, remerge only works after normal"
4800
" merges. Not cherrypicking or multi-merges."))
4801
interesting_files = None
4803
conflicts = tree.conflicts()
4804
if file_list is not None:
4805
interesting_files = set()
4806
for filename in file_list:
4807
if not tree.is_versioned(filename):
4808
raise errors.NotVersionedError(filename)
4809
interesting_files.add(filename)
4810
if tree.kind(filename) != "directory":
4813
for path, ie in tree.iter_entries_by_dir(
4814
specific_files=[filename]):
4815
interesting_files.add(path)
4816
new_conflicts = conflicts.select_conflicts(tree, file_list)[0]
4818
# Remerge only supports resolving contents conflicts
4819
allowed_conflicts = ('text conflict', 'contents conflict')
4820
restore_files = [c.path for c in conflicts
4821
if c.typestring in allowed_conflicts]
4822
_mod_merge.transform_tree(tree, tree.basis_tree(), interesting_files)
4823
tree.set_conflicts(ConflictList(new_conflicts))
4824
if file_list is not None:
4825
restore_files = file_list
4826
for filename in restore_files:
4828
restore(tree.abspath(filename))
4829
except errors.NotConflicted:
4831
# Disable pending merges, because the file texts we are remerging
4832
# have not had those merges performed. If we use the wrong parents
4833
# list, we imply that the working tree text has seen and rejected
4834
# all the changes from the other tree, when in fact those changes
4835
# have not yet been seen.
4836
tree.set_parent_ids(parents[:1])
4838
merger = _mod_merge.Merger.from_revision_ids(tree, parents[1])
4839
merger.interesting_files = interesting_files
4840
merger.merge_type = merge_type
4841
merger.show_base = show_base
4842
merger.reprocess = reprocess
4843
conflicts = merger.do_merge()
4845
tree.set_parent_ids(parents)
4852
class cmd_revert(Command):
4854
Set files in the working tree back to the contents of a previous revision.
4856
Giving a list of files will revert only those files. Otherwise, all files
4857
will be reverted. If the revision is not specified with '--revision', the
4858
working tree basis revision is used. A revert operation affects only the
4859
working tree, not any revision history like the branch and repository or
4860
the working tree basis revision.
4862
To remove only some changes, without reverting to a prior version, use
4863
merge instead. For example, "merge . -r -2..-3" (don't forget the ".")
4864
will remove the changes introduced by the second last commit (-2), without
4865
affecting the changes introduced by the last commit (-1). To remove
4866
certain changes on a hunk-by-hunk basis, see the shelve command.
4867
To update the branch to a specific revision or the latest revision and
4868
update the working tree accordingly while preserving local changes, see the
4871
Uncommitted changes to files that are reverted will be discarded.
4872
However, by default, any files that have been manually changed will be
4873
backed up first. (Files changed only by merge are not backed up.) Backup
4874
files have '.~#~' appended to their name, where # is a number.
4876
When you provide files, you can use their current pathname or the pathname
4877
from the target revision. So you can use revert to "undelete" a file by
4878
name. If you name a directory, all the contents of that directory will be
4881
If you have newly added files since the target revision, they will be
4882
removed. If the files to be removed have been changed, backups will be
4883
created as above. Directories containing unknown files will not be
4886
The working tree contains a list of revisions that have been merged but
4887
not yet committed. These revisions will be included as additional parents
4888
of the next commit. Normally, using revert clears that list as well as
4889
reverting the files. If any files are specified, revert leaves the list
4890
of uncommitted merges alone and reverts only the files. Use ``brz revert
4891
.`` in the tree root to revert all files but keep the recorded merges,
4892
and ``brz revert --forget-merges`` to clear the pending merge list without
4893
reverting any files.
4895
Using "brz revert --forget-merges", it is possible to apply all of the
4896
changes from a branch in a single revision. To do this, perform the merge
4897
as desired. Then doing revert with the "--forget-merges" option will keep
4898
the content of the tree as it was, but it will clear the list of pending
4899
merges. The next commit will then contain all of the changes that are
4900
present in the other branch, but without any other parent revisions.
4901
Because this technique forgets where these changes originated, it may
4902
cause additional conflicts on later merges involving the same source and
4906
_see_also = ['cat', 'export', 'merge', 'shelve']
4909
Option('no-backup', "Do not save backups of reverted files."),
4910
Option('forget-merges',
4911
'Remove pending merge marker, without changing any files.'),
4913
takes_args = ['file*']
4915
def run(self, revision=None, no_backup=False, file_list=None,
4916
forget_merges=None):
4917
tree, file_list = WorkingTree.open_containing_paths(file_list)
4918
self.enter_context(tree.lock_tree_write())
4920
tree.set_parent_ids(tree.get_parent_ids()[:1])
4922
self._revert_tree_to_revision(tree, revision, file_list, no_backup)
4925
def _revert_tree_to_revision(tree, revision, file_list, no_backup):
4926
rev_tree = _get_one_revision_tree('revert', revision, tree=tree)
4927
tree.revert(file_list, rev_tree, not no_backup, None,
4928
report_changes=True)
4931
class cmd_assert_fail(Command):
4932
__doc__ = """Test reporting of assertion failures"""
4933
# intended just for use in testing
4938
raise AssertionError("always fails")
4941
class cmd_help(Command):
4942
__doc__ = """Show help on a command or other topic.
4945
_see_also = ['topics']
4947
Option('long', 'Show help on all commands.'),
4949
takes_args = ['topic?']
4950
aliases = ['?', '--help', '-?', '-h']
4953
def run(self, topic=None, long=False):
4955
if topic is None and long:
4957
breezy.help.help(topic)
4960
class cmd_shell_complete(Command):
4961
__doc__ = """Show appropriate completions for context.
4963
For a list of all available commands, say 'brz shell-complete'.
4965
takes_args = ['context?']
4970
def run(self, context=None):
4971
from . import shellcomplete
4972
shellcomplete.shellcomplete(context)
4975
class cmd_missing(Command):
4976
__doc__ = """Show unmerged/unpulled revisions between two branches.
4978
OTHER_BRANCH may be local or remote.
4980
To filter on a range of revisions, you can use the command -r begin..end
4981
-r revision requests a specific revision, -r ..end or -r begin.. are
4985
1 - some missing revisions
4986
0 - no missing revisions
4990
Determine the missing revisions between this and the branch at the
4991
remembered pull location::
4995
Determine the missing revisions between this and another branch::
4997
brz missing http://server/branch
4999
Determine the missing revisions up to a specific revision on the other
5002
brz missing -r ..-10
5004
Determine the missing revisions up to a specific revision on this
5007
brz missing --my-revision ..-10
5010
_see_also = ['merge', 'pull']
5011
takes_args = ['other_branch?']
5014
Option('reverse', 'Reverse the order of revisions.'),
5016
'Display changes in the local branch only.'),
5017
Option('this', 'Same as --mine-only.'),
5018
Option('theirs-only',
5019
'Display changes in the remote branch only.'),
5020
Option('other', 'Same as --theirs-only.'),
5024
custom_help('revision',
5025
help='Filter on other branch revisions (inclusive). '
5026
'See "help revisionspec" for details.'),
5027
Option('my-revision',
5028
type=_parse_revision_str,
5029
help='Filter on local branch revisions (inclusive). '
5030
'See "help revisionspec" for details.'),
5031
Option('include-merged',
5032
'Show all revisions in addition to the mainline ones.'),
5033
Option('include-merges', hidden=True,
5034
help='Historical alias for --include-merged.'),
5036
encoding_type = 'replace'
5039
def run(self, other_branch=None, reverse=False, mine_only=False,
5041
log_format=None, long=False, short=False, line=False,
5042
show_ids=False, verbose=False, this=False, other=False,
5043
include_merged=None, revision=None, my_revision=None,
5045
from breezy.missing import find_unmerged, iter_log_revisions
5051
if include_merged is None:
5052
include_merged = False
5057
# TODO: We should probably check that we don't have mine-only and
5058
# theirs-only set, but it gets complicated because we also have
5059
# this and other which could be used.
5066
local_branch = Branch.open_containing(directory)[0]
5067
self.enter_context(local_branch.lock_read())
5069
parent = local_branch.get_parent()
5070
if other_branch is None:
5071
other_branch = parent
5072
if other_branch is None:
5073
raise errors.CommandError(gettext("No peer location known"
5075
display_url = urlutils.unescape_for_display(parent,
5077
message(gettext("Using saved parent location: {0}\n").format(
5080
remote_branch = Branch.open(other_branch)
5081
if remote_branch.base == local_branch.base:
5082
remote_branch = local_branch
5084
self.enter_context(remote_branch.lock_read())
5086
local_revid_range = _revision_range_to_revid_range(
5087
_get_revision_range(my_revision, local_branch,
5090
remote_revid_range = _revision_range_to_revid_range(
5091
_get_revision_range(revision,
5092
remote_branch, self.name()))
5094
local_extra, remote_extra = find_unmerged(
5095
local_branch, remote_branch, restrict,
5096
backward=not reverse,
5097
include_merged=include_merged,
5098
local_revid_range=local_revid_range,
5099
remote_revid_range=remote_revid_range)
5101
if log_format is None:
5102
registry = log.log_formatter_registry
5103
log_format = registry.get_default(local_branch)
5104
lf = log_format(to_file=self.outf,
5106
show_timezone='original')
5109
if local_extra and not theirs_only:
5110
message(ngettext("You have %d extra revision:\n",
5111
"You have %d extra revisions:\n",
5115
if local_branch.supports_tags():
5116
rev_tag_dict = local_branch.tags.get_reverse_tag_dict()
5117
for revision in iter_log_revisions(local_extra,
5118
local_branch.repository,
5121
lf.log_revision(revision)
5122
printed_local = True
5125
printed_local = False
5127
if remote_extra and not mine_only:
5128
if printed_local is True:
5130
message(ngettext("You are missing %d revision:\n",
5131
"You are missing %d revisions:\n",
5132
len(remote_extra)) %
5134
if remote_branch.supports_tags():
5135
rev_tag_dict = remote_branch.tags.get_reverse_tag_dict()
5136
for revision in iter_log_revisions(remote_extra,
5137
remote_branch.repository,
5140
lf.log_revision(revision)
5143
if mine_only and not local_extra:
5144
# We checked local, and found nothing extra
5145
message(gettext('This branch has no new revisions.\n'))
5146
elif theirs_only and not remote_extra:
5147
# We checked remote, and found nothing extra
5148
message(gettext('Other branch has no new revisions.\n'))
5149
elif not (mine_only or theirs_only or local_extra or
5151
# We checked both branches, and neither one had extra
5153
message(gettext("Branches are up to date.\n"))
5155
if not status_code and parent is None and other_branch is not None:
5156
self.enter_context(local_branch.lock_write())
5157
# handle race conditions - a parent might be set while we run.
5158
if local_branch.get_parent() is None:
5159
local_branch.set_parent(remote_branch.base)
5163
class cmd_pack(Command):
5164
__doc__ = """Compress the data within a repository.
5166
This operation compresses the data within a bazaar repository. As
5167
bazaar supports automatic packing of repository, this operation is
5168
normally not required to be done manually.
5170
During the pack operation, bazaar takes a backup of existing repository
5171
data, i.e. pack files. This backup is eventually removed by bazaar
5172
automatically when it is safe to do so. To save disk space by removing
5173
the backed up pack files, the --clean-obsolete-packs option may be
5176
Warning: If you use --clean-obsolete-packs and your machine crashes
5177
during or immediately after repacking, you may be left with a state
5178
where the deletion has been written to disk but the new packs have not
5179
been. In this case the repository may be unusable.
5182
_see_also = ['repositories']
5183
takes_args = ['branch_or_repo?']
5185
Option('clean-obsolete-packs',
5186
'Delete obsolete packs to save disk space.'),
5189
def run(self, branch_or_repo='.', clean_obsolete_packs=False):
5190
dir = controldir.ControlDir.open_containing(branch_or_repo)[0]
5192
branch = dir.open_branch()
5193
repository = branch.repository
5194
except errors.NotBranchError:
5195
repository = dir.open_repository()
5196
repository.pack(clean_obsolete_packs=clean_obsolete_packs)
5199
class cmd_plugins(Command):
5200
__doc__ = """List the installed plugins.
5202
This command displays the list of installed plugins including
5203
version of plugin and a short description of each.
5205
--verbose shows the path where each plugin is located.
5207
A plugin is an external component for Breezy that extends the
5208
revision control system, by adding or replacing code in Breezy.
5209
Plugins can do a variety of things, including overriding commands,
5210
adding new commands, providing additional network transports and
5211
customizing log output.
5213
See the Bazaar Plugin Guide <http://doc.bazaar.canonical.com/plugins/en/>
5214
for further information on plugins including where to find them and how to
5215
install them. Instructions are also provided there on how to write new
5216
plugins using the Python programming language.
5218
takes_options = ['verbose']
5221
def run(self, verbose=False):
5222
from . import plugin
5223
# Don't give writelines a generator as some codecs don't like that
5224
self.outf.writelines(
5225
list(plugin.describe_plugins(show_paths=verbose)))
5228
class cmd_testament(Command):
5229
__doc__ = """Show testament (signing-form) of a revision."""
5232
Option('long', help='Produce long-format testament.'),
5234
help='Produce a strict-format testament.')]
5235
takes_args = ['branch?']
5236
encoding_type = 'exact'
5239
def run(self, branch=u'.', revision=None, long=False, strict=False):
5240
from .bzr.testament import Testament, StrictTestament
5242
testament_class = StrictTestament
5244
testament_class = Testament
5246
b = Branch.open_containing(branch)[0]
5248
b = Branch.open(branch)
5249
self.enter_context(b.lock_read())
5250
if revision is None:
5251
rev_id = b.last_revision()
5253
rev_id = revision[0].as_revision_id(b)
5254
t = testament_class.from_revision(b.repository, rev_id)
5256
self.outf.writelines(t.as_text_lines())
5258
self.outf.write(t.as_short_text())
5261
class cmd_annotate(Command):
5262
__doc__ = """Show the origin of each line in a file.
5264
This prints out the given file with an annotation on the left side
5265
indicating which revision, author and date introduced the change.
5267
If the origin is the same for a run of consecutive lines, it is
5268
shown only at the top, unless the --all option is given.
5270
# TODO: annotate directories; showing when each file was last changed
5271
# TODO: if the working copy is modified, show annotations on that
5272
# with new uncommitted lines marked
5273
aliases = ['ann', 'blame', 'praise']
5274
takes_args = ['filename']
5275
takes_options = [Option('all', help='Show annotations on all lines.'),
5276
Option('long', help='Show commit date in annotations.'),
5281
encoding_type = 'exact'
5284
def run(self, filename, all=False, long=False, revision=None,
5285
show_ids=False, directory=None):
5286
from .annotate import (
5289
wt, branch, relpath = \
5290
_open_directory_or_containing_tree_or_branch(filename, directory)
5292
self.enter_context(wt.lock_read())
5294
self.enter_context(branch.lock_read())
5295
tree = _get_one_revision_tree('annotate', revision, branch=branch)
5296
self.enter_context(tree.lock_read())
5297
if wt is not None and revision is None:
5298
if not wt.is_versioned(relpath):
5299
raise errors.NotVersionedError(relpath)
5300
# If there is a tree and we're not annotating historical
5301
# versions, annotate the working tree's content.
5302
annotate_file_tree(wt, relpath, self.outf, long, all,
5305
if not tree.is_versioned(relpath):
5306
raise errors.NotVersionedError(relpath)
5307
annotate_file_tree(tree, relpath, self.outf, long, all,
5308
show_ids=show_ids, branch=branch)
5311
class cmd_re_sign(Command):
5312
__doc__ = """Create a digital signature for an existing revision."""
5313
# TODO be able to replace existing ones.
5315
hidden = True # is this right ?
5316
takes_args = ['revision_id*']
5317
takes_options = ['directory', 'revision']
5319
def run(self, revision_id_list=None, revision=None, directory=u'.'):
5320
if revision_id_list is not None and revision is not None:
5321
raise errors.CommandError(
5322
gettext('You can only supply one of revision_id or --revision'))
5323
if revision_id_list is None and revision is None:
5324
raise errors.CommandError(
5325
gettext('You must supply either --revision or a revision_id'))
5326
b = WorkingTree.open_containing(directory)[0].branch
5327
self.enter_context(b.lock_write())
5328
return self._run(b, revision_id_list, revision)
5330
def _run(self, b, revision_id_list, revision):
5331
from .repository import WriteGroup
5332
gpg_strategy = gpg.GPGStrategy(b.get_config_stack())
5333
if revision_id_list is not None:
5334
with WriteGroup(b.repository):
5335
for revision_id in revision_id_list:
5336
revision_id = cache_utf8.encode(revision_id)
5337
b.repository.sign_revision(revision_id, gpg_strategy)
5338
elif revision is not None:
5339
if len(revision) == 1:
5340
revno, rev_id = revision[0].in_history(b)
5341
with WriteGroup(b.repository):
5342
b.repository.sign_revision(rev_id, gpg_strategy)
5343
elif len(revision) == 2:
5344
# are they both on rh- if so we can walk between them
5345
# might be nice to have a range helper for arbitrary
5346
# revision paths. hmm.
5347
from_revno, from_revid = revision[0].in_history(b)
5348
to_revno, to_revid = revision[1].in_history(b)
5349
if to_revid is None:
5350
to_revno = b.revno()
5351
if from_revno is None or to_revno is None:
5352
raise errors.CommandError(
5353
gettext('Cannot sign a range of non-revision-history revisions'))
5354
with WriteGroup(b.repository):
5355
for revno in range(from_revno, to_revno + 1):
5356
b.repository.sign_revision(b.get_rev_id(revno),
5359
raise errors.CommandError(
5360
gettext('Please supply either one revision, or a range.'))
5363
class cmd_bind(Command):
5364
__doc__ = """Convert the current branch into a checkout of the supplied branch.
5365
If no branch is supplied, rebind to the last bound location.
5367
Once converted into a checkout, commits must succeed on the master branch
5368
before they will be applied to the local branch.
5370
Bound branches use the nickname of its master branch unless it is set
5371
locally, in which case binding will update the local nickname to be
5375
_see_also = ['checkouts', 'unbind']
5376
takes_args = ['location?']
5377
takes_options = ['directory']
5379
def run(self, location=None, directory=u'.'):
5380
b, relpath = Branch.open_containing(directory)
5381
if location is None:
5383
location = b.get_old_bound_location()
5384
except errors.UpgradeRequired:
5385
raise errors.CommandError(
5386
gettext('No location supplied. '
5387
'This format does not remember old locations.'))
5389
if location is None:
5390
if b.get_bound_location() is not None:
5391
raise errors.CommandError(
5392
gettext('Branch is already bound'))
5394
raise errors.CommandError(
5395
gettext('No location supplied'
5396
' and no previous location known'))
5397
b_other = Branch.open(location)
5400
except errors.DivergedBranches:
5401
raise errors.CommandError(
5402
gettext('These branches have diverged.'
5403
' Try merging, and then bind again.'))
5404
if b.get_config().has_explicit_nickname():
5405
b.nick = b_other.nick
5408
class cmd_unbind(Command):
5409
__doc__ = """Convert the current checkout into a regular branch.
5411
After unbinding, the local branch is considered independent and subsequent
5412
commits will be local only.
5415
_see_also = ['checkouts', 'bind']
5417
takes_options = ['directory']
5419
def run(self, directory=u'.'):
5420
b, relpath = Branch.open_containing(directory)
5422
raise errors.CommandError(gettext('Local branch is not bound'))
5425
class cmd_uncommit(Command):
5426
__doc__ = """Remove the last committed revision.
5428
--verbose will print out what is being removed.
5429
--dry-run will go through all the motions, but not actually
5432
If --revision is specified, uncommit revisions to leave the branch at the
5433
specified revision. For example, "brz uncommit -r 15" will leave the
5434
branch at revision 15.
5436
Uncommit leaves the working tree ready for a new commit. The only change
5437
it may make is to restore any pending merges that were present before
5441
# TODO: jam 20060108 Add an option to allow uncommit to remove
5442
# unreferenced information in 'branch-as-repository' branches.
5443
# TODO: jam 20060108 Add the ability for uncommit to remove unreferenced
5444
# information in shared branches as well.
5445
_see_also = ['commit']
5446
takes_options = ['verbose', 'revision',
5447
Option('dry-run', help='Don\'t actually make changes.'),
5448
Option('force', help='Say yes to all questions.'),
5450
help='Keep tags that point to removed revisions.'),
5452
help="Only remove the commits from the local "
5453
"branch when in a checkout."
5456
takes_args = ['location?']
5458
encoding_type = 'replace'
5460
def run(self, location=None, dry_run=False, verbose=False,
5461
revision=None, force=False, local=False, keep_tags=False):
5462
if location is None:
5464
control, relpath = controldir.ControlDir.open_containing(location)
5466
tree = control.open_workingtree()
5468
except (errors.NoWorkingTree, errors.NotLocalUrl):
5470
b = control.open_branch()
5472
if tree is not None:
5473
self.enter_context(tree.lock_write())
5475
self.enter_context(b.lock_write())
5476
return self._run(b, tree, dry_run, verbose, revision, force,
5477
local, keep_tags, location)
5479
def _run(self, b, tree, dry_run, verbose, revision, force, local,
5480
keep_tags, location):
5481
from .log import log_formatter, show_log
5482
from .uncommit import uncommit
5484
last_revno, last_rev_id = b.last_revision_info()
5487
if revision is None:
5489
rev_id = last_rev_id
5491
# 'brz uncommit -r 10' actually means uncommit
5492
# so that the final tree is at revno 10.
5493
# but breezy.uncommit.uncommit() actually uncommits
5494
# the revisions that are supplied.
5495
# So we need to offset it by one
5496
revno = revision[0].in_history(b).revno + 1
5497
if revno <= last_revno:
5498
rev_id = b.get_rev_id(revno)
5500
if rev_id is None or _mod_revision.is_null(rev_id):
5501
self.outf.write(gettext('No revisions to uncommit.\n'))
5504
lf = log_formatter('short',
5506
show_timezone='original')
5511
direction='forward',
5512
start_revision=revno,
5513
end_revision=last_revno)
5516
self.outf.write(gettext('Dry-run, pretending to remove'
5517
' the above revisions.\n'))
5520
gettext('The above revision(s) will be removed.\n'))
5523
if not ui.ui_factory.confirm_action(
5524
gettext(u'Uncommit these revisions'),
5525
'breezy.builtins.uncommit',
5527
self.outf.write(gettext('Canceled\n'))
5530
mutter('Uncommitting from {%s} to {%s}',
5531
last_rev_id, rev_id)
5532
uncommit(b, tree=tree, dry_run=dry_run, verbose=verbose,
5533
revno=revno, local=local, keep_tags=keep_tags)
5536
gettext('You can restore the old tip by running:\n'
5537
' brz pull -d %s %s -r revid:%s\n')
5538
% (location, location, last_rev_id.decode('utf-8')))
5541
gettext('You can restore the old tip by running:\n'
5542
' brz pull . -r revid:%s\n')
5543
% last_rev_id.decode('utf-8'))
5546
class cmd_break_lock(Command):
5547
__doc__ = """Break a dead lock.
5549
This command breaks a lock on a repository, branch, working directory or
5552
CAUTION: Locks should only be broken when you are sure that the process
5553
holding the lock has been stopped.
5555
You can get information on what locks are open via the 'brz info
5556
[location]' command.
5560
brz break-lock brz+ssh://example.com/brz/foo
5561
brz break-lock --conf ~/.config/breezy
5564
takes_args = ['location?']
5567
help='LOCATION is the directory where the config lock is.'),
5569
help='Do not ask for confirmation before breaking the lock.'),
5572
def run(self, location=None, config=False, force=False):
5573
if location is None:
5576
ui.ui_factory = ui.ConfirmationUserInterfacePolicy(ui.ui_factory,
5578
{'breezy.lockdir.break': True})
5580
conf = _mod_config.LockableConfig(file_name=location)
5583
control, relpath = controldir.ControlDir.open_containing(location)
5585
control.break_lock()
5586
except NotImplementedError:
5590
class cmd_wait_until_signalled(Command):
5591
__doc__ = """Test helper for test_start_and_stop_brz_subprocess_send_signal.
5593
This just prints a line to signal when it is ready, then blocks on stdin.
5599
self.outf.write("running\n")
5601
sys.stdin.readline()
5604
class cmd_serve(Command):
5605
__doc__ = """Run the brz server."""
5607
aliases = ['server']
5611
help='Serve on stdin/out for use from inetd or sshd.'),
5612
RegistryOption('protocol',
5613
help="Protocol to serve.",
5614
lazy_registry=('breezy.transport',
5615
'transport_server_registry'),
5616
value_switches=True),
5618
help='Listen for connections on nominated address.',
5621
help='Listen for connections on nominated port. Passing 0 as '
5622
'the port number will result in a dynamically allocated '
5623
'port. The default port depends on the protocol.',
5625
custom_help('directory',
5626
help='Serve contents of this directory.'),
5627
Option('allow-writes',
5628
help='By default the server is a readonly server. Supplying '
5629
'--allow-writes enables write access to the contents of '
5630
'the served directory and below. Note that ``brz serve`` '
5631
'does not perform authentication, so unless some form of '
5632
'external authentication is arranged supplying this '
5633
'option leads to global uncontrolled write access to your '
5636
Option('client-timeout', type=float,
5637
help='Override the default idle client timeout (5min).'),
5640
def run(self, listen=None, port=None, inet=False, directory=None,
5641
allow_writes=False, protocol=None, client_timeout=None):
5642
from . import location, transport
5643
if directory is None:
5644
directory = osutils.getcwd()
5645
if protocol is None:
5646
protocol = transport.transport_server_registry.get()
5647
url = location.location_to_url(directory)
5648
if not allow_writes:
5649
url = 'readonly+' + url
5650
t = transport.get_transport_from_url(url)
5651
protocol(t, listen, port, inet, client_timeout)
5654
class cmd_join(Command):
5655
__doc__ = """Combine a tree into its containing tree.
5657
This command requires the target tree to be in a rich-root format.
5659
The TREE argument should be an independent tree, inside another tree, but
5660
not part of it. (Such trees can be produced by "brz split", but also by
5661
running "brz branch" with the target inside a tree.)
5663
The result is a combined tree, with the subtree no longer an independent
5664
part. This is marked as a merge of the subtree into the containing tree,
5665
and all history is preserved.
5668
_see_also = ['split']
5669
takes_args = ['tree']
5671
Option('reference', help='Join by reference.', hidden=True),
5674
def run(self, tree, reference=False):
5675
from breezy.mutabletree import BadReferenceTarget
5676
sub_tree = WorkingTree.open(tree)
5677
parent_dir = osutils.dirname(sub_tree.basedir)
5678
containing_tree = WorkingTree.open_containing(parent_dir)[0]
5679
repo = containing_tree.branch.repository
5680
if not repo.supports_rich_root():
5681
raise errors.CommandError(gettext(
5682
"Can't join trees because %s doesn't support rich root data.\n"
5683
"You can use brz upgrade on the repository.")
5687
containing_tree.add_reference(sub_tree)
5688
except BadReferenceTarget as e:
5689
# XXX: Would be better to just raise a nicely printable
5690
# exception from the real origin. Also below. mbp 20070306
5691
raise errors.CommandError(
5692
gettext("Cannot join {0}. {1}").format(tree, e.reason))
5695
containing_tree.subsume(sub_tree)
5696
except errors.BadSubsumeSource as e:
5697
raise errors.CommandError(
5698
gettext("Cannot join {0}. {1}").format(tree, e.reason))
5701
class cmd_split(Command):
5702
__doc__ = """Split a subdirectory of a tree into a separate tree.
5704
This command will produce a target tree in a format that supports
5705
rich roots, like 'rich-root' or 'rich-root-pack'. These formats cannot be
5706
converted into earlier formats like 'dirstate-tags'.
5708
The TREE argument should be a subdirectory of a working tree. That
5709
subdirectory will be converted into an independent tree, with its own
5710
branch. Commits in the top-level tree will not apply to the new subtree.
5713
_see_also = ['join']
5714
takes_args = ['tree']
5716
def run(self, tree):
5717
containing_tree, subdir = WorkingTree.open_containing(tree)
5718
if not containing_tree.is_versioned(subdir):
5719
raise errors.NotVersionedError(subdir)
5721
containing_tree.extract(subdir)
5722
except errors.RootNotRich:
5723
raise errors.RichRootUpgradeRequired(containing_tree.branch.base)
5726
class cmd_merge_directive(Command):
5727
__doc__ = """Generate a merge directive for auto-merge tools.
5729
A directive requests a merge to be performed, and also provides all the
5730
information necessary to do so. This means it must either include a
5731
revision bundle, or the location of a branch containing the desired
5734
A submit branch (the location to merge into) must be supplied the first
5735
time the command is issued. After it has been supplied once, it will
5736
be remembered as the default.
5738
A public branch is optional if a revision bundle is supplied, but required
5739
if --diff or --plain is specified. It will be remembered as the default
5740
after the first use.
5743
takes_args = ['submit_branch?', 'public_branch?']
5747
_see_also = ['send']
5751
RegistryOption.from_kwargs(
5753
'The type of patch to include in the directive.',
5755
value_switches=True,
5757
bundle='Bazaar revision bundle (default).',
5758
diff='Normal unified diff.',
5759
plain='No patch, just directive.'),
5760
Option('sign', help='GPG-sign the directive.'), 'revision',
5761
Option('mail-to', type=str,
5762
help='Instead of printing the directive, email to this '
5764
Option('message', type=str, short_name='m',
5765
help='Message to use when committing this merge.')
5768
encoding_type = 'exact'
5770
def run(self, submit_branch=None, public_branch=None, patch_type='bundle',
5771
sign=False, revision=None, mail_to=None, message=None,
5773
from .revision import ensure_null, NULL_REVISION
5774
include_patch, include_bundle = {
5775
'plain': (False, False),
5776
'diff': (True, False),
5777
'bundle': (True, True),
5779
branch = Branch.open(directory)
5780
stored_submit_branch = branch.get_submit_branch()
5781
if submit_branch is None:
5782
submit_branch = stored_submit_branch
5784
if stored_submit_branch is None:
5785
branch.set_submit_branch(submit_branch)
5786
if submit_branch is None:
5787
submit_branch = branch.get_parent()
5788
if submit_branch is None:
5789
raise errors.CommandError(
5790
gettext('No submit branch specified or known'))
5792
stored_public_branch = branch.get_public_branch()
5793
if public_branch is None:
5794
public_branch = stored_public_branch
5795
elif stored_public_branch is None:
5796
# FIXME: Should be done only if we succeed ? -- vila 2012-01-03
5797
branch.set_public_branch(public_branch)
5798
if not include_bundle and public_branch is None:
5799
raise errors.CommandError(
5800
gettext('No public branch specified or known'))
5801
base_revision_id = None
5802
if revision is not None:
5803
if len(revision) > 2:
5804
raise errors.CommandError(
5805
gettext('brz merge-directive takes '
5806
'at most two one revision identifiers'))
5807
revision_id = revision[-1].as_revision_id(branch)
5808
if len(revision) == 2:
5809
base_revision_id = revision[0].as_revision_id(branch)
5811
revision_id = branch.last_revision()
5812
revision_id = ensure_null(revision_id)
5813
if revision_id == NULL_REVISION:
5814
raise errors.CommandError(gettext('No revisions to bundle.'))
5815
directive = merge_directive.MergeDirective2.from_objects(
5816
branch.repository, revision_id, time.time(),
5817
osutils.local_time_offset(), submit_branch,
5818
public_branch=public_branch, include_patch=include_patch,
5819
include_bundle=include_bundle, message=message,
5820
base_revision_id=base_revision_id)
5823
self.outf.write(directive.to_signed(branch))
5825
self.outf.writelines(directive.to_lines())
5827
message = directive.to_email(mail_to, branch, sign)
5828
s = SMTPConnection(branch.get_config_stack())
5829
s.send_email(message)
5832
class cmd_send(Command):
5833
__doc__ = """Mail or create a merge-directive for submitting changes.
5835
A merge directive provides many things needed for requesting merges:
5837
* A machine-readable description of the merge to perform
5839
* An optional patch that is a preview of the changes requested
5841
* An optional bundle of revision data, so that the changes can be applied
5842
directly from the merge directive, without retrieving data from a
5845
`brz send` creates a compact data set that, when applied using brz
5846
merge, has the same effect as merging from the source branch.
5848
By default the merge directive is self-contained and can be applied to any
5849
branch containing submit_branch in its ancestory without needing access to
5852
If --no-bundle is specified, then Breezy doesn't send the contents of the
5853
revisions, but only a structured request to merge from the
5854
public_location. In that case the public_branch is needed and it must be
5855
up-to-date and accessible to the recipient. The public_branch is always
5856
included if known, so that people can check it later.
5858
The submit branch defaults to the parent of the source branch, but can be
5859
overridden. Both submit branch and public branch will be remembered in
5860
branch.conf the first time they are used for a particular branch. The
5861
source branch defaults to that containing the working directory, but can
5862
be changed using --from.
5864
Both the submit branch and the public branch follow the usual behavior with
5865
respect to --remember: If there is no default location set, the first send
5866
will set it (use --no-remember to avoid setting it). After that, you can
5867
omit the location to use the default. To change the default, use
5868
--remember. The value will only be saved if the location can be accessed.
5870
In order to calculate those changes, brz must analyse the submit branch.
5871
Therefore it is most efficient for the submit branch to be a local mirror.
5872
If a public location is known for the submit_branch, that location is used
5873
in the merge directive.
5875
The default behaviour is to send the merge directive by mail, unless -o is
5876
given, in which case it is sent to a file.
5878
Mail is sent using your preferred mail program. This should be transparent
5879
on Windows (it uses MAPI). On Unix, it requires the xdg-email utility.
5880
If the preferred client can't be found (or used), your editor will be used.
5882
To use a specific mail program, set the mail_client configuration option.
5883
Supported values for specific clients are "claws", "evolution", "kmail",
5884
"mail.app" (MacOS X's Mail.app), "mutt", and "thunderbird"; generic options
5885
are "default", "editor", "emacsclient", "mapi", and "xdg-email". Plugins
5886
may also add supported clients.
5888
If mail is being sent, a to address is required. This can be supplied
5889
either on the commandline, by setting the submit_to configuration
5890
option in the branch itself or the child_submit_to configuration option
5891
in the submit branch.
5893
The merge directives created by brz send may be applied using brz merge or
5894
brz pull by specifying a file containing a merge directive as the location.
5896
brz send makes extensive use of public locations to map local locations into
5897
URLs that can be used by other people. See `brz help configuration` to
5898
set them, and use `brz info` to display them.
5901
encoding_type = 'exact'
5903
_see_also = ['merge', 'pull']
5905
takes_args = ['submit_branch?', 'public_branch?']
5909
help='Do not include a bundle in the merge directive.'),
5910
Option('no-patch', help='Do not include a preview patch in the merge'
5913
help='Remember submit and public branch.'),
5915
help='Branch to generate the submission from, '
5916
'rather than the one containing the working directory.',
5919
Option('output', short_name='o',
5920
help='Write merge directive to this file or directory; '
5921
'use - for stdout.',
5924
help='Refuse to send if there are uncommitted changes in'
5925
' the working tree, --no-strict disables the check.'),
5926
Option('mail-to', help='Mail the request to this address.',
5930
Option('body', help='Body for the email.', type=str),
5931
RegistryOption('format',
5932
help='Use the specified output format.',
5933
lazy_registry=('breezy.send', 'format_registry')),
5936
def run(self, submit_branch=None, public_branch=None, no_bundle=False,
5937
no_patch=False, revision=None, remember=None, output=None,
5938
format=None, mail_to=None, message=None, body=None,
5939
strict=None, **kwargs):
5940
from .send import send
5941
return send(submit_branch, revision, public_branch, remember,
5942
format, no_bundle, no_patch, output,
5943
kwargs.get('from', '.'), mail_to, message, body,
5948
class cmd_bundle_revisions(cmd_send):
5949
__doc__ = """Create a merge-directive for submitting changes.
5951
A merge directive provides many things needed for requesting merges:
5953
* A machine-readable description of the merge to perform
5955
* An optional patch that is a preview of the changes requested
5957
* An optional bundle of revision data, so that the changes can be applied
5958
directly from the merge directive, without retrieving data from a
5961
If --no-bundle is specified, then public_branch is needed (and must be
5962
up-to-date), so that the receiver can perform the merge using the
5963
public_branch. The public_branch is always included if known, so that
5964
people can check it later.
5966
The submit branch defaults to the parent, but can be overridden. Both
5967
submit branch and public branch will be remembered if supplied.
5969
If a public_branch is known for the submit_branch, that public submit
5970
branch is used in the merge instructions. This means that a local mirror
5971
can be used as your actual submit branch, once you have set public_branch
5977
help='Do not include a bundle in the merge directive.'),
5978
Option('no-patch', help='Do not include a preview patch in the merge'
5981
help='Remember submit and public branch.'),
5983
help='Branch to generate the submission from, '
5984
'rather than the one containing the working directory.',
5987
Option('output', short_name='o', help='Write directive to this file.',
5990
help='Refuse to bundle revisions if there are uncommitted'
5991
' changes in the working tree, --no-strict disables the check.'),
5993
RegistryOption('format',
5994
help='Use the specified output format.',
5995
lazy_registry=('breezy.send', 'format_registry')),
5997
aliases = ['bundle']
5999
_see_also = ['send', 'merge']
6003
def run(self, submit_branch=None, public_branch=None, no_bundle=False,
6004
no_patch=False, revision=None, remember=False, output=None,
6005
format=None, strict=None, **kwargs):
6008
from .send import send
6009
return send(submit_branch, revision, public_branch, remember,
6010
format, no_bundle, no_patch, output,
6011
kwargs.get('from', '.'), None, None, None,
6012
self.outf, strict=strict)
6015
class cmd_tag(Command):
6016
__doc__ = """Create, remove or modify a tag naming a revision.
6018
Tags give human-meaningful names to revisions. Commands that take a -r
6019
(--revision) option can be given -rtag:X, where X is any previously
6022
Tags are stored in the branch. Tags are copied from one branch to another
6023
along when you branch, push, pull or merge.
6025
It is an error to give a tag name that already exists unless you pass
6026
--force, in which case the tag is moved to point to the new revision.
6028
To rename a tag (change the name but keep it on the same revsion), run ``brz
6029
tag new-name -r tag:old-name`` and then ``brz tag --delete oldname``.
6031
If no tag name is specified it will be determined through the
6032
'automatic_tag_name' hook. This can e.g. be used to automatically tag
6033
upstream releases by reading configure.ac. See ``brz help hooks`` for
6037
_see_also = ['commit', 'tags']
6038
takes_args = ['tag_name?']
6041
help='Delete this tag rather than placing it.',
6043
custom_help('directory',
6044
help='Branch in which to place the tag.'),
6046
help='Replace existing tags.',
6051
def run(self, tag_name=None,
6057
branch, relpath = Branch.open_containing(directory)
6058
self.enter_context(branch.lock_write())
6060
if tag_name is None:
6061
raise errors.CommandError(
6062
gettext("No tag specified to delete."))
6063
branch.tags.delete_tag(tag_name)
6064
note(gettext('Deleted tag %s.') % tag_name)
6067
if len(revision) != 1:
6068
raise errors.CommandError(gettext(
6069
"Tags can only be placed on a single revision, "
6071
revision_id = revision[0].as_revision_id(branch)
6073
revision_id = branch.last_revision()
6074
if tag_name is None:
6075
tag_name = branch.automatic_tag_name(revision_id)
6076
if tag_name is None:
6077
raise errors.CommandError(gettext(
6078
"Please specify a tag name."))
6080
existing_target = branch.tags.lookup_tag(tag_name)
6081
except errors.NoSuchTag:
6082
existing_target = None
6083
if not force and existing_target not in (None, revision_id):
6084
raise errors.TagAlreadyExists(tag_name)
6085
if existing_target == revision_id:
6086
note(gettext('Tag %s already exists for that revision.') % tag_name)
6088
branch.tags.set_tag(tag_name, revision_id)
6089
if existing_target is None:
6090
note(gettext('Created tag %s.') % tag_name)
6092
note(gettext('Updated tag %s.') % tag_name)
6095
class cmd_tags(Command):
6096
__doc__ = """List tags.
6098
This command shows a table of tag names and the revisions they reference.
6103
custom_help('directory',
6104
help='Branch whose tags should be displayed.'),
6105
RegistryOption('sort',
6106
'Sort tags by different criteria.', title='Sorting',
6107
lazy_registry=('breezy.tag', 'tag_sort_methods')
6114
def run(self, directory='.', sort=None, show_ids=False, revision=None):
6115
from .tag import tag_sort_methods
6116
branch, relpath = Branch.open_containing(directory)
6118
tags = list(branch.tags.get_tag_dict().items())
6122
self.enter_context(branch.lock_read())
6124
# Restrict to the specified range
6125
tags = self._tags_for_range(branch, revision)
6127
sort = tag_sort_methods.get()
6130
# [ (tag, revid), ... ] -> [ (tag, dotted_revno), ... ]
6131
for index, (tag, revid) in enumerate(tags):
6133
revno = branch.revision_id_to_dotted_revno(revid)
6134
if isinstance(revno, tuple):
6135
revno = '.'.join(map(str, revno))
6136
except (errors.NoSuchRevision,
6137
errors.GhostRevisionsHaveNoRevno,
6138
errors.UnsupportedOperation):
6139
# Bad tag data/merges can lead to tagged revisions
6140
# which are not in this branch. Fail gracefully ...
6142
tags[index] = (tag, revno)
6144
tags = [(tag, revid.decode('utf-8')) for (tag, revid) in tags]
6146
for tag, revspec in tags:
6147
self.outf.write('%-20s %s\n' % (tag, revspec))
6149
def _tags_for_range(self, branch, revision):
6150
rev1, rev2 = _get_revision_range(revision, branch, self.name())
6151
revid1, revid2 = rev1.rev_id, rev2.rev_id
6152
# _get_revision_range will always set revid2 if it's not specified.
6153
# If revid1 is None, it means we want to start from the branch
6154
# origin which is always a valid ancestor. If revid1 == revid2, the
6155
# ancestry check is useless.
6156
if revid1 and revid1 != revid2:
6157
# FIXME: We really want to use the same graph than
6158
# branch.iter_merge_sorted_revisions below, but this is not
6159
# easily available -- vila 2011-09-23
6160
if branch.repository.get_graph().is_ancestor(revid2, revid1):
6161
# We don't want to output anything in this case...
6163
# only show revisions between revid1 and revid2 (inclusive)
6164
tagged_revids = branch.tags.get_reverse_tag_dict()
6166
for r in branch.iter_merge_sorted_revisions(
6167
start_revision_id=revid2, stop_revision_id=revid1,
6168
stop_rule='include'):
6169
revid_tags = tagged_revids.get(r[0], None)
6171
found.extend([(tag, r[0]) for tag in revid_tags])
6175
class cmd_reconfigure(Command):
6176
__doc__ = """Reconfigure the type of a brz directory.
6178
A target configuration must be specified.
6180
For checkouts, the bind-to location will be auto-detected if not specified.
6181
The order of preference is
6182
1. For a lightweight checkout, the current bound location.
6183
2. For branches that used to be checkouts, the previously-bound location.
6184
3. The push location.
6185
4. The parent location.
6186
If none of these is available, --bind-to must be specified.
6189
_see_also = ['branches', 'checkouts', 'standalone-trees', 'working-trees']
6190
takes_args = ['location?']
6192
RegistryOption.from_kwargs(
6195
help='The relation between branch and tree.',
6196
value_switches=True, enum_switch=False,
6197
branch='Reconfigure to be an unbound branch with no working tree.',
6198
tree='Reconfigure to be an unbound branch with a working tree.',
6199
checkout='Reconfigure to be a bound branch with a working tree.',
6200
lightweight_checkout='Reconfigure to be a lightweight'
6201
' checkout (with no local history).',
6203
RegistryOption.from_kwargs(
6205
title='Repository type',
6206
help='Location fo the repository.',
6207
value_switches=True, enum_switch=False,
6208
standalone='Reconfigure to be a standalone branch '
6209
'(i.e. stop using shared repository).',
6210
use_shared='Reconfigure to use a shared repository.',
6212
RegistryOption.from_kwargs(
6214
title='Trees in Repository',
6215
help='Whether new branches in the repository have trees.',
6216
value_switches=True, enum_switch=False,
6217
with_trees='Reconfigure repository to create '
6218
'working trees on branches by default.',
6219
with_no_trees='Reconfigure repository to not create '
6220
'working trees on branches by default.'
6222
Option('bind-to', help='Branch to bind checkout to.', type=str),
6224
help='Perform reconfiguration even if local changes'
6226
Option('stacked-on',
6227
help='Reconfigure a branch to be stacked on another branch.',
6231
help='Reconfigure a branch to be unstacked. This '
6232
'may require copying substantial data into it.',
6236
def run(self, location=None, bind_to=None, force=False,
6237
tree_type=None, repository_type=None, repository_trees=None,
6238
stacked_on=None, unstacked=None):
6239
directory = controldir.ControlDir.open(location)
6240
if stacked_on and unstacked:
6241
raise errors.CommandError(
6242
gettext("Can't use both --stacked-on and --unstacked"))
6243
elif stacked_on is not None:
6244
reconfigure.ReconfigureStackedOn().apply(directory, stacked_on)
6246
reconfigure.ReconfigureUnstacked().apply(directory)
6247
# At the moment you can use --stacked-on and a different
6248
# reconfiguration shape at the same time; there seems no good reason
6250
if (tree_type is None and
6251
repository_type is None and
6252
repository_trees is None):
6253
if stacked_on or unstacked:
6256
raise errors.CommandError(gettext('No target configuration '
6258
reconfiguration = None
6259
if tree_type == 'branch':
6260
reconfiguration = reconfigure.Reconfigure.to_branch(directory)
6261
elif tree_type == 'tree':
6262
reconfiguration = reconfigure.Reconfigure.to_tree(directory)
6263
elif tree_type == 'checkout':
6264
reconfiguration = reconfigure.Reconfigure.to_checkout(
6266
elif tree_type == 'lightweight-checkout':
6267
reconfiguration = reconfigure.Reconfigure.to_lightweight_checkout(
6270
reconfiguration.apply(force)
6271
reconfiguration = None
6272
if repository_type == 'use-shared':
6273
reconfiguration = reconfigure.Reconfigure.to_use_shared(directory)
6274
elif repository_type == 'standalone':
6275
reconfiguration = reconfigure.Reconfigure.to_standalone(directory)
6277
reconfiguration.apply(force)
6278
reconfiguration = None
6279
if repository_trees == 'with-trees':
6280
reconfiguration = reconfigure.Reconfigure.set_repository_trees(
6282
elif repository_trees == 'with-no-trees':
6283
reconfiguration = reconfigure.Reconfigure.set_repository_trees(
6286
reconfiguration.apply(force)
6287
reconfiguration = None
6290
class cmd_switch(Command):
6291
__doc__ = """Set the branch of a checkout and update.
6293
For lightweight checkouts, this changes the branch being referenced.
6294
For heavyweight checkouts, this checks that there are no local commits
6295
versus the current bound branch, then it makes the local branch a mirror
6296
of the new location and binds to it.
6298
In both cases, the working tree is updated and uncommitted changes
6299
are merged. The user can commit or revert these as they desire.
6301
Pending merges need to be committed or reverted before using switch.
6303
The path to the branch to switch to can be specified relative to the parent
6304
directory of the current branch. For example, if you are currently in a
6305
checkout of /path/to/branch, specifying 'newbranch' will find a branch at
6308
Bound branches use the nickname of its master branch unless it is set
6309
locally, in which case switching will update the local nickname to be
6313
takes_args = ['to_location?']
6314
takes_options = ['directory',
6316
help='Switch even if local commits will be lost.'),
6318
Option('create-branch', short_name='b',
6319
help='Create the target branch from this one before'
6320
' switching to it.'),
6322
help='Store and restore uncommitted changes in the'
6326
def run(self, to_location=None, force=False, create_branch=False,
6327
revision=None, directory=u'.', store=False):
6328
from . import switch
6329
tree_location = directory
6330
revision = _get_one_revision('switch', revision)
6331
control_dir = controldir.ControlDir.open_containing(tree_location)[0]
6332
possible_transports = [control_dir.root_transport]
6333
if to_location is None:
6334
if revision is None:
6335
raise errors.CommandError(gettext('You must supply either a'
6336
' revision or a location'))
6337
to_location = tree_location
6339
branch = control_dir.open_branch(
6340
possible_transports=possible_transports)
6341
had_explicit_nick = branch.get_config().has_explicit_nickname()
6342
except errors.NotBranchError:
6344
had_explicit_nick = False
6346
possible_transports.append(branch.user_transport)
6349
raise errors.CommandError(
6350
gettext('cannot create branch without source branch'))
6351
to_location = lookup_new_sibling_branch(
6352
control_dir, to_location,
6353
possible_transports=possible_transports)
6354
if revision is not None:
6355
revision = revision.as_revision_id(branch)
6356
to_branch = branch.controldir.sprout(
6358
possible_transports=possible_transports,
6359
revision_id=revision,
6360
source_branch=branch).open_branch()
6363
to_branch = Branch.open(
6364
to_location, possible_transports=possible_transports)
6365
except errors.NotBranchError:
6366
to_branch = open_sibling_branch(
6367
control_dir, to_location,
6368
possible_transports=possible_transports)
6369
if revision is not None:
6370
revision = revision.as_revision_id(to_branch)
6371
possible_transports.append(to_branch.user_transport)
6373
switch.switch(control_dir, to_branch, force, revision_id=revision,
6374
store_uncommitted=store,
6375
possible_transports=possible_transports)
6376
except controldir.BranchReferenceLoop:
6377
raise errors.CommandError(
6378
gettext('switching would create a branch reference loop. '
6379
'Use the "bzr up" command to switch to a '
6380
'different revision.'))
6381
if had_explicit_nick:
6382
branch = control_dir.open_branch() # get the new branch!
6383
branch.nick = to_branch.nick
6385
if to_branch.controldir.control_url != control_dir.control_url:
6386
note(gettext('Switched to branch %s at %s'),
6387
to_branch.name, urlutils.unescape_for_display(to_branch.base, 'utf-8'))
6389
note(gettext('Switched to branch %s'), to_branch.name)
6391
note(gettext('Switched to branch at %s'),
6392
urlutils.unescape_for_display(to_branch.base, 'utf-8'))
6395
class cmd_view(Command):
6396
__doc__ = """Manage filtered views.
6398
Views provide a mask over the tree so that users can focus on
6399
a subset of a tree when doing their work. After creating a view,
6400
commands that support a list of files - status, diff, commit, etc -
6401
effectively have that list of files implicitly given each time.
6402
An explicit list of files can still be given but those files
6403
must be within the current view.
6405
In most cases, a view has a short life-span: it is created to make
6406
a selected change and is deleted once that change is committed.
6407
At other times, you may wish to create one or more named views
6408
and switch between them.
6410
To disable the current view without deleting it, you can switch to
6411
the pseudo view called ``off``. This can be useful when you need
6412
to see the whole tree for an operation or two (e.g. merge) but
6413
want to switch back to your view after that.
6416
To define the current view::
6418
brz view file1 dir1 ...
6420
To list the current view::
6424
To delete the current view::
6428
To disable the current view without deleting it::
6430
brz view --switch off
6432
To define a named view and switch to it::
6434
brz view --name view-name file1 dir1 ...
6436
To list a named view::
6438
brz view --name view-name
6440
To delete a named view::
6442
brz view --name view-name --delete
6444
To switch to a named view::
6446
brz view --switch view-name
6448
To list all views defined::
6452
To delete all views::
6454
brz view --delete --all
6458
takes_args = ['file*']
6461
help='Apply list or delete action to all views.',
6464
help='Delete the view.',
6467
help='Name of the view to define, list or delete.',
6471
help='Name of the view to switch to.',
6476
def run(self, file_list,
6482
tree, file_list = WorkingTree.open_containing_paths(file_list,
6484
current_view, view_dict = tree.views.get_view_info()
6489
raise errors.CommandError(gettext(
6490
"Both --delete and a file list specified"))
6492
raise errors.CommandError(gettext(
6493
"Both --delete and --switch specified"))
6495
tree.views.set_view_info(None, {})
6496
self.outf.write(gettext("Deleted all views.\n"))
6498
raise errors.CommandError(
6499
gettext("No current view to delete"))
6501
tree.views.delete_view(name)
6502
self.outf.write(gettext("Deleted '%s' view.\n") % name)
6505
raise errors.CommandError(gettext(
6506
"Both --switch and a file list specified"))
6508
raise errors.CommandError(gettext(
6509
"Both --switch and --all specified"))
6510
elif switch == 'off':
6511
if current_view is None:
6512
raise errors.CommandError(
6513
gettext("No current view to disable"))
6514
tree.views.set_view_info(None, view_dict)
6515
self.outf.write(gettext("Disabled '%s' view.\n") %
6518
tree.views.set_view_info(switch, view_dict)
6519
view_str = views.view_display_str(tree.views.lookup_view())
6521
gettext("Using '{0}' view: {1}\n").format(switch, view_str))
6524
self.outf.write(gettext('Views defined:\n'))
6525
for view in sorted(view_dict):
6526
if view == current_view:
6530
view_str = views.view_display_str(view_dict[view])
6531
self.outf.write('%s %-20s %s\n' % (active, view, view_str))
6533
self.outf.write(gettext('No views defined.\n'))
6536
# No name given and no current view set
6539
raise errors.CommandError(gettext(
6540
"Cannot change the 'off' pseudo view"))
6541
tree.views.set_view(name, sorted(file_list))
6542
view_str = views.view_display_str(tree.views.lookup_view())
6544
gettext("Using '{0}' view: {1}\n").format(name, view_str))
6548
# No name given and no current view set
6549
self.outf.write(gettext('No current view.\n'))
6551
view_str = views.view_display_str(tree.views.lookup_view(name))
6553
gettext("'{0}' view is: {1}\n").format(name, view_str))
6556
class cmd_hooks(Command):
6557
__doc__ = """Show hooks."""
6562
for hook_key in sorted(hooks.known_hooks.keys()):
6563
some_hooks = hooks.known_hooks_key_to_object(hook_key)
6564
self.outf.write("%s:\n" % type(some_hooks).__name__)
6565
for hook_name, hook_point in sorted(some_hooks.items()):
6566
self.outf.write(" %s:\n" % (hook_name,))
6567
found_hooks = list(hook_point)
6569
for hook in found_hooks:
6570
self.outf.write(" %s\n" %
6571
(some_hooks.get_hook_name(hook),))
6573
self.outf.write(gettext(" <no hooks installed>\n"))
6576
class cmd_remove_branch(Command):
6577
__doc__ = """Remove a branch.
6579
This will remove the branch from the specified location but
6580
will keep any working tree or repository in place.
6584
Remove the branch at repo/trunk::
6586
brz remove-branch repo/trunk
6590
takes_args = ["location?"]
6592
takes_options = ['directory',
6593
Option('force', help='Remove branch even if it is the active branch.')]
6595
aliases = ["rmbranch"]
6597
def run(self, directory=None, location=None, force=False):
6598
br = open_nearby_branch(near=directory, location=location)
6599
if not force and br.controldir.has_workingtree():
6601
active_branch = br.controldir.open_branch(name="")
6602
except errors.NotBranchError:
6603
active_branch = None
6604
if (active_branch is not None and
6605
br.control_url == active_branch.control_url):
6606
raise errors.CommandError(
6607
gettext("Branch is active. Use --force to remove it."))
6608
br.controldir.destroy_branch(br.name)
6611
class cmd_shelve(Command):
6612
__doc__ = """Temporarily set aside some changes from the current tree.
6614
Shelve allows you to temporarily put changes you've made "on the shelf",
6615
ie. out of the way, until a later time when you can bring them back from
6616
the shelf with the 'unshelve' command. The changes are stored alongside
6617
your working tree, and so they aren't propagated along with your branch nor
6618
will they survive its deletion.
6620
If shelve --list is specified, previously-shelved changes are listed.
6622
Shelve is intended to help separate several sets of changes that have
6623
been inappropriately mingled. If you just want to get rid of all changes
6624
and you don't need to restore them later, use revert. If you want to
6625
shelve all text changes at once, use shelve --all.
6627
If filenames are specified, only the changes to those files will be
6628
shelved. Other files will be left untouched.
6630
If a revision is specified, changes since that revision will be shelved.
6632
You can put multiple items on the shelf, and by default, 'unshelve' will
6633
restore the most recently shelved changes.
6635
For complicated changes, it is possible to edit the changes in a separate
6636
editor program to decide what the file remaining in the working copy
6637
should look like. To do this, add the configuration option
6639
change_editor = PROGRAM {new_path} {old_path}
6641
where {new_path} is replaced with the path of the new version of the
6642
file and {old_path} is replaced with the path of the old version of
6643
the file. The PROGRAM should save the new file with the desired
6644
contents of the file in the working tree.
6648
takes_args = ['file*']
6653
Option('all', help='Shelve all changes.'),
6655
RegistryOption('writer', 'Method to use for writing diffs.',
6656
breezy.option.diff_writer_registry,
6657
value_switches=True, enum_switch=False),
6659
Option('list', help='List shelved changes.'),
6661
help='Destroy removed changes instead of shelving them.'),
6663
_see_also = ['unshelve', 'configuration']
6665
def run(self, revision=None, all=False, file_list=None, message=None,
6666
writer=None, list=False, destroy=False, directory=None):
6668
return self.run_for_list(directory=directory)
6669
from .shelf_ui import Shelver
6671
writer = breezy.option.diff_writer_registry.get()
6673
shelver = Shelver.from_args(writer(self.outf), revision, all,
6674
file_list, message, destroy=destroy, directory=directory)
6679
except errors.UserAbort:
6682
def run_for_list(self, directory=None):
6683
if directory is None:
6685
tree = WorkingTree.open_containing(directory)[0]
6686
self.enter_context(tree.lock_read())
6687
manager = tree.get_shelf_manager()
6688
shelves = manager.active_shelves()
6689
if len(shelves) == 0:
6690
note(gettext('No shelved changes.'))
6692
for shelf_id in reversed(shelves):
6693
message = manager.get_metadata(shelf_id).get(b'message')
6695
message = '<no message>'
6696
self.outf.write('%3d: %s\n' % (shelf_id, message))
6700
class cmd_unshelve(Command):
6701
__doc__ = """Restore shelved changes.
6703
By default, the most recently shelved changes are restored. However if you
6704
specify a shelf by id those changes will be restored instead. This works
6705
best when the changes don't depend on each other.
6708
takes_args = ['shelf_id?']
6711
RegistryOption.from_kwargs(
6712
'action', help="The action to perform.",
6713
enum_switch=False, value_switches=True,
6714
apply="Apply changes and remove from the shelf.",
6715
dry_run="Show changes, but do not apply or remove them.",
6716
preview="Instead of unshelving the changes, show the diff that "
6717
"would result from unshelving.",
6718
delete_only="Delete changes without applying them.",
6719
keep="Apply changes but don't delete them.",
6722
_see_also = ['shelve']
6724
def run(self, shelf_id=None, action='apply', directory=u'.'):
6725
from .shelf_ui import Unshelver
6726
unshelver = Unshelver.from_args(shelf_id, action, directory=directory)
6730
unshelver.tree.unlock()
6733
class cmd_clean_tree(Command):
6734
__doc__ = """Remove unwanted files from working tree.
6736
By default, only unknown files, not ignored files, are deleted. Versioned
6737
files are never deleted.
6739
Another class is 'detritus', which includes files emitted by brz during
6740
normal operations and selftests. (The value of these files decreases with
6743
If no options are specified, unknown files are deleted. Otherwise, option
6744
flags are respected, and may be combined.
6746
To check what clean-tree will do, use --dry-run.
6748
takes_options = ['directory',
6749
Option('ignored', help='Delete all ignored files.'),
6750
Option('detritus', help='Delete conflict files, merge and revert'
6751
' backups, and failed selftest dirs.'),
6753
help='Delete files unknown to brz (default).'),
6754
Option('dry-run', help='Show files to delete instead of'
6756
Option('force', help='Do not prompt before deleting.')]
6758
def run(self, unknown=False, ignored=False, detritus=False, dry_run=False,
6759
force=False, directory=u'.'):
6760
from .clean_tree import clean_tree
6761
if not (unknown or ignored or detritus):
6765
clean_tree(directory, unknown=unknown, ignored=ignored,
6766
detritus=detritus, dry_run=dry_run, no_prompt=force)
6769
class cmd_reference(Command):
6770
__doc__ = """list, view and set branch locations for nested trees.
6772
If no arguments are provided, lists the branch locations for nested trees.
6773
If one argument is provided, display the branch location for that tree.
6774
If two arguments are provided, set the branch location for that tree.
6779
takes_args = ['path?', 'location?']
6782
Option('force-unversioned',
6783
help='Set reference even if path is not versioned.'),
6786
def run(self, path=None, directory='.', location=None, force_unversioned=False):
6787
tree, branch, relpath = (
6788
controldir.ControlDir.open_containing_tree_or_branch(directory))
6790
tree = branch.basis_tree()
6792
with tree.lock_read():
6794
(path, tree.get_reference_info(path, branch))
6795
for path in tree.iter_references()]
6796
self._display_reference_info(tree, branch, info)
6798
if not tree.is_versioned(path) and not force_unversioned:
6799
raise errors.NotVersionedError(path)
6800
if location is None:
6801
info = [(path, tree.get_reference_info(path, branch))]
6802
self._display_reference_info(tree, branch, info)
6804
tree.set_reference_info(path, location)
6806
def _display_reference_info(self, tree, branch, info):
6808
for path, location in info:
6809
ref_list.append((path, location))
6810
for path, location in sorted(ref_list):
6811
self.outf.write('%s %s\n' % (path, location))
6814
class cmd_export_pot(Command):
6815
__doc__ = """Export command helps and error messages in po format."""
6818
takes_options = [Option('plugin',
6819
help='Export help text from named command '
6820
'(defaults to all built in commands).',
6822
Option('include-duplicates',
6823
help='Output multiple copies of the same msgid '
6824
'string if it appears more than once.'),
6827
def run(self, plugin=None, include_duplicates=False):
6828
from .export_pot import export_pot
6829
export_pot(self.outf, plugin, include_duplicates)
6832
class cmd_import(Command):
6833
__doc__ = """Import sources from a directory, tarball or zip file
6835
This command will import a directory, tarball or zip file into a bzr
6836
tree, replacing any versioned files already present. If a directory is
6837
specified, it is used as the target. If the directory does not exist, or
6838
is not versioned, it is created.
6840
Tarballs may be gzip or bzip2 compressed. This is autodetected.
6842
If the tarball or zip has a single root directory, that directory is
6843
stripped when extracting the tarball. This is not done for directories.
6846
takes_args = ['source', 'tree?']
6848
def run(self, source, tree=None):
6849
from .upstream_import import do_import
6850
do_import(source, tree)
6853
class cmd_link_tree(Command):
6854
__doc__ = """Hardlink matching files to another tree.
6856
Only files with identical content and execute bit will be linked.
6859
takes_args = ['location']
6861
def run(self, location):
6862
from .transform import link_tree
6863
target_tree = WorkingTree.open_containing(".")[0]
6864
source_tree = WorkingTree.open(location)
6865
with target_tree.lock_write(), source_tree.lock_read():
6866
link_tree(target_tree, source_tree)
6869
class cmd_fetch_ghosts(Command):
6870
__doc__ = """Attempt to retrieve ghosts from another branch.
6872
If the other branch is not supplied, the last-pulled branch is used.
6876
aliases = ['fetch-missing']
6877
takes_args = ['branch?']
6878
takes_options = [Option('no-fix', help="Skip additional synchonization.")]
6880
def run(self, branch=None, no_fix=False):
6881
from .fetch_ghosts import GhostFetcher
6882
installed, failed = GhostFetcher.from_cmdline(branch).run()
6883
if len(installed) > 0:
6884
self.outf.write("Installed:\n")
6885
for rev in installed:
6886
self.outf.write(rev.decode('utf-8') + "\n")
6888
self.outf.write("Still missing:\n")
6890
self.outf.write(rev.decode('utf-8') + "\n")
6891
if not no_fix and len(installed) > 0:
6892
cmd_reconcile().run(".")
6895
class cmd_grep(Command):
6896
"""Print lines matching PATTERN for specified files and revisions.
6898
This command searches the specified files and revisions for a given
6899
pattern. The pattern is specified as a Python regular expressions[1].
6901
If the file name is not specified, the revisions starting with the
6902
current directory are searched recursively. If the revision number is
6903
not specified, the working copy is searched. To search the last committed
6904
revision, use the '-r -1' or '-r last:1' option.
6906
Unversioned files are not searched unless explicitly specified on the
6907
command line. Unversioned directores are not searched.
6909
When searching a pattern, the output is shown in the 'filepath:string'
6910
format. If a revision is explicitly searched, the output is shown as
6911
'filepath~N:string', where N is the revision number.
6913
--include and --exclude options can be used to search only (or exclude
6914
from search) files with base name matches the specified Unix style GLOB
6915
pattern. The GLOB pattern an use *, ?, and [...] as wildcards, and \\
6916
to quote wildcard or backslash character literally. Note that the glob
6917
pattern is not a regular expression.
6919
[1] http://docs.python.org/library/re.html#regular-expression-syntax
6922
encoding_type = 'replace'
6923
takes_args = ['pattern', 'path*']
6927
Option('color', type=str, argname='when',
6928
help='Show match in color. WHEN is never, always or auto.'),
6929
Option('diff', short_name='p',
6930
help='Grep for pattern in changeset for each revision.'),
6931
ListOption('exclude', type=str, argname='glob', short_name='X',
6932
help="Skip files whose base name matches GLOB."),
6933
ListOption('include', type=str, argname='glob', short_name='I',
6934
help="Search only files whose base name matches GLOB."),
6935
Option('files-with-matches', short_name='l',
6936
help='Print only the name of each input file in '
6937
'which PATTERN is found.'),
6938
Option('files-without-match', short_name='L',
6939
help='Print only the name of each input file in '
6940
'which PATTERN is not found.'),
6941
Option('fixed-string', short_name='F',
6942
help='Interpret PATTERN is a single fixed string (not regex).'),
6944
help='Search for pattern starting from the root of the branch. '
6945
'(implies --recursive)'),
6946
Option('ignore-case', short_name='i',
6947
help='Ignore case distinctions while matching.'),
6949
help='Number of levels to display - 0 for all, 1 for collapsed '
6952
type=_parse_levels),
6953
Option('line-number', short_name='n',
6954
help='Show 1-based line number.'),
6955
Option('no-recursive',
6956
help="Don't recurse into subdirectories. (default is --recursive)"),
6957
Option('null', short_name='Z',
6958
help='Write an ASCII NUL (\\0) separator '
6959
'between output lines rather than a newline.'),
6963
def run(self, verbose=False, ignore_case=False, no_recursive=False,
6964
from_root=False, null=False, levels=None, line_number=False,
6965
path_list=None, revision=None, pattern=None, include=None,
6966
exclude=None, fixed_string=False, files_with_matches=False,
6967
files_without_match=False, color=None, diff=False):
6968
from breezy import _termcolor
6971
if path_list is None:
6975
raise errors.CommandError(
6976
'cannot specify both --from-root and PATH.')
6978
if files_with_matches and files_without_match:
6979
raise errors.CommandError(
6980
'cannot specify both '
6981
'-l/--files-with-matches and -L/--files-without-matches.')
6983
global_config = _mod_config.GlobalConfig()
6986
color = global_config.get_user_option('grep_color')
6991
if color not in ['always', 'never', 'auto']:
6992
raise errors.CommandError('Valid values for --color are '
6993
'"always", "never" or "auto".')
6999
if revision is not None or levels == 0:
7000
# print revision numbers as we may be showing multiple revisions
7007
if not ignore_case and grep.is_fixed_string(pattern):
7008
# if the pattern isalnum, implicitly use to -F for faster grep
7010
elif ignore_case and fixed_string:
7011
# GZ 2010-06-02: Fall back to regexp rather than lowercasing
7012
# pattern and text which will cause pain later
7013
fixed_string = False
7014
pattern = re.escape(pattern)
7017
re_flags = re.MULTILINE
7019
re_flags |= re.IGNORECASE
7021
if not fixed_string:
7022
patternc = grep.compile_pattern(
7023
pattern.encode(grep._user_encoding), re_flags)
7025
if color == 'always':
7027
elif color == 'never':
7029
elif color == 'auto':
7030
show_color = _termcolor.allow_color()
7032
opts = grep.GrepOptions()
7034
opts.verbose = verbose
7035
opts.ignore_case = ignore_case
7036
opts.no_recursive = no_recursive
7037
opts.from_root = from_root
7039
opts.levels = levels
7040
opts.line_number = line_number
7041
opts.path_list = path_list
7042
opts.revision = revision
7043
opts.pattern = pattern
7044
opts.include = include
7045
opts.exclude = exclude
7046
opts.fixed_string = fixed_string
7047
opts.files_with_matches = files_with_matches
7048
opts.files_without_match = files_without_match
7052
opts.eol_marker = eol_marker
7053
opts.print_revno = print_revno
7054
opts.patternc = patternc
7055
opts.recursive = not no_recursive
7056
opts.fixed_string = fixed_string
7057
opts.outf = self.outf
7058
opts.show_color = show_color
7062
# files_with_matches, files_without_match
7063
# levels(?), line_number, from_root
7065
# These are silently ignored.
7066
grep.grep_diff(opts)
7067
elif revision is None:
7068
grep.workingtree_grep(opts)
7070
grep.versioned_grep(opts)
7073
class cmd_patch(Command):
7074
"""Apply a named patch to the current tree.
7078
takes_args = ['filename?']
7079
takes_options = [Option('strip', type=int, short_name='p',
7080
help=("Strip the smallest prefix containing num "
7081
"leading slashes from filenames.")),
7082
Option('silent', help='Suppress chatter.')]
7084
def run(self, filename=None, strip=None, silent=False):
7085
from .patch import patch_tree
7086
wt = WorkingTree.open_containing('.')[0]
7090
if filename is None:
7091
my_file = getattr(sys.stdin, 'buffer', sys.stdin)
7093
my_file = open(filename, 'rb')
7094
patches = [my_file.read()]
7095
return patch_tree(wt, patches, strip, quiet=is_quiet(), out=self.outf)
7098
class cmd_resolve_location(Command):
7099
__doc__ = """Expand a location to a full URL.
7102
Look up a Launchpad URL.
7104
brz resolve-location lp:brz
7106
takes_args = ['location']
7109
def run(self, location):
7110
from .location import location_to_url
7111
url = location_to_url(location)
7112
display_url = urlutils.unescape_for_display(url, self.outf.encoding)
7113
self.outf.write('%s\n' % display_url)
7116
def _register_lazy_builtins():
7117
# register lazy builtins from other modules; called at startup and should
7118
# be only called once.
7119
for (name, aliases, module_name) in [
7120
('cmd_bisect', [], 'breezy.bisect'),
7121
('cmd_bundle_info', [], 'breezy.bzr.bundle.commands'),
7122
('cmd_config', [], 'breezy.config'),
7123
('cmd_dump_btree', [], 'breezy.bzr.debug_commands'),
7124
('cmd_file_id', [], 'breezy.bzr.debug_commands'),
7125
('cmd_file_path', [], 'breezy.bzr.debug_commands'),
7126
('cmd_version_info', [], 'breezy.cmd_version_info'),
7127
('cmd_resolve', ['resolved'], 'breezy.conflicts'),
7128
('cmd_conflicts', [], 'breezy.conflicts'),
7129
('cmd_ping', [], 'breezy.bzr.smart.ping'),
7130
('cmd_sign_my_commits', [], 'breezy.commit_signature_commands'),
7131
('cmd_verify_signatures', [], 'breezy.commit_signature_commands'),
7132
('cmd_test_script', [], 'breezy.cmd_test_script'),
7134
builtin_command_registry.register_lazy(name, aliases, module_name)