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', b'\xc3\xaedg'),
46
conflicts.ContentsConflict(u'p\xe5tha', None, b'\xc3\xaeda'),
47
conflicts.TextConflict(u'p\xe5tha'),
48
conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', b'\xc3\xaedb'),
49
conflicts.DuplicateID('Unversioned existing file',
50
u'p\xe5thc', u'p\xe5thc2',
51
b'\xc3\xaedc', b'\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',
56
None, b'\xc3\xaed2e'),
57
conflicts.UnversionedParent('Versioned directory',
58
u'p\xe5thf', b'\xc3\xaedf'),
59
conflicts.NonDirectoryParent('Created directory',
60
u'p\xe5thg', b'\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', b'hello world4'),
74
('hello.THIS', b'hello world2'),
75
('hello.BASE', b'hello world1'),
77
os.mkdir('hello.OTHER')
78
tree.add('hello', b'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(
118
[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 = str(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, str)
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, str)
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, str(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(
490
b'trunk content\nmore content\n', 'branch/new-file')
492
def do_delete_file(self):
493
return [('unversion', 'file')]
495
def do_delete_file_in_dir(self):
496
return [('unversion', 'dir/file')]
498
def check_file_doesnt_exist(self):
499
self.assertPathDoesNotExist('branch/file')
501
def do_create_file_in_dir(self):
502
return [('add', ('dir', b'dir-id', 'directory', '')),
503
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
505
def do_modify_file_in_dir(self):
506
return [('modify', ('dir/file', b'trunk content\nmore content\n'))]
508
def check_file_in_dir_has_more_content(self):
509
self.assertFileEqual(
510
b'trunk content\nmore content\n', 'branch/dir/file')
512
def check_file_in_dir_doesnt_exist(self):
513
self.assertPathDoesNotExist('branch/dir/file')
515
def _get_resolve_path_arg(self, wt, action):
518
def assertContentsConflict(self, wt, c):
519
self.assertEqual(self._file_id, c.file_id)
520
self.assertEqual(self._path, c.path)
521
_assert_conflict = assertContentsConflict
524
class TestResolvePathConflict(TestParametrizedResolveConflicts):
526
_conflict_type = conflicts.PathConflict
528
def do_nothing(self):
531
# Each side dict additionally defines:
532
# - path path involved (can be '<deleted>')
534
scenarios = mirror_scenarios(
536
# File renamed/deleted
537
(dict(_base_actions='create_file'),
539
dict(actions='rename_file', check='file_renamed',
540
path='new-file', file_id=b'file-id')),
542
dict(actions='delete_file', check='file_doesnt_exist',
543
# PathConflicts deletion handling requires a special
545
path='<deleted>', file_id=b'file-id')),),
546
# File renamed/deleted in dir
547
(dict(_base_actions='create_file_in_dir'),
548
('file_renamed_in_dir',
549
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
550
path='dir/new-file', file_id=b'file-id')),
552
dict(actions='delete_file_in_dir', check='file_in_dir_doesnt_exist',
553
# PathConflicts deletion handling requires a special
555
path='<deleted>', file_id=b'file-id')),),
556
# File renamed/renamed differently
557
(dict(_base_actions='create_file'),
559
dict(actions='rename_file', check='file_renamed',
560
path='new-file', file_id=b'file-id')),
562
dict(actions='rename_file2', check='file_renamed2',
563
path='new-file2', file_id=b'file-id')),),
564
# Dir renamed/deleted
565
(dict(_base_actions='create_dir'),
567
dict(actions='rename_dir', check='dir_renamed',
568
path='new-dir', file_id=b'dir-id')),
570
dict(actions='delete_dir', check='dir_doesnt_exist',
571
# PathConflicts deletion handling requires a special
573
path='<deleted>', file_id=b'dir-id')),),
574
# Dir renamed/renamed differently
575
(dict(_base_actions='create_dir'),
577
dict(actions='rename_dir', check='dir_renamed',
578
path='new-dir', file_id=b'dir-id')),
580
dict(actions='rename_dir2', check='dir_renamed2',
581
path='new-dir2', file_id=b'dir-id')),),
584
def do_create_file(self):
585
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
587
def do_create_dir(self):
588
return [('add', ('dir', b'dir-id', 'directory', ''))]
590
def do_rename_file(self):
591
return [('rename', ('file', 'new-file'))]
593
def check_file_renamed(self):
594
self.assertPathDoesNotExist('branch/file')
595
self.assertPathExists('branch/new-file')
597
def do_rename_file2(self):
598
return [('rename', ('file', 'new-file2'))]
600
def check_file_renamed2(self):
601
self.assertPathDoesNotExist('branch/file')
602
self.assertPathExists('branch/new-file2')
604
def do_rename_dir(self):
605
return [('rename', ('dir', 'new-dir'))]
607
def check_dir_renamed(self):
608
self.assertPathDoesNotExist('branch/dir')
609
self.assertPathExists('branch/new-dir')
611
def do_rename_dir2(self):
612
return [('rename', ('dir', 'new-dir2'))]
614
def check_dir_renamed2(self):
615
self.assertPathDoesNotExist('branch/dir')
616
self.assertPathExists('branch/new-dir2')
618
def do_delete_file(self):
619
return [('unversion', 'file')]
621
def do_delete_file_in_dir(self):
622
return [('unversion', 'dir/file')]
624
def check_file_doesnt_exist(self):
625
self.assertPathDoesNotExist('branch/file')
627
def do_delete_dir(self):
628
return [('unversion', 'dir')]
630
def check_dir_doesnt_exist(self):
631
self.assertPathDoesNotExist('branch/dir')
633
def do_create_file_in_dir(self):
634
return [('add', ('dir', b'dir-id', 'directory', '')),
635
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
637
def do_rename_file_in_dir(self):
638
return [('rename', ('dir/file', 'dir/new-file'))]
640
def check_file_in_dir_renamed(self):
641
self.assertPathDoesNotExist('branch/dir/file')
642
self.assertPathExists('branch/dir/new-file')
644
def check_file_in_dir_doesnt_exist(self):
645
self.assertPathDoesNotExist('branch/dir/file')
647
def _get_resolve_path_arg(self, wt, action):
648
tpath = self._this['path']
649
opath = self._other['path']
650
if tpath == '<deleted>':
656
def assertPathConflict(self, wt, c):
657
tpath = self._this['path']
658
tfile_id = self._this['file_id']
659
opath = self._other['path']
660
ofile_id = self._other['file_id']
661
self.assertEqual(tfile_id, ofile_id) # Sanity check
662
self.assertEqual(tfile_id, c.file_id)
663
self.assertEqual(tpath, c.path)
664
self.assertEqual(opath, c.conflict_path)
665
_assert_conflict = assertPathConflict
668
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
669
"""Same as TestResolvePathConflict but a specific conflict object.
672
def assertPathConflict(self, c):
673
# We create a conflict object as it was created before the fix and
674
# inject it into the working tree, the test will exercise the
675
# compatibility code.
676
old_c = conflicts.PathConflict('<deleted>', self._item_path,
678
wt.set_conflicts(conflicts.ConflictList([old_c]))
681
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
683
_conflict_type = conflicts.DuplicateEntry
685
scenarios = mirror_scenarios(
687
# File created with different file-ids
688
(dict(_base_actions='nothing'),
690
dict(actions='create_file_a', check='file_content_a',
691
path='file', file_id=b'file-a-id')),
693
dict(actions='create_file_b', check='file_content_b',
694
path='file', file_id=b'file-b-id')),),
695
# File created with different file-ids but deleted on one side
696
(dict(_base_actions='create_file_a'),
698
dict(actions='replace_file_a_by_b', check='file_content_b',
699
path='file', file_id=b'file-b-id')),
701
dict(actions='modify_file_a', check='file_new_content',
702
path='file', file_id=b'file-a-id')),),
705
def do_nothing(self):
708
def do_create_file_a(self):
709
return [('add', ('file', b'file-a-id', 'file', b'file a content\n'))]
711
def check_file_content_a(self):
712
self.assertFileEqual(b'file a content\n', 'branch/file')
714
def do_create_file_b(self):
715
return [('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
717
def check_file_content_b(self):
718
self.assertFileEqual(b'file b content\n', 'branch/file')
720
def do_replace_file_a_by_b(self):
721
return [('unversion', 'file'),
722
('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
724
def do_modify_file_a(self):
725
return [('modify', ('file', b'new content\n'))]
727
def check_file_new_content(self):
728
self.assertFileEqual(b'new content\n', 'branch/file')
730
def _get_resolve_path_arg(self, wt, action):
731
return self._this['path']
733
def assertDuplicateEntry(self, wt, c):
734
tpath = self._this['path']
735
tfile_id = self._this['file_id']
736
opath = self._other['path']
737
ofile_id = self._other['file_id']
738
self.assertEqual(tpath, opath) # Sanity check
739
self.assertEqual(tfile_id, c.file_id)
740
self.assertEqual(tpath + '.moved', c.path)
741
self.assertEqual(tpath, c.conflict_path)
742
_assert_conflict = assertDuplicateEntry
745
class TestResolveUnversionedParent(TestResolveConflicts):
747
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
749
# FIXME: While this *creates* UnversionedParent conflicts, this really only
750
# tests MissingParent resolution :-/
757
$ brz commit -m 'Create trunk' -q
758
$ echo 'trunk content' >dir/file
759
$ brz add -q dir/file
760
$ brz commit -q -m 'Add dir/file in trunk'
761
$ brz branch -q . -r 1 ../branch
764
$ brz commit -q -m 'Remove dir in branch'
768
2>Conflict adding files to dir. Created directory.
769
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
770
2>2 conflicts encountered.
773
def test_take_this(self):
775
$ brz rm -q dir --no-backup
777
2>2 conflicts resolved, 0 remaining
778
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
781
def test_take_other(self):
784
2>2 conflicts resolved, 0 remaining
785
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
789
class TestResolveMissingParent(TestResolveConflicts):
796
$ echo 'trunk content' >dir/file
798
$ brz commit -m 'Create trunk' -q
799
$ echo 'trunk content' >dir/file2
800
$ brz add -q dir/file2
801
$ brz commit -q -m 'Add dir/file2 in branch'
802
$ brz branch -q . -r 1 ../branch
804
$ brz rm -q dir/file --no-backup
806
$ brz commit -q -m 'Remove dir/file'
810
2>Conflict adding files to dir. Created directory.
811
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
812
2>2 conflicts encountered.
815
def test_keep_them_all(self):
818
2>2 conflicts resolved, 0 remaining
819
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
822
def test_adopt_child(self):
824
$ brz mv -q dir/file2 file2
825
$ brz rm -q dir --no-backup
827
2>2 conflicts resolved, 0 remaining
828
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
831
def test_kill_them_all(self):
833
$ brz rm -q dir --no-backup
835
2>2 conflicts resolved, 0 remaining
836
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
839
def test_resolve_taking_this(self):
841
$ brz resolve --take-this dir
843
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
846
def test_resolve_taking_other(self):
848
$ brz resolve --take-other dir
850
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
854
class TestResolveDeletingParent(TestResolveConflicts):
861
$ echo 'trunk content' >dir/file
863
$ brz commit -m 'Create trunk' -q
864
$ brz rm -q dir/file --no-backup
865
$ brz rm -q dir --no-backup
866
$ brz commit -q -m 'Remove dir/file'
867
$ brz branch -q . -r 1 ../branch
869
$ echo 'branch content' >dir/file2
870
$ brz add -q dir/file2
871
$ brz commit -q -m 'Add dir/file2 in branch'
874
2>Conflict: can't delete dir because it is not empty. Not deleting.
875
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
876
2>2 conflicts encountered.
879
def test_keep_them_all(self):
882
2>2 conflicts resolved, 0 remaining
883
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
886
def test_adopt_child(self):
888
$ brz mv -q dir/file2 file2
889
$ brz rm -q dir --no-backup
891
2>2 conflicts resolved, 0 remaining
892
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
895
def test_kill_them_all(self):
897
$ brz rm -q dir --no-backup
899
2>2 conflicts resolved, 0 remaining
900
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
903
def test_resolve_taking_this(self):
905
$ brz resolve --take-this dir
906
2>2 conflicts resolved, 0 remaining
907
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
910
def test_resolve_taking_other(self):
912
$ brz resolve --take-other dir
915
2>2 conflicts resolved, 0 remaining
916
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
920
class TestResolveParentLoop(TestParametrizedResolveConflicts):
922
_conflict_type = conflicts.ParentLoop
927
# Each side dict additionally defines:
928
# - dir_id: the directory being moved
929
# - target_id: The target directory
930
# - xfail: whether the test is expected to fail if the action is
931
# involved as 'other'
932
scenarios = mirror_scenarios(
934
# Dirs moved into each other
935
(dict(_base_actions='create_dir1_dir2'),
937
dict(actions='move_dir1_into_dir2', check='dir1_moved',
938
dir_id=b'dir1-id', target_id=b'dir2-id', xfail=False)),
940
dict(actions='move_dir2_into_dir1', check='dir2_moved',
941
dir_id=b'dir2-id', target_id=b'dir1-id', xfail=False))),
942
# Subdirs moved into each other
943
(dict(_base_actions='create_dir1_4'),
945
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
946
dir_id=b'dir1-id', target_id=b'dir4-id', xfail=True)),
948
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
949
dir_id=b'dir3-id', target_id=b'dir2-id', xfail=True))),
952
def do_create_dir1_dir2(self):
953
return [('add', ('dir1', b'dir1-id', 'directory', '')),
954
('add', ('dir2', b'dir2-id', 'directory', '')), ]
956
def do_move_dir1_into_dir2(self):
957
return [('rename', ('dir1', 'dir2/dir1'))]
959
def check_dir1_moved(self):
960
self.assertPathDoesNotExist('branch/dir1')
961
self.assertPathExists('branch/dir2/dir1')
963
def do_move_dir2_into_dir1(self):
964
return [('rename', ('dir2', 'dir1/dir2'))]
966
def check_dir2_moved(self):
967
self.assertPathDoesNotExist('branch/dir2')
968
self.assertPathExists('branch/dir1/dir2')
970
def do_create_dir1_4(self):
971
return [('add', ('dir1', b'dir1-id', 'directory', '')),
972
('add', ('dir1/dir2', b'dir2-id', 'directory', '')),
973
('add', ('dir3', b'dir3-id', 'directory', '')),
974
('add', ('dir3/dir4', b'dir4-id', 'directory', '')), ]
976
def do_move_dir1_into_dir4(self):
977
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
979
def check_dir1_2_moved(self):
980
self.assertPathDoesNotExist('branch/dir1')
981
self.assertPathExists('branch/dir3/dir4/dir1')
982
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
984
def do_move_dir3_into_dir2(self):
985
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
987
def check_dir3_4_moved(self):
988
self.assertPathDoesNotExist('branch/dir3')
989
self.assertPathExists('branch/dir1/dir2/dir3')
990
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
992
def _get_resolve_path_arg(self, wt, action):
993
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
994
# But since <path> doesn't exist in the working tree, we need to use
995
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
996
return wt.id2path(self._other['dir_id'])
998
def assertParentLoop(self, wt, c):
999
self.assertEqual(self._other['dir_id'], c.file_id)
1000
self.assertEqual(self._other['target_id'], c.conflict_file_id)
1001
# The conflict paths are irrelevant (they are deterministic but not
1002
# worth checking since they don't provide the needed information
1004
if self._other['xfail']:
1005
# It's a bit hackish to raise from here relying on being called for
1006
# both tests but this avoid overriding test_resolve_taking_other
1008
"ParentLoop doesn't carry enough info to resolve --take-other")
1009
_assert_conflict = assertParentLoop
1012
class TestResolveNonDirectoryParent(TestResolveConflicts):
1020
$ brz commit -m 'Create trunk' -q
1021
$ echo "Boing" >foo/bar
1022
$ brz add -q foo/bar
1023
$ brz commit -q -m 'Add foo/bar'
1024
$ brz branch -q . -r 1 ../branch
1028
$ brz commit -q -m 'foo is now a file'
1029
$ brz merge ../trunk
1030
2>RK foo => foo.new/
1032
# FIXME: The message is misleading, foo.new *is* a directory when the message
1033
# is displayed -- vila 090916
1034
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1035
2>1 conflicts encountered.
1038
def test_take_this(self):
1040
$ brz rm -q foo.new --no-backup
1041
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1042
# aside ? -- vila 090916
1044
$ brz resolve foo.new
1045
2>1 conflict resolved, 0 remaining
1046
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1049
def test_take_other(self):
1051
$ brz rm -q foo --no-backup
1052
$ brz mv -q foo.new foo
1054
2>1 conflict resolved, 0 remaining
1055
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1058
def test_resolve_taking_this(self):
1060
$ brz resolve --take-this foo.new
1062
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1065
def test_resolve_taking_other(self):
1067
$ brz resolve --take-other foo.new
1069
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1073
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1075
def test_bug_430129(self):
1076
# This is nearly like TestResolveNonDirectoryParent but with branch and
1077
# trunk switched. As such it should certainly produce the same
1079
self.assertRaises(errors.MalformedTransform,
1080
self.run_script, """
1086
$ brz commit -m 'Create trunk' -q
1089
$ brz commit -m 'foo is now a file' -q
1090
$ brz branch -q . -r 1 ../branch -q
1092
$ echo "Boing" >foo/bar
1093
$ brz add -q foo/bar -q
1094
$ brz commit -m 'Add foo/bar' -q
1095
$ brz merge ../trunk
1096
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1100
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1102
def test_bug_805809(self):
1105
Created a standalone tree (format: 2a)
1110
$ brz commit -m 'create file on trunk'
1111
2>Committing to: .../trunk/
1113
2>Committed revision 1.
1114
# Create a debian branch based on trunk
1116
$ brz branch trunk -r 1 debian
1117
2>Branched 1 revision.
1124
$ brz commit -m 'rename file to dir/file for debian'
1125
2>Committing to: .../debian/
1127
2>renamed file => dir/file
1128
2>Committed revision 2.
1129
# Create an experimental branch with a new root-id
1131
$ brz init experimental
1132
Created a standalone tree (format: 2a)
1134
# Work around merging into empty branch not being supported
1135
# (http://pad.lv/308562)
1136
$ echo something >not-empty
1139
$ brz commit -m 'Add some content in experimental'
1140
2>Committing to: .../experimental/
1142
2>Committed revision 1.
1143
# merge debian even without a common ancestor
1144
$ brz merge ../debian -r0..2
1147
2>All changes applied successfully.
1148
$ brz commit -m 'merging debian into experimental'
1149
2>Committing to: .../experimental/
1152
2>Committed revision 2.
1153
# Create an ubuntu branch with yet another root-id
1156
Created a standalone tree (format: 2a)
1158
# Work around merging into empty branch not being supported
1159
# (http://pad.lv/308562)
1160
$ echo something >not-empty-ubuntu
1162
adding not-empty-ubuntu
1163
$ brz commit -m 'Add some content in experimental'
1164
2>Committing to: .../ubuntu/
1165
2>added not-empty-ubuntu
1166
2>Committed revision 1.
1168
$ brz merge ../debian -r0..2
1171
2>All changes applied successfully.
1172
$ brz commit -m 'merging debian'
1173
2>Committing to: .../ubuntu/
1176
2>Committed revision 2.
1177
# Now try to merge experimental
1178
$ brz merge ../experimental
1180
2>Path conflict: dir / dir
1181
2>1 conflicts encountered.
1185
class TestResolveActionOption(tests.TestCase):
1188
super(TestResolveActionOption, self).setUp()
1189
self.options = [conflicts.ResolveActionOption()]
1190
self.parser = option.get_optparser(self.options)
1192
def parse(self, args):
1193
return self.parser.parse_args(args)
1195
def test_unknown_action(self):
1196
self.assertRaises(option.BadOptionValue,
1197
self.parse, ['--action', 'take-me-to-the-moon'])
1199
def test_done(self):
1200
opts, args = self.parse(['--action', 'done'])
1201
self.assertEqual({'action': 'done'}, opts)
1203
def test_take_this(self):
1204
opts, args = self.parse(['--action', 'take-this'])
1205
self.assertEqual({'action': 'take_this'}, opts)
1206
opts, args = self.parse(['--take-this'])
1207
self.assertEqual({'action': 'take_this'}, opts)
1209
def test_take_other(self):
1210
opts, args = self.parse(['--action', 'take-other'])
1211
self.assertEqual({'action': 'take_other'}, opts)
1212
opts, args = self.parse(['--take-other'])
1213
self.assertEqual({'action': 'take_other'}, opts)