~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/delta.py

merge David Clymer's patch for TestCaseInTestDir.runcmd

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)
 
30
    modified
 
31
        (path, id, kind)
 
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.
 
39
 
 
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.
 
43
 
 
44
    The lists are normally sorted when the delta is created.
 
45
    """
 
46
    def __init__(self):
 
47
        self.added = []
 
48
        self.removed = []
 
49
        self.renamed = []
 
50
        self.modified = []
 
51
        self.unchanged = []
 
52
 
 
53
    def __eq__(self, other):
 
54
        if not isinstance(other, TreeDelta):
 
55
            return False
 
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
 
61
 
 
62
    def __ne__(self, other):
 
63
        return not (self == other)
 
64
 
 
65
    def __repr__(self):
 
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)
 
69
 
 
70
    def has_changed(self):
 
71
        changes = len(self.added) + len(self.removed) + len(self.renamed)
 
72
        changes += len(self.modified) 
 
73
        return (changes != 0)
 
74
 
 
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:
 
78
            for v in l:
 
79
                if v[1] == file_id:
 
80
                    return True
 
81
        for v in self.renamed:
 
82
            if v[2] == file_id:
 
83
                return True
 
84
        return False
 
85
            
 
86
 
 
87
    def show(self, to_file, show_ids=False, show_unchanged=False):
 
88
        def show_list(files):
 
89
            for path, fid, kind in files:
 
90
                if kind == 'directory':
 
91
                    path += '/'
 
92
                elif kind == 'symlink':
 
93
                    path += '@'
 
94
                    
 
95
                if show_ids:
 
96
                    print >>to_file, '  %-30s %s' % (path, fid)
 
97
                else:
 
98
                    print >>to_file, ' ', path
 
99
            
 
100
        if self.removed:
 
101
            print >>to_file, 'removed:'
 
102
            show_list(self.removed)
 
103
                
 
104
        if self.added:
 
105
            print >>to_file, 'added:'
 
106
            show_list(self.added)
 
107
 
 
108
        if self.renamed:
 
109
            print >>to_file, 'renamed:'
 
110
            for oldpath, newpath, fid, kind, text_modified in self.renamed:
 
111
                if show_ids:
 
112
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
113
                else:
 
114
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
115
                    
 
116
        if self.modified:
 
117
            print >>to_file, 'modified:'
 
118
            show_list(self.modified)
 
119
            
 
120
        if show_unchanged and self.unchanged:
 
121
            print >>to_file, 'unchanged:'
 
122
            show_list(self.unchanged)
 
123
 
 
124
 
 
125
 
 
126
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
 
127
    """Describe changes from one tree to another.
 
128
 
 
129
    Returns a TreeDelta with details of added, modified, renamed, and
 
130
    deleted entries.
 
131
 
 
132
    The root entry is specifically exempt.
 
133
 
 
134
    This only considers versioned files.
 
135
 
 
136
    want_unchanged
 
137
        If true, also list files unchanged from one version to
 
138
        the next.
 
139
 
 
140
    specific_files
 
141
        If true, only check for changes to specified names or
 
142
        files within them.
 
143
    """
 
144
 
 
145
    from osutils import is_inside_any
 
146
    
 
147
    old_inv = old_tree.inventory
 
148
    new_inv = new_tree.inventory
 
149
    delta = TreeDelta()
 
150
    mutter('start compare_trees')
 
151
 
 
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.
 
154
 
 
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]
 
159
 
 
160
            kind = old_ie.kind
 
161
            assert kind == new_ie.kind
 
162
            
 
163
            assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
 
164
                   'invalid file kind %r' % kind
 
165
 
 
166
            if kind == 'root_directory':
 
167
                continue
 
168
            
 
169
            if specific_files:
 
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))):
 
172
                    continue
 
173
 
 
174
            if kind == 'file':
 
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)
 
178
            else:
 
179
                ## mutter("no text to check for %r %r" % (file_id, kind))
 
180
                text_modified = False
 
181
 
 
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.
 
186
            
 
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),
 
191
                                      file_id, kind,
 
192
                                      text_modified))
 
193
            elif text_modified:
 
194
                delta.modified.append((new_inv.id2path(file_id), file_id, kind))
 
195
            elif want_unchanged:
 
196
                delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
 
197
        else:
 
198
            kind = old_inv.get_file_kind(file_id)
 
199
            if kind == 'root_directory':
 
200
                continue
 
201
            old_path = old_inv.id2path(file_id)
 
202
            if specific_files:
 
203
                if not is_inside_any(specific_files, old_path):
 
204
                    continue
 
205
            delta.removed.append((old_path, file_id, kind))
 
206
 
 
207
    mutter('start looking for new files')
 
208
    for file_id in new_inv:
 
209
        if file_id in old_inv:
 
210
            continue
 
211
        kind = new_inv.get_file_kind(file_id)
 
212
        if kind == 'root_directory':
 
213
            continue
 
214
        new_path = new_inv.id2path(file_id)
 
215
        if specific_files:
 
216
            if not is_inside_any(specific_files, new_path):
 
217
                continue
 
218
        delta.added.append((new_path, file_id, kind))
 
219
            
 
220
    delta.removed.sort()
 
221
    delta.added.sort()
 
222
    delta.renamed.sort()
 
223
    delta.modified.sort()
 
224
    delta.unchanged.sort()
 
225
 
 
226
    return delta