/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

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

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,
421
428
        return self._cached_trees[revision_id]
422
429
 
423
430
    def _get_tree(self, treespec, possible_transports=None):
424
 
        from bzrlib import workingtree
425
431
        location, revno = treespec
426
432
        if revno is None:
427
433
            tree = workingtree.WorkingTree.open_containing(location)[0]
856
862
        finally:
857
863
            child_pb.finished()
858
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
        """
859
872
        child_pb = ui.ui_factory.nested_progress_bar()
860
873
        try:
861
874
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1071
1084
                          ))
1072
1085
        return result
1073
1086
 
1074
 
 
1075
1087
    def fix_root(self):
1076
 
        try:
1077
 
            self.tt.final_kind(self.tt.root)
1078
 
        except errors.NoSuchFile:
 
1088
        if self.tt.final_kind(self.tt.root) is None:
1079
1089
            self.tt.cancel_deletion(self.tt.root)
1080
1090
        if self.tt.final_file_id(self.tt.root) is None:
1081
1091
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1090
1100
            # the other tree's root is a non-root in the current tree (as when
1091
1101
            # a previously unrelated branch is merged into another)
1092
1102
            return
1093
 
        try:
1094
 
            self.tt.final_kind(other_root)
 
1103
        if self.tt.final_kind(other_root) is not None:
1095
1104
            other_root_is_present = True
1096
 
        except errors.NoSuchFile:
 
1105
        else:
1097
1106
            # other_root doesn't have a physical representation. We still need
1098
1107
            # to move any references to the actual root of the tree.
1099
1108
            other_root_is_present = False
1103
1112
        for thing, child in self.other_tree.inventory.root.children.iteritems():
1104
1113
            trans_id = self.tt.trans_id_file_id(child.file_id)
1105
1114
            if not other_root_is_present:
1106
 
                # FIXME: Make final_kind returns None instead of raising
1107
 
                # NoSuchFile to avoid the ugly construct below -- vila 20100402
1108
 
                try:
1109
 
                    self.tt.final_kind(trans_id)
 
1115
                if self.tt.final_kind(trans_id) is not None:
1110
1116
                    # The item exist in the final tree and has a defined place
1111
1117
                    # to go already.
1112
1118
                    continue
1113
 
                except errors.NoSuchFile, e:
1114
 
                    pass
1115
1119
            # Move the item into the root
1116
1120
            self.tt.adjust_path(self.tt.final_name(trans_id),
1117
1121
                                self.tt.root, trans_id)
1393
1397
            self.tt.version_file(file_id, trans_id)
1394
1398
        # The merge has been performed, so the old contents should not be
1395
1399
        # retained.
1396
 
        try:
1397
 
            self.tt.delete_contents(trans_id)
1398
 
        except errors.NoSuchFile:
1399
 
            pass
 
1400
        self.tt.delete_contents(trans_id)
1400
1401
        return result
1401
1402
 
1402
1403
    def _default_other_winner_merge(self, merge_hook_params):
1455
1456
    def get_lines(self, tree, file_id):
1456
1457
        """Return the lines in a file, or an empty list."""
1457
1458
        if tree.has_id(file_id):
1458
 
            return tree.get_file(file_id).readlines()
 
1459
            return tree.get_file_lines(file_id)
1459
1460
        else:
1460
1461
            return []
1461
1462
 
1574
1575
        if winner == 'this' and file_status != "modified":
1575
1576
            return
1576
1577
        trans_id = self.tt.trans_id_file_id(file_id)
1577
 
        try:
1578
 
            if self.tt.final_kind(trans_id) != "file":
1579
 
                return
1580
 
        except errors.NoSuchFile:
 
1578
        if self.tt.final_kind(trans_id) != "file":
1581
1579
            return
1582
1580
        if winner == "this":
1583
1581
            executability = this_executable
1750
1748
            osutils.rmtree(temp_dir)
1751
1749
 
1752
1750
 
 
1751
class PathNotInTree(errors.BzrError):
 
1752
 
 
1753
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1754
 
 
1755
    def __init__(self, path, tree):
 
1756
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1757
 
 
1758
 
 
1759
class MergeIntoMerger(Merger):
 
1760
    """Merger that understands other_tree will be merged into a subdir.
 
1761
 
 
1762
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1763
    and RevisonTree objects, rather than using revision specs.
 
1764
    """
 
1765
 
 
1766
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1767
            source_subpath, other_rev_id=None):
 
1768
        """Create a new MergeIntoMerger object.
 
1769
 
 
1770
        source_subpath in other_tree will be effectively copied to
 
1771
        target_subdir in this_tree.
 
1772
 
 
1773
        :param this_tree: The tree that we will be merging into.
 
1774
        :param other_branch: The Branch we will be merging from.
 
1775
        :param other_tree: The RevisionTree object we want to merge.
 
1776
        :param target_subdir: The relative path where we want to merge
 
1777
            other_tree into this_tree
 
1778
        :param source_subpath: The relative path specifying the subtree of
 
1779
            other_tree to merge into this_tree.
 
