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, meta_modified)
31
(path, id, kind, text_modified, meta_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. The text_modified
39
applies either to the the content of the file or the target of the
40
symbolic link, depending of the kind of file.
42
Files are only considered renamed if their name has changed or
43
their parent directory has changed. Renaming a directory
44
does not count as renaming all its contents.
46
The lists are normally sorted when the delta is created.
55
def __eq__(self, other):
56
if not isinstance(other, TreeDelta):
58
return self.added == other.added \
59
and self.removed == other.removed \
60
and self.renamed == other.renamed \
61
and self.modified == other.modified \
62
and self.unchanged == other.unchanged
64
def __ne__(self, other):
65
return not (self == other)
68
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
69
" unchanged=%r)" % (self.added, self.removed, self.renamed,
70
self.modified, self.unchanged)
72
def has_changed(self):
73
return bool(self.modified
78
def touches_file_id(self, file_id):
79
"""Return True if file_id is modified by this delta."""
80
for l in self.added, self.removed, self.modified:
84
for v in self.renamed:
90
def show(self, to_file, show_ids=False, show_unchanged=False):
93
path, fid, kind = item[:3]
95
if kind == 'directory':
97
elif kind == 'symlink':
100
if len(item) == 5 and item[4]:
104
print >>to_file, ' %-30s %s' % (path, fid)
106
print >>to_file, ' ', path
109
print >>to_file, 'removed:'
110
show_list(self.removed)
113
print >>to_file, 'added:'
114
show_list(self.added)
117
print >>to_file, 'renamed:'
118
for (oldpath, newpath, fid, kind,
119
text_modified, meta_modified) in self.renamed:
123
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
125
print >>to_file, ' %s => %s' % (oldpath, newpath)
128
print >>to_file, 'modified:'
129
show_list(self.modified)
131
if show_unchanged and self.unchanged:
132
print >>to_file, 'unchanged:'
133
show_list(self.unchanged)
137
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
138
"""Describe changes from one tree to another.
140
Returns a TreeDelta with details of added, modified, renamed, and
143
The root entry is specifically exempt.
145
This only considers versioned files.
148
If true, also list files unchanged from one version to
152
If true, only check for changes to specified names or
156
from osutils import is_inside_any
158
old_inv = old_tree.inventory
159
new_inv = new_tree.inventory
161
mutter('start compare_trees')
163
# TODO: match for specific files can be rather smarter by finding
164
# the IDs of those files up front and then considering only that.
166
for file_id in old_tree:
167
if file_id in new_tree:
168
old_ie = old_inv[file_id]
169
new_ie = new_inv[file_id]
172
assert kind == new_ie.kind
174
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
175
'invalid file kind %r' % kind
177
if kind == 'root_directory':
181
if (not is_inside_any(specific_files, old_inv.id2path(file_id))
182
and not is_inside_any(specific_files, new_inv.id2path(file_id))):
186
old_sha1 = old_tree.get_file_sha1(file_id)
187
new_sha1 = new_tree.get_file_sha1(file_id)
188
text_modified = (old_sha1 != new_sha1)
189
old_exec = old_tree.is_executable(file_id)
190
new_exec = new_tree.is_executable(file_id)
191
meta_modified = (old_exec != new_exec)
192
elif kind == 'symlink':
193
t1 = old_tree.get_symlink_target(file_id)
194
t2 = new_tree.get_symlink_target(file_id)
196
mutter(" symlink target changed")
197
# FIXME: which should we use ?
199
meta_modified = False
201
text_modified = False
202
meta_modified = False
204
## mutter("no text to check for %r %r" % (file_id, kind))
205
text_modified = False
206
meta_modified = False
208
# TODO: Can possibly avoid calculating path strings if the
209
# two files are unchanged and their names and parents are
210
# the same and the parents are unchanged all the way up.
211
# May not be worthwhile.
213
if (old_ie.name != new_ie.name
214
or old_ie.parent_id != new_ie.parent_id):
215
delta.renamed.append((old_inv.id2path(file_id),
216
new_inv.id2path(file_id),
218
text_modified, meta_modified))
219
elif text_modified or meta_modified:
220
delta.modified.append((new_inv.id2path(file_id), file_id, kind,
221
text_modified, meta_modified))
223
delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
225
kind = old_inv.get_file_kind(file_id)
226
if kind == 'root_directory':
228
old_path = old_inv.id2path(file_id)
230
if not is_inside_any(specific_files, old_path):
232
delta.removed.append((old_path, file_id, kind))
234
mutter('start looking for new files')
235
for file_id in new_inv:
236
if file_id in old_inv:
238
kind = new_inv.get_file_kind(file_id)
239
if kind == 'root_directory':
241
new_path = new_inv.id2path(file_id)
243
if not is_inside_any(specific_files, new_path):
245
delta.added.append((new_path, file_id, kind))
250
delta.modified.sort()
251
delta.unchanged.sort()