/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: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
 
import warnings
 
17
import contextlib
20
18
 
21
19
from .lazy_import import lazy_import
22
20
lazy_import(globals(), """
 
21
import patiencediff
 
22
 
23
23
from breezy import (
24
24
    branch as _mod_branch,
25
 
    cleanup,
26
25
    conflicts as _mod_conflicts,
27
26
    debug,
28
 
    generate_ids,
29
27
    graph as _mod_graph,
30
28
    merge3,
31
29
    osutils,
32
 
    patiencediff,
33
30
    revision as _mod_revision,
34
31
    textfile,
35
32
    trace,
40
37
    workingtree,
41
38
    )
42
39
from breezy.bzr import (
 
40
    generate_ids,
43
41
    versionedfile,
44
42
    )
45
43
from breezy.i18n import gettext
50
48
    hooks,
51
49
    registry,
52
50
    )
53
 
from .sixish import (
54
 
    viewitems,
55
 
    )
56
51
# TODO: Report back as changes are merged in
57
52
 
58
53
 
59
 
def transform_tree(from_tree, to_tree, interesting_ids=None):
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, interesting_ids=interesting_ids, this_tree=from_tree)
 
54
def transform_tree(from_tree, to_tree, interesting_files=None):
 
55
    with from_tree.lock_tree_write():
 
56
        merge_inner(from_tree.branch, to_tree, from_tree,
 
57
                    ignore_zero=True, this_tree=from_tree,
 
58
                    interesting_files=interesting_files)
65
59
 
66
60
 
67
61
class MergeHooks(hooks.Hooks):
69
63
    def __init__(self):
70
64
        hooks.Hooks.__init__(self, "breezy.merge", "Merger.hooks")
71
65
        self.add_hook('merge_file_content',
72
 
            "Called with a breezy.merge.Merger object to create a per file "
73
 
            "merge object when starting a merge. "
74
 
            "Should return either None or a subclass of "
75
 
            "``breezy.merge.AbstractPerFileMerger``. "
76
 
            "Such objects will then be called per file "
77
 
            "that needs to be merged (including when one "
78
 
            "side has deleted the file and the other has changed it). "
79
 
            "See the AbstractPerFileMerger API docs for details on how it is "
80
 
            "used by merge.",
81
 
            (2, 1))
 
66
                      "Called with a breezy.merge.Merger object to create a per file "
 
67
                      "merge object when starting a merge. "
 
68
                      "Should return either None or a subclass of "
 
69
                      "``breezy.merge.AbstractPerFileMerger``. "
 
70
                      "Such objects will then be called per file "
 
71
                      "that needs to be merged (including when one "
 
72
                      "side has deleted the file and the other has changed it). "
 
73
                      "See the AbstractPerFileMerger API docs for details on how it is "
 
74
                      "used by merge.",
 
75
                      (2, 1))
82
76
        self.add_hook('pre_merge',
83
 
            'Called before a merge. '
84
 
            'Receives a Merger object as the single argument.',
85
 
            (2, 5))
 
77
                      'Called before a merge. '
 
78
                      'Receives a Merger object as the single argument.',
 
79
                      (2, 5))
86
80
        self.add_hook('post_merge',
87
 
            'Called after a merge. '
88
 
            'Receives a Merger object as the single argument. '
89
 
            'The return value is ignored.',
90
 
            (2, 5))
 
81
                      'Called after a merge. '
 
82
                      'Receives a Merger object as the single argument. '
 
83
                      'The return value is ignored.',
 
84
                      (2, 5))
91
85
 
92
86
 
93
87
class AbstractPerFileMerger(object):
94
88
    """PerFileMerger objects are used by plugins extending merge for breezy.
95
89
 
96
90
    See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
97
 
    
 
91
 
98
92
    :ivar merger: The Merge3Merger performing the merge.
99
93
    """
100
94
 
104
98
 
105
99
    def merge_contents(self, merge_params):
106
100
        """Attempt to merge the contents of a single file.
107
 
        
 
101
 
108
102
        :param merge_params: A breezy.merge.MergeFileHookParams
109
103
        :return: A tuple of (status, chunks), where status is one of
110
104
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
130
124
        """
131
125
        raise NotImplementedError(self.file_matches)
132
126
 
133
 
    def get_filename(self, params, tree):
134
 
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
135
 
        self.merger.this_tree) and a MergeFileHookParams.
136
 
        """
137
 
        return osutils.basename(tree.id2path(params.file_id))
138
 
 
139
 
    def get_filepath(self, params, tree):
140
 
        """Calculate the path to the file in a tree.
141
 
 
142
 
        :param params: A MergeFileHookParams describing the file to merge
143
 
        :param tree: a Tree, e.g. self.merger.this_tree.
144
 
        """
145
 
        return tree.id2path(params.file_id)
146
 
 
147
127
    def merge_contents(self, params):
148
128
        """Merge the contents of a single file."""
149
129
        # Check whether this custom merge logic should be used.
153
133
            # THIS and OTHER aren't both files.
154
134
            not params.is_file_merge() or
155
135
            # The filename doesn't match
156
 
            not self.file_matches(params)):
 
136
                not self.file_matches(params)):
157
137
            return 'not_applicable', None
158
138
        return self.merge_matching(params)
159
139
 
174
154
    classes should implement ``merge_text``.
175
155
 
176
156
    See ``breezy.plugins.news_merge.news_merge`` for an example concrete class.
177
 
    
 
157
 
178
158
    :ivar affected_files: The configured file paths to merge.
179
159
 
180
160
    :cvar name_prefix: The prefix to use when looking up configuration
181
161
        details. <name_prefix>_merge_files describes the files targeted by the
182
162
        hook for example.
183
 
        
 
163
 
184
164
    :cvar default_files: The default file paths to merge when no configuration
185
165
        is present.
186
166
    """
216
196
                affected_files = self.default_files
217
197
            self.affected_files = affected_files
218
198
        if affected_files:
219
 
            filepath = self.get_filepath(params, self.merger.this_tree)
 
199
            filepath = params.this_path
220
200
            if filepath in affected_files:
221
201
                return True
222
202
        return False
239
219
 
240
220
    There are some fields hooks can access:
241
221
 
242
 
    :ivar file_id: the file ID of the file being merged
 
222
    :ivar base_path: Path in base tree
 
223
    :ivar other_path: Path in other tree
 
224
    :ivar this_path: Path in this tree
243
225
    :ivar trans_id: the transform ID for the merge of this file
244
 
    :ivar this_kind: kind of file_id in 'this' tree
245
 
    :ivar other_kind: kind of file_id in 'other' tree
 
226
    :ivar this_kind: kind of file in 'this' tree
 
227
    :ivar other_kind: kind of file in 'other' tree
246
228
    :ivar winner: one of 'this', 'other', 'conflict'
247
229
    """
248
230
 
249
 
    def __init__(self, merger, file_id, trans_id, this_kind, other_kind,
250
 
            winner):
 
231
    def __init__(self, merger, paths, trans_id, this_kind, other_kind,
 
232
                 winner):
251
233
        self._merger = merger
252
 
        self.file_id = file_id
 
234
        self.paths = paths
 
235
        self.base_path, self.other_path, self.this_path = paths
253
236
        self.trans_id = trans_id
254
237
        self.this_kind = this_kind
255
238
        self.other_kind = other_kind
262
245
    @decorators.cachedproperty
263
246
    def base_lines(self):
264
247
        """The lines of the 'base' version of the file."""
265
 
        return self._merger.get_lines(self._merger.base_tree, self.file_id)
 
248
        return self._merger.get_lines(self._merger.base_tree, self.base_path)
266
249
 
267
250
    @decorators.cachedproperty
268
251
    def this_lines(self):
269
252
        """The lines of the 'this' version of the file."""
270
 
        return self._merger.get_lines(self._merger.this_tree, self.file_id)
 
253
        return self._merger.get_lines(self._merger.this_tree, self.this_path)
271
254
 
272
255
    @decorators.cachedproperty
273
256
    def other_lines(self):
274
257
        """The lines of the 'other' version of the file."""
275
 
        return self._merger.get_lines(self._merger.other_tree, self.file_id)
 
258
        return self._merger.get_lines(self._merger.other_tree, self.other_path)
276
259
 
277
260
 
278
261
class Merger(object):
295
278
        self.base_tree = base_tree
296
279
        self.ignore_zero = False
297
280
        self.backup_files = False
298
 
        self.interesting_ids = None
299
281
        self.interesting_files = None
300
282
        self.show_base = False
301
283
        self.reprocess = False
382
364
        if base_revision_id is not None:
383
365
            if (base_revision_id != _mod_revision.NULL_REVISION and
384
366
                revision_graph.is_ancestor(
385
 
                base_revision_id, tree.branch.last_revision())):
 
367
                    base_revision_id, tree.branch.last_revision())):
386
368
                base_revision_id = None
387
369
            else:
388
370
                trace.warning('Performing cherrypick')
389
371
        merger = klass.from_revision_ids(tree, other_revision_id,
390
 
                                         base_revision_id, revision_graph=
391
 
                                         revision_graph)
 
372
                                         base_revision_id, revision_graph=revision_graph)
392
373
        return merger, verified
393
374
 
394
375
    @staticmethod
455
436
 
456
437
    def set_pending(self):
457
438
        if (not self.base_is_ancestor or not self.base_is_other_ancestor
458
 
            or self.other_rev_id is None):
 
439
                or self.other_rev_id is None):
459
440
            return
460
441
        self._add_parent()
461
442
 
462
443
    def _add_parent(self):
463
444
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
464
445
        new_parent_trees = []
465
 
        operation = cleanup.OperationWithCleanups(
466
 
            self.this_tree.set_parent_trees)
467
 
        for revision_id in new_parents:
468
 
            try:
469
 
                tree = self.revision_tree(revision_id)
470
 
            except errors.NoSuchRevision:
471
 
                tree = None
472
 
            else:
473
 
                tree.lock_read()
474
 
                operation.add_cleanup(tree.unlock)
475
 
            new_parent_trees.append((revision_id, tree))
476
 
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
 
446
        with contextlib.ExitStack() as stack:
 
447
            for revision_id in new_parents:
 
448
                try:
 
449
                    tree = self.revision_tree(revision_id)
 
450
                except errors.NoSuchRevision:
 
451
                    tree = None
 
452
                else:
 
453
                    stack.enter_context(tree.lock_read())
 
454
                new_parent_trees.append((revision_id, tree))
 
455
            self.this_tree.set_parent_trees(new_parent_trees, allow_leftmost_as_ghost=True)
477
456
 
478
457
    def set_other(self, other_revision, possible_transports=None):
479
458
        """Set the revision and tree to merge from.
500
479
                raise errors.NoCommits(self.other_branch)
501
480
        if self.other_rev_id is not None:
502
481
            self._cached_trees[self.other_rev_id] = self.other_tree
503
 
        self._maybe_fetch(self.other_branch, self.this_branch, self.other_basis)
 
482
        self._maybe_fetch(self.other_branch,
 
483
                          self.this_branch, self.other_basis)
504
484
 
505
485
    def set_other_revision(self, revision_id, other_branch):
506
486
        """Set 'other' based on a branch and revision id
543
523
                self.base_rev_id = _mod_revision.NULL_REVISION
544
524
            elif len(lcas) == 1:
545
525
                self.base_rev_id = list(lcas)[0]
546
 
            else: # len(lcas) > 1
 
526
            else:  # len(lcas) > 1
547
527
                self._is_criss_cross = True
548
528
                if len(lcas) > 2:
549
529
                    # find_unique_lca can only handle 2 nodes, so we have to
551
531
                    # the graph again, but better than re-implementing
552
532
                    # find_unique_lca.
553
533
                    self.base_rev_id = self.revision_graph.find_unique_lca(
554
 
                                            revisions[0], revisions[1])
 
534
                        revisions[0], revisions[1])
555
535
                else:
556
536
                    self.base_rev_id = self.revision_graph.find_unique_lca(
557
 
                                            *lcas)
 
537
                        *lcas)
558
538
                sorted_lca_keys = self.revision_graph.find_merge_order(
559
539
                    revisions[0], lcas)
560
540
                if self.base_rev_id == _mod_revision.NULL_REVISION:
573
553
                interesting_revision_ids = set(lcas)
574
554
                interesting_revision_ids.add(self.base_rev_id)
575
555
                interesting_trees = dict((t.get_revision_id(), t)
576
 
                    for t in self.this_branch.repository.revision_trees(
577
 
                        interesting_revision_ids))
 
556
                                         for t in self.this_branch.repository.revision_trees(
 
557
                    interesting_revision_ids))
578
558
                self._cached_trees.update(interesting_trees)
579
559
                if self.base_rev_id in lcas:
580
560
                    self.base_tree = interesting_trees[self.base_rev_id]
610
590
    def make_merger(self):
611
591
        kwargs = {'working_tree': self.this_tree, 'this_tree': self.this_tree,
612
592
                  'other_tree': self.other_tree,
613
 
                  'interesting_ids': self.interesting_ids,
614
593
                  'interesting_files': self.interesting_files,
615
594
                  'this_branch': self.this_branch,
616
595
                  'other_branch': self.other_branch,
629
608
            raise errors.BzrError("Showing base is not supported for this"
630
609
                                  " merge type. %s" % self.merge_type)
631
610
        if (not getattr(self.merge_type, 'supports_reverse_cherrypick', True)
632
 
            and not self.base_is_other_ancestor):
 
611
                and not self.base_is_other_ancestor):
633
612
            raise errors.CannotReverseCherrypick()
634
613
        if self.merge_type.supports_cherrypick:
635
614
            kwargs['cherrypick'] = (not self.base_is_ancestor or
650
629
        for hook in Merger.hooks['post_merge']:
651
630
            hook(merge)
652
631
        if self.recurse == 'down':
653
 
            for relpath, file_id in self.this_tree.iter_references():
654
 
                sub_tree = self.this_tree.get_nested_tree(relpath, file_id)
 
632
            for relpath in self.this_tree.iter_references():
 
633
                sub_tree = self.this_tree.get_nested_tree(relpath)
655
634
                other_revision = self.other_tree.get_reference_revision(
656
 
                    relpath, file_id)
657
 
                if  other_revision == sub_tree.last_revision():
 
635
                    relpath)
 
636
                if other_revision == sub_tree.last_revision():
658
637
                    continue
659
638
                sub_merge = Merger(sub_tree.branch, this_tree=sub_tree)
660
639
                sub_merge.merge_type = self.merge_type
661
 
                other_branch = self.other_branch.reference_parent(file_id,
662
 
                                                                  relpath)
 
640
                other_branch = self.other_tree.reference_parent(relpath)
663
641
                sub_merge.set_other_revision(other_revision, other_branch)
664
 
                base_tree_path = self.base_tree.id2path(file_id)
 
642
                base_tree_path = _mod_tree.find_previous_path(
 
643
                    self.this_tree, self.base_tree, relpath)
665
644
                base_revision = self.base_tree.get_reference_revision(
666
 
                    base_tree_path, file_id)
 
645
                    base_tree_path)
667
646
                sub_merge.base_tree = \
668
647
                    sub_tree.branch.repository.revision_tree(base_revision)
669
648
                sub_merge.base_rev_id = base_revision
671
650
        return merge
672
651
 
673
652
    def do_merge(self):
674
 
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
675
 
        self.this_tree.lock_tree_write()
676
 
        operation.add_cleanup(self.this_tree.unlock)
677
 
        if self.base_tree is not None:
678
 
            self.base_tree.lock_read()
679
 
            operation.add_cleanup(self.base_tree.unlock)
680
 
        if self.other_tree is not None:
681
 
            self.other_tree.lock_read()
682
 
            operation.add_cleanup(self.other_tree.unlock)
683
 
        merge = operation.run_simple()
 
653
        with contextlib.ExitStack() as stack:
 
654
            stack.enter_context(self.this_tree.lock_tree_write())
 
655
            if self.base_tree is not None:
 
656
                stack.enter_context(self.base_tree.lock_read())
 
657
            if self.other_tree is not None:
 
658
                stack.enter_context(self.other_tree.lock_read())
 
659
            merge = self._do_merge_to()
684
660
        if len(merge.cooked_conflicts) == 0:
685
661
            if not self.ignore_zero and not trace.is_quiet():
686
662
                trace.note(gettext("All changes applied successfully."))
705
681
    symlink_target = None
706
682
    text_sha1 = None
707
683
 
 
684
    def is_unmodified(self, other):
 
685
        return other is self
 
686
 
 
687
 
708
688
_none_entry = _InventoryNoneEntry()
709
689
 
710
690
 
718
698
    supports_reverse_cherrypick = True
719
699
    winner_idx = {"this": 2, "other": 1, "conflict": 1}
720
700
    supports_lca_trees = True
 
701
    requires_file_merge_plan = False
721
702
 
722
703
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
723
 
                 interesting_ids=None, reprocess=False, show_base=False,
 
704
                 reprocess=False, show_base=False,
724
705
                 change_reporter=None, interesting_files=None, do_merge=True,
725
706
                 cherrypick=False, lca_trees=None, this_branch=None,
726
707
                 other_branch=None):
733
714
        :param this_branch: The branch associated with this_tree.  Defaults to
734
715
            this_tree.branch if not supplied.
735
716
        :param other_branch: The branch associated with other_tree, if any.
736
 
        :param interesting_ids: The file_ids of files that should be
737
 
            participate in the merge.  May not be combined with
738
 
            interesting_files.
739
717
        :param: reprocess If True, perform conflict-reduction processing.
740
718
        :param show_base: If True, show the base revision in text conflicts.
741
719
            (incompatible with reprocess)
742
720
        :param change_reporter: An object that should report changes made
743
721
        :param interesting_files: The tree-relative paths of files that should
744
722
            participate in the merge.  If these paths refer to directories,
745
 
            the contents of those directories will also be included.  May not
746
 
            be combined with interesting_ids.  If neither interesting_files nor
747
 
            interesting_ids is specified, all files may participate in the
 
723
            the contents of those directories will also be included.  If not
 
724
            specified, all files may participate in the
748
725
            merge.
749
726
        :param lca_trees: Can be set to a dictionary of {revision_id:rev_tree}
750
727
            if the ancestry was found to include a criss-cross merge.
751
728
            Otherwise should be None.
752
729
        """
753
730
        object.__init__(self)
754
 
        if interesting_files is not None and interesting_ids is not None:
755
 
            raise ValueError(
756
 
                'specify either interesting_ids or interesting_files')
757
731
        if this_branch is None:
758
732
            this_branch = this_tree.branch
759
 
        self.interesting_ids = interesting_ids
760
733
        self.interesting_files = interesting_files
761
734
        self.working_tree = working_tree
762
735
        self.this_tree = this_tree
780
753
            self.do_merge()
781
754
 
782
755
    def do_merge(self):
783
 
        operation = cleanup.OperationWithCleanups(self._do_merge)
784
 
        self.working_tree.lock_tree_write()
785
 
        operation.add_cleanup(self.working_tree.unlock)
786
 
        self.this_tree.lock_read()
787
 
        operation.add_cleanup(self.this_tree.unlock)
788
 
        self.base_tree.lock_read()
789
 
        operation.add_cleanup(self.base_tree.unlock)
790
 
        self.other_tree.lock_read()
791
 
        operation.add_cleanup(self.other_tree.unlock)
792
 
        operation.run()
793
 
 
794
 
    def _do_merge(self, operation):
795
 
        self.tt = transform.TreeTransform(self.working_tree, None)
796
 
        operation.add_cleanup(self.tt.finalize)
797
 
        self._compute_transform()
798
 
        results = self.tt.apply(no_conflicts=True)
799
 
        self.write_modified(results)
800
 
        try:
801
 
            self.working_tree.add_conflicts(self.cooked_conflicts)
802
 
        except errors.UnsupportedOperation:
803
 
            pass
 
756
        with contextlib.ExitStack() as stack:
 
757
            stack.enter_context(self.working_tree.lock_tree_write())
 
758
            stack.enter_context(self.this_tree.lock_read())
 
759
            stack.enter_context(self.base_tree.lock_read())
 
760
            stack.enter_context(self.other_tree.lock_read())
 
761
            self.tt = self.working_tree.get_transform()
 
762
            stack.enter_context(self.tt)
 
763
            self._compute_transform()
 
764
            results = self.tt.apply(no_conflicts=True)
 
765
            self.write_modified(results)
 
766
            try:
 
767
                self.working_tree.add_conflicts(self.cooked_conflicts)
 
768
            except errors.UnsupportedOperation:
 
769
                pass
804
770
 
805
771
    def make_preview_transform(self):
806
 
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
807
 
        self.base_tree.lock_read()
808
 
        operation.add_cleanup(self.base_tree.unlock)
809
 
        self.other_tree.lock_read()
810
 
        operation.add_cleanup(self.other_tree.unlock)
811
 
        return operation.run_simple()
812
 
 
813
 
    def _make_preview_transform(self):
814
 
        self.tt = transform.TransformPreview(self.working_tree)
815
 
        self._compute_transform()
816
 
        return self.tt
 
772
        with self.base_tree.lock_read(), self.other_tree.lock_read():
 
773
            self.tt = transform.TransformPreview(self.working_tree)
 
774
            self._compute_transform()
 
775
            return self.tt
817
776
 
818
777
    def _compute_transform(self):
819
778
        if self._lca_trees is None:
827
786
        # One hook for each registered one plus our default merger
828
787
        hooks = [factory(self) for factory in factories] + [self]
829
788
        self.active_hooks = [hook for hook in hooks if hook is not None]
830
 
        child_pb = ui.ui_factory.nested_progress_bar()
831
 
        try:
832
 
            for num, (file_id, changed, parents3, names3,
 
789
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
790
            for num, (file_id, changed, paths3, parents3, names3,
833
791
                      executable3) in enumerate(entries):
 
792
                trans_id = self.tt.trans_id_file_id(file_id)
 
793
 
834
794
                # Try merging each entry
835
795
                child_pb.update(gettext('Preparing file merge'),
836
796
                                num, len(entries))
837
 
                self._merge_names(file_id, parents3, names3, resolver=resolver)
 
797
                self._merge_names(trans_id, file_id, paths3, parents3,
 
798
                                  names3, resolver=resolver)
838
799
                if changed:
839
 
                    file_status = self._do_merge_contents(file_id)
 
800
                    file_status = self._do_merge_contents(paths3, trans_id, file_id)
840
801
                else:
841
802
                    file_status = 'unmodified'
842
 
                self._merge_executable(file_id,
843
 
                    executable3, file_status, resolver=resolver)
844
 
        finally:
845
 
            child_pb.finished()
 
803
                self._merge_executable(paths3, trans_id, executable3,
 
804
                                       file_status, resolver=resolver)
846
805
        self.tt.fixup_new_roots()
847
806
        self._finish_computing_transform()
848
807
 
851
810
 
852
811
        This is the second half of _compute_transform.
853
812
        """
854
 
        child_pb = ui.ui_factory.nested_progress_bar()
855
 
        try:
856
 
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
 
813
        with ui.ui_factory.nested_progress_bar() as child_pb:
 
814
            fs_conflicts = transform.resolve_conflicts(
 
815
                self.tt, child_pb,
857
816
                lambda t, c: transform.conflict_pass(t, c, self.other_tree))
858
 
        finally:
859
 
            child_pb.finished()
860
817
        if self.change_reporter is not None:
861
818
            from breezy import delta
862
819
            delta.report_changes(
863
820
                self.tt.iter_changes(), self.change_reporter)
864
821
        self.cook_conflicts(fs_conflicts)
865
822
        for conflict in self.cooked_conflicts:
866
 
            trace.warning(unicode(conflict))
 
823
            trace.warning('%s', conflict.describe())
867
824
 
868
825
    def _entries3(self):
869
826
        """Gather data about files modified between three trees.
876
833
        """
877
834
        result = []
878
835
        iterator = self.other_tree.iter_changes(self.base_tree,
879
 
                specific_files=self.interesting_files,
880
 
                extra_trees=[self.this_tree])
881
 
        this_entries = dict((e.file_id, e) for p, e in
882
 
                            self.this_tree.iter_entries_by_dir(
883
 
                            self.interesting_ids))
884
 
        for (file_id, paths, changed, versioned, parents, names, kind,
885
 
             executable) in iterator:
886
 
            if (self.interesting_ids is not None and
887
 
                file_id not in self.interesting_ids):
888
 
                continue
889
 
            entry = this_entries.get(file_id)
890
 
            if entry is not None:
891
 
                this_name = entry.name
892
 
                this_parent = entry.parent_id
893
 
                this_executable = entry.executable
 
836
                                                specific_files=self.interesting_files,
 
837
                                                extra_trees=[self.this_tree])
 
838
        this_interesting_files = self.this_tree.find_related_paths_across_trees(
 
839
            self.interesting_files, trees=[self.other_tree])
 
840
        this_entries = dict(self.this_tree.iter_entries_by_dir(
 
841
                            specific_files=this_interesting_files))
 
842
        for change in iterator:
 
843
            if change.path[0] is not None:
 
844
                this_path = _mod_tree.find_previous_path(
 
845
                    self.base_tree, self.this_tree, change.path[0])
 
846
            else:
 
847
                this_path = _mod_tree.find_previous_path(
 
848
                    self.other_tree, self.this_tree, change.path[1])
 
849
            this_entry = this_entries.get(this_path)
 
850
            if this_entry is not None:
 
851
                this_name = this_entry.name
 
852
                this_parent = this_entry.parent_id
 
853
                this_executable = this_entry.executable
894
854
            else:
895
855
                this_name = None
896
856
                this_parent = None
897
857
                this_executable = None
898
 
            parents3 = parents + (this_parent,)
899
 
            names3 = names + (this_name,)
900
 
            executable3 = executable + (this_executable,)
901
 
            result.append((file_id, changed, parents3, names3, executable3))
 
858
            parents3 = change.parent_id + (this_parent,)
 
859
            names3 = change.name + (this_name,)
 
860
            paths3 = change.path + (this_path, )
 
861
            executable3 = change.executable + (this_executable,)
 
862
            result.append(
 
863
                (change.file_id, change.changed_content, paths3,
 
864
                 parents3, names3, executable3))
902
865
        return result
903
866
 
904
867
    def _entries_lca(self):
909
872
 
910
873
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
911
874
 
912
 
        :return: [(file_id, changed, parents, names, executable)], where:
 
875
        :return: [(file_id, changed, paths, parents, names, executable)], where:
913
876
 
914
877
            * file_id: Simple file_id of the entry
915
878
            * changed: Boolean, True if the kind or contents changed else False
 
879
            * paths: ((base, [path, in, lcas]), path_other, path_this)
916
880
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
917
881
                        parent_id_this)
918
882
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
923
887
            lookup_trees = [self.this_tree, self.base_tree]
924
888
            lookup_trees.extend(self._lca_trees)
925
889
            # I think we should include the lca trees as well
926
 
            interesting_ids = self.other_tree.paths2ids(self.interesting_files,
927
 
                                                        lookup_trees)
 
890
            interesting_files = self.other_tree.find_related_paths_across_trees(
 
891
                self.interesting_files, lookup_trees)
928
892
        else:
929
 
            interesting_ids = self.interesting_ids
 
893
            interesting_files = None
930
894
        result = []
931
 
        walker = _mod_tree.MultiWalker(self.other_tree, self._lca_trees)
 
895
        from .multiwalker import MultiWalker
 
896
        walker = MultiWalker(self.other_tree, self._lca_trees)
932
897
 
933
 
        base_inventory = self.base_tree.root_inventory
934
 
        this_inventory = self.this_tree.root_inventory
935
 
        for path, file_id, other_ie, lca_values in walker.iter_all():
 
898
        for other_path, file_id, other_ie, lca_values in walker.iter_all():
936
899
            # Is this modified at all from any of the other trees?
937
900
            if other_ie is None:
938
901
                other_ie = _none_entry
939
 
            if interesting_ids is not None and file_id not in interesting_ids:
 
902
                other_path = None
 
903
            if interesting_files is not None and other_path not in interesting_files:
940
904
                continue
941
905
 
942
906
            # If other_revision is found in any of the lcas, that means this
945
909
            # we know that the ancestry is linear, and that OTHER did not
946
910
            # modify anything
947
911
            # See doc/developers/lca_merge_resolution.txt for details
948
 
            other_revision = other_ie.revision
949
 
            if other_revision is not None:
950
 
                # We can't use this shortcut when other_revision is None,
951
 
                # because it may be None because things are WorkingTrees, and
952
 
                # not because it is *actually* None.
953
 
                is_unmodified = False
954
 
                for lca_path, ie in lca_values:
955
 
                    if ie is not None and ie.revision == other_revision:
956
 
                        is_unmodified = True
957
 
                        break
958
 
                if is_unmodified:
959
 
                    continue
 
912
            # We can't use this shortcut when other_revision is None,
 
913
            # because it may be None because things are WorkingTrees, and
 
914
            # not because it is *actually* None.
 
915
            is_unmodified = False
 
916
            for lca_path, ie in lca_values:
 
917
                if ie is not None and other_ie.is_unmodified(ie):
 
918
                    is_unmodified = True
 
919
                    break
 
920
            if is_unmodified:
 
921
                continue
960
922
 
961
923
            lca_entries = []
 
924
            lca_paths = []
962
925
            for lca_path, lca_ie in lca_values:
963
926
                if lca_ie is None:
964
927
                    lca_entries.append(_none_entry)
 
928
                    lca_paths.append(None)
965
929
                else:
966
930
                    lca_entries.append(lca_ie)
 
931
                    lca_paths.append(lca_path)
967
932
 
968
 
            if base_inventory.has_id(file_id):
969
 
                base_ie = base_inventory[file_id]
970
 
            else:
 
933
            try:
 
934
                base_path = self.base_tree.id2path(file_id)
 
935
            except errors.NoSuchId:
 
936
                base_path = None
971
937
                base_ie = _none_entry
 
938
            else:
 
939
                base_ie = next(self.base_tree.iter_entries_by_dir(specific_files=[base_path]))[1]
972
940
 
973
 
            if this_inventory.has_id(file_id):
974
 
                this_ie = this_inventory[file_id]
975
 
            else:
 
941
            try:
 
942
                this_path = self.this_tree.id2path(file_id)
 
943
            except errors.NoSuchId:
976
944
                this_ie = _none_entry
 
945
                this_path = None
 
946
            else:
 
947
                this_ie = next(self.this_tree.iter_entries_by_dir(specific_files=[this_path]))[1]
977
948
 
978
949
            lca_kinds = []
979
950
            lca_parent_ids = []
1004
975
                        continue
1005
976
                    content_changed = False
1006
977
                elif other_ie.kind is None or other_ie.kind == 'file':
1007
 
                    def get_sha1(ie, tree):
1008
 
                        if ie.kind != 'file':
1009
 
                            return None
1010
 
                        return tree.get_file_sha1(tree.id2path(file_id), file_id)
1011
 
                    base_sha1 = get_sha1(base_ie, self.base_tree)
1012
 
                    lca_sha1s = [get_sha1(ie, tree) for ie, tree
1013
 
                                 in zip(lca_entries, self._lca_trees)]
1014
 
                    this_sha1 = get_sha1(this_ie, self.this_tree)
1015
 
                    other_sha1 = get_sha1(other_ie, self.other_tree)
 
978
                    def get_sha1(tree, path):
 
979
                        if path is None:
 
980
                            return None
 
981
                        try:
 
982
                            return tree.get_file_sha1(path)
 
983
                        except errors.NoSuchFile:
 
984
                            return None
 
985
                    base_sha1 = get_sha1(self.base_tree, base_path)
 
986
                    lca_sha1s = [get_sha1(tree, lca_path)
 
987
                                 for tree, lca_path
 
988
                                 in zip(self._lca_trees, lca_paths)]
 
989
                    this_sha1 = get_sha1(self.this_tree, this_path)
 
990
                    other_sha1 = get_sha1(self.other_tree, other_path)
1016
991
                    sha1_winner = self._lca_multi_way(
1017
992
                        (base_sha1, lca_sha1s), other_sha1, this_sha1,
1018
993
                        allow_overriding_lca=False)
1020
995
                        (base_ie.executable, lca_executable),
1021
996
                        other_ie.executable, this_ie.executable)
1022
997
                    if (parent_id_winner == 'this' and name_winner == 'this'
1023
 
                        and sha1_winner == 'this' and exec_winner == 'this'):
 
998
                            and sha1_winner == 'this' and exec_winner == 'this'):
1024
999
                        # No kind, parent, name, exec, or content change for
1025
1000
                        # OTHER, so this node is not considered interesting
1026
1001
                        continue
1027
1002
                    if sha1_winner == 'this':
1028
1003
                        content_changed = False
1029
1004
                elif other_ie.kind == 'symlink':
1030
 
                    def get_target(ie, tree):
 
1005
                    def get_target(ie, tree, path):
1031
1006
                        if ie.kind != 'symlink':
1032
1007
                            return None
1033
 
                        path = tree.id2path(file_id)
1034
 
                        return tree.get_symlink_target(path, file_id)
1035
 
                    base_target = get_target(base_ie, self.base_tree)
1036
 
                    lca_targets = [get_target(ie, tree) for ie, tree
1037
 
                                   in zip(lca_entries, self._lca_trees)]
1038
 
                    this_target = get_target(this_ie, self.this_tree)
1039
 
                    other_target = get_target(other_ie, self.other_tree)
 
1008
                        return tree.get_symlink_target(path)
 
1009
                    base_target = get_target(base_ie, self.base_tree, base_path)
 
1010
                    lca_targets = [get_target(ie, tree, lca_path) for ie, tree, lca_path
 
1011
                                   in zip(lca_entries, self._lca_trees, lca_paths)]
 
1012
                    this_target = get_target(
 
1013
                        this_ie, self.this_tree, this_path)
 
1014
                    other_target = get_target(
 
1015
                        other_ie, self.other_tree, other_path)
1040
1016
                    target_winner = self._lca_multi_way(
1041
1017
                        (base_target, lca_targets),
1042
1018
                        other_target, this_target)
1043
1019
                    if (parent_id_winner == 'this' and name_winner == 'this'
1044
 
                        and target_winner == 'this'):
 
1020
                            and target_winner == 'this'):
1045
1021
                        # No kind, parent, name, or symlink target change
1046
1022
                        # not interesting
1047
1023
                        continue
1060
1036
 
1061
1037
            # If we have gotten this far, that means something has changed
1062
1038
            result.append((file_id, content_changed,
 
1039
                           ((base_path, lca_paths),
 
1040
                            other_path, this_path),
1063
1041
                           ((base_ie.parent_id, lca_parent_ids),
1064
1042
                            other_ie.parent_id, this_ie.parent_id),
1065
1043
                           ((base_ie.name, lca_names),
1066
1044
                            other_ie.name, this_ie.name),
1067
1045
                           ((base_ie.executable, lca_executable),
1068
1046
                            other_ie.executable, this_ie.executable)
1069
 
                          ))
 
1047
                           ))
1070
1048
        return result
1071
1049
 
1072
1050
    def write_modified(self, results):
 
1051
        if not self.working_tree.supports_merge_modified():
 
1052
            return
1073
1053
        modified_hashes = {}
1074
1054
        for path in results.modified_paths:
1075
1055
            wt_relpath = self.working_tree.relpath(path)
1076
 
            file_id = self.working_tree.path2id(wt_relpath)
1077
 
            if file_id is None:
 
1056
            if not self.working_tree.is_versioned(wt_relpath):
1078
1057
                continue
1079
 
            hash = self.working_tree.get_file_sha1(wt_relpath, file_id)
 
1058
            hash = self.working_tree.get_file_sha1(wt_relpath)
1080
1059
            if hash is None:
1081
1060
                continue
1082
 
            modified_hashes[file_id] = hash
 
1061
            modified_hashes[wt_relpath] = hash
1083
1062
        self.working_tree.set_merge_modified(modified_hashes)
1084
1063
 
1085
1064
    @staticmethod
1086
 
    def parent(entry, file_id):
 
1065
    def parent(entry):
1087
1066
        """Determine the parent for a file_id (used as a key method)"""
1088
1067
        if entry is None:
1089
1068
            return None
1090
1069
        return entry.parent_id
1091
1070
 
1092
1071
    @staticmethod
1093
 
    def name(entry, file_id):
 
1072
    def name(entry):
1094
1073
        """Determine the name for a file_id (used as a key method)"""
1095
1074
        if entry is None:
1096
1075
            return None
1097
1076
        return entry.name
1098
1077
 
1099
1078
    @staticmethod
1100
 
    def contents_sha1(tree, file_id):
 
1079
    def contents_sha1(tree, path):
1101
1080
        """Determine the sha1 of the file contents (used as a key method)."""
1102
1081
        try:
1103
 
            path = tree.id2path(file_id)
1104
 
        except errors.NoSuchId:
 
1082
            return tree.get_file_sha1(path)
 
1083
        except errors.NoSuchFile:
1105
1084
            return None
1106
 
        return tree.get_file_sha1(path, file_id)
1107
1085
 
1108
1086
    @staticmethod
1109
 
    def executable(tree, file_id):
 
1087
    def executable(tree, path):
1110
1088
        """Determine the executability of a file-id (used as a key method)."""
1111
1089
        try:
1112
 
            path = tree.id2path(file_id)
1113
 
        except errors.NoSuchId:
 
1090
            if tree.kind(path) != "file":
 
1091
                return False
 
1092
        except errors.NoSuchFile:
1114
1093
            return None
1115
 
        if tree.kind(path, file_id) != "file":
1116
 
            return False
1117
 
        return tree.is_executable(path, file_id)
 
1094
        return tree.is_executable(path)
1118
1095
 
1119
1096
    @staticmethod
1120
 
    def kind(tree, path, file_id):
 
1097
    def kind(tree, path):
1121
1098
        """Determine the kind of a file-id (used as a key method)."""
1122
1099
        try:
1123
 
            path = tree.id2path(file_id)
1124
 
        except errors.NoSuchId:
 
1100
            return tree.kind(path)
 
1101
        except errors.NoSuchFile:
1125
1102
            return None
1126
 
        return tree.kind(path, file_id)
1127
1103
 
1128
1104
    @staticmethod
1129
1105
    def _three_way(base, other, this):
1166
1142
        base_val, lca_vals = bases
1167
1143
        # Remove 'base_val' from the lca_vals, because it is not interesting
1168
1144
        filtered_lca_vals = [lca_val for lca_val in lca_vals
1169
 
                                      if lca_val != base_val]
 
1145
                             if lca_val != base_val]
1170
1146
        if len(filtered_lca_vals) == 0:
1171
1147
            return Merge3Merger._three_way(base_val, other, this)
1172
1148
 
1191
1167
        # At this point, the lcas disagree, and the tip disagree
1192
1168
        return 'conflict'
1193
1169
 
1194
 
    def merge_names(self, file_id):
1195
 
        def get_entry(tree):
1196
 
            try:
1197
 
                return tree.root_inventory[file_id]
1198
 
            except errors.NoSuchId:
1199
 
                return None
1200
 
        this_entry = get_entry(self.this_tree)
1201
 
        other_entry = get_entry(self.other_tree)
1202
 
        base_entry = get_entry(self.base_tree)
1203
 
        entries = (base_entry, other_entry, this_entry)
1204
 
        names = []
1205
 
        parents = []
1206
 
        for entry in entries:
1207
 
            if entry is None:
1208
 
                names.append(None)
1209
 
                parents.append(None)
1210
 
            else:
1211
 
                names.append(entry.name)
1212
 
                parents.append(entry.parent_id)
1213
 
        return self._merge_names(file_id, parents, names,
1214
 
                                 resolver=self._three_way)
1215
 
 
1216
 
    def _merge_names(self, file_id, parents, names, resolver):
1217
 
        """Perform a merge on file_id names and parents"""
 
1170
    def _merge_names(self, trans_id, file_id, paths, parents, names, resolver):
 
1171
        """Perform a merge on file names and parents"""
1218
1172
        base_name, other_name, this_name = names
1219
1173
        base_parent, other_parent, this_parent = parents
 
1174
        unused_base_path, other_path, this_path = paths
1220
1175
 
1221
1176
        name_winner = resolver(*names)
1222
1177
 
1232
1187
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1233
1188
            # road if a ContentConflict needs to be created so we should not do
1234
1189
            # that
1235
 
            trans_id = self.tt.trans_id_file_id(file_id)
1236
1190
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1237
1191
                                        this_parent, this_name,
1238
1192
                                        other_parent, other_name))
1239
 
        if not self.other_tree.has_id(file_id):
 
1193
        if other_path is None:
1240
1194
            # it doesn't matter whether the result was 'other' or
1241
1195
            # 'conflict'-- if it has no file id, we leave it alone.
1242
1196
            return
1255
1209
                parent_trans_id = transform.ROOT_PARENT
1256
1210
            else:
1257
1211
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1258
 
            self.tt.adjust_path(name, parent_trans_id,
1259
 
                                self.tt.trans_id_file_id(file_id))
 
1212
            self.tt.adjust_path(name, parent_trans_id, trans_id)
1260
1213
 
1261
 
    def _do_merge_contents(self, file_id):
 
1214
    def _do_merge_contents(self, paths, trans_id, file_id):
1262
1215
        """Performs a merge on file_id contents."""
1263
 
        def contents_pair(tree):
1264
 
            try:
1265
 
                path = tree.id2path(file_id)
1266
 
            except errors.NoSuchId:
 
1216
        def contents_pair(tree, path):
 
1217
            if path is None:
1267
1218
                return (None, None)
1268
1219
            try:
1269
 
                kind = tree.kind(path, file_id)
 
1220
                kind = tree.kind(path)
1270
1221
            except errors.NoSuchFile:
1271
1222
                return (None, None)
1272
1223
            if kind == "file":
1273
 
                contents = tree.get_file_sha1(path, file_id)
 
1224
                contents = tree.get_file_sha1(path)
1274
1225
            elif kind == "symlink":
1275
 
                contents = tree.get_symlink_target(path, file_id)
 
1226
                contents = tree.get_symlink_target(path)
1276
1227
            else:
1277
1228
                contents = None
1278
1229
            return kind, contents
1279
1230
 
 
1231
        base_path, other_path, this_path = paths
1280
1232
        # See SPOT run.  run, SPOT, run.
1281
1233
        # So we're not QUITE repeating ourselves; we do tricky things with
1282
1234
        # file kind...
1283
 
        base_pair = contents_pair(self.base_tree)
1284
 
        other_pair = contents_pair(self.other_tree)
 
1235
        other_pair = contents_pair(self.other_tree, other_path)
 
1236
        this_pair = contents_pair(self.this_tree, this_path)
1285
1237
        if self._lca_trees:
1286
 
            this_pair = contents_pair(self.this_tree)
1287
 
            lca_pairs = [contents_pair(tree) for tree in self._lca_trees]
 
1238
            (base_path, lca_paths) = base_path
 
1239
            base_pair = contents_pair(self.base_tree, base_path)
 
1240
            lca_pairs = [contents_pair(tree, path)
 
1241
                         for tree, path in zip(self._lca_trees, lca_paths)]
1288
1242
            winner = self._lca_multi_way((base_pair, lca_pairs), other_pair,
1289
1243
                                         this_pair, allow_overriding_lca=False)
1290
1244
        else:
 
1245
            base_pair = contents_pair(self.base_tree, base_path)
1291
1246
            if base_pair == other_pair:
1292
1247
                winner = 'this'
1293
1248
            else:
1294
1249
                # We delayed evaluating this_pair as long as we can to avoid
1295
1250
                # unnecessary sha1 calculation
1296
 
                this_pair = contents_pair(self.this_tree)
 
1251
                this_pair = contents_pair(self.this_tree, this_path)
1297
1252
                winner = self._three_way(base_pair, other_pair, this_pair)
1298
1253
        if winner == 'this':
1299
1254
            # No interesting changes introduced by OTHER
1300
1255
            return "unmodified"
1301
1256
        # We have a hypothetical conflict, but if we have files, then we
1302
1257
        # can try to merge the content
1303
 
        trans_id = self.tt.trans_id_file_id(file_id)
1304
 
        params = MergeFileHookParams(self, file_id, trans_id, this_pair[0],
 
1258
        params = MergeFileHookParams(
 
1259
            self, (base_path, other_path, this_path), trans_id, this_pair[0],
1305
1260
            other_pair[0], winner)
1306
1261
        hooks = self.active_hooks
1307
1262
        hook_status = 'not_applicable'
1321
1276
            result = None
1322
1277
            name = self.tt.final_name(trans_id)
1323
1278
            parent_id = self.tt.final_parent(trans_id)
1324
 
            duplicate = False
1325
1279
            inhibit_content_conflict = False
1326
 
            if params.this_kind is None: # file_id is not in THIS
 
1280
            if params.this_kind is None:  # file_id is not in THIS
1327
1281
                # Is the name used for a different file_id ?
1328
 
                dupe_path = self.other_tree.id2path(file_id)
1329
 
                this_id = self.this_tree.path2id(dupe_path)
1330
 
                if this_id is not None:
 
1282
                if self.this_tree.is_versioned(other_path):
1331
1283
                    # Two entries for the same path
1332
1284
                    keep_this = True
1333
1285
                    # versioning the merged file will trigger a duplicate
1335
1287
                    self.tt.version_file(file_id, trans_id)
1336
1288
                    transform.create_from_tree(
1337
1289
                        self.tt, trans_id, self.other_tree,
1338
 
                        self.other_tree.id2path(file_id), file_id=file_id,
1339
 
                        filter_tree_path=self._get_filter_tree_path(file_id))
 
1290
                        other_path,
 
1291
                        filter_tree_path=self._get_filter_tree_path(other_path))
1340
1292
                    inhibit_content_conflict = True
1341
 
            elif params.other_kind is None: # file_id is not in OTHER
 
1293
            elif params.other_kind is None:  # file_id is not in OTHER
1342
1294
                # Is the name used for a different file_id ?
1343
 
                dupe_path = self.this_tree.id2path(file_id)
1344
 
                other_id = self.other_tree.path2id(dupe_path)
1345
 
                if other_id is not None:
 
1295
                if self.other_tree.is_versioned(this_path):
1346
1296
                    # Two entries for the same path again, but here, the other
1347
1297
                    # entry will also be merged.  We simply inhibit the
1348
1298
                    # 'content' conflict creation because we know OTHER will
1356
1306
                    self.tt.unversion_file(trans_id)
1357
1307
                # This is a contents conflict, because none of the available
1358
1308
                # functions could merge it.
1359
 
                file_group = self._dump_conflicts(name, parent_id, file_id,
1360
 
                                                  set_version=True)
 
1309
                file_group = self._dump_conflicts(
 
1310
                    name, (base_path, other_path, this_path), parent_id,
 
1311
                    file_id, set_version=True)
1361
1312
                self._raw_conflicts.append(('contents conflict', file_group))
1362
1313
        elif hook_status == 'success':
1363
1314
            self.tt.create_file(lines, trans_id)
1368
1319
            self._raw_conflicts.append(('text conflict', trans_id))
1369
1320
            name = self.tt.final_name(trans_id)
1370
1321
            parent_id = self.tt.final_parent(trans_id)
1371
 
            self._dump_conflicts(name, parent_id, file_id)
 
1322
            self._dump_conflicts(
 
1323
                name, (base_path, other_path, this_path), parent_id, file_id)
1372
1324
        elif hook_status == 'delete':
1373
1325
            self.tt.unversion_file(trans_id)
1374
1326
            result = "deleted"
1378
1330
            pass
1379
1331
        else:
1380
1332
            raise AssertionError('unknown hook_status: %r' % (hook_status,))
1381
 
        if not self.this_tree.has_id(file_id) and result == "modified":
 
1333
        if not this_path and result == "modified":
1382
1334
            self.tt.version_file(file_id, trans_id)
1383
1335
        if not keep_this:
1384
1336
            # The merge has been performed and produced a new content, so the
1388
1340
 
1389
1341
    def _default_other_winner_merge(self, merge_hook_params):
1390
1342
        """Replace this contents with other."""
1391
 
        file_id = merge_hook_params.file_id
1392
1343
        trans_id = merge_hook_params.trans_id
1393
 
        if self.other_tree.has_id(file_id):
 
1344
        if merge_hook_params.other_path is not None:
1394
1345
            # OTHER changed the file
1395
1346
            transform.create_from_tree(
1396
1347
                self.tt, trans_id, self.other_tree,
1397
 
                self.other_tree.id2path(file_id), file_id=file_id,
1398
 
                filter_tree_path=self._get_filter_tree_path(file_id))
 
1348
                merge_hook_params.other_path,
 
1349
                filter_tree_path=self._get_filter_tree_path(merge_hook_params.other_path))
1399
1350
            return 'done', None
1400
 
        elif self.this_tree.has_id(file_id):
 
1351
        elif merge_hook_params.this_path is not None:
1401
1352
            # OTHER deleted the file
1402
1353
            return 'delete', None
1403
1354
        else:
1404
1355
            raise AssertionError(
1405
 
                'winner is OTHER, but file_id %r not in THIS or OTHER tree'
1406
 
                % (file_id,))
 
1356
                'winner is OTHER, but file %r not in THIS or OTHER tree'
 
1357
                % (merge_hook_params.base_path,))
1407
1358
 
1408
1359
    def merge_contents(self, merge_hook_params):
1409
1360
        """Fallback merge logic after user installed hooks."""
1410
1361
        # This function is used in merge hooks as the fallback instance.
1411
 
        # Perhaps making this function and the functions it calls be a 
 
1362
        # Perhaps making this function and the functions it calls be a
1412
1363
        # a separate class would be better.
1413
1364
        if merge_hook_params.winner == 'other':
1414
1365
            # OTHER is a straight winner, so replace this contents with other
1418
1369
            # BASE is a file, or both converted to files, so at least we
1419
1370
            # have agreement that output should be a file.
1420
1371
            try:
1421
 
                self.text_merge(merge_hook_params.file_id,
1422
 
                    merge_hook_params.trans_id)
 
1372
                self.text_merge(merge_hook_params.trans_id,
 
1373
                                merge_hook_params.paths)
1423
1374
            except errors.BinaryFile:
1424
1375
                return 'not_applicable', None
1425
1376
            return 'done', None
1426
1377
        else:
1427
1378
            return 'not_applicable', None
1428
1379
 
1429
 
    def get_lines(self, tree, file_id):
 
1380
    def get_lines(self, tree, path):
1430
1381
        """Return the lines in a file, or an empty list."""
 
1382
        if path is None:
 
1383
            return []
1431
1384
        try:
1432
 
            path = tree.id2path(file_id)
1433
 
        except errors.NoSuchId:
 
1385
            kind = tree.kind(path)
 
1386
        except errors.NoSuchFile:
1434
1387
            return []
1435
1388
        else:
1436
 
            return tree.get_file_lines(path, file_id)
 
1389
            if kind != 'file':
 
1390
                return []
 
1391
            return tree.get_file_lines(path)
1437
1392
 
1438
 
    def text_merge(self, file_id, trans_id):
1439
 
        """Perform a three-way text merge on a file_id"""
 
1393
    def text_merge(self, trans_id, paths):
 
1394
        """Perform a three-way text merge on a file"""
1440
1395
        # it's possible that we got here with base as a different type.
1441
1396
        # if so, we just want two-way text conflicts.
1442
 
        try:
1443
 
            base_path = self.base_tree.id2path(file_id)
1444
 
        except errors.NoSuchId:
1445
 
            base_lines = []
1446
 
        else:
1447
 
            if self.base_tree.kind(base_path, file_id) == "file":
1448
 
                base_lines = self.get_lines(self.base_tree, file_id)
1449
 
            else:
1450
 
                base_lines = []
1451
 
        other_lines = self.get_lines(self.other_tree, file_id)
1452
 
        this_lines = self.get_lines(self.this_tree, file_id)
 
1397
        base_path, other_path, this_path = paths
 
1398
        base_lines = self.get_lines(self.base_tree, base_path)
 
1399
        other_lines = self.get_lines(self.other_tree, other_path)
 
1400
        this_lines = self.get_lines(self.this_tree, this_path)
1453
1401
        m3 = merge3.Merge3(base_lines, this_lines, other_lines,
1454
1402
                           is_cherrypick=self.cherrypick)
1455
 
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
 
1403
        start_marker = b"!START OF MERGE CONFLICT!" + b"I HOPE THIS IS UNIQUE"
1456
1404
        if self.show_base is True:
1457
 
            base_marker = '|' * 7
 
1405
            base_marker = b'|' * 7
1458
1406
        else:
1459
1407
            base_marker = None
1460
1408
 
1461
1409
        def iter_merge3(retval):
1462
1410
            retval["text_conflicts"] = False
1463
 
            for line in m3.merge_lines(name_a = "TREE",
1464
 
                                       name_b = "MERGE-SOURCE",
1465
 
                                       name_base = "BASE-REVISION",
 
1411
            for line in m3.merge_lines(name_a=b"TREE",
 
1412
                                       name_b=b"MERGE-SOURCE",
 
1413
                                       name_base=b"BASE-REVISION",
1466
1414
                                       start_marker=start_marker,
1467
1415
                                       base_marker=base_marker,
1468
1416
                                       reprocess=self.reprocess):
1469
1417
                if line.startswith(start_marker):
1470
1418
                    retval["text_conflicts"] = True
1471
 
                    yield line.replace(start_marker, '<' * 7)
 
1419
                    yield line.replace(start_marker, b'<' * 7)
1472
1420
                else:
1473
1421
                    yield line
1474
1422
        retval = {}
1478
1426
            self._raw_conflicts.append(('text conflict', trans_id))
1479
1427
            name = self.tt.final_name(trans_id)
1480
1428
            parent_id = self.tt.final_parent(trans_id)
1481
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1429
            file_id = self.tt.final_file_id(trans_id)
 
1430
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1482
1431
                                              this_lines, base_lines,
1483
1432
                                              other_lines)
1484
1433
            file_group.append(trans_id)
1485
1434
 
1486
 
 
1487
 
    def _get_filter_tree_path(self, file_id):
 
1435
    def _get_filter_tree_path(self, path):
1488
1436
        if self.this_tree.supports_content_filtering():
1489
1437
            # We get the path from the working tree if it exists.
1490
1438
            # That fails though when OTHER is adding a file, so
1491
1439
            # we fall back to the other tree to find the path if
1492
1440
            # it doesn't exist locally.
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
 
1441
            filter_path = _mod_tree.find_previous_path(
 
1442
                self.other_tree, self.working_tree, path)
 
1443
            if filter_path is None:
 
1444
                filter_path = path
 
1445
            return filter_path
 
1446
        # Skip the lookup for older formats
1498
1447
        return None
1499
1448
 
1500
 
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None,
 
1449
    def _dump_conflicts(self, name, paths, parent_id, file_id, this_lines=None,
1501
1450
                        base_lines=None, other_lines=None, set_version=False,
1502
1451
                        no_base=False):
1503
1452
        """Emit conflict files.
1505
1454
        determined automatically.  If set_version is true, the .OTHER, .THIS
1506
1455
        or .BASE (in that order) will be created as versioned files.
1507
1456
        """
1508
 
        data = [('OTHER', self.other_tree, other_lines),
1509
 
                ('THIS', self.this_tree, this_lines)]
 
1457
        base_path, other_path, this_path = paths
 
1458
        data = [('OTHER', self.other_tree, other_path, other_lines),
 
1459
                ('THIS', self.this_tree, this_path, this_lines)]
1510
1460
        if not no_base:
1511
 
            data.append(('BASE', self.base_tree, base_lines))
 
1461
            data.append(('BASE', self.base_tree, base_path, base_lines))
1512
1462
 
1513
1463
        # We need to use the actual path in the working tree of the file here,
1514
1464
        # ignoring the conflict suffixes
1525
1475
 
1526
1476
        versioned = False
1527
1477
        file_group = []
1528
 
        for suffix, tree, lines in data:
1529
 
            try:
1530
 
                path = tree.id2path(file_id)
1531
 
            except errors.NoSuchId:
1532
 
                pass
1533
 
            else:
 
1478
        for suffix, tree, path, lines in data:
 
1479
            if path is not None:
1534
1480
                trans_id = self._conflict_file(
1535
 
                        name, parent_id, path, tree, file_id, suffix, lines,
1536
 
                        filter_tree_path)
 
1481
                    name, parent_id, path, tree, suffix, lines,
 
1482
                    filter_tree_path)
1537
1483
                file_group.append(trans_id)
1538
1484
                if set_version and not versioned:
1539
1485
                    self.tt.version_file(file_id, trans_id)
1540
1486
                    versioned = True
1541
1487
        return file_group
1542
1488
 
1543
 
    def _conflict_file(self, name, parent_id, path, tree, file_id, suffix,
 
1489
    def _conflict_file(self, name, parent_id, path, tree, suffix,
1544
1490
                       lines=None, filter_tree_path=None):
1545
1491
        """Emit a single conflict file."""
1546
1492
        name = name + '.' + suffix
1547
1493
        trans_id = self.tt.create_path(name, parent_id)
1548
1494
        transform.create_from_tree(
1549
 
                self.tt, trans_id, tree, path,
1550
 
                file_id=file_id, bytes=lines,
1551
 
                filter_tree_path=filter_tree_path)
 
1495
            self.tt, trans_id, tree, path,
 
1496
            chunks=lines,
 
1497
            filter_tree_path=filter_tree_path)
1552
1498
        return trans_id
1553
1499
 
1554
 
    def merge_executable(self, file_id, file_status):
1555
 
        """Perform a merge on the execute bit."""
1556
 
        executable = [self.executable(t, file_id) for t in (self.base_tree,
1557
 
                      self.other_tree, self.this_tree)]
1558
 
        self._merge_executable(file_id, executable, file_status,
1559
 
                               resolver=self._three_way)
1560
 
 
1561
 
    def _merge_executable(self, file_id, executable, file_status,
 
1500
    def _merge_executable(self, paths, trans_id, executable, file_status,
1562
1501
                          resolver):
1563
1502
        """Perform a merge on the execute bit."""
1564
1503
        base_executable, other_executable, this_executable = executable
 
1504
        base_path, other_path, this_path = paths
1565
1505
        if file_status == "deleted":
1566
1506
            return
1567
1507
        winner = resolver(*executable)
1568
1508
        if winner == "conflict":
1569
 
        # There must be a None in here, if we have a conflict, but we
1570
 
        # need executability since file status was not deleted.
1571
 
            if self.executable(self.other_tree, file_id) is None:
 
1509
            # There must be a None in here, if we have a conflict, but we
 
1510
            # need executability since file status was not deleted.
 
1511
            if other_path is None:
1572
1512
                winner = "this"
1573
1513
            else:
1574
1514
                winner = "other"
1575
1515
        if winner == 'this' and file_status != "modified":
1576
1516
            return
1577
 
        trans_id = self.tt.trans_id_file_id(file_id)
1578
1517
        if self.tt.final_kind(trans_id) != "file":
1579
1518
            return
1580
1519
        if winner == "this":
1581
1520
            executability = this_executable
1582
1521
        else:
1583
 
            if self.other_tree.has_id(file_id):
 
1522
            if other_path is not None:
1584
1523
                executability = other_executable
1585
 
            elif self.this_tree.has_id(file_id):
 
1524
            elif this_path is not None:
1586
1525
                executability = this_executable
1587
 
            elif self.base_tree_has_id(file_id):
 
1526
            elif base_path is not None:
1588
1527
                executability = base_executable
1589
1528
        if executability is not None:
1590
 
            trans_id = self.tt.trans_id_file_id(file_id)
1591
1529
            self.tt.set_executability(executability, trans_id)
1592
1530
 
1593
1531
    def cook_conflicts(self, fs_conflicts):
1599
1537
            conflict_type = conflict[0]
1600
1538
            if conflict_type == 'path conflict':
1601
1539
                (trans_id, file_id,
1602
 
                this_parent, this_name,
1603
 
                other_parent, other_name) = conflict[1:]
 
1540
                 this_parent, this_name,
 
1541
                 other_parent, other_name) = conflict[1:]
1604
1542
                if this_parent is None or this_name is None:
1605
1543
                    this_path = '<deleted>'
1606
1544
                else:
1607
 
                    parent_path =  fp.get_path(
 
1545
                    parent_path = fp.get_path(
1608
1546
                        self.tt.trans_id_file_id(this_parent))
1609
1547
                    this_path = osutils.pathjoin(parent_path, this_name)
1610
1548
                if other_parent is None or other_name is None:
1611
1549
                    other_path = '<deleted>'
1612
1550
                else:
1613
 
                    if other_parent == self.other_tree.get_root_id():
 
1551
                    if other_parent == self.other_tree.path2id(''):
1614
1552
                        # The tree transform doesn't know about the other root,
1615
1553
                        # so we special case here to avoid a NoFinalPath
1616
1554
                        # exception
1617
1555
                        parent_path = ''
1618
1556
                    else:
1619
 
                        parent_path =  fp.get_path(
 
1557
                        parent_path = fp.get_path(
1620
1558
                            self.tt.trans_id_file_id(other_parent))
1621
1559
                    other_path = osutils.pathjoin(parent_path, other_name)
1622
1560
                c = _mod_conflicts.Conflict.factory(
1655
1593
        # conflict is enough.
1656
1594
        for c in cooked_conflicts:
1657
1595
            if (c.typestring == 'path conflict'
1658
 
                and c.file_id in content_conflict_file_ids):
 
1596
                    and c.file_id in content_conflict_file_ids):
1659
1597
                continue
1660
1598
            self.cooked_conflicts.append(c)
1661
1599
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1667
1605
    supports_show_base = False
1668
1606
    supports_reverse_cherrypick = False
1669
1607
    history_based = True
 
1608
    requires_file_merge_plan = True
1670
1609
 
1671
 
    def _generate_merge_plan(self, file_id, base):
1672
 
        return self.this_tree.plan_file_merge(file_id, self.other_tree,
 
1610
    def _generate_merge_plan(self, this_path, base):
 
1611
        return self.this_tree.plan_file_merge(this_path, self.other_tree,
1673
1612
                                              base=base)
1674
1613
 
1675
 
    def _merged_lines(self, file_id):
 
1614
    def _merged_lines(self, this_path):
1676
1615
        """Generate the merged lines.
1677
1616
        There is no distinction between lines that are meant to contain <<<<<<<
1678
1617
        and conflicts.
1681
1620
            base = self.base_tree
1682
1621
        else:
1683
1622
            base = None
1684
 
        plan = self._generate_merge_plan(file_id, base)
 
1623
        plan = self._generate_merge_plan(this_path, base)
1685
1624
        if 'merge' in debug.debug_flags:
1686
1625
            plan = list(plan)
1687
1626
            trans_id = self.tt.trans_id_file_id(file_id)
1688
1627
            name = self.tt.final_name(trans_id) + '.plan'
1689
 
            contents = ('%11s|%s' % l for l in plan)
 
1628
            contents = (b'%11s|%s' % l for l in plan)
1690
1629
            self.tt.new_file(name, self.tt.final_parent(trans_id), contents)
1691
 
        textmerge = versionedfile.PlanWeaveMerge(plan, '<<<<<<< TREE\n',
1692
 
                                                 '>>>>>>> MERGE-SOURCE\n')
 
1630
        textmerge = versionedfile.PlanWeaveMerge(plan, b'<<<<<<< TREE\n',
 
1631
                                                 b'>>>>>>> MERGE-SOURCE\n')
1693
1632
        lines, conflicts = textmerge.merge_lines(self.reprocess)
1694
1633
        if conflicts:
1695
1634
            base_lines = textmerge.base_from_plan()
1697
1636
            base_lines = None
1698
1637
        return lines, base_lines
1699
1638
 
1700
 
    def text_merge(self, file_id, trans_id):
 
1639
    def text_merge(self, trans_id, paths):
1701
1640
        """Perform a (weave) text merge for a given file and file-id.
1702
1641
        If conflicts are encountered, .THIS and .OTHER files will be emitted,
1703
1642
        and a conflict will be noted.
1704
1643
        """
1705
 
        lines, base_lines = self._merged_lines(file_id)
 
1644
        base_path, other_path, this_path = paths
 
1645
        lines, base_lines = self._merged_lines(this_path)
1706
1646
        lines = list(lines)
1707
1647
        # Note we're checking whether the OUTPUT is binary in this case,
1708
1648
        # because we don't want to get into weave merge guts.
1713
1653
            self._raw_conflicts.append(('text conflict', trans_id))
1714
1654
            name = self.tt.final_name(trans_id)
1715
1655
            parent_id = self.tt.final_parent(trans_id)
1716
 
            file_group = self._dump_conflicts(name, parent_id, file_id,
 
1656
            file_id = self.tt.final_file_id(trans_id)
 
1657
            file_group = self._dump_conflicts(name, paths, parent_id, file_id,
1717
1658
                                              no_base=False,
1718
1659
                                              base_lines=base_lines)
1719
1660
            file_group.append(trans_id)
1721
1662
 
1722
1663
class LCAMerger(WeaveMerger):
1723
1664
 
1724
 
    def _generate_merge_plan(self, file_id, base):
1725
 
        return self.this_tree.plan_file_lca_merge(file_id, self.other_tree,
 
1665
    requires_file_merge_plan = True
 
1666
 
 
1667
    def _generate_merge_plan(self, this_path, base):
 
1668
        return self.this_tree.plan_file_lca_merge(this_path, self.other_tree,
1726
1669
                                                  base=base)
1727
1670
 
 
1671
 
1728
1672
class Diff3Merger(Merge3Merger):
1729
1673
    """Three-way merger using external diff3 for text merging"""
1730
1674
 
1731
 
    def dump_file(self, temp_dir, name, tree, file_id):
 
1675
    requires_file_merge_plan = False
 
1676
 
 
1677
    def dump_file(self, temp_dir, name, tree, path):
1732
1678
        out_path = osutils.pathjoin(temp_dir, name)
1733
 
        out_file = open(out_path, "wb")
1734
 
        try:
1735
 
            in_file = tree.get_file(tree.id2path(file_id), file_id)
 
1679
        with open(out_path, "wb") as out_file:
 
1680
            in_file = tree.get_file(path)
1736
1681
            for line in in_file:
1737
1682
                out_file.write(line)
1738
 
        finally:
1739
 
            out_file.close()
1740
1683
        return out_path
1741
1684
 
1742
 
    def text_merge(self, file_id, trans_id):
 
1685
    def text_merge(self, trans_id, paths):
1743
1686
        """Perform a diff3 merge using a specified file-id and trans-id.
1744
1687
        If conflicts are encountered, .BASE, .THIS. and .OTHER conflict files
1745
1688
        will be dumped, and a will be conflict noted.
1746
1689
        """
1747
1690
        import breezy.patch
 
1691
        base_path, other_path, this_path = paths
1748
1692
        temp_dir = osutils.mkdtemp(prefix="bzr-")
1749
1693
        try:
1750
1694
            new_file = osutils.pathjoin(temp_dir, "new")
1751
 
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1752
 
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1753
 
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
 
1695
            this = self.dump_file(
 
1696
                temp_dir, "this", self.this_tree, this_path)
 
1697
            base = self.dump_file(
 
1698
                temp_dir, "base", self.base_tree, base_path)
 
1699
            other = self.dump_file(
 
1700
                temp_dir, "other", self.other_tree, other_path)
1754
1701
            status = breezy.patch.diff3(new_file, this, base, other)
1755
1702
            if status not in (0, 1):
1756
1703
                raise errors.BzrError("Unhandled diff3 exit code")
1757
 
            f = open(new_file, 'rb')
1758
 
            try:
 
1704
            with open(new_file, 'rb') as f:
1759
1705
                self.tt.create_file(f, trans_id)
1760
 
            finally:
1761
 
                f.close()
1762
1706
            if status == 1:
1763
1707
                name = self.tt.final_name(trans_id)
1764
1708
                parent_id = self.tt.final_parent(trans_id)
1765
 
                self._dump_conflicts(name, parent_id, file_id)
 
1709
                file_id = self.tt.final_file_id(trans_id)
 
1710
                self._dump_conflicts(name, paths, parent_id, file_id)
1766
1711
                self._raw_conflicts.append(('text conflict', trans_id))
1767
1712
        finally:
1768
1713
            osutils.rmtree(temp_dir)
1784
1729
    """
1785
1730
 
1786
1731
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1787
 
            source_subpath, other_rev_id=None):
 
1732
                 source_subpath, other_rev_id=None):
1788
1733
        """Create a new MergeIntoMerger object.
1789
1734
 
1790
1735
        source_subpath in other_tree will be effectively copied to
1801
1746
        # It is assumed that we are merging a tree that is not in our current
1802
1747
        # ancestry, which means we are using the "EmptyTree" as our basis.
1803
1748
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1804
 
                                _mod_revision.NULL_REVISION)
 
1749
            _mod_revision.NULL_REVISION)
1805
1750
        super(MergeIntoMerger, self).__init__(
1806
1751
            this_branch=this_tree.branch,
1807
1752
            this_tree=this_tree,
1819
1764
        self.merge_type = Merge3Merger
1820
1765
        self.show_base = False
1821
1766
        self.reprocess = False
1822
 
        self.interesting_ids = None
 
1767
        self.interesting_files = None
1823
1768
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1824
 
              target_subdir=self._target_subdir,
1825
 
              source_subpath=self._source_subpath)
 
1769
                                                  target_subdir=self._target_subdir,
 
1770
                                                  source_subpath=self._source_subpath)
1826
1771
        if self._source_subpath != '':
1827
1772
            # If this isn't a partial merge make sure the revisions will be
1828
1773
            # present.
1829
1774
            self._maybe_fetch(self.other_branch, self.this_branch,
1830
 
                self.other_basis)
 
1775
                              self.other_basis)
1831
1776
 
1832
1777
    def set_pending(self):
1833
1778
        if self._source_subpath != '':
1837
1782
 
1838
1783
class _MergeTypeParameterizer(object):
1839
1784
    """Wrap a merge-type class to provide extra parameters.
1840
 
    
 
1785
 
1841
1786
    This is hack used by MergeIntoMerger to pass some extra parameters to its
1842
1787
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
1843
1788
    the 'merge_type' member.  It is difficult override do_merge without
1878
1823
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1879
1824
 
1880
1825
    def _compute_transform(self):
1881
 
        child_pb = ui.ui_factory.nested_progress_bar()
1882
 
        try:
 
1826
        with ui.ui_factory.nested_progress_bar() as child_pb:
1883
1827
            entries = self._entries_to_incorporate()
1884
1828
            entries = list(entries)
1885
 
            for num, (entry, parent_id, path) in enumerate(entries):
1886
 
                child_pb.update(gettext('Preparing file merge'), num, len(entries))
 
1829
            for num, (entry, parent_id, relpath) in enumerate(entries):
 
1830
                child_pb.update(gettext('Preparing file merge'),
 
1831
                                num, len(entries))
1887
1832
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1833
                path = osutils.pathjoin(self._source_subpath, relpath)
1888
1834
                trans_id = transform.new_by_entry(path, self.tt, entry,
1889
 
                    parent_trans_id, self.other_tree)
1890
 
        finally:
1891
 
            child_pb.finished()
 
1835
                                                  parent_trans_id, self.other_tree)
1892
1836
        self._finish_computing_transform()
1893
1837
 
1894
1838
    def _entries_to_incorporate(self):
1895
1839
        """Yields pairs of (inventory_entry, new_parent)."""
1896
 
        other_inv = self.other_tree.root_inventory
1897
 
        subdir_id = other_inv.path2id(self._source_subpath)
 
1840
        subdir_id = self.other_tree.path2id(self._source_subpath)
1898
1841
        if subdir_id is None:
1899
1842
            # XXX: The error would be clearer if it gave the URL of the source
1900
1843
            # branch, but we don't have a reference to that here.
1901
1844
            raise PathNotInTree(self._source_subpath, "Source tree")
1902
 
        subdir = other_inv[subdir_id]
 
1845
        subdir = next(self.other_tree.iter_entries_by_dir(
 
1846
            specific_files=[self._source_subpath]))[1]
1903
1847
        parent_in_target = osutils.dirname(self._target_subdir)
1904
1848
        target_id = self.this_tree.path2id(parent_in_target)
1905
1849
        if target_id is None:
1907
1851
        name_in_target = osutils.basename(self._target_subdir)
1908
1852
        merge_into_root = subdir.copy()
1909
1853
        merge_into_root.name = name_in_target
1910
 
        if self.this_tree.has_id(merge_into_root.file_id):
 
1854
        try:
 
1855
            self.this_tree.id2path(merge_into_root.file_id)
 
1856
        except errors.NoSuchId:
 
1857
            pass
 
1858
        else:
1911
1859
            # Give the root a new file-id.
1912
1860
            # This can happen fairly easily if the directory we are
1913
1861
            # incorporating is the root, and both trees have 'TREE_ROOT' as
1921
1869
        if subdir.kind != 'directory':
1922
1870
            # No children, so we are done.
1923
1871
            return
1924
 
        for path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1872
        for path, entry in self.other_tree.root_inventory.iter_entries_by_dir(subdir_id):
1925
1873
            parent_id = entry.parent_id
1926
1874
            if parent_id == subdir.file_id:
1927
1875
                # The root's parent ID has changed, so make sure children of
1933
1881
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1934
1882
                backup_files=False,
1935
1883
                merge_type=Merge3Merger,
1936
 
                interesting_ids=None,
1937
1884
                show_base=False,
1938
1885
                reprocess=False,
1939
1886
                other_rev_id=None,
1954
1901
                    change_reporter=change_reporter)
1955
1902
    merger.backup_files = backup_files
1956
1903
    merger.merge_type = merge_type
1957
 
    merger.interesting_ids = interesting_ids
1958
1904
    merger.ignore_zero = ignore_zero
1959
 
    if interesting_files:
1960
 
        if interesting_ids:
1961
 
            raise ValueError('Only supply interesting_ids'
1962
 
                             ' or interesting_files')
1963
 
        merger.interesting_files = interesting_files
 
1905
    merger.interesting_files = interesting_files
1964
1906
    merger.show_base = show_base
1965
1907
    merger.reprocess = reprocess
1966
1908
    merger.other_rev_id = other_rev_id
2061
2003
        for record in self.vf.get_record_stream(keys, 'unordered', True):
2062
2004
            if record.storage_kind == 'absent':
2063
2005
                raise errors.RevisionNotPresent(record.key, self.vf)
2064
 
            result[record.key[-1]] = osutils.chunks_to_lines(
2065
 
                record.get_bytes_as('chunked'))
 
2006
            result[record.key[-1]] = record.get_bytes_as('lines')
2066
2007
        return result
2067
2008
 
2068
2009
    def plan_merge(self):
2101
2042
                else:
2102
2043
                    yield 'killed-a', self.lines_b[b_index]
2103
2044
            # handle common lines
2104
 
            for a_index in range(i, i+n):
 
2045
            for a_index in range(i, i + n):
2105
2046
                yield 'unchanged', self.lines_a[a_index]
2106
 
            last_i = i+n
2107
 
            last_j = j+n
 
2047
            last_i = i + n
 
2048
            last_j = j + n
2108
2049
 
2109
2050
    def _get_matching_blocks(self, left_revision, right_revision):
2110
2051
        """Return a description of which sections of two revisions match.
2166
2107
        for i, j, n in matcher.get_matching_blocks():
2167
2108
            for jj in range(last_j, j):
2168
2109
                yield new_plan[jj]
2169
 
            for jj in range(j, j+n):
 
2110
            for jj in range(j, j + n):
2170
2111
                plan_line = new_plan[jj]
2171
2112
                if plan_line[0] == 'new-b':
2172
2113
                    pass
2310
2251
        filtered_parent_map = {}
2311
2252
        child_map = {}
2312
2253
        tails = []
2313
 
        for key, parent_keys in viewitems(parent_map):
 
2254
        for key, parent_keys in parent_map.items():
2314
2255
            culled_parent_keys = [p for p in parent_keys if p in parent_map]
2315
2256
            if not culled_parent_keys:
2316
2257
                tails.append(key)
2403
2344
        are combined, they are written out in the format described in
2404
2345
        VersionedFile.plan_merge
2405
2346
        """
2406
 
        if self._head_key is not None: # There was a single head
 
2347
        if self._head_key is not None:  # There was a single head
2407
2348
            if self._head_key == self.a_key:
2408
2349
                plan = 'new-a'
2409
2350
            else: