275
94
        oldtmpf.writelines(oldlines)
 
276
95
        newtmpf.writelines(newlines)
 
281
100
        if not diff_opts:
 
283
 
        if sys.platform == 'win32':
 
284
 
            # Popen doesn't do the proper encoding for external commands
 
285
 
            # Since we are dealing with an ANSI api, use mbcs encoding
 
286
 
            old_label = old_label.encode('mbcs')
 
287
 
            new_label = new_label.encode('mbcs')
 
288
102
        diffcmd = ['diff',
 
289
 
                   '--label', old_label,
 
291
 
                   '--label', new_label,
 
296
 
        diff_opts = default_style_unified(diff_opts)
 
 
103
                   '--label', old_filename+'\t',
 
 
105
                   '--label', new_filename+'\t',
 
 
108
        # diff only allows one style to be specified; they don't override.
 
 
109
        # note that some of these take optargs, and the optargs can be
 
 
110
        # directly appended to the options.
 
 
111
        # this is only an approximate parser; it doesn't properly understand
 
 
113
        for s in ['-c', '-u', '-C', '-U',
 
 
118
                  '-y', '--side-by-side',
 
299
130
            diffcmd.extend(diff_opts)
 
301
 
        pipe = _spawn_external_diff(diffcmd, capture_errors=True)
 
302
 
        out, err = pipe.communicate()
 
305
 
        # internal_diff() adds a trailing newline, add one here for consistency
 
308
 
            # 'diff' gives retcode == 2 for all sorts of errors
 
309
 
            # one of those is 'Binary files differ'.
 
310
 
            # Bad options could also be the problem.
 
311
 
            # 'Binary files' is not a real error, so we suppress that error.
 
314
 
            # Since we got here, we want to make sure to give an i18n error
 
315
 
            pipe = _spawn_external_diff(diffcmd, capture_errors=False)
 
316
 
            out, err = pipe.communicate()
 
318
 
            # Write out the new i18n diff response
 
319
 
            to_file.write(out + b'\n')
 
320
 
            if pipe.returncode != 2:
 
321
 
                raise errors.BzrError(
 
322
 
                    'external diff failed with exit code 2'
 
323
 
                    ' when run with LANG=C and LC_ALL=C,'
 
324
 
                    ' but not when run natively: %r' % (diffcmd,))
 
326
 
            first_line = lang_c_out.split(b'\n', 1)[0]
 
327
 
            # Starting with diffutils 2.8.4 the word "binary" was dropped.
 
328
 
            m = re.match(b'^(binary )?files.*differ$', first_line, re.I)
 
330
 
                raise errors.BzrError('external diff failed with exit code 2;'
 
331
 
                                      ' command: %r' % (diffcmd,))
 
333
 
                # Binary files differ, just return
 
336
 
        # If we got to here, we haven't written out the output of diff
 
 
132
        rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
 
 
134
        if rc != 0 and rc != 1:
 
340
135
            # returns 1 if files differ; that's OK
 
342
137
                msg = 'signal %d' % (-rc)
 
344
139
                msg = 'exit code %d' % rc
 
346
 
            raise errors.BzrError('external diff failed with %s; command: %r'
 
 
141
            raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
 
350
143
        oldtmpf.close()                 # and delete
 
354
 
            # Warn in case the file couldn't be deleted (in case windows still
 
355
 
            # holds the file open, but not if the files have already been
 
360
 
                if e.errno not in (errno.ENOENT,):
 
361
 
                    warning('Failed to delete temporary file: %s %s', path, e)
 
367
 
def get_trees_and_branches_to_diff_locked(
 
368
 
        path_list, revision_specs, old_url, new_url, exit_stack, apply_view=True):
 
369
 
    """Get the trees and specific files to diff given a list of paths.
 
371
 
    This method works out the trees to be diff'ed and the files of
 
372
 
    interest within those trees.
 
375
 
        the list of arguments passed to the diff command
 
376
 
    :param revision_specs:
 
377
 
        Zero, one or two RevisionSpecs from the diff command line,
 
378
 
        saying what revisions to compare.
 
380
 
        The url of the old branch or tree. If None, the tree to use is
 
381
 
        taken from the first path, if any, or the current working tree.
 
383
 
        The url of the new branch or tree. If None, the tree to use is
 
384
 
        taken from the first path, if any, or the current working tree.
 
386
 
        an ExitStack object. get_trees_and_branches_to_diff
 
387
 
        will register cleanups that must be run to unlock the trees, etc.
 
389
 
        if True and a view is set, apply the view or check that the paths
 
392
 
        a tuple of (old_tree, new_tree, old_branch, new_branch,
 
393
 
        specific_files, extra_trees) where extra_trees is a sequence of
 
394
 
        additional trees to search in for file-ids.  The trees and branches
 
395
 
        will be read-locked until the cleanups registered via the exit_stack
 
 
147
@deprecated_function(zero_eight)
 
 
148
def show_diff(b, from_spec, specific_files, external_diff_options=None,
 
 
149
              revision2=None, output=None, b2=None):
 
 
150
    """Shortcut for showing the diff to the working tree.
 
 
152
    Please use show_diff_trees instead.
 
 
158
        None for 'basis tree', or otherwise the old revision to compare against.
 
 
160
    The more general form is show_diff_trees(), where the caller
 
 
161
    supplies any two trees.
 
398
 
    # Get the old and new revision specs
 
399
 
    old_revision_spec = None
 
400
 
    new_revision_spec = None
 
401
 
    if revision_specs is not None:
 
402
 
        if len(revision_specs) > 0:
 
403
 
            old_revision_spec = revision_specs[0]
 
405
 
                old_url = old_revision_spec.get_branch()
 
406
 
        if len(revision_specs) > 1:
 
407
 
            new_revision_spec = revision_specs[1]
 
409
 
                new_url = new_revision_spec.get_branch()
 
412
 
    make_paths_wt_relative = True
 
413
 
    consider_relpath = True
 
414
 
    if path_list is None or len(path_list) == 0:
 
415
 
        # If no path is given, the current working tree is used
 
416
 
        default_location = u'.'
 
417
 
        consider_relpath = False
 
418
 
    elif old_url is not None and new_url is not None:
 
419
 
        other_paths = path_list
 
420
 
        make_paths_wt_relative = False
 
 
167
    if from_spec is None:
 
 
168
        old_tree = b.bzrdir.open_workingtree()
 
 
170
            old_tree = old_tree = old_tree.basis_tree()
 
422
 
        default_location = path_list[0]
 
423
 
        other_paths = path_list[1:]
 
425
 
    def lock_tree_or_branch(wt, br):
 
427
 
            exit_stack.enter_context(wt.lock_read())
 
429
 
            exit_stack.enter_context(br.lock_read())
 
431
 
    # Get the old location
 
434
 
        old_url = default_location
 
435
 
    working_tree, branch, relpath = \
 
436
 
        controldir.ControlDir.open_containing_tree_or_branch(old_url)
 
437
 
    lock_tree_or_branch(working_tree, branch)
 
438
 
    if consider_relpath and relpath != '':
 
439
 
        if working_tree is not None and apply_view:
 
440
 
            views.check_path_in_view(working_tree, relpath)
 
441
 
        specific_files.append(relpath)
 
442
 
    old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
 
445
 
    # Get the new location
 
447
 
        new_url = default_location
 
448
 
    if new_url != old_url:
 
449
 
        working_tree, branch, relpath = \
 
450
 
            controldir.ControlDir.open_containing_tree_or_branch(new_url)
 
451
 
        lock_tree_or_branch(working_tree, branch)
 
452
 
        if consider_relpath and relpath != '':
 
453
 
            if working_tree is not None and apply_view:
 
454
 
                views.check_path_in_view(working_tree, relpath)
 
455
 
            specific_files.append(relpath)
 
456
 
    new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
 
457
 
                                 basis_is_default=working_tree is None)
 
460
 
    # Get the specific files (all files is None, no files is [])
 
461
 
    if make_paths_wt_relative and working_tree is not None:
 
462
 
        other_paths = working_tree.safe_relpath_files(
 
464
 
            apply_view=apply_view)
 
465
 
    specific_files.extend(other_paths)
 
466
 
    if len(specific_files) == 0:
 
467
 
        specific_files = None
 
468
 
        if (working_tree is not None and working_tree.supports_views() and
 
470
 
            view_files = working_tree.views.lookup_view()
 
472
 
                specific_files = view_files
 
473
 
                view_str = views.view_display_str(view_files)
 
474
 
                note(gettext("*** Ignoring files outside view. View is %s") % view_str)
 
476
 
    # Get extra trees that ought to be searched for file-ids
 
478
 
    if working_tree is not None and working_tree not in (old_tree, new_tree):
 
479
 
        extra_trees = (working_tree,)
 
480
 
    return (old_tree, new_tree, old_branch, new_branch,
 
481
 
            specific_files, extra_trees)
 
484
 
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
 
485
 
    if branch is None and tree is not None:
 
487
 
    if spec is None or spec.spec is None:
 
490
 
                return tree.basis_tree()
 
492
 
                return branch.basis_tree()
 
 
172
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
 
174
    if revision2 is None:
 
 
176
            new_tree = b.bzrdir.open_workingtree()
 
495
 
    return spec.as_tree(branch)
 
 
178
            new_tree = b2.bzrdir.open_workingtree()
 
 
180
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
 
 
182
    return show_diff_trees(old_tree, new_tree, output, specific_files,
 
 
183
                           external_diff_options)
 
 
186
def diff_cmd_helper(tree, specific_files, external_diff_options, 
 
 
187
                    old_revision_spec=None, new_revision_spec=None):
 
 
188
    """Helper for cmd_diff.
 
 
194
        The specific files to compare, or None
 
 
196
    external_diff_options
 
 
197
        If non-None, run an external diff, and pass it these options
 
 
200
        If None, use basis tree as old revision, otherwise use the tree for
 
 
201
        the specified revision. 
 
 
204
        If None, use working tree as new revision, otherwise use the tree for
 
 
205
        the specified revision.
 
 
207
    The more general form is show_diff_trees(), where the caller
 
 
208
    supplies any two trees.
 
 
213
        revision_id = spec.in_store(tree.branch).rev_id
 
 
214
        return tree.branch.repository.revision_tree(revision_id)
 
 
215
    if old_revision_spec is None:
 
 
216
        old_tree = tree.basis_tree()
 
 
218
        old_tree = spec_tree(old_revision_spec)
 
 
220
    if new_revision_spec is None:
 
 
223
        new_tree = spec_tree(new_revision_spec)
 
 
225
    return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
 
 
226
                           external_diff_options)
 
498
229
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
 
499
 
                    external_diff_options=None,
 
500
 
                    old_label='a/', new_label='b/',
 
502
 
                    path_encoding='utf8',
 
505
 
                    context=DEFAULT_CONTEXT_AMOUNT):
 
 
230
                    external_diff_options=None):
 
506
231
    """Show in text form the changes from one tree to another.
 
508
 
    :param to_file: The output stream.
 
509
 
    :param specific_files: Include only changes to these files - None for all
 
511
 
    :param external_diff_options: If set, use an external GNU diff and pass
 
513
 
    :param extra_trees: If set, more Trees to use for looking up file ids
 
514
 
    :param path_encoding: If set, the path will be encoded as specified,
 
515
 
        otherwise is supposed to be utf8
 
516
 
    :param format_cls: Formatter class (DiffTree subclass)
 
 
234
        If set, include only changes to these files.
 
 
236
    external_diff_options
 
 
237
        If set, use an external GNU diff and pass these options.
 
519
 
        context = DEFAULT_CONTEXT_AMOUNT
 
520
 
    if format_cls is None:
 
521
 
        format_cls = DiffTree
 
522
 
    with cleanup.ExitStack() as exit_stack:
 
523
 
        exit_stack.enter_context(old_tree.lock_read())
 
524
 
        if extra_trees is not None:
 
525
 
            for tree in extra_trees:
 
526
 
                exit_stack.enter_context(tree.lock_read())
 
527
 
        exit_stack.enter_context(new_tree.lock_read())
 
528
 
        differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
 
530
 
                                               external_diff_options,
 
531
 
                                               old_label, new_label, using,
 
532
 
                                               context_lines=context)
 
533
 
        return differ.show_diff(specific_files, extra_trees)
 
536
 
def _patch_header_date(tree, path):
 
537
 
    """Returns a timestamp suitable for use in a patch header."""
 
539
 
        mtime = tree.get_file_mtime(path)
 
540
 
    except FileTimestampUnavailable:
 
542
 
    return timestamp.format_patch_date(mtime)
 
545
 
def get_executable_change(old_is_x, new_is_x):
 
546
 
    descr = {True: b"+x", False: b"-x", None: b"??"}
 
547
 
    if old_is_x != new_is_x:
 
548
 
        return [b"%s to %s" % (descr[old_is_x], descr[new_is_x],)]
 
553
 
class DiffPath(object):
 
554
 
    """Base type for command object that compare files"""
 
556
 
    # The type or contents of the file were unsuitable for diffing
 
557
 
    CANNOT_DIFF = 'CANNOT_DIFF'
 
558
 
    # The file has changed in a semantic way
 
560
 
    # The file content may have changed, but there is no semantic change
 
561
 
    UNCHANGED = 'UNCHANGED'
 
563
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
 
566
 
        :param old_tree: The tree to show as the old tree in the comparison
 
567
 
        :param new_tree: The tree to show as new in the comparison
 
568
 
        :param to_file: The file to write comparison data to
 
569
 
        :param path_encoding: The character encoding to write paths in
 
571
 
        self.old_tree = old_tree
 
572
 
        self.new_tree = new_tree
 
573
 
        self.to_file = to_file
 
574
 
        self.path_encoding = path_encoding
 
580
 
    def from_diff_tree(klass, diff_tree):
 
581
 
        return klass(diff_tree.old_tree, diff_tree.new_tree,
 
582
 
                     diff_tree.to_file, diff_tree.path_encoding)
 
585
 
    def _diff_many(differs, old_path, new_path, old_kind, new_kind):
 
586
 
        for file_differ in differs:
 
587
 
            result = file_differ.diff(old_path, new_path, old_kind, new_kind)
 
588
 
            if result is not DiffPath.CANNOT_DIFF:
 
591
 
            return DiffPath.CANNOT_DIFF
 
594
 
class DiffKindChange(object):
 
595
 
    """Special differ for file kind changes.
 
597
 
    Represents kind change as deletion + creation.  Uses the other differs
 
601
 
    def __init__(self, differs):
 
602
 
        self.differs = differs
 
608
 
    def from_diff_tree(klass, diff_tree):
 
609
 
        return klass(diff_tree.differs)
 
611
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
612
 
        """Perform comparison
 
614
 
        :param old_path: Path of the file in the old tree
 
615
 
        :param new_path: Path of the file in the new tree
 
616
 
        :param old_kind: Old file-kind of the file
 
617
 
        :param new_kind: New file-kind of the file
 
619
 
        if None in (old_kind, new_kind):
 
620
 
            return DiffPath.CANNOT_DIFF
 
621
 
        result = DiffPath._diff_many(
 
622
 
            self.differs, old_path, new_path, old_kind, None)
 
623
 
        if result is DiffPath.CANNOT_DIFF:
 
625
 
        return DiffPath._diff_many(
 
626
 
            self.differs, old_path, new_path, None, new_kind)
 
629
 
class DiffTreeReference(DiffPath):
 
631
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
632
 
        """Perform comparison between two tree references.  (dummy)
 
635
 
        if 'tree-reference' not in (old_kind, new_kind):
 
636
 
            return self.CANNOT_DIFF
 
637
 
        if old_kind not in ('tree-reference', None):
 
638
 
            return self.CANNOT_DIFF
 
639
 
        if new_kind not in ('tree-reference', None):
 
640
 
            return self.CANNOT_DIFF
 
644
 
class DiffDirectory(DiffPath):
 
646
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
647
 
        """Perform comparison between two directories.  (dummy)
 
650
 
        if 'directory' not in (old_kind, new_kind):
 
651
 
            return self.CANNOT_DIFF
 
652
 
        if old_kind not in ('directory', None):
 
653
 
            return self.CANNOT_DIFF
 
654
 
        if new_kind not in ('directory', None):
 
655
 
            return self.CANNOT_DIFF
 
659
 
class DiffSymlink(DiffPath):
 
661
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
662
 
        """Perform comparison between two symlinks
 
664
 
        :param old_path: Path of the file in the old tree
 
665
 
        :param new_path: Path of the file in the new tree
 
666
 
        :param old_kind: Old file-kind of the file
 
667
 
        :param new_kind: New file-kind of the file
 
669
 
        if 'symlink' not in (old_kind, new_kind):
 
670
 
            return self.CANNOT_DIFF
 
671
 
        if old_kind == 'symlink':
 
672
 
            old_target = self.old_tree.get_symlink_target(old_path)
 
673
 
        elif old_kind is None:
 
676
 
            return self.CANNOT_DIFF
 
677
 
        if new_kind == 'symlink':
 
678
 
            new_target = self.new_tree.get_symlink_target(new_path)
 
679
 
        elif new_kind is None:
 
682
 
            return self.CANNOT_DIFF
 
683
 
        return self.diff_symlink(old_target, new_target)
 
685
 
    def diff_symlink(self, old_target, new_target):
 
686
 
        if old_target is None:
 
687
 
            self.to_file.write(b'=== target is \'%s\'\n' %
 
688
 
                               new_target.encode(self.path_encoding, 'replace'))
 
689
 
        elif new_target is None:
 
690
 
            self.to_file.write(b'=== target was \'%s\'\n' %
 
691
 
                               old_target.encode(self.path_encoding, 'replace'))
 
693
 
            self.to_file.write(b'=== target changed \'%s\' => \'%s\'\n' %
 
694
 
                               (old_target.encode(self.path_encoding, 'replace'),
 
695
 
                                new_target.encode(self.path_encoding, 'replace')))
 
699
 
class DiffText(DiffPath):
 
701
 
    # GNU Patch uses the epoch date to detect files that are being added
 
702
 
    # or removed in a diff.
 
703
 
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
 
705
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
 
706
 
                 old_label='', new_label='', text_differ=internal_diff,
 
707
 
                 context_lines=DEFAULT_CONTEXT_AMOUNT):
 
708
 
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
 
709
 
        self.text_differ = text_differ
 
710
 
        self.old_label = old_label
 
711
 
        self.new_label = new_label
 
712
 
        self.path_encoding = path_encoding
 
713
 
        self.context_lines = context_lines
 
715
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
716
 
        """Compare two files in unified diff format
 
718
 
        :param old_path: Path of the file in the old tree
 
719
 
        :param new_path: Path of the file in the new tree
 
720
 
        :param old_kind: Old file-kind of the file
 
721
 
        :param new_kind: New file-kind of the file
 
723
 
        if 'file' not in (old_kind, new_kind):
 
724
 
            return self.CANNOT_DIFF
 
725
 
        if old_kind == 'file':
 
726
 
            old_date = _patch_header_date(self.old_tree, old_path)
 
727
 
        elif old_kind is None:
 
728
 
            old_date = self.EPOCH_DATE
 
730
 
            return self.CANNOT_DIFF
 
731
 
        if new_kind == 'file':
 
732
 
            new_date = _patch_header_date(self.new_tree, new_path)
 
733
 
        elif new_kind is None:
 
734
 
            new_date = self.EPOCH_DATE
 
736
 
            return self.CANNOT_DIFF
 
737
 
        from_label = '%s%s\t%s' % (self.old_label, old_path,
 
739
 
        to_label = '%s%s\t%s' % (self.new_label, new_path,
 
741
 
        return self.diff_text(old_path, new_path, from_label, to_label)
 
743
 
    def diff_text(self, from_path, to_path, from_label, to_label):
 
744
 
        """Diff the content of given files in two trees
 
746
 
        :param from_path: The path in the from tree. If None,
 
747
 
            the file is not present in the from tree.
 
748
 
        :param to_path: The path in the to tree. This may refer
 
749
 
            to a different file from from_path.  If None,
 
750
 
            the file is not present in the to tree.
 
752
 
        def _get_text(tree, path):
 
756
 
                return tree.get_file_lines(path)
 
757
 
            except errors.NoSuchFile:
 
760
 
            from_text = _get_text(self.old_tree, from_path)
 
761
 
            to_text = _get_text(self.new_tree, to_path)
 
762
 
            self.text_differ(from_label, from_text, to_label, to_text,
 
763
 
                             self.to_file, path_encoding=self.path_encoding,
 
764
 
                             context_lines=self.context_lines)
 
765
 
        except errors.BinaryFile:
 
767
 
                ("Binary files %s%s and %s%s differ\n" %
 
768
 
                 (self.old_label, from_path, self.new_label, to_path)).encode(self.path_encoding, 'replace'))
 
772
 
class DiffFromTool(DiffPath):
 
774
 
    def __init__(self, command_template, old_tree, new_tree, to_file,
 
775
 
                 path_encoding='utf-8'):
 
776
 
        DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
 
777
 
        self.command_template = command_template
 
778
 
        self._root = osutils.mkdtemp(prefix='brz-diff-')
 
781
 
    def from_string(klass, command_template, old_tree, new_tree, to_file,
 
782
 
                    path_encoding='utf-8'):
 
783
 
        return klass(command_template, old_tree, new_tree, to_file,
 
787
 
    def make_from_diff_tree(klass, command_string, external_diff_options=None):
 
788
 
        def from_diff_tree(diff_tree):
 
789
 
            full_command_string = [command_string]
 
790
 
            if external_diff_options is not None:
 
791
 
                full_command_string += ' ' + external_diff_options
 
792
 
            return klass.from_string(full_command_string, diff_tree.old_tree,
 
793
 
                                     diff_tree.new_tree, diff_tree.to_file)
 
794
 
        return from_diff_tree
 
796
 
    def _get_command(self, old_path, new_path):
 
797
 
        my_map = {'old_path': old_path, 'new_path': new_path}
 
798
 
        command = [t.format(**my_map) for t in
 
799
 
                   self.command_template]
 
800
 
        if command == self.command_template:
 
801
 
            command += [old_path, new_path]
 
802
 
        if sys.platform == 'win32':  # Popen doesn't accept unicode on win32
 
805
 
                if isinstance(c, text_type):
 
806
 
                    command_encoded.append(c.encode('mbcs'))
 
808
 
                    command_encoded.append(c)
 
809
 
            return command_encoded
 
813
 
    def _execute(self, old_path, new_path):
 
814
 
        command = self._get_command(old_path, new_path)
 
816
 
            proc = subprocess.Popen(command, stdout=subprocess.PIPE,
 
819
 
            if e.errno == errno.ENOENT:
 
820
 
                raise errors.ExecutableMissing(command[0])
 
823
 
        self.to_file.write(proc.stdout.read())
 
827
 
    def _try_symlink_root(self, tree, prefix):
 
828
 
        if (getattr(tree, 'abspath', None) is None or
 
829
 
                not osutils.host_os_dereferences_symlinks()):
 
832
 
            os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
 
834
 
            if e.errno != errno.EEXIST:
 
840
 
        """Returns safe encoding for passing file path to diff tool"""
 
841
 
        if sys.platform == 'win32':
 
844
 
            # Don't fallback to 'utf-8' because subprocess may not be able to
 
845
 
            # handle utf-8 correctly when locale is not utf-8.
 
846
 
            return sys.getfilesystemencoding() or 'ascii'
 
848
 
    def _is_safepath(self, path):
 
849
 
        """Return true if `path` may be able to pass to subprocess."""
 
852
 
            return path == path.encode(fenc).decode(fenc)
 
856
 
    def _safe_filename(self, prefix, relpath):
 
857
 
        """Replace unsafe character in `relpath` then join `self._root`,
 
858
 
        `prefix` and `relpath`."""
 
860
 
        # encoded_str.replace('?', '_') may break multibyte char.
 
861
 
        # So we should encode, decode, then replace(u'?', u'_')
 
862
 
        relpath_tmp = relpath.encode(fenc, 'replace').decode(fenc, 'replace')
 
863
 
        relpath_tmp = relpath_tmp.replace(u'?', u'_')
 
864
 
        return osutils.pathjoin(self._root, prefix, relpath_tmp)
 
866
 
    def _write_file(self, relpath, tree, prefix, force_temp=False,
 
868
 
        if not force_temp and isinstance(tree, WorkingTree):
 
869
 
            full_path = tree.abspath(relpath)
 
870
 
            if self._is_safepath(full_path):
 
873
 
        full_path = self._safe_filename(prefix, relpath)
 
874
 
        if not force_temp and self._try_symlink_root(tree, prefix):
 
876
 
        parent_dir = osutils.dirname(full_path)
 
878
 
            os.makedirs(parent_dir)
 
880
 
            if e.errno != errno.EEXIST:
 
882
 
        with tree.get_file(relpath) as source, \
 
883
 
                open(full_path, 'wb') as target:
 
884
 
            osutils.pumpfile(source, target)
 
886
 
            mtime = tree.get_file_mtime(relpath)
 
887
 
        except FileTimestampUnavailable:
 
890
 
            os.utime(full_path, (mtime, mtime))
 
892
 
            osutils.make_readonly(full_path)
 
895
 
    def _prepare_files(self, old_path, new_path, force_temp=False,
 
896
 
                       allow_write_new=False):
 
897
 
        old_disk_path = self._write_file(
 
898
 
            old_path, self.old_tree, 'old', force_temp)
 
899
 
        new_disk_path = self._write_file(
 
900
 
            new_path, self.new_tree, 'new', force_temp,
 
901
 
            allow_write=allow_write_new)
 
902
 
        return old_disk_path, new_disk_path
 
906
 
            osutils.rmtree(self._root)
 
908
 
            if e.errno != errno.ENOENT:
 
909
 
                mutter("The temporary directory \"%s\" was not "
 
910
 
                       "cleanly removed: %s." % (self._root, e))
 
912
 
    def diff(self, old_path, new_path, old_kind, new_kind):
 
913
 
        if (old_kind, new_kind) != ('file', 'file'):
 
914
 
            return DiffPath.CANNOT_DIFF
 
915
 
        (old_disk_path, new_disk_path) = self._prepare_files(
 
917
 
        self._execute(old_disk_path, new_disk_path)
 
919
 
    def edit_file(self, old_path, new_path):
 
920
 
        """Use this tool to edit a file.
 
922
 
        A temporary copy will be edited, and the new contents will be
 
925
 
        :return: The new contents of the file.
 
927
 
        old_abs_path, new_abs_path = self._prepare_files(
 
928
 
            old_path, new_path, allow_write_new=True, force_temp=True)
 
929
 
        command = self._get_command(old_abs_path, new_abs_path)
 
930
 
        subprocess.call(command, cwd=self._root)
 
931
 
        with open(new_abs_path, 'rb') as new_file:
 
932
 
            return new_file.read()
 
935
 
class DiffTree(object):
 
936
 
    """Provides textual representations of the difference between two trees.
 
938
 
    A DiffTree examines two trees and where a file-id has altered
 
939
 
    between them, generates a textual representation of the difference.
 
940
 
    DiffTree uses a sequence of DiffPath objects which are each
 
941
 
    given the opportunity to handle a given altered fileid. The list
 
942
 
    of DiffPath objects can be extended globally by appending to
 
943
 
    DiffTree.diff_factories, or for a specific diff operation by
 
944
 
    supplying the extra_factories option to the appropriate method.
 
947
 
    # list of factories that can provide instances of DiffPath objects
 
948
 
    # may be extended by plugins.
 
949
 
    diff_factories = [DiffSymlink.from_diff_tree,
 
950
 
                      DiffDirectory.from_diff_tree,
 
951
 
                      DiffTreeReference.from_diff_tree]
 
953
 
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
 
954
 
                 diff_text=None, extra_factories=None):
 
957
 
        :param old_tree: Tree to show as old in the comparison
 
958
 
        :param new_tree: Tree to show as new in the comparison
 
959
 
        :param to_file: File to write comparision to
 
960
 
        :param path_encoding: Character encoding to write paths in
 
961
 
        :param diff_text: DiffPath-type object to use as a last resort for
 
963
 
        :param extra_factories: Factories of DiffPaths to try before any other
 
965
 
        if diff_text is None:
 
966
 
            diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
 
967
 
                                 '', '', internal_diff)
 
968
 
        self.old_tree = old_tree
 
969
 
        self.new_tree = new_tree
 
970
 
        self.to_file = to_file
 
971
 
        self.path_encoding = path_encoding
 
973
 
        if extra_factories is not None:
 
974
 
            self.differs.extend(f(self) for f in extra_factories)
 
975
 
        self.differs.extend(f(self) for f in self.diff_factories)
 
976
 
        self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
 
979
 
    def from_trees_options(klass, old_tree, new_tree, to_file,
 
980
 
                           path_encoding, external_diff_options, old_label,
 
981
 
                           new_label, using, context_lines):
 
982
 
        """Factory for producing a DiffTree.
 
984
 
        Designed to accept options used by show_diff_trees.
 
986
 
        :param old_tree: The tree to show as old in the comparison
 
987
 
        :param new_tree: The tree to show as new in the comparison
 
988
 
        :param to_file: File to write comparisons to
 
989
 
        :param path_encoding: Character encoding to use for writing paths
 
990
 
        :param external_diff_options: If supplied, use the installed diff
 
991
 
            binary to perform file comparison, using supplied options.
 
992
 
        :param old_label: Prefix to use for old file labels
 
993
 
        :param new_label: Prefix to use for new file labels
 
994
 
        :param using: Commandline to use to invoke an external diff tool
 
996
 
        if using is not None:
 
997
 
            extra_factories = [DiffFromTool.make_from_diff_tree(
 
998
 
                using, external_diff_options)]
 
1000
 
            extra_factories = []
 
1001
 
        if external_diff_options:
 
1002
 
            opts = external_diff_options.split()
 
1004
 
            def diff_file(olab, olines, nlab, nlines, to_file, path_encoding=None, context_lines=None):
 
1005
 
                """:param path_encoding: not used but required
 
1006
 
                        to match the signature of internal_diff.
 
1008
 
                external_diff(olab, olines, nlab, nlines, to_file, opts)
 
1010
 
            diff_file = internal_diff
 
1011
 
        diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
 
1012
 
                             old_label, new_label, diff_file, context_lines=context_lines)
 
1013
 
        return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
 
1016
 
    def show_diff(self, specific_files, extra_trees=None):
 
1017
 
        """Write tree diff to self.to_file
 
1019
 
        :param specific_files: the specific files to compare (recursive)
 
1020
 
        :param extra_trees: extra trees to use for mapping paths to file_ids
 
1023
 
            return self._show_diff(specific_files, extra_trees)
 
 
243
            return _show_diff_trees(old_tree, new_tree, to_file,
 
 
244
                                    specific_files, external_diff_options)
 
1025
 
            for differ in self.differs:
 
1028
 
    def _show_diff(self, specific_files, extra_trees):
 
1029
 
        # TODO: Generation of pseudo-diffs for added/deleted files could
 
1030
 
        # be usefully made into a much faster special case.
 
1031
 
        iterator = self.new_tree.iter_changes(self.old_tree,
 
1032
 
                                              specific_files=specific_files,
 
1033
 
                                              extra_trees=extra_trees,
 
1034
 
                                              require_versioned=True)
 
1037
 
        def changes_key(change):
 
1038
 
            old_path, new_path = change.path
 
1044
 
        def get_encoded_path(path):
 
1045
 
            if path is not None:
 
1046
 
                return path.encode(self.path_encoding, "replace")
 
1047
 
        for change in sorted(iterator, key=changes_key):
 
1048
 
            # The root does not get diffed, and items with no known kind (that
 
1049
 
            # is, missing) in both trees are skipped as well.
 
1050
 
            if change.parent_id == (None, None) or change.kind == (None, None):
 
1052
 
            if change.kind[0] == 'symlink' and not self.new_tree.supports_symlinks():
 
1054
 
                    'Ignoring "%s" as symlinks are not '
 
1055
 
                    'supported on this filesystem.' % (change.path[0],))
 
1057
 
            oldpath, newpath = change.path
 
1058
 
            oldpath_encoded = get_encoded_path(change.path[0])
 
1059
 
            newpath_encoded = get_encoded_path(change.path[1])
 
1060
 
            old_present = (change.kind[0] is not None and change.versioned[0])
 
1061
 
            new_present = (change.kind[1] is not None and change.versioned[1])
 
1062
 
            executable = change.executable
 
1064
 
            renamed = (change.parent_id[0], change.name[0]) != (change.parent_id[1], change.name[1])
 
1066
 
            properties_changed = []
 
1067
 
            properties_changed.extend(
 
1068
 
                get_executable_change(executable[0], executable[1]))
 
1070
 
            if properties_changed:
 
1071
 
                prop_str = b" (properties changed: %s)" % (
 
1072
 
                    b", ".join(properties_changed),)
 
1076
 
            if (old_present, new_present) == (True, False):
 
1077
 
                self.to_file.write(b"=== removed %s '%s'\n" %
 
1078
 
                                   (kind[0].encode('ascii'), oldpath_encoded))
 
1080
 
            elif (old_present, new_present) == (False, True):
 
1081
 
                self.to_file.write(b"=== added %s '%s'\n" %
 
1082
 
                                   (kind[1].encode('ascii'), newpath_encoded))
 
1085
 
                self.to_file.write(b"=== renamed %s '%s' => '%s'%s\n" %
 
1086
 
                                   (kind[0].encode('ascii'), oldpath_encoded, newpath_encoded, prop_str))
 
1088
 
                # if it was produced by iter_changes, it must be
 
1089
 
                # modified *somehow*, either content or execute bit.
 
1090
 
                self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
 
1091
 
                                                                  newpath_encoded, prop_str))
 
1092
 
            if change.changed_content:
 
1093
 
                self._diff(oldpath, newpath, kind[0], kind[1])
 
1099
 
    def diff(self, old_path, new_path):
 
1100
 
        """Perform a diff of a single file
 
1102
 
        :param old_path: The path of the file in the old tree
 
1103
 
        :param new_path: The path of the file in the new tree
 
1105
 
        if old_path is None:
 
1108
 
            old_kind = self.old_tree.kind(old_path)
 
1109
 
        if new_path is None:
 
1112
 
            new_kind = self.new_tree.kind(new_path)
 
1113
 
        self._diff(old_path, new_path, old_kind, new_kind)
 
1115
 
    def _diff(self, old_path, new_path, old_kind, new_kind):
 
1116
 
        result = DiffPath._diff_many(
 
1117
 
            self.differs, old_path, new_path, old_kind, new_kind)
 
1118
 
        if result is DiffPath.CANNOT_DIFF:
 
1119
 
            error_path = new_path
 
1120
 
            if error_path is None:
 
1121
 
                error_path = old_path
 
1122
 
            raise errors.NoDiffFound(error_path)
 
1125
 
format_registry = Registry()
 
1126
 
format_registry.register('default', DiffTree)
 
 
251
def _show_diff_trees(old_tree, new_tree, to_file,
 
 
252
                     specific_files, external_diff_options):
 
 
254
    # TODO: Options to control putting on a prefix or suffix, perhaps
 
 
255
    # as a format string?
 
 
259
    DEVNULL = '/dev/null'
 
 
260
    # Windows users, don't panic about this filename -- it is a
 
 
261
    # special signal to GNU patch that the file should be created or
 
 
262
    # deleted respectively.
 
 
264
    # TODO: Generation of pseudo-diffs for added/deleted files could
 
 
265
    # be usefully made into a much faster special case.
 
 
267
    _raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
 
 
269
    if external_diff_options:
 
 
270
        assert isinstance(external_diff_options, basestring)
 
 
271
        opts = external_diff_options.split()
 
 
272
        def diff_file(olab, olines, nlab, nlines, to_file):
 
 
273
            external_diff(olab, olines, nlab, nlines, to_file, opts)
 
 
275
        diff_file = internal_diff
 
 
277
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
 
 
278
                          specific_files=specific_files)
 
 
281
    for path, file_id, kind in delta.removed:
 
 
283
        print >>to_file, '=== removed %s %r' % (kind, old_label + path)
 
 
284
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
 
 
285
                                         DEVNULL, None, None, to_file)
 
 
286
    for path, file_id, kind in delta.added:
 
 
288
        print >>to_file, '=== added %s %r' % (kind, new_label + path)
 
 
289
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
 
 
290
                                         DEVNULL, None, None, to_file, 
 
 
292
    for (old_path, new_path, file_id, kind,
 
 
293
         text_modified, meta_modified) in delta.renamed:
 
 
295
        prop_str = get_prop_change(meta_modified)
 
 
296
        print >>to_file, '=== renamed %s %r => %r%s' % (
 
 
297
                    kind, old_label + old_path, new_label + new_path, prop_str)
 
 
298
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
 
 
299
                                    new_label, new_path, new_tree,
 
 
300
                                    text_modified, kind, to_file, diff_file)
 
 
301
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
 
 
303
        prop_str = get_prop_change(meta_modified)
 
 
304
        print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
 
 
307
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
 
 
308
                                        new_label, path, new_tree,
 
 
309
                                        True, kind, to_file, diff_file)
 
 
314
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
 
 
315
    """Complain if paths are not versioned in either tree."""
 
 
316
    if not specific_files:
 
 
318
    old_unversioned = old_tree.filter_unversioned_files(specific_files)
 
 
319
    new_unversioned = new_tree.filter_unversioned_files(specific_files)
 
 
320
    unversioned = old_unversioned.intersection(new_unversioned)
 
 
322
        raise errors.PathsNotVersionedError(sorted(unversioned))
 
 
325
def get_prop_change(meta_modified):
 
 
327
        return " (properties changed)"
 
 
332
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
 
 
333
                                new_label, new_path, new_tree, text_modified,
 
 
334
                                kind, to_file, diff_file):
 
 
336
        new_entry = new_tree.inventory[file_id]
 
 
337
        old_tree.inventory[file_id].diff(diff_file,
 
 
338
                                         old_label + old_path, old_tree,
 
 
339
                                         new_label + new_path, new_entry,