/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-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:
223
223
 
224
224
    There are some fields hooks can access:
225
225
 
 
226
    :ivar file_id: the file ID of the file being merged
226
227
    :ivar base_path: Path in base tree
227
228
    :ivar other_path: Path in other tree
228
229
    :ivar this_path: Path in this tree
229
230
    :ivar trans_id: the transform ID for the merge of this file
230
 
    :ivar this_kind: kind of file in 'this' tree
231
 
    :ivar other_kind: kind of file in 'other' tree
 
231
    :ivar this_kind: kind of file_id in 'this' tree
 
232
    :ivar other_kind: kind of file_id in 'other' tree
232
233
    :ivar winner: one of 'this', 'other', 'conflict'
233
234
    """
234
235
 
235
 
    def __init__(self, merger, paths, trans_id, this_kind, other_kind,
 
236
    def __init__(self, merger, file_id, paths, trans_id, this_kind, other_kind,
236
237
                 winner):
237
238
        self._merger = merger
 
239
        self.file_id = file_id
238
240
        self.paths = paths
239
241
        self.base_path, self.other_path, self.this_path = paths
240
242
        self.trans_id = trans_id
447
449
    def _add_parent(self):
448
450
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
449
451
        new_parent_trees = []
450
 
        with cleanup.ExitStack() as stack:
451
 
            for revision_id in new_parents:
452
 
                try:
453
 
                    tree = self.revision_tree(revision_id)
454
 
                except errors.NoSuchRevision:
455
 
                    tree = None
456
 
                else:
457
 
                    stack.enter_context(tree.lock_read())
458
 
                new_parent_trees.append((revision_id, tree))
459
 
            self.this_tree.set_parent_trees(new_parent_trees, allow_leftmost_as_ghost=True)
 
452
        operation = cleanup.OperationWithCleanups(
 
453
            self.this_tree.set_parent_trees)
 
454
        for revision_id in new_parents:
 
455
            try:
 
456
                tree = self.revision_tree(revision_id)
 
457
            except errors.NoSuchRevision:
 
458
                tree = None
 
459
            else:
 
460
                tree.lock_read()
 
461
                operation.add_cleanup(tree.unlock)
 
462
            new_parent_trees.append((revision_id, tree))
 
463
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
460
464
 
461
465
    def set_other(self, other_revision, possible_transports=None):
462
466
        """Set the revision and tree to merge from.
633
637
        for hook in Merger.hooks['post_merge']:
634
638
            hook(merge)
635
639
        if self.recurse == 'down':
636
 
            for relpath in self.this_tree.iter_references():
 
640
            for relpath, file_id in self.this_tree.iter_references():
637
641
                sub_tree = self.this_tree.get_nested_tree(relpath)
638
642
                other_revision = self.other_tree.get_reference_revision(
639
643
                    relpath)
641
645
                    continue
642
646
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
643
647
                sub_merge.merge_type = self.merge_type
644
 
                other_branch = self.other_tree.reference_parent(relpath)
 
648
                other_branch = self.other_branch.reference_parent(
 
649
                    relpath, file_id)
645
650
                sub_merge.set_other_revision(other_revision, other_branch)
646
651
                base_tree_path = _mod_tree.find_previous_path(
647
652
                    self.this_tree, self.base_tree, relpath)
654
659
        return merge
655
660
 
656
661
    def do_merge(self):
657
 
        with cleanup.ExitStack() as stack:
658
 
            stack.enter_context(self.this_tree.lock_tree_write())
659
 
            if self.base_tree is not None:
660
 
                stack.enter_context(self.base_tree.lock_read())
661
 
            if self.other_tree is not None:
662
 
                stack.enter_context(self.other_tree.lock_read())
663
 
            merge = self._do_merge_to()
 
662
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
 
663
        self.this_tree.lock_tree_write()
 
664
        operation.add_cleanup(self.this_tree.unlock)
 
665
        if self.base_tree is not None:
 
666
            self.base_tree.lock_read()
 
667
            operation.add_cleanup(self.base_tree.unlock)
 
668
        if self.other_tree is not None:
 
669
            self.other_tree.lock_read()
 
670
            operation.add_cleanup(self.other_tree.unlock)
 
671
        merge = operation.run_simple()
664
672
        if len(merge.cooked_conflicts) == 0:
665
673
            if not self.ignore_zero and not trace.is_quiet():
