/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/tests/test_conflicts.py

renamed copy_ownership to copy_ownership_from_path.
updated .bzr.log to have 0644 permissions.
improved docstring for create_log_file

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
import os
 
19
 
 
20
from bzrlib import (
 
21
    branchbuilder,
 
22
    bzrdir,
 
23
    conflicts,
 
24
    errors,
 
25
    option,
 
26
    tests,
 
27
    workingtree,
 
28
    )
 
29
from bzrlib.tests import script
 
30
 
 
31
 
 
32
def load_tests(standard_tests, module, loader):
 
33
    result = loader.suiteClass()
 
34
 
 
35
    sp_tests, remaining_tests = tests.split_suite_by_condition(
 
36
        standard_tests, tests.condition_isinstance((
 
37
                TestParametrizedResolveConflicts,
 
38
                )))
 
39
    # Each test class defines its own scenarios. This is needed for
 
40
    # TestResolvePathConflictBefore531967 that verifies that the same tests as
 
41
    # TestResolvePathConflict still pass.
 
42
    for test in tests.iter_suite_tests(sp_tests):
 
43
        tests.apply_scenarios(test, test.scenarios(), result)
 
44
 
 
45
    # No parametrization for the remaining tests
 
46
    result.addTests(remaining_tests)
 
47
 
 
48
    return result
 
49
 
 
50
 
 
51
# TODO: Test commit with some added, and added-but-missing files
 
52
# RBC 20060124 is that not tested in test_commit.py ?
 
53
 
 
54
# The order of 'path' here is important - do not let it
 
55
# be a sorted list.
 
56
# u'\xe5' == a with circle
 
57
# '\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',
 
69
                             '\xc3\xaedd', None),
 
70
    conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
 
71
                         None, '\xc3\xaed2e'),
 
72
    conflicts.UnversionedParent('Versioned directory',
 
73
                                u'p\xe5thf', '\xc3\xaedf'),
 
74
    conflicts.NonDirectoryParent('Created directory',
 
75
                                 u'p\xe5thg', '\xc3\xaedg'),
 
76
])
 
77
 
 
78
 
 
79
class TestConflicts(tests.TestCaseWithTransport):
 
80
 
 
81
    def test_conflicts(self):
 
