245
class TreeDelta(object):
246
"""Describes changes from one tree to another.
255
(oldpath, newpath, id, kind, text_modified)
261
Each id is listed only once.
263
Files that are both modified and renamed are listed only in
264
renamed, with the text_modified flag true.
266
Files are only considered renamed if their name has changed or
267
their parent directory has changed. Renaming a directory
268
does not count as renaming all its contents.
270
The lists are normally sorted when the delta is created.
279
def __eq__(self, other):
280
if not isinstance(other, TreeDelta):
282
return self.added == other.added \
283
and self.removed == other.removed \
284
and self.renamed == other.renamed \
285
and self.modified == other.modified \
286
and self.unchanged == other.unchanged
288
def __ne__(self, other):
289
return not (self == other)
292
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
293
" unchanged=%r)" % (self.added, self.removed, self.renamed,
294
self.modified, self.unchanged)
296
def has_changed(self):
297
changes = len(self.added) + len(self.removed) + len(self.renamed)
298
changes += len(self.modified)
299
return (changes != 0)
301
def touches_file_id(self, file_id):
302
"""Return True if file_id is modified by this delta."""
303
for l in self.added, self.removed, self.modified:
307
for v in self.renamed:
313
def show(self, to_file, show_ids=False, show_unchanged=False):
314
def show_list(files):
315
for path, fid, kind in files:
316
if kind == 'directory':
318
elif kind == 'symlink':
322
print >>to_file, ' %-30s %s' % (path, fid)
324
print >>to_file, ' ', path
327
print >>to_file, 'removed:'
328
show_list(self.removed)
331
print >>to_file, 'added:'
332
show_list(self.added)
335
print >>to_file, 'renamed:'
336
for oldpath, newpath, fid, kind, text_modified in self.renamed:
338
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
340
print >>to_file, ' %s => %s' % (oldpath, newpath)
343
print >>to_file, 'modified:'
344
show_list(self.modified)
346
if show_unchanged and self.unchanged:
347
print >>to_file, 'unchanged:'
348
show_list(self.unchanged)
352
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
353
"""Describe changes from one tree to another.
355
Returns a TreeDelta with details of added, modified, renamed, and
358
The root entry is specifically exempt.
360
This only considers versioned files.
363
If true, also list files unchanged from one version to
367
If true, only check for changes to specified names or
371
from osutils import is_inside_any
373
old_inv = old_tree.inventory
374
new_inv = new_tree.inventory
376
mutter('start compare_trees')
378
# TODO: match for specific files can be rather smarter by finding
379
# the IDs of those files up front and then considering only that.
381
for file_id in old_tree:
382
if file_id in new_tree:
383
old_ie = old_inv[file_id]
384
new_ie = new_inv[file_id]
387
assert kind == new_ie.kind
389
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
390
'invalid file kind %r' % kind
392
if kind == 'root_directory':
396
if (not is_inside_any(specific_files, old_inv.id2path(file_id))
397
and not is_inside_any(specific_files, new_inv.id2path(file_id))):
401
old_sha1 = old_tree.get_file_sha1(file_id)
402
new_sha1 = new_tree.get_file_sha1(file_id)
403
text_modified = (old_sha1 != new_sha1)
405
## mutter("no text to check for %r %r" % (file_id, kind))
406
text_modified = False
408
# TODO: Can possibly avoid calculating path strings if the
409
# two files are unchanged and their names and parents are
410
# the same and the parents are unchanged all the way up.
411
# May not be worthwhile.
413
if (old_ie.name != new_ie.name
414
or old_ie.parent_id != new_ie.parent_id):
415
delta.renamed.append((old_inv.id2path(file_id),
416
new_inv.id2path(file_id),
420
delta.modified.append((new_inv.id2path(file_id), file_id, kind))
422
delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
424
kind = old_inv.get_file_kind(file_id)
425
old_path = old_inv.id2path(file_id)
427
if not is_inside_any(specific_files, old_path):
429
delta.removed.append((old_path, file_id, kind))
431
mutter('start looking for new files')
432
for file_id in new_inv:
433
if file_id in old_inv:
435
new_path = new_inv.id2path(file_id)
437
if not is_inside_any(specific_files, new_path):
439
kind = new_inv.get_file_kind(file_id)
440
delta.added.append((new_path, file_id, kind))
445
delta.modified.sort()
446
delta.unchanged.sort()