/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/merge.py

  • Committer: Jelmer Vernooij
  • Date: 2019-06-02 02:35:46 UTC
  • mfrom: (7309 work)
  • mto: This revision was merged to the branch mainline in revision 7319.
  • Revision ID: jelmer@jelmer.uk-20190602023546-lqco868tnv26d8ow
merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
from __future__ import absolute_import
18
18
 
19
 
import warnings
20
 
 
21
19
from .lazy_import import lazy_import
22
20
lazy_import(globals(), """
 
21
import patiencediff
 
22
 
23
23
from breezy import (
24
24
    branch as _mod_branch,
25
25
    cleanup,
26
26
    conflicts as _mod_conflicts,
27
27
    debug,
28
 
    generate_ids,
29
28
    graph as _mod_graph,
30
29
    merge3,
31
30
    osutils,
32
 
    patiencediff,
33
31
    revision as _mod_revision,
34
32
    textfile,
35
33
    trace,
40
38
    workingtree,
41
39
    )
42
40
from breezy.bzr import (
 
41
    generate_ids,
43
42
    versionedfile,
44
43
    )
45
44
from breezy.i18n import gettext
51
50
    registry,
52
51
    )
53
52
from .sixish import (
54
 
    text_type,
55
53
    viewitems,
56
54
    )
57
55
# TODO: Report back as changes are merged in
60
58
def transform_tree(from_tree, to_tree, interesting_files=None):
61
59
    with from_tree.lock_tree_write():
62
60
        merge_inner(from_tree.branch, to_tree, from_tree,
63
 
            ignore_zero=True, this_tree=from_tree,
64
 
            interesting_files=interesting_files)
 
61
                    ignore_zero=True, this_tree=from_tree,
 
62
                    interesting_files=interesting_files)
65
63
 
66
64
 
67
65
class MergeHooks(hooks.Hooks):
69
67
    def __init__(self):
70
68
        hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
71
69
        self.add_hook('merge_file_content',
72
 
            "Called with a breezy.merge.Merger object to create a per file "
73
 
            "merge object when starting a merge. "
74
 
            "Should return either None or a subclass of "
75
 
            "``breezy.merge.AbstractPerFileMerger``. "
76
 
            "Such objects will then be called per file "
77
 
            "that needs to be merged (including when one "
78
 
            "side has deleted the file and the other has changed it). "
79
 
            "See the AbstractPerFileMerger API docs for details on how it is "
80
 
            "used by merge.",
81
 
            (2, 1))
 
70
                      "Called with a breezy.merge.Merger object to create a per file "
 
71
                      "merge object when starting a merge. "
 
72
                      "Should return either None or a subclass of "
 
73
                      "``breezy.merge.AbstractPerFileMerger``. "
 
74
                      "Such objects will then be called per file "
 
75
                      "that needs to be merged (including when one "
 
76
                      "side has deleted the file and the other has changed it). "
 
77
                      "See the AbstractPerFileMerger API docs for details on how it is "
 
78
                      "used by merge.",
 
79
                      (2, 1))
82
80
        self.add_hook('pre_merge',
83
 
            'Called before a merge. '
84
 
            'Receives a Merger object as the single argument.',
85
 
            (2, 5))
 
81
                      'Called before a merge. '
 
82
                      'Receives a Merger object as the single argument.',
 
83
                      (2, 5))
86
84
        self.add_hook('post_merge',
87
 
            'Called after a merge. '
88
 
            'Receives a Merger object as the single argument. '
89
 
            'The return value is ignored.',
90
 
            (2, 5))
 
85
                      'Called after a merge. '
 
86
                      'Receives a Merger object as the single argument. '
 
87
                      'The return value is ignored.',
 
88
                      (2, 5))
91
89
 
92
90
 
93
91
class AbstractPerFileMerger(object):
139
137
            # THIS and OTHER aren't both files.
140
138
            not params.is_file_merge() or
141
139
            # The filename doesn't match
142
 
            not self.file_matches(params)):
 
140
                not self.file_matches(params)):
143
141
            return 'not_applicable', None
144
142
        return self.merge_matching(params)
145
143
 
236
234
    """
237
235
 
238
236
    def __init__(self, merger, file_id, paths, trans_id, this_kind, other_kind,
239
 
            winner):
 
237
                 winner):
240
238
        self._merger = merger
