~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

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