1
# Copyright (C) 2005-2011 Canonical Ltd
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.
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.
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
29
from bzrlib.tests import script
32
def load_tests(standard_tests, module, loader):
33
result = loader.suiteClass()
35
sp_tests, remaining_tests = tests.split_suite_by_condition(
36
standard_tests, tests.condition_isinstance((
37
TestParametrizedResolveConflicts,
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)
45
# No parametrization for the remaining tests
46
result.addTests(remaining_tests)
51
# TODO: Test commit with some added, and added-but-missing files
52
# RBC 20060124 is that not tested in test_commit.py ?
54
# The order of 'path' here is important - do not let it
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',
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'),
79
class TestConflicts(tests.TestCaseWithTransport):
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'),
93
self.assertLength(6, list(tree.list_files()))
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')
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'),
114
os.mkdir('hello.OTHER')
115
tree.add('hello', 'q')
116
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
119
def test_select_conflicts(self):
120
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)
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'])
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):
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)
173
self.assertIsInstance(o.path, unicode)
175
if o.file_id is not None:
176
self.assertIsInstance(o.file_id, str)
178
conflict_path = getattr(o, 'conflict_path', None)
179
if conflict_path is not None:
180
self.assertIsInstance(conflict_path, unicode)
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)
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')
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')),),
410
# File modified/deleted in dir
411
(dict(_base_actions='create_file_in_dir',
412
_path='dir/file', _file_id='file-id'),
413
('file_modified_in_dir',
414
dict(actions='modify_file_in_dir',
415
check='file_in_dir_has_more_content')),
416
('file_deleted_in_dir',
417
dict(actions='delete_file',
418
check='file_in_dir_doesnt_exist')),),
420
return mirror_scenarios(base_scenarios)
422
def do_create_file(self):
423
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
425
def do_modify_file(self):
426
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
428
def check_file_has_more_content(self):
429
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
431
def do_delete_file(self):
432
return [('unversion', 'file-id')]
434
def check_file_doesnt_exist(self):
435
self.failIfExists('branch/file')
437
def do_create_file_in_dir(self):
438
return [('add', ('dir', 'dir-id', 'directory', '')),
439
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
441
def do_modify_file_in_dir(self):
442
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
444
def check_file_in_dir_has_more_content(self):
445
self.assertFileEqual('trunk content\nmore content\n', 'branch/dir/file')
447
def check_file_in_dir_doesnt_exist(self):
448
self.failIfExists('branch/dir/file')
450
def _get_resolve_path_arg(self, wt, action):
453
def assertContentsConflict(self, wt, c):
454
self.assertEqual(self._file_id, c.file_id)
455
self.assertEqual(self._path, c.path)
456
_assert_conflict = assertContentsConflict
459
class TestResolvePathConflict(TestParametrizedResolveConflicts):
461
_conflict_type = conflicts.PathConflict,
463
def do_nothing(self):
468
# Each side dict additionally defines:
469
# - path path involved (can be '<deleted>')
472
# File renamed/deleted
473
(dict(_base_actions='create_file'),
475
dict(actions='rename_file', check='file_renamed',
476
path='new-file', file_id='file-id')),
478
dict(actions='delete_file', check='file_doesnt_exist',
479
# PathConflicts deletion handling requires a special
481
path='<deleted>', file_id='file-id')),),
482
# File renamed/deleted in dir
483
(dict(_base_actions='create_file_in_dir'),
484
('file_renamed_in_dir',
485
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
486
path='dir/new-file', file_id='file-id')),
488
dict(actions='delete_file', check='file_in_dir_doesnt_exist',
489
# PathConflicts deletion handling requires a special
491
path='<deleted>', file_id='file-id')),),
492
# File renamed/renamed differently
493
(dict(_base_actions='create_file'),
495
dict(actions='rename_file', check='file_renamed',
496
path='new-file', file_id='file-id')),
498
dict(actions='rename_file2', check='file_renamed2',
499
path='new-file2', file_id='file-id')),),
500
# Dir renamed/deleted
501
(dict(_base_actions='create_dir'),
503
dict(actions='rename_dir', check='dir_renamed',
504
path='new-dir', file_id='dir-id')),
506
dict(actions='delete_dir', check='dir_doesnt_exist',
507
# PathConflicts deletion handling requires a special
509
path='<deleted>', file_id='dir-id')),),
510
# Dir renamed/renamed differently
511
(dict(_base_actions='create_dir'),
513
dict(actions='rename_dir', check='dir_renamed',
514
path='new-dir', file_id='dir-id')),
516
dict(actions='rename_dir2', check='dir_renamed2',
517
path='new-dir2', file_id='dir-id')),),
519
return mirror_scenarios(base_scenarios)
521
def do_create_file(self):
522
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
524
def do_create_dir(self):
525
return [('add', ('dir', 'dir-id', 'directory', ''))]
527
def do_rename_file(self):
528
return [('rename', ('file', 'new-file'))]
530
def check_file_renamed(self):
531
self.failIfExists('branch/file')
532
self.failUnlessExists('branch/new-file')
534
def do_rename_file2(self):
535
return [('rename', ('file', 'new-file2'))]
537
def check_file_renamed2(self):
538
self.failIfExists('branch/file')
539
self.failUnlessExists('branch/new-file2')
541
def do_rename_dir(self):
542
return [('rename', ('dir', 'new-dir'))]
544
def check_dir_renamed(self):
545
self.failIfExists('branch/dir')
546
self.failUnlessExists('branch/new-dir')
548
def do_rename_dir2(self):
549
return [('rename', ('dir', 'new-dir2'))]
551
def check_dir_renamed2(self):
552
self.failIfExists('branch/dir')
553
self.failUnlessExists('branch/new-dir2')
555
def do_delete_file(self):
556
return [('unversion', 'file-id')]
558
def check_file_doesnt_exist(self):
559
self.failIfExists('branch/file')
561
def do_delete_dir(self):
562
return [('unversion', 'dir-id')]
564
def check_dir_doesnt_exist(self):
565
self.failIfExists('branch/dir')
567
def do_create_file_in_dir(self):
568
return [('add', ('dir', 'dir-id', 'directory', '')),
569
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
571
def do_rename_file_in_dir(self):
572
return [('rename', ('dir/file', 'dir/new-file'))]
574
def check_file_in_dir_renamed(self):
575
self.failIfExists('branch/dir/file')
576
self.failUnlessExists('branch/dir/new-file')
578
def check_file_in_dir_doesnt_exist(self):
579
self.failIfExists('branch/dir/file')
581
def _get_resolve_path_arg(self, wt, action):
582
tpath = self._this['path']
583
opath = self._other['path']
584
if tpath == '<deleted>':
590
def assertPathConflict(self, wt, c):
591
tpath = self._this['path']
592
tfile_id = self._this['file_id']
593
opath = self._other['path']
594
ofile_id = self._other['file_id']
595
self.assertEqual(tfile_id, ofile_id) # Sanity check
596
self.assertEqual(tfile_id, c.file_id)
597
self.assertEqual(tpath, c.path)
598
self.assertEqual(opath, c.conflict_path)
599
_assert_conflict = assertPathConflict
602
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
603
"""Same as TestResolvePathConflict but a specific conflict object.
606
def assertPathConflict(self, c):
607
# We create a conflict object as it was created before the fix and
608
# inject it into the working tree, the test will exercise the
609
# compatibility code.
610
old_c = conflicts.PathConflict('<deleted>', self._item_path,
612
wt.set_conflicts(conflicts.ConflictList([old_c]))
615
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
617
_conflict_type = conflicts.DuplicateEntry,
621
# Each side dict additionally defines:
625
# File created with different file-ids
626
(dict(_base_actions='nothing'),
628
dict(actions='create_file_a', check='file_content_a',
629
path='file', file_id='file-a-id')),
631
dict(actions='create_file_b', check='file_content_b',
632
path='file', file_id='file-b-id')),),
634
return mirror_scenarios(base_scenarios)
636
def do_nothing(self):
639
def do_create_file_a(self):
640
return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
642
def check_file_content_a(self):
643
self.assertFileEqual('file a content\n', 'branch/file')
645
def do_create_file_b(self):
646
return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
648
def check_file_content_b(self):
649
self.assertFileEqual('file b content\n', 'branch/file')
651
def _get_resolve_path_arg(self, wt, action):
652
return self._this['path']
654
def assertDuplicateEntry(self, wt, c):
655
tpath = self._this['path']
656
tfile_id = self._this['file_id']
657
opath = self._other['path']
658
ofile_id = self._other['file_id']
659
self.assertEqual(tpath, opath) # Sanity check
660
self.assertEqual(tfile_id, c.file_id)
661
self.assertEqual(tpath + '.moved', c.path)
662
self.assertEqual(tpath, c.conflict_path)
663
_assert_conflict = assertDuplicateEntry
666
class TestResolveUnversionedParent(TestResolveConflicts):
668
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
670
# FIXME: While this *creates* UnversionedParent conflicts, this really only
671
# tests MissingParent resolution :-/
677
$ bzr commit -m 'Create trunk'
679
$ echo 'trunk content' >dir/file
681
$ bzr commit -m 'Add dir/file in trunk'
683
$ bzr branch . -r 1 ../branch
686
$ bzr commit -m 'Remove dir in branch'
691
2>Conflict adding files to dir. Created directory.
692
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
693
2>2 conflicts encountered.
696
def test_take_this(self):
700
$ bzr commit --strict -m 'No more conflicts nor unknown files'
703
def test_take_other(self):
706
$ bzr commit --strict -m 'No more conflicts nor unknown files'
710
class TestResolveMissingParent(TestResolveConflicts):
716
$ echo 'trunk content' >dir/file
718
$ bzr commit -m 'Create trunk'
720
$ echo 'trunk content' >dir/file2
722
$ bzr commit -m 'Add dir/file2 in branch'
724
$ bzr branch . -r 1 ../branch
726
$ bzr rm dir/file --force
728
$ bzr commit -m 'Remove dir/file'
733
2>Conflict adding files to dir. Created directory.
734
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
735
2>2 conflicts encountered.
738
def test_keep_them_all(self):
741
$ bzr commit --strict -m 'No more conflicts nor unknown files'
744
def test_adopt_child(self):
746
$ bzr mv dir/file2 file2
749
$ bzr commit --strict -m 'No more conflicts nor unknown files'
752
def test_kill_them_all(self):
756
$ bzr commit --strict -m 'No more conflicts nor unknown files'
759
def test_resolve_taking_this(self):
761
$ bzr resolve --take-this dir
762
$ bzr commit --strict -m 'No more conflicts nor unknown files'
765
def test_resolve_taking_other(self):
767
$ bzr resolve --take-other dir
768
$ bzr commit --strict -m 'No more conflicts nor unknown files'
772
class TestResolveDeletingParent(TestResolveConflicts):
778
$ echo 'trunk content' >dir/file
780
$ bzr commit -m 'Create trunk'
782
$ bzr rm dir/file --force
784
$ bzr commit -m 'Remove dir/file'
786
$ bzr branch . -r 1 ../branch
788
$ echo 'branch content' >dir/file2
790
$ bzr commit -m 'Add dir/file2 in branch'
794
2>Conflict: can't delete dir because it is not empty. Not deleting.
795
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
796
2>2 conflicts encountered.
799
def test_keep_them_all(self):
802
$ bzr commit --strict -m 'No more conflicts nor unknown files'
805
def test_adopt_child(self):
807
$ bzr mv dir/file2 file2
810
$ bzr commit --strict -m 'No more conflicts nor unknown files'
813
def test_kill_them_all(self):
817
$ bzr commit --strict -m 'No more conflicts nor unknown files'
820
def test_resolve_taking_this(self):
822
$ bzr resolve --take-this dir
823
$ bzr commit --strict -m 'No more conflicts nor unknown files'
826
def test_resolve_taking_other(self):
828
$ bzr resolve --take-other dir
829
$ bzr commit --strict -m 'No more conflicts nor unknown files'
833
class TestResolveParentLoop(TestParametrizedResolveConflicts):
835
_conflict_type = conflicts.ParentLoop,
842
# Each side dict additionally defines:
843
# - dir_id: the directory being moved
844
# - target_id: The target directory
845
# - xfail: whether the test is expected to fail if the action is
846
# involved as 'other'
848
# Dirs moved into each other
849
(dict(_base_actions='create_dir1_dir2'),
851
dict(actions='move_dir1_into_dir2', check='dir1_moved',
852
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
854
dict(actions='move_dir2_into_dir1', check='dir2_moved',
855
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
856
# Subdirs moved into each other
857
(dict(_base_actions='create_dir1_4'),
859
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
860
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
862
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
863
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
865
return mirror_scenarios(base_scenarios)
867
def do_create_dir1_dir2(self):
868
return [('add', ('dir1', 'dir1-id', 'directory', '')),
869
('add', ('dir2', 'dir2-id', 'directory', '')),]
871
def do_move_dir1_into_dir2(self):
872
return [('rename', ('dir1', 'dir2/dir1'))]
874
def check_dir1_moved(self):
875
self.failIfExists('branch/dir1')
876
self.failUnlessExists('branch/dir2/dir1')
878
def do_move_dir2_into_dir1(self):
879
return [('rename', ('dir2', 'dir1/dir2'))]
881
def check_dir2_moved(self):
882
self.failIfExists('branch/dir2')
883
self.failUnlessExists('branch/dir1/dir2')
885
def do_create_dir1_4(self):
886
return [('add', ('dir1', 'dir1-id', 'directory', '')),
887
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
888
('add', ('dir3', 'dir3-id', 'directory', '')),
889
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
891
def do_move_dir1_into_dir4(self):
892
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
894
def check_dir1_2_moved(self):
895
self.failIfExists('branch/dir1')
896
self.failUnlessExists('branch/dir3/dir4/dir1')
897
self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
899
def do_move_dir3_into_dir2(self):
900
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
902
def check_dir3_4_moved(self):
903
self.failIfExists('branch/dir3')
904
self.failUnlessExists('branch/dir1/dir2/dir3')
905
self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
907
def _get_resolve_path_arg(self, wt, action):
908
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
909
# But since <path> doesn't exist in the working tree, we need to use
910
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
911
return wt.id2path(self._other['dir_id'])
913
def assertParentLoop(self, wt, c):
914
self.assertEqual(self._other['dir_id'], c.file_id)
915
self.assertEqual(self._other['target_id'], c.conflict_file_id)
916
# The conflict paths are irrelevant (they are deterministic but not
917
# worth checking since they don't provide the needed information
919
if self._other['xfail']:
920
# It's a bit hackish to raise from here relying on being called for
921
# both tests but this avoid overriding test_resolve_taking_other
922
raise tests.KnownFailure(
923
"ParentLoop doesn't carry enough info to resolve --take-other")
924
_assert_conflict = assertParentLoop
927
class TestResolveNonDirectoryParent(TestResolveConflicts):
933
$ bzr commit -m 'Create trunk'
934
$ echo "Boing" >foo/bar
936
$ bzr commit -m 'Add foo/bar'
938
$ bzr branch . -r 1 ../branch
942
$ bzr commit -m 'foo is now a file'
947
# FIXME: The message is misleading, foo.new *is* a directory when the message
948
# is displayed -- vila 090916
949
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
950
2>1 conflicts encountered.
953
def test_take_this(self):
955
$ bzr rm foo.new --force
956
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
957
# aside ? -- vila 090916
959
$ bzr resolve foo.new
960
$ bzr commit --strict -m 'No more conflicts nor unknown files'
963
def test_take_other(self):
968
$ bzr commit --strict -m 'No more conflicts nor unknown files'
971
def test_resolve_taking_this(self):
973
$ bzr resolve --take-this foo.new
974
$ bzr commit --strict -m 'No more conflicts nor unknown files'
977
def test_resolve_taking_other(self):
979
$ bzr resolve --take-other foo.new
980
$ bzr commit --strict -m 'No more conflicts nor unknown files'
984
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
986
def test_bug_430129(self):
987
# This is nearly like TestResolveNonDirectoryParent but with branch and
988
# trunk switched. As such it should certainly produce the same
994
$ bzr commit -m 'Create trunk'
997
$ bzr commit -m 'foo is now a file'
999
$ bzr branch . -r 1 ../branch
1001
$ echo "Boing" >foo/bar
1003
$ bzr commit -m 'Add foo/bar'
1005
$ bzr merge ../trunk
1006
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1009
def test_bug_660935(self):
1012
Created a standalone tree (format: 2a)
1015
$ echo trunk > src/file
1019
$ bzr commit -m 'create file on trunk'
1020
2>Committing to: .../trunk/
1023
2>Committed revision 1.
1025
$ bzr branch trunk featureA
1026
2>Branched 1 revision(s).
1028
$ echo featureA > src/file
1029
$ bzr commit -m 'modify file for featureA'
1030
2>Committing to: .../featureA/
1032
2>Committed revision 2.
1037
$ bzr commit -m 'Delete file'
1038
2>Committing to: .../trunk/
1040
2>Committed revision 2.
1042
$ bzr merge ../trunk
1043
2>RM src/file => src/file.THIS
1044
2>Contents conflict in src/file
1045
2>1 conflicts encountered.
1047
Contents conflict in src/file
1048
$ bzr resolve --take-other
1052
class TestResolveActionOption(tests.TestCase):
1055
super(TestResolveActionOption, self).setUp()
1056
self.options = [conflicts.ResolveActionOption()]
1057
self.parser = option.get_optparser(dict((o.name, o)
1058
for o in self.options))
1060
def parse(self, args):
1061
return self.parser.parse_args(args)
1063
def test_unknown_action(self):
1064
self.assertRaises(errors.BadOptionValue,
1065
self.parse, ['--action', 'take-me-to-the-moon'])
1067
def test_done(self):
1068
opts, args = self.parse(['--action', 'done'])
1069
self.assertEqual({'action':'done'}, opts)
1071
def test_take_this(self):
1072
opts, args = self.parse(['--action', 'take-this'])
1073
self.assertEqual({'action': 'take_this'}, opts)
1074
opts, args = self.parse(['--take-this'])
1075
self.assertEqual({'action': 'take_this'}, opts)
1077
def test_take_other(self):
1078
opts, args = self.parse(['--action', 'take-other'])
1079
self.assertEqual({'action': 'take_other'}, opts)
1080
opts, args = self.parse(['--take-other'])
1081
self.assertEqual({'action': 'take_other'}, opts)