~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-23 13:59:30 UTC
  • Revision ID: mbp@sourcefrog.net-20050723135930-d81530c82c925cb0
- less dodgy is_inside function

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
 
    errors,
23
 
    generate_ids,
24
 
    symbol_versioning,
25
 
    tests,
26
 
    urlutils,
27
 
    )
28
 
from bzrlib.bzrdir import BzrDir
29
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
 
                              UnversionedParent, ParentLoop, DeletingParent,)
31
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
 
                           ReusingTransform, CantMoveRoot, 
33
 
                           PathsNotVersionedError, ExistingLimbo,
34
 
                           ImmortalLimbo, LockError)
35
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
36
 
from bzrlib.merge import Merge3Merger
37
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
38
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
39
 
                              resolve_conflicts, cook_conflicts, 
40
 
                              find_interesting, build_tree, get_backup_name,
41
 
                              change_entry)
42
 
 
43
 
 
44
 
class TestTreeTransform(tests.TestCaseWithTransport):
45
 
 
46
 
    def setUp(self):
47
 
        super(TestTreeTransform, self).setUp()
48
 
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
49
 
        os.chdir('..')
50
 
 
51
 
    def get_transform(self):
52
 
        transform = TreeTransform(self.wt)
53
 
        #self.addCleanup(transform.finalize)
54
 
        return transform, transform.root
55
 
 
56
 
    def test_existing_limbo(self):
57
 
        limbo_name = urlutils.local_path_from_url(
58
 
            self.wt._control_files.controlfilename('limbo'))
59
 
        transform, root = self.get_transform()
60
 
        os.mkdir(pathjoin(limbo_name, 'hehe'))
61
 
        self.assertRaises(ImmortalLimbo, transform.apply)
62
 
        self.assertRaises(LockError, self.wt.unlock)
63
 
        self.assertRaises(ExistingLimbo, self.get_transform)
64
 
        self.assertRaises(LockError, self.wt.unlock)
65
 
        os.rmdir(pathjoin(limbo_name, 'hehe'))
66
 
        os.rmdir(limbo_name)
67
 
        transform, root = self.get_transform()
68
 
        transform.apply()
69
 
 
70
 
    def test_build(self):
71
 
        transform, root = self.get_transform() 
72
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
73
 
        imaginary_id = transform.trans_id_tree_path('imaginary')
74
 
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
75
 
        self.assertEqual(imaginary_id, imaginary_id2)
76
 
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
77
 
        self.assertEqual(transform.final_kind(root), 'directory')
78
 
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
79
 
        trans_id = transform.create_path('name', root)
80
 
        self.assertIs(transform.final_file_id(trans_id), None)
81
 
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
82
 
        transform.create_file('contents', trans_id)
83
 
        transform.set_executability(True, trans_id)
84
 
        transform.version_file('my_pretties', trans_id)
85
 
        self.assertRaises(DuplicateKey, transform.version_file,
86
 
                          'my_pretties', trans_id)
87
 
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
88
 
        self.assertEqual(transform.final_parent(trans_id), root)
89
 
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
90
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
91
 
        oz_id = transform.create_path('oz', root)
92
 
        transform.create_directory(oz_id)
93
 
        transform.version_file('ozzie', oz_id)
94
 
        trans_id2 = transform.create_path('name2', root)
95
 
        transform.create_file('contents', trans_id2)
96
 
        transform.set_executability(False, trans_id2)
97
 
        transform.version_file('my_pretties2', trans_id2)
98
 
        modified_paths = transform.apply().modified_paths
99
 
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
100
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
101
 
        self.assertIs(self.wt.is_executable('my_pretties'), True)
102
 
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
103
 
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
104
 
        self.assertEqual(len(modified_paths), 3)
105
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
106
 
                          ('ozzie', 'my_pretties', 'my_pretties2')]
107
 
        self.assertSubset(tree_mod_paths, modified_paths)
108
 
        # is it safe to finalize repeatedly?
109
 
        transform.finalize()
110
 
        transform.finalize()
111
 
 
112
 
    def test_convenience(self):
113
 
        transform, root = self.get_transform()
114
 
        trans_id = transform.new_file('name', root, 'contents', 
115
 
                                      'my_pretties', True)
116
 
        oz = transform.new_directory('oz', root, 'oz-id')
117
 
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
118
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
119
 
                                  'toto-id', False)
120
 
 
121
 
        self.assertEqual(len(transform.find_conflicts()), 0)
122
 
        transform.apply()
123
 
        self.assertRaises(ReusingTransform, transform.find_conflicts)
124
 
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
125
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
126
 
        self.assertIs(self.wt.is_executable('my_pretties'), True)
127
 
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
128
 
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
129
 
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
130
 
 
131
 
        self.assertEqual('toto-contents',
132
 
                         self.wt.get_file_byname('oz/dorothy/toto').read())
133
 
        self.assertIs(self.wt.is_executable('toto-id'), False)
134
 
 
135
 
    def test_tree_reference(self):
136
 
        transform, root = self.get_transform()
137
 
        tree = transform._tree
138
 
        trans_id = transform.new_directory('reference', root, 'subtree-id')
139
 
        transform.set_tree_reference('subtree-revision', trans_id)
140
 
        transform.apply()
141
 
        tree.lock_read()
142
 
        self.addCleanup(tree.unlock)
143
 
        self.assertEqual('subtree-revision',
144
 
                         tree.inventory['subtree-id'].reference_revision)
145
 
 
146
 
    def test_conflicts(self):
147
 
        transform, root = self.get_transform()
148
 
        trans_id = transform.new_file('name', root, 'contents', 
149
 
                                      'my_pretties')
150
 
        self.assertEqual(len(transform.find_conflicts()), 0)
151
 
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
152
 
        self.assertEqual(transform.find_conflicts(), 
153
 
                         [('duplicate', trans_id, trans_id2, 'name')])
154
 
        self.assertRaises(MalformedTransform, transform.apply)
155
 
        transform.adjust_path('name', trans_id, trans_id2)
156
 
        self.assertEqual(transform.find_conflicts(), 
157
 
                         [('non-directory parent', trans_id)])
158
 
        tinman_id = transform.trans_id_tree_path('tinman')
159
 
        transform.adjust_path('name', tinman_id, trans_id2)
160
 
        self.assertEqual(transform.find_conflicts(), 
161
 
                         [('unversioned parent', tinman_id), 
162
 
                          ('missing parent', tinman_id)])
163
 
        lion_id = transform.create_path('lion', root)
164
 
        self.assertEqual(transform.find_conflicts(), 
165
 
                         [('unversioned parent', tinman_id), 
166
 
                          ('missing parent', tinman_id)])
167
 
        transform.adjust_path('name', lion_id, trans_id2)
168
 
        self.assertEqual(transform.find_conflicts(), 
169
 
                         [('unversioned parent', lion_id),
170
 
                          ('missing parent', lion_id)])
171
 
        transform.version_file("Courage", lion_id)
172
 
        self.assertEqual(transform.find_conflicts(), 
173
 
                         [('missing parent', lion_id), 
174
 
                          ('versioning no contents', lion_id)])
