1
# Copyright (C) 2006 Canonical Ltd
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.
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.
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
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)
43
class TestTreeTransform(tests.TestCaseWithTransport):
46
super(TestTreeTransform, self).setUp()
47
self.wt = self.make_branch_and_tree('.', format='experimental-reference-dirstate')
50
def get_transform(self):
51
transform = TreeTransform(self.wt)
52
#self.addCleanup(transform.finalize)
53
return transform, transform.root
55
def test_existing_limbo(self):
56
limbo_name = urlutils.local_path_from_url(
57
self.wt._control_files.controlfilename('limbo'))
58
transform, root = self.get_transform()
59
os.mkdir(pathjoin(limbo_name, 'hehe'))
60
self.assertRaises(ImmortalLimbo, transform.apply)
61
self.assertRaises(LockError, self.wt.unlock)
62
self.assertRaises(ExistingLimbo, self.get_transform)
63
self.assertRaises(LockError, self.wt.unlock)
64
os.rmdir(pathjoin(limbo_name, 'hehe'))
66
transform, root = self.get_transform()
70
transform, root = self.get_transform()
71
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
72
imaginary_id = transform.trans_id_tree_path('imaginary')
73
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
74
self.assertEqual(imaginary_id, imaginary_id2)
75
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
76
self.assertEqual(transform.final_kind(root), 'directory')
77
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
78
trans_id = transform.create_path('name', root)
79
self.assertIs(transform.final_file_id(trans_id), None)
80
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
81
transform.create_file('contents', trans_id)
82
transform.set_executability(True, trans_id)
83
transform.version_file('my_pretties', trans_id)
84
self.assertRaises(DuplicateKey, transform.version_file,
85
'my_pretties', trans_id)
86
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
87
self.assertEqual(transform.final_parent(trans_id), root)
88
self.assertIs(transform.final_parent(root), ROOT_PARENT)
89
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
90
oz_id = transform.create_path('oz', root)
91
transform.create_directory(oz_id)
92
transform.version_file('ozzie', oz_id)
93
trans_id2 = transform.create_path('name2', root)
94
transform.create_file('contents', trans_id2)
95
transform.set_executability(False, trans_id2)
96
transform.version_file('my_pretties2', trans_id2)
97
modified_paths = transform.apply().modified_paths
98
self.assertEqual('contents', self.wt.get_file_byname('name').read())
99
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
100
self.assertIs(self.wt.is_executable('my_pretties'), True)
101
self.assertIs(self.wt.is_executable('my_pretties2'), False)
102
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
103
self.assertEqual(len(modified_paths), 3)
104
tree_mod_paths = [self.wt.id2abspath(f) for f in
105
('ozzie', 'my_pretties', 'my_pretties2')]
106
self.assertSubset(tree_mod_paths, modified_paths)
107
# is it safe to finalize repeatedly?
111
def test_convenience(self):
112
transform, root = self.get_transform()
113
trans_id = transform.new_file('name', root, 'contents',
115
oz = transform.new_directory('oz', root, 'oz-id')
116
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
117
toto = transform.new_file('toto', dorothy, 'toto-contents',
120
self.assertEqual(len(transform.find_conflicts()), 0)
122
self.assertRaises(ReusingTransform, transform.find_conflicts)
123
self.assertEqual('contents', file(self.wt.abspath('name')).read())
124
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
125
self.assertIs(self.wt.is_executable('my_pretties'), True)
126
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
127
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
128
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
130
self.assertEqual('toto-contents',
131
self.wt.get_file_byname('oz/dorothy/toto').read())
132
self.assertIs(self.wt.is_executable('toto-id'), False)
134
def test_tree_reference(self):
135
transform, root = self.get_transform()
136
tree = transform._tree
137
trans_id = transform.new_directory('reference', root, 'subtree-id')
138
transform.set_tree_reference('subtree-revision', trans_id)
140
self.assertEqual('subtree-revision',
141
tree.inventory['subtree-id'].reference_revision)
143
def test_conflicts(self):
144
transform, root = self.get_transform()
145
trans_id = transform.new_file('name', root, 'contents',
147
self.assertEqual(len(transform.find_conflicts()), 0)
148
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
149
self.assertEqual(transform.find_conflicts(),
150
[('duplicate', trans_id, trans_id2, 'name')])
151
self.assertRaises(MalformedTransform, transform.apply)
152
transform.adjust_path('name', trans_id, trans_id2)
153
self.assertEqual(transform.find_conflicts(),
154
[('non-directory parent', trans_id)])
155
tinman_id = transform.trans_id_tree_path('tinman')
156
transform.adjust_path('name', tinman_id, trans_id2)
157
self.assertEqual(transform.find_conflicts(),
158
[('unversioned parent', tinman_id),
159
('missing parent', tinman_id)])
160
lion_id = transform.create_path('lion', root)
161
self.assertEqual(transform.find_conflicts(),
162
[('unversioned parent', tinman_id),
163
('missing parent', tinman_id)])
164
transform.adjust_path('name', lion_id, trans_id2)
165
self.assertEqual(transform.find_conflicts(),
166
[('unversioned parent', lion_id),
167
('missing parent', lion_id)])
168
transform.version_file("Courage", lion_id)
169
self.assertEqual(transform.find_conflicts(),
170
[('missing parent', lion_id),
171
('versioning no contents', lion_id)])
172
transform.adjust_path('name2', root, trans_id2)
173
self.assertEqual(transform.find_conflicts(),
174
[('versioning no contents', lion_id)])
175
transform.create_file('Contents, okay?', lion_id)
176
transform.adjust_path('name2', trans_id2, trans_id2)
177
self.assertEqual(transform.find_conflicts(),
178
[('parent loop', trans_id2),
179
('non-directory parent', trans_id2)])
180
transform.adjust_path('name2', root, trans_id2)
181
oz_id = transform.new_directory('oz', root)
182
transform.set_executability(True, oz_id)
183
self.assertEqual(transform.find_conflicts(),
184
[('unversioned executability', oz_id)])
185
transform.version_file('oz-id', oz_id)
186
self.assertEqual(transform.find_conflicts(),
187
[('non-file executability', oz_id)])
188
transform.set_executability(None, oz_id)
189
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
191
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
192
self.assertEqual('contents', file(self.wt.abspath('name')).read())
193
transform2, root = self.get_transform()
194
oz_id = transform2.trans_id_tree_file_id('oz-id')
195
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
196
result = transform2.find_conflicts()
197
fp = FinalPaths(transform2)
198
self.assert_('oz/tip' in transform2._tree_path_ids)
199
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
200
self.assertEqual(len(result), 2)
201
self.assertEqual((result[0][0], result[0][1]),
202
('duplicate', newtip))
203
self.assertEqual((result[1][0], result[1][2]),
204
('duplicate id', newtip))
205
transform2.finalize()
206
transform3 = TreeTransform(self.wt)
207
self.addCleanup(transform3.finalize)
208
oz_id = transform3.trans_id_tree_file_id('oz-id')
209
transform3.delete_contents(oz_id)
210
self.assertEqual(transform3.find_conflicts(),
211
[('missing parent', oz_id)])
212
root_id = transform3.root
213
tip_id = transform3.trans_id_tree_file_id('tip-id')
214
transform3.adjust_path('tip', root_id, tip_id)
217
def test_add_del(self):
218
start, root = self.get_transform()
219
start.new_directory('a', root, 'a')
221
transform, root = self.get_transform()
222
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
223
transform.new_directory('a', root, 'a')
226
def test_unversioning(self):
227
create_tree, root = self.get_transform()
228
parent_id = create_tree.new_directory('parent', root, 'parent-id')
229
create_tree.new_file('child', parent_id, 'child', 'child-id')
231
unversion = TreeTransform(self.wt)
232
self.addCleanup(unversion.finalize)
233
parent = unversion.trans_id_tree_path('parent')
234
unversion.unversion_file(parent)
235
self.assertEqual(unversion.find_conflicts(),
236
[('unversioned parent', parent_id)])
237
file_id = unversion.trans_id_tree_file_id('child-id')
238
unversion.unversion_file(file_id)
241
def test_name_invariants(self):
242
create_tree, root = self.get_transform()
244
root = create_tree.root
245
create_tree.new_file('name1', root, 'hello1', 'name1')
246
create_tree.new_file('name2', root, 'hello2', 'name2')
247
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
248
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
249
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
250
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
253
mangle_tree,root = self.get_transform()
254
root = mangle_tree.root
256
name1 = mangle_tree.trans_id_tree_file_id('name1')
257
name2 = mangle_tree.trans_id_tree_file_id('name2')
258
mangle_tree.adjust_path('name2', root, name1)
259
mangle_tree.adjust_path('name1', root, name2)
261
#tests for deleting parent directories
262
ddir = mangle_tree.trans_id_tree_file_id('ddir')
263
mangle_tree.delete_contents(ddir)
264
dfile = mangle_tree.trans_id_tree_file_id('dfile')
265
mangle_tree.delete_versioned(dfile)
266
mangle_tree.unversion_file(dfile)
267
mfile = mangle_tree.trans_id_tree_file_id('mfile')
268
mangle_tree.adjust_path('mfile', root, mfile)
270
#tests for adding parent directories
271
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
272
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
273
mangle_tree.adjust_path('mfile2', newdir, mfile2)
274
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
275
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
276
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
277
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
279
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
280
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
281
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
282
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
283
self.assertEqual(file(mfile2_path).read(), 'later2')
284
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
285
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
286
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
287
self.assertEqual(file(newfile_path).read(), 'hello3')
288
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
289
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
290
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
292
def test_both_rename(self):
293
create_tree,root = self.get_transform()
294
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
295
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
297
mangle_tree,root = self.get_transform()
298
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
299
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
300
mangle_tree.adjust_path('test', root, selftest)
301
mangle_tree.adjust_path('test_too_much', root, selftest)
302
mangle_tree.set_executability(True, blackbox)
305
def test_both_rename2(self):
306
create_tree,root = self.get_transform()
307
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
308
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
309
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
310
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
313
mangle_tree,root = self.get_transform()
314
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
315
tests = mangle_tree.trans_id_tree_file_id('tests-id')
316
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
317
mangle_tree.adjust_path('selftest', bzrlib, tests)
318
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
319
mangle_tree.set_executability(True, test_too_much)
322
def test_both_rename3(self):
323
create_tree,root = self.get_transform()
324
tests = create_tree.new_directory('tests', root, 'tests-id')
325
create_tree.new_file('test_too_much.py', tests, 'hello1',
328
mangle_tree,root = self.get_transform()
329
tests = mangle_tree.trans_id_tree_file_id('tests-id')
330
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
331
mangle_tree.adjust_path('selftest', root, tests)
332
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
333
mangle_tree.set_executability(True, test_too_much)
336
def test_move_dangling_ie(self):
337
create_tree, root = self.get_transform()
339
root = create_tree.root
340
create_tree.new_file('name1', root, 'hello1', 'name1')
342
delete_contents, root = self.get_transform()
343
file = delete_contents.trans_id_tree_file_id('name1')
344
delete_contents.delete_contents(file)
345
delete_contents.apply()
346
move_id, root = self.get_transform()
347
name1 = move_id.trans_id_tree_file_id('name1')
348
newdir = move_id.new_directory('dir', root, 'newdir')
349
move_id.adjust_path('name2', newdir, name1)
352
def test_replace_dangling_ie(self):
353
create_tree, root = self.get_transform()
355
root = create_tree.root
356
create_tree.new_file('name1', root, 'hello1', 'name1')
358
delete_contents = TreeTransform(self.wt)
359
self.addCleanup(delete_contents.finalize)
360
file = delete_contents.trans_id_tree_file_id('name1')
361
delete_contents.delete_contents(file)
362
delete_contents.apply()
363
delete_contents.finalize()
364
replace = TreeTransform(self.wt)
365
self.addCleanup(replace.finalize)
366
name2 = replace.new_file('name2', root, 'hello2', 'name1')
367
conflicts = replace.find_conflicts()
368
name1 = replace.trans_id_tree_file_id('name1')
369
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
370
resolve_conflicts(replace)
373
def test_symlinks(self):
374
if not has_symlinks():
375
raise TestSkipped('Symlinks are not supported on this platform')
376
transform,root = self.get_transform()
377
oz_id = transform.new_directory('oz', root, 'oz-id')
378
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
380
wiz_id = transform.create_path('wizard2', oz_id)
381
transform.create_symlink('behind_curtain', wiz_id)
382
transform.version_file('wiz-id2', wiz_id)
383
transform.set_executability(True, wiz_id)
384
self.assertEqual(transform.find_conflicts(),
385
[('non-file executability', wiz_id)])
386
transform.set_executability(None, wiz_id)
388
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
389
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
390
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
392
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
396
def get_conflicted(self):
397
create,root = self.get_transform()
398
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
399
oz = create.new_directory('oz', root, 'oz-id')
400
create.new_directory('emeraldcity', oz, 'emerald-id')
402
conflicts,root = self.get_transform()
403
# set up duplicate entry, duplicate id
404
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
406
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
407
oz = conflicts.trans_id_tree_file_id('oz-id')
408
# set up DeletedParent parent conflict
409
conflicts.delete_versioned(oz)
410
emerald = conflicts.trans_id_tree_file_id('emerald-id')
411
# set up MissingParent conflict
412
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
413
conflicts.adjust_path('munchkincity', root, munchkincity)
414
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
416
conflicts.adjust_path('emeraldcity', emerald, emerald)
417
return conflicts, emerald, oz, old_dorothy, new_dorothy
419
def test_conflict_resolution(self):
420
conflicts, emerald, oz, old_dorothy, new_dorothy =\
421
self.get_conflicted()
422
resolve_conflicts(conflicts)
423
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
424
self.assertIs(conflicts.final_file_id(old_dorothy), None)
425
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
426
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
427
self.assertEqual(conflicts.final_parent(emerald), oz)
430
def test_cook_conflicts(self):
431
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
432
raw_conflicts = resolve_conflicts(tt)
433
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
434
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
435
'dorothy', None, 'dorothy-id')
436
self.assertEqual(cooked_conflicts[0], duplicate)
437
duplicate_id = DuplicateID('Unversioned existing file',
438
'dorothy.moved', 'dorothy', None,
440
self.assertEqual(cooked_conflicts[1], duplicate_id)
441
missing_parent = MissingParent('Created directory', 'munchkincity',
443
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
444
self.assertEqual(cooked_conflicts[2], missing_parent)
445
unversioned_parent = UnversionedParent('Versioned directory',
448
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
450
self.assertEqual(cooked_conflicts[3], unversioned_parent)
451
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
452
'oz/emeraldcity', 'emerald-id', 'emerald-id')
453
self.assertEqual(cooked_conflicts[4], deleted_parent)
454
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
455
self.assertEqual(cooked_conflicts[6], parent_loop)
456
self.assertEqual(len(cooked_conflicts), 7)
459
def test_string_conflicts(self):
460
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
461
raw_conflicts = resolve_conflicts(tt)
462
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
464
conflicts_s = [str(c) for c in cooked_conflicts]
465
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
466
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
467
'Moved existing file to '
469
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
470
'Unversioned existing file '
472
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
473
' munchkincity. Created directory.')
474
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
475
' versioned, but has versioned'
476
' children. Versioned directory.')
477
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
478
" is not empty. Not deleting.")
479
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
480
' versioned, but has versioned'
481
' children. Versioned directory.')
482
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
483
' oz/emeraldcity. Cancelled move.')
485
def test_moving_versioned_directories(self):
486
create, root = self.get_transform()
487
kansas = create.new_directory('kansas', root, 'kansas-id')
488
create.new_directory('house', kansas, 'house-id')
489
create.new_directory('oz', root, 'oz-id')
491
cyclone, root = self.get_transform()
492
oz = cyclone.trans_id_tree_file_id('oz-id')
493
house = cyclone.trans_id_tree_file_id('house-id')
494
cyclone.adjust_path('house', oz, house)
497
def test_moving_root(self):
498
create, root = self.get_transform()
499
fun = create.new_directory('fun', root, 'fun-id')
500
create.new_directory('sun', root, 'sun-id')
501
create.new_directory('moon', root, 'moon')
503
transform, root = self.get_transform()
504
transform.adjust_root_path('oldroot', fun)
505
new_root=transform.trans_id_tree_path('')
506
transform.version_file('new-root', new_root)
509
def test_renames(self):
510
create, root = self.get_transform()
511
old = create.new_directory('old-parent', root, 'old-id')
512
intermediate = create.new_directory('intermediate', old, 'im-id')
513
myfile = create.new_file('myfile', intermediate, 'myfile-text',
516
rename, root = self.get_transform()
517
old = rename.trans_id_file_id('old-id')
518
rename.adjust_path('new', root, old)
519
myfile = rename.trans_id_file_id('myfile-id')
520
rename.set_executability(True, myfile)
523
def test_find_interesting(self):
524
create, root = self.get_transform()
526
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
527
create.new_file('uvfile', root, 'othertext')
529
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
530
find_interesting, wt, wt, ['vfile'])
531
self.assertEqual(result, set(['myfile-id']))
533
def test_set_executability_order(self):
534
"""Ensure that executability behaves the same, no matter what order.
536
- create file and set executability simultaneously
537
- create file and set executability afterward
538
- unsetting the executability of a file whose executability has not been
539
declared should throw an exception (this may happen when a
540
merge attempts to create a file with a duplicate ID)
542
transform, root = self.get_transform()
544
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
546
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
547
transform.set_executability(True, sac)
548
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
549
self.assertRaises(KeyError, transform.set_executability, None, uws)
551
self.assertTrue(wt.is_executable('soc'))
552
self.assertTrue(wt.is_executable('sac'))
554
def test_preserve_mode(self):
555
"""File mode is preserved when replacing content"""
556
if sys.platform == 'win32':
557
raise TestSkipped('chmod has no effect on win32')
558
transform, root = self.get_transform()
559
transform.new_file('file1', root, 'contents', 'file1-id', True)
561
self.assertTrue(self.wt.is_executable('file1-id'))
562
transform, root = self.get_transform()
563
file1_id = transform.trans_id_tree_file_id('file1-id')
564
transform.delete_contents(file1_id)
565
transform.create_file('contents2', file1_id)
567
self.assertTrue(self.wt.is_executable('file1-id'))
569
def test__set_mode_stats_correctly(self):
570
"""_set_mode stats to determine file mode."""
571
if sys.platform == 'win32':
572
raise TestSkipped('chmod has no effect on win32')
576
def instrumented_stat(path):
577
stat_paths.append(path)
578
return real_stat(path)
580
transform, root = self.get_transform()
582
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
583
file_id='bar-id-1', executable=False)
586
transform, root = self.get_transform()
587
bar1_id = transform.trans_id_tree_path('bar')
588
bar2_id = transform.trans_id_tree_path('bar2')
590
os.stat = instrumented_stat
591
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
596
bar1_abspath = self.wt.abspath('bar')
597
self.assertEqual([bar1_abspath], stat_paths)
599
def test_iter_changes(self):
600
self.wt.set_root_id('eert_toor')
601
transform, root = self.get_transform()
602
transform.new_file('old', root, 'blah', 'id-1', True)
604
transform, root = self.get_transform()
606
self.assertEqual([], list(transform._iter_changes()))
607
old = transform.trans_id_tree_file_id('id-1')
608
transform.unversion_file(old)
609
self.assertEqual([('id-1', ('old', None), False, (True, False),
610
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
611
(True, True))], list(transform._iter_changes()))
612
transform.new_directory('new', root, 'id-1')
613
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
614
('eert_toor', 'eert_toor'), ('old', 'new'),
615
('file', 'directory'),
616
(True, False))], list(transform._iter_changes()))
620
def test_iter_changes_new(self):
621
self.wt.set_root_id('eert_toor')
622
transform, root = self.get_transform()
623
transform.new_file('old', root, 'blah')
625
transform, root = self.get_transform()
627
old = transform.trans_id_tree_path('old')
628
transform.version_file('id-1', old)
629
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
630
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
631
(False, False))], list(transform._iter_changes()))
635
def test_iter_changes_modifications(self):
636
self.wt.set_root_id('eert_toor')
637
transform, root = self.get_transform()
638
transform.new_file('old', root, 'blah', 'id-1')
639
transform.new_file('new', root, 'blah')
640
transform.new_directory('subdir', root, 'subdir-id')
642
transform, root = self.get_transform()
644
old = transform.trans_id_tree_path('old')
645
subdir = transform.trans_id_tree_file_id('subdir-id')
646
new = transform.trans_id_tree_path('new')
647
self.assertEqual([], list(transform._iter_changes()))
650
transform.delete_contents(old)
651
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
652
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
653
(False, False))], list(transform._iter_changes()))
656
transform.create_file('blah', old)
657
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
658
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
659
(False, False))], list(transform._iter_changes()))
660
transform.cancel_deletion(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_creation(old)
666
# move file_id to a different file
667
self.assertEqual([], list(transform._iter_changes()))
668
transform.unversion_file(old)
669
transform.version_file('id-1', new)
670
transform.adjust_path('old', root, new)
671
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
672
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
673
(False, False))], list(transform._iter_changes()))
674
transform.cancel_versioning(new)
675
transform._removed_id = set()
678
self.assertEqual([], list(transform._iter_changes()))
679
transform.set_executability(True, old)
680
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
681
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
682
(False, True))], list(transform._iter_changes()))
683
transform.set_executability(None, old)
686
self.assertEqual([], list(transform._iter_changes()))
687
transform.adjust_path('new', root, old)
688
transform._new_parent = {}
689
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
690
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
691
(False, False))], list(transform._iter_changes()))
692
transform._new_name = {}
695
self.assertEqual([], list(transform._iter_changes()))
696
transform.adjust_path('new', subdir, old)
697
transform._new_name = {}
698
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
699
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
700
('file', 'file'), (False, False))],
701
list(transform._iter_changes()))
702
transform._new_path = {}
707
def test_iter_changes_modified_bleed(self):
708
self.wt.set_root_id('eert_toor')
709
"""Modified flag should not bleed from one change to another"""
710
# unfortunately, we have no guarantee that file1 (which is modified)
711
# will be applied before file2. And if it's applied after file2, it
712
# obviously can't bleed into file2's change output. But for now, it
714
transform, root = self.get_transform()
715
transform.new_file('file1', root, 'blah', 'id-1')
716
transform.new_file('file2', root, 'blah', 'id-2')
718
transform, root = self.get_transform()
720
transform.delete_contents(transform.trans_id_file_id('id-1'))
721
transform.set_executability(True,
722
transform.trans_id_file_id('id-2'))
723
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
724
('eert_toor', 'eert_toor'), ('file1', u'file1'),
725
('file', None), (False, False)),
726
('id-2', (u'file2', u'file2'), False, (True, True),
727
('eert_toor', 'eert_toor'), ('file2', u'file2'),
728
('file', 'file'), (False, True))],
729
list(transform._iter_changes()))
733
def test_iter_changes_pointless(self):
734
"""Ensure that no-ops are not treated as modifications"""
735
self.wt.set_root_id('eert_toor')
736
transform, root = self.get_transform()
737
transform.new_file('old', root, 'blah', 'id-1')
738
transform.new_directory('subdir', root, 'subdir-id')
740
transform, root = self.get_transform()
742
old = transform.trans_id_tree_path('old')
743
subdir = transform.trans_id_tree_file_id('subdir-id')
744
self.assertEqual([], list(transform._iter_changes()))
745
transform.delete_contents(subdir)
746
transform.create_directory(subdir)
747
transform.set_executability(False, old)
748
transform.unversion_file(old)
749
transform.version_file('id-1', old)
750
transform.adjust_path('old', root, old)
751
self.assertEqual([], list(transform._iter_changes()))
755
class TransformGroup(object):
756
def __init__(self, dirname, root_id):
759
self.wt = BzrDir.create_standalone_workingtree(dirname)
760
self.wt.set_root_id(root_id)
761
self.b = self.wt.branch
762
self.tt = TreeTransform(self.wt)
763
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
766
def conflict_text(tree, merge):
767
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
768
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
771
class TestTransformMerge(TestCaseInTempDir):
772
def test_text_merge(self):
773
root_id = generate_ids.gen_root_id()
774
base = TransformGroup("base", root_id)
775
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
776
base.tt.new_file('b', base.root, 'b1', 'b')
777
base.tt.new_file('c', base.root, 'c', 'c')
778
base.tt.new_file('d', base.root, 'd', 'd')
779
base.tt.new_file('e', base.root, 'e', 'e')
780
base.tt.new_file('f', base.root, 'f', 'f')
781
base.tt.new_directory('g', base.root, 'g')
782
base.tt.new_directory('h', base.root, 'h')
784
other = TransformGroup("other", root_id)
785
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
786
other.tt.new_file('b', other.root, 'b2', 'b')
787
other.tt.new_file('c', other.root, 'c2', 'c')
788
other.tt.new_file('d', other.root, 'd', 'd')
789
other.tt.new_file('e', other.root, 'e2', 'e')
790
other.tt.new_file('f', other.root, 'f', 'f')
791
other.tt.new_file('g', other.root, 'g', 'g')
792
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
793
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
795
this = TransformGroup("this", root_id)
796
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
797
this.tt.new_file('b', this.root, 'b', 'b')
798
this.tt.new_file('c', this.root, 'c', 'c')
799
this.tt.new_file('d', this.root, 'd2', 'd')
800
this.tt.new_file('e', this.root, 'e2', 'e')
801
this.tt.new_file('f', this.root, 'f', 'f')
802
this.tt.new_file('g', this.root, 'g', 'g')
803
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
804
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
806
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
808
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
809
# three-way text conflict
810
self.assertEqual(this.wt.get_file('b').read(),
811
conflict_text('b', 'b2'))
813
self.assertEqual(this.wt.get_file('c').read(), 'c2')
815
self.assertEqual(this.wt.get_file('d').read(), 'd2')
816
# Ambigious clean merge
817
self.assertEqual(this.wt.get_file('e').read(), 'e2')
819
self.assertEqual(this.wt.get_file('f').read(), 'f')
820
# Correct correct results when THIS == OTHER
821
self.assertEqual(this.wt.get_file('g').read(), 'g')
822
# Text conflict when THIS & OTHER are text and BASE is dir
823
self.assertEqual(this.wt.get_file('h').read(),
824
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
825
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
827
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
829
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
830
self.assertEqual(this.wt.get_file('i').read(),
831
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
832
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
834
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
836
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
837
modified = ['a', 'b', 'c', 'h', 'i']
838
merge_modified = this.wt.merge_modified()
839
self.assertSubset(merge_modified, modified)
840
self.assertEqual(len(merge_modified), len(modified))
841
file(this.wt.id2abspath('a'), 'wb').write('booga')
843
merge_modified = this.wt.merge_modified()
844
self.assertSubset(merge_modified, modified)
845
self.assertEqual(len(merge_modified), len(modified))
849
def test_file_merge(self):
850
if not has_symlinks():
851
raise TestSkipped('Symlinks are not supported on this platform')
852
root_id = generate_ids.gen_root_id()
853
base = TransformGroup("BASE", root_id)
854
this = TransformGroup("THIS", root_id)
855
other = TransformGroup("OTHER", root_id)
856
for tg in this, base, other:
857
tg.tt.new_directory('a', tg.root, 'a')
858
tg.tt.new_symlink('b', tg.root, 'b', 'b')
859
tg.tt.new_file('c', tg.root, 'c', 'c')
860
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
861
targets = ((base, 'base-e', 'base-f', None, None),
862
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
863
(other, 'other-e', None, 'other-g', 'other-h'))
864
for tg, e_target, f_target, g_target, h_target in targets:
865
for link, target in (('e', e_target), ('f', f_target),
866
('g', g_target), ('h', h_target)):
867
if target is not None:
868
tg.tt.new_symlink(link, tg.root, target, link)
870
for tg in this, base, other:
872
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
873
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
874
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
875
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
876
for suffix in ('THIS', 'BASE', 'OTHER'):
877
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
878
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
879
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
880
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
881
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
882
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
883
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
884
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
885
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
886
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
887
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
888
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
889
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
890
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
892
def test_filename_merge(self):
893
root_id = generate_ids.gen_root_id()
894
base = TransformGroup("BASE", root_id)
895
this = TransformGroup("THIS", root_id)
896
other = TransformGroup("OTHER", root_id)
897
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
898
for t in [base, this, other]]
899
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
900
for t in [base, this, other]]
901
base.tt.new_directory('c', base_a, 'c')
902
this.tt.new_directory('c1', this_a, 'c')
903
other.tt.new_directory('c', other_b, 'c')
905
base.tt.new_directory('d', base_a, 'd')
906
this.tt.new_directory('d1', this_b, 'd')
907
other.tt.new_directory('d', other_a, 'd')
909
base.tt.new_directory('e', base_a, 'e')
910
this.tt.new_directory('e', this_a, 'e')
911
other.tt.new_directory('e1', other_b, 'e')
913
base.tt.new_directory('f', base_a, 'f')
914
this.tt.new_directory('f1', this_b, 'f')
915
other.tt.new_directory('f1', other_b, 'f')
917
for tg in [this, base, other]:
919
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
920
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
921
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
922
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
923
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
925
def test_filename_merge_conflicts(self):
926
root_id = generate_ids.gen_root_id()
927
base = TransformGroup("BASE", root_id)
928
this = TransformGroup("THIS", root_id)
929
other = TransformGroup("OTHER", root_id)
930
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
931
for t in [base, this, other]]
932
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
933
for t in [base, this, other]]
935
base.tt.new_file('g', base_a, 'g', 'g')
936
other.tt.new_file('g1', other_b, 'g1', 'g')
938
base.tt.new_file('h', base_a, 'h', 'h')
939
this.tt.new_file('h1', this_b, 'h1', 'h')
941
base.tt.new_file('i', base.root, 'i', 'i')
942
other.tt.new_directory('i1', this_b, 'i')
944
for tg in [this, base, other]:
946
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
948
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
949
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
950
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
951
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
952
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
953
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
954
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
957
class TestBuildTree(tests.TestCaseWithTransport):
959
def test_build_tree(self):
960
if not has_symlinks():
961
raise TestSkipped('Test requires symlink support')
963
a = BzrDir.create_standalone_workingtree('a')
965
file('a/foo/bar', 'wb').write('contents')
966
os.symlink('a/foo/bar', 'a/foo/baz')
967
a.add(['foo', 'foo/bar', 'foo/baz'])
968
a.commit('initial commit')
969
b = BzrDir.create_standalone_workingtree('b')
970
build_tree(a.basis_tree(), b)
971
self.assertIs(os.path.isdir('b/foo'), True)
972
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
973
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
975
def test_build_with_references(self):
976
tree = self.make_branch_and_tree('source', format='experimental-reference-dirstate')
977
subtree = self.make_branch_and_tree('source/subtree',
978
format='experimental-reference-dirstate')
979
tree.add_reference(subtree)
980
tree.commit('a revision')
981
tree.branch.create_checkout('target')
982
self.failUnlessExists('target')
983
self.failUnlessExists('target/subtree')
985
def test_file_conflict_handling(self):
986
"""Ensure that when building trees, conflict handling is done"""
987
source = self.make_branch_and_tree('source')
988
target = self.make_branch_and_tree('target')
989
self.build_tree(['source/file', 'target/file'])
990
source.add('file', 'new-file')
991
source.commit('added file')
992
build_tree(source.basis_tree(), target)
993
self.assertEqual([DuplicateEntry('Moved existing file to',
994
'file.moved', 'file', None, 'new-file')],
996
target2 = self.make_branch_and_tree('target2')
997
target_file = file('target2/file', 'wb')
999
source_file = file('source/file', 'rb')
1001
target_file.write(source_file.read())
1006
build_tree(source.basis_tree(), target2)
1007
self.assertEqual([], target2.conflicts())
1009
def test_symlink_conflict_handling(self):
1010
"""Ensure that when building trees, conflict handling is done"""
1011
if not has_symlinks():
1012
raise TestSkipped('Test requires symlink support')
1013
source = self.make_branch_and_tree('source')
1014
os.symlink('foo', 'source/symlink')
1015
source.add('symlink', 'new-symlink')
1016
source.commit('added file')
1017
target = self.make_branch_and_tree('target')
1018
os.symlink('bar', 'target/symlink')
1019
build_tree(source.basis_tree(), target)
1020
self.assertEqual([DuplicateEntry('Moved existing file to',
1021
'symlink.moved', 'symlink', None, 'new-symlink')],
1023
target = self.make_branch_and_tree('target2')
1024
os.symlink('foo', 'target2/symlink')
1025
build_tree(source.basis_tree(), target)
1026
self.assertEqual([], target.conflicts())
1028
def test_directory_conflict_handling(self):
1029
"""Ensure that when building trees, conflict handling is done"""
1030
source = self.make_branch_and_tree('source')
1031
target = self.make_branch_and_tree('target')
1032
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1033
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1034
source.commit('added file')
1035
build_tree(source.basis_tree(), target)
1036
self.assertEqual([], target.conflicts())
1037
self.failUnlessExists('target/dir1/file')
1039
# Ensure contents are merged
1040
target = self.make_branch_and_tree('target2')
1041
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1042
build_tree(source.basis_tree(), target)
1043
self.assertEqual([], target.conflicts())
1044
self.failUnlessExists('target2/dir1/file2')
1045
self.failUnlessExists('target2/dir1/file')
1047
# Ensure new contents are suppressed for existing branches
1048
target = self.make_branch_and_tree('target3')
1049
self.make_branch('target3/dir1')
1050
self.build_tree(['target3/dir1/file2'])
1051
build_tree(source.basis_tree(), target)
1052
self.failIfExists('target3/dir1/file')
1053
self.failUnlessExists('target3/dir1/file2')
1054
self.failUnlessExists('target3/dir1.diverted/file')
1055
self.assertEqual([DuplicateEntry('Diverted to',
1056
'dir1.diverted', 'dir1', 'new-dir1', None)],
1059
target = self.make_branch_and_tree('target4')
1060
self.build_tree(['target4/dir1/'])
1061
self.make_branch('target4/dir1/file')
1062
build_tree(source.basis_tree(), target)
1063
self.failUnlessExists('target4/dir1/file')
1064
self.assertEqual('directory', file_kind('target4/dir1/file'))
1065
self.failUnlessExists('target4/dir1/file.diverted')
1066
self.assertEqual([DuplicateEntry('Diverted to',
1067
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1070
def test_mixed_conflict_handling(self):
1071
"""Ensure that when building trees, conflict handling is done"""
1072
source = self.make_branch_and_tree('source')
1073
target = self.make_branch_and_tree('target')
1074
self.build_tree(['source/name', 'target/name/'])
1075
source.add('name', 'new-name')
1076
source.commit('added file')
1077
build_tree(source.basis_tree(), target)
1078
self.assertEqual([DuplicateEntry('Moved existing file to',
1079
'name.moved', 'name', None, 'new-name')], target.conflicts())
1081
def test_raises_in_populated(self):
1082
source = self.make_branch_and_tree('source')
1083
self.build_tree(['source/name'])
1085
source.commit('added name')
1086
target = self.make_branch_and_tree('target')
1087
self.build_tree(['target/name'])
1089
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1090
build_tree, source.basis_tree(), target)
1093
class MockTransform(object):
1095
def has_named_child(self, by_parent, parent_id, name):
1096
for child_id in by_parent[parent_id]:
1100
elif name == "name.~%s~" % child_id:
1104
class MockEntry(object):
1106
object.__init__(self)
1109
class TestGetBackupName(TestCase):
1110
def test_get_backup_name(self):
1111
tt = MockTransform()
1112
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1113
self.assertEqual(name, 'name.~1~')
1114
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1115
self.assertEqual(name, 'name.~2~')
1116
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1117
self.assertEqual(name, 'name.~1~')
1118
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1119
self.assertEqual(name, 'name.~1~')
1120
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1121
self.assertEqual(name, 'name.~4~')