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()