82
        """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'),
 
91
                                  ])
 
92
        tree.lock_read()
 
93
        self.assertLength(6, list(tree.list_files()))
 
94
        tree.unlock()
 
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())
 
102
        self.assertFileEqual('hello world2', 'hello')
 
103
        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')
 
107
 
 
108
    def test_resolve_conflict_dir(self):
 
109
        tree = self.make_branch_and_tree('.')
 
110
        self.build_tree_contents([('hello', 'hello world4'),
 
111
                                  ('hello.THIS', 'hello world2'),
 
112
                                  ('hello.BASE', 'hello world1'),
 
113
                                  ])
 
114
        os.mkdir('hello.OTHER')
 
115
        tree.add('hello', 'q')
 
116
        l = conflicts.ConflictList([conflicts.TextConflict('hello')])
 
117
        l.remove_files(tree)
 
118
 
 
119
    def test_select_conflicts(self):
 
120
        tree = self.make_branch_and_tree('.')
 
121
        clist = conflicts.ConflictList
 
122
 
 
123
        def check_select(not_selected, selected, paths, **kwargs):
 
124
            self.assertEqual(
 
125
                (not_selected, selected),
 
126
                tree_conflicts.select_conflicts(tree, paths, **kwargs))
 
127
 
 
128
        foo = conflicts.ContentsConflict('foo')
 
129
        bar = conflicts.ContentsConflict('bar')
 
130
        tree_conflicts = clist([foo, bar])
 
131
 
 
132
        check_select(clist([bar]), clist([foo]), ['foo'])
 
133
        check_select(clist(), tree_conflicts,
 
134
                     [''], ignore_misses=True, recurse=True)
 
135
 
 
136
        foobaz  = conflicts.ContentsConflict('foo/baz')
 
137
        tree_conflicts = clist([foobaz, bar])
 
138
 
 
139
        check_select(clist([bar]), clist([foobaz]),
 
140
                     ['foo'], ignore_misses=True, recurse=True)
 
141
 
 
142
        qux = conflicts.PathConflict('qux', 'foo/baz')
 
143
        tree_conflicts = clist([qux])
 
144
 
 
145
        check_select(clist(), tree_conflicts,
 
146
                     ['foo'], ignore_misses=True, recurse=True)
 
147
        check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
 
148
 
 
149
    def test_resolve_conflicts_recursive(self):
 
150
        tree = self.make_branch_and_tree('.')
 
151
        self.build_tree(['dir/', 'dir/hello'])
 
152
        tree.add(['dir', 'dir/hello'])
 
153
 
 
154
        dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
 
155
        tree.set_conflicts(dirhello)
 
156
 
 
157
        conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
 
158
        self.assertEqual(dirhello, tree.conflicts())
 
159
 
 
160
        conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
 
161
        self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
 
162
 
 
163
 
 
164
class TestConflictStanzas(tests.TestCase):
 
165
 
 
166
    def test_stanza_roundtrip(self):
 
167
        # write and read our example stanza.
 
168
        stanza_iter = example_conflicts.to_stanzas()
 
169
        processed = conflicts.ConflictList.from_stanzas(stanza_iter)
 
170
        for o, p in zip(processed, example_conflicts):
 
171
            self.assertEqual(o, p)
 
172
 
 
173
            self.assertIsInstance(o.path, unicode)
 
174
 
 
175
            if o.file_id is not None:
 
176
                self.assertIsInstance(o.file_id, str)
 
177
 
 
178
            conflict_path = getattr(o, 'conflict_path', None)
 
179
            if conflict_path is not None:
 
180
                self.assertIsInstance(conflict_path, unicode)
 
181
 
 
182
            conflict_file_id = getattr(o, 'conflict_file_id', None)
 
183
            if conflict_file_id is not None:
 
184
                self.assertIsInstance(conflict_file_id, str)
 
185
 
 
186
    def test_stanzification(self):
 
187
        for stanza in example_conflicts.to_stanzas():
 
188
            if 'file_id' in stanza:
 
189
                # In Stanza form, the file_id has to be unicode.
 
190
                self.assertStartsWith(stanza['file_id'], u'\xeed')
 
191
            self.assertStartsWith(stanza['path'], u'p\xe5th')
 
192
            if 'conflict_path' in stanza:
 
193
                self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
 
194
            if 'conflict_file_id' in stanza:
 
195
                self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
 
196
 
 
197
 
 
198
# FIXME: The shell-like tests should be converted to real whitebox tests... or
 
199
# moved to a blackbox module -- vila 20100205
 
200
 
 
201
# FIXME: test missing for multiple conflicts
 
202
 
 
203
# FIXME: Tests missing for DuplicateID conflict type
 
204
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
 
205
 
 
206
    preamble = None # The setup script set by daughter classes
 
207
 
 
208
    def setUp(self):
 
209
        super(TestResolveConflicts, self).setUp()
 
210
        self.run_script(self.preamble)
 
211
 
 
212
 
 
213
class TestResolveTextConflicts(TestResolveConflicts):
 
214
    # TBC
 
215
    pass
 
216
 
 
217
 
 
218
# FIXME: Get rid of parametrized (in the class name) once we delete
 
219
# TestResolveConflicts -- vila 20100308
 
220
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
 
221
    """This class provides a base to test single conflict resolution.
 
222
 
 
223
    The aim is to define scenarios in daughter classes (one for each conflict
 
224
    type) that create a single conflict object when one branch is merged in
 
225
    another (and vice versa). Each class can define as many scenarios as
 
226
    needed. Each scenario should define a couple of actions that will be
 
227
    swapped to define the sibling scenarios.
 
228
 
 
229
    From there, both resolutions are tested (--take-this and --take-other).
 
230
 
 
231
    Each conflict type use its attributes in a specific way, so each class 
 
232
    should define a specific _assert_conflict method.
 
233
 
 
234
    Since the resolution change the working tree state, each action should
 
235
    define an associated check.
 
236
    """
 
237
 
 
238
    # Set by daughter classes
 
239
    _conflict_type = None
 
240
    _assert_conflict = None
 
241
 
 
242
    # Set by load_tests
 
243
    _base_actions = None
 
244
    _this_actions = None
 
245
    _other_actions = None
 
246
    _item_path = None
 
247
    _item_id = None
 
248
 
 
249
    # Set by _this_actions and other_actions
 
250
    # FIXME: rename them this_args and other_args so the tests can use them
 
251
    # more freely
 
252
    _this_path = None
 
253
    _this_id = None
 
254
    _other_path = None
 
255
    _other_id = None
 
256
 
 
257
    @classmethod
 
258
    def mirror_scenarios(klass, base_scenarios):
 
259
        scenarios = []
 
260
        def adapt(d, side):
 
261
            """Modify dict to apply to the given side.
 
262
 
 
263
            'actions' key is turned into '_actions_this' if side is 'this' for
 
264
            example.
 
