/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_conflicts.py

  • Committer: Martin
  • Date: 2018-11-16 19:10:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116191017-kyedz1qck0ovon3h
Remove lazy_regexp reset in bt.test_source

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
 
 
18
import os
 
19
 
 
20
from .. import (
 
21
    conflicts,
 
22
    errors,
 
23
    option,
 
24
    osutils,
 
25
    tests,
 
26
    )
 
27
from ..sixish import text_type
 
28
from . import (
 
29
    script,
 
30
    scenarios,
 
31
    )
 
32
 
 
33
 
 
34
load_tests = scenarios.load_tests_apply_scenarios
 
35
 
 
36
 
 
37
# TODO: Test commit with some added, and added-but-missing files
 
38
# RBC 20060124 is that not tested in test_commit.py ?
 
39
 
 
40
# The order of 'path' here is important - do not let it
 
41
# be a sorted list.
 
42
# u'\xe5' == a with circle
 
43
# '\xc3\xae' == u'\xee' == i with hat
 
44
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
 
45
example_conflicts = conflicts.ConflictList(
 
46
    [conflicts.MissingParent('Not deleting', u'p\xe5thg', b'\xc3\xaedg'),
 
47
     conflicts.ContentsConflict(u'p\xe5tha', None, b'\xc3\xaeda'),
 
48
     conflicts.TextConflict(u'p\xe5tha'),
 
49
     conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', b'\xc3\xaedb'),
 
50
     conflicts.DuplicateID('Unversioned existing file',
 
51
                           u'p\xe5thc', u'p\xe5thc2',
 
52
                           b'\xc3\xaedc', b'\xc3\xaedc'),
 
53
     conflicts.DuplicateEntry('Moved existing file to',
 
54
                              u'p\xe5thdd.moved', u'p\xe5thd',
 
55
                              b'\xc3\xaedd', None),
 
56
     conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
 
57
                          None, b'\xc3\xaed2e'),
 
58
     conflicts.UnversionedParent('Versioned directory',
 
59
                                 u'p\xe5thf', b'\xc3\xaedf'),
 
60
     conflicts.NonDirectoryParent('Created directory',
 
61
                                  u'p\xe5thg', b'\xc3\xaedg'),
 
62
     ])
 
63
 
 
64
 
 
65
def vary_by_conflicts():
 
66
    for conflict in example_conflicts:
 
67
        yield (conflict.__class__.__name__, {"conflict": conflict})
 
68
 
 
69
 
 
70
class TestConflicts(tests.TestCaseWithTransport):
 
71
 
 
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'),
 
77
                                  ])
 
78
        os.mkdir('hello.OTHER')
 
79
        tree.add('hello', b'q')
 
80
        l = conflicts.ConflictList([conflicts.TextConflict('hello')])
 
81
        l.remove_files(tree)
 
82
 
 
83
    def test_select_conflicts(self):
 
84
        tree = self.make_branch_and_tree('.')
 
85
        clist = conflicts.ConflictList
 
86
 
 
87
        def check_select(not_selected, selected, paths, **kwargs):
 
88
            self.assertEqual(
 
89
                (not_selected, selected),
 
90
                tree_conflicts.select_conflicts(tree, paths, **kwargs))
 
91
 
 
92
        foo = conflicts.ContentsConflict('foo')
 
93
        bar = conflicts.ContentsConflict('bar')
 
94
        tree_conflicts = clist([foo, bar])
 
95
 
 
96
        check_select(clist([bar]), clist([foo]), ['foo'])
 
97
        check_select(clist(), tree_conflicts,
 
98
                     [''], ignore_misses=True, recurse=True)
 
99
 
 
100
        foobaz = conflicts.ContentsConflict('foo/baz')
 
101
        tree_conflicts = clist([foobaz, bar])
 
102
 
 
103
        check_select(clist([bar]), clist([foobaz]),
 
104
                     ['foo'], ignore_misses=True, recurse=True)
 
105
 
 
106
        qux = conflicts.PathConflict('qux', 'foo/baz')
 
107
        tree_conflicts = clist([qux])
 
108
 
 
109
        check_select(clist(), tree_conflicts,
 
110
                     ['foo'], ignore_misses=True, recurse=True)
 
111
        check_select(tree_conflicts, clist(), ['foo'], ignore_misses=True)
 
112
 
 
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'])
 
117
 
 
118
        dirhello = conflicts.ConflictList(
 
119
            [conflicts.TextConflict('dir/hello')])
 
120
        tree.set_conflicts(dirhello)
 
121
 
 
122
        conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
 
123
        self.assertEqual(dirhello, tree.conflicts())
 
124
 
 
125
        conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
 
126
        self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
 
127
 
 
128
 
 
129
class TestPerConflict(tests.TestCase):
 
130
 
 
131
    scenarios = scenarios.multiply_scenarios(vary_by_conflicts())
 
132
 
 
133
    def test_stringification(self):
 
134
        text = text_type(self.conflict)
 
135
        self.assertContainsString(text, self.conflict.path)
 
136
        self.assertContainsString(text.lower(), "conflict")
 
137
        self.assertContainsString(repr(self.conflict),
 
138
                                  self.conflict.__class__.__name__)
 
139
 
 
140
    def test_stanza_roundtrip(self):
 
141
        p = self.conflict
 
142
        o = conflicts.Conflict.factory(**p.as_stanza().as_dict())
 
143
        self.assertEqual(o, p)
 
144
 
 
145
        self.assertIsInstance(o.path, text_type)
 
146
 
 
147
        if o.file_id is not None:
 
148
            self.assertIsInstance(o.file_id, bytes)
 
149
 
 
150
        conflict_path = getattr(o, 'conflict_path', None)
 
151
        if conflict_path is not None:
 
152
            self.assertIsInstance(conflict_path, text_type)
 
153
 
 
154
        conflict_file_id = getattr(o, 'conflict_file_id', None)
 
155
        if conflict_file_id is not None:
 
156
            self.assertIsInstance(conflict_file_id, bytes)
 
157
 
 
158
    def test_stanzification(self):
 
159
        stanza = self.conflict.as_stanza()
 
160
        if 'file_id' in stanza:
 
161
            # In Stanza form, the file_id has to be unicode.
 
162
            self.assertStartsWith(stanza['file_id'], u'\xeed')
 
