~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Andrew Bennetts
  • Date: 2010-12-08 10:19:59 UTC
  • mto: This revision was merged to the branch mainline in revision 5565.
  • Revision ID: andrew.bennetts@canonical.com-20101208101959-tngr07xid60qsn98
Fix double dash, highlight the significant '.' param.

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 warnings
17
18
 
 
19
from bzrlib.lazy_import import lazy_import
 
20
lazy_import(globals(), """
18
21
from bzrlib import (
19
22
    branch as _mod_branch,
20
23
    conflicts as _mod_conflicts,
21
24
    debug,
22
 
    decorators,
23
 
    errors,
 
25
    generate_ids,
24
26
    graph as _mod_graph,
25
 
    hooks,
26
27
    merge3,
27
28
    osutils,
28
29
    patiencediff,
29
 
    progress,
30
30
    revision as _mod_revision,
31
31
    textfile,
32
32
    trace,
34
34
    tree as _mod_tree,
35
35
    tsort,
36
36
    ui,
37
 
    versionedfile
 
37
    versionedfile,
 
38
    workingtree,
 
39
    )
 
40
from bzrlib.cleanup import OperationWithCleanups
 
41
""")
 
42
from bzrlib import (
 
43
    decorators,
 
44
    errors,
 
45
    hooks,
38
46
    )
39
47
from bzrlib.symbol_versioning import (
40
48
    deprecated_in,
45
53
 
46
54
def transform_tree(from_tree, to_tree, interesting_ids=None):
47
55
    from_tree.lock_tree_write()
48
 
    try:
49
 
        merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
50
 
                    interesting_ids=interesting_ids, this_tree=from_tree)
51
 
    finally:
52
 
        from_tree.unlock()
 
56
    operation = OperationWithCleanups(merge_inner)
 
57
    operation.add_cleanup(from_tree.unlock)
 
58
    operation.run_simple(from_tree.branch, to_tree, from_tree,
 
59
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
53
60
 
54
61
 
55
62
class MergeHooks(hooks.Hooks):
93
100
        return ('not applicable', None)
94
101
 
95
102
 
96
 
class ConfigurableFileMerger(AbstractPerFileMerger):
 
103
class PerFileMerger(AbstractPerFileMerger):
 
104
    """Merge individual files when self.file_matches returns True.
 
105
 
 
106
    This class is intended to be subclassed.  The file_matches and
 
107
    merge_matching methods should be overridden with concrete implementations.
 
108
    """
 
109
 
 
110
    def file_matches(self, params):
 
111
        """Return True if merge_matching should be called on this file.
 
112
 
 
113
        Only called with merges of plain files with no clear winner.
 
114
 
 
115
        Subclasses must override this.
 
116
        """
 
117
        raise NotImplementedError(self.file_matches)
 
118
 
 
119
    def get_filename(self, params, tree):
 
120
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
 
121
        self.merger.this_tree) and a MergeHookParams.
 
122
        """
 
123
        return osutils.basename(tree.id2path(params.file_id))
 
124
 
 
125
    def get_filepath(self, params, tree):
 
126
        """Calculate the path to the file in a tree.
 
127
 
 
128
        :param params: A MergeHookParams describing the file to merge
 
129
        :param tree: a Tree, e.g. self.merger.this_tree.
 
130
        """
 
131
        return tree.id2path(params.file_id)
 
132
 
 
133
    def merge_contents(self, params):
 
134
        """Merge the contents of a single file."""
 
135
        # Check whether this custom merge logic should be used.
 
136
        if (
 
137
            # OTHER is a straight winner, rely on default merge.
 
138
            params.winner == 'other' or
 
139
            # THIS and OTHER aren't both files.
 
140
            not params.is_file_merge() or
 
141
            # The filename doesn't match *.xml
 
142
            not self.file_matches(params)):
 
143
            return 'not_applicable', None
 
144
        return self.merge_matching(params)
 
145
 
 
146
    def merge_matching(self, params):
 
147
        """Merge the contents of a single file that has matched the criteria
 
148
        in PerFileMerger.merge_contents (is a conflict, is a file,
 
149
        self.file_matches is True).
 
150
 
 
151
        Subclasses must override this.
 
152
        """
 
153
        raise NotImplementedError(self.merge_matching)
 
154
 
 
155
 
 
156
class ConfigurableFileMerger(PerFileMerger):
97
157
    """Merge individual files when configured via a .conf file.
98
158
 
99
159
    This is a base class for concrete custom file merging logic. Concrete
100
160
    classes should implement ``merge_text``.
101
161
 
 
162
    See ``bzrlib.plugins.news_merge.news_merge`` for an example concrete class.
 
163
    
102
164
    :ivar affected_files: The configured file paths to merge.
 
165
 
103
166
    :cvar name_prefix: The prefix to use when looking up configuration
