~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-02-11 04:02:41 UTC
  • mfrom: (5017.2.2 tariff)
  • Revision ID: pqm@pqm.ubuntu.com-20100211040241-w6n021dz0uus341n
(mbp) add import-tariff tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import warnings
18
18
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
19
from bzrlib import (
22
20
    branch as _mod_branch,
23
21
    conflicts as _mod_conflicts,
24
22
    debug,
25
 
    generate_ids,
 
23
    decorators,
 
24
    errors,
26
25
    graph as _mod_graph,
 
26
    hooks,
27
27
    merge3,
28
28
    osutils,
29
29
    patiencediff,
 
30
    progress,
30
31
    revision as _mod_revision,
31
32
    textfile,
32
33
    trace,
34
35
    tree as _mod_tree,
35
36
    tsort,
36
37
    ui,
37
 
    versionedfile,
38
 
    workingtree,
39
 
    )
40
 
from bzrlib.cleanup import OperationWithCleanups
41
 
""")
42
 
from bzrlib import (
43
 
    decorators,
44
 
    errors,
45
 
    hooks,
 
38
    versionedfile
46
39
    )
47
40
from bzrlib.symbol_versioning import (
48
41
    deprecated_in,
53
46
 
54
47
def transform_tree(from_tree, to_tree, interesting_ids=None):
55
48
    from_tree.lock_tree_write()
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)
 
49
    try:
 
50
        merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
 
51
                    interesting_ids=interesting_ids, this_tree=from_tree)
 
52
    finally:
 
53
        from_tree.unlock()
60
54
 
61
55
 
62
56
class MergeHooks(hooks.Hooks):
100
94
        return ('not applicable', None)
101
95
 
102
96
 
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
class ConfigurableFileMerger(AbstractPerFileMerger):
157
98
    """Merge individual files when configured via a .conf file.
158
99
 
159
100
    This is a base class for concrete custom file merging logic. Concrete
182
123
        if self.name_prefix is None:
183
124
            raise ValueError("name_prefix must be set.")
184
125
 
185
 
    def file_matches(self, params):
 
126
    def filename_matches_config(self, params):
186
127
        """Check whether the file should call the merge hook.
187
128
 
188
129
        <name_prefix>_merge_files configuration variable is a list of files
190
131
        """
191
132
        affected_files = self.affected_files
192
133
        if affected_files is None:
193
 
            config = self.merger.this_branch.get_config()
 
134
            config = self.merger.this_tree.branch.get_config()
194
135
            # Until bzr provides a better policy for caching the config, we
195
136
            # just add the part we're interested in to the params to avoid
196
137
            # reading the config files repeatedly (bazaar.conf, location.conf,
202
143
                affected_files = self.default_files
203
144
            self.affected_files = affected_files
204
145
        if affected_files:
205
 
            filepath = self.get_filepath(params, self.merger.this_tree)
206
 
            if filepath in affected_files:
 
146
            filename = self.merger.this_tree.id2path(params.file_id)
 
147
            if filename in affected_files:
207
148
                return True
208
149
        return False
209
150
 
210
 
    def merge_matching(self, params):
 
151
    def merge_contents(self, params):
 
152
        """Merge the contents of a single file."""
 
153
        # First, check whether this custom merge logic should be used.  We
 
154
        # expect most files should not be merged by this handler.
 
155
        if (
 
156
            # OTHER is a straight winner, rely on default merge.
 
157
            params.winner == 'other' or
 
158
            # THIS and OTHER aren't both files.
 
159
            not params.is_file_merge() or
 
160
            # The filename isn't listed in the 'NAME_merge_files' config
 
161
            # option.
 
162
            not self.filename_matches_config(params)):
 
163
            return 'not_applicable', None
211
164
        return self.merge_text(params)
212
165
 
213
166
    def merge_text(self, params):
428
381
        return self._cached_trees[revision_id]
429
382
 
430
383
    def _get_tree(self, treespec, possible_transports=None):
 
384
        from bzrlib import workingtree
431
385
        location, revno = treespec
432
386
        if revno is None:
433
387
            tree = workingtree.WorkingTree.open_containing(location)[0]
501
455
    def _add_parent(self):
502
456
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
503
457
        new_parent_trees = []
