/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: 2019-09-21 19:49:11 UTC
  • mto: This revision was merged to the branch mainline in revision 7394.
  • Revision ID: jelmer@jelmer.uk-20190921194911-bxlcotmw2j003ex3
Port testr-run to Python3.

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)
34
37
    kind_changed
35
 
        (path, id, old_kind, new_kind)
36
38
    modified
37
 
        (path, id, kind, text_modified, meta_modified)
38
39
    unchanged
39
 
        (path, id, kind)
40
40
    unversioned
41
 
        (path, None, kind)
42
41
 
43
42
    Each id is listed only once.
44
43
 
53
52
 
54
53
    The lists are normally sorted when the delta is created.
55
54
    """
 
55
 
56
56
    def __init__(self):
57
57
        self.added = []
58
58
        self.removed = []
61
61
        self.modified = []
62
62
        self.unchanged = []
63
63
        self.unversioned = []
 
64
        self.missing = []
64
65
 
65
66
    def __eq__(self, other):
66
67
        if not isinstance(other, TreeDelta):
67
68
            return False
68
69
        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
 
70
            and self.removed == other.removed \
 
71
            and self.renamed == other.renamed \
 
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
76
 
76
77
    def __ne__(self, other):
77
78
        return not (self == other)
80
81
        return "TreeDelta(added=%r, removed=%r, renamed=%r," \
81
82
            " kind_changed=%r, modified=%r, unchanged=%r," \
82
83
            " unversioned=%r)" % (self.added,
83
 
            self.removed, self.renamed, self.kind_changed, self.modified,
84
 
            self.unchanged, self.unversioned)
 
84
                                  self.removed, self.renamed, self.kind_changed, self.modified,
 
85
                                  self.unchanged, self.unversioned)
85
86
 
86
87
    def has_changed(self):
87
88
        return bool(self.modified
90
91
                    or self.renamed
91
92
                    or self.kind_changed)
92
93
 
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
94
    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
108
95
                            short_status=False):
109
 
        import StringIO
110
 
        output = StringIO.StringIO()
 
96
        output = StringIO()
111
97
        report_delta(output, self, short_status, show_ids, show_unchanged)
112
98
        return output.getvalue()
113
99
 
119
105
    delta = TreeDelta()
120
106
    # mutter('start compare_trees')
121
107
 
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,
 
108
    for change in new_tree.iter_changes(
 
109
            old_tree, want_unchanged, specific_files, extra_trees=extra_trees,
125
110
            require_versioned=require_versioned,
126
111
            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))
 
112
        if change.versioned == (False, False):
 
113
            delta.unversioned.append(change)
 
114
            continue
 
115
        if not include_root and (None, None) == change.parent_id:
 
116
            continue
 
117
        fully_present = tuple(
 
118
            (change.versioned[x] and change.kind[x] is not None)
 
119
            for x in range(2))
134
120
        if fully_present[0] != fully_present[1]:
135
121
            if fully_present[1] is True:
136
 
                delta.added.append((path[1], file_id, kind[1]))
 
122
                delta.added.append(change)
137
123
            else:
138
 
                delta.removed.append((path[0], file_id, kind[0]))
 
124
                if change.kind[0] == 'symlink' and not new_tree.supports_symlinks():
 
125
                    trace.warning(
 
126
                        'Ignoring "%s" as symlinks '
 
127
                        'are not supported on this filesystem.' % (change.path[0],))
 
128
                else:
 
129
                    delta.removed.append(change)
139
130
        elif fully_present[0] is False:
140
 
            continue
141
 
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
 
131
            delta.missing.append(change)
 
132
        elif change.name[0] != change.name[1] or change.parent_id[0] != change.parent_id[1]:
142
133
            # If the name changes, or the parent_id changes, we have a rename
143
134
            # (if we move a parent, that doesn't count as a rename for the
144
135
            # 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()
 
136
            delta.renamed.append(change)
 
137
        elif change.kind[0] != change.kind[1]:
 
138
            delta.kind_changed.append(change)
 
139
        elif change.changed_content or change.executable[0] != change.executable[1]:
 
140
            delta.modified.append(change)
 
141
        else:
 
142
            delta.unchanged.append(change)
 
143
 
 
144
    def change_key(change):
 
145
        if change.path[0] is None:
 
146
            path = change.path[1]
 
147
        else:
 
148
            path = change.path[0]
 
149
        return (path, change.file_id)
 
150
 
 
151
    delta.removed.sort(key=change_key)
 
152
    delta.added.sort(key=change_key)
 
153
    delta.renamed.sort(key=change_key)
 
154
    delta.missing.sort(key=change_key)
163
155
    # TODO: jam 20060529 These lists shouldn't need to be sorted
164
156
    #       since we added them in alphabetical order.
165
 
    delta.modified.sort()
166
 
    delta.unchanged.sort()
 
157
    delta.modified.sort(key=change_key)
 
158
    delta.unchanged.sort(key=change_key)
 
159
    delta.unversioned.sort(key=change_key)
167
160
 
168
161
    return delta
169
162
 
172
165
    """Report changes between two trees"""
173
166
 
174
167
    def __init__(self, output=None, suppress_root_add=True,
175
 
                 output_file=None, unversioned_filter=None, view_info=None):
 
168
                 output_file=None, unversioned_filter=None, view_info=None,
 
169
                 classify=True):
176
170
        """Constructor
