41
34
# be a sorted list.
42
35
# u'\xe5' == a with circle
43
36
# '\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'),
37
# So these are u'pathg' and 'idg' only with a circle and a hat. (shappo?)
38
example_conflicts = ConflictList([
39
MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
40
ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
41
TextConflict(u'p\xe5tha'),
42
PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
43
DuplicateID('Unversioned existing file', u'p\xe5thc', u'p\xe5thc2',
44
'\xc3\xaedc', '\xc3\xaedc'),
45
DuplicateEntry('Moved existing file to', u'p\xe5thdd.moved', u'p\xe5thd',
47
ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
49
UnversionedParent('Versioned directory', u'p\xe5thf', '\xc3\xaedf'),
65
def vary_by_conflicts():
66
for conflict in example_conflicts:
67
yield (conflict.__class__.__name__, {"conflict": conflict})
70
class TestConflicts(tests.TestCaseWithTransport):
53
class TestConflicts(TestCaseWithTransport):
55
def test_conflicts(self):
56
"""Conflicts are detected properly"""
57
tree = self.make_branch_and_tree('.',
58
format=bzrdir.BzrDirFormat6())
60
file('hello', 'w').write('hello world4')
61
file('hello.THIS', 'w').write('hello world2')
62
file('hello.BASE', 'w').write('hello world1')
63
file('hello.OTHER', 'w').write('hello world3')
64
file('hello.sploo.BASE', 'w').write('yellow world')
65
file('hello.sploo.OTHER', 'w').write('yellow world2')
67
self.assertEqual(len(list(tree.list_files())), 6)
69
conflicts = tree.conflicts()
70
self.assertEqual(len(conflicts), 2)
71
self.assert_('hello' in conflicts[0].path)
72
self.assert_('hello.sploo' in conflicts[1].path)
74
restore('hello.sploo')
75
self.assertEqual(len(tree.conflicts()), 0)
76
self.assertFileEqual('hello world2', 'hello')
77
assert not os.path.lexists('hello.sploo')
78
self.assertRaises(NotConflicted, restore, 'hello')
79
self.assertRaises(NotConflicted, restore, 'hello.sploo')
72
81
def test_resolve_conflict_dir(self):
73
82
tree = self.make_branch_and_tree('.')
74
self.build_tree_contents([('hello', 'hello world4'),
75
('hello.THIS', 'hello world2'),
76
('hello.BASE', 'hello world1'),
84
file('hello', 'w').write('hello world4')
85
tree.add('hello', 'q')
86
file('hello.THIS', 'w').write('hello world2')
87
file('hello.BASE', 'w').write('hello world1')
78
88
os.mkdir('hello.OTHER')
79
tree.add('hello', 'q')
80
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
89
l = ConflictList([TextConflict('hello')])
81
90
l.remove_files(tree)
83
def test_select_conflicts(self):
84
tree = self.make_branch_and_tree('.')
85
clist = conflicts.ConflictList
87
def check_select(not_selected, selected, paths, **kwargs):
89
(not_selected, selected),
90
tree_conflicts.select_conflicts(tree, paths, **kwargs))
92
foo = conflicts.ContentsConflict('foo')
93
bar = conflicts.ContentsConflict('bar')
94
tree_conflicts = clist([foo, bar])
96
check_select(clist([bar]), clist([foo]), ['foo'])
97
check_select(clist(), tree_conflicts,
98
[''], ignore_misses=True, recurse=True)
100
foobaz = conflicts.ContentsConflict('foo/baz')
101
tree_conflicts = clist([foobaz, bar])
103
check_select(clist([bar]), clist([foobaz]),
104
['foo'], ignore_misses=True, recurse=True)
106
qux = conflicts.PathConflict('qux', 'foo/baz')
107
tree_conflicts = clist([qux])
109
check_select(clist(), tree_conflicts,
110
['foo'], ignore_misses=True, recurse=True)
111
check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
113
def test_resolve_conflicts_recursive(self):
114
tree = self.make_branch_and_tree('.')
115
self.build_tree(['dir/', 'dir/hello'])
116
tree.add(['dir', 'dir/hello'])
118
dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
119
tree.set_conflicts(dirhello)
121
conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
122
self.assertEqual(dirhello, tree.conflicts())
124
conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
125
self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
128
class TestPerConflict(tests.TestCase):
130
scenarios = scenarios.multiply_scenarios(vary_by_conflicts())
132
def test_stringification(self):
133
text = unicode(self.conflict)
134
self.assertContainsString(text, self.conflict.path)
135
self.assertContainsString(text.lower(), "conflict")
136
self.assertContainsString(repr(self.conflict),
137
self.conflict.__class__.__name__)
93
class TestConflictStanzas(TestCase):
139
95
def test_stanza_roundtrip(self):
141
o = conflicts.Conflict.factory(**p.as_stanza().as_dict())
142
self.assertEqual(o, p)
144
self.assertIsInstance(o.path, unicode)
146
if o.file_id is not None:
147
self.assertIsInstance(o.file_id, str)
149
conflict_path = getattr(o, 'conflict_path', None)
150
if conflict_path is not None:
151
self.assertIsInstance(conflict_path, unicode)
153
conflict_file_id = getattr(o, 'conflict_file_id', None)
154
if conflict_file_id is not None:
155
self.assertIsInstance(conflict_file_id, str)
96
# write and read our example stanza.
97
stanza_iter = example_conflicts.to_stanzas()
98
processed = ConflictList.from_stanzas(stanza_iter)
99
for o, p in zip(processed, example_conflicts):
100
self.assertEqual(o, p)
102
self.assertIsInstance(o.path, unicode)
104
if o.file_id is not None:
105
self.assertIsInstance(o.file_id, str)
107
conflict_path = getattr(o, 'conflict_path', None)
108
if conflict_path is not None:
109
self.assertIsInstance(conflict_path, unicode)
111
conflict_file_id = getattr(o, 'conflict_file_id', None)
112
if conflict_file_id is not None:
113
self.assertIsInstance(conflict_file_id, str)
157
115
def test_stanzification(self):
158
stanza = self.conflict.as_stanza()
159
if 'file_id' in stanza:
160
# In Stanza form, the file_id has to be unicode.
161
self.assertStartsWith(stanza['file_id'], u'\xeed')
162
self.assertStartsWith(stanza['path'], u'p\xe5th')
163
if 'conflict_path' in stanza:
164
self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
165
if 'conflict_file_id' in stanza:
166
self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
169
class TestConflictList(tests.TestCase):
171
def test_stanzas_roundtrip(self):
172
stanzas_iter = example_conflicts.to_stanzas()
173
processed = conflicts.ConflictList.from_stanzas(stanzas_iter)
174
self.assertEqual(example_conflicts, processed)
176
def test_stringification(self):
177
for text, o in zip(example_conflicts.to_strings(), example_conflicts):
178
self.assertEqual(text, unicode(o))
181
# FIXME: The shell-like tests should be converted to real whitebox tests... or
182
# moved to a blackbox module -- vila 20100205
184
# FIXME: test missing for multiple conflicts
186
# FIXME: Tests missing for DuplicateID conflict type
187
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
189
preamble = None # The setup script set by daughter classes
192
super(TestResolveConflicts, self).setUp()
193
self.run_script(self.preamble)
196
def mirror_scenarios(base_scenarios):
197
"""Return a list of mirrored scenarios.
199
Each scenario in base_scenarios is duplicated switching the roles of 'this'
203
for common, (lname, ldict), (rname, rdict) in base_scenarios:
204
a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
205
[(rname, dict(_other=rdict))])
206
b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
207
[(lname, dict(_other=ldict))])
208
# Inject the common parameters in all scenarios
209
for name, d in a + b:
211
scenarios.extend(a + b)
215
# FIXME: Get rid of parametrized (in the class name) once we delete
216
# TestResolveConflicts -- vila 20100308
217
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
218
"""This class provides a base to test single conflict resolution.
220
Since all conflict objects are created with specific semantics for their
221
attributes, each class should implement the necessary functions and
222
attributes described below.
224
Each class should define the scenarios that create the expected (single)
227
Each scenario describes:
228
* how to create 'base' tree (and revision)
229
* how to create 'left' tree (and revision, parent rev 'base')
230
* how to create 'right' tree (and revision, parent rev 'base')
231
* how to check that changes in 'base'->'left' have been taken
232
* how to check that changes in 'base'->'right' have been taken
234
From each base scenario, we generate two concrete scenarios where:
235
* this=left, other=right
236
* this=right, other=left
238
Then the test case verifies each concrete scenario by:
239
* creating a branch containing the 'base', 'this' and 'other' revisions
240
* creating a working tree for the 'this' revision
241
* performing the merge of 'other' into 'this'
242
* verifying the expected conflict was generated
243
* resolving with --take-this or --take-other, and running the corresponding
244
checks (for either 'base'->'this', or 'base'->'other')
246
:cvar _conflict_type: The expected class of the generated conflict.
248
:cvar _assert_conflict: A method receiving the working tree and the
249
conflict object and checking its attributes.
251
:cvar _base_actions: The branchbuilder actions to create the 'base'
254
:cvar _this: The dict related to 'base' -> 'this'. It contains at least:
255
* 'actions': The branchbuilder actions to create the 'this'
257
* 'check': how to check the changes after resolution with --take-this.
259
:cvar _other: The dict related to 'base' -> 'other'. It contains at least:
260
* 'actions': The branchbuilder actions to create the 'other'
262
* 'check': how to check the changes after resolution with --take-other.
265
# Set by daughter classes
266
_conflict_type = None
267
_assert_conflict = None
275
"""The scenario list for the conflict type defined by the class.
277
Each scenario is of the form:
278
(common, (left_name, left_dict), (right_name, right_dict))
282
* left_name and right_name are the scenario names that will be combined
284
* left_dict and right_dict are the attributes specific to each half of
285
the scenario. They should include at least 'actions' and 'check' and
286
will be available as '_this' and '_other' test instance attributes.
288
Daughters classes are free to add their specific attributes as they see
289
fit in any of the three dicts.
291
This is a class method so that load_tests can find it.
293
'_base_actions' in the common dict, 'actions' and 'check' in the left
294
and right dicts use names that map to methods in the test classes. Some
295
prefixes are added to these names to get the correspong methods (see
296
_get_actions() and _get_check()). The motivation here is to avoid
297
collisions in the class namespace.
301
super(TestParametrizedResolveConflicts, self).setUp()
302
builder = self.make_branch_builder('trunk')
303
builder.start_series()
305
# Create an empty trunk
306
builder.build_snapshot('start', None, [
307
('add', ('', 'root-id', 'directory', ''))])
308
# Add a minimal base content
309
base_actions = self._get_actions(self._base_actions)()
310
builder.build_snapshot('base', ['start'], base_actions)
311
# Modify the base content in branch
312
actions_other = self._get_actions(self._other['actions'])()
313
builder.build_snapshot('other', ['base'], actions_other)
314
# Modify the base content in trunk
315
actions_this = self._get_actions(self._this['actions'])()
316
builder.build_snapshot('this', ['base'], actions_this)
317
# builder.get_branch() tip is now 'this'
319
builder.finish_series()
320
self.builder = builder
322
def _get_actions(self, name):
323
return getattr(self, 'do_%s' % name)
325
def _get_check(self, name):
326
return getattr(self, 'check_%s' % name)
328
def _merge_other_into_this(self):
329
b = self.builder.get_branch()
330
wt = b.bzrdir.sprout('branch').open_workingtree()
331
wt.merge_from_branch(b, 'other')
334
def assertConflict(self, wt):
335
confs = wt.conflicts()
336
self.assertLength(1, confs)
338
self.assertIsInstance(c, self._conflict_type)
339
self._assert_conflict(wt, c)
341
def _get_resolve_path_arg(self, wt, action):
342
raise NotImplementedError(self._get_resolve_path_arg)
344
def check_resolved(self, wt, action):
345
path = self._get_resolve_path_arg(wt, action)
346
conflicts.resolve(wt, [path], action=action)
347
# Check that we don't have any conflicts nor unknown left
348
self.assertLength(0, wt.conflicts())
349
self.assertLength(0, list(wt.unknowns()))
351
def test_resolve_taking_this(self):
352
wt = self._merge_other_into_this()
353
self.assertConflict(wt)
354
self.check_resolved(wt, 'take_this')
355
check_this = self._get_check(self._this['check'])
358
def test_resolve_taking_other(self):
359
wt = self._merge_other_into_this()
360
self.assertConflict(wt)
361
self.check_resolved(wt, 'take_other')
362
check_other = self._get_check(self._other['check'])
366
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
368
_conflict_type = conflicts.TextConflict
370
# Set by the scenarios
371
# path and file-id for the file involved in the conflict
375
scenarios = mirror_scenarios(
377
# File modified on both sides
378
(dict(_base_actions='create_file',
379
_path='file', _file_id='file-id'),
381
dict(actions='modify_file_A', check='file_has_content_A')),
383
dict(actions='modify_file_B', check='file_has_content_B')),),
384
# File modified on both sides in dir
385
(dict(_base_actions='create_file_in_dir',
386
_path='dir/file', _file_id='file-id'),
387
('filed_modified_A_in_dir',
388
dict(actions='modify_file_A',
389
check='file_in_dir_has_content_A')),
391
dict(actions='modify_file_B',
392
check='file_in_dir_has_content_B')),),
395
def do_create_file(self, path='file'):
396
return [('add', (path, 'file-id', 'file', 'trunk content\n'))]
398
def do_modify_file_A(self):
399
return [('modify', ('file-id', 'trunk content\nfeature A\n'))]
401
def do_modify_file_B(self):
402
return [('modify', ('file-id', 'trunk content\nfeature B\n'))]
404
def check_file_has_content_A(self, path='file'):
405
self.assertFileEqual('trunk content\nfeature A\n',
406
osutils.pathjoin('branch', path))
408
def check_file_has_content_B(self, path='file'):
409
self.assertFileEqual('trunk content\nfeature B\n',
410
osutils.pathjoin('branch', path))
412
def do_create_file_in_dir(self):
413
return [('add', ('dir', 'dir-id', 'directory', '')),
414
] + self.do_create_file('dir/file')
416
def check_file_in_dir_has_content_A(self):
417
self.check_file_has_content_A('dir/file')
419
def check_file_in_dir_has_content_B(self):
420
self.check_file_has_content_B('dir/file')
422
def _get_resolve_path_arg(self, wt, action):
425
def assertTextConflict(self, wt, c):
426
self.assertEqual(self._file_id, c.file_id)
427
self.assertEqual(self._path, c.path)
428
_assert_conflict = assertTextConflict
431
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
433
_conflict_type = conflicts.ContentsConflict
435
# Set by the scenarios
436
# path and file-id for the file involved in the conflict
440
scenarios = mirror_scenarios(
442
# File modified/deleted
443
(dict(_base_actions='create_file',
444
_path='file', _file_id='file-id'),
446
dict(actions='modify_file', check='file_has_more_content')),
448
dict(actions='delete_file', check='file_doesnt_exist')),),
449
# File renamed-modified/deleted
450
(dict(_base_actions='create_file',
451
_path='new-file', _file_id='file-id'),
452
('file_renamed_and_modified',
453
dict(actions='modify_and_rename_file',
454
check='file_renamed_and_more_content')),
456
dict(actions='delete_file', check='file_doesnt_exist')),),
457
# File modified/deleted in dir
458
(dict(_base_actions='create_file_in_dir',
459
_path='dir/file', _file_id='file-id'),
460
('file_modified_in_dir',
461
dict(actions='modify_file_in_dir',
462
check='file_in_dir_has_more_content')),
463
('file_deleted_in_dir',
464
dict(actions='delete_file',
465
check='file_in_dir_doesnt_exist')),),
468
def do_create_file(self):
469
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
471
def do_modify_file(self):
472
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
474
def do_modify_and_rename_file(self):
475
return [('modify', ('file-id', 'trunk content\nmore content\n')),
476
('rename', ('file', 'new-file'))]
478
def check_file_has_more_content(self):
479
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
481
def check_file_renamed_and_more_content(self):
482
self.assertFileEqual('trunk content\nmore content\n', 'branch/new-file')
484
def do_delete_file(self):
485
return [('unversion', 'file-id')]
487
def check_file_doesnt_exist(self):
488
self.assertPathDoesNotExist('branch/file')
490
def do_create_file_in_dir(self):
491
return [('add', ('dir', 'dir-id', 'directory', '')),
492
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
494
def do_modify_file_in_dir(self):
495
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
497
def check_file_in_dir_has_more_content(self):
498
self.assertFileEqual('trunk content\nmore content\n', 'branch/dir/file')
500
def check_file_in_dir_doesnt_exist(self):
501
self.assertPathDoesNotExist('branch/dir/file')
503
def _get_resolve_path_arg(self, wt, action):
506
def assertContentsConflict(self, wt, c):
507
self.assertEqual(self._file_id, c.file_id)
508
self.assertEqual(self._path, c.path)
509
_assert_conflict = assertContentsConflict
512
class TestResolvePathConflict(TestParametrizedResolveConflicts):
514
_conflict_type = conflicts.PathConflict
516
def do_nothing(self):
519
# Each side dict additionally defines:
520
# - path path involved (can be '<deleted>')
522
scenarios = mirror_scenarios(
524
# File renamed/deleted
525
(dict(_base_actions='create_file'),
527
dict(actions='rename_file', check='file_renamed',
528
path='new-file', file_id='file-id')),
530
dict(actions='delete_file', check='file_doesnt_exist',
531
# PathConflicts deletion handling requires a special
533
path='<deleted>', file_id='file-id')),),
534
# File renamed/deleted in dir
535
(dict(_base_actions='create_file_in_dir'),
536
('file_renamed_in_dir',
537
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
538
path='dir/new-file', file_id='file-id')),
540
dict(actions='delete_file', check='file_in_dir_doesnt_exist',
541
# PathConflicts deletion handling requires a special
543
path='<deleted>', file_id='file-id')),),
544
# File renamed/renamed differently
545
(dict(_base_actions='create_file'),
547
dict(actions='rename_file', check='file_renamed',
548
path='new-file', file_id='file-id')),
550
dict(actions='rename_file2', check='file_renamed2',
551
path='new-file2', file_id='file-id')),),
552
# Dir renamed/deleted
553
(dict(_base_actions='create_dir'),
555
dict(actions='rename_dir', check='dir_renamed',
556
path='new-dir', file_id='dir-id')),
558
dict(actions='delete_dir', check='dir_doesnt_exist',
559
# PathConflicts deletion handling requires a special
561
path='<deleted>', file_id='dir-id')),),
562
# Dir renamed/renamed differently
563
(dict(_base_actions='create_dir'),
565
dict(actions='rename_dir', check='dir_renamed',
566
path='new-dir', file_id='dir-id')),
568
dict(actions='rename_dir2', check='dir_renamed2',
569
path='new-dir2', file_id='dir-id')),),
572
def do_create_file(self):
573
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
575
def do_create_dir(self):
576
return [('add', ('dir', 'dir-id', 'directory', ''))]
578
def do_rename_file(self):
579
return [('rename', ('file', 'new-file'))]
581
def check_file_renamed(self):
582
self.assertPathDoesNotExist('branch/file')
583
self.assertPathExists('branch/new-file')
585
def do_rename_file2(self):
586
return [('rename', ('file', 'new-file2'))]
588
def check_file_renamed2(self):
589
self.assertPathDoesNotExist('branch/file')
590
self.assertPathExists('branch/new-file2')
592
def do_rename_dir(self):
593
return [('rename', ('dir', 'new-dir'))]
595
def check_dir_renamed(self):
596
self.assertPathDoesNotExist('branch/dir')
597
self.assertPathExists('branch/new-dir')
599
def do_rename_dir2(self):
600
return [('rename', ('dir', 'new-dir2'))]
602
def check_dir_renamed2(self):
603
self.assertPathDoesNotExist('branch/dir')
604
self.assertPathExists('branch/new-dir2')
606
def do_delete_file(self):
607
return [('unversion', 'file-id')]
609
def check_file_doesnt_exist(self):
610
self.assertPathDoesNotExist('branch/file')
612
def do_delete_dir(self):
613
return [('unversion', 'dir-id')]
615
def check_dir_doesnt_exist(self):
616
self.assertPathDoesNotExist('branch/dir')
618
def do_create_file_in_dir(self):
619
return [('add', ('dir', 'dir-id', 'directory', '')),
620
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
622
def do_rename_file_in_dir(self):
623
return [('rename', ('dir/file', 'dir/new-file'))]
625
def check_file_in_dir_renamed(self):
626
self.assertPathDoesNotExist('branch/dir/file')
627
self.assertPathExists('branch/dir/new-file')
629
def check_file_in_dir_doesnt_exist(self):
630
self.assertPathDoesNotExist('branch/dir/file')
632
def _get_resolve_path_arg(self, wt, action):
633
tpath = self._this['path']
634
opath = self._other['path']
635
if tpath == '<deleted>':
641
def assertPathConflict(self, wt, c):
642
tpath = self._this['path']
643
tfile_id = self._this['file_id']
644
opath = self._other['path']
645
ofile_id = self._other['file_id']
646
self.assertEqual(tfile_id, ofile_id) # Sanity check
647
self.assertEqual(tfile_id, c.file_id)
648
self.assertEqual(tpath, c.path)
649
self.assertEqual(opath, c.conflict_path)
650
_assert_conflict = assertPathConflict
653
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
654
"""Same as TestResolvePathConflict but a specific conflict object.
657
def assertPathConflict(self, c):
658
# We create a conflict object as it was created before the fix and
659
# inject it into the working tree, the test will exercise the
660
# compatibility code.
661
old_c = conflicts.PathConflict('<deleted>', self._item_path,
663
wt.set_conflicts(conflicts.ConflictList([old_c]))
666
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
668
_conflict_type = conflicts.DuplicateEntry
670
scenarios = mirror_scenarios(
672
# File created with different file-ids
673
(dict(_base_actions='nothing'),
675
dict(actions='create_file_a', check='file_content_a',
676
path='file', file_id='file-a-id')),
678
dict(actions='create_file_b', check='file_content_b',
679
path='file', file_id='file-b-id')),),
682
def do_nothing(self):
685
def do_create_file_a(self):
686
return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
688
def check_file_content_a(self):
689
self.assertFileEqual('file a content\n', 'branch/file')
691
def do_create_file_b(self):
692
return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
694
def check_file_content_b(self):
695
self.assertFileEqual('file b content\n', 'branch/file')
697
def _get_resolve_path_arg(self, wt, action):
698
return self._this['path']
700
def assertDuplicateEntry(self, wt, c):
701
tpath = self._this['path']
702
tfile_id = self._this['file_id']
703
opath = self._other['path']
704
ofile_id = self._other['file_id']
705
self.assertEqual(tpath, opath) # Sanity check
706
self.assertEqual(tfile_id, c.file_id)
707
self.assertEqual(tpath + '.moved', c.path)
708
self.assertEqual(tpath, c.conflict_path)
709
_assert_conflict = assertDuplicateEntry
712
class TestResolveUnversionedParent(TestResolveConflicts):
714
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
716
# FIXME: While this *creates* UnversionedParent conflicts, this really only
717
# tests MissingParent resolution :-/
724
$ bzr commit -m 'Create trunk' -q
725
$ echo 'trunk content' >dir/file
726
$ bzr add -q dir/file
727
$ bzr commit -q -m 'Add dir/file in trunk'
728
$ bzr branch -q . -r 1 ../branch
731
$ bzr commit -q -m 'Remove dir in branch'
735
2>Conflict adding files to dir. Created directory.
736
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
737
2>2 conflicts encountered.
740
def test_take_this(self):
742
$ bzr rm -q dir --force
744
2>2 conflict(s) resolved, 0 remaining
745
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
748
def test_take_other(self):
751
2>2 conflict(s) resolved, 0 remaining
752
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
756
class TestResolveMissingParent(TestResolveConflicts):
763
$ echo 'trunk content' >dir/file
765
$ bzr commit -m 'Create trunk' -q
766
$ echo 'trunk content' >dir/file2
767
$ bzr add -q dir/file2
768
$ bzr commit -q -m 'Add dir/file2 in branch'
769
$ bzr branch -q . -r 1 ../branch
771
$ bzr rm -q dir/file --force
773
$ bzr commit -q -m 'Remove dir/file'
777
2>Conflict adding files to dir. Created directory.
778
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
779
2>2 conflicts encountered.
782
def test_keep_them_all(self):
785
2>2 conflict(s) resolved, 0 remaining
786
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
789
def test_adopt_child(self):
791
$ bzr mv -q dir/file2 file2
792
$ bzr rm -q dir --force
794
2>2 conflict(s) resolved, 0 remaining
795
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
798
def test_kill_them_all(self):
800
$ bzr rm -q dir --force
802
2>2 conflict(s) resolved, 0 remaining
803
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
806
def test_resolve_taking_this(self):
808
$ bzr resolve --take-this dir
810
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
813
def test_resolve_taking_other(self):
815
$ bzr resolve --take-other dir
817
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
821
class TestResolveDeletingParent(TestResolveConflicts):
828
$ echo 'trunk content' >dir/file
830
$ bzr commit -m 'Create trunk' -q
831
$ bzr rm -q dir/file --force
832
$ bzr rm -q dir --force
833
$ bzr commit -q -m 'Remove dir/file'
834
$ bzr branch -q . -r 1 ../branch
836
$ echo 'branch content' >dir/file2
837
$ bzr add -q dir/file2
838
$ bzr commit -q -m 'Add dir/file2 in branch'
841
2>Conflict: can't delete dir because it is not empty. Not deleting.
842
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
843
2>2 conflicts encountered.
846
def test_keep_them_all(self):
849
2>2 conflict(s) resolved, 0 remaining
850
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
853
def test_adopt_child(self):
855
$ bzr mv -q dir/file2 file2
856
$ bzr rm -q dir --force
858
2>2 conflict(s) resolved, 0 remaining
859
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
862
def test_kill_them_all(self):
864
$ bzr rm -q dir --force
866
2>2 conflict(s) resolved, 0 remaining
867
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
870
def test_resolve_taking_this(self):
872
$ bzr resolve --take-this dir
873
2>2 conflict(s) resolved, 0 remaining
874
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
877
def test_resolve_taking_other(self):
879
$ bzr resolve --take-other dir
882
2>2 conflict(s) resolved, 0 remaining
883
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
887
class TestResolveParentLoop(TestParametrizedResolveConflicts):
889
_conflict_type = conflicts.ParentLoop
894
# Each side dict additionally defines:
895
# - dir_id: the directory being moved
896
# - target_id: The target directory
897
# - xfail: whether the test is expected to fail if the action is
898
# involved as 'other'
899
scenarios = mirror_scenarios(
901
# Dirs moved into each other
902
(dict(_base_actions='create_dir1_dir2'),
904
dict(actions='move_dir1_into_dir2', check='dir1_moved',
905
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
907
dict(actions='move_dir2_into_dir1', check='dir2_moved',
908
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
909
# Subdirs moved into each other
910
(dict(_base_actions='create_dir1_4'),
912
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
913
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
915
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
916
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
919
def do_create_dir1_dir2(self):
920
return [('add', ('dir1', 'dir1-id', 'directory', '')),
921
('add', ('dir2', 'dir2-id', 'directory', '')),]
923
def do_move_dir1_into_dir2(self):
924
return [('rename', ('dir1', 'dir2/dir1'))]
926
def check_dir1_moved(self):
927
self.assertPathDoesNotExist('branch/dir1')
928
self.assertPathExists('branch/dir2/dir1')
930
def do_move_dir2_into_dir1(self):
931
return [('rename', ('dir2', 'dir1/dir2'))]
933
def check_dir2_moved(self):
934
self.assertPathDoesNotExist('branch/dir2')
935
self.assertPathExists('branch/dir1/dir2')
937
def do_create_dir1_4(self):
938
return [('add', ('dir1', 'dir1-id', 'directory', '')),
939
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
940
('add', ('dir3', 'dir3-id', 'directory', '')),
941
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
943
def do_move_dir1_into_dir4(self):
944
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
946
def check_dir1_2_moved(self):
947
self.assertPathDoesNotExist('branch/dir1')
948
self.assertPathExists('branch/dir3/dir4/dir1')
949
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
951
def do_move_dir3_into_dir2(self):
952
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
954
def check_dir3_4_moved(self):
955
self.assertPathDoesNotExist('branch/dir3')
956
self.assertPathExists('branch/dir1/dir2/dir3')
957
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
959
def _get_resolve_path_arg(self, wt, action):
960
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
961
# But since <path> doesn't exist in the working tree, we need to use
962
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
963
return wt.id2path(self._other['dir_id'])
965
def assertParentLoop(self, wt, c):
966
self.assertEqual(self._other['dir_id'], c.file_id)
967
self.assertEqual(self._other['target_id'], c.conflict_file_id)
968
# The conflict paths are irrelevant (they are deterministic but not
969
# worth checking since they don't provide the needed information
971
if self._other['xfail']:
972
# It's a bit hackish to raise from here relying on being called for
973
# both tests but this avoid overriding test_resolve_taking_other
975
"ParentLoop doesn't carry enough info to resolve --take-other")
976
_assert_conflict = assertParentLoop
979
class TestResolveNonDirectoryParent(TestResolveConflicts):
987
$ bzr commit -m 'Create trunk' -q
988
$ echo "Boing" >foo/bar
990
$ bzr commit -q -m 'Add foo/bar'
991
$ bzr branch -q . -r 1 ../branch
995
$ bzr commit -q -m 'foo is now a file'
999
# FIXME: The message is misleading, foo.new *is* a directory when the message
1000
# is displayed -- vila 090916
1001
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1002
2>1 conflicts encountered.
1005
def test_take_this(self):
1007
$ bzr rm -q foo.new --force
1008
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1009
# aside ? -- vila 090916
1011
$ bzr resolve foo.new
1012
2>1 conflict(s) resolved, 0 remaining
1013
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1016
def test_take_other(self):
1018
$ bzr rm -q foo --force
1019
$ bzr mv -q foo.new foo
1021
2>1 conflict(s) resolved, 0 remaining
1022
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1025
def test_resolve_taking_this(self):
1027
$ bzr resolve --take-this foo.new
1029
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1032
def test_resolve_taking_other(self):
1034
$ bzr resolve --take-other foo.new
1036
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1040
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1042
def test_bug_430129(self):
1043
# This is nearly like TestResolveNonDirectoryParent but with branch and
1044
# trunk switched. As such it should certainly produce the same
1052
$ bzr commit -m 'Create trunk' -q
1055
$ bzr commit -m 'foo is now a file' -q
1056
$ bzr branch -q . -r 1 ../branch -q
1058
$ echo "Boing" >foo/bar
1059
$ bzr add -q foo/bar -q
1060
$ bzr commit -m 'Add foo/bar' -q
1061
$ bzr merge ../trunk
1062
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1066
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1068
def test_bug_805809(self):
1071
Created a standalone tree (format: 2a)
1076
$ bzr commit -m 'create file on trunk'
1077
2>Committing to: .../trunk/
1079
2>Committed revision 1.
1080
# Create a debian branch based on trunk
1082
$ bzr branch trunk -r 1 debian
1083
2>Branched 1 revision(s).
1090
$ bzr commit -m 'rename file to dir/file for debian'
1091
2>Committing to: .../debian/
1093
2>renamed file => dir/file
1094
2>Committed revision 2.
1095
# Create an experimental branch with a new root-id
1097
$ bzr init experimental
1098
Created a standalone tree (format: 2a)
1100
# Work around merging into empty branch not being supported
1101
# (http://pad.lv/308562)
1102
$ echo something >not-empty
1105
$ bzr commit -m 'Add some content in experimental'
1106
2>Committing to: .../experimental/
1108
2>Committed revision 1.
1109
# merge debian even without a common ancestor
1110
$ bzr merge ../debian -r0..2
1113
2>All changes applied successfully.
1114
$ bzr commit -m 'merging debian into experimental'
1115
2>Committing to: .../experimental/
1118
2>Committed revision 2.
1119
# Create an ubuntu branch with yet another root-id
1122
Created a standalone tree (format: 2a)
1124
# Work around merging into empty branch not being supported
1125
# (http://pad.lv/308562)
1126
$ echo something >not-empty-ubuntu
1128
adding not-empty-ubuntu
1129
$ bzr commit -m 'Add some content in experimental'
1130
2>Committing to: .../ubuntu/
1131
2>added not-empty-ubuntu
1132
2>Committed revision 1.
1134
$ bzr merge ../debian -r0..2
1137
2>All changes applied successfully.
1138
$ bzr commit -m 'merging debian'
1139
2>Committing to: .../ubuntu/
1142
2>Committed revision 2.
1143
# Now try to merge experimental
1144
$ bzr merge ../experimental
1146
2>Path conflict: dir / dir
1147
2>1 conflicts encountered.
1151
class TestResolveActionOption(tests.TestCase):
1154
super(TestResolveActionOption, self).setUp()
1155
self.options = [conflicts.ResolveActionOption()]
1156
self.parser = option.get_optparser(dict((o.name, o)
1157
for o in self.options))
1159
def parse(self, args):
1160
return self.parser.parse_args(args)
1162
def test_unknown_action(self):
1163
self.assertRaises(errors.BadOptionValue,
1164
self.parse, ['--action', 'take-me-to-the-moon'])
1166
def test_done(self):
1167
opts, args = self.parse(['--action', 'done'])
1168
self.assertEqual({'action':'done'}, opts)
1170
def test_take_this(self):
1171
opts, args = self.parse(['--action', 'take-this'])
1172
self.assertEqual({'action': 'take_this'}, opts)
1173
opts, args = self.parse(['--take-this'])
1174
self.assertEqual({'action': 'take_this'}, opts)
1176
def test_take_other(self):
1177
opts, args = self.parse(['--action', 'take-other'])
1178
self.assertEqual({'action': 'take_other'}, opts)
1179
opts, args = self.parse(['--take-other'])
1180
self.assertEqual({'action': 'take_other'}, opts)
116
for stanza in example_conflicts.to_stanzas():
117
if 'file_id' in stanza:
118
# In Stanza form, the file_id has to be unicode.
119
self.assertStartsWith(stanza['file_id'], u'\xeed')
120
self.assertStartsWith(stanza['path'], u'p\xe5th')
121
if 'conflict_path' in stanza:
122
self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
123
if 'conflict_file_id' in stanza:
124
self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')