265
            """
 
266
            t = {}
 
267
            # Turn each key into _side_key
 
268
            for k,v in d.iteritems():
 
269
                t['_%s_%s' % (k, side)] = v
 
270
            return t
 
271
        # Each base scenario is duplicated switching the roles of 'this' and
 
272
        # 'other'
 
273
        left = [l for l, r, c in base_scenarios]
 
274
        right = [r for l, r, c in base_scenarios]
 
275
        common = [c for l, r, c in base_scenarios]
 
276
        for (lname, ldict), (rname, rdict), common in zip(left, right, common):
 
277
            a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
 
278
                                         [(rname, adapt(rdict, 'other'))])
 
279
            b = tests.multiply_scenarios(
 
280
                    [(rname, adapt(rdict, 'this'))],
 
281
                    [(lname, adapt(ldict, 'other'))])
 
282
            # Inject the common parameters in all scenarios
 
283
            for name, d in a + b:
 
284
                d.update(common)
 
285
            scenarios.extend(a + b)
 
286
        return scenarios
 
287
 
 
288
    @classmethod
 
289
    def scenarios(klass):
 
290
        # Only concrete classes return actual scenarios
 
291
        return []
 
292
 
 
293
    def setUp(self):
 
294
        super(TestParametrizedResolveConflicts, self).setUp()
 
295
        builder = self.make_branch_builder('trunk')
 
296
        builder.start_series()
 
297
 
 
298
        # Create an empty trunk
 
299
        builder.build_snapshot('start', None, [
 
300
                ('add', ('', 'root-id', 'directory', ''))])
 
301
        # Add a minimal base content
 
302
        _, _, actions_base = self._get_actions(self._actions_base)()
 
303
        builder.build_snapshot('base', ['start'], actions_base)
 
304
        # Modify the base content in branch
 
305
        (self._other_path, self._other_id,
 
306
         actions_other) = self._get_actions(self._actions_other)()
 
307
        builder.build_snapshot('other', ['base'], actions_other)
 
308
        # Modify the base content in trunk
 
309
        (self._this_path, self._this_id,
 
310
         actions_this) = self._get_actions(self._actions_this)()
 
311
        builder.build_snapshot('this', ['base'], actions_this)
 
312
        # builder.get_branch() tip is now 'this'
 
313
 
 
314
        builder.finish_series()
 
315
        self.builder = builder
 
316
 
 
317
    def _get_actions(self, name):
 
318
        return getattr(self, 'do_%s' % name)
 
319
 
 
320
    def _get_check(self, name):
 
321
        return getattr(self, 'check_%s' % name)
 
322
 
 
323
    def do_nothing(self):
 
324
        return (None, None, [])
 
325
 
 
326
    def do_create_file(self):
 
327
        return ('file', 'file-id',
 
328
                [('add', ('file', 'file-id', 'file', 'trunk content\n'))])
 
329
 
 
330
    def do_create_file_a(self):
 
331
        return ('file', 'file-a-id',
 
332
                [('add', ('file', 'file-a-id', 'file', 'file a content\n'))])
 
333
 
 
334
    def check_file_content_a(self):
 
335
        self.assertFileEqual('file a content\n', 'branch/file')
 
336
 
 
337
    def do_create_file_b(self):
 
338
        return ('file', 'file-b-id',
 
339
                [('add', ('file', 'file-b-id', 'file', 'file b content\n'))])
 
340
 
 
341
    def check_file_content_b(self):
 
342
        self.assertFileEqual('file b content\n', 'branch/file')
 
343
 
 
344
    def do_create_dir(self):
 
345
        return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
 
346
 
 
347
    def do_modify_file(self):
 
348
        return ('file', 'file-id',
 
349
                [('modify', ('file-id', 'trunk content\nmore content\n'))])
 
350
 
 
351
    def check_file_has_more_content(self):
 
352
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
 
353
 
 
354
    def do_delete_file(self):
 
355
        return ('file', 'file-id', [('unversion', 'file-id')])
 
356
 
 
357
    def check_file_doesnt_exist(self):
 
358
        self.failIfExists('branch/file')
 
359
 
 
360
    def do_rename_file(self):
 
361
        return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
 
362
 
 
363
    def check_file_renamed(self):
 
364
        self.failIfExists('branch/file')
 
365
        self.failUnlessExists('branch/new-file')
 
366
 
 
367
    def do_rename_file2(self):
 
368
        return ('new-file2', 'file-id', [('rename', ('file', 'new-file2'))])
 
369
 
 
370
    def check_file_renamed2(self):
 
371
        self.failIfExists('branch/file')
 
372
        self.failUnlessExists('branch/new-file2')
 
373
 
 
374
    def do_rename_dir(self):
 
375
        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
 
376
 
 
377
    def check_dir_renamed(self):
 
378
        self.failIfExists('branch/dir')
 
379
        self.failUnlessExists('branch/new-dir')
 
380
 
 
381
    def do_rename_dir2(self):
 
382
        return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
 
383
 
 
384
    def check_dir_renamed2(self):
 
385
        self.failIfExists('branch/dir')
 
386
        self.failUnlessExists('branch/new-dir2')
 
387
 
 
388
    def do_delete_dir(self):
 
389
        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
 
390
 
 
391
    def check_dir_doesnt_exist(self):
 
392
        self.failIfExists('branch/dir')
 
393
 
 
394
    def _merge_other_into_this(self):
 
395
        b = self.builder.get_branch()
 
396
        wt = b.bzrdir.sprout('branch').open_workingtree()
 
397
        wt.merge_from_branch(b, 'other')
 
398
        return wt
 
399
 
 
400
    def assertConflict(self, wt):
 
401
        confs = wt.conflicts()
 
402
        self.assertLength(1, confs)
 
403
        c = confs[0]
 
404
        self.assertIsInstance(c, self._conflict_type)
 
405
        self._assert_conflict(wt, c)
 
406
 
 
407
    def _get_resolve_path_arg(self, wt, action):
 
408
        return self._item_path
 
409
 
 
410
    def check_resolved(self, wt, action):
 
411
        path = self._get_resolve_path_arg(wt, action)
 
412
        conflicts.resolve(wt, [path], action=action)
 
413
        # Check that we don't have any conflicts nor unknown left
 
414
        self.assertLength(0, wt.conflicts())
 
415
        self.assertLength(0, list(wt.unknowns()))
 
416
 
 
417
    def test_resolve_taking_this(self):
 
418
        wt = self._merge_other_into_this()
 
419
        self.assertConflict(wt)
 
420
        self.check_resolved(wt, 'take_this')
 
421
        check_this = self._get_check(self._check_this)
 
422
        check_this()
 
423
 
 
424
    def test_resolve_taking_other(self):
 
425
        wt = self._merge_other_into_this()
 
426
        self.assertConflict(wt)
 
427
        self.check_resolved(wt, 'take_other')
 
428
        check_other = self._get_check(self._check_other)
 
429
        check_other()
 
430
 
 
431
 
 
432
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
433
 
 
434
    _conflict_type = conflicts.ContentsConflict,
 
435
    @classmethod
 
436
    def scenarios(klass):
 
437
        base_scenarios = [
 
438
            (('file_modified', dict(actions='modify_file',
 
439
                                   check='file_has_more_content')),
 
440
             ('file_deleted', dict(actions='delete_file',
 
441
                                   check='file_doesnt_exist')),
 
442
             dict(_actions_base='create_file', _item_path='file')),
 
443
            ]
 
444
        return klass.mirror_scenarios(base_scenarios)
 
445
 
 
446
    def assertContentsConflict(self, wt, c):
 
447
        self.assertEqual(self._other_id, c.file_id)
 
448
        self.assertEqual(self._other_path, c.path)
 
449
    _assert_conflict = assertContentsConflict
 
450
 
 
451
 
 
452
 
 
453
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
454
 
 
455
    _conflict_type = conflicts.PathConflict,
 
456
 
 
457
    @classmethod
 
458
    def scenarios(klass):
 
459
        for_file = dict(_actions_base='create_file',
 
460
                  _item_path='new-file', _item_id='file-id',)
 
461
        for_dir = dict(_actions_base='create_dir',
 
462
                        _item_path='new-dir', _item_id='dir-id',)
 
463
        base_scenarios = [
 
464
            (('file_renamed',
 
465
              dict(actions='rename_file', check='file_renamed')),
 
466
             ('file_deleted',
 
467
              dict(actions='delete_file', check='file_doesnt_exist')),
 
468
             for_file),
 
469
            (('file_renamed',
 
470
              dict(actions='rename_file', check='file_renamed')),
 
471
             ('file_renamed2',
 
472
              dict(actions='rename_file2', check='file_renamed2')),
 
473
             for_file),
 
474
            (('dir_renamed',
 
475
              dict(actions='rename_dir', check='dir_renamed')),
 
476
             ('dir_deleted',
 
477
              dict(actions='delete_dir', check='dir_doesnt_exist')),
 
478
             for_dir),
 
479
            (('dir_renamed',
 
480
              dict(actions='rename_dir', check='dir_renamed')),
 
481
             ('dir_renamed2',
 
482
              dict(actions='rename_dir2', check='dir_renamed2')),
 
483
             for_dir),
 
484
        ]
 
485
        return klass.mirror_scenarios(base_scenarios)
 
486
 
 
487
    def do_delete_file(self):
 
488
        sup = super(TestResolvePathConflict, self).do_delete_file()
 
489
        # PathConflicts handle deletion differently and requires a special
 
490
        # hard-coded value
 
491
        return ('<deleted>',) + sup[1:]
 
492
 
 
493
    def assertPathConflict(self, wt, c):
 
494
        self.assertEqual(self._item_id, c.file_id)
 
495
        self.assertEqual(self._this_path, c.path)
 
496
        self.assertEqual(self._other_path, c.conflict_path)
 
497
    _assert_conflict = assertPathConflict
 
498
 
 
499
 
 
500
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
501
    """Same as TestResolvePathConflict but a specific conflict object.
 
502
    """
 
503
 
 
504
    def assertPathConflict(self, c):
 
505
        # We create a conflict object as it was created before the fix and
 
506
        # inject it into the working tree, the test will exercise the
 
507
        # compatibility code.
 
508
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
509
                                       file_id=None)
 
510
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
511
 
 
512
 
 
513
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
 
514
 
 
515
    _conflict_type = conflicts.DuplicateEntry,
 
516
    @classmethod
 
517
    def scenarios(klass):
 
518
        base_scenarios = [
 
519
            (('filea_created', dict(actions='create_file_a',
 
520
                                    check='file_content_a')),
 
521
             ('fileb_created', dict(actions='create_file_b',
 
522
                                   check='file_content_b')),
 
523
             dict(_actions_base='nothing', _item_path='file')),
 
524
            ]
 
525
        return klass.mirror_scenarios(base_scenarios)
 
526
 
 
527
    def assertDuplicateEntry(self, wt, c):
 
528
        self.assertEqual(self._this_id, c.file_id)
 
529
        self.assertEqual(self._item_path + '.moved', c.path)
 
530
        self.assertEqual(self._item_path, c.conflict_path)
 
531
    _assert_conflict = assertDuplicateEntry
 
532
 
 
533
 
 
534
class TestResolveUnversionedParent(TestResolveConflicts):
 
535
 
 
536
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
 
537
 
 
538
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
 
539
    # tests MissingParent resolution :-/
 
540
    preamble = """
 
