291
 
@deprecated_function(deprecated_in((2, 2, 0)))
 
292
 
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
 
294
 
    """Get the trees and specific files to diff given a list of paths.
 
296
 
    This method works out the trees to be diff'ed and the files of
 
297
 
    interest within those trees.
 
300
 
        the list of arguments passed to the diff command
 
301
 
    :param revision_specs:
 
302
 
        Zero, one or two RevisionSpecs from the diff command line,
 
303
 
        saying what revisions to compare.
 
305
 
        The url of the old branch or tree. If None, the tree to use is
 
306
 
        taken from the first path, if any, or the current working tree.
 
308
 
        The url of the new branch or tree. If None, the tree to use is
 
309
 
        taken from the first path, if any, or the current working tree.
 
311
 
        if True and a view is set, apply the view or check that the paths
 
314
 
        a tuple of (old_tree, new_tree, old_branch, new_branch,
 
315
 
        specific_files, extra_trees) where extra_trees is a sequence of
 
316
 
        additional trees to search in for file-ids.  The trees and branches
 
319
 
    op = cleanup.OperationWithCleanups(get_trees_and_branches_to_diff_locked)
 
320
 
    return op.run_simple(path_list, revision_specs, old_url, new_url,
 
321
 
            op.add_cleanup, apply_view=apply_view)
 
324
 
def get_trees_and_branches_to_diff_locked(
 
325
 
    path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
 
326
 
    """Get the trees and specific files to diff given a list of paths.
 
328
 
    This method works out the trees to be diff'ed and the files of
 
329
 
    interest within those trees.
 
332
 
        the list of arguments passed to the diff command
 
333
 
    :param revision_specs:
 
334
 
        Zero, one or two RevisionSpecs from the diff command line,
 
335
 
        saying what revisions to compare.
 
337
 
        The url of the old branch or tree. If None, the tree to use is
 
338
 
        taken from the first path, if any, or the current working tree.
 
340
 
        The url of the new branch or tree. If None, the tree to use is
 
341
 
        taken from the first path, if any, or the current working tree.
 
343
 
        a callable like Command.add_cleanup.  get_trees_and_branches_to_diff
 
344
 
        will register cleanups that must be run to unlock the trees, etc.
 
346
 
        if True and a view is set, apply the view or check that the paths
 
349
 
        a tuple of (old_tree, new_tree, old_branch, new_branch,
 
350
 
        specific_files, extra_trees) where extra_trees is a sequence of
 
351
 
        additional trees to search in for file-ids.  The trees and branches
 
352
 
        will be read-locked until the cleanups registered via the add_cleanup
 
355
 
    # Get the old and new revision specs
 
356
 
    old_revision_spec = None
 
357
 
    new_revision_spec = None
 
 
264
@deprecated_function(zero_eight)
 
 
265
def show_diff(b, from_spec, specific_files, external_diff_options=None,
 
 
266
              revision2=None, output=None, b2=None):
 
 
267
    """Shortcut for showing the diff to the working tree.
 
 
269
    Please use show_diff_trees instead.
 
 
275
        None for 'basis tree', or otherwise the old revision to compare against.
 
 
277
    The more general form is show_diff_trees(), where the caller
 
 
278
    supplies any two trees.
 
 
283
    if from_spec is None:
 
 
284
        old_tree = b.bzrdir.open_workingtree()
 
 
286
            old_tree = old_tree = old_tree.basis_tree()
 
 
288
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
 
290
    if revision2 is None:
 
 
292
            new_tree = b.bzrdir.open_workingtree()
 
 
294
            new_tree = b2.bzrdir.open_workingtree()
 
 
296
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
 
 
298
    return show_diff_trees(old_tree, new_tree, output, specific_files,
 
 
299
                           external_diff_options)
 
 
302
def diff_cmd_helper(tree, specific_files, external_diff_options, 
 
 
303
                    old_revision_spec=None, new_revision_spec=None,
 
 
305
                    old_label='a/', new_label='b/'):
 
 
306
    """Helper for cmd_diff.
 
 
311
    :param specific_files:
 
 
312
        The specific files to compare, or None
 
 
314
    :param external_diff_options:
 
 
315
        If non-None, run an external diff, and pass it these options
 
 
317
    :param old_revision_spec:
 
 
318
        If None, use basis tree as old revision, otherwise use the tree for
 
 