175
 
        transform.adjust_path('name2', root, trans_id2)
176
 
        self.assertEqual(transform.find_conflicts(), 
177
 
                         [('versioning no contents', lion_id)])
178
 
        transform.create_file('Contents, okay?', lion_id)
179
 
        transform.adjust_path('name2', trans_id2, trans_id2)
180
 
        self.assertEqual(transform.find_conflicts(), 
181
 
                         [('parent loop', trans_id2), 
182
 
                          ('non-directory parent', trans_id2)])
183
 
        transform.adjust_path('name2', root, trans_id2)
184
 
        oz_id = transform.new_directory('oz', root)
185
 
        transform.set_executability(True, oz_id)
186
 
        self.assertEqual(transform.find_conflicts(), 
187
 
                         [('unversioned executability', oz_id)])
188
 
        transform.version_file('oz-id', oz_id)
189
 
        self.assertEqual(transform.find_conflicts(), 
190
 
                         [('non-file executability', oz_id)])
191
 
        transform.set_executability(None, oz_id)
192
 
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
193
 
        transform.apply()
194
 
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
195
 
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
196
 
        transform2, root = self.get_transform()
197
 
        oz_id = transform2.trans_id_tree_file_id('oz-id')
198
 
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
199
 
        result = transform2.find_conflicts()
200
 
        fp = FinalPaths(transform2)
201
 
        self.assert_('oz/tip' in transform2._tree_path_ids)
202
 
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
203
 
        self.assertEqual(len(result), 2)
204
 
        self.assertEqual((result[0][0], result[0][1]), 
205
 
                         ('duplicate', newtip))
206
 
        self.assertEqual((result[1][0], result[1][2]), 
207
 
                         ('duplicate id', newtip))
208
 
        transform2.finalize()
209
 
        transform3 = TreeTransform(self.wt)
210
 
        self.addCleanup(transform3.finalize)
211
 
        oz_id = transform3.trans_id_tree_file_id('oz-id')
212
 
        transform3.delete_contents(oz_id)
213
 
        self.assertEqual(transform3.find_conflicts(), 
214
 
                         [('missing parent', oz_id)])
215
 
        root_id = transform3.root
216
 
        tip_id = transform3.trans_id_tree_file_id('tip-id')
217
 
        transform3.adjust_path('tip', root_id, tip_id)
218
 
        transform3.apply()
219
 
 
220
 
    def test_add_del(self):
221
 
        start, root = self.get_transform()
222
 
        start.new_directory('a', root, 'a')
223
 
        start.apply()
224
 
        transform, root = self.get_transform()
225
 
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
226
 
        transform.new_directory('a', root, 'a')
227
 
        transform.apply()
228
 
 
229
 
    def test_unversioning(self):
230
 
        create_tree, root = self.get_transform()
231
 
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
232
 
        create_tree.new_file('child', parent_id, 'child', 'child-id')
233
 
        create_tree.apply()
234
 
        unversion = TreeTransform(self.wt)
235
 
        self.addCleanup(unversion.finalize)
236
 
        parent = unversion.trans_id_tree_path('parent')
237
 
        unversion.unversion_file(parent)
238
 
        self.assertEqual(unversion.find_conflicts(), 
239
 
                         [('unversioned parent', parent_id)])
240
 
        file_id = unversion.trans_id_tree_file_id('child-id')
241
 
        unversion.unversion_file(file_id)
242
 
        unversion.apply()
243
 
 
244
 
    def test_name_invariants(self):
245
 
        create_tree, root = self.get_transform()
246
 
        # prepare tree
247
 
        root = create_tree.root
248
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
249
 
        create_tree.new_file('name2', root, 'hello2', 'name2')
250
 
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
251
 
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
252
 
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
253
 
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
254
 
        create_tree.apply()
255
 
 
256
 
        mangle_tree,root = self.get_transform()
257
 
        root = mangle_tree.root
258
 
        #swap names
259
 
        name1 = mangle_tree.trans_id_tree_file_id('name1')
260
 
        name2 = mangle_tree.trans_id_tree_file_id('name2')
261
 
        mangle_tree.adjust_path('name2', root, name1)
262
 
        mangle_tree.adjust_path('name1', root, name2)
263
 
 
264
 
        #tests for deleting parent directories 
265
 
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
266
 
        mangle_tree.delete_contents(ddir)
267
 
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
268
 
        mangle_tree.delete_versioned(dfile)
269
 
        mangle_tree.unversion_file(dfile)
270
 
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
271
 
        mangle_tree.adjust_path('mfile', root, mfile)
272
 
 
273
 
        #tests for adding parent directories
274
 
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
275
 
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
276
 
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
277
 
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
278
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
279
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
280
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
281
 
        mangle_tree.apply()
282
 
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
283
 
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
284
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
285
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
286
 
        self.assertEqual(file(mfile2_path).read(), 'later2')
287
 
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
288
 
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
289
 
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
290
 
        self.assertEqual(file(newfile_path).read(), 'hello3')
291
 
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
292
 
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
293
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
294
 
 
295
 
    def test_both_rename(self):
296
 
        create_tree,root = self.get_transform()
297
 
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
298
 
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
299
 
        create_tree.apply()        
300
 
        mangle_tree,root = self.get_transform()
301
 
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
302
 
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
303
 
        mangle_tree.adjust_path('test', root, selftest)
304
 
        mangle_tree.adjust_path('test_too_much', root, selftest)
305
 
        mangle_tree.set_executability(True, blackbox)
306
 
        mangle_tree.apply()
307
 
 
308
 
    def test_both_rename2(self):
309
 
        create_tree,root = self.get_transform()
310
 
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
311
 
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
312
 
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
313
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
314
 
                             'test_too_much-id')
315
 
        create_tree.apply()        
316
 
        mangle_tree,root = self.get_transform()
317
 
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
318
 
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
319
 
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
320
 
        mangle_tree.adjust_path('selftest', bzrlib, tests)
321
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
322
 
        mangle_tree.set_executability(True, test_too_much)
323
 
        mangle_tree.apply()
324
 
 
325
 
    def test_both_rename3(self):
326
 
        create_tree,root = self.get_transform()
327
 
        tests = create_tree.new_directory('tests', root, 'tests-id')
328
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
329
 
                             'test_too_much-id')
330
 
        create_tree.apply()        
331
 
        mangle_tree,root = self.get_transform()
332
 
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
333
 
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
334
 
        mangle_tree.adjust_path('selftest', root, tests)
335
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
336
 
        mangle_tree.set_executability(True, test_too_much)
337
 
        mangle_tree.apply()
338
 
 
339
 
    def test_move_dangling_ie(self):
340
 
        create_tree, root = self.get_transform()
341
 
        # prepare tree
342
 
        root = create_tree.root
343
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
344
 
        create_tree.apply()
345
 
        delete_contents, root = self.get_transform()
346
 
        file = delete_contents.trans_id_tree_file_id('name1')
347
 
        delete_contents.delete_contents(file)
348
 
        delete_contents.apply()
349
 
        move_id, root = self.get_transform()
350
 
        name1 = move_id.trans_id_tree_file_id('name1')
351
 
        newdir = move_id.new_directory('dir', root, 'newdir')
