/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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 .bzr.inventorytree import InventoryTreeChange
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
 
63
55
        self.added = []
64
56
        self.removed = []
65
57
        self.renamed = []
 
58
        self.copied = []
66
59
        self.kind_changed = []
67
60
        self.modified = []
68
61
        self.unchanged = []
75
68
        return self.added == other.added \
76
69
            and self.removed == other.removed \
77
70
            and self.renamed == other.renamed \
 
71
            and self.copied == other.copied \
78
72
            and self.modified == other.modified \
79
73
            and self.unchanged == other.unchanged \
80
74
            and self.kind_changed == other.kind_changed \
85
79
 
86
80
    def __repr__(self):
87
81
        return "TreeDelta(added=%r, removed=%r, renamed=%r," \
88
 
            " kind_changed=%r, modified=%r, unchanged=%r," \
89
 
            " unversioned=%r)" % (self.added,
90
 
                                  self.removed, self.renamed, self.kind_changed, self.modified,
91
 
                                  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)
92
87
 
93
88
    def has_changed(self):
94
89
        return bool(self.modified
95
90
                    or self.added
96
91
                    or self.removed
97
92
                    or self.renamed
 
93
                    or self.copied
98
94
                    or self.kind_changed)
99
95
 
100
 
    def touches_file_id(self, file_id):
101
 
        """Return True if file_id is modified by this delta."""
102
 
        for l in self.added, self.removed, self.modified:
103
 
            for v in l:
104
 
                if v[1] == file_id:
105
 
                    return True
106
 
        for v in self.renamed:
107
 
            if v[2] == file_id:
108
 
                return True
109
 
        for v in self.kind_changed:
110
 
            if v[1] == file_id:
111
 
                return True
112
 
        return False
113
 
 
114
96
    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
115
97
                            short_status=False):
116
98
        output = StringIO()
125
107
    delta = TreeDelta()
126
108
    # mutter('start compare_trees')
127
109
 
128
 
    for (file_id, path, content_change, versioned, parent_id, name, kind,
129
 
         executable) in new_tree.iter_changes(old_tree, want_unchanged,
130
 
                                              specific_files, extra_trees=extra_trees,
131
 
                                              require_versioned=require_versioned,
132
 
                                              want_unversioned=want_unversioned):
133
 
        if versioned == (False, False):
134
 
            delta.unversioned.append((path[1], None, kind[1]))
135
 
            continue
136
 
        if not include_root and (None, None) == parent_id:
137
 
            continue
138
 
        fully_present = tuple((versioned[x] and kind[x] is not None) for
139
 
                              x in range(2))
 
110
    for change in new_tree.iter_changes(
 
111
            old_tree, want_unchanged, specific_files, extra_trees=extra_trees,
 
112
            require_versioned=require_versioned,
 
113
            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))
140
122
        if fully_present[0] != fully_present[1]:
141
123
            if fully_present[1] is True:
142
 
                delta.added.append((path[1], file_id, kind[1]))
 
124
                delta.added.append(change)
143
125
            else:
144
 
                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)
145
132
        elif fully_present[0] is False:
146
 
            delta.missing.append((path[1], file_id, kind[1]))
147
 
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
148
 
            # 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
149
136
            # (if we move a parent, that doesn't count as a rename for the
150
137
            # file)
151
 
            delta.renamed.append((path[0],
152
 
                                  path[1],
153
 
                                  file_id,
154
 
                                  kind[1],
155
 
                                  content_change,
156
 
                                  (executable[0] != executable[1])))
157
 
        elif kind[0] != kind[1]:
158
 
            delta.kind_changed.append((path[1], file_id, kind[0], kind[1]))
159
 
        elif content_change or executable[0] != executable[1]:
160
 
            delta.modified.append((path[1], file_id, kind[1],
161
 
                                   content_change,
162
 
                                   (executable[0] != executable[1])))
163
 
        else:
164
 
            delta.unchanged.append((path[1], file_id, kind[1]))
165
 
 
166
 
    delta.removed.sort()
167
 
    delta.added.sort()
168
 
    delta.renamed.sort()
169
 
 
170
 
    def missing_key(change):
171
 
        return (change[0] or '', change[1])
172
 
    delta.missing.sort(key=missing_key)
 
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)
173
161
    # TODO: jam 20060529 These lists shouldn't need to be sorted
174
162
    #       since we added them in alphabetical order.
175
 
    delta.modified.sort()
176
 
    delta.unchanged.sort()
