/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/delta.py

  • Committer: Robert Collins
  • Date: 2005-10-02 21:51:29 UTC
  • mfrom: (1396)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20051002215128-5686c7d24bf9bdb9
merge from martins newformat branch - brings in transport abstraction

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. 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 path, fid, kind in files:
 
93
                if kind == 'directory':
 
94
                    path += '/'
 
95
                elif kind == 'symlink':
 
96
                    path += '@'
 
97
                    
 
98
                if show_ids:
 
99
                    print >>to_file, '  %-30s %s' % (path, fid)
 
100
                else:
 
101
                    print >>to_file, ' ', path
 
102
            
 
103
        if self.removed:
 
104
            print >>to_file, 'removed:'
 
105
            show_list(self.removed)
 
106
                
 
107
        if self.added:
 
108
            print >>to_file, 'added:'
 
109
            show_list(self.added)
 
110
 
 
111
        if self.renamed:
 
112
            print >>to_file, 'renamed:'
 
113
            for oldpath, newpath, fid, kind, text_modified in self.renamed:
 
114
                if show_ids:
 
115
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
116
                else:
 
117
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
118
                    
 
119
        if self.modified:
 
120
            print >>to_file, 'modified:'
 
121
            show_list(self.modified)
 
122
            
 
123
        if show_unchanged and self.unchanged:
 
124
            print >>to_file, 'unchanged:'
 
125
            show_list(self.unchanged)
 
126
 
 
127
 
 
128
 
 
129
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
 
130
    """Describe changes from one tree to another.
 
131
 
 
132
    Returns a TreeDelta with details of added, modified, renamed, and
 
133
    deleted entries.
 
134
 
 
135
    The root entry is specifically exempt.
 
136
 
 
137
    This only considers versioned files.
 
138
 
 
139
    want_unchanged
 
140
        If true, also list files unchanged from one version to
 
141
        the next.
 
142
 
 
143
    specific_files
 
144
        If true, only check for changes to specified names or
 
145
        files within them.
 
146
    """
 
147
 
 
148
    from osutils import is_inside_any
 
149
    
 
150
    old_inv = old_tree.inventory
 
151
    new_inv = new_tree.inventory
 
152
    delta = TreeDelta()
 
153
    mutter('start compare_trees')
 
154
 
 
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.
 
157
 
 
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]
 
162
 
 
163
            kind = old_ie.kind
 
164
            assert kind == new_ie.kind
 
165
            
 
166
            assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
 
167
                   'invalid file kind %r' % kind
 
168
 
 
169
            if kind == 'root_directory':
 
170
                continue
 
171
            
 
172
            if specific_files:
 
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))):
 
175
                    continue
 
176
 
 
177
            if kind == 'file':
 
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)
 
184
                if t1 != t2:
 
185
                    mutter("    symlink target changed")
 
186
                    text_modified = True
 
187
                else:
 
188
                    text_modified = False
 
189
            else:
 
190
                ## mutter("no text to check for %r %r" % (file_id, kind))
 
191
                text_modified = False
 
192
 
 
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.
 
197
            
 
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),
 
202
                                      file_id, kind,
 
203
                                      text_modified))
 
204
            elif text_modified:
 
205
                delta.modified.append((new_inv.id2path(file_id), file_id, kind))
 
206
            elif want_unchanged:
 
207
                delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
 
208
        else:
 
209
            kind = old_inv.get_file_kind(file_id)
 
210
            if kind == 'root_directory':
 
211
                continue
 
212
            old_path = old_inv.id2path(file_id)
 
213
            if specific_files:
 
214
                if not is_inside_any(specific_files, old_path):
 
215
                    continue
 
216
            delta.removed.append((old_path, file_id, kind))
 
217
 
 
218
    mutter('start looking for new files')
 
219
    for file_id in new_inv:
 
220
        if file_id in old_inv:
 
221
            continue
 
222
        kind = new_inv.get_file_kind(file_id)
 
223
        if kind == 'root_directory':
 
224
            continue
 
225
        new_path = new_inv.id2path(file_id)
 
226
        if specific_files:
 
227
            if not is_inside_any(specific_files, new_path):
 
228
                continue
 
229
        delta.added.append((new_path, file_id, kind))
 
230
            
 
231
    delta.removed.sort()
 
232
    delta.added.sort()
 
233
    delta.renamed.sort()
 
234
    delta.modified.sort()
 
235
    delta.unchanged.sort()
 
236
 
 
237
    return delta