~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: John Arbash Meinel
  • Date: 2010-02-17 17:11:16 UTC
  • mfrom: (4797.2.17 2.1)
  • mto: (4797.2.18 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: john@arbash-meinel.com-20100217171116-h7t9223ystbnx5h8
merge bzr.2.1 in preparation for NEWS entry.

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
18
17
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
18
from bzrlib import (
22
19
    branch as _mod_branch,
23
20
    conflicts as _mod_conflicts,
24
21
    debug,
25
 
    generate_ids,
 
22
    decorators,
 
23
    errors,
26
24
    graph as _mod_graph,
 
25
    hooks,
27
26
    merge3,
28
27
    osutils,
29
28
    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,
38
 
    workingtree,
 
37
    versionedfile
39
38
    )
40
39
from bzrlib.cleanup import OperationWithCleanups
41
 
""")
42
 
from bzrlib import (
43
 
    decorators,
44
 
    errors,
45
 
    hooks,
46
 
    )
47
40
from bzrlib.symbol_versioning import (
48
41
    deprecated_in,
49
42
    deprecated_method,
100
93
        return ('not applicable', None)
101
94
 
102
95
 
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):
 
96
class ConfigurableFileMerger(AbstractPerFileMerger):
157
97
    """Merge individual files when configured via a .conf file.
158
98
 
159
99
    This is a base class for concrete custom file merging logic. Concrete
182
122
        if self.name_prefix is None:
183
123
            raise ValueError("name_prefix must be set.")
184
124
 
185
 
    def file_matches(self, params):
 
125
    def filename_matches_config(self, params):
186
126
        """Check whether the file should call the merge hook.
187
127
 
188
128
        <name_prefix>_merge_files configuration variable is a list of files
190
130
        """
191
131
        affected_files = self.affected_files
192
132
        if affected_files is None:
193
 
            config = self.merger.this_branch.get_config()
 
133
            config = self.merger.this_tree.branch.get_config()
194
134
            # Until bzr provides a better policy for caching the config, we
195
135
            # just add the part we're interested in to the params to avoid
196
136
            # reading the config files repeatedly (bazaar.conf, location.conf,
202
142
                affected_files = self.default_files
203
143
            self.affected_files = affected_files
204
144
        if affected_files:
205
 
            filepath = self.get_filepath(params, self.merger.this_tree)
206
 
            if filepath in affected_files:
 
145
            filename = self.merger.this_tree.id2path(params.file_id)
 
146
            if filename in affected_files:
207
147
                return True
208
148
        return False
209
149
 
210
 
    def merge_matching(self, params):
 
150
    def merge_contents(self, params):
 
151
        """Merge the contents of a single file."""
 
152
        # First, check whether this custom merge logic should be used.  We
 
153
        # expect most files should not be merged by this handler.
 
154
        if (
 
155
            # OTHER is a straight winner, rely on default merge.
 
156
            params.winner == 'other' or
 
157
            # THIS and OTHER aren't both files.
 
158
            not params.is_file_merge() or
 
159
            # The filename isn't listed in the 'NAME_merge_files' config
 
160
            # option.
 
161
            not self.filename_matches_config(params)):
 
162
            return 'not_applicable', None
211
163
        return self.merge_text(params)
212
164
 
213
165
    def merge_text(self, params):
285
237
        self.interesting_files = None
286
238
        self.show_base = False
287
239
        self.reprocess = False
288
 
        if pb is not None:
289
 
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
 
240
        if pb is None:
 
241
            pb = progress.DummyProgress()
 
242
        self._pb = pb
290
243
        self.pp = None
291
244
        self.recurse = recurse
292
245
        self.change_reporter = change_reporter
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]
640
594
                  'other_tree': self.other_tree,
641
595
                  'interesting_ids': self.interesting_ids,
642
596
                  'interesting_files': self.interesting_files,
643
 
                  'this_branch': self.this_branch,
 
597
                  'pp': self.pp, 'this_branch': self.this_branch,
644
598
                  'do_merge': False}
645
599
        if self.merge_type.requires_base:
646
600
            kwargs['base_tree'] = self.base_tree
664
618
        if self._is_criss_cross and getattr(self.merge_type,
665
619
                                            'supports_lca_trees', False):
666
620
            kwargs['lca_trees'] = self._lca_trees
667
 
        return self.merge_type(pb=None,
 
621
        return self.merge_type(pb=self._pb,
668
622
                               change_reporter=self.change_reporter,
669
623
                               **kwargs)
670
624
 
742
696
 
743
697
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
744
698
                 interesting_ids=None, reprocess=False, show_base=False,
745
 
                 pb=None, pp=None, change_reporter=None,
 
699
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
746
700
                 interesting_files=None, do_merge=True,
747
701
                 cherrypick=False, lca_trees=None, this_branch=None):
748
702
        """Initialize the merger object and perform the merge.
751
705
        :param this_tree: The local tree in the merge operation
752
706
        :param base_tree: The common tree in the merge operation
753
707
        :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.
 
708
        :param this_branch: The branch associated with this_tree
756
709
        :param interesting_ids: The file_ids of files that should be
757
710
            participate in the merge.  May not be combined with
758
711
            interesting_files.
759
712
        :param: reprocess If True, perform conflict-reduction processing.
760
713
        :param show_base: If True, show the base revision in text conflicts.
761
714
            (incompatible with reprocess)
762
 
        :param pb: ignored
 
715
        :param pb: A Progress bar
763
716
        :param pp: A ProgressPhase object
764
717
        :param change_reporter: An object that should report changes made
765
718
        :param interesting_files: The tree-relative paths of files that should
776
729
        if interesting_files is not None and interesting_ids is not None:
777
730
            raise ValueError(
778
731
                'specify either interesting_ids or interesting_files')
779
 
        if this_branch is None:
780
 
            this_branch = this_tree.branch
781
732
        self.interesting_ids = interesting_ids
782
733
        self.interesting_files = interesting_files
783
734
        self.this_tree = working_tree
794
745
        # making sure we haven't missed any corner cases.
795
746
        # if lca_trees is None:
796
747
        #     self._lca_trees = [self.base_tree]
 
748
        self.pb = pb
 
749
        self.pp = pp
797
750
        self.change_reporter = change_reporter
798
751
        self.cherrypick = cherrypick
 
752
        if self.pp is None:
 
753
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
799
754
        if do_merge:
800
755
            self.do_merge()
801
 
        if pp is not None:
802
 
            warnings.warn("pp argument to Merge3Merger is deprecated")
803
 
        if pb is not None:
804
 
            warnings.warn("pb argument to Merge3Merger is deprecated")
805
756
 
806
757
    def do_merge(self):
807
758
        operation = OperationWithCleanups(self._do_merge)
 
759
        operation.add_cleanup(self.pb.clear)
808
760
        self.this_tree.lock_tree_write()
809
761
        operation.add_cleanup(self.this_tree.unlock)
810
762
        self.base_tree.lock_read()
814
766
        operation.run()
815
767
 
816
768
    def _do_merge(self, operation):
817
 
        self.tt = transform.TreeTransform(self.this_tree, None)
 
769
        self.tt = transform.TreeTransform(self.this_tree, self.pb)
818
770
        operation.add_cleanup(self.tt.finalize)
 
771
        self.pp.next_phase()
819
772
        self._compute_transform()
 
773
        self.pp.next_phase()
820
774
        results = self.tt.apply(no_conflicts=True)
821
775
        self.write_modified(results)
822
776
        try:
826
780
 
827
781
    def make_preview_transform(self):
828
782
        operation = OperationWithCleanups(self._make_preview_transform)
 
783
        operation.add_cleanup(self.pb.clear)
829
784
        self.base_tree.lock_read()
830
785
        operation.add_cleanup(self.base_tree.unlock)
831
786
        self.other_tree.lock_read()
834
789
 
835
790
    def _make_preview_transform(self):
836
791
        self.tt = transform.TransformPreview(self.this_tree)
 
792
        self.pp.next_phase()
837
793
        self._compute_transform()
 
794
        self.pp.next_phase()
838
795
        return self.tt
839
796
 
840
797
    def _compute_transform(self):
862
819
        finally:
863
820
            child_pb.finished()
864
821
        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
 
        """
 
822
        self.pp.next_phase()
872
823
        child_pb = ui.ui_factory.nested_progress_bar()
873
824
        try:
874
825
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1072
1023
                        continue
1073
1024
                else:
1074
1025
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1026
                # XXX: We need to handle kind == 'symlink'
1075
1027
 
1076
1028
            # If we have gotten this far, that means something has changed
1077
1029
            result.append((file_id, content_changed,
1084
1036
                          ))
1085
1037
        return result
1086
1038
 
 
1039
 
1087
1040
    def fix_root(self):
1088
1041
        try:
1089
1042
            self.tt.final_kind(self.tt.root)
1098
1051
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1099
1052
        if other_root == self.tt.root:
1100
1053
            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
1054
        try:
1106
1055
            self.tt.final_kind(other_root)
1107
 
            other_root_is_present = True
1108
1056
        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():
 
1057
            return
 
1058
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
1059
            # the other tree's root is a non-root in the current tree
 
1060
            return
 
1061
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
1062
        self.tt.cancel_creation(other_root)
 
1063
        self.tt.cancel_versioning(other_root)
 
1064
 
 
1065
    def reparent_children(self, ie, target):
 
1066
        for thing, child in ie.children.iteritems():
1116
1067
            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)
 
1068
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1133
1069
 
1134
1070
    def write_modified(self, results):
1135
1071
        modified_hashes = {}
1182
1118
 
1183
1119
    @staticmethod
1184
1120
    def _three_way(base, other, this):
 
1121
        #if base == other, either they all agree, or only THIS has changed.
1185
1122
        if base == other:
1186
 
            # if 'base == other', either they all agree, or only 'this' has
1187
 
            # changed.
1188
1123
            return 'this'
1189
1124
        elif this not in (base, other):
1190
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1191
1125
            return 'conflict'
 
1126
        # "Ambiguous clean merge" -- both sides have made the same change.
1192
1127
        elif this == other:
1193
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1194
1128
            return "this"
 
1129
        # this == base: only other has changed.
1195
1130
        else:
1196
 
            # this == base: only other has changed.
1197
1131
            return "other"
1198
1132
 
1199
1133
    @staticmethod
1243
1177
                # only has an lca value
1244
1178
                return 'other'
1245
1179
 
1246
 
        # At this point, the lcas disagree, and the tip disagree
 
1180
        # At this point, the lcas disagree, and the tips disagree
1247
1181
        return 'conflict'
1248
1182
 
1249
1183
    @staticmethod
1250
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1251
1184
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1252
1185
        """Do a three-way test on a scalar.
1253
1186
        Return "this", "other" or "conflict", depending whether a value wins.
1303
1236
                parent_id_winner = "other"
1304
1237
        if name_winner == "this" and parent_id_winner == "this":
1305
1238
            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))
 
1239
        if name_winner == "conflict":
 
1240
            trans_id = self.tt.trans_id_file_id(file_id)
 
1241
            self._raw_conflicts.append(('name conflict', trans_id,
 
1242
                                        this_name, other_name))
 
1243
        if parent_id_winner == "conflict":
 
1244
            trans_id = self.tt.trans_id_file_id(file_id)
 
1245
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1246
                                        this_parent, other_parent))
1314
1247
        if other_name is None:
1315
1248
            # it doesn't matter whether the result was 'other' or
1316
1249
            # 'conflict'-- if there's no 'other', we leave it alone.
1317
1250
            return
 
1251
        # if we get here, name_winner and parent_winner are set to safe values.
 
1252
        trans_id = self.tt.trans_id_file_id(file_id)
1318
1253
        parent_id = parents[self.winner_idx[parent_id_winner]]
1319
1254
        if parent_id is not None:
1320
 
            # if we get here, name_winner and parent_winner are set to safe
1321
 
            # values.
 
1255
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1322
1256
            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))
 
1257
                                parent_trans_id, trans_id)
1325
1258
 
1326
1259
    def _do_merge_contents(self, file_id):
1327
1260
        """Performs a merge on file_id contents."""
1467
1400
    def get_lines(self, tree, file_id):
1468
1401
        """Return the lines in a file, or an empty list."""
1469
1402
        if tree.has_id(file_id):
1470
 
            return tree.get_file_lines(file_id)
 
1403
            return tree.get_file(file_id).readlines()
1471
1404
        else:
1472
1405
            return []
1473
1406
 
1606
1539
 
1607
1540
    def cook_conflicts(self, fs_conflicts):
1608
1541
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1542
        name_conflicts = {}
1609
1543
        self.cooked_conflicts.extend(transform.cook_conflicts(
1610
1544
                fs_conflicts, self.tt))
1611
1545
        fp = transform.FinalPaths(self.tt)
1612
1546
        for conflict in self._raw_conflicts:
1613
1547
            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':
 
1548
            if conflict_type in ('name conflict', 'parent conflict'):
 
1549
                trans_id = conflict[1]
 
1550
                conflict_args = conflict[2:]
 
1551
                if trans_id not in name_conflicts:
 
1552
                    name_conflicts[trans_id] = {}
 
1553
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1554
                                     conflict_args)
 
1555
            if conflict_type == 'contents conflict':
1635
1556
                for trans_id in conflict[1]:
1636
1557
                    file_id = self.tt.final_file_id(trans_id)
1637
1558
                    if file_id is not None:
1643
1564
                        break
1644
1565
                c = _mod_conflicts.Conflict.factory(conflict_type,
1645
1566
                                                    path=path, file_id=file_id)
1646
 
            elif conflict_type == 'text conflict':
 
1567
                self.cooked_conflicts.append(c)
 
1568
            if conflict_type == 'text conflict':
1647
1569
                trans_id = conflict[1]
1648
1570
                path = fp.get_path(trans_id)
1649
1571
                file_id = self.tt.final_file_id(trans_id)
1650
1572
                c = _mod_conflicts.Conflict.factory(conflict_type,
1651
1573
                                                    path=path, file_id=file_id)
 
1574
                self.cooked_conflicts.append(c)
 
1575
 
 
1576
        for trans_id, conflicts in name_conflicts.iteritems():
 
1577
            try:
 
1578
                this_parent, other_parent = conflicts['parent conflict']
 
1579
                if this_parent == other_parent:
 
1580
                    raise AssertionError()
 
1581
            except KeyError:
 
1582
                this_parent = other_parent = \
 
1583
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1584
            try:
 
1585
                this_name, other_name = conflicts['name conflict']
 
1586
                if this_name == other_name:
 
1587
                    raise AssertionError()
 
1588
            except KeyError:
 
1589
                this_name = other_name = self.tt.final_name(trans_id)
 
1590
            other_path = fp.get_path(trans_id)
 
1591
            if this_parent is not None and this_name is not None:
 
1592
                this_parent_path = \
 
1593
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1594
                this_path = osutils.pathjoin(this_parent_path, this_name)
1652
1595
            else:
1653
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1596
                this_path = "<deleted>"
 
1597
            file_id = self.tt.final_file_id(trans_id)
 
1598
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1599
                                                conflict_path=other_path,
 
1600
                                                file_id=file_id)
1654
1601
            self.cooked_conflicts.append(c)
1655
1602
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1656
1603
 
1762
1709
            osutils.rmtree(temp_dir)
1763
1710
 
1764
1711
 
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
1712
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1928
1713
                backup_files=False,
1929
1714
                merge_type=Merge3Merger,
1933
1718
                other_rev_id=None,
1934
1719
                interesting_files=None,
1935
1720
                this_tree=None,
1936
 
                pb=None,
 
1721
                pb=progress.DummyProgress(),
1937
1722
                change_reporter=None):
1938
1723
    """Primary interface for merging.
1939
1724
 
1940
 
    Typical use is probably::
1941
 
 
1942
 
        merge_inner(branch, branch.get_revision_tree(other_revision),
1943
 
                    branch.get_revision_tree(base_revision))
1944
 
    """
 
1725
        typical use is probably
 
1726
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
1727
                     branch.get_revision_tree(base_revision))'
 
1728
        """
1945
1729
    if this_tree is None:
1946
1730
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1947
1731
                              "parameter as of bzrlib version 0.8.")