~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2005-07-29 19:53:21 UTC
  • Revision ID: mbp@sourcefrog.net-20050729195321-6d9eef1a43dd6c81
- notes from discussion on splitting and joining files

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
import os
18
 
import stat
19
 
import sys
20
 
 
21
 
from bzrlib import (
22
 
    tests,
23
 
    urlutils,
24
 
    )
25
 
from bzrlib.bzrdir import BzrDir
26
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
27
 
                              UnversionedParent, ParentLoop, DeletingParent,)
28
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
29
 
                           ReusingTransform, CantMoveRoot, 
30
 
                           PathsNotVersionedError, ExistingLimbo,
31
 
                           ImmortalLimbo, LockError)
32
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
33
 
from bzrlib.merge import Merge3Merger
34
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
35
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
36
 
                              resolve_conflicts, cook_conflicts, 
37
 
                              find_interesting, build_tree, get_backup_name)
38
 
 
39
 
 
40
 
class TestTreeTransform(TestCaseInTempDir):
41
 
 
42
 
    def setUp(self):
43
 
        super(TestTreeTransform, self).setUp()
44
 
        self.wt = BzrDir.create_standalone_workingtree('.')
45
 
        os.chdir('..')
46
 
 
47
 
    def get_transform(self):
48
 
        transform = TreeTransform(self.wt)
49
 
        #self.addCleanup(transform.finalize)
50
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
51
 
 
52
 
    def test_existing_limbo(self):
53
 
        limbo_name = urlutils.local_path_from_url(
54
 
            self.wt._control_files.controlfilename('limbo'))
55
 
        transform, root = self.get_transform()
56
 
        os.mkdir(pathjoin(limbo_name, 'hehe'))
57
 
        self.assertRaises(ImmortalLimbo, transform.apply)
58
 
        self.assertRaises(LockError, self.wt.unlock)
59
 
        self.assertRaises(ExistingLimbo, self.get_transform)
60
 
        self.assertRaises(LockError, self.wt.unlock)
61
 
        os.rmdir(pathjoin(limbo_name, 'hehe'))
62
 
        os.rmdir(limbo_name)
63
 
        transform, root = self.get_transform()
64
 
        transform.apply()
65
 
 
66
 
    def test_build(self):
67
 
        transform, root = self.get_transform() 
68
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
69
 
        imaginary_id = transform.trans_id_tree_path('imaginary')
70
 
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
71
 
        self.assertEqual(imaginary_id, imaginary_id2)
72
 
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
73
 
        self.assertEqual(transform.final_kind(root), 'directory')
74
 
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
75
 
        trans_id = transform.create_path('name', root)
76
 
        self.assertIs(transform.final_file_id(trans_id), None)
77
 
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
78
 
        transform.create_file('contents', trans_id)
79
 
        transform.set_executability(True, trans_id)
80
 
        transform.version_file('my_pretties', trans_id)
81
 
        self.assertRaises(DuplicateKey, transform.version_file,
82
 
                          'my_pretties', trans_id)
83
 
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
84
 
        self.assertEqual(transform.final_parent(trans_id), root)
85
 
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
86
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
87
 
        oz_id = transform.create_path('oz', root)
88
 
        transform.create_directory(oz_id)
89
 
        transform.version_file('ozzie', oz_id)
90
 
        trans_id2 = transform.create_path('name2', root)
91
 
        transform.create_file('contents', trans_id2)
92
 
        transform.set_executability(False, trans_id2)
93
 
        transform.version_file('my_pretties2', trans_id2)
94
 
        modified_paths = transform.apply().modified_paths
95
 
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
96
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
97
 
        self.assertIs(self.wt.is_executable('my_pretties'), True)
98
 
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
99
 
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
100
 
        self.assertEqual(len(modified_paths), 3)
101
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
102
 
                          ('ozzie', 'my_pretties', 'my_pretties2')]
103
 
        self.assertSubset(tree_mod_paths, modified_paths)
104
 
        # is it safe to finalize repeatedly?
105
 
        transform.finalize()
106
 
        transform.finalize()
107
 
 
108
 
    def test_convenience(self):
109
 
        transform, root = self.get_transform()
110
 
        trans_id = transform.new_file('name', root, 'contents', 
111
 
                                      'my_pretties', True)
112
 
        oz = transform.new_directory('oz', root, 'oz-id')
113
 
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
114
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
115
 
                                  'toto-id', False)
116
 
 
117
 
        self.assertEqual(len(transform.find_conflicts()), 0)
118
 
        transform.apply()
119
 
        self.assertRaises(ReusingTransform, transform.find_conflicts)
120
 
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
121
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
122
 
        self.assertIs(self.wt.is_executable('my_pretties'), True)
123
 
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
124
 
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
125
 
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
126
 
 
127
 
        self.assertEqual('toto-contents', 
128
 
                         self.wt.get_file_byname('oz/dorothy/toto').read())