504
 
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
505
458
        for revision_id in new_parents:
506
459
            try:
507
460
                tree = self.revision_tree(revision_id)
509
462
                tree = None
510
463
            else:
511
464
                tree.lock_read()
512
 
                operation.add_cleanup(tree.unlock)
513
465
            new_parent_trees.append((revision_id, tree))
514
 
        operation.run_simple(new_parent_trees, allow_leftmost_as_ghost=True)
 
466
        try:
 
467
            self.this_tree.set_parent_trees(new_parent_trees,
 
468
                                            allow_leftmost_as_ghost=True)
 
469
        finally:
 
470
            for _revision_id, tree in new_parent_trees:
 
471
                if tree is not None:
 
472
                    tree.unlock()
515
473
 
516
474
    def set_other(self, other_revision, possible_transports=None):
517
475
        """Set the revision and tree to merge from.
668
626
                               change_reporter=self.change_reporter,
669
627
                               **kwargs)
670
628
 
671
 
    def _do_merge_to(self):
672
 
        merge = self.make_merger()
 
629
    def _do_merge_to(self, merge):
673
630
        if self.other_branch is not None:
674
631
            self.other_branch.update_references(self.this_branch)
675
632
        merge.do_merge()
689
646
                    sub_tree.branch.repository.revision_tree(base_revision)
690
647
                sub_merge.base_rev_id = base_revision
691
648
                sub_merge.do_merge()
692
 
        return merge
693
649
 
694
650
    def do_merge(self):
695
 
        operation = OperationWithCleanups(self._do_merge_to)
696
651
        self.this_tree.lock_tree_write()
697
 
        operation.add_cleanup(self.this_tree.unlock)
698
 
        if self.base_tree is not None:
699
 
            self.base_tree.lock_read()
700
 
            operation.add_cleanup(self.base_tree.unlock)
701
 
        if self.other_tree is not None:
702
 
            self.other_tree.lock_read()
703
 
            operation.add_cleanup(self.other_tree.unlock)
704
 
        merge = operation.run_simple()
 
652
        try:
 
653
            if self.base_tree is not None:
 
654
                self.base_tree.lock_read()
 
655
            try:
 
656
                if self.other_tree is not None:
 
657
                    self.other_tree.lock_read()
 
658
                try:
 
659
                    merge = self.make_merger()
 
660
                    self._do_merge_to(merge)
 
661
                finally:
 
662
                    if self.other_tree is not None:
 
663
                        self.other_tree.unlock()
 
664
            finally:
 
665
                if self.base_tree is not None:
 
666
                    self.base_tree.unlock()
 
667
        finally:
 
668
            self.this_tree.unlock()
705
669
        if len(merge.cooked_conflicts) == 0:
706
670
            if not self.ignore_zero and not trace.is_quiet():
707
671
                trace.note("All changes applied successfully.")
751
715
        :param this_tree: The local tree in the merge operation
752
716
        :param base_tree: The common tree in the merge operation
753
717
        :param other_tree: The other tree to merge changes from
754
 
        :param this_branch: The branch associated with this_tree.  Defaults to
755
 
            this_tree.branch if not supplied.
 
718
        :param this_branch: The branch associated with this_tree
756
719
        :param interesting_ids: The file_ids of files that should be
757
720
            participate in the merge.  May not be combined with
758
721
            interesting_files.
776
739
        if interesting_files is not None and interesting_ids is not None:
777
740
            raise ValueError(
778
741
                'specify either interesting_ids or interesting_files')
779
 
        if this_branch is None:
780
 
            this_branch = this_tree.branch
781
742
        self.interesting_ids = interesting_ids
782
743
        self.interesting_files = interesting_files
783
744
        self.this_tree = working_tree
804
765
            warnings.warn("pb argument to Merge3Merger is deprecated")
805
766
 
806
767
    def do_merge(self):
807
 
        operation = OperationWithCleanups(self._do_merge)
808
768
        self.this_tree.lock_tree_write()
809
 
        operation.add_cleanup(self.this_tree.unlock)
810
769
        self.base_tree.lock_read()
811
 
        operation.add_cleanup(self.base_tree.unlock)
812
770
        self.other_tree.lock_read()
813
 
        operation.add_cleanup(self.other_tree.unlock)
814
 
        operation.run()
815
 
 
816
 
    def _do_merge(self, operation):
817
 
        self.tt = transform.TreeTransform(self.this_tree, None)
818
 
        operation.add_cleanup(self.tt.finalize)
819
 
        self._compute_transform()
820
 
        results = self.tt.apply(no_conflicts=True)
821
 
        self.write_modified(results)
822
771
        try:
823
 
            self.this_tree.add_conflicts(self.cooked_conflicts)
824
 
        except errors.UnsupportedOperation:
825
 
            pass
 
772
            self.tt = transform.TreeTransform(self.this_tree, None)
 
773
            try:
 
774
                self._compute_transform()
 
775
                results = self.tt.apply(no_conflicts=True)
 
776
                self.write_modified(results)
 
777
                try:
 
778
                    self.this_tree.add_conflicts(self.cooked_conflicts)
 
779
                except errors.UnsupportedOperation:
 
780
                    pass
 
781
            finally:
 
782
                self.tt.finalize()
 
783
        finally:
 
784
            self.other_tree.unlock()
 
785
            self.base_tree.unlock()
 
786
            self.this_tree.unlock()
826
787
 
827
788
    def make_preview_transform(self):
828
 
        operation = OperationWithCleanups(self._make_preview_transform)
829
789
        self.base_tree.lock_read()
830
 
        operation.add_cleanup(self.base_tree.unlock)
831
790
        self.other_tree.lock_read()
832
 
        operation.add_cleanup(self.other_tree.unlock)
833
 
        return operation.run_simple()
834
 
 
835
 
    def _make_preview_transform(self):
836
791
        self.tt = transform.TransformPreview(self.this_tree)
837
 
        self._compute_transform()
 
792
        try:
 
793
            self._compute_transform()
 
794
        finally:
 
795
            self.other_tree.unlock()
 
796
            self.base_tree.unlock()
838
797
        return self.tt
839
798
 
840
799
    def _compute_transform(self):
862
821
        finally:
863
822
            child_pb.finished()
864
823
        self.fix_root()
865
 
        self._finish_computing_transform()
866
 
 
867
 
    def _finish_computing_transform(self):
868
 
        """Finalize the transform and report the changes.
