~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2005-06-22 06:38:18 UTC
  • Revision ID: mbp@sourcefrog.net-20050622063818-cd8d21b14bd4b636
- add plugin patch, still being integrated

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
17
 
18
 
from bzrlib.trace import mutter
19
 
from bzrlib.errors import BzrError
20
 
from bzrlib.delta import compare_trees
 
18
from trace import mutter
 
19
from errors import BzrError
21
20
 
22
 
# TODO: Rather than building a changeset object, we should probably
23
 
# invoke callbacks on an object.  That object can either accumulate a
24
 
# list, write them out directly, etc etc.
25
21
 
26
22
def internal_diff(old_label, oldlines, new_label, newlines, to_file):
27
23
    import difflib
42
38
    if not oldlines and not newlines:
43
39
        return
44
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
 
45
50
    ud = difflib.unified_diff(oldlines, newlines,
46
51
                              fromfile=old_label, tofile=new_label)
47
52
 
54
59
        ud = list(ud)
55
60
        ud[2] = ud[2].replace('+1,0', '+0,0')
56
61
 
57
 
    for line in ud:
58
 
        to_file.write(line)
59
 
        if not line.endswith('\n'):
60
 
            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"
61
65
    print >>to_file
62
66
 
63
67
 
143
147
    
144
148
 
145
149
 
146
 
def show_diff(b, revision, specific_files, external_diff_options=None,
147
 
              revision2=None):
 
150
def show_diff(b, revision, specific_files, external_diff_options=None):
148
151
    """Shortcut for showing the diff to the working tree.
149
152
 
150
153
    b
162
165
        old_tree = b.basis_tree()
163
166
    else:
164
167
        old_tree = b.revision_tree(b.lookup_revision(revision))
165
 
 
166
 
    if revision2 == None:
167
 
        new_tree = b.working_tree()
168
 
    else:
169
 
        new_tree = b.revision_tree(b.lookup_revision(revision2))
 
168
        
 
169
    new_tree = b.working_tree()
170
170
 
171
171
    show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
172
172
                    external_diff_options)
246
246
 
247
247
 
248
248
 
249
 
 
250
 
 
 
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