/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
  • Date: 2010-05-03 20:57:39 UTC
  • mto: This revision was merged to the branch mainline in revision 5204.
  • Revision ID: gzlist@googlemail.com-20100503205739-n326zdvevv0rmruh
Retain original stack and error message when translating to ValueError in bencode

Show diffs side-by-side

added added

removed removed

Lines of Context:
93
93
        return ('not applicable', None)
94
94
 
95
95
 
96
 
class ConfigurableFileMerger(AbstractPerFileMerger):
 
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):
97
150
    """Merge individual files when configured via a .conf file.
98
151
 
99
152
    This is a base class for concrete custom file merging logic. Concrete
122
175
        if self.name_prefix is None:
123
176
            raise ValueError("name_prefix must be set.")
124
177
 
125
 
    def filename_matches_config(self, params):
 
178
    def file_matches(self, params):
126
179
        """Check whether the file should call the merge hook.
127
180
 
128
181
        <name_prefix>_merge_files configuration variable is a list of files
130
183
        """
131
184
        affected_files = self.affected_files
132
185
        if affected_files is None:
133
 
            config = self.merger.this_tree.branch.get_config()
 
186
            config = self.merger.this_branch.get_config()
134
187
            # Until bzr provides a better policy for caching the config, we
135
188
            # just add the part we're interested in to the params to avoid
136
189
            # reading the config files repeatedly (bazaar.conf, location.conf,
142
195
                affected_files = self.default_files
143
196
            self.affected_files = affected_files
144
197
        if affected_files:
145
 
            filename = self.merger.this_tree.id2path(params.file_id)
146
 
            if filename in affected_files:
 
198
            filepath = self.get_filepath(params, self.merger.this_tree)
 
199
            if filepath in affected_files:
147
200
                return True
148
201
        return False
149
202
 
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
 
203
    def merge_matching(self, params):
163
204
        return self.merge_text(params)
164
205
 
165
206
    def merge_text(self, params):
704
745
        :param this_tree: The local tree in the merge operation
705
746
        :param base_tree: The common tree in the merge operation
706
747
        :param other_tree: The other tree to merge changes from
707
 
        :param this_branch: The branch associated with this_tree
 
748
        :param this_branch: The branch associated with this_tree.  Defaults to
 
749
            this_tree.branch if not supplied.
708
750
        :param interesting_ids: The file_ids of files that should be
709
751
            participate in the merge.  May not be combined with
710
752
            interesting_files.
728
770
        if interesting_files is not None and interesting_ids is not None:
729
771
            raise ValueError(
730
772
                'specify either interesting_ids or interesting_files')
 
773
        if this_branch is None:
 
774
            this_branch = this_tree.branch
731
775
        self.interesting_ids = interesting_ids
732
776
        self.interesting_files = interesting_files
733
777
        self.this_tree = working_tree
1015
1059
                        continue
1016
1060
                else:
1017
1061
                    raise AssertionError('unhandled kind: %s' % other_ie.kind)
1018
 
                # XXX: We need to handle kind == 'symlink'
1019
1062
 
1020
1063
            # If we have gotten this far, that means something has changed
1021
1064
            result.append((file_id, content_changed,
1043
1086
        other_root = self.tt.trans_id_file_id(other_root_file_id)
1044
1087
        if other_root == self.tt.root:
1045
1088
            return
 
1089
        if self.other_tree.inventory.root.file_id in self.this_tree.inventory:
 
1090
            # the other tree's root is a non-root in the current tree (as when
 
1091
            # a previously unrelated branch is merged into another)
 
1092
            return
1046
1093
        try:
1047
1094
            self.tt.final_kind(other_root)
 
1095
            other_root_is_present = True
1048
1096
        except errors.NoSuchFile:
1049
 
            return
1050
 
        if self.this_tree.has_id(self.other_tree.inventory.root.file_id):
1051
 
            # the other tree's root is a non-root in the current tree
1052
 
            return
1053
 
        self.reparent_children(self.other_tree.inventory.root, self.tt.root)
1054
 
        self.tt.cancel_creation(other_root)
1055
 
        self.tt.cancel_versioning(other_root)
1056
 
 
1057
 
    def reparent_children(self, ie, target):
1058
 
        for thing, child in ie.children.iteritems():
 
1097
            # other_root doesn't have a physical representation. We still need
 
1098
            # to move any references to the actual root of the tree.
 
1099
            other_root_is_present = False
 
1100
        # 'other_tree.inventory.root' is not present in this tree. We are
 
1101
        # calling adjust_path for children which *want* to be present with a
 
1102
        # correct place to go.
 
1103
        for thing, child in self.other_tree.inventory.root.children.iteritems():
1059
1104
            trans_id = self.tt.trans_id_file_id(child.file_id)
1060
 
            self.tt.adjust_path(self.tt.final_name(trans_id), target, trans_id)
 
1105
            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)
 
1110
                    # The item exist in the final tree and has a defined place
 
1111
                    # to go already.
 
1112
                    continue
 
1113
                except errors.NoSuchFile, e:
 
1114
                    pass
 
1115
            # Move the item into the root
 
1116
            self.tt.adjust_path(self.tt.final_name(trans_id),
 
1117
                                self.tt.root, trans_id)
 
1118
        if other_root_is_present:
 
1119
            self.tt.cancel_creation(other_root)
 
1120
            self.tt.cancel_versioning(other_root)
1061
1121
 
1062
1122
    def write_modified(self, results):
1063
1123
        modified_hashes = {}
1110
1170
 
1111
1171
    @staticmethod
1112
1172
    def _three_way(base, other, this):
1113
 
        #if base == other, either they all agree, or only THIS has changed.
1114
1173
        if base == other:
 
1174
            # if 'base == other', either they all agree, or only 'this' has
 
1175
            # changed.
1115
1176
            return 'this'
1116
1177
        elif this not in (base, other):
 
1178
            # 'this' is neither 'base' nor 'other', so both sides changed
1117
1179
            return 'conflict'
1118
 
        # "Ambiguous clean merge" -- both sides have made the same change.
1119
1180
        elif this == other:
 
1181
            # "Ambiguous clean merge" -- both sides have made the same change.
1120
1182
            return "this"
1121
 
        # this == base: only other has changed.
1122
1183
        else:
 
1184
            # this == base: only other has changed.
1123
1185
            return "other"
1124
1186
 
1125
1187
    @staticmethod
1169
1231
                # only has an lca value
1170
1232
                return 'other'
1171
1233
 
1172
 
        # At this point, the lcas disagree, and the tips disagree
 
1234
        # At this point, the lcas disagree, and the tip disagree
1173
1235
        return 'conflict'
1174
1236
 
1175
1237
    @staticmethod
 
1238
    @deprecated_method(deprecated_in((2, 2, 0)))
1176
1239
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1177
1240
        """Do a three-way test on a scalar.