869
 
 
870
 
        This is the second half of _compute_transform.
871
 
        """
872
824
        child_pb = ui.ui_factory.nested_progress_bar()
873
825
        try:
874
826
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1072
1024
                        continue
1073
1025
                else:
1074
1026
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1027
                # XXX: We need to handle kind == 'symlink'
1075
1028
 
1076
1029
            # If we have gotten this far, that means something has changed
1077
1030
            result.append((file_id, content_changed,
1084
1037
                          ))
1085
1038
        return result
1086
1039
 
 
1040
 
1087
1041
    def fix_root(self):
1088
1042
        try:
1089
1043
            self.tt.final_kind(self.tt.root)
1098
1052
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1099
1053
        if other_root == self.tt.root:
1100
1054
            return
1101
 
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
1102
 
            # the other tree's root is a non-root in the current tree (as when
1103
 
            # a previously unrelated branch is merged into another)
1104
 
            return
1105
1055
        try:
1106
1056
            self.tt.final_kind(other_root)
1107
 
            other_root_is_present = True
1108
1057
        except errors.NoSuchFile:
1109
 
            # other_root doesn't have a physical representation. We still need
1110
 
            # to move any references to the actual root of the tree.
1111
 
            other_root_is_present = False
1112
 
        # 'other_tree.inventory.root' is not present in this tree. We are
1113
 
        # calling adjust_path for children which *want* to be present with a
1114
 
        # correct place to go.
1115
 
        for thing, child in self.other_tree.inventory.root.children.iteritems():
 
1058
            return
 
1059
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
1060
            # the other tree's root is a non-root in the current tree
 
1061
            return
 
1062
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
1063
        self.tt.cancel_creation(other_root)
 
1064
        self.tt.cancel_versioning(other_root)
 
1065
 
 
1066
    def reparent_children(self, ie, target):
 
1067
        for thing, child in ie.children.iteritems():
1116
1068
            trans_id = self.tt.trans_id_file_id(child.file_id)
1117
 
            if not other_root_is_present:
1118
 
                # FIXME: Make final_kind returns None instead of raising
1119
 
                # NoSuchFile to avoid the ugly construct below -- vila 20100402
1120
 
                try:
1121
 
                    self.tt.final_kind(trans_id)
1122
 
                    # The item exist in the final tree and has a defined place
1123
 
                    # to go already.
1124
 
                    continue
1125
 
                except errors.NoSuchFile, e:
1126
 
                    pass
1127
 
            # Move the item into the root
1128
 
            self.tt.adjust_path(self.tt.final_name(trans_id),
1129
 
                                self.tt.root, trans_id)
1130
 
        if other_root_is_present:
1131
 
            self.tt.cancel_creation(other_root)
1132
 
            self.tt.cancel_versioning(other_root)
 
1069
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1133
1070
 
1134
1071
    def write_modified(self, results):
1135
1072
        modified_hashes = {}
1182
1119
 
1183
1120
    @staticmethod
1184
1121
    def _three_way(base, other, this):
 
1122
        #if base == other, either they all agree, or only THIS has changed.
1185
1123
        if base == other:
1186
 
            # if 'base == other', either they all agree, or only 'this' has
1187
 
            # changed.
1188
1124
            return 'this'
1189
1125
        elif this not in (base, other):
1190
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1191
1126
            return 'conflict'
 
1127
        # "Ambiguous clean merge" -- both sides have made the same change.
1192
1128
        elif this == other:
1193
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1194
1129
            return "this"
 
1130
        # this == base: only other has changed.
1195
1131
        else:
1196
 
            # this == base: only other has changed.
1197
1132
            return "other"
1198
1133
 
1199
1134
    @staticmethod
1243
1178
                # only has an lca value
1244
1179
                return 'other'
1245
1180
 
1246
 
        # At this point, the lcas disagree, and the tip disagree
 
1181
        # At this point, the lcas disagree, and the tips disagree
1247
1182
        return 'conflict'
1248
1183
 
1249
1184
    @staticmethod
1250
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1251
1185
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1252
1186
        """Do a three-way test on a scalar.
