~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Florian Dorn
  • Date: 2011-05-25 09:45:08 UTC
  • mto: This revision was merged to the branch mainline in revision 6546.
  • Revision ID: florian.dorn@boku.ac.at-20110525094508-o1c8dagf310i8ey3
added a Base64CredentialStore, refs #788015 

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
 
 
44
from bzrlib.registry import (
 
45
    Registry,
 
46
    )
42
47
from bzrlib.symbol_versioning import (
43
 
        deprecated_function,
44
 
        one_three
45
 
        )
46
 
from bzrlib.trace import mutter, warning
 
48
    deprecated_function,
 
49
    deprecated_in,
 
50
    )
 
51
from bzrlib.trace import mutter, note, warning
 
52
 
 
53
 
 
54
class AtTemplate(string.Template):
 
55
    """Templating class that uses @ instead of $."""
 
56
 
 
57
    delimiter = '@'
47
58
 
48
59
 
49
60
# TODO: Rather than building a changeset object, we should probably
78
89
    # both sequences are empty.
79
90
    if not oldlines and not newlines:
80
91
        return
81
 
    
 
92
 
82
93
    if allow_binary is False:
83
94
        textfile.check_text_lines(oldlines)
84
95
        textfile.check_text_lines(newlines)
86
97
    if sequence_matcher is None:
87
98
        sequence_matcher = patiencediff.PatienceSequenceMatcher
88
99
    ud = patiencediff.unified_diff(oldlines, newlines,
89
 
                      fromfile=old_filename.encode(path_encoding),
90
 
                      tofile=new_filename.encode(path_encoding),
 
100
                      fromfile=old_filename.encode(path_encoding, 'replace'),
 
101
                      tofile=new_filename.encode(path_encoding, 'replace'),
91
102
                      sequencematcher=sequence_matcher)
92
103
 
93
104
    ud = list(ud)
99
110
        ud[2] = ud[2].replace('-1,0', '-0,0')
100
111
    elif not newlines:
101
112
        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
113
 
106
114
    for line in ud:
107
115
        to_file.write(line)
174
182
 
175
183
        if not diff_opts:
176
184
            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')