177
171
 
178
172
        :param output: a function with the signature of trace.note, i.e.
187
181
        :param view_info: A tuple of view_name,view_files if only
188
182
            items inside a view are to be reported on, or None for
189
183
            no view filtering.
 
184
        :param classify: Add special symbols to indicate file kind.
190
185
        """
191
186
        if output_file is not None:
192
187
            if output is not None:
193
188
                raise BzrError('Cannot specify both output and output_file')
 
189
 
194
190
            def output(fmt, *args):
195
191
                output_file.write((fmt % args) + '\n')
196
192
        self.output = output
197
193
        if self.output is None:
198
 
            from bzrlib import trace
 
194
            from . import trace
199
195
            self.output = trace.note
200
196
        self.suppress_root_add = suppress_root_add
201
197
        self.modified_map = {'kind changed': 'K',
202
198
                             'unchanged': ' ',
203
199
                             'created': 'N',
204
200
                             '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
 
201
                             'deleted': 'D',
 
202
                             'missing': '!',
 
203
                             }
 
204
        self.versioned_map = {'added': '+',  # versioned target
 
205
                              'unchanged': ' ',  # versioned in both
 
206
                              'removed': '-',  # versioned in source
 
207
                              'unversioned': '?',  # versioned in neither
210
208
                              }
211
209
        self.unversioned_filter = unversioned_filter
 
210
        if classify:
 
211
            self.kind_marker = osutils.kind_marker
 
212
        else:
 
213
            self.kind_marker = lambda kind: ''
212
214
        if view_info is None:
213
215
            self.view_name = None
214
216
            self.view_files = []
218
220
            self.output("Operating on whole tree but only reporting on "
219
221
                        "'%s' view." % (self.view_name,))
220
222
 
221
 
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
 
223
    def report(self, paths, versioned, renamed, modified, exe_change,
222
224
               kind):
223
225
        """Report one change to a file
224
226
 
225
 
        :param file_id: The file_id of the file
226
227
        :param path: The old and new paths as generated by Tree.iter_changes.
227
228
        :param versioned: may be 'added', 'removed', 'unchanged', or
228
229
            'unversioned.
233
234
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
234
235
            None indicates no file present.
