1
# Copyright (C) 2005-2010 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 (
35
load_tests = scenarios.load_tests_apply_scenarios
38
# TODO: Test commit with some added, and added-but-missing files
39
# RBC 20060124 is that not tested in test_commit.py ?
41
# The order of 'path' here is important - do not let it
43
# u'\xe5' == a with circle
44
# '\xc3\xae' == u'\xee' == i with hat
45
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
46
example_conflicts = conflicts.ConflictList(
47
[conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
48
conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
49
conflicts.TextConflict(u'p\xe5tha'),
50
conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
51
conflicts.DuplicateID('Unversioned existing file',
52
u'p\xe5thc', u'p\xe5thc2',
53
'\xc3\xaedc', '\xc3\xaedc'),
54
conflicts.DuplicateEntry('Moved existing file to',
55
u'p\xe5thdd.moved', u'p\xe5thd',
57
conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
59
conflicts.UnversionedParent('Versioned directory',
60
u'p\xe5thf', '\xc3\xaedf'),
61
conflicts.NonDirectoryParent('Created directory',
62
u'p\xe5thg', '\xc3\xaedg'),
66
class TestConflicts(tests.TestCaseWithTransport):
68
def test_conflicts(self):
69
"""Conflicts are detected properly"""
70
# Use BzrDirFormat6 so we can fake conflicts
71
tree = self.make_branch_and_tree('.', format=bzrdir.BzrDirFormat6())
72
self.build_tree_contents([('hello', 'hello world4'),
73
('hello.THIS', 'hello world2'),
74
('hello.BASE', 'hello world1'),
75
('hello.OTHER', 'hello world3'),
76
('hello.sploo.BASE', 'yellowworld'),
77
('hello.sploo.OTHER', 'yellowworld2'),
80
self.assertLength(6, list(tree.list_files()))
82
tree_conflicts = tree.conflicts()
83
self.assertLength(2, tree_conflicts)
84
self.assertTrue('hello' in tree_conflicts[0].path)
85
self.assertTrue('hello.sploo' in tree_conflicts[1].path)
86
conflicts.restore('hello')
87
conflicts.restore('hello.sploo')
88
self.assertLength(0, tree.conflicts())
89
self.assertFileEqual('hello world2', 'hello')
90
self.assertFalse(os.path.lexists('hello.sploo'))
91
self.assertRaises(errors.NotConflicted, conflicts.restore, 'hello')
92
self.assertRaises(errors.NotConflicted,
93
conflicts.restore, 'hello.sploo')
95
def test_resolve_conflict_dir(self):
96
tree = self.make_branch_and_tree('.')
97
self.build_tree_contents([('hello', 'hello world4'),
98
('hello.THIS', 'hello world2'),
99
('hello.BASE', 'hello world1'),
101
os.mkdir('hello.OTHER')
102
tree.add('hello', 'q')
103
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
106
def test_select_conflicts(self):
107
tree = self.make_branch_and_tree('.')
108
clist = conflicts.ConflictList
110
def check_select(not_selected, selected, paths, **kwargs):
112
(not_selected, selected),
113
tree_conflicts.select_conflicts(tree, paths, **kwargs))
115
foo = conflicts.ContentsConflict('foo')
116
bar = conflicts.ContentsConflict('bar')
117
tree_conflicts = clist([foo, bar])
119
check_select(clist([bar]), clist([foo]), ['foo'])
120
check_select(clist(), tree_conflicts,
121
[''], ignore_misses=True, recurse=True)
123
foobaz = conflicts.ContentsConflict('foo/baz')
124
tree_conflicts = clist([foobaz, bar])
126
check_select(clist([bar]), clist([foobaz]),
127
['foo'], ignore_misses=True, recurse=True)
129
qux = conflicts.PathConflict('qux', 'foo/baz')
130
tree_conflicts = clist([qux])
132
check_select(clist(), tree_conflicts,
133
['foo'], ignore_misses=True, recurse=True)
134
check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
136
def test_resolve_conflicts_recursive(self):
137
tree = self.make_branch_and_tree('.')
138
self.build_tree(['dir/', 'dir/hello'])
139
tree.add(['dir', 'dir/hello'])
141
dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
142
tree.set_conflicts(dirhello)
144
conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
145
self.assertEqual(dirhello, tree.conflicts())
147
conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
148
self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
151
class TestConflictStanzas(tests.TestCase):
153
def test_stanza_roundtrip(self):
154
# write and read our example stanza.
155
stanza_iter = example_conflicts.to_stanzas()
156
processed = conflicts.ConflictList.from_stanzas(stanza_iter)
157
for o, p in zip(processed, example_conflicts):
158
self.assertEqual(o, p)
160
self.assertIsInstance(o.path, unicode)
162
if o.file_id is not None:
163
self.assertIsInstance(o.file_id, str)
165
conflict_path = getattr(o, 'conflict_path', None)
166
if conflict_path is not None:
167
self.assertIsInstance(conflict_path, unicode)
169
conflict_file_id = getattr(o, 'conflict_file_id', None)
170
if conflict_file_id is not None:
171
self.assertIsInstance(conflict_file_id, str)
173
def test_stanzification(self):
174
for stanza in example_conflicts.to_stanzas():
175
if 'file_id' in stanza:
176
# In Stanza form, the file_id has to be unicode.
177
self.assertStartsWith(stanza['file_id'], u'\xeed')
178
self.assertStartsWith(stanza['path'], u'p\xe5th')
179
if 'conflict_path' in stanza:
180
self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
181
if 'conflict_file_id' in stanza:
182
self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
185
# FIXME: The shell-like tests should be converted to real whitebox tests... or
186
# moved to a blackbox module -- vila 20100205
188
# FIXME: test missing for multiple conflicts
190
# FIXME: Tests missing for DuplicateID conflict type
191
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
193
preamble = None # The setup script set by daughter classes
196
super(TestResolveConflicts, self).setUp()
197
self.run_script(self.preamble)
200
class TestResolveTextConflicts(TestResolveConflicts):
205
def mirror_scenarios(base_scenarios):
206
"""Return a list of mirrored scenarios.
208
Each scenario in base_scenarios is duplicated switching the roles of 'this'
212
for common, (lname, ldict), (rname, rdict) in base_scenarios:
213
a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
214
[(rname, dict(_other=rdict))])
215
b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
216
[(lname, dict(_other=ldict))])
217
# Inject the common parameters in all scenarios
218
for name, d in a + b:
220
scenarios.extend(a + b)
224
# FIXME: Get rid of parametrized (in the class name) once we delete
225
# TestResolveConflicts -- vila 20100308
226
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
227
"""This class provides a base to test single conflict resolution.
229
Since all conflict objects are created with specific semantics for their
230
attributes, each class should implement the necessary functions and
231
attributes described below.
233
Each class should define the scenarios that create the expected (single)
236
Each scenario describes:
237
* how to create 'base' tree (and revision)
238
* how to create 'left' tree (and revision, parent rev 'base')
239
* how to create 'right' tree (and revision, parent rev 'base')
240
* how to check that changes in 'base'->'left' have been taken
241
* how to check that changes in 'base'->'right' have been taken
243
From each base scenario, we generate two concrete scenarios where:
244
* this=left, other=right
245
* this=right, other=left
247
Then the test case verifies each concrete scenario by:
248
* creating a branch containing the 'base', 'this' and 'other' revisions
249
* creating a working tree for the 'this' revision
250
* performing the merge of 'other' into 'this'
251
* verifying the expected conflict was generated
252
* resolving with --take-this or --take-other, and running the corresponding
253
checks (for either 'base'->'this', or 'base'->'other')
255
:cvar _conflict_type: The expected class of the generated conflict.
257
:cvar _assert_conflict: A method receiving the working tree and the
258
conflict object and checking its attributes.
260
:cvar _base_actions: The branchbuilder actions to create the 'base'
263
:cvar _this: The dict related to 'base' -> 'this'. It contains at least:
264
* 'actions': The branchbuilder actions to create the 'this'
266
* 'check': how to check the changes after resolution with --take-this.
268
:cvar _other: The dict related to 'base' -> 'other'. It contains at least:
269
* 'actions': The branchbuilder actions to create the 'other'
271
* 'check': how to check the changes after resolution with --take-other.
274
# Set by daughter classes
275
_conflict_type = None
276
_assert_conflict = None
284
"""The scenario list for the conflict type defined by the class.
286
Each scenario is of the form:
287
(common, (left_name, left_dict), (right_name, right_dict))
291
* left_name and right_name are the scenario names that will be combined
293
* left_dict and right_dict are the attributes specific to each half of
294
the scenario. They should include at least 'actions' and 'check' and
295
will be available as '_this' and '_other' test instance attributes.
297
Daughters classes are free to add their specific attributes as they see
298
fit in any of the three dicts.
300
This is a class method so that load_tests can find it.
302
'_base_actions' in the common dict, 'actions' and 'check' in the left
303
and right dicts use names that map to methods in the test classes. Some
304
prefixes are added to these names to get the correspong methods (see
305
_get_actions() and _get_check()). The motivation here is to avoid
306
collisions in the class namespace.
310
super(TestParametrizedResolveConflicts, self).setUp()
311
builder = self.make_branch_builder('trunk')
312
builder.start_series()
314
# Create an empty trunk
315
builder.build_snapshot('start', None, [
316
('add', ('', 'root-id', 'directory', ''))])
317
# Add a minimal base content
318
base_actions = self._get_actions(self._base_actions)()
319
builder.build_snapshot('base', ['start'], base_actions)
320
# Modify the base content in branch
321
actions_other = self._get_actions(self._other['actions'])()
322
builder.build_snapshot('other', ['base'], actions_other)
323
# Modify the base content in trunk
324
actions_this = self._get_actions(self._this['actions'])()
325
builder.build_snapshot('this', ['base'], actions_this)
326
# builder.get_branch() tip is now 'this'
328
builder.finish_series()
329
self.builder = builder
331
def _get_actions(self, name):
332
return getattr(self, 'do_%s' % name)
334
def _get_check(self, name):
335
return getattr(self, 'check_%s' % name)
337
def _merge_other_into_this(self):
338
b = self.builder.get_branch()
339
wt = b.bzrdir.sprout('branch').open_workingtree()
340
wt.merge_from_branch(b, 'other')
343
def assertConflict(self, wt):
344
confs = wt.conflicts()
345
self.assertLength(1, confs)
347
self.assertIsInstance(c, self._conflict_type)
348
self._assert_conflict(wt, c)
350
def _get_resolve_path_arg(self, wt, action):
351
raise NotImplementedError(self._get_resolve_path_arg)
353
def check_resolved(self, wt, action):
354
path = self._get_resolve_path_arg(wt, action)
355
conflicts.resolve(wt, [path], action=action)
356
# Check that we don't have any conflicts nor unknown left
357
self.assertLength(0, wt.conflicts())
358
self.assertLength(0, list(wt.unknowns()))
360
def test_resolve_taking_this(self):
361
wt = self._merge_other_into_this()
362
self.assertConflict(wt)
363
self.check_resolved(wt, 'take_this')
364
check_this = self._get_check(self._this['check'])
367
def test_resolve_taking_other(self):
368
wt = self._merge_other_into_this()
369
self.assertConflict(wt)
370
self.check_resolved(wt, 'take_other')
371
check_other = self._get_check(self._other['check'])
375
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
377
_conflict_type = conflicts.ContentsConflict
379
# Set by the scenarios
380
# path and file-id for the file involved in the conflict
384
scenarios = mirror_scenarios(
386
# File modified/deleted
387
(dict(_base_actions='create_file',
388
_path='file', _file_id='file-id'),
390
dict(actions='modify_file', check='file_has_more_content')),
392
dict(actions='delete_file', check='file_doesnt_exist')),),
395
def do_create_file(self):
396
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
398
def do_modify_file(self):
399
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
401
def check_file_has_more_content(self):
402
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
404
def do_delete_file(self):
405
return [('unversion', 'file-id')]
407
def check_file_doesnt_exist(self):
408
self.failIfExists('branch/file')
410
def _get_resolve_path_arg(self, wt, action):
413
def assertContentsConflict(self, wt, c):
414
self.assertEqual(self._file_id, c.file_id)
415
self.assertEqual(self._path, c.path)
416
_assert_conflict = assertContentsConflict
419
class TestResolvePathConflict(TestParametrizedResolveConflicts):
421
_conflict_type = conflicts.PathConflict
423
def do_nothing(self):
426
# Each side dict additionally defines:
427
# - path path involved (can be '<deleted>')
429
scenarios = mirror_scenarios(
431
# File renamed/deleted
432
(dict(_base_actions='create_file'),
434
dict(actions='rename_file', check='file_renamed',
435
path='new-file', file_id='file-id')),
437
dict(actions='delete_file', check='file_doesnt_exist',
438
# PathConflicts deletion handling requires a special
440
path='<deleted>', file_id='file-id')),),
441
# File renamed/renamed differently
442
(dict(_base_actions='create_file'),
444
dict(actions='rename_file', check='file_renamed',
445
path='new-file', file_id='file-id')),
447
dict(actions='rename_file2', check='file_renamed2',
448
path='new-file2', file_id='file-id')),),
449
# Dir renamed/deleted
450
(dict(_base_actions='create_dir'),
452
dict(actions='rename_dir', check='dir_renamed',
453
path='new-dir', file_id='dir-id')),
455
dict(actions='delete_dir', check='dir_doesnt_exist',
456
# PathConflicts deletion handling requires a special
458
path='<deleted>', file_id='dir-id')),),
459
# Dir renamed/renamed differently
460
(dict(_base_actions='create_dir'),
462
dict(actions='rename_dir', check='dir_renamed',
463
path='new-dir', file_id='dir-id')),
465
dict(actions='rename_dir2', check='dir_renamed2',
466
path='new-dir2', file_id='dir-id')),),
469
def do_create_file(self):
470
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
472
def do_create_dir(self):
473
return [('add', ('dir', 'dir-id', 'directory', ''))]
475
def do_rename_file(self):
476
return [('rename', ('file', 'new-file'))]
478
def check_file_renamed(self):
479
self.failIfExists('branch/file')
480
self.failUnlessExists('branch/new-file')
482
def do_rename_file2(self):
483
return [('rename', ('file', 'new-file2'))]
485
def check_file_renamed2(self):
486
self.failIfExists('branch/file')
487
self.failUnlessExists('branch/new-file2')
489
def do_rename_dir(self):
490
return [('rename', ('dir', 'new-dir'))]
492
def check_dir_renamed(self):
493
self.failIfExists('branch/dir')
494
self.failUnlessExists('branch/new-dir')
496
def do_rename_dir2(self):
497
return [('rename', ('dir', 'new-dir2'))]
499
def check_dir_renamed2(self):
500
self.failIfExists('branch/dir')
501
self.failUnlessExists('branch/new-dir2')
503
def do_delete_file(self):
504
return [('unversion', 'file-id')]
506
def check_file_doesnt_exist(self):
507
self.failIfExists('branch/file')
509
def do_delete_dir(self):
510
return [('unversion', 'dir-id')]
512
def check_dir_doesnt_exist(self):
513
self.failIfExists('branch/dir')
515
def _get_resolve_path_arg(self, wt, action):
516
tpath = self._this['path']
517
opath = self._other['path']
518
if tpath == '<deleted>':
524
def assertPathConflict(self, wt, c):
525
tpath = self._this['path']
526
tfile_id = self._this['file_id']
527
opath = self._other['path']
528
ofile_id = self._other['file_id']
529
self.assertEqual(tfile_id, ofile_id) # Sanity check
530
self.assertEqual(tfile_id, c.file_id)
531
self.assertEqual(tpath, c.path)
532
self.assertEqual(opath, c.conflict_path)
533
_assert_conflict = assertPathConflict
536
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
537
"""Same as TestResolvePathConflict but a specific conflict object.
540
def assertPathConflict(self, c):
541
# We create a conflict object as it was created before the fix and
542
# inject it into the working tree, the test will exercise the
543
# compatibility code.
544
old_c = conflicts.PathConflict('<deleted>', self._item_path,
546
wt.set_conflicts(conflicts.ConflictList([old_c]))
549
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
551
_conflict_type = conflicts.DuplicateEntry
553
scenarios = mirror_scenarios(
555
# File created with different file-ids
556
(dict(_base_actions='nothing'),
558
dict(actions='create_file_a', check='file_content_a',
559
path='file', file_id='file-a-id')),
561
dict(actions='create_file_b', check='file_content_b',
562
path='file', file_id='file-b-id')),),
565
def do_nothing(self):
568
def do_create_file_a(self):
569
return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
571
def check_file_content_a(self):
572
self.assertFileEqual('file a content\n', 'branch/file')
574
def do_create_file_b(self):
575
return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
577
def check_file_content_b(self):
578
self.assertFileEqual('file b content\n', 'branch/file')
580
def _get_resolve_path_arg(self, wt, action):
581
return self._this['path']
583
def assertDuplicateEntry(self, wt, c):
584
tpath = self._this['path']
585
tfile_id = self._this['file_id']
586
opath = self._other['path']
587
ofile_id = self._other['file_id']
588
self.assertEqual(tpath, opath) # Sanity check
589
self.assertEqual(tfile_id, c.file_id)
590
self.assertEqual(tpath + '.moved', c.path)
591
self.assertEqual(tpath, c.conflict_path)
592
_assert_conflict = assertDuplicateEntry
595
class TestResolveUnversionedParent(TestResolveConflicts):
597
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
599
# FIXME: While this *creates* UnversionedParent conflicts, this really only
600
# tests MissingParent resolution :-/
607
$ bzr commit -m 'Create trunk' -q
608
$ echo 'trunk content' >dir/file
609
$ bzr add -q dir/file
610
$ bzr commit -q -m 'Add dir/file in trunk'
611
$ bzr branch -q . -r 1 ../branch
614
$ bzr commit -q -m 'Remove dir in branch'
618
2>Conflict adding files to dir. Created directory.
619
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
620
2>2 conflicts encountered.
623
def test_take_this(self):
625
$ bzr rm -q dir --force
627
2>2 conflict(s) resolved, 0 remaining
628
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
631
def test_take_other(self):
634
2>2 conflict(s) resolved, 0 remaining
635
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
639
class TestResolveMissingParent(TestResolveConflicts):
646
$ echo 'trunk content' >dir/file
648
$ bzr commit -m 'Create trunk' -q
649
$ echo 'trunk content' >dir/file2
650
$ bzr add -q dir/file2
651
$ bzr commit -q -m 'Add dir/file2 in branch'
652
$ bzr branch -q . -r 1 ../branch
654
$ bzr rm -q dir/file --force
656
$ bzr commit -q -m 'Remove dir/file'
660
2>Conflict adding files to dir. Created directory.
661
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
662
2>2 conflicts encountered.
665
def test_keep_them_all(self):
668
2>2 conflict(s) resolved, 0 remaining
669
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
672
def test_adopt_child(self):
674
$ bzr mv -q dir/file2 file2
675
$ bzr rm -q dir --force
677
2>2 conflict(s) resolved, 0 remaining
678
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
681
def test_kill_them_all(self):
683
$ bzr rm -q dir --force
685
2>2 conflict(s) resolved, 0 remaining
686
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
689
def test_resolve_taking_this(self):
691
$ bzr resolve --take-this dir
693
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
696
def test_resolve_taking_other(self):
698
$ bzr resolve --take-other dir
700
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
704
class TestResolveDeletingParent(TestResolveConflicts):
711
$ echo 'trunk content' >dir/file
713
$ bzr commit -m 'Create trunk' -q
714
$ bzr rm -q dir/file --force
715
$ bzr rm -q dir --force
716
$ bzr commit -q -m 'Remove dir/file'
717
$ bzr branch -q . -r 1 ../branch
719
$ echo 'branch content' >dir/file2
720
$ bzr add -q dir/file2
721
$ bzr commit -q -m 'Add dir/file2 in branch'
724
2>Conflict: can't delete dir because it is not empty. Not deleting.
725
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
726
2>2 conflicts encountered.
729
def test_keep_them_all(self):
732
2>2 conflict(s) resolved, 0 remaining
733
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
736
def test_adopt_child(self):
738
$ bzr mv -q dir/file2 file2
739
$ bzr rm -q dir --force
741
2>2 conflict(s) resolved, 0 remaining
742
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
745
def test_kill_them_all(self):
747
$ bzr rm -q dir --force
749
2>2 conflict(s) resolved, 0 remaining
750
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
753
def test_resolve_taking_this(self):
755
$ bzr resolve --take-this dir
756
2>2 conflict(s) resolved, 0 remaining
757
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
760
def test_resolve_taking_other(self):
762
$ bzr resolve --take-other dir
765
2>2 conflict(s) resolved, 0 remaining
766
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
770
class TestResolveParentLoop(TestParametrizedResolveConflicts):
772
_conflict_type = conflicts.ParentLoop
777
# Each side dict additionally defines:
778
# - dir_id: the directory being moved
779
# - target_id: The target directory
780
# - xfail: whether the test is expected to fail if the action is
781
# involved as 'other'
782
scenarios = mirror_scenarios(
784
# Dirs moved into each other
785
(dict(_base_actions='create_dir1_dir2'),
787
dict(actions='move_dir1_into_dir2', check='dir1_moved',
788
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
790
dict(actions='move_dir2_into_dir1', check='dir2_moved',
791
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
792
# Subdirs moved into each other
793
(dict(_base_actions='create_dir1_4'),
795
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
796
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
798
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
799
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
802
def do_create_dir1_dir2(self):
803
return [('add', ('dir1', 'dir1-id', 'directory', '')),
804
('add', ('dir2', 'dir2-id', 'directory', '')),]
806
def do_move_dir1_into_dir2(self):
807
return [('rename', ('dir1', 'dir2/dir1'))]
809
def check_dir1_moved(self):
810
self.failIfExists('branch/dir1')
811
self.failUnlessExists('branch/dir2/dir1')
813
def do_move_dir2_into_dir1(self):
814
return [('rename', ('dir2', 'dir1/dir2'))]
816
def check_dir2_moved(self):
817
self.failIfExists('branch/dir2')
818
self.failUnlessExists('branch/dir1/dir2')
820
def do_create_dir1_4(self):
821
return [('add', ('dir1', 'dir1-id', 'directory', '')),
822
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
823
('add', ('dir3', 'dir3-id', 'directory', '')),
824
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
826
def do_move_dir1_into_dir4(self):
827
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
829
def check_dir1_2_moved(self):
830
self.failIfExists('branch/dir1')
831
self.failUnlessExists('branch/dir3/dir4/dir1')
832
self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
834
def do_move_dir3_into_dir2(self):
835
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
837
def check_dir3_4_moved(self):
838
self.failIfExists('branch/dir3')
839
self.failUnlessExists('branch/dir1/dir2/dir3')
840
self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
842
def _get_resolve_path_arg(self, wt, action):
843
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
844
# But since <path> doesn't exist in the working tree, we need to use
845
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
846
return wt.id2path(self._other['dir_id'])
848
def assertParentLoop(self, wt, c):
849
self.assertEqual(self._other['dir_id'], c.file_id)
850
self.assertEqual(self._other['target_id'], c.conflict_file_id)
851
# The conflict paths are irrelevant (they are deterministic but not
852
# worth checking since they don't provide the needed information
854
if self._other['xfail']:
855
# It's a bit hackish to raise from here relying on being called for
856
# both tests but this avoid overriding test_resolve_taking_other
857
raise tests.KnownFailure(
858
"ParentLoop doesn't carry enough info to resolve --take-other")
859
_assert_conflict = assertParentLoop
862
class TestResolveNonDirectoryParent(TestResolveConflicts):
870
$ bzr commit -m 'Create trunk' -q
871
$ echo "Boing" >foo/bar
873
$ bzr commit -q -m 'Add foo/bar'
874
$ bzr branch -q . -r 1 ../branch
878
$ bzr commit -q -m 'foo is now a file'
882
# FIXME: The message is misleading, foo.new *is* a directory when the message
883
# is displayed -- vila 090916
884
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
885
2>1 conflicts encountered.
888
def test_take_this(self):
890
$ bzr rm -q foo.new --force
891
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
892
# aside ? -- vila 090916
894
$ bzr resolve foo.new
895
2>1 conflict(s) resolved, 0 remaining
896
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
899
def test_take_other(self):
901
$ bzr rm -q foo --force
902
$ bzr mv -q foo.new foo
904
2>1 conflict(s) resolved, 0 remaining
905
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
908
def test_resolve_taking_this(self):
910
$ bzr resolve --take-this foo.new
912
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
915
def test_resolve_taking_other(self):
917
$ bzr resolve --take-other foo.new
919
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
923
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
925
def test_bug_430129(self):
926
# This is nearly like TestResolveNonDirectoryParent but with branch and
927
# trunk switched. As such it should certainly produce the same
935
$ bzr commit -m 'Create trunk' -q
938
$ bzr commit -m 'foo is now a file' -q
939
$ bzr branch -q . -r 1 ../branch -q
941
$ echo "Boing" >foo/bar
942
$ bzr add -q foo/bar -q
943
$ bzr commit -m 'Add foo/bar' -q
945
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
949
class TestResolveActionOption(tests.TestCase):
952
super(TestResolveActionOption, self).setUp()
953
self.options = [conflicts.ResolveActionOption()]
954
self.parser = option.get_optparser(dict((o.name, o)
955
for o in self.options))
957
def parse(self, args):
958
return self.parser.parse_args(args)
960
def test_unknown_action(self):
961
self.assertRaises(errors.BadOptionValue,
962
self.parse, ['--action', 'take-me-to-the-moon'])
965
opts, args = self.parse(['--action', 'done'])
966
self.assertEqual({'action':'done'}, opts)
968
def test_take_this(self):
969
opts, args = self.parse(['--action', 'take-this'])
970
self.assertEqual({'action': 'take_this'}, opts)
971
opts, args = self.parse(['--take-this'])
972
self.assertEqual({'action': 'take_this'}, opts)
974
def test_take_other(self):
975
opts, args = self.parse(['--action', 'take-other'])
976
self.assertEqual({'action': 'take_other'}, opts)
977
opts, args = self.parse(['--take-other'])
978
self.assertEqual({'action': 'take_other'}, opts)