/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-04-20 14:27:19 UTC
  • mto: This revision was merged to the branch mainline in revision 5837.
  • Revision ID: john@arbash-meinel.com-20110420142719-advs1k5vztqzbrgv
Fix bug #767177. Be more agressive with file.close() calls.

Our test suite gets a number of thread leaks and failures because it happens to get async
SFTPFile.close() calls. (if an SFTPFile closes due to __del__ it is done as an async request,
while if you call SFTPFile.close() it is done as a synchronous request.)
We have a couple other cases, probably. Namely SFTPTransport.get() also does an async
prefetch of the content, so if you don't .read() you'll also leak threads that think they
are doing work that you want.

The biggest change here, though, is using a try/finally in a generator, which is not 
python2.4 compatible.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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,
 
23
    cleanup,
21
24
    conflicts as _mod_conflicts,
22
25
    debug,
23
 
    decorators,
24
 
    errors,
 
26
    generate_ids,
25
27
    graph as _mod_graph,
26
 
    hooks,
27
28
    merge3,
28
29
    osutils,
29
30
    patiencediff,
34
35
    tree as _mod_tree,
35
36
    tsort,
36
37
    ui,
37
 
    versionedfile
38
 
    )
39
 
from bzrlib.cleanup import OperationWithCleanups
 
38
    versionedfile,
 
39
    workingtree,
 
40
    )
 
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,
46
53
 
47
54
def transform_tree(from_tree, to_tree, interesting_ids=None):
48
55
    from_tree.lock_tree_write()
49
 
    operation = OperationWithCleanups(merge_inner)
 
56
    operation = cleanup.OperationWithCleanups(merge_inner)
50
57
    operation.add_cleanup(from_tree.unlock)
51
58
    operation.run_simple(from_tree.branch, to_tree, from_tree,
52
59
        ignore_zero=True, interesting_ids=interesting_ids, this_tree=from_tree)
55
62
class MergeHooks(hooks.Hooks):
56
63
 
57
64
    def __init__(self):
58
 
        hooks.Hooks.__init__(self)
59
 
        self.create_hook(hooks.HookPoint('merge_file_content',
 
65
        hooks.Hooks.__init__(self, "bzrlib.merge", "Merger.hooks")
 
66
        self.add_hook('merge_file_content',
60
67
            "Called with a bzrlib.merge.Merger object to create a per file "
61
68
            "merge object when starting a merge. "
62
69
            "Should return either None or a subclass of "
66
73
            "side has deleted the file and the other has changed it). "
67
74
            "See the AbstractPerFileMerger API docs for details on how it is "
68
75
            "used by merge.",
69
 
            (2, 1), None))
 
76
            (2, 1))
70
77
 
71
78
 
72
79
class AbstractPerFileMerger(object):
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]
452
458
    @deprecated_method(deprecated_in((2, 1, 0)))
453
459
    def file_revisions(self, file_id):
454
460
        self.ensure_revision_trees()
455
 
        def get_id(tree, file_id):
456
 
            revision_id = tree.inventory[file_id].revision
457
 
            return revision_id
458
461
        if self.this_rev_id is None:
459
462
            if self.this_basis_tree.get_file_sha1(file_id) != \
460
463
                self.this_tree.get_file_sha1(file_id):
461
464
                raise errors.WorkingTreeNotRevision(self.this_tree)
462
465
 
463
466
        trees = (self.this_basis_tree, self.other_tree)
464
 
        return [get_id(tree, file_id) for tree in trees]
 
467
        return [tree.get_file_revision(file_id) for tree in trees]
465
468
 
466
469
    @deprecated_method(deprecated_in((2, 1, 0)))
467
470
    def check_basis(self, check_clean, require_commits=True):
495
498
    def _add_parent(self):
496
499
        new_parents = self.this_tree.get_parent_ids() + [self.other_rev_id]
497
500
        new_parent_trees = []
498
 
        operation = OperationWithCleanups(self.this_tree.set_parent_trees)
 
501
        operation = cleanup.OperationWithCleanups(
 
502
            self.this_tree.set_parent_trees)
499
503
        for revision_id in new_parents:
500
504
            try:
501
505
                tree = self.revision_tree(revision_id)
559
563
 
560
564
    def _maybe_fetch(self, source, target, revision_id):
561
565
        if not source.repository.has_same_location(target.repository):
562
 
            target.fetch(source, revision_id)
 
566
            try:
 
567
                tags_to_fetch = set(source.tags.get_reverse_tag_dict())
 
568
            except errors.TagsNotSupported:
 
569
                tags_to_fetch = None
 
570
            fetch_spec = _mod_graph.NotInOtherForRevs(target.repository,
 
571
                source.repository, [revision_id],
 
572
                if_present_ids=tags_to_fetch).execute()
 
573
            target.fetch(source, fetch_spec=fetch_spec)
563
574
 
564
575
    def find_base(self):
565
576
        revisions = [_mod_revision.ensure_null(self.this_basis),
576
587
            elif len(lcas) == 1:
577
588
                self.base_rev_id = list(lcas)[0]
578
589
            else: # len(lcas) > 1
 
590
                self._is_criss_cross = True
579
591
                if len(lcas) > 2:
580
592
                    # find_unique_lca can only handle 2 nodes, so we have to
581
593
                    # start back at the beginning. It is a shame to traverse
586
598
                else:
587
599
                    self.base_rev_id = self.revision_graph.find_unique_lca(
588
600
                                            *lcas)
589
 
                self._is_criss_cross = True
 
601
                sorted_lca_keys = self.revision_graph.find_merge_order(                
 
602
                    revisions[0], lcas)
 
603
                if self.base_rev_id == _mod_revision.NULL_REVISION:
 
604
                    self.base_rev_id = sorted_lca_keys[0]
 
605
                
590
606
            if self.base_rev_id == _mod_revision.NULL_REVISION:
591
607
                raise errors.UnrelatedBranches()
592
608
            if self._is_criss_cross:
593
609
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
594
610
                              ' help criss-cross.')
595
611
                trace.mutter('Criss-cross lcas: %r' % lcas)
596
 
                interesting_revision_ids = [self.base_rev_id]
597
 
                interesting_revision_ids.extend(lcas)
 
612
                if self.base_rev_id in lcas:
 
613
                    trace.mutter('Unable to find unique lca. '
 
614
                                 'Fallback %r as best option.' % self.base_rev_id)
 
615
                interesting_revision_ids = set(lcas)
 
616
                interesting_revision_ids.add(self.base_rev_id)
598
617
                interesting_trees = dict((t.get_revision_id(), t)
599
618
                    for t in self.this_branch.repository.revision_trees(
600
619
                        interesting_revision_ids))
601
620
                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)
 
621
                if self.base_rev_id in lcas:
 
622
                    self.base_tree = interesting_trees[self.base_rev_id]
 
623
                else:
 
624
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
605
625
                self._lca_trees = [interesting_trees[key]
606
626
                                   for key in sorted_lca_keys]
607
627
            else:
686
706
        return merge
687
707
 
688
708
    def do_merge(self):
689
 
        operation = OperationWithCleanups(self._do_merge_to)
 
709
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
690
710
        self.this_tree.lock_tree_write()
691
711
        operation.add_cleanup(self.this_tree.unlock)
692
712
        if self.base_tree is not None:
798
818
            warnings.warn("pb argument to Merge3Merger is deprecated")
799
819
 
800
820
    def do_merge(self):
801
 
        operation = OperationWithCleanups(self._do_merge)
 
821
        operation = cleanup.OperationWithCleanups(self._do_merge)
802
822
        self.this_tree.lock_tree_write()
803
823
        operation.add_cleanup(self.this_tree.unlock)
804
824
        self.base_tree.lock_read()
819
839
            pass
820
840
 
821
841
    def make_preview_transform(self):
822
 
        operation = OperationWithCleanups(self._make_preview_transform)
 
842
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
823
843
        self.base_tree.lock_read()
