~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2009-01-13 03:06:36 UTC
  • mfrom: (3932.2.3 1.11)
  • mto: This revision was merged to the branch mainline in revision 3937.
  • Revision ID: mbp@sourcefrog.net-20090113030636-dqx4t8yaaqgdvam5
MergeĀ 1.11rc1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd.
 
1
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from __future__ import absolute_import
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
16
 
19
17
import difflib
20
18
import os
21
19
import re
22
 
import string
 
20
import shutil
23
21
import sys
24
22
 
25
23
from bzrlib.lazy_import import lazy_import
27
25
import errno
28
26
import subprocess
29
27
import tempfile
 
28
import time
30
29
 
31
30
from bzrlib import (
32
 
    cleanup,
33
 
    cmdline,
34
 
    controldir,
 
31
    branch as _mod_branch,
 
32
    bzrdir,
 
33
    commands,
35
34
    errors,
36
35
    osutils,
37
36
    patiencediff,
38
37
    textfile,
39
38
    timestamp,
40
 
    views,
41
39
    )
42
 
 
43
 
from bzrlib.workingtree import WorkingTree
44
 
from bzrlib.i18n import gettext
45
40
""")
46
41
 
47
 
from bzrlib.registry import (
48
 
    Registry,
49
 
    )
50
 
from bzrlib.trace import mutter, note, warning
51
 
 
52
 
DEFAULT_CONTEXT_AMOUNT = 3
53
 
 
54
 
class AtTemplate(string.Template):
55
 
    """Templating class that uses @ instead of $."""
56
 
 
57
 
    delimiter = '@'
 
42
from bzrlib.symbol_versioning import (
 
43
        deprecated_function,
 
44
        one_three
 
45
        )
 
46
from bzrlib.trace import warning
58
47
 
59
48
 
60
49
# TODO: Rather than building a changeset object, we should probably
73
62
 
74
63
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
75
64
                  allow_binary=False, sequence_matcher=None,
76
 
                  path_encoding='utf8', context_lines=DEFAULT_CONTEXT_AMOUNT):
 
65
                  path_encoding='utf8'):
77
66
    # FIXME: difflib is wrong if there is no trailing newline.
78
67
    # The syntax used by patch seems to be "\ No newline at
79
68
    # end of file" following the last diff line from that
89
78
    # both sequences are empty.
90
79
    if not oldlines and not newlines:
91
80
        return
92
 
 
 
81
    
93
82
    if allow_binary is False:
94
83
        textfile.check_text_lines(oldlines)
95
84
        textfile.check_text_lines(newlines)
97
86
    if sequence_matcher is None:
98
87
        sequence_matcher = patiencediff.PatienceSequenceMatcher
99
88
    ud = patiencediff.unified_diff(oldlines, newlines,
100
 
                      fromfile=old_filename.encode(path_encoding, 'replace'),
101
 
                      tofile=new_filename.encode(path_encoding, 'replace'),
102
 
                      n=context_lines, sequencematcher=sequence_matcher)
 
89
                      fromfile=old_filename.encode(path_encoding),
 
90
                      tofile=new_filename.encode(path_encoding),
 
91
                      sequencematcher=sequence_matcher)
103
92
 
104
93
    ud = list(ud)
105
94
    if len(ud) == 0: # Identical contents, nothing to do
182
171
 
183
172
        if not diff_opts:
184
173
            diff_opts = []
185
 
        if sys.platform == 'win32':
186
 
            # Popen doesn't do the proper encoding for external commands
187
 
            # Since we are dealing with an ANSI api, use mbcs encoding
188
 
            old_filename = old_filename.encode('mbcs')
189
 
            new_filename = new_filename.encode('mbcs')
190
174
        diffcmd = ['diff',
191
175
                   '--label', old_filename,
192
176
                   old_abspath,
215
199
            break
216
200
        else:
217
201
            diffcmd.append('-u')
218
 
 
 
202
                  
219
203
        if diff_opts:
220
204
            diffcmd.extend(diff_opts)
221
205
 
222
206
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
223
207
        out,err = pipe.communicate()
224
208
        rc = pipe.returncode
225
 
 
 
209
        
226
210
        # internal_diff() adds a trailing newline, add one here for consistency
227
211
        out += '\n'
228
212
        if rc == 2:
263
247
                msg = 'signal %d' % (-rc)
264
248
            else:
265
249
                msg = 'exit code %d' % rc
266
 
 
267
 
            raise errors.BzrError('external diff failed with %s; command: %r'
 
250
                
 
251
            raise errors.BzrError('external diff failed with %s; command: %r' 
268
252
                                  % (rc, diffcmd))
269
253
 
270
254
 
288
272
                        new_abspath, e)
289
273
 
290
274
 
291
 
def get_trees_and_branches_to_diff_locked(
292
 
    path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
 
275
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
293
276
    """Get the trees and specific files to diff given a list of paths.
