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

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from io import StringIO
18
 
 
19
 
from breezy import (
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
from bzrlib import (
 
18
    errors,
20
19
    osutils,
21
 
    trace,
22
20
    )
23
 
from .tree import TreeChange
 
21
from bzrlib.inventory import InventoryEntry
 
22
from bzrlib.trace import mutter
 
23
from bzrlib.symbol_versioning import deprecated_function, zero_nine
24
24
 
25
25
 
26
26
class TreeDelta(object):
27
27
    """Describes changes from one tree to another.
28
28
 
29
 
    Contains seven lists with TreeChange objects.
 
29
    Contains four lists:
30
30
 
31
31
    added
 
32
        (path, id, kind)
32
33
    removed
 
34
        (path, id, kind)
33
35
    renamed
34
 
    copied
35
 
    kind_changed
 
36
        (oldpath, newpath, id, kind, text_modified, meta_modified)
36
37
    modified
 
38
        (path, id, kind, text_modified, meta_modified)
37
39
    unchanged
 
40
        (path, id, kind)
38
41
    unversioned
 
42
        (path, kind)
39
43
 
40
44
    Each id is listed only once.
41
45
 
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
44
 
    applies either to the content of the file or the target of the
 
46
    Files that are both modified and renamed are listed only in
 
47
    renamed, with the text_modified flag true. The text_modified
 
48
    applies either to the the content of the file or the target of the
45
49
    symbolic link, depending of the kind of file.
46
50
 
47
51
    Files are only considered renamed if their name has changed or
50
54
 
51
55
    The lists are normally sorted when the delta is created.
52
56
    """
53
 
 
54
57
    def __init__(self):
55
58
        self.added = []
56
59
        self.removed = []
57
60
        self.renamed = []
58
 
        self.copied = []
59
61
        self.kind_changed = []
60
62
        self.modified = []
61
63
        self.unchanged = []
62
64
        self.unversioned = []
63
 
        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.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
 
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
76
76
 
77
77
    def __ne__(self, other):
78
78
        return not (self == other)
79
79
 
80
80
    def __repr__(self):
81
81
        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)
 
82
            " kind_changed=%r, modified=%r, unchanged=%r," \
 
83
            " unversioned=%r)" % (self.added,
 
84
            self.removed, self.renamed, self.kind_changed, self.modified,
 
85
            self.unchanged, self.unversioned)
87
86
 
88
87
    def has_changed(self):
89
88
        return bool(self.modified
90
89
                    or self.added
91
90
                    or self.removed
92
91
                    or self.renamed
93
 
                    or self.copied
94
92
                    or self.kind_changed)
95
93
 
96
 
    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
97
 
                            short_status=False):
98
 
        output = StringIO()
99
 
        report_delta(output, self, short_status, show_ids, show_unchanged)
100
 
        return output.getvalue()
 
94
    def touches_file_id(self, file_id):
 
95
        """Return True if file_id is modified by this delta."""
 
96
        for l in self.added, self.removed, self.modified:
 
97
            for v in l:
 
98
                if v[1] == file_id:
 
99
                    return True
 
100
        for v in self.renamed:
 
101
            if v[2] == file_id:
 
102
                return True
 
103
        for v in self.kind_changed:
 
104
            if v[1] == file_id:
 
105
                return True
 
106
        return False
 
107
            
 
108
 
 
109
    def show(self, to_file, show_ids=False, show_unchanged=False,
 
110
             short_status=False):
 
111
        """output this delta in status-like form to to_file."""
 
112
        def show_list(files, short_status_letter=''):
 
113
            for item in files:
 
114
                path, fid, kind = item[:3]
 
115
 
 
116
                if kind == 'directory':
 
117
                    path += '/'
 
118
                elif kind == 'symlink':
 
119
                    path += '@'
 
120
 
 
121
                if len(item) == 5 and item[4]:
 
122
                    path += '*'
 
123
 
 
124
                if show_ids:
 
125
                    print >>to_file, '%s  %-30s %s' % (short_status_letter,
 
126
                        path, fid)
 
127
                else:
 
128
                    print >>to_file, '%s  %s' % (short_status_letter, path)
 
129
            
 
130
        if self.removed:
 
131
            if not short_status:
 
132
                print >>to_file, 'removed:'
 
133
                show_list(self.removed)
 
134
            else:
 
135
                show_list(self.removed, 'D')
 
136
                
 
137
        if self.added:
 
138
            if not short_status:
 
139
                print >>to_file, 'added:'
 
140
                show_list(self.added)
 
141
            else:
 
142
                show_list(self.added, 'A')
 
143
 
 
144
        extra_modified = []
 
145
 
 
146
        if self.renamed:
 
147
            short_status_letter = 'R'
 
148
            if not short_status:
 
149
                print >>to_file, 'renamed:'
 
150
                short_status_letter = ''
 
151
            for (oldpath, newpath, fid, kind,
 
152
                 text_modified, meta_modified) in self.renamed:
 
153
                if text_modified or meta_modified:
 
154
                    extra_modified.append((newpath, fid, kind,
 
155
                                           text_modified, meta_modified))
 
156
                if meta_modified:
 
157
                    newpath += '*'
 
158
                if show_ids:
 
159
                    print >>to_file, '%s  %s => %s %s' % (
 
160
                        short_status_letter, oldpath, newpath, fid)
 
161
                else:
 
162
                    print >>to_file, '%s  %s => %s' % (
 
163
                        short_status_letter, oldpath, newpath)
 
164
 
 
165
        if self.kind_changed:
 
166
            if short_status:
 
167
                short_status_letter = 'K'
 
168
            else:
 
169
                print >>to_file, 'kind changed:'
 
170
                short_status_letter = ''
 
171
            for (path, fid, old_kind, new_kind) in self.kind_changed:
 
172
                if show_ids:
 
173
                    suffix = ' '+fid
 
174
                else:
 
175
                    suffix = ''
 
176
                print >>to_file, '%s  %s (%s => %s)%s' % (
 
177
                    short_status_letter, path, old_kind, new_kind, suffix)
 
178
 
 
179
        if self.modified or extra_modified:
 
180
            short_status_letter = 'M'
 
181
            if not short_status:
 
182
                print >>to_file, 'modified:'
 
183
                short_status_letter = ''
 
184
            show_list(self.modified, short_status_letter)
 
185
            show_list(extra_modified, short_status_letter)
 
186
            
 
187
        if show_unchanged and self.unchanged:
 
188
            if not short_status:
 
189
                print >>to_file, 'unchanged:'
 
190
                show_list(self.unchanged)
 
191
            else:
 
192
                show_list(self.unchanged, 'S')
 
193
 
 
194
        if self.unversioned:
 
195
            print >>to_file, 'unknown:'
 
196
            show_list(self.unversioned)
 
197
 
 
198
 
 
199
@deprecated_function(zero_nine)
 
200
def compare_trees(old_tree, new_tree, want_unchanged=False,
 
201
                  specific_files=None, extra_trees=None,
 
202
                  require_versioned=False):
 
203
    """compare_trees was deprecated in 0.10. Please see Tree.changes_from."""
 
204
    return new_tree.changes_from(old_tree,
 
205
        want_unchanged=want_unchanged,
 
206
        specific_files=specific_files,
 
207
        extra_trees=extra_trees,
 
208
        require_versioned=require_versioned,
 
209
        include_root=False)
101
210
 
102
211
 
103
212
def _compare_trees(old_tree, new_tree, want_unchanged, specific_files,
104
213
                   include_root, extra_trees=None,
105
 
                   require_versioned=False, want_unversioned=False):
 
214
                   want_unversioned=False):
106
215
    """Worker function that implements Tree.changes_from."""
107
216
    delta = TreeDelta()
108
217
    # mutter('start compare_trees')
109
218
 
110
 
    for change in new_tree.iter_changes(
111
 
            old_tree, want_unchanged, specific_files, extra_trees=extra_trees,
112
 
            require_versioned=require_versioned,
 
219
    for (file_id, path, content_change, versioned, parent_id, name, kind,
 
220
         executable) in new_tree._iter_changes(old_tree, want_unchanged,
 
221
            specific_files, extra_trees=extra_trees,
113
222
            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))
 
223
        if versioned == (False, False):
 
224
            delta.unversioned.append((path[1], None, kind[1]))
 
225
            continue
 
226
        if not include_root and (None, None) == parent_id:
 
227
            continue
 
228
        fully_present = tuple((versioned[x] and kind[x] is not None) for
 
229
                              x in range(2))
122
230
        if fully_present[0] != fully_present[1]:
123
231
            if fully_present[1] is True:
124
 
                delta.added.append(change)
 
232
                delta.added.append((path[1], file_id, kind[1]))
125
233
            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)
 
234
                assert fully_present[0] is True
 
235
                delta.removed.append((path[0], file_id, kind[0]))
132
236
        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
 
237
            continue
 
238
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
 
239
            # If the name changes, or the parent_id changes, we have a rename
136
240
            # (if we move a parent, that doesn't count as a rename for the
137
241
            # 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)
 
242
            delta.renamed.append((path[0],
 
243
                                  path[1],
 
244
                                  file_id,
 
245
                                  kind[1],
 
246
                                  content_change,
 
247
                                  (executable[0] != executable[1])))
 
248
        elif kind[0] != kind[1]:
 
249
            delta.kind_changed.append((path[1], file_id, kind[0], kind[1]))
 
250
        elif content_change is True or executable[0] != executable[1]:
 
251
            delta.modified.append((path[1], file_id, kind[1],
 
252
                                   content_change,
 
253
                                   (executable[0] != executable[1])))
 
254
        else:
 
255
            delta.unchanged.append((path[1], file_id, kind[1]))
 
256
 
 
257
    delta.removed.sort()
 
258
    delta.added.sort()
 
259
    delta.renamed.sort()
161
260
    # TODO: jam 20060529 These lists shouldn't need to be sorted
162
261
    #       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)
 
262
    delta.modified.sort()
 
263
    delta.unchanged.sort()
166
264
 
167
265
    return delta
168
266
 
171
269
    """Report changes between two trees"""
172
270
 
173
271
    def __init__(self, output=None, suppress_root_add=True,
174
 
                 output_file=None, unversioned_filter=None, view_info=None,
175
 
                 classify=True):
 
272
                 output_file=None, unversioned_filter=None):
176
273
        """Constructor
177
274
 
178
275
        :param output: a function with the signature of trace.note, i.e.
181
278
            (i.e. when a tree has just been initted)
182
279
        :param output_file: If supplied, a file-like object to write to.
183
280
            Only one of output and output_file may be supplied.
184
 
        :param unversioned_filter: A filter function to be called on
 
281
        :param unversioned_filter: A filter function to be called on 
185
282
            unversioned files. This should return True to ignore a path.
186
283
            By default, no filtering takes place.
187
 
        :param view_info: A tuple of view_name,view_files if only
188
 
            items inside a view are to be reported on, or None for
189
 
            no view filtering.
190
 
        :param classify: Add special symbols to indicate file kind.
191
284
        """
192
285
        if output_file is not None:
193
286
            if output is not None:
194
287
                raise BzrError('Cannot specify both output and output_file')
195
 
 
196
288
            def output(fmt, *args):
197
289
                output_file.write((fmt % args) + '\n')
198
290
        self.output = output
199
291
        if self.output is None:
200
 
            from . import trace
 
292
            from bzrlib import trace
201
293
            self.output = trace.note
202
294
        self.suppress_root_add = suppress_root_add
203
295
        self.modified_map = {'kind changed': 'K',
204
296
                             'unchanged': ' ',
205
297
                             'created': 'N',
206
298
                             'modified': 'M',
207
 
                             'deleted': 'D',
208
 
                             'missing': '!',
209
 
                             }
210
 
        self.versioned_map = {'added': '+',  # versioned target
211
 
                              'unchanged': ' ',  # versioned in both
212
 
                              'removed': '-',  # versioned in source
213
 
                              'unversioned': '?',  # versioned in neither
 
299
                             'deleted': 'D'}
 
300
        self.versioned_map = {'added': '+', # versioned target
 
301
                              'unchanged': ' ', # versioned in both
 
302
                              'removed': '-', # versioned in source
 
303
                              'unversioned': '?', # versioned in neither
214
304
                              }
215
305
        self.unversioned_filter = unversioned_filter
216
 
        if classify:
217
 
            self.kind_marker = osutils.kind_marker
218
 
        else:
219
 
            self.kind_marker = lambda kind: ''
220
 
        if view_info is None:
221
 
            self.view_name = None
222
 
            self.view_files = []
223
 
        else:
224
 
            self.view_name = view_info[0]
225
 
            self.view_files = view_info[1]
226
 
            self.output("Operating on whole tree but only reporting on "
227
 
                        "'%s' view." % (self.view_name,))
228
306
 
229
 
    def report(self, paths, versioned, renamed, copied, modified, exe_change,
 
307
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
230
308
               kind):
231
309
        """Report one change to a file
232
310
 
233
 
        :param path: The old and new paths as generated by Tree.iter_changes.
 
311
        :param file_id: The file_id of the file
 
312
        :param path: The old and new paths as generated by Tree._iter_changes.
234
313
        :param versioned: may be 'added', 'removed', 'unchanged', or
235
314
            'unversioned.
236
315
        :param renamed: may be True or False
237
 
        :param copied: may be True or False
238
316
        :param modified: may be 'created', 'deleted', 'kind changed',
239
317
            'modified' or 'unchanged'.
240
318
        :param exe_change: True if the execute bit has changed
241
 
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
 
319
        :param kind: A pair of file kinds, as generated by Tree._iter_changes.
242
320
            None indicates no file present.
243
321
        """
244
 
        if trace.is_quiet():
245
 
            return
246
322
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
247
323
            return
248
 
        if self.view_files and not osutils.is_inside_any(self.view_files,
249
 
                                                         paths[1]):
250
 
            return
251
324
        if versioned == 'unversioned':
252
325
            # skip ignored unversioned files if needed.
253
326
            if self.unversioned_filter is not None:
260
333
        # ( the path is different OR
261
334
        #   the kind is different)
262
335
        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
 
336
            (renamed or modified == 'kind changed')):
 
337
            if renamed:
 
338
                # on a rename, we show old and new
266
339
                old_path, path = paths
267
340
            else:
268
 
                # if it's not renamed or copied, we're showing both for kind
269
 
                # changes so only show the new path
 
341
                # if its not renamed, we're showing both for kind changes
 
342
                # so only show the new path
270
343
                old_path, path = paths[1], paths[1]
271
344
            # if the file is not missing in the source, we show its kind
272
345
            # when we show two paths.
273
346
            if kind[0] is not None:
274
 
                old_path += self.kind_marker(kind[0])
 
347
                old_path += osutils.kind_marker(kind[0])
275
348
            old_path += " => "
276
349
        elif versioned == 'removed':
277
350
            # not present in target
282
355
            path = paths[1]
283
356
        if renamed:
284
357
            rename = "R"
285
 
        elif copied:
286
 
            rename = "C"
287
358
        else:
288
359
            rename = self.versioned_map[versioned]
289
360
        # we show the old kind on the new path when the content is deleted.
290
361
        if modified == 'deleted':
291
 
            path += self.kind_marker(kind[0])
 
362
            path += osutils.kind_marker(kind[0])
292
363
        # otherwise we always show the current kind when there is one
293
364
        elif kind[1] is not None:
294
 
            path += self.kind_marker(kind[1])
 
365
            path += osutils.kind_marker(kind[1])
295
366
        if exe_change:
296
367
            exe = '*'
297
368
        else:
307
378
    Further processing may be required to produce a human-readable output.
308
379
    Unfortunately, some tree-changing operations are very complex
309
380
    :change_iterator: an iterator or sequence of changes in the format
310
 
        generated by Tree.iter_changes
 
381
        generated by Tree._iter_changes
311
382
    :param reporter: The _ChangeReporter that will report the changes.
312
383
    """
313
384
    versioned_change_map = {
314
 
        (True, True): 'unchanged',
315
 
        (True, False): 'removed',
316
 
        (False, True): 'added',
 
385
        (True, True)  : 'unchanged',
 
386
        (True, False) : 'removed',
 
387
        (False, True) : 'added',
317
388
        (False, False): 'unversioned',
318
389
        }
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):
 
390
    for (file_id, path, content_change, versioned, parent_id, name, kind,
 
391
         executable) in change_iterator:
327
392
        exe_change = False
328
393
        # files are "renamed" if they are moved or if name changes, as long
329
394
        # 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
 
395
        if None not in name and None not in parent_id and\
 
396
            (name[0] != name[1] or parent_id[0] != parent_id[1]):
 
397
            renamed = True
338
398
        else:
339
 
            copied = False
340
399
            renamed = False
341
 
        if change.kind[0] != change.kind[1]:
342
 
            if change.kind[0] is None:
 
400
        if kind[0] != kind[1]:
 
401
            if kind[0] is None:
343
402
                modified = "created"
344
 
            elif change.kind[1] is None:
 
403
            elif kind[1] is None:
345
404
                modified = "deleted"
346
405
            else:
347
406
                modified = "kind changed"
348
407
        else:
349
 
            if change.changed_content:
 
408
            if content_change:
350
409
                modified = "modified"
351
 
            elif change.kind[0] is None:
352
 
                modified = "missing"
353
410
            else:
354
411
                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)
360
 
 
361
 
 
362
 
def report_delta(to_file, delta, short_status=False, show_ids=False,
363
 
                 show_unchanged=False, indent='', predicate=None, classify=True):
364
 
    """Output this delta in status-like form to to_file.
365
 
 
366
 
    :param to_file: A file-like object where the output is displayed.
367
 
 
368
 
    :param delta: A TreeDelta containing the changes to be displayed
369
 
 
370
 
    :param short_status: Single-line status if True.
371
 
 
372
 
    :param show_ids: Output the file ids if True.
373
 
 
374
 
    :param show_unchanged: Output the unchanged files if True.
375
 
 
376
 
    :param indent: Added at the beginning of all output lines (for merged
377
 
        revisions).
378
 
 
379
 
    :param predicate: A callable receiving a path returning True if the path
380
 
        should be displayed.
381
 
 
382
 
    :param classify: Add special symbols to indicate file kind.
383
 
    """
384
 
 
385
 
    def decorate_path(path, kind, meta_modified=None):
386
 
        if not classify:
387
 
            return path
388
 
        if kind == 'directory':
389
 
            path += '/'
390
 
        elif kind == 'symlink':
391
 
            path += '@'
392
 
        if meta_modified:
393
 
            path += '*'
394
 
        return path
395
 
 
396
 
    def show_more_renamed(item):
397
 
        dec_new_path = decorate_path(item.path[1], item.kind[1], item.meta_modified())
398
 
        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))
408
 
 
409
 
    def show_more_kind_changed(item):
410
 
        to_file.write(' (%s => %s)' % (item.kind[0], item.kind[1]))
411
 
 
412
 
    def show_path(path, kind, meta_modified,
413
 
                  default_format, with_file_id_format):
414
 
        dec_path = decorate_path(path, kind, meta_modified)
415
 
        if show_ids:
416
 
            to_file.write(with_file_id_format % dec_path)
417
 
        else:
418
 
            to_file.write(default_format % dec_path)
419
 
 
420
 
    def show_list(files, long_status_name, short_status_letter,
421
 
                  default_format='%s', with_file_id_format='%-30s',
422
 
                  show_more=None):
423
 
        if files:
424
 
            header_shown = False
425
 
            if short_status:
426
 
                prefix = short_status_letter
427
 
            else:
428
 
                prefix = ''
429
 
            prefix = indent + prefix + '  '
430
 
 
431
 
            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):
439
 
                    continue
440
 
                if not header_shown and not short_status:
441
 
                    to_file.write(indent + long_status_name + ':\n')
442
 
                    header_shown = True
443
 
                to_file.write(prefix)
444
 
                show_path(path, kind, item.meta_modified(),
445
 
                          default_format, with_file_id_format)
446
 
                if show_more is not None:
447
 
                    show_more(item)
448
 
                if show_ids and getattr(item, 'file_id', None):
449
 
                    to_file.write(' %s' % item.file_id.decode('utf-8'))
450
 
                to_file.write('\n')
451
 
 
452
 
    show_list(delta.removed, 'removed', 'D')
453
 
    show_list(delta.added, 'added', 'A')
454
 
    show_list(delta.missing, 'missing', '!')
455
 
    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',
459
 
              show_more=show_more_renamed)
460
 
    show_list(delta.kind_changed, 'kind changed', 'K',
461
 
              with_file_id_format='%s',
462
 
              show_more=show_more_kind_changed)
463
 
    show_list(delta.modified + extra_modified, 'modified', 'M')
464
 
    if show_unchanged:
465
 
        show_list(delta.unchanged, 'unchanged', 'S')
466
 
 
467
 
    show_list(delta.unversioned, 'unknown', ' ')
 
412
            if kind[1] == "file":
 
413
                exe_change = (executable[0] != executable[1])
 
414
        versioned_change = versioned_change_map[versioned]
 
415
        reporter.report(file_id, path, versioned_change, renamed, modified,
 
416
                        exe_change, kind)