253
class TreeDelta(object):
254
"""Describes changes from one tree to another.
263
(oldpath, newpath, id, kind, text_modified)
269
Each id is listed only once.
271
Files that are both modified and renamed are listed only in
272
renamed, with the text_modified flag true.
274
Files are only considered renamed if their name has changed or
275
their parent directory has changed. Renaming a directory
276
does not count as renaming all its contents.
278
The lists are normally sorted when the delta is created.
287
def __eq__(self, other):
288
if not isinstance(other, TreeDelta):
290
return self.added == other.added \
291
and self.removed == other.removed \
292
and self.renamed == other.renamed \
293
and self.modified == other.modified \
294
and self.unchanged == other.unchanged
296
def __ne__(self, other):
297
return not (self == other)
300
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
301
" unchanged=%r)" % (self.added, self.removed, self.renamed,
302
self.modified, self.unchanged)
304
def has_changed(self):
305
changes = len(self.added) + len(self.removed) + len(self.renamed)
306
changes += len(self.modified)
307
return (changes != 0)
309
def touches_file_id(self, file_id):
310
"""Return True if file_id is modified by this delta."""
311
for l in self.added, self.removed, self.modified:
315
for v in self.renamed:
321
def show(self, to_file, show_ids=False, show_unchanged=False):
322
def show_list(files):
323
for path, fid, kind in files:
324
if kind == 'directory':
326
elif kind == 'symlink':
330
print >>to_file, ' %-30s %s' % (path, fid)
332
print >>to_file, ' ', path
335
print >>to_file, 'removed:'
336
show_list(self.removed)
339
print >>to_file, 'added:'
340
show_list(self.added)
343
print >>to_file, 'renamed:'
344
for oldpath, newpath, fid, kind, text_modified in self.renamed:
346
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
348
print >>to_file, ' %s => %s' % (oldpath, newpath)
351
print >>to_file, 'modified:'
352
show_list(self.modified)
354
if show_unchanged and self.unchanged:
355
print >>to_file, 'unchanged:'
356
show_list(self.unchanged)
360
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
361
"""Describe changes from one tree to another.
363
Returns a TreeDelta with details of added, modified, renamed, and
366
The root entry is specifically exempt.
368
This only considers versioned files.
371
If true, also list files unchanged from one version to
375
If true, only check for changes to specified names or
379
from osutils import is_inside_any
381
old_inv = old_tree.inventory
382
new_inv = new_tree.inventory
384
mutter('start compare_trees')
386
# TODO: match for specific files can be rather smarter by finding
387
# the IDs of those files up front and then considering only that.
389
for file_id in old_tree:
390
if file_id in new_tree:
391
kind = old_inv.get_file_kind(file_id)
392
assert kind == new_inv.get_file_kind(file_id)
394
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
395
'invalid file kind %r' % kind
397
if kind == 'root_directory':
400
old_path = old_inv.id2path(file_id)
401
new_path = new_inv.id2path(file_id)
403
old_ie = old_inv[file_id]
404
new_ie = new_inv[file_id]
407
if (not is_inside_any(specific_files, old_path)
408
and not is_inside_any(specific_files, new_path)):
412
old_sha1 = old_tree.get_file_sha1(file_id)
413
new_sha1 = new_tree.get_file_sha1(file_id)
414
text_modified = (old_sha1 != new_sha1)
416
## mutter("no text to check for %r %r" % (file_id, kind))
417
text_modified = False
419
# TODO: Can possibly avoid calculating path strings if the
420
# two files are unchanged and their names and parents are
421
# the same and the parents are unchanged all the way up.
422
# May not be worthwhile.
424
if (old_ie.name != new_ie.name
425
or old_ie.parent_id != new_ie.parent_id):
426
delta.renamed.append((old_path, new_path, file_id, kind,
429
delta.modified.append((new_path, file_id, kind))
431
delta.unchanged.append((new_path, file_id, kind))
433
kind = old_inv.get_file_kind(file_id)
434
old_path = old_inv.id2path(file_id)
436
if not is_inside_any(specific_files, old_path):
438
delta.removed.append((old_path, file_id, kind))
440
mutter('start looking for new files')
441
for file_id in new_inv:
442
if file_id in old_inv:
444
new_path = new_inv.id2path(file_id)
446
if not is_inside_any(specific_files, new_path):
448
kind = new_inv.get_file_kind(file_id)
449
delta.added.append((new_path, file_id, kind))
454
delta.modified.sort()
455
delta.unchanged.sort()