/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: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

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