/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

Better PathConflict resolution when a deletion is involved

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
    _this_path = None
 
251
    _this_id = None
 
252
    _other_path = None
 
253
    _other_id = None
 
254
 
 
255
    @classmethod
 
256
    def mirror_scenarios(klass, base_scenarios):
 
257
        scenarios = []
 
258
        def adapt(d, side):
 
259
            """Modify dict to apply to the given side.
 
260
 
 
261
            'actions' key is turned into '_actions_this' if side is 'this' for
 
262
            example.
 
263
            """
 
264
            t = {}
 
265
            # Turn each key into _side_key
 
266
            for k,v in d.iteritems():
 
267
                t['_%s_%s' % (k, side)] = v
 
268
            return t
 
269
        # Each base scenario is duplicated switching the roles of 'this' and
 
270
        # 'other'
 
271
        left = [l for l, r, c in base_scenarios]
 
272
        right = [r for l, r, c in base_scenarios]
 
273
        common = [c for l, r, c in base_scenarios]
 
274
        for (lname, ldict), (rname, rdict), common in zip(left, right, common):
 
275
            a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
 
276
                                         [(rname, adapt(rdict, 'other'))])
 
277
            b = tests.multiply_scenarios(
 
278
                    [(rname, adapt(rdict, 'this'))],
 
279
                    [(lname, adapt(ldict, 'other'))])
 
280
            # Inject the common parameters in all scenarios
 
281
            for name, d in a + b:
 
282
                d.update(common)
 
283
            scenarios.extend(a + b)
 
284
        return scenarios
 
285
 
 
286
    @classmethod
 
287
    def scenarios(klass):
 
288
        # Only concrete classes return actual scenarios
 
289
        return []
 
290
 
 
291
    def setUp(self):
 
292
        super(TestParametrizedResolveConflicts, self).setUp()
 
293
        builder = self.make_branch_builder('trunk')
 
294
        builder.start_series()
 
295
 
 
296
        # Create an empty trunk
 
297
        builder.build_snapshot('start', None, [
 
298
                ('add', ('', 'root-id', 'directory', ''))])
 
299
        # Add a minimal base content
 
300
        _, _, actions_base = self._get_actions(self._actions_base)()
 
301
        builder.build_snapshot('base', ['start'], actions_base)
 
302
        # Modify the base content in branch
 
303
        (self._other_path, self._other_id,
 
304
         actions_other) = self._get_actions(self._actions_other)()
 
305
        builder.build_snapshot('other', ['base'], actions_other)
 
306
        # Modify the base content in trunk
 
307
        (self._this_path, self._this_id,
 
308
         actions_this) = self._get_actions(self._actions_this)()
 
309
        builder.build_snapshot('this', ['base'], actions_this)
 
310
        # builder.get_branch() tip is now 'this'
 
311
 
 
312
        builder.finish_series()
 
313
        self.builder = builder
 
314
 
 
315
    def _get_actions(self, name):
 
316
        return getattr(self, 'do_%s' % name)
 
317
 
 
318
    def _get_check(self, name):
 
319
        return getattr(self, 'check_%s' % name)
 
320
 
 
321
    def assertConflict(self, wt):
 
322
        confs = wt.conflicts()
 
323
        self.assertLength(1, confs)
 
324
        c = confs[0]
 
325
        self.assertIsInstance(c, self._conflict_type)
 
326
        self._assert_conflict(wt, c)
 
327
 
 
328
    def check_resolved(self, wt, path, action):
 
329
        conflicts.resolve(wt, [path], action=action)
 
330
        # Check that we don't have any conflicts nor unknown left
 
331
        self.assertLength(0, wt.conflicts())
 
332
        self.assertLength(0, list(wt.unknowns()))
 
333
 
 
334
    def do_create_file(self):
 
335
        return ('file', 'file-id',
 
336
                [('add', ('file', 'file-id', 'file', 'trunk content\n'))])
 
337
 
 
338
    def do_create_dir(self):
 
339
        return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
 
340
 
 
341
    def do_modify_file(self):
 
342
        return ('file', 'file-id',
 
343
                [('modify', ('file-id', 'trunk content\nmore content\n'))])
 
344
 
 
345
    def check_file_has_more_content(self):
 
346
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
 
347
 
 
348
    def do_delete_file(self):
 
349
        return ('file', 'file-id', [('unversion', 'file-id')])
 
350
 
 
351
    def check_file_doesnt_exist(self):
 
352
        self.failIfExists('branch/file')
 
353
 
 
354
    def do_rename_file(self):
 
355
        return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
 
356
 
 
357
    def check_file_renamed(self):
 
358
        self.failIfExists('branch/file')
 
359
        self.failUnlessExists('branch/new-file')
 
360
 
 
361
    def do_rename_dir(self):
 
362
        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
 
363
 
 
364
    def check_dir_renamed(self):
 
365
        self.failIfExists('branch/dir')
 
366
        self.failUnlessExists('branch/new-dir')
 
367
 
 
368
    def do_rename_dir2(self):
 
369
        return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
 
370
 
 
371
    def check_dir_renamed2(self):
 
372
        self.failIfExists('branch/dir')
 
373
        self.failUnlessExists('branch/new-dir2')
 
374
 
 
375
    def do_delete_dir(self):
 
376
        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
 
377
 
 
378
    def check_dir_doesnt_exist(self):
 
379
        self.failIfExists('branch/dir')
 
380
 
 
381
    def _merge_other_into_this(self):
 
382
        b = self.builder.get_branch()
 
383
        wt = b.bzrdir.sprout('branch').open_workingtree()
 
384
        wt.merge_from_branch(b, 'other')
 
385
        return wt
 
386
 
 
387
    def test_resolve_taking_this(self):
 
388
        wt = self._merge_other_into_this()
 
389
        self.assertConflict(wt)
 
390
        self.check_resolved(wt, self._item_path, 'take_this')
 
391
        check_this = self._get_check(self._check_this)
 
392
        check_this()
 
393
 
 
394
    def test_resolve_taking_other(self):
 
395
        wt = self._merge_other_into_this()
 
396
        self.assertConflict(wt)
 
397
        self.check_resolved(wt, self._item_path, 'take_other')
 
398
        check_other = self._get_check(self._check_other)
 
399
        check_other()
 
400
 
 
401
 
 
402
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
403
 
 
404
    _conflict_type = conflicts.ContentsConflict,
 
405
    @classmethod
 
406
    def scenarios(klass):
 
407
        common = dict(_actions_base='create_file',
 
408
                      _item_path='file', item_id='file-id',
 
409
                      )
 
410
        base_scenarios = [
 
411
            (('file_modified', dict(actions='modify_file',
 
412
                                   check='file_has_more_content')),
 
413
             ('file_deleted', dict(actions='delete_file',
 
414
                                   check='file_doesnt_exist')),
 
415
             dict(_actions_base='create_file',
 
416
                  _item_path='file', item_id='file-id',)),
 
417
            ]
 
418
        return klass.mirror_scenarios(base_scenarios)
 
419
 
 
420
    def assertContentsConflict(self, wt, c):
 
421
        self.assertEqual(self._other_id, c.file_id)
 
422
        self.assertEqual(self._other_path, c.path)
 
423
    _assert_conflict = assertContentsConflict
 
424
 
 
425
 
 
426
 
 
427
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
428
 
 
429
    _conflict_type = conflicts.PathConflict,
 
430
 
 
431
    @classmethod
 
432
    def scenarios(klass):
 
433
        for_dirs = dict(_actions_base='create_dir',
 
434
                        _item_path='new-dir', _item_id='dir-id',)
 
435
        base_scenarios = [
 
436
            (('file_renamed',
 
437
              dict(actions='rename_file', check='file_renamed')),
 
438
             ('file_deleted',
 
439
              dict(actions='delete_file', check='file_doesnt_exist')),
 
440
             dict(_actions_base='create_file',
 
441
                  _item_path='new-file', _item_id='file-id',)),
 
442
            (('dir_renamed',
 
443
              dict(actions='rename_dir', check='dir_renamed')),
 
444
             ('dir_deleted',
 
445
              dict(actions='delete_dir', check='dir_doesnt_exist')),
 
446
             for_dirs),
 
447
            (('dir_renamed',
 
448
              dict(actions='rename_dir', check='dir_renamed')),
 
449
             ('dir_renamed2',
 
450
              dict(actions='rename_dir2', check='dir_renamed2')),
 
451
             for_dirs),
 
452
        ]
 
453
        return klass.mirror_scenarios(base_scenarios)
 
454
 
 
455
    def do_delete_file(self):
 
456
        sup = super(TestResolvePathConflict, self).do_delete_file()
 
457
        # PathConflicts handle deletion differently and requires a special
 
458
        # hard-coded value
 
459
        return ('<deleted>',) + sup[1:]
 
460
 
 
461
    def assertPathConflict(self, wt, c):
 
462
        self.assertEqual(self._item_id, c.file_id)
 
463
        self.assertEqual(self._this_path, c.path)
 
464
        self.assertEqual(self._other_path, c.conflict_path)
 
465
    _assert_conflict = assertPathConflict
 
466
 
 
467
 
 
468
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
469
    """Same as TestResolvePathConflict but a specific conflict object.
 
470
    """
 
471
 
 
472
    def assertPathConflict(self, c):
 
473
        # We create a conflict object as it was created before the fix and
 
474
        # inject it into the working tree, the test will exercise the
 
475
        # compatibility code.
 
476
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
477
                                       file_id=None)
 
478
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
479
 
 
480
 
 
481
class TestResolveDuplicateEntry(TestResolveConflicts):
 
482
 
 
483
    preamble = """
 
484
$ bzr init trunk
 
485
$ cd trunk
 
486
$ echo 'trunk content' >file
 
487
$ bzr add file
 
488
$ bzr commit -m 'Create trunk'
 
489
 
 
490
$ echo 'trunk content too' >file2
 
491
$ bzr add file2
 
492
$ bzr commit -m 'Add file2 in trunk'
 
493
 
 
494
$ bzr branch . -r 1 ../branch
 
495
$ cd ../branch
 
496
$ echo 'branch content' >file2
 
497
$ bzr add file2
 
498
$ bzr commit -m 'Add file2 in branch'
 
499
 
 
500
$ bzr merge ../trunk
 
501
2>+N  file2
 
502
2>R   file2 => file2.moved
 
503
2>Conflict adding file file2.  Moved existing file to file2.moved.
 
504
2>1 conflicts encountered.
 
505
"""
 
506
 
 
507
    def test_keep_this(self):
 
508
        self.run_script("""
 
509
$ bzr rm file2  --force
 
510
$ bzr mv file2.moved file2
 
511
$ bzr resolve file2
 
512
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
513
""")
 
514
 
 
515
    def test_keep_other(self):
 
516
        self.failIfExists('branch/file2.moved')
 
517
        self.run_script("""
 
518
$ bzr rm file2.moved --force
 
519
$ bzr resolve file2
 
520
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
521
""")
 
522
        self.failIfExists('branch/file2.moved')
 
523
 
 
524
    def test_resolve_taking_this(self):
 
525
        self.run_script("""
 
526
$ bzr resolve --take-this file2
 
527
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
528
""")
 
529
 
 
530
    def test_resolve_taking_other(self):
 
531
        self.run_script("""
 
532
$ bzr resolve --take-other file2
 
533
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
534
""")
 
535
 
 
536
 
 
537
class TestResolveUnversionedParent(TestResolveConflicts):
 
538
 
 
539
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
 
540
 
 
541
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
 
542
    # tests MissingParent resolution :-/
 
543
    preamble = """
 
544
$ bzr init trunk
 
545
$ cd trunk
 
546
$ mkdir dir
 
547
$ bzr add dir
 
548
$ bzr commit -m 'Create trunk'
 
549
 
 
550
$ echo 'trunk content' >dir/file
 
551
$ bzr add dir/file
 
552
$ bzr commit -m 'Add dir/file in trunk'
 
553
 
 
554
$ bzr branch . -r 1 ../branch
 
555
$ cd ../branch
 
556
$ bzr rm dir
 
557
$ bzr commit -m 'Remove dir in branch'
 
558
 
 
559
$ bzr merge ../trunk
 
560
2>+N  dir/
 
561
2>+N  dir/file
 
562
2>Conflict adding files to dir.  Created directory.
 
563
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
564
2>2 conflicts encountered.
 
565
"""
 
566
 
 
567
    def test_take_this(self):
 
568
        self.run_script("""
 
569
$ bzr rm dir  --force
 
570
$ bzr resolve dir
 
571
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
572
""")
 
573
 
 
574
    def test_take_other(self):
 
575
        self.run_script("""
 
576
$ bzr resolve dir
 
577
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
578
""")
 
579
 
 
580
 
 
581
class TestResolveMissingParent(TestResolveConflicts):
 
582
 
 
583
    preamble = """
 
584
$ bzr init trunk
 
585
$ cd trunk
 
586
$ mkdir dir
 
587
$ echo 'trunk content' >dir/file
 
588
$ bzr add
 
589
$ bzr commit -m 'Create trunk'
 
590
 
 
591
$ echo 'trunk content' >dir/file2
 
592
$ bzr add dir/file2
 
593
$ bzr commit -m 'Add dir/file2 in branch'
 
594
 
 
595
$ bzr branch . -r 1 ../branch
 
596
$ cd ../branch
 
597
$ bzr rm dir/file --force
 
598
$ bzr rm dir
 
599
$ bzr commit -m 'Remove dir/file'
 
600
 
 
601
$ bzr merge ../trunk
 
602
2>+N  dir/
 
603
2>+N  dir/file2
 
604
2>Conflict adding files to dir.  Created directory.
 
605
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
606
2>2 conflicts encountered.
 
607
"""
 
608
 
 
609
    def test_keep_them_all(self):
 
610
        self.run_script("""
 
611
$ bzr resolve dir
 
612
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
613
""")
 
614
 
 
615
    def test_adopt_child(self):
 
616
        self.run_script("""
 
617
$ bzr mv dir/file2 file2
 
618
$ bzr rm dir --force
 
619
$ bzr resolve dir
 
620
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
621
""")
 
622
 
 
623
    def test_kill_them_all(self):
 
624
        self.run_script("""
 
625
$ bzr rm dir --force
 
626
$ bzr resolve dir
 
627
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
628
""")
 
629
 
 
630
    def test_resolve_taking_this(self):
 
631
        self.run_script("""
 
632
$ bzr resolve --take-this dir
 
633
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
634
""")
 
635
 
 
636
    def test_resolve_taking_other(self):
 
637
        self.run_script("""
 
638
$ bzr resolve --take-other dir
 
639
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
640
""")
 
641
 
 
642
 
 
643
class TestResolveDeletingParent(TestResolveConflicts):
 
644
 
 
645
    preamble = """
 
646
$ bzr init trunk
 
647
$ cd trunk
 
648
$ mkdir dir
 
649
$ echo 'trunk content' >dir/file
 
650
$ bzr add
 
651
$ bzr commit -m 'Create trunk'
 
652
 
 
653
$ bzr rm dir/file --force
 
654
$ bzr rm dir --force
 
655
$ bzr commit -m 'Remove dir/file'
 
656
 
 
657
$ bzr branch . -r 1 ../branch
 
658
$ cd ../branch
 
659
$ echo 'branch content' >dir/file2
 
660
$ bzr add dir/file2
 
661
$ bzr commit -m 'Add dir/file2 in branch'
 
662
 
 
663
$ bzr merge ../trunk
 
664
2>-D  dir/file
 
665
2>Conflict: can't delete dir because it is not empty.  Not deleting.
 
666
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
667
2>2 conflicts encountered.
 
668
"""
 
669
 
 
670
    def test_keep_them_all(self):
 
671
        self.run_script("""
 
672
$ bzr resolve dir
 
673
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
674
""")
 
675
 
 
676
    def test_adopt_child(self):
 
677
        self.run_script("""
 
678
$ bzr mv dir/file2 file2
 
679
$ bzr rm dir --force
 
680
$ bzr resolve dir
 
681
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
682
""")
 
683
 
 
684
    def test_kill_them_all(self):
 
685
        self.run_script("""
 
686
$ bzr rm dir --force
 
687
$ bzr resolve dir
 
688
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
689
""")
 
690
 
 
691
    def test_resolve_taking_this(self):
 
692
        self.run_script("""
 
693
$ bzr resolve --take-this dir
 
694
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
695
""")
 
696
 
 
697
    def test_resolve_taking_other(self):
 
698
        self.run_script("""
 
699
$ bzr resolve --take-other dir
 
700
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
701
""")
 
702
 
 
703
 
 
704
class OldTestResolvePathConflict(TestResolveConflicts):
 
705
 
 
706
    preamble = """
 
707
$ bzr init trunk
 
708
$ cd trunk
 
709
$ echo 'Boo!' >file
 
710
$ bzr add
 
711
$ bzr commit -m 'Create trunk'
 
712
 
 
713
$ bzr mv file file-in-trunk
 
714
$ bzr commit -m 'Renamed to file-in-trunk'
 
715
 
 
716
$ bzr branch . -r 1 ../branch
 
717
$ cd ../branch
 
718
$ bzr mv file file-in-branch
 
719
$ bzr commit -m 'Renamed to file-in-branch'
 
720
 
 
721
$ bzr merge ../trunk
 
722
2>R   file-in-branch => file-in-trunk
 
723
2>Path conflict: file-in-branch / file-in-trunk
 
724
2>1 conflicts encountered.
 
725
"""
 
726
 
 
727
    def test_keep_source(self):
 
728
        self.run_script("""
 
729
$ bzr resolve file-in-trunk
 
730
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
731
""")
 
732
 
 
733
    def test_keep_target(self):
 
734
        self.run_script("""
 
735
$ bzr mv file-in-trunk file-in-branch
 
736
$ bzr resolve file-in-branch
 
737
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
738
""")
 
739
 
 
740
    def test_resolve_taking_this(self):
 
741
        self.run_script("""
 
742
$ bzr resolve --take-this file-in-branch
 
743
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
744
""")
 
745
 
 
746
    def test_resolve_taking_other(self):
 
747
        self.run_script("""
 
748
$ bzr resolve --take-other file-in-branch
 
749
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
750
""")
 
751
 
 
752
 
 
753
class TestResolveParentLoop(TestResolveConflicts):
 
754
 
 
755
    preamble = """
 
756
$ bzr init trunk
 
757
$ cd trunk
 
758
$ bzr mkdir dir1
 
759
$ bzr mkdir dir2
 
760
$ bzr commit -m 'Create trunk'
 
761
 
 
762
$ bzr mv dir2 dir1
 
763
$ bzr commit -m 'Moved dir2 into dir1'
 
764
 
 
765
$ bzr branch . -r 1 ../branch
 
766
$ cd ../branch
 
767
$ bzr mv dir1 dir2
 
768
$ bzr commit -m 'Moved dir1 into dir2'
 
769
 
 
770
$ bzr merge ../trunk
 
771
2>Conflict moving dir2/dir1 into dir2.  Cancelled move.
 
772
2>1 conflicts encountered.
 
773
"""
 
774
 
 
775
    def test_take_this(self):
 
776
        self.run_script("""
 
777
$ bzr resolve dir2
 
778
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
779
""")
 
780
 
 
781
    def test_take_other(self):
 
782
        self.run_script("""
 
783
$ bzr mv dir2/dir1 dir1
 
784
$ bzr mv dir2 dir1
 
785
$ bzr resolve dir2
 
786
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
787
""")
 
788
 
 
789
    def test_resolve_taking_this(self):
 
790
        self.run_script("""
 
791
$ bzr resolve --take-this dir2
 
792
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
793
""")
 
794
        self.failUnlessExists('dir2')
 
795
 
 
796
    def test_resolve_taking_other(self):
 
797
        self.run_script("""
 
798
$ bzr resolve --take-other dir2
 
799
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
800
""")
 
801
        self.failUnlessExists('dir1')
 
802
 
 
803
 
 
804
class TestResolveNonDirectoryParent(TestResolveConflicts):
 
805
 
 
806
    preamble = """
 
807
$ bzr init trunk
 
808
$ cd trunk
 
809
$ bzr mkdir foo
 
810
$ bzr commit -m 'Create trunk'
 
811
$ echo "Boing" >foo/bar
 
812
$ bzr add foo/bar
 
813
$ bzr commit -m 'Add foo/bar'
 
814
 
 
815
$ bzr branch . -r 1 ../branch
 
816
$ cd ../branch
 
817
$ rm -r foo
 
818
$ echo "Boo!" >foo
 
819
$ bzr commit -m 'foo is now a file'
 
820
 
 
821
$ bzr merge ../trunk
 
822
2>+N  foo.new/bar
 
823
2>RK  foo => foo.new/
 
824
# FIXME: The message is misleading, foo.new *is* a directory when the message
 
825
# is displayed -- vila 090916
 
826
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
 
827
2>1 conflicts encountered.
 
828
"""
 
829
 
 
830
    def test_take_this(self):
 
831
        self.run_script("""
 
832
$ bzr rm foo.new --force
 
833
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
 
834
# aside ? -- vila 090916
 
835
$ bzr add foo
 
836
$ bzr resolve foo.new
 
837
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
838
""")
 
839
 
 
840
    def test_take_other(self):
 
841
        self.run_script("""
 
842
$ bzr rm foo --force
 
843
$ bzr mv foo.new foo
 
844
$ bzr resolve foo
 
845
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
846
""")
 
847
 
 
848
    def test_resolve_taking_this(self):
 
849
        self.run_script("""
 
850
$ bzr resolve --take-this foo.new
 
851
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
852
""")
 
853
 
 
854
    def test_resolve_taking_other(self):
 
855
        self.run_script("""
 
856
$ bzr resolve --take-other foo.new
 
857
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
858
""")
 
859
 
 
860
 
 
861
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
 
862
 
 
863
    def test_bug_430129(self):
 
864
        # This is nearly like TestResolveNonDirectoryParent but with branch and
 
865
        # trunk switched. As such it should certainly produce the same
 
866
        # conflict.
 
867
        self.run_script("""
 
868
$ bzr init trunk
 
869
$ cd trunk
 
870
$ bzr mkdir foo
 
871
$ bzr commit -m 'Create trunk'
 
872
$ rm -r foo
 
873
$ echo "Boo!" >foo
 
874
$ bzr commit -m 'foo is now a file'
 
875
 
 
876
$ bzr branch . -r 1 ../branch
 
877
$ cd ../branch
 
878
$ echo "Boing" >foo/bar
 
879
$ bzr add foo/bar
 
880
$ bzr commit -m 'Add foo/bar'
 
881
 
 
882
$ bzr merge ../trunk
 
883
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
 
884
""")
 
885
 
 
886
 
 
887
class TestResolveActionOption(tests.TestCase):
 
888
 
 
889
    def setUp(self):
 
890
        super(TestResolveActionOption, self).setUp()
 
891
        self.options = [conflicts.ResolveActionOption()]
 
892
        self.parser = option.get_optparser(dict((o.name, o)
 
893
                                                for o in self.options))
 
894
 
 
895
    def parse(self, args):
 
896
        return self.parser.parse_args(args)
 
897
 
 
898
    def test_unknown_action(self):
 
899
        self.assertRaises(errors.BadOptionValue,
 
900
                          self.parse, ['--action', 'take-me-to-the-moon'])
 
901
 
 
902
    def test_done(self):
 
903
        opts, args = self.parse(['--action', 'done'])
 
904
        self.assertEqual({'action':'done'}, opts)
 
905
 
 
906
    def test_take_this(self):
 
907
        opts, args = self.parse(['--action', 'take-this'])
 
908
        self.assertEqual({'action': 'take_this'}, opts)
 
909
        opts, args = self.parse(['--take-this'])
 
910
        self.assertEqual({'action': 'take_this'}, opts)
 
911
 
 
912
    def test_take_other(self):
 
913
        opts, args = self.parse(['--action', 'take-other'])
 
914
        self.assertEqual({'action': 'take_other'}, opts)
 
915
        opts, args = self.parse(['--take-other'])
 
916
        self.assertEqual({'action': 'take_other'}, opts)