~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Merge bzr.dev into cleanup resolving conflicts

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(), """
19
21
from bzrlib import (
20
22
    branch as _mod_branch,
21
23
    conflicts as _mod_conflicts,
22
24
    debug,
23
 
    decorators,
24
 
    errors,
 
25
    generate_ids,
25
26
    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
 
37
    versionedfile,
 
38
    workingtree,
38
39
    )
39
40
from bzrlib.cleanup import OperationWithCleanups
 
41
""")
 
42
from bzrlib import (
 
43
    decorators,
 
44
    errors,
 
45
    hooks,
 
46
    )
40
47
from bzrlib.symbol_versioning import (
41
48
    deprecated_in,
42
49
    deprecated_method,
93
100
        return ('not applicable', None)
94
101
 
95
102
 
96
 
class ConfigurableFileMerger(AbstractPerFileMerger):
 
103
class PerFileMerger(AbstractPerFileMerger):
 
104
    """Merge individual files when self.file_matches returns True.
 
105
 
 
106
    This class is intended to be subclassed.  The file_matches and
 
107
    merge_matching methods should be overridden with concrete implementations.
 
108
    """
 
109
 
 
110
    def file_matches(self, params):
 
111
        """Return True if merge_matching should be called on this file.
 
112
 
 
113
        Only called with merges of plain files with no clear winner.
 
114
 
 
115
        Subclasses must override this.
 
116
        """
 
117
        raise NotImplementedError(self.file_matches)
 
118
 
 
119
    def get_filename(self, params, tree):
 
120
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
 
121
        self.merger.this_tree) and a MergeHookParams.
 
122
        """
 
123
        return osutils.basename(tree.id2path(params.file_id))
 
124
 
 
125
    def get_filepath(self, params, tree):
 
126
        """Calculate the path to the file in a tree.
 
127
 
 
128
        :param params: A MergeHookParams describing the file to merge
 
129
        :param tree: a Tree, e.g. self.merger.this_tree.
 
130
        """
 
131
        return tree.id2path(params.file_id)
 
132
 
 
133
    def merge_contents(self, params):
 
134
        """Merge the contents of a single file."""
 
135
        # Check whether this custom merge logic should be used.
 
136
        if (
 
137
            # OTHER is a straight winner, rely on default merge.
 
138
            params.winner == 'other' or
 
139
            # THIS and OTHER aren't both files.
 
140
            not params.is_file_merge() or
 
141
            # The filename doesn't match *.xml
 
142
            not self.file_matches(params)):
 
143
            return 'not_applicable', None
 
144
        return self.merge_matching(params)
 
145
 
 
146
    def merge_matching(self, params):
 
147
        """Merge the contents of a single file that has matched the criteria
 
148
        in PerFileMerger.merge_contents (is a conflict, is a file,
 
149
        self.file_matches is True).
 
150
 
 
151
        Subclasses must override this.
 
152
        """
 
153
        raise NotImplementedError(self.merge_matching)
 
154
 
 
155
 
 
156
class ConfigurableFileMerger(PerFileMerger):
97
157
    """Merge individual files when configured via a .conf file.
98
158
 
99
159
    This is a base class for concrete custom file merging logic. Concrete
122
182
        if self.name_prefix is None:
123
183
            raise ValueError("name_prefix must be set.")
124
184
 
125
 
    def filename_matches_config(self, params):
 
185
    def file_matches(self, params):
126
186
        """Check whether the file should call the merge hook.
127
187
 
128
188
        <name_prefix>_merge_files configuration variable is a list of files
142
202
                affected_files = self.default_files
143
203
            self.affected_files = affected_files
144
204
        if affected_files:
145
 
            filename = self.merger.this_tree.id2path(params.file_id)
146
 
            if filename in affected_files:
 
205
            filepath = self.get_filepath(params, self.merger.this_tree)
 
206
            if filepath in affected_files:
147
207
                return True
148
208
        return False
149
209
 
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
 
210
    def merge_matching(self, params):
163
211
        return self.merge_text(params)
164
212
 
165
213
    def merge_text(self, params):
380
428
        return self._cached_trees[revision_id]
381
429
 
382
430
    def _get_tree(self, treespec, possible_transports=None):
383
 
        from bzrlib import workingtree
384
431
        location, revno = treespec
385
432
        if revno is None:
386
433
            tree = workingtree.WorkingTree.open_containing(location)[0]
704
751
        :param this_tree: The local tree in the merge operation
705
752
        :param base_tree: The common tree in the merge operation
706
753
        :param other_tree: The other tree to merge changes from
707
 
        :param this_branch: The branch associated with this_tree
 
754
        :param this_branch: The branch associated with this_tree.  Defaults to
 
755
            this_tree.branch if not supplied.
708
756
        :param interesting_ids: The file_ids of files that should be
709
757
            participate in the merge.  May not be combined with
710
758
            interesting_files.
728
776
        if interesting_files is not None and interesting_ids is not None:
729
777
            raise ValueError(
730
778
                'specify either interesting_ids or interesting_files')
 
779
        if this_branch is None:
 
780
            this_branch = this_tree.branch
731
781
        self.interesting_ids = interesting_ids
732
782
        self.interesting_files = interesting_files
733
783
        self.this_tree = working_tree
812
862
        finally:
813
863
            child_pb.finished()
814
864
        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
        """