241
239
        self.file_id = file_id
242
240
        self.paths = paths
253
251
    @decorators.cachedproperty
254
252
    def base_lines(self):
255
253
        """The lines of the 'base' version of the file."""
256
 
        return self._merger.get_lines(self._merger.base_tree, self.base_path, self.file_id)
 
254
        return self._merger.get_lines(self._merger.base_tree, self.base_path)
257
255
 
258
256
    @decorators.cachedproperty
259
257
    def this_lines(self):
260
258
        """The lines of the 'this' version of the file."""
261
 
        return self._merger.get_lines(self._merger.this_tree, self.this_path, self.file_id)
 
259
        return self._merger.get_lines(self._merger.this_tree, self.this_path)
262
260
 
263
261
    @decorators.cachedproperty
264
262
    def other_lines(self):
265
263
        """The lines of the 'other' version of the file."""
266
 
        return self._merger.get_lines(self._merger.other_tree, self.other_path, self.file_id)
 
264
        return self._merger.get_lines(self._merger.other_tree, self.other_path)
267
265
 
268
266
 
269
267
class Merger(object):
372
370
        if base_revision_id is not None:
373
371
            if (base_revision_id != _mod_revision.NULL_REVISION and
374
372
                revision_graph.is_ancestor(
375
 
                base_revision_id, tree.branch.last_revision())):
 
373
                    base_revision_id, tree.branch.last_revision())):
376
374
                base_revision_id = None
377
375
            else:
378
376
                trace.warning('Performing cherrypick')
379
377
        merger = klass.from_revision_ids(tree, other_revision_id,
380
 
                                         base_revision_id, revision_graph=
381
 
                                         revision_graph)
 
378
                                         base_revision_id, revision_graph=revision_graph)
382
379
        return merger, verified
383
380
 
384
381
    @staticmethod
445
442
 
446
443
    def set_pending(self):
447
444
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
448
 
            or self.other_rev_id is None):
 
445
                or self.other_rev_id is None):
449
446
            return
450
447
        self._add_parent()
451
448
 
490
487
                raise errors.NoCommits(self.other_branch)
491
488
        if self.other_rev_id is not None:
492
489
            self._cached_trees[self.other_rev_id] = self.other_tree
493
 
        self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
 
490
        self._maybe_fetch(self.other_branch,
 
491
                          self.this_branch, self.other_basis)
494
492
 
495
493
    def set_other_revision(self, revision_id, other_branch):