1253
1187
        Return "this", "other" or "conflict", depending whether a value wins.
1303
1237
                parent_id_winner = "other"
1304
1238
        if name_winner == "this" and parent_id_winner == "this":
1305
1239
            return
1306
 
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
1307
 
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1308
 
            # road if a ContentConflict needs to be created so we should not do
1309
 
            # that
1310
 
            trans_id = self.tt.trans_id_file_id(file_id)
1311
 
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1312
 
                                        this_parent, this_name,
1313
 
                                        other_parent, other_name))
 
1240
        if name_winner == "conflict":
 
1241
            trans_id = self.tt.trans_id_file_id(file_id)
 
1242
            self._raw_conflicts.append(('name conflict', trans_id,
 
1243
                                        this_name, other_name))
 
1244
        if parent_id_winner == "conflict":
 
1245
            trans_id = self.tt.trans_id_file_id(file_id)
 
1246
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1247
                                        this_parent, other_parent))
1314
1248
        if other_name is None:
1315
1249
            # it doesn't matter whether the result was 'other' or
1316
1250
            # 'conflict'-- if there's no 'other', we leave it alone.
1317
1251
            return
 
1252
        # if we get here, name_winner and parent_winner are set to safe values.
 
1253
        trans_id = self.tt.trans_id_file_id(file_id)
1318
1254
        parent_id = parents[self.winner_idx[parent_id_winner]]
1319
1255
        if parent_id is not None:
1320
 
            # if we get here, name_winner and parent_winner are set to safe
1321
 
            # values.
 
1256
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1322
1257
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1323
 
                                self.tt.trans_id_file_id(parent_id),
1324
 
                                self.tt.trans_id_file_id(file_id))
 
1258
                                parent_trans_id, trans_id)
1325
1259
 
1326
1260
    def _do_merge_contents(self, file_id):
1327
1261
        """Performs a merge on file_id contents."""
1467
1401
    def get_lines(self, tree, file_id):
1468
1402
        """Return the lines in a file, or an empty list."""
1469
1403
        if tree.has_id(file_id):
1470
 
            return tree.get_file_lines(file_id)
 
1404
            return tree.get_file(file_id).readlines()
1471
1405
        else:
1472
1406
            return []
1473
1407
 
1606
1540
 
1607
1541
    def cook_conflicts(self, fs_conflicts):
1608
1542
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1543
        name_conflicts = {}
1609
1544
        self.cooked_conflicts.extend(transform.cook_conflicts(
1610
1545
                fs_conflicts, self.tt))
1611
1546
        fp = transform.FinalPaths(self.tt)
1612
1547
        for conflict in self._raw_conflicts:
1613
1548
            conflict_type = conflict[0]
1614
 
            if conflict_type == 'path conflict':
1615
 
                (trans_id, file_id,
1616
 
                this_parent, this_name,
1617
 
                other_parent, other_name) = conflict[1:]
1618
 
                if this_parent is None or this_name is None:
1619
 
                    this_path = '<deleted>'
1620
 
                else:
1621
 
                    parent_path =  fp.get_path(
1622
 
                        self.tt.trans_id_file_id(this_parent))
1623
 
                    this_path = osutils.pathjoin(parent_path, this_name)
1624
 
                if other_parent is None or other_name is None:
1625
 
                    other_path = '<deleted>'
1626
 
                else:
1627
 
                    parent_path =  fp.get_path(
1628
 
                        self.tt.trans_id_file_id(other_parent))
1629
 
                    other_path = osutils.pathjoin(parent_path, other_name)
1630
 
                c = _mod_conflicts.Conflict.factory(
1631
 
                    'path conflict', path=this_path,
1632
 
                    conflict_path=other_path,
1633
 
                    file_id=file_id)
1634
 
            elif conflict_type == 'contents conflict':
 
1549
            if conflict_type in ('name conflict', 'parent conflict'):
 
1550
                trans_id = conflict[1]
 
1551
                conflict_args = conflict[2:]
 
1552
                if trans_id not in name_conflicts:
 
1553
                    name_conflicts[trans_id] = {}
 
1554
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1555
                                     conflict_args)
 
1556
            if conflict_type == 'contents conflict':
1635
1557
                for trans_id in conflict[1]:
1636
1558
                    file_id = self.tt.final_file_id(trans_id)
1637
1559
                    if file_id is not None:
1643
1565
                        break
1644
1566
                c = _mod_conflicts.Conflict.factory(conflict_type,
1645
1567
                                                    path=path, file_id=file_id)
1646
 
            elif conflict_type == 'text conflict':
 
1568
                self.cooked_conflicts.append(c)
 
1569
            if conflict_type == 'text conflict':
1647
1570
                trans_id = conflict[1]
1648
1571
                path = fp.get_path(trans_id)
1649
1572
                file_id = self.tt.final_file_id(trans_id)
1650
1573
                c = _mod_conflicts.Conflict.factory(conflict_type,
1651
1574
                                                    path=path, file_id=file_id)
 
1575
                self.cooked_conflicts.append(c)
 
1576
 
 
1577
        for trans_id, conflicts in name_conflicts.iteritems():
 
1578
            try:
 
1579
                this_parent, other_parent = conflicts['parent conflict']
 
1580
                if this_parent == other_parent:
 
1581
                    raise AssertionError()
 
1582
            except KeyError:
 
1583
                this_parent = other_parent = \
 
1584
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1585
            try:
 
1586
                this_name, other_name = conflicts['name conflict']
 
1587
                if this_name == other_name:
 
1588
                    raise AssertionError()
 
1589
            except KeyError:
 
1590
                this_name = other_name = self.tt.final_name(trans_id)
 
1591
            other_path = fp.get_path(trans_id)
 
1592
            if this_parent is not None and this_name is not None:
 
1593
                this_parent_path = \
 
1594
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1595
                this_path = osutils.pathjoin(this_parent_path, this_name)
1652
1596
            else:
1653
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1597
                this_path = "<deleted>"
 
1598
            file_id = self.tt.final_file_id(trans_id)
 
1599
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1600
                                                conflict_path=other_path,
 
1601
                                                file_id=file_id)
1654
1602
            self.cooked_conflicts.append(c)
1655
1603
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1656
1604
 
1762
1710
            osutils.rmtree(temp_dir)
1763
1711
 
1764
1712
 
1765
 
class PathNotInTree(errors.BzrError):
1766
 
 
1767
 
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
1768
 
 
1769
 
    def __init__(self, path, tree):
1770
 
        errors.BzrError.__init__(self, path=path, tree=tree)
1771
 
 
1772
 
 
1773
 
class MergeIntoMerger(Merger):
1774
 
    """Merger that understands other_tree will be merged into a subdir.
1775
 
 
1776
 
    This also changes the Merger api so that it uses real Branch, revision_id,
1777
 
    and RevisonTree objects, rather than using revision specs.
1778
 
    """
1779
 
 
1780
 
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
1781
 
            source_subpath, other_rev_id=None):
1782
 
        """Create a new MergeIntoMerger object.
1783
 
 
1784
 
        source_subpath in other_tree will be effectively copied to
1785
 
        target_subdir in this_tree.
1786
 
 
1787
 
        :param this_tree: The tree that we will be merging into.
1788
 
        :param other_branch: The Branch we will be merging from.
1789
 
        :param other_tree: The RevisionTree object we want to merge.
1790
 
        :param target_subdir: The relative path where we want to merge
1791
 
            other_tree into this_tree
1792
 
        :param source_subpath: The relative path specifying the subtree of
1793
 
            other_tree to merge into this_tree.
1794
 
        """
1795
 
        # It is assumed that we are merging a tree that is not in our current
1796
 
        # ancestry, which means we are using the "EmptyTree" as our basis.
1797
 
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
1798
 
                                _mod_revision.NULL_REVISION)
1799
 
        super(MergeIntoMerger, self).__init__(
1800
 
            this_branch=this_tree.branch,
1801
 
            this_tree=this_tree,
1802
 
            other_tree=other_tree,
1803
 
            base_tree=null_ancestor_tree,
1804
 
            )
1805
 
        self._target_subdir = target_subdir
1806
 
        self._source_subpath = source_subpath
1807
 
        self.other_branch = other_branch
1808
 
        if other_rev_id is None:
1809
 
            other_rev_id = other_tree.get_revision_id()
1810
 
        self.other_rev_id = self.other_basis = other_rev_id
1811
 
        self.base_is_ancestor = True
1812
 
        self.backup_files = True
1813
 
        self.merge_type = Merge3Merger
1814
 
        self.show_base = False
1815
 
        self.reprocess = False
1816
 
        self.interesting_ids = None
1817
 
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
1818
 
              target_subdir=self._target_subdir,
1819
 
              source_subpath=self._source_subpath)
1820
 
        if self._source_subpath != '':
1821
 
            # If this isn't a partial merge make sure the revisions will be
1822
 
            # present.
1823
 
            self._maybe_fetch(self.other_branch, self.this_branch,
1824
 
                self.other_basis)
1825
 
 
1826
 
    def set_pending(self):
1827
 
        if self._source_subpath != '':
1828
 
            return
1829
 
        Merger.set_pending(self)
1830
 
 
1831
 
 
1832
 
class _MergeTypeParameterizer(object):
1833
 
    """Wrap a merge-type class to provide extra parameters.
1834
 
    
1835
 
    This is hack used by MergeIntoMerger to pass some extra parameters to its
1836
 
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
1837
 
    the 'merge_type' member.  It is difficult override do_merge without
1838
 
    re-writing the whole thing, so instead we create a wrapper which will pass
1839
 
    the extra parameters.
1840
 
    """
1841
 
 
1842
 
    def __init__(self, merge_type, **kwargs):
1843
 
        self._extra_kwargs = kwargs
1844
 
        self._merge_type = merge_type
1845
 
 
1846
 
    def __call__(self, *args, **kwargs):
1847
 
        kwargs.update(self._extra_kwargs)
1848
 
        return self._merge_type(*args, **kwargs)
1849
 
 
1850
 
    def __getattr__(self, name):
1851
 
        return getattr(self._merge_type, name)
1852
 
 
1853
 
 
1854
 
class MergeIntoMergeType(Merge3Merger):
1855
 
    """Merger that incorporates a tree (or part of a tree) into another."""
1856
 
 
1857
 
    def __init__(self, *args, **kwargs):
1858
 
        """Initialize the merger object.
1859
 
 
1860
 
        :param args: See Merge3Merger.__init__'s args.
1861
 
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
1862
 
            source_subpath and target_subdir.
1863
 
        :keyword source_subpath: The relative path specifying the subtree of
1864
 
            other_tree to merge into this_tree.
1865
 
        :keyword target_subdir: The relative path where we want to merge
1866
 
            other_tree into this_tree
1867
 
        """
1868
 
        # All of the interesting work happens during Merge3Merger.__init__(),
1869
 
        # so we have have to hack in to get our extra parameters set.
1870
 
        self._source_subpath = kwargs.pop('source_subpath')
1871
 
        self._target_subdir = kwargs.pop('target_subdir')
1872
 
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
1873
 
 
1874
 
    def _compute_transform(self):
1875
 
        child_pb = ui.ui_factory.nested_progress_bar()
1876
 
        try:
1877
 
            entries = self._entries_to_incorporate()
1878
 
            entries = list(entries)
1879
 
            for num, (entry, parent_id) in enumerate(entries):
1880
 
                child_pb.update('Preparing file merge', num, len(entries))
1881
 
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
1882
 
                trans_id = transform.new_by_entry(self.tt, entry,
1883
 
                    parent_trans_id, self.other_tree)
1884
 
        finally:
1885
 
            child_pb.finished()
1886
 
        self._finish_computing_transform()
1887
 
 
1888
 
    def _entries_to_incorporate(self):
1889
 
        """Yields pairs of (inventory_entry, new_parent)."""
1890
 
        other_inv = self.other_tree.inventory
1891
 
        subdir_id = other_inv.path2id(self._source_subpath)
1892
 
        if subdir_id is None:
1893
 
            # XXX: The error would be clearer if it gave the URL of the source
1894
 
            # branch, but we don't have a reference to that here.
1895
 
            raise PathNotInTree(self._source_subpath, "Source tree")
1896
 
        subdir = other_inv[subdir_id]
1897
 
        parent_in_target = osutils.dirname(self._target_subdir)
1898
 
        target_id = self.this_tree.inventory.path2id(parent_in_target)
1899
 
        if target_id is None:
1900
 
            raise PathNotInTree(self._target_subdir, "Target tree")
1901
 
        name_in_target = osutils.basename(self._target_subdir)
1902
 
        merge_into_root = subdir.copy()
1903
 
        merge_into_root.name = name_in_target
1904
 
        if merge_into_root.file_id in self.this_tree.inventory:
1905
 
            # Give the root a new file-id.
1906
 
            # This can happen fairly easily if the directory we are
1907
 
            # incorporating is the root, and both trees have 'TREE_ROOT' as
1908
 
            # their root_id.  Users will expect this to Just Work, so we
1909
 
            # change the file-id here.
1910
 
            # Non-root file-ids could potentially conflict too.  That's really
1911
 
            # an edge case, so we don't do anything special for those.  We let
1912
 
            # them cause conflicts.
1913
 
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
1914
 
        yield (merge_into_root, target_id)
1915
 
        if subdir.kind != 'directory':
1916
 
            # No children, so we are done.
1917
 
            return
1918
 
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
1919
 
            parent_id = entry.parent_id
1920
 
            if parent_id == subdir.file_id:
1921
 
                # The root's parent ID has changed, so make sure children of
1922
 
                # the root refer to the new ID.
1923
 
                parent_id = merge_into_root.file_id
1924
 
            yield (entry, parent_id)
1925
 
 
1926
 
 
1927
1713
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1928
1714
                backup_files=False,
1929
1715
                merge_type=Merge3Merger,
1937
1723
                change_reporter=None):
1938
1724
    """Primary interface for merging.
1939
1725
 
1940
 
    Typical use is probably::
1941
 
 
1942
 
        merge_inner(branch, branch.get_revision_tree(other_revision),
1943
 
                    branch.get_revision_tree(base_revision))
1944
 
    """
 
1726
        typical use is probably
 
1727
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
1728
                     branch.get_revision_tree(base_revision))'
 
1729
        """
1945
1730
    if this_tree is None:
1946
1731
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1947
1732
                              "parameter as of bzrlib version 0.8.")