352
 
        move_id.adjust_path('name2', newdir, name1)
353
 
        move_id.apply()
354
 
        
355
 
    def test_replace_dangling_ie(self):
356
 
        create_tree, root = self.get_transform()
357
 
        # prepare tree
358
 
        root = create_tree.root
359
 
        create_tree.new_file('name1', root, 'hello1', 'name1')
360
 
        create_tree.apply()
361
 
        delete_contents = TreeTransform(self.wt)
362
 
        self.addCleanup(delete_contents.finalize)
363
 
        file = delete_contents.trans_id_tree_file_id('name1')
364
 
        delete_contents.delete_contents(file)
365
 
        delete_contents.apply()
366
 
        delete_contents.finalize()
367
 
        replace = TreeTransform(self.wt)
368
 
        self.addCleanup(replace.finalize)
369
 
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
370
 
        conflicts = replace.find_conflicts()
371
 
        name1 = replace.trans_id_tree_file_id('name1')
372
 
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
373
 
        resolve_conflicts(replace)
374
 
        replace.apply()
375
 
 
376
 
    def test_symlinks(self):
377
 
        if not has_symlinks():
378
 
            raise TestSkipped('Symlinks are not supported on this platform')
379
 
        transform,root = self.get_transform()
380
 
        oz_id = transform.new_directory('oz', root, 'oz-id')
381
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
382
 
                                       'wizard-id')
383
 
        wiz_id = transform.create_path('wizard2', oz_id)
384
 
        transform.create_symlink('behind_curtain', wiz_id)
385
 
        transform.version_file('wiz-id2', wiz_id)            
386
 
        transform.set_executability(True, wiz_id)
387
 
        self.assertEqual(transform.find_conflicts(), 
388
 
                         [('non-file executability', wiz_id)])
389
 
        transform.set_executability(None, wiz_id)
390
 
        transform.apply()
391
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
392
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
393
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
394
 
                         'behind_curtain')
395
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
396
 
                         'wizard-target')
397
 
 
398
 
    def get_conflicted(self):
399
 
        create,root = self.get_transform()
400
 
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
401
 
        oz = create.new_directory('oz', root, 'oz-id')
402
 
        create.new_directory('emeraldcity', oz, 'emerald-id')
403
 
        create.apply()
404
 
        conflicts,root = self.get_transform()
405
 
        # set up duplicate entry, duplicate id
406
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
407
 
                                         'dorothy-id')
408
 
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
409
 
        oz = conflicts.trans_id_tree_file_id('oz-id')
410
 
        # set up DeletedParent parent conflict
411
 
        conflicts.delete_versioned(oz)
412
 
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
413
 
        # set up MissingParent conflict
414
 
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
415
 
        conflicts.adjust_path('munchkincity', root, munchkincity)
416
 
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
417
 
        # set up parent loop
418
 
        conflicts.adjust_path('emeraldcity', emerald, emerald)
419
 
        return conflicts, emerald, oz, old_dorothy, new_dorothy
420
 
 
421
 
    def test_conflict_resolution(self):
422
 
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
423
 
            self.get_conflicted()
424
 
        resolve_conflicts(conflicts)
425
 
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
426
 
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
427
 
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
428
 
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
429
 
        self.assertEqual(conflicts.final_parent(emerald), oz)
430
 
        conflicts.apply()
431
 
 
432
 
    def test_cook_conflicts(self):
433
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
434
 
        raw_conflicts = resolve_conflicts(tt)
435
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
436
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
437
 
                                   'dorothy', None, 'dorothy-id')
438
 
        self.assertEqual(cooked_conflicts[0], duplicate)
439
 
        duplicate_id = DuplicateID('Unversioned existing file', 
440
 
                                   'dorothy.moved', 'dorothy', None,
441
 
                                   'dorothy-id')
442
 
        self.assertEqual(cooked_conflicts[1], duplicate_id)
443
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
444
 
                                       'munchkincity-id')
445
 
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
446
 
        self.assertEqual(cooked_conflicts[2], missing_parent)
447
 
        unversioned_parent = UnversionedParent('Versioned directory',
448
 
                                               'munchkincity',
449
 
                                               'munchkincity-id')
450
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
451
 
                                               'oz-id')
452
 
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
453
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
454
 
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
455
 
        self.assertEqual(cooked_conflicts[4], deleted_parent)
456
 
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
457
 
        self.assertEqual(cooked_conflicts[6], parent_loop)
458
 
        self.assertEqual(len(cooked_conflicts), 7)
459
 
        tt.finalize()
460
 
 
461
 
    def test_string_conflicts(self):
462
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
463
 
        raw_conflicts = resolve_conflicts(tt)
464
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
465
 
        tt.finalize()
466
 
        conflicts_s = [str(c) for c in cooked_conflicts]
467
 
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
468
 
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
469
 
                                         'Moved existing file to '
470
 
                                         'dorothy.moved.')
471
 
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
472
 
                                         'Unversioned existing file '
473
 
                                         'dorothy.moved.')
474
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
475
 
                                         ' munchkincity.  Created directory.')
476
 
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
477
 
                                         ' versioned, but has versioned'
478
 
                                         ' children.  Versioned directory.')
479
 
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
480
 
                                         " is not empty.  Not deleting.")
481
 
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
482
 
                                         ' versioned, but has versioned'
483
 
                                         ' children.  Versioned directory.')
484
 
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
485
 
                                         ' oz/emeraldcity.  Cancelled move.')
486
 
 
487
 
    def test_moving_versioned_directories(self):
488
 
        create, root = self.get_transform()
489
 
        kansas = create.new_directory('kansas', root, 'kansas-id')
490
 
        create.new_directory('house', kansas, 'house-id')
491
 
        create.new_directory('oz', root, 'oz-id')
492
 
        create.apply()
493
 
        cyclone, root = self.get_transform()
494
 
        oz = cyclone.trans_id_tree_file_id('oz-id')
495
 
        house = cyclone.trans_id_tree_file_id('house-id')
496
 
        cyclone.adjust_path('house', oz, house)
497
 
        cyclone.apply()
498
 
 
499
 
    def test_moving_root(self):
500
 
        create, root = self.get_transform()
501
 
        fun = create.new_directory('fun', root, 'fun-id')
502
 
        create.new_directory('sun', root, 'sun-id')
503
 
        create.new_directory('moon', root, 'moon')
504
 
        create.apply()
505
 
        transform, root = self.get_transform()
506
 
        transform.adjust_root_path('oldroot', fun)
507
 
        new_root=transform.trans_id_tree_path('')
508
 
        transform.version_file('new-root', new_root)
509
 
        transform.apply()
510
 
 
511
 
    def test_renames(self):
512
 
        create, root = self.get_transform()
513
 
        old = create.new_directory('old-parent', root, 'old-id')
514
 
        intermediate = create.new_directory('intermediate', old, 'im-id')
515
 
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
516
 
                                 'myfile-id')
517
 
        create.apply()
518
 
        rename, root = self.get_transform()
519
 
        old = rename.trans_id_file_id('old-id')
520
 
        rename.adjust_path('new', root, old)