319
        the specified revision. 
 
 
321
    :param new_revision_spec:
 
 
322
        If None, use working tree as new revision, otherwise use the tree for
 
 
323
        the specified revision.
 
 
325
    :param revision_specs: 
 
 
326
        Zero, one or two RevisionSpecs from the command line, saying what revisions 
 
 
327
        to compare.  This can be passed as an alternative to the old_revision_spec 
 
 
328
        and new_revision_spec parameters.
 
 
330
    The more general form is show_diff_trees(), where the caller
 
 
331
    supplies any two trees.
 
 
334
    # TODO: perhaps remove the old parameters old_revision_spec and
 
 
335
    # new_revision_spec, since this is only really for use from cmd_diff and
 
 
336
    # it now always passes through a sequence of revision_specs -- mbp
 
 
341
            revision = spec.in_store(tree.branch)
 
 
343
            revision = spec.in_store(None)
 
 
344
        revision_id = revision.rev_id
 
 
345
        branch = revision.branch
 
 
346
        return branch.repository.revision_tree(revision_id)
 
358
348
    if revision_specs is not None:
 
 
349
        assert (old_revision_spec is None
 
 
350
                and new_revision_spec is None)
 
359
351
        if len(revision_specs) > 0:
 
360
352
            old_revision_spec = revision_specs[0]
 
362
 
                old_url = old_revision_spec.get_branch()
 
363
353
        if len(revision_specs) > 1:
 
364
354
            new_revision_spec = revision_specs[1]
 
366
 
                new_url = new_revision_spec.get_branch()
 
369
 
    make_paths_wt_relative = True
 
370
 
    consider_relpath = True
 
371
 
    if path_list is None or len(path_list) == 0:
 
372
 
        # If no path is given, the current working tree is used
 
373
 
        default_location = u'.'
 
374
 
        consider_relpath = False
 
375
 
    elif old_url is not None and new_url is not None:
 
376
 
        other_paths = path_list
 
377
 
        make_paths_wt_relative = False
 
379
 
        default_location = path_list[0]
 
380
 
        other_paths = path_list[1:]
 
382
 
    def lock_tree_or_branch(wt, br):
 
385
 
            add_cleanup(wt.unlock)
 
388
 
            add_cleanup(br.unlock)
 
390
 
    # Get the old location
 
393
 
        old_url = default_location
 
394
 
    working_tree, branch, relpath = \
 
395
 
        bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
 
396
 
    lock_tree_or_branch(working_tree, branch)
 
397
 
    if consider_relpath and relpath != '':
 
398
 
        if working_tree is not None and apply_view:
 
399
 
            views.check_path_in_view(working_tree, relpath)
 
400
 
        specific_files.append(relpath)
 
401
 
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
 
404
 
    # Get the new location
 
406
 
        new_url = default_location
 
407
 
    if new_url != old_url:
 
408
 
        working_tree, branch, relpath = \
 
409
 
            bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
 
410
 
        lock_tree_or_branch(working_tree, branch)
 
411
 
        if consider_relpath and relpath != '':
 
412
 
            if working_tree is not None and apply_view:
 
413
 
                views.check_path_in_view(working_tree, relpath)
 
414
 
            specific_files.append(relpath)
 
415
 
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
 
416
 
        basis_is_default=working_tree is None)
 
419
 
    # Get the specific files (all files is None, no files is [])
 
420
 
    if make_paths_wt_relative and working_tree is not None:
 
421
 
        other_paths = working_tree.safe_relpath_files(
 
423
 
            apply_view=apply_view)
 
424
 
    specific_files.extend(other_paths)
 
425
 
    if len(specific_files) == 0:
 
426
 
        specific_files = None
 