541
$ bzr init trunk
 
542
$ cd trunk
 
543
$ mkdir dir
 
544
$ bzr add dir
 
545
$ bzr commit -m 'Create trunk'
 
546
 
 
547
$ echo 'trunk content' >dir/file
 
548
$ bzr add dir/file
 
549
$ bzr commit -m 'Add dir/file in trunk'
 
550
 
 
551
$ bzr branch . -r 1 ../branch
 
552
$ cd ../branch
 
553
$ bzr rm dir
 
554
$ bzr commit -m 'Remove dir in branch'
 
555
 
 
556
$ bzr merge ../trunk
 
557
2>+N  dir/
 
558
2>+N  dir/file
 
559
2>Conflict adding files to dir.  Created directory.
 
560
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
561
2>2 conflicts encountered.
 
562
"""
 
563
 
 
564
    def test_take_this(self):
 
565
        self.run_script("""
 
566
$ bzr rm dir  --force
 
567
$ bzr resolve dir
 
568
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
569
""")
 
570
 
 
571
    def test_take_other(self):
 
572
        self.run_script("""
 
573
$ bzr resolve dir
 
574
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
575
""")
 
576
 
 
577
 
 
578
class TestResolveMissingParent(TestResolveConflicts):
 
579
 
 
580
    preamble = """
 
581
$ bzr init trunk
 
582
$ cd trunk
 
583
$ mkdir dir
 
584
$ echo 'trunk content' >dir/file
 
585
$ bzr add
 
586
$ bzr commit -m 'Create trunk'
 
587
 
 
588
$ echo 'trunk content' >dir/file2
 
589
$ bzr add dir/file2
 
590
$ bzr commit -m 'Add dir/file2 in branch'
 
591
 
 
592
$ bzr branch . -r 1 ../branch
 
593
$ cd ../branch
 
594
$ bzr rm dir/file --force
 
595
$ bzr rm dir
 
596
$ bzr commit -m 'Remove dir/file'
 
597
 
 
598
$ bzr merge ../trunk
 
599
2>+N  dir/
 
600
2>+N  dir/file2
 
601
2>Conflict adding files to dir.  Created directory.
 
602
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
603
2>2 conflicts encountered.
 
604
"""
 
605
 
 
606
    def test_keep_them_all(self):
 
607
        self.run_script("""
 
608
$ bzr resolve dir
 
