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 ..bzr import conflicts as bzr_conflicts
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?)
46
bzr_conflicts.MissingParent('Not deleting', u'p\xe5thg', b'\xc3\xaedg'),
47
bzr_conflicts.ContentsConflict(u'p\xe5tha', None, b'\xc3\xaeda'),
48
bzr_conflicts.TextConflict(u'p\xe5tha'),
49
bzr_conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', b'\xc3\xaedb'),
50
bzr_conflicts.DuplicateID('Unversioned existing file',
51
u'p\xe5thc', u'p\xe5thc2',
52
b'\xc3\xaedc', b'\xc3\xaedc'),
53
bzr_conflicts.DuplicateEntry('Moved existing file to',
54
u'p\xe5thdd.moved', u'p\xe5thd',
56
bzr_conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
57
None, b'\xc3\xaed2e'),
58
bzr_conflicts.UnversionedParent('Versioned directory',
59
u'p\xe5thf', b'\xc3\xaedf'),
60
bzr_conflicts.NonDirectoryParent('Created directory',
61
u'p\xe5thg', b'\xc3\xaedg'),
65
def vary_by_conflicts():
66
for conflict in example_conflicts:
67
yield (conflict.__class__.__name__, {"conflict": conflict})
70
class TestConflicts(tests.TestCaseWithTransport):
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([bzr_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 = bzr_conflicts.ContentsConflict('foo')
93
bar = bzr_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 = bzr_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 = bzr_conflicts.PathConflict('qux', 'foo/baz')
107
tree_conflicts = clist([qux])
109
check_select(tree_conflicts, clist(),
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 = [bzr_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__)
140
class TestConflictList(tests.TestCase):
142
def test_stanzas_roundtrip(self):
143
stanzas_iter = bzr_conflicts.ConflictList(example_conflicts).to_stanzas()
144
processed = bzr_conflicts.ConflictList.from_stanzas(stanzas_iter)
145
self.assertEqual(example_conflicts, processed)
147
def test_stringification(self):
149
bzr_conflicts.ConflictList(example_conflicts).to_strings(),
151
self.assertEqual(text, str(o))
154
# FIXME: The shell-like tests should be converted to real whitebox tests... or
155
# moved to a blackbox module -- vila 20100205
157
# FIXME: test missing for multiple conflicts
159
# FIXME: Tests missing for DuplicateID conflict type
160
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
162
preamble = None # The setup script set by daughter classes
165
super(TestResolveConflicts, self).setUp()
166
self.run_script(self.preamble)
169
def mirror_scenarios(base_scenarios):
170
"""Return a list of mirrored scenarios.
172
Each scenario in base_scenarios is duplicated switching the roles of 'this'
176
for common, (lname, ldict), (rname, rdict) in base_scenarios:
177
a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
178
[(rname, dict(_other=rdict))])
179
b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
180
[(lname, dict(_other=ldict))])
181
# Inject the common parameters in all scenarios
182
for name, d in a + b:
184
scenarios.extend(a + b)
188
# FIXME: Get rid of parametrized (in the class name) once we delete
189
# TestResolveConflicts -- vila 20100308
190
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
191
"""This class provides a base to test single conflict resolution.
193
Since all conflict objects are created with specific semantics for their
194
attributes, each class should implement the necessary functions and
195
attributes described below.
197
Each class should define the scenarios that create the expected (single)
200
Each scenario describes:
201
* how to create 'base' tree (and revision)
202
* how to create 'left' tree (and revision, parent rev 'base')
203
* how to create 'right' tree (and revision, parent rev 'base')
204
* how to check that changes in 'base'->'left' have been taken
205
* how to check that changes in 'base'->'right' have been taken
207
From each base scenario, we generate two concrete scenarios where:
208
* this=left, other=right
209
* this=right, other=left
211
Then the test case verifies each concrete scenario by:
212
* creating a branch containing the 'base', 'this' and 'other' revisions
213
* creating a working tree for the 'this' revision
214
* performing the merge of 'other' into 'this'
215
* verifying the expected conflict was generated
216
* resolving with --take-this or --take-other, and running the corresponding
217
checks (for either 'base'->'this', or 'base'->'other')
219
:cvar _conflict_type: The expected class of the generated conflict.
221
:cvar _assert_conflict: A method receiving the working tree and the
222
conflict object and checking its attributes.
224
:cvar _base_actions: The branchbuilder actions to create the 'base'
227
:cvar _this: The dict related to 'base' -> 'this'. It contains at least:
228
* 'actions': The branchbuilder actions to create the 'this'
230
* 'check': how to check the changes after resolution with --take-this.
232
:cvar _other: The dict related to 'base' -> 'other'. It contains at least:
233
* 'actions': The branchbuilder actions to create the 'other'
235
* 'check': how to check the changes after resolution with --take-other.
238
# Set by daughter classes
239
_conflict_type = None
240
_assert_conflict = None
248
"""The scenario list for the conflict type defined by the class.
250
Each scenario is of the form:
251
(common, (left_name, left_dict), (right_name, right_dict))
255
* left_name and right_name are the scenario names that will be combined
257
* left_dict and right_dict are the attributes specific to each half of
258
the scenario. They should include at least 'actions' and 'check' and
259
will be available as '_this' and '_other' test instance attributes.
261
Daughters classes are free to add their specific attributes as they see
262
fit in any of the three dicts.
264
This is a class method so that load_tests can find it.
266
'_base_actions' in the common dict, 'actions' and 'check' in the left
267
and right dicts use names that map to methods in the test classes. Some
268
prefixes are added to these names to get the correspong methods (see
269
_get_actions() and _get_check()). The motivation here is to avoid
270
collisions in the class namespace.
274
super(TestParametrizedResolveConflicts, self).setUp()
275
builder = self.make_branch_builder('trunk')
276
builder.start_series()
278
# Create an empty trunk
279
builder.build_snapshot(None, [
280
('add', (u'', b'root-id', 'directory', ''))],
281
revision_id=b'start')
282
# Add a minimal base content
283
base_actions = self._get_actions(self._base_actions)()
284
builder.build_snapshot([b'start'], base_actions, revision_id=b'base')
285
# Modify the base content in branch
286
actions_other = self._get_actions(self._other['actions'])()
287
builder.build_snapshot([b'base'], actions_other, revision_id=b'other')
288
# Modify the base content in trunk
289
actions_this = self._get_actions(self._this['actions'])()
290
builder.build_snapshot([b'base'], actions_this, revision_id=b'this')
291
# builder.get_branch() tip is now 'this'
293
builder.finish_series()
294
self.builder = builder
296
def _get_actions(self, name):
297
return getattr(self, 'do_%s' % name)
299
def _get_check(self, name):
300
return getattr(self, 'check_%s' % name)
302
def _merge_other_into_this(self):
303
b = self.builder.get_branch()
304
wt = b.controldir.sprout('branch').open_workingtree()
305
wt.merge_from_branch(b, b'other')
308
def assertConflict(self, wt):
309
confs = wt.conflicts()
310
self.assertLength(1, confs)
312
self.assertIsInstance(c, self._conflict_type)
313
self._assert_conflict(wt, c)
315
def _get_resolve_path_arg(self, wt, action):
316
raise NotImplementedError(self._get_resolve_path_arg)
318
def check_resolved(self, wt, action):
319
path = self._get_resolve_path_arg(wt, action)
320
conflicts.resolve(wt, [path], action=action)
321
# Check that we don't have any conflicts nor unknown left
322
self.assertLength(0, wt.conflicts())
323
self.assertLength(0, list(wt.unknowns()))
325
def test_resolve_taking_this(self):
326
wt = self._merge_other_into_this()
327
self.assertConflict(wt)
328
self.check_resolved(wt, 'take_this')
329
check_this = self._get_check(self._this['check'])
332
def test_resolve_taking_other(self):
333
wt = self._merge_other_into_this()
334
self.assertConflict(wt)
335
self.check_resolved(wt, 'take_other')
336
check_other = self._get_check(self._other['check'])
340
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
342
_conflict_type = bzr_conflicts.TextConflict
344
# Set by the scenarios
345
# path and file-id for the file involved in the conflict
349
scenarios = mirror_scenarios(
351
# File modified on both sides
352
(dict(_base_actions='create_file',
353
_path='file', _file_id=b'file-id'),
355
dict(actions='modify_file_A', check='file_has_content_A')),
357
dict(actions='modify_file_B', check='file_has_content_B')),),
358
# File modified on both sides in dir
359
(dict(_base_actions='create_file_in_dir',
360
_path='dir/file', _file_id=b'file-id'),
361
('filed_modified_A_in_dir',
362
dict(actions='modify_file_A_in_dir',
363
check='file_in_dir_has_content_A')),
365
dict(actions='modify_file_B_in_dir',
366
check='file_in_dir_has_content_B')),),
369
def do_create_file(self, path='file'):
370
return [('add', (path, b'file-id', 'file', b'trunk content\n'))]
372
def do_modify_file_A(self):
373
return [('modify', ('file', b'trunk content\nfeature A\n'))]
375
def do_modify_file_B(self):
376
return [('modify', ('file', b'trunk content\nfeature B\n'))]
378
def do_modify_file_A_in_dir(self):
379
return [('modify', ('dir/file', b'trunk content\nfeature A\n'))]
381
def do_modify_file_B_in_dir(self):
382
return [('modify', ('dir/file', b'trunk content\nfeature B\n'))]
384
def check_file_has_content_A(self, path='file'):
385
self.assertFileEqual(b'trunk content\nfeature A\n',
386
osutils.pathjoin('branch', path))
388
def check_file_has_content_B(self, path='file'):
389
self.assertFileEqual(b'trunk content\nfeature B\n',
390
osutils.pathjoin('branch', path))
392
def do_create_file_in_dir(self):
393
return [('add', ('dir', b'dir-id', 'directory', '')),
394
] + self.do_create_file('dir/file')
396
def check_file_in_dir_has_content_A(self):
397
self.check_file_has_content_A('dir/file')
399
def check_file_in_dir_has_content_B(self):
400
self.check_file_has_content_B('dir/file')
402
def _get_resolve_path_arg(self, wt, action):
405
def assertTextConflict(self, wt, c):
406
self.assertEqual(self._file_id, c.file_id)
407
self.assertEqual(self._path, c.path)
408
_assert_conflict = assertTextConflict
411
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
413
_conflict_type = bzr_conflicts.ContentsConflict
415
# Set by the scenarios
416
# path and file-id for the file involved in the conflict
420
scenarios = mirror_scenarios(
422
# File modified/deleted
423
(dict(_base_actions='create_file',
424
_path='file', _file_id=b'file-id'),
426
dict(actions='modify_file', check='file_has_more_content')),
428
dict(actions='delete_file', check='file_doesnt_exist')),),
429
# File renamed-modified/deleted
430
(dict(_base_actions='create_file',
431
_path='new-file', _file_id=b'file-id'),
432
('file_renamed_and_modified',
433
dict(actions='modify_and_rename_file',
434
check='file_renamed_and_more_content')),
436
dict(actions='delete_file', check='file_doesnt_exist')),),
437
# File modified/deleted in dir
438
(dict(_base_actions='create_file_in_dir',
439
_path='dir/file', _file_id=b'file-id'),
440
('file_modified_in_dir',
441
dict(actions='modify_file_in_dir',
442
check='file_in_dir_has_more_content')),
443
('file_deleted_in_dir',
444
dict(actions='delete_file_in_dir',
445
check='file_in_dir_doesnt_exist')),),
448
def do_create_file(self):
449
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
451
def do_modify_file(self):
452
return [('modify', ('file', b'trunk content\nmore content\n'))]
454
def do_modify_and_rename_file(self):
455
return [('modify', ('new-file', b'trunk content\nmore content\n')),
456
('rename', ('file', 'new-file'))]
458
def check_file_has_more_content(self):
459
self.assertFileEqual(b'trunk content\nmore content\n', 'branch/file')
461
def check_file_renamed_and_more_content(self):
462
self.assertFileEqual(
463
b'trunk content\nmore content\n', 'branch/new-file')
465
def do_delete_file(self):
466
return [('unversion', 'file')]
468
def do_delete_file_in_dir(self):
469
return [('unversion', 'dir/file')]
471
def check_file_doesnt_exist(self):
472
self.assertPathDoesNotExist('branch/file')
474
def do_create_file_in_dir(self):
475
return [('add', ('dir', b'dir-id', 'directory', '')),
476
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
478
def do_modify_file_in_dir(self):
479
return [('modify', ('dir/file', b'trunk content\nmore content\n'))]
481
def check_file_in_dir_has_more_content(self):
482
self.assertFileEqual(
483
b'trunk content\nmore content\n', 'branch/dir/file')
485
def check_file_in_dir_doesnt_exist(self):
486
self.assertPathDoesNotExist('branch/dir/file')
488
def _get_resolve_path_arg(self, wt, action):
491
def assertContentsConflict(self, wt, c):
492
self.assertEqual(self._file_id, c.file_id)
493
self.assertEqual(self._path, c.path)
494
_assert_conflict = assertContentsConflict
497
class TestResolvePathConflict(TestParametrizedResolveConflicts):
499
_conflict_type = bzr_conflicts.PathConflict
501
def do_nothing(self):
504
# Each side dict additionally defines:
505
# - path path involved (can be '<deleted>')
507
scenarios = mirror_scenarios(
509
# File renamed/deleted
510
(dict(_base_actions='create_file'),
512
dict(actions='rename_file', check='file_renamed',
513
path='new-file', file_id=b'file-id')),
515
dict(actions='delete_file', check='file_doesnt_exist',
516
# PathConflicts deletion handling requires a special
518
path='<deleted>', file_id=b'file-id')),),
519
# File renamed/deleted in dir
520
(dict(_base_actions='create_file_in_dir'),
521
('file_renamed_in_dir',
522
dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
523
path='dir/new-file', file_id=b'file-id')),
525
dict(actions='delete_file_in_dir', check='file_in_dir_doesnt_exist',
526
# PathConflicts deletion handling requires a special
528
path='<deleted>', file_id=b'file-id')),),
529
# File renamed/renamed differently
530
(dict(_base_actions='create_file'),
532
dict(actions='rename_file', check='file_renamed',
533
path='new-file', file_id=b'file-id')),
535
dict(actions='rename_file2', check='file_renamed2',
536
path='new-file2', file_id=b'file-id')),),
537
# Dir renamed/deleted
538
(dict(_base_actions='create_dir'),
540
dict(actions='rename_dir', check='dir_renamed',
541
path='new-dir', file_id=b'dir-id')),
543
dict(actions='delete_dir', check='dir_doesnt_exist',
544
# PathConflicts deletion handling requires a special
546
path='<deleted>', file_id=b'dir-id')),),
547
# Dir renamed/renamed differently
548
(dict(_base_actions='create_dir'),
550
dict(actions='rename_dir', check='dir_renamed',
551
path='new-dir', file_id=b'dir-id')),
553
dict(actions='rename_dir2', check='dir_renamed2',
554
path='new-dir2', file_id=b'dir-id')),),
557
def do_create_file(self):
558
return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
560
def do_create_dir(self):
561
return [('add', ('dir', b'dir-id', 'directory', ''))]
563
def do_rename_file(self):
564
return [('rename', ('file', 'new-file'))]
566
def check_file_renamed(self):
567
self.assertPathDoesNotExist('branch/file')
568
self.assertPathExists('branch/new-file')
570
def do_rename_file2(self):
571
return [('rename', ('file', 'new-file2'))]
573
def check_file_renamed2(self):
574
self.assertPathDoesNotExist('branch/file')
575
self.assertPathExists('branch/new-file2')
577
def do_rename_dir(self):
578
return [('rename', ('dir', 'new-dir'))]
580
def check_dir_renamed(self):
581
self.assertPathDoesNotExist('branch/dir')
582
self.assertPathExists('branch/new-dir')
584
def do_rename_dir2(self):
585
return [('rename', ('dir', 'new-dir2'))]
587
def check_dir_renamed2(self):
588
self.assertPathDoesNotExist('branch/dir')
589
self.assertPathExists('branch/new-dir2')
591
def do_delete_file(self):
592
return [('unversion', 'file')]
594
def do_delete_file_in_dir(self):
595
return [('unversion', 'dir/file')]
597
def check_file_doesnt_exist(self):
598
self.assertPathDoesNotExist('branch/file')
600
def do_delete_dir(self):
601
return [('unversion', 'dir')]
603
def check_dir_doesnt_exist(self):
604
self.assertPathDoesNotExist('branch/dir')
606
def do_create_file_in_dir(self):
607
return [('add', ('dir', b'dir-id', 'directory', '')),
608
('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
610
def do_rename_file_in_dir(self):
611
return [('rename', ('dir/file', 'dir/new-file'))]
613
def check_file_in_dir_renamed(self):
614
self.assertPathDoesNotExist('branch/dir/file')
615
self.assertPathExists('branch/dir/new-file')
617
def check_file_in_dir_doesnt_exist(self):
618
self.assertPathDoesNotExist('branch/dir/file')
620
def _get_resolve_path_arg(self, wt, action):
621
tpath = self._this['path']
622
opath = self._other['path']
623
if tpath == '<deleted>':
629
def assertPathConflict(self, wt, c):
630
tpath = self._this['path']
631
tfile_id = self._this['file_id']
632
opath = self._other['path']
633
ofile_id = self._other['file_id']
634
self.assertEqual(tfile_id, ofile_id) # Sanity check
635
self.assertEqual(tfile_id, c.file_id)
636
self.assertEqual(tpath, c.path)
637
self.assertEqual(opath, c.conflict_path)
638
_assert_conflict = assertPathConflict
641
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
642
"""Same as TestResolvePathConflict but a specific conflict object.
645
def assertPathConflict(self, c):
646
# We create a conflict object as it was created before the fix and
647
# inject it into the working tree, the test will exercise the
648
# compatibility code.
649
old_c = bzr_conflicts.PathConflict('<deleted>', self._item_path,
651
wt.set_conflicts([old_c])
654
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
656
_conflict_type = bzr_conflicts.DuplicateEntry
658
scenarios = mirror_scenarios(
660
# File created with different file-ids
661
(dict(_base_actions='nothing'),
663
dict(actions='create_file_a', check='file_content_a',
664
path='file', file_id=b'file-a-id')),
666
dict(actions='create_file_b', check='file_content_b',
667
path='file', file_id=b'file-b-id')),),
668
# File created with different file-ids but deleted on one side
669
(dict(_base_actions='create_file_a'),
671
dict(actions='replace_file_a_by_b', check='file_content_b',
672
path='file', file_id=b'file-b-id')),
674
dict(actions='modify_file_a', check='file_new_content',
675
path='file', file_id=b'file-a-id')),),
678
def do_nothing(self):
681
def do_create_file_a(self):
682
return [('add', ('file', b'file-a-id', 'file', b'file a content\n'))]
684
def check_file_content_a(self):
685
self.assertFileEqual(b'file a content\n', 'branch/file')
687
def do_create_file_b(self):
688
return [('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
690
def check_file_content_b(self):
691
self.assertFileEqual(b'file b content\n', 'branch/file')
693
def do_replace_file_a_by_b(self):
694
return [('unversion', 'file'),
695
('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
697
def do_modify_file_a(self):
698
return [('modify', ('file', b'new content\n'))]
700
def check_file_new_content(self):
701
self.assertFileEqual(b'new content\n', 'branch/file')
703
def _get_resolve_path_arg(self, wt, action):
704
return self._this['path']
706
def assertDuplicateEntry(self, wt, c):
707
tpath = self._this['path']
708
tfile_id = self._this['file_id']
709
opath = self._other['path']
710
ofile_id = self._other['file_id']
711
self.assertEqual(tpath, opath) # Sanity check
712
self.assertEqual(tfile_id, c.file_id)
713
self.assertEqual(tpath + '.moved', c.path)
714
self.assertEqual(tpath, c.conflict_path)
715
_assert_conflict = assertDuplicateEntry
718
class TestResolveUnversionedParent(TestResolveConflicts):
720
# FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
722
# FIXME: While this *creates* UnversionedParent conflicts, this really only
723
# tests MissingParent resolution :-/
730
$ brz commit -m 'Create trunk' -q
731
$ echo 'trunk content' >dir/file
732
$ brz add -q dir/file
733
$ brz commit -q -m 'Add dir/file in trunk'
734
$ brz branch -q . -r 1 ../branch
737
$ brz commit -q -m 'Remove dir in branch'
741
2>Conflict adding files to dir. Created directory.
742
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
743
2>2 conflicts encountered.
746
def test_take_this(self):
748
$ brz rm -q dir --no-backup
750
2>2 conflicts resolved, 0 remaining
751
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
754
def test_take_other(self):
757
2>2 conflicts resolved, 0 remaining
758
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
762
class TestResolveMissingParent(TestResolveConflicts):
769
$ echo 'trunk content' >dir/file
771
$ brz commit -m 'Create trunk' -q
772
$ echo 'trunk content' >dir/file2
773
$ brz add -q dir/file2
774
$ brz commit -q -m 'Add dir/file2 in branch'
775
$ brz branch -q . -r 1 ../branch
777
$ brz rm -q dir/file --no-backup
779
$ brz commit -q -m 'Remove dir/file'
783
2>Conflict adding files to dir. Created directory.
784
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
785
2>2 conflicts encountered.
788
def test_keep_them_all(self):
791
2>2 conflicts resolved, 0 remaining
792
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
795
def test_adopt_child(self):
797
$ brz mv -q dir/file2 file2
798
$ brz rm -q dir --no-backup
800
2>2 conflicts resolved, 0 remaining
801
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
804
def test_kill_them_all(self):
806
$ brz rm -q dir --no-backup
808
2>2 conflicts resolved, 0 remaining
809
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
812
def test_resolve_taking_this(self):
814
$ brz resolve --take-this dir
816
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
819
def test_resolve_taking_other(self):
821
$ brz resolve --take-other dir
823
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
827
class TestResolveDeletingParent(TestResolveConflicts):
834
$ echo 'trunk content' >dir/file
836
$ brz commit -m 'Create trunk' -q
837
$ brz rm -q dir/file --no-backup
838
$ brz rm -q dir --no-backup
839
$ brz commit -q -m 'Remove dir/file'
840
$ brz branch -q . -r 1 ../branch
842
$ echo 'branch content' >dir/file2
843
$ brz add -q dir/file2
844
$ brz commit -q -m 'Add dir/file2 in branch'
847
2>Conflict: can't delete dir because it is not empty. Not deleting.
848
2>Conflict because dir is not versioned, but has versioned children. Versioned directory.
849
2>2 conflicts encountered.
852
def test_keep_them_all(self):
855
2>2 conflicts resolved, 0 remaining
856
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
859
def test_adopt_child(self):
861
$ brz mv -q dir/file2 file2
862
$ brz rm -q dir --no-backup
864
2>2 conflicts resolved, 0 remaining
865
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
868
def test_kill_them_all(self):
870
$ brz rm -q dir --no-backup
872
2>2 conflicts resolved, 0 remaining
873
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
876
def test_resolve_taking_this(self):
878
$ brz resolve --take-this dir
879
2>2 conflicts resolved, 0 remaining
880
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
883
def test_resolve_taking_other(self):
885
$ brz resolve --take-other dir
888
2>2 conflicts resolved, 0 remaining
889
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
893
class TestResolveParentLoop(TestParametrizedResolveConflicts):
895
_conflict_type = bzr_conflicts.ParentLoop
900
# Each side dict additionally defines:
901
# - dir_id: the directory being moved
902
# - target_id: The target directory
903
# - xfail: whether the test is expected to fail if the action is
904
# involved as 'other'
905
scenarios = mirror_scenarios(
907
# Dirs moved into each other
908
(dict(_base_actions='create_dir1_dir2'),
910
dict(actions='move_dir1_into_dir2', check='dir1_moved',
911
dir_id=b'dir1-id', target_id=b'dir2-id', xfail=False)),
913
dict(actions='move_dir2_into_dir1', check='dir2_moved',
914
dir_id=b'dir2-id', target_id=b'dir1-id', xfail=False))),
915
# Subdirs moved into each other
916
(dict(_base_actions='create_dir1_4'),
918
dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
919
dir_id=b'dir1-id', target_id=b'dir4-id', xfail=True)),
921
dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
922
dir_id=b'dir3-id', target_id=b'dir2-id', xfail=True))),
925
def do_create_dir1_dir2(self):
926
return [('add', ('dir1', b'dir1-id', 'directory', '')),
927
('add', ('dir2', b'dir2-id', 'directory', '')), ]
929
def do_move_dir1_into_dir2(self):
930
return [('rename', ('dir1', 'dir2/dir1'))]
932
def check_dir1_moved(self):
933
self.assertPathDoesNotExist('branch/dir1')
934
self.assertPathExists('branch/dir2/dir1')
936
def do_move_dir2_into_dir1(self):
937
return [('rename', ('dir2', 'dir1/dir2'))]
939
def check_dir2_moved(self):
940
self.assertPathDoesNotExist('branch/dir2')
941
self.assertPathExists('branch/dir1/dir2')
943
def do_create_dir1_4(self):
944
return [('add', ('dir1', b'dir1-id', 'directory', '')),
945
('add', ('dir1/dir2', b'dir2-id', 'directory', '')),
946
('add', ('dir3', b'dir3-id', 'directory', '')),
947
('add', ('dir3/dir4', b'dir4-id', 'directory', '')), ]
949
def do_move_dir1_into_dir4(self):
950
return [('rename', ('dir1', 'dir3/dir4/dir1'))]
952
def check_dir1_2_moved(self):
953
self.assertPathDoesNotExist('branch/dir1')
954
self.assertPathExists('branch/dir3/dir4/dir1')
955
self.assertPathExists('branch/dir3/dir4/dir1/dir2')
957
def do_move_dir3_into_dir2(self):
958
return [('rename', ('dir3', 'dir1/dir2/dir3'))]
960
def check_dir3_4_moved(self):
961
self.assertPathDoesNotExist('branch/dir3')
962
self.assertPathExists('branch/dir1/dir2/dir3')
963
self.assertPathExists('branch/dir1/dir2/dir3/dir4')
965
def _get_resolve_path_arg(self, wt, action):
966
# ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
967
# But since <path> doesn't exist in the working tree, we need to use
968
# <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
969
return wt.id2path(self._other['dir_id'])
971
def assertParentLoop(self, wt, c):
972
self.assertEqual(self._other['dir_id'], c.file_id)
973
self.assertEqual(self._other['target_id'], c.conflict_file_id)
974
# The conflict paths are irrelevant (they are deterministic but not
975
# worth checking since they don't provide the needed information
977
if self._other['xfail']:
978
# It's a bit hackish to raise from here relying on being called for
979
# both tests but this avoid overriding test_resolve_taking_other
981
"ParentLoop doesn't carry enough info to resolve --take-other")
982
_assert_conflict = assertParentLoop
985
class TestResolveNonDirectoryParent(TestResolveConflicts):
993
$ brz commit -m 'Create trunk' -q
994
$ echo "Boing" >foo/bar
996
$ brz commit -q -m 'Add foo/bar'
997
$ brz branch -q . -r 1 ../branch
1001
$ brz commit -q -m 'foo is now a file'
1002
$ brz merge ../trunk
1003
2>RK foo => foo.new/
1005
# FIXME: The message is misleading, foo.new *is* a directory when the message
1006
# is displayed -- vila 090916
1007
2>Conflict: foo.new is not a directory, but has files in it. Created directory.
1008
2>1 conflicts encountered.
1011
def test_take_this(self):
1013
$ brz rm -q foo.new --no-backup
1014
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
1015
# aside ? -- vila 090916
1017
$ brz resolve foo.new
1018
2>1 conflict resolved, 0 remaining
1019
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1022
def test_take_other(self):
1024
$ brz rm -q foo --no-backup
1025
$ brz mv -q foo.new foo
1027
2>1 conflict resolved, 0 remaining
1028
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1031
def test_resolve_taking_this(self):
1033
$ brz resolve --take-this foo.new
1035
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1038
def test_resolve_taking_other(self):
1040
$ brz resolve --take-other foo.new
1042
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
1046
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
1048
def test_bug_430129(self):
1049
# This is nearly like TestResolveNonDirectoryParent but with branch and
1050
# trunk switched. As such it should certainly produce the same
1052
self.assertRaises(transform.MalformedTransform,
1053
self.run_script, """
1059
$ brz commit -m 'Create trunk' -q
1062
$ brz commit -m 'foo is now a file' -q
1063
$ brz branch -q . -r 1 ../branch -q
1065
$ echo "Boing" >foo/bar
1066
$ brz add -q foo/bar -q
1067
$ brz commit -m 'Add foo/bar' -q
1068
$ brz merge ../trunk
1069
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
1073
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
1075
def test_bug_805809(self):
1078
Created a standalone tree (format: 2a)
1083
$ brz commit -m 'create file on trunk'
1084
2>Committing to: .../trunk/
1086
2>Committed revision 1.
1087
# Create a debian branch based on trunk
1089
$ brz branch trunk -r 1 debian
1090
2>Branched 1 revision.
1097
$ brz commit -m 'rename file to dir/file for debian'
1098
2>Committing to: .../debian/
1100
2>renamed file => dir/file
1101
2>Committed revision 2.
1102
# Create an experimental branch with a new root-id
1104
$ brz init experimental
1105
Created a standalone tree (format: 2a)
1107
# Work around merging into empty branch not being supported
1108
# (http://pad.lv/308562)
1109
$ echo something >not-empty
1112
$ brz commit -m 'Add some content in experimental'
1113
2>Committing to: .../experimental/
1115
2>Committed revision 1.
1116
# merge debian even without a common ancestor
1117
$ brz merge ../debian -r0..2
1120
2>All changes applied successfully.
1121
$ brz commit -m 'merging debian into experimental'
1122
2>Committing to: .../experimental/
1125
2>Committed revision 2.
1126
# Create an ubuntu branch with yet another root-id
1129
Created a standalone tree (format: 2a)
1131
# Work around merging into empty branch not being supported
1132
# (http://pad.lv/308562)
1133
$ echo something >not-empty-ubuntu
1135
adding not-empty-ubuntu
1136
$ brz commit -m 'Add some content in experimental'
1137
2>Committing to: .../ubuntu/
1138
2>added not-empty-ubuntu
1139
2>Committed revision 1.
1141
$ brz merge ../debian -r0..2
1144
2>All changes applied successfully.
1145
$ brz commit -m 'merging debian'
1146
2>Committing to: .../ubuntu/
1149
2>Committed revision 2.
1150
# Now try to merge experimental
1151
$ brz merge ../experimental
1153
2>Path conflict: dir / dir
1154
2>1 conflicts encountered.
1158
class TestResolveActionOption(tests.TestCase):
1161
super(TestResolveActionOption, self).setUp()
1162
self.options = [conflicts.ResolveActionOption()]
1163
self.parser = option.get_optparser(self.options)
1165
def parse(self, args):
1166
return self.parser.parse_args(args)
1168
def test_unknown_action(self):
1169
self.assertRaises(option.BadOptionValue,
1170
self.parse, ['--action', 'take-me-to-the-moon'])
1172
def test_done(self):
1173
opts, args = self.parse(['--action', 'done'])
1174
self.assertEqual({'action': 'done'}, opts)
1176
def test_take_this(self):
1177
opts, args = self.parse(['--action', 'take-this'])
1178
self.assertEqual({'action': 'take_this'}, opts)
1179
opts, args = self.parse(['--take-this'])
1180
self.assertEqual({'action': 'take_this'}, opts)
1182
def test_take_other(self):
1183
opts, args = self.parse(['--action', 'take-other'])
1184
self.assertEqual({'action': 'take_other'}, opts)
1185
opts, args = self.parse(['--take-other'])
1186
self.assertEqual({'action': 'take_other'}, opts)