1
# -*- coding: UTF-8 -*-
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from bzrlib.trace import mutter
19
class TreeDelta(object):
20
"""Describes changes from one tree to another.
29
(oldpath, newpath, id, kind, text_modified)
35
Each id is listed only once.
37
Files that are both modified and renamed are listed only in
38
renamed, with the text_modified flag true.
40
Files are only considered renamed if their name has changed or
41
their parent directory has changed. Renaming a directory
42
does not count as renaming all its contents.
44
The lists are normally sorted when the delta is created.
53
def __eq__(self, other):
54
if not isinstance(other, TreeDelta):
56
return self.added == other.added \
57
and self.removed == other.removed \
58
and self.renamed == other.renamed \
59
and self.modified == other.modified \
60
and self.unchanged == other.unchanged
62
def __ne__(self, other):
63
return not (self == other)
66
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
67
" unchanged=%r)" % (self.added, self.removed, self.renamed,
68
self.modified, self.unchanged)
70
def has_changed(self):
71
changes = len(self.added) + len(self.removed) + len(self.renamed)
72
changes += len(self.modified)
75
def touches_file_id(self, file_id):
76
"""Return True if file_id is modified by this delta."""
77
for l in self.added, self.removed, self.modified:
81
for v in self.renamed:
87
def show(self, to_file, show_ids=False, show_unchanged=False):
89
for path, fid, kind in files:
90
if kind == 'directory':
92
elif kind == 'symlink':
96
print >>to_file, ' %-30s %s' % (path, fid)
98
print >>to_file, ' ', path
101
print >>to_file, 'removed:'
102
show_list(self.removed)
105
print >>to_file, 'added:'
106
show_list(self.added)
109
print >>to_file, 'renamed:'
110
for oldpath, newpath, fid, kind, text_modified in self.renamed:
112
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
114
print >>to_file, ' %s => %s' % (oldpath, newpath)
117
print >>to_file, 'modified:'
118
show_list(self.modified)
120
if show_unchanged and self.unchanged:
121
print >>to_file, 'unchanged:'
122
show_list(self.unchanged)
126
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
127
"""Describe changes from one tree to another.
129
Returns a TreeDelta with details of added, modified, renamed, and
132
The root entry is specifically exempt.
134
This only considers versioned files.
137
If true, also list files unchanged from one version to
141
If true, only check for changes to specified names or
145
from osutils import is_inside_any
147
old_inv = old_tree.inventory
148
new_inv = new_tree.inventory
150
mutter('start compare_trees')
152
# TODO: match for specific files can be rather smarter by finding
153
# the IDs of those files up front and then considering only that.
155
for file_id in old_tree:
156
if file_id in new_tree:
157
old_ie = old_inv[file_id]
158
new_ie = new_inv[file_id]
161
assert kind == new_ie.kind
163
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
164
'invalid file kind %r' % kind
166
if kind == 'root_directory':
170
if (not is_inside_any(specific_files, old_inv.id2path(file_id))
171
and not is_inside_any(specific_files, new_inv.id2path(file_id))):
175
old_sha1 = old_tree.get_file_sha1(file_id)
176
new_sha1 = new_tree.get_file_sha1(file_id)
177
text_modified = (old_sha1 != new_sha1)
179
## mutter("no text to check for %r %r" % (file_id, kind))
180
text_modified = False
182
# TODO: Can possibly avoid calculating path strings if the
183
# two files are unchanged and their names and parents are
184
# the same and the parents are unchanged all the way up.
185
# May not be worthwhile.
187
if (old_ie.name != new_ie.name
188
or old_ie.parent_id != new_ie.parent_id):
189
delta.renamed.append((old_inv.id2path(file_id),
190
new_inv.id2path(file_id),
194
delta.modified.append((new_inv.id2path(file_id), file_id, kind))
196
delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
198
kind = old_inv.get_file_kind(file_id)
199
if kind == 'root_directory':
201
old_path = old_inv.id2path(file_id)
203
if not is_inside_any(specific_files, old_path):
205
delta.removed.append((old_path, file_id, kind))
207
mutter('start looking for new files')
208
for file_id in new_inv:
209
if file_id in old_inv:
211
kind = new_inv.get_file_kind(file_id)
212
if kind == 'root_directory':
214
new_path = new_inv.id2path(file_id)
216
if not is_inside_any(specific_files, new_path):
218
delta.added.append((new_path, file_id, kind))
223
delta.modified.sort()
224
delta.unchanged.sort()