177
190
        diffcmd = ['diff',
178
191
                   '--label', old_filename,
179
192
                   old_abspath,
202
215
            break
203
216
        else:
204
217
            diffcmd.append('-u')
205
 
                  
 
218
 
206
219
        if diff_opts:
207
220
            diffcmd.extend(diff_opts)
208
221
 
209
222
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
210
223
        out,err = pipe.communicate()
211
224
        rc = pipe.returncode
212
 
        
 
225
 
213
226
        # internal_diff() adds a trailing newline, add one here for consistency
214
227
        out += '\n'
215
228
        if rc == 2:
250
263
                msg = 'signal %d' % (-rc)
251
264
            else:
252
265
                msg = 'exit code %d' % rc
253
 
                
254
 
            raise errors.BzrError('external diff failed with %s; command: %r' 
 
266
 
 
267
            raise errors.BzrError('external diff failed with %s; command: %r'
255
268
                                  % (rc, diffcmd))
256
269
 
257
270
 
275
288
                        new_abspath, e)
276
289
 
277
290
 
278
 
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
279
 
    """Get the trees and specific files to diff given a list of paths.
280
 
 
281
 
    This method works out the trees to be diff'ed and the files of
282
 
    interest within those trees.
283
 
 
284
 
    :param path_list:
285
 
        the list of arguments passed to the diff command
286
 
    :param revision_specs:
287
 
        Zero, one or two RevisionSpecs from the diff command line,
288
 
        saying what revisions to compare.
289
 
    :param old_url:
290
 
        The url of the old branch or tree. If None, the tree to use is
291
 
        taken from the first path, if any, or the current working tree.
292
 
    :param new_url:
293
 
        The url of the new branch or tree. If None, the tree to use is
294
 
        taken from the first path, if any, or the current working tree.
295
 
    :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.
 
291
@deprecated_function(deprecated_in((2, 2, 0)))
 
292
def get_trees_and_branches_to_diff(path_list, revision_specs, old_url, new_url,
 
293
                                   apply_view=True):
 
294
    """Get the trees and specific files to diff given a list of paths.
 
295
 
 
296
    This method works out the trees to be diff'ed and the files of
 
297
    interest within those trees.
 
298
 
 
299
    :param path_list:
 
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.
 
304
    :param old_url:
 
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.
 
307
    :param new_url:
 
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.
 
310
    :param apply_view:
 
311
        if True and a view is set, apply the view or check that the paths
 
312
        are within it
 
313
    :returns:
 
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
 
317
        are not locked.
 
318
    """
 
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)
 
322
    
 
323
 
 
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.
 
327
 
 
328
    This method works out the trees to be diff'ed and the files of
 
329
    interest within those trees.
 
330
 
 
331
    :param path_list:
 
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.
 
336
    :param old_url:
 
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.
 
339
    :param new_url:
 
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.
 
342
    :param add_cleanup:
 
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.
 
345
    :param apply_view:
 
346
        if True and a view is set, apply the view or check that the paths
 
347
        are within it
 
348
    :returns:
 
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
 
353
        param are run.
299
354
    """
300
355
    # Get the old and new revision specs
301
356
    old_revision_spec = None
324
379
        default_location = path_list[0]
325
380
        other_paths = path_list[1:]
326
381
 
 
382
    def lock_tree_or_branch(wt, br):
 
383
        if wt is not None:
 
384
            wt.lock_read()
 
385
            add_cleanup(wt.unlock)
 
386
        elif br is not None:
 
387
            br.lock_read()
 
388
            add_cleanup(br.unlock)
 
389
 
327
390
    # Get the old location
328
391
    specific_files = []
329
392
    if old_url is None:
330
393
        old_url = default_location
331
394
    working_tree, branch, relpath = \
332
395
        bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
 
396
    lock_tree_or_branch(working_tree, branch)
333
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)
334
400
        specific_files.append(relpath)
335
401
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
 
402
    old_branch = branch
336
403
 
337
404
    # Get the new location
338
405
    if new_url is None:
340
407
    if new_url != old_url:
341
408
        working_tree, branch, relpath = \
342
409
            bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
 
410
        lock_tree_or_branch(working_tree, branch)
343
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)
344
414
            specific_files.append(relpath)
345
415
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
346
416
        basis_is_default=working_tree is None)
 
417
    new_branch = branch
347
418
 
348
419
    # Get the specific files (all files is None, no files is [])
349
420
    if make_paths_wt_relative and working_tree is not None:
350
 
        other_paths = _relative_paths_in_tree(working_tree, other_paths)
 
421
        other_paths = working_tree.safe_relpath_files(
 
422
            other_paths,
 
423
            apply_view=apply_view)
351
424
    specific_files.extend(other_paths)
352
425
    if len(specific_files) == 0:
353
426
        specific_files = None
 
427
        if (working_tree is not None and working_tree.supports_views()
 
428
            and apply_view):
 
429
            view_files = working_tree.views.lookup_view()
 
430
            if view_files:
 
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)
354
434
 
355
435
    # Get extra trees that ought to be searched for file-ids
356
436
    extra_trees = None
357
437
    if working_tree is not None and working_tree not in (old_tree, new_tree):
358
438
        extra_trees = (working_tree,)
359
 
    return old_tree, new_tree, specific_files, extra_trees
 
439
    return old_tree, new_tree, old_branch, new_branch, specific_files, extra_trees
360
440
 
361
441
 
362
442
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
370
450
                return branch.basis_tree()
371
451
        else:
372
452
            return tree
373
 
    if not spec.needs_branch():
374
 
        branch = _mod_branch.Branch.open(spec.get_branch())
375
 
    revision_id = spec.as_revision_id(branch)
