~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: Patch Queue Manager
  • Date: 2011-09-15 15:37:20 UTC
  • mfrom: (6140.1.3 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110915153720-n17t6m5oh5bblqad
(vila) Open 2.5b2 for bugfixes (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
import os
19
19
 
20
20
from bzrlib import (
21
 
    branchbuilder,
22
21
    bzrdir,
23
22
    conflicts,
24
23
    errors,
25
24
    option,
 
25
    osutils,
26
26
    tests,
27
 
    workingtree,
28
 
    )
29
 
from bzrlib.tests import script
30
 
 
31
 
 
32
 
def load_tests(standard_tests, module, loader):
33
 
    result = loader.suiteClass()
34
 
 
35
 
    sp_tests, remaining_tests = tests.split_suite_by_condition(
36
 
        standard_tests, tests.condition_isinstance((
37
 
                TestParametrizedResolveConflicts,
38
 
                )))
39
 
    # Each test class defines its own scenarios. This is needed for
40
 
    # TestResolvePathConflictBefore531967 that verifies that the same tests as
41
 
    # TestResolvePathConflict still pass.
42
 
    for test in tests.iter_suite_tests(sp_tests):
43
 
        tests.apply_scenarios(test, test.scenarios(), result)
44
 
 
45
 
    # No parametrization for the remaining tests
46
 
    result.addTests(remaining_tests)
47
 
 
48
 
    return result
 
27
    )
 
28
from bzrlib.tests import (
 
29
    script,
 
30
    scenarios,
 
31
    )
 
32
 
 
33
 
 
34
load_tests = scenarios.load_tests_apply_scenarios
49
35
 
50
36
 
51
37
# TODO: Test commit with some added, and added-but-missing files
76
62
])
77
63
 
78
64
 
 
65
def vary_by_conflicts():
 
66
    for conflict in example_conflicts:
 
67
        yield (conflict.__class__.__name__, {"conflict": conflict})
 
68
 
 
69
 
79
70
class TestConflicts(tests.TestCaseWithTransport):
80
71
 
81
 
    def test_conflicts(self):
82
 
        """Conflicts are detected properly"""
83
 
        # Use BzrDirFormat6 so we can fake conflicts
84
 
        tree = self.make_branch_and_tree('.', format=bzrdir.BzrDirFormat6())
85
 
        self.build_tree_contents([('hello', 'hello world4'),
86
 
                                  ('hello.THIS', 'hello world2'),
87
 
                                  ('hello.BASE', 'hello world1'),
88
 
                                  ('hello.OTHER', 'hello world3'),
89
 
                                  ('hello.sploo.BASE', 'yellowworld'),
90
 
                                  ('hello.sploo.OTHER', 'yellowworld2'),
91
 
                                  ])
92
 
        tree.lock_read()
93
 
        self.assertLength(6, list(tree.list_files()))
94
 
        tree.unlock()
95
 
        tree_conflicts = tree.conflicts()
96
 
        self.assertLength(2, tree_conflicts)
97
 
        self.assertTrue('hello' in tree_conflicts[0].path)
98
 
        self.assertTrue('hello.sploo' in tree_conflicts[1].path)
99
 
        conflicts.restore('hello')
100
 
        conflicts.restore('hello.sploo')
101
 
        self.assertLength(0, tree.conflicts())
102
 
        self.assertFileEqual('hello world2', 'hello')
103
 
        self.assertFalse(os.path.lexists('hello.sploo'))
104
 
        self.assertRaises(errors.NotConflicted, conflicts.restore, 'hello')
105
 
        self.assertRaises(errors.NotConflicted,
106
 
                          conflicts.restore, 'hello.sploo')
107
 
 
108
72
    def test_resolve_conflict_dir(self):
109
73
        tree = self.make_branch_and_tree('.')
110
74
        self.build_tree_contents([('hello', 'hello world4'),
161
125
        self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
162
126
 
163
127
 
164
 
class TestConflictStanzas(tests.TestCase):
 
128
class TestPerConflict(tests.TestCase):
 
129
 
 
130
    scenarios = scenarios.multiply_scenarios(vary_by_conflicts())
 
131
 
 
132
    def test_stringification(self):
 
133
        text = unicode(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__)
165
138
 
166
139
    def test_stanza_roundtrip(self):
167
 
        # write and read our example stanza.
168
 
        stanza_iter = example_conflicts.to_stanzas()
169
 
        processed = conflicts.ConflictList.from_stanzas(stanza_iter)
170
 
        for o, p in zip(processed, example_conflicts):
171
 
            self.assertEqual(o, p)
172
 
 
173
 
            self.assertIsInstance(o.path, unicode)
174
 
 
175
 
            if o.file_id is not None:
176
 
                self.assertIsInstance(o.file_id, str)
177
 
 
178
 
            conflict_path = getattr(o, 'conflict_path', None)
179
 
            if conflict_path is not None:
180
 
                self.assertIsInstance(conflict_path, unicode)
181
 
 
182
 
            conflict_file_id = getattr(o, 'conflict_file_id', None)
183
 
            if conflict_file_id is not None:
184
 
                self.assertIsInstance(conflict_file_id, str)
 
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, unicode)
 
145
 
 
146
        if o.file_id is not None:
 
147
            self.assertIsInstance(o.file_id, str)
 
148
 
 
149
        conflict_path = getattr(o, 'conflict_path', None)
 
150
        if conflict_path is not None:
 
151
            self.assertIsInstance(conflict_path, unicode)
 
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, str)
185
156
 
186
157
    def test_stanzification(self):
187
 
        for stanza in example_conflicts.to_stanzas():
188
 
            if 'file_id' in stanza:
189
 
                # In Stanza form, the file_id has to be unicode.
190
 
                self.assertStartsWith(stanza['file_id'], u'\xeed')
191
 
            self.assertStartsWith(stanza['path'], u'p\xe5th')
192
 
            if 'conflict_path' in stanza:
193
 
                self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
194
 
            if 'conflict_file_id' in stanza:
195
 
                self.assertStartsWith(stanza['conflict_file_id'], u'\xeed')
 
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, unicode(o))
196
179
 
197
180
 
198
181
# FIXME: The shell-like tests should be converted to real whitebox tests... or
210
193
        self.run_script(self.preamble)
211
194
 
212
195
 
213
 
class TestResolveTextConflicts(TestResolveConflicts):
214
 
    # TBC
215
 
    pass
 
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
216
213
 
217
214
 
218
215
# FIXME: Get rid of parametrized (in the class name) once we delete
220
217
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
221
218
    """This class provides a base to test single conflict resolution.
222
219
 
223
 
    The aim is to define scenarios in daughter classes (one for each conflict
224
 
    type) that create a single conflict object when one branch is merged in
225
 
    another (and vice versa). Each class can define as many scenarios as
226
 
    needed. Each scenario should define a couple of actions that will be
227
 
    swapped to define the sibling scenarios.
228
 
 
229
 
    From there, both resolutions are tested (--take-this and --take-other).
230
 
 
231
 
    Each conflict type use its attributes in a specific way, so each class 
232
 
    should define a specific _assert_conflict method.
233
 
 
234
 
    Since the resolution change the working tree state, each action should
235
 
    define an associated check.
 
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.
236
263
    """