104
 
        details.
 
167
        details. <name_prefix>_merge_files describes the files targeted by the
 
168
        hook for example.
 
169
        
105
170
    :cvar default_files: The default file paths to merge when no configuration
106
171
        is present.
107
172
    """
117
182
        if self.name_prefix is None:
118
183
            raise ValueError("name_prefix must be set.")
119
184
 
120
 
    def filename_matches_config(self, params):
 
185
    def file_matches(self, params):
 
186
        """Check whether the file should call the merge hook.
 
187
 
 
188
        <name_prefix>_merge_files configuration variable is a list of files
 
189
        that should use the hook.
 
190
        """
121
191
        affected_files = self.affected_files
122
192
        if affected_files is None:
123
 
            config = self.merger.this_tree.branch.get_config()
 
193
            config = self.merger.this_branch.get_config()
124
194
            # Until bzr provides a better policy for caching the config, we
125
195
            # just add the part we're interested in to the params to avoid
126
196
            # reading the config files repeatedly (bazaar.conf, location.conf,
132
202
                affected_files = self.default_files
133
203
            self.affected_files = affected_files
134
204
        if affected_files:
135
 
            filename = self.merger.this_tree.id2path(params.file_id)
136
 
            if filename in affected_files:
 
205
            filepath = self.get_filepath(params, self.merger.this_tree)
 
206
            if filepath in affected_files:
137
207
                return True
138
208
        return False
139
209
 
140
 
    def merge_contents(self, params):
141
 
        """Merge the contents of a single file."""
142
 
        # First, check whether this custom merge logic should be used.  We
143
 
        # expect most files should not be merged by this handler.
144
 
        if (
145
 
            # OTHER is a straight winner, rely on default merge.
146
 
            params.winner == 'other' or
147
 
            # THIS and OTHER aren't both files.
148
 
            not params.is_file_merge() or
149
 
            # The filename isn't listed in the 'NAME_merge_files' config
150
 
            # option.
151
 
            not self.filename_matches_config(params)):
152
 
            return 'not_applicable', None
153
 
        return self.merge_text(self, params)
 
210
    def merge_matching(self, params):
 
211
        return self.merge_text(params)
154
212
 
155
213
    def merge_text(self, params):
156
214
        """Merge the byte contents of a single file.
227
285
        self.interesting_files = None
228
286
        self.show_base = False
229
287
        self.reprocess = False
230
 
        if pb is None:
231
 
            pb = progress.DummyProgress()
232
 
        self._pb = pb
 
288
        if pb is not None:
 
289
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
233
290
        self.pp = None
234
291
        self.recurse = recurse
235
292
        self.change_reporter = change_reporter
371
428
        return self._cached_trees[revision_id]
372
429
 
373
430
    def _get_tree(self, treespec, possible_transports=None):
374
 
        from bzrlib import workingtree
375
431
        location, revno = treespec
376
432
        if revno is None:
377
433
            tree = workingtree.WorkingTree.open_containing(location)[0]
445
501
    def _add_parent(self):
446
502
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
447
503
        new_parent_trees = []
 
504
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
448
505
        for revision_id in new_parents:
449
506
            try:
450
507
                tree = self.revision_tree(revision_id)
452
509
                tree = None
453
510
            else:
454
511
                tree.lock_read()
 
512
                operation.add_cleanup(tree.unlock)
455
513
            new_parent_trees.append((revision_id, tree))
456
 
        try:
457
 
            self.this_tree.set_parent_trees(new_parent_trees,
458
 
                                            allow_leftmost_as_ghost=True)
459
 
        finally:
460
 
            for _revision_id, tree in new_parent_trees:
461
 
                if tree is not None:
462
 
                    tree.unlock()
 
514
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
463
515
 
464
516
    def set_other(self, other_revision, possible_transports=None):
