~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
 
1
# Copyright (C) 2005-2011 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import difflib
18
18
import os
19
19
import re
20
 
import shutil
 
20
import string
21
21
import sys
22
22
 
23
23
from bzrlib.lazy_import import lazy_import
25
25
import errno
26
26
import subprocess
27
27
import tempfile
28
 
import time
29
28
 
30
29
from bzrlib import (
31
 
    branch as _mod_branch,
32
30
    bzrdir,
33
 
    commands,
 
31
    cmdline,
 
32
    cleanup,
34
33
    errors,
35
34
    osutils,
36
35
    patiencediff,
37
36
    textfile,
38
37
    timestamp,
 
38
    views,
39
39
    )
 
40
 
 
41
from bzrlib.workingtree import WorkingTree
40
42
""")
41
43
 
42
 
from bzrlib.symbol_versioning import (
43
 
        deprecated_function,
44
 
        one_three
45
 
        )
46
 
from bzrlib.trace import warning
 
44
from bzrlib.registry import (
 
45
    Registry,
 
46
    )
 
47
from bzrlib.trace import mutter, note, warning
 
48
 
 
49
 
 
50
class AtTemplate(string.Template):
 
51
    """Templating class that uses @ instead of $."""
 
52
 
 
53
    delimiter = '@'
47
54
 
48
55
 
49
56
# TODO: Rather than building a changeset object, we should probably
78
85
    # both sequences are empty.
79
86
    if not oldlines and not newlines:
80
87
        return
81
 
    
 
88
 
82
89
    if allow_binary is False:
83
90
        textfile.check_text_lines(oldlines)
84
91
        textfile.check_text_lines(newlines)
86
93
    if sequence_matcher is None:
87
94
        sequence_matcher = patiencediff.PatienceSequenceMatcher
88
95
    ud = patiencediff.unified_diff(oldlines, newlines,
89
 
                      fromfile=old_filename.encode(path_encoding),
90
 
                      tofile=new_filename.encode(path_encoding),
 
96
                      fromfile=old_filename.encode(path_encoding, 'replace'),
 
97
                      tofile=new_filename.encode(path_encoding, 'replace'),
91
98
                      sequencematcher=sequence_matcher)
92
99
 
93
100
    ud = list(ud)
99
106
        ud[2] = ud[2].replace('-1,0', '-0,0')
100
107
    elif not newlines:
101
108
        ud[2] = ud[2].replace('+1,0', '+0,0')
102
 
    # work around for difflib emitting random spaces after the label
103
 
    ud[0] = ud[0][:-2] + '\n'
104
 
    ud[1] = ud[1][:-2] + '\n'
105
109
 
106
110
    for line in ud:
107
111
        to_file.write(line)
174
178
 
175
179
        if not diff_opts:
176
180
            diff_opts = []
 
181
        if sys.platform == 'win32':
 
182
            # Popen doesn't do the proper encoding for external commands
 
183
            # Since we are dealing with an ANSI api, use mbcs encoding
 
184
            old_filename = old_filename.encode('mbcs')
 
185
            new_filename = new_filename.encode('mbcs')
177
186
        diffcmd = ['diff',
178
187
                   '--label', old_filename,
179
188
                   old_abspath,
202
211
            break
203
212
        else:
204
213
            diffcmd.append('-u')
205
 
                  
 
214
 
206
215
        if diff_opts:
207
216
            diffcmd.extend(diff_opts)
208
217
 
209
218
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
210
219
        out,err = pipe.communicate()
211
220
        rc = pipe.returncode
212
 
        
 
221
 
213
222
        # internal_diff() adds a trailing newline, add one here for consistency
214
223
        out += '\n'
215
224
        if rc == 2:
250
259
                msg = 'signal %d' % (-rc)
251
260
            else:
252
261
                msg = 'exit code %d' % rc
253
 
                
254
 
            raise errors.BzrError('external diff failed with %s; command: %r' 
 
262
 
 
263
            raise errors.BzrError('external diff failed with %s; command: %r'
255
264
                                  % (rc, diffcmd))
256
265
 
257
266
 
275
284
                        new_abspath, e)
276
285
 
277
286
 
278
 
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
 
287
def get_trees_and_branches_to_diff_locked(
 
288
    path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
279
289
    """Get the trees and specific files to diff given a list of paths.