177
 
    delta.unversioned.sort()
 
163
    delta.modified.sort(key=change_key)
 
164
    delta.unchanged.sort(key=change_key)
 
165
    delta.unversioned.sort(key=change_key)
178
166
 
179
167
    return delta
180
168
 
238
226
            self.output("Operating on whole tree but only reporting on "
239
227
                        "'%s' view." % (self.view_name,))
240
228
 
241
 
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
 
229
    def report(self, paths, versioned, renamed, copied, modified, exe_change,
242
230
               kind):
243
231
        """Report one change to a file
244
232
 
245
 
        :param file_id: The file_id of the file
246
233
        :param path: The old and new paths as generated by Tree.iter_changes.
247
234
        :param versioned: may be 'added', 'removed', 'unchanged', or
248
235
            'unversioned.
249
236
        :param renamed: may be True or False
 
237
        :param copied: may be True or False
250
238
        :param modified: may be 'created', 'deleted', 'kind changed',
251
239
            'modified' or 'unchanged'.
252
240
        :param exe_change: True if the execute bit has changed
253
241
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
254
242
            None indicates no file present.
255
243
        """
256
 
        if is_quiet():
 
244
        if trace.is_quiet():
257
245
            return
258
246
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
259
247
            return
272
260
        # ( the path is different OR
273
261
        #   the kind is different)
274
262
        if (versioned == 'unchanged' and
275
 
                (renamed or modified == 'kind changed')):
276
 
            if renamed:
277
 
                # 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
278
266
                old_path, path = paths
279
267
            else:
280
 
                # if it's not renamed, we're showing both for kind changes
281
 
                # 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
282
270
                old_path, path = paths[1], paths[1]
283
271
            # if the file is not missing in the source, we show its kind
284
272
            # when we show two paths.
294
282
            path = paths[1]
295
283
        if renamed:
296
284
            rename = "R"
 
285
        elif copied:
 
286
            rename = "C"
297
287
        else:
298
288
            rename = self.versioned_map[versioned]
299
289
        # we show the old kind on the new path when the content is deleted.
328
318
        }
329
319
 
330
320
    def path_key(change):
331
 
        if change[1][0] is not None:
332
 
            path = change[1][0]
 
321
        if change.path[0] is not None:
 
322
            path = change.path[0]
333
323
        else:
334
 
            path = change[1][1]
 
324
            path = change.path[1]
335
325
        return osutils.splitpath(path)
336
 
    for (file_id, path, content_change, versioned, parent_id, name, kind,
337
 
         executable) in sorted(change_iterator, key=path_key):
 
326
    for change in sorted(change_iterator, key=path_key):
338
327
        exe_change = False
339
328
        # files are "renamed" if they are moved or if name changes, as long
340
329
        # as it had a value
341
 
        if None not in name and None not in parent_id and\
342
 
                (name[0] != name[1] or parent_id[0] != parent_id[1]):
 
330
        if change.copied:
 
331
            copied = True
 
332
            renamed = False
 
333
        elif change.renamed:
343
334
            renamed = True
 
335
            copied = False
344
336
        else:
 
337
            copied = False
345
338
            renamed = False
346
 
        if kind[0] != kind[1]:
347
 
            if kind[0] is None:
 
339
        if change.kind[0] != change.kind[1]:
 
340
            if change.kind[0] is None:
348
341
                modified = "created"
349
 
            elif kind[1] is None:
 
342
            elif change.kind[1] is None:
350
343
                modified = "deleted"
351
344
            else:
352
345
                modified = "kind changed"
353
346
        else:
354
 
            if content_change:
 
347
            if change.changed_content:
355
348
                modified = "modified"
356
 
            elif kind[0] is None:
 
349
            elif change.kind[0] is None:
357
350
                modified = "missing"
358
351
            else:
359
352
                modified = "unchanged"
360
 
            if kind[1] == "file":
361
 
                exe_change = (executable[0] != executable[1])
362
 
        versioned_change = versioned_change_map[versioned]
363
 
        reporter.report(file_id, path, versioned_change, renamed, modified,
364
 
                        exe_change, kind)
 
353
            if change.kind[1] == "file":
 
354
                exe_change = (change.executable[0] != change.executable[1])
 
355
        versioned_change = versioned_change_map[change.versioned]
 
356
        reporter.report(change.path, versioned_change, renamed, copied, modified,
 
357
                        exe_change, change.kind)
365
358
 
366
359
 