609
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
610
""")
 
611
 
 
612
    def test_adopt_child(self):
 
613
        self.run_script("""
 
614
$ bzr mv dir/file2 file2
 
615
$ bzr rm dir --force
 
616
$ bzr resolve dir
 
617
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
618
""")
 
619
 
 
620
    def test_kill_them_all(self):
 
621
        self.run_script("""
 
622
$ bzr rm dir --force
 
623
$ bzr resolve dir
 
624
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
625
""")
 
626
 
 
627
    def test_resolve_taking_this(self):
 
628
        self.run_script("""
 
629
$ bzr resolve --take-this dir
 
630
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
631
""")
 
632
 
 
633
    def test_resolve_taking_other(self):
 
634
        self.run_script("""
 
635
$ bzr resolve --take-other dir
 
636
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
637
""")
 
638
 
 
639
 
 
640
class TestResolveDeletingParent(TestResolveConflicts):
 
641
 
 
642
    preamble = """
 
643
$ bzr init trunk
 
644
$ cd trunk
 
645
$ mkdir dir
 
646
$ echo 'trunk content' >dir/file
 
647
$ bzr add
 
648
$ bzr commit -m 'Create trunk'
 
649
 
 
650
$ bzr rm dir/file --force
 
651
$ bzr rm dir --force
 
652
$ bzr commit -m 'Remove dir/file'
 
653
 
 
654
$ bzr branch . -r 1 ../branch
 
655
$ cd ../branch
 
656
$ echo 'branch content' >dir/file2
 
657
$ bzr add dir/file2
 
658
$ bzr commit -m 'Add dir/file2 in branch'
 
659
 
 
660
$ bzr merge ../trunk
 
661
2>-D  dir/file
 
662
2>Conflict: can't delete dir because it is not empty.  Not deleting.
 
663
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
664
2>2 conflicts encountered.
 
665
"""
 
666
 
 
667
    def test_keep_them_all(self):
 
668
        self.run_script("""
 
669
$ bzr resolve dir
 
670
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
671
""")
 
672
 
 
673
    def test_adopt_child(self):
 
674
        self.run_script("""
 
675
$ bzr mv dir/file2 file2
 
676
$ bzr rm dir --force
 
677
$ bzr resolve dir
 
678
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
679
""")
 
680
 
 
681
    def test_kill_them_all(self):
 
682
        self.run_script("""
 
683
$ bzr rm dir --force
 
684
$ bzr resolve dir
 
685
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
686
""")
 
687
 
 
688
    def test_resolve_taking_this(self):
 
689
        self.run_script("""
 
690
$ bzr resolve --take-this dir
 
691
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
692
""")
 
693
 
 
694
    def test_resolve_taking_other(self):
 
695
        self.run_script("""
 
696
$ bzr resolve --take-other dir
 