465
517
        """Set the revision and tree to merge from.
530
582
            elif len(lcas) == 1:
531
583
                self.base_rev_id = list(lcas)[0]
532
584
            else: # len(lcas) > 1
 
585
                self._is_criss_cross = True
533
586
                if len(lcas) > 2:
534
587
                    # find_unique_lca can only handle 2 nodes, so we have to
535
588
                    # start back at the beginning. It is a shame to traverse
540
593
                else:
541
594
                    self.base_rev_id = self.revision_graph.find_unique_lca(
542
595
                                            *lcas)
543
 
                self._is_criss_cross = True
 
596
                sorted_lca_keys = self.revision_graph.find_merge_order(                
 
597
                    revisions[0], lcas)
 
598
                if self.base_rev_id == _mod_revision.NULL_REVISION:
 
599
                    self.base_rev_id = sorted_lca_keys[0]
 
600
                
544
601
            if self.base_rev_id == _mod_revision.NULL_REVISION:
545
602
                raise errors.UnrelatedBranches()
546
603
            if self._is_criss_cross:
547
604
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
548
605
                              ' help criss-cross.')
549
606
                trace.mutter('Criss-cross lcas: %r' % lcas)
550
 
                interesting_revision_ids = [self.base_rev_id]
551
 
                interesting_revision_ids.extend(lcas)
 
607
                if self.base_rev_id in lcas:
 
608
                    trace.mutter('Unable to find unique lca. '
 
609
                                 'Fallback %r as best option.' % self.base_rev_id)
 
610
                interesting_revision_ids = set(lcas)
 
611
                interesting_revision_ids.add(self.base_rev_id)
552
612
                interesting_trees = dict((t.get_revision_id(), t)
553
613
                    for t in self.this_branch.repository.revision_trees(
554
614
                        interesting_revision_ids))
555
615
                self._cached_trees.update(interesting_trees)
556
 
                self.base_tree = interesting_trees.pop(self.base_rev_id)
557
 
                sorted_lca_keys = self.revision_graph.find_merge_order(
558
 
                    revisions[0], lcas)
 
616
                if self.base_rev_id in lcas:
 
617
                    self.base_tree = interesting_trees[self.base_rev_id]
 
618
                else:
 
619
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
559
620
                self._lca_trees = [interesting_trees[key]
560
621
                                   for key in sorted_lca_keys]
561
622
            else:
588
649
                  'other_tree': self.other_tree,
589
650
                  'interesting_ids': self.interesting_ids,
590
651
                  'interesting_files': self.interesting_files,
591
 
                  'pp': self.pp, 'this_branch': self.this_branch,
 
652
                  'this_branch': self.this_branch,
592
653
                  'do_merge': False}
593
654
        if self.merge_type.requires_base:
594
655
            kwargs['base_tree'] = self.base_tree
612
673
        if self._is_criss_cross and getattr(self.merge_type,
613
674
                                            'supports_lca_trees', False):
614
675
            kwargs['lca_trees'] = self._lca_trees
615
 
        return self.merge_type(pb=self._pb,
 
676
        return self.merge_type(pb=None,
616
677
                               change_reporter=self.change_reporter,
617
678
                               **kwargs)
618
679
 
619
 
    def _do_merge_to(self, merge):
 
680
    def _do_merge_to(self):
 
681
        merge = self.make_merger()
620
682
        if self.other_branch is not None:
621
683
            self.other_branch.update_references(self.this_branch)
622
684
        merge.do_merge()
636
698
                    sub_tree.branch.repository.revision_tree(base_revision)
637
699
                sub_merge.base_rev_id = base_revision
638
700
                sub_merge.do_merge()
 
701
        return merge
639
702
 
640
703
    def do_merge(self):
 
704
        operation = OperationWithCleanups(self._do_merge_to)
641
705
        self.this_tree.lock_tree_write()
642
 
        try:
643
 
            if self.base_tree is not None:
644
 
                self.base_tree.lock_read()
645
 
            try:
646
 
                if self.other_tree is not None:
647
 
                    self.other_tree.lock_read()
648
 
                try:
649
 
                    merge = self.make_merger()
650
 
                    self._do_merge_to(merge)
651
 
                finally:
652
 
                    if self.other_tree is not None:
653
 
                        self.other_tree.unlock()
654
 
            finally:
655
 
                if self.base_tree is not None:
656
 
                    self.base_tree.unlock()
657
 
        finally:
658
 
            self.this_tree.unlock()
 
706
        operation.add_cleanup(self.this_tree.unlock)
 
707
        if self.base_tree is not None:
 
708
            self.base_tree.lock_read()
 
709
            operation.add_cleanup(self.base_tree.unlock)
 
710
        if self.other_tree is not None:
 
711
            self.other_tree.lock_read()
 
712
            operation.add_cleanup(self.other_tree.unlock)
 
713
        merge = operation.run_simple()
659
714
        if len(merge.cooked_conflicts) == 0:
660
715
            if not self.ignore_zero and not trace.is_quiet():
661
716
                trace.note("All changes applied successfully.")
696
751
 
697
752
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
698
753
                 interesting_ids=None, reprocess=False, show_base=False,
699
 
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
 
754
                 pb=None, pp=None, change_reporter=None,
700
755
                 interesting_files=None, do_merge=True,
701
756
                 cherrypick=False, lca_trees=None, this_branch=None):
702
757
        """Initialize the merger object and perform the merge.
705
760
        :param this_tree: The local tree in the merge operation
706
761
        :param base_tree: The common tree in the merge operation
707
762
        :param other_tree: The other tree to merge changes from
708
 
        :param this_branch: The branch associated with this_tree
 
763
        :param this_branch: The branch associated with this_tree.  Defaults to
 
764
            this_tree.branch if not supplied.
709
765
        :param interesting_ids: The file_ids of files that should be
710
766
            participate in the merge.  May not be combined with
711
767
            interesting_files.
712
768
        :param: reprocess If True, perform conflict-reduction processing.
713
769
        :param show_base: If True, show the base revision in text conflicts.
714
770
            (incompatible with reprocess)
715
 
        :param pb: A Progress bar
 
771
        :param pb: ignored
716
772
        :param pp: A ProgressPhase object
717
773
        :param change_reporter: An object that should report changes made
718
774
        :param interesting_files: The tree-relative paths of files that should
729
785
        if interesting_files is not None and interesting_ids is not None:
730
786
            raise ValueError(
731
787
                'specify either interesting_ids or interesting_files')
 
788
        if this_branch is None:
 
789
            this_branch = this_tree.branch
732
790
        self.interesting_ids = interesting_ids
733
791
        self.interesting_files = interesting_files
734
792
        self.this_tree = working_tree
745
803
        # making sure we haven't missed any corner cases.
746
804
        # if lca_trees is None:
747
805
        #     self._lca_trees = [self.base_tree]
748
 
        self.pb = pb
749
 
        self.pp = pp
750
806
        self.change_reporter = change_reporter
751
807
        self.cherrypick = cherrypick
752
 
        if self.pp is None:
753
 
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
754
808
        if do_merge:
755
809
            self.do_merge()
 
810
        if pp is not None:
 
811
            warnings.warn("pp argument to Merge3Merger is deprecated")
 
812
        if pb is not None:
 
813
            warnings.warn("pb argument to Merge3Merger is deprecated")
756
814
 
757
815
    def do_merge(self):
 
816
        operation = OperationWithCleanups(self._do_merge)
758
817
        self.this_tree.lock_tree_write()
 
818
        operation.add_cleanup(self.this_tree.unlock)
759
819
        self.base_tree.lock_read()
 
820
        operation.add_cleanup(self.base_tree.unlock)
760
821
        self.other_tree.lock_read()
 
822
        operation.add_cleanup(self.other_tree.unlock)
 
823
        operation.run()
 
824
 
 
825
    def _do_merge(self, operation):
 
826
        self.tt = transform.TreeTransform(self.this_tree, None)
 
827
        operation.add_cleanup(self.tt.finalize)
 
828
        self._compute_transform()
 
829
        results = self.tt.apply(no_conflicts=True)
 
830
        self.write_modified(results)
761
831
        try:
762
 
            self.tt = transform.TreeTransform(self.this_tree, self.pb)
763
 
            try:
764
 
                self.pp.next_phase()
765
 
                self._compute_transform()
766
 
                self.pp.next_phase()
767
 
                results = self.tt.apply(no_conflicts=True)
768
 
                self.write_modified(results)
769
 
                try:
770
 
                    self.this_tree.add_conflicts(self.cooked_conflicts)
771
 
                except errors.UnsupportedOperation:
772
 
                    pass
773
 
            finally:
774
 
                self.tt.finalize()
775
 
        finally:
776
 
            self.other_tree.unlock()
777
 
            self.base_tree.unlock()
778
 
            self.this_tree.unlock()
779
 
            self.pb.clear()
 
832
            self.this_tree.add_conflicts(self.cooked_conflicts)
 
833
        except errors.UnsupportedOperation:
 
834
            pass
780
835
 
781
836
    def make_preview_transform(self):
 
837
        operation = OperationWithCleanups(self._make_preview_transform)
782
838
        self.base_tree.lock_read()
 
839
        operation.add_cleanup(self.base_tree.unlock)
783
840
        self.other_tree.lock_read()
 
841
        operation.add_cleanup(self.other_tree.unlock)
 
842
        return operation.run_simple()
 
843
 
 
844
    def _make_preview_transform(self):
784
845
        self.tt = transform.TransformPreview(self.this_tree)
785
 
        try:
786
 
            self.pp.next_phase()
787
 
            self._compute_transform()
788
 
            self.pp.next_phase()
789
 
        finally:
790
 
            self.other_tree.unlock()
791
 
            self.base_tree.unlock()
792
 
            self.pb.clear()
 
846
        self._compute_transform()
793
847
        return self.tt
794
848
 
795
849
    def _compute_transform(self):
817
871
        finally:
818
872
            child_pb.finished()
819
873
        self.fix_root()
820
 
        self.pp.next_phase()
 
874
        self._finish_computing_transform()
 
875
 
 
876
    def _finish_computing_transform(self):
 
877
        """Finalize the transform and report the changes.
 
878
 
 
879
        This is the second half of _compute_transform.
 
880
        """
821
881
        child_pb = ui.ui_factory.nested_progress_bar()
822
882
        try:
823
883
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1021
1081
                        continue
1022
1082
                else:
1023
1083
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
1024
 
                # XXX: We need to handle kind == 'symlink'
1025
1084
 
1026
1085
            # If we have gotten this far, that means something has changed
1027
1086
            result.append((file_id, content_changed,
1034
1093
                          ))
1035
1094
        return result
1036
1095
 
1037
 
 
1038
1096
    def fix_root(self):
1039
 
        try:
1040
 
            self.tt.final_kind(self.tt.root)
1041
 
        except errors.NoSuchFile:
 
1097
        if self.tt.final_kind(self.tt.root) is None:
1042
1098
            self.tt.cancel_deletion(self.tt.root)
1043
1099
        if self.tt.final_file_id(self.tt.root) is None:
1044
1100
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1049
1105
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1050
1106
        if other_root == self.tt.root:
1051
1107
            return
1052
 
        try:
1053
 
            self.tt.final_kind(other_root)
1054
 
        except errors.NoSuchFile:
1055
 
            return
1056
 
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
1057
 
            # the other tree's root is a non-root in the current tree
1058
 
            return
1059
 
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
1060
 
        self.tt.cancel_creation(other_root)
1061
 
        self.tt.cancel_versioning(other_root)
1062
 
 
1063
 
    def reparent_children(self, ie, target):
1064
 
        for thing, child in ie.children.iteritems():
 
1108
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1109
            # the other tree's root is a non-root in the current tree (as when
 
1110
            # a previously unrelated branch is merged into another)
 
1111
            return
 
1112
        if self.tt.final_kind(other_root) is not None:
 
1113
            other_root_is_present = True
 
1114
        else:
 
1115
            # other_root doesn't have a physical representation. We still need
 
1116
            # to move any references to the actual root of the tree.
 
1117
            other_root_is_present = False
 
1118
        # 'other_tree.inventory.root' is not present in this tree. We are
 
1119
        # calling adjust_path for children which *want* to be present with a
 
1120
        # correct place to go.
 
1121
        for thing, child in self.other_tree.inventory.root.children.iteritems():
1065
1122
            trans_id = self.tt.trans_id_file_id(child.file_id)
1066
 
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
1123
            if not other_root_is_present:
 
1124
                if self.tt.final_kind(trans_id) is not None:
 
1125
                    # The item exist in the final tree and has a defined place
 
1126
                    # to go already.
 
1127
                    continue
 
1128
            # Move the item into the root
 
1129
            self.tt.adjust_path(self.tt.final_name(trans_id),
 
1130
                                self.tt.root, trans_id)
 
1131
        if other_root_is_present:
 
1132
            self.tt.cancel_creation(other_root)
 
1133
            self.tt.cancel_versioning(other_root)
1067
1134
 
1068
1135
    def write_modified(self, results):
1069
1136
        modified_hashes = {}
1116
1183
 
1117
1184
    @staticmethod
1118
1185
    def _three_way(base, other, this):
1119
 
        #if base == other, either they all agree, or only THIS has changed.
1120
1186
        if base == other:
 
1187
            # if 'base == other', either they all agree, or only 'this' has
 
1188
            # changed.
1121
1189
            return 'this'
1122
1190
        elif this not in (base, other):
 
1191
            # 'this' is neither 'base' nor 'other', so both sides changed
1123
1192
            return 'conflict'
1124
 
        # "Ambiguous clean merge" -- both sides have made the same change.
1125
1193
        elif this == other:
 
1194
            # "Ambiguous clean merge" -- both sides have made the same change.
1126
1195
            return "this"
1127
 
        # this == base: only other has changed.
1128
1196
        else:
 
1197
            # this == base: only other has changed.
1129
1198
            return "other"
1130
1199
 
1131
1200
    @staticmethod
1175
1244
                # only has an lca value
1176
1245
                return 'other'
1177
1246
 
1178
 
        # At this point, the lcas disagree, and the tips disagree
 
1247
        # At this point, the lcas disagree, and the tip disagree
1179
1248
        return 'conflict'
1180
1249
 
1181
1250
    @staticmethod
 
1251
    @deprecated_method(deprecated_in((2, 2, 0)))