1178
1241
        Return "this", "other" or "conflict", depending whether a value wins.
1228
1291
                parent_id_winner = "other"
1229
1292
        if name_winner == "this" and parent_id_winner == "this":
1230
1293
            return
1231
 
        if name_winner == "conflict":
1232
 
            trans_id = self.tt.trans_id_file_id(file_id)
1233
 
            self._raw_conflicts.append(('name conflict', trans_id,
1234
 
                                        this_name, other_name))
1235
 
        if parent_id_winner == "conflict":
1236
 
            trans_id = self.tt.trans_id_file_id(file_id)
1237
 
            self._raw_conflicts.append(('parent conflict', trans_id,
1238
 
                                        this_parent, other_parent))
 
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))
1239
1302
        if other_name is None:
1240
1303
            # it doesn't matter whether the result was 'other' or
1241
1304
            # 'conflict'-- if there's no 'other', we leave it alone.
1242
1305
            return
1243
 
        # if we get here, name_winner and parent_winner are set to safe values.
1244
 
        trans_id = self.tt.trans_id_file_id(file_id)
1245
1306
        parent_id = parents[self.winner_idx[parent_id_winner]]
1246
1307
        if parent_id is not None:
1247
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1308
            # if we get here, name_winner and parent_winner are set to safe
 
