/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

Merge 2.0 into 2.1 including fixes for #262450, #373898, #498409

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
import warnings
18
17
 
19
18
from bzrlib import (
20
19
    branch as _mod_branch,
27
26
    merge3,
28
27
    osutils,
29
28
    patiencediff,
 
29
    progress,
30
30
    revision as _mod_revision,
31
31
    textfile,
32
32
    trace,
93
93
        return ('not applicable', None)
94
94
 
95
95
 
96
 
class PerFileMerger(AbstractPerFileMerger):
97
 
    """Merge individual files when self.file_matches returns True.
98
 
 
99
 
    This class is intended to be subclassed.  The file_matches and
100
 
    merge_matching methods should be overridden with concrete implementations.
101
 
    """
102
 
 
103
 
    def file_matches(self, params):
104
 
        """Return True if merge_matching should be called on this file.
105
 
 
106
 
        Only called with merges of plain files with no clear winner.
107
 
 
108
 
        Subclasses must override this.
109
 
        """
110
 
        raise NotImplementedError(self.file_matches)
111
 
 
112
 
    def get_filename(self, params, tree):
113
 
        """Lookup the filename (i.e. basename, not path), given a Tree (e.g.
114
 
        self.merger.this_tree) and a MergeHookParams.
115
 
        """
116
 
        return osutils.basename(tree.id2path(params.file_id))
117
 
 
118
 
    def get_filepath(self, params, tree):
119
 
        """Calculate the path to the file in a tree.
120
 
 
121
 
        :param params: A MergeHookParams describing the file to merge
122
 
        :param tree: a Tree, e.g. self.merger.this_tree.
123
 
        """
124
 
        return tree.id2path(params.file_id)
125
 
 
126
 
    def merge_contents(self, params):
127
 
        """Merge the contents of a single file."""
128
 
        # Check whether this custom merge logic should be used.
129
 
        if (
130
 
            # OTHER is a straight winner, rely on default merge.
131
 
            params.winner == 'other' or
132
 
            # THIS and OTHER aren't both files.
133
 
            not params.is_file_merge() or
134
 
            # The filename doesn't match *.xml
135
 
            not self.file_matches(params)):
136
 
            return 'not_applicable', None
137
 
        return self.merge_matching(params)
138
 
 
139
 
    def merge_matching(self, params):
140
 
        """Merge the contents of a single file that has matched the criteria
141
 
        in PerFileMerger.merge_contents (is a conflict, is a file,
142
 
        self.file_matches is True).
143
 
 
144
 
        Subclasses must override this.
145
 
        """
146
 
        raise NotImplementedError(self.merge_matching)
147
 
 
148
 
 
149
 
class ConfigurableFileMerger(PerFileMerger):
 
96
class ConfigurableFileMerger(AbstractPerFileMerger):
150
97
    """Merge individual files when configured via a .conf file.
151
98
 
152
99
    This is a base class for concrete custom file merging logic. Concrete
175
122
        if self.name_prefix is None:
176
123
            raise ValueError("name_prefix must be set.")
177
124
 
178
 
    def file_matches(self, params):
 
125
    def filename_matches_config(self, params):
179
126
        """Check whether the file should call the merge hook.
180
127
 
181
128
        <name_prefix>_merge_files configuration variable is a list of files
195
142
                affected_files = self.default_files
196
143
            self.affected_files = affected_files
197
144
        if affected_files:
198
 
            filepath = self.get_filepath(params, self.merger.this_tree)
199
 
            if filepath in affected_files:
 
145
            filename = self.merger.this_tree.id2path(params.file_id)
 
146
            if filename in affected_files:
200
147
                return True
201
148
        return False
202
149
 
203
 
    def merge_matching(self, params):
 
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
204
163
        return self.merge_text(params)
205
164
 
206
165
    def merge_text(self, params):
278
237
        self.interesting_files = None
279
238
        self.show_base = False
280
239
        self.reprocess = False
281
 
        if pb is not None:
282
 
            warnings.warn("pb parameter to Merger() is deprecated and ignored")
 
240
        if pb is None:
 
241
            pb = progress.DummyProgress()
 
242
        self._pb = pb
283
243
        self.pp = None
284
244
        self.recurse = recurse
285
245
        self.change_reporter = change_reporter
634
594
                  'other_tree': self.other_tree,
635
595
                  'interesting_ids': self.interesting_ids,
636
596
                  'interesting_files': self.interesting_files,
637
 
                  'this_branch': self.this_branch,
 
597
                  'pp': self.pp, 'this_branch': self.this_branch,
638
598
                  'do_merge': False}