280
290
 
281
291
    This method works out the trees to be diff'ed and the files of
292
302
    :param new_url:
293
303
        The url of the new branch or tree. If None, the tree to use is
294
304
        taken from the first path, if any, or the current working tree.
 
305
    :param add_cleanup:
 
306
        a callable like Command.add_cleanup.  get_trees_and_branches_to_diff
 
307
        will register cleanups that must be run to unlock the trees, etc.
 
308
    :param apply_view:
 
309
        if True and a view is set, apply the view or check that the paths
 
310
        are within it
295
311
    :returns:
296
 
        a tuple of (old_tree, new_tree, specific_files, extra_trees) where
297
 
        extra_trees is a sequence of additional trees to search in for
298
 
        file-ids.
 
312
        a tuple of (old_tree, new_tree, old_branch, new_branch,
 
313
        specific_files, extra_trees) where extra_trees is a sequence of
 
314
        additional trees to search in for file-ids.  The trees and branches
 
315
        will be read-locked until the cleanups registered via the add_cleanup
 
316
        param are run.
299
317
    """
300
318
    # Get the old and new revision specs
301
319
    old_revision_spec = None
324
342
        default_location = path_list[0]
325
343
        other_paths = path_list[1:]
326
344
 
 
345
    def lock_tree_or_branch(wt, br):
 
346
        if wt is not None:
 
347
            wt.lock_read()
 
348
            add_cleanup(wt.unlock)
 
349
        elif br is not None:
 
350
            br.lock_read()
 
351
            add_cleanup(br.unlock)
 
352
 
327
353
    # Get the old location
328
354
    specific_files = []
329
355
    if old_url is None:
330
356
        old_url = default_location
331
357
    working_tree, branch, relpath = \
332
358
        bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
 
359
    lock_tree_or_branch(working_tree, branch)
333
360
    if consider_relpath and relpath != '':
 
361
        if working_tree is not None and apply_view:
 
362
            views.check_path_in_view(working_tree, relpath)
334
363
        specific_files.append(relpath)
335
364
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
 
365
    old_branch = branch
336
366
 
337
367
    # Get the new location
338
368
    if new_url is None:
340
370
    if new_url != old_url:
341
371
        working_tree, branch, relpath = \
342
372
            bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
 
373
        lock_tree_or_branch(working_tree, branch)
343
374
        if consider_relpath and relpath != '':
 
375
            if working_tree is not None and apply_view:
 
376
                views.check_path_in_view(working_tree, relpath)
344
377
            specific_files.append(relpath)
345
378
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
346
379
        basis_is_default=working_tree is None)
 
380
    new_branch = branch
347
381
 
348
382
    # Get the specific files (all files is None, no files is [])
349
383
    if make_paths_wt_relative and working_tree is not None:
350
 
        other_paths = _relative_paths_in_tree(working_tree, other_paths)
 
384
        other_paths = working_tree.safe_relpath_files(
 
385
            other_paths,
 
386
            apply_view=apply_view)
351
387
    specific_files.extend(other_paths)
352
388
    if len(specific_files) == 0:
353
389
        specific_files = None
 
390
        if (working_tree is not None and working_tree.supports_views()
 
391
            and apply_view):
 
392
            view_files = working_tree.views.lookup_view()
 
393
            if view_files:
 
394
                specific_files = view_files
 
395
                view_str = views.view_display_str(view_files)
 
396
                note("*** Ignoring files outside view. View is %s" % view_str)
354
397
 
355
398
    # Get extra trees that ought to be searched for file-ids
356
399
    extra_trees = None
357
400
    if working_tree is not None and working_tree not in (old_tree, new_tree):
358
401
        extra_trees = (working_tree,)
359
 
    return old_tree, new_tree, specific_files, extra_trees
 
402
    return (old_tree, new_tree, old_branch, new_branch,
 
403
            specific_files, extra_trees)
360
404
 
361
405
 
362
406
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
373
417
    return spec.as_tree(branch)
374
418
 
375
419
 
376
 
def _relative_paths_in_tree(tree, paths):
377
 
    """Get the relative paths within a working tree.
