/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 breezy/delta.py

  • Committer: Jelmer Vernooij
  • Date: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

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