521
 
        myfile = rename.trans_id_file_id('myfile-id')
522
 
        rename.set_executability(True, myfile)
523
 
        rename.apply()
524
 
 
525
 
    def test_find_interesting(self):
526
 
        create, root = self.get_transform()
527
 
        wt = create._tree
528
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
529
 
        create.new_file('uvfile', root, 'othertext')
530
 
        create.apply()
531
 
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
532
 
            find_interesting, wt, wt, ['vfile'])
533
 
        self.assertEqual(result, set(['myfile-id']))
534
 
 
535
 
    def test_set_executability_order(self):
536
 
        """Ensure that executability behaves the same, no matter what order.
537
 
        
538
 
        - create file and set executability simultaneously
539
 
        - create file and set executability afterward
540
 
        - unsetting the executability of a file whose executability has not been
541
 
        declared should throw an exception (this may happen when a
542
 
        merge attempts to create a file with a duplicate ID)
543
 
        """
544
 
        transform, root = self.get_transform()
545
 
        wt = transform._tree
546
 
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
547
 
                           True)
548
 
        sac = transform.new_file('set_after_creation', root,
549
 
                                 'Set after creation', 'sac')
550
 
        transform.set_executability(True, sac)
551
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
552
 
                                 'uws')
553
 
        self.assertRaises(KeyError, transform.set_executability, None, uws)
554
 
        transform.apply()
555
 
        self.assertTrue(wt.is_executable('soc'))
556
 
        self.assertTrue(wt.is_executable('sac'))
557
 
 
558
 
    def test_preserve_mode(self):
559
 
        """File mode is preserved when replacing content"""
560
 
        if sys.platform == 'win32':
561
 
            raise TestSkipped('chmod has no effect on win32')
562
 
        transform, root = self.get_transform()
563
 
        transform.new_file('file1', root, 'contents', 'file1-id', True)
564
 
        transform.apply()
565
 
        self.assertTrue(self.wt.is_executable('file1-id'))
566
 
        transform, root = self.get_transform()
567
 
        file1_id = transform.trans_id_tree_file_id('file1-id')
568
 
        transform.delete_contents(file1_id)
569
 
        transform.create_file('contents2', file1_id)
570
 
        transform.apply()
571
 
        self.assertTrue(self.wt.is_executable('file1-id'))
572
 
 
573
 
    def test__set_mode_stats_correctly(self):
574
 
        """_set_mode stats to determine file mode."""
575
 
        if sys.platform == 'win32':
576
 
            raise TestSkipped('chmod has no effect on win32')
577
 
 
578
 
        stat_paths = []
579
 
        real_stat = os.stat
580
 
        def instrumented_stat(path):
581
 
            stat_paths.append(path)
582
 
            return real_stat(path)
583
 
 
584
 
        transform, root = self.get_transform()
585
 
 
586
 
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
587
 
                                     file_id='bar-id-1', executable=False)
588
 
        transform.apply()
589
 
 
590
 
        transform, root = self.get_transform()
591
 
        bar1_id = transform.trans_id_tree_path('bar')
592
 
        bar2_id = transform.trans_id_tree_path('bar2')
593
 
        try:
594
 
            os.stat = instrumented_stat
595
 
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
596
 
        finally:
597
 
            os.stat = real_stat
598
 
            transform.finalize()
599
 
 
600
 
        bar1_abspath = self.wt.abspath('bar')
601
 
        self.assertEqual([bar1_abspath], stat_paths)
602
 
 
603
 
    def test_iter_changes(self):
604
 
        self.wt.set_root_id('eert_toor')
605
 
        transform, root = self.get_transform()
606
 
        transform.new_file('old', root, 'blah', 'id-1', True)
607
 
        transform.apply()
608
 
        transform, root = self.get_transform()
609
 
        try:
610
 
            self.assertEqual([], list(transform._iter_changes()))
611
 
            old = transform.trans_id_tree_file_id('id-1')
612
 
            transform.unversion_file(old)
613
 
            self.assertEqual([('id-1', ('old', None), False, (True, False),
614
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
615
 
                (True, True))], list(transform._iter_changes()))
616
 
            transform.new_directory('new', root, 'id-1')
617
 
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
618
 
                ('eert_toor', 'eert_toor'), ('old', 'new'),
619
 
                ('file', 'directory'),
620
 
                (True, False))], list(transform._iter_changes()))
621
 
        finally:
622
 
            transform.finalize()
623
 
 
624
 
    def test_iter_changes_new(self):
625
 
        self.wt.set_root_id('eert_toor')
626
 
        transform, root = self.get_transform()
627
 
        transform.new_file('old', root, 'blah')
628
 
        transform.apply()
629
 
        transform, root = self.get_transform()
630
 
        try:
631
 
            old = transform.trans_id_tree_path('old')
632
 
            transform.version_file('id-1', old)
633
 
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
634
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
635
 
                (False, False))], list(transform._iter_changes()))
636
 
        finally:
637
 
            transform.finalize()
638
 
 
639
 
    def test_iter_changes_modifications(self):
640
 
        self.wt.set_root_id('eert_toor')
641
 
        transform, root = self.get_transform()
642
 
        transform.new_file('old', root, 'blah', 'id-1')
643
 
        transform.new_file('new', root, 'blah')
644
 
        transform.new_directory('subdir', root, 'subdir-id')
645
 
        transform.apply()
646
 
        transform, root = self.get_transform()
647
 
        try:
648
 
            old = transform.trans_id_tree_path('old')
649
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
650
 
            new = transform.trans_id_tree_path('new')
651
 
            self.assertEqual([], list(transform._iter_changes()))
652
 
 
653
 
            #content deletion
654
 
            transform.delete_contents(old)
655
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
656
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
657
 
                (False, False))], list(transform._iter_changes()))
658
 
 
659
 
            #content change
660
 
            transform.create_file('blah', old)
661
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
662
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
663
 
                (False, False))], list(transform._iter_changes()))
664
 
            transform.cancel_deletion(old)
665
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
666
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
667
 
                (False, False))], list(transform._iter_changes()))
668
 
            transform.cancel_creation(old)
669
 
 
670
 
            # move file_id to a different file
671
 
            self.assertEqual([], list(transform._iter_changes()))
672
 
            transform.unversion_file(old)
673
 
            transform.version_file('id-1', new)
674
 
            transform.adjust_path('old', root, new)
675
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
676
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
677
 
                (False, False))], list(transform._iter_changes()))
678
 
            transform.cancel_versioning(new)
679
 
            transform._removed_id = set()
680
 
 
681
 
            #execute bit
682
 
            self.assertEqual([], list(transform._iter_changes()))
683
 
            transform.set_executability(True, old)
684
 
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
685
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
686
 
                (False, True))], list(transform._iter_changes()))
687
 
            transform.set_executability(None, old)
688
 
 
689
 
            # filename
690
 
            self.assertEqual([], list(transform._iter_changes()))
691
 
            transform.adjust_path('new', root, old)
692
 
            transform._new_parent = {}
693
 
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
694
 
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
695
 
                (False, False))], list(transform._iter_changes()))
696
 
            transform._new_name = {}
697
 
 
698
 
            # parent directory
