/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Robey Pointer
  • Date: 2006-09-08 18:46:29 UTC
  • mto: This revision was merged to the branch mainline in revision 1996.
  • Revision ID: robey@lag.net-20060908184629-e3fc4c61ca21508c
pychecker is on crack; go back to using 'is None'.

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