294
277
 
295
278
    This method works out the trees to be diff'ed and the files of
306
289
    :param new_url:
307
290
        The url of the new branch or tree. If None, the tree to use is
308
291
        taken from the first path, if any, or the current working tree.
309
 
    :param add_cleanup:
310
 
        a callable like Command.add_cleanup.  get_trees_and_branches_to_diff
311
 
        will register cleanups that must be run to unlock the trees, etc.
312
 
    :param apply_view:
313
 
        if True and a view is set, apply the view or check that the paths
314
 
        are within it
315
292
    :returns:
316
 
        a tuple of (old_tree, new_tree, old_branch, new_branch,
317
 
        specific_files, extra_trees) where extra_trees is a sequence of
318
 
        additional trees to search in for file-ids.  The trees and branches
319
 
        will be read-locked until the cleanups registered via the add_cleanup
320
 
        param are run.
 
293
        a tuple of (old_tree, new_tree, specific_files, extra_trees) where
 
294
        extra_trees is a sequence of additional trees to search in for
 
295
        file-ids.
321
296
    """
322
297
    # Get the old and new revision specs
323
298
    old_revision_spec = None
346
321
        default_location = path_list[0]
347
322
        other_paths = path_list[1:]
348
323
 
349
 
    def lock_tree_or_branch(wt, br):
350
 
        if wt is not None:
351
 
            wt.lock_read()
352
 
            add_cleanup(wt.unlock)
353
 
        elif br is not None:
354
 
            br.lock_read()
355
 
            add_cleanup(br.unlock)
356
 
 
357
324
    # Get the old location
358
325
    specific_files = []
359
326
    if old_url is None:
360
327
        old_url = default_location
361
328
    working_tree, branch, relpath = \
362
 
        controldir.ControlDir.open_containing_tree_or_branch(old_url)
363
 
    lock_tree_or_branch(working_tree, branch)
 
329
        bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
364
330
    if consider_relpath and relpath != '':
365
 
        if working_tree is not None and apply_view:
366
 
            views.check_path_in_view(working_tree, relpath)
367
331
        specific_files.append(relpath)
368
332
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
369
 
    old_branch = branch
370
333
 
371
334
    # Get the new location
372
335
    if new_url is None:
373
336
        new_url = default_location
374
337
    if new_url != old_url:
375
338
        working_tree, branch, relpath = \
376
 
            controldir.ControlDir.open_containing_tree_or_branch(new_url)
377
 
        lock_tree_or_branch(working_tree, branch)
 
339
            bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
378
340
        if consider_relpath and relpath != '':
379
 
            if working_tree is not None and apply_view:
380
 
                views.check_path_in_view(working_tree, relpath)
381
341
            specific_files.append(relpath)
382
342
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
383
343
        basis_is_default=working_tree is None)
384
 
    new_branch = branch
385
344
 
386
345
    # Get the specific files (all files is None, no files is [])
387
346
    if make_paths_wt_relative and working_tree is not None:
388
 
        other_paths = working_tree.safe_relpath_files(
389
 
            other_paths,
390
 
            apply_view=apply_view)
 
347
        other_paths = _relative_paths_in_tree(working_tree, other_paths)
391
348
    specific_files.extend(other_paths)
392
349
    if len(specific_files) == 0:
393
350
        specific_files = None
394
 
        if (working_tree is not None and working_tree.supports_views()
395
 
            and apply_view):
396
 
            view_files = working_tree.views.lookup_view()
397
 
            if view_files:
398
 
                specific_files = view_files
399
 
                view_str = views.view_display_str(view_files)
400
 
                note(gettext("*** Ignoring files outside view. View is %s") % view_str)
401
351
 
402
352
    # Get extra trees that ought to be searched for file-ids
403
353
    extra_trees = None
404
354
    if working_tree is not None and working_tree not in (old_tree, new_tree):
405
355
        extra_trees = (working_tree,)
406
 
    return (old_tree, new_tree, old_branch, new_branch,
407
 
            specific_files, extra_trees)
 
356
    return old_tree, new_tree, specific_files, extra_trees
408
357
 
409
358
 
410
359
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
421
370
    return spec.as_tree(branch)
422
371
 
423
372
 
 
373
def _relative_paths_in_tree(tree, paths):
 
374
    """Get the relative paths within a working tree.
 