367
360
def report_delta(to_file, delta, short_status=False, show_ids=False,
381
374
    :param indent: Added at the beginning of all output lines (for merged
382
375
        revisions).
383
376
 
384
 
    :param predicate: A callable receiving a path and a file id and
385
 
        returning True if the path should be displayed.
 
377
    :param predicate: A callable receiving a path returning True if the path
 
378
        should be displayed.
386
379
 
387
380
    :param classify: Add special symbols to indicate file kind.
388
381
    """
399
392
        return path
400
393
 
401
394
    def show_more_renamed(item):
402
 
        (oldpath, file_id, kind,
403
 
         text_modified, meta_modified, newpath) = item
404
 
        dec_new_path = decorate_path(newpath, kind, meta_modified)
 
395
        dec_new_path = decorate_path(item.path[1], item.kind[1], item.meta_modified())
405
396
        to_file.write(' => %s' % dec_new_path)
406
 
        if text_modified or meta_modified:
407
 
            extra_modified.append((newpath, file_id, kind,
408
 
                                   text_modified, meta_modified))
 
397
        if item.changed_content or item.meta_modified():
 
398
            extra_modified.append(InventoryTreeChange(
 
399
                item.file_id, (item.path[1], item.path[1]),
 
400
                item.changed_content,
 
401
                item.versioned,
 
402
                (item.parent_id[1], item.parent_id[1]),
 
403
                (item.name[1], item.name[1]),
 
404
                (item.kind[1], item.kind[1]),
 
405
                item.executable))
409
406
 
410
407
    def show_more_kind_changed(item):
411
 
        (path, file_id, old_kind, new_kind) = item
412
 
        to_file.write(' (%s => %s)' % (old_kind, new_kind))
 
408
        to_file.write(' (%s => %s)' % (item.kind[0], item.kind[1]))
413
409
 
414
 
    def show_path(path, file_id, kind, meta_modified,
 
410
    def show_path(path, kind, meta_modified,
415
411
                  default_format, with_file_id_format):
416
412
        dec_path = decorate_path(path, kind, meta_modified)
417
413
        if show_ids:
431
427
            prefix = indent + prefix + '  '
432
428
 
433
429
            for item in files:
434
 
                path, file_id, kind = item[:3]
435
 
                if (predicate is not None and not predicate(path, file_id)):
 
430
                if item.path[0] is None:
 
431
                    path = item.path[1]
 
432
                    kind = item.kind[1]
 
433
                else:
 
434
                    path = item.path[0]
 
435
                    kind = item.kind[0]
 
436
                if predicate is not None and not predicate(path):
436
437
                    continue
437
438
                if not header_shown and not short_status:
438
439
                    to_file.write(indent + long_status_name + ':\n')
439
440
                    header_shown = True
440
 
                meta_modified = None
441
 
                if len(item) == 5:
442
 
                    meta_modified = item[4]
443
 
 
444
441
                to_file.write(prefix)
445
 
                show_path(path, file_id, kind, meta_modified,
 
442
                show_path(path, kind, item.meta_modified(),
446
443
                          default_format, with_file_id_format)
447
444
                if show_more is not None:
448
445
                    show_more(item)
449
 
                if show_ids:
450
 
                    to_file.write(' %s' % file_id.decode('utf-8'))
 
446
                if show_ids and getattr(item, 'file_id', None):
 
447
                    to_file.write(' %s' % item.file_id.decode('utf-8'))
451
448
                to_file.write('\n')
452
449
 
453
450
    show_list(delta.removed, 'removed', 'D')
454
451
    show_list(delta.added, 'added', 'A')
455
452
    show_list(delta.missing, 'missing', '!')
456
453
    extra_modified = []
457
 
    # Reorder delta.renamed tuples so that all lists share the same
458
 
    # order for their 3 first fields and that they also begin like
459
 
    # the delta.modified tuples
460
 
    renamed = [(p, i, k, tm, mm, np)
461
 
               for p, np, i, k, tm, mm in delta.renamed]
462
 
    show_list(renamed, 'renamed', 'R', with_file_id_format='%s',
 
454
    show_list(delta.renamed, 'renamed', 'R', with_file_id_format='%s',
 
455
              show_more=show_more_renamed)
 
456
    show_list(delta.copied, 'copied', 'C', with_file_id_format='%s',
463
457
              show_more=show_more_renamed)
464
458
    show_list(delta.kind_changed, 'kind changed', 'K',
465
459
              with_file_id_format='%s',