815
872
        child_pb = ui.ui_factory.nested_progress_bar()
816
873
        try:
817
874
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1027
1084
                          ))
1028
1085
        return result
1029
1086
 
1030
 
 
1031
1087
    def fix_root(self):
1032
1088
        if self.tt.final_kind(self.tt.root) is None:
1033
1089
            self.tt.cancel_deletion(self.tt.root)
1403
1459
    def get_lines(self, tree, file_id):
1404
1460
        """Return the lines in a file, or an empty list."""
1405
1461
        if tree.has_id(file_id):
1406
 
            return tree.get_file(file_id).readlines()
 
1462
            return tree.get_file_lines(file_id)
1407
1463
        else:
1408
1464
            return []
1409
1465
 
1695
1751
            osutils.rmtree(temp_dir)
1696
1752
 
1697
1753
 
 
1754
class PathNotInTree(errors.BzrError):
 
1755
 
 
1756
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1757
 
 
1758
    def __init__(self, path, tree):
 
1759
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1760
 
 
1761
 
 
1762
class MergeIntoMerger(Merger):
 
1763
    """Merger that understands other_tree will be merged into a subdir.
 
1764
 
 
1765
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1766
    and RevisonTree objects, rather than using revision specs.
 
1767
    """
 
1768
 
 
1769
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1770
            source_subpath, other_rev_id=None):
 
1771
        """Create a new MergeIntoMerger object.
 
1772
 
 
1773
        source_subpath in other_tree will be effectively copied to
 
1774
        target_subdir in this_tree.
 
1775
 
 
1776
        :param this_tree: The tree that we will be merging into.
 
1777
        :param other_branch: The Branch we will be merging from.
 
1778
        :param other_tree: The RevisionTree object we want to merge.
 
1779
        :param target_subdir: The relative path where we want to merge
 
1780
            other_tree into this_tree
 
1781
        :param source_subpath: The relative path specifying the subtree of
 
1782
            other_tree to merge into this_tree.
 
1783
        """
 
1784
        # It is assumed that we are merging a tree that is not in our current
 
1785
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1786
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1787
                                _mod_revision.NULL_REVISION)
 
1788
        super(MergeIntoMerger, self).__init__(
 
1789
            this_branch=this_tree.branch,
 
1790
            this_tree=this_tree,
 
1791
            other_tree=other_tree,
 
1792
            base_tree=null_ancestor_tree,
 
1793
            )
 
1794
        self._target_subdir = target_subdir
 
1795
        self._source_subpath = source_subpath
 
1796
        self.other_branch = other_branch
 
1797
        if other_rev_id is None:
 
1798
            other_rev_id = other_tree.get_revision_id()
 
1799
        self.other_rev_id = self.other_basis = other_rev_id
 
1800
        self.base_is_ancestor = True
 
1801
        self.backup_files = True
 
1802
        self.merge_type = Merge3Merger
 
1803
        self.show_base = False
 
1804
        self.reprocess = False
 
1805
        self.interesting_ids = None
 
1806
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1807
              target_subdir=self._target_subdir,
 
1808
              source_subpath=self._source_subpath)
 
1809
        if self._source_subpath != '':
 
1810
            # If this isn't a partial merge make sure the revisions will be
 
1811
            # present.
 
1812
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1813
                self.other_basis)
 
1814
 
 
1815
    def set_pending(self):
 
1816
        if self._source_subpath != '':
 
1817
            return
 
1818
        Merger.set_pending(self)
 
1819
 
 
1820
 
 
1821
class _MergeTypeParameterizer(object):
 
1822
    """Wrap a merge-type class to provide extra parameters.
 
1823
    
 
1824
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1825
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1826
    the 'merge_type' member.  It is difficult override do_merge without
 
1827
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1828
    the extra parameters.
 
1829
    """
 