427
 
        if (working_tree is not None and working_tree.supports_views()
 
429
 
            view_files = working_tree.views.lookup_view()
 
431
 
                specific_files = view_files
 
432
 
                view_str = views.view_display_str(view_files)
 
433
 
                note("*** Ignoring files outside view. View is %s" % view_str)
 
435
 
    # Get extra trees that ought to be searched for file-ids
 
437
 
    if working_tree is not None and working_tree not in (old_tree, new_tree):
 
438
 
        extra_trees = (working_tree,)
 
439
 
    return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
 
442
 
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
 
443
 
    if branch is None and tree is not None:
 
445
 
    if spec is None or spec.spec is None:
 
448
 
                return tree.basis_tree()
 
450
 
                return branch.basis_tree()
 
453
 
    return spec.as_tree(branch)
 
 
356
    if old_revision_spec is None:
 
 
357
        old_tree = tree.basis_tree()
 
 
359
        old_tree = spec_tree(old_revision_spec)
 
 
361
    if (new_revision_spec is None
 
 
362
        or new_revision_spec.spec is None):
 
 
365
        new_tree = spec_tree(new_revision_spec)
 
 
367
    if new_tree is not tree:
 
 
368
        extra_trees = (tree,)
 
 
372
    return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
 
 
373
                           external_diff_options,
 
 
374
                           old_label=old_label, new_label=new_label,
 
 
375
                           extra_trees=extra_trees)
 
456
378
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
 
457
379
                    external_diff_options=None,
 
458
380
                    old_label='a/', new_label='b/',
 
460
 
                    path_encoding='utf8',
 
463
382
    """Show in text form the changes from one tree to another.
 
465
 
    :param to_file: The output stream.
 
466
 
    :param specific_files:Include only changes to these files - None for all
 
468
 
    :param external_diff_options: If set, use an external GNU diff and pass 
 
470
 
    :param extra_trees: If set, more Trees to use for looking up file ids
 
471
 
    :param path_encoding: If set, the path will be encoded as specified, 
 
472
 
        otherwise is supposed to be utf8
 
473
 
    :param format_cls: Formatter class (DiffTree subclass)
 
 
385
        If set, include only changes to these files.
 
 
387
    external_diff_options
 
 
388
        If set, use an external GNU diff and pass these options.
 
 
391
        If set, more Trees to use for looking up file ids
 
475
 
    if format_cls is None:
 
476
 
        format_cls = DiffTree
 
477
393
    old_tree.lock_read()
 
479
395
        if extra_trees is not None:
 
 
495
410
        old_tree.unlock()
 
498
 
def _patch_header_date(tree, file_id, path):
 
499
 
    """Returns a timestamp suitable for use in a patch header."""
 
501
 
        mtime = tree.get_file_mtime(file_id, path)
 
502
 
    except errors.FileTimestampUnavailable:
 
504
 
    return timestamp.format_patch_date(mtime)
 
507
 
def get_executable_change(old_is_x, new_is_x):
 
508
 
    descr = { True:"+x", False:"-x", None:"??" }
 
509
 
    if old_is_x != new_is_x:
 
510
 
        return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
 
515
 
class DiffPath(object):
 
516
 
    """Base type for command object that compare files"""
 
518
 
    # The type or contents of the file were unsuitable for diffing
 
519
 
    CANNOT_DIFF = 'CANNOT_DIFF'
 
520
 
    # The file has changed in a semantic way
 
522
 
    # The file content may have changed, but there is no semantic change
 
523
 
    UNCHANGED = 'UNCHANGED'
 
525
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
 
528
 
        :param old_tree: The tree to show as the old tree in the comparison
 
529
 
        :param new_tree: The tree to show as new in the comparison
 
530
 
        :param to_file: The file to write comparison data to
 
531
 
        :param path_encoding: The character encoding to write paths in
 
533
 
        self.old_tree = old_tree
 
534
 
        self.new_tree = new_tree
 
535
 
        self.to_file = to_file
 
536
 
        self.path_encoding = path_encoding
 
542
 
    def from_diff_tree(klass, diff_tree):
 
543
 
        return klass(diff_tree.old_tree, diff_tree.new_tree,
 
544
 
                     diff_tree.to_file, diff_tree.path_encoding)
 
547
 
    def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
 
548
 
        for file_differ in differs:
 
549
 
            result = file_differ.diff(file_id, old_path, new_path, old_kind,
 
551
 
            if result is not DiffPath.CANNOT_DIFF:
 
554
 
            return DiffPath.CANNOT_DIFF
 
557
 
class DiffKindChange(object):
 
558
 
    """Special differ for file kind changes.
 
560
 
    Represents kind change as deletion + creation.  Uses the other differs
 
563
 
    def __init__(self, differs):
 
564
 
        self.differs = differs
 
570
 
    def from_diff_tree(klass, diff_tree):
 
571
 
        return klass(diff_tree.differs)
 