376
 
    return branch.repository.revision_tree(revision_id)
377
 
 
378
 
 
379
 
def _relative_paths_in_tree(tree, paths):
380
 
    """Get the relative paths within a working tree.
381
 
 
382
 
    Each path may be either an absolute path or a path relative to the
383
 
    current working directory.
384
 
    """
385
 
    result = []
386
 
    for filename in paths:
387
 
        try:
388
 
            result.append(tree.relpath(osutils.dereference_path(filename)))
389
 
        except errors.PathNotChild:
390
 
            raise errors.BzrCommandError("Files are in different branches")
391
 
    return result
 
453
    return spec.as_tree(branch)
392
454
 
393
455
 
394
456
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
396
458
                    old_label='a/', new_label='b/',
397
459
                    extra_trees=None,
398
460
                    path_encoding='utf8',
399
 
                    using=None):
 
461
                    using=None,
 
462
                    format_cls=None):
400
463
    """Show in text form the changes from one tree to another.
401
464
 
402
 
    to_file
403
 
        The output stream.
404
 
 
405
 
    specific_files
406
 
        Include only changes to these files - None for all changes.
407
 
 
408
 
    external_diff_options
409
 
        If set, use an external GNU diff and pass these options.
410
 
 
411
 
    extra_trees
412
 
        If set, more Trees to use for looking up file ids
413
 
 
414
 
    path_encoding
415
 
        If set, the path will be encoded as specified, otherwise is supposed
416
 
        to be utf8
 
465
    :param to_file: The output stream.
 
466
    :param specific_files: Include only changes to these files - None for all
 
467
        changes.
 
468
    :param external_diff_options: If set, use an external GNU diff and pass 
 
469
        these options.
 
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)
417
474
    """
 
475
    if format_cls is None:
 
476
        format_cls = DiffTree
418
477
    old_tree.lock_read()
419
478
    try:
420
479
        if extra_trees is not None:
422
481
                tree.lock_read()
423
482
        new_tree.lock_read()
424
483
        try:
425
 
            differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
426
 
                                                 path_encoding,
427
 
                                                 external_diff_options,
428
 
                                                 old_label, new_label, using)
 
484
            differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
 
485
                                                   path_encoding,
 
486
                                                   external_diff_options,
 
487
                                                   old_label, new_label, using)
429
488
            return differ.show_diff(specific_files, extra_trees)
430
489
        finally:
431
490
            new_tree.unlock()
438
497
 
439
498
def _patch_header_date(tree, file_id, path):
440
499
    """Returns a timestamp suitable for use in a patch header."""
441
 
    mtime = tree.get_file_mtime(file_id, path)
 
500
    try:
 
501
        mtime = tree.get_file_mtime(file_id, path)
 
502
    except errors.FileTimestampUnavailable:
 
503
        mtime = 0
442
504
    return timestamp.format_patch_date(mtime)
443
505
 
444
506
 
445
 
def _raise_if_nonexistent(paths, old_tree, new_tree):
446
 
    """Complain if paths are not in either inventory or tree.
447
 
 
448
 
    It's OK with the files exist in either tree's inventory, or 
449
 
    if they exist in the tree but are not versioned.
450
 
    
451
 
    This can be used by operations such as bzr status that can accept
452
 
    unknown or ignored files.
453
 
    """
454
 
    mutter("check paths: %r", paths)
455
 
    if not paths:
456
 
        return
457
 
    s = old_tree.filter_unversioned_files(paths)
458
 
    s = new_tree.filter_unversioned_files(s)
459
 
    s = [path for path in s if not new_tree.has_filename(path)]
460
 
    if s:
461
 
        raise errors.PathsDoNotExist(sorted(s))
462
 
 
463
 
 
464
 
@deprecated_function(one_three)
465
 
def get_prop_change(meta_modified):
466
 
    if meta_modified:
467
 
        return " (properties changed)"
468
 
    else:
469
 
        return  ""
470
 
 
471
507
def get_executable_change(old_is_x, new_is_x):
472
508
    descr = { True:"+x", False:"-x", None:"??" }
473
509
    if old_is_x != new_is_x:
648
684
            return self.CANNOT_DIFF
649
685
        from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
650
686
        to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
651
 
        return self.diff_text(from_file_id, to_file_id, from_label, to_label)
 
687
        return self.diff_text(from_file_id, to_file_id, from_label, to_label,
 
688
            old_path, new_path)
652
689
 
653
 
    def diff_text(self, 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):
654
692
        """Diff the content of given files in two trees
655
693
 
656
694
        :param from_file_id: The id of the file in the from tree.  If None,
658
696
        :param to_file_id: The id of the file in the to tree.  This may refer
659
697
            to a different file from from_file_id.  If None,
660
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.
661
701
        """
662
 
        def _get_text(tree, file_id):
 
702
        def _get_text(tree, file_id, path):
663
703
            if file_id is not None:
664
 
                return tree.get_file(file_id).readlines()
 
704
                return tree.get_file_lines(file_id, path)
665
705
            else:
666
706
                return []
667
707
        try:
668
 
            from_text = _get_text(self.old_tree, from_file_id)
669
 
            to_text = _get_text(self.new_tree, to_file_id)
 
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)
670
710
            self.text_differ(from_label, from_text, to_label, to_text,
671
 
                             self.to_file)
 
711
                             self.to_file, path_encoding=self.path_encoding)
672
712
        except errors.BinaryFile:
673
713
            self.to_file.write(
674
714
                  ("Binary files %s and %s differ\n" %
675
 
                  (from_label, to_label)).encode(self.path_encoding))
 
715
                  (from_label, to_label)).encode(self.path_encoding,'replace'))
676
716
        return self.CHANGED
677
717
 
678
718
 
687
727
    @classmethod
688
728
    def from_string(klass, command_string, old_tree, new_tree, to_file,
689
729
                    path_encoding='utf-8'):
690
 
        command_template = commands.shlex_split_unicode(command_string)
691
 
        command_template.extend(['%(old_path)s', '%(new_path)s'])
 
730
        command_template = cmdline.split(command_string)
 
731
        if '@' not in command_string:
 
732
            command_template.extend(['@old_path', '@new_path'])
692
733
        return klass(command_template, old_tree, new_tree, to_file,
693
734
                     path_encoding)
694
735
 
695
736
    @classmethod
696
 
    def make_from_diff_tree(klass, command_string):
 
737
    def make_from_diff_tree(klass, command_string, external_diff_options=None):
697
738
        def from_diff_tree(diff_tree):
698
 
            return klass.from_string(command_string, diff_tree.old_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,
699
743
                                     diff_tree.new_tree, diff_tree.to_file)
700
744
        return from_diff_tree
701
745
 
702
746
    def _get_command(self, old_path, new_path):
703
747
        my_map = {'old_path': old_path, 'new_path': new_path}
704
 
        return [t % my_map for t in self.command_template]
 
748
        command = [AtTemplate(t).substitute(my_map) for t in
 
749
                   self.command_template]
 
750
        if sys.platform == 'win32': # Popen doesn't accept unicode on win32
 
751
            command_encoded = []
 
752
            for c in command:
 
753
                if isinstance(c, unicode):
 
754
                    command_encoded.append(c.encode('mbcs'))
 
755
                else:
 
756
                    command_encoded.append(c)
 
757
            return command_encoded
 
758
        else:
 
759
            return command
705
760
 
706
761
    def _execute(self, old_path, new_path):
707
762
        command = self._get_command(old_path, new_path)
727
782
                raise
728
783
        return True
729
784
 
730
 
    def _write_file(self, file_id, tree, prefix, relpath):
731
 
        full_path = osutils.pathjoin(self._root, prefix, relpath)