129
 
        self.assertIs(self.wt.is_executable('toto-id'), False)
130
 
 
131
 
    def test_conflicts(self):
132
 
        transform, root = self.get_transform()
133
 
        trans_id = transform.new_file('name', root, 'contents', 
134
 
                                      'my_pretties')
135
 
        self.assertEqual(len(transform.find_conflicts()), 0)
136
 
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
137
 
        self.assertEqual(transform.find_conflicts(), 
138
 
                         [('duplicate', trans_id, trans_id2, 'name')])
139
 
        self.assertRaises(MalformedTransform, transform.apply)
140
 
        transform.adjust_path('name', trans_id, trans_id2)
141
 
        self.assertEqual(transform.find_conflicts(), 
142
 
                         [('non-directory parent', trans_id)])
143
 
        tinman_id = transform.trans_id_tree_path('tinman')
144
 
        transform.adjust_path('name', tinman_id, trans_id2)
145
 
        self.assertEqual(transform.find_conflicts(), 
146
 
                         [('unversioned parent', tinman_id), 
147
 
                          ('missing parent', tinman_id)])
148
 
        lion_id = transform.create_path('lion', root)
149
 
        self.assertEqual(transform.find_conflicts(), 
150
 
                         [('unversioned parent', tinman_id), 
151
 
                          ('missing parent', tinman_id)])
152
 
        transform.adjust_path('name', lion_id, trans_id2)
153
 
        self.assertEqual(transform.find_conflicts(), 
154
 
                         [('unversioned parent', lion_id),
155
 
                          ('missing parent', lion_id)])
156
 
        transform.version_file("Courage", lion_id)
157
 
        self.assertEqual(transform.find_conflicts(), 
158
 
                         [('missing parent', lion_id), 
159
 
                          ('versioning no contents', lion_id)])
160
 
        transform.adjust_path('name2', root, trans_id2)
161
 
        self.assertEqual(transform.find_conflicts(), 
162
 
                         [('versioning no contents', lion_id)])
163
 
        transform.create_file('Contents, okay?', lion_id)
164
 
        transform.adjust_path('name2', trans_id2, trans_id2)
165
 
        self.assertEqual(transform.find_conflicts(), 
166
 
                         [('parent loop', trans_id2), 
167
 
                          ('non-directory parent', trans_id2)])
168
 
        transform.adjust_path('name2', root, trans_id2)
169
 
        oz_id = transform.new_directory('oz', root)
170
 
        transform.set_executability(True, oz_id)
171
 
        self.assertEqual(transform.find_conflicts(), 
172
 
                         [('unversioned executability', oz_id)])
173
 
        transform.version_file('oz-id', oz_id)
174
 
        self.assertEqual(transform.find_conflicts(), 
175
 
                         [('non-file executability', oz_id)])
176
 
        transform.set_executability(None, oz_id)
177
 
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
178
 
        transform.apply()
179
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
180
 
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
181
 
        transform2, root = self.get_transform()
182
 
        oz_id = transform2.trans_id_tree_file_id('oz-id')
183
 
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
184
 
        result = transform2.find_conflicts()
185
 
        fp = FinalPaths(transform2)
186
 
        self.assert_('oz/tip' in transform2._tree_path_ids)
187
 
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
188
 
        self.assertEqual(len(result), 2)
189
 
        self.assertEqual((result[0][0], result[0][1]), 
190
 
                         ('duplicate', newtip))
191
 
        self.assertEqual((result[1][0], result[1][2]), 
192
 
                         ('duplicate id', newtip))
193
 
        transform2.finalize()
194
 
        transform3 = TreeTransform(self.wt)
195
 
        self.addCleanup(transform3.finalize)
196
 
        oz_id = transform3.trans_id_tree_file_id('oz-id')
197
 
        transform3.delete_contents(oz_id)
198
 
        self.assertEqual(transform3.find_conflicts(), 
199
 
                         [('missing parent', oz_id)])
200
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
201
 
        tip_id = transform3.trans_id_tree_file_id('tip-id')
202
 
        transform3.adjust_path('tip', root_id, tip_id)
203
 
        transform3.apply()
204
 
 
205
 
    def test_add_del(self):
206
 
        start, root = self.get_transform()
207
 
        start.new_directory('a', root, 'a')
208
 
        start.apply()
209
 
        transform, root = self.get_transform()
210
 
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
211
 
        transform.new_directory('a', root, 'a')
212
 
        transform.apply()
213
 
 
214
 
    def test_unversioning(self):
215
 
        create_tree, root = self.get_transform()
216
 
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
217
 
        create_tree.new_file('child', parent_id, 'child', 'child-id')
218
 
        create_tree.apply()
219
 
        unversion = TreeTransform(self.wt)
220
 
        self.addCleanup(unversion.finalize)
221
 
        parent = unversion.trans_id_tree_path('parent')
222
 
        unversion.unversion_file(parent)
223
 
        self.assertEqual(unversion.find_conflicts(), 
224
 
                         [('unversioned parent', parent_id)])
225
 
        file_id = unversion.trans_id_tree_file_id('child-id')
226
 
        unversion.unversion_file(file_id)
227
 
        unversion.apply()
228
 
 
229
 
    def test_name_invariants(self):
230
 
        create_tree, root = self.get_transform()
231
 
        # prepare tree
232
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
233
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
234
 
        create_tree.new_file('name2', root, 'hello2', 'name2')
235
 
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
236
 
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
237
 
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
238
 
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
239
 
        create_tree.apply()
240
 
 
241
 
        mangle_tree,root = self.get_transform()
242
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
243
 
        #swap names
244
 
        name1 = mangle_tree.trans_id_tree_file_id('name1')
245
 
        name2 = mangle_tree.trans_id_tree_file_id('name2')
246
 
        mangle_tree.adjust_path('name2', root, name1)
247
 
        mangle_tree.adjust_path('name1', root, name2)
248
 
 
249
 
        #tests for deleting parent directories 
250
 
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
251
 
        mangle_tree.delete_contents(ddir)
252
 
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
253
 
        mangle_tree.delete_versioned(dfile)
254
 
        mangle_tree.unversion_file(dfile)
255
 
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
256
 
        mangle_tree.adjust_path('mfile', root, mfile)
257
 
 
258
 
        #tests for adding parent directories
259
 
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
260
 
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
261
 
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
262
 
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
263
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
264
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
265
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
266
 
        mangle_tree.apply()
267
 
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
268
 
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
269
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
270
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
271
 
        self.assertEqual(file(mfile2_path).read(), 'later2')
272
 
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
273
 
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
274
 
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
275
 
        self.assertEqual(file(newfile_path).read(), 'hello3')
276
 
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
277
 
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
278
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
279
 
 
280
 
    def test_both_rename(self):
281
 
        create_tree,root = self.get_transform()
282
 
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
283
 
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
284
 
        create_tree.apply()        
285
 
        mangle_tree,root = self.get_transform()
286
 
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
287
 
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
288
 
        mangle_tree.adjust_path('test', root, selftest)
289
 
        mangle_tree.adjust_path('test_too_much', root, selftest)
290
 
        mangle_tree.set_executability(True, blackbox)
291
 
        mangle_tree.apply()
292
 
 
293
 
    def test_both_rename2(self):
294
 
        create_tree,root = self.get_transform()
295
 
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
296
 
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
297
 
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
298
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
299
 
                             'test_too_much-id')
300
 
        create_tree.apply()        
301
 
        mangle_tree,root = self.get_transform()
302
 
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
303
 
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
304
 
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
305
 
        mangle_tree.adjust_path('selftest', bzrlib, tests)
306
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
307
 
        mangle_tree.set_executability(True, test_too_much)
308
 
        mangle_tree.apply()
309
 
 
310
 
    def test_both_rename3(self):
311
 
        create_tree,root = self.get_transform()
312
 
        tests = create_tree.new_directory('tests', root, 'tests-id')
313
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
314
 
                             'test_too_much-id')
315
 
        create_tree.apply()        
316
 
        mangle_tree,root = self.get_transform()
317
 
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
318
 
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
319
 
        mangle_tree.adjust_path('selftest', root, tests)
320
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
321
 
        mangle_tree.set_executability(True, test_too_much)
322
 
        mangle_tree.apply()
323
 
 
324
 
    def test_move_dangling_ie(self):
325
 
        create_tree, root = self.get_transform()
326
 
        # prepare tree
327
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
328
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
329
 
        create_tree.apply()
330
 
        delete_contents, root = self.get_transform()
331
 
        file = delete_contents.trans_id_tree_file_id('name1')
332
 
        delete_contents.delete_contents(file)
333
 
        delete_contents.apply()
334
 
        move_id, root = self.get_transform()
335
 
        name1 = move_id.trans_id_tree_file_id('name1')
336
 
        newdir = move_id.new_directory('dir', root, 'newdir')
337
 
        move_id.adjust_path('name2', newdir, name1)
338
 
        move_id.apply()
339
 
        
340
 
    def test_replace_dangling_ie(self):
341
 
        create_tree, root = self.get_transform()
342
 
        # prepare tree
343
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
344
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
345
 
        create_tree.apply()
346
 
        delete_contents = TreeTransform(self.wt)
347
 
        self.addCleanup(delete_contents.finalize)
348
 
        file = delete_contents.trans_id_tree_file_id('name1')
349
 
        delete_contents.delete_contents(file)
350
 
        delete_contents.apply()
351
 
        delete_contents.finalize()
352
 
        replace = TreeTransform(self.wt)
353
 
        self.addCleanup(replace.finalize)
354
 
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
355
 
        conflicts = replace.find_conflicts()