639
599
        if self.merge_type.requires_base:
640
600
            kwargs['base_tree'] = self.base_tree
658
618
        if self._is_criss_cross and getattr(self.merge_type,
659
619
                                            'supports_lca_trees', False):
660
620
            kwargs['lca_trees'] = self._lca_trees
661
 
        return self.merge_type(pb=None,
 
621
        return self.merge_type(pb=self._pb,
662
622
                               change_reporter=self.change_reporter,
663
623
                               **kwargs)
664
624
 
736
696
 
737
697
    def __init__(self, working_tree, this_tree, base_tree, other_tree,
738
698
                 interesting_ids=None, reprocess=False, show_base=False,
739
 
                 pb=None, pp=None, change_reporter=None,
 
699
                 pb=progress.DummyProgress(), pp=None, change_reporter=None,
740
700
                 interesting_files=None, do_merge=True,
741
701
                 cherrypick=False, lca_trees=None, this_branch=None):
742
702
        """Initialize the merger object and perform the merge.
745
705
        :param this_tree: The local tree in the merge operation
746
706
        :param base_tree: The common tree in the merge operation
747
707
        :param other_tree: The other tree to merge changes from
748
 
        :param this_branch: The branch associated with this_tree.  Defaults to
749
 
            this_tree.branch if not supplied.
 
708
        :param this_branch: The branch associated with this_tree
750
709
        :param interesting_ids: The file_ids of files that should be
751
710
            participate in the merge.  May not be combined with
752
711
            interesting_files.
753
712
        :param: reprocess If True, perform conflict-reduction processing.
754
713
        :param show_base: If True, show the base revision in text conflicts.
755
714
            (incompatible with reprocess)
756
 
        :param pb: ignored
 
715
        :param pb: A Progress bar
757
716
        :param pp: A ProgressPhase object
758
717
        :param change_reporter: An object that should report changes made
759
718
        :param interesting_files: The tree-relative paths of files that should
770
729
        if interesting_files is not None and interesting_ids is not None:
771
730
            raise ValueError(
772
731
                'specify either interesting_ids or interesting_files')
773
 
        if this_branch is None:
774
 
            this_branch = this_tree.branch
775
732
        self.interesting_ids = interesting_ids
776
733
        self.interesting_files = interesting_files
777
734
        self.this_tree = working_tree
788
745
        # making sure we haven't missed any corner cases.
789
746
        # if lca_trees is None:
790
747
        #     self._lca_trees = [self.base_tree]
 
748
        self.pb = pb
 
749
        self.pp = pp
791
750
        self.change_reporter = change_reporter
792
751
        self.cherrypick = cherrypick
 
752
        if self.pp is None:
 
753
            self.pp = progress.ProgressPhase("Merge phase", 3, self.pb)
793
754
        if do_merge:
794
755
            self.do_merge()
795
 
        if pp is not None:
796
 
            warnings.warn("pp argument to Merge3Merger is deprecated")
797
 
        if pb is not None:
798
 
            warnings.warn("pb argument to Merge3Merger is deprecated")
799
756
 
800
757
    def do_merge(self):
801
758
        operation = OperationWithCleanups(self._do_merge)
 
759
        operation.add_cleanup(self.pb.clear)
802
760
        self.this_tree.lock_tree_write()
803
761
        operation.add_cleanup(self.this_tree.unlock)
804
762
        self.base_tree.lock_read()
808
766
        operation.run()
809
767
 
810
768
    def _do_merge(self, operation):
811
 
        self.tt = transform.TreeTransform(self.this_tree, None)
 
769
        self.tt = transform.TreeTransform(self.this_tree, self.pb)
812
770
        operation.add_cleanup(self.tt.finalize)
 
771
        self.pp.next_phase()
813
772
        self._compute_transform()
 
773
        self.pp.next_phase()
814
774
        results = self.tt.apply(no_conflicts=True)
815
775
        self.write_modified(results)
816
776
        try:
820
780
 
821
781
    def make_preview_transform(self):
822
782
        operation = OperationWithCleanups(self._make_preview_transform)
 
783
        operation.add_cleanup(self.pb.clear)
823
784
        self.base_tree.lock_read()
824
785
        operation.add_cleanup(self.base_tree.unlock)
825
786
        self.other_tree.lock_read()
828
789
 
829
790
    def _make_preview_transform(self):
830
791
        self.tt = transform.TransformPreview(self.this_tree)
 
792
        self.pp.next_phase()
831
793
        self._compute_transform()
 
794
        self.pp.next_phase()
832
795
        return self.tt
833
796
 
834
797
    def _compute_transform(self):
856
819
        finally:
857
820
            child_pb.finished()
858
821
        self.fix_root()
 
822
        self.pp.next_phase()
859
823
        child_pb = ui.ui_factory.nested_progress_bar()
860
824
        try:
861
825
            fs_conflicts = transform.resolve_conflicts(self.tt, child_pb,
1059
1023
                        continue
1060
1024
                else:
1061
1025
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
 
1026
                # XXX: We need to handle kind == 'symlink'
1062
1027
 
1063
1028
            # If we have gotten this far, that means something has changed
1064
1029
            result.append((file_id, content_changed,
1093
1058
        try:
1094
1059
            self.tt.final_kind(other_root)
1095
1060
            other_root_is_present = True
1096
 
        except errors.NoSuchFile:
 
1061
        except NoSuchFile:
1097
1062
            # other_root doesn't have a physical representation. We still need
1098
1063
            # to move any references to the actual root of the tree.
1099
1064
            other_root_is_present = False
1170
1135
 
1171
1136
    @staticmethod
1172
1137
    def _three_way(base, other, this):
 
1138
        #if base == other, either they all agree, or only THIS has changed.
1173
1139
        if base == other:
1174
 
            # if 'base == other', either they all agree, or only 'this' has
1175
 
            # changed.
1176
1140
            return 'this'
1177
1141
        elif this not in (base, other):
1178
 
            # 'this' is neither 'base' nor 'other', so both sides changed
1179
1142
            return 'conflict'
 
1143
        # "Ambiguous clean merge" -- both sides have made the same change.
1180
1144
        elif this == other:
1181
 
            # "Ambiguous clean merge" -- both sides have made the same change.
1182
1145
            return "this"
 
1146
        # this == base: only other has changed.
1183
1147
        else:
1184
 
            # this == base: only other has changed.
1185
1148
            return "other"
1186
1149
 
1187
1150
    @staticmethod
1231
1194
                # only has an lca value
1232
1195
                return 'other'
1233
1196
 
1234
 
        # At this point, the lcas disagree, and the tip disagree
 
1197
        # At this point, the lcas disagree, and the tips disagree
1235
1198
        return 'conflict'
1236
1199
 
1237
1200
    @staticmethod
1238
 
    @deprecated_method(deprecated_in((2, 2, 0)))
1239
1201
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1240
1202
        """Do a three-way test on a scalar.
1241
1203
        Return "this", "other" or "conflict", depending whether a value wins.
1291
1253
                parent_id_winner = "other"
1292
1254
        if name_winner == "this" and parent_id_winner == "this":
1293
1255
            return
1294
 
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
1295
 
            # Creating helpers (.OTHER or .THIS) here cause problems down the
1296
 
            # road if a ContentConflict needs to be created so we should not do
1297
 
            # that
1298
 
            trans_id = self.tt.trans_id_file_id(file_id)
1299
 
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
1300
 
                                        this_parent, this_name,
1301
 
                                        other_parent, other_name))
 