237
264
 
238
265
    # Set by daughter classes
241
268
 
242
269
    # Set by load_tests
243
270
    _base_actions = None
244
 
    _this_actions = None
245
 
    _other_actions = None
246
 
    _item_path = None
247
 
    _item_id = None
248
 
 
249
 
    # Set by _this_actions and other_actions
250
 
    # FIXME: rename them this_args and other_args so the tests can use them
251
 
    # more freely
252
 
    _this_path = None
253
 
    _this_id = None
254
 
    _other_path = None
255
 
    _other_id = None
256
 
 
257
 
    @classmethod
258
 
    def mirror_scenarios(klass, base_scenarios):
259
 
        scenarios = []
260
 
        def adapt(d, side):
261
 
            """Modify dict to apply to the given side.
262
 
 
263
 
            'actions' key is turned into '_actions_this' if side is 'this' for
264
 
            example.
265
 
            """
266
 
            t = {}
267
 
            # Turn each key into _side_key
268
 
            for k,v in d.iteritems():
269
 
                t['_%s_%s' % (k, side)] = v
270
 
            return t
271
 
        # Each base scenario is duplicated switching the roles of 'this' and
272
 
        # 'other'
273
 
        left = [l for l, r, c in base_scenarios]
274
 
        right = [r for l, r, c in base_scenarios]
275
 
        common = [c for l, r, c in base_scenarios]
276
 
        for (lname, ldict), (rname, rdict), common in zip(left, right, common):
277
 
            a = tests.multiply_scenarios([(lname, adapt(ldict, 'this'))],
278
 
                                         [(rname, adapt(rdict, 'other'))])
279
 
            b = tests.multiply_scenarios(
280
 
                    [(rname, adapt(rdict, 'this'))],
281
 
                    [(lname, adapt(ldict, 'other'))])
282
 
            # Inject the common parameters in all scenarios
283
 
            for name, d in a + b:
284
 
                d.update(common)
285
 
            scenarios.extend(a + b)
286
 
        return scenarios
287
 
 
288
 
    @classmethod
289
 
    def scenarios(klass):
290
 
        # Only concrete classes return actual scenarios