697
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
698
""")
 
699
 
 
700
 
 
701
class TestResolveParentLoop(TestParametrizedResolveConflicts):
 
702
 
 
703
    _conflict_type = conflicts.ParentLoop,
 
704
    @classmethod
 
705
    def scenarios(klass):
 
706
        base_scenarios = [
 
707
            (('dir1_into_dir2', dict(actions='move_dir1_into_dir2',
 
708
                                      check='dir1_moved')),
 
709
             ('dir2_into_dir1', dict(actions='move_dir2_into_dir1',
 
710
                                      check='dir2_moved')),
 
711
             dict(_actions_base='create_dir1_dir2')),
 
712
            (('dir1_into_dir4', dict(actions='move_dir1_into_dir4',
 
713
                                      check='dir1_2_moved')),
 
714
             ('dir3_into_dir2', dict(actions='move_dir3_into_dir2',
 
715
                                      check='dir3_4_moved')),
 
716
             dict(_actions_base='create_dir1_4')),
 
717
            ]
 
718
        return klass.mirror_scenarios(base_scenarios)
 
719
 
 
720
    def do_create_dir1_dir2(self):
 
721
        return (None, None,
 
722
                [('add', ('dir1', 'dir1-id', 'directory', '')),
 
723
                 ('add', ('dir2', 'dir2-id', 'directory', '')),
 
724
                 ])
 
725
 
 
726
    def do_move_dir1_into_dir2(self):
 
727
        # The arguments are the file-id to move and the targeted file-id dir.
 
728
        return ('dir1-id', 'dir2-id', [('rename', ('dir1', 'dir2/dir1'))])
 
729
 
 
730
    def check_dir1_moved(self):
 
731
        self.failIfExists('branch/dir1')
 
732
        self.failUnlessExists('branch/dir2/dir1')
 
733
 
 
734
    def do_move_dir2_into_dir1(self):
 
735
        # The arguments are the file-id to move and the targeted file-id dir.
 
736
        return ('dir2-id', 'dir1-id', [('rename', ('dir2', 'dir1/dir2'))])
 
737
 
 
738
    def check_dir2_moved(self):
 
739
        self.failIfExists('branch/dir2')
 
740
        self.failUnlessExists('branch/dir1/dir2')
 
741
 
 
742
    def do_create_dir1_4(self):
 
743
        return (None, None,
 
744
                [('add', ('dir1', 'dir1-id', 'directory', '')),
 
745
                 ('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
 
746
                 ('add', ('dir3', 'dir3-id', 'directory', '')),
 
747
                 ('add', ('dir3/dir4', 'dir4-id', 'directory', '')),
 
748
                 ])
 
749
 
 
750
    def do_move_dir1_into_dir4(self):
 
751
        # The arguments are the file-id to move and the targeted file-id dir.
 
752
        return ('dir1-id', 'dir4-id',
 
753
                [('rename', ('dir1', 'dir3/dir4/dir1'))])
 
754
 
 
755
    def check_dir1_2_moved(self):
 
756
        self.failIfExists('branch/dir1')
 
757
        self.failUnlessExists('branch/dir3/dir4/dir1')
 
758
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
 
759
 
 
760
    def do_move_dir3_into_dir2(self):
 
761
        # The arguments are the file-id to move and the targeted file-id dir.
 
762
        return ('dir3-id', 'dir2-id',
 
763
                [('rename', ('dir3', 'dir1/dir2/dir3'))])
 
764
 
 
765
    def check_dir3_4_moved(self):
 
766
        self.failIfExists('branch/dir3')
 
767
        self.failUnlessExists('branch/dir1/dir2/dir3')
 
768
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
 
769
 
 
770
    def _get_resolve_path_arg(self, wt, action):
 
771
        # ParentLoop is unsual as it says: 
 
772
        # moving <conflict_path> into <path>.  Cancelled move.
 
773
        # But since <path> doesn't exist in the working tree, we need to use
 
774
        # <conflict_path> instead
 
775
        path = wt.id2path(self._other_id)
 
776
        return path
 
777
 
 
778
    def assertParentLoop(self, wt, c):
 
779
        if 'taking_other(' in self.id() and 'dir4' in self.id():
 
780
            raise tests.KnownFailure(
 
781
                "ParentLoop doesn't carry enough info to resolve")
 
782
        # The relevant file-ids are other_args swapped (which is the main
 
783
        # reason why they should be renamed other_args instead of Other_path
 
784
        # and other_id). In the conflict object, they represent:
 
785
        # c.file_id: the directory being moved
 
786
        # c.conflict_id_id: The target directory
 
787
        self.assertEqual(self._other_path, c.file_id)
 
788
        self.assertEqual(self._other_id, c.conflict_file_id)
 
789
        # The conflict paths are irrelevant (they are deterministic but not
 
790
        # worth checking since they don't provide the needed information
 
791
        # anyway)
 
792
    _assert_conflict = assertParentLoop
 
793
 
 
794
 
 
795
class OldTestResolveParentLoop(TestResolveConflicts):
 
796
 
 
797
    preamble = """
 
798
$ bzr init trunk
 
799
$ cd trunk
 
800
$ bzr mkdir dir1
 
801
$ bzr mkdir dir2
 
802
$ bzr commit -m 'Create trunk'
 
803
 
 
804
$ bzr mv dir2 dir1
 
805
$ bzr commit -m 'Moved dir2 into dir1'
 
806
 
 
807
$ bzr branch . -r 1 ../branch
 
808
$ cd ../branch
 
809
$ bzr mv dir1 dir2
 
810
$ bzr commit -m 'Moved dir1 into dir2'
 
811
 
 
812
$ bzr merge ../trunk
 
813
2>Conflict moving dir2 into dir2/dir1. Cancelled move.
 
814
2>1 conflicts encountered.
 
815
"""
 
816
 
 
817
    def test_take_this(self):
 
818
        self.run_script("""
 
819
$ bzr resolve dir2
 
820
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
821
""")
 
822
 
 
823
    def test_take_other(self):
 
824
        self.run_script("""
 
825
$ bzr mv dir2/dir1 dir1
 
826
$ bzr mv dir2 dir1
 
827
$ bzr resolve dir2
 
828
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
829
""")
 
830
 
 
831
    def test_resolve_taking_this(self):
 
832
        self.run_script("""
 
833
$ bzr resolve --take-this dir2
 
