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
28
from bzrlib.tests import (
34
load_tests = scenarios.load_tests_apply_scenarios
37
# TODO: Test commit with some added, and added-but-missing files
38
# RBC 20060124 is that not tested in test_commit.py ?
40
# The order of 'path' here is important - do not let it
42
# u'\xe5' == a with circle
43
# '\xc3\xae' == u'\xee' == i with hat
44
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
45
example_conflicts = conflicts.ConflictList(
46
[conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
47
conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
48
conflicts.TextConflict(u'p\xe5tha'),
49
conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
50
conflicts.DuplicateID('Unversioned existing file',
51
u'p\xe5thc', u'p\xe5thc2',
52
'\xc3\xaedc', '\xc3\xaedc'),
53
conflicts.DuplicateEntry('Moved existing file to',
54
u'p\xe5thdd.moved', u'p\xe5thd',
56
conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
58
conflicts.UnversionedParent('Versioned directory',
59
u'p\xe5thf', '\xc3\xaedf'),
60
conflicts.NonDirectoryParent('Created directory',
61
u'p\xe5thg', '\xc3\xaedg'),
65
class TestConflicts(tests.TestCaseWithTransport):
67
def test_resolve_conflict_dir(self):
68
tree = self.make_branch_and_tree('.')
69
self.build_tree_contents([('hello', 'hello world4'),
70
('hello.THIS', 'hello world2'),
71
('hello.BASE', 'hello world1'),
73
os.mkdir('hello.OTHER')
74
tree.add('hello', 'q')
75
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
78
def test_select_conflicts(self):
79
tree = self.make_branch_and_tree('.')
80
clist = conflicts.ConflictList
82
def check_select(not_selected, selected, paths, **kwargs):
84
(not_selected, selected),
85
tree_conflicts.select_conflicts(tree, paths, **kwargs))
87
foo = conflicts.ContentsConflict('foo')
88
bar = conflicts.ContentsConflict('bar')
89
tree_conflicts = clist([foo, bar])
91
check_select(clist([bar]), clist([foo]), ['foo'])
92
check_select(clist(), tree_conflicts,
93
[''], ignore_misses=True, recurse=True)
95
foobaz = conflicts.ContentsConflict('foo/baz')
96
tree_conflicts = clist([foobaz, bar])
98
check_select(clist([bar]), clist([foobaz]),
99
['foo'], ignore_misses=True, recurse=True)
101
qux = conflicts.PathConflict('qux', 'foo/baz')
102
tree_conflicts = clist([qux])
104
check_select(clist(), tree_conflicts,
105
['foo'], ignore_misses=True, recurse=True)
106
check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
108
def test_resolve_conflicts_recursive(self):
109
tree = self.make_branch_and_tree('.')
110
self.build_tree(['dir/', 'dir/hello'])
111
tree.add(['dir', 'dir/hello'])
113
dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
114
tree.set_conflicts(dirhello)
116
conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
117
self.assertEqual(dirhello, tree.conflicts())
119
conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
120
self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
123
class TestConflictStanzas(tests.TestCase):
125
def test_stanza_roundtrip(self):
126
# write and read our example stanza.
127
stanza_iter = example_conflicts.to_stanzas()
128
processed = conflicts.ConflictList.from_stanzas(stanza_iter)
129
for o, p in zip(processed, example_conflicts):
130
self.assertEqual(o, p)
132
self.assertIsInstance(o.path, unicode)
134
if o.file_id is not None:
135
self.assertIsInstance(o.file_id, str)
137
conflict_path = getattr(o, 'conflict_path', None)
138
if conflict_path is not None:
139
self.assertIsInstance(conflict_path, unicode)
141
conflict_file_id = getattr(o, 'conflict_file_id', None)
142
if conflict_file_id is not None:
143
self.assertIsInstance(conflict_file_id, str)
145
def test_stanzification(self):
146
for stanza in example_conflicts.to_stanzas():
147
if 'file_id' in stanza:
148
# In Stanza form, the file_id has to be unicode.
149
self.assertStartsWith(stanza['file_id'], u'\xeed')
150
self.assertStartsWith(stanza['path'], u'p\xe5th')
151
if 'conflict_path' in stanza:
152
self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
153
if 'conflict_file_id' in stanza:
154
self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
157
# FIXME: The shell-like tests should be converted to real whitebox tests... or
158
# moved to a blackbox module -- vila 20100205
160
# FIXME: test missing for multiple conflicts
162
# FIXME: Tests missing for DuplicateID conflict type
163
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
165
preamble = None # The setup script set by daughter classes
168
super(TestResolveConflicts, self).setUp()
169
self.run_script(self.preamble)
172
def mirror_scenarios(base_scenarios):
173
"""Return a list of mirrored scenarios.
175
Each scenario in base_scenarios is duplicated switching the roles of 'this'
179
for common, (lname, ldict), (rname, rdict) in base_scenarios:
180
a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
181
[(rname, dict(_other=rdict))])
182
b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
183
[(lname, dict(_other=ldict))])
184
# Inject the common parameters in all scenarios
185
for name, d in a + b:
187
scenarios.extend(a + b)
191
# FIXME: Get rid of parametrized (in the class name) once we delete
192
# TestResolveConflicts -- vila 20100308
193
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
194
"""This class provides a base to test single conflict resolution.
196
Since all conflict objects are created with specific semantics for their
197
attributes, each class should implement the necessary functions and
198
attributes described below.
200
Each class should define the scenarios that create the expected (single)
203
Each scenario describes:
204
* how to create 'base' tree (and revision)
205
* how to create 'left' tree (and revision, parent rev 'base')
206
* how to create 'right' tree (and revision, parent rev 'base')
207
* how to check that changes in 'base'->'left' have been taken
208
* how to check that changes in 'base'->'right' have been taken
210
From each base scenario, we generate two concrete scenarios where:
211
* this=left, other=right
212
* this=right, other=left
214
Then the test case verifies each concrete scenario by:
215
* creating a branch containing the 'base', 'this' and 'other' revisions
216
* creating a working tree for the 'this' revision
217
* performing the merge of 'other' into 'this'
218
* verifying the expected conflict was generated
219
* resolving with --take-this or --take-other, and running the corresponding
220
checks (for either 'base'->'this', or 'base'->'other')
222
:cvar _conflict_type: The expected class of the generated conflict.
224
:cvar _assert_conflict: A method receiving the working tree and the
225
conflict object and checking its attributes.
227
:cvar _base_actions: The branchbuilder actions to create the 'base'
230
:cvar _this: The dict related to 'base' -> 'this'. It contains at least:
231
* 'actions': The branchbuilder actions to create the 'this'
233
* 'check': how to check the changes after resolution with --take-this.
235
:cvar _other: The dict related to 'base' -> 'other'. It contains at least:
236
* 'actions': The branchbuilder actions to create the 'other'
238
* 'check': how to check the changes after resolution with --take-other.
241
# Set by daughter classes
242
_conflict_type = None
243
_assert_conflict = None
251
"""The scenario list for the conflict type defined by the class.
253
Each scenario is of the form:
254
(common, (left_name, left_dict), (right_name, right_dict))
258
* left_name and right_name are the scenario names that will be combined
260
* left_dict and right_dict are the attributes specific to each half of
261
the scenario. They should include at least 'actions' and 'check' and
262
will be available as '_this' and '_other' test instance attributes.
264
Daughters classes are free to add their specific attributes as they see
265
fit in any of the three dicts.
267
This is a class method so that load_tests can find it.
269
'_base_actions' in the common dict, 'actions' and 'check' in the left
270
and right dicts use names that map to methods in the test classes. Some
271
prefixes are added to these names to get the correspong methods (see
272
_get_actions() and _get_check()). The motivation here is to avoid
273
collisions in the class namespace.
277
super(TestParametrizedResolveConflicts, self).setUp()
278
builder = self.make_branch_builder('trunk')
279
builder.start_series()
281
# Create an empty trunk
282
builder.build_snapshot('start', None, [
283
('add', ('', 'root-id', 'directory', ''))])
284
# Add a minimal base content
285
base_actions = self._get_actions(self._base_actions)()
286
builder.build_snapshot('base', ['start'], base_actions)
287
# Modify the base content in branch
288
actions_other = self._get_actions(self._other['actions'])()
289
builder.build_snapshot('other', ['base'], actions_other)
290
# Modify the base content in trunk
291
actions_this = self._get_actions(self._this['actions'])()
292
builder.build_snapshot('this', ['base'], actions_this)
293
# builder.get_branch() tip is now 'this'
295
builder.finish_series()
296
self.builder = builder
298
def _get_actions(self, name):
299
return getattr(self, 'do_%s' % name)
301
def _get_check(self, name):
302
return getattr(self, 'check_%s' % name)
304
def _merge_other_into_this(self):
305
b = self.builder.get_branch()
306
wt = b.bzrdir.sprout('branch').open_workingtree()
307
wt.merge_from_branch(b, 'other')
310
def assertConflict(self, wt):
311
confs = wt.conflicts()
312
self.assertLength(1, confs)
314
self.assertIsInstance(c, self._conflict_type)
315
self._assert_conflict(wt, c)
317
def _get_resolve_path_arg(self, wt, action):
318
raise NotImplementedError(self._get_resolve_path_arg)
320
def check_resolved(self, wt, action):
321
path = self._get_resolve_path_arg(wt, action)
322
conflicts.resolve(wt, [path], action=action)
323
# Check that we don't have any conflicts nor unknown left
324
self.assertLength(0, wt.conflicts())
325
self.assertLength(0, list(wt.unknowns()))
327
def test_resolve_taking_this(self):
328
wt = self._merge_other_into_this()
329
self.assertConflict(wt)
330
self.check_resolved(wt, 'take_this')
331
check_this = self._get_check(self._this['check'])
334
def test_resolve_taking_other(self):
335
wt = self._merge_other_into_this()
336
self.assertConflict(wt)
337
self.check_resolved(wt, 'take_other')
338
check_other = self._get_check(self._other['check'])
342
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
344
_conflict_type = conflicts.TextConflict
346
# Set by the scenarios
347
# path and file-id for the file involved in the conflict
351
scenarios = mirror_scenarios(
353
# File modified on both sides
354
(dict(_base_actions='create_file',
355
_path='file', _file_id='file-id'),
357
dict(actions='modify_file_A', check='file_has_content_A')),
359
dict(actions='modify_file_B', check='file_has_content_B')),),
360
# File modified on both sides in dir
361
(dict(_base_actions='create_file_in_dir',
362
_path='dir/file', _file_id='file-id'),
363
('filed_modified_A_in_dir',
364
dict(actions='modify_file_A',
365
check='file_in_dir_has_content_A')),
367
dict(actions='modify_file_B',
368
check='file_in_dir_has_content_B')),),
371
def do_create_file(self, path='file'):
372
return [('add', (path, 'file-id', 'file', 'trunk content\n'))]
374
def do_modify_file_A(self):
375
return [('modify', ('file-id', 'trunk content\nfeature A\n'))]
377
def do_modify_file_B(self):
378
return [('modify', ('file-id', 'trunk content\nfeature B\n'))]
380
def check_file_has_content_A(self, path='file'):
381
self.assertFileEqual('trunk content\nfeature A\n',
382
osutils.pathjoin('branch', path))
384
def check_file_has_content_B(self, path='file'):
385
self.assertFileEqual('trunk content\nfeature B\n',
386
osutils.pathjoin('branch', path))
388
def do_create_file_in_dir(self):
389
return [('add', ('dir', 'dir-id', 'directory', '')),
390
] + self.do_create_file('dir/file')
392
def check_file_in_dir_has_content_A(self):
393
self.check_file_has_content_A('dir/file')
395
def check_file_in_dir_has_content_B(self):
396
self.check_file_has_content_B('dir/file')
398
def _get_resolve_path_arg(self, wt, action):
401
def assertTextConflict(self, wt, c):
402
self.assertEqual(self._file_id, c.file_id)
403
self.assertEqual(self._path, c.path)
404
_assert_conflict = assertTextConflict
407
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
409
_conflict_type = conflicts.ContentsConflict
411
# Set by the scenarios
412
# path and file-id for the file involved in the conflict
416
scenarios = mirror_scenarios(
418
# File modified/deleted
419
(dict(_base_actions='create_file',
420
_path='file', _file_id='file-id'),
422
dict(actions='modify_file', check='file_has_more_content')),
424
dict(actions='delete_file', check='file_doesnt_exist')),),
425
# File modified/deleted in dir
426
(dict(_base_actions='create_file_in_dir',
427
_path='dir/file', _file_id='file-id'),
428
('file_modified_in_dir',
429
dict(actions='modify_file_in_dir',
430
check='file_in_dir_has_more_content')),
431
('file_deleted_in_dir',
432
dict(actions='delete_file',
433
check='file_in_dir_doesnt_exist')),),
436
def do_create_file(self):
437
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
439
def do_modify_file(self):
440
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
442
def check_file_has_more_content(self):
443
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
445
def do_delete_file(self):
446
return [('unversion', 'file-id')]
448
def check_file_doesnt_exist(self):
449
self.failIfExists('branch/file')
451
def do_create_file_in_dir(self):
452
return [('add', ('dir', 'dir-id', 'directory', '')),
453
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
455
def do_modify_file_in_dir(self):
456
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
458
def check_file_in_dir_has_more_content(self):
459
self.assertFileEqual('trunk content\nmore content\n', 'branch/dir/file')
461
def check_file_in_dir_doesnt_exist(self):
462
self.failIfExists('branch/dir/file')
464
def _get_resolve_path_arg(self, wt, action):
467
def assertContentsConflict(self, wt, c):
468
self.assertEqual(self._file_id, c.file_id)
469
self.assertEqual(self._path, c.path)
470
_assert_conflict = assertContentsConflict
473
class TestResolvePathConflict(TestParametrizedResolveConflicts):
475
_conflict_type = conflicts.PathConflict
477
def do_nothing(self):
480
# Each side dict additionally defines:
481
# - path path involved (can be '<deleted>')
483
scenarios = mirror_scenarios(
485
# File renamed/deleted
486
(dict(_base_actions='create_file'),
488
dict(actions='rename_file', check='file_renamed',
489
path='new-file', file_id='file-id')),
491
dict(actions='delete_file', check='file_doesnt_exist',
492
# PathConflicts deletion handling requires a special
494
path='<deleted>', file_id='file-id')),),
495
# File renamed/deleted in dir
496
(dict(_base_actions='create_file_in_dir'),
497
('file_renamed_in_dir',
498
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
499
path='dir/new-file', file_id='file-id')),
501
dict(actions='delete_file', check='file_in_dir_doesnt_exist',
502
# PathConflicts deletion handling requires a special
504
path='<deleted>', file_id='file-id')),),
505
# File renamed/renamed differently
506
(dict(_base_actions='create_file'),
508
dict(actions='rename_file', check='file_renamed',
509
path='new-file', file_id='file-id')),
511
dict(actions='rename_file2', check='file_renamed2',
512
path='new-file2', file_id='file-id')),),
513
# Dir renamed/deleted
514
(dict(_base_actions='create_dir'),
516
dict(actions='rename_dir', check='dir_renamed',
517
path='new-dir', file_id='dir-id')),
519
dict(actions='delete_dir', check='dir_doesnt_exist',
520
# PathConflicts deletion handling requires a special
522
path='<deleted>', file_id='dir-id')),),
523
# Dir renamed/renamed differently
524
(dict(_base_actions='create_dir'),
526
dict(actions='rename_dir', check='dir_renamed',
527
path='new-dir', file_id='dir-id')),
529
dict(actions='rename_dir2', check='dir_renamed2',
530
path='new-dir2', file_id='dir-id')),),
533
def do_create_file(self):
534
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
536
def do_create_dir(self):
537
return [('add', ('dir', 'dir-id', 'directory', ''))]
539
def do_rename_file(self):
540
return [('rename', ('file', 'new-file'))]
542
def check_file_renamed(self):
543
self.failIfExists('branch/file')
544
self.failUnlessExists('branch/new-file')
546
def do_rename_file2(self):
547
return [('rename', ('file', 'new-file2'))]
549
def check_file_renamed2(self):
550
self.failIfExists('branch/file')
551
self.failUnlessExists('branch/new-file2')
553
def do_rename_dir(self):
554
return [('rename', ('dir', 'new-dir'))]
556
def check_dir_renamed(self):
557
self.failIfExists('branch/dir')
558
self.failUnlessExists('branch/new-dir')
560
def do_rename_dir2(self):
561
return [('rename', ('dir', 'new-dir2'))]
563
def check_dir_renamed2(self):
564
self.failIfExists('branch/dir')
565
self.failUnlessExists('branch/new-dir2')
567
def do_delete_file(self):
568
return [('unversion', 'file-id')]
570
def check_file_doesnt_exist(self):
571
self.failIfExists('branch/file')
573
def do_delete_dir(self):
574
return [('unversion', 'dir-id')]
576
def check_dir_doesnt_exist(self):
577
self.failIfExists('branch/dir')
579
def do_create_file_in_dir(self):
580
return [('add', ('dir', 'dir-id', 'directory', '')),
581
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
583
def do_rename_file_in_dir(self):
584
return [('rename', ('dir/file', 'dir/new-file'))]
586
def check_file_in_dir_renamed(self):
587
self.failIfExists('branch/dir/file')
588
self.failUnlessExists('branch/dir/new-file')
590
def check_file_in_dir_doesnt_exist(self):
591
self.failIfExists('branch/dir/file')
593
def _get_resolve_path_arg(self, wt, action):
594
tpath = self._this['path']
595
opath = self._other['path']
596
if tpath == '<deleted>':
602
def assertPathConflict(self, wt, c):
603
tpath = self._this['path']
604
tfile_id = self._this['file_id']
605
opath = self._other['path']
606
ofile_id = self._other['file_id']
607
self.assertEqual(tfile_id, ofile_id) # Sanity check
608
self.assertEqual(tfile_id, c.file_id)
609
self.assertEqual(tpath, c.path)
610
self.assertEqual(opath, c.conflict_path)
611
_assert_conflict = assertPathConflict
614
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
615
"""Same as TestResolvePathConflict but a specific conflict object.
618
def assertPathConflict(self, c):
619
# We create a conflict object as it was created before the fix and
620
# inject it into the working tree, the test will exercise the
621
# compatibility code.
622
old_c = conflicts.PathConflict('<deleted>', self._item_path,
624
wt.set_conflicts(conflicts.ConflictList([old_c]))
627
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
629
_conflict_type = conflicts.DuplicateEntry
631
scenarios = mirror_scenarios(
633
# File created with different file-ids
634
(dict(_base_actions='nothing'),
636
dict(actions='create_file_a', check='file_content_a',
637
path='file', file_id='file-a-id')),
639
dict(actions='create_file_b', check='file_content_b',
640
path='file', file_id='file-b-id')),),
643
def do_nothing(self):
646
def do_create_file_a(self):
647
return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
649
def check_file_content_a(self):
650
self.assertFileEqual('file a content\n', 'branch/file')
652
def do_create_file_b(self):
653
return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
655
def check_file_content_b(self):
656
self.assertFileEqual('file b content\n', 'branch/file')
658
def _get_resolve_path_arg(self, wt, action):
659
return self._this['path']
661
def assertDuplicateEntry(self, wt, c):
662
tpath = self._this['path']
663
tfile_id = self._this['file_id']
664
opath = self._other['path']
665
ofile_id = self._other['file_id']
666
self.assertEqual(tpath, opath) # Sanity check
667
self.assertEqual(tfile_id, c.file_id)
668
self.assertEqual(tpath + '.moved', c.path)
669
self.assertEqual(tpath, c.conflict_path)
670
_assert_conflict = assertDuplicateEntry
673
class TestResolveUnversionedParent(TestResolveConflicts):
675
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
677
# FIXME: While this *creates* UnversionedParent conflicts, this really only
678
# tests MissingParent resolution :-/
685
$ bzr commit -m 'Create trunk' -q
686
$ echo 'trunk content' >dir/file
687
$ bzr add -q dir/file
688
$ bzr commit -q -m 'Add dir/file in trunk'
689
$ bzr branch -q . -r 1 ../branch
692
$ bzr commit -q -m 'Remove dir in branch'
696
2>Conflict adding files to dir. Created directory.
697
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
698
2>2 conflicts encountered.
701
def test_take_this(self):
703
$ bzr rm -q dir --force
705
2>2 conflict(s) resolved, 0 remaining
706
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
709
def test_take_other(self):
712
2>2 conflict(s) resolved, 0 remaining
713
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
717
class TestResolveMissingParent(TestResolveConflicts):
724
$ echo 'trunk content' >dir/file
726
$ bzr commit -m 'Create trunk' -q
727
$ echo 'trunk content' >dir/file2
728
$ bzr add -q dir/file2
729
$ bzr commit -q -m 'Add dir/file2 in branch'
730
$ bzr branch -q . -r 1 ../branch
732
$ bzr rm -q dir/file --force
734
$ bzr commit -q -m 'Remove dir/file'
738
2>Conflict adding files to dir. Created directory.
739
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
740
2>2 conflicts encountered.
743
def test_keep_them_all(self):
746
2>2 conflict(s) resolved, 0 remaining
747
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
750
def test_adopt_child(self):
752
$ bzr mv -q dir/file2 file2
753
$ bzr rm -q dir --force
755
2>2 conflict(s) resolved, 0 remaining
756
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
759
def test_kill_them_all(self):
761
$ bzr rm -q dir --force
763
2>2 conflict(s) resolved, 0 remaining
764
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
767
def test_resolve_taking_this(self):
769
$ bzr resolve --take-this dir
771
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
774
def test_resolve_taking_other(self):
776
$ bzr resolve --take-other dir
778
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
782
class TestResolveDeletingParent(TestResolveConflicts):
789
$ echo 'trunk content' >dir/file
791
$ bzr commit -m 'Create trunk' -q
792
$ bzr rm -q dir/file --force
793
$ bzr rm -q dir --force
794
$ bzr commit -q -m 'Remove dir/file'
795
$ bzr branch -q . -r 1 ../branch
797
$ echo 'branch content' >dir/file2
798
$ bzr add -q dir/file2
799
$ bzr commit -q -m 'Add dir/file2 in branch'
802
2>Conflict: can't delete dir because it is not empty. Not deleting.
803
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
804
2>2 conflicts encountered.
807
def test_keep_them_all(self):
810
2>2 conflict(s) resolved, 0 remaining
811
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
814
def test_adopt_child(self):
816
$ bzr mv -q dir/file2 file2
817
$ bzr rm -q dir --force
819
2>2 conflict(s) resolved, 0 remaining
820
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
823
def test_kill_them_all(self):
825
$ bzr rm -q dir --force
827
2>2 conflict(s) resolved, 0 remaining
828
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
831
def test_resolve_taking_this(self):
833
$ bzr resolve --take-this dir
834
2>2 conflict(s) resolved, 0 remaining
835
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
838
def test_resolve_taking_other(self):
840
$ bzr resolve --take-other dir
843
2>2 conflict(s) resolved, 0 remaining
844
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
848
class TestResolveParentLoop(TestParametrizedResolveConflicts):
850
_conflict_type = conflicts.ParentLoop
855
# Each side dict additionally defines:
856
# - dir_id: the directory being moved
857
# - target_id: The target directory
858
# - xfail: whether the test is expected to fail if the action is
859
# involved as 'other'
860
scenarios = mirror_scenarios(
862
# Dirs moved into each other
863
(dict(_base_actions='create_dir1_dir2'),
865
dict(actions='move_dir1_into_dir2', check='dir1_moved',
866
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
868
dict(actions='move_dir2_into_dir1', check='dir2_moved',
869
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
870
# Subdirs moved into each other
871
(dict(_base_actions='create_dir1_4'),
873
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
874
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
876
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
877
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
880
def do_create_dir1_dir2(self):
881
return [('add', ('dir1', 'dir1-id', 'directory', '')),
882
('add', ('dir2', 'dir2-id', 'directory', '')),]
884
def do_move_dir1_into_dir2(self):
885
return [('rename', ('dir1', 'dir2/dir1'))]
887
def check_dir1_moved(self):
888
self.failIfExists('branch/dir1')
889
self.failUnlessExists('branch/dir2/dir1')
891
def do_move_dir2_into_dir1(self):
892
return [('rename', ('dir2', 'dir1/dir2'))]
894
def check_dir2_moved(self):
895
self.failIfExists('branch/dir2')
896
self.failUnlessExists('branch/dir1/dir2')
898
def do_create_dir1_4(self):
899
return [('add', ('dir1', 'dir1-id', 'directory', '')),
900
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
901
('add', ('dir3', 'dir3-id', 'directory', '')),
902
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
904
def do_move_dir1_into_dir4(self):
905
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
907
def check_dir1_2_moved(self):
908
self.failIfExists('branch/dir1')
909
self.failUnlessExists('branch/dir3/dir4/dir1')
910
self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
912
def do_move_dir3_into_dir2(self):
913
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
915
def check_dir3_4_moved(self):
916
self.failIfExists('branch/dir3')
917
self.failUnlessExists('branch/dir1/dir2/dir3')
918
self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
920
def _get_resolve_path_arg(self, wt, action):
921
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
922
# But since <path> doesn't exist in the working tree, we need to use
923
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
924
return wt.id2path(self._other['dir_id'])
926
def assertParentLoop(self, wt, c):
927
self.assertEqual(self._other['dir_id'], c.file_id)
928
self.assertEqual(self._other['target_id'], c.conflict_file_id)
929
# The conflict paths are irrelevant (they are deterministic but not
930
# worth checking since they don't provide the needed information
932
if self._other['xfail']:
933
# It's a bit hackish to raise from here relying on being called for
934
# both tests but this avoid overriding test_resolve_taking_other
935
raise tests.KnownFailure(
936
"ParentLoop doesn't carry enough info to resolve --take-other")
937
_assert_conflict = assertParentLoop
940
class TestResolveNonDirectoryParent(TestResolveConflicts):
948
$ bzr commit -m 'Create trunk' -q
949
$ echo "Boing" >foo/bar
951
$ bzr commit -q -m 'Add foo/bar'
952
$ bzr branch -q . -r 1 ../branch
956
$ bzr commit -q -m 'foo is now a file'
960
# FIXME: The message is misleading, foo.new *is* a directory when the message
961
# is displayed -- vila 090916
962
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
963
2>1 conflicts encountered.
966
def test_take_this(self):
968
$ bzr rm -q foo.new --force
969
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
970
# aside ? -- vila 090916
972
$ bzr resolve foo.new
973
2>1 conflict(s) resolved, 0 remaining
974
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
977
def test_take_other(self):
979
$ bzr rm -q foo --force
980
$ bzr mv -q foo.new foo
982
2>1 conflict(s) resolved, 0 remaining
983
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
986
def test_resolve_taking_this(self):
988
$ bzr resolve --take-this foo.new
990
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
993
def test_resolve_taking_other(self):
995
$ bzr resolve --take-other foo.new
997
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1001
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1003
def test_bug_430129(self):
1004
# This is nearly like TestResolveNonDirectoryParent but with branch and
1005
# trunk switched. As such it should certainly produce the same
1013
$ bzr commit -m 'Create trunk' -q
1016
$ bzr commit -m 'foo is now a file' -q
1017
$ bzr branch -q . -r 1 ../branch -q
1019
$ echo "Boing" >foo/bar
1020
$ bzr add -q foo/bar -q
1021
$ bzr commit -m 'Add foo/bar' -q
1022
$ bzr merge ../trunk
1023
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1027
class TestResolveActionOption(tests.TestCase):
1030
super(TestResolveActionOption, self).setUp()
1031
self.options = [conflicts.ResolveActionOption()]
1032
self.parser = option.get_optparser(dict((o.name, o)
1033
for o in self.options))
1035
def parse(self, args):
1036
return self.parser.parse_args(args)
1038
def test_unknown_action(self):
1039
self.assertRaises(errors.BadOptionValue,
1040
self.parse, ['--action', 'take-me-to-the-moon'])
1042
def test_done(self):
1043
opts, args = self.parse(['--action', 'done'])
1044
self.assertEqual({'action':'done'}, opts)
1046
def test_take_this(self):
1047
opts, args = self.parse(['--action', 'take-this'])
1048
self.assertEqual({'action': 'take_this'}, opts)
1049
opts, args = self.parse(['--take-this'])
1050
self.assertEqual({'action': 'take_this'}, opts)
1052
def test_take_other(self):
1053
opts, args = self.parse(['--action', 'take-other'])
1054
self.assertEqual({'action': 'take_other'}, opts)
1055
opts, args = self.parse(['--take-other'])
1056
self.assertEqual({'action': 'take_other'}, opts)