13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
revision as _mod_revision,
27
from . import errors as errors
28
from .trace import mutter, warning
29
from .workingtree import ShelvingUnsupported
18
from bzrlib.osutils import is_inside_any
19
from bzrlib.delta import compare_trees
20
from bzrlib.log import line_log
21
from bzrlib.errors import NoSuchRevision
32
23
# TODO: when showing single-line logs, truncate to the width of the terminal
33
24
# 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,
95
show_long_callback=_mod_delta.report_delta):
96
"""Display summary of changes.
98
By default this compares the working tree to a previous revision.
99
If the revision argument is given, summarizes changes between the
27
def show_status(branch, show_unchanged=False,
33
"""Display summary of changes.
35
By default this compares the working tree to a previous revision.
36
If the revision argument is given, summarizes changes between the
100
37
working tree and another, or between two revisions.
102
The result is written out as Unicode and to_file should be able
39
The result is written out as Unicode and to_file should be able
105
If showing the status of a working tree, extra information is included
106
about unknown files, conflicts, and pending merges.
108
:param specific_files: If set, a list of filenames whose status should be
109
shown. It is an error to give a filename that is not in the working
110
tree, or in the working inventory or in the basis inventory.
111
:param show_ids: If set, includes each file's id.
112
:param to_file: If set, write to this file (default stdout.)
113
:param show_pending: If set, write pending merges.
114
:param revision: If None, compare latest revision with working tree
115
If not None, it must be a RevisionSpec list.
116
If one revision, compare with working tree.
117
If two revisions, show status between first and second.
118
:param short: If True, gives short SVN-style status lines.
119
:param verbose: If True, show all merged revisions, not just
121
: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
43
If set, includes unchanged files.
46
If set, only show the status of files in this list.
49
If set, includes each file's id.
52
If set, write to this file (default stdout.)
55
If set, write pending merges.
58
If None the compare latest revision with working tree
59
If one revision show compared it with working tree.
60
If two revisions show status between first and second.
127
63
to_file = sys.stdout
130
67
new_is_working_tree = True
131
68
if revision is None:
132
if wt.last_revision() != wt.branch.last_revision():
133
warning("working tree is out of date, run 'brz update'")
135
old = new.basis_tree()
69
old = branch.basis_tree()
70
new = branch.working_tree()
136
71
elif len(revision) > 0:
138
old = revision[0].as_tree(wt.branch)
139
except errors.NoSuchRevision as e:
140
raise errors.CommandError(str(e))
141
if (len(revision) > 1) and (revision[1].spec is not None):
73
rev_id = revision[0].in_history(branch).rev_id
74
old = branch.revision_tree(rev_id)
75
except NoSuchRevision, e:
76
raise BzrCommandError(str(e))
143
new = revision[1].as_tree(wt.branch)
79
rev_id = revision[1].in_history(branch).rev_id
80
new = branch.revision_tree(rev_id)
144
81
new_is_working_tree = False
145
except errors.NoSuchRevision as e:
146
raise errors.CommandError(str(e))
82
except NoSuchRevision, e:
83
raise 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
specific_files, nonexistents \
156
= _filter_nonexistent(specific_files, old, new)
157
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))
182
# show the new conflicts only for now. XXX: get them from the
184
conflicts = new.conflicts()
185
if specific_files is not None:
186
conflicts = conflicts.select_conflicts(
187
new, specific_files, ignore_misses=True, recurse=True)[1]
188
if len(conflicts) > 0 and not short:
189
to_file.write("conflicts:\n")
190
for conflict in conflicts:
195
to_file.write("%s %s\n" % (prefix, conflict.describe()))
196
# Show files that were requested but don't exist (and are
197
# not versioned). We don't involve delta in this; these
198
# paths are really the province of just the status
199
# command, since they have more to do with how it was
200
# invoked than with the tree it's operating on.
201
if nonexistents and not short:
202
to_file.write("nonexistent:\n")
203
for nonexistent in nonexistents:
204
# We could calculate prefix outside the loop but, given
205
# how rarely this ought to happen, it's OK and arguably
206
# slightly faster to do it here (ala conflicts above)
211
to_file.write("%s %s\n" % (prefix, nonexistent))
212
if (new_is_working_tree and show_pending):
213
show_pending_merges(new, to_file, short, verbose=verbose)
215
raise errors.PathsDoNotExist(nonexistents)
216
for hook in hooks['post_status']:
217
hook(StatusHookParams(
218
old, new, to_file, versioned, show_ids, short, verbose,
219
specific_files=specific_files))
222
def _get_sorted_revisions(tip_revision, revision_ids, parent_map):
223
"""Get an iterator which will return the revisions in merge sorted order.
225
This will build up a list of all nodes, such that only nodes in the list
226
are referenced. It then uses MergeSorter to return them in 'merge-sorted'
229
:param revision_ids: A set of revision_ids
230
:param parent_map: The parent information for each node. Revisions which
231
are considered ghosts should not be present in the map.
232
:return: iterator from MergeSorter.iter_topo_order()
234
# MergeSorter requires that all nodes be present in the graph, so get rid
235
# of any references pointing outside of this graph.
237
for revision_id in revision_ids:
238
if revision_id not in parent_map: # ghost
239
parent_graph[revision_id] = []
241
# Only include parents which are in this sub-graph
242
parent_graph[revision_id] = [p for p in parent_map[revision_id]
243
if p in revision_ids]
244
sorter = tsort.MergeSorter(parent_graph, tip_revision)
245
return sorter.iter_topo_order()
248
def show_pending_merges(new, to_file, short=False, verbose=False):
85
new = branch.working_tree()
88
delta = compare_trees(old, new, want_unchanged=show_unchanged,
89
specific_files=specific_files)
93
show_unchanged=show_unchanged)
95
if new_is_working_tree:
96
list_paths('unknown', new.unknowns(), specific_files, to_file)
97
list_paths('conflicts', new.iter_conflicts(), specific_files, to_file)
98
if new_is_working_tree and show_pending:
99
show_pending_merges(new, to_file)
103
def show_pending_merges(new, to_file):
249
104
"""Write out a display of pending merges in a working tree."""
250
parents = new.get_parent_ids()
254
term_width = osutils.terminal_width()
255
if term_width is not None:
256
# we need one extra space for terminals that wrap on last char
257
term_width = term_width - 1
265
def show_log_message(rev, prefix):
266
if term_width is None:
269
width = term_width - len(prefix)
270
log_message = log_formatter.log_string(None, rev, width, prefix=prefix)
271
to_file.write(log_message + '\n')
273
pending = parents[1:]
105
pending = new.pending_merges()
274
106
branch = new.branch
275
last_revision = parents[0]
278
to_file.write('pending merges:\n')
280
to_file.write('pending merge tips:'
281
' (use -v to see all merge revisions)\n')
282
graph = branch.repository.get_graph()
283
other_revisions = [last_revision]
284
log_formatter = log.LineLogFormatter(to_file)
285
for merge in pending:
107
if len(pending) == 0:
109
print >>to_file, 'pending merges:'
110
last_revision = branch.last_revision()
111
if last_revision is not None:
112
ignore = set(branch.get_ancestry(last_revision))
115
for merge in new.pending_merges():
287
rev = branch.repository.get_revision(merge)
288
except errors.NoSuchRevision:
289
# 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')
292
other_revisions.append(merge)
295
# Log the merge, as it gets a slightly different formatting
296
show_log_message(rev, first_prefix)
300
# Find all of the revisions in the merge source, which are not in the
301
# last committed revision.
302
merge_extra = graph.find_unique_ancestors(merge, other_revisions)
303
other_revisions.append(merge)
304
merge_extra.discard(_mod_revision.NULL_REVISION)
306
# Get a handle to all of the revisions we will need
307
revisions = dict(branch.repository.iter_revisions(merge_extra))
309
# Display the revisions brought in by this merge.
310
rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
311
branch.repository.get_parent_map(merge_extra))
312
# Skip the first node
313
num, first, depth, eom = next(rev_id_iterator)
315
raise AssertionError('Somehow we misunderstood how'
316
' iter_topo_order works %s != %s' % (first, merge))
317
for num, sub_merge, depth, eom in rev_id_iterator:
318
rev = revisions[sub_merge]
320
to_file.write(sub_prefix + '(ghost) ' +
321
sub_merge.decode('utf-8') + '\n')
323
show_log_message(revisions[sub_merge], sub_prefix)
326
def _filter_nonexistent(orig_paths, old_tree, new_tree):
327
"""Convert orig_paths to two sorted lists and return them.
329
The first is orig_paths paths minus the items in the second list,
330
and the second list is paths that are not in either inventory or
331
tree (they don't qualify if they exist in the tree's inventory, or
332
if they exist in the tree but are not versioned.)
334
If either of the two lists is empty, return it as an empty list.
336
This can be used by operations such as brz status that can accept
337
unknown or ignored files.
339
mutter("check paths: %r", orig_paths)
341
return orig_paths, []
342
s = old_tree.filter_unversioned_files(orig_paths)
343
s = new_tree.filter_unversioned_files(s)
344
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]
346
# Sorting the 'remaining' list doesn't have much effect in
347
# practice, since the various status output sections will sort
348
# their groups individually. But for consistency of this
349
# function's API, it's better to sort both than just 'nonexistent'.
350
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,
118
m_revision = branch.get_revision(merge)
119
print >> to_file, ' ', line_log(m_revision, 77)
120
inner_merges = branch.get_ancestry(merge)
121
inner_merges.reverse()
122
for mmerge in inner_merges:
125
mm_revision = branch.get_revision(mmerge)
126
print >> to_file, ' ', line_log(mm_revision, 75)
128
except NoSuchRevision:
129
print >> to_file, ' ', merge
131
def list_paths(header, paths, specific_files, to_file):
134
if specific_files and not is_inside_any(specific_files, path):
137
print >>to_file, '%s:' % header
139
print >>to_file, ' ', path