1
# Copyright (C) 2005, 2006 Canonical Ltd
 
 
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 import errors
 
 
18
from bzrlib.inventory import InventoryEntry
 
 
19
from bzrlib.trace import mutter
 
 
20
from bzrlib.symbol_versioning import deprecated_function, zero_nine
 
 
23
class TreeDelta(object):
 
 
24
    """Describes changes from one tree to another.
 
 
33
        (oldpath, newpath, id, kind, text_modified, meta_modified)
 
 
35
        (path, id, kind, text_modified, meta_modified)
 
 
39
    Each id is listed only once.
 
 
41
    Files that are both modified and renamed are listed only in
 
 
42
    renamed, with the text_modified flag true. The text_modified
 
 
43
    applies either to the the content of the file or the target of the
 
 
44
    symbolic link, depending of the kind of file.
 
 
46
    Files are only considered renamed if their name has changed or
 
 
47
    their parent directory has changed.  Renaming a directory
 
 
48
    does not count as renaming all its contents.
 
 
50
    The lists are normally sorted when the delta is created.
 
 
59
    def __eq__(self, other):
 
 
60
        if not isinstance(other, TreeDelta):
 
 
62
        return self.added == other.added \
 
 
63
               and self.removed == other.removed \
 
 
64
               and self.renamed == other.renamed \
 
 
65
               and self.modified == other.modified \
 
 
66
               and self.unchanged == other.unchanged
 
 
68
    def __ne__(self, other):
 
 
69
        return not (self == other)
 
 
72
        return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
 
 
73
            " unchanged=%r)" % (self.added, self.removed, self.renamed,
 
 
74
            self.modified, self.unchanged)
 
 
76
    def has_changed(self):
 
 
77
        return bool(self.modified
 
 
82
    def touches_file_id(self, file_id):
 
 
83
        """Return True if file_id is modified by this delta."""
 
 
84
        for l in self.added, self.removed, self.modified:
 
 
88
        for v in self.renamed:
 
 
94
    def show(self, to_file, show_ids=False, show_unchanged=False):
 
 
95
        """output this delta in status-like form to to_file."""
 
 
98
                path, fid, kind = item[:3]
 
 
100
                if kind == 'directory':
 
 
102
                elif kind == 'symlink':
 
 
105
                if len(item) == 5 and item[4]:
 
 
109
                    print >>to_file, '  %-30s %s' % (path, fid)
 
 
111
                    print >>to_file, ' ', path
 
 
114
            print >>to_file, 'removed:'
 
 
115
            show_list(self.removed)
 
 
118
            print >>to_file, 'added:'
 
 
119
            show_list(self.added)
 
 
124
            print >>to_file, 'renamed:'
 
 
125
            for (oldpath, newpath, fid, kind,
 
 
126
                 text_modified, meta_modified) in self.renamed:
 
 
127
                if text_modified or meta_modified:
 
 
128
                    extra_modified.append((newpath, fid, kind,
 
 
129
                                           text_modified, meta_modified))
 
 
133
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
 
135
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
 
137
        if self.modified or extra_modified:
 
 
138
            print >>to_file, 'modified:'
 
 
139
            show_list(self.modified)
 
 
140
            show_list(extra_modified)
 
 
142
        if show_unchanged and self.unchanged:
 
 
143
            print >>to_file, 'unchanged:'
 
 
144
            show_list(self.unchanged)
 
 
147
@deprecated_function(zero_nine)
 
 
148
def compare_trees(old_tree, new_tree, want_unchanged=False,
 
 
149
                  specific_files=None, extra_trees=None,
 
 
150
                  require_versioned=False):
 
 
151
    """compare_trees was deprecated in 0.10. Please see Tree.changes_from."""
 
 
152
    return new_tree.changes_from(old_tree,
 
 
153
        want_unchanged=want_unchanged,
 
 
154
        specific_files=specific_files,
 
 
155
        extra_trees=extra_trees,
 
 
156
        require_versioned=require_versioned,
 
 
160
def _compare_trees(old_tree, new_tree, want_unchanged, specific_file_ids,
 
 
163
    # mutter('start compare_trees')
 
 
165
    for (file_id, path, content_change, versioned, parent_id, name, kind,
 
 
166
         executable) in new_tree._iter_changes(old_tree, want_unchanged, 
 
 
168
        if not include_root and (None, None) == parent_id:
 
 
170
        assert kind[0] == kind[1] or None in kind
 
 
171
        # the only 'kind change' permitted is creation/deletion
 
 
172
        fully_present = tuple((versioned[x] and kind[x] is not None) for
 
 
174
        if fully_present[0] != fully_present[1]:
 
 
175
            if fully_present[1] is True:
 
 
176
                delta.added.append((path, file_id, kind[1]))
 
 
178
                assert fully_present[0] is True
 
 
179
                old_path = old_tree.id2path(file_id)
 
 
180
                delta.removed.append((old_path, file_id, kind[0]))
 
 
181
        elif fully_present[0] is False:
 
 
183
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
 
 
184
            # If the name changes, or the parent_id changes, we have a rename
 
 
185
            # (if we move a parent, that doesn't count as a rename for the
 
 
187
            old_path = old_tree.id2path(file_id)
 
 
188
            delta.renamed.append((old_path,
 
 
193
                                  (executable[0] != executable[1])))
 
 
194
        elif content_change is True or executable[0] != executable[1]:
 
 
195
            delta.modified.append((path, file_id, kind[1],
 
 
197
                                   (executable[0] != executable[1])))
 
 
199
            delta.unchanged.append((path, file_id, kind[1]))
 
 
204
    # TODO: jam 20060529 These lists shouldn't need to be sorted
 
 
205
    #       since we added them in alphabetical order.
 
 
206
    delta.modified.sort()
 
 
207
    delta.unchanged.sort()