824
844
        operation.add_cleanup(self.base_tree.unlock)
825
845
        self.other_tree.lock_read()
856
876
        finally:
857
877
            child_pb.finished()
858
878
        self.fix_root()
 
879
        self._finish_computing_transform()
 
880
 
 
881
    def _finish_computing_transform(self):
 
882
        """Finalize the transform and report the changes.
 
883
 
 
884
        This is the second half of _compute_transform.
 
885
        """
859
886
        child_pb = ui.ui_factory.nested_progress_bar()
860
887
        try:
861
888
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
881
908
        """
882
909
        result = []
883
910
        iterator = self.other_tree.iter_changes(self.base_tree,
884
 
                include_unchanged=True, specific_files=self.interesting_files,
 
911
                specific_files=self.interesting_files,
885
912
                extra_trees=[self.this_tree])
886
913
        this_entries = dict((e.file_id, e) for p, e in
887
914
                            self.this_tree.iter_entries_by_dir(
1071
1098
                          ))
1072
1099
        return result
1073
1100
 
1074
 
 
1075
1101
    def fix_root(self):
1076
 
        try:
1077
 
            self.tt.final_kind(self.tt.root)
1078
 
        except errors.NoSuchFile:
 
1102
        if self.tt.final_kind(self.tt.root) is None:
1079
1103
            self.tt.cancel_deletion(self.tt.root)
1080
1104
        if self.tt.final_file_id(self.tt.root) is None:
1081
1105
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1090
1114
            # the other tree's root is a non-root in the current tree (as when
1091
1115
            # a previously unrelated branch is merged into another)
1092
1116
            return
1093
 
        try:
1094
 
            self.tt.final_kind(other_root)
 
1117
        if self.tt.final_kind(other_root) is not None:
1095
1118
            other_root_is_present = True
1096
 
        except errors.NoSuchFile:
 
1119
        else:
1097
1120
            # other_root doesn't have a physical representation. We still need
1098
1121
            # to move any references to the actual root of the tree.
1099
1122
            other_root_is_present = False
1100
1123
        # 'other_tree.inventory.root' is not present in this tree. We are
1101
1124
        # calling adjust_path for children which *want* to be present with a
1102
1125
        # correct place to go.
1103
 
        for thing, child in self.other_tree.inventory.root.children.iteritems():
 
1126
        for _, child in self.other_tree.inventory.root.children.iteritems():
1104
1127
            trans_id = self.tt.trans_id_file_id(child.file_id)
1105
1128
            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)
 
1129
                if self.tt.final_kind(trans_id) is not None:
1110
1130
                    # The item exist in the final tree and has a defined place
1111
1131
                    # to go already.
1112
1132
                    continue
1113
 
                except errors.NoSuchFile, e:
1114
 
                    pass
1115
1133
            # Move the item into the root
1116
 
            self.tt.adjust_path(self.tt.final_name(trans_id),
1117
 
                                self.tt.root, trans_id)
 
1134
            try:
 
1135
                final_name = self.tt.final_name(trans_id)
 
1136
            except errors.NoFinalPath:
 
1137
                # This file is not present anymore, ignore it.
 
1138
                continue
 
1139
            self.tt.adjust_path(final_name, self.tt.root, trans_id)
1118
1140
        if other_root_is_present:
1119
1141
            self.tt.cancel_creation(other_root)
1120
1142
            self.tt.cancel_versioning(other_root)
1393
1415
            self.tt.version_file(file_id, trans_id)
1394
1416
        # The merge has been performed, so the old contents should not be
1395
1417
        # retained.
1396
 
        try:
1397
 
            self.tt.delete_contents(trans_id)
1398
 
        except errors.NoSuchFile:
1399
 
            pass
 
1418
        self.tt.delete_contents(trans_id)
1400
1419
        return result
1401
1420
 
1402
1421
    def _default_other_winner_merge(self, merge_hook_params):
1455
1474
    def get_lines(self, tree, file_id):
1456
1475
        """Return the lines in a file, or an empty list."""
1457
1476
        if tree.has_id(file_id):
1458
 
            return tree.get_file(file_id).readlines()
 
1477
            return tree.get_file_lines(file_id)
1459
1478
        else:
1460
1479
            return []
1461
1480
 
1574
1593
        if winner == 'this' and file_status != "modified":
1575
1594
            return
1576
1595
        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:
 
1596
        if self.tt.final_kind(trans_id) != "file":
1581
1597
            return
1582
1598
        if winner == "this":
1583
1599
            executability = this_executable
1750
1766
            osutils.rmtree(temp_dir)
1751
1767
 
1752
1768
 
 
1769
class PathNotInTree(errors.BzrError):
 
1770
 
 
1771
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1772
 
 
1773
    def __init__(self, path, tree):
 
1774
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1775
 
 
1776
 
 
1777
class MergeIntoMerger(Merger):
 
1778
    """Merger that understands other_tree will be merged into a subdir.
 
