14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
22
20
delta as _mod_delta,
27
24
revision as _mod_revision,
29
from . import errors as errors
30
from .trace import mutter, warning
31
from .workingtree import ShelvingUnsupported
26
import bzrlib.errors as errors
27
from bzrlib.trace import mutter, warning
34
29
# TODO: when showing single-line logs, truncate to the width of the terminal
35
30
# if known, but only if really going to the terminal (not into a file)
38
def report_changes(to_file, old, new, specific_files,
39
show_short_reporter, show_long_callback,
40
short=False, want_unchanged=False,
41
want_unversioned=False, show_ids=False, classify=True):
33
def report_changes(to_file, old, new, specific_files,
34
show_short_reporter, show_long_callback,
35
short=False, want_unchanged=False,
36
want_unversioned=False, show_ids=False):
42
37
"""Display summary of changes.
44
This compares two trees with regards to a list of files, and delegates
39
This compares two trees with regards to a list of files, and delegates
45
40
the display to underlying elements.
47
42
For short output, it creates an iterator on all changes, and lets a given
64
59
:param show_ids: If set, includes each file's id.
65
60
:param want_unversioned: If False, only shows versioned files.
66
:param classify: Add special symbols to indicate file kind.
70
64
changes = new.iter_changes(old, want_unchanged, specific_files,
71
require_versioned=False, want_unversioned=want_unversioned)
65
require_versioned=False, want_unversioned=want_unversioned)
72
66
_mod_delta.report_changes(changes, show_short_reporter)
74
69
delta = new.changes_from(old, want_unchanged=want_unchanged,
75
specific_files=specific_files,
76
want_unversioned=want_unversioned)
70
specific_files=specific_files,
71
want_unversioned=want_unversioned)
77
72
# filter out unknown files. We may want a tree method for
79
delta.unversioned = [change for change in delta.unversioned
80
if not new.is_ignored(change.path[1])]
81
show_long_callback(to_file, delta,
74
delta.unversioned = [unversioned for unversioned in
75
delta.unversioned if not new.is_ignored(unversioned[0])]
76
show_long_callback(to_file, delta,
83
show_unchanged=want_unchanged,
87
def show_tree_status(wt,
78
show_unchanged=want_unchanged)
81
def show_tree_status(wt, show_unchanged=None,
88
82
specific_files=None,
121
116
:param verbose: If True, show all merged revisions, not just
123
118
:param versioned: If True, only shows versioned files.
124
:param classify: Add special symbols to indicate file kind.
125
:param show_long_callback: A callback: message = show_long_callback(to_file, delta,
119
:param show_long_callback: A callback: message = show_long_callback(to_file, delta,
126
120
show_ids, show_unchanged, indent, filter), only used with the long output
122
if show_unchanged is not None:
123
warn("show_tree_status with show_unchanged has been deprecated "
124
"since bzrlib 0.9", DeprecationWarning, stacklevel=2)
128
126
if to_file is None:
129
127
to_file = sys.stdout
132
131
new_is_working_tree = True
133
132
if revision is None:
134
133
if wt.last_revision() != wt.branch.last_revision():
135
warning("working tree is out of date, run 'brz update'")
134
warning("working tree is out of date, run 'bzr update'")
137
136
old = new.basis_tree()
138
137
elif len(revision) > 0:
140
139
old = revision[0].as_tree(wt.branch)
141
except errors.NoSuchRevision as e:
140
except errors.NoSuchRevision, e:
142
141
raise errors.BzrCommandError(str(e))
143
142
if (len(revision) > 1) and (revision[1].spec is not None):
145
144
new = revision[1].as_tree(wt.branch)
146
145
new_is_working_tree = False
147
except errors.NoSuchRevision as e:
146
except errors.NoSuchRevision, e:
148
147
raise errors.BzrCommandError(str(e))
151
with old.lock_read(), new.lock_read():
152
for hook in hooks['pre_status']:
153
hook(StatusHookParams(
154
old, new, to_file, versioned, show_ids, short, verbose,
155
specific_files=specific_files))
157
153
specific_files, nonexistents \
158
154
= _filter_nonexistent(specific_files, old, new)
159
155
want_unversioned = not versioned
161
157
# Reporter used for short outputs
162
reporter = _mod_delta._ChangeReporter(
163
output_file=to_file, unversioned_filter=new.is_ignored,
165
report_changes(to_file, old, new, specific_files,
166
reporter, show_long_callback,
167
short=short, want_unversioned=want_unversioned,
168
show_ids=show_ids, classify=classify)
158
reporter = _mod_delta._ChangeReporter(output_file=to_file,
159
unversioned_filter=new.is_ignored)
160
report_changes(to_file, old, new, specific_files,
161
reporter, show_long_callback,
162
short=short, want_unchanged=show_unchanged,
163
want_unversioned=want_unversioned, show_ids=show_ids)
170
165
# show the ignored files among specific files (i.e. show the files
171
# identified from input that we choose to ignore).
166
# identified from input that we choose to ignore).
172
167
if specific_files is not None:
173
168
# Ignored files is sorted because specific_files is already sorted
174
169
ignored_files = [specific for specific in
175
specific_files if new.is_ignored(specific)]
170
specific_files if new.is_ignored(specific)]
176
171
if len(ignored_files) > 0 and not short:
177
172
to_file.write("ignored:\n")
306
301
merge_extra.discard(_mod_revision.NULL_REVISION)
308
303
# Get a handle to all of the revisions we will need
309
revisions = dict(branch.repository.iter_revisions(merge_extra))
305
revisions = dict((rev.revision_id, rev) for rev in
306
branch.repository.get_revisions(merge_extra))
307
except errors.NoSuchRevision:
308
# One of the sub nodes is a ghost, check each one
310
for revision_id in merge_extra:
312
rev = branch.repository.get_revisions([revision_id])[0]
313
except errors.NoSuchRevision:
314
revisions[revision_id] = None
316
revisions[revision_id] = rev
311
318
# Display the revisions brought in by this merge.
312
319
rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
313
branch.repository.get_parent_map(merge_extra))
320
branch.repository.get_parent_map(merge_extra))
314
321
# Skip the first node
315
num, first, depth, eom = next(rev_id_iterator)
322
num, first, depth, eom = rev_id_iterator.next()
316
323
if first != merge:
317
324
raise AssertionError('Somehow we misunderstood how'
318
' iter_topo_order works %s != %s' % (first, merge))
325
' iter_topo_order works %s != %s' % (first, merge))
319
326
for num, sub_merge, depth, eom in rev_id_iterator:
320
327
rev = revisions[sub_merge]
322
to_file.write(sub_prefix + '(ghost) ' +
323
sub_merge.decode('utf-8') + '\n')
329
to_file.write(sub_prefix + '(ghost) ' + sub_merge + '\n')
325
331
show_log_message(revisions[sub_merge], sub_prefix)
344
350
s = old_tree.filter_unversioned_files(orig_paths)
345
351
s = new_tree.filter_unversioned_files(s)
346
352
nonexistent = [path for path in s if not new_tree.has_filename(path)]
347
remaining = [path for path in orig_paths if path not in nonexistent]
353
remaining = [path for path in orig_paths if not path in nonexistent]
348
354
# Sorting the 'remaining' list doesn't have much effect in
349
355
# practice, since the various status output sections will sort
350
356
# their groups individually. But for consistency of this
351
357
# function's API, it's better to sort both than just 'nonexistent'.
352
358
return sorted(remaining), sorted(nonexistent)
355
class StatusHooks(_mod_hooks.Hooks):
356
"""A dictionary mapping hook name to a list of callables for status hooks.
358
e.g. ['post_status'] Is the list of items to be called when the
359
status command has finished printing the status.
363
"""Create the default hooks.
365
These are all empty initially, because by default nothing should get
368
_mod_hooks.Hooks.__init__(self, "breezy.status", "hooks")
371
"Called with argument StatusHookParams after Bazaar has "
372
"displayed the status. StatusHookParams has the attributes "
373
"(old_tree, new_tree, to_file, versioned, show_ids, short, "
374
"verbose). The last four arguments correspond to the command "
375
"line options specified by the user for the status command. "
376
"to_file is the output stream for writing.",
380
"Called with argument StatusHookParams before Bazaar "
381
"displays the status. StatusHookParams has the attributes "
382
"(old_tree, new_tree, to_file, versioned, show_ids, short, "
383
"verbose). The last four arguments correspond to the command "
384
"line options specified by the user for the status command. "
385
"to_file is the output stream for writing.",
389
class StatusHookParams(object):
390
"""Object holding parameters passed to post_status hooks.
392
:ivar old_tree: Start tree (basis tree) for comparison.
393
:ivar new_tree: Working tree.
394
:ivar to_file: If set, write to this file.
395
:ivar versioned: Show only versioned files.
396
:ivar show_ids: Show internal object ids.
397
:ivar short: Use short status indicators.
398
:ivar verbose: Verbose flag.
401
def __init__(self, old_tree, new_tree, to_file, versioned, show_ids,
402
short, verbose, specific_files=None):
403
"""Create a group of post_status hook parameters.
405
:param old_tree: Start tree (basis tree) for comparison.
406
:param new_tree: Working tree.
407
:param to_file: If set, write to this file.
408
:param versioned: Show only versioned files.
409
:param show_ids: Show internal object ids.
410
:param short: Use short status indicators.
411
:param verbose: Verbose flag.
412
:param specific_files: If set, a list of filenames whose status should be
413
shown. It is an error to give a filename that is not in the
414
working tree, or in the working inventory or in the basis inventory.
416
self.old_tree = old_tree
417
self.new_tree = new_tree
418
self.to_file = to_file
419
self.versioned = versioned
420
self.show_ids = show_ids
422
self.verbose = verbose
423
self.specific_files = specific_files
425
def __eq__(self, other):
426
return self.__dict__ == other.__dict__
429
return "<%s(%s, %s, %s, %s, %s, %s, %s, %s)>" % (
430
self.__class__.__name__, self.old_tree, self.new_tree,
431
self.to_file, self.versioned, self.show_ids, self.short,
432
self.verbose, self.specific_files)
435
def _show_shelve_summary(params):
436
"""post_status hook to display a summary of shelves.
438
:param params: StatusHookParams.
440
# Don't show shelves if status of specific files is being shown, only if
441
# no file arguments have been passed
442
if params.specific_files:
444
get_shelf_manager = getattr(params.new_tree, 'get_shelf_manager', None)
445
if get_shelf_manager is None:
448
manager = get_shelf_manager()
449
except ShelvingUnsupported:
450
mutter('shelving not supported by tree, not displaying shelves.')
452
shelves = manager.active_shelves()
454
singular = '%d shelf exists. '
455
plural = '%d shelves exist. '
456
if len(shelves) == 1:
460
params.to_file.write(fmt % len(shelves))
461
params.to_file.write('See "brz shelve --list" for details.\n')
464
hooks = StatusHooks()
467
hooks.install_named_hook('post_status', _show_shelve_summary,