/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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