1779
 
 
1780
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1781
    and RevisonTree objects, rather than using revision specs.
 
1782
    """
 
1783
 
 
1784
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1785
            source_subpath, other_rev_id=None):
 
1786
        """Create a new MergeIntoMerger object.
 
1787
 
 
1788
        source_subpath in other_tree will be effectively copied to
 
1789
        target_subdir in this_tree.
 
1790
 
 
1791
        :param this_tree: The tree that we will be merging into.
 
1792
        :param other_branch: The Branch we will be merging from.
 
1793
        :param other_tree: The RevisionTree object we want to merge.
 
1794
        :param target_subdir: The relative path where we want to merge
 
1795
            other_tree into this_tree
 
1796
        :param source_subpath: The relative path specifying the subtree of
 
1797
            other_tree to merge into this_tree.
 
1798
        """
 
1799
        # It is assumed that we are merging a tree that is not in our current
 
1800
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1801
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1802
                                _mod_revision.NULL_REVISION)
 
1803
        super(MergeIntoMerger, self).__init__(
 
1804
            this_branch=this_tree.branch,
 
1805
            this_tree=this_tree,
 
1806
            other_tree=other_tree,
 
1807
            base_tree=null_ancestor_tree,
 
1808
            )
 
1809
        self._target_subdir = target_subdir
 
1810
        self._source_subpath = source_subpath
 
1811
        self.other_branch = other_branch
 
1812
        if other_rev_id is None:
 
1813
            other_rev_id = other_tree.get_revision_id()
 
1814
        self.other_rev_id = self.other_basis = other_rev_id
 
1815
        self.base_is_ancestor = True
 
1816
        self.backup_files = True
 
1817
        self.merge_type = Merge3Merger
 
1818
        self.show_base = False
 
1819
        self.reprocess = False
 
1820
        self.interesting_ids = None
 
1821
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1822
              target_subdir=self._target_subdir,
 
1823
              source_subpath=self._source_subpath)
 
1824
        if self._source_subpath != '':
 
1825
            # If this isn't a partial merge make sure the revisions will be
 
1826
            # present.
 
1827
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1828
                self.other_basis)
 
1829
 
 
1830
    def set_pending(self):
 
1831
        if self._source_subpath != '':
 
1832
            return
 
1833
        Merger.set_pending(self)
 
1834
 
 
1835
 
 
1836
class _MergeTypeParameterizer(object):
 
1837
    """Wrap a merge-type class to provide extra parameters.
 
1838
    
 
1839
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1840
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1841
    the 'merge_type' member.  It is difficult override do_merge without
 
1842
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1843
    the extra parameters.
 
1844
    """
 
1845
 
 
1846
    def __init__(self, merge_type, **kwargs):
 
1847
        self._extra_kwargs = kwargs
 
1848
        self._merge_type = merge_type
 
1849
 
 
1850
    def __call__(self, *args, **kwargs):
 
1851
        kwargs.update(self._extra_kwargs)
 
1852
        return self._merge_type(*args, **kwargs)
 
1853
 
 
1854
    def __getattr__(self, name):
 
1855
        return getattr(self._merge_type, name)
 
1856
 
 
1857
 
 
1858
class MergeIntoMergeType(Merge3Merger):
 
1859
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1860
 
 
1861
    def __init__(self, *args, **kwargs):
 
1862
        """Initialize the merger object.
 
