/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: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

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