732
 
        if self._try_symlink_root(tree, prefix):
 
785
    @staticmethod
 
786
    def _fenc():
 
787
        """Returns safe encoding for passing file path to diff tool"""
 
788
        if sys.platform == 'win32':
 
789
            return 'mbcs'
 
790
        else:
 
791
            # Don't fallback to 'utf-8' because subprocess may not be able to
 
792
            # handle utf-8 correctly when locale is not utf-8.
 
793
            return sys.getfilesystemencoding() or 'ascii'
 
794
 
 
795
    def _is_safepath(self, path):
 
796
        """Return true if `path` may be able to pass to subprocess."""
 
797
        fenc = self._fenc()
 
798
        try:
 
799
            return path == path.encode(fenc).decode(fenc)
 
800
        except UnicodeError:
 
801
            return False
 
802
 
 
803
    def _safe_filename(self, prefix, relpath):
 
804
        """Replace unsafe character in `relpath` then join `self._root`,
 
805
        `prefix` and `relpath`."""
 
806
        fenc = self._fenc()
 
807
        # encoded_str.replace('?', '_') may break multibyte char.
 
808
        # So we should encode, decode, then replace(u'?', u'_')
 
809
        relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
 
810
        relpath_tmp = relpath_tmp.replace(u'?', u'_')
 
811
        return osutils.pathjoin(self._root, prefix, relpath_tmp)
 
812
 
 
813
    def _write_file(self, file_id, tree, prefix, relpath, force_temp=False,
 
814
                    allow_write=False):
 
815
        if not force_temp and isinstance(tree, WorkingTree):
 
816
            full_path = tree.abspath(tree.id2path(file_id))
 
817
            if self._is_safepath(full_path):
 
818
                return full_path
 
819
 
 
820
        full_path = self._safe_filename(prefix, relpath)
 
821
        if not force_temp and self._try_symlink_root(tree, prefix):
733
822
            return full_path
734
823
        parent_dir = osutils.dirname(full_path)
735
824
        try:
746
835
                target.close()
747
836
        finally:
748
837
            source.close()
749
 
        osutils.make_readonly(full_path)
750
 
        mtime = tree.get_file_mtime(file_id)
751
 
        os.utime(full_path, (mtime, mtime))
 
838
        try:
 
839
            mtime = tree.get_file_mtime(file_id)
 
840
        except errors.FileTimestampUnavailable:
 
841
            pass
 
842
        else:
 
843
            os.utime(full_path, (mtime, mtime))
 
844
        if not allow_write:
 
845
            osutils.make_readonly(full_path)
752
846
        return full_path
753
847
 
754
 
    def _prepare_files(self, file_id, old_path, new_path):
 
848
    def _prepare_files(self, file_id, old_path, new_path, force_temp=False,
 
849
                       allow_write_new=False):
755
850
        old_disk_path = self._write_file(file_id, self.old_tree, 'old',
756
 
                                         old_path)
 
851
                                         old_path, force_temp)
757
852
        new_disk_path = self._write_file(file_id, self.new_tree, 'new',
758
 
                                         new_path)
 
853
                                         new_path, force_temp,
 
854
                                         allow_write=allow_write_new)
759
855
        return old_disk_path, new_disk_path
760
856
 
761
857
    def finish(self):
762
 
        osutils.rmtree(self._root)
 
858
        try:
 
859
            osutils.rmtree(self._root)
 
860
        except OSError, e:
 
861
            if e.errno != errno.ENOENT:
 
862
                mutter("The temporary directory \"%s\" was not "
 
863
                        "cleanly removed: %s." % (self._root, e))
763
864
 
764
865
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
765
866
        if (old_kind, new_kind) != ('file', 'file'):
766
867
            return DiffPath.CANNOT_DIFF
767
 
        self._prepare_files(file_id, old_path, new_path)
768
 
        self._execute(osutils.pathjoin('old', old_path),
769
 
                      osutils.pathjoin('new', new_path))
 