1863
 
 
1864
        :param args: See Merge3Merger.__init__'s args.
 
1865
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1866
            source_subpath and target_subdir.
 
1867
        :keyword source_subpath: The relative path specifying the subtree of
 
1868
            other_tree to merge into this_tree.
 
1869
        :keyword target_subdir: The relative path where we want to merge
 
1870
            other_tree into this_tree
 
1871
        """
 
1872
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1873
        # so we have have to hack in to get our extra parameters set.
 
1874
        self._source_subpath = kwargs.pop('source_subpath')
 
1875
        self._target_subdir = kwargs.pop('target_subdir')
 
1876
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1877
 
 
1878
    def _compute_transform(self):
 
1879
        child_pb = ui.ui_factory.nested_progress_bar()
 
1880
        try:
 
1881
            entries = self._entries_to_incorporate()
 
1882
            entries = list(entries)
 
1883
            for num, (entry, parent_id) in enumerate(entries):
 
1884
                child_pb.update('Preparing file merge', num, len(entries))
 
1885
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1886
                trans_id = transform.new_by_entry(self.tt, entry,
 
1887
                    parent_trans_id, self.other_tree)
 
1888
        finally:
 
1889
            child_pb.finished()
 
1890
        self._finish_computing_transform()
 
1891
 
 
1892
    def _entries_to_incorporate(self):
 
1893
        """Yields pairs of (inventory_entry, new_parent)."""
 
1894
        other_inv = self.other_tree.inventory
 
1895
        subdir_id = other_inv.path2id(self._source_subpath)
 
1896
        if subdir_id is None:
 
1897
            # XXX: The error would be clearer if it gave the URL of the source
 
1898
            # branch, but we don't have a reference to that here.
 
1899
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1900
        subdir = other_inv[subdir_id]
 
1901
        parent_in_target = osutils.dirname(self._target_subdir)
 
1902
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1903
        if target_id is None:
 
1904
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1905
        name_in_target = osutils.basename(self._target_subdir)
 
1906
        merge_into_root = subdir.copy()
 
1907
        merge_into_root.name = name_in_target
 
1908
        if merge_into_root.file_id in self.this_tree.inventory:
 
1909
            # Give the root a new file-id.
 
1910
            # This can happen fairly easily if the directory we are
 
1911
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1912
            # their root_id.  Users will expect this to Just Work, so we
 
1913
            # change the file-id here.
 
1914
            # Non-root file-ids could potentially conflict too.  That's really
 
1915
            # an edge case, so we don't do anything special for those.  We let
 
1916
            # them cause conflicts.
 
1917
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1918
        yield (merge_into_root, target_id)
 
1919
        if subdir.kind != 'directory':
 
1920
            # No children, so we are done.
 
1921
            return
 
1922
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1923
            parent_id = entry.parent_id
 
1924
            if parent_id == subdir.file_id:
 
1925
                # The root's parent ID has changed, so make sure children of
 
1926
                # the root refer to the new ID.
 
1927
                parent_id = merge_into_root.file_id
 
1928
            yield (entry, parent_id)
 
1929
 
 
1930
 
1753
1931
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1754
1932
                backup_files=False,
1755
1933
                merge_type=Merge3Merger,
1763
1941
                change_reporter=None):
1764
1942
    """Primary interface for merging.
1765
1943
 
1766
 
        typical use is probably
1767
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1768
 
                     branch.get_revision_tree(base_revision))'
1769
 
        """
 
1944
    Typical use is probably::
 
1945
 
 
1946
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1947
                    branch.get_revision_tree(base_revision))
 
1948
    """
1770
1949
    if this_tree is None:
1771
1950
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1772
1951
                              "parameter as of bzrlib version 0.8.")