378
 
 
379
 
    Each path may be either an absolute path or a path relative to the
380
 
    current working directory.
381
 
    """
382
 
    result = []
383
 
    for filename in paths:
384
 
        try:
385
 
            result.append(tree.relpath(osutils.dereference_path(filename)))
386
 
        except errors.PathNotChild:
387
 
            raise errors.BzrCommandError("Files are in different branches")
388
 
    return result
389
 
 
390
 
 
391
420
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
392
421
                    external_diff_options=None,
393
422
                    old_label='a/', new_label='b/',
394
423
                    extra_trees=None,
395
424
                    path_encoding='utf8',
396
 
                    using=None):
 
425
                    using=None,
 
426
                    format_cls=None):
397
427
    """Show in text form the changes from one tree to another.
398
428
 
399
 
    to_file
400
 
        The output stream.
401
 
 
402
 
    specific_files
403
 
        Include only changes to these files - None for all changes.
404
 
 
405
 
    external_diff_options
406
 
        If set, use an external GNU diff and pass these options.
407
 
 
408
 
    extra_trees
409
 
        If set, more Trees to use for looking up file ids
410
 
 
411
 
    path_encoding
412
 
        If set, the path will be encoded as specified, otherwise is supposed
413
 
        to be utf8
 
429
    :param to_file: The output stream.
 
430
    :param specific_files: Include only changes to these files - None for all
 
431
        changes.
 
432
    :param external_diff_options: If set, use an external GNU diff and pass 
 
433
        these options.
 
434
    :param extra_trees: If set, more Trees to use for looking up file ids
 
435
    :param path_encoding: If set, the path will be encoded as specified, 
 
436
        otherwise is supposed to be utf8
 
437
    :param format_cls: Formatter class (DiffTree subclass)
414
438
    """
 
439
    if format_cls is None:
 
440
        format_cls = DiffTree
415
441
    old_tree.lock_read()
416
442
    try:
417
443
        if extra_trees is not None:
419
445
                tree.lock_read()
420
446
        new_tree.lock_read()
421
447
        try:
422
 
            differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
423
 
                                                 path_encoding,
424
 
                                                 external_diff_options,
425
 
                                                 old_label, new_label, using)
 
448
            differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
 
449
                                                   path_encoding,
 
450
                                                   external_diff_options,
 
451
                                                   old_label, new_label, using)
426
452
            return differ.show_diff(specific_files, extra_trees)
427
453
        finally:
428
454
            new_tree.unlock()
435
461
 
436
462
def _patch_header_date(tree, file_id, path):
437
463
    """Returns a timestamp suitable for use in a patch header."""
438
 
    mtime = tree.get_file_mtime(file_id, path)
 
464
    try:
 
465
        mtime = tree.get_file_mtime(file_id, path)
 
466
    except errors.FileTimestampUnavailable:
 
467
        mtime = 0
439
468
    return timestamp.format_patch_date(mtime)
440
469
 
441
470
 
442
 
@deprecated_function(one_three)
443
 
def get_prop_change(meta_modified):
444
 
    if meta_modified:
445
 
        return " (properties changed)"
446
 
    else:
447
 
        return  ""
448
 
 
449
471
def get_executable_change(old_is_x, new_is_x):
450
472
    descr = { True:"+x", False:"-x", None:"??" }
451
473
    if old_is_x != new_is_x:
626
648
            return self.CANNOT_DIFF
627
649
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
628
650
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
629
 
        return self.diff_text(from_file_id, to_file_id, from_label, to_label)
 
651
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
 
652
            old_path, new_path)
630
653
 
631
 
    def diff_text(self, from_file_id, to_file_id, from_label, to_label):
 
654
    def diff_text(self, from_file_id, to_file_id, from_label, to_label,
 
655
        from_path=None, to_path=None):
632
656
        """Diff the content of given files in two trees
