/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-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

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