/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-05-06 11:48:54 UTC
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@jelmer.uk-20180506114854-h4qd9ojaqy8wxjsd
Move .mailmap to root.

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
 
19
21
from .lazy_import import lazy_import
20
22
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,
28
29
    graph as _mod_graph,
29
30
    merge3,
30
31
    osutils,
 
32
    patiencediff,
31
33
    revision as _mod_revision,
32
34
    textfile,
33
35
    trace,
38
40
    workingtree,
39
41
    )
40
42
from breezy.bzr import (
41
 
    generate_ids,
42
43
    versionedfile,
43
44
    )
44
45
from breezy.i18n import gettext
56
57
 
57
58
 
58
59
def transform_tree(from_tree, to_tree, interesting_files=None):
59
 
    with from_tree.lock_tree_write():
60
 
        merge_inner(from_tree.branch, to_tree, from_tree,
61
 
                    ignore_zero=True, this_tree=from_tree,
62
 
                    interesting_files=interesting_files)
 
60
    from_tree.lock_tree_write()
 
61
    operation = cleanup.OperationWithCleanups(merge_inner)
 
62
    operation.add_cleanup(from_tree.unlock)
 
63
    operation.run_simple(from_tree.branch, to_tree, from_tree,
 
64
        ignore_zero=True, this_tree=from_tree,
 
65
        interesting_files=interesting_files)
63
66
 
64
67
 
65
68
class MergeHooks(hooks.Hooks):
67
70
    def __init__(self):
68
71
        hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
69
72
        self.add_hook('merge_file_content',
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))
 
73
            "Called with a breezy.merge.Merger object to create a per file "
 
74
            "merge object when starting a merge. "
 
75
            "Should return either None or a subclass of "
 
76
            "``breezy.merge.AbstractPerFileMerger``. "
 
77
            "Such objects will then be called per file "
 
78
            "that needs to be merged (including when one "
 
79
            "side has deleted the file and the other has changed it). "
 
80
            "See the AbstractPerFileMerger API docs for details on how it is "
 
81
            "used by merge.",
 
82
            (2, 1))
80
83
        self.add_hook('pre_merge',
81
 
                      'Called before a merge. '
82
 
                      'Receives a Merger object as the single argument.',
83
 
                      (2, 5))
 
84
            'Called before a merge. '
 
85
            'Receives a Merger object as the single argument.',
 
86
            (2, 5))
84
87
        self.add_hook('post_merge',
85
 
                      'Called after a merge. '
86
 
                      'Receives a Merger object as the single argument. '
87
 
                      'The return value is ignored.',
88
 
                      (2, 5))
 
88
            'Called after a merge. '
 
89
            'Receives a Merger object as the single argument. '
 
90
            'The return value is ignored.',
 
91
            (2, 5))
89
92
 
90
93
 
91
94
class AbstractPerFileMerger(object):
137
140
            # THIS and OTHER aren't both files.
138
141
            not params.is_file_merge() or
139
142
            # The filename doesn't match
140
 
                not self.file_matches(params)):
 
143
            not self.file_matches(params)):
141
144
            return 'not_applicable', None
142
145
        return self.merge_matching(params)
143
146
 
223
226
 
224
227
    There are some fields hooks can access:
225
228
 
 
229
    :ivar file_id: the file ID of the file being merged
226
230
    :ivar base_path: Path in base tree
227
231
    :ivar other_path: Path in other tree
228
232
    :ivar this_path: Path in this tree
229
233
    :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
 
234
    :ivar this_kind: kind of file_id in 'this' tree
 
235
    :ivar other_kind: kind of file_id in 'other' tree
232
236
    :ivar winner: one of 'this', 'other', 'conflict'
233
237
    """
234
238
 
235
 
    def __init__(self, merger, paths, trans_id, this_kind, other_kind,
236
 
                 winner):
 
239
    def __init__(self, merger, file_id, paths, trans_id, this_kind, other_kind,
 
240
            winner):
237
241
        self._merger = merger
 
242
        self.file_id = file_id
238
243
        self.paths = paths
239
244
        self.base_path, self.other_path, self.this_path = paths
240
245
        self.trans_id = trans_id
249
254
    @decorators.cachedproperty
250
255
    def base_lines(self):
251
256
        """The lines of the 'base' version of the file."""
252
 
        return self._merger.get_lines(self._merger.base_tree, self.base_path)
 
257
        return self._merger.get_lines(self._merger.base_tree, self.base_path, self.file_id)
253
258
 
254
259
    @decorators.cachedproperty
255
260
    def this_lines(self):
256
261
        """The lines of the 'this' version of the file."""
257
 
        return self._merger.get_lines(self._merger.this_tree, self.this_path)
 
262
        return self._merger.get_lines(self._merger.this_tree, self.this_path, self.file_id)
258
263
 
259
264
    @decorators.cachedproperty
260
265
    def other_lines(self):
261
266
        """The lines of the 'other' version of the file."""
262
 
        return self._merger.get_lines(self._merger.other_tree, self.other_path)
 
267
        return self._merger.get_lines(self._merger.other_tree, self.other_path, self.file_id)
263
268
 
264
269
 
265
270
class Merger(object):
368
373
        if base_revision_id is not None:
369
374
            if (base_revision_id != _mod_revision.NULL_REVISION and
370
375
                revision_graph.is_ancestor(
371
 
                    base_revision_id, tree.branch.last_revision())):
 
376
                base_revision_id, tree.branch.last_revision())):
372
377
                base_revision_id = None
373
378
            else:
374
379
                trace.warning('Performing cherrypick')
375
380
        merger = klass.from_revision_ids(tree, other_revision_id,
376
 
                                         base_revision_id, revision_graph=revision_graph)
 
381
                                         base_revision_id, revision_graph=
 
382
                                         revision_graph)
377
383
        return merger, verified
378
384
 
379
385
    @staticmethod
440
446
 
441
447
    def set_pending(self):
442
448
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
443
 
                or self.other_rev_id is None):
 
449
            or self.other_rev_id is None):
444
450
            return
445
451
        self._add_parent()
446
452
 
447
453
    def _add_parent(self):
448
454
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
449
455
        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)
 
456
        operation = cleanup.OperationWithCleanups(
 
457
            self.this_tree.set_parent_trees)
 
458
        for revision_id in new_parents:
 
459
            try:
 
460
                tree = self.revision_tree(revision_id)
 
461
            except errors.NoSuchRevision:
 
462
                tree = None
 
463
            else:
 
464
                tree.lock_read()
 
465
                operation.add_cleanup(tree.unlock)
 
466
            new_parent_trees.append((revision_id, tree))
 
467
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
460
468
 
461
469
    def set_other(self, other_revision, possible_transports=None):
462
470
        """Set the revision and tree to merge from.
483
491
                raise errors.NoCommits(self.other_branch)
484
492
        if self.other_rev_id is not None:
485
493
            self._cached_trees[self.other_rev_id] = self.other_tree
486
 
        self._maybe_fetch(self.other_branch,
487
 
                          self.this_branch, self.other_basis)
 
494
        self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
488
495
 
489
496
    def set_other_revision(self, revision_id, other_branch):
490
497
        """Set 'other' based on a branch and revision id
527
534
                self.base_rev_id = _mod_revision.NULL_REVISION
528
535
            elif len(lcas) == 1:
529
536
                self.base_rev_id = list(lcas)[0]
530
 
            else:  # len(lcas) > 1
 
537
            else: # len(lcas) > 1
531
538
                self._is_criss_cross = True
532
539
                if len(lcas) > 2:
533
540
                    # find_unique_lca can only handle 2 nodes, so we have to
535
542
                    # the graph again, but better than re-implementing
536
543
                    # find_unique_lca.
537
544
                    self.base_rev_id = self.revision_graph.find_unique_lca(
538
 
                        revisions[0], revisions[1])
 
545
                                            revisions[0], revisions[1])
539
546
                else:
540
547
                    self.base_rev_id = self.revision_graph.find_unique_lca(
541
 
                        *lcas)
 
548
                                            *lcas)
542
549
                sorted_lca_keys = self.revision_graph.find_merge_order(
543
550
                    revisions[0], lcas)
544
551
                if self.base_rev_id == _mod_revision.NULL_REVISION:
557
564
                interesting_revision_ids = set(lcas)
558
565
                interesting_revision_ids.add(self.base_rev_id)
559
566
                interesting_trees = dict((t.get_revision_id(), t)
560
 
                                         for t in self.this_branch.repository.revision_trees(
561
 
                    interesting_revision_ids))
 
567
                    for t in self.this_branch.repository.revision_trees(
 
568
                        interesting_revision_ids))
562
569
                self._cached_trees.update(interesting_trees)
563
570
                if self.base_rev_id in lcas:
564
571
                    self.base_tree = interesting_trees[self.base_rev_id]
612
619
            raise errors.BzrError("Showing base is not supported for this"
613
620
                                  " merge type. %s" % self.merge_type)
614
621
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
615
 
                and not self.base_is_other_ancestor):
 
622
            and not self.base_is_other_ancestor):
616
623
            raise errors.CannotReverseCherrypick()
617
624
        if self.merge_type.supports_cherrypick:
618
625
            kwargs['cherrypick'] = (not self.base_is_ancestor or
633
640
        for hook in Merger.hooks['post_merge']:
634
641
            hook(merge)
635
642
        if self.recurse == 'down':
636
 
            for relpath in self.this_tree.iter_references():
637
 
                sub_tree = self.this_tree.get_nested_tree(relpath)
 
643
            for relpath, file_id in self.this_tree.iter_references():
 
644
                sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
638
645
                other_revision = self.other_tree.get_reference_revision(
639
 
                    relpath)
640
 
                if other_revision == sub_tree.last_revision():
 
646
                    relpath, file_id)
 
647
                if  other_revision == sub_tree.last_revision():
641
648
                    continue
642
649
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
643
650
                sub_merge.merge_type = self.merge_type
644
 
                other_branch = self.other_tree.reference_parent(relpath)
 
651
                other_branch = self.other_branch.reference_parent(
 
652
                    relpath, file_id)
645
653
                sub_merge.set_other_revision(other_revision, other_branch)
646
654
                base_tree_path = _mod_tree.find_previous_path(
647
655
                    self.this_tree, self.base_tree, relpath)
648
656
                base_revision = self.base_tree.get_reference_revision(
649
 
                    base_tree_path)
 
657
                    base_tree_path, file_id)
650
658
                sub_merge.base_tree = \
651
659
                    sub_tree.branch.repository.revision_tree(base_revision)
652
660
                sub_merge.base_rev_id = base_revision
654
662
        return merge
655
663
 
656
664
    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()
 
665
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
 
666
        self.this_tree.lock_tree_write()
 
667
        operation.add_cleanup(self.this_tree.unlock)
 
668
        if self.base_tree is not None:
 
669
            self.base_tree.lock_read()
 
670
            operation.add_cleanup(self.base_tree.unlock)
 
671
        if self.other_tree is not None:
 
672
            self.other_tree.lock_read()
 
673
            operation.add_cleanup(self.other_tree.unlock)
 
674
        merge = operation.run_simple()
664
675
        if len(merge.cooked_conflicts) == 0:
665
676
            if not self.ignore_zero and not trace.is_quiet():
666
677
                trace.note(gettext("All changes applied successfully."))
685
696
    symlink_target = None
686
697
    text_sha1 = None
687
698
 
688
 
    def is_unmodified(self, other):
689
 
        return other is self
690
 
 
691
 
 
692
699
_none_entry = _InventoryNoneEntry()
693
700
 
694
701
 
757
764
            self.do_merge()
758
765
 
759
766
    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
 
767
        operation = cleanup.OperationWithCleanups(self._do_merge)
 
768
        self.working_tree.lock_tree_write()
 
769
        operation.add_cleanup(self.working_tree.unlock)
 
770
        self.this_tree.lock_read()
 
771
        operation.add_cleanup(self.this_tree.unlock)
 
772
        self.base_tree.lock_read()
 
773
        operation.add_cleanup(self.base_tree.unlock)
 
774
        self.other_tree.lock_read()
 
775
        operation.add_cleanup(self.other_tree.unlock)
 
776
        operation.run()
 
777
 
 
778
    def _do_merge(self, operation):
 
779
        self.tt = transform.TreeTransform(self.working_tree, None)
 
780
        operation.add_cleanup(self.tt.finalize)
 
781
        self._compute_transform()
 
782
        results = self.tt.apply(no_conflicts=True)
 
783
        self.write_modified(results)
 
784
        try:
 
785
            self.working_tree.add_conflicts(self.cooked_conflicts)
 
786
        except errors.UnsupportedOperation:
 
787
            pass
774
788
 
775
789
    def make_preview_transform(self):
776
 
        with self.base_tree.lock_read(), self.other_tree.lock_read():
777
 
            self.tt = transform.TransformPreview(self.working_tree)
778
 
            self._compute_transform()
779
 
            return self.tt
 
790
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
 
791
        self.base_tree.lock_read()
 
792
        operation.add_cleanup(self.base_tree.unlock)
 
793
        self.other_tree.lock_read()
 
794
        operation.add_cleanup(self.other_tree.unlock)
 
795
        return operation.run_simple()
 
796
 
 
797
    def _make_preview_transform(self):
 
798
        self.tt = transform.TransformPreview(self.working_tree)
 
799
        self._compute_transform()
 
800
        return self.tt
780
801
 
781
802
    def _compute_transform(self):
782
803
        if self._lca_trees is None:
793
814
        with ui.ui_factory.nested_progress_bar() as child_pb:
794
815
            for num, (file_id, changed, paths3, parents3, names3,
795
816
                      executable3) in enumerate(entries):
796
 
                trans_id = self.tt.trans_id_file_id(file_id)
797
 
 
798
817
                # Try merging each entry
799
818
                child_pb.update(gettext('Preparing file merge'),
800
819
                                num, len(entries))
801
 
                self._merge_names(trans_id, file_id, paths3, parents3,
802
 
                                  names3, resolver=resolver)
 
820
                self._merge_names(file_id, paths3, parents3, names3, resolver=resolver)
803
821
                if changed:
804
 
                    file_status = self._do_merge_contents(paths3, trans_id, file_id)
 
822
                    file_status = self._do_merge_contents(paths3, file_id)
805
823
                else:
806
824
                    file_status = 'unmodified'
807
 
                self._merge_executable(paths3, trans_id, executable3,
808
 
                                       file_status, resolver=resolver)
 
825
                self._merge_executable(paths3, file_id, executable3,
 
826
                        file_status, resolver=resolver)
809
827
        self.tt.fixup_new_roots()
810
828
        self._finish_computing_transform()
811
829
 
815
833
        This is the second half of _compute_transform.
816
834
        """
817
835
        with ui.ui_factory.nested_progress_bar() as child_pb:
818
 
            fs_conflicts = transform.resolve_conflicts(
819
 
                self.tt, child_pb,
 
836
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
820
837
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
821
838
        if self.change_reporter is not None:
822
839
            from breezy import delta
824
841
                self.tt.iter_changes(), self.change_reporter)
825
842
        self.cook_conflicts(fs_conflicts)
826
843
        for conflict in self.cooked_conflicts:
827
 
            trace.warning('%s', conflict.describe())
 
844
            trace.warning(unicode(conflict))
828
845
 
829
846
    def _entries3(self):
830
847
        """Gather data about files modified between three trees.
837
854
        """
838
855
        result = []
839
856
        iterator = self.other_tree.iter_changes(self.base_tree,
840
 
                                                specific_files=self.interesting_files,
841
 
                                                extra_trees=[self.this_tree])
 
857
                specific_files=self.interesting_files,
 
858
                extra_trees=[self.this_tree])
842
859
        this_interesting_files = self.this_tree.find_related_paths_across_trees(
843
 
            self.interesting_files, trees=[self.other_tree])
 
860
                self.interesting_files, trees=[self.other_tree])
844
861
        this_entries = dict(self.this_tree.iter_entries_by_dir(
845
862
                            specific_files=this_interesting_files))
846
 
        for change in iterator:
847
 
            if change.path[0] is not None:
 
863
        for (file_id, paths, changed, versioned, parents, names, kind,
 
864
             executable) in iterator:
 
865
            if paths[0] is not None:
848
866
                this_path = _mod_tree.find_previous_path(
849
 
                    self.base_tree, self.this_tree, change.path[0])
 
867
                        self.base_tree, self.this_tree, paths[0])
850
868
            else:
851
869
                this_path = _mod_tree.find_previous_path(
852
 
                    self.other_tree, self.this_tree, change.path[1])
 
870
                        self.other_tree, self.this_tree, paths[1])
853
871
            this_entry = this_entries.get(this_path)
854
872
            if this_entry is not None:
855
873
                this_name = this_entry.name
859
877
                this_name = None
860
878
                this_parent = None
861
879
                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))
 
880
            parents3 = parents + (this_parent,)
 
881
            names3 = names + (this_name,)
 
882
            paths3 = paths + (this_path, )
 
883
            executable3 = executable + (this_executable,)
 
884
            result.append((file_id, changed, paths3, parents3, names3, executable3))
869
885
        return result
870
886
 
871
887
    def _entries_lca(self):
892
908
            lookup_trees.extend(self._lca_trees)
893
909
            # I think we should include the lca trees as well
894
910
            interesting_files = self.other_tree.find_related_paths_across_trees(
895
 
                self.interesting_files, lookup_trees)
 
911
                    self.interesting_files, lookup_trees)
896
912
        else:
897
913
            interesting_files = None
898
914
        result = []
899
 
        from .multiwalker import MultiWalker
900
 
        walker = MultiWalker(self.other_tree, self._lca_trees)
 
915
        walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
901
916
 
902
 
        for other_path, file_id, other_ie, lca_values in walker.iter_all():
 
917
        base_inventory = self.base_tree.root_inventory
 
918
        this_inventory = self.this_tree.root_inventory
 
919
        for path, file_id, other_ie, lca_values in walker.iter_all():
903
920
            # Is this modified at all from any of the other trees?
904
921
            if other_ie is None:
905
922
                other_ie = _none_entry
906
923
                other_path = None
 
924
            else:
 
925
                other_path = self.other_tree.id2path(file_id)
907
926
            if interesting_files is not None and other_path not in interesting_files:
908
927
                continue
909
928
 
913
932
            # we know that the ancestry is linear, and that OTHER did not
914
933
            # modify anything
915
934
            # 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
 
935
            other_revision = other_ie.revision
 
936
            if other_revision is not None:
 
937
                # We can't use this shortcut when other_revision is None,
 
938
                # because it may be None because things are WorkingTrees, and
 
939
                # not because it is *actually* None.
 
940
                is_unmodified = False
 
941
                for lca_path, ie in lca_values:
 
942
                    if ie is not None and ie.revision == other_revision:
 
943
                        is_unmodified = True
 
944
                        break
 
945
                if is_unmodified:
 
946
                    continue
926
947
 
927
948
            lca_entries = []
928
949
            lca_paths = []
935
956
                    lca_paths.append(lca_path)
936
957
 
937
958
            try:
 
959
                base_ie = base_inventory.get_entry(file_id)
 
960
            except errors.NoSuchId:
 
961
                base_ie = _none_entry
 
962
                base_path = None
 
963
            else:
938
964
                base_path = self.base_tree.id2path(file_id)
939
 
            except errors.NoSuchId:
940
 
                base_path = None
941
 
                base_ie = _none_entry
942
 
            else:
943
 
                base_ie = next(self.base_tree.iter_entries_by_dir(specific_files=[base_path]))[1]
944
965
 
945
966
            try:
946
 
                this_path = self.this_tree.id2path(file_id)
 
967
                this_ie = this_inventory.get_entry(file_id)
947
968
            except errors.NoSuchId:
948
969
                this_ie = _none_entry
949
970
                this_path = None
950
971
            else:
951
 
                this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
 
972
                this_path = self.this_tree.id2path(file_id)
952
973
 
953
974
            lca_kinds = []
954
975
            lca_parent_ids = []
983
1004
                        if path is None:
984
1005
                            return None
985
1006
                        try:
986
 
                            return tree.get_file_sha1(path)
 
1007
                            return tree.get_file_sha1(path, file_id)
987
1008
                        except errors.NoSuchFile:
988
1009
                            return None
989
1010
                    base_sha1 = get_sha1(self.base_tree, base_path)
999
1020
                        (base_ie.executable, lca_executable),
1000
1021
                        other_ie.executable, this_ie.executable)
1001
1022
                    if (parent_id_winner == 'this' and name_winner == 'this'
1002
 
                            and sha1_winner == 'this' and exec_winner == 'this'):
 
1023
                        and sha1_winner == 'this' and exec_winner == 'this'):
1003
1024
                        # No kind, parent, name, exec, or content change for
1004
1025
                        # OTHER, so this node is not considered interesting
1005
1026
                        continue
1009
1030
                    def get_target(ie, tree, path):
1010
1031
                        if ie.kind != 'symlink':
1011
1032
                            return None
1012
 
                        return tree.get_symlink_target(path)
 
1033
                        return tree.get_symlink_target(path, file_id)
1013
1034
                    base_target = get_target(base_ie, self.base_tree, base_path)
1014
1035
                    lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
1015
1036
                                   in zip(lca_entries, self._lca_trees, lca_paths)]
1016
 
                    this_target = get_target(
1017
 
                        this_ie, self.this_tree, this_path)
1018
 
                    other_target = get_target(
1019
 
                        other_ie, self.other_tree, other_path)
 
1037
                    this_target = get_target(this_ie, self.this_tree, this_path)
 
1038
                    other_target = get_target(other_ie, self.other_tree, other_path)
1020
1039
                    target_winner = self._lca_multi_way(
1021
1040
                        (base_target, lca_targets),
1022
1041
                        other_target, this_target)
1023
1042
                    if (parent_id_winner == 'this' and name_winner == 'this'
1024
 
                            and target_winner == 'this'):
 
1043
                        and target_winner == 'this'):
1025
1044
                        # No kind, parent, name, or symlink target change
1026
1045
                        # not interesting
1027
1046
                        continue
1048
1067
                            other_ie.name, this_ie.name),
1049
1068
                           ((base_ie.executable, lca_executable),
1050
1069
                            other_ie.executable, this_ie.executable)
1051
 
                           ))
 
1070
                          ))
1052
1071
        return result
1053
1072
 
1054
1073
    def write_modified(self, results):
1057
1076
        modified_hashes = {}
1058
1077
        for path in results.modified_paths:
1059
1078
            wt_relpath = self.working_tree.relpath(path)
1060
 
            if not self.working_tree.is_versioned(wt_relpath):
 
1079
            file_id = self.working_tree.path2id(wt_relpath)
 
1080
            if file_id is None:
1061
1081
                continue
1062
 
            hash = self.working_tree.get_file_sha1(wt_relpath)
 
1082
            hash = self.working_tree.get_file_sha1(wt_relpath, file_id)
1063
1083
            if hash is None:
1064
1084
                continue
1065
 
            modified_hashes[wt_relpath] = hash
 
1085
            modified_hashes[file_id] = hash
1066
1086
        self.working_tree.set_merge_modified(modified_hashes)
1067
1087
 
1068
1088
    @staticmethod
1069
 
    def parent(entry):
 
1089
    def parent(entry, file_id):
1070
1090
        """Determine the parent for a file_id (used as a key method)"""
1071
1091
        if entry is None:
1072
1092
            return None
1073
1093
        return entry.parent_id
1074
1094
 
1075
1095
    @staticmethod
1076
 
    def name(entry):
 
1096
    def name(entry, file_id):
1077
1097
        """Determine the name for a file_id (used as a key method)"""
1078
1098
        if entry is None:
1079
1099
            return None
1080
1100
        return entry.name
1081
1101
 
1082
1102
    @staticmethod
1083
 
    def contents_sha1(tree, path):
 
1103
    def contents_sha1(tree, path, file_id=None):
1084
1104
        """Determine the sha1 of the file contents (used as a key method)."""
1085
1105
        try:
1086
 
            return tree.get_file_sha1(path)
 
1106
            return tree.get_file_sha1(path, file_id)
1087
1107
        except errors.NoSuchFile:
1088
1108
            return None
1089
1109
 
1090
1110
    @staticmethod
1091
 
    def executable(tree, path):
 
1111
    def executable(tree, path, file_id=None):
1092
1112
        """Determine the executability of a file-id (used as a key method)."""
1093
1113
        try:
1094
 
            if tree.kind(path) != "file":
 
1114
            if tree.kind(path, file_id) != "file":
1095
1115
                return False
1096
1116
        except errors.NoSuchFile:
1097
1117
            return None
1098
 
        return tree.is_executable(path)
 
1118
        return tree.is_executable(path, file_id)
1099
1119
 
1100
1120
    @staticmethod
1101
 
    def kind(tree, path):
 
1121
    def kind(tree, path, file_id=None):
1102
1122
        """Determine the kind of a file-id (used as a key method)."""
1103
1123
        try:
1104
 
            return tree.kind(path)
 
1124
            return tree.kind(path, file_id)
1105
1125
        except errors.NoSuchFile:
1106
1126
            return None
1107
1127
 
1146
1166
        base_val, lca_vals = bases
1147
1167
        # Remove 'base_val' from the lca_vals, because it is not interesting
1148
1168
        filtered_lca_vals = [lca_val for lca_val in lca_vals
1149
 
                             if lca_val != base_val]
 
1169
                                      if lca_val != base_val]
1150
1170
        if len(filtered_lca_vals) == 0:
1151
1171
            return Merge3Merger._three_way(base_val, other, this)
1152
1172
 
1171
1191
        # At this point, the lcas disagree, and the tip disagree
1172
1192
        return 'conflict'
1173
1193
 
1174
 
    def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
1175
 
        """Perform a merge on file names and parents"""
 
1194
    def merge_names(self, paths):
 
1195
        def get_entry(tree, path):
 
1196
            try:
 
1197
                return next(tree.iter_entries_by_dir(specific_files=[path]))[1]
 
1198
            except StopIteration:
 
1199
                return None
 
1200
        used_base_path, other_path, this_path = paths
 
1201
        this_entry = get_entry(self.this_tree, this_path)
 
1202
        other_entry = get_entry(self.other_tree, other_path)
 
1203
        base_entry = get_entry(self.base_tree, base_path)
 
1204
        entries = (base_entry, other_entry, this_entry)
 
1205
        names = []
 
1206
        parents = []
 
1207
        for entry in entries:
 
1208
            if entry is None:
 
1209
                names.append(None)
 
1210
                parents.append(None)
 
1211
            else:
 
1212
                names.append(entry.name)
 
1213
                parents.append(entry.parent_id)
 
1214
        return self._merge_names(file_id, paths, parents, names,
 
1215
                                 resolver=self._three_way)
 
1216
 
 
1217
    def _merge_names(self, file_id, paths, parents, names, resolver):
 
1218
        """Perform a merge on file_id names and parents"""
1176
1219
        base_name, other_name, this_name = names
1177
1220
        base_parent, other_parent, this_parent = parents
1178
1221
        unused_base_path, other_path, this_path = paths
1191
1234
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1192
1235
            # road if a ContentConflict needs to be created so we should not do
1193
1236
            # that
 
1237
            trans_id = self.tt.trans_id_file_id(file_id)
1194
1238
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1195
1239
                                        this_parent, this_name,
1196
1240
                                        other_parent, other_name))
1213
1257
                parent_trans_id = transform.ROOT_PARENT
1214
1258
            else:
1215
1259
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1216
 
            self.tt.adjust_path(name, parent_trans_id, trans_id)
 
1260
            self.tt.adjust_path(name, parent_trans_id,
 
1261
                                self.tt.trans_id_file_id(file_id))
1217
1262
 
1218
 
    def _do_merge_contents(self, paths, trans_id, file_id):
 
1263
    def _do_merge_contents(self, paths, file_id):
1219
1264
        """Performs a merge on file_id contents."""
1220
1265
        def contents_pair(tree, path):
1221
1266
            if path is None:
1222
1267
                return (None, None)
1223
1268
            try:
1224
 
                kind = tree.kind(path)
 
1269
                kind = tree.kind(path, file_id)
1225
1270
            except errors.NoSuchFile:
1226
1271
                return (None, None)
1227
1272
            if kind == "file":
1228
 
                contents = tree.get_file_sha1(path)
 
1273
                contents = tree.get_file_sha1(path, file_id)
1229
1274
            elif kind == "symlink":
1230
 
                contents = tree.get_symlink_target(path)
 
1275
                contents = tree.get_symlink_target(path, file_id)
1231
1276
            else:
1232
1277
                contents = None
1233
1278
            return kind, contents
1259
1304
            return "unmodified"
1260
1305
        # We have a hypothetical conflict, but if we have files, then we
1261
1306
        # can try to merge the content
 
1307
        trans_id = self.tt.trans_id_file_id(file_id)
1262
1308
        params = MergeFileHookParams(
1263
 
            self, (base_path, other_path, this_path), trans_id, this_pair[0],
 
1309
            self, file_id, (base_path, other_path,
 
1310
            this_path), trans_id, this_pair[0],
1264
1311
            other_pair[0], winner)
1265
1312
        hooks = self.active_hooks
1266
1313
        hook_status = 'not_applicable'
1280
1327
            result = None
1281
1328
            name = self.tt.final_name(trans_id)
1282
1329
            parent_id = self.tt.final_parent(trans_id)
 
1330
            duplicate = False
1283
1331
            inhibit_content_conflict = False
1284
 
            if params.this_kind is None:  # file_id is not in THIS
 
1332
            if params.this_kind is None: # file_id is not in THIS
1285
1333
                # Is the name used for a different file_id ?
1286
1334
                if self.this_tree.is_versioned(other_path):
1287
1335
                    # Two entries for the same path
1291
1339
                    self.tt.version_file(file_id, trans_id)
1292
1340
                    transform.create_from_tree(
1293
1341
                        self.tt, trans_id, self.other_tree,
1294
 
                        other_path,
1295
 
                        filter_tree_path=self._get_filter_tree_path(other_path))
 
1342
                        other_path, file_id=file_id,
 
1343
                        filter_tree_path=self._get_filter_tree_path(file_id))
1296
1344
                    inhibit_content_conflict = True
1297
 
            elif params.other_kind is None:  # file_id is not in OTHER
 
1345
            elif params.other_kind is None: # file_id is not in OTHER
1298
1346
                # Is the name used for a different file_id ?
1299
1347
                if self.other_tree.is_versioned(this_path):
1300
1348
                    # Two entries for the same path again, but here, the other
1311
1359
                # This is a contents conflict, because none of the available
1312
1360
                # functions could merge it.
1313
1361
                file_group = self._dump_conflicts(
1314
 
                    name, (base_path, other_path, this_path), parent_id,
1315
 
                    file_id, set_version=True)
 
1362
                        name, (base_path, other_path, this_path), parent_id,
 
1363
                        file_id, set_version=True)
1316
1364
                self._raw_conflicts.append(('contents conflict', file_group))
1317
1365
        elif hook_status == 'success':
1318
1366
            self.tt.create_file(lines, trans_id)
1344
1392
 
1345
1393
    def _default_other_winner_merge(self, merge_hook_params):
1346
1394
        """Replace this contents with other."""
 
1395
        file_id = merge_hook_params.file_id
1347
1396
        trans_id = merge_hook_params.trans_id
1348
1397
        if merge_hook_params.other_path is not None:
1349
1398
            # OTHER changed the file
1350
1399
            transform.create_from_tree(
1351
1400
                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))
 
1401
                merge_hook_params.other_path, file_id=file_id,
 
1402
                filter_tree_path=self._get_filter_tree_path(file_id))
1354
1403
            return 'done', None
1355
1404
        elif merge_hook_params.this_path is not None:
1356
1405
            # OTHER deleted the file
1357
1406
            return 'delete', None
1358
1407
        else:
1359
1408
            raise AssertionError(
1360
 
                'winner is OTHER, but file %r not in THIS or OTHER tree'
1361
 
                % (merge_hook_params.base_path,))
 
1409
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
 
1410
                % (file_id,))
1362
1411
 
1363
1412
    def merge_contents(self, merge_hook_params):
1364
1413
        """Fallback merge logic after user installed hooks."""
1374
1423
            # have agreement that output should be a file.
1375
1424
            try:
1376
1425
                self.text_merge(merge_hook_params.trans_id,
1377
 
                                merge_hook_params.paths)
 
1426
                                merge_hook_params.paths, merge_hook_params.file_id)
1378
1427
            except errors.BinaryFile:
1379
1428
                return 'not_applicable', None
1380
1429
            return 'done', None
1381
1430
        else:
1382
1431
            return 'not_applicable', None
1383
1432
 
1384
 
    def get_lines(self, tree, path):
 
1433
    def get_lines(self, tree, path, file_id=None):
1385
1434
        """Return the lines in a file, or an empty list."""
1386
1435
        if path is None:
1387
1436
            return []
1388
1437
        try:
1389
 
            kind = tree.kind(path)
 
1438
            kind = tree.kind(path, file_id)
1390
1439
        except errors.NoSuchFile:
1391
1440
            return []
1392
1441
        else:
1393
1442
            if kind != 'file':
1394
1443
                return []
1395
 
            return tree.get_file_lines(path)
 
1444
            return tree.get_file_lines(path, file_id)
1396
1445
 
1397
 
    def text_merge(self, trans_id, paths):
1398
 
        """Perform a three-way text merge on a file"""
 
1446
    def text_merge(self, trans_id, paths, file_id):
 
1447
        """Perform a three-way text merge on a file_id"""
1399
1448
        # it's possible that we got here with base as a different type.
1400
1449
        # if so, we just want two-way text conflicts.
1401
1450
        base_path, other_path, this_path = paths
1402
 
        base_lines = self.get_lines(self.base_tree, base_path)
1403
 
        other_lines = self.get_lines(self.other_tree, other_path)
1404
 
        this_lines = self.get_lines(self.this_tree, this_path)
 
1451
        base_lines = self.get_lines(self.base_tree, base_path, file_id)
 
1452
        other_lines = self.get_lines(self.other_tree, other_path, file_id)
 
1453
        this_lines = self.get_lines(self.this_tree, this_path, file_id)
1405
1454
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1406
1455
                           is_cherrypick=self.cherrypick)
1407
 
        start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
 
1456
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1408
1457
        if self.show_base is True:
1409
 
            base_marker = b'|' * 7
 
1458
            base_marker = '|' * 7
1410
1459
        else:
1411
1460
            base_marker = None
1412
1461
 
1413
1462
        def iter_merge3(retval):
1414
1463
            retval["text_conflicts"] = False
1415
 
            for line in m3.merge_lines(name_a=b"TREE",
1416
 
                                       name_b=b"MERGE-SOURCE",
1417
 
                                       name_base=b"BASE-REVISION",
 
1464
            for line in m3.merge_lines(name_a = "TREE",
 
1465
                                       name_b = "MERGE-SOURCE",
 
1466
                                       name_base = "BASE-REVISION",
1418
1467
                                       start_marker=start_marker,
1419
1468
                                       base_marker=base_marker,
1420
1469
                                       reprocess=self.reprocess):
1421
1470
                if line.startswith(start_marker):
1422
1471
                    retval["text_conflicts"] = True
1423
 
                    yield line.replace(start_marker, b'<' * 7)
 
1472
                    yield line.replace(start_marker, '<' * 7)
1424
1473
                else:
1425
1474
                    yield line
1426
1475
        retval = {}
1430
1479
            self._raw_conflicts.append(('text conflict', trans_id))
1431
1480
            name = self.tt.final_name(trans_id)
1432
1481
            parent_id = self.tt.final_parent(trans_id)
1433
 
            file_id = self.tt.final_file_id(trans_id)
1434
1482
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1435
1483
                                              this_lines, base_lines,
1436
1484
                                              other_lines)
1437
1485
            file_group.append(trans_id)
1438
1486
 
1439
 
    def _get_filter_tree_path(self, path):
 
1487
    def _get_filter_tree_path(self, file_id):
1440
1488
        if self.this_tree.supports_content_filtering():
1441
1489
            # We get the path from the working tree if it exists.
1442
1490
            # That fails though when OTHER is adding a file, so
1443
1491
            # we fall back to the other tree to find the path if
1444
1492
            # 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
 
1493
            try:
 
1494
                return self.this_tree.id2path(file_id)
 
1495
            except errors.NoSuchId:
 
1496
                return self.other_tree.id2path(file_id)
 
1497
        # Skip the id2path lookup for older formats
1451
1498
        return None
1452
1499
 
1453
1500
    def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1482
1529
        for suffix, tree, path, lines in data:
1483
1530
            if path is not None:
1484
1531
                trans_id = self._conflict_file(
1485
 
                    name, parent_id, path, tree, suffix, lines,
1486
 
                    filter_tree_path)
 
1532
                        name, parent_id, path, tree, file_id, suffix, lines,
 
1533
                        filter_tree_path)
1487
1534
                file_group.append(trans_id)
1488
1535
                if set_version and not versioned:
1489
1536
                    self.tt.version_file(file_id, trans_id)
1490
1537
                    versioned = True
1491
1538
        return file_group
1492
1539
 
1493
 
    def _conflict_file(self, name, parent_id, path, tree, suffix,
 
1540
    def _conflict_file(self, name, parent_id, path, tree, file_id, suffix,
1494
1541
                       lines=None, filter_tree_path=None):
1495
1542
        """Emit a single conflict file."""
1496
1543
        name = name + '.' + suffix
1497
1544
        trans_id = self.tt.create_path(name, parent_id)
1498
1545
        transform.create_from_tree(
1499
 
            self.tt, trans_id, tree, path,
1500
 
            chunks=lines,
1501
 
            filter_tree_path=filter_tree_path)
 
1546
                self.tt, trans_id, tree, path,
 
1547
                file_id=file_id, bytes=lines,
 
1548
                filter_tree_path=filter_tree_path)
1502
1549
        return trans_id
1503
1550
 
1504
 
    def _merge_executable(self, paths, trans_id, executable, file_status,
 
1551
    def merge_executable(self, paths, file_id, file_status):
 
1552
        """Perform a merge on the execute bit."""
 
1553
        executable = [self.executable(t, p, file_id) for t, p in zip([self.base_tree,
 
1554
                      self.other_tree, self.this_tree], paths)]
 
1555
        self._merge_executable(paths, file_id, executable, file_status,
 
1556
                               resolver=self._three_way)
 
1557
 
 
1558
    def _merge_executable(self, paths, file_id, executable, file_status,
1505
1559
                          resolver):
1506
1560
        """Perform a merge on the execute bit."""
1507
1561
        base_executable, other_executable, this_executable = executable
1510
1564
            return
1511
1565
        winner = resolver(*executable)
1512
1566
        if winner == "conflict":
1513
 
            # There must be a None in here, if we have a conflict, but we
1514
 
            # need executability since file status was not deleted.
 
1567
        # There must be a None in here, if we have a conflict, but we
 
1568
        # need executability since file status was not deleted.
1515
1569
            if other_path is None:
1516
1570
                winner = "this"
1517
1571
            else:
1518
1572
                winner = "other"
1519
1573
        if winner == 'this' and file_status != "modified":
1520
1574
            return
 
1575
        trans_id = self.tt.trans_id_file_id(file_id)
1521
1576
        if self.tt.final_kind(trans_id) != "file":
1522
1577
            return
1523
1578
        if winner == "this":
1530
1585
            elif base_path is not None:
1531
1586
                executability = base_executable
1532
1587
        if executability is not None:
 
1588
            trans_id = self.tt.trans_id_file_id(file_id)
1533
1589
            self.tt.set_executability(executability, trans_id)
1534
1590
 
1535
1591
    def cook_conflicts(self, fs_conflicts):
1541
1597
            conflict_type = conflict[0]
1542
1598
            if conflict_type == 'path conflict':
1543
1599
                (trans_id, file_id,
1544
 
                 this_parent, this_name,
1545
 
                 other_parent, other_name) = conflict[1:]
 
1600
                this_parent, this_name,
 
1601
                other_parent, other_name) = conflict[1:]
1546
1602
                if this_parent is None or this_name is None:
1547
1603
                    this_path = '<deleted>'
1548
1604
                else:
1549
 
                    parent_path = fp.get_path(
 
1605
                    parent_path =  fp.get_path(
1550
1606
                        self.tt.trans_id_file_id(this_parent))
1551
1607
                    this_path = osutils.pathjoin(parent_path, this_name)
1552
1608
                if other_parent is None or other_name is None:
1553
1609
                    other_path = '<deleted>'
1554
1610
                else:
1555
 
                    if other_parent == self.other_tree.path2id(''):
 
1611
                    if other_parent == self.other_tree.get_root_id():
1556
1612
                        # The tree transform doesn't know about the other root,
1557
1613
                        # so we special case here to avoid a NoFinalPath
1558
1614
                        # exception
1559
1615
                        parent_path = ''
1560
1616
                    else:
1561
 
                        parent_path = fp.get_path(
 
1617
                        parent_path =  fp.get_path(
1562
1618
                            self.tt.trans_id_file_id(other_parent))
1563
1619
                    other_path = osutils.pathjoin(parent_path, other_name)
1564
1620
                c = _mod_conflicts.Conflict.factory(
1597
1653
        # conflict is enough.
1598
1654
        for c in cooked_conflicts:
1599
1655
            if (c.typestring == 'path conflict'
1600
 
                    and c.file_id in content_conflict_file_ids):
 
1656
                and c.file_id in content_conflict_file_ids):
1601
1657
                continue
1602
1658
            self.cooked_conflicts.append(c)
1603
1659
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1611
1667
    history_based = True
1612
1668
    requires_file_merge_plan = True
1613
1669
 
1614
 
    def _generate_merge_plan(self, this_path, base):
1615
 
        return self.this_tree.plan_file_merge(this_path, self.other_tree,
 
1670
    def _generate_merge_plan(self, file_id, base):
 
1671
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
1616
1672
                                              base=base)
1617
1673
 
1618
 
    def _merged_lines(self, this_path):
 
1674
    def _merged_lines(self, file_id):
1619
1675
        """Generate the merged lines.
1620
1676
        There is no distinction between lines that are meant to contain <<<<<<<
1621
1677
        and conflicts.
1624
1680
            base = self.base_tree
1625
1681
        else:
1626
1682
            base = None
1627
 
        plan = self._generate_merge_plan(this_path, base)
 
1683
        plan = self._generate_merge_plan(file_id, base)
1628
1684
        if 'merge' in debug.debug_flags:
1629
1685
            plan = list(plan)
1630
1686
            trans_id = self.tt.trans_id_file_id(file_id)
1631
1687
            name = self.tt.final_name(trans_id) + '.plan'
1632
 
            contents = (b'%11s|%s' % l for l in plan)
 
1688
            contents = ('%11s|%s' % l for l in plan)
1633
1689
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1634
 
        textmerge = versionedfile.PlanWeaveMerge(plan, b'<<<<<<< TREE\n',
1635
 
                                                 b'>>>>>>> MERGE-SOURCE\n')
 
1690
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
 
1691
                                                 '>>>>>>> MERGE-SOURCE\n')
1636
1692
        lines, conflicts = textmerge.merge_lines(self.reprocess)
1637
1693
        if conflicts:
1638
1694
            base_lines = textmerge.base_from_plan()
1640
1696
            base_lines = None
1641
1697
        return lines, base_lines
1642
1698
 
1643
 
    def text_merge(self, trans_id, paths):
 
1699
    def text_merge(self, trans_id, paths, file_id):
1644
1700
        """Perform a (weave) text merge for a given file and file-id.
1645
1701
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1646
1702
        and a conflict will be noted.
1647
1703
        """
1648
1704
        base_path, other_path, this_path = paths
1649
 
        lines, base_lines = self._merged_lines(this_path)
 
1705
        lines, base_lines = self._merged_lines(file_id)
1650
1706
        lines = list(lines)
1651
1707
        # Note we're checking whether the OUTPUT is binary in this case,
1652
1708
        # because we don't want to get into weave merge guts.
1657
1713
            self._raw_conflicts.append(('text conflict', trans_id))
1658
1714
            name = self.tt.final_name(trans_id)
1659
1715
            parent_id = self.tt.final_parent(trans_id)
1660
 
            file_id = self.tt.final_file_id(trans_id)
1661
1716
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1662
1717
                                              no_base=False,
1663
1718
                                              base_lines=base_lines)
1668
1723
 
1669
1724
    requires_file_merge_plan = True
1670
1725
 
1671
 
    def _generate_merge_plan(self, this_path, base):
1672
 
        return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
 
1726
    def _generate_merge_plan(self, file_id, base):
 
1727
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
1673
1728
                                                  base=base)
1674
1729
 
1675
 
 
1676
1730
class Diff3Merger(Merge3Merger):
1677
1731
    """Three-way merger using external diff3 for text merging"""
1678
1732
 
1679
1733
    requires_file_merge_plan = False
1680
1734
 
1681
 
    def dump_file(self, temp_dir, name, tree, path):
 
1735
    def dump_file(self, temp_dir, name, tree, path, file_id=None):
1682
1736
        out_path = osutils.pathjoin(temp_dir, name)
1683
1737
        with open(out_path, "wb") as out_file:
1684
 
            in_file = tree.get_file(path)
 
1738
            in_file = tree.get_file(path, file_id=None)
1685
1739
            for line in in_file:
1686
1740
                out_file.write(line)
1687
1741
        return out_path
1688
1742
 
1689
 
    def text_merge(self, trans_id, paths):
 
1743
    def text_merge(self, trans_id, paths, file_id):
1690
1744
        """Perform a diff3 merge using a specified file-id and trans-id.
1691
1745
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1692
1746
        will be dumped, and a will be conflict noted.
1696
1750
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1697
1751
        try:
1698
1752
            new_file = osutils.pathjoin(temp_dir, "new")
1699
 
            this = self.dump_file(
1700
 
                temp_dir, "this", self.this_tree, this_path)
1701
 
            base = self.dump_file(
1702
 
                temp_dir, "base", self.base_tree, base_path)
1703
 
            other = self.dump_file(
1704
 
                temp_dir, "other", self.other_tree, other_path)
 
1753
            this = self.dump_file(temp_dir, "this", self.this_tree, this_path, file_id)
 
1754
            base = self.dump_file(temp_dir, "base", self.base_tree, base_path, file_id)
 
1755
            other = self.dump_file(temp_dir, "other", self.other_tree, other_path, file_id)
1705
1756
            status = breezy.patch.diff3(new_file, this, base, other)
1706
1757
            if status not in (0, 1):
1707
1758
                raise errors.BzrError("Unhandled diff3 exit code")
1710
1761
            if status == 1:
1711
1762
                name = self.tt.final_name(trans_id)
1712
1763
                parent_id = self.tt.final_parent(trans_id)
1713
 
                file_id = self.tt.final_file_id(trans_id)
1714
1764
                self._dump_conflicts(name, paths, parent_id, file_id)
1715
1765
                self._raw_conflicts.append(('text conflict', trans_id))
1716
1766
        finally:
1733
1783
    """
1734
1784
 
1735
1785
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1736
 
                 source_subpath, other_rev_id=None):
 
1786
            source_subpath, other_rev_id=None):
1737
1787
        """Create a new MergeIntoMerger object.
1738
1788
 
1739
1789
        source_subpath in other_tree will be effectively copied to
1750
1800
        # It is assumed that we are merging a tree that is not in our current
1751
1801
        # ancestry, which means we are using the "EmptyTree" as our basis.
1752
1802
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1753
 
            _mod_revision.NULL_REVISION)
 
1803
                                _mod_revision.NULL_REVISION)
1754
1804
        super(MergeIntoMerger, self).__init__(
1755
1805
            this_branch=this_tree.branch,
1756
1806
            this_tree=this_tree,
1770
1820
        self.reprocess = False
1771
1821
        self.interesting_files = None
1772
1822
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1773
 
                                                  target_subdir=self._target_subdir,
1774
 
                                                  source_subpath=self._source_subpath)
 
1823
              target_subdir=self._target_subdir,
 
1824
              source_subpath=self._source_subpath)
1775
1825
        if self._source_subpath != '':
1776
1826
            # If this isn't a partial merge make sure the revisions will be
1777
1827
            # present.
1778
1828
            self._maybe_fetch(self.other_branch, self.this_branch,
1779
 
                              self.other_basis)
 
1829
                self.other_basis)
1780
1830
 
1781
1831
    def set_pending(self):
1782
1832
        if self._source_subpath != '':
1831
1881
            entries = self._entries_to_incorporate()
1832
1882
            entries = list(entries)
1833
1883
            for num, (entry, parent_id, relpath) in enumerate(entries):
1834
 
                child_pb.update(gettext('Preparing file merge'),
1835
 
                                num, len(entries))
 
1884
                child_pb.update(gettext('Preparing file merge'), num, len(entries))
1836
1885
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1837
1886
                path = osutils.pathjoin(self._source_subpath, relpath)
1838
1887
                trans_id = transform.new_by_entry(path, self.tt, entry,
1839
 
                                                  parent_trans_id, self.other_tree)
 
1888
                    parent_trans_id, self.other_tree)
1840
1889
        self._finish_computing_transform()
1841
1890
 
1842
1891
    def _entries_to_incorporate(self):
1843
1892
        """Yields pairs of (inventory_entry, new_parent)."""
1844
 
        subdir_id = self.other_tree.path2id(self._source_subpath)
 
1893
        other_inv = self.other_tree.root_inventory
 
1894
        subdir_id = other_inv.path2id(self._source_subpath)
1845
1895
        if subdir_id is None:
1846
1896
            # XXX: The error would be clearer if it gave the URL of the source
1847
1897
            # branch, but we don't have a reference to that here.
1848
1898
            raise PathNotInTree(self._source_subpath, "Source tree")
1849
 
        subdir = next(self.other_tree.iter_entries_by_dir(
1850
 
            specific_files=[self._source_subpath]))[1]
 
1899
        subdir = other_inv.get_entry(subdir_id)
1851
1900
        parent_in_target = osutils.dirname(self._target_subdir)
1852
1901
        target_id = self.this_tree.path2id(parent_in_target)
1853
1902
        if target_id is None:
1855
1904
        name_in_target = osutils.basename(self._target_subdir)
1856
1905
        merge_into_root = subdir.copy()
1857
1906
        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:
 
1907
        if self.this_tree.has_id(merge_into_root.file_id):
1863
1908
            # Give the root a new file-id.
1864
1909
            # This can happen fairly easily if the directory we are
1865
1910
            # incorporating is the root, and both trees have 'TREE_ROOT' as
1873
1918
        if subdir.kind != 'directory':
1874
1919
            # No children, so we are done.
1875
1920
            return
1876
 
        for path, entry in self.other_tree.root_inventory.iter_entries_by_dir(subdir_id):
 
1921
        for path, entry in other_inv.iter_entries_by_dir(subdir_id):
1877
1922
            parent_id = entry.parent_id
1878
1923
            if parent_id == subdir.file_id:
1879
1924
                # The root's parent ID has changed, so make sure children of
2007
2052
        for record in self.vf.get_record_stream(keys, 'unordered', True):
2008
2053
            if record.storage_kind == 'absent':
2009
2054
                raise errors.RevisionNotPresent(record.key, self.vf)
2010
 
            result[record.key[-1]] = record.get_bytes_as('lines')
 
2055
            result[record.key[-1]] = osutils.chunks_to_lines(
 
2056
                record.get_bytes_as('chunked'))
2011
2057
        return result
2012
2058
 
2013
2059
    def plan_merge(self):
2046
2092
                else:
2047
2093
                    yield 'killed-a', self.lines_b[b_index]
2048
2094
            # handle common lines
2049
 
            for a_index in range(i, i + n):
 
2095
            for a_index in range(i, i+n):
2050
2096
                yield 'unchanged', self.lines_a[a_index]
2051
 
            last_i = i + n
2052
 
            last_j = j + n
 
2097
            last_i = i+n
 
2098
            last_j = j+n
2053
2099
 
2054
2100
    def _get_matching_blocks(self, left_revision, right_revision):
2055
2101
        """Return a description of which sections of two revisions match.
2111
2157
        for i, j, n in matcher.get_matching_blocks():
2112
2158
            for jj in range(last_j, j):
2113
2159
                yield new_plan[jj]
2114
 
            for jj in range(j, j + n):
 
2160
            for jj in range(j, j+n):
2115
2161
                plan_line = new_plan[jj]
2116
2162
                if plan_line[0] == 'new-b':
2117
2163
                    pass
2348
2394
        are combined, they are written out in the format described in
2349
2395
        VersionedFile.plan_merge
2350
2396
        """
2351
 
        if self._head_key is not None:  # There was a single head
 
2397
        if self._head_key is not None: # There was a single head
2352
2398
            if self._head_key == self.a_key:
2353
2399
                plan = 'new-a'
2354
2400
            else: