~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
import os
19
19
 
20
 
from bzrlib import bzrdir
21
 
from bzrlib.tests import TestCaseWithTransport, TestCase
22
 
from bzrlib.branch import Branch
23
 
from bzrlib.conflicts import (
24
 
    ConflictList,
25
 
    ContentsConflict,
26
 
    DuplicateID,
27
 
    DuplicateEntry,
28
 
    MissingParent,
29
 
    NonDirectoryParent,
30
 
    ParentLoop,
31
 
    PathConflict,
32
 
    TextConflict,
33
 
    UnversionedParent,
34
 
    resolve,
35
 
    restore,
36
 
    )
37
 
from bzrlib.errors import NotConflicted
 
20
from bzrlib import (
 
21
    bzrdir,
 
22
    conflicts,
 
23
    errors,
 
24
    option,
 
25
    osutils,
 
26
    tests,
 
27
    )
 
28
from bzrlib.tests import (
 
29
    script,
 
30
    scenarios,
 
31
    )
 
32
 
 
33
 
 
34
load_tests = scenarios.load_tests_apply_scenarios
38
35
 
39
36
 
40
37
# TODO: Test commit with some added, and added-but-missing files
44
41
# be a sorted list.
45
42
# u'\xe5' == a with circle
46
43
# '\xc3\xae' == u'\xee' == i with hat
47
 
# So these are u'pathg' and 'idg' only with a circle and a hat. (shappo?)
48
 
example_conflicts = ConflictList([ 
49
 
    MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
50
 
    ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'), 
51
 
    TextConflict(u'p\xe5tha'),
52
 
    PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
53
 
    DuplicateID('Unversioned existing file', u'p\xe5thc', u'p\xe5thc2',
54
 
                '\xc3\xaedc', '\xc3\xaedc'),
55
 
    DuplicateEntry('Moved existing file to',  u'p\xe5thdd.moved', u'p\xe5thd',
56
 
                   '\xc3\xaedd', None),
57
 
    ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
58
 
               None, '\xc3\xaed2e'),
59
 
    UnversionedParent('Versioned directory', u'p\xe5thf', '\xc3\xaedf'),
60
 
    NonDirectoryParent('Created directory', u'p\xe5thg', '\xc3\xaedg'),
 
44
# So these are u'path' and 'id' only with a circle and a hat. (shappo?)
 
45
example_conflicts = conflicts.ConflictList(
 
46
    [conflicts.MissingParent('Not deleting', u'p\xe5thg', '\xc3\xaedg'),
 
47
     conflicts.ContentsConflict(u'p\xe5tha', None, '\xc3\xaeda'),
 
48
     conflicts.TextConflict(u'p\xe5tha'),
 
49
     conflicts.PathConflict(u'p\xe5thb', u'p\xe5thc', '\xc3\xaedb'),
 
50
     conflicts.DuplicateID('Unversioned existing file',
 
51
                           u'p\xe5thc', u'p\xe5thc2',
 
52
                           '\xc3\xaedc', '\xc3\xaedc'),
 
53
    conflicts.DuplicateEntry('Moved existing file to',
 
54
                             u'p\xe5thdd.moved', u'p\xe5thd',
 
55
                             '\xc3\xaedd', None),
 
56
    conflicts.ParentLoop('Cancelled move', u'p\xe5the', u'p\xe5th2e',
 
57
                         None, '\xc3\xaed2e'),
 
58
    conflicts.UnversionedParent('Versioned directory',
 
59
                                u'p\xe5thf', '\xc3\xaedf'),
 
60
    conflicts.NonDirectoryParent('Created directory',
 
61
                                 u'p\xe5thg', '\xc3\xaedg'),
61
62
])
62
63
 
63
64
 
64
 
class TestConflicts(TestCaseWithTransport):
65
 
 
66
 
    def test_conflicts(self):
67
 
        """Conflicts are detected properly"""
68
 
        tree = self.make_branch_and_tree('.',
69
 
            format=bzrdir.BzrDirFormat6())
70
 
        b = tree.branch
71
 
        file('hello', 'w').write('hello world4')
72
 
        file('hello.THIS', 'w').write('hello world2')
73
 
        file('hello.BASE', 'w').write('hello world1')
74
 
        file('hello.OTHER', 'w').write('hello world3')
75
 
        file('hello.sploo.BASE', 'w').write('yellow world')
76
 
        file('hello.sploo.OTHER', 'w').write('yellow world2')
77
 
        tree.lock_read()
78
 
        self.assertEqual(len(list(tree.list_files())), 6)
79
 
        tree.unlock()
80
 
        conflicts = tree.conflicts()
81
 
        self.assertEqual(len(conflicts), 2)
82
 
        self.assert_('hello' in conflicts[0].path)
83
 
        self.assert_('hello.sploo' in conflicts[1].path)
84
 
        restore('hello')
85
 
        restore('hello.sploo')
86
 
        self.assertEqual(len(tree.conflicts()), 0)
87
 
        self.assertFileEqual('hello world2', 'hello')
88
 
        self.assertFalse(os.path.lexists('hello.sploo'))
89
 
        self.assertRaises(NotConflicted, restore, 'hello')
90
 
        self.assertRaises(NotConflicted, restore, 'hello.sploo')
 
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):
91
71
 
92
72
    def test_resolve_conflict_dir(self):
93
73
        tree = self.make_branch_and_tree('.')
94
 
        b = tree.branch
95
 
        file('hello', 'w').write('hello world4')
 
74
        self.build_tree_contents([('hello', 'hello world4'),
 
75
                                  ('hello.THIS', 'hello world2'),
 
76
                                  ('hello.BASE', 'hello world1'),
 
77
                                  ])
 
78
        os.mkdir('hello.OTHER')
96
79
        tree.add('hello', 'q')
97
 
        file('hello.THIS', 'w').write('hello world2')
98
 
        file('hello.BASE', 'w').write('hello world1')
99
 
        os.mkdir('hello.OTHER')
100
 
        l = ConflictList([TextConflict('hello')])
 
80
        l = conflicts.ConflictList([conflicts.TextConflict('hello')])
101
81
        l.remove_files(tree)
102
82
 
103
83
    def test_select_conflicts(self):
104
84
        tree = self.make_branch_and_tree('.')
105
 
        tree_conflicts = ConflictList([ContentsConflict('foo'),
106
 
                                       ContentsConflict('bar')])
107
 
        self.assertEqual((ConflictList([ContentsConflict('bar')]),
108
 
                          ConflictList([ContentsConflict('foo')])),
109
 
                         tree_conflicts.select_conflicts(tree, ['foo']))
110
 
        self.assertEqual((ConflictList(), tree_conflicts),
111
 
                         tree_conflicts.select_conflicts(tree, [''],
112
 
                         ignore_misses=True, recurse=True))
113
 
        tree_conflicts = ConflictList([ContentsConflict('foo/baz'),
114
 
                                       ContentsConflict('bar')])
115
 
        self.assertEqual((ConflictList([ContentsConflict('bar')]),
116
 
                          ConflictList([ContentsConflict('foo/baz')])),
117
 
                         tree_conflicts.select_conflicts(tree, ['foo'],
118
 
                                                         recurse=True,
119
 
                                                         ignore_misses=True))
120
 
        tree_conflicts = ConflictList([PathConflict('qux', 'foo/baz')])
121
 
        self.assertEqual((ConflictList(), tree_conflicts),
122
 
                         tree_conflicts.select_conflicts(tree, ['foo'],
123
 
                                                         recurse=True,
124
 
                                                         ignore_misses=True))
125
 
        self.assertEqual((tree_conflicts, ConflictList()),
126
 
                         tree_conflicts.select_conflicts(tree, ['foo'],
127
 
                                                         ignore_misses=True))
 
85
        clist = conflicts.ConflictList
 
86
 
 
87
        def check_select(not_selected, selected, paths, **kwargs):
 
88
            self.assertEqual(
 
89
                (not_selected, selected),
 
90
                tree_conflicts.select_conflicts(tree, paths, **kwargs))
 
91
 
 
92
        foo = conflicts.ContentsConflict('foo')
 
93
        bar = conflicts.ContentsConflict('bar')
 
94
        tree_conflicts = clist([foo, bar])
 
95
 
 
96
        check_select(clist([bar]), clist([foo]), ['foo'])
 
97
        check_select(clist(), tree_conflicts,
 
98
                     [''], ignore_misses=True, recurse=True)
 
99
 
 
100
        foobaz  = conflicts.ContentsConflict('foo/baz')
 
101
        tree_conflicts = clist([foobaz, bar])
 
102
 
 
103
        check_select(clist([bar]), clist([foobaz]),
 
104
                     ['foo'], ignore_misses=True, recurse=True)
 
105
 
 
106
        qux = conflicts.PathConflict('qux', 'foo/baz')
 
107
        tree_conflicts = clist([qux])
 
108
 
 
109
        check_select(clist(), tree_conflicts,
 
110
                     ['foo'], ignore_misses=True, recurse=True)
 
111
        check_select (tree_conflicts, clist(), ['foo'], ignore_misses=True)
128
112
 
129
113
    def test_resolve_conflicts_recursive(self):
130
114
        tree = self.make_branch_and_tree('.')
131
115
        self.build_tree(['dir/', 'dir/hello'])
132
116
        tree.add(['dir', 'dir/hello'])
133
 
        tree.set_conflicts(ConflictList([TextConflict('dir/hello')]))
134
 
        resolve(tree, ['dir'], recursive=False, ignore_misses=True)
135
 
        self.assertEqual(ConflictList([TextConflict('dir/hello')]),
136
 
                         tree.conflicts())
137
 
        resolve(tree, ['dir'], recursive=True, ignore_misses=True)
138
 
        self.assertEqual(ConflictList([]),
139
 
                         tree.conflicts())
140
 
 
141
 
 
142
 
class TestConflictStanzas(TestCase):
 
117
 
 
118
        dirhello = conflicts.ConflictList([conflicts.TextConflict('dir/hello')])
 
119
        tree.set_conflicts(dirhello)
 
120
 
 
121
        conflicts.resolve(tree, ['dir'], recursive=False, ignore_misses=True)
 
122
        self.assertEqual(dirhello, tree.conflicts())
 
123
 
 
124
        conflicts.resolve(tree, ['dir'], recursive=True, ignore_misses=True)
 
125
        self.assertEqual(conflicts.ConflictList([]), tree.conflicts())
 
126
 
 
127
 
 
128
class TestPerConflict(tests.TestCase):
 
129
 
 
130
    scenarios = scenarios.multiply_scenarios(vary_by_conflicts())
 
131
 
 
132
    def test_stringification(self):
 
133
        text = 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__)
143
138
 
144
139
    def test_stanza_roundtrip(self):
145
 
        # write and read our example stanza.
146
 
        stanza_iter = example_conflicts.to_stanzas()
147
 
        processed = ConflictList.from_stanzas(stanza_iter)
148
 
        for o, p in zip(processed, example_conflicts):
149
 
            self.assertEqual(o, p)
150
 
 
151
 
            self.assertIsInstance(o.path, unicode)
152
 
 
153
 
            if o.file_id is not None:
154
 
                self.assertIsInstance(o.file_id, str)
155
 
 
156
 
            conflict_path = getattr(o, 'conflict_path', None)
157
 
            if conflict_path is not None:
158
 
                self.assertIsInstance(conflict_path, unicode)
159
 
 
160
 
            conflict_file_id = getattr(o, 'conflict_file_id', None)
161
 
            if conflict_file_id is not None:
162
 
                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)
163
156
 
164
157
    def test_stanzification(self):
165
 
        for stanza in example_conflicts.to_stanzas():
166
 
            if 'file_id' in stanza:
167
 
                # In Stanza form, the file_id has to be unicode.
168
 
                self.assertStartsWith(stanza['file_id'], u'\xeed')
169
 
            self.assertStartsWith(stanza['path'], u'p\xe5th')
170
 
            if 'conflict_path' in stanza:
171
 
                self.assertStartsWith(stanza['conflict_path'], u'p\xe5th')
172
 
            if 'conflict_file_id' in stanza:
173
 
                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))
 
179
 
 
180
 
 
181
# FIXME: The shell-like tests should be converted to real whitebox tests... or
 
182
# moved to a blackbox module -- vila 20100205
 
183
 
 
184
# FIXME: test missing for multiple conflicts
 
185
 
 
186
# FIXME: Tests missing for DuplicateID conflict type
 
187
class TestResolveConflicts(script.TestCaseWithTransportAndScript):
 
188
 
 
189
    preamble = None # The setup script set by daughter classes
 
190
 
 
191
    def setUp(self):
 
192
        super(TestResolveConflicts, self).setUp()
 
193
        self.run_script(self.preamble)
 
194
 
 
195
 
 
196
def mirror_scenarios(base_scenarios):
 
197
    """Return a list of mirrored scenarios.
 
198
 
 
199
    Each scenario in base_scenarios is duplicated switching the roles of 'this'
 
200
    and 'other'
 
201
    """
 
202
    scenarios = []
 
203
    for common, (lname, ldict), (rname, rdict) in base_scenarios:
 
204
        a = tests.multiply_scenarios([(lname, dict(_this=ldict))],
 
205
                                     [(rname, dict(_other=rdict))])
 
206
        b = tests.multiply_scenarios([(rname, dict(_this=rdict))],
 
207
                                     [(lname, dict(_other=ldict))])
 
208
        # Inject the common parameters in all scenarios
 
209
        for name, d in a + b:
 
210
            d.update(common)
 
211
        scenarios.extend(a + b)
 
212
    return scenarios
 
213
 
 
214
 
 
215
# FIXME: Get rid of parametrized (in the class name) once we delete
 
216
# TestResolveConflicts -- vila 20100308
 
217
class TestParametrizedResolveConflicts(tests.TestCaseWithTransport):
 
218
    """This class provides a base to test single conflict resolution.
 
219
 
 
220
    Since all conflict objects are created with specific semantics for their
 
221
    attributes, each class should implement the necessary functions and
 
222
    attributes described below.
 
223
 
 
224
    Each class should define the scenarios that create the expected (single)
 
225
    conflict.
 
226
 
 
227
    Each scenario describes:
 
228
    * how to create 'base' tree (and revision)
 
229
    * how to create 'left' tree (and revision, parent rev 'base')
 
230
    * how to create 'right' tree (and revision, parent rev 'base')
 
231
    * how to check that changes in 'base'->'left' have been taken
 
232
    * how to check that changes in 'base'->'right' have been taken
 
233
 
 
234
    From each base scenario, we generate two concrete scenarios where:
 
235
    * this=left, other=right
 
236
    * this=right, other=left
 
237
 
 
238
    Then the test case verifies each concrete scenario by:
 
239
    * creating a branch containing the 'base', 'this' and 'other' revisions
 
240
    * creating a working tree for the 'this' revision
 
241
    * performing the merge of 'other' into 'this'
 
242
    * verifying the expected conflict was generated
 
243
    * resolving with --take-this or --take-other, and running the corresponding
 
244
      checks (for either 'base'->'this', or 'base'->'other')
 
245
 
 
246
    :cvar _conflict_type: The expected class of the generated conflict.
 
247
 
 
248
    :cvar _assert_conflict: A method receiving the working tree and the
 
249
        conflict object and checking its attributes.
 
250
 
 
251
    :cvar _base_actions: The branchbuilder actions to create the 'base'
 
252
        revision.
 
253
 
 
254
    :cvar _this: The dict related to 'base' -> 'this'. It contains at least:
 
255
      * 'actions': The branchbuilder actions to create the 'this'
 
256
          revision.
 
257
      * 'check': how to check the changes after resolution with --take-this.
 
258
 
 
259
    :cvar _other: The dict related to 'base' -> 'other'. It contains at least:
 
260
      * 'actions': The branchbuilder actions to create the 'other'
 
261
          revision.
 
262
      * 'check': how to check the changes after resolution with --take-other.
 
263
    """
 
264
 
 
265
    # Set by daughter classes
 
266
    _conflict_type = None
 
267
    _assert_conflict = None
 
268
 
 
269
    # Set by load_tests
 
270
    _base_actions = None
 
271
    _this = None
 
272
    _other = None
 
273
 
 
274
    scenarios = []
 
275
    """The scenario list for the conflict type defined by the class.
 
276
 
 
277
    Each scenario is of the form:
 
278
    (common, (left_name, left_dict), (right_name, right_dict))
 
279
 
 
280
    * common is a dict
 
281
 
 
282
    * left_name and right_name are the scenario names that will be combined
 
283
 
 
284
    * left_dict and right_dict are the attributes specific to each half of
 
285
      the scenario. They should include at least 'actions' and 'check' and
 
286
      will be available as '_this' and '_other' test instance attributes.
 
287
 
 
288
    Daughters classes are free to add their specific attributes as they see
 
289
    fit in any of the three dicts.
 
290
 
 
291
    This is a class method so that load_tests can find it.
 
292
 
 
293
    '_base_actions' in the common dict, 'actions' and 'check' in the left
 
294
    and right dicts use names that map to methods in the test classes. Some
 
295
    prefixes are added to these names to get the correspong methods (see
 
296
    _get_actions() and _get_check()). The motivation here is to avoid
 
297
    collisions in the class namespace.
 
298
    """
 
299
 
 
300
    def setUp(self):
 
301
        super(TestParametrizedResolveConflicts, self).setUp()
 
302
        builder = self.make_branch_builder('trunk')
 
303
        builder.start_series()
 
304
 
 
305
        # Create an empty trunk
 
306
        builder.build_snapshot('start', None, [
 
307
                ('add', ('', 'root-id', 'directory', ''))])
 
308
        # Add a minimal base content
 
309
        base_actions = self._get_actions(self._base_actions)()
 
310
        builder.build_snapshot('base', ['start'], base_actions)
 
311
        # Modify the base content in branch
 
312
        actions_other = self._get_actions(self._other['actions'])()
 
313
        builder.build_snapshot('other', ['base'], actions_other)
 
314
        # Modify the base content in trunk
 
315
        actions_this = self._get_actions(self._this['actions'])()
 
316
        builder.build_snapshot('this', ['base'], actions_this)
 
317
        # builder.get_branch() tip is now 'this'
 
318
 
 
319
        builder.finish_series()
 
320
        self.builder = builder
 
321
 
 
322
    def _get_actions(self, name):
 
323
        return getattr(self, 'do_%s' % name)
 
324
 
 
325
    def _get_check(self, name):
 
326
        return getattr(self, 'check_%s' % name)
 
327
 
 
328
    def _merge_other_into_this(self):
 
329
        b = self.builder.get_branch()
 
330
        wt = b.bzrdir.sprout('branch').open_workingtree()
 
331
        wt.merge_from_branch(b, 'other')
 
332
        return wt
 
333
 
 
334
    def assertConflict(self, wt):
 
335
        confs = wt.conflicts()
 
336
        self.assertLength(1, confs)
 
337
        c = confs[0]
 
338
        self.assertIsInstance(c, self._conflict_type)
 
339
        self._assert_conflict(wt, c)
 
340
 
 
341
    def _get_resolve_path_arg(self, wt, action):
 
342
        raise NotImplementedError(self._get_resolve_path_arg)
 
343
 
 
344
    def check_resolved(self, wt, action):
 
345
        path = self._get_resolve_path_arg(wt, action)
 
346
        conflicts.resolve(wt, [path], action=action)
 
347
        # Check that we don't have any conflicts nor unknown left
 
348
        self.assertLength(0, wt.conflicts())
 
349
        self.assertLength(0, list(wt.unknowns()))
 
350
 
 
351
    def test_resolve_taking_this(self):
 
352
        wt = self._merge_other_into_this()
 
353
        self.assertConflict(wt)
 
354
        self.check_resolved(wt, 'take_this')
 
355
        check_this = self._get_check(self._this['check'])
 
356
        check_this()
 
357
 
 
358
    def test_resolve_taking_other(self):
 
359
        wt = self._merge_other_into_this()
 
360
        self.assertConflict(wt)
 
361
        self.check_resolved(wt, 'take_other')
 
362
        check_other = self._get_check(self._other['check'])
 
363
        check_other()
 
364
 
 
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
 
 
431
class TestResolveContentsConflict(TestParametrizedResolveConflicts):
 
432
 
 
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
 
505
 
 
506
    def assertContentsConflict(self, wt, c):
 
507
        self.assertEqual(self._file_id, c.file_id)
 
508
        self.assertEqual(self._path, c.path)
 
509
    _assert_conflict = assertContentsConflict
 
510
 
 
511
 
 
512
class TestResolvePathConflict(TestParametrizedResolveConflicts):
 
513
 
 
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')),
 
549
             ('file_renamed2',
 
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')),
 
557
             ('dir_deleted',
 
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')),
 
567
             ('dir_renamed2',
 
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')
 
605
 
 
606
    def do_delete_file(self):
 
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
 
640
 
 
641
    def assertPathConflict(self, wt, c):
 
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)
 
650
    _assert_conflict = assertPathConflict
 
651
 
 
652
 
 
653
class TestResolvePathConflictBefore531967(TestResolvePathConflict):
 
654
    """Same as TestResolvePathConflict but a specific conflict object.
 
655
    """
 
656
 
 
657
    def assertPathConflict(self, c):
 
658
        # We create a conflict object as it was created before the fix and
 
659
        # inject it into the working tree, the test will exercise the
 
660
        # compatibility code.
 
661
        old_c = conflicts.PathConflict('<deleted>', self._item_path,
 
662
                                       file_id=None)
 
663
        wt.set_conflicts(conflicts.ConflictList([old_c]))
 
664
 
 
665
 
 
666
class TestResolveDuplicateEntry(TestParametrizedResolveConflicts):
 
667
 
 
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']
 
699
 
 
700
    def assertDuplicateEntry(self, wt, c):
 
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)
 
709
    _assert_conflict = assertDuplicateEntry
 
710
 
 
711
 
 
712
class TestResolveUnversionedParent(TestResolveConflicts):
 
713
 
 
714
    # FIXME: Add the reverse tests: dir deleted in trunk, file added in branch
 
715
 
 
716
    # FIXME: While this *creates* UnversionedParent conflicts, this really only
 
717
    # tests MissingParent resolution :-/
 
718
    preamble = """
 
719
$ bzr init trunk
 
720
...
 
721
$ cd trunk
 
722
$ mkdir dir
 
723
$ bzr add -q dir
 
724
$ bzr commit -m 'Create trunk' -q
 
725
$ echo 'trunk content' >dir/file
 
726
$ bzr add -q dir/file
 
727
$ bzr commit -q -m 'Add dir/file in trunk'
 
728
$ bzr branch -q . -r 1 ../branch
 
729
$ cd ../branch
 
730
$ bzr rm dir -q
 
731
$ bzr commit -q -m 'Remove dir in branch'
 
732
$ bzr merge ../trunk
 
733
2>+N  dir/
 
734
2>+N  dir/file
 
735
2>Conflict adding files to dir.  Created directory.
 
736
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
737
2>2 conflicts encountered.
 
738
"""
 
739
 
 
740
    def test_take_this(self):
 
741
        self.run_script("""
 
742
$ bzr rm -q dir  --force
 
743
$ bzr resolve dir
 
744
2>2 conflicts resolved, 0 remaining
 
745
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
746
""")
 
747
 
 
748
    def test_take_other(self):
 
749
        self.run_script("""
 
750
$ bzr resolve dir
 
751
2>2 conflicts resolved, 0 remaining
 
752
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
753
""")
 
754
 
 
755
 
 
756
class TestResolveMissingParent(TestResolveConflicts):
 
757
 
 
758
    preamble = """
 
759
$ bzr init trunk
 
760
...
 
761
$ cd trunk
 
762
$ mkdir dir
 
763
$ echo 'trunk content' >dir/file
 
764
$ bzr add -q
 
765
$ bzr commit -m 'Create trunk' -q
 
766
$ echo 'trunk content' >dir/file2
 
767
$ bzr add -q dir/file2
 
768
$ bzr commit -q -m 'Add dir/file2 in branch'
 
769
$ bzr branch -q . -r 1 ../branch
 
770
$ cd ../branch
 
771
$ bzr rm -q dir/file --force
 
772
$ bzr rm -q dir
 
773
$ bzr commit -q -m 'Remove dir/file'
 
774
$ bzr merge ../trunk
 
775
2>+N  dir/
 
776
2>+N  dir/file2
 
777
2>Conflict adding files to dir.  Created directory.
 
778
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
779
2>2 conflicts encountered.
 
780
"""
 
781
 
 
782
    def test_keep_them_all(self):
 
783
        self.run_script("""
 
784
$ bzr resolve dir
 
785
2>2 conflicts resolved, 0 remaining
 
786
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
787
""")
 
788
 
 
789
    def test_adopt_child(self):
 
790
        self.run_script("""
 
791
$ bzr mv -q dir/file2 file2
 
792
$ bzr rm -q dir --force
 
793
$ bzr resolve dir
 
794
2>2 conflicts resolved, 0 remaining
 
795
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
796
""")
 
797
 
 
798
    def test_kill_them_all(self):
 
799
        self.run_script("""
 
800
$ bzr rm -q dir --force
 
801
$ bzr resolve dir
 
802
2>2 conflicts resolved, 0 remaining
 
803
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
804
""")
 
805
 
 
806
    def test_resolve_taking_this(self):
 
807
        self.run_script("""
 
808
$ bzr resolve --take-this dir
 
809
2>...
 
810
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
811
""")
 
812
 
 
813
    def test_resolve_taking_other(self):
 
814
        self.run_script("""
 
815
$ bzr resolve --take-other dir
 
816
2>...
 
817
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
818
""")
 
819
 
 
820
 
 
821
class TestResolveDeletingParent(TestResolveConflicts):
 
822
 
 
823
    preamble = """
 
824
$ bzr init trunk
 
825
...
 
826
$ cd trunk
 
827
$ mkdir dir
 
828
$ echo 'trunk content' >dir/file
 
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
 
835
$ cd ../branch
 
836
$ echo 'branch content' >dir/file2
 
837
$ bzr add -q dir/file2
 
838
$ bzr commit -q -m 'Add dir/file2 in branch'
 
839
$ bzr merge ../trunk
 
840
2>-D  dir/file
 
841
2>Conflict: can't delete dir because it is not empty.  Not deleting.
 
842
2>Conflict because dir is not versioned, but has versioned children.  Versioned directory.
 
843
2>2 conflicts encountered.
 
844
"""
 
845
 
 
846
    def test_keep_them_all(self):
 
847
        self.run_script("""
 
848
$ bzr resolve dir
 
849
2>2 conflicts resolved, 0 remaining
 
850
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
851
""")
 
852
 
 
853
    def test_adopt_child(self):
 
854
        self.run_script("""
 
855
$ bzr mv -q dir/file2 file2
 
856
$ bzr rm -q dir --force
 
857
$ bzr resolve dir
 
858
2>2 conflicts resolved, 0 remaining
 
859
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
860
""")
 
861
 
 
862
    def test_kill_them_all(self):
 
863
        self.run_script("""
 
864
$ bzr rm -q dir --force
 
865
$ bzr resolve dir
 
866
2>2 conflicts resolved, 0 remaining
 
867
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
868
""")
 
869
 
 
870
    def test_resolve_taking_this(self):
 
871
        self.run_script("""
 
872
$ bzr resolve --take-this dir
 
873
2>2 conflicts resolved, 0 remaining
 
874
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
875
""")
 
876
 
 
877
    def test_resolve_taking_other(self):
 
878
        self.run_script("""
 
879
$ bzr resolve --take-other dir
 
880
2>deleted dir/file2
 
881
2>deleted dir
 
882
2>2 conflicts resolved, 0 remaining
 
883
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
884
""")
 
885
 
 
886
 
 
887
class TestResolveParentLoop(TestParametrizedResolveConflicts):
 
888
 
 
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
            ])
 
918
 
 
919
    def do_create_dir1_dir2(self):
 
920
        return [('add', ('dir1', 'dir1-id', 'directory', '')),
 
921
                ('add', ('dir2', 'dir2-id', 'directory', '')),]
 
922
 
 
923
    def do_move_dir1_into_dir2(self):
 
924
        return [('rename', ('dir1', 'dir2/dir1'))]
 
925
 
 
926
    def check_dir1_moved(self):
 
927
        self.assertPathDoesNotExist('branch/dir1')
 
928
        self.assertPathExists('branch/dir2/dir1')
 
929
 
 
930
    def do_move_dir2_into_dir1(self):
 
931
        return [('rename', ('dir2', 'dir1/dir2'))]
 
932
 
 
933
    def check_dir2_moved(self):
 
934
        self.assertPathDoesNotExist('branch/dir2')
 
935
        self.assertPathExists('branch/dir1/dir2')
 
936
 
 
937
    def do_create_dir1_4(self):
 
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', '')),]
 
942
 
 
943
    def do_move_dir1_into_dir4(self):
 
944
        return [('rename', ('dir1', 'dir3/dir4/dir1'))]
 
945
 
 
946
    def check_dir1_2_moved(self):
 
947
        self.assertPathDoesNotExist('branch/dir1')
 
948
        self.assertPathExists('branch/dir3/dir4/dir1')
 
949
        self.assertPathExists('branch/dir3/dir4/dir1/dir2')
 
950
 
 
951
    def do_move_dir3_into_dir2(self):
 
952
        return [('rename', ('dir3', 'dir1/dir2/dir3'))]
 
953
 
 
954
    def check_dir3_4_moved(self):
 
955
        self.assertPathDoesNotExist('branch/dir3')
 
956
        self.assertPathExists('branch/dir1/dir2/dir3')
 
957
        self.assertPathExists('branch/dir1/dir2/dir3/dir4')
 
958
 
 
959
    def _get_resolve_path_arg(self, wt, action):
 
960
        # ParentLoop says: moving <conflict_path> into <path>. Cancelled move.
 
961
        # But since <path> doesn't exist in the working tree, we need to use
 
962
        # <conflict_path> instead, and that, in turn, is given by dir_id. Pfew.
 
963
        return wt.id2path(self._other['dir_id'])
 
964
 
 
965
    def assertParentLoop(self, wt, c):
 
966
        self.assertEqual(self._other['dir_id'], c.file_id)
 
967
        self.assertEqual(self._other['target_id'], c.conflict_file_id)
 
968
        # The conflict paths are irrelevant (they are deterministic but not
 
969
        # worth checking since they don't provide the needed information
 
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")
 
976
    _assert_conflict = assertParentLoop
 
977
 
 
978
 
 
979
class TestResolveNonDirectoryParent(TestResolveConflicts):
 
980
 
 
981
    preamble = """
 
982
$ bzr init trunk
 
983
...
 
984
$ cd trunk
 
985
$ bzr mkdir foo
 
986
...
 
987
$ bzr commit -m 'Create trunk' -q
 
988
$ echo "Boing" >foo/bar
 
989
$ bzr add -q foo/bar
 
990
$ bzr commit -q -m 'Add foo/bar'
 
991
$ bzr branch -q . -r 1 ../branch
 
992
$ cd ../branch
 
993
$ rm -r foo
 
994
$ echo "Boo!" >foo
 
995
$ bzr commit -q -m 'foo is now a file'
 
996
$ bzr merge ../trunk
 
997
2>+N  foo.new/bar
 
998
2>RK  foo => foo.new/
 
999
# FIXME: The message is misleading, foo.new *is* a directory when the message
 
1000
# is displayed -- vila 090916
 
1001
2>Conflict: foo.new is not a directory, but has files in it.  Created directory.
 
1002
2>1 conflicts encountered.
 
1003
"""
 
1004
 
 
1005
    def test_take_this(self):
 
1006
        self.run_script("""
 
1007
$ bzr rm -q foo.new --force
 
1008
# FIXME: Isn't it weird that foo is now unkown even if foo.new has been put
 
1009
# aside ? -- vila 090916
 
1010
$ bzr add -q foo
 
1011
$ bzr resolve foo.new
 
1012
2>1 conflict resolved, 0 remaining
 
1013
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
1014
""")
 
1015
 
 
1016
    def test_take_other(self):
 
1017
        self.run_script("""
 
1018
$ bzr rm -q foo --force
 
1019
$ bzr mv -q foo.new foo
 
1020
$ bzr resolve foo
 
1021
2>1 conflict resolved, 0 remaining
 
1022
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
1023
""")
 
1024
 
 
1025
    def test_resolve_taking_this(self):
 
1026
        self.run_script("""
 
1027
$ bzr resolve --take-this foo.new
 
1028
2>...
 
1029
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
1030
""")
 
1031
 
 
1032
    def test_resolve_taking_other(self):
 
1033
        self.run_script("""
 
1034
$ bzr resolve --take-other foo.new
 
1035
2>...
 
1036
$ bzr commit -q --strict -m 'No more conflicts nor unknown files'
 
1037
""")
 
1038
 
 
1039
 
 
1040
class TestMalformedTransform(script.TestCaseWithTransportAndScript):
 
1041
 
 
1042
    def test_bug_430129(self):
 
1043
        # This is nearly like TestResolveNonDirectoryParent but with branch and
 
1044
        # trunk switched. As such it should certainly produce the same
 
1045
        # conflict.
 
1046
        self.run_script("""
 
1047
$ bzr init trunk
 
1048
...
 
1049
$ cd trunk
 
1050
$ bzr mkdir foo
 
1051
...
 
1052
$ bzr commit -m 'Create trunk' -q
 
1053
$ rm -r foo
 
1054
$ echo "Boo!" >foo
 
1055
$ bzr commit -m 'foo is now a file' -q
 
1056
$ bzr branch -q . -r 1 ../branch -q
 
1057
$ cd ../branch
 
1058
$ echo "Boing" >foo/bar
 
1059
$ bzr add -q foo/bar -q
 
1060
$ bzr commit -m 'Add foo/bar' -q
 
1061
$ bzr merge ../trunk
 
1062
2>bzr: ERROR: Tree transform is malformed [('unversioned executability', 'new-1')]
 
1063
""")
 
1064
 
 
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.
 
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
 
 
1151
class TestResolveActionOption(tests.TestCase):
 
1152
 
 
1153
    def setUp(self):
 
1154
        super(TestResolveActionOption, self).setUp()
 
1155
        self.options = [conflicts.ResolveActionOption()]
 
1156
        self.parser = option.get_optparser(dict((o.name, o)
 
1157
                                                for o in self.options))
 
1158
 
 
1159
    def parse(self, args):
 
1160
        return self.parser.parse_args(args)
 
1161
 
 
1162
    def test_unknown_action(self):
 
1163
        self.assertRaises(errors.BadOptionValue,
 
1164
                          self.parse, ['--action', 'take-me-to-the-moon'])
 
1165
 
 
1166
    def test_done(self):
 
1167
        opts, args = self.parse(['--action', 'done'])
 
1168
        self.assertEqual({'action':'done'}, opts)
 
1169
 
 
1170
    def test_take_this(self):
 
1171
        opts, args = self.parse(['--action', 'take-this'])
 
1172
        self.assertEqual({'action': 'take_this'}, opts)
 
1173
        opts, args = self.parse(['--take-this'])
 
1174
        self.assertEqual({'action': 'take_this'}, opts)
 
1175
 
 
1176
    def test_take_other(self):
 
1177
        opts, args = self.parse(['--action', 'take-other'])
 
1178
        self.assertEqual({'action': 'take_other'}, opts)
 
1179
        opts, args = self.parse(['--take-other'])
 
1180
        self.assertEqual({'action': 'take_other'}, opts)