496
494
        """Set 'other' based on a branch and revision id
533
531
                self.base_rev_id = _mod_revision.NULL_REVISION
534
532
            elif len(lcas) == 1:
535
533
                self.base_rev_id = list(lcas)[0]
536
 
            else: # len(lcas) > 1
 
534
            else:  # len(lcas) > 1
537
535
                self._is_criss_cross = True
538
536
                if len(lcas) > 2:
539
537
                    # find_unique_lca can only handle 2 nodes, so we have to
541
539
                    # the graph again, but better than re-implementing
542
540
                    # find_unique_lca.
543
541
                    self.base_rev_id = self.revision_graph.find_unique_lca(
544
 
                                            revisions[0], revisions[1])
 
542
                        revisions[0], revisions[1])
545
543
                else:
546
544
                    self.base_rev_id = self.revision_graph.find_unique_lca(
547
 
                                            *lcas)
 
545
                        *lcas)
548
546
                sorted_lca_keys = self.revision_graph.find_merge_order(
549
547
                    revisions[0], lcas)
550
548
                if self.base_rev_id == _mod_revision.NULL_REVISION:
563
561
                interesting_revision_ids = set(lcas)
564
562
                interesting_revision_ids.add(self.base_rev_id)
565
563
                interesting_trees = dict((t.get_revision_id(), t)
566
 
                    for t in self.this_branch.repository.revision_trees(
567
 
                        interesting_revision_ids))
 
564
                                         for t in self.this_branch.repository.revision_trees(
 
565
                    interesting_revision_ids))
568
566
                self._cached_trees.update(interesting_trees)
569
567
                if self.base_rev_id in lcas:
570
568
                    self.base_tree = interesting_trees[self.base_rev_id]
618
616
            raise errors.BzrError("Showing base is not supported for this"
619
617
                                  " merge type. %s" % self.merge_type)
620
618
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
621
 
            and not self.base_is_other_ancestor):
 
619
                and not self.base_is_other_ancestor):
622
620
            raise errors.CannotReverseCherrypick()
623
621
        if self.merge_type.supports_cherrypick:
624
622
            kwargs['cherrypick'] = (not self.base_is_ancestor or
640
638
            hook(merge)
641
639
        if self.recurse == 'down':
642
640
            for relpath, file_id in self.this_tree.iter_references():
643
 
                sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
 
641
                sub_tree = self.this_tree.get_nested_tree(relpath)
644
642
                other_revision = self.other_tree.get_reference_revision(
645
 
                    relpath, file_id)
646
 
                if  other_revision == sub_tree.last_revision():
 
643
                    relpath)
 
644
                if other_revision == sub_tree.last_revision():
647
645
                    continue
648
646
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
649
647
                sub_merge.merge_type = self.merge_type
653
651
                base_tree_path = _mod_tree.find_previous_path(
654
652
                    self.this_tree, self.base_tree, relpath)
655
653
                base_revision = self.base_tree.get_reference_revision(
656
 
                    base_tree_path, file_id)
 
654
                    base_tree_path)
657
655
                sub_merge.base_tree = \
658
656
                    sub_tree.branch.repository.revision_tree(base_revision)
659
657
                sub_merge.base_rev_id = base_revision
695
693
    symlink_target = None
696
694
    text_sha1 = None
697
695
 
 
696
 
698
697
_none_entry = _InventoryNoneEntry()
699
698
 
700
699
 
809
808
                # Try merging each entry
810
809
                child_pb.update(gettext('Preparing file merge'),
811
810
                                num, len(entries))
812
 
                self._merge_names(file_id, paths3, parents3, names3, resolver=resolver)
 
811
                self._merge_names(file_id, paths3, parents3,
 
812
                                  names3, resolver=resolver)
813
813
                if changed:
814
814
                    file_status = self._do_merge_contents(paths3, file_id)
815
815
                else:
816
816
                    file_status = 'unmodified'
817
817
                self._merge_executable(paths3, file_id, executable3,
818
 
                        file_status, resolver=resolver)
 
818
                                       file_status, resolver=resolver)
819
819
        self.tt.fixup_new_roots()
820
820
        self._finish_computing_transform()
821
821
 
826
826
        """
827
827
        with ui.ui_factory.nested_progress_bar() as child_pb:
828
828
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
829
 
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
 
829
                                                       lambda t, c: transform.conflict_pass(t, c, self.other_tree))
830
830
        if self.change_reporter is not None:
831
831
            from breezy import delta
832
832
            delta.report_changes(
846
846
        """
847
847
        result = []
848
848
        iterator = self.other_tree.iter_changes(self.base_tree,
849
 
                specific_files=self.interesting_files,
850
 
                extra_trees=[self.this_tree])
 
849
                                                specific_files=self.interesting_files,
 
850
                                                extra_trees=[self.this_tree])
851
851
        this_interesting_files = self.this_tree.find_related_paths_across_trees(
852
 
                self.interesting_files, trees=[self.other_tree])
 
852
            self.interesting_files, trees=[self.other_tree])
853
853
        this_entries = dict(self.this_tree.iter_entries_by_dir(
854
854
                            specific_files=this_interesting_files))
855
855
        for (file_id, paths, changed, versioned, parents, names, kind,
856
856
             executable) in iterator:
857
857
            if paths[0] is not None:
858
858
                this_path = _mod_tree.find_previous_path(
859
 
                        self.base_tree, self.this_tree, paths[0])
 
859
                    self.base_tree, self.this_tree, paths[0])
860
860
            else:
861
861
                this_path = _mod_tree.find_previous_path(
862
 
                        self.other_tree, self.this_tree, paths[1])
 
862
                    self.other_tree, self.this_tree, paths[1])
863
863
            this_entry = this_entries.get(this_path)
864
864
            if this_entry is not None:
865
865
                this_name = this_entry.name
873
873
            names3 = names + (this_name,)
874
874
            paths3 = paths + (this_path, )
875
875
            executable3 = executable + (this_executable,)
876
 
            result.append((file_id, changed, paths3, parents3, names3, executable3))
 
876
            result.append((file_id, changed, paths3,
 
877
                           parents3, names3, executable3))
877
878
        return result
878
879
 
879
880
    def _entries_lca(self):
900
901
            lookup_trees.extend(self._lca_trees)
901
902
            # I think we should include the lca trees as well
902
903
            interesting_files = self.other_tree.find_related_paths_across_trees(
903
 
                    self.interesting_files, lookup_trees)
 
904
                self.interesting_files, lookup_trees)
904
905
        else:
905
906
            interesting_files = None
906
907
        result = []
996
997
                        if path is None:
997
998
                            return None
998
999
                        try:
999
 
                            return tree.get_file_sha1(path, file_id)
 
1000
                            return tree.get_file_sha1(path)
1000
1001
                        except errors.NoSuchFile:
1001
1002
                            return None
1002
1003
                    base_sha1 = get_sha1(self.base_tree, base_path)
1012
1013
                        (base_ie.executable, lca_executable),
1013
1014
                        other_ie.executable, this_ie.executable)
1014
1015
                    if (parent_id_winner == 'this' and name_winner == 'this'
1015
 
                        and sha1_winner == 'this' and exec_winner == 'this'):
 
1016
                            and sha1_winner == 'this' and exec_winner == 'this'):
1016
1017
                        # No kind, parent, name, exec, or content change for
1017
1018
                        # OTHER, so this node is not considered interesting
1018
1019
                        continue
1022
1023
                    def get_target(ie, tree, path):
1023
1024
                        if ie.kind != 'symlink':
1024
1025
                            return None
1025
 
                        return tree.get_symlink_target(path, file_id)
 
1026
                        return tree.get_symlink_target(path)
1026
1027
                    base_target = get_target(base_ie, self.base_tree, base_path)
1027
1028
                    lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1028
1029
                                   in zip(lca_entries, self._lca_trees, lca_paths)]
1029
 
                    this_target = get_target(this_ie, self.this_tree, this_path)
1030
 
                    other_target = get_target(other_ie, self.other_tree, other_path)
 
1030
                    this_target = get_target(
 
1031
                        this_ie, self.this_tree, this_path)
 
1032
                    other_target = get_target(
 
1033
                        other_ie, self.other_tree, other_path)
1031
1034
                    target_winner = self._lca_multi_way(
1032
1035
                        (base_target, lca_targets),
1033
1036
                        other_target, this_target)
1034
1037
                    if (parent_id_winner == 'this' and name_winner == 'this'
1035
 
                        and target_winner == 'this'):
 
1038
                            and target_winner == 'this'):
1036
1039
                        # No kind, parent, name, or symlink target change
1037
1040
                        # not interesting
1038
1041
                        continue
1059
1062
                            other_ie.name, this_ie.name),
1060
1063
                           ((base_ie.executable, lca_executable),
1061
1064
                            other_ie.executable, this_ie.executable)
1062
 
                          ))
 
1065
                           ))
1063
1066
        return result
1064
1067
 
1065
1068
    def write_modified(self, results):
1071
1074
            file_id = self.working_tree.path2id(wt_relpath)
1072
1075
            if file_id is None:
1073
1076
                continue
1074
 
            hash = self.working_tree.get_file_sha1(wt_relpath, file_id)
 
1077
            hash = self.working_tree.get_file_sha1(wt_relpath)
1075
1078
            if hash is None:
1076
1079
                continue
1077
1080
            modified_hashes[file_id] = hash
1078
1081
        self.working_tree.set_merge_modified(modified_hashes)
1079
1082
 
1080
1083
    @staticmethod
1081
 
    def parent(entry, file_id):
 
1084
    def parent(entry):
1082
1085
        """Determine the parent for a file_id (used as a key method)"""
1083
1086
        if entry is None:
1084
1087
            return None
1085
1088
        return entry.parent_id
1086
1089
 
1087
1090
    @staticmethod
1088
 
    def name(entry, file_id):
 
1091
    def name(entry):
1089
1092
        """Determine the name for a file_id (used as a key method)"""
1090
1093
        if entry is None:
1091
1094
            return None
1092
1095
        return entry.name
1093
1096
 
1094
1097
    @staticmethod
1095
 
    def contents_sha1(tree, path, file_id=None):
 
1098
    def contents_sha1(tree, path):
1096
1099
        """Determine the sha1 of the file contents (used as a key method)."""
1097
1100
        try:
1098
 
            return tree.get_file_sha1(path, file_id)
 
1101
            return tree.get_file_sha1(path)
1099
1102
        except errors.NoSuchFile:
1100
1103
            return None
1101
1104
 
1102
1105
    @staticmethod
1103
 
    def executable(tree, path, file_id=None):
 
1106
    def executable(tree, path):
1104
1107
        """Determine the executability of a file-id (used as a key method)."""
1105
1108
        try:
1106
 
            if tree.kind(path, file_id) != "file":
 
1109
            if tree.kind(path) != "file":
1107
1110
                return False
1108
1111
        except errors.NoSuchFile:
1109
1112
            return None
1110
 
        return tree.is_executable(path, file_id)
 
1113
        return tree.is_executable(path)
1111
1114
 
1112
1115
    @staticmethod
1113
 
    def kind(tree, path, file_id=None):
 
1116
    def kind(tree, path):
1114
1117
        """Determine the kind of a file-id (used as a key method)."""
1115
1118
        try:
1116
 
            return tree.kind(path, file_id)
 
1119
            return tree.kind(path)
1117
1120
        except errors.NoSuchFile:
1118
1121
            return None
1119
1122
 
1158
1161
        base_val, lca_vals = bases
1159
1162
        # Remove 'base_val' from the lca_vals, because it is not interesting
1160
1163
        filtered_lca_vals = [lca_val for lca_val in lca_vals
1161
 
                                      if lca_val != base_val]
 
1164
                             if lca_val != base_val]
1162
1165
        if len(filtered_lca_vals) == 0:
1163
1166
            return Merge3Merger._three_way(base_val, other, this)
1164
1167
 
1183
1186
        # At this point, the lcas disagree, and the tip disagree
1184
1187
        return 'conflict'
1185
1188
 
1186
 
    def merge_names(self, paths):
1187
 
        def get_entry(tree, path):
1188
 
            try:
1189
 
                return next(tree.iter_entries_by_dir(specific_files=[path]))[1]
1190
 
            except StopIteration:
1191
 
                return None
1192
 
        used_base_path, other_path, this_path = paths
1193
 
        this_entry = get_entry(self.this_tree, this_path)
1194
 
        other_entry = get_entry(self.other_tree, other_path)
1195
 
        base_entry = get_entry(self.base_tree, base_path)
1196
 
        entries = (base_entry, other_entry, this_entry)
1197
 
        names = []
1198
 
        parents = []
1199
 
        for entry in entries:
1200
 
            if entry is None:
1201
 
                names.append(None)
1202
 
                parents.append(None)
1203
 
            else:
1204
 
                names.append(entry.name)
1205
 
                parents.append(entry.parent_id)
1206
 
        return self._merge_names(file_id, paths, parents, names,
1207
 
                                 resolver=self._three_way)
1208
 
 
1209
1189
    def _merge_names(self, file_id, paths, parents, names, resolver):
1210
1190
        """Perform a merge on file_id names and parents"""
1211
1191
        base_name, other_name, this_name = names
1258
1238
            if path is None:
1259
1239
                return (None, None)
1260
1240
            try:
1261
 
                kind = tree.kind(path, file_id)
 
1241
                kind = tree.kind(path)
1262
1242
            except errors.NoSuchFile:
1263
1243
                return (None, None)
1264
1244
            if kind == "file":
1265
 
                contents = tree.get_file_sha1(path, file_id)
 
1245
                contents = tree.get_file_sha1(path)
1266
1246
            elif kind == "symlink":
1267
 
                contents = tree.get_symlink_target(path, file_id)
 
1247
                contents = tree.get_symlink_target(path)
1268
1248
            else:
1269
1249
                contents = None
1270
1250
            return kind, contents
1299
1279
        trans_id = self.tt.trans_id_file_id(file_id)
1300
1280
        params = MergeFileHookParams(
1301
1281
            self, file_id, (base_path, other_path,
1302
 
            this_path), trans_id, this_pair[0],
 
1282
                            this_path), trans_id, this_pair[0],
1303
1283
            other_pair[0], winner)
1304
1284
        hooks = self.active_hooks
1305
1285
        hook_status = 'not_applicable'
1319
1299
            result = None
1320
1300
            name = self.tt.final_name(trans_id)
1321
1301
            parent_id = self.tt.final_parent(trans_id)
1322
 
            duplicate = False
1323
1302
            inhibit_content_conflict = False
1324
 
            if params.this_kind is None: # file_id is not in THIS
 
1303
            if params.this_kind is None:  # file_id is not in THIS
1325
1304
                # Is the name used for a different file_id ?
1326
1305
                if self.this_tree.is_versioned(other_path):
1327
1306
                    # Two entries for the same path
1334
1313
                        other_path, file_id=file_id,
1335
1314
                        filter_tree_path=self._get_filter_tree_path(file_id))
1336
1315
                    inhibit_content_conflict = True
1337
 
            elif params.other_kind is None: # file_id is not in OTHER
 
1316
            elif params.other_kind is None:  # file_id is not in OTHER
1338
1317
                # Is the name used for a different file_id ?
1339
1318
                if self.other_tree.is_versioned(this_path):
1340
1319
                    # Two entries for the same path again, but here, the other
1351
1330
                # This is a contents conflict, because none of the available
1352
1331
                # functions could merge it.
1353
1332
                file_group = self._dump_conflicts(
1354
 
                        name, (base_path, other_path, this_path), parent_id,
1355
 
                        file_id, set_version=True)
 
1333
                    name, (base_path, other_path, this_path), parent_id,
 
1334
                    file_id, set_version=True)
1356
1335
                self._raw_conflicts.append(('contents conflict', file_group))
1357
1336
        elif hook_status == 'success':
1358
1337
            self.tt.create_file(lines, trans_id)
1422
1401
        else:
1423
1402
            return 'not_applicable', None
1424
1403
 
1425
 
    def get_lines(self, tree, path, file_id=None):
 
1404
    def get_lines(self, tree, path):
1426
1405
        """Return the lines in a file, or an empty list."""
1427
1406
        if path is None:
1428
1407
            return []
1429
1408
        try:
1430
 
            kind = tree.kind(path, file_id)
 
1409
            kind = tree.kind(path)
1431
1410
        except errors.NoSuchFile:
1432
1411
            return []
1433
1412
        else:
1434
1413
            if kind != 'file':
1435
1414
                return []
1436
 
            return tree.get_file_lines(path, file_id)
 
1415
            return tree.get_file_lines(path)
1437
1416
 
1438
1417
    def text_merge(self, trans_id, paths, file_id):
1439
1418
        """Perform a three-way text merge on a file_id"""
1440
1419
        # it's possible that we got here with base as a different type.
1441
1420
        # if so, we just want two-way text conflicts.
1442
1421
        base_path, other_path, this_path = paths
1443
 
        base_lines = self.get_lines(self.base_tree, base_path, file_id)
1444
 
        other_lines = self.get_lines(self.other_tree, other_path, file_id)
1445
 
        this_lines = self.get_lines(self.this_tree, this_path, file_id)
 
1422
        base_lines = self.get_lines(self.base_tree, base_path)
 
1423
        other_lines = self.get_lines(self.other_tree, other_path)
 
1424
        this_lines = self.get_lines(self.this_tree, this_path)
1446
1425
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1447
1426
                           is_cherrypick=self.cherrypick)
1448
1427
        start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
1453
1432
 
1454
1433
        def iter_merge3(retval):
1455
1434
            retval["text_conflicts"] = False
1456
 
            for line in m3.merge_lines(name_a = b"TREE",
1457
 
                                       name_b = b"MERGE-SOURCE",
1458
 
                                       name_base = b"BASE-REVISION",
 
1435
            for line in m3.merge_lines(name_a=b"TREE",
 
1436
                                       name_b=b"MERGE-SOURCE",
 
1437
                                       name_base=b"BASE-REVISION",
1459
1438
                                       start_marker=start_marker,
1460
1439
                                       base_marker=base_marker,
1461
1440
                                       reprocess=self.reprocess):
1521
1500
        for suffix, tree, path, lines in data:
1522
1501
            if path is not None:
1523
1502
                trans_id = self._conflict_file(
1524
 
                        name, parent_id, path, tree, file_id, suffix, lines,
1525
 
                        filter_tree_path)
 
1503
                    name, parent_id, path, tree, file_id, suffix, lines,
 
1504
                    filter_tree_path)
1526
1505
                file_group.append(trans_id)
1527
1506
                if set_version and not versioned:
1528
1507
                    self.tt.version_file(file_id, trans_id)
1535
1514
        name = name + '.' + suffix
1536
1515
        trans_id = self.tt.create_path(name, parent_id)
1537
1516
        transform.create_from_tree(
1538
 
                self.tt, trans_id, tree, path,
1539
 
                file_id=file_id, chunks=lines,
1540
 
                filter_tree_path=filter_tree_path)
 
1517
            self.tt, trans_id, tree, path,
 
1518
            file_id=file_id, chunks=lines,
 
1519
            filter_tree_path=filter_tree_path)
1541
1520
        return trans_id
1542
1521
 
1543
1522
    def merge_executable(self, paths, file_id, file_status):
1544
1523
        """Perform a merge on the execute bit."""
1545
 
        executable = [self.executable(t, p, file_id) for t, p in zip([self.base_tree,
1546
 
                      self.other_tree, self.this_tree], paths)]
 
1524
        executable = [self.executable(t, p, file_id)
 
1525
                      for t, p in zip([self.base_tree, self.other_tree, self.this_tree], paths)]
1547
1526
        self._merge_executable(paths, file_id, executable, file_status,
1548
1527
                               resolver=self._three_way)
1549
1528
 
1556
1535
            return
1557
1536
        winner = resolver(*executable)
1558
1537
        if winner == "conflict":
1559
 
        # There must be a None in here, if we have a conflict, but we
1560
 
        # need executability since file status was not deleted.
 
1538
            # There must be a None in here, if we have a conflict, but we
 
1539
            # need executability since file status was not deleted.
1561
1540
            if other_path is None:
1562
1541
                winner = "this"
1563
1542
            else:
1589
1568
            conflict_type = conflict[0]
1590
1569
            if conflict_type == 'path conflict':
1591
1570
                (trans_id, file_id,
1592
 
                this_parent, this_name,
1593
 
                other_parent, other_name) = conflict[1:]
 
1571
                 this_parent, this_name,
 
1572
                 other_parent, other_name) = conflict[1:]
1594
1573
                if this_parent is None or this_name is None:
1595
1574
                    this_path = '<deleted>'
1596
1575
                else:
1597
 
                    parent_path =  fp.get_path(
 
1576
                    parent_path = fp.get_path(
1598
1577
                        self.tt.trans_id_file_id(this_parent))
1599
1578
                    this_path = osutils.pathjoin(parent_path, this_name)
1600
1579
                if other_parent is None or other_name is None:
1606
1585
                        # exception
1607
1586
                        parent_path = ''
1608
1587
                    else:
1609
 
                        parent_path =  fp.get_path(
 
1588
                        parent_path = fp.get_path(
1610
1589
                            self.tt.trans_id_file_id(other_parent))
1611
1590
                    other_path = osutils.pathjoin(parent_path, other_name)
1612
1591
                c = _mod_conflicts.Conflict.factory(
1645
1624
        # conflict is enough.
1646
1625
        for c in cooked_conflicts:
1647
1626
            if (c.typestring == 'path conflict'
1648
 
                and c.file_id in content_conflict_file_ids):
 
1627
                    and c.file_id in content_conflict_file_ids):
1649
1628
                continue
1650
1629
            self.cooked_conflicts.append(c)
1651
1630
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1719
1698
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1720
1699
                                                  base=base)
1721
1700
 
 
1701
 
1722
1702
class Diff3Merger(Merge3Merger):
1723
1703
    """Three-way merger using external diff3 for text merging"""
1724
1704
 
1725
1705
    requires_file_merge_plan = False
1726
1706
 
1727
 
    def dump_file(self, temp_dir, name, tree, path, file_id=None):
 
1707
    def dump_file(self, temp_dir, name, tree, path):
1728
1708
        out_path = osutils.pathjoin(temp_dir, name)
1729
1709
        with open(out_path, "wb") as out_file:
1730
 
            in_file = tree.get_file(path, file_id=None)
 
1710
            in_file = tree.get_file(path)
1731
1711
            for line in in_file:
1732
1712
                out_file.write(line)
1733
1713
        return out_path
1742
1722
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1743
1723
        try:
1744
1724
            new_file = osutils.pathjoin(temp_dir, "new")
1745
 
            this = self.dump_file(temp_dir, "this", self.this_tree, this_path, file_id)
1746
 
            base = self.dump_file(temp_dir, "base", self.base_tree, base_path, file_id)
1747
 
            other = self.dump_file(temp_dir, "other", self.other_tree, other_path, file_id)
 
1725
            this = self.dump_file(
 
1726
                temp_dir, "this", self.this_tree, this_path)
 
1727
            base = self.dump_file(
 
1728
                temp_dir, "base", self.base_tree, base_path)
 
1729
            other = self.dump_file(
 
1730
                temp_dir, "other", self.other_tree, other_path)
1748
1731
            status = breezy.patch.diff3(new_file, this, base, other)
1749
1732
            if status not in (0, 1):
1750
1733
                raise errors.BzrError("Unhandled diff3 exit code")
1775
1758
    """
1776
1759
 
1777
1760
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1778
 
            source_subpath, other_rev_id=None):
 
