40
29
# The order of 'path' here is important - do not let it
41
30
# be a sorted list.
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', b'\xc3\xaedg'),
47
conflicts.ContentsConflict(u'p\xe5tha', None, b'\xc3\xaeda'),
48
conflicts.TextConflict(u'p\xe5tha'),
49
conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', b'\xc3\xaedb'),
50
conflicts.DuplicateID('Unversioned existing file',
51
u'p\xe5thc', u'p\xe5thc2',
52
b'\xc3\xaedc', b'\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',
57
None, b'\xc3\xaed2e'),
58
conflicts.UnversionedParent('Versioned directory',
59
u'p\xe5thf', b'\xc3\xaedf'),
60
conflicts.NonDirectoryParent('Created directory',
61
u'p\xe5thg', b'\xc3\xaedg'),
65
def vary_by_conflicts():
66
for conflict in example_conflicts:
67
yield (conflict.__class__.__name__, {"conflict": conflict})
70
class TestConflicts(tests.TestCaseWithTransport):
31
example_conflicts = ConflictList([
32
MissingParent('Not deleting', 'pathg', 'idg'),
33
ContentsConflict('patha', 'ida'),
34
TextConflict('patha'),
35
PathConflict('pathb', 'pathc', 'idb'),
36
DuplicateID('Unversioned existing file', 'pathc', 'pathc2', 'idc', 'idc'),
37
DuplicateEntry('Moved existing file to', 'pathdd.moved', 'pathd', 'idd',
39
ParentLoop('Cancelled move', 'pathe', 'path2e', None, 'id2e'),
40
UnversionedParent('Versioned directory', 'pathf', 'idf'),
44
class TestConflicts(TestCaseWithTransport):
46
def test_conflicts(self):
47
"""Conflicts are detected properly"""
48
tree = self.make_branch_and_tree('.',
49
format=bzrlib.bzrdir.BzrDirFormat6())
51
file('hello', 'w').write('hello world4')
52
file('hello.THIS', 'w').write('hello world2')
53
file('hello.BASE', 'w').write('hello world1')
54
file('hello.OTHER', 'w').write('hello world3')
55
file('hello.sploo.BASE', 'w').write('yellow world')
56
file('hello.sploo.OTHER', 'w').write('yellow world2')
57
self.assertEqual(len(list(tree.list_files())), 6)
58
conflicts = tree.conflicts()
59
self.assertEqual(len(conflicts), 2)
60
self.assert_('hello' in conflicts[0].path)
61
self.assert_('hello.sploo' in conflicts[1].path)
63
restore('hello.sploo')
64
self.assertEqual(len(tree.conflicts()), 0)
65
self.assertFileEqual('hello world2', 'hello')
66
assert not os.path.lexists('hello.sploo')
67
self.assertRaises(NotConflicted, restore, 'hello')
68
self.assertRaises(NotConflicted, restore, 'hello.sploo')
72
70
def test_resolve_conflict_dir(self):
73
71
tree = self.make_branch_and_tree('.')
74
self.build_tree_contents([('hello', b'hello world4'),
75
('hello.THIS', b'hello world2'),
76
('hello.BASE', b'hello world1'),
73
file('hello', 'w').write('hello world4')
74
tree.add('hello', 'q')
75
file('hello.THIS', 'w').write('hello world2')
76
file('hello.BASE', 'w').write('hello world1')
78
77
os.mkdir('hello.OTHER')
79
tree.add('hello', b'q')
80
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
78
l = ConflictList([TextConflict('hello')])
81
79
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(
119
[conflicts.TextConflict('dir/hello')])
120
tree.set_conflicts(dirhello)
122
conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
123
self.assertEqual(dirhello, tree.conflicts())
125
conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
126
self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
129
class TestPerConflict(tests.TestCase):
131
scenarios = scenarios.multiply_scenarios(vary_by_conflicts())
133
def test_stringification(self):
134
text = text_type(self.conflict)
135
self.assertContainsString(text, self.conflict.path)
136
self.assertContainsString(text.lower(), "conflict")
137
self.assertContainsString(repr(self.conflict),
138
self.conflict.__class__.__name__)
82
class TestConflictStanzas(TestCase):
140
84
def test_stanza_roundtrip(self):
142
o = conflicts.Conflict.factory(**p.as_stanza().as_dict())
143
self.assertEqual(o, p)
145
self.assertIsInstance(o.path, text_type)
147
if o.file_id is not None:
148
self.assertIsInstance(o.file_id, bytes)
150
conflict_path = getattr(o, 'conflict_path', None)
151
if conflict_path is not None:
152
self.assertIsInstance(conflict_path, text_type)
154
conflict_file_id = getattr(o, 'conflict_file_id', None)
155
if conflict_file_id is not None:
156
self.assertIsInstance(conflict_file_id, bytes)
85
# write and read our example stanza.
86
stanza_iter = example_conflicts.to_stanzas()
87
processed = ConflictList.from_stanzas(stanza_iter)
88
for o,p in zip(processed, example_conflicts):
89
self.assertEqual(o, p)
158
91
def test_stanzification(self):
159
stanza = self.conflict.as_stanza()
160
if 'file_id' in stanza:
161
# In Stanza form, the file_id has to be unicode.
162
self.assertStartsWith(stanza['file_id'], u'\xeed')
163
self.assertStartsWith(stanza['path'], u'p\xe5th')
164
if 'conflict_path' in stanza:
165
self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
166
if 'conflict_file_id' in stanza:
167
self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
170
class TestConflictList(tests.TestCase):
172
def test_stanzas_roundtrip(self):
173
stanzas_iter = example_conflicts.to_stanzas()
174
processed = conflicts.ConflictList.from_stanzas(stanzas_iter)
175
self.assertEqual(example_conflicts, processed)
177
def test_stringification(self):
178
for text, o in zip(example_conflicts.to_strings(), example_conflicts):
179
self.assertEqual(text, text_type(o))
182
# FIXME: The shell-like tests should be converted to real whitebox tests... or
183
# moved to a blackbox module -- vila 20100205
185
# FIXME: test missing for multiple conflicts
187
# FIXME: Tests missing for DuplicateID conflict type
188
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
190
preamble = None # The setup script set by daughter classes
193
super(TestResolveConflicts, self).setUp()
194
self.run_script(self.preamble)
197
def mirror_scenarios(base_scenarios):
198
"""Return a list of mirrored scenarios.
200
Each scenario in base_scenarios is duplicated switching the roles of 'this'
204
for common, (lname, ldict), (rname, rdict) in base_scenarios:
205
a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
206
[(rname, dict(_other=rdict))])
207
b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
208
[(lname, dict(_other=ldict))])
209
# Inject the common parameters in all scenarios
210
for name, d in a + b:
212
scenarios.extend(a + b)
216
# FIXME: Get rid of parametrized (in the class name) once we delete
217
# TestResolveConflicts -- vila 20100308
218
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
219
"""This class provides a base to test single conflict resolution.
221
Since all conflict objects are created with specific semantics for their
222
attributes, each class should implement the necessary functions and
223
attributes described below.
225
Each class should define the scenarios that create the expected (single)
228
Each scenario describes:
229
* how to create 'base' tree (and revision)
230
* how to create 'left' tree (and revision, parent rev 'base')
231
* how to create 'right' tree (and revision, parent rev 'base')
232
* how to check that changes in 'base'->'left' have been taken
233
* how to check that changes in 'base'->'right' have been taken
235
From each base scenario, we generate two concrete scenarios where:
236
* this=left, other=right
237
* this=right, other=left
239
Then the test case verifies each concrete scenario by:
240
* creating a branch containing the 'base', 'this' and 'other' revisions
241
* creating a working tree for the 'this' revision
242
* performing the merge of 'other' into 'this'
243
* verifying the expected conflict was generated
244
* resolving with --take-this or --take-other, and running the corresponding
245
checks (for either 'base'->'this', or 'base'->'other')
247
:cvar _conflict_type: The expected class of the generated conflict.
249
:cvar _assert_conflict: A method receiving the working tree and the
250
conflict object and checking its attributes.
252
:cvar _base_actions: The branchbuilder actions to create the 'base'
255
:cvar _this: The dict related to 'base' -> 'this'. It contains at least:
256
* 'actions': The branchbuilder actions to create the 'this'
258
* 'check': how to check the changes after resolution with --take-this.
260
:cvar _other: The dict related to 'base' -> 'other'. It contains at least:
261
* 'actions': The branchbuilder actions to create the 'other'
263
* 'check': how to check the changes after resolution with --take-other.
266
# Set by daughter classes
267
_conflict_type = None
268
_assert_conflict = None
276
"""The scenario list for the conflict type defined by the class.
278
Each scenario is of the form:
279
(common, (left_name, left_dict), (right_name, right_dict))
283
* left_name and right_name are the scenario names that will be combined
285
* left_dict and right_dict are the attributes specific to each half of
286
the scenario. They should include at least 'actions' and 'check' and
287
will be available as '_this' and '_other' test instance attributes.
289
Daughters classes are free to add their specific attributes as they see
290
fit in any of the three dicts.
292
This is a class method so that load_tests can find it.
294
'_base_actions' in the common dict, 'actions' and 'check' in the left
295
and right dicts use names that map to methods in the test classes. Some
296
prefixes are added to these names to get the correspong methods (see
297
_get_actions() and _get_check()). The motivation here is to avoid
298
collisions in the class namespace.
302
super(TestParametrizedResolveConflicts, self).setUp()
303
builder = self.make_branch_builder('trunk')
304
builder.start_series()
306
# Create an empty trunk
307
builder.build_snapshot(None, [
308
('add', (u'', b'root-id', 'directory', ''))],
309
revision_id=b'start')
310
# Add a minimal base content
311
base_actions = self._get_actions(self._base_actions)()
312
builder.build_snapshot([b'start'], base_actions, revision_id=b'base')
313
# Modify the base content in branch
314
actions_other = self._get_actions(self._other['actions'])()
315
builder.build_snapshot([b'base'], actions_other, revision_id=b'other')
316
# Modify the base content in trunk
317
actions_this = self._get_actions(self._this['actions'])()
318
builder.build_snapshot([b'base'], actions_this, revision_id=b'this')
319
# builder.get_branch() tip is now 'this'
321
builder.finish_series()
322
self.builder = builder
324
def _get_actions(self, name):
325
return getattr(self, 'do_%s' % name)
327
def _get_check(self, name):
328
return getattr(self, 'check_%s' % name)
330
def _merge_other_into_this(self):
331
b = self.builder.get_branch()
332
wt = b.controldir.sprout('branch').open_workingtree()
333
wt.merge_from_branch(b, b'other')
336
def assertConflict(self, wt):
337
confs = wt.conflicts()
338
self.assertLength(1, confs)
340
self.assertIsInstance(c, self._conflict_type)
341
self._assert_conflict(wt, c)
343
def _get_resolve_path_arg(self, wt, action):
344
raise NotImplementedError(self._get_resolve_path_arg)
346
def check_resolved(self, wt, action):
347
path = self._get_resolve_path_arg(wt, action)
348
conflicts.resolve(wt, [path], action=action)
349
# Check that we don't have any conflicts nor unknown left
350
self.assertLength(0, wt.conflicts())
351
self.assertLength(0, list(wt.unknowns()))
353
def test_resolve_taking_this(self):
354
wt = self._merge_other_into_this()
355
self.assertConflict(wt)
356
self.check_resolved(wt, 'take_this')
357
check_this = self._get_check(self._this['check'])
360
def test_resolve_taking_other(self):
361
wt = self._merge_other_into_this()
362
self.assertConflict(wt)
363
self.check_resolved(wt, 'take_other')
364
check_other = self._get_check(self._other['check'])
368
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
370
_conflict_type = conflicts.TextConflict
372
# Set by the scenarios
373
# path and file-id for the file involved in the conflict
377
scenarios = mirror_scenarios(
379
# File modified on both sides
380
(dict(_base_actions='create_file',
381
_path='file', _file_id=b'file-id'),
383
dict(actions='modify_file_A', check='file_has_content_A')),
385
dict(actions='modify_file_B', check='file_has_content_B')),),
386
# File modified on both sides in dir
387
(dict(_base_actions='create_file_in_dir',
388
_path='dir/file', _file_id=b'file-id'),
389
('filed_modified_A_in_dir',
390
dict(actions='modify_file_A_in_dir',
391
check='file_in_dir_has_content_A')),
393
dict(actions='modify_file_B_in_dir',
394
check='file_in_dir_has_content_B')),),
397
def do_create_file(self, path='file'):
398
return [('add', (path, b'file-id', 'file', b'trunk content\n'))]
400
def do_modify_file_A(self):
401
return [('modify', ('file', b'trunk content\nfeature A\n'))]
403
def do_modify_file_B(self):
404
return [('modify', ('file', b'trunk content\nfeature B\n'))]
406
def do_modify_file_A_in_dir(self):
407
return [('modify', ('dir/file', b'trunk content\nfeature A\n'))]
409
def do_modify_file_B_in_dir(self):
410
return [('modify', ('dir/file', b'trunk content\nfeature B\n'))]
412
def check_file_has_content_A(self, path='file'):
413
self.assertFileEqual(b'trunk content\nfeature A\n',
414
osutils.pathjoin('branch', path))
416
def check_file_has_content_B(self, path='file'):
417
self.assertFileEqual(b'trunk content\nfeature B\n',
418
osutils.pathjoin('branch', path))
420
def do_create_file_in_dir(self):
421
return [('add', ('dir', b'dir-id', 'directory', '')),
422
] + self.do_create_file('dir/file')
424
def check_file_in_dir_has_content_A(self):
425
self.check_file_has_content_A('dir/file')
427
def check_file_in_dir_has_content_B(self):
428
self.check_file_has_content_B('dir/file')
430
def _get_resolve_path_arg(self, wt, action):
433
def assertTextConflict(self, wt, c):
434
self.assertEqual(self._file_id, c.file_id)
435
self.assertEqual(self._path, c.path)
436
_assert_conflict = assertTextConflict
439
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
441
_conflict_type = conflicts.ContentsConflict
443
# Set by the scenarios
444
# path and file-id for the file involved in the conflict
448
scenarios = mirror_scenarios(
450
# File modified/deleted
451
(dict(_base_actions='create_file',
452
_path='file', _file_id=b'file-id'),
454
dict(actions='modify_file', check='file_has_more_content')),
456
dict(actions='delete_file', check='file_doesnt_exist')),),
457
# File renamed-modified/deleted
458
(dict(_base_actions='create_file',
459
_path='new-file', _file_id=b'file-id'),
460
('file_renamed_and_modified',
461
dict(actions='modify_and_rename_file',
462
check='file_renamed_and_more_content')),
464
dict(actions='delete_file', check='file_doesnt_exist')),),
465
# File modified/deleted in dir
466
(dict(_base_actions='create_file_in_dir',
467
_path='dir/file', _file_id=b'file-id'),
468
('file_modified_in_dir',
469
dict(actions='modify_file_in_dir',
470
check='file_in_dir_has_more_content')),
471
('file_deleted_in_dir',
472
dict(actions='delete_file_in_dir',
473
check='file_in_dir_doesnt_exist')),),
476
def do_create_file(self):
477
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
479
def do_modify_file(self):
480
return [('modify', ('file', b'trunk content\nmore content\n'))]
482
def do_modify_and_rename_file(self):
483
return [('modify', ('new-file', b'trunk content\nmore content\n')),
484
('rename', ('file', 'new-file'))]
486
def check_file_has_more_content(self):
487
self.assertFileEqual(b'trunk content\nmore content\n', 'branch/file')
489
def check_file_renamed_and_more_content(self):
490
self.assertFileEqual(
491
b'trunk content\nmore content\n', 'branch/new-file')
493
def do_delete_file(self):
494
return [('unversion', 'file')]
496
def do_delete_file_in_dir(self):
497
return [('unversion', 'dir/file')]
499
def check_file_doesnt_exist(self):
500
self.assertPathDoesNotExist('branch/file')
502
def do_create_file_in_dir(self):
503
return [('add', ('dir', b'dir-id', 'directory', '')),
504
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
506
def do_modify_file_in_dir(self):
507
return [('modify', ('dir/file', b'trunk content\nmore content\n'))]
509
def check_file_in_dir_has_more_content(self):
510
self.assertFileEqual(
511
b'trunk content\nmore content\n', 'branch/dir/file')
513
def check_file_in_dir_doesnt_exist(self):
514
self.assertPathDoesNotExist('branch/dir/file')
516
def _get_resolve_path_arg(self, wt, action):
519
def assertContentsConflict(self, wt, c):
520
self.assertEqual(self._file_id, c.file_id)
521
self.assertEqual(self._path, c.path)
522
_assert_conflict = assertContentsConflict
525
class TestResolvePathConflict(TestParametrizedResolveConflicts):
527
_conflict_type = conflicts.PathConflict
529
def do_nothing(self):
532
# Each side dict additionally defines:
533
# - path path involved (can be '<deleted>')
535
scenarios = mirror_scenarios(
537
# File renamed/deleted
538
(dict(_base_actions='create_file'),
540
dict(actions='rename_file', check='file_renamed',
541
path='new-file', file_id=b'file-id')),
543
dict(actions='delete_file', check='file_doesnt_exist',
544
# PathConflicts deletion handling requires a special
546
path='<deleted>', file_id=b'file-id')),),
547
# File renamed/deleted in dir
548
(dict(_base_actions='create_file_in_dir'),
549
('file_renamed_in_dir',
550
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
551
path='dir/new-file', file_id=b'file-id')),
553
dict(actions='delete_file_in_dir', check='file_in_dir_doesnt_exist',
554
# PathConflicts deletion handling requires a special
556
path='<deleted>', file_id=b'file-id')),),
557
# File renamed/renamed differently
558
(dict(_base_actions='create_file'),
560
dict(actions='rename_file', check='file_renamed',
561
path='new-file', file_id=b'file-id')),
563
dict(actions='rename_file2', check='file_renamed2',
564
path='new-file2', file_id=b'file-id')),),
565
# Dir renamed/deleted
566
(dict(_base_actions='create_dir'),
568
dict(actions='rename_dir', check='dir_renamed',
569
path='new-dir', file_id=b'dir-id')),
571
dict(actions='delete_dir', check='dir_doesnt_exist',
572
# PathConflicts deletion handling requires a special
574
path='<deleted>', file_id=b'dir-id')),),
575
# Dir renamed/renamed differently
576
(dict(_base_actions='create_dir'),
578
dict(actions='rename_dir', check='dir_renamed',
579
path='new-dir', file_id=b'dir-id')),
581
dict(actions='rename_dir2', check='dir_renamed2',
582
path='new-dir2', file_id=b'dir-id')),),
585
def do_create_file(self):
586
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
588
def do_create_dir(self):
589
return [('add', ('dir', b'dir-id', 'directory', ''))]
591
def do_rename_file(self):
592
return [('rename', ('file', 'new-file'))]
594
def check_file_renamed(self):
595
self.assertPathDoesNotExist('branch/file')
596
self.assertPathExists('branch/new-file')
598
def do_rename_file2(self):
599
return [('rename', ('file', 'new-file2'))]
601
def check_file_renamed2(self):
602
self.assertPathDoesNotExist('branch/file')
603
self.assertPathExists('branch/new-file2')
605
def do_rename_dir(self):
606
return [('rename', ('dir', 'new-dir'))]
608
def check_dir_renamed(self):
609
self.assertPathDoesNotExist('branch/dir')
610
self.assertPathExists('branch/new-dir')
612
def do_rename_dir2(self):
613
return [('rename', ('dir', 'new-dir2'))]
615
def check_dir_renamed2(self):
616
self.assertPathDoesNotExist('branch/dir')
617
self.assertPathExists('branch/new-dir2')
619
def do_delete_file(self):
620
return [('unversion', 'file')]
622
def do_delete_file_in_dir(self):
623
return [('unversion', 'dir/file')]
625
def check_file_doesnt_exist(self):
626
self.assertPathDoesNotExist('branch/file')
628
def do_delete_dir(self):
629
return [('unversion', 'dir')]
631
def check_dir_doesnt_exist(self):
632
self.assertPathDoesNotExist('branch/dir')
634
def do_create_file_in_dir(self):
635
return [('add', ('dir', b'dir-id', 'directory', '')),
636
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
638
def do_rename_file_in_dir(self):
639
return [('rename', ('dir/file', 'dir/new-file'))]
641
def check_file_in_dir_renamed(self):
642
self.assertPathDoesNotExist('branch/dir/file')
643
self.assertPathExists('branch/dir/new-file')
645
def check_file_in_dir_doesnt_exist(self):
646
self.assertPathDoesNotExist('branch/dir/file')
648
def _get_resolve_path_arg(self, wt, action):
649
tpath = self._this['path']
650
opath = self._other['path']
651
if tpath == '<deleted>':
657
def assertPathConflict(self, wt, c):
658
tpath = self._this['path']
659
tfile_id = self._this['file_id']
660
opath = self._other['path']
661
ofile_id = self._other['file_id']
662
self.assertEqual(tfile_id, ofile_id) # Sanity check
663
self.assertEqual(tfile_id, c.file_id)
664
self.assertEqual(tpath, c.path)
665
self.assertEqual(opath, c.conflict_path)
666
_assert_conflict = assertPathConflict
669
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
670
"""Same as TestResolvePathConflict but a specific conflict object.
673
def assertPathConflict(self, c):
674
# We create a conflict object as it was created before the fix and
675
# inject it into the working tree, the test will exercise the
676
# compatibility code.
677
old_c = conflicts.PathConflict('<deleted>', self._item_path,
679
wt.set_conflicts(conflicts.ConflictList([old_c]))
682
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
684
_conflict_type = conflicts.DuplicateEntry
686
scenarios = mirror_scenarios(
688
# File created with different file-ids
689
(dict(_base_actions='nothing'),
691
dict(actions='create_file_a', check='file_content_a',
692
path='file', file_id=b'file-a-id')),
694
dict(actions='create_file_b', check='file_content_b',
695
path='file', file_id=b'file-b-id')),),
696
# File created with different file-ids but deleted on one side
697
(dict(_base_actions='create_file_a'),
699
dict(actions='replace_file_a_by_b', check='file_content_b',
700
path='file', file_id=b'file-b-id')),
702
dict(actions='modify_file_a', check='file_new_content',
703
path='file', file_id=b'file-a-id')),),
706
def do_nothing(self):
709
def do_create_file_a(self):
710
return [('add', ('file', b'file-a-id', 'file', b'file a content\n'))]
712
def check_file_content_a(self):
713
self.assertFileEqual(b'file a content\n', 'branch/file')
715
def do_create_file_b(self):
716
return [('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
718
def check_file_content_b(self):
719
self.assertFileEqual(b'file b content\n', 'branch/file')
721
def do_replace_file_a_by_b(self):
722
return [('unversion', 'file'),
723
('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
725
def do_modify_file_a(self):
726
return [('modify', ('file', b'new content\n'))]
728
def check_file_new_content(self):
729
self.assertFileEqual(b'new content\n', 'branch/file')
731
def _get_resolve_path_arg(self, wt, action):
732
return self._this['path']
734
def assertDuplicateEntry(self, wt, c):
735
tpath = self._this['path']
736
tfile_id = self._this['file_id']
737
opath = self._other['path']
738
ofile_id = self._other['file_id']
739
self.assertEqual(tpath, opath) # Sanity check
740
self.assertEqual(tfile_id, c.file_id)
741
self.assertEqual(tpath + '.moved', c.path)
742
self.assertEqual(tpath, c.conflict_path)
743
_assert_conflict = assertDuplicateEntry
746
class TestResolveUnversionedParent(TestResolveConflicts):
748
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
750
# FIXME: While this *creates* UnversionedParent conflicts, this really only
751
# tests MissingParent resolution :-/
758
$ brz commit -m 'Create trunk' -q
759
$ echo 'trunk content' >dir/file
760
$ brz add -q dir/file
761
$ brz commit -q -m 'Add dir/file in trunk'
762
$ brz branch -q . -r 1 ../branch
765
$ brz commit -q -m 'Remove dir in branch'
769
2>Conflict adding files to dir. Created directory.
770
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
771
2>2 conflicts encountered.
774
def test_take_this(self):
776
$ brz rm -q dir --no-backup
778
2>2 conflicts resolved, 0 remaining
779
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
782
def test_take_other(self):
785
2>2 conflicts resolved, 0 remaining
786
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
790
class TestResolveMissingParent(TestResolveConflicts):
797
$ echo 'trunk content' >dir/file
799
$ brz commit -m 'Create trunk' -q
800
$ echo 'trunk content' >dir/file2
801
$ brz add -q dir/file2
802
$ brz commit -q -m 'Add dir/file2 in branch'
803
$ brz branch -q . -r 1 ../branch
805
$ brz rm -q dir/file --no-backup
807
$ brz commit -q -m 'Remove dir/file'
811
2>Conflict adding files to dir. Created directory.
812
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
813
2>2 conflicts encountered.
816
def test_keep_them_all(self):
819
2>2 conflicts resolved, 0 remaining
820
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
823
def test_adopt_child(self):
825
$ brz mv -q dir/file2 file2
826
$ brz rm -q dir --no-backup
828
2>2 conflicts resolved, 0 remaining
829
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
832
def test_kill_them_all(self):
834
$ brz rm -q dir --no-backup
836
2>2 conflicts resolved, 0 remaining
837
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
840
def test_resolve_taking_this(self):
842
$ brz resolve --take-this dir
844
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
847
def test_resolve_taking_other(self):
849
$ brz resolve --take-other dir
851
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
855
class TestResolveDeletingParent(TestResolveConflicts):
862
$ echo 'trunk content' >dir/file
864
$ brz commit -m 'Create trunk' -q
865
$ brz rm -q dir/file --no-backup
866
$ brz rm -q dir --no-backup
867
$ brz commit -q -m 'Remove dir/file'
868
$ brz branch -q . -r 1 ../branch
870
$ echo 'branch content' >dir/file2
871
$ brz add -q dir/file2
872
$ brz commit -q -m 'Add dir/file2 in branch'
875
2>Conflict: can't delete dir because it is not empty. Not deleting.
876
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
877
2>2 conflicts encountered.
880
def test_keep_them_all(self):
883
2>2 conflicts resolved, 0 remaining
884
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
887
def test_adopt_child(self):
889
$ brz mv -q dir/file2 file2
890
$ brz rm -q dir --no-backup
892
2>2 conflicts resolved, 0 remaining
893
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
896
def test_kill_them_all(self):
898
$ brz rm -q dir --no-backup
900
2>2 conflicts resolved, 0 remaining
901
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
904
def test_resolve_taking_this(self):
906
$ brz resolve --take-this dir
907
2>2 conflicts resolved, 0 remaining
908
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
911
def test_resolve_taking_other(self):
913
$ brz resolve --take-other dir
916
2>2 conflicts resolved, 0 remaining
917
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
921
class TestResolveParentLoop(TestParametrizedResolveConflicts):
923
_conflict_type = conflicts.ParentLoop
928
# Each side dict additionally defines:
929
# - dir_id: the directory being moved
930
# - target_id: The target directory
931
# - xfail: whether the test is expected to fail if the action is
932
# involved as 'other'
933
scenarios = mirror_scenarios(
935
# Dirs moved into each other
936
(dict(_base_actions='create_dir1_dir2'),
938
dict(actions='move_dir1_into_dir2', check='dir1_moved',
939
dir_id=b'dir1-id', target_id=b'dir2-id', xfail=False)),
941
dict(actions='move_dir2_into_dir1', check='dir2_moved',
942
dir_id=b'dir2-id', target_id=b'dir1-id', xfail=False))),
943
# Subdirs moved into each other
944
(dict(_base_actions='create_dir1_4'),
946
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
947
dir_id=b'dir1-id', target_id=b'dir4-id', xfail=True)),
949
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
950
dir_id=b'dir3-id', target_id=b'dir2-id', xfail=True))),
953
def do_create_dir1_dir2(self):
954
return [('add', ('dir1', b'dir1-id', 'directory', '')),
955
('add', ('dir2', b'dir2-id', 'directory', '')), ]
957
def do_move_dir1_into_dir2(self):
958
return [('rename', ('dir1', 'dir2/dir1'))]
960
def check_dir1_moved(self):
961
self.assertPathDoesNotExist('branch/dir1')
962
self.assertPathExists('branch/dir2/dir1')
964
def do_move_dir2_into_dir1(self):
965
return [('rename', ('dir2', 'dir1/dir2'))]
967
def check_dir2_moved(self):
968
self.assertPathDoesNotExist('branch/dir2')
969
self.assertPathExists('branch/dir1/dir2')
971
def do_create_dir1_4(self):
972
return [('add', ('dir1', b'dir1-id', 'directory', '')),
973
('add', ('dir1/dir2', b'dir2-id', 'directory', '')),
974
('add', ('dir3', b'dir3-id', 'directory', '')),
975
('add', ('dir3/dir4', b'dir4-id', 'directory', '')), ]
977
def do_move_dir1_into_dir4(self):
978
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
980
def check_dir1_2_moved(self):
981
self.assertPathDoesNotExist('branch/dir1')
982
self.assertPathExists('branch/dir3/dir4/dir1')
983
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
985
def do_move_dir3_into_dir2(self):
986
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
988
def check_dir3_4_moved(self):
989
self.assertPathDoesNotExist('branch/dir3')
990
self.assertPathExists('branch/dir1/dir2/dir3')
991
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
993
def _get_resolve_path_arg(self, wt, action):
994
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
995
# But since <path> doesn't exist in the working tree, we need to use
996
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
997
return wt.id2path(self._other['dir_id'])
999
def assertParentLoop(self, wt, c):
1000
self.assertEqual(self._other['dir_id'], c.file_id)
1001
self.assertEqual(self._other['target_id'], c.conflict_file_id)
1002
# The conflict paths are irrelevant (they are deterministic but not
1003
# worth checking since they don't provide the needed information
1005
if self._other['xfail']:
1006
# It's a bit hackish to raise from here relying on being called for
1007
# both tests but this avoid overriding test_resolve_taking_other
1009
"ParentLoop doesn't carry enough info to resolve --take-other")
1010
_assert_conflict = assertParentLoop
1013
class TestResolveNonDirectoryParent(TestResolveConflicts):
1021
$ brz commit -m 'Create trunk' -q
1022
$ echo "Boing" >foo/bar
1023
$ brz add -q foo/bar
1024
$ brz commit -q -m 'Add foo/bar'
1025
$ brz branch -q . -r 1 ../branch
1029
$ brz commit -q -m 'foo is now a file'
1030
$ brz merge ../trunk
1031
2>RK foo => foo.new/
1033
# FIXME: The message is misleading, foo.new *is* a directory when the message
1034
# is displayed -- vila 090916
1035
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1036
2>1 conflicts encountered.
1039
def test_take_this(self):
1041
$ brz rm -q foo.new --no-backup
1042
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1043
# aside ? -- vila 090916
1045
$ brz resolve foo.new
1046
2>1 conflict resolved, 0 remaining
1047
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1050
def test_take_other(self):
1052
$ brz rm -q foo --no-backup
1053
$ brz mv -q foo.new foo
1055
2>1 conflict resolved, 0 remaining
1056
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1059
def test_resolve_taking_this(self):
1061
$ brz resolve --take-this foo.new
1063
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1066
def test_resolve_taking_other(self):
1068
$ brz resolve --take-other foo.new
1070
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1074
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1076
def test_bug_430129(self):
1077
# This is nearly like TestResolveNonDirectoryParent but with branch and
1078
# trunk switched. As such it should certainly produce the same
1080
self.assertRaises(errors.MalformedTransform,
1081
self.run_script, """
1087
$ brz commit -m 'Create trunk' -q
1090
$ brz commit -m 'foo is now a file' -q
1091
$ brz branch -q . -r 1 ../branch -q
1093
$ echo "Boing" >foo/bar
1094
$ brz add -q foo/bar -q
1095
$ brz commit -m 'Add foo/bar' -q
1096
$ brz merge ../trunk
1097
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1101
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1103
def test_bug_805809(self):
1106
Created a standalone tree (format: 2a)
1111
$ brz commit -m 'create file on trunk'
1112
2>Committing to: .../trunk/
1114
2>Committed revision 1.
1115
# Create a debian branch based on trunk
1117
$ brz branch trunk -r 1 debian
1118
2>Branched 1 revision.
1125
$ brz commit -m 'rename file to dir/file for debian'
1126
2>Committing to: .../debian/
1128
2>renamed file => dir/file
1129
2>Committed revision 2.
1130
# Create an experimental branch with a new root-id
1132
$ brz init experimental
1133
Created a standalone tree (format: 2a)
1135
# Work around merging into empty branch not being supported
1136
# (http://pad.lv/308562)
1137
$ echo something >not-empty
1140
$ brz commit -m 'Add some content in experimental'
1141
2>Committing to: .../experimental/
1143
2>Committed revision 1.
1144
# merge debian even without a common ancestor
1145
$ brz merge ../debian -r0..2
1148
2>All changes applied successfully.
1149
$ brz commit -m 'merging debian into experimental'
1150
2>Committing to: .../experimental/
1153
2>Committed revision 2.
1154
# Create an ubuntu branch with yet another root-id
1157
Created a standalone tree (format: 2a)
1159
# Work around merging into empty branch not being supported
1160
# (http://pad.lv/308562)
1161
$ echo something >not-empty-ubuntu
1163
adding not-empty-ubuntu
1164
$ brz commit -m 'Add some content in experimental'
1165
2>Committing to: .../ubuntu/
1166
2>added not-empty-ubuntu
1167
2>Committed revision 1.
1169
$ brz merge ../debian -r0..2
1172
2>All changes applied successfully.
1173
$ brz commit -m 'merging debian'
1174
2>Committing to: .../ubuntu/
1177
2>Committed revision 2.
1178
# Now try to merge experimental
1179
$ brz merge ../experimental
1181
2>Path conflict: dir / dir
1182
2>1 conflicts encountered.
1186
class TestResolveActionOption(tests.TestCase):
1189
super(TestResolveActionOption, self).setUp()
1190
self.options = [conflicts.ResolveActionOption()]
1191
self.parser = option.get_optparser(self.options)
1193
def parse(self, args):
1194
return self.parser.parse_args(args)
1196
def test_unknown_action(self):
1197
self.assertRaises(option.BadOptionValue,
1198
self.parse, ['--action', 'take-me-to-the-moon'])
1200
def test_done(self):
1201
opts, args = self.parse(['--action', 'done'])
1202
self.assertEqual({'action': 'done'}, opts)
1204
def test_take_this(self):
1205
opts, args = self.parse(['--action', 'take-this'])
1206
self.assertEqual({'action': 'take_this'}, opts)
1207
opts, args = self.parse(['--take-this'])
1208
self.assertEqual({'action': 'take_this'}, opts)
1210
def test_take_other(self):
1211
opts, args = self.parse(['--action', 'take-other'])
1212
self.assertEqual({'action': 'take_other'}, opts)
1213
opts, args = self.parse(['--take-other'])
1214
self.assertEqual({'action': 'take_other'}, opts)
92
for stanza in example_conflicts.to_stanzas():
94
self.assertStartsWith(stanza['file_id'], 'id')
97
self.assertStartsWith(stanza['path'], 'path')
99
self.assertStartsWith(stanza['conflict_file_id'], 'id')
100
self.assertStartsWith(stanza['conflict_file_path'], 'path')