1256
        if name_winner == "conflict":
 
1257
            trans_id = self.tt.trans_id_file_id(file_id)
 
1258
            self._raw_conflicts.append(('name conflict', trans_id,
 
1259
                                        this_name, other_name))
 
1260
        if parent_id_winner == "conflict":
 
1261
            trans_id = self.tt.trans_id_file_id(file_id)
 
1262
            self._raw_conflicts.append(('parent conflict', trans_id,
 
1263
                                        this_parent, other_parent))
1302
1264
        if other_name is None:
1303
1265
            # it doesn't matter whether the result was 'other' or
1304
1266
            # 'conflict'-- if there's no 'other', we leave it alone.
1305
1267
            return
 
1268
        # if we get here, name_winner and parent_winner are set to safe values.
 
1269
        trans_id = self.tt.trans_id_file_id(file_id)
1306
1270
        parent_id = parents[self.winner_idx[parent_id_winner]]
1307
1271
        if parent_id is not None:
1308
 
            # if we get here, name_winner and parent_winner are set to safe
1309
 
            # values.
 
1272
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
1310
1273
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1311
 
                                self.tt.trans_id_file_id(parent_id),
1312
 
                                self.tt.trans_id_file_id(file_id))
 
1274
                                parent_trans_id, trans_id)
1313
1275
 
1314
1276
    def _do_merge_contents(self, file_id):
1315
1277
        """Performs a merge on file_id contents."""
1594
1556
 
