~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/merge.py

Fix #531967 by creating helpers for PathConflicts when a deletion
is involved.

* bzrlib/tests/test_conflicts.py:
(TestParametrizedResolveConflicts.mirror_scenarios): Renamed from
multiply_scenarios to make the intent clearer. Turned into a
classmethod too for the same reason.
(TestParametrizedResolveConflicts.scenarios): Now a classmethod.

* bzrlib/merge.py:
(Merge3Merger._merge_names): 'name conflict' and 'parent conflict'
can (and must) be handled in the same way. If a deletion is
involved we create an unversioned copy of the rejected item so the
user can restore that easily.
(Merge3Merger.cook_conflicts): Get rid of 'name conflict', 'parent
conflict' distinction and just create PathConflicts with a file_id
to address bug #531967.

* bzrlib/conflicts.py:
(PathConflict.associated_filenames): Helpers exist only when a
deletion is involved.
(PathConflict._resolve): We may have to version one path
again. This may happen when a deletion have occurred.
(PathConflict.action_take_this, PathConflict.action_take_other):
As a special case, we may have an helper to use when deletion was
involved.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1173
1173
        return 'conflict'
1174
1174
 
1175
1175
    @staticmethod
 
1176
    # FIXME: this looks unused and probably needs to be deprecated, the
 
1177
    # parameter order (this, base, other) doesn't match the other methods
 
1178
    # (base, other, this) anyway -- vila 20100308
1176
1179
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1177
1180
        """Do a three-way test on a scalar.
1178
1181
        Return "this", "other" or "conflict", depending whether a value wins.
1228
1231
                parent_id_winner = "other"
1229
1232
        if name_winner == "this" and parent_id_winner == "this":
1230
1233
            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))
 
1234
        if name_winner == 'conflict' or parent_id_winner == 'conflict':
 
1235
            if other_name is None or other_parent is None:
 
1236
                # 'other' has been deleted, leave a .THIS
 
1237
                parent_id = self.tt.trans_id_file_id(this_parent)
 
1238
                trans_id = self.tt.create_path(this_name + '.THIS', parent_id)
 
1239
                transform.create_from_tree(self.tt, trans_id, self.this_tree,
 
1240
                                           file_id)
 
1241
            elif this_name is None or this_parent is None:
 
1242
                # 'this' has been deleted, leave a .OTHER
 
1243
                parent_id = self.tt.trans_id_file_id(other_parent)
 
1244
                trans_id = self.tt.create_path(other_name + '.OTHER', parent_id)
 
1245
                transform.create_from_tree(self.tt, trans_id, self.other_tree,
 
1246
                                           file_id)
 
1247
            trans_id = self.tt.trans_id_file_id(file_id)
 
1248
            self._raw_conflicts.append(('path conflict', trans_id, file_id,
 
1249
                                        this_parent, this_name,
 
1250
                                        other_parent, other_name))
1239
1251
        if other_name is None:
1240
1252
            # it doesn't matter whether the result was 'other' or
1241
1253
            # 'conflict'-- if there's no 'other', we leave it alone.
1242
1254
            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
1255
        parent_id = parents[self.winner_idx[parent_id_winner]]
1246
1256
        if parent_id is not None:
1247
 
            parent_trans_id = self.tt.trans_id_file_id(parent_id)
 
1257
            # if we get here, name_winner and parent_winner are set to safe
 
1258
            # values.
1248
1259
            self.tt.adjust_path(names[self.winner_idx[name_winner]],
1249
 
                                parent_trans_id, trans_id)
 
1260
                                self.tt.trans_id_file_id(parent_id),
 
1261
                                self.tt.trans_id_file_id(file_id))
1250
1262
 
1251
1263
    def _do_merge_contents(self, file_id):
1252
1264
        """Performs a merge on file_id contents."""
1531
1543
 
1532
1544
    def cook_conflicts(self, fs_conflicts):
1533
1545
        """Convert all conflicts into a form that doesn't depend on trans_id"""
1534
 
        name_conflicts = {}
1535
1546
        self.cooked_conflicts.extend(transform.cook_conflicts(
1536
1547
                fs_conflicts, self.tt))
1537
1548
        fp = transform.FinalPaths(self.tt)
1538
1549
        for conflict in self._raw_conflicts:
1539
1550
            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':
 
1551
            if conflict_type == 'path conflict':
 
1552
                (trans_id, file_id,
 
1553
                this_parent, this_name,
 
1554
                other_parent, other_name) = conflict[1:]
 
1555
                if this_parent is None or this_name is None:
 
1556
                    this_path = '<deleted>'
 
1557
                else:
 
1558
                    parent_path =  fp.get_path(
 
1559
                        self.tt.trans_id_file_id(this_parent))
 
1560
                    this_path = osutils.pathjoin(parent_path, this_name)
 
1561
                if other_parent is None or other_name is None:
 
1562
                    other_path = '<deleted>'
 
1563
                else:
 
1564
                    parent_path =  fp.get_path(
 
1565
                        self.tt.trans_id_file_id(other_parent))
 
1566
                    other_path = osutils.pathjoin(parent_path, other_name)
 
1567
                c = _mod_conflicts.Conflict.factory(
 
1568
                    'path conflict', path=this_path,
 
1569
                    conflict_path=other_path,
 
1570
                    file_id=file_id)
 
1571
            elif conflict_type == 'contents conflict':
1548
1572
                for trans_id in conflict[1]:
1549
1573
                    file_id = self.tt.final_file_id(trans_id)
1550
1574
                    if file_id is not None:
1556
1580
                        break
1557
1581
                c = _mod_conflicts.Conflict.factory(conflict_type,
1558
1582
                                                    path=path, file_id=file_id)
1559
 
                self.cooked_conflicts.append(c)
1560
 
            if conflict_type == 'text conflict':
 
1583
            elif conflict_type == 'text conflict':
1561
1584
                trans_id = conflict[1]
1562
1585
                path = fp.get_path(trans_id)
1563
1586
                file_id = self.tt.final_file_id(trans_id)
1564
1587
                c = _mod_conflicts.Conflict.factory(conflict_type,
1565
1588
                                                    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
1589
            else:
1588
 
                this_path = "<deleted>"
1589
 
            file_id = self.tt.final_file_id(trans_id)
1590
 
            # FIXME: PathConflicts objects are created with other/this
1591
 
            # path/conflict_path paths reversed -- vila 20100304
1592
 
            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
1593
 
                                                conflict_path=other_path,
1594
 
                                                file_id=file_id)
 
1590
                raise AssertionError()
1595
1591
            self.cooked_conflicts.append(c)
 
1592
 
1596
1593
        self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
1597
1594
 
1598
1595