~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-02-18 20:16:49 UTC
  • mfrom: (5622.2.11 lazy-hooks)
  • Revision ID: pqm@pqm.ubuntu.com-20110218201649-woj0pygeukoq6z6h
(jelmer) Allow installing hooks without importing the hook point. (Jelmer
 Vernooij)

Show diffs side-by-side

added added

removed removed

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