573
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
574
 
        """Perform comparison
 
576
 
        :param file_id: The file_id of the file to compare
 
577
 
        :param old_path: Path of the file in the old tree
 
578
 
        :param new_path: Path of the file in the new tree
 
579
 
        :param old_kind: Old file-kind of the file
 
580
 
        :param new_kind: New file-kind of the file
 
582
 
        if None in (old_kind, new_kind):
 
583
 
            return DiffPath.CANNOT_DIFF
 
584
 
        result = DiffPath._diff_many(self.differs, file_id, old_path,
 
585
 
                                       new_path, old_kind, None)
 
586
 
        if result is DiffPath.CANNOT_DIFF:
 
588
 
        return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
 
592
 
class DiffDirectory(DiffPath):
 
594
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
595
 
        """Perform comparison between two directories.  (dummy)
 
598
 
        if 'directory' not in (old_kind, new_kind):
 
599
 
            return self.CANNOT_DIFF
 
600
 
        if old_kind not in ('directory', None):
 
601
 
            return self.CANNOT_DIFF
 
602
 
        if new_kind not in ('directory', None):
 
603
 
            return self.CANNOT_DIFF
 
607
 
class DiffSymlink(DiffPath):
 
609
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
610
 
        """Perform comparison between two symlinks
 
612
 
        :param file_id: The file_id of the file to compare
 
613
 
        :param old_path: Path of the file in the old tree
 
614
 
        :param new_path: Path of the file in the new tree
 
615
 
        :param old_kind: Old file-kind of the file
 
616
 
        :param new_kind: New file-kind of the file
 
618
 
        if 'symlink' not in (old_kind, new_kind):
 
619
 
            return self.CANNOT_DIFF
 
620
 
        if old_kind == 'symlink':
 
621
 
            old_target = self.old_tree.get_symlink_target(file_id)
 
622
 
        elif old_kind is None:
 
625
 
            return self.CANNOT_DIFF
 
626
 
        if new_kind == 'symlink':
 
627
 
            new_target = self.new_tree.get_symlink_target(file_id)
 
628
 
        elif new_kind is None:
 
631
 
            return self.CANNOT_DIFF
 
632
 
        return self.diff_symlink(old_target, new_target)
 
634
 
    def diff_symlink(self, old_target, new_target):
 
635
 
        if old_target is None:
 
636
 
            self.to_file.write('=== target is %r\n' % new_target)
 
637
 
        elif new_target is None:
 
638
 
            self.to_file.write('=== target was %r\n' % old_target)
 
640
 
            self.to_file.write('=== target changed %r => %r\n' %
 
641
 
                              (old_target, new_target))
 
645
 
class DiffText(DiffPath):
 
 
413
def _show_diff_trees(old_tree, new_tree, to_file,
 
 
414
                     specific_files, external_diff_options, 
 
 
415
                     old_label='a/', new_label='b/', extra_trees=None):
 
647
417
    # GNU Patch uses the epoch date to detect files that are being added
 
648
418
    # or removed in a diff.
 
649
419
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
 
651
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
 
652
 
                 old_label='', new_label='', text_differ=internal_diff):
 
653
 
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
 
654
 
        self.text_differ = text_differ
 
655
 
        self.old_label = old_label
 
656
 
        self.new_label = new_label
 
657
 
        self.path_encoding = path_encoding
 
659
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
660
 
        """Compare two files in unified diff format
 
662
 
        :param file_id: The file_id of the file to compare
 
663
 
        :param old_path: Path of the file in the old tree
 
664
 
        :param new_path: Path of the file in the new tree
 
665
 
        :param old_kind: Old file-kind of the file
 
666
 
        :param new_kind: New file-kind of the file
 
668
 
        if 'file' not in (old_kind, new_kind):
 
669
 
            return self.CANNOT_DIFF
 
670
 
        from_file_id = to_file_id = file_id
 
671
 
        if old_kind == 'file':
 
672
 
            old_date = _patch_header_date(self.old_tree, file_id, old_path)
 
673
 
        elif old_kind is None:
 
674
 
            old_date = self.EPOCH_DATE
 
677
 
            return self.CANNOT_DIFF
 
678
 
        if new_kind == 'file':
 
679
 
            new_date = _patch_header_date(self.new_tree, file_id, new_path)
 
680
 
        elif new_kind is None:
 
681
 
            new_date = self.EPOCH_DATE
 