163
        self.assertStartsWith(stanza['path'], u'p\xe5th')
 
164
        if 'conflict_path' in stanza:
 
165
            self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
 
166
        if 'conflict_file_id' in stanza:
 
167
            self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
 
168
 
 
169
 
 
170
class TestConflictList(tests.TestCase):
 
171
 
 
172
    def test_stanzas_roundtrip(self):
 
173
        stanzas_iter = example_conflicts.to_stanzas()
 
174
        processed = conflicts.ConflictList.from_stanzas(stanzas_iter)
 
175
        self.assertEqual(example_conflicts, processed)
 
176
 
 
177
    def test_stringification(self):
 
178
        for text, o in zip(example_conflicts.to_strings(), example_conflicts):
 
179
            self.assertEqual(text, text_type(o))
 
180
 
 
181
 
 
182
# FIXME: The shell-like tests should be converted to real whitebox tests... or
 
183
# moved to a blackbox module -- vila 20100205
 
184
 
 
185
# FIXME: test missing for multiple conflicts
 
186
 
 
187
# FIXME: Tests missing for DuplicateID conflict type
 
188
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
 
189
 
 
190
    preamble = None  # The setup script set by daughter classes
 
191
 
 
192
    def setUp(self):
 
193
        super(TestResolveConflicts, self).setUp()
 
194
        self.run_script(self.preamble)
 
195
 
 
196
 
 
197
def mirror_scenarios(base_scenarios):
 
198
    """Return a list of mirrored scenarios.
 
199
 
 
200
    Each scenario in base_scenarios is duplicated switching the roles of 'this'
 
201
    and 'other'
 
202
    """
 
203
    scenarios = []
 
204
    for common, (lname, ldict), (rname, rdict) in base_scenarios:
 
205
        a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
 
206
                                     [(rname, dict(_other=rdict))])
 
207
        b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
 
208
                                     [(lname, dict(_other=ldict))])
 
209
        # Inject the common parameters in all scenarios
 
210
        for name, d in a + b:
 
211
            d.update(common)
 
212
        scenarios.extend(a + b)
 
213
    return scenarios
 
214
 
 
215
 
 
216
# FIXME: Get rid of parametrized (in the class name) once we delete
 
217
# TestResolveConflicts -- vila 20100308
 
218
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
 
219
    """This class provides a base to test single conflict resolution.
 
220
 
 
221
    Since all conflict objects are created with specific semantics for their
 
222
    attributes, each class should implement the necessary functions and
 
223
    attributes described below.
 
224
 
 
225
    Each class should define the scenarios that create the expected (single)
 
226
    conflict.
 
227
 
 
228
    Each scenario describes:
 
229
    * how to create 'base' tree (and revision)
 
230
    * how to create 'left' tree (and revision, parent rev 'base')
 
231
    * how to create 'right' tree (and revision, parent rev 'base')
 
232
    * how to check that changes in 'base'->'left' have been taken
 
233
    * how to check that changes in 'base'->'right' have been taken
 
234
 
 
235
    From each base scenario, we generate two concrete scenarios where:
 
236
    * this=left, other=right
 
237
    * this=right, other=left
 
238
 
 
239
    Then the test case verifies each concrete scenario by:
 
240
    * creating a branch containing the 'base', 'this' and 'other' revisions
 
241
    * creating a working tree for the 'this' revision
 
242
    * performing the merge of 'other' into 'this'
 
243
    * verifying the expected conflict was generated
 
244
    * resolving with --take-this or --take-other, and running the corresponding
 
245
      checks (for either 'base'->'this', or 'base'->'other')
 
246
 
 
247
    :cvar _conflict_type: The expected class of the generated conflict.
 
248
 
 
249
    :cvar _assert_conflict: A method receiving the working tree and the
 
250
        conflict object and checking its attributes.
 
251
 
 
252
    :cvar _base_actions: The branchbuilder actions to create the 'base'
 
253
        revision.
 
254
 
 
255
    :cvar _this: The dict related to 'base' -> 'this'. It contains at least:
 
256
      * 'actions': The branchbuilder actions to create the 'this'
 
257
          revision.
 
258
      * 'check': how to check the changes after resolution with --take-this.
 
259
 
 
260
    :cvar _other: The dict related to 'base' -> 'other'. It contains at least:
 
261
      * 'actions': The branchbuilder actions to create the 'other'
 
262
          revision.
 
263
      * 'check': how to check the changes after resolution with --take-other.
 
264
    """
 
265
 
 
266
    # Set by daughter classes
 
267
    _conflict_type = None
 
268
    _assert_conflict = None
 
269
 
 
270
    # Set by load_tests
 
271
    _base_actions = None
 
272
    _this = None
 
273
    _other = None
 
274
 
 
275
    scenarios = []
 
276
    """The scenario list for the conflict type defined by the class.
 
277
 
 
278
    Each scenario is of the form:
 
279
    (common, (left_name, left_dict), (right_name, right_dict))
 
280
 
 
281
    * common is a dict
 
282
 
 
283
    * left_name and right_name are the scenario names that will be combined
 
284
 
 
285
    * left_dict and right_dict are the attributes specific to each half of
 
286
      the scenario. They should include at least 'actions' and 'check' and
 
287
      will be available as '_this' and '_other' test instance attributes.
 
288
 
 
289
    Daughters classes are free to add their specific attributes as they see
 
290
    fit in any of the three dicts.
 
291
 
 
292
    This is a class method so that load_tests can find it.
 
293
 
 
294
    '_base_actions' in the common dict, 'actions' and 'check' in the left
 
295
    and right dicts use names that map to methods in the test classes. Some
 
296
    prefixes are added to these names to get the correspong methods (see
 
297
    _get_actions() and _get_check()). The motivation here is to avoid
 
298
    collisions in the class namespace.
 
299
    """
 
300
 
 
301
    def setUp(self):
 
302
        super(TestParametrizedResolveConflicts, self).setUp()
 
303
        builder = self.make_branch_builder('trunk')
 
304
        builder.start_series()
 
305
 
 
306
        # Create an empty trunk
 
307
        builder.build_snapshot(None, [
 
308
            ('add', (u'', b'root-id', 'directory', ''))],
 
309
            revision_id=b'start')
 
310
        # Add a minimal base content
 
311
        base_actions = self._get_actions(self._base_actions)()
 