1309
            # values.
1248
1310
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1249
 
                                parent_trans_id, trans_id)
 
1311
                                self.tt.trans_id_file_id(parent_id),
 
1312
                                self.tt.trans_id_file_id(file_id))
1250
1313
 
1251
1314
    def _do_merge_contents(self, file_id):
1252
1315
        """Performs a merge on file_id contents."""
1531
1594
 
1532
1595
    def cook_conflicts(self, fs_conflicts):
1533
1596
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1534
 
        name_conflicts = {}
1535
1597
        self.cooked_conflicts.extend(transform.cook_conflicts(
1536
1598
                fs_conflicts, self.tt))
1537
1599
        fp = transform.FinalPaths(self.tt)
1538
1600
        for conflict in self._raw_conflicts:
1539
1601
            conflict_type = conflict[0]
1540
 
            if conflict_type in ('name conflict', 'parent conflict'):
1541
 
                trans_id = conflict[1]
1542
 
                conflict_args = conflict[2:]
1543
 
                if trans_id not in name_conflicts:
1544
 
                    name_conflicts[trans_id] = {}
1545
 
                transform.unique_add(name_conflicts[trans_id], conflict_type,
1546
 
                                     conflict_args)
1547
 
            if conflict_type == 'contents conflict':
 
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':
1548
1623
                for trans_id in conflict[1]:
1549
1624
                    file_id = self.tt.final_file_id(trans_id)
1550
1625
                    if file_id is not None:
1556
1631
                        break
1557
1632
                c = _mod_conflicts.Conflict.factory(conflict_type,
1558
1633
                                                    path=path, file_id=file_id)
1559
 
                self.cooked_conflicts.append(c)
1560
 
            if conflict_type == 'text conflict':
 
1634
            elif conflict_type == 'text conflict':
1561
1635
                trans_id = conflict[1]
1562
1636
                path = fp.get_path(trans_id)
1563
1637
                file_id = self.tt.final_file_id(trans_id)
1564
1638
                c = _mod_conflicts.Conflict.factory(conflict_type,
1565
1639
                                                    path=path, file_id=file_id)
1566
 
                self.cooked_conflicts.append(c)
1567
 
 
1568
 
        for trans_id, conflicts in name_conflicts.iteritems():
1569
 
            try:
1570
 
                this_parent, other_parent = conflicts['parent conflict']
1571
 
                if this_parent == other_parent:
1572
 
                    raise AssertionError()
1573
 
            except KeyError:
1574
 
                this_parent = other_parent = \
1575
 
                    self.tt.final_file_id(self.tt.final_parent(trans_id))
1576
 
            try:
1577
 
                this_name, other_name = conflicts['name conflict']
1578
 
                if this_name == other_name:
1579
 
                    raise AssertionError()
1580
 
            except KeyError:
1581
 
                this_name = other_name = self.tt.final_name(trans_id)
1582
 
            other_path = fp.get_path(trans_id)
1583
 
            if this_parent is not None and this_name is not None:
1584
 
                this_parent_path = \
1585
 
                    fp.get_path(self.tt.trans_id_file_id(this_parent))
1586
 
                this_path = osutils.pathjoin(this_parent_path, this_name)
1587
1640
            else:
1588
 
                this_path = "<deleted>"
1589
 
            file_id = self.tt.final_file_id(trans_id)
1590
 
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1591
 
                                                conflict_path=other_path,
1592
 
                                                file_id=file_id)
 
1641
                raise AssertionError('bad conflict type: %r' % (conflict,))
1593
1642
            self.cooked_conflicts.append(c)
1594
1643
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1595
1644