699
 
            self.assertEqual([], list(transform._iter_changes()))
700
 
            transform.adjust_path('new', subdir, old)
701
 
            transform._new_name = {}
702
 
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
703
 
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
704
 
                ('file', 'file'), (False, False))],
705
 
                list(transform._iter_changes()))
706
 
            transform._new_path = {}
707
 
 
708
 
        finally:
709
 
            transform.finalize()
710
 
 
711
 
    def test_iter_changes_modified_bleed(self):
712
 
        self.wt.set_root_id('eert_toor')
713
 
        """Modified flag should not bleed from one change to another"""
714
 
        # unfortunately, we have no guarantee that file1 (which is modified)
715
 
        # will be applied before file2.  And if it's applied after file2, it
716
 
        # obviously can't bleed into file2's change output.  But for now, it
717
 
        # works.
718
 
        transform, root = self.get_transform()
719
 
        transform.new_file('file1', root, 'blah', 'id-1')
720
 
        transform.new_file('file2', root, 'blah', 'id-2')
721
 
        transform.apply()
722
 
        transform, root = self.get_transform()
723
 
        try:
724
 
            transform.delete_contents(transform.trans_id_file_id('id-1'))
725
 
            transform.set_executability(True,
726
 
            transform.trans_id_file_id('id-2'))
727
 
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
728
 
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
729
 
                ('file', None), (False, False)),
730
 
                ('id-2', (u'file2', u'file2'), False, (True, True),
731
 
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
732
 
                ('file', 'file'), (False, True))],
733
 
                list(transform._iter_changes()))
734
 
        finally:
735
 
            transform.finalize()
736
 
 
737
 
    def test_iter_changes_move_missing(self):
738
 
        """Test moving ids with no files around"""
739
 
        self.wt.set_root_id('toor_eert')
740
 
        # Need two steps because versioning a non-existant file is a conflict.
741
 
        transform, root = self.get_transform()
742
 
        transform.new_directory('floater', root, 'floater-id')
743
 
        transform.apply()
744
 
        transform, root = self.get_transform()
745
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
746
 
        transform.apply()
747
 
        transform, root = self.get_transform()
748
 
        floater = transform.trans_id_tree_path('floater')
749
 
        try:
750
 
            transform.adjust_path('flitter', root, floater)
751
 
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
752
 
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
753
 
            (None, None), (False, False))], list(transform._iter_changes()))
754
 
        finally:
755
 
            transform.finalize()
756
 
 
757
 
    def test_iter_changes_pointless(self):
758
 
        """Ensure that no-ops are not treated as modifications"""
759
 
        self.wt.set_root_id('eert_toor')
760
 
        transform, root = self.get_transform()
761
 
        transform.new_file('old', root, 'blah', 'id-1')
762
 
        transform.new_directory('subdir', root, 'subdir-id')
763
 
        transform.apply()
764
 
        transform, root = self.get_transform()
765
 
        try:
766
 
            old = transform.trans_id_tree_path('old')
767
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
768
 
            self.assertEqual([], list(transform._iter_changes()))
769
 
            transform.delete_contents(subdir)
770
 
            transform.create_directory(subdir)
771
 
            transform.set_executability(False, old)
772
 
            transform.unversion_file(old)
773
 
            transform.version_file('id-1', old)
774
 
            transform.adjust_path('old', root, old)
775
 
            self.assertEqual([], list(transform._iter_changes()))
776
 
        finally:
777
 
            transform.finalize()
778
 
 
779
 
    def test_rename_count(self):
780
 
        transform, root = self.get_transform()
781
 
        transform.new_file('name1', root, 'contents')
782
 
        self.assertEqual(transform.rename_count, 0)
783
 
        transform.apply()
784
 
        self.assertEqual(transform.rename_count, 1)
785
 
        transform2, root = self.get_transform()
786
 
        transform2.adjust_path('name2', root,
787
 
                               transform2.trans_id_tree_path('name1'))
788
 
        self.assertEqual(transform2.rename_count, 0)
789
 
        transform2.apply()
790
 
        self.assertEqual(transform2.rename_count, 2)
791
 
 
792
 
    def test_change_parent(self):
793
 
        """Ensure that after we change a parent, the results are still right.
794
 
 
795
 
        Renames and parent changes on pending transforms can happen as part
796
 
        of conflict resolution, and are explicitly permitted by the
797
 
        TreeTransform API.
798
 
 
799
 
        This test ensures they work correctly with the rename-avoidance
800
 
        optimization.
801
 
        """
802
 
        transform, root = self.get_transform()
803
 
        parent1 = transform.new_directory('parent1', root)
804
 
        child1 = transform.new_file('child1', parent1, 'contents')
805
 
        parent2 = transform.new_directory('parent2', root)
806
 
        transform.adjust_path('child1', parent2, child1)
807
 
        transform.apply()
808
 
        self.failIfExists(self.wt.abspath('parent1/child1'))
809
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
810
 
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
811
 
        # no rename for child1 (counting only renames during apply)
812
 
        self.failUnlessEqual(2, transform.rename_count)
813
 
 
814
 
    def test_cancel_parent(self):
815
 
        """Cancelling a parent doesn't cause deletion of a non-empty directory
816
 
 
817
 
        This is like the test_change_parent, except that we cancel the parent
818
 
        before adjusting the path.  The transform must detect that the
819
 
        directory is non-empty, and move children to safe locations.
820
 
        """
821
 
        transform, root = self.get_transform()
822
 
        parent1 = transform.new_directory('parent1', root)
823
 
        child1 = transform.new_file('child1', parent1, 'contents')
824
 
        child2 = transform.new_file('child2', parent1, 'contents')
825
 
        try:
826
 
            transform.cancel_creation(parent1)
827
 
        except OSError:
828
 
            self.fail('Failed to move child1 before deleting parent1')
829
 
        transform.cancel_creation(child2)
830
 
        transform.create_directory(parent1)
831
 
        try:
832
 
            transform.cancel_creation(parent1)
833
 
        # If the transform incorrectly believes that child2 is still in
834
 
        # parent1's limbo directory, it will try to rename it and fail
835
 
        # because was already moved by the first cancel_creation.
836
 
        except OSError:
837
 
            self.fail('Transform still thinks child2 is a child of parent1')
838
 
        parent2 = transform.new_directory('parent2', root)
839
 
        transform.adjust_path('child1', parent2, child1)
840
 
        transform.apply()
841
 
        self.failIfExists(self.wt.abspath('parent1'))
842
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
843
 
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
844
 
        self.failUnlessEqual(2, transform.rename_count)
845
 
 
846
 
    def test_adjust_and_cancel(self):
847
 
        """Make sure adjust_path keeps track of limbo children properly"""
848
 
        transform, root = self.get_transform()
849
 
        parent1 = transform.new_directory('parent1', root)
850
 
        child1 = transform.new_file('child1', parent1, 'contents')
851
 
        parent2 = transform.new_directory('parent2', root)
852
 
        transform.adjust_path('child1', parent2, child1)
853
 
        transform.cancel_creation(child1)
854
 
        try:
855
 
            transform.cancel_creation(parent1)
856
 
        # if the transform thinks child1 is still in parent1's limbo
