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
17
from io import StringIO
19
19
from breezy import (
25
from .trace import is_quiet
23
from .bzr.inventorytree import InventoryTreeChange
28
26
class TreeDelta(object):
29
27
"""Describes changes from one tree to another.
29
Contains seven lists with TreeChange objects.
38
(oldpath, newpath, id, kind, text_modified, meta_modified)
40
(path, id, old_kind, new_kind)
42
(path, id, kind, text_modified, meta_modified)
48
40
Each id is listed only once.
50
Files that are both modified and renamed are listed only in
51
renamed, with the text_modified flag true. The text_modified
42
Files that are both modified and renamed or copied are listed only in
43
renamed or copied, with the text_modified flag true. The text_modified
52
44
applies either to the content of the file or the target of the
53
45
symbolic link, depending of the kind of file.
86
80
def __repr__(self):
87
81
return "TreeDelta(added=%r, removed=%r, renamed=%r," \
88
" kind_changed=%r, modified=%r, unchanged=%r," \
89
" unversioned=%r)" % (self.added,
90
self.removed, self.renamed, self.kind_changed, self.modified,
91
self.unchanged, self.unversioned)
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,
93
88
def has_changed(self):
94
89
return bool(self.modified
98
94
or self.kind_changed)
100
def touches_file_id(self, file_id):
101
"""Return True if file_id is modified by this delta."""
102
for l in self.added, self.removed, self.modified:
106
for v in self.renamed:
109
for v in self.kind_changed:
114
96
def get_changes_as_text(self, show_ids=False, show_unchanged=False,
115
97
short_status=False):
116
98
output = StringIO()
125
107
delta = TreeDelta()
126
108
# mutter('start compare_trees')
128
for (file_id, path, content_change, versioned, parent_id, name, kind,
129
executable) in new_tree.iter_changes(old_tree, want_unchanged,
130
specific_files, extra_trees=extra_trees,
131
require_versioned=require_versioned,
132
want_unversioned=want_unversioned):
133
if versioned == (False, False):
134
delta.unversioned.append((path[1], None, kind[1]))
136
if not include_root and (None, None) == parent_id:
138
fully_present = tuple((versioned[x] and kind[x] is not None) for
110
for change in new_tree.iter_changes(
111
old_tree, want_unchanged, specific_files, extra_trees=extra_trees,
112
require_versioned=require_versioned,
113
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)
140
122
if fully_present[0] != fully_present[1]:
141
123
if fully_present[1] is True:
142
delta.added.append((path[1], file_id, kind[1]))
124
delta.added.append(change)
144
delta.removed.append((path[0], file_id, kind[0]))
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)
145
132
elif fully_present[0] is False:
146
delta.missing.append((path[1], file_id, kind[1]))
147
elif name[0] != name[1] or parent_id[0] != parent_id[1]:
148
# If the name changes, or the parent_id changes, we have a rename
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
149
136
# (if we move a parent, that doesn't count as a rename for the
151
delta.renamed.append((path[0],
156
(executable[0] != executable[1])))
157
elif kind[0] != kind[1]:
158
delta.kind_changed.append((path[1], file_id, kind[0], kind[1]))
159
elif content_change or executable[0] != executable[1]:
160
delta.modified.append((path[1], file_id, kind[1],
162
(executable[0] != executable[1])))
164
delta.unchanged.append((path[1], file_id, kind[1]))
170
def missing_key(change):
171
return (change[0] or '', change[1])
172
delta.missing.sort(key=missing_key)
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)
173
161
# TODO: jam 20060529 These lists shouldn't need to be sorted
174
162
# since we added them in alphabetical order.
175
delta.modified.sort()
176
delta.unchanged.sort()
177
delta.unversioned.sort()
163
delta.modified.sort(key=change_key)
164
delta.unchanged.sort(key=change_key)
165
delta.unversioned.sort(key=change_key)
238
226
self.output("Operating on whole tree but only reporting on "
239
227
"'%s' view." % (self.view_name,))
241
def report(self, file_id, paths, versioned, renamed, modified, exe_change,
229
def report(self, paths, versioned, renamed, copied, modified, exe_change,
243
231
"""Report one change to a file
245
:param file_id: The file_id of the file
246
233
:param path: The old and new paths as generated by Tree.iter_changes.
247
234
:param versioned: may be 'added', 'removed', 'unchanged', or
249
236
:param renamed: may be True or False
237
:param copied: may be True or False
250
238
:param modified: may be 'created', 'deleted', 'kind changed',
251
239
'modified' or 'unchanged'.
252
240
:param exe_change: True if the execute bit has changed
253
241
:param kind: A pair of file kinds, as generated by Tree.iter_changes.
254
242
None indicates no file present.
258
246
if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
272
260
# ( the path is different OR
273
261
# the kind is different)
274
262
if (versioned == 'unchanged' and
275
(renamed or modified == 'kind changed')):
277
# on a rename, we show old and new
263
(renamed or copied or modified == 'kind changed')):
264
if renamed or copied:
265
# on a rename or copy, we show old and new
278
266
old_path, path = paths
280
# if it's not renamed, we're showing both for kind changes
281
# so only show the new path
268
# if it's not renamed or copied, we're showing both for kind
269
# changes so only show the new path
282
270
old_path, path = paths[1], paths[1]
283
271
# if the file is not missing in the source, we show its kind
284
272
# when we show two paths.
330
320
def path_key(change):
331
if change[1][0] is not None:
321
if change.path[0] is not None:
322
path = change.path[0]
324
path = change.path[1]
335
325
return osutils.splitpath(path)
336
for (file_id, path, content_change, versioned, parent_id, name, kind,
337
executable) in sorted(change_iterator, key=path_key):
326
for change in sorted(change_iterator, key=path_key):
338
327
exe_change = False
339
328
# files are "renamed" if they are moved or if name changes, as long
340
329
# as it had a value
341
if None not in name and None not in parent_id and\
342
(name[0] != name[1] or parent_id[0] != parent_id[1]):
346
if kind[0] != kind[1]:
339
if change.kind[0] != change.kind[1]:
340
if change.kind[0] is None:
348
341
modified = "created"
349
elif kind[1] is None:
342
elif change.kind[1] is None:
350
343
modified = "deleted"
352
345
modified = "kind changed"
347
if change.changed_content:
355
348
modified = "modified"
356
elif kind[0] is None:
349
elif change.kind[0] is None:
357
350
modified = "missing"
359
352
modified = "unchanged"
360
if kind[1] == "file":
361
exe_change = (executable[0] != executable[1])
362
versioned_change = versioned_change_map[versioned]
363
reporter.report(file_id, path, versioned_change, renamed, modified,
353
if change.kind[1] == "file":
354
exe_change = (change.executable[0] != change.executable[1])
355
versioned_change = versioned_change_map[change.versioned]
356
reporter.report(change.path, versioned_change, renamed, copied, modified,
357
exe_change, change.kind)
367
360
def report_delta(to_file, delta, short_status=False, show_ids=False,
401
394
def show_more_renamed(item):
402
(oldpath, file_id, kind,
403
text_modified, meta_modified, newpath) = item
404
dec_new_path = decorate_path(newpath, kind, meta_modified)
395
dec_new_path = decorate_path(item.path[1], item.kind[1], item.meta_modified())
405
396
to_file.write(' => %s' % dec_new_path)
406
if text_modified or meta_modified:
407
extra_modified.append((newpath, file_id, kind,
408
text_modified, meta_modified))
397
if item.changed_content or item.meta_modified():
398
extra_modified.append(InventoryTreeChange(
399
item.file_id, (item.path[1], item.path[1]),
400
item.changed_content,
402
(item.parent_id[1], item.parent_id[1]),
403
(item.name[1], item.name[1]),
404
(item.kind[1], item.kind[1]),
410
407
def show_more_kind_changed(item):
411
(path, file_id, old_kind, new_kind) = item
412
to_file.write(' (%s => %s)' % (old_kind, new_kind))
408
to_file.write(' (%s => %s)' % (item.kind[0], item.kind[1]))
414
def show_path(path, file_id, kind, meta_modified,
410
def show_path(path, kind, meta_modified,
415
411
default_format, with_file_id_format):
416
412
dec_path = decorate_path(path, kind, meta_modified)
431
427
prefix = indent + prefix + ' '
433
429
for item in files:
434
path, file_id, kind = item[:3]
435
if (predicate is not None and not predicate(path, file_id)):
430
if item.path[0] is None:
436
if predicate is not None and not predicate(path):
437
438
if not header_shown and not short_status:
438
439
to_file.write(indent + long_status_name + ':\n')
439
440
header_shown = True
442
meta_modified = item[4]
444
441
to_file.write(prefix)
445
show_path(path, file_id, kind, meta_modified,
442
show_path(path, kind, item.meta_modified(),
446
443
default_format, with_file_id_format)
447
444
if show_more is not None:
450
to_file.write(' %s' % file_id.decode('utf-8'))
446
if show_ids and getattr(item, 'file_id', None):
447
to_file.write(' %s' % item.file_id.decode('utf-8'))
451
448
to_file.write('\n')
453
450
show_list(delta.removed, 'removed', 'D')
454
451
show_list(delta.added, 'added', 'A')
455
452
show_list(delta.missing, 'missing', '!')
456
453
extra_modified = []
457
# Reorder delta.renamed tuples so that all lists share the same
458
# order for their 3 first fields and that they also begin like
459
# the delta.modified tuples
460
renamed = [(p, i, k, tm, mm, np)
461
for p, np, i, k, tm, mm in delta.renamed]
462
show_list(renamed, 'renamed', 'R', with_file_id_format='%s',
454
show_list(delta.renamed, 'renamed', 'R', with_file_id_format='%s',
455
show_more=show_more_renamed)
456
show_list(delta.copied, 'copied', 'C', with_file_id_format='%s',
463
457
show_more=show_more_renamed)
464
458
show_list(delta.kind_changed, 'kind changed', 'K',
465
459
with_file_id_format='%s',