Rev 5113: (vila) Better PathConflict resolution when a deletion is involved in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Thu Mar 25 09:38:24 GMT 2010


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5113 [merge]
revision-id: pqm at pqm.ubuntu.com-20100325093823-kwkh6crnkc0xfxh6
parent: pqm at pqm.ubuntu.com-20100325081424-674xjyvnoudkglip
parent: v.ladeuil+lp at free.fr-20100325090714-po9tte5yne04e9k1
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2010-03-25 09:38:23 +0000
message:
  (vila) Better PathConflict resolution when a deletion is involved
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/conflicts.py            conflicts.py-20051001061850-78ef952ba63d2b42
  bzrlib/merge.py                merge.py-20050513021216-953b65a438527106
  bzrlib/tests/test_conflicts.py test_conflicts.py-20051006031059-e2dad9bbeaa5891f
=== modified file 'NEWS'
--- a/NEWS	2010-03-24 13:58:37 +0000
+++ b/NEWS	2010-03-25 09:07:14 +0000
@@ -141,6 +141,10 @@
 * Fix stub sftp test server to call os.getcwdu().
   (Vincent Ladeuil, #526211, #526353)
 
+* Path conflicts now support --take-this and --take-other even when a
+  deletion is involved.
+  (Vincent Ladeuil, #531967)
+
 * Network transfer amounts and rates are now displayed in SI units according
   to the Ubuntu Units Policy <https://wiki.ubuntu.com/UnitsPolicy>.
   (Gordon Tyler, #514399)

=== modified file 'bzrlib/conflicts.py'
--- a/bzrlib/conflicts.py	2010-03-02 07:58:53 +0000
+++ b/bzrlib/conflicts.py	2010-03-24 11:34:16 +0000
@@ -436,6 +436,12 @@
     def action_take_other(self, tree):
         raise NotImplementedError(self.action_take_other)
 
+    def _resolve_with_cleanups(self, tree, *args, **kwargs):
+        tt = transform.TreeTransform(tree)
+        op = cleanup.OperationWithCleanups(self._resolve)
+        op.add_cleanup(tt.finalize)
+        op.run_simple(tt, *args, **kwargs)
+
 
 class PathConflict(Conflict):
     """A conflict was encountered merging file paths"""
@@ -460,12 +466,92 @@
         # No additional files have been generated here
         return []
 
+    def _resolve(self, tt, file_id, path, winner):
+        """Resolve the conflict.
+
+        :param tt: The TreeTransform where the conflict is resolved.
+        :param file_id: The retained file id.
+        :param path: The retained path.
+        :param winner: 'this' or 'other' indicates which side is the winner.
+        """
+        path_to_create = None
+        if winner == 'this':
+            if self.path == '<deleted>':
+                return # Nothing to do
+            if self.conflict_path == '<deleted>':
+                path_to_create = self.path
+                revid = tt._tree.get_parent_ids()[0]
+        elif winner == 'other':
+            if self.conflict_path == '<deleted>':
+                return  # Nothing to do
+            if self.path == '<deleted>':
+                path_to_create = self.conflict_path
+                # FIXME: If there are more than two parents we may need to
+                # iterate. Taking the last parent is the safer bet in the mean
+                # time. -- vila 20100309
+                revid = tt._tree.get_parent_ids()[-1]
+        else:
+            # Programmer error
+            raise AssertionError('bad winner: %r' % (winner,))
+        if path_to_create is not None:
+            tid = tt.trans_id_tree_path(path_to_create)
+            transform.create_from_tree(
+                tt, tt.trans_id_tree_path(path_to_create),
+                self._revision_tree(tt._tree, revid), file_id)
+            tt.version_file(file_id, tid)
+
+        # Adjust the path for the retained file id
+        tid = tt.trans_id_file_id(file_id)
+        parent_tid = tt.get_tree_parent(tid)
+        tt.adjust_path(path, parent_tid, tid)
+        tt.apply()
+
+    def _revision_tree(self, tree, revid):
+        return tree.branch.repository.revision_tree(revid)
+
+    def _infer_file_id(self, tree):
+        # Prior to bug #531967, file_id wasn't always set, there may still be
+        # conflict files in the wild so we need to cope with them
+        # Establish which path we should use to find back the file-id
+        possible_paths = []
+        for p in (self.path, self.conflict_path):
+            if p == '<deleted>':
+                # special hard-coded path 
+                continue
+            if p is not None:
+                possible_paths.append(p)
+        # Search the file-id in the parents with any path available
+        file_id = None
+        for revid in tree.get_parent_ids():
+            revtree = self._revision_tree(tree, revid)
+            for p in possible_paths:
+                file_id = revtree.path2id(p)
+                if file_id is not None:
+                    return revtree, file_id
+        return None, None
+
     def action_take_this(self, tree):
-        tree.rename_one(self.conflict_path, self.path)
+        if self.file_id is not None:
+            self._resolve_with_cleanups(tree, self.file_id, self.path,
+                                        winner='this')
+        else:
+            # Prior to bug #531967 we need to find back the file_id and restore
+            # the content from there
+            revtree, file_id = self._infer_file_id(tree)
+            tree.revert([revtree.id2path(file_id)],
+                        old_tree=revtree, backups=False)
 
     def action_take_other(self, tree):
-        # just acccept bzr proposal
-        pass
+        if self.file_id is not None:
+            self._resolve_with_cleanups(tree, self.file_id,
+                                        self.conflict_path,
+                                        winner='other')
+        else:
+            # Prior to bug #531967 we need to find back the file_id and restore
+            # the content from there
+            revtree, file_id = self._infer_file_id(tree)
+            tree.revert([revtree.id2path(file_id)],
+                        old_tree=revtree, backups=False)
 
 
 class ContentsConflict(PathConflict):
@@ -480,7 +566,7 @@
     def associated_filenames(self):
         return [self.path + suffix for suffix in ('.BASE', '.OTHER')]
 
-    def _take_it(self, tt, suffix_to_remove):
+    def _resolve(self, tt, suffix_to_remove):
         """Resolve the conflict.
 
         :param tt: The TreeTransform where the conflict is resolved.
@@ -506,17 +592,11 @@
         tt.adjust_path(self.path, parent_tid, this_tid)
         tt.apply()
 
-    def _take_it_with_cleanups(self, tree, suffix_to_remove):
-        tt = transform.TreeTransform(tree)
-        op = cleanup.OperationWithCleanups(self._take_it)
-        op.add_cleanup(tt.finalize)
-        op.run_simple(tt, suffix_to_remove)
-
     def action_take_this(self, tree):
-        self._take_it_with_cleanups(tree, 'OTHER')
+        self._resolve_with_cleanups(tree, 'OTHER')
 
     def action_take_other(self, tree):
-        self._take_it_with_cleanups(tree, 'THIS')
+        self._resolve_with_cleanups(tree, 'THIS')
 
 
 # FIXME: TextConflict is about a single file-id, there never is a conflict_path

=== modified file 'bzrlib/merge.py'
--- a/bzrlib/merge.py	2010-03-09 15:33:25 +0000
+++ b/bzrlib/merge.py	2010-03-25 09:07:14 +0000
@@ -1229,25 +1229,25 @@
                 parent_id_winner = "other"
         if name_winner == "this" and parent_id_winner == "this":
             return
-        if name_winner == "conflict":
-            trans_id = self.tt.trans_id_file_id(file_id)
-            self._raw_conflicts.append(('name conflict', trans_id,
-                                        this_name, other_name))
-        if parent_id_winner == "conflict":
-            trans_id = self.tt.trans_id_file_id(file_id)
-            self._raw_conflicts.append(('parent conflict', trans_id,
-                                        this_parent, other_parent))
+        if name_winner == 'conflict' or parent_id_winner == 'conflict':
+            # Creating helpers (.OTHER or .THIS) here cause problems down the
+            # road if a ContentConflict needs to be created so we should not do
+            # that
+            trans_id = self.tt.trans_id_file_id(file_id)
+            self._raw_conflicts.append(('path conflict', trans_id, file_id,
+                                        this_parent, this_name,
+                                        other_parent, other_name))
         if other_name is None:
             # it doesn't matter whether the result was 'other' or
             # 'conflict'-- if there's no 'other', we leave it alone.
             return
-        # if we get here, name_winner and parent_winner are set to safe values.
-        trans_id = self.tt.trans_id_file_id(file_id)
         parent_id = parents[self.winner_idx[parent_id_winner]]
         if parent_id is not None:
-            parent_trans_id = self.tt.trans_id_file_id(parent_id)
+            # if we get here, name_winner and parent_winner are set to safe
+            # values.
             self.tt.adjust_path(names[self.winner_idx[name_winner]],
-                                parent_trans_id, trans_id)
+                                self.tt.trans_id_file_id(parent_id),
+                                self.tt.trans_id_file_id(file_id))
 
     def _do_merge_contents(self, file_id):
         """Performs a merge on file_id contents."""
@@ -1532,20 +1532,32 @@
 
     def cook_conflicts(self, fs_conflicts):
         """Convert all conflicts into a form that doesn't depend on trans_id"""
-        name_conflicts = {}
         self.cooked_conflicts.extend(transform.cook_conflicts(
                 fs_conflicts, self.tt))
         fp = transform.FinalPaths(self.tt)
         for conflict in self._raw_conflicts:
             conflict_type = conflict[0]
-            if conflict_type in ('name conflict', 'parent conflict'):
-                trans_id = conflict[1]
-                conflict_args = conflict[2:]
-                if trans_id not in name_conflicts:
-                    name_conflicts[trans_id] = {}
-                transform.unique_add(name_conflicts[trans_id], conflict_type,
-                                     conflict_args)
-            if conflict_type == 'contents conflict':
+            if conflict_type == 'path conflict':
+                (trans_id, file_id,
+                this_parent, this_name,
+                other_parent, other_name) = conflict[1:]
+                if this_parent is None or this_name is None:
+                    this_path = '<deleted>'
+                else:
+                    parent_path =  fp.get_path(
+                        self.tt.trans_id_file_id(this_parent))
+                    this_path = osutils.pathjoin(parent_path, this_name)
+                if other_parent is None or other_name is None:
+                    other_path = '<deleted>'
+                else:
+                    parent_path =  fp.get_path(
+                        self.tt.trans_id_file_id(other_parent))
+                    other_path = osutils.pathjoin(parent_path, other_name)
+                c = _mod_conflicts.Conflict.factory(
+                    'path conflict', path=this_path,
+                    conflict_path=other_path,
+                    file_id=file_id)
+            elif conflict_type == 'contents conflict':
                 for trans_id in conflict[1]:
                     file_id = self.tt.final_file_id(trans_id)
                     if file_id is not None:
@@ -1557,40 +1569,14 @@
                         break
                 c = _mod_conflicts.Conflict.factory(conflict_type,
                                                     path=path, file_id=file_id)
-                self.cooked_conflicts.append(c)
-            if conflict_type == 'text conflict':
+            elif conflict_type == 'text conflict':
                 trans_id = conflict[1]
                 path = fp.get_path(trans_id)
                 file_id = self.tt.final_file_id(trans_id)
                 c = _mod_conflicts.Conflict.factory(conflict_type,
                                                     path=path, file_id=file_id)
-                self.cooked_conflicts.append(c)
-
-        for trans_id, conflicts in name_conflicts.iteritems():
-            try:
-                this_parent, other_parent = conflicts['parent conflict']
-                if this_parent == other_parent:
-                    raise AssertionError()
-            except KeyError:
-                this_parent = other_parent = \
-                    self.tt.final_file_id(self.tt.final_parent(trans_id))
-            try:
-                this_name, other_name = conflicts['name conflict']
-                if this_name == other_name:
-                    raise AssertionError()
-            except KeyError:
-                this_name = other_name = self.tt.final_name(trans_id)
-            other_path = fp.get_path(trans_id)
-            if this_parent is not None and this_name is not None:
-                this_parent_path = \
-                    fp.get_path(self.tt.trans_id_file_id(this_parent))
-                this_path = osutils.pathjoin(this_parent_path, this_name)
             else:
-                this_path = "<deleted>"
-            file_id = self.tt.final_file_id(trans_id)
-            c = _mod_conflicts.Conflict.factory('path conflict', path=this_path,
-                                                conflict_path=other_path,
-                                                file_id=file_id)
+                raise AssertionError('bad conflict type: %r' % (conflict,))
             self.cooked_conflicts.append(c)
         self.cooked_conflicts.sort(key=_mod_conflicts.Conflict.sort_key)
 

=== modified file 'bzrlib/tests/test_conflicts.py'
--- a/bzrlib/tests/test_conflicts.py	2010-03-02 07:38:15 +0000
+++ b/bzrlib/tests/test_conflicts.py	2010-03-24 11:34:16 +0000
@@ -34,9 +34,13 @@
 
     sp_tests, remaining_tests = tests.split_suite_by_condition(
         standard_tests, tests.condition_isinstance((
-                TestResolveContentConflicts,
+                TestParametrizedResolveConflicts,
                 )))
-    tests.multiply_tests(sp_tests, content_conflict_scenarios(), result)
+    # Each test class defines its own scenarios. This is needed for
+    # TestResolvePathConflictBefore531967 that verifies that the same tests as
+    # TestResolvePathConflict still pass.
+    for test in tests.iter_suite_tests(sp_tests):
+        tests.apply_scenarios(test, test.scenarios(), result)
 
     # No parametrization for the remaining tests
     result.addTests(remaining_tests)
@@ -194,6 +198,8 @@
 # FIXME: The shell-like tests should be converted to real whitebox tests... or
 # moved to a blackbox module -- vila 20100205
 
+# FIXME: test missing for multiple conflicts
+
 # FIXME: Tests missing for DuplicateID conflict type
 class TestResolveConflicts(script.TestCaseWithTransportAndScript):
 
@@ -209,42 +215,100 @@
     pass
 
 
-def content_conflict_scenarios():
-    return [('file,None', dict(_this_actions='modify_file',
-                               _check_this='file_has_more_content',
-                               _other_actions='delete_file',
-                               _check_other='file_doesnt_exist',
-                               )),
-            ('None,file', dict(_this_actions='delete_file',
-                               _check_this='file_doesnt_exist',
-                               _other_actions='modify_file',
-                               _check_other='file_has_more_content',
-                               )),
-            ]
-
-
-class TestResolveContentConflicts(tests.TestCaseWithTransport):
+# FIXME: Get rid of parametrized (in the class name) once we delete
+# TestResolveConflicts -- vila 20100308
+class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
+    """This class provides a base to test single conflict resolution.
+
+    The aim is to define scenarios in daughter classes (one for each conflict
+    type) that create a single conflict object when one branch is merged in
+    another (and vice versa). Each class can define as many scenarios as
+    needed. Each scenario should define a couple of actions that will be
+    swapped to define the sibling scenarios.
+
+    From there, both resolutions are tested (--take-this and --take-other).
+
+    Each conflict type use its attributes in a specific way, so each class 
+    should define a specific _assert_conflict method.
+
+    Since the resolution change the working tree state, each action should
+    define an associated check.
+    """
+
+    # Set by daughter classes
+    _conflict_type = None
+    _assert_conflict = None
 
     # Set by load_tests
-    this_actions = None
-    other_actions = None
+    _base_actions = None
+    _this_actions = None
+    _other_actions = None
+    _item_path = None
+    _item_id = None
+
+    # Set by _this_actions and other_actions
+    _this_path = None
+    _this_id = None
+    _other_path = None
+    _other_id = None
+
+    @classmethod
+    def mirror_scenarios(klass, base_scenarios):
+        scenarios = []
+        def adapt(d, side):
+            """Modify dict to apply to the given side.
+
+            'actions' key is turned into '_actions_this' if side is 'this' for
+            example.
+            """
+            t = {}
+            # Turn each key into _side_key
+            for k,v in d.iteritems():
+                t['_%s_%s' % (k, side)] = v
+            return t
+        # Each base scenario is duplicated switching the roles of 'this' and
+        # 'other'
+        left = [l for l, r, c in base_scenarios]
+        right = [r for l, r, c in base_scenarios]
+        common = [c for l, r, c in base_scenarios]
+        for (lname, ldict), (rname, rdict), common in zip(left, right, common):
+            a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
+                                         [(rname, adapt(rdict, 'other'))])
+            b = tests.multiply_scenarios(
+                    [(rname, adapt(rdict, 'this'))],
+                    [(lname, adapt(ldict, 'other'))])
+            # Inject the common parameters in all scenarios
+            for name, d in a + b:
+                d.update(common)
+            scenarios.extend(a + b)
+        return scenarios
+
+    @classmethod
+    def scenarios(klass):
+        # Only concrete classes return actual scenarios
+        return []
 
     def setUp(self):
-        super(TestResolveContentConflicts, self).setUp()
+        super(TestParametrizedResolveConflicts, self).setUp()
         builder = self.make_branch_builder('trunk')
         builder.start_series()
+
         # Create an empty trunk
         builder.build_snapshot('start', None, [
                 ('add', ('', 'root-id', 'directory', ''))])
         # Add a minimal base content
-        builder.build_snapshot('base', ['start'], [
-                ('add', ('file', 'file-id', 'file', 'trunk content\n'))])
+        _, _, actions_base = self._get_actions(self._actions_base)()
+        builder.build_snapshot('base', ['start'], actions_base)
         # Modify the base content in branch
-        other_actions = self._get_actions(self._other_actions)
-        builder.build_snapshot('other', ['base'], other_actions())
+        (self._other_path, self._other_id,
+         actions_other) = self._get_actions(self._actions_other)()
+        builder.build_snapshot('other', ['base'], actions_other)
         # Modify the base content in trunk
-        this_actions = self._get_actions(self._this_actions)
-        builder.build_snapshot('this', ['base'], this_actions())
+        (self._this_path, self._this_id,
+         actions_this) = self._get_actions(self._actions_this)()
+        builder.build_snapshot('this', ['base'], actions_this)
+        # builder.get_branch() tip is now 'this'
+
         builder.finish_series()
         self.builder = builder
 
@@ -254,56 +318,166 @@
     def _get_check(self, name):
         return getattr(self, 'check_%s' % name)
 
+    def assertConflict(self, wt):
+        confs = wt.conflicts()
+        self.assertLength(1, confs)
+        c = confs[0]
+        self.assertIsInstance(c, self._conflict_type)
+        self._assert_conflict(wt, c)
+
+    def check_resolved(self, wt, path, action):
+        conflicts.resolve(wt, [path], action=action)
+        # Check that we don't have any conflicts nor unknown left
+        self.assertLength(0, wt.conflicts())
+        self.assertLength(0, list(wt.unknowns()))
+
+    def do_create_file(self):
+        return ('file', 'file-id',
+                [('add', ('file', 'file-id', 'file', 'trunk content\n'))])
+
+    def do_create_dir(self):
+        return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
+
     def do_modify_file(self):
-        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
+        return ('file', 'file-id',
+                [('modify', ('file-id', 'trunk content\nmore content\n'))])
 
     def check_file_has_more_content(self):
         self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
 
     def do_delete_file(self):
-        return [('unversion', 'file-id')]
+        return ('file', 'file-id', [('unversion', 'file-id')])
 
     def check_file_doesnt_exist(self):
         self.failIfExists('branch/file')
 
+    def do_rename_file(self):
+        return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
+
+    def check_file_renamed(self):
+        self.failIfExists('branch/file')
+        self.failUnlessExists('branch/new-file')
+
+    def do_rename_dir(self):
+        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
+
+    def check_dir_renamed(self):
+        self.failIfExists('branch/dir')
+        self.failUnlessExists('branch/new-dir')
+
+    def do_rename_dir2(self):
+        return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
+
+    def check_dir_renamed2(self):
+        self.failIfExists('branch/dir')
+        self.failUnlessExists('branch/new-dir2')
+
+    def do_delete_dir(self):
+        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
+
+    def check_dir_doesnt_exist(self):
+        self.failIfExists('branch/dir')
+
     def _merge_other_into_this(self):
         b = self.builder.get_branch()
         wt = b.bzrdir.sprout('branch').open_workingtree()
         wt.merge_from_branch(b, 'other')
         return wt
 
-    def assertConflict(self, wt, ctype, **kwargs):
-        confs = wt.conflicts()
-        self.assertLength(1, confs)
-        c = confs[0]
-        self.assertIsInstance(c, ctype)
-        sentinel = object() # An impossible value
-        for k, v in kwargs.iteritems():
-            self.assertEqual(v, getattr(c, k, sentinel))
-
-    def check_resolved(self, wt, item, action):
-        conflicts.resolve(wt, [item], action=action)
-        # Check that we don't have any conflicts nor unknown left
-        self.assertLength(0, wt.conflicts())
-        self.assertLength(0, list(wt.unknowns()))
-
     def test_resolve_taking_this(self):
         wt = self._merge_other_into_this()
-        self.assertConflict(wt, conflicts.ContentsConflict,
-                            path='file', file_id='file-id',)
-        self.check_resolved(wt, 'file', 'take_this')
+        self.assertConflict(wt)
+        self.check_resolved(wt, self._item_path, 'take_this')
         check_this = self._get_check(self._check_this)
         check_this()
 
     def test_resolve_taking_other(self):
         wt = self._merge_other_into_this()
-        self.assertConflict(wt, conflicts.ContentsConflict,
-                            path='file', file_id='file-id',)
-        self.check_resolved(wt, 'file', 'take_other')
+        self.assertConflict(wt)
+        self.check_resolved(wt, self._item_path, 'take_other')
         check_other = self._get_check(self._check_other)
         check_other()
 
 
+class TestResolveContentsConflict(TestParametrizedResolveConflicts):
+
+    _conflict_type = conflicts.ContentsConflict,
+    @classmethod
+    def scenarios(klass):
+        common = dict(_actions_base='create_file',
+                      _item_path='file', item_id='file-id',
+                      )
+        base_scenarios = [
+            (('file_modified', dict(actions='modify_file',
+                                   check='file_has_more_content')),
+             ('file_deleted', dict(actions='delete_file',
+                                   check='file_doesnt_exist')),
+             dict(_actions_base='create_file',
+                  _item_path='file', item_id='file-id',)),
+            ]
+        return klass.mirror_scenarios(base_scenarios)
+
+    def assertContentsConflict(self, wt, c):
+        self.assertEqual(self._other_id, c.file_id)
+        self.assertEqual(self._other_path, c.path)
+    _assert_conflict = assertContentsConflict
+
+
+
+class TestResolvePathConflict(TestParametrizedResolveConflicts):
+
+    _conflict_type = conflicts.PathConflict,
+
+    @classmethod
+    def scenarios(klass):
+        for_dirs = dict(_actions_base='create_dir',
+                        _item_path='new-dir', _item_id='dir-id',)
+        base_scenarios = [
+            (('file_renamed',
+              dict(actions='rename_file', check='file_renamed')),
+             ('file_deleted',
+              dict(actions='delete_file', check='file_doesnt_exist')),
+             dict(_actions_base='create_file',
+                  _item_path='new-file', _item_id='file-id',)),
+            (('dir_renamed',
+              dict(actions='rename_dir', check='dir_renamed')),
+             ('dir_deleted',
+              dict(actions='delete_dir', check='dir_doesnt_exist')),
+             for_dirs),
+            (('dir_renamed',
+              dict(actions='rename_dir', check='dir_renamed')),
+             ('dir_renamed2',
+              dict(actions='rename_dir2', check='dir_renamed2')),
+             for_dirs),
+        ]
+        return klass.mirror_scenarios(base_scenarios)
+
+    def do_delete_file(self):
+        sup = super(TestResolvePathConflict, self).do_delete_file()
+        # PathConflicts handle deletion differently and requires a special
+        # hard-coded value
+        return ('<deleted>',) + sup[1:]
+
+    def assertPathConflict(self, wt, c):
+        self.assertEqual(self._item_id, c.file_id)
+        self.assertEqual(self._this_path, c.path)
+        self.assertEqual(self._other_path, c.conflict_path)
+    _assert_conflict = assertPathConflict
+
+
+class TestResolvePathConflictBefore531967(TestResolvePathConflict):
+    """Same as TestResolvePathConflict but a specific conflict object.
+    """
+
+    def assertPathConflict(self, c):
+        # We create a conflict object as it was created before the fix and
+        # inject it into the working tree, the test will exercise the
+        # compatibility code.
+        old_c = conflicts.PathConflict('<deleted>', self._item_path,
+                                       file_id=None)
+        wt.set_conflicts(conflicts.ConflictList([old_c]))
+
+
 class TestResolveDuplicateEntry(TestResolveConflicts):
 
     preamble = """
@@ -527,7 +701,7 @@
 """)
 
 
-class TestResolvePathConflict(TestResolveConflicts):
+class OldTestResolvePathConflict(TestResolveConflicts):
 
     preamble = """
 $ bzr init trunk




More information about the bazaar-commits mailing list