1182
1252
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1183
1253
        """Do a three-way test on a scalar.
1184
1254
        Return "this", "other" or "conflict", depending whether a value wins.
1234
1304
                parent_id_winner = "other"
1235
1305
        if name_winner == "this" and parent_id_winner == "this":
1236
1306
            return
1237
 
        if name_winner == "conflict":
1238
 
            trans_id = self.tt.trans_id_file_id(file_id)
1239
 
            self._raw_conflicts.append(('name conflict', trans_id,
1240
 
                                        this_name, other_name))
1241
 
        if parent_id_winner == "conflict":
1242
 
            trans_id = self.tt.trans_id_file_id(file_id)
1243
 
            self._raw_conflicts.append(('parent conflict', trans_id,
1244
 
                                        this_parent, other_parent))
 
1307
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
 
1308
            # Creating helpers (.OTHER or .THIS) here cause problems down the
 
1309
            # road if a ContentConflict needs to be created so we should not do
 
1310
            # that
 
1311
            trans_id = self.tt.trans_id_file_id(file_id)
 
1312
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
 
1313
                                        this_parent, this_name,
 
1314
                                        other_parent, other_name))
1245
1315
        if other_name is None:
1246
1316
            # it doesn't matter whether the result was 'other' or
1247
1317
            # 'conflict'-- if there's no 'other', we leave it alone.
1248
1318
            return
1249
 
        # if we get here, name_winner and parent_winner are set to safe values.
1250
 
        trans_id = self.tt.trans_id_file_id(file_id)
1251
1319
        parent_id = parents[self.winner_idx[parent_id_winner]]
1252
1320
        if parent_id is not None:
1253
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1321
            # if we get here, name_winner and parent_winner are set to safe
 
1322
            # values.
1254
1323
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1255
 
                                parent_trans_id, trans_id)
 
1324
                                self.tt.trans_id_file_id(parent_id),
 
1325
                                self.tt.trans_id_file_id(file_id))
1256
1326
 
1257
1327
    def _do_merge_contents(self, file_id):
1258
1328
        """Performs a merge on file_id contents."""
1336
1406
            self.tt.version_file(file_id, trans_id)
1337
1407
        # The merge has been performed, so the old contents should not be
1338
1408
        # retained.
1339
 
        try:
1340
 
            self.tt.delete_contents(trans_id)
1341
 
        except errors.NoSuchFile:
1342
 
            pass
 
1409
        self.tt.delete_contents(trans_id)
1343
1410
        return result
1344
1411
 
1345
1412
    def _default_other_winner_merge(self, merge_hook_params):
1398
1465
    def get_lines(self, tree, file_id):
1399
1466
        """Return the lines in a file, or an empty list."""
1400
1467
        if tree.has_id(file_id):
1401
 
            return tree.get_file(file_id).readlines()
 
1468
            return tree.get_file_lines(file_id)
1402
1469
        else:
1403
1470
            return []
1404
1471
 
1517
1584
        if winner == 'this' and file_status != "modified":
1518
1585
            return
1519
1586
        trans_id = self.tt.trans_id_file_id(file_id)
1520
 
        try:
1521
 
            if self.tt.final_kind(trans_id) != "file":
1522
 
                return
1523
 
        except errors.NoSuchFile:
 
1587
        if self.tt.final_kind(trans_id) != "file":
1524
1588
            return
1525
1589
        if winner == "this":
1526
1590
            executability = this_executable
1537
1601
 
1538
1602
    def cook_conflicts(self, fs_conflicts):
1539
1603
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1540
 
        name_conflicts = {}
1541
1604
        self.cooked_conflicts.extend(transform.cook_conflicts(
1542
1605
                fs_conflicts, self.tt))
1543
1606
        fp = transform.FinalPaths(self.tt)
1544
1607
        for conflict in self._raw_conflicts:
1545
1608
            conflict_type = conflict[0]
1546
 
            if conflict_type in ('name conflict', 'parent conflict'):
1547
 
                trans_id = conflict[1]
1548
 
                conflict_args = conflict[2:]
1549
 
                if trans_id not in name_conflicts:
1550
 
                    name_conflicts[trans_id] = {}
1551
 
                transform.unique_add(name_conflicts[trans_id], conflict_type,
1552
 
                                     conflict_args)
1553
 
            if conflict_type == 'contents conflict':
 
1609
            if conflict_type == 'path conflict':
 
1610
                (trans_id, file_id,
 
1611
                this_parent, this_name,
 
1612
                other_parent, other_name) = conflict[1:]
 
1613
                if this_parent is None or this_name is None:
 
1614
                    this_path = '<deleted>'
 
1615
                else:
 
1616
                    parent_path =  fp.get_path(
 
1617
                        self.tt.trans_id_file_id(this_parent))
 
1618
                    this_path = osutils.pathjoin(parent_path, this_name)
 
1619
                if other_parent is None or other_name is None:
 
1620
                    other_path = '<deleted>'
 
1621
                else:
 
1622
                    parent_path =  fp.get_path(
 
1623
                        self.tt.trans_id_file_id(other_parent))
 
1624
                    other_path = osutils.pathjoin(parent_path, other_name)
 
1625
                c = _mod_conflicts.Conflict.factory(
 
1626
                    'path conflict', path=this_path,
 
1627
                    conflict_path=other_path,
 
1628
                    file_id=file_id)
 
1629
            elif conflict_type == 'contents conflict':
1554
1630
                for trans_id in conflict[1]:
1555
1631
                    file_id = self.tt.final_file_id(trans_id)
1556
1632
                    if file_id is not None:
1562
1638
                        break
1563
1639
                c = _mod_conflicts.Conflict.factory(conflict_type,
1564
1640
                                                    path=path, file_id=file_id)
1565
 
                self.cooked_conflicts.append(c)
1566
 
            if conflict_type == 'text conflict':
 
1641
            elif conflict_type == 'text conflict':
1567
1642
                trans_id = conflict[1]
1568
1643
                path = fp.get_path(trans_id)
1569
1644
                file_id = self.tt.final_file_id(trans_id)
1570
1645
                c = _mod_conflicts.Conflict.factory(conflict_type,
1571
1646
                                                    path=path, file_id=file_id)
1572
 
                self.cooked_conflicts.append(c)
1573
 
 
1574
 
        for trans_id, conflicts in name_conflicts.iteritems():
1575
 
            try:
1576
 
                this_parent, other_parent = conflicts['parent conflict']
1577
 
                if this_parent == other_parent:
1578
 
                    raise AssertionError()
1579
 
            except KeyError:
1580
 
                this_parent = other_parent = \
1581
 
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
1582
 
            try:
1583
 
                this_name, other_name = conflicts['name conflict']
1584
 
                if this_name == other_name:
1585
 
                    raise AssertionError()
1586
 
            except KeyError:
1587
 
                this_name = other_name = self.tt.final_name(trans_id)
1588
 
            other_path = fp.get_path(trans_id)
1589
 
            if this_parent is not None and this_name is not None:
1590
 
                this_parent_path = \
1591
 
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1592
 
                this_path = osutils.pathjoin(this_parent_path, this_name)
1593
1647
            else:
1594
 
                this_path = "<deleted>"
1595
 
            file_id = self.tt.final_file_id(trans_id)
1596
 
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1597
 
                                                conflict_path=other_path,
1598
 
                                                file_id=file_id)
 
1648
                raise AssertionError('bad conflict type: %r' % (conflict,))
1599
1649
            self.cooked_conflicts.append(c)
1600
1650
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1601
1651
 
1707
1757
            osutils.rmtree(temp_dir)
1708
1758
 
1709
1759
 
 
1760
class PathNotInTree(errors.BzrError):
 
1761
 
 
1762
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1763
 
 
1764
    def __init__(self, path, tree):
 
1765
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1766
 
 
1767
 
 
1768
class MergeIntoMerger(Merger):
 
1769
    """Merger that understands other_tree will be merged into a subdir.
 
1770
 
 
1771
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1772
    and RevisonTree objects, rather than using revision specs.
 
1773
    """
 
1774
 
 
1775
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1776
            source_subpath, other_rev_id=None):
 
1777
        """Create a new MergeIntoMerger object.
 
1778
 
 
1779
        source_subpath in other_tree will be effectively copied to
 
1780
        target_subdir in this_tree.
 
1781
 
 
1782
        :param this_tree: The tree that we will be merging into.
 
1783
        :param other_branch: The Branch we will be merging from.
 
1784
        :param other_tree: The RevisionTree object we want to merge.
 
1785
        :param target_subdir: The relative path where we want to merge
 
1786
            other_tree into this_tree
 
1787
        :param source_subpath: The relative path specifying the subtree of
 
1788
            other_tree to merge into this_tree.
 
1789
        """
 
1790
        # It is assumed that we are merging a tree that is not in our current
 
1791
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1792
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1793
                                _mod_revision.NULL_REVISION)
 
1794
        super(MergeIntoMerger, self).__init__(
 
1795
            this_branch=this_tree.branch,
 
1796
            this_tree=this_tree,
 
1797
            other_tree=other_tree,
 
1798
            base_tree=null_ancestor_tree,
 
1799
            )
 
1800
        self._target_subdir = target_subdir
 
1801
        self._source_subpath = source_subpath
 
1802
        self.other_branch = other_branch
 
1803
        if other_rev_id is None:
 
1804
            other_rev_id = other_tree.get_revision_id()
 
1805
        self.other_rev_id = self.other_basis = other_rev_id
 
1806
        self.base_is_ancestor = True
 
1807
        self.backup_files = True
 
1808
        self.merge_type = Merge3Merger
 
1809
        self.show_base = False
 
1810
        self.reprocess = False
 
1811
        self.interesting_ids = None
 
1812
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1813
              target_subdir=self._target_subdir,
 
1814
              source_subpath=self._source_subpath)
 
1815
        if self._source_subpath != '':
 
1816
            # If this isn't a partial merge make sure the revisions will be
 
1817
            # present.
 
1818
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1819
                self.other_basis)
 
1820
 
 
1821
    def set_pending(self):
 
1822
        if self._source_subpath != '':
 
1823
            return
 
1824
        Merger.set_pending(self)
 
1825
 
 
1826
 
 
1827
class _MergeTypeParameterizer(object):
 
1828
    """Wrap a merge-type class to provide extra parameters.
 