375
 
 
376
    Each path may be either an absolute path or a path relative to the
 
377
    current working directory.
 
378
    """
 
379
    result = []
 
380
    for filename in paths:
 
381
        try:
 
382
            result.append(tree.relpath(osutils.dereference_path(filename)))
 
383
        except errors.PathNotChild:
 
384
            raise errors.BzrCommandError("Files are in different branches")
 
385
    return result
 
386
 
 
387
 
424
388
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
425
389
                    external_diff_options=None,
426
390
                    old_label='a/', new_label='b/',
427
391
                    extra_trees=None,
428
392
                    path_encoding='utf8',
429
 
                    using=None,
430
 
                    format_cls=None,
431
 
                    context=DEFAULT_CONTEXT_AMOUNT):
 
393
                    using=None):
432
394
    """Show in text form the changes from one tree to another.
433
395
 
434
 
    :param to_file: The output stream.
435
 
    :param specific_files: Include only changes to these files - None for all
436
 
        changes.
437
 
    :param external_diff_options: If set, use an external GNU diff and pass 
438
 
        these options.
439
 
    :param extra_trees: If set, more Trees to use for looking up file ids
440
 
    :param path_encoding: If set, the path will be encoded as specified, 
441
 
        otherwise is supposed to be utf8
442
 
    :param format_cls: Formatter class (DiffTree subclass)
 
396
    to_file
 
397
        The output stream.
 
398
 
 
399
    specific_files
 
400
        Include only changes to these files - None for all changes.
 
401
 
 
402
    external_diff_options
 
403
        If set, use an external GNU diff and pass these options.
 
404
 
 
405
    extra_trees
 
406
        If set, more Trees to use for looking up file ids
 
407
 
 
408
    path_encoding
 
409
        If set, the path will be encoded as specified, otherwise is supposed
 
410
        to be utf8
443
411
    """
444
 
    if context is None:
445
 
        context = DEFAULT_CONTEXT_AMOUNT
446
 
    if format_cls is None:
447
 
        format_cls = DiffTree
448
412
    old_tree.lock_read()
449
413
    try:
450
414
        if extra_trees is not None:
452
416
                tree.lock_read()
453
417
        new_tree.lock_read()
454
418
        try:
455
 
            differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
456
 
                                                   path_encoding,
457
 
                                                   external_diff_options,
458
 
                                                   old_label, new_label, using,
459
 
                                                   context_lines=context)
 
419
            differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
 
420
                                                 path_encoding,
 
421
                                                 external_diff_options,
 
422
                                                 old_label, new_label, using)
460
423
            return differ.show_diff(specific_files, extra_trees)
461
424
        finally:
462
425
            new_tree.unlock()
469
432
 
470
433
def _patch_header_date(tree, file_id, path):
471
434
    """Returns a timestamp suitable for use in a patch header."""
472
 
    try:
473
 
        mtime = tree.get_file_mtime(file_id, path)
474
 
    except errors.FileTimestampUnavailable:
475
 
        mtime = 0
 
435
    mtime = tree.get_file_mtime(file_id, path)
476
436
    return timestamp.format_patch_date(mtime)
477
437
 
478
438
 
 
439
@deprecated_function(one_three)
 
440
def get_prop_change(meta_modified):
 
441
    if meta_modified:
 
442
        return " (properties changed)"
 
443
    else:
 
444
        return  ""
 
445
 
479
446
def get_executable_change(old_is_x, new_is_x):
480
447
    descr = { True:"+x", False:"-x", None:"??" }
481
448
    if old_is_x != new_is_x:
620
587
    # or removed in a diff.
621
588
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
622
589
 
623
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8', 
624
 
                 old_label='', new_label='', text_differ=internal_diff, 
625
 
                 context_lines=DEFAULT_CONTEXT_AMOUNT):
 
590
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
 
591
                 old_label='', new_label='', text_differ=internal_diff):
626
592
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
627
593
        self.text_differ = text_differ
628
594
        self.old_label = old_label
629
595
        self.new_label = new_label
630
596
        self.path_encoding = path_encoding
631
 
        self.context_lines = context_lines
632
597
 
633
598
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
634
599
        """Compare two files in unified diff format
