/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: John Arbash Meinel
  • Date: 2011-03-15 10:28:20 UTC
  • mto: This revision was merged to the branch mainline in revision 5725.
  • Revision ID: john@arbash-meinel.com-20110315102820-51wy8wjre5ol34mu
'bzr export' needs to use 'exact' encoding.

If we are going to be writing binary bites out of stdout, then it needs to
be in binary mode, or it will corrupt the data stream.
Oddly enough, it only seemed to fail if we set '--verbose'. I didn't
bother to track into that bug.

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]
559
565
 
560
566
    def _maybe_fetch(self, source, target, revision_id):
561
567
        if not source.repository.has_same_location(target.repository):
562
 
            target.fetch(source, revision_id)
 
568
            try:
 
569
                tags_to_fetch = set(source.tags.get_reverse_tag_dict())
 
570
            except errors.TagsNotSupported:
 
571
                tags_to_fetch = None
 
572
            fetch_spec = _mod_graph.NotInOtherForRevs(target.repository,
 
573
                source.repository, [revision_id],
 
574
                if_present_ids=tags_to_fetch).execute()
 
575
            target.fetch(source, fetch_spec=fetch_spec)
563
576
 
564
577
    def find_base(self):
565
578
        revisions = [_mod_revision.ensure_null(self.this_basis),
576
589
            elif len(lcas) == 1:
577
590
                self.base_rev_id = list(lcas)[0]
578
591
            else: # len(lcas) > 1
 
592
                self._is_criss_cross = True
579
593
                if len(lcas) > 2:
580
594
                    # find_unique_lca can only handle 2 nodes, so we have to
581
595
                    # start back at the beginning. It is a shame to traverse
586
600
                else:
587
601
                    self.base_rev_id = self.revision_graph.find_unique_lca(
588
602
                                            *lcas)
589
 
                self._is_criss_cross = True
 
603
                sorted_lca_keys = self.revision_graph.find_merge_order(                
 
604
                    revisions[0], lcas)
 
605
                if self.base_rev_id == _mod_revision.NULL_REVISION:
 
606
                    self.base_rev_id = sorted_lca_keys[0]
 
607
                
590
608
            if self.base_rev_id == _mod_revision.NULL_REVISION:
591
609
                raise errors.UnrelatedBranches()
592
610
            if self._is_criss_cross:
593
611
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
594
612
                              ' help criss-cross.')
595
613
                trace.mutter('Criss-cross lcas: %r' % lcas)
596
 
                interesting_revision_ids = [self.base_rev_id]
597
 
                interesting_revision_ids.extend(lcas)
 
614
                if self.base_rev_id in lcas:
 
615
                    trace.mutter('Unable to find unique lca. '
 
616
                                 'Fallback %r as best option.' % self.base_rev_id)
 
617
                interesting_revision_ids = set(lcas)
 
618
                interesting_revision_ids.add(self.base_rev_id)
598
619
                interesting_trees = dict((t.get_revision_id(), t)
599
620
                    for t in self.this_branch.repository.revision_trees(
600
621
                        interesting_revision_ids))
601
622
                self._cached_trees.update(interesting_trees)
602
 
                self.base_tree = interesting_trees.pop(self.base_rev_id)
603
 
                sorted_lca_keys = self.revision_graph.find_merge_order(
604
 
                    revisions[0], lcas)
 
623
                if self.base_rev_id in lcas:
 
624
                    self.base_tree = interesting_trees[self.base_rev_id]
 
625
                else:
 
626
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
605
627
                self._lca_trees = [interesting_trees[key]
606
628
                                   for key in sorted_lca_keys]
607
629
            else:
856
878
        finally:
857
879
            child_pb.finished()
858
880
        self.fix_root()
 
881
        self._finish_computing_transform()
 
882
 
 
883
    def _finish_computing_transform(self):
 
884
        """Finalize the transform and report the changes.
 
885
 
 
886
        This is the second half of _compute_transform.
 
887
        """
859
888
        child_pb = ui.ui_factory.nested_progress_bar()
860
889
        try:
861
890
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1071
1100
                          ))
1072
1101
        return result
1073
1102
 
1074
 
 
1075
1103
    def fix_root(self):
1076
 
        try:
1077
 
            self.tt.final_kind(self.tt.root)
1078
 
        except errors.NoSuchFile:
 
1104
        if self.tt.final_kind(self.tt.root) is None:
1079
1105
            self.tt.cancel_deletion(self.tt.root)
1080
1106
        if self.tt.final_file_id(self.tt.root) is None:
1081
1107
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1090
1116
            # the other tree's root is a non-root in the current tree (as when
1091
1117
            # a previously unrelated branch is merged into another)
1092
1118
            return
1093
 
        try:
1094
 
            self.tt.final_kind(other_root)
 
