/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: Martin Pool
  • Date: 2011-05-20 14:46:02 UTC
  • mto: This revision was merged to the branch mainline in revision 5923.
  • Revision ID: mbp@canonical.com-20110520144602-bqli0t6dj01gl0pv
Various pyflakes import fixes.

Some modules were used for subclassing or at module load time, so there is no
point loading them lazily.

Some were not imported when they should be.

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):
85
92
        """Attempt to merge the contents of a single file.
86
93
        
87
94
        :param merge_params: A bzrlib.merge.MergeHookParams
88
 
        :return : A tuple of (status, chunks), where status is one of
 
95
        :return: A tuple of (status, chunks), where status is one of
89
96
            'not_applicable', 'success', 'conflicted', or 'delete'.  If status
90
97
            is 'success' or 'conflicted', then chunks should be an iterable of
91
98
            strings for the new file contents.
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)
576
580
            elif len(lcas) == 1:
577
581
                self.base_rev_id = list(lcas)[0]
578
582
            else: # len(lcas) > 1
 
583
                self._is_criss_cross = True
579
584
                if len(lcas) > 2:
580
585
                    # find_unique_lca can only handle 2 nodes, so we have to
581
586
                    # start back at the beginning. It is a shame to traverse
586
591
                else:
587
592
                    self.base_rev_id = self.revision_graph.find_unique_lca(
588
593
                                            *lcas)
589
 
                self._is_criss_cross = True
 
594
                sorted_lca_keys = self.revision_graph.find_merge_order(                
 
595
                    revisions[0], lcas)
 
596
                if self.base_rev_id == _mod_revision.NULL_REVISION:
 
597
                    self.base_rev_id = sorted_lca_keys[0]
 
598
                
590
599
            if self.base_rev_id == _mod_revision.NULL_REVISION:
591
600
                raise errors.UnrelatedBranches()
592
601
            if self._is_criss_cross:
593
602
                trace.warning('Warning: criss-cross merge encountered.  See bzr'
594
603
                              ' help criss-cross.')
595
604
                trace.mutter('Criss-cross lcas: %r' % lcas)
596
 
                interesting_revision_ids = [self.base_rev_id]
597
 
                interesting_revision_ids.extend(lcas)
 
605
                if self.base_rev_id in lcas:
 
606
                    trace.mutter('Unable to find unique lca. '
 
607
                                 'Fallback %r as best option.' % self.base_rev_id)
 
608
                interesting_revision_ids = set(lcas)
 
609
                interesting_revision_ids.add(self.base_rev_id)
598
610
                interesting_trees = dict((t.get_revision_id(), t)
599
611
                    for t in self.this_branch.repository.revision_trees(
600
612
                        interesting_revision_ids))
601
613
                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)
 
614
                if self.base_rev_id in lcas:
 
615
                    self.base_tree = interesting_trees[self.base_rev_id]
 
616
                else:
 
617
                    self.base_tree = interesting_trees.pop(self.base_rev_id)
605
618
                self._lca_trees = [interesting_trees[key]
606
619
                                   for key in sorted_lca_keys]
607
620
            else:
686
699
        return merge
687
700
 
688
701
    def do_merge(self):
689
 
        operation = OperationWithCleanups(self._do_merge_to)
 
702
        operation = cleanup.OperationWithCleanups(self._do_merge_to)
690
703
        self.this_tree.lock_tree_write()
691
704
        operation.add_cleanup(self.this_tree.unlock)
692
705
        if self.base_tree is not None:
798
811
            warnings.warn("pb argument to Merge3Merger is deprecated")
799
812
 
800
813
    def do_merge(self):
801
 
        operation = OperationWithCleanups(self._do_merge)
 
814
        operation = cleanup.OperationWithCleanups(self._do_merge)
802
815
        self.this_tree.lock_tree_write()
803
816
        operation.add_cleanup(self.this_tree.unlock)
804
817
        self.base_tree.lock_read()
819
832
            pass
820
833
 
821
834
    def make_preview_transform(self):
822
 
        operation = OperationWithCleanups(self._make_preview_transform)
 
835
        operation = cleanup.OperationWithCleanups(self._make_preview_transform)
823
836
        self.base_tree.lock_read()
824
837
        operation.add_cleanup(self.base_tree.unlock)
825
838
        self.other_tree.lock_read()
856
869
        finally:
857
870
            child_pb.finished()
858
871
        self.fix_root()
 
872
        self._finish_computing_transform()
 
873
 
 
874
    def _finish_computing_transform(self):
 
875
        """Finalize the transform and report the changes.
 