857
 
        # directory, it will attempt to move it and fail.
858
 
        except OSError:
859
 
            self.fail('Transform still thinks child1 is a child of parent1')
860
 
        transform.finalize()
861
 
 
862
 
    def test_noname_contents(self):
863
 
        """TreeTransform should permit deferring naming files."""
864
 
        transform, root = self.get_transform()
865
 
        parent = transform.trans_id_file_id('parent-id')
866
 
        try:
867
 
            transform.create_directory(parent)
868
 
        except KeyError:
869
 
            self.fail("Can't handle contents with no name")
870
 
        transform.finalize()
871
 
 
872
 
    def test_noname_contents_nested(self):
873
 
        """TreeTransform should permit deferring naming files."""
874
 
        transform, root = self.get_transform()
875
 
        parent = transform.trans_id_file_id('parent-id')
876
 
        try:
877
 
            transform.create_directory(parent)
878
 
        except KeyError:
879
 
            self.fail("Can't handle contents with no name")
880
 
        child = transform.new_directory('child', parent)
881
 
        transform.adjust_path('parent', root, parent)
882
 
        transform.apply()
883
 
        self.failUnlessExists(self.wt.abspath('parent/child'))
884
 
        self.assertEqual(1, transform.rename_count)
885
 
 
886
 
    def test_reuse_name(self):
887
 
        """Avoid reusing the same limbo name for different files"""
888
 
        transform, root = self.get_transform()
889
 
        parent = transform.new_directory('parent', root)
890
 
        child1 = transform.new_directory('child', parent)
891
 
        try:
892
 
            child2 = transform.new_directory('child', parent)
893
 
        except OSError:
894
 
            self.fail('Tranform tried to use the same limbo name twice')
895
 
        transform.adjust_path('child2', parent, child2)
896
 
        transform.apply()
897
 
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
898
 
        # child2 is put into top-level limbo because child1 has already
899
 
        # claimed the direct limbo path when child2 is created.  There is no
900
 
        # advantage in renaming files once they're in top-level limbo, except
901
 
        # as part of apply.
902
 
        self.assertEqual(2, transform.rename_count)
903
 
 
904
 
    def test_reuse_when_first_moved(self):
905
 
        """Don't avoid direct paths when it is safe to use them"""
906
 
        transform, root = self.get_transform()
907
 
        parent = transform.new_directory('parent', root)
908
 
        child1 = transform.new_directory('child', parent)
909
 
        transform.adjust_path('child1', parent, child1)
910
 
        child2 = transform.new_directory('child', parent)
911
 
        transform.apply()
912
 
        # limbo/new-1 => parent
913
 
        self.assertEqual(1, transform.rename_count)
914
 
 
915
 
    def test_reuse_after_cancel(self):
916
 
        """Don't avoid direct paths when it is safe to use them"""
917
 
        transform, root = self.get_transform()
918
 
        parent2 = transform.new_directory('parent2', root)
919
 
        child1 = transform.new_directory('child1', parent2)
920
 
        transform.cancel_creation(parent2)
921
 
        transform.create_directory(parent2)
922
 
        child2 = transform.new_directory('child1', parent2)
923
 
        transform.adjust_path('child2', parent2, child1)
924
 
        transform.apply()
925
 
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
926
 
        self.assertEqual(2, transform.rename_count)
927
 
 
928
 
    def test_finalize_order(self):
929
 
        """Finalize must be done in child-to-parent order"""
930
 
        transform, root = self.get_transform()
931
 
        parent = transform.new_directory('parent', root)
932
 
        child = transform.new_directory('child', parent)
933
 
        try:
934
 
            transform.finalize()
935
 
        except OSError:
936
 
            self.fail('Tried to remove parent before child1')
937
 
 
938
 
    def test_cancel_with_cancelled_child_should_succeed(self):
939
 
        transform, root = self.get_transform()
940
 
        parent = transform.new_directory('parent', root)
941
 
        child = transform.new_directory('child', parent)
942
 
        transform.cancel_creation(child)
943
 
        transform.cancel_creation(parent)
944
 
        transform.finalize()
945
 
 
946
 
    def test_change_entry(self):
947
 
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
948
 
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
949
 
            None, None, None)
950
 
 
951
 
 
952
 
class TransformGroup(object):
953
 
    def __init__(self, dirname, root_id):
954
 
        self.name = dirname
955
 
        os.mkdir(dirname)
956
 
        self.wt = BzrDir.create_standalone_workingtree(dirname)
957
 
        self.wt.set_root_id(root_id)
958
 
        self.b = self.wt.branch
959
 
        self.tt = TreeTransform(self.wt)
960
 
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
961
 
 
962
 
 
963
 
def conflict_text(tree, merge):
964
 
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
965
 
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
966
 
 
967
 
 
968
 
class TestTransformMerge(TestCaseInTempDir):
969
 
    def test_text_merge(self):
970
 
        root_id = generate_ids.gen_root_id()
971
 
        base = TransformGroup("base", root_id)
972
 
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
973
 
        base.tt.new_file('b', base.root, 'b1', 'b')
974
 
        base.tt.new_file('c', base.root, 'c', 'c')
975
 
        base.tt.new_file('d', base.root, 'd', 'd')
976
 
        base.tt.new_file('e', base.root, 'e', 'e')
977
 
        base.tt.new_file('f', base.root, 'f', 'f')
978
 
        base.tt.new_directory('g', base.root, 'g')
979
 
        base.tt.new_directory('h', base.root, 'h')
980
 
        base.tt.apply()
981
 
        other = TransformGroup("other", root_id)
982
 
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
983
 
        other.tt.new_file('b', other.root, 'b2', 'b')
984
 
        other.tt.new_file('c', other.root, 'c2', 'c')
985
 
        other.tt.new_file('d', other.root, 'd', 'd')
986
 
        other.tt.new_file('e', other.root, 'e2', 'e')
987
 
        other.tt.new_file('f', other.root, 'f', 'f')
988
 
        other.tt.new_file('g', other.root, 'g', 'g')
989
 
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
990
 
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
991
 
        other.tt.apply()
992
 
        this = TransformGroup("this", root_id)
993
 
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
994
 
        this.tt.new_file('b', this.root, 'b', 'b')
995
 
        this.tt.new_file('c', this.root, 'c', 'c')
996
 
        this.tt.new_file('d', this.root, 'd2', 'd')
997
 
        this.tt.new_file('e', this.root, 'e2', 'e')
998
 
        this.tt.new_file('f', this.root, 'f', 'f')
999
 
        this.tt.new_file('g', this.root, 'g', 'g')
1000
 
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1001
 
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1002
 
        this.tt.apply()
1003
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1004
 
        # textual merge
1005
 
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1006
 
        # three-way text conflict
1007
 
        self.assertEqual(this.wt.get_file('b').read(), 
1008
 
                         conflict_text('b', 'b2'))
1009
 
        # OTHER wins
1010
 
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1011
 
        # THIS wins
1012
 
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
1013
 
        # Ambigious clean merge
1014
 
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1015
 
        # No change
1016
 
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1017
 
        # Correct correct results when THIS == OTHER 