1780
        """
 
1781
        # It is assumed that we are merging a tree that is not in our current
 
1782
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1783
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1784
                                _mod_revision.NULL_REVISION)
 
1785
        super(MergeIntoMerger, self).__init__(
 
1786
            this_branch=this_tree.branch,
 
1787
            this_tree=this_tree,
 
1788
            other_tree=other_tree,
 
1789
            base_tree=null_ancestor_tree,
 
1790
            )
 
1791
        self._target_subdir = target_subdir
 
1792
        self._source_subpath = source_subpath
 
1793
        self.other_branch = other_branch
 
1794
        if other_rev_id is None:
 
1795
            other_rev_id = other_tree.get_revision_id()
 
1796
        self.other_rev_id = self.other_basis = other_rev_id
 
1797
        self.base_is_ancestor = True
 
1798
        self.backup_files = True
 
1799
        self.merge_type = Merge3Merger
 
1800
        self.show_base = False
 
1801
        self.reprocess = False
 
1802
        self.interesting_ids = None
 
1803
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1804
              target_subdir=self._target_subdir,
 
1805
              source_subpath=self._source_subpath)
 
1806
        if self._source_subpath != '':
 
1807
            # If this isn't a partial merge make sure the revisions will be
 
1808
            # present.
 
1809
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1810
                self.other_basis)
 
1811
 
 
1812
    def set_pending(self):
 
1813
        if self._source_subpath != '':
 
1814
            return
 
1815
        Merger.set_pending(self)
 
1816
 
 
1817
 
 
1818
class _MergeTypeParameterizer(object):
 
1819
    """Wrap a merge-type class to provide extra parameters.
 
1820
    
 
1821
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1822
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1823
    the 'merge_type' member.  It is difficult override do_merge without
 
1824
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1825
    the extra parameters.
 
1826
    """
 
1827
 
 
1828
    def __init__(self, merge_type, **kwargs):
 
1829
        self._extra_kwargs = kwargs
 
1830
        self._merge_type = merge_type
 
1831
 
 
1832
    def __call__(self, *args, **kwargs):
 
1833
        kwargs.update(self._extra_kwargs)
 
1834
        return self._merge_type(*args, **kwargs)
 
1835
 
 
1836
    def __getattr__(self, name):
 
1837
        return getattr(self._merge_type, name)
 
1838
 
 
1839
 
 
1840
class MergeIntoMergeType(Merge3Merger):
 
1841
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1842
 
 
1843
    def __init__(self, *args, **kwargs):
 
1844
        """Initialize the merger object.
 
1845
 
 
1846
        :param args: See Merge3Merger.__init__'s args.
 
1847
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1848
            source_subpath and target_subdir.
 
1849
        :keyword source_subpath: The relative path specifying the subtree of
 
1850
            other_tree to merge into this_tree.
 
1851
        :keyword target_subdir: The relative path where we want to merge
 
1852
            other_tree into this_tree
 
1853
        """
 
1854
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1855
        # so we have have to hack in to get our extra parameters set.
 
1856
        self._source_subpath = kwargs.pop('source_subpath')
 
1857
        self._target_subdir = kwargs.pop('target_subdir')
 
1858
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1859
 
 
1860
    def _compute_transform(self):
 
1861
        child_pb = ui.ui_factory.nested_progress_bar()
 
1862
        try:
 
1863
            entries = self._entries_to_incorporate()
 
1864
            entries = list(entries)
 
1865
            for num, (entry, parent_id) in enumerate(entries):
 
1866
                child_pb.update('Preparing file merge', num, len(entries))
 
1867
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1868
                trans_id = transform.new_by_entry(self.tt, entry,
 
1869
                    parent_trans_id, self.other_tree)
 
1870
        finally:
 
1871
            child_pb.finished()
 
1872
        self._finish_computing_transform()
 
1873
 
 
1874
    def _entries_to_incorporate(self):
 
1875
        """Yields pairs of (inventory_entry, new_parent)."""
 
1876
        other_inv = self.other_tree.inventory
 
1877
        subdir_id = other_inv.path2id(self._source_subpath)
 
1878
        if subdir_id is None:
 
1879
            # XXX: The error would be clearer if it gave the URL of the source
 
1880
            # branch, but we don't have a reference to that here.
 
1881
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1882
        subdir = other_inv[subdir_id]
 
1883
        parent_in_target = osutils.dirname(self._target_subdir)
 
1884
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1885
        if target_id is None:
 
1886
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1887
        name_in_target = osutils.basename(self._target_subdir)
 
1888
        merge_into_root = subdir.copy()
 
1889
        merge_into_root.name = name_in_target
 
1890
        if merge_into_root.file_id in self.this_tree.inventory:
 
1891
            # Give the root a new file-id.
 
1892
            # This can happen fairly easily if the directory we are
 
1893
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1894
            # their root_id.  Users will expect this to Just Work, so we
 
1895
            # change the file-id here.
 
1896
            # Non-root file-ids could potentially conflict too.  That's really
 
1897
            # an edge case, so we don't do anything special for those.  We let
 
1898
            # them cause conflicts.
 
1899
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1900
        yield (merge_into_root, target_id)
 
1901
        if subdir.kind != 'directory':
 
1902
            # No children, so we are done.
 
1903
            return
 
1904
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1905
            parent_id = entry.parent_id
 
1906
            if parent_id == subdir.file_id:
 
1907
                # The root's parent ID has changed, so make sure children of
 
1908
                # the root refer to the new ID.
 
1909
                parent_id = merge_into_root.file_id
 
1910
            yield (entry, parent_id)
 
1911
 
 
1912
 
1753
1913
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1754
1914
                backup_files=False,
1755
1915
                merge_type=Merge3Merger,
1763
1923
                change_reporter=None):
1764
1924
    """Primary interface for merging.
1765
1925
 
1766
 
        typical use is probably
1767
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1768
 
                     branch.get_revision_tree(base_revision))'
1769
 
        """
 
1926
    Typical use is probably::
 
1927
 
 
1928
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1929
                    branch.get_revision_tree(base_revision))
 
1930
    """
1770
1931
    if this_tree is None:
1771
1932
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1772
1933
                              "parameter as of bzrlib version 0.8.")