658
623
            return self.CANNOT_DIFF
659
624
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
660
625
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
661
 
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
662
 
            old_path, new_path)
 
626
        return self.diff_text(from_file_id, to_file_id, from_label, to_label)
663
627
 
664
 
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
665
 
        from_path=None, to_path=None):
 
628
    def diff_text(self, from_file_id, to_file_id, from_label, to_label):
666
629
        """Diff the content of given files in two trees
667
630
 
668
631
        :param from_file_id: The id of the file in the from tree.  If None,
670
633
        :param to_file_id: The id of the file in the to tree.  This may refer
671
634
            to a different file from from_file_id.  If None,
672
635
            the file is not present in the to tree.
673
 
        :param from_path: The path in the from tree or None if unknown.
674
 
        :param to_path: The path in the to tree or None if unknown.
675
636
        """
676
 
        def _get_text(tree, file_id, path):
 
637
        def _get_text(tree, file_id):
677
638
            if file_id is not None:
678
 
                return tree.get_file_lines(file_id, path)
 
639
                return tree.get_file(file_id).readlines()
679
640
            else:
680
641
                return []
681
642
        try:
682
 
            from_text = _get_text(self.old_tree, from_file_id, from_path)
683
 
            to_text = _get_text(self.new_tree, to_file_id, to_path)
 
643
            from_text = _get_text(self.old_tree, from_file_id)
 
644
            to_text = _get_text(self.new_tree, to_file_id)
684
645
            self.text_differ(from_label, from_text, to_label, to_text,
685
 
                             self.to_file, path_encoding=self.path_encoding,
686
 
                             context_lines=self.context_lines)
 
646
                             self.to_file)
687
647
        except errors.BinaryFile:
688
648
            self.to_file.write(
689
649
                  ("Binary files %s and %s differ\n" %
690
 
                  (from_label, to_label)).encode(self.path_encoding,'replace'))
 
650
                  (from_label, to_label)).encode(self.path_encoding))
691
651
        return self.CHANGED
692
652
 
693
653
 
702
662
    @classmethod
703
663
    def from_string(klass, command_string, old_tree, new_tree, to_file,
704
664
                    path_encoding='utf-8'):
705
 
        command_template = cmdline.split(command_string)
706
 
        if '@' not in command_string:
707
 
            command_template.extend(['@old_path', '@new_path'])
 
665
        command_template = commands.shlex_split_unicode(command_string)
 
666
        command_template.extend(['%(old_path)s', '%(new_path)s'])
708
667
        return klass(command_template, old_tree, new_tree, to_file,
709
668
                     path_encoding)
710
669
 
711
670
    @classmethod
712
 
    def make_from_diff_tree(klass, command_string, external_diff_options=None):
 
671
    def make_from_diff_tree(klass, command_string):
713
672
        def from_diff_tree(diff_tree):
714
 
            full_command_string = [command_string]
715
 
            if external_diff_options is not None:
716
 
                full_command_string += ' ' + external_diff_options