312
        builder.build_snapshot([b'start'], base_actions, revision_id=b'base')
 
313
        # Modify the base content in branch
 
314
        actions_other = self._get_actions(self._other['actions'])()
 
315
        builder.build_snapshot([b'base'], actions_other, revision_id=b'other')
 
316
        # Modify the base content in trunk
 
317
        actions_this = self._get_actions(self._this['actions'])()
 
318
        builder.build_snapshot([b'base'], actions_this, revision_id=b'this')
 
319
        # builder.get_branch() tip is now 'this'
 
320
 
 
321
        builder.finish_series()
 
322
        self.builder = builder
 
323
 
 
324
    def _get_actions(self, name):
 
325
        return getattr(self, 'do_%s' % name)
 
326
 
 
327
    def _get_check(self, name):
 
328
        return getattr(self, 'check_%s' % name)
 
329
 
 
330
    def _merge_other_into_this(self):
 
331
        b = self.builder.get_branch()
 
332
        wt = b.controldir.sprout('branch').open_workingtree()
 
333
        wt.merge_from_branch(b, b'other')
 
334
        return wt
 
335
 
 
336
    def assertConflict(self, wt):
 
337
        confs = wt.conflicts()
 
338
        self.assertLength(1, confs)
 
339
        c = confs[0]
 
340
        self.assertIsInstance(c, self._conflict_type)
 
341
        self._assert_conflict(wt, c)
 
342
 
 
343
    def _get_resolve_path_arg(self, wt, action):
 
344
        raise NotImplementedError(self._get_resolve_path_arg)
 
345
 
 
346
    def check_resolved(self, wt, action):
 
347
        path = self._get_resolve_path_arg(wt, action)
 
348
        conflicts.resolve(wt, [path], action=action)
 
349
        # Check that we don't have any conflicts nor unknown left
 
350
        self.assertLength(0, wt.conflicts())
 
351
        self.assertLength(0, list(wt.unknowns()))
 
352
 
 
353
    def test_resolve_taking_this(self):
 
354
        wt = self._merge_other_into_this()
 
355
        self.assertConflict(wt)
 
356
        self.check_resolved(wt, 'take_this')
 
357
        check_this = self._get_check(self._this['check'])
 
358
        check_this()
 
359
 
 
360
    def test_resolve_taking_other(self):
 
361
        wt = self._merge_other_into_this()
 
362
        self.assertConflict(wt)
 
363
        self.check_resolved(wt, 'take_other')
 
364
        check_other = self._get_check(self._other['check'])
 
365
        check_other()
 
366
 
 
367
 
 
368
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
 
369
 
 
370
    _conflict_type = conflicts.TextConflict
 
371
 
 
372
    # Set by the scenarios
 
373
    # path and file-id for the file involved in the conflict
 
374
    _path = None
 
375
    _file_id = None
 
376
 
 
377
    scenarios = mirror_scenarios(
 
378
        [
 
379
            # File modified on both sides
 
380
            (dict(_base_actions='create_file',
 
381
                  _path='file', _file_id=b'file-id'),
 
382
             ('filed_modified_A',
 
383
              dict(actions='modify_file_A', check='file_has_content_A')),
 
384
             ('file_modified_B',
 
385
              dict(actions='modify_file_B', check='file_has_content_B')),),
 
386
            # File modified on both sides in dir
 
387
            (dict(_base_actions='create_file_in_dir',
 
388
                  _path='dir/file', _file_id=b'file-id'),
 
389
             ('filed_modified_A_in_dir',
 
390
              dict(actions='modify_file_A_in_dir',
 
391
                   check='file_in_dir_has_content_A')),
 
392
             ('file_modified_B',
 
393
              dict(actions='modify_file_B_in_dir',
 
394
                   check='file_in_dir_has_content_B')),),
 
395
            ])
 
396
 
 
397
    def do_create_file(self, path='file'):
 
398
        return [('add', (path, b'file-id', 'file', b'trunk content\n'))]
 
399
 
 
400
    def do_modify_file_A(self):
 
401
        return [('modify', ('file', b'trunk content\nfeature A\n'))]
 
402
 
 
403
    def do_modify_file_B(self):
 
404
        return [('modify', ('file', b'trunk content\nfeature B\n'))]
 
405
 
 
406
    def do_modify_file_A_in_dir(self):
 
407
        return [('modify', ('dir/file', b'trunk content\nfeature A\n'))]
 
408
 
 
409
    def do_modify_file_B_in_dir(self):
 
410
        return [('modify', ('dir/file', b'trunk content\nfeature B\n'))]
 
411
 
 
412
    def check_file_has_content_A(self, path='file'):
 
413
        self.assertFileEqual(b'trunk content\nfeature A\n',
 
414
                             osutils.pathjoin('branch', path))
 
415
 
 
416
    def check_file_has_content_B(self, path='file'):
 
417
        self.assertFileEqual(b'trunk content\nfeature B\n',
 
418
                             osutils.pathjoin('branch', path))
 
419
 
 
420
    def do_create_file_in_dir(self):
 
421
        return [('add', ('dir', b'dir-id', 'directory', '')),
 
422
                ] + self.do_create_file('dir/file')
 
423
 
 
424
    def check_file_in_dir_has_content_A(self):
 
425
        self.check_file_has_content_A('dir/file')
 
426
 
 
427
    def check_file_in_dir_has_content_B(self):
 
428
        self.check_file_has_content_B('dir/file')
 
429
 
 
430
    def _get_resolve_path_arg(self, wt, action):
 
431
        return self._path
 
432
 
 
433
    def assertTextConflict(self, wt, c):
 
434
        self.assertEqual(self._file_id, c.file_id)
 
435
        self.assertEqual(self._path, c.path)
 
436
    _assert_conflict = assertTextConflict
 
437
 
 
438
 
 
439
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
440
 
 
441
    _conflict_type = conflicts.ContentsConflict
 
442
 
 
443
    # Set by the scenarios
 
444
    # path and file-id for the file involved in the conflict
 
445
    _path = None
 
446
    _file_id = None
 
447
 
 
448
    scenarios = mirror_scenarios(
 
449
        [
 
450
            # File modified/deleted
 
451
            (dict(_base_actions='create_file',
 
452
                  _path='file', _file_id=b'file-id'),
 
453
             ('file_modified',
 
454
              dict(actions='modify_file', check='file_has_more_content')),
 
455
             ('file_deleted',
 
456
              dict(actions='delete_file', check='file_doesnt_exist')),),
 
457
            # File renamed-modified/deleted
 
458
            (dict(_base_actions='create_file',
 
459
                  _path='new-file', _file_id=b'file-id'),
 
460
             ('file_renamed_and_modified',
 
461
              dict(actions='modify_and_rename_file',
 
462
                   check='file_renamed_and_more_content')),
 
463
             ('file_deleted',
 
464
              dict(actions='delete_file', check='file_doesnt_exist')),),
 
465
            # File modified/deleted in dir
 
466
            (dict(_base_actions='create_file_in_dir',
 
467
                  _path='dir/file', _file_id=b'file-id'),
 
468
             ('file_modified_in_dir',
 
469
              dict(actions='modify_file_in_dir',
 
470
                   check='file_in_dir_has_more_content')),
 
471
             ('file_deleted_in_dir',
 
472
              dict(actions='delete_file_in_dir',
 
473
                   check='file_in_dir_doesnt_exist')),),
 
474
            ])
 
475
 
 
476
    def do_create_file(self):
 
477
        return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
 
478
 
 
479
    def do_modify_file(self):
 
480
        return [('modify', ('file', b'trunk content\nmore content\n'))]
 
481
 
 
482
    def do_modify_and_rename_file(self):
 
483
        return [('modify', ('new-file', b'trunk content\nmore content\n')),
 
484
                ('rename', ('file', 'new-file'))]
 
485
 
 
486
    def check_file_has_more_content(self):
 
487
        self.assertFileEqual(b'trunk content\nmore content\n', 'branch/file')
 
488
 
 
489
    def check_file_renamed_and_more_content(self):
 
490
        self.assertFileEqual(
 
491
            b'trunk content\nmore content\n', 'branch/new-file')
 
492
 
 
493
    def do_delete_file(self):
 
494
        return [('unversion', 'file')]
 
495
 
 
496
    def do_delete_file_in_dir(self):
 
497
        return [('unversion', 'dir/file')]
 
498
 
 
499
    def check_file_doesnt_exist(self):
 
500
        self.assertPathDoesNotExist('branch/file')
 
501
 
 
502
    def do_create_file_in_dir(self):
 
