/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: 2018-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

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