/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: John Arbash Meinel
  • Date: 2006-05-27 03:11:07 UTC
  • mto: (1711.2.26 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1734.
  • Revision ID: john@arbash-meinel.com-20060527031107-6969266aa397354e
Adding a benchmark which checks 'bzr status' time after a commit.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical
 
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.inventory import InventoryEntry
 
18
from bzrlib.osutils import pathjoin
 
19
from bzrlib.trace import mutter
 
20
 
 
21
 
 
22
class TreeDelta(object):
 
23
    """Describes changes from one tree to another.
 
24
 
 
25
    Contains four lists:
 
26
 
 
27
    added
 
28
        (path, id, kind)
 
29
    removed
 
30
        (path, id, kind)
 
31
    renamed
 
32
        (oldpath, newpath, id, kind, text_modified, meta_modified)
 
33
    modified
 
34
        (path, id, kind, text_modified, meta_modified)
 
35
    unchanged
 
36
        (path, id, kind)
 
37
 
 
38
    Each id is listed only once.
 
39
 
 
40
    Files that are both modified and renamed are listed only in
 
41
    renamed, with the text_modified flag true. The text_modified
 
42
    applies either to the the content of the file or the target of the
 
43
    symbolic link, depending of the kind of file.
 
44
 
 
45
    Files are only considered renamed if their name has changed or
 
46
    their parent directory has changed.  Renaming a directory
 
47
    does not count as renaming all its contents.
 
48
 
 
49
    The lists are normally sorted when the delta is created.
 
50
    """
 
51
    def __init__(self):
 
52
        self.added = []
 
53
        self.removed = []
 
54
        self.renamed = []
 
55
        self.modified = []
 
56
        self.unchanged = []
 
57
 
 
58
    def __eq__(self, other):
 
59
        if not isinstance(other, TreeDelta):
 
60
            return False
 
61
        return self.added == other.added \
 
62
               and self.removed == other.removed \
 
63
               and self.renamed == other.renamed \
 
64
               and self.modified == other.modified \
 
65
               and self.unchanged == other.unchanged
 
66
 
 
67
    def __ne__(self, other):
 
68
        return not (self == other)
 
69
 
 
70
    def __repr__(self):
 
71
        return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
 
72
            " unchanged=%r)" % (self.added, self.removed, self.renamed,
 
73
            self.modified, self.unchanged)
 
74
 
 
75
    def has_changed(self):
 
76
        return bool(self.modified
 
77
                    or self.added
 
78
                    or self.removed
 
79
                    or self.renamed)
 
80
 
 
81
    def touches_file_id(self, file_id):
 
82
        """Return True if file_id is modified by this delta."""
 
83
        for l in self.added, self.removed, self.modified:
 
84
            for v in l:
 
85
                if v[1] == file_id:
 
86
                    return True
 
87
        for v in self.renamed:
 
88
            if v[2] == file_id:
 
89
                return True
 
90
        return False
 
91
            
 
92
 
 
93
    def show(self, to_file, show_ids=False, show_unchanged=False):
 
94
        def show_list(files):
 
95
            for item in files:
 
96
                path, fid, kind = item[:3]
 
97
 
 
98
                if kind == 'directory':
 
99
                    path += '/'
 
100
                elif kind == 'symlink':
 
101
                    path += '@'
 
102
 
 
103
                if len(item) == 5 and item[4]:
 
104
                    path += '*'
 
105
 
 
106
                if show_ids:
 
107
                    print >>to_file, '  %-30s %s' % (path, fid)
 
108
                else:
 
109
                    print >>to_file, ' ', path
 
110
            
 
111
        if self.removed:
 
112
            print >>to_file, 'removed:'
 
113
            show_list(self.removed)
 
114
                
 
115
        if self.added:
 
116
            print >>to_file, 'added:'
 
117
            show_list(self.added)
 
118
 
 
119
        extra_modified = []
 
120
 
 
121
        if self.renamed:
 
122
            print >>to_file, 'renamed:'
 
123
            for (oldpath, newpath, fid, kind,
 
124
                 text_modified, meta_modified) in self.renamed:
 
125
                if text_modified or meta_modified:
 
126
                    extra_modified.append((newpath, fid, kind,
 
127
                                           text_modified, meta_modified))
 
128
                if meta_modified:
 
129
                    newpath += '*'
 
130
                if show_ids:
 
131
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
132
                else:
 
133
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
134
                    
 
135
        if self.modified or extra_modified:
 
136
            print >>to_file, 'modified:'
 
137
            show_list(self.modified)
 
138
            show_list(extra_modified)
 
139
            
 
140
        if show_unchanged and self.unchanged:
 
141
            print >>to_file, 'unchanged:'
 
142
            show_list(self.unchanged)
 
143
 
 
144
 
 
145
 
 
146
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
 
147
    """Describe changes from one tree to another.
 
148
 
 
149
    Returns a TreeDelta with details of added, modified, renamed, and
 
150
    deleted entries.
 
151
 
 
152
    The root entry is specifically exempt.
 
153
 
 
154
    This only considers versioned files.
 
155
 
 
156
    want_unchanged
 
157
        If true, also list files unchanged from one version to
 
158
        the next.
 
159
 
 
160
    specific_files
 
161
        If true, only check for changes to specified names or
 
162
        files within them.  Any unversioned files given have no effect
 
163
        (but this might change in the future).
 
164
    """
 
165
    # NB: show_status depends on being able to pass in non-versioned files and
 
166
    # report them as unknown
 
167
    old_tree.lock_read()
 
168
    try:
 
169
        new_tree.lock_read()
 
170
        try:
 
171
            return _compare_trees(old_tree, new_tree, want_unchanged,
 
172
                                  specific_files)
 
173
        finally:
 
174
            new_tree.unlock()
 
175
    finally:
 
176
        old_tree.unlock()
 
177
 
 
178
 
 
179
def _compare_trees(old_tree, new_tree, want_unchanged, specific_files):
 
180
 
 
181
    from bzrlib.osutils import is_inside_any
 
182
    
 
183
    old_inv = old_tree.inventory
 
184
    new_inv = new_tree.inventory
 
185
    delta = TreeDelta()
 
186
    mutter('start compare_trees')
 
187
 
 
188
    # TODO: Rather than iterating over the whole tree and then filtering, we
 
189
    # could diff just the specified files (if any) and their subtrees.  
 
190
    # Perhaps should take a list of file-ids instead?   Need to indicate any
 
191
    # ids or names which were not found in the trees.
 
192
 
 
193
    # Map file_id to path and inventory entry
 
194
    # We probably need to 
 
195
    new_id_to_path_map = {None:''}
 
196
 
 
197
    def get_new_path(new_ie):
 
198
        if new_ie.file_id in new_id_to_path_map:
 
199
            return new_id_to_path_map[new_ie.file_id]
 
200
        if new_ie.parent_id is None:
 
201
            return new_ie.name
 
202
        return pathjoin(get_new_path(new_inv[new_ie.parent_id]), new_ie.name)
 
203
 
 
204
    for old_path, old_ie in old_inv.iter_entries():
 
205
        if not old_tree.has_file_or_id(old_path, old_ie.file_id):
 
206
            # In case old_tree is a WorkingTree, and the file
 
207
            # has been deleted
 
208
            continue
 
209
        if new_inv.has_id(old_ie.file_id):
 
210
            new_ie = new_inv[old_ie.file_id]
 
211
            new_path = get_new_path(new_ie)
 
212
        else:
 
213
            new_path = None
 
214
            new_ie = None
 
215
        if new_path and new_tree.has_file_or_id(new_path, old_ie.file_id):
 
216
            assert old_ie.kind == new_ie.kind
 
217
            
 
218
            assert old_ie.kind in InventoryEntry.known_kinds, \
 
219
                   'invalid file kind %r' % old_ie.kind
 
220
 
 
221
            if old_ie.kind == 'root_directory':
 
222
                continue
 
223
            
 
224
            if specific_files:
 
225
                if (not is_inside_any(specific_files, old_path)
 
226
                    and not is_inside_any(specific_files, new_path)):
 
227
                    continue
 
228
 
 
229
            old_ie._read_tree_state(old_path, old_tree)
 
230
            new_ie._read_tree_state(new_path, new_tree)
 
231
            text_modified, meta_modified = new_ie.detect_changes(old_ie)
 
232
 
 
233
            # TODO: Can possibly avoid calculating path strings if the
 
234
            # two files are unchanged and their names and parents are
 
235
            # the same and the parents are unchanged all the way up.
 
236
            # May not be worthwhile.
 
237
            
 
238
            if (old_ie.name != new_ie.name
 
239
                or old_ie.parent_id != new_ie.parent_id):
 
240
                delta.renamed.append((old_path,
 
241
                                      new_path,
 
242
                                      old_ie.file_id, old_ie.kind,
 
243
                                      text_modified, meta_modified))
 
244
            elif text_modified or meta_modified:
 
245
                delta.modified.append((new_path, old_ie.file_id, old_ie.kind,
 
246
                                       text_modified, meta_modified))
 
247
            elif want_unchanged:
 
248
                delta.unchanged.append((new_path, old_ie.file_id, old_ie.kind))
 
249
        else:
 
250
            if old_ie.kind == 'root_directory':
 
251
                continue
 
252
            if specific_files:
 
253
                if not is_inside_any(specific_files, old_path):
 
254
                    continue
 
255
            delta.removed.append((old_path, old_ie.file_id, old_ie.kind))
 
256
 
 
257
    mutter('start looking for new files')
 
258
    for new_path, new_ie in new_inv.iter_entries():
 
259
        if (new_ie.file_id in old_inv 
 
260
            or not new_tree.has_file_or_id(new_path, new_ie.file_id)):
 
261
            continue
 
262
        if new_ie.kind == 'root_directory':
 
263
            continue
 
264
        if specific_files:
 
265
            if not is_inside_any(specific_files, new_path):
 
266
                continue
 
267
        delta.added.append((new_path, new_ie.file_id, new_ie.kind))
 
268
            
 
269
    delta.removed.sort()
 
270
    delta.added.sort()
 
271
    delta.renamed.sort()
 
272
    delta.modified.sort()
 
273
    delta.unchanged.sort()
 
274
 
 
275
    return delta