356
 
        name1 = replace.trans_id_tree_file_id('name1')
357
 
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
358
 
        resolve_conflicts(replace)
359
 
        replace.apply()
360
 
 
361
 
    def test_symlinks(self):
362
 
        if not has_symlinks():
363
 
            raise TestSkipped('Symlinks are not supported on this platform')
364
 
        transform,root = self.get_transform()
365
 
        oz_id = transform.new_directory('oz', root, 'oz-id')
366
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
367
 
                                       'wizard-id')
368
 
        wiz_id = transform.create_path('wizard2', oz_id)
369
 
        transform.create_symlink('behind_curtain', wiz_id)
370
 
        transform.version_file('wiz-id2', wiz_id)            
371
 
        transform.set_executability(True, wiz_id)
372
 
        self.assertEqual(transform.find_conflicts(), 
373
 
                         [('non-file executability', wiz_id)])
374
 
        transform.set_executability(None, wiz_id)
375
 
        transform.apply()
376
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
377
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
378
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
379
 
                         'behind_curtain')
380
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
381
 
                         'wizard-target')
382
 
 
383
 
 
384
 
    def get_conflicted(self):
385
 
        create,root = self.get_transform()
386
 
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
387
 
        oz = create.new_directory('oz', root, 'oz-id')
388
 
        create.new_directory('emeraldcity', oz, 'emerald-id')
389
 
        create.apply()
390
 
        conflicts,root = self.get_transform()
391
 
        # set up duplicate entry, duplicate id
392
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
393
 
                                         'dorothy-id')
394
 
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
395
 
        oz = conflicts.trans_id_tree_file_id('oz-id')
396
 
        # set up DeletedParent parent conflict
397
 
        conflicts.delete_versioned(oz)
398
 
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
399
 
        # set up MissingParent conflict
400
 
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
401
 
        conflicts.adjust_path('munchkincity', root, munchkincity)
402
 
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
403
 
        # set up parent loop
404
 
        conflicts.adjust_path('emeraldcity', emerald, emerald)
405
 
        return conflicts, emerald, oz, old_dorothy, new_dorothy
406
 
 
407
 
    def test_conflict_resolution(self):
408
 
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
409
 
            self.get_conflicted()
410
 
        resolve_conflicts(conflicts)
411
 
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
412
 
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
413
 
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
414
 
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
415
 
        self.assertEqual(conflicts.final_parent(emerald), oz)
416
 
        conflicts.apply()
417
 
 
418
 
    def test_cook_conflicts(self):
419
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
420
 
        raw_conflicts = resolve_conflicts(tt)
421
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
422
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
423
 
                                   'dorothy', None, 'dorothy-id')
424
 
        self.assertEqual(cooked_conflicts[0], duplicate)
425
 
        duplicate_id = DuplicateID('Unversioned existing file', 
426
 
                                   'dorothy.moved', 'dorothy', None,
427
 
                                   'dorothy-id')
428
 
        self.assertEqual(cooked_conflicts[1], duplicate_id)
429
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
430
 
                                       'munchkincity-id')
431
 
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
432
 
        self.assertEqual(cooked_conflicts[2], missing_parent)
433
 
        unversioned_parent = UnversionedParent('Versioned directory',
434
 
                                               'munchkincity',
435
 
                                               'munchkincity-id')
436
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
437
 
                                               'oz-id')
438
 
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
439
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
440
 
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
441
 
        self.assertEqual(cooked_conflicts[4], deleted_parent)
442
 
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
443
 
        self.assertEqual(cooked_conflicts[6], parent_loop)
444
 
        self.assertEqual(len(cooked_conflicts), 7)
445
 
        tt.finalize()
446
 
 
447
 
    def test_string_conflicts(self):
448
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
449
 
        raw_conflicts = resolve_conflicts(tt)
450
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
451
 
        tt.finalize()
452
 
        conflicts_s = [str(c) for c in cooked_conflicts]
453
 
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
454
 
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
455
 
                                         'Moved existing file to '
456
 
                                         'dorothy.moved.')
457
 
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
458
 
                                         'Unversioned existing file '
459
 
                                         'dorothy.moved.')
460
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
461
 
                                         ' munchkincity.  Created directory.')
462
 
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
463
 
                                         ' versioned, but has versioned'
464
 
                                         ' children.  Versioned directory.')
465
 
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
466
 
                                         " is not empty.  Not deleting.")
467
 
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
468
 
                                         ' versioned, but has versioned'
469
 
                                         ' children.  Versioned directory.')
470
 
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
471
 
                                         ' oz/emeraldcity.  Cancelled move.')
472
 
 
473
 
    def test_moving_versioned_directories(self):
474
 
        create, root = self.get_transform()
475
 
        kansas = create.new_directory('kansas', root, 'kansas-id')
476
 
        create.new_directory('house', kansas, 'house-id')
477
 
        create.new_directory('oz', root, 'oz-id')
478
 
        create.apply()
479
 
        cyclone, root = self.get_transform()
480
 
        oz = cyclone.trans_id_tree_file_id('oz-id')
481
 
        house = cyclone.trans_id_tree_file_id('house-id')
482
 
        cyclone.adjust_path('house', oz, house)
483
 
        cyclone.apply()
484
 
 
485
 
    def test_moving_root(self):
486
 
        create, root = self.get_transform()
487
 
        fun = create.new_directory('fun', root, 'fun-id')
488
 
        create.new_directory('sun', root, 'sun-id')
489
 
        create.new_directory('moon', root, 'moon')
490
 
        create.apply()
491
 
        transform, root = self.get_transform()
492
 
        transform.adjust_root_path('oldroot', fun)
493
 
        new_root=transform.trans_id_tree_path('')
494
 
        transform.version_file('new-root', new_root)
495
 
        transform.apply()
496
 
 
497
 
    def test_renames(self):
498
 
        create, root = self.get_transform()
499
 
        old = create.new_directory('old-parent', root, 'old-id')
500
 
        intermediate = create.new_directory('intermediate', old, 'im-id')
501
 
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
502
 
                                 'myfile-id')
503
 
        create.apply()
504
 
        rename, root = self.get_transform()
505
 
        old = rename.trans_id_file_id('old-id')
506
 
        rename.adjust_path('new', root, old)
507
 
        myfile = rename.trans_id_file_id('myfile-id')
508
 
        rename.set_executability(True, myfile)
509
 
        rename.apply()
510
 
 
511
 
    def test_find_interesting(self):
512
 
        create, root = self.get_transform()
513
 
        wt = create._tree
514
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
515
 
        create.new_file('uvfile', root, 'othertext')
516
 
        create.apply()
517
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
518
 
                         set(['myfile-id']))
519
 
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
520
 
                          ['uvfile'])
521
 
 
522
 
    def test_set_executability_order(self):
523
 
        """Ensure that executability behaves the same, no matter what order.
524
 
        
525
 
        - create file and set executability simultaneously
526
 
        - create file and set executability afterward
527
 
        - unsetting the executability of a file whose executability has not been
528
 
        declared should throw an exception (this may happen when a
529
 
        merge attempts to create a file with a duplicate ID)
530
 
        """
531
 
        transform, root = self.get_transform()
532
 
        wt = transform._tree
533
 
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
534
 
                           True)
535
 
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
536
 
        transform.set_executability(True, sac)
537
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
538
 
        self.assertRaises(KeyError, transform.set_executability, None, uws)
539
 
        transform.apply()
540
 
        self.assertTrue(wt.is_executable('soc'))
541
 
        self.assertTrue(wt.is_executable('sac'))
542
 
 
543
 
    def test_preserve_mode(self):
544
 
        """File mode is preserved when replacing content"""
545
 
        if sys.platform == 'win32':
546
 
            raise TestSkipped('chmod has no effect on win32')
547
 
        transform, root = self.get_transform()
548
 
        transform.new_file('file1', root, 'contents', 'file1-id', True)
549
 
        transform.apply()
550
 
        self.assertTrue(self.wt.is_executable('file1-id'))
551
 
        transform, root = self.get_transform()
552
 
        file1_id = transform.trans_id_tree_file_id('file1-id')
553
 
        transform.delete_contents(file1_id)
554
 
        transform.create_file('contents2', file1_id)
555
 
        transform.apply()
556
 
        self.assertTrue(self.wt.is_executable('file1-id'))
557
 
 
558
 
    def test__set_mode_stats_correctly(self):
559
 
        """_set_mode stats to determine file mode."""
560
 
        if sys.platform == 'win32':
561
 
            raise TestSkipped('chmod has no effect on win32')
562
 
 
563
 
        stat_paths = []
564
 
        real_stat = os.stat
565
 
        def instrumented_stat(path):
566
 
            stat_paths.append(path)
567
 
            return real_stat(path)
568
 
 
569
 
        transform, root = self.get_transform()
570
 
 
571
 
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
572
 
                                     file_id='bar-id-1', executable=False)
573
 
        transform.apply()
574
 
 
575
 
        transform, root = self.get_transform()
576
 
        bar1_id = transform.trans_id_tree_path('bar')
577
 
        bar2_id = transform.trans_id_tree_path('bar2')
578
 
        try:
579
 
            os.stat = instrumented_stat
580
 
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
581
 
        finally:
582
 
            os.stat = real_stat
583
 
            transform.finalize()
584
 
 
585
 
        bar1_abspath = self.wt.abspath('bar')
586
 
        self.assertEqual([bar1_abspath], stat_paths)
587
 
 
588
 
 
589
 
class TransformGroup(object):
590
 
    def __init__(self, dirname):
591
 
        self.name = dirname
592
 
        os.mkdir(dirname)
593
 
        self.wt = BzrDir.create_standalone_workingtree(dirname)
594
 
        self.b = self.wt.branch
595
 
        self.tt = TreeTransform(self.wt)
596
 
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
597
 
 
598
 
def conflict_text(tree, merge):
599
 
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
600
 
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
601
 
 
602
 
 
603
 
class TestTransformMerge(TestCaseInTempDir):
604
 
    def test_text_merge(self):
605
 
        base = TransformGroup("base")
606
 
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
607
 
        base.tt.new_file('b', base.root, 'b1', 'b')
608
 
        base.tt.new_file('c', base.root, 'c', 'c')
609
 
        base.tt.new_file('d', base.root, 'd', 'd')
610
 
        base.tt.new_file('e', base.root, 'e', 'e')
611
 
        base.tt.new_file('f', base.root, 'f', 'f')
612
 
        base.tt.new_directory('g', base.root, 'g')
613
 
        base.tt.new_directory('h', base.root, 'h')
614
 
        base.tt.apply()
615
 
        other = TransformGroup("other")
616
 
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
617
 
        other.tt.new_file('b', other.root, 'b2', 'b')
618
 
        other.tt.new_file('c', other.root, 'c2', 'c')
619
 
        other.tt.new_file('d', other.root, 'd', 'd')
620
 
        other.tt.new_file('e', other.root, 'e2', 'e')
621
 
        other.tt.new_file('f', other.root, 'f', 'f')
622
 
        other.tt.new_file('g', other.root, 'g', 'g')
623
 
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
624
 
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
625
 
        other.tt.apply()
626
 
        this = TransformGroup("this")
627
 
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
628
 
        this.tt.new_file('b', this.root, 'b', 'b')
629
 
        this.tt.new_file('c', this.root, 'c', 'c')
630
 
        this.tt.new_file('d', this.root, 'd2', 'd')
631
 
        this.tt.new_file('e', this.root, 'e2', 'e')
632
 
        this.tt.new_file('f', this.root, 'f', 'f')
633
 
        this.tt.new_file('g', this.root, 'g', 'g')
634
 
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
635
 
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
636
 
        this.tt.apply()
637
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
638
 
        # textual merge
639
 
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
640
 
        # three-way text conflict
641
 
        self.assertEqual(this.wt.get_file('b').read(), 
642
 
                         conflict_text('b', 'b2'))
643
 
        # OTHER wins
644
 
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
645
 
        # THIS wins
646
 
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
647
 
        # Ambigious clean merge
648
 
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
649
 
        # No change
650
 
        self.assertEqual(this.wt.get_file('f').read(), 'f')
651
 
        # Correct correct results when THIS == OTHER 
652
 
        self.assertEqual(this.wt.get_file('g').read(), 'g')
653
 
        # Text conflict when THIS & OTHER are text and BASE is dir
654
 
        self.assertEqual(this.wt.get_file('h').read(), 
655
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
656
 
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
657
 
                         '1\n2\n3\n4\n')
658
 
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
659
 
                         'h\ni\nj\nk\n')
660
 
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
661
 
        self.assertEqual(this.wt.get_file('i').read(), 
662
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
663
 
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
664
 
                         '1\n2\n3\n4\n')
665
 
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
666
 
                         'h\ni\nj\nk\n')
667
 
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
668
 
        modified = ['a', 'b', 'c', 'h', 'i']
669
 
        merge_modified = this.wt.merge_modified()
670
 
        self.assertSubset(merge_modified, modified)
671
 
        self.assertEqual(len(merge_modified), len(modified))
672
 
        file(this.wt.id2abspath('a'), 'wb').write('booga')
673
 
        modified.pop(0)
674
 
        merge_modified = this.wt.merge_modified()
675
 
        self.assertSubset(merge_modified, modified)
676
 
        self.assertEqual(len(merge_modified), len(modified))
677
 
        this.wt.remove('b')
678
 
        this.wt.revert([])
679
 
 
680
 
    def test_file_merge(self):
681
 
        if not has_symlinks():
682
 
            raise TestSkipped('Symlinks are not supported on this platform')
683
 
        base = TransformGroup("BASE")
684
 
        this = TransformGroup("THIS")
685
 
        other = TransformGroup("OTHER")
686
 
        for tg in this, base, other:
687
 
            tg.tt.new_directory('a', tg.root, 'a')
688
 
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
689
 
            tg.tt.new_file('c', tg.root, 'c', 'c')
690
 
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
691
 
        targets = ((base, 'base-e', 'base-f', None, None), 
692
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
693
 
                   (other, 'other-e', None, 'other-g', 'other-h'))
694
 
        for tg, e_target, f_target, g_target, h_target in targets:
695
 
            for link, target in (('e', e_target), ('f', f_target), 
696
 
                                 ('g', g_target), ('h', h_target)):
697
 
                if target is not None:
698
 
                    tg.tt.new_symlink(link, tg.root, target, link)
699
 
 
700
 
        for tg in this, base, other:
701
 
            tg.tt.apply()
702
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
703
 
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
704
 
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
705
 
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
706
 
        for suffix in ('THIS', 'BASE', 'OTHER'):
707
 
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
708
 
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
709
 
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
710
 
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
711
 
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
712
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
713
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
714
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
715
 
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
716
 
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
717
 
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
718
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
719
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
720
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
721
 
 
722
 
    def test_filename_merge(self):
723
 
        base = TransformGroup("BASE")
724
 
        this = TransformGroup("THIS")
725
 
        other = TransformGroup("OTHER")
726
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
727
 
                                   for t in [base, this, other]]
728
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
729
 
                                   for t in [base, this, other]]
730
 
        base.tt.new_directory('c', base_a, 'c')
731
 
        this.tt.new_directory('c1', this_a, 'c')
732
 
        other.tt.new_directory('c', other_b, 'c')
733
 
 
734
 
        base.tt.new_directory('d', base_a, 'd')
735
 
        this.tt.new_directory('d1', this_b, 'd')
736
 
        other.tt.new_directory('d', other_a, 'd')
737
 
 
738
 
        base.tt.new_directory('e', base_a, 'e')
739
 
        this.tt.new_directory('e', this_a, 'e')
740
 
        other.tt.new_directory('e1', other_b, 'e')
741
 
 
742
 
        base.tt.new_directory('f', base_a, 'f')
743
 
        this.tt.new_directory('f1', this_b, 'f')
744
 
        other.tt.new_directory('f1', other_b, 'f')
745
 
 
746
 
        for tg in [this, base, other]:
747
 
            tg.tt.apply()
748
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
749
 
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
750
 
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
751
 
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
752
 
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
753
 
 
754
 
    def test_filename_merge_conflicts(self):
755
 
        base = TransformGroup("BASE")
756
 
        this = TransformGroup("THIS")
757
 
        other = TransformGroup("OTHER")
758
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
759
 
                                   for t in [base, this, other]]
760
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
761
 
                                   for t in [base, this, other]]
762
 
 
763
 
        base.tt.new_file('g', base_a, 'g', 'g')
764
 
        other.tt.new_file('g1', other_b, 'g1', 'g')
765
 
 
766
 
        base.tt.new_file('h', base_a, 'h', 'h')
767
 
        this.tt.new_file('h1', this_b, 'h1', 'h')
768
 
 
769
 
        base.tt.new_file('i', base.root, 'i', 'i')
770
 
        other.tt.new_directory('i1', this_b, 'i')
771
 
 
772
 
        for tg in [this, base, other]:
773
 
            tg.tt.apply()
774
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
775
 
 
776
 
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
777
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
778
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
779
 
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
780
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
781
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
782
 
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
783
 
 
784
 
 
785
 
class TestBuildTree(tests.TestCaseWithTransport):
786
 
 
787
 
    def test_build_tree(self):
788
 
        if not has_symlinks():
789
 
            raise TestSkipped('Test requires symlink support')
790
 
        os.mkdir('a')
791
 
        a = BzrDir.create_standalone_workingtree('a')
792
 
        os.mkdir('a/foo')
793
 
        file('a/foo/bar', 'wb').write('contents')
794
 
        os.symlink('a/foo/bar', 'a/foo/baz')
795
 
        a.add(['foo', 'foo/bar', 'foo/baz'])
796
 
        a.commit('initial commit')
797
 
        b = BzrDir.create_standalone_workingtree('b')
798
 
        build_tree(a.basis_tree(), b)
799
 
        self.assertIs(os.path.isdir('b/foo'), True)
800
 
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
801
 
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
802
 
 
803
 
    def test_file_conflict_handling(self):
804
 
        """Ensure that when building trees, conflict handling is done"""
805
 
        source = self.make_branch_and_tree('source')
806
 
        target = self.make_branch_and_tree('target')
807
 
        self.build_tree(['source/file', 'target/file'])
808
 
        source.add('file', 'new-file')
809
 
        source.commit('added file')
810
 
        build_tree(source.basis_tree(), target)
811
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
812
 
                          'file.moved', 'file', None, 'new-file')],
813
 
                         target.conflicts())
814
 
        target2 = self.make_branch_and_tree('target2')
815
 
        target_file = file('target2/file', 'wb')
816
 
        try:
817
 
            source_file = file('source/file', 'rb')
818
 
            try:
819
 
                target_file.write(source_file.read())
820
 
            finally:
821
 
                source_file.close()
822
 
        finally:
823
 
            target_file.close()
824
 
        build_tree(source.basis_tree(), target2)
825
 
        self.assertEqual([], target2.conflicts())
826
 
 
827
 
    def test_symlink_conflict_handling(self):
828
 
        """Ensure that when building trees, conflict handling is done"""
829
 
        if not has_symlinks():
830
 
            raise TestSkipped('Test requires symlink support')
