~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2005-06-10 06:22:11 UTC
  • Revision ID: mbp@sourcefrog.net-20050610062211-3540a4591114f483
- fix redirection of messages to file in diff
  (from Johan Rydberg)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/env python
1
2
# -*- coding: UTF-8 -*-
2
3
 
3
4
# This program is free software; you can redistribute it and/or modify
14
15
# along with this program; if not, write to the Free Software
15
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
 
17
 
from bzrlib.trace import mutter
18
 
from bzrlib.errors import BzrError
19
 
from bzrlib.delta import compare_trees
 
18
from trace import mutter
 
19
from errors import BzrError
20
20
 
21
 
# TODO: Rather than building a changeset object, we should probably
22
 
# invoke callbacks on an object.  That object can either accumulate a
23
 
# list, write them out directly, etc etc.
24
21
 
25
22
def internal_diff(old_label, oldlines, new_label, newlines, to_file):
26
23
    import difflib
41
38
    if not oldlines and not newlines:
42
39
        return
43
40
 
 
41
    nonl = False
 
42
 
 
43
    if oldlines and (oldlines[-1][-1] != '\n'):
 
44
        oldlines[-1] += '\n'
 
45
        nonl = True
 
46
    if newlines and (newlines[-1][-1] != '\n'):
 
47
        newlines[-1] += '\n'
 
48
        nonl = True
 
49
 
44
50
    ud = difflib.unified_diff(oldlines, newlines,
45
51
                              fromfile=old_label, tofile=new_label)
46
52
 
47
 
    ud = list(ud)
48
53
    # work-around for difflib being too smart for its own good
49
54
    # if /dev/null is "1,0", patch won't recognize it as /dev/null
50
55
    if not oldlines:
 
56
        ud = list(ud)
51
57
        ud[2] = ud[2].replace('-1,0', '-0,0')
52
58
    elif not newlines:
 
59
        ud = list(ud)
53
60
        ud[2] = ud[2].replace('+1,0', '+0,0')
54
 
    # work around for difflib emitting random spaces after the label
55
 
    ud[0] = ud[0][:-2] + '\n'
56
 
    ud[1] = ud[1][:-2] + '\n'
57
61
 
58
 
    for line in ud:
59
 
        to_file.write(line)
60
 
        if not line.endswith('\n'):
61
 
            to_file.write("\n\\ No newline at end of file\n")
 
62
    to_file.writelines(ud)
 
63
    if nonl:
 
64
        print >>to_file, "\\ No newline at end of file"
62
65
    print >>to_file
63
66
 
64
67
 
144
147
    
145
148
 
146
149
 
147
 
def show_diff(b, revision, specific_files, external_diff_options=None,
148
 
              revision2=None, output=None):
 
150
def show_diff(b, revision, specific_files, external_diff_options=None):
149
151
    """Shortcut for showing the diff to the working tree.
150
152
 
151
153
    b
157
159
    The more general form is show_diff_trees(), where the caller
158
160
    supplies any two trees.
159
161
    """
160
 
    if output is None:
161
 
        import sys
162
 
        output = sys.stdout
 
162
    import sys
163
163
 
164
164
    if revision == None:
165
165
        old_tree = b.basis_tree()
166
166
    else:
167
167
        old_tree = b.revision_tree(b.lookup_revision(revision))
168
 
 
169
 
    if revision2 == None:
170
 
        new_tree = b.working_tree()
171
 
    else:
172
 
        new_tree = b.revision_tree(b.lookup_revision(revision2))
173
 
 
174
 
    show_diff_trees(old_tree, new_tree, output, specific_files,
 
168
        
 
169
    new_tree = b.working_tree()
 
170
 
 
171
    show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
175
172
                    external_diff_options)
176
173
 
177
174
 
212
209
                          specific_files=specific_files)
213
210
 
214
211
    for path, file_id, kind in delta.removed:
215
 
        print >>to_file, '=== removed %s %r' % (kind, path)
 
212
        print >>to_file, '*** removed %s %r' % (kind, path)
216
213
        if kind == 'file':
217
214
            diff_file(old_label + path,
218
215
                      old_tree.get_file(file_id).readlines(),
221
218
                      to_file)
222
219
 
223
220
    for path, file_id, kind in delta.added:
224
 
        print >>to_file, '=== added %s %r' % (kind, path)
 
221
        print >>to_file, '*** added %s %r' % (kind, path)
225
222
        if kind == 'file':
226
223
            diff_file(DEVNULL,
227
224
                      [],
230
227
                      to_file)
231
228
 
232
229
    for old_path, new_path, file_id, kind, text_modified in delta.renamed:
233
 
        print >>to_file, '=== renamed %s %r => %r' % (kind, old_path, new_path)
 
230
        print >>to_file, '*** renamed %s %r => %r' % (kind, old_path, new_path)
234
231
        if text_modified:
235
232
            diff_file(old_label + old_path,
236
233
                      old_tree.get_file(file_id).readlines(),
239
236
                      to_file)
240
237
 
241
238
    for path, file_id, kind in delta.modified:
242
 
        print >>to_file, '=== modified %s %r' % (kind, path)
 
239
        print >>to_file, '*** modified %s %r' % (kind, path)
243
240
        if kind == 'file':
244
241
            diff_file(old_label + path,
245
242
                      old_tree.get_file(file_id).readlines(),
249
246
 
250
247
 
251
248
 
252
 
 
253
 
 
 
249
class TreeDelta(object):
 
250
    """Describes changes from one tree to another.
 
251
 
 
252
    Contains four lists:
 
253
 
 
254
    added
 
255
        (path, id, kind)
 
256
    removed
 
257
        (path, id, kind)
 
258
    renamed
 
259
        (oldpath, newpath, id, kind, text_modified)
 
260
    modified
 
261
        (path, id, kind)
 
262
    unchanged
 
263
        (path, id, kind)
 
264
 
 
265
    Each id is listed only once.
 
266
 
 
267
    Files that are both modified and renamed are listed only in
 
268
    renamed, with the text_modified flag true.
 
269
 
 
270
    The lists are normally sorted when the delta is created.
 
271
    """
 
272
    def __init__(self):
 
273
        self.added = []
 
274
        self.removed = []
 
275
        self.renamed = []
 
276
        self.modified = []
 
277
        self.unchanged = []
 
278
 
 
279
    def __repr__(self):
 
280
        return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
 
281
            " unchanged=%r)" % (self.added, self.removed, self.renamed,
 
282
            self.modified, self.unchanged)
 
283
 
 
284
    def has_changed(self):
 
285
        changes = len(self.added) + len(self.removed) + len(self.renamed)
 
286
        changes += len(self.modified) 
 
287
        return (changes != 0)
 
288
 
 
289
    def touches_file_id(self, file_id):
 
290
        """Return True if file_id is modified by this delta."""
 
291
        for l in self.added, self.removed, self.modified:
 
292
            for v in l:
 
293
                if v[1] == file_id:
 
294
                    return True
 
295
        for v in self.renamed:
 
296
            if v[2] == file_id:
 
297
                return True
 
298
        return False
 
299
            
 
300
 
 
301
    def show(self, to_file, show_ids=False, show_unchanged=False):
 
302
        def show_list(files):
 
303
            for path, fid, kind in files:
 
304
                if kind == 'directory':
 
305
                    path += '/'
 
306
                elif kind == 'symlink':
 
307
                    path += '@'
 
308
                    
 
309
                if show_ids:
 
310
                    print >>to_file, '  %-30s %s' % (path, fid)
 
311
                else:
 
312
                    print >>to_file, ' ', path
 
313
            
 
314
        if self.removed:
 
315
            print >>to_file, 'removed:'
 
316
            show_list(self.removed)
 
317
                
 
318
        if self.added:
 
319
            print >>to_file, 'added:'
 
320
            show_list(self.added)
 
321
 
 
322
        if self.renamed:
 
323
            print >>to_file, 'renamed:'
 
324
            for oldpath, newpath, fid, kind, text_modified in self.renamed:
 
325
                if show_ids:
 
326
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
327
                else:
 
328
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
329
                    
 
330
        if self.modified:
 
331
            print >>to_file, 'modified:'
 
332
            show_list(self.modified)
 
333
            
 
334
        if show_unchanged and self.unchanged:
 
335
            print >>to_file, 'unchanged:'
 
336
            show_list(self.unchanged)
 
337
 
 
338
 
 
339
 
 
340
def compare_trees(old_tree, new_tree, want_unchanged, specific_files=None):
 
341
    """Describe changes from one tree to another.
 
342
 
 
343
    Returns a TreeDelta with details of added, modified, renamed, and
 
344
    deleted entries.
 
345
 
 
346
    The root entry is specifically exempt.
 
347
 
 
348
    This only considers versioned files.
 
349
 
 
350
    want_unchanged
 
351
        If true, also list files unchanged from one version to
 
352
        the next.
 
353
 
 
354
    specific_files
 
355
        If true, only check for changes to specified names or
 
356
        files within them.
 
357
    """
 
358
 
 
359
    from osutils import is_inside_any
 
360
    
 
361
    old_inv = old_tree.inventory
 
362
    new_inv = new_tree.inventory
 
363
    delta = TreeDelta()
 
364
    mutter('start compare_trees')
 
365
 
 
366
    # TODO: match for specific files can be rather smarter by finding
 
367
    # the IDs of those files up front and then considering only that.
 
368
 
 
369
    for file_id in old_tree:
 
370
        if file_id in new_tree:
 
371
            kind = old_inv.get_file_kind(file_id)
 
372
            assert kind == new_inv.get_file_kind(file_id)
 
373
            
 
374
            assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
 
375
                   'invalid file kind %r' % kind
 
376
 
 
377
            if kind == 'root_directory':
 
378
                continue
 
379
            
 
380
            old_path = old_inv.id2path(file_id)
 
381
            new_path = new_inv.id2path(file_id)
 
382
 
 
383
            if specific_files:
 
384
                if (not is_inside_any(specific_files, old_path) 
 
385
                    and not is_inside_any(specific_files, new_path)):
 
386
                    continue
 
387
 
 
388
            if kind == 'file':
 
389
                old_sha1 = old_tree.get_file_sha1(file_id)
 
390
                new_sha1 = new_tree.get_file_sha1(file_id)
 
391
                text_modified = (old_sha1 != new_sha1)
 
392
            else:
 
393
                ## mutter("no text to check for %r %r" % (file_id, kind))
 
394
                text_modified = False
 
395
 
 
396
            # TODO: Can possibly avoid calculating path strings if the
 
397
            # two files are unchanged and their names and parents are
 
398
            # the same and the parents are unchanged all the way up.
 
399
            # May not be worthwhile.
 
400
            
 
401
            if old_path != new_path:
 
402
                delta.renamed.append((old_path, new_path, file_id, kind,
 
403
                                      text_modified))
 
404
            elif text_modified:
 
405
                delta.modified.append((new_path, file_id, kind))
 
406
            elif want_unchanged:
 
407
                delta.unchanged.append((new_path, file_id, kind))
 
408
        else:
 
409
            kind = old_inv.get_file_kind(file_id)
 
410
            old_path = old_inv.id2path(file_id)
 
411
            if specific_files:
 
412
                if not is_inside_any(specific_files, old_path):
 
413
                    continue
 
414
            delta.removed.append((old_path, file_id, kind))
 
415
 
 
416
    mutter('start looking for new files')
 
417
    for file_id in new_inv:
 
418
        if file_id in old_inv:
 
419
            continue
 
420
        new_path = new_inv.id2path(file_id)
 
421
        if specific_files:
 
422
            if not is_inside_any(specific_files, new_path):
 
423
                continue
 
424
        kind = new_inv.get_file_kind(file_id)
 
425
        delta.added.append((new_path, file_id, kind))
 
426
            
 
427
    delta.removed.sort()
 
428
    delta.added.sort()
 
429
    delta.renamed.sort()
 
430
    delta.modified.sort()
 
431
    delta.unchanged.sort()
 
432
 
 
433
    return delta