1830
 
 
1831
    def __init__(self, merge_type, **kwargs):
 
1832
        self._extra_kwargs = kwargs
 
1833
        self._merge_type = merge_type
 
1834
 
 
1835
    def __call__(self, *args, **kwargs):
 
1836
        kwargs.update(self._extra_kwargs)
 
1837
        return self._merge_type(*args, **kwargs)
 
1838
 
 
1839
    def __getattr__(self, name):
 
1840
        return getattr(self._merge_type, name)
 
1841
 
 
1842
 
 
1843
class MergeIntoMergeType(Merge3Merger):
 
1844
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1845
 
 
1846
    def __init__(self, *args, **kwargs):
 
1847
        """Initialize the merger object.
 
1848
 
 
1849
        :param args: See Merge3Merger.__init__'s args.
 
1850
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1851
            source_subpath and target_subdir.
 
1852
        :keyword source_subpath: The relative path specifying the subtree of
 
1853
            other_tree to merge into this_tree.
 
1854
        :keyword target_subdir: The relative path where we want to merge
 
1855
            other_tree into this_tree
 
1856
        """
 
1857
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1858
        # so we have have to hack in to get our extra parameters set.
 
1859
        self._source_subpath = kwargs.pop('source_subpath')
 
1860
        self._target_subdir = kwargs.pop('target_subdir')
 
1861
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1862
 
 
1863
    def _compute_transform(self):
 
1864
        child_pb = ui.ui_factory.nested_progress_bar()
 
1865
        try:
 
1866
            entries = self._entries_to_incorporate()
 
1867
            entries = list(entries)
 
1868
            for num, (entry, parent_id) in enumerate(entries):
 
1869
                child_pb.update('Preparing file merge', num, len(entries))
 
1870
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1871
                trans_id = transform.new_by_entry(self.tt, entry,
 
1872
                    parent_trans_id, self.other_tree)
 
1873
        finally:
 
1874
            child_pb.finished()
 
1875
        self._finish_computing_transform()
 
1876
 
 
1877
    def _entries_to_incorporate(self):
 
1878
        """Yields pairs of (inventory_entry, new_parent)."""
 
1879
        other_inv = self.other_tree.inventory
 
1880
        subdir_id = other_inv.path2id(self._source_subpath)
 
1881
        if subdir_id is None:
 
1882
            # XXX: The error would be clearer if it gave the URL of the source
 
1883
            # branch, but we don't have a reference to that here.
 
1884
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1885
        subdir = other_inv[subdir_id]
 
1886
        parent_in_target = osutils.dirname(self._target_subdir)
 
1887
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1888
        if target_id is None:
 
1889
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1890
        name_in_target = osutils.basename(self._target_subdir)
 
1891
        merge_into_root = subdir.copy()
 
1892
        merge_into_root.name = name_in_target
 
1893
        if merge_into_root.file_id in self.this_tree.inventory:
 
1894
            # Give the root a new file-id.
 
1895
            # This can happen fairly easily if the directory we are
 
1896
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1897
            # their root_id.  Users will expect this to Just Work, so we
 
1898
            # change the file-id here.
 
1899
            # Non-root file-ids could potentially conflict too.  That's really
 
1900
            # an edge case, so we don't do anything special for those.  We let
 
1901
            # them cause conflicts.
 
1902
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1903
        yield (merge_into_root, target_id)
 
1904
        if subdir.kind != 'directory':
 
1905
            # No children, so we are done.
 
1906
            return
 
1907
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1908
            parent_id = entry.parent_id
 
1909
            if parent_id == subdir.file_id:
 
1910
                # The root's parent ID has changed, so make sure children of
 
1911
                # the root refer to the new ID.
 
1912
                parent_id = merge_into_root.file_id
 
1913
            yield (entry, parent_id)
 
1914
 
 
1915
 
1698
1916
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1699
1917
                backup_files=False,
1700
1918
                merge_type=Merge3Merger,
1708
1926
                change_reporter=None):
1709
1927
    """Primary interface for merging.
1710
1928
 
1711
 
        typical use is probably
1712
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1713
 
                     branch.get_revision_tree(base_revision))'
1714
 
        """
 
1929
    Typical use is probably::
 
1930
 
 
1931
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1932
                    branch.get_revision_tree(base_revision))
 
1933
    """
1715
1934
    if this_tree is None:
1716
1935
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1717
1936
                              "parameter as of bzrlib version 0.8.")