633
657
 
634
658
        :param from_file_id: The id of the file in the from tree.  If None,
636
660
        :param to_file_id: The id of the file in the to tree.  This may refer
637
661
            to a different file from from_file_id.  If None,
638
662
            the file is not present in the to tree.
 
663
        :param from_path: The path in the from tree or None if unknown.
 
664
        :param to_path: The path in the to tree or None if unknown.
639
665
        """
640
 
        def _get_text(tree, file_id):
 
666
        def _get_text(tree, file_id, path):
641
667
            if file_id is not None:
642
 
                return tree.get_file(file_id).readlines()
 
668
                return tree.get_file_lines(file_id, path)
643
669
            else:
644
670
                return []
645
671
        try:
646
 
            from_text = _get_text(self.old_tree, from_file_id)
647
 
            to_text = _get_text(self.new_tree, to_file_id)
 
672
            from_text = _get_text(self.old_tree, from_file_id, from_path)
 
673
            to_text = _get_text(self.new_tree, to_file_id, to_path)
648
674
            self.text_differ(from_label, from_text, to_label, to_text,
649
 
                             self.to_file)
 
675
                             self.to_file, path_encoding=self.path_encoding)
650
676
        except errors.BinaryFile:
651
677
            self.to_file.write(
652
678
                  ("Binary files %s and %s differ\n" %
653
 
                  (from_label, to_label)).encode(self.path_encoding))
 
679
                  (from_label, to_label)).encode(self.path_encoding,'replace'))
654
680
        return self.CHANGED
655
681
 
656
682
 
665
691
    @classmethod
666
692
    def from_string(klass, command_string, old_tree, new_tree, to_file,
667
693
                    path_encoding='utf-8'):
668
 
        command_template = commands.shlex_split_unicode(command_string)
669
 
        command_template.extend(['%(old_path)s', '%(new_path)s'])
 
694
        command_template = cmdline.split(command_string)
 
695
        if '@' not in command_string:
 
696
            command_template.extend(['@old_path', '@new_path'])
670
697
        return klass(command_template, old_tree, new_tree, to_file,
671
698
                     path_encoding)
672
699
 
673
700
    @classmethod
674
 
    def make_from_diff_tree(klass, command_string):
 
701
    def make_from_diff_tree(klass, command_string, external_diff_options=None):
675
702
        def from_diff_tree(diff_tree):
676
 
            return klass.from_string(command_string, diff_tree.old_tree,
 
703
            full_command_string = [command_string]
 
704
            if external_diff_options is not None:
 
705
                full_command_string += ' ' + external_diff_options
 
706
            return klass.from_string(full_command_string, diff_tree.old_tree,
677
707
                                     diff_tree.new_tree, diff_tree.to_file)
678
708
        return from_diff_tree
679
709
 
680
710
    def _get_command(self, old_path, new_path):
681
711
        my_map = {'old_path': old_path, 'new_path': new_path}
682
 
        return [t % my_map for t in self.command_template]
 
712
        command = [AtTemplate(t).substitute(my_map) for t in
 
713
                   self.command_template]
 
714
        if sys.platform == 'win32': # Popen doesn't accept unicode on win32
 
715
            command_encoded = []
 
716
            for c in command:
 
717
                if isinstance(c, unicode):
 
718
                    command_encoded.append(c.encode('mbcs'))
 
719
                else:
 
720
                    command_encoded.append(c)
 
721
            return command_encoded
 
722
        else:
 
723
            return command
683
724
 
684
725
    def _execute(self, old_path, new_path):
685
726
        command = self._get_command(old_path, new_path)
705
746
                raise
706
747
        return True
707
748
 
708
 
    def _write_file(self, file_id, tree, prefix, relpath):
709
 
        full_path = osutils.pathjoin(self._root, prefix, relpath)
710
 
        if self._try_symlink_root(tree, prefix):
 
749
    @staticmethod
 
750
    def _fenc():
 
751
        """Returns safe encoding for passing file path to diff tool"""
 
752
        if sys.platform == 'win32':
 
753
            return 'mbcs'
 
754
        else:
 
755
            # Don't fallback to 'utf-8' because subprocess may not be able to
 
756
            # handle utf-8 correctly when locale is not utf-8.
 
757
            return sys.getfilesystemencoding() or 'ascii'
 
758
 
 
759
    def _is_safepath(self, path):
 
760
        """Return true if `path` may be able to pass to subprocess."""
 
761
        fenc = self._fenc()
 
762
        try:
 
763
            return path == path.encode(fenc).decode(fenc)
 
764
        except UnicodeError:
 
765
            return False
 
766
 
 
767
    def _safe_filename(self, prefix, relpath):
 
768
        """Replace unsafe character in `relpath` then join `self._root`,
 