717
 
            return klass.from_string(full_command_string, diff_tree.old_tree,
 
673
            return klass.from_string(command_string, diff_tree.old_tree,
718
674
                                     diff_tree.new_tree, diff_tree.to_file)
719
675
        return from_diff_tree
720
676
 
721
677
    def _get_command(self, old_path, new_path):
722
678
        my_map = {'old_path': old_path, 'new_path': new_path}
723
 
        command = [AtTemplate(t).substitute(my_map) for t in
724
 
                   self.command_template]
725
 
        if sys.platform == 'win32': # Popen doesn't accept unicode on win32
726
 
            command_encoded = []
727
 
            for c in command:
728
 
                if isinstance(c, unicode):
729
 
                    command_encoded.append(c.encode('mbcs'))
730
 
                else:
731
 
                    command_encoded.append(c)
732
 
            return command_encoded
733
 
        else:
734
 
            return command
 
679
        return [t % my_map for t in self.command_template]
735
680
 
736
681
    def _execute(self, old_path, new_path):
737
682
        command = self._get_command(old_path, new_path)
757
702
                raise
758
703
        return True
759
704
 
760
 
    @staticmethod
761
 
    def _fenc():
762
 
        """Returns safe encoding for passing file path to diff tool"""
763
 
        if sys.platform == 'win32':
764
 
            return 'mbcs'
765
 
        else:
766
 
            # Don't fallback to 'utf-8' because subprocess may not be able to
767
 
            # handle utf-8 correctly when locale is not utf-8.
768
 
            return sys.getfilesystemencoding() or 'ascii'
769
 
 
770
 
    def _is_safepath(self, path):
771
 
        """Return true if `path` may be able to pass to subprocess."""
772
 
        fenc = self._fenc()
773
 
        try:
774
 
            return path == path.encode(fenc).decode(fenc)
775
 
        except UnicodeError:
776
 
            return False
777
 
 
778
 
    def _safe_filename(self, prefix, relpath):
779
 
        """Replace unsafe character in `relpath` then join `self._root`,
780
 
        `prefix` and `relpath`."""
781
 
        fenc = self._fenc()
782
 
        # encoded_str.replace('?', '_') may break multibyte char.
783
 
        # So we should encode, decode, then replace(u'?', u'_')
784
 
        relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
785
 
        relpath_tmp = relpath_tmp.replace(u'?', u'_')
786
 
        return osutils.pathjoin(self._root, prefix, relpath_tmp)
787
 
 
788
 
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
789
 
                    allow_write=False):
790
 
        if not force_temp and isinstance(tree, WorkingTree):
791
 
            full_path = tree.abspath(tree.id2path(file_id))
792
 
            if self._is_safepath(full_path):
793
 
                return full_path
794
 
 
795
 
        full_path = self._safe_filename(prefix, relpath)
796
 
        if not force_temp and self._try_symlink_root(tree, prefix):
 
705
    def _write_file(self, file_id, tree, prefix, relpath):
 
706
        full_path = osutils.pathjoin(self._root, prefix, relpath)
 
707
        if self._try_symlink_root(tree, prefix):
797
708
            return full_path
798
709
        parent_dir = osutils.dirname(full_path)
799
710
        try:
810
721
                target.close()
811
722
        finally:
812
723
            source.close()
813
 
        try:
814
 
            mtime = tree.get_file_mtime(file_id)
815
 
        except errors.FileTimestampUnavailable:
816
 
            pass
817
 
        else:
818
 
            os.utime(full_path, (mtime, mtime))
819
 
        if not allow_write:
820
 
            osutils.make_readonly(full_path)
 
724
        osutils.make_readonly(full_path)
 
725
        mtime = tree.get_file_mtime(file_id)
 
726
        os.utime(full_path, (mtime, mtime))
821
727
        return full_path
822
728
 
823
 
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
824
 
                       allow_write_new=False):
 
729
    def _prepare_files(self, file_id, old_path, new_path):
825
730
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
826
 
                                         old_path, force_temp)
 
731
                                         old_path)
827
732
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
828
 
                                         new_path, force_temp,
829
 
                                         allow_write=allow_write_new)
 
733
                                         new_path)
830
734
        return old_disk_path, new_disk_path
831
735
 
832
736
    def finish(self):
833
 
        try:
834
 
            osutils.rmtree(self._root)
835
 
        except OSError, e:
836
 
            if e.errno != errno.ENOENT:
837
 
                mutter("The temporary directory \"%s\" was not "
838
 
                        "cleanly removed: %s." % (self._root, e))
 
737
        osutils.rmtree(self._root)
839
738
 
840
739
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
841
740
        if (old_kind, new_kind) != ('file', 'file'):
842
741
            return DiffPath.CANNOT_DIFF
