~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Aaron Bentley
  • Date: 2005-07-29 17:19:16 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 1020.
  • Revision ID: abentley@panoramicfeedback.com-20050729171916-322fd81b451d2e3e
Added merge-type parameter to merge.

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
 
20
 
21
21
 
22
22
# TODO: Rather than building a changeset object, we should probably
23
23
# invoke callbacks on an object.  That object can either accumulate a
143
143
    
144
144
 
145
145
 
146
 
def show_diff(b, revision, specific_files, external_diff_options=None,
147
 
              revision2=None):
 
146
def show_diff(b, revision, specific_files, external_diff_options=None):
148
147
    """Shortcut for showing the diff to the working tree.
149
148
 
150
149
    b
162
161
        old_tree = b.basis_tree()
163
162
    else:
164
163
        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))
 
164
        
 
165
    new_tree = b.working_tree()
170
166
 
171
167
    show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
172
168
                    external_diff_options)
246
242
 
247
243
 
248
244
 
249
 
 
250
 
 
 
245
class TreeDelta(object):
 
246
    """Describes changes from one tree to another.
 
247
 
 
248
    Contains four lists:
 
249
 
 
250
    added
 
251
        (path, id, kind)
 
252
    removed
 
253
        (path, id, kind)
 
254
    renamed
 
255
        (oldpath, newpath, id, kind, text_modified)
 
256
    modified
 
257
        (path, id, kind)
 
258
    unchanged
 
259
        (path, id, kind)
 
260
 
 
261
    Each id is listed only once.
 
262
 
 
263
    Files that are both modified and renamed are listed only in
 
264
    renamed, with the text_modified flag true.
 
265
 
 
266
    Files are only considered renamed if their name has changed or
 
267
    their parent directory has changed.  Renaming a directory
 
268
    does not count as renaming all its contents.
 
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 __eq__(self, other):
 
280
        if not isinstance(other, TreeDelta):
 
281
            return False
 
282
        return self.added == other.added \
 
283
               and self.removed == other.removed \
 
284
               and self.renamed == other.renamed \
 
285
               and self.modified == other.modified \
 
286
               and self.unchanged == other.unchanged
 
287
 
 
288
    def __ne__(self, other):
 
289
        return not (self == other)
 
290
 
 
291
    def __repr__(self):
 
292
        return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
 
293
            " unchanged=%r)" % (self.added, self.removed, self.renamed,
 
294
            self.modified, self.unchanged)
 
295
 
 
296
    def has_changed(self):
 
297
        changes = len(self.added) + len(self.removed) + len(self.renamed)
 
298
        changes += len(self.modified) 
 
299
        return (changes != 0)
 
300
 
 
301
    def touches_file_id(self, file_id):
 
302
        """Return True if file_id is modified by this delta."""
 
303
        for l in self.added, self.removed, self.modified:
 
304
            for v in l:
 
305
                if v[1] == file_id:
 
306
                    return True
 
307
        for v in self.renamed:
 
308
            if v[2] == file_id:
 
309
                return True
 
310
        return False
 
311
            
 
312
 
 
313
    def show(self, to_file, show_ids=False, show_unchanged=False):
 
314
        def show_list(files):
 
315
            for path, fid, kind in files:
 
316
                if kind == 'directory':
 
317
                    path += '/'
 
318
                elif kind == 'symlink':
 
319
                    path += '@'
 
320
                    
 
321
                if show_ids:
 
322
                    print >>to_file, '  %-30s %s' % (path, fid)
 
323
                else:
 
324
                    print >>to_file, ' ', path
 
325
            
 
326
        if self.removed:
 
327
            print >>to_file, 'removed:'
 
328
            show_list(self.removed)
 
329
                
 
330
        if self.added:
 
331
            print >>to_file, 'added:'
 
332
            show_list(self.added)
 
333
 
 
334
        if self.renamed:
 
335
            print >>to_file, 'renamed:'
 
336
            for oldpath, newpath, fid, kind, text_modified in self.renamed:
 
337
                if show_ids:
 
338
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
 
339
                else:
 
340
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
 
341
                    
 
342
        if self.modified:
 
343
            print >>to_file, 'modified:'
 
344
            show_list(self.modified)
 
345
            
 
346
        if show_unchanged and self.unchanged:
 
347
            print >>to_file, 'unchanged:'
 
348
            show_list(self.unchanged)
 
349
 
 
350
 
 
351
 
 
352
def compare_trees(old_tree, new_tree, want_unchanged=False, specific_files=None):
 
353
    """Describe changes from one tree to another.
 
354
 
 
355
    Returns a TreeDelta with details of added, modified, renamed, and
 
356
    deleted entries.
 
357
 
 
358
    The root entry is specifically exempt.
 
359
 
 
360
    This only considers versioned files.
 
361
 
 
362
    want_unchanged
 
363
        If true, also list files unchanged from one version to
 
364
        the next.
 
365
 
 
366
    specific_files
 
367
        If true, only check for changes to specified names or
 
368
        files within them.
 
369
    """
 
370
 
 
371
    from osutils import is_inside_any
 
372
    
 
373
    old_inv = old_tree.inventory
 
374
    new_inv = new_tree.inventory
 
375
    delta = TreeDelta()
 
376
    mutter('start compare_trees')
 
377
 
 
378
    # TODO: match for specific files can be rather smarter by finding
 
379
    # the IDs of those files up front and then considering only that.
 
380
 
 
381
    for file_id in old_tree:
 
382
        if file_id in new_tree:
 
383
            old_ie = old_inv[file_id]
 
384
            new_ie = new_inv[file_id]
 
385
 
 
386
            kind = old_ie.kind
 
387
            assert kind == new_ie.kind
 
388
            
 
389
            assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
 
390
                   'invalid file kind %r' % kind
 
391
 
 
392
            if kind == 'root_directory':
 
393
                continue
 
394
            
 
395
            if specific_files:
 
396
                if (not is_inside_any(specific_files, old_inv.id2path(file_id)) 
 
397
                    and not is_inside_any(specific_files, new_inv.id2path(file_id))):
 
398
                    continue
 
399
 
 
400
            if kind == 'file':
 
401
                old_sha1 = old_tree.get_file_sha1(file_id)
 
402
                new_sha1 = new_tree.get_file_sha1(file_id)
 
403
                text_modified = (old_sha1 != new_sha1)
 
404
            else:
 
405
                ## mutter("no text to check for %r %r" % (file_id, kind))
 
406
                text_modified = False
 
407
 
 
408
            # TODO: Can possibly avoid calculating path strings if the
 
409
            # two files are unchanged and their names and parents are
 
410
            # the same and the parents are unchanged all the way up.
 
411
            # May not be worthwhile.
 
412
            
 
413
            if (old_ie.name != new_ie.name
 
414
                or old_ie.parent_id != new_ie.parent_id):
 
415
                delta.renamed.append((old_inv.id2path(file_id),
 
416
                                      new_inv.id2path(file_id),
 
417
                                      file_id, kind,
 
418
                                      text_modified))
 
419
            elif text_modified:
 
420
                delta.modified.append((new_inv.id2path(file_id), file_id, kind))
 
421
            elif want_unchanged:
 
422
                delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
 
423
        else:
 
424
            kind = old_inv.get_file_kind(file_id)
 
425
            old_path = old_inv.id2path(file_id)
 
426
            if specific_files:
 
427
                if not is_inside_any(specific_files, old_path):
 
428
                    continue
 
429
            delta.removed.append((old_path, file_id, kind))
 
430
 
 
431
    mutter('start looking for new files')
 
432
    for file_id in new_inv:
 
433
        if file_id in old_inv:
 
434
            continue
 
435
        new_path = new_inv.id2path(file_id)
 
436
        if specific_files:
 
437
            if not is_inside_any(specific_files, new_path):
 
438
                continue
 
439
        kind = new_inv.get_file_kind(file_id)
 
440
        delta.added.append((new_path, file_id, kind))
 
441
            
 
442
    delta.removed.sort()
 
443
    delta.added.sort()
 
444
    delta.renamed.sort()
 
445
    delta.modified.sort()
 
446
    delta.unchanged.sort()
 
447
 
 
448
    return delta