1018
 
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1019
 
        # Text conflict when THIS & OTHER are text and BASE is dir
1020
 
        self.assertEqual(this.wt.get_file('h').read(), 
1021
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1022
 
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1023
 
                         '1\n2\n3\n4\n')
1024
 
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1025
 
                         'h\ni\nj\nk\n')
1026
 
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1027
 
        self.assertEqual(this.wt.get_file('i').read(), 
1028
 
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1029
 
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1030
 
                         '1\n2\n3\n4\n')
1031
 
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1032
 
                         'h\ni\nj\nk\n')
1033
 
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1034
 
        modified = ['a', 'b', 'c', 'h', 'i']
1035
 
        merge_modified = this.wt.merge_modified()
1036
 
        self.assertSubset(merge_modified, modified)
1037
 
        self.assertEqual(len(merge_modified), len(modified))
1038
 
        file(this.wt.id2abspath('a'), 'wb').write('booga')
1039
 
        modified.pop(0)
1040
 
        merge_modified = this.wt.merge_modified()
1041
 
        self.assertSubset(merge_modified, modified)
1042
 
        self.assertEqual(len(merge_modified), len(modified))
1043
 
        this.wt.remove('b')
1044
 
        this.wt.revert([])
1045
 
 
1046
 
    def test_file_merge(self):
1047
 
        if not has_symlinks():
1048
 
            raise TestSkipped('Symlinks are not supported on this platform')
1049
 
        root_id = generate_ids.gen_root_id()
1050
 
        base = TransformGroup("BASE", root_id)
1051
 
        this = TransformGroup("THIS", root_id)
1052
 
        other = TransformGroup("OTHER", root_id)
1053
 
        for tg in this, base, other:
1054
 
            tg.tt.new_directory('a', tg.root, 'a')
1055
 
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1056
 
            tg.tt.new_file('c', tg.root, 'c', 'c')
1057
 
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1058
 
        targets = ((base, 'base-e', 'base-f', None, None), 
1059
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
1060
 
                   (other, 'other-e', None, 'other-g', 'other-h'))
1061
 
        for tg, e_target, f_target, g_target, h_target in targets:
1062
 
            for link, target in (('e', e_target), ('f', f_target), 
1063
 
                                 ('g', g_target), ('h', h_target)):
1064
 
                if target is not None:
1065
 
                    tg.tt.new_symlink(link, tg.root, target, link)
1066
 
 
1067
 
        for tg in this, base, other:
1068
 
            tg.tt.apply()
1069
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1070
 
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1071
 
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1072
 
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1073
 
        for suffix in ('THIS', 'BASE', 'OTHER'):
1074
 
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1075
 
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1076
 
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1077
 
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1078
 
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1079
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1080
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1081
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1082
 
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1083
 
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1084
 
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1085
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1086
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1087
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1088
 
 
1089
 
    def test_filename_merge(self):
1090
 
        root_id = generate_ids.gen_root_id()
1091
 
        base = TransformGroup("BASE", root_id)
1092
 
        this = TransformGroup("THIS", root_id)
1093
 
        other = TransformGroup("OTHER", root_id)
1094
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1095
 
                                   for t in [base, this, other]]
1096
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1097
 
                                   for t in [base, this, other]]
1098
 
        base.tt.new_directory('c', base_a, 'c')
1099
 
        this.tt.new_directory('c1', this_a, 'c')
1100
 
        other.tt.new_directory('c', other_b, 'c')
1101
 
 
1102
 
        base.tt.new_directory('d', base_a, 'd')
1103
 
        this.tt.new_directory('d1', this_b, 'd')
1104
 
        other.tt.new_directory('d', other_a, 'd')
1105
 
 
1106
 
        base.tt.new_directory('e', base_a, 'e')
1107
 
        this.tt.new_directory('e', this_a, 'e')
1108
 
        other.tt.new_directory('e1', other_b, 'e')
1109
 
 
1110
 
        base.tt.new_directory('f', base_a, 'f')
1111
 
        this.tt.new_directory('f1', this_b, 'f')
1112
 
        other.tt.new_directory('f1', other_b, 'f')
1113
 
 
1114
 
        for tg in [this, base, other]:
1115
 
            tg.tt.apply()
1116
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1117
 
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1118
 
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1119
 
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1120
 
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1121
 
 
1122
 
    def test_filename_merge_conflicts(self):
1123
 
        root_id = generate_ids.gen_root_id()
1124
 
        base = TransformGroup("BASE", root_id)
1125
 
        this = TransformGroup("THIS", root_id)
1126
 
        other = TransformGroup("OTHER", root_id)
1127
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1128
 
                                   for t in [base, this, other]]
1129
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1130
 
                                   for t in [base, this, other]]
1131
 
 
1132
 
        base.tt.new_file('g', base_a, 'g', 'g')
1133
 
        other.tt.new_file('g1', other_b, 'g1', 'g')
1134
 
 
1135
 
        base.tt.new_file('h', base_a, 'h', 'h')
1136
 
        this.tt.new_file('h1', this_b, 'h1', 'h')
1137
 
 
1138
 
        base.tt.new_file('i', base.root, 'i', 'i')
1139
 
        other.tt.new_directory('i1', this_b, 'i')
1140
 
 
1141
 
        for tg in [this, base, other]:
1142
 
            tg.tt.apply()
1143
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1144
 
 
1145
 
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1146
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1147
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1148
 
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1149
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1150
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1151
 
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1152
 
 
1153
 
 
1154
 
class TestBuildTree(tests.TestCaseWithTransport):
1155
 
 
1156
 
    def test_build_tree(self):
1157
 
        if not has_symlinks():
1158
 
            raise TestSkipped('Test requires symlink support')
1159
 
        os.mkdir('a')
1160
 
        a = BzrDir.create_standalone_workingtree('a')
1161
 
        os.mkdir('a/foo')
1162
 
        file('a/foo/bar', 'wb').write('contents')
1163
 
        os.symlink('a/foo/bar', 'a/foo/baz')
1164
 
        a.add(['foo', 'foo/bar', 'foo/baz'])
1165
 
        a.commit('initial commit')
1166
 
        b = BzrDir.create_standalone_workingtree('b')
1167
 
        basis = a.basis_tree()
1168
 
        basis.lock_read()
1169
 
        self.addCleanup(basis.unlock)
1170
 
        build_tree(basis, b)
1171
 
        self.assertIs(os.path.isdir('b/foo'), True)
1172
 
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1173
 
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1174
 
 
1175
 
    def test_build_with_references(self):
1176
 
        tree = self.make_branch_and_tree('source',
1177
 
            format='dirstate-with-subtree')
1178
 
        subtree = self.make_branch_and_tree('source/subtree',
1179
 
            format='dirstate-with-subtree')
1180
 
        tree.add_reference(subtree)
1181
 
        tree.commit('a revision')
1182
 
        tree.branch.create_checkout('target')
1183
 
        self.failUnlessExists('target')
1184
 
        self.failUnlessExists('target/subtree')
1185
 
 
1186
 
    def test_file_conflict_handling(self):
1187
 
        """Ensure that when building trees, conflict handling is done"""
