~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/delta.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from bzrlib import (
18
 
    errors,
19
18
    osutils,
20
19
    )
21
 
from bzrlib.inventory import InventoryEntry
22
 
from bzrlib.trace import mutter, is_quiet
23
 
from bzrlib.symbol_versioning import deprecated_function
 
20
from bzrlib.trace import is_quiet
24
21
 
25
22
 
26
23
class TreeDelta(object):
47
44
 
48
45
    Files that are both modified and renamed are listed only in
49
46
    renamed, with the text_modified flag true. The text_modified
50
 
    applies either to the the content of the file or the target of the
 
47
    applies either to the content of the file or the target of the
51
48
    symbolic link, depending of the kind of file.
52
49
 
53
50
    Files are only considered renamed if their name has changed or
64
61
        self.modified = []
65
62
        self.unchanged = []
66
63
        self.unversioned = []
 
64
        self.missing = []
67
65
 
68
66
    def __eq__(self, other):
69
67
        if not isinstance(other, TreeDelta):
107
105
                return True
108
106
        return False
109
107
 
110
 
 
111
 
    def show(self, to_file, show_ids=False, show_unchanged=False,
112
 
             short_status=False, indent='',
113
 
             filter=None):
114
 
        """Output this delta in status-like form to to_file.
115
 
 
116
 
        :param to_file: A file-like object where the output is displayed.
117
 
 
118
 
        :param show_ids: Output the file ids if True.
119
 
 
120
 
        :param show_unchanged: Output the unchanged files if True.
121
 
 
122
 
        :param short_status: Single-line status if True.
123
 
 
124
 
        :param indent: Added at the beginning of all output lines (for merged
125
 
            revisions).
126
 
 
127
 
        :param filter: A callable receiving a path and a file id and
128
 
            returning True if the path should be displayed.
129
 
        """
130
 
 
131
 
        def decorate_path(path, kind, meta_modified=None):
132
 
            if kind == 'directory':
133
 
                path += '/'
134
 
            elif kind == 'symlink':
135
 
                path += '@'
136
 
            if meta_modified:
137
 
                path += '*'
138
 
            return path
139
 
 
140
 
        def show_more_renamed(item):
141
 
            (oldpath, file_id, kind,
142
 
             text_modified, meta_modified, newpath) = item
143
 
            dec_new_path = decorate_path(newpath, kind, meta_modified)
144
 
            to_file.write(' => %s' % dec_new_path)
145
 
            if text_modified or meta_modified:
146
 
                extra_modified.append((newpath, file_id, kind,
147
 
                                       text_modified, meta_modified))
148
 
 
149
 
        def show_more_kind_changed(item):
150
 
            (path, file_id, old_kind, new_kind) = item
151
 
            to_file.write(' (%s => %s)' % (old_kind, new_kind))
152
 
 
153
 
        def show_path(path, file_id, kind, meta_modified,
154
 
                      default_format, with_file_id_format):
155
 
            dec_path = decorate_path(path, kind, meta_modified)
156
 
            if show_ids:
157
 
                to_file.write(with_file_id_format % dec_path)
158
 
            else:
159
 
                to_file.write(default_format % dec_path)
160
 
 
161
 
        def show_list(files, long_status_name, short_status_letter,
162
 
                      default_format='%s', with_file_id_format='%-30s',
163
 
                      show_more=None):
164
 
            if files:
165
 
                header_shown = False
166
 
                if short_status:
167
 
                    prefix = short_status_letter
168
 
                else:
169
 
                    prefix = ''
170
 
                prefix = indent + prefix + '  '
171
 
 
172
 
                for item in files:
173
 
                    path, file_id, kind = item[:3]
174
 
                    if (filter is not None and not filter(path, file_id)):
175
 
                        continue
176
 
                    if not header_shown and not short_status:
177
 
                        to_file.write(indent + long_status_name + ':\n')
178
 
                        header_shown = True
179
 
                    meta_modified = None
180
 
                    if len(item) == 5:
181
 
                        meta_modified = item[4]
182
 
 
183
 
                    to_file.write(prefix)
184
 
                    show_path(path, file_id, kind, meta_modified,
185
 
                              default_format, with_file_id_format)
186
 
                    if show_more is not None:
187
 
                        show_more(item)
188
 
                    if show_ids:
189
 
                        to_file.write(' %s' % file_id)
190
 
                    to_file.write('\n')
191
 
 
192
 
        show_list(self.removed, 'removed', 'D')#
193
 
        show_list(self.added, 'added', 'A')
194
 
        extra_modified = []
195
 
        # Reorder self.renamed tuples so that all lists share the same
196
 
        # order for their 3 first fields and that they also begin like
197
 
        # the self.modified tuples
198
 
        renamed = [(p, i, k, tm, mm, np)
199
 
                   for  p, np, i, k, tm, mm  in self.renamed]
200
 
        show_list(renamed, 'renamed', 'R', with_file_id_format='%s',
201
 
                  show_more=show_more_renamed)
202
 
        show_list(self.kind_changed, 'kind changed', 'K',
203
 
                  with_file_id_format='%s',
204
 
                  show_more=show_more_kind_changed)
205
 
        show_list(self.modified + extra_modified, 'modified', 'M')
206
 
        if show_unchanged:
207
 
            show_list(self.unchanged, 'unchanged', 'S')
208
 
 
209
 
        show_list(self.unversioned, 'unknown', ' ')
210
 
 
211
108
    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
212
 
             short_status=False):
 
109
                            short_status=False):
213
110
        import StringIO
214
111
        output = StringIO.StringIO()
215
 
        self.show(output, show_ids, show_unchanged, short_status)
 
112
        report_delta(output, self, short_status, show_ids, show_unchanged)
216
113
        return output.getvalue()
217
114
 
218
115
 
241
138
            else:
242
139
                delta.removed.append((path[0], file_id, kind[0]))
243
140
        elif fully_present[0] is False:
244
 
            continue
 
141
            delta.missing.append((path[1], file_id, kind[1]))
245
142
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
246
143
            # If the name changes, or the parent_id changes, we have a rename
247
144
            # (if we move a parent, that doesn't count as a rename for the
264
161
    delta.removed.sort()
265
162
    delta.added.sort()
266
163
    delta.renamed.sort()
 
164
    delta.missing.sort()
267
165
    # TODO: jam 20060529 These lists shouldn't need to be sorted
268
166
    #       since we added them in alphabetical order.
269
167
    delta.modified.sort()
276
174
    """Report changes between two trees"""
277
175
 
278
176
    def __init__(self, output=None, suppress_root_add=True,
279
 
                 output_file=None, unversioned_filter=None):
 
177
                 output_file=None, unversioned_filter=None, view_info=None,
 
178
                 classify=True):
280
179
        """Constructor
281
180
 
282
181
        :param output: a function with the signature of trace.note, i.e.
285
184
            (i.e. when a tree has just been initted)
286
185
        :param output_file: If supplied, a file-like object to write to.
287
186
            Only one of output and output_file may be supplied.
288
 
        :param unversioned_filter: A filter function to be called on 
 
187
        :param unversioned_filter: A filter function to be called on
289
188
            unversioned files. This should return True to ignore a path.
290
189
            By default, no filtering takes place.
 
190
        :param view_info: A tuple of view_name,view_files if only
 
191
            items inside a view are to be reported on, or None for
 
192
            no view filtering.
 
193
        :param classify: Add special symbols to indicate file kind.
291
194
        """
292
195
        if output_file is not None:
293
196
            if output is not None:
303
206
                             'unchanged': ' ',
304
207
                             'created': 'N',
305
208
                             'modified': 'M',
306
 
                             'deleted': 'D'}
 
209
                             'deleted': 'D',
 
210
                             'missing': '!',
 
211
                             }
307
212
        self.versioned_map = {'added': '+', # versioned target
308
213
                              'unchanged': ' ', # versioned in both
309
214
                              'removed': '-', # versioned in source
310
215
                              'unversioned': '?', # versioned in neither
311
216
                              }
312
217
        self.unversioned_filter = unversioned_filter
 
218
        if classify:
 
219
            self.kind_marker = osutils.kind_marker
 
220
        else:
 
221
            self.kind_marker = lambda kind: ''
 
222
        if view_info is None:
 
223
            self.view_name = None
 
224
            self.view_files = []
 
225
        else:
 
226
            self.view_name = view_info[0]
 
227
            self.view_files = view_info[1]
 
228
            self.output("Operating on whole tree but only reporting on "
 
229
                        "'%s' view." % (self.view_name,))
313
230
 
314
231
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
315
232
               kind):
330
247
            return
331
248
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
332
249
            return
 
250
        if self.view_files and not osutils.is_inside_any(self.view_files,
 
251
            paths[1]):
 
252
            return
333
253
        if versioned == 'unversioned':
334
254
            # skip ignored unversioned files if needed.
335
255
            if self.unversioned_filter is not None:
353
273
            # if the file is not missing in the source, we show its kind
354
274
            # when we show two paths.
355
275
            if kind[0] is not None:
356
 
                old_path += osutils.kind_marker(kind[0])
 
276
                old_path += self.kind_marker(kind[0])
357
277
            old_path += " => "
358
278
        elif versioned == 'removed':
359
279
            # not present in target
368
288
            rename = self.versioned_map[versioned]
369
289
        # we show the old kind on the new path when the content is deleted.
370
290
        if modified == 'deleted':
371
 
            path += osutils.kind_marker(kind[0])
 
291
            path += self.kind_marker(kind[0])
372
292
        # otherwise we always show the current kind when there is one
373
293
        elif kind[1] is not None:
374
 
            path += osutils.kind_marker(kind[1])
 
294
            path += self.kind_marker(kind[1])
375
295
        if exe_change:
376
296
            exe = '*'
377
297
        else:
379
299
        self.output("%s%s%s %s%s", rename, self.modified_map[modified], exe,
380
300
                    old_path, path)
381
301
 
