~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2007-08-14 19:29:56 UTC
  • mto: This revision was merged to the branch mainline in revision 2698.
  • Revision ID: john@arbash-meinel.com-20070814192956-34h336i5q3m34ods
Switch bzr.dev to 0.91 development

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~')