684
 
            return self.CANNOT_DIFF
 
685
 
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
 
686
 
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
 
687
 
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
 
690
 
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
 
691
 
        from_path=None, to_path=None):
 
692
 
        """Diff the content of given files in two trees
 
694
 
        :param from_file_id: The id of the file in the from tree.  If None,
 
695
 
            the file is not present in the from tree.
 
696
 
        :param to_file_id: The id of the file in the to tree.  This may refer
 
697
 
            to a different file from from_file_id.  If None,
 
698
 
            the file is not present in the to tree.
 
699
 
        :param from_path: The path in the from tree or None if unknown.
 
700
 
        :param to_path: The path in the to tree or None if unknown.
 
702
 
        def _get_text(tree, file_id, path):
 
703
 
            if file_id is not None:
 
704
 
                return tree.get_file_lines(file_id, path)
 
708
 
            from_text = _get_text(self.old_tree, from_file_id, from_path)
 
709
 
            to_text = _get_text(self.new_tree, to_file_id, to_path)
 
710
 
            self.text_differ(from_label, from_text, to_label, to_text,
 
711
 
                             self.to_file, path_encoding=self.path_encoding)
 
712
 
        except errors.BinaryFile:
 
714
 
                  ("Binary files %s and %s differ\n" %
 
715
 
                  (from_label, to_label)).encode(self.path_encoding,'replace'))
 
719
 
class DiffFromTool(DiffPath):
 
721
 
    def __init__(self, command_template, old_tree, new_tree, to_file,
 
722
 
                 path_encoding='utf-8'):
 
723
 
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
 
724
 
        self.command_template = command_template
 
725
 
        self._root = osutils.mkdtemp(prefix='bzr-diff-')
 
728
 
    def from_string(klass, command_string, old_tree, new_tree, to_file,
 
729
 
                    path_encoding='utf-8'):
 
730
 
        command_template = cmdline.split(command_string)
 
731
 
        if '@' not in command_string:
 
732
 
            command_template.extend(['@old_path', '@new_path'])
 
733
 
        return klass(command_template, old_tree, new_tree, to_file,
 
737
 
    def make_from_diff_tree(klass, command_string, external_diff_options=None):
 
738
 
        def from_diff_tree(diff_tree):
 
739
 
            full_command_string = [command_string]
 
740
 
            if external_diff_options is not None:
 
741
 
                full_command_string += ' ' + external_diff_options
 
742
 
            return klass.from_string(full_command_string, diff_tree.old_tree,
 
743
 
                                     diff_tree.new_tree, diff_tree.to_file)
 
744
 
        return from_diff_tree
 
746
 
    def _get_command(self, old_path, new_path):
 
747
 
        my_map = {'old_path': old_path, 'new_path': new_path}
 
748
 
        return [AtTemplate(t).substitute(my_map) for t in
 
749
 
                self.command_template]
 
751
 
    def _execute(self, old_path, new_path):
 
752
 
        command = self._get_command(old_path, new_path)
 
754
 
            proc = subprocess.Popen(command, stdout=subprocess.PIPE,
 
757
 
            if e.errno == errno.ENOENT:
 
758
 
                raise errors.ExecutableMissing(command[0])
 
761
 
        self.to_file.write(proc.stdout.read())
 
764
 
    def _try_symlink_root(self, tree, prefix):
 
765
 
        if (getattr(tree, 'abspath', None) is None
 
766
 
            or not osutils.host_os_dereferences_symlinks()):
 
769
 
            os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
 
771
 
            if e.errno != errno.EEXIST:
 
775
 
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
 
777
 
        if not force_temp and isinstance(tree, WorkingTree):
 
778
 
            return tree.abspath(tree.id2path(file_id))
 
780
 
        full_path = osutils.pathjoin(self._root, prefix, relpath)
 
781
 
        if not force_temp and self._try_symlink_root(tree, prefix):
 
783
 
        parent_dir = osutils.dirname(full_path)
 
785
 
            os.makedirs(parent_dir)
 
787
 
            if e.errno != errno.EEXIST:
 
789
 
        source = tree.get_file(file_id, relpath)
 
791
 
            target = open(full_path, 'wb')
 
793
 
                osutils.pumpfile(source, target)
 
799
 
            mtime = tree.get_file_mtime(file_id)
 
800
 
        except errors.FileTimestampUnavailable:
 
803
 
            os.utime(full_path, (mtime, mtime))
 
805
 
            osutils.make_readonly(full_path)
 
808
 
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
 
809
 
                       allow_write_new=False):
 
810
 
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
 
811
 
                                         old_path, force_temp)
 
812
 
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
 
813
 
                                         new_path, force_temp,
 
814
 
                                         allow_write=allow_write_new)
 
815
 
        return old_disk_path, new_disk_path
 
819
 
            osutils.rmtree(self._root)
 
821
 
            if e.errno != errno.ENOENT:
 
822
 
                mutter("The temporary directory \"%s\" was not "
 
823
 
                        "cleanly removed: %s." % (self._root, e))
 
825
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
826
 
        if (old_kind, new_kind) != ('file', 'file'):
 
827
 
            return DiffPath.CANNOT_DIFF
 
828
 
        (old_disk_path, new_disk_path) = self._prepare_files(
 
829
 
                                                file_id, old_path, new_path)
 
830
 
        self._execute(old_disk_path, new_disk_path)
 
832
 
    def edit_file(self, file_id):
 
833
 
        """Use this tool to edit a file.
 