1829
    
 
1830
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1831
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1832
    the 'merge_type' member.  It is difficult override do_merge without
 
1833
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1834
    the extra parameters.
 
1835
    """
 
1836
 
 
1837
    def __init__(self, merge_type, **kwargs):
 
1838
        self._extra_kwargs = kwargs
 
1839
        self._merge_type = merge_type
 
1840
 
 
1841
    def __call__(self, *args, **kwargs):
 
1842
        kwargs.update(self._extra_kwargs)
 
1843
        return self._merge_type(*args, **kwargs)
 
1844
 
 
1845
    def __getattr__(self, name):
 
1846
        return getattr(self._merge_type, name)
 
1847
 
 
1848
 
 
1849
class MergeIntoMergeType(Merge3Merger):
 
1850
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1851
 
 
1852
    def __init__(self, *args, **kwargs):
 
1853
        """Initialize the merger object.
 
1854
 
 
1855
        :param args: See Merge3Merger.__init__'s args.
 
1856
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1857
            source_subpath and target_subdir.
 
1858
        :keyword source_subpath: The relative path specifying the subtree of
 
1859
            other_tree to merge into this_tree.
 
1860
        :keyword target_subdir: The relative path where we want to merge
 
1861
            other_tree into this_tree
 
1862
        """
 
1863
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1864
        # so we have have to hack in to get our extra parameters set.
 
1865
        self._source_subpath = kwargs.pop('source_subpath')
 
1866
        self._target_subdir = kwargs.pop('target_subdir')
 
1867
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1868
 
 
1869
    def _compute_transform(self):
 
1870
        child_pb = ui.ui_factory.nested_progress_bar()
 
1871
        try:
 
1872
            entries = self._entries_to_incorporate()
 
1873
            entries = list(entries)
 
1874
            for num, (entry, parent_id) in enumerate(entries):
 
1875
                child_pb.update('Preparing file merge', num, len(entries))
 
1876
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1877
                trans_id = transform.new_by_entry(self.tt, entry,
 
1878
                    parent_trans_id, self.other_tree)
 
1879
        finally:
 
1880
            child_pb.finished()
 
1881
        self._finish_computing_transform()
 
1882
 
 
1883
    def _entries_to_incorporate(self):
 
1884
        """Yields pairs of (inventory_entry, new_parent)."""
 
1885
        other_inv = self.other_tree.inventory
 
1886
        subdir_id = other_inv.path2id(self._source_subpath)
 
1887
        if subdir_id is None:
 
1888
            # XXX: The error would be clearer if it gave the URL of the source
 
1889
            # branch, but we don't have a reference to that here.
 
1890
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1891
        subdir = other_inv[subdir_id]
 
1892
        parent_in_target = osutils.dirname(self._target_subdir)
 
1893
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1894
        if target_id is None:
 
1895
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1896
        name_in_target = osutils.basename(self._target_subdir)
 
1897
        merge_into_root = subdir.copy()
 
1898
        merge_into_root.name = name_in_target
 
1899
        if merge_into_root.file_id in self.this_tree.inventory:
 
1900
            # Give the root a new file-id.
 
1901
            # This can happen fairly easily if the directory we are
 
1902
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1903
            # their root_id.  Users will expect this to Just Work, so we
 
1904
            # change the file-id here.
 
1905
            # Non-root file-ids could potentially conflict too.  That's really
 
1906
            # an edge case, so we don't do anything special for those.  We let
 
1907
            # them cause conflicts.
 
1908
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1909
        yield (merge_into_root, target_id)
 
1910
        if subdir.kind != 'directory':
 
1911
            # No children, so we are done.
 
1912
            return
 
1913
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1914
            parent_id = entry.parent_id
 
1915
            if parent_id == subdir.file_id:
 
1916
                # The root's parent ID has changed, so make sure children of
 
1917
                # the root refer to the new ID.
 
1918
                parent_id = merge_into_root.file_id
 
1919
            yield (entry, parent_id)
 
1920
 
 
1921
 
1710
1922
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1711
1923
                backup_files=False,
1712
1924
                merge_type=Merge3Merger,
1716
1928
                other_rev_id=None,
1717
1929
                interesting_files=None,
1718
1930
                this_tree=None,
1719
 
                pb=progress.DummyProgress(),
 
1931
                pb=None,
1720
1932
                change_reporter=None):
1721
1933
    """Primary interface for merging.
1722
1934
 
1723
 
        typical use is probably
1724
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1725
 
                     branch.get_revision_tree(base_revision))'
1726
 
        """
 
1935
    Typical use is probably::
 
1936
 
 
1937
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1938
                    branch.get_revision_tree(base_revision))
 
1939
    """
1727
1940
    if this_tree is None:
1728
1941
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1729
1942
                              "parameter as of bzrlib version 0.8.")