382
 
 
383
302
def report_changes(change_iterator, reporter):
384
303
    """Report the changes from a change iterator.
385
304
 
416
335
        else:
417
336
            if content_change:
418
337
                modified = "modified"
 
338
            elif kind[0] is None:
 
339
                modified = "missing"
419
340
            else:
420
341
                modified = "unchanged"
421
342
            if kind[1] == "file":
423
344
        versioned_change = versioned_change_map[versioned]
424
345
        reporter.report(file_id, path, versioned_change, renamed, modified,
425
346
                        exe_change, kind)
 
347
 
 
348
def report_delta(to_file, delta, short_status=False, show_ids=False, 
 
349
         show_unchanged=False, indent='', filter=None, classify=True):
 
350
    """Output this delta in status-like form to to_file.
 
351
 
 
352
    :param to_file: A file-like object where the output is displayed.
 
353
 
 
354
    :param delta: A TreeDelta containing the changes to be displayed
 
355
 
 
356
    :param short_status: Single-line status if True.
 
357
 
 
358
    :param show_ids: Output the file ids if True.
 
359
 
 
360
    :param show_unchanged: Output the unchanged files if True.
 
361
 
 
362
    :param indent: Added at the beginning of all output lines (for merged
 
363
        revisions).
 
364
 
 
365
    :param filter: A callable receiving a path and a file id and
 
366
        returning True if the path should be displayed.
 
367
 
 
368
    :param classify: Add special symbols to indicate file kind.
 
369
    """
 
370
 
 
371
    def decorate_path(path, kind, meta_modified=None):
 
372
        if not classify:
 
373
            return path
 
374
        if kind == 'directory':
 
375
            path += '/'
 
376
        elif kind == 'symlink':
 
377
            path += '@'
 
378
        if meta_modified:
 
379
            path += '*'
 
380
        return path
 
381
 
 
382
    def show_more_renamed(item):
 
383
        (oldpath, file_id, kind,
 
384
         text_modified, meta_modified, newpath) = item
 
385
        dec_new_path = decorate_path(newpath, kind, meta_modified)
 
386
        to_file.write(' => %s' % dec_new_path)
 
387
        if text_modified or meta_modified:
 
388
            extra_modified.append((newpath, file_id, kind,
 
389
                                   text_modified, meta_modified))
 
390
 
 
391
    def show_more_kind_changed(item):
 
392
        (path, file_id, old_kind, new_kind) = item
 
393
        to_file.write(' (%s => %s)' % (old_kind, new_kind))
 
394
 
 
395
    def show_path(path, file_id, kind, meta_modified,
 
396
                  default_format, with_file_id_format):
 
397
        dec_path = decorate_path(path, kind, meta_modified)
 
398
        if show_ids:
 
399
            to_file.write(with_file_id_format % dec_path)
 
400
        else:
 
401
            to_file.write(default_format % dec_path)
 
402
 
 
403
    def show_list(files, long_status_name, short_status_letter,
 
404
                  default_format='%s', with_file_id_format='%-30s',
 
405
                  show_more=None):
 
406
        if files:
 
407
            header_shown = False
 
408
            if short_status:
 
409
                prefix = short_status_letter
 
410
            else:
 
411
                prefix = ''
 
412
            prefix = indent + prefix + '  '
 
413
 
 
414
            for item in files:
 
415
                path, file_id, kind = item[:3]
 
416
                if (filter is not None and not filter(path, file_id)):
 
417
                    continue
 
418
                if not header_shown and not short_status:
 
419
                    to_file.write(indent + long_status_name + ':\n')
 
420
                    header_shown = True
 
421
                meta_modified = None
 
422
                if len(item) == 5:
 
423
                    meta_modified = item[4]
 
424
 
 
425
                to_file.write(prefix)
 
426
                show_path(path, file_id, kind, meta_modified,
 
427
                          default_format, with_file_id_format)
 
428
                if show_more is not None:
 
429
                    show_more(item)
 
430
                if show_ids:
 
431
                    to_file.write(' %s' % file_id)
 
432
                to_file.write('\n')
 
433
 
 
434
    show_list(delta.removed, 'removed', 'D')
 
435
    show_list(delta.added, 'added', 'A')
 
436
    show_list(delta.missing, 'missing', '!')
 
437
    extra_modified = []
 
438
    # Reorder delta.renamed tuples so that all lists share the same
 
439
    # order for their 3 first fields and that they also begin like
 
440
    # the delta.modified tuples
 
441
    renamed = [(p, i, k, tm, mm, np)
 
442
               for  p, np, i, k, tm, mm  in delta.renamed]
 
443
    show_list(renamed, 'renamed', 'R', with_file_id_format='%s',
 
444
              show_more=show_more_renamed)
 
445
    show_list(delta.kind_changed, 'kind changed', 'K',
 
446
              with_file_id_format='%s',
 
447
              show_more=show_more_kind_changed)
 
448
    show_list(delta.modified + extra_modified, 'modified', 'M')
 
449
    if show_unchanged:
 
450
        show_list(delta.unchanged, 'unchanged', 'S')
 
451
 
 
452
    show_list(delta.unversioned, 'unknown', ' ')
 
453