831
 
        source = self.make_branch_and_tree('source')
832
 
        os.symlink('foo', 'source/symlink')
833
 
        source.add('symlink', 'new-symlink')
834
 
        source.commit('added file')
835
 
        target = self.make_branch_and_tree('target')
836
 
        os.symlink('bar', 'target/symlink')
837
 
        build_tree(source.basis_tree(), target)
838
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
839
 
            'symlink.moved', 'symlink', None, 'new-symlink')],
840
 
            target.conflicts())
841
 
        target = self.make_branch_and_tree('target2')
842
 
        os.symlink('foo', 'target2/symlink')
843
 
        build_tree(source.basis_tree(), target)
844
 
        self.assertEqual([], target.conflicts())
845
 
        
846
 
    def test_directory_conflict_handling(self):
847
 
        """Ensure that when building trees, conflict handling is done"""
848
 
        source = self.make_branch_and_tree('source')
849
 
        target = self.make_branch_and_tree('target')
850
 
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
851
 
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
852
 
        source.commit('added file')
853
 
        build_tree(source.basis_tree(), target)
854
 
        self.assertEqual([], target.conflicts())
855
 
        self.failUnlessExists('target/dir1/file')
856
 
 
857
 
        # Ensure contents are merged
858
 
        target = self.make_branch_and_tree('target2')
859
 
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
860
 
        build_tree(source.basis_tree(), target)
861
 
        self.assertEqual([], target.conflicts())
862
 
        self.failUnlessExists('target2/dir1/file2')
863
 
        self.failUnlessExists('target2/dir1/file')
864
 
 
865
 
        # Ensure new contents are suppressed for existing branches
866
 
        target = self.make_branch_and_tree('target3')
867
 
        self.make_branch('target3/dir1')
868
 
        self.build_tree(['target3/dir1/file2'])
869
 
        build_tree(source.basis_tree(), target)
870
 
        self.failIfExists('target3/dir1/file')
871
 
        self.failUnlessExists('target3/dir1/file2')
872
 
        self.failUnlessExists('target3/dir1.diverted/file')
873
 
        self.assertEqual([DuplicateEntry('Diverted to',
874
 
            'dir1.diverted', 'dir1', 'new-dir1', None)],
875
 
            target.conflicts())
876
 
 
877
 
        target = self.make_branch_and_tree('target4')
878
 
        self.build_tree(['target4/dir1/'])
879
 
        self.make_branch('target4/dir1/file')
880
 
        build_tree(source.basis_tree(), target)
881
 
        self.failUnlessExists('target4/dir1/file')
882
 
        self.assertEqual('directory', file_kind('target4/dir1/file'))
883
 
        self.failUnlessExists('target4/dir1/file.diverted')
884
 
        self.assertEqual([DuplicateEntry('Diverted to',
885
 
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
886
 
            target.conflicts())
887
 
 
888
 
    def test_mixed_conflict_handling(self):
889
 
        """Ensure that when building trees, conflict handling is done"""
890
 
        source = self.make_branch_and_tree('source')
891
 
        target = self.make_branch_and_tree('target')
892
 
        self.build_tree(['source/name', 'target/name/'])
893
 
        source.add('name', 'new-name')
894
 
        source.commit('added file')
895
 
        build_tree(source.basis_tree(), target)
896
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
897
 
            'name.moved', 'name', None, 'new-name')], target.conflicts())
898
 
 
899
 
    def test_raises_in_populated(self):
900
 
        source = self.make_branch_and_tree('source')
901
 
        self.build_tree(['source/name'])
902
 
        source.add('name')
903
 
        source.commit('added name')
904
 
        target = self.make_branch_and_tree('target')
905
 
        self.build_tree(['target/name'])
906
 
        target.add('name')
907
 
        self.assertRaises(AssertionError, build_tree, source.basis_tree(),
908
 
                          target)
909
 
 
910
 
 
911
 
class MockTransform(object):
912
 
 
913
 
    def has_named_child(self, by_parent, parent_id, name):
914
 
        for child_id in by_parent[parent_id]:
915
 
            if child_id == '0':
916
 
                if name == "name~":
917
 
                    return True
918
 
            elif name == "name.~%s~" % child_id:
919
 
                return True
920
 
        return False
921
 
 
922
 
class MockEntry(object):
923
 
    def __init__(self):
924
 
        object.__init__(self)
925
 
        self.name = "name"
926
 
 
927
 
class TestGetBackupName(TestCase):
928
 
    def test_get_backup_name(self):
929
 
        tt = MockTransform()
930
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
931
 
        self.assertEqual(name, 'name.~1~')
932
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
933
 
        self.assertEqual(name, 'name.~2~')
934
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
935
 
        self.assertEqual(name, 'name.~1~')
936
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
937
 
        self.assertEqual(name, 'name.~1~')
938
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
939
 
        self.assertEqual(name, 'name.~4~')