769
        `prefix` and `relpath`."""
 
770
        fenc = self._fenc()
 
771
        # encoded_str.replace('?', '_') may break multibyte char.
 
772
        # So we should encode, decode, then replace(u'?', u'_')
 
773
        relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
 
774
        relpath_tmp = relpath_tmp.replace(u'?', u'_')
 
775
        return osutils.pathjoin(self._root, prefix, relpath_tmp)
 
776
 
 
777
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
 
778
                    allow_write=False):
 
779
        if not force_temp and isinstance(tree, WorkingTree):
 
780
            full_path = tree.abspath(tree.id2path(file_id))
 
781
            if self._is_safepath(full_path):
 
782
                return full_path
 
783
 
 
784
        full_path = self._safe_filename(prefix, relpath)
 
785
        if not force_temp and self._try_symlink_root(tree, prefix):
711
786
            return full_path
712
787
        parent_dir = osutils.dirname(full_path)
713
788
        try:
724
799
                target.close()
725
800
        finally:
726
801
            source.close()
727
 
        osutils.make_readonly(full_path)
728
 
        mtime = tree.get_file_mtime(file_id)
729
 
        os.utime(full_path, (mtime, mtime))
 
802
        try:
 
803
            mtime = tree.get_file_mtime(file_id)
 
804
        except errors.FileTimestampUnavailable:
 
805
            pass
 
806
        else:
 
807
            os.utime(full_path, (mtime, mtime))
 
808
        if not allow_write:
 
809
            osutils.make_readonly(full_path)
730
810
        return full_path
731
811
 
732
 
    def _prepare_files(self, file_id, old_path, new_path):
 
812
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
 
813
                       allow_write_new=False):
733
814
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
734
 
                                         old_path)
 
815
                                         old_path, force_temp)
735
816
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
736
 
                                         new_path)
 
817
                                         new_path, force_temp,
 
818
                                         allow_write=allow_write_new)
737
819
        return old_disk_path, new_disk_path
738
820
 
739
821
    def finish(self):
740
 
        osutils.rmtree(self._root)
 
822
        try:
 
823
            osutils.rmtree(self._root)
 
824
        except OSError, e:
 
825
            if e.errno != errno.ENOENT:
 
826
                mutter("The temporary directory \"%s\" was not "
 
827
                        "cleanly removed: %s." % (self._root, e))
741
828
 
742
829
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
743
830
        if (old_kind, new_kind) != ('file', 'file'):
744
831
            return DiffPath.CANNOT_DIFF
745
 
        self._prepare_files(file_id, old_path, new_path)
746
 
        self._execute(osutils.pathjoin('old', old_path),
747
 
                      osutils.pathjoin('new', new_path))
 
832
        (old_disk_path, new_disk_path) = self._prepare_files(
 
833
                                                file_id, old_path, new_path)
 
834
        self._execute(old_disk_path, new_disk_path)
 
835
 
 
836
    def edit_file(self, file_id):
 
837
        """Use this tool to edit a file.
 
