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