876
 
 
877
        This is the second half of _compute_transform.
 
878
        """
859
879
        child_pb = ui.ui_factory.nested_progress_bar()
860
880
        try:
861
881
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
881
901
        """
882
902
        result = []
883
903
        iterator = self.other_tree.iter_changes(self.base_tree,
884
 
                include_unchanged=True, specific_files=self.interesting_files,
 
904
                specific_files=self.interesting_files,
885
905
                extra_trees=[self.this_tree])
886
906
        this_entries = dict((e.file_id, e) for p, e in
887
907
                            self.this_tree.iter_entries_by_dir(
913
933
        it then compares with THIS and BASE.
914
934
 
915
935
        For the multi-valued entries, the format will be (BASE, [lca1, lca2])
916
 
        :return: [(file_id, changed, parents, names, executable)]
917
 
            file_id     Simple file_id of the entry
918
 
            changed     Boolean, True if the kind or contents changed
919
 
                        else False
920
 
            parents     ((base, [parent_id, in, lcas]), parent_id_other,
921
 
                         parent_id_this)
922
 
            names       ((base, [name, in, lcas]), name_in_other, name_in_this)
923
 
            executable  ((base, [exec, in, lcas]), exec_in_other, exec_in_this)
 
936
 
 
937
        :return: [(file_id, changed, parents, names, executable)], where:
 
938
 
 
939
            * file_id: Simple file_id of the entry
 
940
            * changed: Boolean, True if the kind or contents changed else False
 
941
            * parents: ((base, [parent_id, in, lcas]), parent_id_other,
 
942
                        parent_id_this)
 
943
            * names:   ((base, [name, in, lcas]), name_in_other, name_in_this)
 
944
            * executable: ((base, [exec, in, lcas]), exec_in_other,
 
945
                        exec_in_this)
924
946
        """
925
947
        if self.interesting_files is not None:
926
948
            lookup_trees = [self.this_tree, self.base_tree]
1071
1093
                          ))
1072
1094
        return result
1073
1095
 
1074
 
 
1075
1096
    def fix_root(self):
1076
 
        try:
1077
 
            self.tt.final_kind(self.tt.root)
1078
 
        except errors.NoSuchFile:
 
1097
        if self.tt.final_kind(self.tt.root) is None:
1079
1098
            self.tt.cancel_deletion(self.tt.root)
1080
1099
        if self.tt.final_file_id(self.tt.root) is None:
1081
1100
            self.tt.version_file(self.tt.tree_file_id(self.tt.root),
1090
1109
            # the other tree's root is a non-root in the current tree (as when
1091
1110
            # a previously unrelated branch is merged into another)
1092
1111
            return
1093
 
        try:
1094
 
            self.tt.final_kind(other_root)
 
1112
        if self.tt.final_kind(other_root) is not None:
1095
1113
            other_root_is_present = True
1096
 
        except errors.NoSuchFile:
 
1114
        else:
1097
1115
            # other_root doesn't have a physical representation. We still need
1098
1116
            # to move any references to the actual root of the tree.
1099
1117
            other_root_is_present = False
1100
1118
        # 'other_tree.inventory.root' is not present in this tree. We are
1101
1119
        # calling adjust_path for children which *want* to be present with a
1102
1120
        # correct place to go.
1103
 
        for thing, child in self.other_tree.inventory.root.children.iteritems():
 
1121
        for _, child in self.other_tree.inventory.root.children.iteritems():
1104
1122
            trans_id = self.tt.trans_id_file_id(child.file_id)
1105
1123
            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)
 
1124
                if self.tt.final_kind(trans_id) is not None:
1110
1125
                    # The item exist in the final tree and has a defined place
1111
1126
                    # to go already.
1112
1127
                    continue
1113
 
                except errors.NoSuchFile, e:
1114
 
                    pass
1115
1128
            # Move the item into the root
1116
 
            self.tt.adjust_path(self.tt.final_name(trans_id),
1117
 
                                self.tt.root, trans_id)
 
1129
            try:
 
1130
                final_name = self.tt.final_name(trans_id)
 
1131
            except errors.NoFinalPath:
 
1132
                # This file is not present anymore, ignore it.
 
1133
                continue
 
1134
            self.tt.adjust_path(final_name, self.tt.root, trans_id)
1118
1135
        if other_root_is_present:
1119
1136
            self.tt.cancel_creation(other_root)
1120
1137
            self.tt.cancel_versioning(other_root)
1393
1410
            self.tt.version_file(file_id, trans_id)
1394
1411
        # The merge has been performed, so the old contents should not be
1395
1412
        # retained.
1396
 
        try:
1397
 
            self.tt.delete_contents(trans_id)
1398
 
        except errors.NoSuchFile:
1399
 
            pass
 
1413
        self.tt.delete_contents(trans_id)
1400
1414
        return result
1401
1415
 
1402
1416
    def _default_other_winner_merge(self, merge_hook_params):
1455
1469
    def get_lines(self, tree, file_id):
1456
1470
        """Return the lines in a file, or an empty list."""
1457
1471
        if tree.has_id(file_id):
1458
 
            return tree.get_file(file_id).readlines()
 
1472
            return tree.get_file_lines(file_id)
1459
1473
        else:
1460
1474
            return []
1461
1475
 
1574
1588
        if winner == 'this' and file_status != "modified":
1575
1589
            return
1576
1590
        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:
 
1591
        if self.tt.final_kind(trans_id) != "file":
1581
1592
            return
1582
1593
        if winner == "this":
1583
1594
            executability = this_executable
1750
1761
            osutils.rmtree(temp_dir)
1751
1762
 
1752
1763
 
 
1764
class PathNotInTree(errors.BzrError):
 
1765
 
 
1766
    _fmt = """Merge-into failed because %(tree)s does not contain %(path)s."""
 
1767
 
 
1768
    def __init__(self, path, tree):
 
1769
        errors.BzrError.__init__(self, path=path, tree=tree)
 
1770
 
 
1771
 
 
1772
class MergeIntoMerger(Merger):
 
1773
    """Merger that understands other_tree will be merged into a subdir.
 
1774
 
 
1775
    This also changes the Merger api so that it uses real Branch, revision_id,
 
1776
    and RevisonTree objects, rather than using revision specs.
 
1777
    """
 
1778
 
 
1779
    def __init__(self, this_tree, other_branch, other_tree, target_subdir,
 
1780
            source_subpath, other_rev_id=None):
 
1781
        """Create a new MergeIntoMerger object.
 
1782
 
 
1783
        source_subpath in other_tree will be effectively copied to
 
1784
        target_subdir in this_tree.
 
1785
 
 
1786
        :param this_tree: The tree that we will be merging into.
 
1787
        :param other_branch: The Branch we will be merging from.
 
1788
        :param other_tree: The RevisionTree object we want to merge.
 
1789
        :param target_subdir: The relative path where we want to merge
 
1790
            other_tree into this_tree
 
1791
        :param source_subpath: The relative path specifying the subtree of
 
1792
            other_tree to merge into this_tree.
 
1793
        """
 
1794
        # It is assumed that we are merging a tree that is not in our current
 
1795
        # ancestry, which means we are using the "EmptyTree" as our basis.
 
1796
        null_ancestor_tree = this_tree.branch.repository.revision_tree(
 
1797
                                _mod_revision.NULL_REVISION)
 
1798
        super(MergeIntoMerger, self).__init__(
 
1799
            this_branch=this_tree.branch,
 
1800
            this_tree=this_tree,
 
1801
            other_tree=other_tree,
 
1802
            base_tree=null_ancestor_tree,
 
1803
            )
 
1804
        self._target_subdir = target_subdir
 
1805
        self._source_subpath = source_subpath
 
1806
        self.other_branch = other_branch
 
1807
        if other_rev_id is None:
 
1808
            other_rev_id = other_tree.get_revision_id()
 
1809
        self.other_rev_id = self.other_basis = other_rev_id
 
1810
        self.base_is_ancestor = True
 
1811
        self.backup_files = True
 
1812
        self.merge_type = Merge3Merger
 
1813
        self.show_base = False
 
1814
        self.reprocess = False
 
1815
        self.interesting_ids = None
 
1816
        self.merge_type = _MergeTypeParameterizer(MergeIntoMergeType,
 
1817
              target_subdir=self._target_subdir,
 
1818
              source_subpath=self._source_subpath)
 
1819
        if self._source_subpath != '':
 
1820
            # If this isn't a partial merge make sure the revisions will be
 
1821
            # present.
 
1822
            self._maybe_fetch(self.other_branch, self.this_branch,
 
1823
                self.other_basis)
 
1824
 
 
1825
    def set_pending(self):
 
1826
        if self._source_subpath != '':
 
1827
            return
 
1828
        Merger.set_pending(self)
 
1829
 
 
1830
 
 
1831
class _MergeTypeParameterizer(object):
 
1832
    """Wrap a merge-type class to provide extra parameters.
 
1833
    
 
1834
    This is hack used by MergeIntoMerger to pass some extra parameters to its
 
1835
    merge_type.  Merger.do_merge() sets up its own set of parameters to pass to
 
1836
    the 'merge_type' member.  It is difficult override do_merge without
 
1837
    re-writing the whole thing, so instead we create a wrapper which will pass
 
1838
    the extra parameters.
 
1839
    """
 
1840
 
 
1841
    def __init__(self, merge_type, **kwargs):
 
1842
        self._extra_kwargs = kwargs
 
1843
        self._merge_type = merge_type
 
1844
 
 
1845
    def __call__(self, *args, **kwargs):
 
1846
        kwargs.update(self._extra_kwargs)
 
1847
        return self._merge_type(*args, **kwargs)
 
1848
 
 
1849
    def __getattr__(self, name):
 
1850
        return getattr(self._merge_type, name)
 
1851
 
 
1852
 
 
1853
class MergeIntoMergeType(Merge3Merger):
 
1854
    """Merger that incorporates a tree (or part of a tree) into another."""
 
1855
 
 
1856
    def __init__(self, *args, **kwargs):
 
1857
        """Initialize the merger object.
 
1858
 
 
1859
        :param args: See Merge3Merger.__init__'s args.
 
1860
        :param kwargs: See Merge3Merger.__init__'s keyword args, except for
 
1861
            source_subpath and target_subdir.
 
1862
        :keyword source_subpath: The relative path specifying the subtree of
 
1863
            other_tree to merge into this_tree.
 
1864
        :keyword target_subdir: The relative path where we want to merge
 
1865
            other_tree into this_tree
 
1866
        """
 
1867
        # All of the interesting work happens during Merge3Merger.__init__(),
 
1868
        # so we have have to hack in to get our extra parameters set.
 
1869
        self._source_subpath = kwargs.pop('source_subpath')
 
1870
        self._target_subdir = kwargs.pop('target_subdir')
 
1871
        super(MergeIntoMergeType, self).__init__(*args, **kwargs)
 
1872
 
 
1873
    def _compute_transform(self):
 
1874
        child_pb = ui.ui_factory.nested_progress_bar()
 
1875
        try:
 
1876
            entries = self._entries_to_incorporate()
 
1877
            entries = list(entries)
 
1878
            for num, (entry, parent_id) in enumerate(entries):
 
1879
                child_pb.update('Preparing file merge', num, len(entries))
 
1880
                parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1881
                trans_id = transform.new_by_entry(self.tt, entry,
 
1882
                    parent_trans_id, self.other_tree)
 
1883
        finally:
 
1884
            child_pb.finished()
 
1885
        self._finish_computing_transform()
 
1886
 
 
1887
    def _entries_to_incorporate(self):
 
1888
        """Yields pairs of (inventory_entry, new_parent)."""
 
1889
        other_inv = self.other_tree.inventory
 
1890
        subdir_id = other_inv.path2id(self._source_subpath)
 
1891
        if subdir_id is None:
 
1892
            # XXX: The error would be clearer if it gave the URL of the source
 
1893
            # branch, but we don't have a reference to that here.
 
1894
            raise PathNotInTree(self._source_subpath, "Source tree")
 
1895
        subdir = other_inv[subdir_id]
 
1896
        parent_in_target = osutils.dirname(self._target_subdir)
 
1897
        target_id = self.this_tree.inventory.path2id(parent_in_target)
 
1898
        if target_id is None:
 
1899
            raise PathNotInTree(self._target_subdir, "Target tree")
 
1900
        name_in_target = osutils.basename(self._target_subdir)
 
1901
        merge_into_root = subdir.copy()
 
1902
        merge_into_root.name = name_in_target
 
1903
        if merge_into_root.file_id in self.this_tree.inventory:
 
1904
            # Give the root a new file-id.
 
1905
            # This can happen fairly easily if the directory we are
 
1906
            # incorporating is the root, and both trees have 'TREE_ROOT' as
 
1907
            # their root_id.  Users will expect this to Just Work, so we
 
1908
            # change the file-id here.
 
1909
            # Non-root file-ids could potentially conflict too.  That's really
 
1910
            # an edge case, so we don't do anything special for those.  We let
 
1911
            # them cause conflicts.
 
1912
            merge_into_root.file_id = generate_ids.gen_file_id(name_in_target)
 
1913
        yield (merge_into_root, target_id)
 
1914
        if subdir.kind != 'directory':
 
1915
            # No children, so we are done.
 
1916
            return
 
1917
        for ignored_path, entry in other_inv.iter_entries_by_dir(subdir_id):
 
1918
            parent_id = entry.parent_id
 
1919
            if parent_id == subdir.file_id:
 
1920
                # The root's parent ID has changed, so make sure children of
 
1921
                # the root refer to the new ID.
 
1922
                parent_id = merge_into_root.file_id
 
1923
            yield (entry, parent_id)
 
1924
 
 
1925
 
1753
1926
def merge_inner(this_branch, other_tree, base_tree, ignore_zero=False,
1754
1927
                backup_files=False,
1755
1928
                merge_type=Merge3Merger,
1763
1936
                change_reporter=None):
1764
1937
    """Primary interface for merging.
1765
1938
 
1766
 
        typical use is probably
1767
 
        'merge_inner(branch, branch.get_revision_tree(other_revision),
1768
 
                     branch.get_revision_tree(base_revision))'
1769
 
        """
 
1939
    Typical use is probably::
 
1940
 
 
1941
        merge_inner(branch, branch.get_revision_tree(other_revision),
 
1942
                    branch.get_revision_tree(base_revision))
 
1943
    """
1770
1944
    if this_tree is None:
1771
1945
        raise errors.BzrError("bzrlib.merge.merge_inner requires a this_tree "
1772
1946
                              "parameter as of bzrlib version 0.8.")
2229
2403
class _PlanLCAMerge(_PlanMergeBase):
2230
2404
    """
2231
2405
    This merge algorithm differs from _PlanMerge in that:
 
2406
 
2232
2407
    1. comparisons are done against LCAs only
2233
2408
    2. cases where a contested line is new versus one LCA but old versus
2234
2409
       another are marked as conflicts, by emitting the line as conflicted-a
2275
2450
 
2276
2451
        If a line is killed and new, this indicates that the two merge
2277
2452
        revisions contain differing conflict resolutions.
 
2453
 
2278
2454
        :param revision_id: The id of the revision in which the lines are
2279
2455
            unique
2280
2456
        :param unique_line_numbers: The line numbers of unique lines.
2281
 
        :return a tuple of (new_this, killed_other):
 
2457
        :return: a tuple of (new_this, killed_other)
2282
2458
        """
2283
2459
        new = set()
2284
2460
        killed = set()