20
20
delta as _mod_delta,
25
25
revision as _mod_revision,
27
from . import errors as errors
28
from .trace import mutter, warning
29
from .workingtree import ShelvingUnsupported
27
import bzrlib.errors as errors
28
from bzrlib.osutils import is_inside_any
29
from bzrlib.symbol_versioning import (deprecated_function,
31
from bzrlib.trace import mutter, warning
32
33
# TODO: when showing single-line logs, truncate to the width of the terminal
33
34
# if known, but only if really going to the terminal (not into a file)
36
def report_changes(to_file, old, new, specific_files,
37
show_short_reporter, show_long_callback,
38
short=False, want_unchanged=False,
39
want_unversioned=False, show_ids=False, classify=True):
40
"""Display summary of changes.
42
This compares two trees with regards to a list of files, and delegates
43
the display to underlying elements.
45
For short output, it creates an iterator on all changes, and lets a given
46
reporter display these changes.
48
For stantard output, it creates a delta of the changes, and forwards it
51
:param to_file: If set, write to this file (default stdout.)
52
:param old: Start tree for the comparison
53
:param end: End tree for the comparison
54
:param specific_files: If set, a list of filenames whose status should be
55
shown. It is an error to give a filename that is not in the working
56
tree, or in the working inventory or in the basis inventory.
57
:param show_short_reporter: Reporter in charge of display for short output
58
:param show_long_callback: Callback in charge of display for normal output
59
:param short: If True, gives short SVN-style status lines.
60
:param want_unchanged: Deprecated parameter. If set, includes unchanged
62
:param show_ids: If set, includes each file's id.
63
:param want_unversioned: If False, only shows versioned files.
64
:param classify: Add special symbols to indicate file kind.
68
changes = new.iter_changes(old, want_unchanged, specific_files,
69
require_versioned=False, want_unversioned=want_unversioned)
70
_mod_delta.report_changes(changes, show_short_reporter)
72
delta = new.changes_from(old, want_unchanged=want_unchanged,
73
specific_files=specific_files,
74
want_unversioned=want_unversioned)
75
# filter out unknown files. We may want a tree method for
77
delta.unversioned = [change for change in delta.unversioned
78
if not new.is_ignored(change.path[1])]
79
show_long_callback(to_file, delta,
81
show_unchanged=want_unchanged,
85
def show_tree_status(wt,
37
def show_tree_status(wt, show_unchanged=None,
86
38
specific_files=None,
119
71
:param verbose: If True, show all merged revisions, not just
121
73
:param versioned: If True, only shows versioned files.
122
:param classify: Add special symbols to indicate file kind.
123
:param show_long_callback: A callback: message = show_long_callback(to_file, delta,
124
show_ids, show_unchanged, indent, filter), only used with the long output
75
if show_unchanged is not None:
76
warn("show_tree_status with show_unchanged has been deprecated "
77
"since bzrlib 0.9", DeprecationWarning, stacklevel=2)
126
79
if to_file is None:
127
80
to_file = sys.stdout
130
84
new_is_working_tree = True
131
85
if revision is None:
132
86
if wt.last_revision() != wt.branch.last_revision():
133
warning("working tree is out of date, run 'brz update'")
87
warning("working tree is out of date, run 'bzr update'")
135
89
old = new.basis_tree()
136
90
elif len(revision) > 0:
138
92
old = revision[0].as_tree(wt.branch)
139
except errors.NoSuchRevision as e:
140
raise errors.CommandError(str(e))
93
except errors.NoSuchRevision, e:
94
raise errors.BzrCommandError(str(e))
141
95
if (len(revision) > 1) and (revision[1].spec is not None):
143
97
new = revision[1].as_tree(wt.branch)
144
98
new_is_working_tree = False
145
except errors.NoSuchRevision as e:
146
raise errors.CommandError(str(e))
99
except errors.NoSuchRevision, e:
100
raise errors.BzrCommandError(str(e))
149
with old.lock_read(), new.lock_read():
150
for hook in hooks['pre_status']:
151
hook(StatusHookParams(
152
old, new, to_file, versioned, show_ids, short, verbose,
153
specific_files=specific_files))
155
106
specific_files, nonexistents \
156
107
= _filter_nonexistent(specific_files, old, new)
157
108
want_unversioned = not versioned
159
# Reporter used for short outputs
160
reporter = _mod_delta._ChangeReporter(
161
output_file=to_file, unversioned_filter=new.is_ignored,
163
report_changes(to_file, old, new, specific_files,
164
reporter, show_long_callback,
165
short=short, want_unversioned=want_unversioned,
166
show_ids=show_ids, classify=classify)
168
# show the ignored files among specific files (i.e. show the files
169
# identified from input that we choose to ignore).
170
if specific_files is not None:
171
# Ignored files is sorted because specific_files is already sorted
172
ignored_files = [specific for specific in
173
specific_files if new.is_ignored(specific)]
174
if len(ignored_files) > 0 and not short:
175
to_file.write("ignored:\n")
179
for ignored_file in ignored_files:
180
to_file.write("%s %s\n" % (prefix, ignored_file))
110
changes = new.iter_changes(old, show_unchanged, specific_files,
111
require_versioned=False, want_unversioned=want_unversioned)
112
reporter = _mod_delta._ChangeReporter(output_file=to_file,
113
unversioned_filter=new.is_ignored)
114
_mod_delta.report_changes(changes, reporter)
116
delta = new.changes_from(old, want_unchanged=show_unchanged,
117
specific_files=specific_files,
118
want_unversioned=want_unversioned)
119
# filter out unknown files. We may want a tree method for
121
delta.unversioned = [unversioned for unversioned in
122
delta.unversioned if not new.is_ignored(unversioned[0])]
125
show_unchanged=show_unchanged,
182
127
# show the new conflicts only for now. XXX: get them from the
184
129
conflicts = new.conflicts()
185
130
if specific_files is not None:
186
conflicts = conflicts.select_conflicts(
187
new, specific_files, ignore_misses=True, recurse=True)[1]
131
conflicts = conflicts.select_conflicts(new, specific_files,
132
ignore_misses=True, recurse=True)[1]
188
133
if len(conflicts) > 0 and not short:
189
134
to_file.write("conflicts:\n")
190
135
for conflict in conflicts:
278
214
to_file.write('pending merges:\n')
280
to_file.write('pending merge tips:'
281
' (use -v to see all merge revisions)\n')
216
to_file.write('pending merge tips: (use -v to see all merge revisions)\n')
282
217
graph = branch.repository.get_graph()
283
218
other_revisions = [last_revision]
284
219
log_formatter = log.LineLogFormatter(to_file)
285
220
for merge in pending:
287
rev = branch.repository.get_revision(merge)
222
rev = branch.repository.get_revisions([merge])[0]
288
223
except errors.NoSuchRevision:
289
224
# If we are missing a revision, just print out the revision id
290
to_file.write(first_prefix + '(ghost) ' +
291
merge.decode('utf-8') + '\n')
225
to_file.write(first_prefix + '(ghost) ' + merge + '\n')
292
226
other_revisions.append(merge)
295
229
# Log the merge, as it gets a slightly different formatting
296
show_log_message(rev, first_prefix)
230
log_message = log_formatter.log_string(None, rev,
231
term_width - len(first_prefix))
232
to_file.write(first_prefix + log_message + '\n')
304
240
merge_extra.discard(_mod_revision.NULL_REVISION)
306
242
# Get a handle to all of the revisions we will need
307
revisions = dict(branch.repository.iter_revisions(merge_extra))
244
revisions = dict((rev.revision_id, rev) for rev in
245
branch.repository.get_revisions(merge_extra))
246
except errors.NoSuchRevision:
247
# One of the sub nodes is a ghost, check each one
249
for revision_id in merge_extra:
251
rev = branch.repository.get_revisions([revision_id])[0]
252
except errors.NoSuchRevision:
253
revisions[revision_id] = None
255
revisions[revision_id] = rev
309
257
# Display the revisions brought in by this merge.
310
258
rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
311
branch.repository.get_parent_map(merge_extra))
259
branch.repository.get_parent_map(merge_extra))
312
260
# Skip the first node
313
num, first, depth, eom = next(rev_id_iterator)
261
num, first, depth, eom = rev_id_iterator.next()
314
262
if first != merge:
315
263
raise AssertionError('Somehow we misunderstood how'
316
' iter_topo_order works %s != %s' % (first, merge))
264
' iter_topo_order works %s != %s' % (first, merge))
317
265
for num, sub_merge, depth, eom in rev_id_iterator:
318
266
rev = revisions[sub_merge]
320
to_file.write(sub_prefix + '(ghost) ' +
321
sub_merge.decode('utf-8') + '\n')
268
to_file.write(sub_prefix + '(ghost) ' + sub_merge + '\n')
323
show_log_message(revisions[sub_merge], sub_prefix)
270
log_message = log_formatter.log_string(None,
271
revisions[sub_merge],
272
term_width - len(sub_prefix))
273
to_file.write(sub_prefix + log_message + '\n')
326
276
def _filter_nonexistent(orig_paths, old_tree, new_tree):
342
292
s = old_tree.filter_unversioned_files(orig_paths)
343
293
s = new_tree.filter_unversioned_files(s)
344
294
nonexistent = [path for path in s if not new_tree.has_filename(path)]
345
remaining = [path for path in orig_paths if path not in nonexistent]
295
remaining = [path for path in orig_paths if not path in nonexistent]
346
296
# Sorting the 'remaining' list doesn't have much effect in
347
297
# practice, since the various status output sections will sort
348
298
# their groups individually. But for consistency of this
349
299
# function's API, it's better to sort both than just 'nonexistent'.
350
300
return sorted(remaining), sorted(nonexistent)
353
class StatusHooks(_mod_hooks.Hooks):
354
"""A dictionary mapping hook name to a list of callables for status hooks.
356
e.g. ['post_status'] Is the list of items to be called when the
357
status command has finished printing the status.
361
"""Create the default hooks.
363
These are all empty initially, because by default nothing should get
366
_mod_hooks.Hooks.__init__(self, "breezy.status", "hooks")
369
"Called with argument StatusHookParams after Bazaar has "
370
"displayed the status. StatusHookParams has the attributes "
371
"(old_tree, new_tree, to_file, versioned, show_ids, short, "
372
"verbose). The last four arguments correspond to the command "
373
"line options specified by the user for the status command. "
374
"to_file is the output stream for writing.",
378
"Called with argument StatusHookParams before Bazaar "
379
"displays the status. StatusHookParams has the attributes "
380
"(old_tree, new_tree, to_file, versioned, show_ids, short, "
381
"verbose). The last four arguments correspond to the command "
382
"line options specified by the user for the status command. "
383
"to_file is the output stream for writing.",
387
class StatusHookParams(object):
388
"""Object holding parameters passed to post_status hooks.
390
:ivar old_tree: Start tree (basis tree) for comparison.
391
:ivar new_tree: Working tree.
392
:ivar to_file: If set, write to this file.
393
:ivar versioned: Show only versioned files.
394
:ivar show_ids: Show internal object ids.
395
:ivar short: Use short status indicators.
396
:ivar verbose: Verbose flag.
399
def __init__(self, old_tree, new_tree, to_file, versioned, show_ids,
400
short, verbose, specific_files=None):
401
"""Create a group of post_status hook parameters.
403
:param old_tree: Start tree (basis tree) for comparison.
404
:param new_tree: Working tree.
405
:param to_file: If set, write to this file.
406
:param versioned: Show only versioned files.
407
:param show_ids: Show internal object ids.
408
:param short: Use short status indicators.
409
:param verbose: Verbose flag.
410
:param specific_files: If set, a list of filenames whose status should be
411
shown. It is an error to give a filename that is not in the
412
working tree, or in the working inventory or in the basis inventory.
414
self.old_tree = old_tree
415
self.new_tree = new_tree
416
self.to_file = to_file
417
self.versioned = versioned
418
self.show_ids = show_ids
420
self.verbose = verbose
421
self.specific_files = specific_files
423
def __eq__(self, other):
424
return self.__dict__ == other.__dict__
427
return "<%s(%s, %s, %s, %s, %s, %s, %s, %s)>" % (
428
self.__class__.__name__, self.old_tree, self.new_tree,
429
self.to_file, self.versioned, self.show_ids, self.short,
430
self.verbose, self.specific_files)
433
def _show_shelve_summary(params):
434
"""post_status hook to display a summary of shelves.
436
:param params: StatusHookParams.
438
# Don't show shelves if status of specific files is being shown, only if
439
# no file arguments have been passed
440
if params.specific_files:
442
get_shelf_manager = getattr(params.new_tree, 'get_shelf_manager', None)
443
if get_shelf_manager is None:
446
manager = get_shelf_manager()
447
except ShelvingUnsupported:
448
mutter('shelving not supported by tree, not displaying shelves.')
450
shelves = manager.active_shelves()
452
singular = '%d shelf exists. '
453
plural = '%d shelves exist. '
454
if len(shelves) == 1:
458
params.to_file.write(fmt % len(shelves))
459
params.to_file.write('See "brz shelve --list" for details.\n')
462
hooks = StatusHooks()
465
hooks.install_named_hook('post_status', _show_shelve_summary,