235
236
        """
236
 
        if is_quiet():
 
237
        if trace.is_quiet():
237
238
            return
238
239
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
239
240
            return
240
241
        if self.view_files and not osutils.is_inside_any(self.view_files,
241
 
            paths[1]):
 
242
                                                         paths[1]):
242
243
            return
243
244
        if versioned == 'unversioned':
244
245
            # skip ignored unversioned files if needed.
252
253
        # ( the path is different OR
253
254
        #   the kind is different)
254
255
        if (versioned == 'unchanged' and
255
 
            (renamed or modified == 'kind changed')):
 
256
                (renamed or modified == 'kind changed')):
256
257
            if renamed:
257
258
                # on a rename, we show old and new
258
259
                old_path, path = paths
263
264
            # if the file is not missing in the source, we show its kind
264
265
            # when we show two paths.
265
266
            if kind[0] is not None:
266
 
                old_path += osutils.kind_marker(kind[0])
 
267
                old_path += self.kind_marker(kind[0])
267
268
            old_path += " => "
268
269
        elif versioned == 'removed':
269
270
            # not present in target
278
279
            rename = self.versioned_map[versioned]
279
280
        # we show the old kind on the new path when the content is deleted.
280
281
        if modified == 'deleted':
281
 
            path += osutils.kind_marker(kind[0])
 
282
            path += self.kind_marker(kind[0])
282
283
        # otherwise we always show the current kind when there is one
283
284
        elif kind[1] is not None:
284
 
            path += osutils.kind_marker(kind[1])
 
285
            path += self.kind_marker(kind[1])
285
286
        if exe_change:
286
287
            exe = '*'
287
288
        else:
289
290
        self.output("%s%s%s %s%s", rename, self.modified_map[modified], exe,
290
291
                    old_path, path)
291
292
 
 
293
 
292
294
def report_changes(change_iterator, reporter):
293
295
    """Report the changes from a change iterator.
294
296
 
300
302
    :param reporter: The _ChangeReporter that will report the changes.
301
303
    """
302
304
    versioned_change_map = {
303
 
        (True, True)  : 'unchanged',
304
 
        (True, False) : 'removed',
305
 
        (False, True) : 'added',
 
305
        (True, True): 'unchanged',
 
306
        (True, False): 'removed',
 
307
        (False, True): 'added',
306
308
        (False, False): 'unversioned',
307
309
        }
308
 
    for (file_id, path, content_change, versioned, parent_id, name, kind,
309
 
         executable) in change_iterator:
 
310
 
 
311
    def path_key(change):
 
312
        if change.path[0] is not None:
 
313
            path = change.path[0]
 
314
        else:
 
315
            path = change.path[1]
 
316
        return osutils.splitpath(path)
 
317
    for change in sorted(change_iterator, key=path_key):
310
318
        exe_change = False
311
319
        # files are "renamed" if they are moved or if name changes, as long
312
320
        # 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]):
 
321
        if None not in change.name and None not in change.parent_id and\
 
322
                (change.name[0] != change.name[1] or change.parent_id[0] != change.parent_id[1]):
315
323
            renamed = True
316
324
        else:
317
325
            renamed = False
318
 
        if kind[0] != kind[1]:
319
 
            if kind[0] is None:
 
326
        if change.kind[0] != change.kind[1]:
 
327
            if change.kind[0] is None:
320
328
                modified = "created"
321
 
            elif kind[1] is None:
 
329
            elif change.kind[1] is None:
322
330
                modified = "deleted"
323
331
            else:
324
332
                modified = "kind changed"
325
333
        else:
326
 
            if content_change:
 
334
            if change.changed_content:
327
335
                modified = "modified"
 
336
            elif change.kind[0] is None:
 
337
                modified = "missing"
328
338
            else:
329
339
                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):
 
340
            if change.kind[1] == "file":
 
341
                exe_change = (change.executable[0] != change.executable[1])
 
342
        versioned_change = versioned_change_map[change.versioned]
 
343
        reporter.report(change.path, versioned_change, renamed, modified,
 
344
                        exe_change, change.kind)
 
345
 
 
346
 
 
347
def report_delta(to_file, delta, short_status=False, show_ids=False,
 
348
                 show_unchanged=False, indent='', predicate=None, classify=True):
338
349
    """Output this delta in status-like form to to_file.
