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. 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):
92
for path, fid, kind in files:
93
if kind == 'directory':
95
elif kind == 'symlink':
99
print >>to_file, ' %-30s %s' % (path, fid)
101
print >>to_file, ' ', path
104
print >>to_file, 'removed:'
105
show_list(self.removed)
108
print >>to_file, 'added:'
109
show_list(self.added)
112
print >>to_file, 'renamed:'
113
for oldpath, newpath, fid, kind, text_modified in self.renamed:
115
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
117
print >>to_file, ' %s => %s' % (oldpath, newpath)
120
print >>to_file, 'modified:'
121
show_list(self.modified)
123
if show_unchanged and self.unchanged:
124
print >>to_file, 'unchanged:'
125
show_list(self.unchanged)
129
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
130
"""Describe changes from one tree to another.
132
Returns a TreeDelta with details of added, modified, renamed, and
135
The root entry is specifically exempt.
137
This only considers versioned files.
140
If true, also list files unchanged from one version to
144
If true, only check for changes to specified names or
148
from osutils import is_inside_any
150
old_inv = old_tree.inventory
151
new_inv = new_tree.inventory
153
mutter('start compare_trees')
155
# TODO: match for specific files can be rather smarter by finding
156
# the IDs of those files up front and then considering only that.
158
for file_id in old_tree:
159
if file_id in new_tree:
160
old_ie = old_inv[file_id]
161
new_ie = new_inv[file_id]
164
assert kind == new_ie.kind
166
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
167
'invalid file kind %r' % kind
169
if kind == 'root_directory':
173
if (not is_inside_any(specific_files, old_inv.id2path(file_id))
174
and not is_inside_any(specific_files, new_inv.id2path(file_id))):
178
old_sha1 = old_tree.get_file_sha1(file_id)
179
new_sha1 = new_tree.get_file_sha1(file_id)
180
text_modified = (old_sha1 != new_sha1)
181
elif kind == 'symlink':
182
t1 = old_tree.get_symlink_target(file_id)
183
t2 = new_tree.get_symlink_target(file_id)
185
mutter(" symlink target changed")
188
text_modified = False
190
## mutter("no text to check for %r %r" % (file_id, kind))
191
text_modified = False
193
# TODO: Can possibly avoid calculating path strings if the
194
# two files are unchanged and their names and parents are
195
# the same and the parents are unchanged all the way up.
196
# May not be worthwhile.
198
if (old_ie.name != new_ie.name
199
or old_ie.parent_id != new_ie.parent_id):
200
delta.renamed.append((old_inv.id2path(file_id),
201
new_inv.id2path(file_id),
205
delta.modified.append((new_inv.id2path(file_id), file_id, kind))
207
delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
209
kind = old_inv.get_file_kind(file_id)
210
if kind == 'root_directory':
212
old_path = old_inv.id2path(file_id)
214
if not is_inside_any(specific_files, old_path):
216
delta.removed.append((old_path, file_id, kind))
218
mutter('start looking for new files')
219
for file_id in new_inv:
220
if file_id in old_inv:
222
kind = new_inv.get_file_kind(file_id)
223
if kind == 'root_directory':
225
new_path = new_inv.id2path(file_id)
227
if not is_inside_any(specific_files, new_path):
229
delta.added.append((new_path, file_id, kind))
234
delta.modified.sort()
235
delta.unchanged.sort()