1119
        if self.tt.final_kind(other_root) is not None:
1095
1120
            other_root_is_present = True
1096
 
        except errors.NoSuchFile:
 
1121
        else:
1097
1122
            # other_root doesn't have a physical representation. We still need
1098
1123
            # to move any references to the actual root of the tree.
1099
1124
            other_root_is_present = False
1103
1128
        for thing, child in self.other_tree.inventory.root.children.iteritems():
1104
1129
            trans_id = self.tt.trans_id_file_id(child.file_id)
1105
1130
            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)
 
1131
                if self.tt.final_kind(trans_id) is not None:
1110
1132
                    # The item exist in the final tree and has a defined place
1111
1133
                    # to go already.
1112
1134
                    continue
1113
 
                except errors.NoSuchFile, e:
1114
 
                    pass
1115
1135
            # Move the item into the root
1116
1136
            self.tt.adjust_path(self.tt.final_name(trans_id),
1117
1137
                                self.tt.root, trans_id)
1393
1413
            self.tt.version_file(file_id, trans_id)
1394
1414
        # The merge has been performed, so the old contents should not be
1395
1415
        # retained.
1396
 
        try:
1397
 
            self.tt.delete_contents(trans_id)
1398
 
        except errors.NoSuchFile:
1399
 
            pass
 
1416
        self.tt.delete_contents(trans_id)
1400
1417
        return result
1401
1418
 
1402
1419
    def _default_other_winner_merge(self, merge_hook_params):
1455
1472
    def get_lines(self, tree, file_id):
1456
1473
        """Return the lines in a file, or an empty list."""
1457
1474
        if tree.has_id(file_id):
1458
 
            return tree.get_file(file_id).readlines()
 
1475
            return tree.get_file_lines(file_id)
1459
1476
        else:
1460
1477
            return []
1461
1478
 
1574
1591
        if winner == 'this' and file_status != "modified":
1575
1592
            return
1576
1593
        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:
 
1594
        if self.tt.final_kind(trans_id) != "file":
1581
1595
            return
1582
1596
        if winner == "this":
1583
1597
            executability = this_executable
1750
1764
            osutils.rmtree(temp_dir)
1751
1765
 
1752
1766
 
 
1767
class PathNotInTree(errors.BzrError):
 
1768
 
 
1769
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1770
 
 
1771
    def __init__(self, path, tree):
 
1772
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1773
 
 
1774
 
 
1775
class MergeIntoMerger(Merger):
 
1776
    """Merger that understands other_tree will be merged into a subdir.
 
1777
 
 
1778
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1779
    and RevisonTree objects, rather than using revision specs.
 
1780
    """
 
1781
 
 
1782
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1783
            source_subpath, other_rev_id=None):
 
1784
        """Create a new MergeIntoMerger object.
 
1785
 
 
1786
        source_subpath in other_tree will be effectively copied to
 
1787
        target_subdir in this_tree.
 
1788
 
 
1789
        :param this_tree: The tree that we will be merging into.
 
1790
        :param other_branch: The Branch we will be merging from.
 
1791
        :param other_tree: The RevisionTree object we want to merge.
 
1792
        :param target_subdir: The relative path where we want to merge
 
1793
            other_tree into this_tree
 
1794
        :param source_subpath: The relative path specifying the subtree of
 
1795
            other_tree to merge into this_tree.
 
1796
        """
 
1797
        # It is assumed that we are merging a tree that is not in our current
 
1798
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1799
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1800
                                _mod_revision.NULL_REVISION)
 
1801
        super(MergeIntoMerger, self).__init__(
 
1802
            this_branch=this_tree.branch,
 
1803
            this_tree=this_tree,
 
1804
            other_tree=other_tree,
 
1805
            base_tree=null_ancestor_tree,
 
1806
            )
 
1807
        self._target_subdir = target_subdir
 
1808
        self._source_subpath = source_subpath
 
1809
        self.other_branch = other_branch
 
1810
        if other_rev_id is None:
 
1811
            other_rev_id = other_tree.get_revision_id()
 
1812
        self.other_rev_id = self.other_basis = other_rev_id
 
1813
        self.base_is_ancestor = True
 
1814
        self.backup_files = True
 
1815
        self.merge_type = Merge3Merger
 
1816
        self.show_base = False
 
1817
        self.reprocess = False
 
1818
        self.interesting_ids = None
 
1819
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1820
              target_subdir=self._target_subdir,
 
1821
              source_subpath=self._source_subpath)
 
1822
        if self._source_subpath != '':
 
1823
            # If this isn't a partial merge make sure the revisions will be
 
1824
            # present.
 
1825
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1826
                self.other_basis)
 
1827
 
 
1828
    def set_pending(self):
 
1829
        if self._source_subpath != '':
 
1830
            return
 