843
 
        (old_disk_path, new_disk_path) = self._prepare_files(
844
 
                                                file_id, old_path, new_path)
845
 
        self._execute(old_disk_path, new_disk_path)
846
 
 
847
 
    def edit_file(self, file_id):
848
 
        """Use this tool to edit a file.
849
 
 
850
 
        A temporary copy will be edited, and the new contents will be
851
 
        returned.
852
 
 
853
 
        :param file_id: The id of the file to edit.
854
 
        :return: The new contents of the file.
855
 
        """
856
 
        old_path = self.old_tree.id2path(file_id)
857
 
        new_path = self.new_tree.id2path(file_id)
858
 
        old_abs_path, new_abs_path = self._prepare_files(
859
 
                                            file_id, old_path, new_path,
860
 
                                            allow_write_new=True,
861
 
                                            force_temp=True)
862
 
        command = self._get_command(old_abs_path, new_abs_path)
863
 
        subprocess.call(command, cwd=self._root)
864
 
        new_file = open(new_abs_path, 'rb')
865
 
        try:
866
 
            return new_file.read()
867
 
        finally:
868
 
            new_file.close()
 
742
        self._prepare_files(file_id, old_path, new_path)
 
743
        self._execute(osutils.pathjoin('old', old_path),
 
744
                      osutils.pathjoin('new', new_path))
869
745
 
870
746
 
871
747
class DiffTree(object):
913
789
    @classmethod
914
790
    def from_trees_options(klass, old_tree, new_tree, to_file,
915
791
                           path_encoding, external_diff_options, old_label,
916
 
                           new_label, using, context_lines):
 
792
                           new_label, using):
917
793
        """Factory for producing a DiffTree.
918
794
 
919
795
        Designed to accept options used by show_diff_trees.
920
 
 
921
796
        :param old_tree: The tree to show as old in the comparison
922
797
        :param new_tree: The tree to show as new in the comparison
923
798
        :param to_file: File to write comparisons to
929
804
        :param using: Commandline to use to invoke an external diff tool
930
805
        """
931
806
        if using is not None:
932
 
            extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
 
807
            extra_factories = [DiffFromTool.make_from_diff_tree(using)]
933
808
        else:
934
809
            extra_factories = []
935
810
        if external_diff_options:
936
811
            opts = external_diff_options.split()
937
 
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None, context_lines=None):
938
 
                """:param path_encoding: not used but required
939
 
                        to match the signature of internal_diff.
940
 
                """
 
812
            def diff_file(olab, olines, nlab, nlines, to_file):
941
813
                external_diff(olab, olines, nlab, nlines, to_file, opts)
942
814
        else:
943
815
            diff_file = internal_diff
944
816
        diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
945
 
                             old_label, new_label, diff_file, context_lines=context_lines)
 
817
                             old_label, new_label, diff_file)
946
818
        return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
947
819
                     extra_factories)
948
820
 
949
821
    def show_diff(self, specific_files, extra_trees=None):
950
822
        """Write tree diff to self.to_file
951
823
 
952
 
        :param specific_files: the specific files to compare (recursive)
 
824
        :param sepecific_files: the specific files to compare (recursive)
953
825
        :param extra_trees: extra trees to use for mapping paths to file_ids
954
826
        """
955
827
        try:
1013
885
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
1014
886
                                   newpath_encoded, prop_str))
1015
887
            if changed_content:
1016
 
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
 
888
                self.diff(file_id, oldpath, newpath)
1017
889
                has_changes = 1
1018
890
            if renamed:
1019
891
                has_changes = 1
1034
906
            new_kind = self.new_tree.kind(file_id)
1035
907
        except (errors.NoSuchId, errors.NoSuchFile):
1036
908
            new_kind = None
1037
 
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
1038
 
 
1039
 
 
1040
 
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
 
909
 
1041
910
        result = DiffPath._diff_many(self.differs, file_id, old_path,
1042
911
                                       new_path, old_kind, new_kind)
1043
912
        if result is DiffPath.CANNOT_DIFF:
1045
914
            if error_path is None:
1046
915
                error_path = old_path
1047
916
            raise errors.NoDiffFound(error_path)
1048
 
 
1049
 
 
1050
 
format_registry = Registry()
1051
 
format_registry.register('default', DiffTree)