834
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
835
""")
 
836
        self.failUnlessExists('dir2')
 
837
 
 
838
    def test_resolve_taking_other(self):
 
839
        self.run_script("""
 
840
$ bzr resolve --take-other dir2
 
841
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
842
""")
 
843
        self.failUnlessExists('dir1')
 
844
 
 
845
 
 
846
class TestResolveNonDirectoryParent(TestResolveConflicts):
 
847
 
 
848
    preamble = """
 
849
$ bzr init trunk
 
850
$ cd trunk
 
851
$ bzr mkdir foo
 
852
$ bzr commit -m 'Create trunk'
 
853
$ echo "Boing" >foo/bar
 
854
$ bzr add foo/bar
 
855
$ bzr commit -m 'Add foo/bar'
 
856
 
 
857
$ bzr branch . -r 1 ../branch
 
858
$ cd ../branch
 
859
$ rm -r foo
 
860
$ echo "Boo!" >foo
 
861
$ bzr commit -m 'foo is now a file'
 
862
 
 
863
$ bzr merge ../trunk
 
864
2>+N  foo.new/bar
 
865
2>RK  foo => foo.new/
 
866
# FIXME: The message is misleading, foo.new *is* a directory when the message
 
867
# is displayed -- vila 090916
 
868
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
 
869
2>1 conflicts encountered.
 
870
"""
 
871
 
 
872
    def test_take_this(self):
 
873
        self.run_script("""
 
874
$ bzr rm foo.new --force
 
875
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
 
876
# aside ? -- vila 090916
 
877
$ bzr add foo
 
878
$ bzr resolve foo.new
 
879
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
880
""")
 
881
 
 
882
    def test_take_other(self):
 
883
        self.run_script("""
 
884
$ bzr rm foo --force
 
885
$ bzr mv foo.new foo
 
886
$ bzr resolve foo
 
887
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
888
""")
 
889
 
 
890
    def test_resolve_taking_this(self):
 
891
        self.run_script("""
 
892
$ bzr resolve --take-this foo.new
 
893
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
894
""")
 
895
 
 
896
    def test_resolve_taking_other(self):
 
897
        self.run_script("""
 
898
$ bzr resolve --take-other foo.new
 
899
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
900
""")
 
901
 
 
902
 
 
903
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
 
904
 
 
905
    def test_bug_430129(self):
 
906
        # This is nearly like TestResolveNonDirectoryParent but with branch and
 
907
        # trunk switched. As such it should certainly produce the same
 
908
        # conflict.
 
909
        self.run_script("""
 
910
$ bzr init trunk
 
911
$ cd trunk
 
912
$ bzr mkdir foo
 
913
$ bzr commit -m 'Create trunk'
 
914
$ rm -r foo
 
915
$ echo "Boo!" >foo
 
916
$ bzr commit -m 'foo is now a file'
 
917
 
 
918
$ bzr branch . -r 1 ../branch
 
919
$ cd ../branch
 
920
$ echo "Boing" >foo/bar
 
921
$ bzr add foo/bar
 
922
$ bzr commit -m 'Add foo/bar'
 
923
 
 
924
$ bzr merge ../trunk
 
925
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
 
926
""")
 
927
 
 
928
 
 
929
class TestResolveActionOption(tests.TestCase):
 
930
 
 
931
    def setUp(self):
 
932
        super(TestResolveActionOption, self).setUp()
 
933
        self.options = [conflicts.ResolveActionOption()]
 
934
        self.parser = option.get_optparser(dict((o.name, o)
 
935
                                                for o in self.options))
 
936
 
 
937
    def parse(self, args):
 
938
        return self.parser.parse_args(args)
 
939
 
 
940
    def test_unknown_action(self):
 
941
        self.assertRaises(errors.BadOptionValue,
 
942
                          self.parse, ['--action', 'take-me-to-the-moon'])
 
943
 
 
944
    def test_done(self):
 
945
        opts, args = self.parse(['--action', 'done'])
 
946
        self.assertEqual({'action':'done'}, opts)
 
947
 
 
948
    def test_take_this(self):
 
949
        opts, args = self.parse(['--action', 'take-this'])
 
950
        self.assertEqual({'action': 'take_this'}, opts)
 
951
        opts, args = self.parse(['--take-this'])
 
952
        self.assertEqual({'action': 'take_this'}, opts)
 
953
 
 
954
    def test_take_other(self):
 
955
        opts, args = self.parse(['--action', 'take-other'])
 
956
        self.assertEqual({'action': 'take_other'}, opts)
 
957
        opts, args = self.parse(['--take-other'])
 
958
        self.assertEqual({'action': 'take_other'}, opts)