51
55
The lists are normally sorted when the delta is created.
54
57
def __init__(self):
59
61
self.kind_changed = []
61
63
self.unchanged = []
62
64
self.unversioned = []
65
66
def __eq__(self, other):
66
67
if not isinstance(other, TreeDelta):
68
69
return self.added == other.added \
69
and self.removed == other.removed \
70
and self.renamed == other.renamed \
71
and self.copied == other.copied \
72
and self.modified == other.modified \
73
and self.unchanged == other.unchanged \
74
and self.kind_changed == other.kind_changed \
75
and self.unversioned == other.unversioned
70
and self.removed == other.removed \
71
and self.renamed == other.renamed \
72
and self.modified == other.modified \
73
and self.unchanged == other.unchanged \
74
and self.kind_changed == other.kind_changed \
75
and self.unversioned == other.unversioned
77
77
def __ne__(self, other):
78
78
return not (self == other)
80
80
def __repr__(self):
81
81
return "TreeDelta(added=%r, removed=%r, renamed=%r," \
82
" copied=%r, kind_changed=%r, modified=%r, unchanged=%r," \
83
" unversioned=%r)" % (
84
self.added, self.removed, self.renamed, self.copied,
85
self.kind_changed, self.modified, self.unchanged,
82
" kind_changed=%r, modified=%r, unchanged=%r," \
83
" unversioned=%r)" % (self.added,
84
self.removed, self.renamed, self.kind_changed, self.modified,
85
self.unchanged, self.unversioned)
88
87
def has_changed(self):
89
88
return bool(self.modified
94
92
or self.kind_changed)
96
def get_changes_as_text(self, show_ids=False, show_unchanged=False,
99
report_delta(output, self, short_status, show_ids, show_unchanged)
100
return output.getvalue()
94
def touches_file_id(self, file_id):
95
"""Return True if file_id is modified by this delta."""
96
for l in self.added, self.removed, self.modified:
100
for v in self.renamed:
103
for v in self.kind_changed:
109
def show(self, to_file, show_ids=False, show_unchanged=False,
111
"""output this delta in status-like form to to_file."""
112
def show_list(files, short_status_letter=''):
114
path, fid, kind = item[:3]
116
if kind == 'directory':
118
elif kind == 'symlink':
121
if len(item) == 5 and item[4]:
125
print >>to_file, '%s %-30s %s' % (short_status_letter,
128
print >>to_file, '%s %s' % (short_status_letter, path)
132
print >>to_file, 'removed:'
133
show_list(self.removed)
135
show_list(self.removed, 'D')
139
print >>to_file, 'added:'
140
show_list(self.added)
142
show_list(self.added, 'A')
147
short_status_letter = 'R'
149
print >>to_file, 'renamed:'
150
short_status_letter = ''
151
for (oldpath, newpath, fid, kind,
152
text_modified, meta_modified) in self.renamed:
153
if text_modified or meta_modified:
154
extra_modified.append((newpath, fid, kind,
155
text_modified, meta_modified))
159
print >>to_file, '%s %s => %s %s' % (
160
short_status_letter, oldpath, newpath, fid)
162
print >>to_file, '%s %s => %s' % (
163
short_status_letter, oldpath, newpath)
165
if self.kind_changed:
167
short_status_letter = 'K'
169
print >>to_file, 'kind changed:'
170
short_status_letter = ''
171
for (path, fid, old_kind, new_kind) in self.kind_changed:
176
print >>to_file, '%s %s (%s => %s)%s' % (
177
short_status_letter, path, old_kind, new_kind, suffix)
179
if self.modified or extra_modified:
180
short_status_letter = 'M'
182
print >>to_file, 'modified:'
183
short_status_letter = ''
184
show_list(self.modified, short_status_letter)
185
show_list(extra_modified, short_status_letter)
187
if show_unchanged and self.unchanged:
189
print >>to_file, 'unchanged:'
190
show_list(self.unchanged)
192
show_list(self.unchanged, 'S')
195
print >>to_file, 'unknown:'
196
show_list(self.unversioned)
199
@deprecated_function(zero_nine)
200
def compare_trees(old_tree, new_tree, want_unchanged=False,
201
specific_files=None, extra_trees=None,
202
require_versioned=False):
203
"""compare_trees was deprecated in 0.10. Please see Tree.changes_from."""
204
return new_tree.changes_from(old_tree,
205
want_unchanged=want_unchanged,
206
specific_files=specific_files,
207
extra_trees=extra_trees,
208
require_versioned=require_versioned,
103
212
def _compare_trees(old_tree, new_tree, want_unchanged, specific_files,
104
213
include_root, extra_trees=None,
105
require_versioned=False, want_unversioned=False):
214
want_unversioned=False):
106
215
"""Worker function that implements Tree.changes_from."""
107
216
delta = TreeDelta()
108
217
# mutter('start compare_trees')
110
for change in new_tree.iter_changes(
111
old_tree, want_unchanged, specific_files, extra_trees=extra_trees,
112
require_versioned=require_versioned,
219
for (file_id, path, content_change, versioned, parent_id, name, kind,
220
executable) in new_tree._iter_changes(old_tree, want_unchanged,
221
specific_files, extra_trees=extra_trees,
113
222
want_unversioned=want_unversioned):
114
if change.versioned == (False, False):
115
delta.unversioned.append(change)
117
if not include_root and (None, None) == change.parent_id:
119
fully_present = tuple(
120
(change.versioned[x] and change.kind[x] is not None)
223
if versioned == (False, False):
224
delta.unversioned.append((path[1], None, kind[1]))
226
if not include_root and (None, None) == parent_id:
228
fully_present = tuple((versioned[x] and kind[x] is not None) for
122
230
if fully_present[0] != fully_present[1]:
123
231
if fully_present[1] is True:
124
delta.added.append(change)
232
delta.added.append((path[1], file_id, kind[1]))
126
if change.kind[0] == 'symlink' and not new_tree.supports_symlinks():
128
'Ignoring "%s" as symlinks '
129
'are not supported on this filesystem.' % (change.path[0],))
131
delta.removed.append(change)
234
assert fully_present[0] is True
235
delta.removed.append((path[0], file_id, kind[0]))
132
236
elif fully_present[0] is False:
133
delta.missing.append(change)
134
elif change.name[0] != change.name[1] or change.parent_id[0] != change.parent_id[1]:
135
# If the name changes, or the parent_id changes, we have a rename or copy
238
elif name[0] != name[1] or parent_id[0] != parent_id[1]:
239
# If the name changes, or the parent_id changes, we have a rename
136
240
# (if we move a parent, that doesn't count as a rename for the
139
delta.copied.append(change)
141
delta.renamed.append(change)
142
elif change.kind[0] != change.kind[1]:
143
delta.kind_changed.append(change)
144
elif change.changed_content or change.executable[0] != change.executable[1]:
145
delta.modified.append(change)
147
delta.unchanged.append(change)
149
def change_key(change):
150
if change.path[0] is None:
151
path = change.path[1]
153
path = change.path[0]
154
return (path, change.file_id)
156
delta.removed.sort(key=change_key)
157
delta.added.sort(key=change_key)
158
delta.renamed.sort(key=change_key)
159
delta.copied.sort(key=change_key)
160
delta.missing.sort(key=change_key)
242
delta.renamed.append((path[0],
247
(executable[0] != executable[1])))
248
elif kind[0] != kind[1]:
249
delta.kind_changed.append((path[1], file_id, kind[0], kind[1]))
250
elif content_change is True or executable[0] != executable[1]:
251
delta.modified.append((path[1], file_id, kind[1],
253
(executable[0] != executable[1])))
255
delta.unchanged.append((path[1], file_id, kind[1]))
161
260
# TODO: jam 20060529 These lists shouldn't need to be sorted
162
261
# since we added them in alphabetical order.
163
delta.modified.sort(key=change_key)
164
delta.unchanged.sort(key=change_key)
165
delta.unversioned.sort(key=change_key)
262
delta.modified.sort()
263
delta.unchanged.sort()
181
278
(i.e. when a tree has just been initted)
182
279
:param output_file: If supplied, a file-like object to write to.
183
280
Only one of output and output_file may be supplied.
184
:param unversioned_filter: A filter function to be called on
281
:param unversioned_filter: A filter function to be called on
185
282
unversioned files. This should return True to ignore a path.
186
283
By default, no filtering takes place.
187
:param view_info: A tuple of view_name,view_files if only
188
items inside a view are to be reported on, or None for
190
:param classify: Add special symbols to indicate file kind.
192
285
if output_file is not None:
193
286
if output is not None:
194
287
raise BzrError('Cannot specify both output and output_file')
196
288
def output(fmt, *args):
197
289
output_file.write((fmt % args) + '\n')
198
290
self.output = output
199
291
if self.output is None:
292
from bzrlib import trace
201
293
self.output = trace.note
202
294
self.suppress_root_add = suppress_root_add
203
295
self.modified_map = {'kind changed': 'K',
204
296
'unchanged': ' ',
210
self.versioned_map = {'added': '+', # versioned target
211
'unchanged': ' ', # versioned in both
212
'removed': '-', # versioned in source
213
'unversioned': '?', # versioned in neither
300
self.versioned_map = {'added': '+', # versioned target
301
'unchanged': ' ', # versioned in both
302
'removed': '-', # versioned in source
303
'unversioned': '?', # versioned in neither
215
305
self.unversioned_filter = unversioned_filter
217
self.kind_marker = osutils.kind_marker
219
self.kind_marker = lambda kind: ''
220
if view_info is None:
221
self.view_name = None
224
self.view_name = view_info[0]
225
self.view_files = view_info[1]
226
self.output("Operating on whole tree but only reporting on "
227
"'%s' view." % (self.view_name,))
229
def report(self, paths, versioned, renamed, copied, modified, exe_change,
307
def report(self, file_id, paths, versioned, renamed, modified, exe_change,
231
309
"""Report one change to a file
233
:param path: The old and new paths as generated by Tree.iter_changes.
311
:param file_id: The file_id of the file
312
:param path: The old and new paths as generated by Tree._iter_changes.
234
313
:param versioned: may be 'added', 'removed', 'unchanged', or
236
315
:param renamed: may be True or False
237
:param copied: may be True or False
238
316
:param modified: may be 'created', 'deleted', 'kind changed',
239
317
'modified' or 'unchanged'.
240
318
:param exe_change: True if the execute bit has changed
241
:param kind: A pair of file kinds, as generated by Tree.iter_changes.
319
:param kind: A pair of file kinds, as generated by Tree._iter_changes.
242
320
None indicates no file present.
246
322
if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
248
if self.view_files and not osutils.is_inside_any(self.view_files,
251
324
if versioned == 'unversioned':
252
325
# skip ignored unversioned files if needed.
253
326
if self.unversioned_filter is not None:
307
378
Further processing may be required to produce a human-readable output.
308
379
Unfortunately, some tree-changing operations are very complex
309
380
:change_iterator: an iterator or sequence of changes in the format
310
generated by Tree.iter_changes
381
generated by Tree._iter_changes
311
382
:param reporter: The _ChangeReporter that will report the changes.
313
384
versioned_change_map = {
314
(True, True): 'unchanged',
315
(True, False): 'removed',
316
(False, True): 'added',
385
(True, True) : 'unchanged',
386
(True, False) : 'removed',
387
(False, True) : 'added',
317
388
(False, False): 'unversioned',
320
def path_key(change):
321
if change.path[0] is not None:
322
path = change.path[0]
324
path = change.path[1]
325
return osutils.splitpath(path)
326
for change in sorted(change_iterator, key=path_key):
390
for (file_id, path, content_change, versioned, parent_id, name, kind,
391
executable) in change_iterator:
327
392
exe_change = False
328
393
# files are "renamed" if they are moved or if name changes, as long
329
394
# as it had a value
330
if None not in change.name and None not in change.parent_id and\
331
(change.name[0] != change.name[1] or change.parent_id[0] != change.parent_id[1]):
395
if None not in name and None not in parent_id and\
396
(name[0] != name[1] or parent_id[0] != parent_id[1]):
341
if change.kind[0] != change.kind[1]:
342
if change.kind[0] is None:
400
if kind[0] != kind[1]:
343
402
modified = "created"
344
elif change.kind[1] is None:
403
elif kind[1] is None:
345
404
modified = "deleted"
347
406
modified = "kind changed"
349
if change.changed_content:
350
409
modified = "modified"
351
elif change.kind[0] is None:
354
411
modified = "unchanged"
355
if change.kind[1] == "file":
356
exe_change = (change.executable[0] != change.executable[1])
357
versioned_change = versioned_change_map[change.versioned]
358
reporter.report(change.path, versioned_change, renamed, copied, modified,
359
exe_change, change.kind)
362
def report_delta(to_file, delta, short_status=False, show_ids=False,
363
show_unchanged=False, indent='', predicate=None, classify=True):
364
"""Output this delta in status-like form to to_file.
366
:param to_file: A file-like object where the output is displayed.
368
:param delta: A TreeDelta containing the changes to be displayed
370
:param short_status: Single-line status if True.
372
:param show_ids: Output the file ids if True.
374
:param show_unchanged: Output the unchanged files if True.
376
:param indent: Added at the beginning of all output lines (for merged
379
:param predicate: A callable receiving a path returning True if the path
382
:param classify: Add special symbols to indicate file kind.
385
def decorate_path(path, kind, meta_modified=None):
388
if kind == 'directory':
390
elif kind == 'symlink':
396
def show_more_renamed(item):
397
dec_new_path = decorate_path(item.path[1], item.kind[1], item.meta_modified())
398
to_file.write(' => %s' % dec_new_path)
399
if item.changed_content or item.meta_modified():
400
extra_modified.append(TreeChange(
401
item.file_id, (item.path[1], item.path[1]),
402
item.changed_content,
404
(item.parent_id[1], item.parent_id[1]),
405
(item.name[1], item.name[1]),
406
(item.kind[1], item.kind[1]),
409
def show_more_kind_changed(item):
410
to_file.write(' (%s => %s)' % (item.kind[0], item.kind[1]))
412
def show_path(path, kind, meta_modified,
413
default_format, with_file_id_format):
414
dec_path = decorate_path(path, kind, meta_modified)
416
to_file.write(with_file_id_format % dec_path)
418
to_file.write(default_format % dec_path)
420
def show_list(files, long_status_name, short_status_letter,
421
default_format='%s', with_file_id_format='%-30s',
426
prefix = short_status_letter
429
prefix = indent + prefix + ' '
432
if item.path[0] is None:
438
if predicate is not None and not predicate(path):
440
if not header_shown and not short_status:
441
to_file.write(indent + long_status_name + ':\n')
443
to_file.write(prefix)
444
show_path(path, kind, item.meta_modified(),
445
default_format, with_file_id_format)
446
if show_more is not None:
448
if show_ids and getattr(item, 'file_id', None):
449
to_file.write(' %s' % item.file_id.decode('utf-8'))
452
show_list(delta.removed, 'removed', 'D')
453
show_list(delta.added, 'added', 'A')
454
show_list(delta.missing, 'missing', '!')
456
show_list(delta.renamed, 'renamed', 'R', with_file_id_format='%s',
457
show_more=show_more_renamed)
458
show_list(delta.copied, 'copied', 'C', with_file_id_format='%s',
459
show_more=show_more_renamed)
460
show_list(delta.kind_changed, 'kind changed', 'K',
461
with_file_id_format='%s',
462
show_more=show_more_kind_changed)
463
show_list(delta.modified + extra_modified, 'modified', 'M')
465
show_list(delta.unchanged, 'unchanged', 'S')
467
show_list(delta.unversioned, 'unknown', ' ')
412
if kind[1] == "file":
413
exe_change = (executable[0] != executable[1])
414
versioned_change = versioned_change_map[versioned]
415
reporter.report(file_id, path, versioned_change, renamed, modified,