339
350
 
340
351
    :param to_file: A file-like object where the output is displayed.
350
361
    :param indent: Added at the beginning of all output lines (for merged
351
362
        revisions).
352
363
 
353
 
    :param filter: A callable receiving a path and a file id and
354
 
        returning True if the path should be displayed.
 
364
    :param predicate: A callable receiving a path returning True if the path
 
365
        should be displayed.
 
366
 
 
367
    :param classify: Add special symbols to indicate file kind.
355
368
    """
356
369
 
357
370
    def decorate_path(path, kind, meta_modified=None):
 
371
        if not classify:
 
372
            return path
358
373
        if kind == 'directory':
359
374
            path += '/'
360
375
        elif kind == 'symlink':
364
379
        return path
365
380
 
366
381
    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)
 
382
        dec_new_path = decorate_path(item.path[1], item.kind[1], item.meta_modified())
370
383
        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))
 
384
        if item.changed_content or item.meta_modified():
 
385
            extra_modified.append(TreeChange(
 
386
                item.file_id, (item.path[1], item.path[1]),
 
387
                item.changed_content,
 
388
                item.versioned,
 
389
                (item.parent_id[1], item.parent_id[1]),
 
390
                (item.name[1], item.name[1]),
 
391
                (item.kind[1], item.kind[1]),
 
392
                item.executable))
374
393
 
375
394
    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))
 
395
        to_file.write(' (%s => %s)' % (item.kind[0], item.kind[1]))
378
396
 
379
 
    def show_path(path, file_id, kind, meta_modified,
 
397
    def show_path(path, kind, meta_modified,
380
398
                  default_format, with_file_id_format):
381
399
        dec_path = decorate_path(path, kind, meta_modified)
382
400
        if show_ids:
396
414
            prefix = indent + prefix + '  '
397
415
 
398
416
            for item in files:
399
 
                path, file_id, kind = item[:3]
400
 
                if (filter is not None and not filter(path, file_id)):
 
417
                if item.path[0] is None:
 
418
                    path = item.path[1]
 
419
                    kind = item.kind[1]
 
420
                else:
 
421
                    path = item.path[0]
 
422
                    kind = item.kind[0]
 
423
                if predicate is not None and not predicate(path):
401
424
                    continue
402
425
                if not header_shown and not short_status:
403
426
                    to_file.write(indent + long_status_name + ':\n')
404
427
                    header_shown = True
405
 
                meta_modified = None
406
 
                if len(item) == 5:
407
 
                    meta_modified = item[4]
408
 
 
409
428
                to_file.write(prefix)
410
 
                show_path(path, file_id, kind, meta_modified,
 
429
                show_path(path, kind, item.meta_modified(),
411
430
                          default_format, with_file_id_format)
412
431
                if show_more is not None:
413
432
                    show_more(item)
414
 
                if show_ids:
415
 
                    to_file.write(' %s' % file_id)
 
433
                if show_ids and getattr(item, 'file_id', None):
 
434
                    to_file.write(' %s' % item.file_id.decode('utf-8'))
416
435
                to_file.write('\n')
417
436
 
418
437
    show_list(delta.removed, 'removed', 'D')
419
438
    show_list(delta.added, 'added', 'A')
 
439
    show_list(delta.missing, 'missing', '!')
420
440
    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',
 
441
    show_list(delta.renamed, 'renamed', 'R', with_file_id_format='%s',
427
442
              show_more=show_more_renamed)
428
443
    show_list(delta.kind_changed, 'kind changed', 'K',
429
444
              with_file_id_format='%s',
433
448
        show_list(delta.unchanged, 'unchanged', 'S')
434
449
 
435
450
    show_list(delta.unversioned, 'unknown', ' ')
436