1831
        Merger.set_pending(self)
 
1832
 
 
1833
 
 
1834
class _MergeTypeParameterizer(object):
 
1835
    """Wrap a merge-type class to provide extra parameters.
 
1836
    
 
1837
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1838
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1839
    the 'merge_type' member.  It is difficult override do_merge without
 
1840
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1841
    the extra parameters.
 
1842
    """
 
1843
 
 
1844
    def __init__(self, merge_type, **kwargs):
 
1845
        self._extra_kwargs = kwargs
 
1846
        self._merge_type = merge_type
 
1847
 
 
1848
    def __call__(self, *args, **kwargs):
 
1849
        kwargs.update(self._extra_kwargs)
 
1850
        return self._merge_type(*args, **kwargs)
 
1851
 
 
1852
    def __getattr__(self, name):
 
1853
        return getattr(self._merge_type, name)
 
1854
 
 
1855
 
 
1856
class MergeIntoMergeType(Merge3Merger):
 
1857
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1858
 
 
1859
    def __init__(self, *args, **kwargs):
 
1860
        """Initialize the merger object.
 
1861
 
 
1862
        :param args: See Merge3Merger.__init__'s args.
 
1863
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1864
            source_subpath and target_subdir.
 
1865
        :keyword source_subpath: The relative path specifying the subtree of
 
1866
            other_tree to merge into this_tree.
 
1867
        :keyword target_subdir: The relative path where we want to merge
 
1868
            other_tree into this_tree
 
1869
        """
 
1870
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1871
        # so we have have to hack in to get our extra parameters set.
 
1872
        self._source_subpath = kwargs.pop('source_subpath')
 
1873
        self._target_subdir = kwargs.pop('target_subdir')
 
1874
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1875
 
 
1876
    def _compute_transform(self):
 
1877
        child_pb = ui.ui_factory.nested_progress_bar()
 
1878
        try:
 
1879
            entries = self._entries_to_incorporate()
 
1880
            entries = list(entries)
 
1881
            for num, (entry, parent_id) in enumerate(entries):
 
1882
                child_pb.update('Preparing file merge', num, len(entries))
 
1883
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1884
                trans_id = transform.new_by_entry(self.tt, entry,
 
1885
                    parent_trans_id, self.other_tree)
 
1886
        finally:
 
1887
            child_pb.finished()
 
1888
        self._finish_computing_transform()
 
1889
 
 
1890
    def _entries_to_incorporate(self):
 
1891
        """Yields pairs of (inventory_entry, new_parent)."""
 
1892
        other_inv = self.other_tree.inventory
 
1893
        subdir_id = other_inv.path2id(self._source_subpath)
 
1894
        if subdir_id is None:
 
1895
            # XXX: The error would be clearer if it gave the URL of the source
 
1896
            # branch, but we don't have a reference to that here.
 
1897
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1898
        subdir = other_inv[subdir_id]
 
1899
        parent_in_target = osutils.dirname(self._target_subdir)
 
1900
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1901
        if target_id is None:
 
1902
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1903
        name_in_target = osutils.basename(self._target_subdir)
 
1904
        merge_into_root = subdir.copy()
 
1905
        merge_into_root.name = name_in_target
 
1906
        if merge_into_root.file_id in self.this_tree.inventory:
 
1907
            # Give the root a new file-id.
 
1908
            # This can happen fairly easily if the directory we are
 
1909
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1910
            # their root_id.  Users will expect this to Just Work, so we
 
1911
            # change the file-id here.
 
1912
            # Non-root file-ids could potentially conflict too.  That's really
 
1913
            # an edge case, so we don't do anything special for those.  We let
 
1914
            # them cause conflicts.
 
1915
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1916
        yield (merge_into_root, target_id)
 
1917
        if subdir.kind != 'directory':
 
1918
            # No children, so we are done.
 
1919
            return
 
1920
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1921
            parent_id = entry.parent_id
 
1922
            if parent_id == subdir.file_id:
 
1923
                # The root's parent ID has changed, so make sure children of
 
1924
                # the root refer to the new ID.
 
1925
                parent_id = merge_into_root.file_id
 
1926
            yield (entry, parent_id)
 
1927
 
 
1928
 
1753
1929
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1754
1930
                backup_files=False,
1755
1931
                merge_type=Merge3Merger,
1763
1939
                change_reporter=None):
1764
1940
    """Primary interface for merging.
1765
1941
 
1766
 
        typical use is probably
1767
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1768
 
                     branch.get_revision_tree(base_revision))'
1769
 
        """
 
1942
    Typical use is probably::
 
1943
 
 
1944
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1945
                    branch.get_revision_tree(base_revision))
 
1946
    """
1770
1947
    if this_tree is None:
1771
1948
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1772
1949
                              "parameter as of bzrlib version 0.8.")