/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: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
 
from breezy import (
 
17
from bzrlib import (
20
18
    osutils,
21
 
    trace,
22
 
    )
23
 
from .sixish import (
24
 
    StringIO,
25
 
    )
26
 
from .bzr.inventorytree import InventoryTreeChange
 
19
    )
 
20
from bzrlib.trace import is_quiet
27
21
 
28
22
 
29
23
class TreeDelta(object):
30
24
    """Describes changes from one tree to another.
31
25
 
32
 
    Contains seven lists with TreeChange objects.
 
26
    Contains seven lists:
33
27
 
34
28
    added
 
29
        (path, id, kind)
35
30
    removed
 
31
        (path, id, kind)
36
32
    renamed
37
 
    copied
 
33
        (oldpath, newpath, id, kind, text_modified, meta_modified)
38
34
    kind_changed
 
35
        (path, id, old_kind, new_kind)
39
36
    modified
 
37
        (path, id, kind, text_modified, meta_modified)
40
38
    unchanged
 
39
        (path, id, kind)
41
40
    unversioned
 
41
        (path, None, kind)
42
42
 
43
43
    Each id is listed only once.
44
44
 
45
 
    Files that are both modified and renamed or copied are listed only in
46
 
    renamed or copied, with the text_modified flag true. The text_modified
 
45
    Files that are both modified and renamed are listed only in
 
46
    renamed, with the text_modified flag true. The text_modified
47
47
    applies either to the content of the file or the target of the
48
48
    symbolic link, depending of the kind of file.
49
49
 
53
53
 
54
54
    The lists are normally sorted when the delta is created.
55
55
    """
56
 
 
57
56
    def __init__(self):
58
57
        self.added = []
59
58
        self.removed = []
60
59
        self.renamed = []
61
 
        self.copied = []
62
60
        self.kind_changed = []
63
61
        self.modified = []
64
62
        self.unchanged = []
65
63
        self.unversioned = []
66
 
        self.missing = []
67
64
 
68
65
    def __eq__(self, other):
69
66
        if not isinstance(other, TreeDelta):
70
67
            return False
71
68
        return self.added == other.added \
72
 
            and self.removed == other.removed \
73
 
            and self.renamed == other.renamed \
74
 
            and self.copied == other.copied \
75
 
            and self.modified == other.modified \
76
 
            and self.unchanged == other.unchanged \
77
 
            and self.kind_changed == other.kind_changed \
78
 
            and self.unversioned == other.unversioned
 
69
               and self.removed == other.removed \
 
70
               and self.renamed == other.renamed \
 
71
               and self.modified == other.modified \
 
72
               and self.unchanged == other.unchanged \
 
73
               and self.kind_changed == other.kind_changed \
 
74
               and self.unversioned == other.unversioned
79
75
 
80
76
    def __ne__(self, other):
81
77
        return not (self == other)
82
78
 
83
79
    def __repr__(self):
84
80
        return "TreeDelta(added=%r, removed=%r, renamed=%r," \
85
 
            " copied=%r, kind_changed=%r, modified=%r, unchanged=%r," \
86
 
            " unversioned=%r)" % (
87
 
                self.added, self.removed, self.renamed, self.copied,
88
 
                self.kind_changed, self.modified, self.unchanged,
89
 
                self.unversioned)
 
81
            " kind_changed=%r, modified=%r, unchanged=%r," \
 
82
            " unversioned=%r)" % (self.added,
 
83
            self.removed, self.renamed, self.kind_changed, self.modified,
 
84
            self.unchanged, self.unversioned)
90
85
 
91
86
    def has_changed(self):
92
87
        return bool(self.modified
93
88
                    or self.added
94
89
                    or self.removed
95
90
                    or self.renamed
96
 
                    or self.copied
97
91
                    or self.kind_changed)
98
92
 
 
93
    def touches_file_id(self, file_id):
 
94
        """Return True if file_id is modified by this delta."""
 
95
        for l in self.added, self.removed, self.modified:
 
96
            for v in l:
 
97
                if v[1] == file_id:
 
98
                    return True
 
99
        for v in self.renamed:
 
100
            if v[2] == file_id:
 
101
                return True
 
102
        for v in self.kind_changed:
 
103
            if v[1] == file_id:
 
104
                return True
 
105
        return False
 
106
 
99
107
    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
100
108
                            short_status=False):
101
 
        output = StringIO()
 
109
        import StringIO
 
110
        output = StringIO.StringIO()
102
111
        report_delta(output, self, short_status, show_ids, show_unchanged)
103
112
        return output.getvalue()
104
113
 
110
119
    delta = TreeDelta()
111
120
    # mutter('start compare_trees')
112
121
 
113
 
    for change in new_tree.iter_changes(
114
 
            old_tree, want_unchanged, specific_files, extra_trees=extra_trees,
 
122
    for (file_id, path, content_change, versioned, parent_id, name, kind,
 
123
         executable) in new_tree.iter_changes(old_tree, want_unchanged,
 
124
            specific_files, extra_trees=extra_trees,
115
125
            require_versioned=require_versioned,
116
126
            want_unversioned=want_unversioned):
117
 
        if change.versioned == (False, False):
118
 
            delta.unversioned.append(change)
119
 
            continue
120
 
        if not include_root and (None, None) == change.parent_id:
121
 
            continue
122
 
        fully_present = tuple(
123
 
            (change.versioned[x] and change.kind[x] is not None)
124
 
            for x in range(2))
 
127
        if versioned == (False, False):
 
128
            delta.unversioned.append((path[1], None, kind[1]))
 
129
            continue
 
130
        if not include_root and (None, None) == parent_id:
 
131
            continue
 
132
        fully_present = tuple((versioned[x] and kind[x] is not None) for
 
133
                              x in range(2))
125
134
        if fully_present[0] != fully_present[1]:
126
135
            if fully_present[1] is True:
127
 
                delta.added.append(change)
 
136
                delta.added.append((path[1], file_id, kind[1]))
128
137
            else:
129
 
                if change.kind[0] == 'symlink' and not new_tree.supports_symlinks():
130
 
                    trace.warning(
131
 
                        'Ignoring "%s" as symlinks '
132
 
                        'are not supported on this filesystem.' % (change.path[0],))
133
 
                else:
134
 
                    delta.removed.append(change)
 
138
                delta.removed.append((path[0], file_id, kind[0]))
135
139
        elif fully_present[0] is False:
136
 
            delta.missing.append(change)
137
 
        elif change.name[0] != change.name[1] or change.parent_id[0] != change.parent_id[1]:
138
 
            # If the name changes, or the parent_id changes, we have a rename or copy
 
140
            continue
 
141
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
 
142
            # If the name changes, or the parent_id changes, we have a rename
139
143
            # (if we move a parent, that doesn't count as a rename for the
140
144
            # file)
141
 
            if change.copied:
142
 
                delta.copied.append(change)
143
 
            else:
144
 
                delta.renamed.append(change)
145
 
        elif change.kind[0] != change.kind[1]:
146
 
            delta.kind_changed.append(change)
147
 
        elif change.changed_content or change.executable[0] != change.executable[1]:
148
 
            delta.modified.append(change)
149
 
        else:
150
 
            delta.unchanged.append(change)
151
 
 
152
 
    def change_key(change):
153
 
        if change.path[0] is None:
154
 
            path = change.path[1]
155
 
        else:
156
 
            path = change.path[0]
157
 
        return (path, change.file_id)
158
 
 
159
 
    delta.removed.sort(key=change_key)
160
 
    delta.added.sort(key=change_key)
161
 
    delta.renamed.sort(key=change_key)
162
 
    delta.copied.sort(key=change_key)
163
 
    delta.missing.sort(key=change_key)
 
145
            delta.renamed.append((path[0],
 
146
                                  path[1],
 
147
                                  file_id,
 
148
                                  kind[1],
 
149
                                  content_change,
 
150
                                  (executable[0] != executable[1])))
 
151
        elif kind[0] != kind[1]:
 
152
            delta.kind_changed.append((path[1], file_id, kind[0], kind[1]))
 
153
        elif content_change or executable[0] != executable[1]:
 
154
            delta.modified.append((path[1], file_id, kind[1],
 
155
                                   content_change,
 
156
                                   (executable[0] != executable[1])))
 
157
        else:
 
158
            delta.unchanged.append((path[1], file_id, kind[1]))
 
159
 
 
160
    delta.removed.sort()
 
161
    delta.added.sort()
 
162
    delta.renamed.sort()
164
163
    # TODO: jam 20060529 These lists shouldn't need to be sorted
165
164
    #       since we added them in alphabetical order.
166
 
    delta.modified.sort(key=change_key)
167
 
    delta.unchanged.sort(key=change_key)
168
 
    delta.unversioned.sort(key=change_key)
 
165
    delta.modified.sort()
 
166
    delta.unchanged.sort()
169
167
 
170
168
    return delta
171
169
 
174
172
    """Report changes between two trees"""
175
173
 
176
174
    def __init__(self, output=None, suppress_root_add=True,
177
 
                 output_file=None, unversioned_filter=None, view_info=None,
178
 
                 classify=True):
 
175
                 output_file=None, unversioned_filter=None, view_info=None):
179
176
        """Constructor
180
177
 
181
178
        :param output: a function with the signature of trace.note, i.e.
190
187
        :param view_info: A tuple of view_name,view_files if only
191
188
            items inside a view are to be reported on, or None for
192
189
            no view filtering.
193
 
        :param classify: Add special symbols to indicate file kind.
194
190
        """
195
191
        if output_file is not None:
196
192
            if output is not None:
197
193
                raise BzrError('Cannot specify both output and output_file')
198
 
 
199
194
            def output(fmt, *args):
200
195
                output_file.write((fmt % args) + '\n')
201
196
        self.output = output
202
197
        if self.output is None:
203
 
            from . import trace
 
198
            from bzrlib import trace
204
199
            self.output = trace.note
205
200
        self.suppress_root_add = suppress_root_add
206
201
        self.modified_map = {'kind changed': 'K',
207
202
                             'unchanged': ' ',
208
203
                             'created': 'N',
209
204
                             'modified': 'M',
210
 
                             'deleted': 'D',
211
 
                             'missing': '!',
212
 
                             }
213
 
        self.versioned_map = {'added': '+',  # versioned target
214
 
                              'unchanged': ' ',  # versioned in both
215
 
                              'removed': '-',  # versioned in source
216
 
                              'unversioned': '?',  # versioned in neither
 
205
                             'deleted': 'D'}
 
206
        self.versioned_map = {'added': '+', # versioned target
 
207
                              'unchanged': ' ', # versioned in both
 
208
                              'removed': '-', # versioned in source
 
209
                              'unversioned': '?', # versioned in neither
217
210
                              }
218
211
        self.unversioned_filter = unversioned_filter
219
 
        if classify:
220
 
            self.kind_marker = osutils.kind_marker
221
 
        else:
222
 
            self.kind_marker = lambda kind: ''
223
212
        if view_info is None:
224
213
            self.view_name = None
225
214
            self.view_files = []
229
218
            self.output("Operating on whole tree but only reporting on "
230
219
                        "'%s' view." % (self.view_name,))
231
220
 
232
 
    def report(self, paths, versioned, renamed, copied, modified, exe_change,
 
221
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
233
222
               kind):
234
223
        """Report one change to a file
235
224
 
 
225
        :param file_id: The file_id of the file
236
226
        :param path: The old and new paths as generated by Tree.iter_changes.
237
227
        :param versioned: may be 'added', 'removed', 'unchanged', or
238
228
            'unversioned.
239
229
        :param renamed: may be True or False
240
 
        :param copied: may be True or False
241
230
        :param modified: may be 'created', 'deleted', 'kind changed',
242
231
            'modified' or 'unchanged'.
243
232
        :param exe_change: True if the execute bit has changed
244
233
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
245
234
            None indicates no file present.
246
235
        """
247
 
        if trace.is_quiet():
 
236
        if is_quiet():
248
237
            return
249
238
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
250
239
            return
251
240
        if self.view_files and not osutils.is_inside_any(self.view_files,
252
 
                                                         paths[1]):
 
241
            paths[1]):
253
242
            return
254
243
        if versioned == 'unversioned':
255
244
            # skip ignored unversioned files if needed.
263
252
        # ( the path is different OR
264
253
        #   the kind is different)
265
254
        if (versioned == 'unchanged' and
266
 
                (renamed or copied or modified == 'kind changed')):
267
 
            if renamed or copied:
268
 
                # on a rename or copy, we show old and new
 
255
            (renamed or modified == 'kind changed')):
 
256
            if renamed:
 
257
                # on a rename, we show old and new
269
258
                old_path, path = paths
270
259
            else:
271
 
                # if it's not renamed or copied, we're showing both for kind
272
 
                # changes so only show the new path
 
260
                # if it's not renamed, we're showing both for kind changes
 
261
                # so only show the new path
273
262
                old_path, path = paths[1], paths[1]
274
263
            # if the file is not missing in the source, we show its kind
275
264
            # when we show two paths.
276
265
            if kind[0] is not None:
277
 
                old_path += self.kind_marker(kind[0])
 
266
                old_path += osutils.kind_marker(kind[0])
278
267
            old_path += " => "
279
268
        elif versioned == 'removed':
280
269
            # not present in target
285
274
            path = paths[1]
286
275
        if renamed:
287
276
            rename = "R"
288
 
        elif copied:
289
 
            rename = "C"
290
277
        else:
291
278
            rename = self.versioned_map[versioned]
292
279
        # we show the old kind on the new path when the content is deleted.
293
280
        if modified == 'deleted':
294
 
            path += self.kind_marker(kind[0])
 
281
            path += osutils.kind_marker(kind[0])
295
282
        # otherwise we always show the current kind when there is one
296
283
        elif kind[1] is not None:
297
 
            path += self.kind_marker(kind[1])
 
284
            path += osutils.kind_marker(kind[1])
298
285
        if exe_change:
299
286
            exe = '*'
300
287
        else:
302
289
        self.output("%s%s%s %s%s", rename, self.modified_map[modified], exe,
303
290
                    old_path, path)
304
291
 
305
 
 
306
292
def report_changes(change_iterator, reporter):
307
293
    """Report the changes from a change iterator.
308
294
 
314
300
    :param reporter: The _ChangeReporter that will report the changes.
315
301
    """
316
302
    versioned_change_map = {
317
 
        (True, True): 'unchanged',
318
 
        (True, False): 'removed',
319
 
        (False, True): 'added',
 
303
        (True, True)  : 'unchanged',
 
304
        (True, False) : 'removed',
 
305
        (False, True) : 'added',
320
306
        (False, False): 'unversioned',
321
307
        }
322
 
 
323
 
    def path_key(change):
324
 
        if change.path[0] is not None:
325
 
            path = change.path[0]
326
 
        else:
327
 
            path = change.path[1]
328
 
        return osutils.splitpath(path)
329
 
    for change in sorted(change_iterator, key=path_key):
 
308
    for (file_id, path, content_change, versioned, parent_id, name, kind,
 
309
         executable) in change_iterator:
330
310
        exe_change = False
331
311
        # files are "renamed" if they are moved or if name changes, as long
332
312
        # as it had a value
333
 
        if change.copied:
334
 
            copied = True
335
 
            renamed = False
336
 
        elif change.renamed:
 
313
        if None not in name and None not in parent_id and\
 
314
            (name[0] != name[1] or parent_id[0] != parent_id[1]):
337
315
            renamed = True
338
 
            copied = False
339
316
        else:
340
 
            copied = False
341
317
            renamed = False
342
 
        if change.kind[0] != change.kind[1]:
343
 
            if change.kind[0] is None:
 
318
        if kind[0] != kind[1]:
 
319
            if kind[0] is None:
344
320
                modified = "created"
345
 
            elif change.kind[1] is None:
 
321
            elif kind[1] is None:
346
322
                modified = "deleted"
347
323
            else:
348
324
                modified = "kind changed"
349
325
        else:
350
 
            if change.changed_content:
 
326
            if content_change:
351
327
                modified = "modified"
352
 
            elif change.kind[0] is None:
353
 
                modified = "missing"
354
328
            else:
355
329
                modified = "unchanged"
356
 
            if change.kind[1] == "file":
357
 
                exe_change = (change.executable[0] != change.executable[1])
358
 
        versioned_change = versioned_change_map[change.versioned]
359
 
        reporter.report(change.path, versioned_change, renamed, copied, modified,
360
 
                        exe_change, change.kind)
361
 
 
362
 
 
363
 
def report_delta(to_file, delta, short_status=False, show_ids=False,
364
 
                 show_unchanged=False, indent='', predicate=None, classify=True):
 
330
            if kind[1] == "file":
 
331
                exe_change = (executable[0] != executable[1])
 
332
        versioned_change = versioned_change_map[versioned]
 
333
        reporter.report(file_id, path, versioned_change, renamed, modified,
 
334
                        exe_change, kind)
 
335
 
 
336
def report_delta(to_file, delta, short_status=False, show_ids=False, 
 
337
         show_unchanged=False, indent='', filter=None):
365
338
    """Output this delta in status-like form to to_file.
366
339
 
367
340
    :param to_file: A file-like object where the output is displayed.
377
350
    :param indent: Added at the beginning of all output lines (for merged
378
351
        revisions).
379
352
 
380
 
    :param predicate: A callable receiving a path returning True if the path
381
 
        should be displayed.
382
 
 
383
 
    :param classify: Add special symbols to indicate file kind.
 
353
    :param filter: A callable receiving a path and a file id and
 
354
        returning True if the path should be displayed.
384
355
    """
385
356
 
386
357
    def decorate_path(path, kind, meta_modified=None):
387
 
        if not classify:
388
 
            return path
389
358
        if kind == 'directory':
390
359
            path += '/'
391
360
        elif kind == 'symlink':
395
364
        return path
396
365
 
397
366
    def show_more_renamed(item):
398
 
        dec_new_path = decorate_path(item.path[1], item.kind[1], item.meta_modified())
 
367
        (oldpath, file_id, kind,
 
368
         text_modified, meta_modified, newpath) = item
 
369
        dec_new_path = decorate_path(newpath, kind, meta_modified)
399
370
        to_file.write(' => %s' % dec_new_path)
400
 
        if item.changed_content or item.meta_modified():
401
 
            extra_modified.append(InventoryTreeChange(
402
 
                item.file_id, (item.path[1], item.path[1]),
403
 
                item.changed_content,
404
 
                item.versioned,
405
 
                (item.parent_id[1], item.parent_id[1]),
406
 
                (item.name[1], item.name[1]),
407
 
                (item.kind[1], item.kind[1]),
408
 
                item.executable))
 
371
        if text_modified or meta_modified:
 
372
            extra_modified.append((newpath, file_id, kind,
 
373
                                   text_modified, meta_modified))
409
374
 
410
375
    def show_more_kind_changed(item):
411
 
        to_file.write(' (%s => %s)' % (item.kind[0], item.kind[1]))
 
376
        (path, file_id, old_kind, new_kind) = item
 
377
        to_file.write(' (%s => %s)' % (old_kind, new_kind))
412
378
 
413
 
    def show_path(path, kind, meta_modified,
 
379
    def show_path(path, file_id, kind, meta_modified,
414
380
                  default_format, with_file_id_format):
415
381
        dec_path = decorate_path(path, kind, meta_modified)
416
382
        if show_ids:
430
396
            prefix = indent + prefix + '  '
431
397
 
432
398
            for item in files:
433
 
                if item.path[0] is None:
434
 
                    path = item.path[1]
435
 
                    kind = item.kind[1]
436
 
                else:
437
 
                    path = item.path[0]
438
 
                    kind = item.kind[0]
439
 
                if predicate is not None and not predicate(path):
 
399
                path, file_id, kind = item[:3]
 
400
                if (filter is not None and not filter(path, file_id)):
440
401
                    continue
441
402
                if not header_shown and not short_status:
442
403
                    to_file.write(indent + long_status_name + ':\n')
443
404
                    header_shown = True
 
405
                meta_modified = None
 
406
                if len(item) == 5:
 
407
                    meta_modified = item[4]
 
408
 
444
409
                to_file.write(prefix)
445
 
                show_path(path, kind, item.meta_modified(),
 
410
                show_path(path, file_id, kind, meta_modified,
446
411
                          default_format, with_file_id_format)
447
412
                if show_more is not None:
448
413
                    show_more(item)
449
 
                if show_ids and getattr(item, 'file_id', None):
450
 
                    to_file.write(' %s' % item.file_id.decode('utf-8'))
 
414
                if show_ids:
 
415
                    to_file.write(' %s' % file_id)
451
416
                to_file.write('\n')
452
417
 
453
418
    show_list(delta.removed, 'removed', 'D')
454
419
    show_list(delta.added, 'added', 'A')
455
 
    show_list(delta.missing, 'missing', '!')
456
420
    extra_modified = []
457
 
    show_list(delta.renamed, 'renamed', 'R', with_file_id_format='%s',
458
 
              show_more=show_more_renamed)
459
 
    show_list(delta.copied, 'copied', 'C', with_file_id_format='%s',
 
421
    # Reorder delta.renamed tuples so that all lists share the same
 
422
    # order for their 3 first fields and that they also begin like
 
423
    # the delta.modified tuples
 
424
    renamed = [(p, i, k, tm, mm, np)
 
425
               for  p, np, i, k, tm, mm  in delta.renamed]
 
426
    show_list(renamed, 'renamed', 'R', with_file_id_format='%s',
460
427
              show_more=show_more_renamed)
461
428
    show_list(delta.kind_changed, 'kind changed', 'K',
462
429
              with_file_id_format='%s',
466
433
        show_list(delta.unchanged, 'unchanged', 'S')
467
434
 
468
435
    show_list(delta.unversioned, 'unknown', ' ')
 
436