868
        (old_disk_path, new_disk_path) = self._prepare_files(
 
869
                                                file_id, old_path, new_path)
 
870
        self._execute(old_disk_path, new_disk_path)
 
871
 
 
872
    def edit_file(self, file_id):
 
873
        """Use this tool to edit a file.
 
874
 
 
875
        A temporary copy will be edited, and the new contents will be
 
876
        returned.
 
877
 
 
878
        :param file_id: The id of the file to edit.
 
879
        :return: The new contents of the file.
 
880
        """
 
881
        old_path = self.old_tree.id2path(file_id)
 
882
        new_path = self.new_tree.id2path(file_id)
 
883
        old_abs_path, new_abs_path = self._prepare_files(
 
884
                                            file_id, old_path, new_path,
 
885
                                            allow_write_new=True,
 
886
                                            force_temp=True)
 
887
        command = self._get_command(old_abs_path, new_abs_path)
 
888
        subprocess.call(command, cwd=self._root)
 
889
        new_file = open(new_abs_path, 'rb')
 
890
        try:
 
891
            return new_file.read()
 
892
        finally:
 
893
            new_file.close()
770
894
 
771
895
 
772
896
class DiffTree(object):
818
942
        """Factory for producing a DiffTree.
819
943
 
820
944
        Designed to accept options used by show_diff_trees.
 
945
 
821
946
        :param old_tree: The tree to show as old in the comparison
822
947
        :param new_tree: The tree to show as new in the comparison
823
948
        :param to_file: File to write comparisons to
829
954
        :param using: Commandline to use to invoke an external diff tool
830
955
        """
831
956
        if using is not None:
832
 
            extra_factories = [DiffFromTool.make_from_diff_tree(using)]
 
957
            extra_factories = [DiffFromTool.make_from_diff_tree(using, external_diff_options)]
833
958
        else:
834
959
            extra_factories = []
835
960
        if external_diff_options:
836
961
            opts = external_diff_options.split()
837
 
            def diff_file(olab, olines, nlab, nlines, to_file):
 
962
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None):
 
963
                """:param path_encoding: not used but required
 
964
                        to match the signature of internal_diff.
 
965
                """
838
966
                external_diff(olab, olines, nlab, nlines, to_file, opts)
839
967
        else:
840
968
            diff_file = internal_diff
846
974
    def show_diff(self, specific_files, extra_trees=None):
847
975
        """Write tree diff to self.to_file
848
976
 
849
 
        :param sepecific_files: the specific files to compare (recursive)
 
977
        :param specific_files: the specific files to compare (recursive)
850
978
        :param extra_trees: extra trees to use for mapping paths to file_ids
851
979
        """
852
980
        try:
910
1038
                self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
911
1039
                                   newpath_encoded, prop_str))
912
1040
            if changed_content:
913
 
                self.diff(file_id, oldpath, newpath)
 
1041
                self._diff(file_id, oldpath, newpath, kind[0], kind[1])
914
1042
                has_changes = 1
915
1043
            if renamed:
916
1044
                has_changes = 1
931
1059
            new_kind = self.new_tree.kind(file_id)
932
1060
        except (errors.NoSuchId, errors.NoSuchFile):
933
1061
            new_kind = None
934
 
 
 
1062
        self._diff(file_id, old_path, new_path, old_kind, new_kind)
 
1063
 
 
1064
 
 
1065
    def _diff(self, file_id, old_path, new_path, old_kind, new_kind):
935
1066
        result = DiffPath._diff_many(self.differs, file_id, old_path,
936
1067
                                       new_path, old_kind, new_kind)
937
1068
        if result is DiffPath.CANNOT_DIFF:
939
1070
            if error_path is None:
940
1071
                error_path = old_path
941
1072
            raise errors.NoDiffFound(error_path)
 
1073
 
 
1074
 
 
1075
format_registry = Registry()
 
1076
format_registry.register('default', DiffTree)