838
 
 
839
        A temporary copy will be edited, and the new contents will be
 
840
        returned.
 
841
 
 
842
        :param file_id: The id of the file to edit.
 
843
        :return: The new contents of the file.
 
844
        """
 
845
        old_path = self.old_tree.id2path(file_id)
 
846
        new_path = self.new_tree.id2path(file_id)
 
847
        old_abs_path, new_abs_path = self._prepare_files(
 
848
                                            file_id, old_path, new_path,
 
849
                                            allow_write_new=True,
 
850
                                            force_temp=True)
 
851
        command = self._get_command(old_abs_path, new_abs_path)
 
852
        subprocess.call(command, cwd=self._root)
 
853
        new_file = open(new_abs_path, 'rb')
 
854
        try:
 
855
            return new_file.read()
 
856
        finally:
 
857
            new_file.close()
748
858
 
749
859
 
750
860
class DiffTree(object):
796
906
        """Factory for producing a DiffTree.
797
907
 
798
908
        Designed to accept options used by show_diff_trees.
 
909
 
799
910
        :param old_tree: The tree to show as old in the comparison
800
911
        :param new_tree: The tree to show as new in the comparison
801
912
        :param to_file: File to write comparisons to
807
918
        :param using: Commandline to use to invoke an external diff tool
808
919
        """
809
920
        if using is not None:
810
 
            extra_factories = [DiffFromTool.make_from_diff_tree(using)]
 
921
            extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
811
922
        else:
812
923
            extra_factories = []
813
924
        if external_diff_options:
814
925
            opts = external_diff_options.split()
815
 
            def diff_file(olab, olines, nlab, nlines, to_file):
 
926
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None):
 
927
                """:param path_encoding: not used but required
 
928
                        to match the signature of internal_diff.
 
929
                """
816
930
                external_diff(olab, olines, nlab, nlines, to_file, opts)
817
931
        else:
818
932
            diff_file = internal_diff
824
938
    def show_diff(self, specific_files, extra_trees=None):
825
939
        """Write tree diff to self.to_file
826
940
 
827
 
        :param sepecific_files: the specific files to compare (recursive)
 
941
        :param specific_files: the specific files to compare (recursive)
828
942
        :param extra_trees: extra trees to use for mapping paths to file_ids
829
943
        """
830
944
        try:
888
1002
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
889
1003
                                   newpath_encoded, prop_str))
890
1004
            if changed_content:
891
 
                self.diff(file_id, oldpath, newpath)
 
1005
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
892
1006
                has_changes = 1
893
1007
            if renamed:
894
1008
                has_changes = 1
909
1023
            new_kind = self.new_tree.kind(file_id)
910
1024
        except (errors.NoSuchId, errors.NoSuchFile):
911
1025
            new_kind = None
912
 
 
 
1026
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
 
1027
 
 
1028
 
 
1029
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
913
1030
        result = DiffPath._diff_many(self.differs, file_id, old_path,
914
1031
                                       new_path, old_kind, new_kind)
915
1032
        if result is DiffPath.CANNOT_DIFF:
917
1034
            if error_path is None:
918
1035
                error_path = old_path
919
1036
            raise errors.NoDiffFound(error_path)
 
1037
 
 
1038
 
 
1039
format_registry = Registry()
 
1040
format_registry.register('default', DiffTree)