1761
                 source_subpath, other_rev_id=None):
1779
1762
        """Create a new MergeIntoMerger object.
1780
1763
 
1781
1764
        source_subpath in other_tree will be effectively copied to
1792
1775
        # It is assumed that we are merging a tree that is not in our current
1793
1776
        # ancestry, which means we are using the "EmptyTree" as our basis.
1794
1777
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1795
 
                                _mod_revision.NULL_REVISION)
 
1778
            _mod_revision.NULL_REVISION)
1796
1779
        super(MergeIntoMerger, self).__init__(
1797
1780
            this_branch=this_tree.branch,
1798
1781
            this_tree=this_tree,
1812
1795
        self.reprocess = False
1813
1796
        self.interesting_files = None
1814
1797
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1815
 
              target_subdir=self._target_subdir,
1816
 
              source_subpath=self._source_subpath)
 
1798
                                                  target_subdir=self._target_subdir,
 
1799
                                                  source_subpath=self._source_subpath)
1817
1800
        if self._source_subpath != '':
1818
1801
            # If this isn't a partial merge make sure the revisions will be
1819
1802
            # present.
1820
1803
            self._maybe_fetch(self.other_branch, self.this_branch,
1821
 
                self.other_basis)
 
1804
                              self.other_basis)
1822
1805
 
1823
1806
    def set_pending(self):
1824
1807
        if self._source_subpath != '':
1873
1856
            entries = self._entries_to_incorporate()
1874
1857
            entries = list(entries)
1875
1858
            for num, (entry, parent_id, relpath) in enumerate(entries):
1876
 
                child_pb.update(gettext('Preparing file merge'), num, len(entries))
 
1859
                child_pb.update(gettext('Preparing file merge'),
 
1860
                                num, len(entries))
1877
1861
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1878
1862
                path = osutils.pathjoin(self._source_subpath, relpath)
1879
1863
                trans_id = transform.new_by_entry(path, self.tt, entry,
1880
 
                    parent_trans_id, self.other_tree)
 
1864
                                                  parent_trans_id, self.other_tree)
1881
1865
        self._finish_computing_transform()
1882
1866
 
1883
1867
    def _entries_to_incorporate(self):
2084
2068
                else:
2085
2069
                    yield 'killed-a', self.lines_b[b_index]
2086
2070
            # handle common lines
2087
 
            for a_index in range(i, i+n):
 
2071
            for a_index in range(i, i + n):
2088
2072
                yield 'unchanged', self.lines_a[a_index]
2089
 
            last_i = i+n
2090
 
            last_j = j+n
 
2073
            last_i = i + n
 
2074
            last_j = j + n
2091
2075
 
2092
2076
    def _get_matching_blocks(self, left_revision, right_revision):
2093
2077
        """Return a description of which sections of two revisions match.
2149
2133
        for i, j, n in matcher.get_matching_blocks():
2150
2134
            for jj in range(last_j, j):
2151
2135
                yield new_plan[jj]
2152
 
            for jj in range(j, j+n):
 
2136
            for jj in range(j, j + n):
2153
2137
                plan_line = new_plan[jj]
2154
2138
                if plan_line[0] == 'new-b':
2155
2139
                    pass
2386
2370
        are combined, they are written out in the format described in
2387
2371
        VersionedFile.plan_merge
2388
2372
        """
2389
 
        if self._head_key is not None: # There was a single head
 
2373
        if self._head_key is not None:  # There was a single head
2390
2374
            if self._head_key == self.a_key:
2391
2375
                plan = 'new-a'
2392
2376
            else: