/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-02-18 21:42:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6859.
  • Revision ID: jelmer@jelmer.uk-20180218214257-jpevutp1wa30tz3v
Update TODO to reference Breezy, not Bazaar.

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