~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/delta.py

  • Committer: Robert Collins
  • Date: 2005-10-03 01:42:16 UTC
  • Revision ID: robertc@robertcollins.net-20051003014215-ee2990904cc4c7ad
integrate in Gustavos x-bit patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: UTF-8 -*-
 
2
 
 
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.
 
7
 
 
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.
 
12
 
 
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
 
16
 
 
17
from bzrlib.trace import mutter
 
18
 
 
19
class TreeDelta(object):
 
20
    """Describes changes from one tree to another.
 
21
 
 
22
    Contains four lists:
 
23
 
 
24
    added
 
25
        (path, id, kind)
 
26
    removed
 
27
        (path, id, kind)
 
28
    renamed
 
29
        (oldpath, newpath, id, kind, text_modified, meta_modified)
 
30
    modified
 
31
        (path, id, kind, text_modified, meta_modified)
 
32
    unchanged
 
33
        (path, id, kind)
 
34
 
 
35
    Each id is listed only once.
 
36
 
 
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.
 
41
 
 
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.
 
45
 
 
46
    The lists are normally sorted when the delta is created.
 
47
    """
 
48
    def __init__(self):
 
49
        self.added = []
 
50
        self.removed = []
 
51
        self.renamed = []
 
52
        self.modified = []
 
53
        self.unchanged = []
 
54
 
 
55
    def __eq__(self, other):
 
56
        if not isinstance(other, TreeDelta):
 
57
            return False
 
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
 
63
 
 
64
    def __ne__(self, other):
 
65
        return not (self == other)
 
66
 
 
67
    def __repr__(self):
 
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)
 
71
 
 
72
    def has_changed(self):
 
73
        return bool(self.modified
 
74
                    or self.added
 
75
                    or self.removed
 
76
                    or self.renamed)
 
77
 
 
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:
 
81
            for v in l:
 
82
                if v[1] == file_id:
 
83
                    return True
 
84
        for v in self.renamed:
 
85
            if v[2] == file_id:
 
86
                return True
 
87
        return False
 
88
            
 
89
 
 
90
    def show(self, to_file, show_ids=False, show_unchanged=False):
 
91
        def show_list(files):
 
92
            for item in files:
 
93
                path, fid, kind = item[:3]
 
94
 
 
95
                if kind == 'directory':
 
96
                    path += '/'
 
97
                elif kind == 'symlink':
 
98
                    path += '@'
 
99
 
 
100
                if len(item) == 5 and item[4]:
 
101
                    path += '*'
 
102
 
 
103
                if show_ids:
 
104
                    print >>to_file, '  %-30s %s' % (path, fid)
 
105
                else:
 
106
                    print >>to_file, ' ', path
 
107
            
 
108
        if self.removed:
 
109
            print >>to_file, 'removed:'
 
110
            show_list(self.removed)
 
111
                
 
112
        if self.added:
 
113
            print >>to_file, 'added:'
 
114
            show_list(self.added)
 
115
 
 
116
        if self.renamed:
 
117
            print >>to_file, 'renamed:'
 
118
            for (oldpath, newpath, fid, kind,
 
119
                 text_modified, meta_modified) in self.renamed:
 
120
                if meta_modified:
 
121
                    newpath += '*'
 
122
                if show_ids:
 
123
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
124
                else:
 
125
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
126
                    
 
127
        if self.modified:
 
128
            print >>to_file, 'modified:'
 
129
            show_list(self.modified)
 
130
            
 
131
        if show_unchanged and self.unchanged:
 
132
            print >>to_file, 'unchanged:'
 
133
            show_list(self.unchanged)
 
134
 
 
135
 
 
136
 
 
137
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
 
138
    """Describe changes from one tree to another.
 
139
 
 
140
    Returns a TreeDelta with details of added, modified, renamed, and
 
141
    deleted entries.
 
142
 
 
143
    The root entry is specifically exempt.
 
144
 
 
145
    This only considers versioned files.
 
146
 
 
147
    want_unchanged
 
148
        If true, also list files unchanged from one version to
 
149
        the next.
 
150
 
 
151
    specific_files
 
152
        If true, only check for changes to specified names or
 
153
        files within them.
 
154
    """
 
155
 
 
156
    from osutils import is_inside_any
 
157
    
 
158
    old_inv = old_tree.inventory
 
159
    new_inv = new_tree.inventory
 
160
    delta = TreeDelta()
 
161
    mutter('start compare_trees')
 
162
 
 
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.
 
165
 
 
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]
 
170
 
 
171
            kind = old_ie.kind
 
172
            assert kind == new_ie.kind
 
173
            
 
174
            assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
 
175
                   'invalid file kind %r' % kind
 
176
 
 
177
            if kind == 'root_directory':
 
178
                continue
 
179
            
 
180
            if specific_files:
 
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))):
 
183
                    continue
 
184
 
 
185
            if kind == 'file':
 
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)
 
195
                if t1 != t2:
 
196
                    mutter("    symlink target changed")
 
197
                    # FIXME: which should we use ?
 
198
                    text_modified = True
 
199
                    meta_modified = False
 
200
                else:
 
201
                    text_modified = False
 
202
                    meta_modified = False
 
203
            else:
 
204
                ## mutter("no text to check for %r %r" % (file_id, kind))
 
205
                text_modified = False
 
206
                meta_modified = False
 
207
 
 
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.
 
212
            
 
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),
 
217
                                      file_id, kind,
 
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))
 
222
            elif want_unchanged:
 
223
                delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
 
224
        else:
 
225
            kind = old_inv.get_file_kind(file_id)
 
226
            if kind == 'root_directory':
 
227
                continue
 
228
            old_path = old_inv.id2path(file_id)
 
229
            if specific_files:
 
230
                if not is_inside_any(specific_files, old_path):
 
231
                    continue
 
232
            delta.removed.append((old_path, file_id, kind))
 
233
 
 
234
    mutter('start looking for new files')
 
235
    for file_id in new_inv:
 
236
        if file_id in old_inv:
 
237
            continue
 
238
        kind = new_inv.get_file_kind(file_id)
 
239
        if kind == 'root_directory':
 
240
            continue
 
241
        new_path = new_inv.id2path(file_id)
 
242
        if specific_files:
 
243
            if not is_inside_any(specific_files, new_path):
 
244
                continue
 
245
        delta.added.append((new_path, file_id, kind))
 
246
            
 
247
    delta.removed.sort()
 
248
    delta.added.sort()
 
249
    delta.renamed.sort()
 
250
    delta.modified.sort()
 
251
    delta.unchanged.sort()
 
252
 
 
253
    return delta