254
class TreeDelta(object):
255
"""Describes changes from one tree to another.
264
(oldpath, newpath, id, kind, text_modified)
270
Each id is listed only once.
272
Files that are both modified and renamed are listed only in
273
renamed, with the text_modified flag true.
275
Files are only considered renamed if their name has changed or
276
their parent directory has changed. Renaming a directory
277
does not count as renaming all its contents.
279
The lists are normally sorted when the delta is created.
288
def __eq__(self, other):
289
if not isinstance(other, TreeDelta):
291
return self.added == other.added \
292
and self.removed == other.removed \
293
and self.renamed == other.renamed \
294
and self.modified == other.modified \
295
and self.unchanged == other.unchanged
297
def __ne__(self, other):
298
return not (self == other)
301
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
302
" unchanged=%r)" % (self.added, self.removed, self.renamed,
303
self.modified, self.unchanged)
305
def has_changed(self):
306
changes = len(self.added) + len(self.removed) + len(self.renamed)
307
changes += len(self.modified)
308
return (changes != 0)
310
def touches_file_id(self, file_id):
311
"""Return True if file_id is modified by this delta."""
312
for l in self.added, self.removed, self.modified:
316
for v in self.renamed:
322
def show(self, to_file, show_ids=False, show_unchanged=False):
323
def show_list(files):
324
for path, fid, kind in files:
325
if kind == 'directory':
327
elif kind == 'symlink':
331
print >>to_file, ' %-30s %s' % (path, fid)
333
print >>to_file, ' ', path
336
print >>to_file, 'removed:'
337
show_list(self.removed)
340
print >>to_file, 'added:'
341
show_list(self.added)
344
print >>to_file, 'renamed:'
345
for oldpath, newpath, fid, kind, text_modified in self.renamed:
347
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
349
print >>to_file, ' %s => %s' % (oldpath, newpath)
352
print >>to_file, 'modified:'
353
show_list(self.modified)
355
if show_unchanged and self.unchanged:
356
print >>to_file, 'unchanged:'
357
show_list(self.unchanged)
361
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
362
"""Describe changes from one tree to another.
364
Returns a TreeDelta with details of added, modified, renamed, and
367
The root entry is specifically exempt.
369
This only considers versioned files.
372
If true, also list files unchanged from one version to
376
If true, only check for changes to specified names or
380
from osutils import is_inside_any
382
old_inv = old_tree.inventory
383
new_inv = new_tree.inventory
385
mutter('start compare_trees')
387
# TODO: match for specific files can be rather smarter by finding
388
# the IDs of those files up front and then considering only that.
390
for file_id in old_tree:
391
if file_id in new_tree:
392
kind = old_inv.get_file_kind(file_id)
393
assert kind == new_inv.get_file_kind(file_id)
395
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
396
'invalid file kind %r' % kind
398
if kind == 'root_directory':
401
old_path = old_inv.id2path(file_id)
402
new_path = new_inv.id2path(file_id)
404
old_ie = old_inv[file_id]
405
new_ie = new_inv[file_id]
408
if (not is_inside_any(specific_files, old_path)
409
and not is_inside_any(specific_files, new_path)):
413
old_sha1 = old_tree.get_file_sha1(file_id)
414
new_sha1 = new_tree.get_file_sha1(file_id)
415
text_modified = (old_sha1 != new_sha1)
417
## mutter("no text to check for %r %r" % (file_id, kind))
418
text_modified = False
420
# TODO: Can possibly avoid calculating path strings if the
421
# two files are unchanged and their names and parents are
422
# the same and the parents are unchanged all the way up.
423
# May not be worthwhile.
425
if (old_ie.name != new_ie.name
426
or old_ie.parent_id != new_ie.parent_id):
427
delta.renamed.append((old_path, new_path, file_id, kind,
430
delta.modified.append((new_path, file_id, kind))
432
delta.unchanged.append((new_path, file_id, kind))
434
kind = old_inv.get_file_kind(file_id)
435
old_path = old_inv.id2path(file_id)
437
if not is_inside_any(specific_files, old_path):
439
delta.removed.append((old_path, file_id, kind))
441
mutter('start looking for new files')
442
for file_id in new_inv:
443
if file_id in old_inv:
445
new_path = new_inv.id2path(file_id)
447
if not is_inside_any(specific_files, new_path):
449
kind = new_inv.get_file_kind(file_id)
450
delta.added.append((new_path, file_id, kind))
455
delta.modified.sort()
456
delta.unchanged.sort()