1188
 
        source = self.make_branch_and_tree('source')
1189
 
        target = self.make_branch_and_tree('target')
1190
 
        self.build_tree(['source/file', 'target/file'])
1191
 
        source.add('file', 'new-file')
1192
 
        source.commit('added file')
1193
 
        build_tree(source.basis_tree(), target)
1194
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1195
 
                          'file.moved', 'file', None, 'new-file')],
1196
 
                         target.conflicts())
1197
 
        target2 = self.make_branch_and_tree('target2')
1198
 
        target_file = file('target2/file', 'wb')
1199
 
        try:
1200
 
            source_file = file('source/file', 'rb')
1201
 
            try:
1202
 
                target_file.write(source_file.read())
1203
 
            finally:
1204
 
                source_file.close()
1205
 
        finally:
1206
 
            target_file.close()
1207
 
        build_tree(source.basis_tree(), target2)
1208
 
        self.assertEqual([], target2.conflicts())
1209
 
 
1210
 
    def test_symlink_conflict_handling(self):
1211
 
        """Ensure that when building trees, conflict handling is done"""
1212
 
        if not has_symlinks():
1213
 
            raise TestSkipped('Test requires symlink support')
1214
 
        source = self.make_branch_and_tree('source')
1215
 
        os.symlink('foo', 'source/symlink')
1216
 
        source.add('symlink', 'new-symlink')
1217
 
        source.commit('added file')
1218
 
        target = self.make_branch_and_tree('target')
1219
 
        os.symlink('bar', 'target/symlink')
1220
 
        build_tree(source.basis_tree(), target)
1221
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1222
 
            'symlink.moved', 'symlink', None, 'new-symlink')],
1223
 
            target.conflicts())
1224
 
        target = self.make_branch_and_tree('target2')
1225
 
        os.symlink('foo', 'target2/symlink')
1226
 
        build_tree(source.basis_tree(), target)
1227
 
        self.assertEqual([], target.conflicts())
1228
 
        
1229
 
    def test_directory_conflict_handling(self):
1230
 
        """Ensure that when building trees, conflict handling is done"""
1231
 
        source = self.make_branch_and_tree('source')
1232
 
        target = self.make_branch_and_tree('target')
1233
 
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1234
 
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1235
 
        source.commit('added file')
1236
 
        build_tree(source.basis_tree(), target)
1237
 
        self.assertEqual([], target.conflicts())
1238
 
        self.failUnlessExists('target/dir1/file')
1239
 
 
1240
 
        # Ensure contents are merged
1241
 
        target = self.make_branch_and_tree('target2')
1242
 
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1243
 
        build_tree(source.basis_tree(), target)
1244
 
        self.assertEqual([], target.conflicts())
1245
 
        self.failUnlessExists('target2/dir1/file2')
1246
 
        self.failUnlessExists('target2/dir1/file')
1247
 
 
1248
 
        # Ensure new contents are suppressed for existing branches
1249
 
        target = self.make_branch_and_tree('target3')
1250
 
        self.make_branch('target3/dir1')
1251
 
        self.build_tree(['target3/dir1/file2'])
1252
 
        build_tree(source.basis_tree(), target)
1253
 
        self.failIfExists('target3/dir1/file')
1254
 
        self.failUnlessExists('target3/dir1/file2')
1255
 
        self.failUnlessExists('target3/dir1.diverted/file')
1256
 
        self.assertEqual([DuplicateEntry('Diverted to',
1257
 
            'dir1.diverted', 'dir1', 'new-dir1', None)],
1258
 
            target.conflicts())
1259
 
 
1260
 
        target = self.make_branch_and_tree('target4')
1261
 
        self.build_tree(['target4/dir1/'])
1262
 
        self.make_branch('target4/dir1/file')
1263
 
        build_tree(source.basis_tree(), target)
1264
 
        self.failUnlessExists('target4/dir1/file')
1265
 
        self.assertEqual('directory', file_kind('target4/dir1/file'))
1266
 
        self.failUnlessExists('target4/dir1/file.diverted')
1267
 
        self.assertEqual([DuplicateEntry('Diverted to',
1268
 
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1269
 
            target.conflicts())
1270
 
 
1271
 
    def test_mixed_conflict_handling(self):
1272
 
        """Ensure that when building trees, conflict handling is done"""
1273
 
        source = self.make_branch_and_tree('source')
1274
 
        target = self.make_branch_and_tree('target')
1275
 
        self.build_tree(['source/name', 'target/name/'])
1276
 
        source.add('name', 'new-name')
1277
 
        source.commit('added file')
1278
 
        build_tree(source.basis_tree(), target)
1279
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1280
 
            'name.moved', 'name', None, 'new-name')], target.conflicts())
1281
 
 
1282
 
    def test_raises_in_populated(self):
1283
 
        source = self.make_branch_and_tree('source')
1284
 
        self.build_tree(['source/name'])
1285
 
        source.add('name')
1286
 
        source.commit('added name')
1287
 
        target = self.make_branch_and_tree('target')
1288
 
        self.build_tree(['target/name'])
1289
 
        target.add('name')
1290
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
1291
 
            build_tree, source.basis_tree(), target)
1292
 
 
1293
 
    def test_build_tree_rename_count(self):
1294
 
        source = self.make_branch_and_tree('source')
1295
 
        self.build_tree(['source/file1', 'source/dir1/'])
1296
 
        source.add(['file1', 'dir1'])
1297
 
        source.commit('add1')
1298
 
        target1 = self.make_branch_and_tree('target1')
1299
 
        transform_result = build_tree(source.basis_tree(), target1)
1300
 
        self.assertEqual(2, transform_result.rename_count)
1301
 
 
1302
 
        self.build_tree(['source/dir1/file2'])
1303
 
        source.add(['dir1/file2'])
1304
 
        source.commit('add3')
1305
 
        target2 = self.make_branch_and_tree('target2')
1306
 
        transform_result = build_tree(source.basis_tree(), target2)
1307
 
        # children of non-root directories should not be renamed
1308
 
        self.assertEqual(2, transform_result.rename_count)
1309
 
 
1310
 
 
1311
 
class MockTransform(object):
1312
 
 
1313
 
    def has_named_child(self, by_parent, parent_id, name):
1314
 
        for child_id in by_parent[parent_id]:
1315
 
            if child_id == '0':
1316
 
                if name == "name~":
1317
 
                    return True
1318
 
            elif name == "name.~%s~" % child_id:
1319
 
                return True
1320
 
        return False
1321
 
 
1322
 
 
1323
 
class MockEntry(object):
1324
 
    def __init__(self):
1325
 
        object.__init__(self)
1326
 
        self.name = "name"
1327
 
 
1328
 
class TestGetBackupName(TestCase):
1329
 
    def test_get_backup_name(self):
1330
 
        tt = MockTransform()
1331
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1332
 
        self.assertEqual(name, 'name.~1~')
1333
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1334
 
        self.assertEqual(name, 'name.~2~')
1335
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1336
 
        self.assertEqual(name, 'name.~1~')
1337
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1338
 
        self.assertEqual(name, 'name.~1~')
1339
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1340
 
        self.assertEqual(name, 'name.~4~')