/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/diff.py

  • Committer: Jelmer Vernooij
  • Date: 2019-06-03 23:48:08 UTC
  • mfrom: (7316 work)
  • mto: This revision was merged to the branch mainline in revision 7328.
  • Revision ID: jelmer@jelmer.uk-20190603234808-15yk5c7054tj8e2b
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import difflib
20
20
import os
21
21
import re
 
22
import string
22
23
import sys
23
24
 
24
25
from .lazy_import import lazy_import
29
30
import tempfile
30
31
 
31
32
from breezy import (
32
 
    cleanup,
 
33
    cmdline,
33
34
    controldir,
 
35
    errors,
34
36
    osutils,
35
37
    textfile,
36
38
    timestamp,
41
43
from breezy.i18n import gettext
42
44
""")
43
45
 
44
 
from . import (
45
 
    errors,
46
 
    )
47
46
from .registry import (
48
47
    Registry,
49
48
    )
55
54
DEFAULT_CONTEXT_AMOUNT = 3
56
55
 
57
56
 
 
57
class AtTemplate(string.Template):
 
58
    """Templating class that uses @ instead of $."""
 
59
 
 
60
    delimiter = '@'
 
61
 
 
62
 
58
63
# TODO: Rather than building a changeset object, we should probably
59
64
# invoke callbacks on an object.  That object can either accumulate a
60
65
# list, write them out directly, etc etc.
365
370
 
366
371
 
367
372
def get_trees_and_branches_to_diff_locked(
368
 
        path_list, revision_specs, old_url, new_url, exit_stack, apply_view=True):
 
373
        path_list, revision_specs, old_url, new_url, add_cleanup, apply_view=True):
369
374
    """Get the trees and specific files to diff given a list of paths.
370
375
 
371
376
    This method works out the trees to be diff'ed and the files of
382
387
    :param new_url:
383
388
        The url of the new branch or tree. If None, the tree to use is
384
389
        taken from the first path, if any, or the current working tree.
385
 
    :param exit_stack:
386
 
        an ExitStack object. get_trees_and_branches_to_diff
 
390
    :param add_cleanup:
 
391
        a callable like Command.add_cleanup.  get_trees_and_branches_to_diff
387
392
        will register cleanups that must be run to unlock the trees, etc.
388
393
    :param apply_view:
389
394
        if True and a view is set, apply the view or check that the paths
392
397
        a tuple of (old_tree, new_tree, old_branch, new_branch,
393
398
        specific_files, extra_trees) where extra_trees is a sequence of
394
399
        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
 
400
        will be read-locked until the cleanups registered via the add_cleanup
396
401
        param are run.
397
402
    """
398
403
    # Get the old and new revision specs
424
429
 
425
430
    def lock_tree_or_branch(wt, br):
426
431
        if wt is not None:
427
 
            exit_stack.enter_context(wt.lock_read())
 
432
            wt.lock_read()
 
433
            add_cleanup(wt.unlock)
428
434
        elif br is not None:
429
 
            exit_stack.enter_context(br.lock_read())
 
435
            br.lock_read()
 
436
            add_cleanup(br.unlock)
430
437
 
431
438
    # Get the old location
432
439
    specific_files = []
519
526
        context = DEFAULT_CONTEXT_AMOUNT
520
527
    if format_cls is None:
521
528
        format_cls = DiffTree
522
 
    with cleanup.ExitStack() as exit_stack:
523
 
        exit_stack.enter_context(old_tree.lock_read())
 
529
    with old_tree.lock_read():
524
530
        if extra_trees is not None:
525
531
            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,
529
 
                                               path_encoding,
530
 
                                               external_diff_options,
531
 
                                               old_label, new_label, using,
532
 
                                               context_lines=context)
533
 
        return differ.show_diff(specific_files, extra_trees)
 
532
                tree.lock_read()
 
533
        new_tree.lock_read()
 
534
        try:
 
535
            differ = format_cls.from_trees_options(old_tree, new_tree, to_file,
 
536
                                                   path_encoding,
 
537
                                                   external_diff_options,
 
538
                                                   old_label, new_label, using,
 
539
                                                   context_lines=context)
 
540
            return differ.show_diff(specific_files, extra_trees)
 
541
        finally:
 
542
            new_tree.unlock()
 
543
            if extra_trees is not None:
 
544
                for tree in extra_trees:
 
545
                    tree.unlock()
534
546
 
535
547
 
536
548
def _patch_header_date(tree, path):
626
638
            self.differs, old_path, new_path, None, new_kind)
627
639
 
628
640
 
629
 
class DiffTreeReference(DiffPath):
630
 
 
631
 
    def diff(self, old_path, new_path, old_kind, new_kind):
632
 
        """Perform comparison between two tree references.  (dummy)
633
 
 
634
 
        """
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
641
 
        return self.CHANGED
642
 
 
643
 
 
644
641
class DiffDirectory(DiffPath):
645
642
 
646
643
    def diff(self, old_path, new_path, old_kind, new_kind):
764
761
                             context_lines=self.context_lines)
765
762
        except errors.BinaryFile:
766
763
            self.to_file.write(
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'))
 
764
                ("Binary files %s and %s differ\n" %
 
765
                 (from_label, to_label)).encode(self.path_encoding, 'replace'))
769
766
        return self.CHANGED
770
767
 
771
768
 
778
775
        self._root = osutils.mkdtemp(prefix='brz-diff-')
779
776
 
780
777
    @classmethod
781
 
    def from_string(klass, command_template, old_tree, new_tree, to_file,
 
778
    def from_string(klass, command_string, old_tree, new_tree, to_file,
782
779
                    path_encoding='utf-8'):
 
780
        command_template = cmdline.split(command_string)
 
781
        if '@' not in command_string:
 
782
            command_template.extend(['@old_path', '@new_path'])
783
783
        return klass(command_template, old_tree, new_tree, to_file,
784
784
                     path_encoding)
785
785
 
795
795
 
796
796
    def _get_command(self, old_path, new_path):
797
797
        my_map = {'old_path': old_path, 'new_path': new_path}
798
 
        command = [t.format(**my_map) for t in
 
798
        command = [AtTemplate(t).substitute(my_map) for t in
799
799
                   self.command_template]
800
 
        if command == self.command_template:
801
 
            command += [old_path, new_path]
802
800
        if sys.platform == 'win32':  # Popen doesn't accept unicode on win32
803
801
            command_encoded = []
804
802
            for c in command:
879
877
        except OSError as e:
880
878
            if e.errno != errno.EEXIST:
881
879
                raise
882
 
        with tree.get_file(relpath) as source, \
883
 
                open(full_path, 'wb') as target:
884
 
            osutils.pumpfile(source, target)
 
880
        source = tree.get_file(relpath)
 
881
        try:
 
882
            with open(full_path, 'wb') as target:
 
883
                osutils.pumpfile(source, target)
 
884
        finally:
 
885
            source.close()
885
886
        try:
886
887
            mtime = tree.get_file_mtime(relpath)
887
888
        except FileTimestampUnavailable:
947
948
    # list of factories that can provide instances of DiffPath objects
948
949
    # may be extended by plugins.
949
950
    diff_factories = [DiffSymlink.from_diff_tree,
950
 
                      DiffDirectory.from_diff_tree,
951
 
                      DiffTreeReference.from_diff_tree]
 
951
                      DiffDirectory.from_diff_tree]
952
952
 
953
953
    def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
954
954
                 diff_text=None, extra_factories=None):
1035
1035
        has_changes = 0
1036
1036
 
1037
1037
        def changes_key(change):
1038
 
            old_path, new_path = change.path
 
1038
            old_path, new_path = change[1]
1039
1039
            path = new_path
1040
1040
            if path is None:
1041
1041
                path = old_path
1044
1044
        def get_encoded_path(path):
1045
1045
            if path is not None:
1046
1046
                return path.encode(self.path_encoding, "replace")
1047
 
        for change in sorted(iterator, key=changes_key):
 
1047
        for (file_id, paths, changed_content, versioned, parent, name, kind,
 
1048
             executable) in sorted(iterator, key=changes_key):
1048
1049
            # The root does not get diffed, and items with no known kind (that
1049
1050
            # is, missing) in both trees are skipped as well.
1050
 
            if change.parent_id == (None, None) or change.kind == (None, None):
1051
 
                continue
1052
 
            if change.kind[0] == 'symlink' and not self.new_tree.supports_symlinks():
1053
 
                warning(
1054
 
                    'Ignoring "%s" as symlinks are not '
1055
 
                    'supported on this filesystem.' % (change.path[0],))
1056
 
                continue
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
1063
 
            kind = change.kind
1064
 
            renamed = (change.parent_id[0], change.name[0]) != (change.parent_id[1], change.name[1])
 
1051
            if parent == (None, None) or kind == (None, None):
 
1052
                continue
 
1053
            oldpath, newpath = paths
 
1054
            oldpath_encoded = get_encoded_path(paths[0])
 
1055
            newpath_encoded = get_encoded_path(paths[1])
 
1056
            old_present = (kind[0] is not None and versioned[0])
 
1057
            new_present = (kind[1] is not None and versioned[1])
 
1058
            renamed = (parent[0], name[0]) != (parent[1], name[1])
1065
1059
 
1066
1060
            properties_changed = []
1067
1061
            properties_changed.extend(
1089
1083
                # modified *somehow*, either content or execute bit.
1090
1084
                self.to_file.write(b"=== modified %s '%s'%s\n" % (kind[0].encode('ascii'),
1091
1085
                                                                  newpath_encoded, prop_str))
1092
 
            if change.changed_content:
 
1086
            if changed_content:
1093
1087
                self._diff(oldpath, newpath, kind[0], kind[1])
1094
1088
                has_changes = 1
1095
1089
            if renamed: