/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

  • Committer: Ian Clatworthy
  • Date: 2010-03-04 04:15:18 UTC
  • mto: (5094.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5096.
  • Revision ID: ian.clatworthy@canonical.com-20100304041518-vf8v1ivn8jrh9fs5
Initial cut at a What's New in 2.2 document

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,
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
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):
428
380
        return self._cached_trees[revision_id]
429
381
 
430
382
    def _get_tree(self, treespec, possible_transports=None):
 
383
        from bzrlib import workingtree
431
384
        location, revno = treespec
432
385
        if revno is None:
433
386
            tree = workingtree.WorkingTree.open_containing(location)[0]
751
704
        :param this_tree: The local tree in the merge operation
752
705
        :param base_tree: The common tree in the merge operation
753
706
        :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.
 
707
        :param this_branch: The branch associated with this_tree
756
708
        :param interesting_ids: The file_ids of files that should be
757
709
            participate in the merge.  May not be combined with
758
710
            interesting_files.
776
728
        if interesting_files is not None and interesting_ids is not None:
777
729
            raise ValueError(
778
730
                'specify either interesting_ids or interesting_files')
779
 
        if this_branch is None:
780
 
            this_branch = this_tree.branch
781
731
        self.interesting_ids = interesting_ids
782
732
        self.interesting_files = interesting_files
783
733
        self.this_tree = working_tree
862
812
        finally:
863
813
            child_pb.finished()
864
814
        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
815
        child_pb = ui.ui_factory.nested_progress_bar()
873
816
        try:
874
817
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1072
1015
                        continue
1073
1016
                else:
1074
1017
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1018
                # XXX: We need to handle kind == 'symlink'
1075
1019
 
1076
1020
            # If we have gotten this far, that means something has changed
1077
1021
            result.append((file_id, content_changed,
1084
1028
                          ))
1085
1029
        return result
1086
1030
 
 
1031
 
1087
1032
    def fix_root(self):
1088
1033
        try:
1089
1034
            self.tt.final_kind(self.tt.root)
1098
1043
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1099
1044
        if other_root == self.tt.root:
1100
1045
            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
1046
        try:
1106
1047
            self.tt.final_kind(other_root)
1107
 
            other_root_is_present = True
1108
1048
        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():
 
1049
            return
 
1050
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
 
1051
            # the other tree's root is a non-root in the current tree
 
1052
            return
 
1053
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
 
1054
        self.tt.cancel_creation(other_root)
 
1055
        self.tt.cancel_versioning(other_root)
 
1056
 
 
1057
    def reparent_children(self, ie, target):
 
1058
        for thing, child in ie.children.iteritems():
1116
1059
            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)
 
1060
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
1133
1061
 
1134
1062
    def write_modified(self, results):
1135
1063
        modified_hashes = {}
1182
1110
 
1183
1111
    @staticmethod
1184
1112
    def _three_way(base, other, this):
 
1113
        #if base == other, either they all agree, or only THIS has changed.
1185
1114
        if base == other:
1186
 
            # if 'base == other', either they all agree, or only 'this' has
1187
 
            # changed.
1188
1115
            return 'this'
1189
1116
        elif this not in (base, other):
1190
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1191
1117
            return 'conflict'
 
1118
        # "Ambiguous clean merge" -- both sides have made the same change.
1192
1119
        elif this == other:
1193
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1194
1120
            return "this"
 
1121
        # this == base: only other has changed.
1195
1122
        else:
1196
 
            # this == base: only other has changed.
1197
1123
            return "other"
1198
1124
 
1199
1125
    @staticmethod
1243
1169
                # only has an lca value
1244
1170
                return 'other'
1245
1171
 
1246
 
        # At this point, the lcas disagree, and the tip disagree
 
1172
        # At this point, the lcas disagree, and the tips disagree
1247
1173
        return 'conflict'
1248
1174
 
1249
1175
    @staticmethod
1250
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1251
1176
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1252
1177
        """Do a three-way test on a scalar.
1253
1178
        Return "this", "other" or "conflict", depending whether a value wins.
1303
1228
                parent_id_winner = "other"
1304
1229
        if name_winner == "this" and parent_id_winner == "this":
1305
1230
            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))
 
1231
        if name_winner == "conflict":
 
1232
            trans_id = self.tt.trans_id_file_id(file_id)
 
1233
            self._raw_conflicts.append(('name conflict', trans_id,
 
1234
                                        this_name, other_name))
 
1235
        if parent_id_winner == "conflict":
 
1236
            trans_id = self.tt.trans_id_file_id(file_id)
 
1237
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1238
                                        this_parent, other_parent))
1314
1239
        if other_name is None:
1315
1240
            # it doesn't matter whether the result was 'other' or
1316
1241
            # 'conflict'-- if there's no 'other', we leave it alone.
1317
1242
            return
 
1243
        # if we get here, name_winner and parent_winner are set to safe values.
 
1244
        trans_id = self.tt.trans_id_file_id(file_id)
1318
1245
        parent_id = parents[self.winner_idx[parent_id_winner]]
1319
1246
        if parent_id is not None:
1320
 
            # if we get here, name_winner and parent_winner are set to safe
1321
 
            # values.
 
1247
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1322
1248
            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))
 
1249
                                parent_trans_id, trans_id)
1325
1250
 
1326
1251
    def _do_merge_contents(self, file_id):
1327
1252
        """Performs a merge on file_id contents."""
1467
1392
    def get_lines(self, tree, file_id):
1468
1393
        """Return the lines in a file, or an empty list."""
1469
1394
        if tree.has_id(file_id):
1470
 
            return tree.get_file_lines(file_id)
 
1395
            return tree.get_file(file_id).readlines()
1471
1396
        else:
1472
1397
            return []
1473
1398
 
1606
1531
 
1607
1532
    def cook_conflicts(self, fs_conflicts):
1608
1533
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1534
        name_conflicts = {}
1609
1535
        self.cooked_conflicts.extend(transform.cook_conflicts(
1610
1536
                fs_conflicts, self.tt))
1611
1537
        fp = transform.FinalPaths(self.tt)
1612
1538
        for conflict in self._raw_conflicts:
1613
1539
            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':
 
1540
            if conflict_type in ('name conflict', 'parent conflict'):
 
1541
                trans_id = conflict[1]
 
1542
                conflict_args = conflict[2:]
 
1543
                if trans_id not in name_conflicts:
 
1544
                    name_conflicts[trans_id] = {}
 
1545
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1546
                                     conflict_args)
 
1547
            if conflict_type == 'contents conflict':
1635
1548
                for trans_id in conflict[1]:
1636
1549
                    file_id = self.tt.final_file_id(trans_id)
1637
1550
                    if file_id is not None:
1643
1556
                        break
1644
1557
                c = _mod_conflicts.Conflict.factory(conflict_type,
1645
1558
                                                    path=path, file_id=file_id)
1646
 
            elif conflict_type == 'text conflict':
 
1559
                self.cooked_conflicts.append(c)
 
1560
            if conflict_type == 'text conflict':
1647
1561
                trans_id = conflict[1]
1648
1562
                path = fp.get_path(trans_id)
1649
1563
                file_id = self.tt.final_file_id(trans_id)
1650
1564
                c = _mod_conflicts.Conflict.factory(conflict_type,
1651
1565
                                                    path=path, file_id=file_id)
 
1566
                self.cooked_conflicts.append(c)
 
1567
 
 
1568
        for trans_id, conflicts in name_conflicts.iteritems():
 
1569
            try:
 
1570
                this_parent, other_parent = conflicts['parent conflict']
 
1571
                if this_parent == other_parent:
 
1572
                    raise AssertionError()
 
1573
            except KeyError:
 
1574
                this_parent = other_parent = \
 
1575
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1576
            try:
 
1577
                this_name, other_name = conflicts['name conflict']
 
1578
                if this_name == other_name:
 
1579
                    raise AssertionError()
 
1580
            except KeyError:
 
1581
                this_name = other_name = self.tt.final_name(trans_id)
 
1582
            other_path = fp.get_path(trans_id)
 
1583
            if this_parent is not None and this_name is not None:
 
1584
                this_parent_path = \
 
1585
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1586
                this_path = osutils.pathjoin(this_parent_path, this_name)
1652
1587
            else:
1653
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1588
                this_path = "<deleted>"
 
1589
            file_id = self.tt.final_file_id(trans_id)
 
1590
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1591
                                                conflict_path=other_path,
 
1592
                                                file_id=file_id)
1654
1593
            self.cooked_conflicts.append(c)
1655
1594
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1656
1595
 
1762
1701
            osutils.rmtree(temp_dir)
1763
1702
 
1764
1703
 
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
1704
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1928
1705
                backup_files=False,
1929
1706
                merge_type=Merge3Merger,
1937
1714
                change_reporter=None):
1938
1715
    """Primary interface for merging.
1939
1716
 
1940
 
    Typical use is probably::
1941
 
 
1942
 
        merge_inner(branch, branch.get_revision_tree(other_revision),
1943
 
                    branch.get_revision_tree(base_revision))
1944
 
    """
 
1717
        typical use is probably
 
1718
        'merge_inner(branch, branch.get_revision_tree(other_revision),
 
1719
                     branch.get_revision_tree(base_revision))'
 
1720
        """
1945
1721
    if this_tree is None:
1946
1722
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1947
1723
                              "parameter as of bzrlib version 0.8.")