~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/delta.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
    osutils,
20
20
    )
21
21
from bzrlib.inventory import InventoryEntry
22
 
from bzrlib.trace import mutter, is_quiet
23
 
from bzrlib.symbol_versioning import deprecated_function
 
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):
38
38
        (path, id, kind, text_modified, meta_modified)
39
39
    unchanged
40
40
        (path, id, kind)
41
 
    unversioned
42
 
        (path, kind)
43
41
 
44
42
    Each id is listed only once.
45
43
 
61
59
        self.kind_changed = []
62
60
        self.modified = []
63
61
        self.unchanged = []
64
 
        self.unversioned = []
65
62
 
66
63
    def __eq__(self, other):
67
64
        if not isinstance(other, TreeDelta):
71
68
               and self.renamed == other.renamed \
72
69
               and self.modified == other.modified \
73
70
               and self.unchanged == other.unchanged \
74
 
               and self.kind_changed == other.kind_changed \
75
 
               and self.unversioned == other.unversioned
 
71
               and self.kind_changed == other.kind_changed
76
72
 
77
73
    def __ne__(self, other):
78
74
        return not (self == other)
79
75
 
80
76
    def __repr__(self):
81
77
        return "TreeDelta(added=%r, removed=%r, renamed=%r," \
82
 
            " kind_changed=%r, modified=%r, unchanged=%r," \
83
 
            " unversioned=%r)" % (self.added,
 
78
            " kind_changed=%r, modified=%r, unchanged=%r)" % (self.added,
84
79
            self.removed, self.renamed, self.kind_changed, self.modified,
85
 
            self.unchanged, self.unversioned)
 
80
            self.unchanged)
86
81
 
87
82
    def has_changed(self):
88
83
        return bool(self.modified
107
102
            
108
103
 
109
104
    def show(self, to_file, show_ids=False, show_unchanged=False,
110
 
             short_status=False, indent=''):
 
105
             short_status=False):
111
106
        """output this delta in status-like form to to_file."""
112
107
        def show_list(files, short_status_letter=''):
113
108
            for item in files:
122
117
                    path += '*'
123
118
 
124
119
                if show_ids:
125
 
                    to_file.write(indent + '%s  %-30s %s\n' % (short_status_letter,
126
 
                        path, fid))
 
120
                    print >>to_file, '%s  %-30s %s' % (short_status_letter,
 
121
                        path, fid)
127
122
                else:
128
 
                    to_file.write(indent + '%s  %s\n' % (short_status_letter, path))
 
123
                    print >>to_file, '%s  %s' % (short_status_letter, path)
129
124
            
130
125
        if self.removed:
131
126
            if not short_status:
132
 
                to_file.write(indent + 'removed:\n')
 
127
                print >>to_file, 'removed:'
133
128
                show_list(self.removed)
134
129
            else:
135
130
                show_list(self.removed, 'D')
136
131
                
137
132
        if self.added:
138
133
            if not short_status:
139
 
                to_file.write(indent + 'added:\n')
 
134
                print >>to_file, 'added:'
140
135
                show_list(self.added)
141
136
            else:
142
137
                show_list(self.added, 'A')
146
141
        if self.renamed:
147
142
            short_status_letter = 'R'
148
143
            if not short_status:
149
 
                to_file.write(indent + 'renamed:\n')
 
144
                print >>to_file, 'renamed:'
150
145
                short_status_letter = ''
151
146
            for (oldpath, newpath, fid, kind,
152
147
                 text_modified, meta_modified) in self.renamed:
156
151
                if meta_modified:
157
152
                    newpath += '*'
158
153
                if show_ids:
159
 
                    to_file.write(indent + '%s  %s => %s %s\n' % (
160
 
                        short_status_letter, oldpath, newpath, fid))
 
154
                    print >>to_file, '%s  %s => %s %s' % (
 
155
                        short_status_letter, oldpath, newpath, fid)
161
156
                else:
162
 
                    to_file.write(indent + '%s  %s => %s\n' % (
163
 
                        short_status_letter, oldpath, newpath))
 
157
                    print >>to_file, '%s  %s => %s' % (
 
158
                        short_status_letter, oldpath, newpath)
164
159
 
165
160
        if self.kind_changed:
166
161
            if short_status:
167
162
                short_status_letter = 'K'
168
163
            else:
169
 
                to_file.write(indent + 'kind changed:\n')
 
164
                print >>to_file, 'kind changed:'
170
165
                short_status_letter = ''
171
166
            for (path, fid, old_kind, new_kind) in self.kind_changed:
172
167
                if show_ids:
173
168
                    suffix = ' '+fid
174
169
                else:
175
170
                    suffix = ''
176
 
                to_file.write(indent + '%s  %s (%s => %s)%s\n' % (
177
 
                    short_status_letter, path, old_kind, new_kind, suffix))
 
171
                print >>to_file, '%s  %s (%s => %s)%s' % (
 
172
                    short_status_letter, path, old_kind, new_kind, suffix)
178
173
 
179
174
        if self.modified or extra_modified:
180
175
            short_status_letter = 'M'
181
176
            if not short_status:
182
 
                to_file.write(indent + 'modified:\n')
 
177
                print >>to_file, 'modified:'
183
178
                short_status_letter = ''
184
179
            show_list(self.modified, short_status_letter)
185
180
            show_list(extra_modified, short_status_letter)
186
181
            
187
182
        if show_unchanged and self.unchanged:
188
183
            if not short_status:
189
 
                to_file.write(indent + 'unchanged:\n')
 
184
                print >>to_file, 'unchanged:'
190
185
                show_list(self.unchanged)
191
186
            else:
192
187
                show_list(self.unchanged, 'S')
193
188
 
194
 
        if self.unversioned:
195
 
            to_file.write(indent + 'unknown:\n')
196
 
            show_list(self.unversioned)
197
 
 
198
 
    def get_changes_as_text(self, show_ids=False, show_unchanged=False,
199
 
             short_status=False):
200
 
        import StringIO
201
 
        output = StringIO.StringIO()
202
 
        self.show(output, show_ids, show_unchanged, short_status)
203
 
        return output.getvalue()
204
 
 
205
 
 
206
 
def _compare_trees(old_tree, new_tree, want_unchanged, specific_files,
207
 
                   include_root, extra_trees=None,
208
 
                   require_versioned=False, want_unversioned=False):
209
 
    """Worker function that implements Tree.changes_from."""
 
189
 
 
190
@deprecated_function(zero_nine)
 
191
def compare_trees(old_tree, new_tree, want_unchanged=False,
 
192
                  specific_files=None, extra_trees=None,
 
193
                  require_versioned=False):
 
194
    """compare_trees was deprecated in 0.10. Please see Tree.changes_from."""
 
195
    return new_tree.changes_from(old_tree,
 
196
        want_unchanged=want_unchanged,
 
197
        specific_files=specific_files,
 
198
        extra_trees=extra_trees,
 
199
        require_versioned=require_versioned,
 
200
        include_root=False)
 
201
 
 
202
 
 
203
def _compare_trees(old_tree, new_tree, want_unchanged, specific_file_ids,
 
204
                   include_root):
210
205
    delta = TreeDelta()
211
206
    # mutter('start compare_trees')
212
207
 
213
208
    for (file_id, path, content_change, versioned, parent_id, name, kind,
214
 
         executable) in new_tree.iter_changes(old_tree, want_unchanged,
215
 
            specific_files, extra_trees=extra_trees,
216
 
            require_versioned=require_versioned,
217
 
            want_unversioned=want_unversioned):
218
 
        if versioned == (False, False):
219
 
            delta.unversioned.append((path[1], None, kind[1]))
220
 
            continue
 
209
         executable) in new_tree._iter_changes(old_tree, want_unchanged, 
 
210
                                               specific_file_ids):
221
211
        if not include_root and (None, None) == parent_id:
222
212
            continue
223
213
        fully_present = tuple((versioned[x] and kind[x] is not None) for
224
214
                              x in range(2))
225
215
        if fully_present[0] != fully_present[1]:
226
216
            if fully_present[1] is True:
227
 
                delta.added.append((path[1], file_id, kind[1]))
 
217
                delta.added.append((path, file_id, kind[1]))
228
218
            else:
229
 
                delta.removed.append((path[0], file_id, kind[0]))
 
219
                assert fully_present[0] is True
 
220
                old_path = old_tree.id2path(file_id)
 
221
                delta.removed.append((old_path, file_id, kind[0]))
230
222
        elif fully_present[0] is False:
231
223
            continue
232
224
        elif name[0] != name[1] or parent_id[0] != parent_id[1]:
233
225
            # If the name changes, or the parent_id changes, we have a rename
234
226
            # (if we move a parent, that doesn't count as a rename for the
235
227
            # file)
