/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: 2018-07-26 19:15:27 UTC
  • mto: This revision was merged to the branch mainline in revision 7055.
  • Revision ID: jelmer@jelmer.uk-20180726191527-wniq205k6tzfo1xx
Install fastimport from git.

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', '\xc3\xaedg'),
 
47
     conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
 
48
     conflicts.TextConflict(u'p\xe5tha'),
 
49
     conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
 
50
     conflicts.DuplicateID('Unversioned existing file',
 
51
                           u'p\xe5thc', u'p\xe5thc2',
 
52
                           '\xc3\xaedc', '\xc3\xaedc'),
 
53
    conflicts.DuplicateEntry('Moved existing file to',
 
54
                             u'p\xe5thdd.moved', u'p\xe5thd',
 
55
                             '\xc3\xaedd', None),
 
56
    conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
 
57
                         None, '\xc3\xaed2e'),
 
58
    conflicts.UnversionedParent('Versioned directory',
 
59
                                u'p\xe5thf', '\xc3\xaedf'),
 
60
    conflicts.NonDirectoryParent('Created directory',
 
61
                                 u'p\xe5thg', '\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([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 = text_type(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, text_type)
 
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, text_type)
 
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, text_type(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(b'trunk content\nmore content\n', 'branch/new-file')
 
490
 
 
491
    def do_delete_file(self):
 
492
        return [('unversion', 'file')]
 
493
 
 
494
    def do_delete_file_in_dir(self):
 
495
        return [('unversion', 'dir/file')]
 
496
 
 
497
    def check_file_doesnt_exist(self):
 
498
        self.assertPathDoesNotExist('branch/file')
 
499
 
 
500
    def do_create_file_in_dir(self):
 
501
        return [('add', ('dir', b'dir-id', 'directory', '')),
 
502
                ('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
 
503
 
 
504
    def do_modify_file_in_dir(self):
 
505
        return [('modify', ('dir/file', b'trunk content\nmore content\n'))]
 
506
 
 
507
    def check_file_in_dir_has_more_content(self):
 
508
        self.assertFileEqual(b'trunk content\nmore content\n', 'branch/dir/file')
 
509
 
 
510
    def check_file_in_dir_doesnt_exist(self):
 
511
        self.assertPathDoesNotExist('branch/dir/file')
 
512
 
 
513
    def _get_resolve_path_arg(self, wt, action):
 
514
        return self._path
 
515
 
 
516
    def assertContentsConflict(self, wt, c):
 
517
        self.assertEqual(self._file_id, c.file_id)
 
518
        self.assertEqual(self._path, c.path)
 
519
    _assert_conflict = assertContentsConflict
 
520
 
 
521
 
 
522
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
523
 
 
524
    _conflict_type = conflicts.PathConflict
 
525
 
 
526
    def do_nothing(self):
 
527
        return []
 
528
 
 
529
    # Each side dict additionally defines:
 
530
    # - path path involved (can be '<deleted>')
 
531
    # - file-id involved
 
532
    scenarios = mirror_scenarios(
 
533
        [
 
534
            # File renamed/deleted
 
535
            (dict(_base_actions='create_file'),
 
536
             ('file_renamed',
 
537
              dict(actions='rename_file', check='file_renamed',
 
538
                   path='new-file', file_id=b'file-id')),
 
539
             ('file_deleted',
 
540
              dict(actions='delete_file', check='file_doesnt_exist',
 
541
                   # PathConflicts deletion handling requires a special
 
542
                   # hard-coded value
 
543
                   path='<deleted>', file_id=b'file-id')),),
 
544
            # File renamed/deleted in dir
 
545
            (dict(_base_actions='create_file_in_dir'),
 
546
             ('file_renamed_in_dir',
 
547
              dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
 
548
                   path='dir/new-file', file_id=b'file-id')),
 
549
             ('file_deleted',
 
550
              dict(actions='delete_file_in_dir', check='file_in_dir_doesnt_exist',
 
551
                   # PathConflicts deletion handling requires a special
 
552
                   # hard-coded value
 
553
                   path='<deleted>', file_id=b'file-id')),),
 
554
            # File renamed/renamed differently
 
555
            (dict(_base_actions='create_file'),
 
556
             ('file_renamed',
 
557
              dict(actions='rename_file', check='file_renamed',
 
558
                   path='new-file', file_id=b'file-id')),
 
559
             ('file_renamed2',
 
560
              dict(actions='rename_file2', check='file_renamed2',
 
561
                   path='new-file2', file_id=b'file-id')),),
 
562
            # Dir renamed/deleted
 
563
            (dict(_base_actions='create_dir'),
 
564
             ('dir_renamed',
 
565
              dict(actions='rename_dir', check='dir_renamed',
 
566
                   path='new-dir', file_id=b'dir-id')),
 
567
             ('dir_deleted',
 
568
              dict(actions='delete_dir', check='dir_doesnt_exist',
 
569
                   # PathConflicts deletion handling requires a special
 
570
                   # hard-coded value
 
571
                   path='<deleted>', file_id=b'dir-id')),),
 
572
            # Dir renamed/renamed differently
 
573
            (dict(_base_actions='create_dir'),
 
574
             ('dir_renamed',
 
575
              dict(actions='rename_dir', check='dir_renamed',
 
576
                   path='new-dir', file_id=b'dir-id')),
 
577
             ('dir_renamed2',
 
578
              dict(actions='rename_dir2', check='dir_renamed2',
 
579
                   path='new-dir2', file_id=b'dir-id')),),
 
580
            ])
 
581
 
 
582
    def do_create_file(self):
 
583
        return [('add', ('file', b'file-id', 'file', b'trunk content\n'))]
 
584
 
 
585
    def do_create_dir(self):
 
586
        return [('add', ('dir', b'dir-id', 'directory', ''))]
 
587
 
 
588
    def do_rename_file(self):
 
589
        return [('rename', ('file', 'new-file'))]
 
590
 
 
591
    def check_file_renamed(self):
 
592
        self.assertPathDoesNotExist('branch/file')
 
593
        self.assertPathExists('branch/new-file')
 
594
 
 
595
    def do_rename_file2(self):
 
596
        return [('rename', ('file', 'new-file2'))]
 
597
 
 
598
    def check_file_renamed2(self):
 
599
        self.assertPathDoesNotExist('branch/file')
 
600
        self.assertPathExists('branch/new-file2')
 
601
 
 
602
    def do_rename_dir(self):
 
603
        return [('rename', ('dir', 'new-dir'))]
 
604
 
 
605
    def check_dir_renamed(self):
 
606
        self.assertPathDoesNotExist('branch/dir')
 
607
        self.assertPathExists('branch/new-dir')
 
608
 
 
609
    def do_rename_dir2(self):
 
610
        return [('rename', ('dir', 'new-dir2'))]
 
611
 
 
612
    def check_dir_renamed2(self):
 
613
        self.assertPathDoesNotExist('branch/dir')
 
614
        self.assertPathExists('branch/new-dir2')
 
615
 
 
616
    def do_delete_file(self):
 
617
        return [('unversion', 'file')]
 
618
 
 
619
    def do_delete_file_in_dir(self):
 
620
        return [('unversion', 'dir/file')]
 
621
 
 
622
    def check_file_doesnt_exist(self):
 
623
        self.assertPathDoesNotExist('branch/file')
 
624
 
 
625
    def do_delete_dir(self):
 
626
        return [('unversion', 'dir')]
 
627
 
 
628
    def check_dir_doesnt_exist(self):
 
629
        self.assertPathDoesNotExist('branch/dir')
 
630
 
 
631
    def do_create_file_in_dir(self):
 
632
        return [('add', ('dir', b'dir-id', 'directory', '')),
 
633
                ('add', ('dir/file', b'file-id', 'file', b'trunk content\n'))]
 
634
 
 
635
    def do_rename_file_in_dir(self):
 
636
        return [('rename', ('dir/file', 'dir/new-file'))]
 
637
 
 
638
    def check_file_in_dir_renamed(self):
 
639
        self.assertPathDoesNotExist('branch/dir/file')
 
640
        self.assertPathExists('branch/dir/new-file')
 
641
 
 
642
    def check_file_in_dir_doesnt_exist(self):
 
643
        self.assertPathDoesNotExist('branch/dir/file')
 
644
 
 
645
    def _get_resolve_path_arg(self, wt, action):
 
646
        tpath = self._this['path']
 
647
        opath = self._other['path']
 
648
        if tpath == '<deleted>':
 
649
            path = opath
 
650
        else:
 
651
            path = tpath
 
652
        return path
 
653
 
 
654
    def assertPathConflict(self, wt, c):
 
655
        tpath = self._this['path']
 
656
        tfile_id = self._this['file_id']
 
657
        opath = self._other['path']
 
658
        ofile_id = self._other['file_id']
 
659
        self.assertEqual(tfile_id, ofile_id) # Sanity check
 
660
        self.assertEqual(tfile_id, c.file_id)
 
661
        self.assertEqual(tpath, c.path)
 
662
        self.assertEqual(opath, c.conflict_path)
 
663
    _assert_conflict = assertPathConflict
 
664
 
 
665
 
 
666
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
667
    """Same as TestResolvePathConflict but a specific conflict object.
 
668
    """
 
669
 
 
670
    def assertPathConflict(self, c):
 
671
        # We create a conflict object as it was created before the fix and
 
672
        # inject it into the working tree, the test will exercise the
 
673
        # compatibility code.
 
674
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
675
                                       file_id=None)
 
676
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
677
 
 
678
 
 
679
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
 
680
 
 
681
    _conflict_type = conflicts.DuplicateEntry
 
682
 
 
683
    scenarios = mirror_scenarios(
 
684
        [
 
685
            # File created with different file-ids
 
686
            (dict(_base_actions='nothing'),
 
687
             ('filea_created',
 
688
              dict(actions='create_file_a', check='file_content_a',
 
689
                   path='file', file_id=b'file-a-id')),
 
690
             ('fileb_created',
 
691
              dict(actions='create_file_b', check='file_content_b',
 
692
                   path='file', file_id=b'file-b-id')),),
 
693
            # File created with different file-ids but deleted on one side
 
694
            (dict(_base_actions='create_file_a'),
 
695
             ('filea_replaced',
 
696
              dict(actions='replace_file_a_by_b', check='file_content_b',
 
697
                   path='file', file_id=b'file-b-id')),
 
698
             ('filea_modified',
 
699
              dict(actions='modify_file_a', check='file_new_content',
 
700
                   path='file', file_id=b'file-a-id')),),
 
701
            ])
 
702
 
 
703
    def do_nothing(self):
 
704
        return []
 
705
 
 
706
    def do_create_file_a(self):
 
707
        return [('add', ('file', b'file-a-id', 'file', b'file a content\n'))]
 
708
 
 
709
    def check_file_content_a(self):
 
710
        self.assertFileEqual(b'file a content\n', 'branch/file')
 
711
 
 
712
    def do_create_file_b(self):
 
713
        return [('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
 
714
 
 
715
    def check_file_content_b(self):
 
716
        self.assertFileEqual(b'file b content\n', 'branch/file')
 
717
 
 
718
    def do_replace_file_a_by_b(self):
 
719
        return [('unversion', 'file'),
 
720
                ('add', ('file', b'file-b-id', 'file', b'file b content\n'))]
 
721
 
 
722
    def do_modify_file_a(self):
 
723
        return [('modify', ('file', b'new content\n'))]
 
724
 
 
725
    def check_file_new_content(self):
 
726
        self.assertFileEqual(b'new content\n', 'branch/file')
 
727
 
 
728
    def _get_resolve_path_arg(self, wt, action):
 
729
        return self._this['path']
 
730
 
 
731
    def assertDuplicateEntry(self, wt, c):
 
732
        tpath = self._this['path']
 
733
        tfile_id = self._this['file_id']
 
734
        opath = self._other['path']
 
735
        ofile_id = self._other['file_id']
 
736
        self.assertEqual(tpath, opath) # Sanity check
 
737
        self.assertEqual(tfile_id, c.file_id)
 
738
        self.assertEqual(tpath + '.moved', c.path)
 
739
        self.assertEqual(tpath, c.conflict_path)
 
740
    _assert_conflict = assertDuplicateEntry
 
741
 
 
742
 
 
743
class TestResolveUnversionedParent(TestResolveConflicts):
 
744
 
 
745
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
 
746
 
 
747
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
 
748
    # tests MissingParent resolution :-/
 
749
    preamble = """
 
750
$ brz init trunk
 
751
...
 
752
$ cd trunk
 
753
$ mkdir dir
 
754
$ brz add -q dir
 
755
$ brz commit -m 'Create trunk' -q
 
756
$ echo 'trunk content' >dir/file
 
757
$ brz add -q dir/file
 
758
$ brz commit -q -m 'Add dir/file in trunk'
 
759
$ brz branch -q . -r 1 ../branch
 
760
$ cd ../branch
 
761
$ brz rm dir -q
 
762
$ brz commit -q -m 'Remove dir in branch'
 
763
$ brz merge ../trunk
 
764
2>+N  dir/
 
765
2>+N  dir/file
 
766
2>Conflict adding files to dir.  Created directory.
 
767
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
768
2>2 conflicts encountered.
 
769
"""
 
770
 
 
771
    def test_take_this(self):
 
772
        self.run_script("""
 
773
$ brz rm -q dir --no-backup
 
774
$ brz resolve dir
 
775
2>2 conflicts resolved, 0 remaining
 
776
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
777
""")
 
778
 
 
779
    def test_take_other(self):
 
780
        self.run_script("""
 
781
$ brz resolve dir
 
782
2>2 conflicts resolved, 0 remaining
 
783
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
784
""")
 
785
 
 
786
 
 
787
class TestResolveMissingParent(TestResolveConflicts):
 
788
 
 
789
    preamble = """
 
790
$ brz init trunk
 
791
...
 
792
$ cd trunk
 
793
$ mkdir dir
 
794
$ echo 'trunk content' >dir/file
 
795
$ brz add -q
 
796
$ brz commit -m 'Create trunk' -q
 
797
$ echo 'trunk content' >dir/file2
 
798
$ brz add -q dir/file2
 
799
$ brz commit -q -m 'Add dir/file2 in branch'
 
800
$ brz branch -q . -r 1 ../branch
 
801
$ cd ../branch
 
802
$ brz rm -q dir/file --no-backup
 
803
$ brz rm -q dir
 
804
$ brz commit -q -m 'Remove dir/file'
 
805
$ brz merge ../trunk
 
806
2>+N  dir/
 
807
2>+N  dir/file2
 
808
2>Conflict adding files to dir.  Created directory.
 
809
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
810
2>2 conflicts encountered.
 
811
"""
 
812
 
 
813
    def test_keep_them_all(self):
 
814
        self.run_script("""
 
815
$ brz resolve dir
 
816
2>2 conflicts resolved, 0 remaining
 
817
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
818
""")
 
819
 
 
820
    def test_adopt_child(self):
 
821
        self.run_script("""
 
822
$ brz mv -q dir/file2 file2
 
823
$ brz rm -q dir --no-backup
 
824
$ brz resolve dir
 
825
2>2 conflicts resolved, 0 remaining
 
826
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
827
""")
 
828
 
 
829
    def test_kill_them_all(self):
 
830
        self.run_script("""
 
831
$ brz rm -q dir --no-backup
 
832
$ brz resolve dir
 
833
2>2 conflicts resolved, 0 remaining
 
834
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
835
""")
 
836
 
 
837
    def test_resolve_taking_this(self):
 
838
        self.run_script("""
 
839
$ brz resolve --take-this dir
 
840
2>...
 
841
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
842
""")
 
843
 
 
844
    def test_resolve_taking_other(self):
 
845
        self.run_script("""
 
846
$ brz resolve --take-other dir
 
847
2>...
 
848
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
849
""")
 
850
 
 
851
 
 
852
class TestResolveDeletingParent(TestResolveConflicts):
 
853
 
 
854
    preamble = """
 
855
$ brz init trunk
 
856
...
 
857
$ cd trunk
 
858
$ mkdir dir
 
859
$ echo 'trunk content' >dir/file
 
860
$ brz add -q
 
861
$ brz commit -m 'Create trunk' -q
 
862
$ brz rm -q dir/file --no-backup
 
863
$ brz rm -q dir --no-backup
 
864
$ brz commit -q -m 'Remove dir/file'
 
865
$ brz branch -q . -r 1 ../branch
 
866
$ cd ../branch
 
867
$ echo 'branch content' >dir/file2
 
868
$ brz add -q dir/file2
 
869
$ brz commit -q -m 'Add dir/file2 in branch'
 
870
$ brz merge ../trunk
 
871
2>-D  dir/file
 
872
2>Conflict: can't delete dir because it is not empty.  Not deleting.
 
873
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
874
2>2 conflicts encountered.
 
875
"""
 
876
 
 
877
    def test_keep_them_all(self):
 
878
        self.run_script("""
 
879
$ brz resolve dir
 
880
2>2 conflicts resolved, 0 remaining
 
881
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
882
""")
 
883
 
 
884
    def test_adopt_child(self):
 
885
        self.run_script("""
 
886
$ brz mv -q dir/file2 file2
 
887
$ brz rm -q dir --no-backup
 
888
$ brz resolve dir
 
889
2>2 conflicts resolved, 0 remaining
 
890
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
891
""")
 
892
 
 
893
    def test_kill_them_all(self):
 
894
        self.run_script("""
 
895
$ brz rm -q dir --no-backup
 
896
$ brz resolve dir
 
897
2>2 conflicts resolved, 0 remaining
 
898
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
899
""")
 
900
 
 
901
    def test_resolve_taking_this(self):
 
902
        self.run_script("""
 
903
$ brz resolve --take-this dir
 
904
2>2 conflicts resolved, 0 remaining
 
905
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
906
""")
 
907
 
 
908
    def test_resolve_taking_other(self):
 
909
        self.run_script("""
 
910
$ brz resolve --take-other dir
 
911
2>deleted dir/file2
 
912
2>deleted dir
 
913
2>2 conflicts resolved, 0 remaining
 
914
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
915
""")
 
916
 
 
917
 
 
918
class TestResolveParentLoop(TestParametrizedResolveConflicts):
 
919
 
 
920
    _conflict_type = conflicts.ParentLoop
 
921
 
 
922
    _this_args = None
 
923
    _other_args = None
 
924
 
 
925
    # Each side dict additionally defines:
 
926
    # - dir_id: the directory being moved
 
927
    # - target_id: The target directory
 
928
    # - xfail: whether the test is expected to fail if the action is
 
929
    #   involved as 'other'
 
930
    scenarios = mirror_scenarios(
 
931
        [
 
932
            # Dirs moved into each other
 
933
            (dict(_base_actions='create_dir1_dir2'),
 
934
             ('dir1_into_dir2',
 
935
              dict(actions='move_dir1_into_dir2', check='dir1_moved',
 
936
                   dir_id=b'dir1-id', target_id='dir2-id', xfail=False)),
 
937
             ('dir2_into_dir1',
 
938
              dict(actions='move_dir2_into_dir1', check='dir2_moved',
 
939
                   dir_id=b'dir2-id', target_id='dir1-id', xfail=False))),
 
940
            # Subdirs moved into each other
 
941
            (dict(_base_actions='create_dir1_4'),
 
942
             ('dir1_into_dir4',
 
943
              dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
 
944
                   dir_id=b'dir1-id', target_id='dir4-id', xfail=True)),
 
945
             ('dir3_into_dir2',
 
946
              dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
 
947
                   dir_id=b'dir3-id', target_id='dir2-id', xfail=True))),
 
948
            ])
 
949
 
 
950
    def do_create_dir1_dir2(self):
 
951
        return [('add', ('dir1', b'dir1-id', 'directory', '')),
 
952
                ('add', ('dir2', b'dir2-id', 'directory', '')),]
 
953
 
 
954
    def do_move_dir1_into_dir2(self):
 
955
        return [('rename', ('dir1', 'dir2/dir1'))]
 
956
 
 
957
    def check_dir1_moved(self):
 
958
        self.assertPathDoesNotExist('branch/dir1')
 
959
        self.assertPathExists('branch/dir2/dir1')
 
960
 
 
961
    def do_move_dir2_into_dir1(self):
 
962
        return [('rename', ('dir2', 'dir1/dir2'))]
 
963
 
 
964
    def check_dir2_moved(self):
 
965
        self.assertPathDoesNotExist('branch/dir2')
 
966
        self.assertPathExists('branch/dir1/dir2')
 
967
 
 
968
    def do_create_dir1_4(self):
 
969
        return [('add', ('dir1', b'dir1-id', 'directory', '')),
 
970
                ('add', ('dir1/dir2', b'dir2-id', 'directory', '')),
 
971
                ('add', ('dir3', b'dir3-id', 'directory', '')),
 
972
                ('add', ('dir3/dir4', b'dir4-id', 'directory', '')),]
 
973
 
 
974
    def do_move_dir1_into_dir4(self):
 
975
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
 
976
 
 
977
    def check_dir1_2_moved(self):
 
978
        self.assertPathDoesNotExist('branch/dir1')
 
979
        self.assertPathExists('branch/dir3/dir4/dir1')
 
980
        self.assertPathExists('branch/dir3/dir4/dir1/dir2')
 
981
 
 
982
    def do_move_dir3_into_dir2(self):
 
983
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
 
984
 
 
985
    def check_dir3_4_moved(self):
 
986
        self.assertPathDoesNotExist('branch/dir3')
 
987
        self.assertPathExists('branch/dir1/dir2/dir3')
 
988
        self.assertPathExists('branch/dir1/dir2/dir3/dir4')
 
989
 
 
990
    def _get_resolve_path_arg(self, wt, action):
 
991
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
 
992
        # But since <path> doesn't exist in the working tree, we need to use
 
993
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
 
994
        return wt.id2path(self._other['dir_id'])
 
995
 
 
996
    def assertParentLoop(self, wt, c):
 
997
        self.assertEqual(self._other[b'dir_id'], c.file_id)
 
998
        self.assertEqual(self._other[b'target_id'], c.conflict_file_id)
 
999
        # The conflict paths are irrelevant (they are deterministic but not
 
1000
        # worth checking since they don't provide the needed information
 
1001
        # anyway)
 
1002
        if self._other['xfail']:
 
1003
            # It's a bit hackish to raise from here relying on being called for
 
1004
            # both tests but this avoid overriding test_resolve_taking_other
 
1005
            self.knownFailure(
 
1006
                "ParentLoop doesn't carry enough info to resolve --take-other")
 
1007
    _assert_conflict = assertParentLoop
 
1008
 
 
1009
 
 
1010
class TestResolveNonDirectoryParent(TestResolveConflicts):
 
1011
 
 
1012
    preamble = """
 
1013
$ brz init trunk
 
1014
...
 
1015
$ cd trunk
 
1016
$ brz mkdir foo
 
1017
...
 
1018
$ brz commit -m 'Create trunk' -q
 
1019
$ echo "Boing" >foo/bar
 
1020
$ brz add -q foo/bar
 
1021
$ brz commit -q -m 'Add foo/bar'
 
1022
$ brz branch -q . -r 1 ../branch
 
1023
$ cd ../branch
 
1024
$ rm -r foo
 
1025
$ echo "Boo!" >foo
 
1026
$ brz commit -q -m 'foo is now a file'
 
1027
$ brz merge ../trunk
 
1028
2>+N  foo.new/bar
 
1029
2>RK  foo => foo.new/
 
1030
# FIXME: The message is misleading, foo.new *is* a directory when the message
 
1031
# is displayed -- vila 090916
 
1032
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
 
1033
2>1 conflicts encountered.
 
1034
"""
 
1035
 
 
1036
    def test_take_this(self):
 
1037
        self.run_script("""
 
1038
$ brz rm -q foo.new --no-backup
 
1039
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
 
1040
# aside ? -- vila 090916
 
1041
$ brz add -q foo
 
1042
$ brz resolve foo.new
 
1043
2>1 conflict resolved, 0 remaining
 
1044
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1045
""")
 
1046
 
 
1047
    def test_take_other(self):
 
1048
        self.run_script("""
 
1049
$ brz rm -q foo --no-backup
 
1050
$ brz mv -q foo.new foo
 
1051
$ brz resolve foo
 
1052
2>1 conflict resolved, 0 remaining
 
1053
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1054
""")
 
1055
 
 
1056
    def test_resolve_taking_this(self):
 
1057
        self.run_script("""
 
1058
$ brz resolve --take-this foo.new
 
1059
2>...
 
1060
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1061
""")
 
1062
 
 
1063
    def test_resolve_taking_other(self):
 
1064
        self.run_script("""
 
1065
$ brz resolve --take-other foo.new
 
1066
2>...
 
1067
$ brz commit -q --strict -m 'No more conflicts nor unknown files'
 
1068
""")
 
1069
 
 
1070
 
 
1071
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
 
1072
 
 
1073
    def test_bug_430129(self):
 
1074
        # This is nearly like TestResolveNonDirectoryParent but with branch and
 
1075
        # trunk switched. As such it should certainly produce the same
 
1076
        # conflict.
 
1077
        self.assertRaises(errors.MalformedTransform,
 
1078
                          self.run_script, """
 
1079
$ brz init trunk
 
1080
...
 
1081
$ cd trunk
 
1082
$ brz mkdir foo
 
1083
...
 
1084
$ brz commit -m 'Create trunk' -q
 
1085
$ rm -r foo
 
1086
$ echo "Boo!" >foo
 
1087
$ brz commit -m 'foo is now a file' -q
 
1088
$ brz branch -q . -r 1 ../branch -q
 
1089
$ cd ../branch
 
1090
$ echo "Boing" >foo/bar
 
1091
$ brz add -q foo/bar -q
 
1092
$ brz commit -m 'Add foo/bar' -q
 
1093
$ brz merge ../trunk
 
1094
2>brz: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
 
1095
""")
 
1096
 
 
1097
 
 
1098
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
 
1099
 
 
1100
    def test_bug_805809(self):
 
1101
        self.run_script("""
 
1102
$ brz init trunk
 
1103
Created a standalone tree (format: 2a)
 
1104
$ cd trunk
 
1105
$ echo trunk >file
 
1106
$ brz add
 
1107
adding file
 
1108
$ brz commit -m 'create file on trunk'
 
1109
2>Committing to: .../trunk/
 
1110
2>added file
 
1111
2>Committed revision 1.
 
1112
# Create a debian branch based on trunk
 
1113
$ cd ..
 
1114
$ brz branch trunk -r 1 debian
 
1115
2>Branched 1 revision.
 
1116
$ cd debian
 
1117
$ mkdir dir
 
1118
$ brz add
 
1119
adding dir
 
1120
$ brz mv file dir
 
1121
file => dir/file
 
1122
$ brz commit -m 'rename file to dir/file for debian'
 
1123
2>Committing to: .../debian/
 
1124
2>added dir
 
1125
2>renamed file => dir/file
 
1126
2>Committed revision 2.
 
1127
# Create an experimental branch with a new root-id
 
1128
$ cd ..
 
1129
$ brz init experimental
 
1130
Created a standalone tree (format: 2a)
 
1131
$ cd experimental
 
1132
# Work around merging into empty branch not being supported
 
1133
# (http://pad.lv/308562)
 
1134
$ echo something >not-empty
 
1135
$ brz add
 
1136
adding not-empty
 
1137
$ brz commit -m 'Add some content in experimental'
 
1138
2>Committing to: .../experimental/
 
1139
2>added not-empty
 
1140
2>Committed revision 1.
 
1141
# merge debian even without a common ancestor
 
1142
$ brz merge ../debian -r0..2
 
1143
2>+N  dir/
 
1144
2>+N  dir/file
 
1145
2>All changes applied successfully.
 
1146
$ brz commit -m 'merging debian into experimental'
 
1147
2>Committing to: .../experimental/
 
1148
2>added dir
 
1149
2>added dir/file
 
1150
2>Committed revision 2.
 
1151
# Create an ubuntu branch with yet another root-id
 
1152
$ cd ..
 
1153
$ brz init ubuntu
 
1154
Created a standalone tree (format: 2a)
 
1155
$ cd ubuntu
 
1156
# Work around merging into empty branch not being supported
 
1157
# (http://pad.lv/308562)
 
1158
$ echo something >not-empty-ubuntu
 
1159
$ brz add
 
1160
adding not-empty-ubuntu
 
1161
$ brz commit -m 'Add some content in experimental'
 
1162
2>Committing to: .../ubuntu/
 
1163
2>added not-empty-ubuntu
 
1164
2>Committed revision 1.
 
1165
# Also merge debian
 
1166
$ brz merge ../debian -r0..2
 
1167
2>+N  dir/
 
1168
2>+N  dir/file
 
1169
2>All changes applied successfully.
 
1170
$ brz commit -m 'merging debian'
 
1171
2>Committing to: .../ubuntu/
 
1172
2>added dir
 
1173
2>added dir/file
 
1174
2>Committed revision 2.
 
1175
# Now try to merge experimental
 
1176
$ brz merge ../experimental
 
1177
2>+N  not-empty
 
1178
2>Path conflict: dir / dir
 
1179
2>1 conflicts encountered.
 
1180
""")
 
1181
 
 
1182
 
 
1183
class TestResolveActionOption(tests.TestCase):
 
1184
 
 
1185
    def setUp(self):
 
1186
        super(TestResolveActionOption, self).setUp()
 
1187
        self.options = [conflicts.ResolveActionOption()]
 
1188
        self.parser = option.get_optparser(dict((o.name, o)
 
1189
                                                for o in self.options))
 
1190
 
 
1191
    def parse(self, args):
 
1192
        return self.parser.parse_args(args)
 
1193
 
 
1194
    def test_unknown_action(self):
 
1195
        self.assertRaises(option.BadOptionValue,
 
1196
                          self.parse, ['--action', 'take-me-to-the-moon'])
 
1197
 
 
1198
    def test_done(self):
 
1199
        opts, args = self.parse(['--action', 'done'])
 
1200
        self.assertEqual({'action':'done'}, opts)
 
1201
 
 
1202
    def test_take_this(self):
 
1203
        opts, args = self.parse(['--action', 'take-this'])
 
1204
        self.assertEqual({'action': 'take_this'}, opts)
 
1205
        opts, args = self.parse(['--take-this'])
 
1206
        self.assertEqual({'action': 'take_this'}, opts)
 
1207
 
 
1208
    def test_take_other(self):
 
1209
        opts, args = self.parse(['--action', 'take-other'])
 
1210
        self.assertEqual({'action': 'take_other'}, opts)
 
1211
        opts, args = self.parse(['--take-other'])
 
1212
        self.assertEqual({'action': 'take_other'}, opts)