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 bzrlib.tests import (
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('start', None, [
306
('add', ('', 'root-id', 'directory', ''))])
307
# Add a minimal base content
308
base_actions = self._get_actions(self._base_actions)()
309
builder.build_snapshot('base', ['start'], base_actions)
310
# Modify the base content in branch
311
actions_other = self._get_actions(self._other['actions'])()
312
builder.build_snapshot('other', ['base'], actions_other)
313
# Modify the base content in trunk
314
actions_this = self._get_actions(self._this['actions'])()
315
builder.build_snapshot('this', ['base'], actions_this)
316
# builder.get_branch() tip is now 'this'
318
builder.finish_series()
319
self.builder = builder
321
def _get_actions(self, name):
322
return getattr(self, 'do_%s' % name)
324
def _get_check(self, name):
325
return getattr(self, 'check_%s' % name)
327
def _merge_other_into_this(self):
328
b = self.builder.get_branch()
329
wt = b.bzrdir.sprout('branch').open_workingtree()
330
wt.merge_from_branch(b, 'other')
333
def assertConflict(self, wt):
334
confs = wt.conflicts()
335
self.assertLength(1, confs)
337
self.assertIsInstance(c, self._conflict_type)
338
self._assert_conflict(wt, c)
340
def _get_resolve_path_arg(self, wt, action):
341
raise NotImplementedError(self._get_resolve_path_arg)
343
def check_resolved(self, wt, action):
344
path = self._get_resolve_path_arg(wt, action)
345
conflicts.resolve(wt, [path], action=action)
346
# Check that we don't have any conflicts nor unknown left
347
self.assertLength(0, wt.conflicts())
348
self.assertLength(0, list(wt.unknowns()))
350
def test_resolve_taking_this(self):
351
wt = self._merge_other_into_this()
352
self.assertConflict(wt)
353
self.check_resolved(wt, 'take_this')
354
check_this = self._get_check(self._this['check'])
357
def test_resolve_taking_other(self):
358
wt = self._merge_other_into_this()
359
self.assertConflict(wt)
360
self.check_resolved(wt, 'take_other')
361
check_other = self._get_check(self._other['check'])
365
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
367
_conflict_type = conflicts.TextConflict
369
# Set by the scenarios
370
# path and file-id for the file involved in the conflict
374
scenarios = mirror_scenarios(
376
# File modified on both sides
377
(dict(_base_actions='create_file',
378
_path='file', _file_id='file-id'),
380
dict(actions='modify_file_A', check='file_has_content_A')),
382
dict(actions='modify_file_B', check='file_has_content_B')),),
383
# File modified on both sides in dir
384
(dict(_base_actions='create_file_in_dir',
385
_path='dir/file', _file_id='file-id'),
386
('filed_modified_A_in_dir',
387
dict(actions='modify_file_A',
388
check='file_in_dir_has_content_A')),
390
dict(actions='modify_file_B',
391
check='file_in_dir_has_content_B')),),
394
def do_create_file(self, path='file'):
395
return [('add', (path, 'file-id', 'file', 'trunk content\n'))]
397
def do_modify_file_A(self):
398
return [('modify', ('file-id', 'trunk content\nfeature A\n'))]
400
def do_modify_file_B(self):
401
return [('modify', ('file-id', 'trunk content\nfeature B\n'))]
403
def check_file_has_content_A(self, path='file'):
404
self.assertFileEqual('trunk content\nfeature A\n',
405
osutils.pathjoin('branch', path))
407
def check_file_has_content_B(self, path='file'):
408
self.assertFileEqual('trunk content\nfeature B\n',
409
osutils.pathjoin('branch', path))
411
def do_create_file_in_dir(self):
412
return [('add', ('dir', 'dir-id', 'directory', '')),
413
] + self.do_create_file('dir/file')
415
def check_file_in_dir_has_content_A(self):
416
self.check_file_has_content_A('dir/file')
418
def check_file_in_dir_has_content_B(self):
419
self.check_file_has_content_B('dir/file')
421
def _get_resolve_path_arg(self, wt, action):
424
def assertTextConflict(self, wt, c):
425
self.assertEqual(self._file_id, c.file_id)
426
self.assertEqual(self._path, c.path)
427
_assert_conflict = assertTextConflict
430
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
432
_conflict_type = conflicts.ContentsConflict
434
# Set by the scenarios
435
# path and file-id for the file involved in the conflict
439
scenarios = mirror_scenarios(
441
# File modified/deleted
442
(dict(_base_actions='create_file',
443
_path='file', _file_id='file-id'),
445
dict(actions='modify_file', check='file_has_more_content')),
447
dict(actions='delete_file', check='file_doesnt_exist')),),
448
# File renamed-modified/deleted
449
(dict(_base_actions='create_file',
450
_path='new-file', _file_id='file-id'),
451
('file_renamed_and_modified',
452
dict(actions='modify_and_rename_file',
453
check='file_renamed_and_more_content')),
455
dict(actions='delete_file', check='file_doesnt_exist')),),
456
# File modified/deleted in dir
457
(dict(_base_actions='create_file_in_dir',
458
_path='dir/file', _file_id='file-id'),
459
('file_modified_in_dir',
460
dict(actions='modify_file_in_dir',
461
check='file_in_dir_has_more_content')),
462
('file_deleted_in_dir',
463
dict(actions='delete_file',
464
check='file_in_dir_doesnt_exist')),),
467
def do_create_file(self):
468
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
470
def do_modify_file(self):
471
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
473
def do_modify_and_rename_file(self):
474
return [('modify', ('file-id', 'trunk content\nmore content\n')),
475
('rename', ('file', 'new-file'))]
477
def check_file_has_more_content(self):
478
self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
480
def check_file_renamed_and_more_content(self):
481
self.assertFileEqual('trunk content\nmore content\n', 'branch/new-file')
483
def do_delete_file(self):
484
return [('unversion', 'file-id')]
486
def check_file_doesnt_exist(self):
487
self.assertPathDoesNotExist('branch/file')
489
def do_create_file_in_dir(self):
490
return [('add', ('dir', 'dir-id', 'directory', '')),
491
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
493
def do_modify_file_in_dir(self):
494
return [('modify', ('file-id', 'trunk content\nmore content\n'))]
496
def check_file_in_dir_has_more_content(self):
497
self.assertFileEqual('trunk content\nmore content\n', 'branch/dir/file')
499
def check_file_in_dir_doesnt_exist(self):
500
self.assertPathDoesNotExist('branch/dir/file')
502
def _get_resolve_path_arg(self, wt, action):
505
def assertContentsConflict(self, wt, c):
506
self.assertEqual(self._file_id, c.file_id)
507
self.assertEqual(self._path, c.path)
508
_assert_conflict = assertContentsConflict
511
class TestResolvePathConflict(TestParametrizedResolveConflicts):
513
_conflict_type = conflicts.PathConflict
515
def do_nothing(self):
518
# Each side dict additionally defines:
519
# - path path involved (can be '<deleted>')
521
scenarios = mirror_scenarios(
523
# File renamed/deleted
524
(dict(_base_actions='create_file'),
526
dict(actions='rename_file', check='file_renamed',
527
path='new-file', file_id='file-id')),
529
dict(actions='delete_file', check='file_doesnt_exist',
530
# PathConflicts deletion handling requires a special
532
path='<deleted>', file_id='file-id')),),
533
# File renamed/deleted in dir
534
(dict(_base_actions='create_file_in_dir'),
535
('file_renamed_in_dir',
536
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
537
path='dir/new-file', file_id='file-id')),
539
dict(actions='delete_file', check='file_in_dir_doesnt_exist',
540
# PathConflicts deletion handling requires a special
542
path='<deleted>', file_id='file-id')),),
543
# File renamed/renamed differently
544
(dict(_base_actions='create_file'),
546
dict(actions='rename_file', check='file_renamed',
547
path='new-file', file_id='file-id')),
549
dict(actions='rename_file2', check='file_renamed2',
550
path='new-file2', file_id='file-id')),),
551
# Dir renamed/deleted
552
(dict(_base_actions='create_dir'),
554
dict(actions='rename_dir', check='dir_renamed',
555
path='new-dir', file_id='dir-id')),
557
dict(actions='delete_dir', check='dir_doesnt_exist',
558
# PathConflicts deletion handling requires a special
560
path='<deleted>', file_id='dir-id')),),
561
# Dir renamed/renamed differently
562
(dict(_base_actions='create_dir'),
564
dict(actions='rename_dir', check='dir_renamed',
565
path='new-dir', file_id='dir-id')),
567
dict(actions='rename_dir2', check='dir_renamed2',
568
path='new-dir2', file_id='dir-id')),),
571
def do_create_file(self):
572
return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
574
def do_create_dir(self):
575
return [('add', ('dir', 'dir-id', 'directory', ''))]
577
def do_rename_file(self):
578
return [('rename', ('file', 'new-file'))]
580
def check_file_renamed(self):
581
self.assertPathDoesNotExist('branch/file')
582
self.assertPathExists('branch/new-file')
584
def do_rename_file2(self):
585
return [('rename', ('file', 'new-file2'))]
587
def check_file_renamed2(self):
588
self.assertPathDoesNotExist('branch/file')
589
self.assertPathExists('branch/new-file2')
591
def do_rename_dir(self):
592
return [('rename', ('dir', 'new-dir'))]
594
def check_dir_renamed(self):
595
self.assertPathDoesNotExist('branch/dir')
596
self.assertPathExists('branch/new-dir')
598
def do_rename_dir2(self):
599
return [('rename', ('dir', 'new-dir2'))]
601
def check_dir_renamed2(self):
602
self.assertPathDoesNotExist('branch/dir')
603
self.assertPathExists('branch/new-dir2')
605
def do_delete_file(self):
606
return [('unversion', 'file-id')]
608
def check_file_doesnt_exist(self):
609
self.assertPathDoesNotExist('branch/file')
611
def do_delete_dir(self):
612
return [('unversion', 'dir-id')]
614
def check_dir_doesnt_exist(self):
615
self.assertPathDoesNotExist('branch/dir')
617
def do_create_file_in_dir(self):
618
return [('add', ('dir', 'dir-id', 'directory', '')),
619
('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
621
def do_rename_file_in_dir(self):
622
return [('rename', ('dir/file', 'dir/new-file'))]
624
def check_file_in_dir_renamed(self):
625
self.assertPathDoesNotExist('branch/dir/file')
626
self.assertPathExists('branch/dir/new-file')
628
def check_file_in_dir_doesnt_exist(self):
629
self.assertPathDoesNotExist('branch/dir/file')
631
def _get_resolve_path_arg(self, wt, action):
632
tpath = self._this['path']
633
opath = self._other['path']
634
if tpath == '<deleted>':
640
def assertPathConflict(self, wt, c):
641
tpath = self._this['path']
642
tfile_id = self._this['file_id']
643
opath = self._other['path']
644
ofile_id = self._other['file_id']
645
self.assertEqual(tfile_id, ofile_id) # Sanity check
646
self.assertEqual(tfile_id, c.file_id)
647
self.assertEqual(tpath, c.path)
648
self.assertEqual(opath, c.conflict_path)
649
_assert_conflict = assertPathConflict
652
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
653
"""Same as TestResolvePathConflict but a specific conflict object.
656
def assertPathConflict(self, c):
657
# We create a conflict object as it was created before the fix and
658
# inject it into the working tree, the test will exercise the
659
# compatibility code.
660
old_c = conflicts.PathConflict('<deleted>', self._item_path,
662
wt.set_conflicts(conflicts.ConflictList([old_c]))
665
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
667
_conflict_type = conflicts.DuplicateEntry
669
scenarios = mirror_scenarios(
671
# File created with different file-ids
672
(dict(_base_actions='nothing'),
674
dict(actions='create_file_a', check='file_content_a',
675
path='file', file_id='file-a-id')),
677
dict(actions='create_file_b', check='file_content_b',
678
path='file', file_id='file-b-id')),),
679
# File created with different file-ids but deleted on one side
680
(dict(_base_actions='create_file_a'),
682
dict(actions='replace_file_a_by_b', check='file_content_b',
683
path='file', file_id='file-b-id')),
685
dict(actions='modify_file_a', check='file_new_content',
686
path='file', file_id='file-a-id')),),
689
def do_nothing(self):
692
def do_create_file_a(self):
693
return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
695
def check_file_content_a(self):
696
self.assertFileEqual('file a content\n', 'branch/file')
698
def do_create_file_b(self):
699
return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
701
def check_file_content_b(self):
702
self.assertFileEqual('file b content\n', 'branch/file')
704
def do_replace_file_a_by_b(self):
705
return [('unversion', 'file-a-id'),
706
('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
708
def do_modify_file_a(self):
709
return [('modify', ('file-a-id', 'new content\n'))]
711
def check_file_new_content(self):
712
self.assertFileEqual('new content\n', 'branch/file')
714
def _get_resolve_path_arg(self, wt, action):
715
return self._this['path']
717
def assertDuplicateEntry(self, wt, c):
718
tpath = self._this['path']
719
tfile_id = self._this['file_id']
720
opath = self._other['path']
721
ofile_id = self._other['file_id']
722
self.assertEqual(tpath, opath) # Sanity check
723
self.assertEqual(tfile_id, c.file_id)
724
self.assertEqual(tpath + '.moved', c.path)
725
self.assertEqual(tpath, c.conflict_path)
726
_assert_conflict = assertDuplicateEntry
729
class TestResolveUnversionedParent(TestResolveConflicts):
731
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
733
# FIXME: While this *creates* UnversionedParent conflicts, this really only
734
# tests MissingParent resolution :-/
741
$ bzr commit -m 'Create trunk' -q
742
$ echo 'trunk content' >dir/file
743
$ bzr add -q dir/file
744
$ bzr commit -q -m 'Add dir/file in trunk'
745
$ bzr branch -q . -r 1 ../branch
748
$ bzr commit -q -m 'Remove dir in branch'
752
2>Conflict adding files to dir. Created directory.
753
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
754
2>2 conflicts encountered.
757
def test_take_this(self):
759
$ bzr rm -q dir --no-backup
761
2>2 conflicts resolved, 0 remaining
762
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
765
def test_take_other(self):
768
2>2 conflicts resolved, 0 remaining
769
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
773
class TestResolveMissingParent(TestResolveConflicts):
780
$ echo 'trunk content' >dir/file
782
$ bzr commit -m 'Create trunk' -q
783
$ echo 'trunk content' >dir/file2
784
$ bzr add -q dir/file2
785
$ bzr commit -q -m 'Add dir/file2 in branch'
786
$ bzr branch -q . -r 1 ../branch
788
$ bzr rm -q dir/file --no-backup
790
$ bzr commit -q -m 'Remove dir/file'
794
2>Conflict adding files to dir. Created directory.
795
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
796
2>2 conflicts encountered.
799
def test_keep_them_all(self):
802
2>2 conflicts resolved, 0 remaining
803
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
806
def test_adopt_child(self):
808
$ bzr mv -q dir/file2 file2
809
$ bzr rm -q dir --no-backup
811
2>2 conflicts resolved, 0 remaining
812
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
815
def test_kill_them_all(self):
817
$ bzr rm -q dir --no-backup
819
2>2 conflicts resolved, 0 remaining
820
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
823
def test_resolve_taking_this(self):
825
$ bzr resolve --take-this dir
827
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
830
def test_resolve_taking_other(self):
832
$ bzr resolve --take-other dir
834
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
838
class TestResolveDeletingParent(TestResolveConflicts):
845
$ echo 'trunk content' >dir/file
847
$ bzr commit -m 'Create trunk' -q
848
$ bzr rm -q dir/file --no-backup
849
$ bzr rm -q dir --no-backup
850
$ bzr commit -q -m 'Remove dir/file'
851
$ bzr branch -q . -r 1 ../branch
853
$ echo 'branch content' >dir/file2
854
$ bzr add -q dir/file2
855
$ bzr commit -q -m 'Add dir/file2 in branch'
858
2>Conflict: can't delete dir because it is not empty. Not deleting.
859
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
860
2>2 conflicts encountered.
863
def test_keep_them_all(self):
866
2>2 conflicts resolved, 0 remaining
867
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
870
def test_adopt_child(self):
872
$ bzr mv -q dir/file2 file2
873
$ bzr rm -q dir --no-backup
875
2>2 conflicts resolved, 0 remaining
876
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
879
def test_kill_them_all(self):
881
$ bzr rm -q dir --no-backup
883
2>2 conflicts resolved, 0 remaining
884
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
887
def test_resolve_taking_this(self):
889
$ bzr resolve --take-this dir
890
2>2 conflicts resolved, 0 remaining
891
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
894
def test_resolve_taking_other(self):
896
$ bzr resolve --take-other dir
899
2>2 conflicts resolved, 0 remaining
900
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
904
class TestResolveParentLoop(TestParametrizedResolveConflicts):
906
_conflict_type = conflicts.ParentLoop
911
# Each side dict additionally defines:
912
# - dir_id: the directory being moved
913
# - target_id: The target directory
914
# - xfail: whether the test is expected to fail if the action is
915
# involved as 'other'
916
scenarios = mirror_scenarios(
918
# Dirs moved into each other
919
(dict(_base_actions='create_dir1_dir2'),
921
dict(actions='move_dir1_into_dir2', check='dir1_moved',
922
dir_id='dir1-id', target_id='dir2-id', xfail=False)),
924
dict(actions='move_dir2_into_dir1', check='dir2_moved',
925
dir_id='dir2-id', target_id='dir1-id', xfail=False))),
926
# Subdirs moved into each other
927
(dict(_base_actions='create_dir1_4'),
929
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
930
dir_id='dir1-id', target_id='dir4-id', xfail=True)),
932
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
933
dir_id='dir3-id', target_id='dir2-id', xfail=True))),
936
def do_create_dir1_dir2(self):
937
return [('add', ('dir1', 'dir1-id', 'directory', '')),
938
('add', ('dir2', 'dir2-id', 'directory', '')),]
940
def do_move_dir1_into_dir2(self):
941
return [('rename', ('dir1', 'dir2/dir1'))]
943
def check_dir1_moved(self):
944
self.assertPathDoesNotExist('branch/dir1')
945
self.assertPathExists('branch/dir2/dir1')
947
def do_move_dir2_into_dir1(self):
948
return [('rename', ('dir2', 'dir1/dir2'))]
950
def check_dir2_moved(self):
951
self.assertPathDoesNotExist('branch/dir2')
952
self.assertPathExists('branch/dir1/dir2')
954
def do_create_dir1_4(self):
955
return [('add', ('dir1', 'dir1-id', 'directory', '')),
956
('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
957
('add', ('dir3', 'dir3-id', 'directory', '')),
958
('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
960
def do_move_dir1_into_dir4(self):
961
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
963
def check_dir1_2_moved(self):
964
self.assertPathDoesNotExist('branch/dir1')
965
self.assertPathExists('branch/dir3/dir4/dir1')
966
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
968
def do_move_dir3_into_dir2(self):
969
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
971
def check_dir3_4_moved(self):
972
self.assertPathDoesNotExist('branch/dir3')
973
self.assertPathExists('branch/dir1/dir2/dir3')
974
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
976
def _get_resolve_path_arg(self, wt, action):
977
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
978
# But since <path> doesn't exist in the working tree, we need to use
979
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
980
return wt.id2path(self._other['dir_id'])
982
def assertParentLoop(self, wt, c):
983
self.assertEqual(self._other['dir_id'], c.file_id)
984
self.assertEqual(self._other['target_id'], c.conflict_file_id)
985
# The conflict paths are irrelevant (they are deterministic but not
986
# worth checking since they don't provide the needed information
988
if self._other['xfail']:
989
# It's a bit hackish to raise from here relying on being called for
990
# both tests but this avoid overriding test_resolve_taking_other
992
"ParentLoop doesn't carry enough info to resolve --take-other")
993
_assert_conflict = assertParentLoop
996
class TestResolveNonDirectoryParent(TestResolveConflicts):
1004
$ bzr commit -m 'Create trunk' -q
1005
$ echo "Boing" >foo/bar
1006
$ bzr add -q foo/bar
1007
$ bzr commit -q -m 'Add foo/bar'
1008
$ bzr branch -q . -r 1 ../branch
1012
$ bzr commit -q -m 'foo is now a file'
1013
$ bzr merge ../trunk
1015
2>RK foo => foo.new/
1016
# FIXME: The message is misleading, foo.new *is* a directory when the message
1017
# is displayed -- vila 090916
1018
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1019
2>1 conflicts encountered.
1022
def test_take_this(self):
1024
$ bzr rm -q foo.new --no-backup
1025
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1026
# aside ? -- vila 090916
1028
$ bzr resolve foo.new
1029
2>1 conflict resolved, 0 remaining
1030
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1033
def test_take_other(self):
1035
$ bzr rm -q foo --no-backup
1036
$ bzr mv -q foo.new foo
1038
2>1 conflict resolved, 0 remaining
1039
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1042
def test_resolve_taking_this(self):
1044
$ bzr resolve --take-this foo.new
1046
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1049
def test_resolve_taking_other(self):
1051
$ bzr resolve --take-other foo.new
1053
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
1057
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1059
def test_bug_430129(self):
1060
# This is nearly like TestResolveNonDirectoryParent but with branch and
1061
# trunk switched. As such it should certainly produce the same
1063
self.assertRaises(errors.MalformedTransform,
1070
$ bzr commit -m 'Create trunk' -q
1073
$ bzr commit -m 'foo is now a file' -q
1074
$ bzr branch -q . -r 1 ../branch -q
1076
$ echo "Boing" >foo/bar
1077
$ bzr add -q foo/bar -q
1078
$ bzr commit -m 'Add foo/bar' -q
1079
$ bzr merge ../trunk
1080
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1084
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1086
def test_bug_805809(self):
1089
Created a standalone tree (format: 2a)
1094
$ bzr commit -m 'create file on trunk'
1095
2>Committing to: .../trunk/
1097
2>Committed revision 1.
1098
# Create a debian branch based on trunk
1100
$ bzr branch trunk -r 1 debian
1101
2>Branched 1 revision.
1108
$ bzr commit -m 'rename file to dir/file for debian'
1109
2>Committing to: .../debian/
1111
2>renamed file => dir/file
1112
2>Committed revision 2.
1113
# Create an experimental branch with a new root-id
1115
$ bzr init experimental
1116
Created a standalone tree (format: 2a)
1118
# Work around merging into empty branch not being supported
1119
# (http://pad.lv/308562)
1120
$ echo something >not-empty
1123
$ bzr commit -m 'Add some content in experimental'
1124
2>Committing to: .../experimental/
1126
2>Committed revision 1.
1127
# merge debian even without a common ancestor
1128
$ bzr merge ../debian -r0..2
1131
2>All changes applied successfully.
1132
$ bzr commit -m 'merging debian into experimental'
1133
2>Committing to: .../experimental/
1136
2>Committed revision 2.
1137
# Create an ubuntu branch with yet another root-id
1140
Created a standalone tree (format: 2a)
1142
# Work around merging into empty branch not being supported
1143
# (http://pad.lv/308562)
1144
$ echo something >not-empty-ubuntu
1146
adding not-empty-ubuntu
1147
$ bzr commit -m 'Add some content in experimental'
1148
2>Committing to: .../ubuntu/
1149
2>added not-empty-ubuntu
1150
2>Committed revision 1.
1152
$ bzr merge ../debian -r0..2
1155
2>All changes applied successfully.
1156
$ bzr commit -m 'merging debian'
1157
2>Committing to: .../ubuntu/
1160
2>Committed revision 2.
1161
# Now try to merge experimental
1162
$ bzr merge ../experimental
1164
2>Path conflict: dir / dir
1165
2>1 conflicts encountered.
1169
class TestResolveActionOption(tests.TestCase):
1172
super(TestResolveActionOption, self).setUp()
1173
self.options = [conflicts.ResolveActionOption()]
1174
self.parser = option.get_optparser(dict((o.name, o)
1175
for o in self.options))
1177
def parse(self, args):
1178
return self.parser.parse_args(args)
1180
def test_unknown_action(self):
1181
self.assertRaises(errors.BadOptionValue,
1182
self.parse, ['--action', 'take-me-to-the-moon'])
1184
def test_done(self):
1185
opts, args = self.parse(['--action', 'done'])
1186
self.assertEqual({'action':'done'}, opts)
1188
def test_take_this(self):
1189
opts, args = self.parse(['--action', 'take-this'])
1190
self.assertEqual({'action': 'take_this'}, opts)
1191
opts, args = self.parse(['--take-this'])
1192
self.assertEqual({'action': 'take_this'}, opts)
1194
def test_take_other(self):
1195
opts, args = self.parse(['--action', 'take-other'])
1196
self.assertEqual({'action': 'take_other'}, opts)
1197
opts, args = self.parse(['--take-other'])
1198
self.assertEqual({'action': 'take_other'}, opts)