835
 
        A temporary copy will be edited, and the new contents will be
 
838
 
        :param file_id: The id of the file to edit.
 
839
 
        :return: The new contents of the file.
 
841
 
        old_path = self.old_tree.id2path(file_id)
 
842
 
        new_path = self.new_tree.id2path(file_id)
 
843
 
        new_abs_path = self._prepare_files(file_id, old_path, new_path,
 
844
 
                                           allow_write_new=True,
 
846
 
        command = self._get_command(osutils.pathjoin('old', old_path),
 
847
 
                                    osutils.pathjoin('new', new_path))
 
848
 
        subprocess.call(command, cwd=self._root)
 
849
 
        new_file = open(new_abs_path, 'r')
 
851
 
            return new_file.read()
 
856
 
class DiffTree(object):
 
857
 
    """Provides textual representations of the difference between two trees.
 
859
 
    A DiffTree examines two trees and where a file-id has altered
 
860
 
    between them, generates a textual representation of the difference.
 
861
 
    DiffTree uses a sequence of DiffPath objects which are each
 
862
 
    given the opportunity to handle a given altered fileid. The list
 
863
 
    of DiffPath objects can be extended globally by appending to
 
864
 
    DiffTree.diff_factories, or for a specific diff operation by
 
865
 
    supplying the extra_factories option to the appropriate method.
 
 
421
    # TODO: Generation of pseudo-diffs for added/deleted files could
 
 
422
    # be usefully made into a much faster special case.
 
 
424
    if external_diff_options:
 
 
425
        assert isinstance(external_diff_options, basestring)
 
 
426
        opts = external_diff_options.split()
 
 
427
        def diff_file(olab, olines, nlab, nlines, to_file):
 
 
428
            external_diff(olab, olines, nlab, nlines, to_file, opts)
 
 
430
        diff_file = internal_diff
 
 
432
    delta = new_tree.changes_from(old_tree,
 
 
433
        specific_files=specific_files,
 
 
434
        extra_trees=extra_trees, require_versioned=True)
 
 
437
    for path, file_id, kind in delta.removed:
 
 
439
        print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
 
 
440
        old_name = '%s%s\t%s' % (old_label, path,
 
 
441
                                 _patch_header_date(old_tree, file_id, path))
 
 
442
        new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
 
 
443
        old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
 
 
444
                                         new_name, None, None, to_file)
 
 
445
    for path, file_id, kind in delta.added:
 
 
447
        print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
 
 
448
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
 
 
449
        new_name = '%s%s\t%s' % (new_label, path,
 
 
450
                                 _patch_header_date(new_tree, file_id, path))
 
 
451
        new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
 
 
452
                                         old_name, None, None, to_file, 
 
 
454
    for (old_path, new_path, file_id, kind,
 
 
455
         text_modified, meta_modified) in delta.renamed:
 
 
457
        prop_str = get_prop_change(meta_modified)
 
 
458
        print >>to_file, '=== renamed %s %r => %r%s' % (
 
 
459
                    kind, old_path.encode('utf8'),
 
 
460
                    new_path.encode('utf8'), prop_str)
 
 
461
        old_name = '%s%s\t%s' % (old_label, old_path,
 
 
462
                                 _patch_header_date(old_tree, file_id,
 
 
464
        new_name = '%s%s\t%s' % (new_label, new_path,
 
 
465
                                 _patch_header_date(new_tree, file_id,
 
 
467
        _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
 
 
469
                                    text_modified, kind, to_file, diff_file)
 
 
470
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
 
 
472
        prop_str = get_prop_change(meta_modified)
 
 
473
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
 
 
474
        old_name = '%s%s\t%s' % (old_label, path,
 
 
475
                                 _patch_header_date(old_tree, file_id, path))
 
 
476
        new_name = '%s%s\t%s' % (new_label, path,
 
 
477
                                 _patch_header_date(new_tree, file_id, path))
 
 
479
            _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
 
 
481
                                        True, kind, to_file, diff_file)
 
 
486
def _patch_header_date(tree, file_id, path):
 
 
487
    """Returns a timestamp suitable for use in a patch header."""
 
 
488
    return timestamp.format_patch_date(tree.get_file_mtime(file_id, path))
 
 
491
def _raise_if_nonexistent(paths, old_tree, new_tree):
 
 
492
    """Complain if paths are not in either inventory or tree.
 
 
494
    It's OK with the files exist in either tree's inventory, or 
 
 
495
    if they exist in the tree but are not versioned.
 
 
497
    This can be used by operations such as bzr status that can accept
 
 
498
    unknown or ignored files.
 
868
 
    # list of factories that can provide instances of DiffPath objects
 
869
 
    # may be extended by plugins.
 
870
 
    diff_factories = [DiffSymlink.from_diff_tree,
 
871
 
                      DiffDirectory.from_diff_tree]
 
873
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
 
874
 
                 diff_text=None, extra_factories=None):
 