1595
1557
    def cook_conflicts(self, fs_conflicts):
1596
1558
        """Convert all conflicts into a form that doesn't depend on trans_id"""
 
1559
        name_conflicts = {}
1597
1560
        self.cooked_conflicts.extend(transform.cook_conflicts(
1598
1561
                fs_conflicts, self.tt))
1599
1562
        fp = transform.FinalPaths(self.tt)
1600
1563
        for conflict in self._raw_conflicts:
1601
1564
            conflict_type = conflict[0]
1602
 
            if conflict_type == 'path conflict':
1603
 
                (trans_id, file_id,
1604
 
                this_parent, this_name,
1605
 
                other_parent, other_name) = conflict[1:]
1606
 
                if this_parent is None or this_name is None:
1607
 
                    this_path = '<deleted>'
1608
 
                else:
1609
 
                    parent_path =  fp.get_path(
1610
 
                        self.tt.trans_id_file_id(this_parent))
1611
 
                    this_path = osutils.pathjoin(parent_path, this_name)
1612
 
                if other_parent is None or other_name is None:
1613
 
                    other_path = '<deleted>'
1614
 
                else:
1615
 
                    parent_path =  fp.get_path(
1616
 
                        self.tt.trans_id_file_id(other_parent))
1617
 
                    other_path = osutils.pathjoin(parent_path, other_name)
1618
 
                c = _mod_conflicts.Conflict.factory(
1619
 
                    'path conflict', path=this_path,
1620
 
                    conflict_path=other_path,
1621
 
                    file_id=file_id)
1622
 
            elif conflict_type == 'contents conflict':
 
1565
            if conflict_type in ('name conflict', 'parent conflict'):
 
1566
                trans_id = conflict[1]
 
1567
                conflict_args = conflict[2:]
 
1568
                if trans_id not in name_conflicts:
 
1569
                    name_conflicts[trans_id] = {}
 
1570
                transform.unique_add(name_conflicts[trans_id], conflict_type,
 
1571
                                     conflict_args)
 
1572
            if conflict_type == 'contents conflict':
1623
1573
                for trans_id in conflict[1]:
1624
1574
                    file_id = self.tt.final_file_id(trans_id)
1625
1575
                    if file_id is not None:
1631
1581
                        break
1632
1582
                c = _mod_conflicts.Conflict.factory(conflict_type,
1633
1583
                                                    path=path, file_id=file_id)
1634
 
            elif conflict_type == 'text conflict':
 
1584
                self.cooked_conflicts.append(c)
 
1585
            if conflict_type == 'text conflict':
1635
1586
                trans_id = conflict[1]
1636
1587
                path = fp.get_path(trans_id)
1637
1588
                file_id = self.tt.final_file_id(trans_id)
1638
1589
                c = _mod_conflicts.Conflict.factory(conflict_type,
1639
1590
                                                    path=path, file_id=file_id)
 
1591
                self.cooked_conflicts.append(c)
 
1592
 
 
1593
        for trans_id, conflicts in name_conflicts.iteritems():
 
1594
            try:
 
1595
                this_parent, other_parent = conflicts['parent conflict']
 
1596
                if this_parent == other_parent:
 
1597
                    raise AssertionError()
 
1598
            except KeyError:
 
1599
                this_parent = other_parent = \
 
1600
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
 
1601
            try:
 
1602
                this_name, other_name = conflicts['name conflict']
 
1603
                if this_name == other_name:
 
1604
                    raise AssertionError()
 
1605
            except KeyError:
 
1606
                this_name = other_name = self.tt.final_name(trans_id)
 
1607
            other_path = fp.get_path(trans_id)
 
1608
            if this_parent is not None and this_name is not None:
 
1609
                this_parent_path = \
 
1610
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
 
1611
                this_path = osutils.pathjoin(this_parent_path, this_name)
1640
1612
            else:
1641
 
                raise AssertionError('bad conflict type: %r' % (conflict,))
 
1613
                this_path = "<deleted>"
 
1614
            file_id = self.tt.final_file_id(trans_id)
 
1615
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
 
1616
                                                conflict_path=other_path,
 
1617
                                                file_id=file_id)
1642
1618
            self.cooked_conflicts.append(c)
1643
1619
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1644
1620
 
1759
1735
                other_rev_id=None,
1760
1736
                interesting_files=None,
1761
1737
                this_tree=None,
1762
 
                pb=None,
 
1738
                pb=progress.DummyProgress(),
1763
1739
                change_reporter=None):
1764
1740
    """Primary interface for merging.
1765
1741