236
 
            delta.renamed.append((path[0],
237
 
                                  path[1],
238
 
                                  file_id,
 
228
            old_path = old_tree.id2path(file_id)
 
229
            delta.renamed.append((old_path,
 
230
                                  path,
 
231
                                  file_id, 
239
232
                                  kind[1],
240
 
                                  content_change,
 
233
                                  content_change, 
241
234
                                  (executable[0] != executable[1])))
242
235
        elif kind[0] != kind[1]:
243
 
            delta.kind_changed.append((path[1], file_id, kind[0], kind[1]))
 
236
            delta.kind_changed.append((path, file_id, kind[0], kind[1]))
244
237
        elif content_change is True or executable[0] != executable[1]:
245
 
            delta.modified.append((path[1], file_id, kind[1],
246
 
                                   content_change,
 
238
            delta.modified.append((path, file_id, kind[1],
 
239
                                   content_change, 
247
240
                                   (executable[0] != executable[1])))
248
241
        else:
249
 
            delta.unchanged.append((path[1], file_id, kind[1]))
 
242
            delta.unchanged.append((path, file_id, kind[1]))
250
243
 
251
244
    delta.removed.sort()
252
245
    delta.added.sort()
259
252
    return delta
260
253
 
261
254
 
262
 
class _ChangeReporter(object):
 
255
class ChangeReporter(object):
263
256
    """Report changes between two trees"""
264
257
 
265
 
    def __init__(self, output=None, suppress_root_add=True,
266
 
                 output_file=None, unversioned_filter=None):
267
 
        """Constructor
268
 
 
269
 
        :param output: a function with the signature of trace.note, i.e.
270
 
            accepts a format and parameters.
271
 
        :param supress_root_add: If true, adding the root will be ignored
272
 
            (i.e. when a tree has just been initted)
273
 
        :param output_file: If supplied, a file-like object to write to.
274
 
            Only one of output and output_file may be supplied.
275
 
        :param unversioned_filter: A filter function to be called on 
276
 
            unversioned files. This should return True to ignore a path.
277
 
            By default, no filtering takes place.
278
 
        """
279
 
        if output_file is not None:
280
 
            if output is not None:
281
 
                raise BzrError('Cannot specify both output and output_file')
282
 
            def output(fmt, *args):
283
 
                output_file.write((fmt % args) + '\n')
 
258
    def __init__(self, old_inventory, output=None):
 
259
        self.old_inventory = old_inventory
284
260
        self.output = output
285
261
        if self.output is None:
286
262
            from bzrlib import trace
287
263
            self.output = trace.note
288
 
        self.suppress_root_add = suppress_root_add
289
 
        self.modified_map = {'kind changed': 'K',
290
 
                             'unchanged': ' ',
291
 
                             'created': 'N',
292
 
                             'modified': 'M',
293
 
                             'deleted': 'D'}
294
 
        self.versioned_map = {'added': '+', # versioned target
295
 
                              'unchanged': ' ', # versioned in both
296
 
                              'removed': '-', # versioned in source
297
 
                              'unversioned': '?', # versioned in neither
298
 
                              }
299
 
        self.unversioned_filter = unversioned_filter
300
264
 
301
 
    def report(self, file_id, paths, versioned, renamed, modified, exe_change,
 
265
    def report(self, file_id, path, versioned, renamed, modified, exe_change,
302
266
               kind):
303
267
        """Report one change to a file
304
268
 
305
269
        :param file_id: The file_id of the file
306
 
        :param path: The old and new paths as generated by Tree.iter_changes.
307
 
        :param versioned: may be 'added', 'removed', 'unchanged', or
308
 
            'unversioned.
 
270
        :param path: The path the file has (or would have) in the tree (as
 
271
            generated by Tree._iter_changes)
 
272
        :param versioned: may be 'added', 'removed', or 'unchanged'
309
273
        :param renamed: may be True or False
310
274
        :param modified: may be 'created', 'deleted', 'kind changed',
311
275
            'modified' or 'unchanged'.
312
276
        :param exe_change: True if the execute bit has changed
313
 
        :param kind: A pair of file kinds, as generated by Tree.iter_changes.
 
277
        :param kind: A pair of file kinds, as generated by Tree._iter_changes.
314
278
            None indicates no file present.
315
279
        """
316
 
        if is_quiet():
317
 
            return
318
 
        if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
319
 
            return
320
 
        if versioned == 'unversioned':
321
 
            # skip ignored unversioned files if needed.
322
 
            if self.unversioned_filter is not None:
323
 
                if self.unversioned_filter(paths[1]):
324
 
                    return
325
 
            # dont show a content change in the output.
326
 
            modified = 'unchanged'
327
 
        # we show both paths in the following situations:
328
 
        # the file versioning is unchanged AND
329
 
        # ( the path is different OR
330
 
        #   the kind is different)
331
 
        if (versioned == 'unchanged' and
332
 
            (renamed or modified == 'kind changed')):
333
 
            if renamed:
334
 
                # on a rename, we show old and new
335
 
                old_path, path = paths
336
 
            else:
337
 
                # if it's not renamed, we're showing both for kind changes
338
 
                # so only show the new path
339
 
                old_path, path = paths[1], paths[1]
340
 
            # if the file is not missing in the source, we show its kind
341
 
            # when we show two paths.
342
 
            if kind[0] is not None:
343
 
                old_path += osutils.kind_marker(kind[0])
344
 
            old_path += " => "
345
 
        elif versioned == 'removed':
346
 
            # not present in target
347
 
            old_path = ""
348
 
            path = paths[0]
349
 
        else:
350
 
            old_path = ""
351
 
            path = paths[1]
 
280
        modified_map = {'kind changed': 'K',
 
281
                        'unchanged': ' ',
 
282
                        'created': 'N',
 
283
                        'modified': 'M',
 
284
                        'deleted': 'D'}
 
285
        versioned_map = {'added': '+',
 
286
                         'unchanged': ' ',
 
287
                         'removed': '-'}
 
288
        old_path = ""
352
289
        if renamed:
 
290
            old_path = self.old_inventory.id2path(file_id)
353
291
            rename = "R"
354
292
        else:
355
 
            rename = self.versioned_map[versioned]
356
 
        # we show the old kind on the new path when the content is deleted.
 
293
            rename = versioned_map[versioned]
 
294
        if modified == 'kind changed':
 
295
            if old_path == "":
 
296
                old_path = path
357
297
        if modified == 'deleted':
358
298
            path += osutils.kind_marker(kind[0])
359
 
        # otherwise we always show the current kind when there is one
360
 
        elif kind[1] is not None:
 
299
        else:
361
300
            path += osutils.kind_marker(kind[1])
 
301
        if old_path != "":
 
302
            old_path += "%s => " % osutils.kind_marker(kind[0])
362
303
        if exe_change:
363
304
            exe = '*'
364
305
        else:
365
306
            exe = ' '
366
 
        self.output("%s%s%s %s%s", rename, self.modified_map[modified], exe,
 
307
        self.output("%s%s%s %s%s", rename, modified_map[modified], exe,
367
308
                    old_path, path)
368
309
 
369
310
 
374
315
    Further processing may be required to produce a human-readable output.
375
316
    Unfortunately, some tree-changing operations are very complex
376
317
    :change_iterator: an iterator or sequence of changes in the format
377
 
        generated by Tree.iter_changes
378
 
    :param reporter: The _ChangeReporter that will report the changes.
 
318
        generated by Tree._iter_changes
 
319
    :param reporter: The ChangeReporter that will report the changes.
379
320
    """
380
 
    versioned_change_map = {
381
 
        (True, True)  : 'unchanged',
382
 
        (True, False) : 'removed',
383
 
        (False, True) : 'added',
384
 
        (False, False): 'unversioned',
385
 
        }
386
321
    for (file_id, path, content_change, versioned, parent_id, name, kind,
387
322
         executable) in change_iterator:
388
323
        exe_change = False
389
324
        # files are "renamed" if they are moved or if name changes, as long
390
325
        # as it had a value
391
 
        if None not in name and None not in parent_id and\
392
 
            (name[0] != name[1] or parent_id[0] != parent_id[1]):
 
326
        if None not in name and (name[0] != name[1] or
 
327
                                 parent_id[0] != parent_id[1]):
393
328
            renamed = True
394
329
        else:
395
330
            renamed = False
407
342
                modified = "unchanged"
408
343
            if kind[1] == "file":
409
344
                exe_change = (executable[0] != executable[1])
410
 
        versioned_change = versioned_change_map[versioned]
 
345
        if versioned[0] != versioned[1]:
 
346
            if versioned[0]:
 
347
                versioned_change = "removed"
 
348
            else:
 
349
                versioned_change = "added"
 
350
        else:
 
351
            versioned_change = "unchanged"
411
352
        reporter.report(file_id, path, versioned_change, renamed, modified,
412
353
                        exe_change, kind)