55
44
# be a sorted list.
 
56
45
# u'\xe5' == a with circle
 
57
46
# '\xc3\xae' == u'\xee' == i with hat
 
58
 
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
 
59
 
example_conflicts = conflicts.ConflictList(
 
60
 
    [conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
 
61
 
     conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
 
62
 
     conflicts.TextConflict(u'p\xe5tha'),
 
63
 
     conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
 
64
 
     conflicts.DuplicateID('Unversioned existing file',
 
65
 
                           u'p\xe5thc', u'p\xe5thc2',
 
66
 
                           '\xc3\xaedc', '\xc3\xaedc'),
 
67
 
    conflicts.DuplicateEntry('Moved existing file to',
 
68
 
                             u'p\xe5thdd.moved', u'p\xe5thd',
 
70
 
    conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
 
72
 
    conflicts.UnversionedParent('Versioned directory',
 
73
 
                                u'p\xe5thf', '\xc3\xaedf'),
 
74
 
    conflicts.NonDirectoryParent('Created directory',
 
75
 
                                 u'p\xe5thg', '\xc3\xaedg'),
 
 
47
# So these are u'pathg' and 'idg' only with a circle and a hat. (shappo?)
 
 
48
example_conflicts = ConflictList([
 
 
49
    MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
 
 
50
    ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
 
 
51
    TextConflict(u'p\xe5tha'),
 
 
52
    PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
 
 
53
    DuplicateID('Unversioned existing file', u'p\xe5thc', u'p\xe5thc2',
 
 
54
                '\xc3\xaedc', '\xc3\xaedc'),
 
 
55
    DuplicateEntry('Moved existing file to',  u'p\xe5thdd.moved', u'p\xe5thd',
 
 
57
    ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
 
 
59
    UnversionedParent('Versioned directory', u'p\xe5thf', '\xc3\xaedf'),
 
 
60
    NonDirectoryParent('Created directory', u'p\xe5thg', '\xc3\xaedg'),
 
79
 
class TestConflicts(tests.TestCaseWithTransport):
 
 
64
class TestConflicts(TestCaseWithTransport):
 
81
66
    def test_conflicts(self):
 
82
67
        """Conflicts are detected properly"""
 
83
 
        # Use BzrDirFormat6 so we can fake conflicts
 
84
 
        tree = self.make_branch_and_tree('.', format=bzrdir.BzrDirFormat6())
 
85
 
        self.build_tree_contents([('hello', 'hello world4'),
 
86
 
                                  ('hello.THIS', 'hello world2'),
 
87
 
                                  ('hello.BASE', 'hello world1'),
 
88
 
                                  ('hello.OTHER', 'hello world3'),
 
89
 
                                  ('hello.sploo.BASE', 'yellowworld'),
 
90
 
                                  ('hello.sploo.OTHER', 'yellowworld2'),
 
 
68
        tree = self.make_branch_and_tree('.',
 
 
69
            format=bzrdir.BzrDirFormat6())
 
 
71
        file('hello', 'w').write('hello world4')
 
 
72
        file('hello.THIS', 'w').write('hello world2')
 
 
73
        file('hello.BASE', 'w').write('hello world1')
 
 
74
        file('hello.OTHER', 'w').write('hello world3')
 
 
75
        file('hello.sploo.BASE', 'w').write('yellow world')
 
 
76
        file('hello.sploo.OTHER', 'w').write('yellow world2')
 
93
 
        self.assertLength(6, list(tree.list_files()))
 
 
78
        self.assertEqual(len(list(tree.list_files())), 6)
 
95
 
        tree_conflicts = tree.conflicts()
 
96
 
        self.assertLength(2, tree_conflicts)
 
97
 
        self.assertTrue('hello' in tree_conflicts[0].path)
 
98
 
        self.assertTrue('hello.sploo' in tree_conflicts[1].path)
 
99
 
        conflicts.restore('hello')
 
100
 
        conflicts.restore('hello.sploo')
 
101
 
        self.assertLength(0, tree.conflicts())
 
 
80
        conflicts = tree.conflicts()
 
 
81
        self.assertEqual(len(conflicts), 2)
 
 
82
        self.assert_('hello' in conflicts[0].path)
 
 
83
        self.assert_('hello.sploo' in conflicts[1].path)
 
 
85
        restore('hello.sploo')
 
 
86
        self.assertEqual(len(tree.conflicts()), 0)
 
102
87
        self.assertFileEqual('hello world2', 'hello')
 
103
88
        self.assertFalse(os.path.lexists('hello.sploo'))
 
104
 
        self.assertRaises(errors.NotConflicted, conflicts.restore, 'hello')
 
105
 
        self.assertRaises(errors.NotConflicted,
 
106
 
                          conflicts.restore, 'hello.sploo')
 
 
89
        self.assertRaises(NotConflicted, restore, 'hello')
 
 
90
        self.assertRaises(NotConflicted, restore, 'hello.sploo')
 
108
92
    def test_resolve_conflict_dir(self):
 
109
93
        tree = self.make_branch_and_tree('.')
 
110
 
        self.build_tree_contents([('hello', 'hello world4'),
 
111
 
                                  ('hello.THIS', 'hello world2'),
 
112
 
                                  ('hello.BASE', 'hello world1'),
 
 
95
        file('hello', 'w').write('hello world4')
 
 
96
        tree.add('hello', 'q')
 
 
97
        file('hello.THIS', 'w').write('hello world2')
 
 
98
        file('hello.BASE', 'w').write('hello world1')
 
114
99
        os.mkdir('hello.OTHER')
 
115
 
        tree.add('hello', 'q')
 
116
 
        l = conflicts.ConflictList([conflicts.TextConflict('hello')])
 
 
100
        l = ConflictList([TextConflict('hello')])
 
117
101
        l.remove_files(tree)
 
119
103
    def test_select_conflicts(self):
 
120
104
        tree = self.make_branch_and_tree('.')
 
121
 
        clist = conflicts.ConflictList
 
123
 
        def check_select(not_selected, selected, paths, **kwargs):
 
125
 
                (not_selected, selected),
 
126
 
                tree_conflicts.select_conflicts(tree, paths, **kwargs))
 
128
 
        foo = conflicts.ContentsConflict('foo')
 
129
 
        bar = conflicts.ContentsConflict('bar')
 
130
 
        tree_conflicts = clist([foo, bar])
 
132
 
        check_select(clist([bar]), clist([foo]), ['foo'])
 
133
 
        check_select(clist(), tree_conflicts,
 
134
 
                     [''], ignore_misses=True, recurse=True)
 
136
 
        foobaz  = conflicts.ContentsConflict('foo/baz')
 
137
 
        tree_conflicts = clist([foobaz, bar])
 
139
 
        check_select(clist([bar]), clist([foobaz]),
 
140
 
                     ['foo'], ignore_misses=True, recurse=True)
 
142
 
        qux = conflicts.PathConflict('qux', 'foo/baz')
 
143
 
        tree_conflicts = clist([qux])
 
145
 
        check_select(clist(), tree_conflicts,
 
146
 
                     ['foo'], ignore_misses=True, recurse=True)
 
147
 
        check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
 
 
105
        tree_conflicts = ConflictList([ContentsConflict('foo'),
 
 
106
                                       ContentsConflict('bar')])
 
 
107
        self.assertEqual((ConflictList([ContentsConflict('bar')]),
 
 
108
                          ConflictList([ContentsConflict('foo')])),
 
 
109
                         tree_conflicts.select_conflicts(tree, ['foo']))
 
 
110
        self.assertEqual((ConflictList(), tree_conflicts),
 
 
111
                         tree_conflicts.select_conflicts(tree, [''],
 
 
112
                         ignore_misses=True, recurse=True))
 
 
113
        tree_conflicts = ConflictList([ContentsConflict('foo/baz'),
 
 
114
                                       ContentsConflict('bar')])
 
 
115
        self.assertEqual((ConflictList([ContentsConflict('bar')]),
 
 
116
                          ConflictList([ContentsConflict('foo/baz')])),
 
 
117
                         tree_conflicts.select_conflicts(tree, ['foo'],
 
 
120
        tree_conflicts = ConflictList([PathConflict('qux', 'foo/baz')])
 
 
121
        self.assertEqual((ConflictList(), tree_conflicts),
 
 
122
                         tree_conflicts.select_conflicts(tree, ['foo'],
 
 
125
        self.assertEqual((tree_conflicts, ConflictList()),
 
 
126
                         tree_conflicts.select_conflicts(tree, ['foo'],
 
149
129
    def test_resolve_conflicts_recursive(self):
 
150
130
        tree = self.make_branch_and_tree('.')
 
151
131
        self.build_tree(['dir/', 'dir/hello'])
 
152
132
        tree.add(['dir', 'dir/hello'])
 
154
 
        dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
 
155
 
        tree.set_conflicts(dirhello)
 
157
 
        conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
 
158
 
        self.assertEqual(dirhello, tree.conflicts())
 
160
 
        conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
 
161
 
        self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
 
164
 
class TestConflictStanzas(tests.TestCase):
 
 
133
        tree.set_conflicts(ConflictList([TextConflict('dir/hello')]))
 
 
134
        resolve(tree, ['dir'], recursive=False, ignore_misses=True)
 
 
135
        self.assertEqual(ConflictList([TextConflict('dir/hello')]),
 
 
137
        resolve(tree, ['dir'], recursive=True, ignore_misses=True)
 
 
138
        self.assertEqual(ConflictList([]),
 
 
142
class TestConflictStanzas(TestCase):
 
166
144
    def test_stanza_roundtrip(self):
 
167
145
        # write and read our example stanza.
 
168
146
        stanza_iter = example_conflicts.to_stanzas()
 
169
 
        processed = conflicts.ConflictList.from_stanzas(stanza_iter)
 
 
147
        processed = ConflictList.from_stanzas(stanza_iter)
 
170
148
        for o, p in zip(processed, example_conflicts):
 
171
149
            self.assertEqual(o, p)
 
 
193
171
                self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
 
194
172
            if 'conflict_file_id' in stanza:
 
195
173
                self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
 
198
 
# FIXME: The shell-like tests should be converted to real whitebox tests... or
 
199
 
# moved to a blackbox module -- vila 20100205
 
201
 
# FIXME: test missing for multiple conflicts
 
203
 
# FIXME: Tests missing for DuplicateID conflict type
 
204
 
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
 
206
 
    preamble = None # The setup script set by daughter classes
 
209
 
        super(TestResolveConflicts, self).setUp()
 
210
 
        self.run_script(self.preamble)
 
213
 
class TestResolveTextConflicts(TestResolveConflicts):
 
218
 
def mirror_scenarios(base_scenarios):
 
219
 
    """Return a list of mirrored scenarios.
 
221
 
    Each scenario in base_scenarios is duplicated switching the roles of 'this'
 
225
 
    for common, (lname, ldict), (rname, rdict) in base_scenarios:
 
226
 
        a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
 
227
 
                                     [(rname, dict(_other=rdict))])
 
228
 
        b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
 
229
 
                                     [(lname, dict(_other=ldict))])
 
230
 
        # Inject the common parameters in all scenarios
 
231
 
        for name, d in a + b:
 
233
 
        scenarios.extend(a + b)
 
237
 
# FIXME: Get rid of parametrized (in the class name) once we delete
 
238
 
# TestResolveConflicts -- vila 20100308
 
239
 
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
 
240
 
    """This class provides a base to test single conflict resolution.
 
242
 
    Since all conflict objects are created with specific semantics for their
 
243
 
    attributes, each class should implement the necessary functions and
 
244
 
    attributes described below.
 
246
 
    Each class should define the scenarios that create the expected (single)
 
249
 
    Each scenario describes:
 
250
 
    * how to create 'base' tree (and revision)
 
251
 
    * how to create 'left' tree (and revision, parent rev 'base')
 
252
 
    * how to create 'right' tree (and revision, parent rev 'base')
 
253
 
    * how to check that changes in 'base'->'left' have been taken
 
254
 
    * how to check that changes in 'base'->'right' have been taken
 
256
 
    From each base scenario, we generate two concrete scenarios where:
 
257
 
    * this=left, other=right
 
258
 
    * this=right, other=left
 
260
 
    Then the test case verifies each concrete scenario by:
 
261
 
    * creating a branch containing the 'base', 'this' and 'other' revisions
 
262
 
    * creating a working tree for the 'this' revision
 
263
 
    * performing the merge of 'other' into 'this'
 
264
 
    * verifying the expected conflict was generated
 
265
 
    * resolving with --take-this or --take-other, and running the corresponding
 
266
 
      checks (for either 'base'->'this', or 'base'->'other')
 
268
 
    :cvar _conflict_type: The expected class of the generated conflict.
 
270
 
    :cvar _assert_conflict: A method receiving the working tree and the
 
271
 
        conflict object and checking its attributes.
 
273
 
    :cvar _base_actions: The branchbuilder actions to create the 'base'
 
276
 
    :cvar _this: The dict related to 'base' -> 'this'. It contains at least:
 
277
 
      * 'actions': The branchbuilder actions to create the 'this'
 
279
 
      * 'check': how to check the changes after resolution with --take-this.
 
281
 
    :cvar _other: The dict related to 'base' -> 'other'. It contains at least:
 
282
 
      * 'actions': The branchbuilder actions to create the 'other'
 
284
 
      * 'check': how to check the changes after resolution with --take-other.
 
287
 
    # Set by daughter classes
 
288
 
    _conflict_type = None
 
289
 
    _assert_conflict = None
 
298
 
        """Return the scenario list for the conflict type defined by the class.
 
300
 
        Each scenario is of the form:
 
301
 
        (common, (left_name, left_dict), (right_name, right_dict))
 
305
 
        * left_name and right_name are the scenario names that will be combined
 
307
 
        * left_dict and right_dict are the attributes specific to each half of
 
308
 
          the scenario. They should include at least 'actions' and 'check' and
 
309
 
          will be available as '_this' and '_other' test instance attributes.
 
311
 
        Daughters classes are free to add their specific attributes as they see
 
312
 
        fit in any of the three dicts.
 
314
 
        This is a class method so that load_tests can find it.
 
316
 
        '_base_actions' in the common dict, 'actions' and 'check' in the left
 
317
 
        and right dicts use names that map to methods in the test classes. Some
 
318
 
        prefixes are added to these names to get the correspong methods (see
 
319
 
        _get_actions() and _get_check()). The motivation here is to avoid
 
320
 
        collisions in the class namespace.
 
322
 
        # Only concrete classes return actual scenarios
 
326
 
        super(TestParametrizedResolveConflicts, self).setUp()
 
327
 
        builder = self.make_branch_builder('trunk')
 
328
 
        builder.start_series()
 
330
 
        # Create an empty trunk
 
331
 
        builder.build_snapshot('start', None, [
 
332
 
                ('add', ('', 'root-id', 'directory', ''))])
 
333
 
        # Add a minimal base content
 
334
 
        base_actions = self._get_actions(self._base_actions)()
 
335
 
        builder.build_snapshot('base', ['start'], base_actions)
 
336
 
        # Modify the base content in branch
 
337
 
        actions_other = self._get_actions(self._other['actions'])()
 
338
 
        builder.build_snapshot('other', ['base'], actions_other)
 
339
 
        # Modify the base content in trunk
 
340
 
        actions_this = self._get_actions(self._this['actions'])()
 
341
 
        builder.build_snapshot('this', ['base'], actions_this)
 
342
 
        # builder.get_branch() tip is now 'this'
 
344
 
        builder.finish_series()
 
345
 
        self.builder = builder
 
347
 
    def _get_actions(self, name):
 
348
 
        return getattr(self, 'do_%s' % name)
 
350
 
    def _get_check(self, name):
 
351
 
        return getattr(self, 'check_%s' % name)
 
353
 
    def _merge_other_into_this(self):
 
354
 
        b = self.builder.get_branch()
 
355
 
        wt = b.bzrdir.sprout('branch').open_workingtree()
 
356
 
        wt.merge_from_branch(b, 'other')
 
359
 
    def assertConflict(self, wt):
 
360
 
        confs = wt.conflicts()
 
361
 
        self.assertLength(1, confs)
 
363
 
        self.assertIsInstance(c, self._conflict_type)
 
364
 
        self._assert_conflict(wt, c)
 
366
 
    def _get_resolve_path_arg(self, wt, action):
 
367
 
        raise NotImplementedError(self._get_resolve_path_arg)
 
369
 
    def check_resolved(self, wt, action):
 
370
 
        path = self._get_resolve_path_arg(wt, action)
 
371
 
        conflicts.resolve(wt, [path], action=action)
 
372
 
        # Check that we don't have any conflicts nor unknown left
 
373
 
        self.assertLength(0, wt.conflicts())
 
374
 
        self.assertLength(0, list(wt.unknowns()))
 
376
 
    def test_resolve_taking_this(self):
 
377
 
        wt = self._merge_other_into_this()
 
378
 
        self.assertConflict(wt)
 
379
 
        self.check_resolved(wt, 'take_this')
 
380
 
        check_this = self._get_check(self._this['check'])
 
383
 
    def test_resolve_taking_other(self):
 
384
 
        wt = self._merge_other_into_this()
 
385
 
        self.assertConflict(wt)
 
386
 
        self.check_resolved(wt, 'take_other')
 
387
 
        check_other = self._get_check(self._other['check'])
 
391
 
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
393
 
    _conflict_type = conflicts.ContentsConflict,
 
395
 
    # Set by load_tests from scenarios()
 
396
 
    # path and file-id for the file involved in the conflict
 
403
 
            # File modified/deleted
 
404
 
            (dict(_base_actions='create_file',
 
405
 
                  _path='file', _file_id='file-id'),
 
407
 
              dict(actions='modify_file', check='file_has_more_content')),
 
409
 
              dict(actions='delete_file', check='file_doesnt_exist')),),
 
411
 
        return mirror_scenarios(base_scenarios)
 
413
 
    def do_create_file(self):
 
414
 
        return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
 
416
 
    def do_modify_file(self):
 
417
 
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
 
419
 
    def check_file_has_more_content(self):
 
420
 
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
 
422
 
    def do_delete_file(self):
 
423
 
        return [('unversion', 'file-id')]
 
425
 
    def check_file_doesnt_exist(self):
 
426
 
        self.failIfExists('branch/file')
 
428
 
    def _get_resolve_path_arg(self, wt, action):
 
431
 
    def assertContentsConflict(self, wt, c):
 
432
 
        self.assertEqual(self._file_id, c.file_id)
 
433
 
        self.assertEqual(self._path, c.path)
 
434
 
    _assert_conflict = assertContentsConflict
 
437
 
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
439
 
    _conflict_type = conflicts.PathConflict,
 
441
 
    def do_nothing(self):
 
446
 
        # Each side dict additionally defines:
 
447
 
        # - path path involved (can be '<deleted>')
 
450
 
            # File renamed/deleted
 
451
 
            (dict(_base_actions='create_file'),
 
453
 
              dict(actions='rename_file', check='file_renamed',
 
454
 
                   path='new-file', file_id='file-id')),
 
456
 
              dict(actions='delete_file', check='file_doesnt_exist',
 
457
 
                   # PathConflicts deletion handling requires a special
 
459
 
                   path='<deleted>', file_id='file-id')),),
 
460
 
            # File renamed/renamed differently
 
461
 
            (dict(_base_actions='create_file'),
 
463
 
              dict(actions='rename_file', check='file_renamed',
 
464
 
                   path='new-file', file_id='file-id')),
 
466
 
              dict(actions='rename_file2', check='file_renamed2',
 
467
 
                   path='new-file2', file_id='file-id')),),
 
468
 
            # Dir renamed/deleted
 
469
 
            (dict(_base_actions='create_dir'),
 
471
 
              dict(actions='rename_dir', check='dir_renamed',
 
472
 
                   path='new-dir', file_id='dir-id')),
 
474
 
              dict(actions='delete_dir', check='dir_doesnt_exist',
 
475
 
                   # PathConflicts deletion handling requires a special
 
477
 
                   path='<deleted>', file_id='dir-id')),),
 
478
 
            # Dir renamed/renamed differently
 
479
 
            (dict(_base_actions='create_dir'),
 
481
 
              dict(actions='rename_dir', check='dir_renamed',
 
482
 
                   path='new-dir', file_id='dir-id')),
 
484
 
              dict(actions='rename_dir2', check='dir_renamed2',
 
485
 
                   path='new-dir2', file_id='dir-id')),),
 
487
 
        return mirror_scenarios(base_scenarios)
 
489
 
    def do_create_file(self):
 
490
 
        return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
 
492
 
    def do_create_dir(self):
 
493
 
        return [('add', ('dir', 'dir-id', 'directory', ''))]
 
495
 
    def do_rename_file(self):
 
496
 
        return [('rename', ('file', 'new-file'))]
 
498
 
    def check_file_renamed(self):
 
499
 
        self.failIfExists('branch/file')
 
500
 
        self.failUnlessExists('branch/new-file')
 
502
 
    def do_rename_file2(self):
 
503
 
        return [('rename', ('file', 'new-file2'))]
 
505
 
    def check_file_renamed2(self):
 
506
 
        self.failIfExists('branch/file')
 
507
 
        self.failUnlessExists('branch/new-file2')
 
509
 
    def do_rename_dir(self):
 
510
 
        return [('rename', ('dir', 'new-dir'))]
 
512
 
    def check_dir_renamed(self):
 
513
 
        self.failIfExists('branch/dir')
 
514
 
        self.failUnlessExists('branch/new-dir')
 
516
 
    def do_rename_dir2(self):
 
517
 
        return [('rename', ('dir', 'new-dir2'))]
 
519
 
    def check_dir_renamed2(self):
 
520
 
        self.failIfExists('branch/dir')
 
521
 
        self.failUnlessExists('branch/new-dir2')
 
523
 
    def do_delete_file(self):
 
524
 
        return [('unversion', 'file-id')]
 
526
 
    def check_file_doesnt_exist(self):
 
527
 
        self.failIfExists('branch/file')
 
529
 
    def do_delete_dir(self):
 
530
 
        return [('unversion', 'dir-id')]
 
532
 
    def check_dir_doesnt_exist(self):
 
533
 
        self.failIfExists('branch/dir')
 
535
 
    def _get_resolve_path_arg(self, wt, action):
 
536
 
        tpath = self._this['path']
 
537
 
        opath = self._other['path']
 
538
 
        if tpath == '<deleted>':
 
544
 
    def assertPathConflict(self, wt, c):
 
545
 
        tpath = self._this['path']
 
546
 
        tfile_id = self._this['file_id']
 
547
 
        opath = self._other['path']
 
548
 
        ofile_id = self._other['file_id']
 
549
 
        self.assertEqual(tfile_id, ofile_id) # Sanity check
 
550
 
        self.assertEqual(tfile_id, c.file_id)
 
551
 
        self.assertEqual(tpath, c.path)
 
552
 
        self.assertEqual(opath, c.conflict_path)
 
553
 
    _assert_conflict = assertPathConflict
 
556
 
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
557
 
    """Same as TestResolvePathConflict but a specific conflict object.
 
560
 
    def assertPathConflict(self, c):
 
561
 
        # We create a conflict object as it was created before the fix and
 
562
 
        # inject it into the working tree, the test will exercise the
 
563
 
        # compatibility code.
 
564
 
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
566
 
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
569
 
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
 
571
 
    _conflict_type = conflicts.DuplicateEntry,
 
575
 
        # Each side dict additionally defines:
 
579
 
            # File created with different file-ids
 
580
 
            (dict(_base_actions='nothing'),
 
582
 
              dict(actions='create_file_a', check='file_content_a',
 
583
 
                   path='file', file_id='file-a-id')),
 
585
 
              dict(actions='create_file_b', check='file_content_b',
 
586
 
                   path='file', file_id='file-b-id')),),
 
588
 
        return mirror_scenarios(base_scenarios)
 
590
 
    def do_nothing(self):
 
593
 
    def do_create_file_a(self):
 
594
 
        return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
 
596
 
    def check_file_content_a(self):
 
597
 
        self.assertFileEqual('file a content\n', 'branch/file')
 
599
 
    def do_create_file_b(self):
 
600
 
        return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
 
602
 
    def check_file_content_b(self):
 
603
 
        self.assertFileEqual('file b content\n', 'branch/file')
 
605
 
    def _get_resolve_path_arg(self, wt, action):
 
606
 
        return self._this['path']
 
608
 
    def assertDuplicateEntry(self, wt, c):
 
609
 
        tpath = self._this['path']
 
610
 
        tfile_id = self._this['file_id']
 
611
 
        opath = self._other['path']
 
612
 
        ofile_id = self._other['file_id']
 
613
 
        self.assertEqual(tpath, opath) # Sanity check
 
614
 
        self.assertEqual(tfile_id, c.file_id)
 
615
 
        self.assertEqual(tpath + '.moved', c.path)
 
616
 
        self.assertEqual(tpath, c.conflict_path)
 
617
 
    _assert_conflict = assertDuplicateEntry
 
620
 
class TestResolveUnversionedParent(TestResolveConflicts):
 
622
 
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
 
624
 
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
 
625
 
    # tests MissingParent resolution :-/
 
631
 
$ bzr commit -m 'Create trunk'
 
633
 
$ echo 'trunk content' >dir/file
 
635
 
$ bzr commit -m 'Add dir/file in trunk'
 
637
 
$ bzr branch . -r 1 ../branch
 
640
 
$ bzr commit -m 'Remove dir in branch'
 
645
 
2>Conflict adding files to dir.  Created directory.
 
646
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
647
 
2>2 conflicts encountered.
 
650
 
    def test_take_this(self):
 
654
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
657
 
    def test_take_other(self):
 
660
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
664
 
class TestResolveMissingParent(TestResolveConflicts):
 
670
 
$ echo 'trunk content' >dir/file
 
672
 
$ bzr commit -m 'Create trunk'
 
674
 
$ echo 'trunk content' >dir/file2
 
676
 
$ bzr commit -m 'Add dir/file2 in branch'
 
678
 
$ bzr branch . -r 1 ../branch
 
680
 
$ bzr rm dir/file --force
 
682
 
$ bzr commit -m 'Remove dir/file'
 
687
 
2>Conflict adding files to dir.  Created directory.
 
688
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
689
 
2>2 conflicts encountered.
 
692
 
    def test_keep_them_all(self):
 
695
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
698
 
    def test_adopt_child(self):
 
700
 
$ bzr mv dir/file2 file2
 
703
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
706
 
    def test_kill_them_all(self):
 
710
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
713
 
    def test_resolve_taking_this(self):
 
715
 
$ bzr resolve --take-this dir
 
716
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
719
 
    def test_resolve_taking_other(self):
 
721
 
$ bzr resolve --take-other dir
 
722
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
726
 
class TestResolveDeletingParent(TestResolveConflicts):
 
732
 
$ echo 'trunk content' >dir/file
 
734
 
$ bzr commit -m 'Create trunk'
 
736
 
$ bzr rm dir/file --force
 
738
 
$ bzr commit -m 'Remove dir/file'
 
740
 
$ bzr branch . -r 1 ../branch
 
742
 
$ echo 'branch content' >dir/file2
 
744
 
$ bzr commit -m 'Add dir/file2 in branch'
 
748
 
2>Conflict: can't delete dir because it is not empty.  Not deleting.
 
749
 
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
750
 
2>2 conflicts encountered.
 
753
 
    def test_keep_them_all(self):
 
756
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
759
 
    def test_adopt_child(self):
 
761
 
$ bzr mv dir/file2 file2
 
764
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
767
 
    def test_kill_them_all(self):
 
771
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
774
 
    def test_resolve_taking_this(self):
 
776
 
$ bzr resolve --take-this dir
 
777
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
780
 
    def test_resolve_taking_other(self):
 
782
 
$ bzr resolve --take-other dir
 
783
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
787
 
class TestResolveParentLoop(TestParametrizedResolveConflicts):
 
789
 
    _conflict_type = conflicts.ParentLoop,
 
796
 
        # Each side dict additionally defines:
 
797
 
        # - dir_id: the directory being moved
 
798
 
        # - target_id: The target directory
 
799
 
        # - xfail: whether the test is expected to fail if the action is
 
800
 
        #     involved as 'other'
 
802
 
            # Dirs moved into each other
 
803
 
            (dict(_base_actions='create_dir1_dir2'),
 
805
 
              dict(actions='move_dir1_into_dir2', check='dir1_moved',
 
806
 
                   dir_id='dir1-id', target_id='dir2-id', xfail=False)),
 
808
 
              dict(actions='move_dir2_into_dir1', check='dir2_moved',
 
809
 
                   dir_id='dir2-id', target_id='dir1-id', xfail=False))),
 
810
 
            # Subdirs moved into each other
 
811
 
            (dict(_base_actions='create_dir1_4'),
 
813
 
              dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
 
814
 
                   dir_id='dir1-id', target_id='dir4-id', xfail=True)),
 
816
 
              dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
 
817
 
                   dir_id='dir3-id', target_id='dir2-id', xfail=True))),
 
819
 
        return mirror_scenarios(base_scenarios)
 
821
 
    def do_create_dir1_dir2(self):
 
822
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
 
823
 
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
 
825
 
    def do_move_dir1_into_dir2(self):
 
826
 
        return [('rename', ('dir1', 'dir2/dir1'))]
 
828
 
    def check_dir1_moved(self):
 
829
 
        self.failIfExists('branch/dir1')
 
830
 
        self.failUnlessExists('branch/dir2/dir1')
 
832
 
    def do_move_dir2_into_dir1(self):
 
833
 
        return [('rename', ('dir2', 'dir1/dir2'))]
 
835
 
    def check_dir2_moved(self):
 
836
 
        self.failIfExists('branch/dir2')
 
837
 
        self.failUnlessExists('branch/dir1/dir2')
 
839
 
    def do_create_dir1_4(self):
 
840
 
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
 
841
 
                ('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
 
842
 
                ('add', ('dir3', 'dir3-id', 'directory', '')),
 
843
 
                ('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
 
845
 
    def do_move_dir1_into_dir4(self):
 
846
 
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
 
848
 
    def check_dir1_2_moved(self):
 
849
 
        self.failIfExists('branch/dir1')
 
850
 
        self.failUnlessExists('branch/dir3/dir4/dir1')
 
851
 
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
 
853
 
    def do_move_dir3_into_dir2(self):
 
854
 
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
 
856
 
    def check_dir3_4_moved(self):
 
857
 
        self.failIfExists('branch/dir3')
 
858
 
        self.failUnlessExists('branch/dir1/dir2/dir3')
 
859
 
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
 
861
 
    def _get_resolve_path_arg(self, wt, action):
 
862
 
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
 
863
 
        # But since <path> doesn't exist in the working tree, we need to use
 
864
 
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
 
865
 
        return wt.id2path(self._other['dir_id'])
 
867
 
    def assertParentLoop(self, wt, c):
 
868
 
        self.assertEqual(self._other['dir_id'], c.file_id)
 
869
 
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
 
870
 
        # The conflict paths are irrelevant (they are deterministic but not
 
871
 
        # worth checking since they don't provide the needed information
 
873
 
        if self._other['xfail']:
 
874
 
            # It's a bit hackish to raise from here relying on being called for
 
875
 
            # both tests but this avoid overriding test_resolve_taking_other
 
876
 
            raise tests.KnownFailure(
 
877
 
                "ParentLoop doesn't carry enough info to resolve --take-other")
 
878
 
    _assert_conflict = assertParentLoop
 
881
 
class TestResolveNonDirectoryParent(TestResolveConflicts):
 
887
 
$ bzr commit -m 'Create trunk'
 
888
 
$ echo "Boing" >foo/bar
 
890
 
$ bzr commit -m 'Add foo/bar'
 
892
 
$ bzr branch . -r 1 ../branch
 
896
 
$ bzr commit -m 'foo is now a file'
 
901
 
# FIXME: The message is misleading, foo.new *is* a directory when the message
 
902
 
# is displayed -- vila 090916
 
903
 
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
 
904
 
2>1 conflicts encountered.
 
907
 
    def test_take_this(self):
 
909
 
$ bzr rm foo.new --force
 
910
 
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
 
911
 
# aside ? -- vila 090916
 
913
 
$ bzr resolve foo.new
 
914
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
917
 
    def test_take_other(self):
 
922
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
925
 
    def test_resolve_taking_this(self):
 
927
 
$ bzr resolve --take-this foo.new
 
928
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
931
 
    def test_resolve_taking_other(self):
 
933
 
$ bzr resolve --take-other foo.new
 
934
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
938
 
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
 
940
 
    def test_bug_430129(self):
 
941
 
        # This is nearly like TestResolveNonDirectoryParent but with branch and
 
942
 
        # trunk switched. As such it should certainly produce the same
 
948
 
$ bzr commit -m 'Create trunk'
 
951
 
$ bzr commit -m 'foo is now a file'
 
953
 
$ bzr branch . -r 1 ../branch
 
955
 
$ echo "Boing" >foo/bar
 
957
 
$ bzr commit -m 'Add foo/bar'
 
960
 
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
 
964
 
class TestResolveActionOption(tests.TestCase):
 
967
 
        super(TestResolveActionOption, self).setUp()
 
968
 
        self.options = [conflicts.ResolveActionOption()]
 
969
 
        self.parser = option.get_optparser(dict((o.name, o)
 
970
 
                                                for o in self.options))
 
972
 
    def parse(self, args):
 
973
 
        return self.parser.parse_args(args)
 
975
 
    def test_unknown_action(self):
 
976
 
        self.assertRaises(errors.BadOptionValue,
 
977
 
                          self.parse, ['--action', 'take-me-to-the-moon'])
 
980
 
        opts, args = self.parse(['--action', 'done'])
 
981
 
        self.assertEqual({'action':'done'}, opts)
 
983
 
    def test_take_this(self):
 
984
 
        opts, args = self.parse(['--action', 'take-this'])
 
985
 
        self.assertEqual({'action': 'take_this'}, opts)
 
986
 
        opts, args = self.parse(['--take-this'])
 
987
 
        self.assertEqual({'action': 'take_this'}, opts)
 
989
 
    def test_take_other(self):
 
990
 
        opts, args = self.parse(['--action', 'take-other'])
 
991
 
        self.assertEqual({'action': 'take_other'}, opts)
 
992
 
        opts, args = self.parse(['--take-other'])
 
993
 
        self.assertEqual({'action': 'take_other'}, opts)