666
674
                trace.note(gettext("All changes applied successfully."))
685
693
    symlink_target = None
686
694
    text_sha1 = None
687
695
 
688
 
    def is_unmodified(self, other):
689
 
        return other is self
690
 
 
691
696
 
692
697
_none_entry = _InventoryNoneEntry()
693
698
 
757
762
            self.do_merge()
758
763
 
759
764
    def do_merge(self):
760
 
        with cleanup.ExitStack() as stack:
761
 
            stack.enter_context(self.working_tree.lock_tree_write())
762
 
            stack.enter_context(self.this_tree.lock_read())
763
 
            stack.enter_context(self.base_tree.lock_read())
764
 
            stack.enter_context(self.other_tree.lock_read())
765
 
            self.tt = self.working_tree.get_transform()
766
 
            stack.enter_context(self.tt)
767
 
            self._compute_transform()
768
 
            results = self.tt.apply(no_conflicts=True)
769
 
            self.write_modified(results)
770
 
            try:
771
 
                self.working_tree.add_conflicts(self.cooked_conflicts)
772
 
            except errors.UnsupportedOperation:
773
 
                pass
 
765
        operation = cleanup.OperationWithCleanups(self._do_merge)
 
766
        self.working_tree.lock_tree_write()
 
767
        operation.add_cleanup(self.working_tree.unlock)
 
768
        self.this_tree.lock_read()
 
769
        operation.add_cleanup(self.this_tree.unlock)
 
770
        self.base_tree.lock_read()
 
771
        operation.add_cleanup(self.base_tree.unlock)
 
772
        self.other_tree.lock_read()
 
773
        operation.add_cleanup(self.other_tree.unlock)
 
774
        operation.run()
 
775
 
 
776
    def _do_merge(self, operation):
 
777
        self.tt = transform.TreeTransform(self.working_tree, None)
 
778
        operation.add_cleanup(self.tt.finalize)
 
779
        self._compute_transform()
 
780
        results = self.tt.apply(no_conflicts=True)
 
781
        self.write_modified(results)
 
782
        try:
 
783
            self.working_tree.add_conflicts(self.cooked_conflicts)
 
784
        except errors.UnsupportedOperation:
 
785
            pass
774
786
 
775
787
    def make_preview_transform(self):
776
788
        with self.base_tree.lock_read(), self.other_tree.lock_read():
793
805
        with ui.ui_factory.nested_progress_bar() as child_pb:
794
806
            for num, (file_id, changed, paths3, parents3, names3,
795
807
                      executable3) in enumerate(entries):
796
 
                trans_id = self.tt.trans_id_file_id(file_id)
797
 
 
798
808
                # Try merging each entry
799
809
                child_pb.update(gettext('Preparing file merge'),
800
810
                                num, len(entries))
801
 
                self._merge_names(trans_id, file_id, paths3, parents3,
 
811
                self._merge_names(file_id, paths3, parents3,
802
812
                                  names3, resolver=resolver)
803
813
                if changed:
804
 
                    file_status = self._do_merge_contents(paths3, trans_id, file_id)
 
814
                    file_status = self._do_merge_contents(paths3, file_id)
805
815
                else:
806
816
                    file_status = 'unmodified'
807
 
                self._merge_executable(paths3, trans_id, executable3,
 
817
                self._merge_executable(paths3, file_id, executable3,
808
818
                                       file_status, resolver=resolver)
809
819
        self.tt.fixup_new_roots()
810
820
        self._finish_computing_transform()
815
825
        This is the second half of _compute_transform.
816
826
        """
817
827
        with ui.ui_factory.nested_progress_bar() as child_pb:
818
 
            fs_conflicts = transform.resolve_conflicts(
819
 
                self.tt, child_pb,
820
 
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
 
828
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
 
829
                                                       lambda t, c: transform.conflict_pass(t, c, self.other_tree))
821
830
        if self.change_reporter is not None:
822
831
            from breezy import delta
823
832
            delta.report_changes(
843
852
            self.interesting_files, trees=[self.other_tree])
844
853
        this_entries = dict(self.this_tree.iter_entries_by_dir(
845
854
                            specific_files=this_interesting_files))
846
 
        for change in iterator:
847
 
            if change.path[0] is not None:
 
855
        for (file_id, paths, changed, versioned, parents, names, kind,
 
856
             executable) in iterator:
 
857
            if paths[0] is not None:
848
858
                this_path = _mod_tree.find_previous_path(
849
 
                    self.base_tree, self.this_tree, change.path[0])
 
859
                    self.base_tree, self.this_tree, paths[0])
850
860
            else:
851
861
                this_path = _mod_tree.find_previous_path(
852
 
                    self.other_tree, self.this_tree, change.path[1])
 
862
                    self.other_tree, self.this_tree, paths[1])
853
863
            this_entry = this_entries.get(this_path)
854
864
            if this_entry is not None:
855
865
                this_name = this_entry.name
859
869
                this_name = None
860
870
                this_parent = None
861
871
                this_executable = None
862
 
            parents3 = change.parent_id + (this_parent,)
863
 
            names3 = change.name + (this_name,)
864
 
            paths3 = change.path + (this_path, )
865
 
            executable3 = change.executable + (this_executable,)
866
 
            result.append(
867
 
                (change.file_id, change.changed_content, paths3,
868
 
                 parents3, names3, executable3))
 
872
            parents3 = parents + (this_parent,)
 
873
            names3 = names + (this_name,)
 
874
            paths3 = paths + (this_path, )
 
875
            executable3 = executable + (this_executable,)
 
876
            result.append((file_id, changed, paths3,
 
877
                           parents3, names3, executable3))
869
878
        return result
870
879
 
871
880
    def _entries_lca(self):
913
922
            # we know that the ancestry is linear, and that OTHER did not
914
923
            # modify anything
915
924
            # See doc/developers/lca_merge_resolution.txt for details
916
 
            # We can't use this shortcut when other_revision is None,
917
 
            # because it may be None because things are WorkingTrees, and
918
 
            # not because it is *actually* None.
919
 
            is_unmodified = False
920
 
            for lca_path, ie in lca_values:
921
 
                if ie is not None and other_ie.is_unmodified(ie):
922
 
                    is_unmodified = True
923
 
                    break
924
 
            if is_unmodified:
925
 
                continue
 
925
            other_revision = other_ie.revision
 
926
            if other_revision is not None:
 
927
                # We can't use this shortcut when other_revision is None,
 
928
                # because it may be None because things are WorkingTrees, and
 
929
                # not because it is *actually* None.
 
930
                is_unmodified = False
 
931
                for lca_path, ie in lca_values:
 
932
                    if ie is not None and ie.revision == other_revision:
 
933
                        is_unmodified = True
 
934
                        break
 
935
                if is_unmodified:
 
936
                    continue
926
937
 
927
938
            lca_entries = []
928
939
            lca_paths = []
1057
1068
        modified_hashes = {}
1058
1069
        for path in results.modified_paths:
1059
1070
            wt_relpath = self.working_tree.relpath(path)
1060
 
            if not self.working_tree.is_versioned(wt_relpath):
 
1071
            file_id = self.working_tree.path2id(wt_relpath)
 
1072
            if file_id is None:
1061
1073
                continue
1062
1074
            hash = self.working_tree.get_file_sha1(wt_relpath)
1063
1075
            if hash is None:
1064
1076
                continue
1065
 
            modified_hashes[wt_relpath] = hash
 
1077
            modified_hashes[file_id] = hash
1066
1078
        self.working_tree.set_merge_modified(modified_hashes)
1067
1079
 
1068
1080
    @staticmethod
1171
1183
        # At this point, the lcas disagree, and the tip disagree
1172
1184
        return 'conflict'
1173
1185
 
1174
 
    def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
1175
 
        """Perform a merge on file names and parents"""
 
1186
    def _merge_names(self, file_id, paths, parents, names, resolver):
 
1187
        """Perform a merge on file_id names and parents"""
1176
1188
        base_name, other_name, this_name = names
1177
1189
        base_parent, other_parent, this_parent = parents
1178
1190
        unused_base_path, other_path, this_path = paths
1191
1203
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1192
1204
            # road if a ContentConflict needs to be created so we should not do
1193
1205
            # that
 
1206
            trans_id = self.tt.trans_id_file_id(file_id)
1194
1207
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1195
1208
                                        this_parent, this_name,
1196
1209
                                        other_parent, other_name))
1213
1226
                parent_trans_id = transform.ROOT_PARENT
1214
1227
            else:
1215
1228
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1216
 
            self.tt.adjust_path(name, parent_trans_id, trans_id)
 
1229
            self.tt.adjust_path(name, parent_trans_id,
 
1230
                                self.tt.trans_id_file_id(file_id))
1217
1231
 
1218
 
    def _do_merge_contents(self, paths, trans_id, file_id):
 
1232
    def _do_merge_contents(self, paths, file_id):
1219
1233
        """Performs a merge on file_id contents."""
1220
1234
        def contents_pair(tree, path):
1221
1235
            if path is None:
1259
1273
            return "unmodified"
1260
1274
        # We have a hypothetical conflict, but if we have files, then we
1261
1275
        # can try to merge the content
 
1276
        trans_id = self.tt.trans_id_file_id(file_id)
1262
1277
        params = MergeFileHookParams(
1263
 
            self, (base_path, other_path, this_path), trans_id, this_pair[0],
 
1278
            self, file_id, (base_path, other_path,
 
1279
                            this_path), trans_id, this_pair[0],
1264
1280
            other_pair[0], winner)
1265
1281
        hooks = self.active_hooks
1266
1282
        hook_status = 'not_applicable'
1291
1307
                    self.tt.version_file(file_id, trans_id)
1292
1308
                    transform.create_from_tree(
1293
1309
                        self.tt, trans_id, self.other_tree,
1294
 
                        other_path,
1295
 
                        filter_tree_path=self._get_filter_tree_path(other_path))
 
1310
                        other_path, file_id=file_id,
 
1311
                        filter_tree_path=self._get_filter_tree_path(file_id))
1296
1312
                    inhibit_content_conflict = True
1297
1313
            elif params.other_kind is None:  # file_id is not in OTHER
1298
1314
                # Is the name used for a different file_id ?
1344
1360
 
1345
1361
    def _default_other_winner_merge(self, merge_hook_params):
1346
1362
        """Replace this contents with other."""
 
1363
        file_id = merge_hook_params.file_id
1347
1364
        trans_id = merge_hook_params.trans_id
1348
1365
        if merge_hook_params.other_path is not None:
1349
1366
            # OTHER changed the file
1350
1367
            transform.create_from_tree(
1351
1368
                self.tt, trans_id, self.other_tree,
1352
 
                merge_hook_params.other_path,
1353
 
                filter_tree_path=self._get_filter_tree_path(merge_hook_params.other_path))
 
1369
                merge_hook_params.other_path, file_id=file_id,
 
1370
                filter_tree_path=self._get_filter_tree_path(file_id))
1354
1371
            return 'done', None
1355
1372
        elif merge_hook_params.this_path is not None:
1356
1373
            # OTHER deleted the file
1357
1374
            return 'delete', None
1358
1375
        else:
1359
1376
            raise AssertionError(
1360
 
                'winner is OTHER, but file %r not in THIS or OTHER tree'
1361
 
                % (merge_hook_params.base_path,))
 
1377
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
 
1378
                % (file_id,))
1362
1379
 
1363
1380
    def merge_contents(self, merge_hook_params):
1364
1381
        """Fallback merge logic after user installed hooks."""
1374
1391
            # have agreement that output should be a file.
1375
1392
            try:
1376
1393
                self.text_merge(merge_hook_params.trans_id,
1377
 
                                merge_hook_params.paths)
 
1394
                                merge_hook_params.paths, merge_hook_params.file_id)
1378
1395
            except errors.BinaryFile:
1379
1396
                return 'not_applicable', None
1380
1397
            return 'done', None
1394
1411
                return []
1395
1412
            return tree.get_file_lines(path)
1396
1413
 
1397
 
    def text_merge(self, trans_id, paths):
1398
 
        """Perform a three-way text merge on a file"""
 
1414
    def text_merge(self, trans_id, paths, file_id):
 
1415
        """Perform a three-way text merge on a file_id"""
1399
1416
        # it's possible that we got here with base as a different type.
1400
1417
        # if so, we just want two-way text conflicts.
1401
1418
        base_path, other_path, this_path = paths
1430
1447
            self._raw_conflicts.append(('text conflict', trans_id))
1431
1448
            name = self.tt.final_name(trans_id)
1432
1449
            parent_id = self.tt.final_parent(trans_id)
1433
 
            file_id = self.tt.final_file_id(trans_id)
1434
1450
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1435
1451
                                              this_lines, base_lines,
1436
1452
                                              other_lines)
1437
1453
            file_group.append(trans_id)
1438
1454
 
1439
 
    def _get_filter_tree_path(self, path):
 
1455
    def _get_filter_tree_path(self, file_id):
1440
1456
        if self.this_tree.supports_content_filtering():
1441
1457
            # We get the path from the working tree if it exists.
1442
1458
            # That fails though when OTHER is adding a file, so
1443
1459
            # we fall back to the other tree to find the path if
1444
1460
            # it doesn't exist locally.
1445
 
            filter_path = _mod_tree.find_previous_path(
1446
 
                self.other_tree, self.working_tree, path)
1447
 
            if filter_path is None:
1448
 
                filter_path = path
1449
 
            return filter_path
1450
 
        # Skip the lookup for older formats
 
1461
            try:
 
1462
                return self.this_tree.id2path(file_id)
 
1463
            except errors.NoSuchId:
 
1464
                return self.other_tree.id2path(file_id)
 
1465
        # Skip the id2path lookup for older formats
1451
1466
        return None
1452
1467
 
1453
1468
    def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1482
1497
        for suffix, tree, path, lines in data:
1483
1498
            if path is not None:
1484
1499
                trans_id = self._conflict_file(
1485
 
                    name, parent_id, path, tree, suffix, lines,
 
1500
                    name, parent_id, path, tree, file_id, suffix, lines,
1486
1501
                    filter_tree_path)
1487
1502
                file_group.append(trans_id)
1488
1503
                if set_version and not versioned:
1490
1505
                    versioned = True
1491
1506
        return file_group
1492
1507
 
1493
 
    def _conflict_file(self, name, parent_id, path, tree, suffix,
 
1508
    def _conflict_file(self, name, parent_id, path, tree, file_id, suffix,
1494
1509
                       lines=None, filter_tree_path=None):
1495
1510
        """Emit a single conflict file."""
1496
1511
        name = name + '.' + suffix
1497
1512
        trans_id = self.tt.create_path(name, parent_id)
1498
1513
        transform.create_from_tree(
1499
1514
            self.tt, trans_id, tree, path,
1500
 
            chunks=lines,
 
1515
            file_id=file_id, chunks=lines,
1501
1516
            filter_tree_path=filter_tree_path)
1502
1517
        return trans_id
1503
1518
 
1504
 
    def _merge_executable(self, paths, trans_id, executable, file_status,
 
1519
    def merge_executable(self, paths, file_id, file_status):
 
1520
        """Perform a merge on the execute bit."""
 
1521
        executable = [self.executable(t, p, file_id)
 
1522
                      for t, p in zip([self.base_tree, self.other_tree, self.this_tree], paths)]
 
1523
        self._merge_executable(paths, file_id, executable, file_status,
 
1524
                               resolver=self._three_way)
 
1525
 
 
1526
    def _merge_executable(self, paths, file_id, executable, file_status,
1505
1527
                          resolver):
1506
1528
        """Perform a merge on the execute bit."""
1507
1529
        base_executable, other_executable, this_executable = executable
1518
1540
                winner = "other"
1519
1541
        if winner == 'this' and file_status != "modified":
1520
1542
            return
 
1543
        trans_id = self.tt.trans_id_file_id(file_id)
1521
1544
        if self.tt.final_kind(trans_id) != "file":
1522
1545
            return
1523
1546
        if winner == "this":
1530
1553
            elif base_path is not None:
1531
1554
                executability = base_executable
1532
1555
        if executability is not None:
 
1556
            trans_id = self.tt.trans_id_file_id(file_id)
1533
1557
            self.tt.set_executability(executability, trans_id)
1534
1558
 
1535
1559
    def cook_conflicts(self, fs_conflicts):
1552
1576
                if other_parent is None or other_name is None:
1553
1577
                    other_path = '<deleted>'
1554
1578
                else:
1555
 
                    if other_parent == self.other_tree.path2id(''):
 
1579
                    if other_parent == self.other_tree.get_root_id():
1556
1580
                        # The tree transform doesn't know about the other root,
1557
1581
                        # so we special case here to avoid a NoFinalPath
1558
1582
                        # exception
1611
1635
    history_based = True
1612
1636
    requires_file_merge_plan = True
1613
1637
 
1614
 
    def _generate_merge_plan(self, this_path, base):
1615
 
        return self.this_tree.plan_file_merge(this_path, self.other_tree,
 
1638
    def _generate_merge_plan(self, file_id, base):
 
1639
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1616
1640
                                              base=base)
1617
1641
 
1618
 
    def _merged_lines(self, this_path):
 
1642
    def _merged_lines(self, file_id):
1619
1643
        """Generate the merged lines.
1620
1644
        There is no distinction between lines that are meant to contain <<<<<<<
1621
1645
        and conflicts.
1624
1648
            base = self.base_tree
1625
1649
        else:
1626
1650
            base = None
1627
 
        plan = self._generate_merge_plan(this_path, base)
 
1651
        plan = self._generate_merge_plan(file_id, base)
1628
1652
        if 'merge' in debug.debug_flags:
1629
1653
            plan = list(plan)
1630
1654
            trans_id = self.tt.trans_id_file_id(file_id)
1640
1664
            base_lines = None
1641
1665
        return lines, base_lines
1642
1666
 
1643
 
    def text_merge(self, trans_id, paths):
 
1667
    def text_merge(self, trans_id, paths, file_id):
1644
1668
        """Perform a (weave) text merge for a given file and file-id.
1645
1669
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1646
1670
        and a conflict will be noted.
1647
1671
        """
1648
1672
        base_path, other_path, this_path = paths
1649
 
        lines, base_lines = self._merged_lines(this_path)
 
1673
        lines, base_lines = self._merged_lines(file_id)
1650
1674
        lines = list(lines)
1651
1675
        # Note we're checking whether the OUTPUT is binary in this case,
1652
1676
        # because we don't want to get into weave merge guts.
1657
1681
            self._raw_conflicts.append(('text conflict', trans_id))
1658
1682
            name = self.tt.final_name(trans_id)
1659
1683
            parent_id = self.tt.final_parent(trans_id)
1660
 
            file_id = self.tt.final_file_id(trans_id)
1661
1684
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1662
1685
                                              no_base=False,
1663
1686
                                              base_lines=base_lines)
1668
1691
 
1669
1692
    requires_file_merge_plan = True
1670
1693
 
1671
 
    def _generate_merge_plan(self, this_path, base):
1672
 
        return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
 
1694
    def _generate_merge_plan(self, file_id, base):
 
1695
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1673
1696
                                                  base=base)
1674
1697
 
1675
1698
 
1686
1709
                out_file.write(line)
1687
1710
        return out_path
1688
1711
 
1689
 
    def text_merge(self, trans_id, paths):
 
1712
    def text_merge(self, trans_id, paths, file_id):
1690
1713
        """Perform a diff3 merge using a specified file-id and trans-id.
1691
1714
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1692
1715
        will be dumped, and a will be conflict noted.
1710
1733
            if status == 1:
1711
1734
                name = self.tt.final_name(trans_id)
1712
1735
                parent_id = self.tt.final_parent(trans_id)
1713
 
                file_id = self.tt.final_file_id(trans_id)
1714
1736
                self._dump_conflicts(name, paths, parent_id, file_id)
1715
1737
                self._raw_conflicts.append(('text conflict', trans_id))
1716
1738
        finally:
1855
1877
        name_in_target = osutils.basename(self._target_subdir)
1856
1878
        merge_into_root = subdir.copy()
1857
1879
        merge_into_root.name = name_in_target
1858
 
        try:
1859
 
            self.this_tree.id2path(merge_into_root.file_id)
1860
 
        except errors.NoSuchId:
1861
 
            pass
1862
 
        else:
 
1880
        if self.this_tree.has_id(merge_into_root.file_id):
1863
1881
            # Give the root a new file-id.
1864
1882
            # This can happen fairly easily if the directory we are
1865
1883
            # incorporating is the root, and both trees have 'TREE_ROOT' as
2007
2025
        for record in self.vf.get_record_stream(keys, 'unordered', True):
2008
2026
            if record.storage_kind == 'absent':
2009
2027
                raise errors.RevisionNotPresent(record.key, self.vf)
2010
 
            result[record.key[-1]] = record.get_bytes_as('lines')
 
2028
            result[record.key[-1]] = osutils.chunks_to_lines(
 
2029
                record.get_bytes_as('chunked'))
2011
2030
        return result
2012
2031
 
2013
2032
    def plan_merge(self):