877
 
        :param old_tree: Tree to show as old in the comparison
 
878
 
        :param new_tree: Tree to show as new in the comparison
 
879
 
        :param to_file: File to write comparision to
 
880
 
        :param path_encoding: Character encoding to write paths in
 
881
 
        :param diff_text: DiffPath-type object to use as a last resort for
 
883
 
        :param extra_factories: Factories of DiffPaths to try before any other
 
885
 
        if diff_text is None:
 
886
 
            diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
 
887
 
                                 '', '',  internal_diff)
 
888
 
        self.old_tree = old_tree
 
889
 
        self.new_tree = new_tree
 
890
 
        self.to_file = to_file
 
891
 
        self.path_encoding = path_encoding
 
893
 
        if extra_factories is not None:
 
894
 
            self.differs.extend(f(self) for f in extra_factories)
 
895
 
        self.differs.extend(f(self) for f in self.diff_factories)
 
896
 
        self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
 
899
 
    def from_trees_options(klass, old_tree, new_tree, to_file,
 
900
 
                           path_encoding, external_diff_options, old_label,
 
902
 
        """Factory for producing a DiffTree.
 
904
 
        Designed to accept options used by show_diff_trees.
 
905
 
        :param old_tree: The tree to show as old in the comparison
 
906
 
        :param new_tree: The tree to show as new in the comparison
 
907
 
        :param to_file: File to write comparisons to
 
908
 
        :param path_encoding: Character encoding to use for writing paths
 
909
 
        :param external_diff_options: If supplied, use the installed diff
 
910
 
            binary to perform file comparison, using supplied options.
 
911
 
        :param old_label: Prefix to use for old file labels
 
912
 
        :param new_label: Prefix to use for new file labels
 
913
 
        :param using: Commandline to use to invoke an external diff tool
 
915
 
        if using is not None:
 
916
 
            extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
 
919
 
        if external_diff_options:
 
920
 
            opts = external_diff_options.split()
 
921
 
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None):
 
922
 
                """:param path_encoding: not used but required
 
923
 
                        to match the signature of internal_diff.
 
925
 
                external_diff(olab, olines, nlab, nlines, to_file, opts)
 
927
 
            diff_file = internal_diff
 
928
 
        diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
 
929
 
                             old_label, new_label, diff_file)
 
930
 
        return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
 
933
 
    def show_diff(self, specific_files, extra_trees=None):
 
934
 
        """Write tree diff to self.to_file
 
936
 
        :param specific_files: the specific files to compare (recursive)
 
937
 
        :param extra_trees: extra trees to use for mapping paths to file_ids
 
940
 
            return self._show_diff(specific_files, extra_trees)
 
942
 
            for differ in self.differs:
 
945
 
    def _show_diff(self, specific_files, extra_trees):
 
946
 
        # TODO: Generation of pseudo-diffs for added/deleted files could
 
947
 
        # be usefully made into a much faster special case.
 
948
 
        iterator = self.new_tree.iter_changes(self.old_tree,
 
949
 
                                               specific_files=specific_files,
 
950
 
                                               extra_trees=extra_trees,
 
951
 
                                               require_versioned=True)
 
953
 
        def changes_key(change):
 
954
 
            old_path, new_path = change[1]
 
959
 
        def get_encoded_path(path):
 
961
 
                return path.encode(self.path_encoding, "replace")
 
962
 
        for (file_id, paths, changed_content, versioned, parent, name, kind,
 
963
 
             executable) in sorted(iterator, key=changes_key):
 
964
 
            # The root does not get diffed, and items with no known kind (that
 
965
 
            # is, missing) in both trees are skipped as well.
 
966
 
            if parent == (None, None) or kind == (None, None):
 
968
 
            oldpath, newpath = paths
 
969
 
            oldpath_encoded = get_encoded_path(paths[0])
 
970
 
            newpath_encoded = get_encoded_path(paths[1])
 
971
 
            old_present = (kind[0] is not None and versioned[0])
 
972
 
            new_present = (kind[1] is not None and versioned[1])
 
973
 
            renamed = (parent[0], name[0]) != (parent[1], name[1])
 
975
 
            properties_changed = []
 
976
 
            properties_changed.extend(get_executable_change(executable[0], executable[1]))
 
978
 
            if properties_changed:
 
979
 
                prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
 
983
 
            if (old_present, new_present) == (True, False):
 
984
 
                self.to_file.write("=== removed %s '%s'\n" %
 
985
 
                                   (kind[0], oldpath_encoded))
 
987
 
            elif (old_present, new_present) == (False, True):
 
988
 
                self.to_file.write("=== added %s '%s'\n" %
 
989
 
                                   (kind[1], newpath_encoded))
 
992
 
                self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
 
993
 
                    (kind[0], oldpath_encoded, newpath_encoded, prop_str))
 
995
 
                # if it was produced by iter_changes, it must be
 
996
 
                # modified *somehow*, either content or execute bit.
 
997
 
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
 
998
 
                                   newpath_encoded, prop_str))
 
1000
 
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
 
1006
 
    def diff(self, file_id, old_path, new_path):
 
1007
 
        """Perform a diff of a single file
 
1009
 
        :param file_id: file-id of the file
 
1010
 
        :param old_path: The path of the file in the old tree
 
1011
 
        :param new_path: The path of the file in the new tree
 
1014
 
            old_kind = self.old_tree.kind(file_id)
 
1015
 
        except (errors.NoSuchId, errors.NoSuchFile):
 
1018
 
            new_kind = self.new_tree.kind(file_id)
 
1019
 
        except (errors.NoSuchId, errors.NoSuchFile):
 
1021
 
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
 
1024
 
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
1025
 
        result = DiffPath._diff_many(self.differs, file_id, old_path,
 
1026
 
                                       new_path, old_kind, new_kind)
 
1027
 
        if result is DiffPath.CANNOT_DIFF:
 
1028
 
            error_path = new_path
 
1029
 
            if error_path is None:
 
1030
 
                error_path = old_path
 
1031
 
            raise errors.NoDiffFound(error_path)
 
1034
 
format_registry = Registry()
 
1035
 
format_registry.register('default', DiffTree)
 
 
500
    mutter("check paths: %r", paths)
 
 
503
    s = old_tree.filter_unversioned_files(paths)
 
 
504
    s = new_tree.filter_unversioned_files(s)
 
 
505
    s = [path for path in s if not new_tree.has_filename(path)]
 
 
507
        raise errors.PathsDoNotExist(sorted(s))
 
 
510
def get_prop_change(meta_modified):
 
 
512
        return " (properties changed)"
 
 
517
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
 
 
518
                                new_path, new_tree, text_modified,
 
 
519
                                kind, to_file, diff_file):
 
 
521
        new_entry = new_tree.inventory[file_id]
 
 
522
        old_tree.inventory[file_id].diff(diff_file,