1
# Copyright (C) 2005-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
33
load_tests = scenarios.load_tests_apply_scenarios
36
# TODO: Test commit with some added, and added-but-missing files
37
# RBC 20060124 is that not tested in test_commit.py ?
39
# The order of 'path' here is important - do not let it
41
# u'\xe5' == a with circle
42
# '\xc3\xae' == u'\xee' == i with hat
43
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
44
example_conflicts = conflicts.ConflictList(
45
[conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
46
conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
47
conflicts.TextConflict(u'p\xe5tha'),
48
conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
49
conflicts.DuplicateID('Unversioned existing file',
50
u'p\xe5thc', u'p\xe5thc2',
51
'\xc3\xaedc', '\xc3\xaedc'),
52
conflicts.DuplicateEntry('Moved existing file to',
53
u'p\xe5thdd.moved', u'p\xe5thd',
55
conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
57
conflicts.UnversionedParent('Versioned directory',
58
u'p\xe5thf', '\xc3\xaedf'),
59
conflicts.NonDirectoryParent('Created directory',
60
u'p\xe5thg', '\xc3\xaedg'),
64
def vary_by_conflicts():
65
for conflict in example_conflicts:
66
yield (conflict.__class__.__name__, {"conflict": conflict})
69
class TestConflicts(tests.TestCaseWithTransport):
71
def test_resolve_conflict_dir(self):
72
tree = self.make_branch_and_tree('.')
73
self.build_tree_contents([('hello', 'hello world4'),
74
('hello.THIS', 'hello world2'),
75
('hello.BASE', 'hello world1'),
77
os.mkdir('hello.OTHER')
78
tree.add('hello', 'q')
79
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
82
def test_select_conflicts(self):
83
tree = self.make_branch_and_tree('.')
84
clist = conflicts.ConflictList
86
def check_select(not_selected, selected, paths, **kwargs):
88
(not_selected, selected),
89
tree_conflicts.select_conflicts(tree, paths, **kwargs))
91
foo = conflicts.ContentsConflict('foo')
92
bar = conflicts.ContentsConflict('bar')
93
tree_conflicts = clist([foo, bar])
95
check_select(clist([bar]), clist([foo]), ['foo'])
96
check_select(clist(), tree_conflicts,
97
[''], ignore_misses=True, recurse=True)
99
foobaz = conflicts.ContentsConflict('foo/baz')
100
tree_conflicts = clist([foobaz, bar])
102
check_select(clist([bar]), clist([foobaz]),
103
['foo'], ignore_misses=True, recurse=True)
105
qux = conflicts.PathConflict('qux', 'foo/baz')
106
tree_conflicts = clist([qux])
108
check_select(clist(), tree_conflicts,
109
['foo'], ignore_misses=True, recurse=True)
110
check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
112
def test_resolve_conflicts_recursive(self):
113
tree = self.make_branch_and_tree('.')
114
self.build_tree(['dir/', 'dir/hello'])
115
tree.add(['dir', 'dir/hello'])
117
dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
118
tree.set_conflicts(dirhello)
120
conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
121
self.assertEqual(dirhello, tree.conflicts())
123
conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
124
self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
127
class TestPerConflict(tests.TestCase):
129
scenarios = scenarios.multiply_scenarios(vary_by_conflicts())
131
def test_stringification(self):
132
text = unicode(self.conflict)
133
self.assertContainsString(text, self.conflict.path)
134
self.assertContainsString(text.lower(), "conflict")
135
self.assertContainsString(repr(self.conflict),
136
self.conflict.__class__.__name__)
138
def test_stanza_roundtrip(self):
140
o = conflicts.Conflict.factory(**p.as_stanza().as_dict())
141
self.assertEqual(o, p)
143
self.assertIsInstance(o.path, unicode)
145
if o.file_id is not None:
146
self.assertIsInstance(o.file_id, str)
148
conflict_path = getattr(o, 'conflict_path', None)
149
if conflict_path is not None:
150
self.assertIsInstance(conflict_path, unicode)
152
conflict_file_id = getattr(o, 'conflict_file_id', None)
153
if conflict_file_id is not None:
154
self.assertIsInstance(conflict_file_id, str)
156
def test_stanzification(self):
157
stanza = self.conflict.as_stanza()
158
if 'file_id' in stanza:
159
# In Stanza form, the file_id has to be unicode.
160
self.assertStartsWith(stanza['file_id'], u'\xeed')
161
self.assertStartsWith(stanza['path'], u'p\xe5th')
162
if 'conflict_path' in stanza:
163
self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
164
if 'conflict_file_id' in stanza:
165
self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
168
class TestConflictList(tests.TestCase):
170
def test_stanzas_roundtrip(self):
171
stanzas_iter = example_conflicts.to_stanzas()
172
processed = conflicts.ConflictList.from_stanzas(stanzas_iter)
173
self.assertEqual(example_conflicts, processed)
175
def test_stringification(self):
176
for text, o in zip(example_conflicts.to_strings(), example_conflicts):
177
self.assertEqual(text, unicode(o))
180
# FIXME: The shell-like tests should be converted to real whitebox tests... or
181
# moved to a blackbox module -- vila 20100205
183
# FIXME: test missing for multiple conflicts
185
# FIXME: Tests missing for DuplicateID conflict type
186
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
188
preamble = None # The setup script set by daughter classes
191
super(TestResolveConflicts, self).setUp()
192
self.run_script(self.preamble)
195
def mirror_scenarios(base_scenarios):
196
"""Return a list of mirrored scenarios.
198
Each scenario in base_scenarios is duplicated switching the roles of 'this'
202
for common, (lname, ldict), (rname, rdict) in base_scenarios:
203
a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
204
[(rname, dict(_other=rdict))])
205
b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
206
[(lname, dict(_other=ldict))])
207
# Inject the common parameters in all scenarios
208
for name, d in a + b:
210
scenarios.extend(a + b)
214
# FIXME: Get rid of parametrized (in the class name) once we delete
215
# TestResolveConflicts -- vila 20100308
216
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
217
"""This class provides a base to test single conflict resolution.
219
Since all conflict objects are created with specific semantics for their
220
attributes, each class should implement the necessary functions and
221
attributes described below.
223
Each class should define the scenarios that create the expected (single)
226
Each scenario describes:
227
* how to create 'base' tree (and revision)
228
* how to create 'left' tree (and revision, parent rev 'base')
229
* how to create 'right' tree (and revision, parent rev 'base')
230
* how to check that changes in 'base'->'left' have been taken
231
* how to check that changes in 'base'->'right' have been taken
233
From each base scenario, we generate two concrete scenarios where:
234
* this=left, other=right
235
* this=right, other=left
237
Then the test case verifies each concrete scenario by:
238
* creating a branch containing the 'base', 'this' and 'other' revisions
239
* creating a working tree for the 'this' revision
240
* performing the merge of 'other' into 'this'
241
* verifying the expected conflict was generated
242
* resolving with --take-this or --take-other, and running the corresponding
243
checks (for either 'base'->'this', or 'base'->'other')
245
:cvar _conflict_type: The expected class of the generated conflict.
247
:cvar _assert_conflict: A method receiving the working tree and the
248
conflict object and checking its attributes.
250
:cvar _base_actions: The branchbuilder actions to create the 'base'
253
:cvar _this: The dict related to 'base' -> 'this'. It contains at least:
254
* 'actions': The branchbuilder actions to create the 'this'
256
* 'check': how to check the changes after resolution with --take-this.
258
:cvar _other: The dict related to 'base' -> 'other'. It contains at least:
259
* 'actions': The branchbuilder actions to create the 'other'
261
* 'check': how to check the changes after resolution with --take-other.
264
# Set by daughter classes
265
_conflict_type = None
266
_assert_conflict = None
274
"""The scenario list for the conflict type defined by the class.
276
Each scenario is of the form:
277
(common, (left_name, left_dict), (right_name, right_dict))
281
* left_name and right_name are the scenario names that will be combined
283
* left_dict and right_dict are the attributes specific to each half of
284
the scenario. They should include at least 'actions' and 'check' and
285
will be available as '_this' and '_other' test instance attributes.
287
Daughters classes are free to add their specific attributes as they see
288
fit in any of the three dicts.
290
This is a class method so that load_tests can find it.
292
'_base_actions' in the common dict, 'actions' and 'check' in the left
293
and right dicts use names that map to methods in the test classes. Some
294
prefixes are added to these names to get the correspong methods (see
295
_get_actions() and _get_check()). The motivation here is to avoid
296
collisions in the class namespace.
300
super(TestParametrizedResolveConflicts, self).setUp()
301
builder = self.make_branch_builder('trunk')
302
builder.start_series()
304
# Create an empty trunk
305
builder.build_snapshot(None, [
306
('add', ('', 'root-id', 'directory', ''))],
308
# Add a minimal base content
309
base_actions = self._get_actions(self._base_actions)()
310
builder.build_snapshot(['start'], base_actions, revision_id='base')
311
# Modify the base content in branch
312
actions_other = self._get_actions(self._other['actions'])()
313
builder.build_snapshot(['base'], actions_other, revision_id='other')
314
# Modify the base content in trunk
315
actions_this = self._get_actions(self._this['actions'])()
316
builder.build_snapshot(['base'], actions_this, revision_id='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.controldir.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')),),
680
# File created with different file-ids but deleted on one side
681
(dict(_base_actions='create_file_a'),
683
dict(actions='replace_file_a_by_b', check='file_content_b',
684
path='file', file_id='file-b-id')),
686
dict(actions='modify_file_a', check='file_new_content',
687
path='file', file_id='file-a-id')),),
690
def do_nothing(self):
693
def do_create_file_a(self):
694
return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
696
def check_file_content_a(self):
697
self.assertFileEqual('file a content\n', 'branch/file')
699
def do_create_file_b(self):
700
return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
702
def check_file_content_b(self):
703
self.assertFileEqual('file b content\n', 'branch/file')
705
def do_replace_file_a_by_b(self):
706
return [('unversion', 'file-a-id'),
707
('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
709
def do_modify_file_a(self):
710
return [('modify', ('file-a-id', 'new content\n'))]
712
def check_file_new_content(self):
713
self.assertFileEqual('new content\n', 'branch/file')
715
def _get_resolve_path_arg(self, wt, action):
716
return self._this['path']
718
def assertDuplicateEntry(self, wt, c):
719
tpath = self._this['path']
720
tfile_id = self._this['file_id']
721
opath = self._other['path']
722
ofile_id = self._other['file_id']
723
self.assertEqual(tpath, opath) # Sanity check
724
self.assertEqual(tfile_id, c.file_id)
725
self.assertEqual(tpath + '.moved', c.path)
726
self.assertEqual(tpath, c.conflict_path)
727
_assert_conflict = assertDuplicateEntry
730
class TestResolveUnversionedParent(TestResolveConflicts):
732
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
734
# FIXME: While this *creates* UnversionedParent conflicts, this really only
735
# tests MissingParent resolution :-/
742
$ brz commit -m 'Create trunk' -q
743
$ echo 'trunk content' >dir/file
744
$ brz add -q dir/file
745
$ brz commit -q -m 'Add dir/file in trunk'
746
$ brz branch -q . -r 1 ../branch
749
$ brz commit -q -m 'Remove dir in branch'
753
2>Conflict adding files to dir. Created directory.
754
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
755
2>2 conflicts encountered.
758
def test_take_this(self):
760
$ brz rm -q dir --no-backup
762
2>2 conflicts resolved, 0 remaining
763
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
766
def test_take_other(self):
769
2>2 conflicts resolved, 0 remaining
770
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
774
class TestResolveMissingParent(TestResolveConflicts):
781
$ echo 'trunk content' >dir/file
783
$ brz commit -m 'Create trunk' -q
784
$ echo 'trunk content' >dir/file2
785
$ brz add -q dir/file2
786
$ brz commit -q -m 'Add dir/file2 in branch'
787
$ brz branch -q . -r 1 ../branch
789
$ brz rm -q dir/file --no-backup
791
$ brz commit -q -m 'Remove dir/file'
795
2>Conflict adding files to dir. Created directory.
796
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
797
2>2 conflicts encountered.
800
def test_keep_them_all(self):
803
2>2 conflicts resolved, 0 remaining
804
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
807
def test_adopt_child(self):
809
$ brz mv -q dir/file2 file2
810
$ brz rm -q dir --no-backup
812
2>2 conflicts resolved, 0 remaining
813
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
816
def test_kill_them_all(self):
818
$ brz rm -q dir --no-backup
820
2>2 conflicts resolved, 0 remaining
821
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
824
def test_resolve_taking_this(self):
826
$ brz resolve --take-this dir
828
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
831
def test_resolve_taking_other(self):
833
$ brz resolve --take-other dir
835
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
839
class TestResolveDeletingParent(TestResolveConflicts):
846
$ echo 'trunk content' >dir/file
848
$ brz commit -m 'Create trunk' -q
849
$ brz rm -q dir/file --no-backup
850
$ brz rm -q dir --no-backup
851
$ brz commit -q -m 'Remove dir/file'
852
$ brz branch -q . -r 1 ../branch
854
$ echo 'branch content' >dir/file2
855
$ brz add -q dir/file2
856
$ brz commit -q -m 'Add dir/file2 in branch'
859
2>Conflict: can't delete dir because it is not empty. Not deleting.
860
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
861
2>2 conflicts encountered.
864
def test_keep_them_all(self):
867
2>2 conflicts resolved, 0 remaining
868
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
871
def test_adopt_child(self):
873
$ brz mv -q dir/file2 file2
874
$ brz rm -q dir --no-backup
876
2>2 conflicts resolved, 0 remaining
877
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
880
def test_kill_them_all(self):
882
$ brz rm -q dir --no-backup
884
2>2 conflicts resolved, 0 remaining
885
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
888
def test_resolve_taking_this(self):
890
$ brz resolve --take-this dir
891
2>2 conflicts resolved, 0 remaining
892
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
895
def test_resolve_taking_other(self):
897
$ brz resolve --take-other dir
900
2>2 conflicts resolved, 0 remaining
901
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
905
class TestResolveParentLoop(TestParametrizedResolveConflicts):
907
_conflict_type = conflicts.ParentLoop
912
# Each side dict additionally defines:
913
# - dir_id: the directory being moved
914
# - target_id: The target directory
915
# - xfail: whether the test is expected to fail if the action is
916
# involved as 'other'
917
scenarios = mirror_scenarios(
919
# Dirs moved into each other
920
(dict(_base_actions='create_dir1_dir2'),
922
dict(actions='move_dir1_into_dir2', check='dir1_moved',
923
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
925
dict(actions='move_dir2_into_dir1', check='dir2_moved',
926
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
927
# Subdirs moved into each other
928
(dict(_base_actions='create_dir1_4'),
930
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
931
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
933
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
934
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
937
def do_create_dir1_dir2(self):
938
return [('add', ('dir1', 'dir1-id', 'directory', '')),
939
('add', ('dir2', 'dir2-id', 'directory', '')),]
941
def do_move_dir1_into_dir2(self):
942
return [('rename', ('dir1', 'dir2/dir1'))]
944
def check_dir1_moved(self):
945
self.assertPathDoesNotExist('branch/dir1')
946
self.assertPathExists('branch/dir2/dir1')
948
def do_move_dir2_into_dir1(self):
949
return [('rename', ('dir2', 'dir1/dir2'))]
951
def check_dir2_moved(self):
952
self.assertPathDoesNotExist('branch/dir2')
953
self.assertPathExists('branch/dir1/dir2')
955
def do_create_dir1_4(self):
956
return [('add', ('dir1', 'dir1-id', 'directory', '')),
957
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
958
('add', ('dir3', 'dir3-id', 'directory', '')),
959
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
961
def do_move_dir1_into_dir4(self):
962
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
964
def check_dir1_2_moved(self):
965
self.assertPathDoesNotExist('branch/dir1')
966
self.assertPathExists('branch/dir3/dir4/dir1')
967
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
969
def do_move_dir3_into_dir2(self):
970
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
972
def check_dir3_4_moved(self):
973
self.assertPathDoesNotExist('branch/dir3')
974
self.assertPathExists('branch/dir1/dir2/dir3')
975
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
977
def _get_resolve_path_arg(self, wt, action):
978
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
979
# But since <path> doesn't exist in the working tree, we need to use
980
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
981
return wt.id2path(self._other['dir_id'])
983
def assertParentLoop(self, wt, c):
984
self.assertEqual(self._other['dir_id'], c.file_id)
985
self.assertEqual(self._other['target_id'], c.conflict_file_id)
986
# The conflict paths are irrelevant (they are deterministic but not
987
# worth checking since they don't provide the needed information
989
if self._other['xfail']:
990
# It's a bit hackish to raise from here relying on being called for
991
# both tests but this avoid overriding test_resolve_taking_other
993
"ParentLoop doesn't carry enough info to resolve --take-other")
994
_assert_conflict = assertParentLoop
997
class TestResolveNonDirectoryParent(TestResolveConflicts):
1005
$ brz commit -m 'Create trunk' -q
1006
$ echo "Boing" >foo/bar
1007
$ brz add -q foo/bar
1008
$ brz commit -q -m 'Add foo/bar'
1009
$ brz branch -q . -r 1 ../branch
1013
$ brz commit -q -m 'foo is now a file'
1014
$ brz merge ../trunk
1016
2>RK foo => foo.new/
1017
# FIXME: The message is misleading, foo.new *is* a directory when the message
1018
# is displayed -- vila 090916
1019
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1020
2>1 conflicts encountered.
1023
def test_take_this(self):
1025
$ brz rm -q foo.new --no-backup
1026
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1027
# aside ? -- vila 090916
1029
$ brz resolve foo.new
1030
2>1 conflict resolved, 0 remaining
1031
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1034
def test_take_other(self):
1036
$ brz rm -q foo --no-backup
1037
$ brz mv -q foo.new foo
1039
2>1 conflict resolved, 0 remaining
1040
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1043
def test_resolve_taking_this(self):
1045
$ brz resolve --take-this foo.new
1047
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1050
def test_resolve_taking_other(self):
1052
$ brz resolve --take-other foo.new
1054
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1058
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1060
def test_bug_430129(self):
1061
# This is nearly like TestResolveNonDirectoryParent but with branch and
1062
# trunk switched. As such it should certainly produce the same
1064
self.assertRaises(errors.MalformedTransform,
1065
self.run_script, """
1071
$ brz commit -m 'Create trunk' -q
1074
$ brz commit -m 'foo is now a file' -q
1075
$ brz branch -q . -r 1 ../branch -q
1077
$ echo "Boing" >foo/bar
1078
$ brz add -q foo/bar -q
1079
$ brz commit -m 'Add foo/bar' -q
1080
$ brz merge ../trunk
1081
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1085
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1087
def test_bug_805809(self):
1090
Created a standalone tree (format: 2a)
1095
$ brz commit -m 'create file on trunk'
1096
2>Committing to: .../trunk/
1098
2>Committed revision 1.
1099
# Create a debian branch based on trunk
1101
$ brz branch trunk -r 1 debian
1102
2>Branched 1 revision.
1109
$ brz commit -m 'rename file to dir/file for debian'
1110
2>Committing to: .../debian/
1112
2>renamed file => dir/file
1113
2>Committed revision 2.
1114
# Create an experimental branch with a new root-id
1116
$ brz init experimental
1117
Created a standalone tree (format: 2a)
1119
# Work around merging into empty branch not being supported
1120
# (http://pad.lv/308562)
1121
$ echo something >not-empty
1124
$ brz commit -m 'Add some content in experimental'
1125
2>Committing to: .../experimental/
1127
2>Committed revision 1.
1128
# merge debian even without a common ancestor
1129
$ brz merge ../debian -r0..2
1132
2>All changes applied successfully.
1133
$ brz commit -m 'merging debian into experimental'
1134
2>Committing to: .../experimental/
1137
2>Committed revision 2.
1138
# Create an ubuntu branch with yet another root-id
1141
Created a standalone tree (format: 2a)
1143
# Work around merging into empty branch not being supported
1144
# (http://pad.lv/308562)
1145
$ echo something >not-empty-ubuntu
1147
adding not-empty-ubuntu
1148
$ brz commit -m 'Add some content in experimental'
1149
2>Committing to: .../ubuntu/
1150
2>added not-empty-ubuntu
1151
2>Committed revision 1.
1153
$ brz merge ../debian -r0..2
1156
2>All changes applied successfully.
1157
$ brz commit -m 'merging debian'
1158
2>Committing to: .../ubuntu/
1161
2>Committed revision 2.
1162
# Now try to merge experimental
1163
$ brz merge ../experimental
1165
2>Path conflict: dir / dir
1166
2>1 conflicts encountered.
1170
class TestResolveActionOption(tests.TestCase):
1173
super(TestResolveActionOption, self).setUp()
1174
self.options = [conflicts.ResolveActionOption()]
1175
self.parser = option.get_optparser(dict((o.name, o)
1176
for o in self.options))
1178
def parse(self, args):
1179
return self.parser.parse_args(args)
1181
def test_unknown_action(self):
1182
self.assertRaises(option.BadOptionValue,
1183
self.parse, ['--action', 'take-me-to-the-moon'])
1185
def test_done(self):
1186
opts, args = self.parse(['--action', 'done'])
1187
self.assertEqual({'action':'done'}, opts)
1189
def test_take_this(self):
1190
opts, args = self.parse(['--action', 'take-this'])
1191
self.assertEqual({'action': 'take_this'}, opts)
1192
opts, args = self.parse(['--take-this'])
1193
self.assertEqual({'action': 'take_this'}, opts)
1195
def test_take_other(self):
1196
opts, args = self.parse(['--action', 'take-other'])
1197
self.assertEqual({'action': 'take_other'}, opts)
1198
opts, args = self.parse(['--take-other'])
1199
self.assertEqual({'action': 'take_other'}, opts)