291
 
        return []
 
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
    """
292
299
 
293
300
    def setUp(self):
294
301
        super(TestParametrizedResolveConflicts, self).setUp()
299
306
        builder.build_snapshot('start', None, [
300
307
                ('add', ('', 'root-id', 'directory', ''))])
301
308
        # Add a minimal base content
302
 
        _, _, actions_base = self._get_actions(self._actions_base)()
303
 
        builder.build_snapshot('base', ['start'], actions_base)
 
309
        base_actions = self._get_actions(self._base_actions)()
 
310
        builder.build_snapshot('base', ['start'], base_actions)
304
311
        # Modify the base content in branch
305
 
        (self._other_path, self._other_id,
306
 
         actions_other) = self._get_actions(self._actions_other)()
 
312
        actions_other = self._get_actions(self._other['actions'])()
307
313
        builder.build_snapshot('other', ['base'], actions_other)
308
314
        # Modify the base content in trunk
309
 
        (self._this_path, self._this_id,
310
 
         actions_this) = self._get_actions(self._actions_this)()
 
315
        actions_this = self._get_actions(self._this['actions'])()
311
316
        builder.build_snapshot('this', ['base'], actions_this)
312
317
        # builder.get_branch() tip is now 'this'
313
318
 
320
325
    def _get_check(self, name):
321
326
        return getattr(self, 'check_%s' % name)
322
327
 
323
 
    def do_nothing(self):
324
 
        return (None, None, [])
325
 
 
326
 
    def do_create_file(self):
327
 
        return ('file', 'file-id',
328
 
                [('add', ('file', 'file-id', 'file', 'trunk content\n'))])
329
 
 
330
 
    def do_create_file_a(self):
331
 
        return ('file', 'file-a-id',
332
 
                [('add', ('file', 'file-a-id', 'file', 'file a content\n'))])
333
 
 
334
 
    def check_file_content_a(self):
335
 
        self.assertFileEqual('file a content\n', 'branch/file')
336
 
 
337
 
    def do_create_file_b(self):
338
 
        return ('file', 'file-b-id',
339
 
                [('add', ('file', 'file-b-id', 'file', 'file b content\n'))])
340
 
 
341
 
    def check_file_content_b(self):
342
 
        self.assertFileEqual('file b content\n', 'branch/file')
343
 
 
344
 
    def do_create_dir(self):
345
 
        return ('dir', 'dir-id', [('add', ('dir', 'dir-id', 'directory', ''))])
346
 
 
347
 
    def do_modify_file(self):
348
 
        return ('file', 'file-id',
349
 
                [('modify', ('file-id', 'trunk content\nmore content\n'))])
350
 
 
351
 
    def check_file_has_more_content(self):
352
 
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
353
 
 
354
 
    def do_delete_file(self):
355
 
        return ('file', 'file-id', [('unversion', 'file-id')])
356
 
 
357
 
    def check_file_doesnt_exist(self):
358
 
        self.failIfExists('branch/file')
359
 
 
360
 
    def do_rename_file(self):
361
 
        return ('new-file', 'file-id', [('rename', ('file', 'new-file'))])
362
 
 
363
 
    def check_file_renamed(self):
364
 
        self.failIfExists('branch/file')
365
 
        self.failUnlessExists('branch/new-file')
366
 
 
367
 
    def do_rename_file2(self):
368
 
        return ('new-file2', 'file-id', [('rename', ('file', 'new-file2'))])
369
 
 
370
 
    def check_file_renamed2(self):
371
 
        self.failIfExists('branch/file')
372
 
        self.failUnlessExists('branch/new-file2')
373
 
 
374
 
    def do_rename_dir(self):
375
 
        return ('new-dir', 'dir-id', [('rename', ('dir', 'new-dir'))])
376
 
 
377
 
    def check_dir_renamed(self):
378
 
        self.failIfExists('branch/dir')
379
 
        self.failUnlessExists('branch/new-dir')
380
 
 
381
 
    def do_rename_dir2(self):
382
 
        return ('new-dir2', 'dir-id', [('rename', ('dir', 'new-dir2'))])
383
 
 
384
 
    def check_dir_renamed2(self):
385
 
        self.failIfExists('branch/dir')
386
 
        self.failUnlessExists('branch/new-dir2')
387
 
 
388
 
    def do_delete_dir(self):
389
 
        return ('<deleted>', 'dir-id', [('unversion', 'dir-id')])
390
 
 
391
 
    def check_dir_doesnt_exist(self):
392
 
        self.failIfExists('branch/dir')
393
 
 
394
328
    def _merge_other_into_this(self):
395
329
        b = self.builder.get_branch()
396
330
        wt = b.bzrdir.sprout('branch').open_workingtree()
405
339
        self._assert_conflict(wt, c)
406
340
 
407
341
    def _get_resolve_path_arg(self, wt, action):
408
 
        return self._item_path
 
342
        raise NotImplementedError(self._get_resolve_path_arg)
409
343
 
410
344
    def check_resolved(self, wt, action):
411
345
        path = self._get_resolve_path_arg(wt, action)
418
352
        wt = self._merge_other_into_this()
419
353
        self.assertConflict(wt)
420
354
        self.check_resolved(wt, 'take_this')
421
 
        check_this = self._get_check(self._check_this)
 
355
        check_this = self._get_check(self._this['check'])
422
356
        check_this()
423
357
 
424
358
    def test_resolve_taking_other(self):
425
359
        wt = self._merge_other_into_this()
426
360
        self.assertConflict(wt)
427
361
        self.check_resolved(wt, 'take_other')
428
 
        check_other = self._get_check(self._check_other)
 
362
        check_other = self._get_check(self._other['check'])
429
363
        check_other()
430
364
 
431
365
 
 
366
class TestResolveTextConflicts(TestParametrizedResolveConflicts):
 
367
 
 
368
    _conflict_type = conflicts.TextConflict
 
369
 
 
370
    # Set by the scenarios
 
371
    # path and file-id for the file involved in the conflict
 
372
    _path = None
 
373
    _file_id = None
 
374
 
 
375
    scenarios = mirror_scenarios(
 
376
        [
 
377
            # File modified on both sides
 
378
            (dict(_base_actions='create_file',
 
379
                  _path='file', _file_id='file-id'),
 
380
             ('filed_modified_A',
 
381
              dict(actions='modify_file_A', check='file_has_content_A')),
 
382
             ('file_modified_B',
 
383
              dict(actions='modify_file_B', check='file_has_content_B')),),
 
384
            # File modified on both sides in dir
 
385
            (dict(_base_actions='create_file_in_dir',
 
386
                  _path='dir/file', _file_id='file-id'),
 
387
             ('filed_modified_A_in_dir',
 
388
              dict(actions='modify_file_A',
 
389
                   check='file_in_dir_has_content_A')),
 
390
             ('file_modified_B',
 
391
              dict(actions='modify_file_B',
 
392
                   check='file_in_dir_has_content_B')),),
 
393
            ])
 
394
 
 
395
    def do_create_file(self, path='file'):
 
396
        return [('add', (path, 'file-id', 'file', 'trunk content\n'))]
 
397
 
 
398
    def do_modify_file_A(self):
 
399
        return [('modify', ('file-id', 'trunk content\nfeature A\n'))]
 
400
 
 
401
    def do_modify_file_B(self):
 
402
        return [('modify', ('file-id', 'trunk content\nfeature B\n'))]
 
403
 
 
404
    def check_file_has_content_A(self, path='file'):
 
405
        self.assertFileEqual('trunk content\nfeature A\n',
 
406
                             osutils.pathjoin('branch', path))
 
407
 
 
408
    def check_file_has_content_B(self, path='file'):
 
409
        self.assertFileEqual('trunk content\nfeature B\n',
 
410
                             osutils.pathjoin('branch', path))
 
411
 
 
412
    def do_create_file_in_dir(self):
 
413
        return [('add', ('dir', 'dir-id', 'directory', '')),
 
414
            ] + self.do_create_file('dir/file')
 
415
 
 
416
    def check_file_in_dir_has_content_A(self):
 
417
        self.check_file_has_content_A('dir/file')
 
418
 
 
419
    def check_file_in_dir_has_content_B(self):
 
420
        self.check_file_has_content_B('dir/file')
 
421
 
 
422
    def _get_resolve_path_arg(self, wt, action):
 
423
        return self._path
 
424
 
 
425
    def assertTextConflict(self, wt, c):
 
426
        self.assertEqual(self._file_id, c.file_id)
 
427
        self.assertEqual(self._path, c.path)
 
428
    _assert_conflict = assertTextConflict
 
429
 
 
430
 
432
431
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
433
432
 
434
 
    _conflict_type = conflicts.ContentsConflict,
435
 
    @classmethod
436
 
    def scenarios(klass):
437
 
        base_scenarios = [
438
 
            (('file_modified', dict(actions='modify_file',
439
 
                                   check='file_has_more_content')),
440
 
             ('file_deleted', dict(actions='delete_file',
441
 
                                   check='file_doesnt_exist')),
442
 
             dict(_actions_base='create_file', _item_path='file')),
443
 
            ]
444
 
        return klass.mirror_scenarios(base_scenarios)
 
433
    _conflict_type = conflicts.ContentsConflict
 
434
 
 
435
    # Set by the scenarios
 
436
    # path and file-id for the file involved in the conflict
 
437
    _path = None
 
438
    _file_id = None
 
439
 
 
440
    scenarios = mirror_scenarios(
 
441
        [
 
442
            # File modified/deleted
 
443
            (dict(_base_actions='create_file',
 
444
                  _path='file', _file_id='file-id'),
 
445
             ('file_modified',
 
446
              dict(actions='modify_file', check='file_has_more_content')),
 
447
             ('file_deleted',
 
448
              dict(actions='delete_file', check='file_doesnt_exist')),),
 
449
            # File renamed-modified/deleted
 
450
            (dict(_base_actions='create_file',
 
451
                  _path='new-file', _file_id='file-id'),
 
452
             ('file_renamed_and_modified',
 
453
              dict(actions='modify_and_rename_file',
 
454
                   check='file_renamed_and_more_content')),
 
455
             ('file_deleted',
 
456
              dict(actions='delete_file', check='file_doesnt_exist')),),
 
457
            # File modified/deleted in dir
 
458
            (dict(_base_actions='create_file_in_dir',
 
459
                  _path='dir/file', _file_id='file-id'),
 
460
             ('file_modified_in_dir',
 
461
              dict(actions='modify_file_in_dir',
 
462
                   check='file_in_dir_has_more_content')),
 
463
             ('file_deleted_in_dir',
 
464
              dict(actions='delete_file',
 
465
                   check='file_in_dir_doesnt_exist')),),
 
466
            ])
 
467
 
 
468
    def do_create_file(self):
 
469
        return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
 
470
 
 
471
    def do_modify_file(self):
 
472
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
 
473
 
 
474
    def do_modify_and_rename_file(self):
 
475
        return [('modify', ('file-id', 'trunk content\nmore content\n')),
 
476
                ('rename', ('file', 'new-file'))]
 
477
 
 
478
    def check_file_has_more_content(self):
 
479
        self.assertFileEqual('trunk content\nmore content\n', 'branch/file')
 
480
 
 
481
    def check_file_renamed_and_more_content(self):
 
482
        self.assertFileEqual('trunk content\nmore content\n', 'branch/new-file')
 
483
 
 
484
    def do_delete_file(self):
 
485
        return [('unversion', 'file-id')]
 
486
 
 
487
    def check_file_doesnt_exist(self):
 
488
        self.assertPathDoesNotExist('branch/file')
 
489
 
 
490
    def do_create_file_in_dir(self):
 
491
        return [('add', ('dir', 'dir-id', 'directory', '')),
 
492
                ('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
 
493
 
 
494
    def do_modify_file_in_dir(self):
 
495
        return [('modify', ('file-id', 'trunk content\nmore content\n'))]
 
496
 
 
497
    def check_file_in_dir_has_more_content(self):
 
498
        self.assertFileEqual('trunk content\nmore content\n', 'branch/dir/file')
 
499
 
 
500
    def check_file_in_dir_doesnt_exist(self):
 
501
        self.assertPathDoesNotExist('branch/dir/file')
 
502
 
 
503
    def _get_resolve_path_arg(self, wt, action):
 
504
        return self._path
445
505
 
446
506
    def assertContentsConflict(self, wt, c):
447
 
        self.assertEqual(self._other_id, c.file_id)
448
 
        self.assertEqual(self._other_path, c.path)
 
507
        self.assertEqual(self._file_id, c.file_id)
 
508
        self.assertEqual(self._path, c.path)
449
509
    _assert_conflict = assertContentsConflict
450
510
 
451
511
 
452
 
 
453
512
class TestResolvePathConflict(TestParametrizedResolveConflicts):
454
513
 
455
 
    _conflict_type = conflicts.PathConflict,
456
 
 
457
 
    @classmethod
458
 
    def scenarios(klass):
459
 
        for_file = dict(_actions_base='create_file',
460
 
                  _item_path='new-file', _item_id='file-id',)
461
 
        for_dir = dict(_actions_base='create_dir',
462
 
                        _item_path='new-dir', _item_id='dir-id',)
463
 
        base_scenarios = [
464
 
            (('file_renamed',
465
 
              dict(actions='rename_file', check='file_renamed')),
466
 
             ('file_deleted',
467
 
              dict(actions='delete_file', check='file_doesnt_exist')),
468
 
             for_file),
469
 
            (('file_renamed',
470
 
              dict(actions='rename_file', check='file_renamed')),
 
514
    _conflict_type = conflicts.PathConflict
 
515
 
 
516
    def do_nothing(self):
 
517
        return []
 
518
 
 
519
    # Each side dict additionally defines:
 
520
    # - path path involved (can be '<deleted>')
 
521
    # - file-id involved
 
522
    scenarios = mirror_scenarios(
 
523
        [
 
524
            # File renamed/deleted
 
525
            (dict(_base_actions='create_file'),
 
526
             ('file_renamed',
 
527
              dict(actions='rename_file', check='file_renamed',
 
528
                   path='new-file', file_id='file-id')),
 
529
             ('file_deleted',
 
530
              dict(actions='delete_file', check='file_doesnt_exist',
 
531
                   # PathConflicts deletion handling requires a special
 
532
                   # hard-coded value
 
533
                   path='<deleted>', file_id='file-id')),),
 
534
            # File renamed/deleted in dir
 
535
            (dict(_base_actions='create_file_in_dir'),
 
536
             ('file_renamed_in_dir',
 
537
              dict(actions='rename_file_in_dir', check='file_in_dir_renamed',
 
538
                   path='dir/new-file', file_id='file-id')),
 
539
             ('file_deleted',
 
540
              dict(actions='delete_file', check='file_in_dir_doesnt_exist',
 
541
                   # PathConflicts deletion handling requires a special
 
542
                   # hard-coded value
 
543
                   path='<deleted>', file_id='file-id')),),
 
544
            # File renamed/renamed differently
 
545
            (dict(_base_actions='create_file'),
 
546
             ('file_renamed',
 
547
              dict(actions='rename_file', check='file_renamed',
 
548
                   path='new-file', file_id='file-id')),
471
549
             ('file_renamed2',
472
 
              dict(actions='rename_file2', check='file_renamed2')),
473
 
             for_file),
474
 
            (('dir_renamed',
475
 
              dict(actions='rename_dir', check='dir_renamed')),
 
550
              dict(actions='rename_file2', check='file_renamed2',
 
551
                   path='new-file2', file_id='file-id')),),
 
552
            # Dir renamed/deleted
 
553
            (dict(_base_actions='create_dir'),
 
554
             ('dir_renamed',
 
555
              dict(actions='rename_dir', check='dir_renamed',
 
556
                   path='new-dir', file_id='dir-id')),
476
557
             ('dir_deleted',
477
 
              dict(actions='delete_dir', check='dir_doesnt_exist')),
478
 
             for_dir),
479
 
            (('dir_renamed',
480
 
              dict(actions='rename_dir', check='dir_renamed')),
 
558
              dict(actions='delete_dir', check='dir_doesnt_exist',
 
559
                   # PathConflicts deletion handling requires a special
 
560
                   # hard-coded value
 
561
                   path='<deleted>', file_id='dir-id')),),
 
562
            # Dir renamed/renamed differently
 
563
            (dict(_base_actions='create_dir'),
 
564
             ('dir_renamed',
 
565
              dict(actions='rename_dir', check='dir_renamed',
 
566
                   path='new-dir', file_id='dir-id')),
481
567
             ('dir_renamed2',
482
 
              dict(actions='rename_dir2', check='dir_renamed2')),
483
 
             for_dir),
484
 
        ]
485
 
        return klass.mirror_scenarios(base_scenarios)
 
568
              dict(actions='rename_dir2', check='dir_renamed2',
 
569
                   path='new-dir2', file_id='dir-id')),),
 
570
            ])
 
571
 
 
572
    def do_create_file(self):
 
573
        return [('add', ('file', 'file-id', 'file', 'trunk content\n'))]
 
574
 
 
575
    def do_create_dir(self):
 
576
        return [('add', ('dir', 'dir-id', 'directory', ''))]
 
577
 
 
578
    def do_rename_file(self):
 
579
        return [('rename', ('file', 'new-file'))]
 
580
 
 
581
    def check_file_renamed(self):
 
582
        self.assertPathDoesNotExist('branch/file')
 
583
        self.assertPathExists('branch/new-file')
 
584
 
 
585
    def do_rename_file2(self):
 
586
        return [('rename', ('file', 'new-file2'))]
 
587
 
 
588
    def check_file_renamed2(self):
 
589
        self.assertPathDoesNotExist('branch/file')
 
590
        self.assertPathExists('branch/new-file2')
 
591
 
 
592
    def do_rename_dir(self):
 
593
        return [('rename', ('dir', 'new-dir'))]
 
594
 
 
595
    def check_dir_renamed(self):
 
596
        self.assertPathDoesNotExist('branch/dir')
 
597
        self.assertPathExists('branch/new-dir')
 
598
 
 
599
    def do_rename_dir2(self):
 
600
        return [('rename', ('dir', 'new-dir2'))]
 
601
 
 
602
    def check_dir_renamed2(self):
 
603
        self.assertPathDoesNotExist('branch/dir')
 
604
        self.assertPathExists('branch/new-dir2')
486
605
 
487
606
    def do_delete_file(self):
488
 
        sup = super(TestResolvePathConflict, self).do_delete_file()
489
 
        # PathConflicts handle deletion differently and requires a special
490
 
        # hard-coded value
491
 
        return ('<deleted>',) + sup[1:]
 
607
        return [('unversion', 'file-id')]
 
608
 
 
609
    def check_file_doesnt_exist(self):
 
610
        self.assertPathDoesNotExist('branch/file')
 
611
 
 
612
    def do_delete_dir(self):
 
613
        return [('unversion', 'dir-id')]
 
614
 
 
615
    def check_dir_doesnt_exist(self):
 
616
        self.assertPathDoesNotExist('branch/dir')
 
617
 
 
618
    def do_create_file_in_dir(self):
 
619
        return [('add', ('dir', 'dir-id', 'directory', '')),
 
620
                ('add', ('dir/file', 'file-id', 'file', 'trunk content\n'))]
 
621
 
 
622
    def do_rename_file_in_dir(self):
 
623
        return [('rename', ('dir/file', 'dir/new-file'))]
 
624
 
 
625
    def check_file_in_dir_renamed(self):
 
626
        self.assertPathDoesNotExist('branch/dir/file')
 
627
        self.assertPathExists('branch/dir/new-file')
 
628
 
 
629
    def check_file_in_dir_doesnt_exist(self):
 
630
        self.assertPathDoesNotExist('branch/dir/file')
 
631
 
 
632
    def _get_resolve_path_arg(self, wt, action):
 
633
        tpath = self._this['path']
 
634
        opath = self._other['path']
 
635
        if tpath == '<deleted>':
 
636
            path = opath
 
637
        else:
 
638
            path = tpath
 
639
        return path
492
640
 
493
641
    def assertPathConflict(self, wt, c):
494
 
        self.assertEqual(self._item_id, c.file_id)
495
 
        self.assertEqual(self._this_path, c.path)
496
 
        self.assertEqual(self._other_path, c.conflict_path)
 
642
        tpath = self._this['path']
 
643
        tfile_id = self._this['file_id']
 
644
        opath = self._other['path']
 
645
        ofile_id = self._other['file_id']
 
646
        self.assertEqual(tfile_id, ofile_id) # Sanity check
 
647
        self.assertEqual(tfile_id, c.file_id)
 
648
        self.assertEqual(tpath, c.path)
 
649
        self.assertEqual(opath, c.conflict_path)
497
650
    _assert_conflict = assertPathConflict
498
651
 
499
652
 
512
665
 
513
666
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
514
667
 
515
 
    _conflict_type = conflicts.DuplicateEntry,
516
 
    @classmethod
517
 
    def scenarios(klass):
518
 
        base_scenarios = [
519
 
            (('filea_created', dict(actions='create_file_a',
520
 
                                    check='file_content_a')),
521
 
             ('fileb_created', dict(actions='create_file_b',
522
 
                                   check='file_content_b')),
523
 
             dict(_actions_base='nothing', _item_path='file')),
524
 
            ]
525
 
        return klass.mirror_scenarios(base_scenarios)
 
668
    _conflict_type = conflicts.DuplicateEntry
 
669
 
 
670
    scenarios = mirror_scenarios(
 
671
        [
 
672
            # File created with different file-ids
 
673
            (dict(_base_actions='nothing'),
 
674
             ('filea_created',
 
675
              dict(actions='create_file_a', check='file_content_a',
 
676
                   path='file', file_id='file-a-id')),
 
677
             ('fileb_created',
 
678
              dict(actions='create_file_b', check='file_content_b',
 
679
                   path='file', file_id='file-b-id')),),
 
680
            ])
 
681
 
 
682
    def do_nothing(self):
 
683
        return []
 
684
 
 
685
    def do_create_file_a(self):
 
686
        return [('add', ('file', 'file-a-id', 'file', 'file a content\n'))]
 
687
 
 
688
    def check_file_content_a(self):
 
689
        self.assertFileEqual('file a content\n', 'branch/file')
 
690
 
 
691
    def do_create_file_b(self):
 
692
        return [('add', ('file', 'file-b-id', 'file', 'file b content\n'))]
 
693
 
 
694
    def check_file_content_b(self):
 
695
        self.assertFileEqual('file b content\n', 'branch/file')
 
696
 
 
697
    def _get_resolve_path_arg(self, wt, action):
 
698
        return self._this['path']
526
699
 
527
700
    def assertDuplicateEntry(self, wt, c):
528
 
        self.assertEqual(self._this_id, c.file_id)
529
 
        self.assertEqual(self._item_path + '.moved', c.path)
530
 
        self.assertEqual(self._item_path, c.conflict_path)
 
701
        tpath = self._this['path']
 
702
        tfile_id = self._this['file_id']
 
703
        opath = self._other['path']
 
704
        ofile_id = self._other['file_id']
 
705
        self.assertEqual(tpath, opath) # Sanity check
 
706
        self.assertEqual(tfile_id, c.file_id)
 
707
        self.assertEqual(tpath + '.moved', c.path)
 
708
        self.assertEqual(tpath, c.conflict_path)
531
709
    _assert_conflict = assertDuplicateEntry
532
710
 
533
711
 
539
717
    # tests MissingParent resolution :-/
540
718
    preamble = """
541
719
$ bzr init trunk
 
720
...
542
721
$ cd trunk
543
722
$ mkdir dir
544
 
$ bzr add dir
545
 
$ bzr commit -m 'Create trunk'
546
 
 
 
723
$ bzr add -q dir
 
724
$ bzr commit -m 'Create trunk' -q
547
725
$ echo 'trunk content' >dir/file
548
 
$ bzr add dir/file
549
 
$ bzr commit -m 'Add dir/file in trunk'
550
 
 
551
 
$ bzr branch . -r 1 ../branch
 
726
$ bzr add -q dir/file
 
727
$ bzr commit -q -m 'Add dir/file in trunk'
 
728
$ bzr branch -q . -r 1 ../branch
552
729
$ cd ../branch
553
 
$ bzr rm dir
554
 
$ bzr commit -m 'Remove dir in branch'
555
 
 
 
730
$ bzr rm dir -q
 
731
$ bzr commit -q -m 'Remove dir in branch'
556
732
$ bzr merge ../trunk
557
733
2>+N  dir/
558
734
2>+N  dir/file
563
739
 
564
740
    def test_take_this(self):
565
741
        self.run_script("""
566
 
$ bzr rm dir  --force
 
742
$ bzr rm -q dir  --force
567
743
$ bzr resolve dir
568
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
744
2>2 conflict(s) resolved, 0 remaining
 
745
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
569
746
""")
570
747
 
571
748
    def test_take_other(self):
572
749
        self.run_script("""
573
750
$ bzr resolve dir
574
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
751
2>2 conflict(s) resolved, 0 remaining
 
752
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
575
753
""")
576
754
 
577
755
 
579
757
 
580
758
    preamble = """
581
759
$ bzr init trunk
 
760
...
582
761
$ cd trunk
583
762
$ mkdir dir
584
763
$ echo 'trunk content' >dir/file
585
 
$ bzr add
586
 
$ bzr commit -m 'Create trunk'
587
 
 
 
764
$ bzr add -q
 
765
$ bzr commit -m 'Create trunk' -q
588
766
$ echo 'trunk content' >dir/file2
589
 
$ bzr add dir/file2
590
 
$ bzr commit -m 'Add dir/file2 in branch'
591
 
 
592
 
$ bzr branch . -r 1 ../branch
 
767
$ bzr add -q dir/file2
 
768
$ bzr commit -q -m 'Add dir/file2 in branch'
 
769
$ bzr branch -q . -r 1 ../branch
593
770
$ cd ../branch
594
 
$ bzr rm dir/file --force
595
 
$ bzr rm dir
596
 
$ bzr commit -m 'Remove dir/file'
597
 
 
 
771
$ bzr rm -q dir/file --force
 
772
$ bzr rm -q dir
 
773
$ bzr commit -q -m 'Remove dir/file'
598
774
$ bzr merge ../trunk
599
775
2>+N  dir/
600
776
2>+N  dir/file2
606
782
    def test_keep_them_all(self):
607
783
        self.run_script("""
608
784
$ bzr resolve dir
609
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
785
2>2 conflict(s) resolved, 0 remaining
 
786
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
610
787
""")
611
788
 
612
789
    def test_adopt_child(self):
613
790
        self.run_script("""
614
 
$ bzr mv dir/file2 file2
615
 
$ bzr rm dir --force
 
791
$ bzr mv -q dir/file2 file2
 
792
$ bzr rm -q dir --force
616
793
$ bzr resolve dir
617
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
794
2>2 conflict(s) resolved, 0 remaining
 
795
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
618
796
""")
619
797
 
620
798
    def test_kill_them_all(self):
621
799
        self.run_script("""
622
 
$ bzr rm dir --force
 
800
$ bzr rm -q dir --force
623
801
$ bzr resolve dir
624
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
802
2>2 conflict(s) resolved, 0 remaining
 
803
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
625
804
""")
626
805
 
627
806
    def test_resolve_taking_this(self):
628
807
        self.run_script("""
629
808
$ bzr resolve --take-this dir
630
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
809
2>...
 
810
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
631
811
""")
632
812
 
633
813
    def test_resolve_taking_other(self):
634
814
        self.run_script("""
635
815
$ bzr resolve --take-other dir
636
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
816
2>...
 
817
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
637
818
""")
638
819
 
639
820
 
641
822
 
642
823
    preamble = """
643
824
$ bzr init trunk
 
825
...
644
826
$ cd trunk
645
827
$ mkdir dir
646
828
$ echo 'trunk content' >dir/file
647
 
$ bzr add
648
 
$ bzr commit -m 'Create trunk'
649
 
 
650
 
$ bzr rm dir/file --force
651
 
$ bzr rm dir --force
652
 
$ bzr commit -m 'Remove dir/file'
653
 
 
654
 
$ bzr branch . -r 1 ../branch
 
829
$ bzr add -q
 
830
$ bzr commit -m 'Create trunk' -q
 
831
$ bzr rm -q dir/file --force
 
832
$ bzr rm -q dir --force
 
833
$ bzr commit -q -m 'Remove dir/file'
 
834
$ bzr branch -q . -r 1 ../branch
655
835
$ cd ../branch
656
836
$ echo 'branch content' >dir/file2
657
 
$ bzr add dir/file2
658
 
$ bzr commit -m 'Add dir/file2 in branch'
659
 
 
 
837
$ bzr add -q dir/file2
 
838
$ bzr commit -q -m 'Add dir/file2 in branch'
660
839
$ bzr merge ../trunk
661
840
2>-D  dir/file
662
841
2>Conflict: can't delete dir because it is not empty.  Not deleting.
667
846
    def test_keep_them_all(self):
668
847
        self.run_script("""
669
848
$ bzr resolve dir
670
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
849
2>2 conflict(s) resolved, 0 remaining
 
850
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
671
851
""")
672
852
 
673
853
    def test_adopt_child(self):
674
854
        self.run_script("""
675
 
$ bzr mv dir/file2 file2
676
 
$ bzr rm dir --force
 
855
$ bzr mv -q dir/file2 file2
 
856
$ bzr rm -q dir --force
677
857
$ bzr resolve dir
678
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
858
2>2 conflict(s) resolved, 0 remaining
 
859
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
679
860
""")
680
861
 
681
862
    def test_kill_them_all(self):
682
863
        self.run_script("""
683
 
$ bzr rm dir --force
 
864
$ bzr rm -q dir --force
684
865
$ bzr resolve dir
685
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
866
2>2 conflict(s) resolved, 0 remaining
 
867
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
686
868
""")
687
869
 
688
870
    def test_resolve_taking_this(self):
689
871
        self.run_script("""
690
872
$ bzr resolve --take-this dir
691
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
873
2>2 conflict(s) resolved, 0 remaining
 
874
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
692
875
""")
693
876
 
694
877
    def test_resolve_taking_other(self):
695
878
        self.run_script("""
696
879
$ bzr resolve --take-other dir
697
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
880
2>deleted dir/file2
 
881
2>deleted dir
 
882
2>2 conflict(s) resolved, 0 remaining
 
883
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
698
884
""")
699
885
 
700
886
 
701
887
class TestResolveParentLoop(TestParametrizedResolveConflicts):
702
888
 
703
 
    _conflict_type = conflicts.ParentLoop,
704
 
    @classmethod
705
 
    def scenarios(klass):
706
 
        base_scenarios = [
707
 
            (('dir1_into_dir2', dict(actions='move_dir1_into_dir2',
708
 
                                      check='dir1_moved')),
709
 
             ('dir2_into_dir1', dict(actions='move_dir2_into_dir1',
710
 
                                      check='dir2_moved')),
711
 
             dict(_actions_base='create_dir1_dir2')),
712
 
            (('dir1_into_dir4', dict(actions='move_dir1_into_dir4',
713
 
                                      check='dir1_2_moved')),
714
 
             ('dir3_into_dir2', dict(actions='move_dir3_into_dir2',
715
 
                                      check='dir3_4_moved')),
716
 
             dict(_actions_base='create_dir1_4')),
717
 
            ]
718
 
        return klass.mirror_scenarios(base_scenarios)
 
889
    _conflict_type = conflicts.ParentLoop
 
890
 
 
891
    _this_args = None
 
892
    _other_args = None
 
893
 
 
894
    # Each side dict additionally defines:
 
895
    # - dir_id: the directory being moved
 
896
    # - target_id: The target directory
 
897
    # - xfail: whether the test is expected to fail if the action is
 
898
    #   involved as 'other'
 
899
    scenarios = mirror_scenarios(
 
900
        [
 
901
            # Dirs moved into each other
 
902
            (dict(_base_actions='create_dir1_dir2'),
 
903
             ('dir1_into_dir2',
 
904
              dict(actions='move_dir1_into_dir2', check='dir1_moved',
 
905
                   dir_id='dir1-id', target_id='dir2-id', xfail=False)),
 
906
             ('dir2_into_dir1',
 
907
              dict(actions='move_dir2_into_dir1', check='dir2_moved',
 
908
                   dir_id='dir2-id', target_id='dir1-id', xfail=False))),
 
909
            # Subdirs moved into each other
 
910
            (dict(_base_actions='create_dir1_4'),
 
911
             ('dir1_into_dir4',
 
912
              dict(actions='move_dir1_into_dir4', check='dir1_2_moved',
 
913
                   dir_id='dir1-id', target_id='dir4-id', xfail=True)),
 
914
             ('dir3_into_dir2',
 
915
              dict(actions='move_dir3_into_dir2', check='dir3_4_moved',
 
916
                   dir_id='dir3-id', target_id='dir2-id', xfail=True))),
 
917
            ])
719
918
 
720
919
    def do_create_dir1_dir2(self):
721
 
        return (None, None,
722
 
                [('add', ('dir1', 'dir1-id', 'directory', '')),
723
 
                 ('add', ('dir2', 'dir2-id', 'directory', '')),
724
 
                 ])
 
920
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
 
921
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
725
922
 
726
923
    def do_move_dir1_into_dir2(self):
727
 
        # The arguments are the file-id to move and the targeted file-id dir.
728
 
        return ('dir1-id', 'dir2-id', [('rename', ('dir1', 'dir2/dir1'))])
 
924
        return [('rename', ('dir1', 'dir2/dir1'))]
729
925
 
730
926
    def check_dir1_moved(self):
731
 
        self.failIfExists('branch/dir1')
732
 
        self.failUnlessExists('branch/dir2/dir1')
 
927
        self.assertPathDoesNotExist('branch/dir1')
 
928
        self.assertPathExists('branch/dir2/dir1')
733
929
 
734
930
    def do_move_dir2_into_dir1(self):
735
 
        # The arguments are the file-id to move and the targeted file-id dir.
736
 
        return ('dir2-id', 'dir1-id', [('rename', ('dir2', 'dir1/dir2'))])
 
931
        return [('rename', ('dir2', 'dir1/dir2'))]
737
932
 
738
933
    def check_dir2_moved(self):
739
 
        self.failIfExists('branch/dir2')
740
 
        self.failUnlessExists('branch/dir1/dir2')
 
934
        self.assertPathDoesNotExist('branch/dir2')
 
935
        self.assertPathExists('branch/dir1/dir2')
741
936
 
742
937
    def do_create_dir1_4(self):
743
 
        return (None, None,
744
 
                [('add', ('dir1', 'dir1-id', 'directory', '')),
745
 
                 ('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
746
 
                 ('add', ('dir3', 'dir3-id', 'directory', '')),
747
 
                 ('add', ('dir3/dir4', 'dir4-id', 'directory', '')),
748
 
                 ])
 
938
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
 
939
                ('add', ('dir1/dir2', 'dir2-id', 'directory', '')),
 
940
                ('add', ('dir3', 'dir3-id', 'directory', '')),
 
941
                ('add', ('dir3/dir4', 'dir4-id', 'directory', '')),]
749
942
 
750
943
    def do_move_dir1_into_dir4(self):
751
 
        # The arguments are the file-id to move and the targeted file-id dir.
752
 
        return ('dir1-id', 'dir4-id',
753
 
                [('rename', ('dir1', 'dir3/dir4/dir1'))])
 
944
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
754
945
 
755
946
    def check_dir1_2_moved(self):
756
 
        self.failIfExists('branch/dir1')
757
 
        self.failUnlessExists('branch/dir3/dir4/dir1')
758
 
        self.failUnlessExists('branch/dir3/dir4/dir1/dir2')
 
947
        self.assertPathDoesNotExist('branch/dir1')
 
948
        self.assertPathExists('branch/dir3/dir4/dir1')
 
949
        self.assertPathExists('branch/dir3/dir4/dir1/dir2')
759
950
 
760
951
    def do_move_dir3_into_dir2(self):
761
 
        # The arguments are the file-id to move and the targeted file-id dir.
762
 
        return ('dir3-id', 'dir2-id',
763
 
                [('rename', ('dir3', 'dir1/dir2/dir3'))])
 
952
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
764
953
 
765
954
    def check_dir3_4_moved(self):
766
 
        self.failIfExists('branch/dir3')
767
 
        self.failUnlessExists('branch/dir1/dir2/dir3')
768
 
        self.failUnlessExists('branch/dir1/dir2/dir3/dir4')
 
955
        self.assertPathDoesNotExist('branch/dir3')
 
956
        self.assertPathExists('branch/dir1/dir2/dir3')
 
957
        self.assertPathExists('branch/dir1/dir2/dir3/dir4')
769
958
 
770
959
    def _get_resolve_path_arg(self, wt, action):
771
 
        # ParentLoop is unsual as it says: 
772
 
        # moving <conflict_path> into <path>.  Cancelled move.
 
960
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
773
961
        # But since <path> doesn't exist in the working tree, we need to use
774
 
        # <conflict_path> instead
775
 
        path = wt.id2path(self._other_id)
776
 
        return path
 
962
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
 
963
        return wt.id2path(self._other['dir_id'])
777
964
 
778
965
    def assertParentLoop(self, wt, c):
779
 
        if 'taking_other(' in self.id() and 'dir4' in self.id():
780
 
            raise tests.KnownFailure(
781
 
                "ParentLoop doesn't carry enough info to resolve")
782
 
        # The relevant file-ids are other_args swapped (which is the main
783
 
        # reason why they should be renamed other_args instead of Other_path
784
 
        # and other_id). In the conflict object, they represent:
785
 
        # c.file_id: the directory being moved
786
 
        # c.conflict_id_id: The target directory
787
 
        self.assertEqual(self._other_path, c.file_id)
788
 
        self.assertEqual(self._other_id, c.conflict_file_id)
 
966
        self.assertEqual(self._other['dir_id'], c.file_id)
 
967
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
789
968
        # The conflict paths are irrelevant (they are deterministic but not
790
969
        # worth checking since they don't provide the needed information
791
970
        # anyway)
 
971
        if self._other['xfail']:
 
972
            # It's a bit hackish to raise from here relying on being called for
 
973
            # both tests but this avoid overriding test_resolve_taking_other
 
974
            self.knownFailure(
 
975
                "ParentLoop doesn't carry enough info to resolve --take-other")
792
976
    _assert_conflict = assertParentLoop
793
977
 
794
978
 
795
 
class OldTestResolveParentLoop(TestResolveConflicts):
796
 
 
797
 
    preamble = """
798
 
$ bzr init trunk
799
 
$ cd trunk
800
 
$ bzr mkdir dir1
801
 
$ bzr mkdir dir2
802
 
$ bzr commit -m 'Create trunk'
803
 
 
804
 
$ bzr mv dir2 dir1
805
 
$ bzr commit -m 'Moved dir2 into dir1'
806
 
 
807
 
$ bzr branch . -r 1 ../branch
808
 
$ cd ../branch
809
 
$ bzr mv dir1 dir2
810
 
$ bzr commit -m 'Moved dir1 into dir2'
811
 
 
812
 
$ bzr merge ../trunk
813
 
2>Conflict moving dir2 into dir2/dir1. Cancelled move.
814
 
2>1 conflicts encountered.
815
 
"""
816
 
 
817
 
    def test_take_this(self):
818
 
        self.run_script("""
819
 
$ bzr resolve dir2
820
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
821
 
""")
822
 
 
823
 
    def test_take_other(self):
824
 
        self.run_script("""
825
 
$ bzr mv dir2/dir1 dir1
826
 
$ bzr mv dir2 dir1
827
 
$ bzr resolve dir2
828
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
829
 
""")
830
 
 
831
 
    def test_resolve_taking_this(self):
832
 
        self.run_script("""
833
 
$ bzr resolve --take-this dir2
834
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
835
 
""")
836
 
        self.failUnlessExists('dir2')
837
 
 
838
 
    def test_resolve_taking_other(self):
839
 
        self.run_script("""
840
 
$ bzr resolve --take-other dir2
841
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
842
 
""")
843
 
        self.failUnlessExists('dir1')
844
 
 
845
 
 
846
979
class TestResolveNonDirectoryParent(TestResolveConflicts):
847
980
 
848
981
    preamble = """
849
982
$ bzr init trunk
 
983
...
850
984
$ cd trunk
851
985
$ bzr mkdir foo
852
 
$ bzr commit -m 'Create trunk'
 
986
...
 
987
$ bzr commit -m 'Create trunk' -q
853
988
$ echo "Boing" >foo/bar
854
 
$ bzr add foo/bar
855
 
$ bzr commit -m 'Add foo/bar'
856
 
 
857
 
$ bzr branch . -r 1 ../branch
 
989
$ bzr add -q foo/bar
 
990
$ bzr commit -q -m 'Add foo/bar'
 
991
$ bzr branch -q . -r 1 ../branch
858
992
$ cd ../branch
859
993
$ rm -r foo
860
994
$ echo "Boo!" >foo
861
 
$ bzr commit -m 'foo is now a file'
862
 
 
 
995
$ bzr commit -q -m 'foo is now a file'
863
996
$ bzr merge ../trunk
864
997
2>+N  foo.new/bar
865
998
2>RK  foo => foo.new/
871
1004
 
872
1005
    def test_take_this(self):
873
1006
        self.run_script("""
874
 
$ bzr rm foo.new --force
 
1007
$ bzr rm -q foo.new --force
875
1008
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
876
1009
# aside ? -- vila 090916
877
 
$ bzr add foo
 
1010
$ bzr add -q foo
878
1011
$ bzr resolve foo.new
879
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
1012
2>1 conflict(s) resolved, 0 remaining
 
1013
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
880
1014
""")
881
1015
 
882
1016
    def test_take_other(self):
883
1017
        self.run_script("""
884
 
$ bzr rm foo --force
885
 
$ bzr mv foo.new foo
 
1018
$ bzr rm -q foo --force
 
1019
$ bzr mv -q foo.new foo
886
1020
$ bzr resolve foo
887
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
1021
2>1 conflict(s) resolved, 0 remaining
 
1022
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
888
1023
""")
889
1024
 
890
1025
    def test_resolve_taking_this(self):
891
1026
        self.run_script("""
892
1027
$ bzr resolve --take-this foo.new
893
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
1028
2>...
 
1029
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
894
1030
""")
895
1031
 
896
1032
    def test_resolve_taking_other(self):
897
1033
        self.run_script("""
898
1034
$ bzr resolve --take-other foo.new
899
 
$ bzr commit --strict -m 'No more conflicts nor unknown files'
 
1035
2>...
 
1036
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
900
1037
""")
901
1038
 
902
1039
 
908
1045
        # conflict.
909
1046
        self.run_script("""
910
1047
$ bzr init trunk
 
1048
...
911
1049
$ cd trunk
912
1050
$ bzr mkdir foo
913
 
$ bzr commit -m 'Create trunk'
 
1051
...
 
1052
$ bzr commit -m 'Create trunk' -q
914
1053
$ rm -r foo
915
1054
$ echo "Boo!" >foo
916
 
$ bzr commit -m 'foo is now a file'
917
 
 
918
 
$ bzr branch . -r 1 ../branch
 
1055
$ bzr commit -m 'foo is now a file' -q
 
1056
$ bzr branch -q . -r 1 ../branch -q
919
1057
$ cd ../branch
920
1058
$ echo "Boing" >foo/bar
921
 
$ bzr add foo/bar
922
 
$ bzr commit -m 'Add foo/bar'
923
 
 
 
1059
$ bzr add -q foo/bar -q
 
1060
$ bzr commit -m 'Add foo/bar' -q
924
1061
$ bzr merge ../trunk
925
1062
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
926
1063
""")
927
1064
 
928
1065
 
 
1066
class TestNoFinalPath(script.TestCaseWithTransportAndScript):
 
1067
 
 
1068
    def test_bug_805809(self):
 
1069
        self.run_script("""
 
1070
$ bzr init trunk
 
1071
Created a standalone tree (format: 2a)
 
1072
$ cd trunk
 
1073
$ echo trunk >file
 
1074
$ bzr add
 
1075
adding file
 
1076
$ bzr commit -m 'create file on trunk'
 
1077
2>Committing to: .../trunk/
 
1078
2>added file
 
1079
2>Committed revision 1.
 
1080
# Create a debian branch based on trunk
 
1081
$ cd ..
 
1082
$ bzr branch trunk -r 1 debian
 
1083
2>Branched 1 revision(s).
 
1084
$ cd debian
 
1085
$ mkdir dir
 
1086
$ bzr add
 
1087
adding dir
 
1088
$ bzr mv file dir
 
1089
file => dir/file
 
1090
$ bzr commit -m 'rename file to dir/file for debian'
 
1091
2>Committing to: .../debian/
 
1092
2>added dir
 
1093
2>renamed file => dir/file
 
1094
2>Committed revision 2.
 
1095
# Create an experimental branch with a new root-id
 
1096
$ cd ..
 
1097
$ bzr init experimental
 
1098
Created a standalone tree (format: 2a)
 
1099
$ cd experimental
 
1100
# Work around merging into empty branch not being supported
 
1101
# (http://pad.lv/308562)
 
1102
$ echo something >not-empty
 
1103
$ bzr add
 
1104
adding not-empty
 
1105
$ bzr commit -m 'Add some content in experimental'
 
1106
2>Committing to: .../experimental/
 
1107
2>added not-empty
 
1108
2>Committed revision 1.
 
1109
# merge debian even without a common ancestor
 
1110
$ bzr merge ../debian -r0..2
 
1111
2>+N  dir/
 
1112
2>+N  dir/file
 
1113
2>All changes applied successfully.
 
1114
$ bzr commit -m 'merging debian into experimental'
 
1115
2>Committing to: .../experimental/
 
1116
2>added dir
 
1117
2>added dir/file
 
1118
2>Committed revision 2.
 
1119
# Create an ubuntu branch with yet another root-id
 
1120
$ cd ..
 
1121
$ bzr init ubuntu
 
1122
Created a standalone tree (format: 2a)
 
1123
$ cd ubuntu
 
1124
# Work around merging into empty branch not being supported
 
1125
# (http://pad.lv/308562)
 
1126
$ echo something >not-empty-ubuntu
 
1127
$ bzr add
 
1128
adding not-empty-ubuntu
 
1129
$ bzr commit -m 'Add some content in experimental'
 
1130
2>Committing to: .../ubuntu/
 
1131
2>added not-empty-ubuntu
 
1132
2>Committed revision 1.
 
1133
# Also merge debian
 
1134
$ bzr merge ../debian -r0..2
 
1135
2>+N  dir/
 
1136
2>+N  dir/file
 
1137
2>All changes applied successfully.
 
1138
$ bzr commit -m 'merging debian'
 
1139
2>Committing to: .../ubuntu/
 
1140
2>added dir
 
1141
2>added dir/file
 
1142
2>Committed revision 2.
 
1143
# Now try to merge experimental
 
1144
$ bzr merge ../experimental
 
1145
2>+N  not-empty
 
1146
2>Path conflict: dir / dir
 
1147
2>1 conflicts encountered.
 
1148
""")
 
1149
 
 
1150
 
929
1151
class TestResolveActionOption(tests.TestCase):
930
1152
 
931
1153
    def setUp(self):