503
        return [('add', ('dir', b'dir-id', 'directory', '')),
 
504
                ('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
 
505
 
 
506
    def do_modify_file_in_dir(self):
 
507
        return [('modify', ('dir/file', b'trunk content\nmore content\n'))]
 
508
 
 
509
    def check_file_in_dir_has_more_content(self):
 
510
        self.assertFileEqual(
 
511
            b'trunk content\nmore content\n', 'branch/dir/file')
 
512
 
 
513
    def check_file_in_dir_doesnt_exist(self):
 
514
        self.assertPathDoesNotExist('branch/dir/file')
 
515
 
 
516
    def _get_resolve_path_arg(self, wt, action):
 
517
        return self._path
 
518
 
 
519
    def assertContentsConflict(self, wt, c):
 
520
        self.assertEqual(self._file_id, c.file_id)
 
521
        self.assertEqual(self._path, c.path)
 
522
    _assert_conflict = assertContentsConflict
 
523
 
 
524
 
 
525
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
526
 
 
527
    _conflict_type = conflicts.PathConflict
 
528
 
 
529
    def do_nothing(self):
 
530
        return []
 
531
 
 
532
    # Each side dict additionally defines:
 
533
    # - path path involved (can be '<deleted>')
 
534
    # - file-id involved
 
535
    scenarios = mirror_scenarios(
 
536
        [
 
537
            # File renamed/deleted
 
538
            (dict(_base_actions='create_file'),
 
539
             ('file_renamed',
 
540
              dict(actions='rename_file', check='file_renamed',
 
541
                   path='new-file', file_id=b'file-id')),
 
542
             ('file_deleted',
 
543
              dict(actions='delete_file', check='file_doesnt_exist',
 
544
                   # PathConflicts deletion handling requires a special
 
545
                   # hard-coded value
 
546
                   path='<deleted>', file_id=b'file-id')),),
 
547
            # File renamed/deleted in dir
 
548
            (dict(_base_actions='create_file_in_dir'),
 
549
             ('file_renamed_in_dir',
 
550
              dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
 
551
                   path='dir/new-file', file_id=b'file-id')),
 
552
             ('file_deleted',
 
553
              dict(actions='delete_file_in_dir', check='file_in_dir_doesnt_exist',
 
554
                   # PathConflicts deletion handling requires a special
 
555
                   # hard-coded value
 
556
                   path='<deleted>', file_id=b'file-id')),),
 
557
            # File renamed/renamed differently
 
558
            (dict(_base_actions='create_file'),
 
559
             ('file_renamed',
 
560
              dict(actions='rename_file', check='file_renamed',
 
561
                   path='new-file', file_id=b'file-id')),
 
562
             ('file_renamed2',
 
563
              dict(actions='rename_file2', check='file_renamed2',
 
564
                   path='new-file2', file_id=b'file-id')),),
 
565
            # Dir renamed/deleted
 
566
            (dict(_base_actions='create_dir'),
 
567
             ('dir_renamed',
 
568
              dict(actions='rename_dir', check='dir_renamed',
 
569
                   path='new-dir', file_id=b'dir-id')),
 
570
             ('dir_deleted',
 
571
              dict(actions='delete_dir', check='dir_doesnt_exist',
 
572
                   # PathConflicts deletion handling requires a special
 
573
                   # hard-coded value
 
574
                   path='<deleted>', file_id=b'dir-id')),),
 
575
            # Dir renamed/renamed differently
 
576
            (dict(_base_actions='create_dir'),
 
577
             ('dir_renamed',
 
578
              dict(actions='rename_dir', check='dir_renamed',
 
579
                   path='new-dir', file_id=b'dir-id')),
 
580
             ('dir_renamed2',
 
581
              dict(actions='rename_dir2', check='dir_renamed2',
 
582
                   path='new-dir2', file_id=b'dir-id')),),
 
583
            ])
 
584
 
 
585
    def do_create_file(self):
 
586
        return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
 
587
 
 
588
    def do_create_dir(self):
 
589
        return [('add', ('dir', b'dir-id', 'directory', ''))]
 
590
 
 
591
    def do_rename_file(self):
 
592
        return [('rename', ('file', 'new-file'))]
 
593
 
 
594
    def check_file_renamed(self):
 
595
        self.assertPathDoesNotExist('branch/file')
 
596
        self.assertPathExists('branch/new-file')
 
597
 
 
598
    def do_rename_file2(self):
 
599
        return [('rename', ('file', 'new-file2'))]
 
600
 
 
601
    def check_file_renamed2(self):
 
602
        self.assertPathDoesNotExist('branch/file')
 
603
        self.assertPathExists('branch/new-file2')
 
604
 
 
605
    def do_rename_dir(self):
 
606
        return [('rename', ('dir', 'new-dir'))]
 
607
 
 
608
    def check_dir_renamed(self):
 
609
        self.assertPathDoesNotExist('branch/dir')
 
610
        self.assertPathExists('branch/new-dir')
 
611
 
 
612
    def do_rename_dir2(self):
 
613
        return [('rename', ('dir', 'new-dir2'))]
 
614
 
 
615
    def check_dir_renamed2(self):
 
616
        self.assertPathDoesNotExist('branch/dir')
 
617
        self.assertPathExists('branch/new-dir2')
 
618
 
 
619
    def do_delete_file(self):
 
620
        return [('unversion', 'file')]
 
621
 
 
622
    def do_delete_file_in_dir(self):
 
623
        return [('unversion', 'dir/file')]
 
624
 
 
625
    def check_file_doesnt_exist(self):
 
626
        self.assertPathDoesNotExist('branch/file')
 
627
 
 
628
    def do_delete_dir(self):
 
629
        return [('unversion', 'dir')]
 
630
 
 
631
    def check_dir_doesnt_exist(self):
 
632
        self.assertPathDoesNotExist('branch/dir')
 
633
 
 
634
    def do_create_file_in_dir(self):
 
635
        return [('add', ('dir', b'dir-id', 'directory', '')),
 
636
                ('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
 
637
 
 
638
    def do_rename_file_in_dir(self):
 
639
        return [('rename', ('dir/file', 'dir/new-file'))]
 
640
 
 
641
    def check_file_in_dir_renamed(self):
 
642
        self.assertPathDoesNotExist('branch/dir/file')
 
643
        self.assertPathExists('branch/dir/new-file')
 
644
 
 
645
    def check_file_in_dir_doesnt_exist(self):
 
646
        self.assertPathDoesNotExist('branch/dir/file')
 
647
 
 
648
    def _get_resolve_path_arg(self, wt, action):
 
649
        tpath = self._this['path']
 
650
        opath = self._other['path']
 
651
        if tpath == '<deleted>':
 
652
            path = opath
 
653
        else:
 
654
            path = tpath
 
655
        return path
 
656
 
 
657
    def assertPathConflict(self, wt, c):
 
658
        tpath = self._this['path']
 
659
        tfile_id = self._this['file_id']
 
660
        opath = self._other['path']
 
661
        ofile_id = self._other['file_id']
 
662
        self.assertEqual(tfile_id, ofile_id)  # Sanity check
 
663
        self.assertEqual(tfile_id, c.file_id)
 
664
        self.assertEqual(tpath, c.path)
 
665
        self.assertEqual(opath, c.conflict_path)
 
666
    _assert_conflict = assertPathConflict
 
667
 
 
668
 
 
669
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
670
    """Same as TestResolvePathConflict but a specific conflict object.
 
671
    """
 
672
 
 
673
    def assertPathConflict(self, c):
 
674
        # We create a conflict object as it was created before the fix and
 
675
        # inject it into the working tree, the test will exercise the
 
676
        # compatibility code.
 
677
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
678
                                       file_id=None)
 
679
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
680
 
 
681
 
 
682
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
 
683
 
 
684
    _conflict_type = conflicts.DuplicateEntry
 
685
 
 
686
    scenarios = mirror_scenarios(
 
687
        [
 
688
            # File created with different file-ids
 
689
            (dict(_base_actions='nothing'),
 
690
             ('filea_created',
 
691
              dict(actions='create_file_a', check='file_content_a',
 
692
                   path='file', file_id=b'file-a-id')),
 
693
             ('fileb_created',
 
694
              dict(actions='create_file_b', check='file_content_b',
 
695
                   path='file', file_id=b'file-b-id')),),
 
696
            # File created with different file-ids but deleted on one side
 
697
            (dict(_base_actions='create_file_a'),
 
698
             ('filea_replaced',
 
699
              dict(actions='replace_file_a_by_b', check='file_content_b',
 
700
                   path='file', file_id=b'file-b-id')),
 
701
             ('filea_modified',
 
702
              dict(actions='modify_file_a', check='file_new_content',
 
703
                   path='file', file_id=b'file-a-id')),),
 
704
            ])
 
705
 
 
706
    def do_nothing(self):
 
707
        return []
 
708
 
 
709
    def do_create_file_a(self):
 
710
        return [('add', ('file', b'file-a-id', 'file', b'file a content\n'))]
 
711
 
 
712
    def check_file_content_a(self):
 
713
        self.assertFileEqual(b'file a content\n', 'branch/file')
 
714
 
 
715
    def do_create_file_b(self):
 
716
        return [('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
 
717
 
 
718
    def check_file_content_b(self):
 
719
        self.assertFileEqual(b'file b content\n', 'branch/file')
 
720
 
 
721
    def do_replace_file_a_by_b(self):
 
722
        return [('unversion', 'file'),
 
723
                ('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
 
724
 
 
725
    def do_modify_file_a(self):
 
726
        return [('modify', ('file', b'new content\n'))]
 
727
 
 
728
    def check_file_new_content(self):
 
729
        self.assertFileEqual(b'new content\n', 'branch/file')
 
730
 
 
731
    def _get_resolve_path_arg(self, wt, action):
 
732
        return self._this['path']
 
733
 
 
734
    def assertDuplicateEntry(self, wt, c):
 
735
        tpath = self._this['path']
 
736
        tfile_id = self._this['file_id']
 
737
        opath = self._other['path']
 
738
        ofile_id = self._other['file_id']
 
739
        self.assertEqual(tpath, opath)  # Sanity check
 
740
        self.assertEqual(tfile_id, c.file_id)
 
741
        self.assertEqual(tpath + '.moved', c.path)
 
742
        self.assertEqual(tpath, c.conflict_path)
 
743
    _assert_conflict = assertDuplicateEntry
 
744
 
 
745
 
 
746
class TestResolveUnversionedParent(TestResolveConflicts):
 
747
 
 
748
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
 
749
 
 
750
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
 
751
    # tests MissingParent resolution :-/
 
752
    preamble = """
 
753
$ brz init trunk
 
754
...
 
755
$ cd trunk
 
756
$ mkdir dir
 
757
$ brz add -q dir
 
758
$ brz commit -m 'Create trunk' -q
 
759
$ echo 'trunk content' >dir/file
 
760
$ brz add -q dir/file
 
761
$ brz commit -q -m 'Add dir/file in trunk'
 
762
$ brz branch -q . -r 1 ../branch
 
763
$ cd ../branch
 
764
$ brz rm dir -q
 
765
$ brz commit -q -m 'Remove dir in branch'
 
766
$ brz merge ../trunk
 
767
2>+N  dir/
 
768
2>+N  dir/file
 
769
2>Conflict adding files to dir.  Created directory.
 
770
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
771
2>2 conflicts encountered.
 
772
"""
 
773
 
 
774
    def test_take_this(self):
 
775
        self.run_script("""
 
776
$ brz rm -q dir --no-backup
 
777
$ brz resolve dir
 
778
2>2 conflicts resolved, 0 remaining
 
779
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
780
""")
 
781
 
 
782
    def test_take_other(self):
 
783
        self.run_script("""
 
784
$ brz resolve dir
 
785
2>2 conflicts resolved, 0 remaining
 
786
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
787
""")
 
788
 
 
789
 
 
790
class TestResolveMissingParent(TestResolveConflicts):
 
791
 
 
792
    preamble = """
 
793
$ brz init trunk
 
794
...
 
795
$ cd trunk
 
796
$ mkdir dir
 
797
$ echo 'trunk content' >dir/file
 
798
$ brz add -q
 
799
$ brz commit -m 'Create trunk' -q
 
800
$ echo 'trunk content' >dir/file2
 
801
$ brz add -q dir/file2
 
802
$ brz commit -q -m 'Add dir/file2 in branch'
 
803
$ brz branch -q . -r 1 ../branch
 
804
$ cd ../branch
 
805
$ brz rm -q dir/file --no-backup
 
806
$ brz rm -q dir
 
807
$ brz commit -q -m 'Remove dir/file'
 
808
$ brz merge ../trunk
 
809
2>+N  dir/
 
810
2>+N  dir/file2
 
811
2>Conflict adding files to dir.  Created directory.
 
812
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
813
2>2 conflicts encountered.
 
814
"""
 
815
 
 
816
    def test_keep_them_all(self):
 
817
        self.run_script("""
 
818
$ brz resolve dir
 
819
2>2 conflicts resolved, 0 remaining
 
820
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
821
""")
 
822
 
 
823
    def test_adopt_child(self):
 
824
        self.run_script("""
 
825
$ brz mv -q dir/file2 file2
 
826
$ brz rm -q dir --no-backup
 
827
$ brz resolve dir
 
828
2>2 conflicts resolved, 0 remaining
 
829
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
830
""")
 
831
 
 
832
    def test_kill_them_all(self):
 
833
        self.run_script("""
 
834
$ brz rm -q dir --no-backup
 
835
$ brz resolve dir
 
836
2>2 conflicts resolved, 0 remaining
 
837
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
838
""")
 
839
 
 
840
    def test_resolve_taking_this(self):
 
841
        self.run_script("""
 
842
$ brz resolve --take-this dir
 
843
2>...
 
844
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
845
""")
 
846
 
 
847
    def test_resolve_taking_other(self):
 
848
        self.run_script("""
 
849
$ brz resolve --take-other dir
 
850
2>...
 
851
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
852
""")
 
853
 
 
854
 
 
855
class TestResolveDeletingParent(TestResolveConflicts):
 
856
 
 
857
    preamble = """
 
858
$ brz init trunk
 
859
...
 
860
$ cd trunk
 
861
$ mkdir dir
 
862
$ echo 'trunk content' >dir/file
 
863
$ brz add -q
 
864
$ brz commit -m 'Create trunk' -q
 
865
$ brz rm -q dir/file --no-backup
 
866
$ brz rm -q dir --no-backup
 
867
$ brz commit -q -m 'Remove dir/file'
 
868
$ brz branch -q . -r 1 ../branch
 
869
$ cd ../branch
 
870
$ echo 'branch content' >dir/file2
 
871
$ brz add -q dir/file2
 
872
$ brz commit -q -m 'Add dir/file2 in branch'
 
873
$ brz merge ../trunk
 
874
2>-D  dir/file
 
875
2>Conflict: can't delete dir because it is not empty.  Not deleting.
 
876
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
877
2>2 conflicts encountered.
 
878
"""
 
879
 
 
880
    def test_keep_them_all(self):
 
881
        self.run_script("""
 
882
$ brz resolve dir
 
883
2>2 conflicts resolved, 0 remaining
 
884
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
885
""")
 
886
 
 
887
    def test_adopt_child(self):
 
888
        self.run_script("""
 
889
$ brz mv -q dir/file2 file2
 
890
$ brz rm -q dir --no-backup
 
891
$ brz resolve dir
 
892
2>2 conflicts resolved, 0 remaining
 
893
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
894
""")
 
895
 
 
896
    def test_kill_them_all(self):
 
897
        self.run_script("""
 
898
$ brz rm -q dir --no-backup
 
899
$ brz resolve dir
 
900
2>2 conflicts resolved, 0 remaining
 
901
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
902
""")
 
903
 
 
904
    def test_resolve_taking_this(self):
 
905
        self.run_script("""
 
906
$ brz resolve --take-this dir
 
907
2>2 conflicts resolved, 0 remaining
 
908
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
909
""")
 
910
 
 
911
    def test_resolve_taking_other(self):
 
912
        self.run_script("""
 
913
$ brz resolve --take-other dir
 
914
2>deleted dir/file2
 
915
2>deleted dir
 
916
2>2 conflicts resolved, 0 remaining
 
917
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
918
""")
 
919
 
 
920
 
 
921
class TestResolveParentLoop(TestParametrizedResolveConflicts):
 
922
 
 
923
    _conflict_type = conflicts.ParentLoop
 
924
 
 
925
    _this_args = None
 
926
    _other_args = None
 
927
 
 
928
    # Each side dict additionally defines:
 
929
    # - dir_id: the directory being moved
 
930
    # - target_id: The target directory
 
931
    # - xfail: whether the test is expected to fail if the action is
 
932
    #   involved as 'other'
 
933
    scenarios = mirror_scenarios(
 
934
        [
 
935
            # Dirs moved into each other
 
936
            (dict(_base_actions='create_dir1_dir2'),
 
937
             ('dir1_into_dir2',
 
938
              dict(actions='move_dir1_into_dir2', check='dir1_moved',
 
939
                   dir_id=b'dir1-id', target_id=b'dir2-id', xfail=False)),
 
940
             ('dir2_into_dir1',
 
941
              dict(actions='move_dir2_into_dir1', check='dir2_moved',
 
942
                   dir_id=b'dir2-id', target_id=b'dir1-id', xfail=False))),
 
943
            # Subdirs moved into each other
 
944
            (dict(_base_actions='create_dir1_4'),
 
945
             ('dir1_into_dir4',
 
946
              dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
 
947
                   dir_id=b'dir1-id', target_id=b'dir4-id', xfail=True)),
 
948
             ('dir3_into_dir2',
 
949
              dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
 
950
                   dir_id=b'dir3-id', target_id=b'dir2-id', xfail=True))),
 
951
            ])
 
952
 
 
953
    def do_create_dir1_dir2(self):
 
954
        return [('add', ('dir1', b'dir1-id', 'directory', '')),
 
955
                ('add', ('dir2', b'dir2-id', 'directory', '')), ]
 
956
 
 
957
    def do_move_dir1_into_dir2(self):
 
958
        return [('rename', ('dir1', 'dir2/dir1'))]
 
959
 
 
960
    def check_dir1_moved(self):
 
961
        self.assertPathDoesNotExist('branch/dir1')
 
962
        self.assertPathExists('branch/dir2/dir1')
 
963
 
 
964
    def do_move_dir2_into_dir1(self):
 
965
        return [('rename', ('dir2', 'dir1/dir2'))]
 
966
 
 
967
    def check_dir2_moved(self):
 
968
        self.assertPathDoesNotExist('branch/dir2')
 
969
        self.assertPathExists('branch/dir1/dir2')
 
970
 
 
971
    def do_create_dir1_4(self):
 
972
        return [('add', ('dir1', b'dir1-id', 'directory', '')),
 
973
                ('add', ('dir1/dir2', b'dir2-id', 'directory', '')),
 
974
                ('add', ('dir3', b'dir3-id', 'directory', '')),
 
975
                ('add', ('dir3/dir4', b'dir4-id', 'directory', '')), ]
 
976
 
 
977
    def do_move_dir1_into_dir4(self):
 
978
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
 
979
 
 
980
    def check_dir1_2_moved(self):
 
981
        self.assertPathDoesNotExist('branch/dir1')
 
982
        self.assertPathExists('branch/dir3/dir4/dir1')
 
983
        self.assertPathExists('branch/dir3/dir4/dir1/dir2')
 
984
 
 
985
    def do_move_dir3_into_dir2(self):
 
986
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
 
987
 
 
988
    def check_dir3_4_moved(self):
 
989
        self.assertPathDoesNotExist('branch/dir3')
 
990
        self.assertPathExists('branch/dir1/dir2/dir3')
 
991
        self.assertPathExists('branch/dir1/dir2/dir3/dir4')
 
992
 
 
993
    def _get_resolve_path_arg(self, wt, action):
 
994
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
 
995
        # But since <path> doesn't exist in the working tree, we need to use
 
996
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
 
997
        return wt.id2path(self._other['dir_id'])
 
998
 
 
999
    def assertParentLoop(self, wt, c):
 
1000
        self.assertEqual(self._other['dir_id'], c.file_id)
 
1001
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
 
1002
        # The conflict paths are irrelevant (they are deterministic but not
 
1003
        # worth checking since they don't provide the needed information
 
1004
        # anyway)
 
1005
        if self._other['xfail']:
 
1006
            # It's a bit hackish to raise from here relying on being called for
 
1007
            # both tests but this avoid overriding test_resolve_taking_other
 
1008
            self.knownFailure(
 
1009
                "ParentLoop doesn't carry enough info to resolve --take-other")
 
1010
    _assert_conflict = assertParentLoop
 
1011
 
 
1012
 
 
1013
class TestResolveNonDirectoryParent(TestResolveConflicts):
 
1014
 
 
1015
    preamble = """
 
1016
$ brz init trunk
 
1017
...
 
1018
$ cd trunk
 
1019
$ brz mkdir foo
 
1020
...
 
1021
$ brz commit -m 'Create trunk' -q
 
1022
$ echo "Boing" >foo/bar
 
1023
$ brz add -q foo/bar
 
1024
$ brz commit -q -m 'Add foo/bar'
 
1025
$ brz branch -q . -r 1 ../branch
 
1026
$ cd ../branch
 
1027
$ rm -r foo
 
1028
$ echo "Boo!" >foo
 
1029
$ brz commit -q -m 'foo is now a file'
 
1030
$ brz merge ../trunk
 
1031
2>RK  foo => foo.new/
 
1032
2>+N  foo.new/bar
 
1033
# FIXME: The message is misleading, foo.new *is* a directory when the message
 
1034
# is displayed -- vila 090916
 
1035
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
 
1036
2>1 conflicts encountered.
 
1037
"""
 
1038
 
 
1039
    def test_take_this(self):
 
1040
        self.run_script("""
 
1041
$ brz rm -q foo.new --no-backup
 
1042
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
 
1043
# aside ? -- vila 090916
 
1044
$ brz add -q foo
 
1045
$ brz resolve foo.new
 
1046
2>1 conflict resolved, 0 remaining
 
1047
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1048
""")
 
1049
 
 
1050
    def test_take_other(self):
 
1051
        self.run_script("""
 
1052
$ brz rm -q foo --no-backup
 
1053
$ brz mv -q foo.new foo
 
1054
$ brz resolve foo
 
1055
2>1 conflict resolved, 0 remaining
 
1056
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1057
""")
 
1058
 
 
1059
    def test_resolve_taking_this(self):
 
1060
        self.run_script("""
 
1061
$ brz resolve --take-this foo.new
 
1062
2>...
 
1063
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1064
""")
 
1065
 
 
1066
    def test_resolve_taking_other(self):
 
1067
        self.run_script("""
 
1068
$ brz resolve --take-other foo.new
 
1069
2>...
 
1070
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1071
""")
 
1072
 
 
1073
 
 
1074
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
 
1075
 
 
1076
    def test_bug_430129(self):
 
1077
        # This is nearly like TestResolveNonDirectoryParent but with branch and
 
1078
        # trunk switched. As such it should certainly produce the same
 
1079
        # conflict.
 
1080
        self.assertRaises(errors.MalformedTransform,
 
1081
                          self.run_script, """
 
1082
$ brz init trunk
 
1083
...
 
1084
$ cd trunk
 
1085
$ brz mkdir foo
 
1086
...
 
1087
$ brz commit -m 'Create trunk' -q
 
1088
$ rm -r foo
 
1089
$ echo "Boo!" >foo
 
1090
$ brz commit -m 'foo is now a file' -q
 
1091
$ brz branch -q . -r 1 ../branch -q
 
1092
$ cd ../branch
 
1093
$ echo "Boing" >foo/bar
 
1094
$ brz add -q foo/bar -q
 
1095
$ brz commit -m 'Add foo/bar' -q
 
1096
$ brz merge ../trunk
 
1097
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
 
1098
""")
 
1099
 
 
1100
 
 
1101
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
 
1102
 
 
1103
    def test_bug_805809(self):
 
1104
        self.run_script("""
 
1105
$ brz init trunk
 
1106
Created a standalone tree (format: 2a)
 
1107
$ cd trunk
 
1108
$ echo trunk >file
 
1109
$ brz add
 
1110
adding file
 
1111
$ brz commit -m 'create file on trunk'
 
1112
2>Committing to: .../trunk/
 
1113
2>added file
 
1114
2>Committed revision 1.
 
1115
# Create a debian branch based on trunk
 
1116
$ cd ..
 
1117
$ brz branch trunk -r 1 debian
 
1118
2>Branched 1 revision.
 
1119
$ cd debian
 
1120
$ mkdir dir
 
1121
$ brz add
 
1122
adding dir
 
1123
$ brz mv file dir
 
1124
file => dir/file
 
1125
$ brz commit -m 'rename file to dir/file for debian'
 
1126
2>Committing to: .../debian/
 
1127
2>added dir
 
1128
2>renamed file => dir/file
 
1129
2>Committed revision 2.
 
1130
# Create an experimental branch with a new root-id
 
1131
$ cd ..
 
1132
$ brz init experimental
 
1133
Created a standalone tree (format: 2a)
 
1134
$ cd experimental
 
1135
# Work around merging into empty branch not being supported
 
1136
# (http://pad.lv/308562)
 
1137
$ echo something >not-empty
 
1138
$ brz add
 
1139
adding not-empty
 
1140
$ brz commit -m 'Add some content in experimental'
 
1141
2>Committing to: .../experimental/
 
1142
2>added not-empty
 
1143
2>Committed revision 1.
 
1144
# merge debian even without a common ancestor
 
1145
$ brz merge ../debian -r0..2
 
1146
2>+N  dir/
 
1147
2>+N  dir/file
 
1148
2>All changes applied successfully.
 
1149
$ brz commit -m 'merging debian into experimental'
 
1150
2>Committing to: .../experimental/
 
1151
2>added dir
 
1152
2>added dir/file
 
1153
2>Committed revision 2.
 
1154
# Create an ubuntu branch with yet another root-id
 
1155
$ cd ..
 
1156
$ brz init ubuntu
 
1157
Created a standalone tree (format: 2a)
 
1158
$ cd ubuntu
 
1159
# Work around merging into empty branch not being supported
 
1160
# (http://pad.lv/308562)
 
1161
$ echo something >not-empty-ubuntu
 
1162
$ brz add
 
1163
adding not-empty-ubuntu
 
1164
$ brz commit -m 'Add some content in experimental'
 
1165
2>Committing to: .../ubuntu/
 
1166
2>added not-empty-ubuntu
 
1167
2>Committed revision 1.
 
1168
# Also merge debian
 
1169
$ brz merge ../debian -r0..2
 
1170
2>+N  dir/
 
1171
2>+N  dir/file
 
1172
2>All changes applied successfully.
 
1173
$ brz commit -m 'merging debian'
 
1174
2>Committing to: .../ubuntu/
 
1175
2>added dir
 
1176
2>added dir/file
 
1177
2>Committed revision 2.
 
1178
# Now try to merge experimental
 
1179
$ brz merge ../experimental
 
1180
2>+N  not-empty
 
1181
2>Path conflict: dir / dir
 
1182
2>1 conflicts encountered.
 
1183
""")
 
1184
 
 
1185
 
 
1186
class TestResolveActionOption(tests.TestCase):
 
1187
 
 
1188
    def setUp(self):
 
1189
        super(TestResolveActionOption, self).setUp()
 
1190
        self.options = [conflicts.ResolveActionOption()]
 
1191
        self.parser = option.get_optparser(self.options)
 
1192
 
 
1193
    def parse(self, args):
 
1194
        return self.parser.parse_args(args)
 
1195
 
 
1196
    def test_unknown_action(self):
 
1197
        self.assertRaises(option.BadOptionValue,
 
1198
                          self.parse, ['--action', 'take-me-to-the-moon'])
 
1199
 
 
1200
    def test_done(self):
 
1201
        opts, args = self.parse(['--action', 'done'])
 
1202
        self.assertEqual({'action': 'done'}, opts)
 
1203
 
 
1204
    def test_take_this(self):
 
1205
        opts, args = self.parse(['--action', 'take-this'])
 
1206
        self.assertEqual({'action': 'take_this'}, opts)
 
1207
        opts, args = self.parse(['--take-this'])
 
1208
        self.assertEqual({'action': 'take_this'}, opts)
 
1209
 
 
1210
    def test_take_other(self):
 
1211
        opts, args = self.parse(['--action', 'take-other'])
 
1212
        self.assertEqual({'action': 'take_other'}, opts)
 
1213
        opts, args = self.parse(['--take-other'])
 
1214
        self.assertEqual({'action': 'take_other'}, opts)