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
27
from ..sixish import text_type
34
load_tests = scenarios.load_tests_apply_scenarios
37
# TODO: Test commit with some added, and added-but-missing files
38
# RBC 20060124 is that not tested in test_commit.py ?
40
# The order of 'path' here is important - do not let it
42
# u'\xe5' == a with circle
43
# '\xc3\xae' == u'\xee' == i with hat
44
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
45
example_conflicts = conflicts.ConflictList(
46
[conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
47
conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
48
conflicts.TextConflict(u'p\xe5tha'),
49
conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
50
conflicts.DuplicateID('Unversioned existing file',
51
u'p\xe5thc', u'p\xe5thc2',
52
'\xc3\xaedc', '\xc3\xaedc'),
53
conflicts.DuplicateEntry('Moved existing file to',
54
u'p\xe5thdd.moved', u'p\xe5thd',
56
conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
58
conflicts.UnversionedParent('Versioned directory',
59
u'p\xe5thf', '\xc3\xaedf'),
60
conflicts.NonDirectoryParent('Created directory',
61
u'p\xe5thg', '\xc3\xaedg'),
65
def vary_by_conflicts():
66
for conflict in example_conflicts:
67
yield (conflict.__class__.__name__, {"conflict": conflict})
70
class TestConflicts(tests.TestCaseWithTransport):
72
def test_resolve_conflict_dir(self):
73
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'),
78
os.mkdir('hello.OTHER')
79
tree.add('hello', b'q')
80
l = conflicts.ConflictList([conflicts.TextConflict('hello')])
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 = text_type(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__)
139
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, text_type)
146
if o.file_id is not None:
147
self.assertIsInstance(o.file_id, bytes)
149
conflict_path = getattr(o, 'conflict_path', None)
150
if conflict_path is not None:
151
self.assertIsInstance(conflict_path, text_type)
153
conflict_file_id = getattr(o, 'conflict_file_id', None)
154
if conflict_file_id is not None:
155
self.assertIsInstance(conflict_file_id, bytes)
157
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, text_type(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(None, [
307
('add', (u'', b'root-id', 'directory', ''))],
308
revision_id=b'start')
309
# Add a minimal base content
310
base_actions = self._get_actions(self._base_actions)()
311
builder.build_snapshot([b'start'], base_actions, revision_id=b'base')
312
# Modify the base content in branch
313
actions_other = self._get_actions(self._other['actions'])()
314
builder.build_snapshot([b'base'], actions_other, revision_id=b'other')
315
# Modify the base content in trunk
316
actions_this = self._get_actions(self._this['actions'])()
317
builder.build_snapshot([b'base'], actions_this, revision_id=b'this')
318
# builder.get_branch() tip is now 'this'
320
builder.finish_series()
321
self.builder = builder
323
def _get_actions(self, name):
324
return getattr(self, 'do_%s' % name)
326
def _get_check(self, name):
327
return getattr(self, 'check_%s' % name)
329
def _merge_other_into_this(self):
330
b = self.builder.get_branch()
331
wt = b.controldir.sprout('branch').open_workingtree()
332
wt.merge_from_branch(b, b'other')
335
def assertConflict(self, wt):
336
confs = wt.conflicts()
337
self.assertLength(1, confs)
339
self.assertIsInstance(c, self._conflict_type)
340
self._assert_conflict(wt, c)
342
def _get_resolve_path_arg(self, wt, action):
343
raise NotImplementedError(self._get_resolve_path_arg)
345
def check_resolved(self, wt, action):
346
path = self._get_resolve_path_arg(wt, action)
347
conflicts.resolve(wt, [path], action=action)
348
# Check that we don't have any conflicts nor unknown left
349
self.assertLength(0, wt.conflicts())
350
self.assertLength(0, list(wt.unknowns()))
352
def test_resolve_taking_this(self):
353
wt = self._merge_other_into_this()
354
self.assertConflict(wt)
355
self.check_resolved(wt, 'take_this')
356
check_this = self._get_check(self._this['check'])
359
def test_resolve_taking_other(self):
360
wt = self._merge_other_into_this()
361
self.assertConflict(wt)
362
self.check_resolved(wt, 'take_other')
363
check_other = self._get_check(self._other['check'])
367
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
369
_conflict_type = conflicts.TextConflict
371
# Set by the scenarios
372
# path and file-id for the file involved in the conflict
376
scenarios = mirror_scenarios(
378
# File modified on both sides
379
(dict(_base_actions='create_file',
380
_path='file', _file_id=b'file-id'),
382
dict(actions='modify_file_A', check='file_has_content_A')),
384
dict(actions='modify_file_B', check='file_has_content_B')),),
385
# File modified on both sides in dir
386
(dict(_base_actions='create_file_in_dir',
387
_path='dir/file', _file_id=b'file-id'),
388
('filed_modified_A_in_dir',
389
dict(actions='modify_file_A_in_dir',
390
check='file_in_dir_has_content_A')),
392
dict(actions='modify_file_B_in_dir',
393
check='file_in_dir_has_content_B')),),
396
def do_create_file(self, path='file'):
397
return [('add', (path, b'file-id', 'file', b'trunk content\n'))]
399
def do_modify_file_A(self):
400
return [('modify', ('file', b'trunk content\nfeature A\n'))]
402
def do_modify_file_B(self):
403
return [('modify', ('file', b'trunk content\nfeature B\n'))]
405
def do_modify_file_A_in_dir(self):
406
return [('modify', ('dir/file', b'trunk content\nfeature A\n'))]
408
def do_modify_file_B_in_dir(self):
409
return [('modify', ('dir/file', b'trunk content\nfeature B\n'))]
411
def check_file_has_content_A(self, path='file'):
412
self.assertFileEqual(b'trunk content\nfeature A\n',
413
osutils.pathjoin('branch', path))
415
def check_file_has_content_B(self, path='file'):
416
self.assertFileEqual(b'trunk content\nfeature B\n',
417
osutils.pathjoin('branch', path))
419
def do_create_file_in_dir(self):
420
return [('add', ('dir', b'dir-id', 'directory', '')),
421
] + self.do_create_file('dir/file')
423
def check_file_in_dir_has_content_A(self):
424
self.check_file_has_content_A('dir/file')
426
def check_file_in_dir_has_content_B(self):
427
self.check_file_has_content_B('dir/file')
429
def _get_resolve_path_arg(self, wt, action):
432
def assertTextConflict(self, wt, c):
433
self.assertEqual(self._file_id, c.file_id)
434
self.assertEqual(self._path, c.path)
435
_assert_conflict = assertTextConflict
438
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
440
_conflict_type = conflicts.ContentsConflict
442
# Set by the scenarios
443
# path and file-id for the file involved in the conflict
447
scenarios = mirror_scenarios(
449
# File modified/deleted
450
(dict(_base_actions='create_file',
451
_path='file', _file_id=b'file-id'),
453
dict(actions='modify_file', check='file_has_more_content')),
455
dict(actions='delete_file', check='file_doesnt_exist')),),
456
# File renamed-modified/deleted
457
(dict(_base_actions='create_file',
458
_path='new-file', _file_id=b'file-id'),
459
('file_renamed_and_modified',
460
dict(actions='modify_and_rename_file',
461
check='file_renamed_and_more_content')),
463
dict(actions='delete_file', check='file_doesnt_exist')),),
464
# File modified/deleted in dir
465
(dict(_base_actions='create_file_in_dir',
466
_path='dir/file', _file_id=b'file-id'),
467
('file_modified_in_dir',
468
dict(actions='modify_file_in_dir',
469
check='file_in_dir_has_more_content')),
470
('file_deleted_in_dir',
471
dict(actions='delete_file_in_dir',
472
check='file_in_dir_doesnt_exist')),),
475
def do_create_file(self):
476
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
478
def do_modify_file(self):
479
return [('modify', ('file', b'trunk content\nmore content\n'))]
481
def do_modify_and_rename_file(self):
482
return [('modify', ('new-file', b'trunk content\nmore content\n')),
483
('rename', ('file', 'new-file'))]
485
def check_file_has_more_content(self):
486
self.assertFileEqual(b'trunk content\nmore content\n', 'branch/file')
488
def check_file_renamed_and_more_content(self):
489
self.assertFileEqual(b'trunk content\nmore content\n', 'branch/new-file')
491
def do_delete_file(self):
492
return [('unversion', 'file')]
494
def do_delete_file_in_dir(self):
495
return [('unversion', 'dir/file')]
497
def check_file_doesnt_exist(self):
498
self.assertPathDoesNotExist('branch/file')
500
def do_create_file_in_dir(self):
501
return [('add', ('dir', b'dir-id', 'directory', '')),
502
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
504
def do_modify_file_in_dir(self):
505
return [('modify', ('dir/file', b'trunk content\nmore content\n'))]
507
def check_file_in_dir_has_more_content(self):
508
self.assertFileEqual(b'trunk content\nmore content\n', 'branch/dir/file')
510
def check_file_in_dir_doesnt_exist(self):
511
self.assertPathDoesNotExist('branch/dir/file')
513
def _get_resolve_path_arg(self, wt, action):
516
def assertContentsConflict(self, wt, c):
517
self.assertEqual(self._file_id, c.file_id)
518
self.assertEqual(self._path, c.path)
519
_assert_conflict = assertContentsConflict
522
class TestResolvePathConflict(TestParametrizedResolveConflicts):
524
_conflict_type = conflicts.PathConflict
526
def do_nothing(self):
529
# Each side dict additionally defines:
530
# - path path involved (can be '<deleted>')
532
scenarios = mirror_scenarios(
534
# File renamed/deleted
535
(dict(_base_actions='create_file'),
537
dict(actions='rename_file', check='file_renamed',
538
path='new-file', file_id=b'file-id')),
540
dict(actions='delete_file', check='file_doesnt_exist',
541
# PathConflicts deletion handling requires a special
543
path='<deleted>', file_id=b'file-id')),),
544
# File renamed/deleted in dir
545
(dict(_base_actions='create_file_in_dir'),
546
('file_renamed_in_dir',
547
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
548
path='dir/new-file', file_id=b'file-id')),
550
dict(actions='delete_file_in_dir', check='file_in_dir_doesnt_exist',
551
# PathConflicts deletion handling requires a special
553
path='<deleted>', file_id=b'file-id')),),
554
# File renamed/renamed differently
555
(dict(_base_actions='create_file'),
557
dict(actions='rename_file', check='file_renamed',
558
path='new-file', file_id=b'file-id')),
560
dict(actions='rename_file2', check='file_renamed2',
561
path='new-file2', file_id=b'file-id')),),
562
# Dir renamed/deleted
563
(dict(_base_actions='create_dir'),
565
dict(actions='rename_dir', check='dir_renamed',
566
path='new-dir', file_id=b'dir-id')),
568
dict(actions='delete_dir', check='dir_doesnt_exist',
569
# PathConflicts deletion handling requires a special
571
path='<deleted>', file_id=b'dir-id')),),
572
# Dir renamed/renamed differently
573
(dict(_base_actions='create_dir'),
575
dict(actions='rename_dir', check='dir_renamed',
576
path='new-dir', file_id=b'dir-id')),
578
dict(actions='rename_dir2', check='dir_renamed2',
579
path='new-dir2', file_id=b'dir-id')),),
582
def do_create_file(self):
583
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
585
def do_create_dir(self):
586
return [('add', ('dir', b'dir-id', 'directory', ''))]
588
def do_rename_file(self):
589
return [('rename', ('file', 'new-file'))]
591
def check_file_renamed(self):
592
self.assertPathDoesNotExist('branch/file')
593
self.assertPathExists('branch/new-file')
595
def do_rename_file2(self):
596
return [('rename', ('file', 'new-file2'))]
598
def check_file_renamed2(self):
599
self.assertPathDoesNotExist('branch/file')
600
self.assertPathExists('branch/new-file2')
602
def do_rename_dir(self):
603
return [('rename', ('dir', 'new-dir'))]
605
def check_dir_renamed(self):
606
self.assertPathDoesNotExist('branch/dir')
607
self.assertPathExists('branch/new-dir')
609
def do_rename_dir2(self):
610
return [('rename', ('dir', 'new-dir2'))]
612
def check_dir_renamed2(self):
613
self.assertPathDoesNotExist('branch/dir')
614
self.assertPathExists('branch/new-dir2')
616
def do_delete_file(self):
617
return [('unversion', 'file')]
619
def do_delete_file_in_dir(self):
620
return [('unversion', 'dir/file')]
622
def check_file_doesnt_exist(self):
623
self.assertPathDoesNotExist('branch/file')
625
def do_delete_dir(self):
626
return [('unversion', 'dir')]
628
def check_dir_doesnt_exist(self):
629
self.assertPathDoesNotExist('branch/dir')
631
def do_create_file_in_dir(self):
632
return [('add', ('dir', b'dir-id', 'directory', '')),
633
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
635
def do_rename_file_in_dir(self):
636
return [('rename', ('dir/file', 'dir/new-file'))]
638
def check_file_in_dir_renamed(self):
639
self.assertPathDoesNotExist('branch/dir/file')
640
self.assertPathExists('branch/dir/new-file')
642
def check_file_in_dir_doesnt_exist(self):
643
self.assertPathDoesNotExist('branch/dir/file')
645
def _get_resolve_path_arg(self, wt, action):
646
tpath = self._this['path']
647
opath = self._other['path']
648
if tpath == '<deleted>':
654
def assertPathConflict(self, wt, c):
655
tpath = self._this['path']
656
tfile_id = self._this['file_id']
657
opath = self._other['path']
658
ofile_id = self._other['file_id']
659
self.assertEqual(tfile_id, ofile_id) # Sanity check
660
self.assertEqual(tfile_id, c.file_id)
661
self.assertEqual(tpath, c.path)
662
self.assertEqual(opath, c.conflict_path)
663
_assert_conflict = assertPathConflict
666
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
667
"""Same as TestResolvePathConflict but a specific conflict object.
670
def assertPathConflict(self, c):
671
# We create a conflict object as it was created before the fix and
672
# inject it into the working tree, the test will exercise the
673
# compatibility code.
674
old_c = conflicts.PathConflict('<deleted>', self._item_path,
676
wt.set_conflicts(conflicts.ConflictList([old_c]))
679
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
681
_conflict_type = conflicts.DuplicateEntry
683
scenarios = mirror_scenarios(
685
# File created with different file-ids
686
(dict(_base_actions='nothing'),
688
dict(actions='create_file_a', check='file_content_a',
689
path='file', file_id=b'file-a-id')),
691
dict(actions='create_file_b', check='file_content_b',
692
path='file', file_id=b'file-b-id')),),
693
# File created with different file-ids but deleted on one side
694
(dict(_base_actions='create_file_a'),
696
dict(actions='replace_file_a_by_b', check='file_content_b',
697
path='file', file_id=b'file-b-id')),
699
dict(actions='modify_file_a', check='file_new_content',
700
path='file', file_id=b'file-a-id')),),
703
def do_nothing(self):
706
def do_create_file_a(self):
707
return [('add', ('file', b'file-a-id', 'file', b'file a content\n'))]
709
def check_file_content_a(self):
710
self.assertFileEqual(b'file a content\n', 'branch/file')
712
def do_create_file_b(self):
713
return [('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
715
def check_file_content_b(self):
716
self.assertFileEqual(b'file b content\n', 'branch/file')
718
def do_replace_file_a_by_b(self):
719
return [('unversion', 'file'),
720
('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
722
def do_modify_file_a(self):
723
return [('modify', ('file', b'new content\n'))]
725
def check_file_new_content(self):
726
self.assertFileEqual(b'new content\n', 'branch/file')
728
def _get_resolve_path_arg(self, wt, action):
729
return self._this['path']
731
def assertDuplicateEntry(self, wt, c):
732
tpath = self._this['path']
733
tfile_id = self._this['file_id']
734
opath = self._other['path']
735
ofile_id = self._other['file_id']
736
self.assertEqual(tpath, opath) # Sanity check
737
self.assertEqual(tfile_id, c.file_id)
738
self.assertEqual(tpath + '.moved', c.path)
739
self.assertEqual(tpath, c.conflict_path)
740
_assert_conflict = assertDuplicateEntry
743
class TestResolveUnversionedParent(TestResolveConflicts):
745
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
747
# FIXME: While this *creates* UnversionedParent conflicts, this really only
748
# tests MissingParent resolution :-/
755
$ brz commit -m 'Create trunk' -q
756
$ echo 'trunk content' >dir/file
757
$ brz add -q dir/file
758
$ brz commit -q -m 'Add dir/file in trunk'
759
$ brz branch -q . -r 1 ../branch
762
$ brz commit -q -m 'Remove dir in branch'
766
2>Conflict adding files to dir. Created directory.
767
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
768
2>2 conflicts encountered.
771
def test_take_this(self):
773
$ brz rm -q dir --no-backup
775
2>2 conflicts resolved, 0 remaining
776
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
779
def test_take_other(self):
782
2>2 conflicts resolved, 0 remaining
783
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
787
class TestResolveMissingParent(TestResolveConflicts):
794
$ echo 'trunk content' >dir/file
796
$ brz commit -m 'Create trunk' -q
797
$ echo 'trunk content' >dir/file2
798
$ brz add -q dir/file2
799
$ brz commit -q -m 'Add dir/file2 in branch'
800
$ brz branch -q . -r 1 ../branch
802
$ brz rm -q dir/file --no-backup
804
$ brz commit -q -m 'Remove dir/file'
808
2>Conflict adding files to dir. Created directory.
809
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
810
2>2 conflicts encountered.
813
def test_keep_them_all(self):
816
2>2 conflicts resolved, 0 remaining
817
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
820
def test_adopt_child(self):
822
$ brz mv -q dir/file2 file2
823
$ brz rm -q dir --no-backup
825
2>2 conflicts resolved, 0 remaining
826
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
829
def test_kill_them_all(self):
831
$ brz rm -q dir --no-backup
833
2>2 conflicts resolved, 0 remaining
834
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
837
def test_resolve_taking_this(self):
839
$ brz resolve --take-this dir
841
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
844
def test_resolve_taking_other(self):
846
$ brz resolve --take-other dir
848
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
852
class TestResolveDeletingParent(TestResolveConflicts):
859
$ echo 'trunk content' >dir/file
861
$ brz commit -m 'Create trunk' -q
862
$ brz rm -q dir/file --no-backup
863
$ brz rm -q dir --no-backup
864
$ brz commit -q -m 'Remove dir/file'
865
$ brz branch -q . -r 1 ../branch
867
$ echo 'branch content' >dir/file2
868
$ brz add -q dir/file2
869
$ brz commit -q -m 'Add dir/file2 in branch'
872
2>Conflict: can't delete dir because it is not empty. Not deleting.
873
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
874
2>2 conflicts encountered.
877
def test_keep_them_all(self):
880
2>2 conflicts resolved, 0 remaining
881
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
884
def test_adopt_child(self):
886
$ brz mv -q dir/file2 file2
887
$ brz rm -q dir --no-backup
889
2>2 conflicts resolved, 0 remaining
890
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
893
def test_kill_them_all(self):
895
$ brz rm -q dir --no-backup
897
2>2 conflicts resolved, 0 remaining
898
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
901
def test_resolve_taking_this(self):
903
$ brz resolve --take-this dir
904
2>2 conflicts resolved, 0 remaining
905
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
908
def test_resolve_taking_other(self):
910
$ brz resolve --take-other dir
913
2>2 conflicts resolved, 0 remaining
914
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
918
class TestResolveParentLoop(TestParametrizedResolveConflicts):
920
_conflict_type = conflicts.ParentLoop
925
# Each side dict additionally defines:
926
# - dir_id: the directory being moved
927
# - target_id: The target directory
928
# - xfail: whether the test is expected to fail if the action is
929
# involved as 'other'
930
scenarios = mirror_scenarios(
932
# Dirs moved into each other
933
(dict(_base_actions='create_dir1_dir2'),
935
dict(actions='move_dir1_into_dir2', check='dir1_moved',
936
dir_id=b'dir1-id', target_id='dir2-id', xfail=False)),
938
dict(actions='move_dir2_into_dir1', check='dir2_moved',
939
dir_id=b'dir2-id', target_id='dir1-id', xfail=False))),
940
# Subdirs moved into each other
941
(dict(_base_actions='create_dir1_4'),
943
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
944
dir_id=b'dir1-id', target_id='dir4-id', xfail=True)),
946
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
947
dir_id=b'dir3-id', target_id='dir2-id', xfail=True))),
950
def do_create_dir1_dir2(self):
951
return [('add', ('dir1', b'dir1-id', 'directory', '')),
952
('add', ('dir2', b'dir2-id', 'directory', '')),]
954
def do_move_dir1_into_dir2(self):
955
return [('rename', ('dir1', 'dir2/dir1'))]
957
def check_dir1_moved(self):
958
self.assertPathDoesNotExist('branch/dir1')
959
self.assertPathExists('branch/dir2/dir1')
961
def do_move_dir2_into_dir1(self):
962
return [('rename', ('dir2', 'dir1/dir2'))]
964
def check_dir2_moved(self):
965
self.assertPathDoesNotExist('branch/dir2')
966
self.assertPathExists('branch/dir1/dir2')
968
def do_create_dir1_4(self):
969
return [('add', ('dir1', b'dir1-id', 'directory', '')),
970
('add', ('dir1/dir2', b'dir2-id', 'directory', '')),
971
('add', ('dir3', b'dir3-id', 'directory', '')),
972
('add', ('dir3/dir4', b'dir4-id', 'directory', '')),]
974
def do_move_dir1_into_dir4(self):
975
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
977
def check_dir1_2_moved(self):
978
self.assertPathDoesNotExist('branch/dir1')
979
self.assertPathExists('branch/dir3/dir4/dir1')
980
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
982
def do_move_dir3_into_dir2(self):
983
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
985
def check_dir3_4_moved(self):
986
self.assertPathDoesNotExist('branch/dir3')
987
self.assertPathExists('branch/dir1/dir2/dir3')
988
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
990
def _get_resolve_path_arg(self, wt, action):
991
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
992
# But since <path> doesn't exist in the working tree, we need to use
993
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
994
return wt.id2path(self._other['dir_id'])
996
def assertParentLoop(self, wt, c):
997
self.assertEqual(self._other[b'dir_id'], c.file_id)
998
self.assertEqual(self._other[b'target_id'], c.conflict_file_id)
999
# The conflict paths are irrelevant (they are deterministic but not
1000
# worth checking since they don't provide the needed information
1002
if self._other['xfail']:
1003
# It's a bit hackish to raise from here relying on being called for
1004
# both tests but this avoid overriding test_resolve_taking_other
1006
"ParentLoop doesn't carry enough info to resolve --take-other")
1007
_assert_conflict = assertParentLoop
1010
class TestResolveNonDirectoryParent(TestResolveConflicts):
1018
$ brz commit -m 'Create trunk' -q
1019
$ echo "Boing" >foo/bar
1020
$ brz add -q foo/bar
1021
$ brz commit -q -m 'Add foo/bar'
1022
$ brz branch -q . -r 1 ../branch
1026
$ brz commit -q -m 'foo is now a file'
1027
$ brz merge ../trunk
1029
2>RK foo => foo.new/
1030
# FIXME: The message is misleading, foo.new *is* a directory when the message
1031
# is displayed -- vila 090916
1032
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1033
2>1 conflicts encountered.
1036
def test_take_this(self):
1038
$ brz rm -q foo.new --no-backup
1039
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1040
# aside ? -- vila 090916
1042
$ brz resolve foo.new
1043
2>1 conflict resolved, 0 remaining
1044
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1047
def test_take_other(self):
1049
$ brz rm -q foo --no-backup
1050
$ brz mv -q foo.new foo
1052
2>1 conflict resolved, 0 remaining
1053
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1056
def test_resolve_taking_this(self):
1058
$ brz resolve --take-this foo.new
1060
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1063
def test_resolve_taking_other(self):
1065
$ brz resolve --take-other foo.new
1067
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1071
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1073
def test_bug_430129(self):
1074
# This is nearly like TestResolveNonDirectoryParent but with branch and
1075
# trunk switched. As such it should certainly produce the same
1077
self.assertRaises(errors.MalformedTransform,
1078
self.run_script, """
1084
$ brz commit -m 'Create trunk' -q
1087
$ brz commit -m 'foo is now a file' -q
1088
$ brz branch -q . -r 1 ../branch -q
1090
$ echo "Boing" >foo/bar
1091
$ brz add -q foo/bar -q
1092
$ brz commit -m 'Add foo/bar' -q
1093
$ brz merge ../trunk
1094
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1098
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1100
def test_bug_805809(self):
1103
Created a standalone tree (format: 2a)
1108
$ brz commit -m 'create file on trunk'
1109
2>Committing to: .../trunk/
1111
2>Committed revision 1.
1112
# Create a debian branch based on trunk
1114
$ brz branch trunk -r 1 debian
1115
2>Branched 1 revision.
1122
$ brz commit -m 'rename file to dir/file for debian'
1123
2>Committing to: .../debian/
1125
2>renamed file => dir/file
1126
2>Committed revision 2.
1127
# Create an experimental branch with a new root-id
1129
$ brz init experimental
1130
Created a standalone tree (format: 2a)
1132
# Work around merging into empty branch not being supported
1133
# (http://pad.lv/308562)
1134
$ echo something >not-empty
1137
$ brz commit -m 'Add some content in experimental'
1138
2>Committing to: .../experimental/
1140
2>Committed revision 1.
1141
# merge debian even without a common ancestor
1142
$ brz merge ../debian -r0..2
1145
2>All changes applied successfully.
1146
$ brz commit -m 'merging debian into experimental'
1147
2>Committing to: .../experimental/
1150
2>Committed revision 2.
1151
# Create an ubuntu branch with yet another root-id
1154
Created a standalone tree (format: 2a)
1156
# Work around merging into empty branch not being supported
1157
# (http://pad.lv/308562)
1158
$ echo something >not-empty-ubuntu
1160
adding not-empty-ubuntu
1161
$ brz commit -m 'Add some content in experimental'
1162
2>Committing to: .../ubuntu/
1163
2>added not-empty-ubuntu
1164
2>Committed revision 1.
1166
$ brz merge ../debian -r0..2
1169
2>All changes applied successfully.
1170
$ brz commit -m 'merging debian'
1171
2>Committing to: .../ubuntu/
1174
2>Committed revision 2.
1175
# Now try to merge experimental
1176
$ brz merge ../experimental
1178
2>Path conflict: dir / dir
1179
2>1 conflicts encountered.
1183
class TestResolveActionOption(tests.TestCase):
1186
super(TestResolveActionOption, self).setUp()
1187
self.options = [conflicts.ResolveActionOption()]
1188
self.parser = option.get_optparser(dict((o.name, o)
1189
for o in self.options))
1191
def parse(self, args):
1192
return self.parser.parse_args(args)
1194
def test_unknown_action(self):
1195
self.assertRaises(option.BadOptionValue,
1196
self.parse, ['--action', 'take-me-to-the-moon'])
1198
def test_done(self):
1199
opts, args = self.parse(['--action', 'done'])
1200
self.assertEqual({'action':'done'}, opts)
1202
def test_take_this(self):
1203
opts, args = self.parse(['--action', 'take-this'])
1204
self.assertEqual({'action': 'take_this'}, opts)
1205
opts, args = self.parse(['--take-this'])
1206
self.assertEqual({'action': 'take_this'}, opts)
1208
def test_take_other(self):
1209
opts, args = self.parse(['--action', 'take-other'])
1210
self.assertEqual({'action': 'take_other'}, opts)
1211
opts, args = self.parse(['--take-other'])
1212
self.assertEqual({'action': 'take_other'}, opts)