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='dirstate-with-subtree')
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)
141
self.addCleanup(tree.unlock)
142
self.assertEqual('subtree-revision',
143
tree.inventory['subtree-id'].reference_revision)
145
def test_conflicts(self):
146
transform, root = self.get_transform()
147
trans_id = transform.new_file('name', root, 'contents',
149
self.assertEqual(len(transform.find_conflicts()), 0)
150
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
151
self.assertEqual(transform.find_conflicts(),
152
[('duplicate', trans_id, trans_id2, 'name')])
153
self.assertRaises(MalformedTransform, transform.apply)
154
transform.adjust_path('name', trans_id, trans_id2)
155
self.assertEqual(transform.find_conflicts(),
156
[('non-directory parent', trans_id)])
157
tinman_id = transform.trans_id_tree_path('tinman')
158
transform.adjust_path('name', tinman_id, trans_id2)
159
self.assertEqual(transform.find_conflicts(),
160
[('unversioned parent', tinman_id),
161
('missing parent', tinman_id)])
162
lion_id = transform.create_path('lion', root)
163
self.assertEqual(transform.find_conflicts(),
164
[('unversioned parent', tinman_id),
165
('missing parent', tinman_id)])
166
transform.adjust_path('name', lion_id, trans_id2)
167
self.assertEqual(transform.find_conflicts(),
168
[('unversioned parent', lion_id),
169
('missing parent', lion_id)])
170
transform.version_file("Courage", lion_id)
171
self.assertEqual(transform.find_conflicts(),
172
[('missing parent', lion_id),
173
('versioning no contents', lion_id)])
174
transform.adjust_path('name2', root, trans_id2)
175
self.assertEqual(transform.find_conflicts(),
176
[('versioning no contents', lion_id)])
177
transform.create_file('Contents, okay?', lion_id)
178
transform.adjust_path('name2', trans_id2, trans_id2)
179
self.assertEqual(transform.find_conflicts(),
180
[('parent loop', trans_id2),
181
('non-directory parent', trans_id2)])
182
transform.adjust_path('name2', root, trans_id2)
183
oz_id = transform.new_directory('oz', root)
184
transform.set_executability(True, oz_id)
185
self.assertEqual(transform.find_conflicts(),
186
[('unversioned executability', oz_id)])
187
transform.version_file('oz-id', oz_id)
188
self.assertEqual(transform.find_conflicts(),
189
[('non-file executability', oz_id)])
190
transform.set_executability(None, oz_id)
191
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
193
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
194
self.assertEqual('contents', file(self.wt.abspath('name')).read())
195
transform2, root = self.get_transform()
196
oz_id = transform2.trans_id_tree_file_id('oz-id')
197
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
198
result = transform2.find_conflicts()
199
fp = FinalPaths(transform2)
200
self.assert_('oz/tip' in transform2._tree_path_ids)
201
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
202
self.assertEqual(len(result), 2)
203
self.assertEqual((result[0][0], result[0][1]),
204
('duplicate', newtip))
205
self.assertEqual((result[1][0], result[1][2]),
206
('duplicate id', newtip))
207
transform2.finalize()
208
transform3 = TreeTransform(self.wt)
209
self.addCleanup(transform3.finalize)
210
oz_id = transform3.trans_id_tree_file_id('oz-id')
211
transform3.delete_contents(oz_id)
212
self.assertEqual(transform3.find_conflicts(),
213
[('missing parent', oz_id)])
214
root_id = transform3.root
215
tip_id = transform3.trans_id_tree_file_id('tip-id')
216
transform3.adjust_path('tip', root_id, tip_id)
219
def test_add_del(self):
220
start, root = self.get_transform()
221
start.new_directory('a', root, 'a')
223
transform, root = self.get_transform()
224
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
225
transform.new_directory('a', root, 'a')
228
def test_unversioning(self):
229
create_tree, root = self.get_transform()
230
parent_id = create_tree.new_directory('parent', root, 'parent-id')
231
create_tree.new_file('child', parent_id, 'child', 'child-id')
233
unversion = TreeTransform(self.wt)
234
self.addCleanup(unversion.finalize)
235
parent = unversion.trans_id_tree_path('parent')
236
unversion.unversion_file(parent)
237
self.assertEqual(unversion.find_conflicts(),
238
[('unversioned parent', parent_id)])
239
file_id = unversion.trans_id_tree_file_id('child-id')
240
unversion.unversion_file(file_id)
243
def test_name_invariants(self):
244
create_tree, root = self.get_transform()
246
root = create_tree.root
247
create_tree.new_file('name1', root, 'hello1', 'name1')
248
create_tree.new_file('name2', root, 'hello2', 'name2')
249
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
250
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
251
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
252
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
255
mangle_tree,root = self.get_transform()
256
root = mangle_tree.root
258
name1 = mangle_tree.trans_id_tree_file_id('name1')
259
name2 = mangle_tree.trans_id_tree_file_id('name2')
260
mangle_tree.adjust_path('name2', root, name1)
261
mangle_tree.adjust_path('name1', root, name2)
263
#tests for deleting parent directories
264
ddir = mangle_tree.trans_id_tree_file_id('ddir')
265
mangle_tree.delete_contents(ddir)
266
dfile = mangle_tree.trans_id_tree_file_id('dfile')
267
mangle_tree.delete_versioned(dfile)
268
mangle_tree.unversion_file(dfile)
269
mfile = mangle_tree.trans_id_tree_file_id('mfile')
270
mangle_tree.adjust_path('mfile', root, mfile)
272
#tests for adding parent directories
273
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
274
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
275
mangle_tree.adjust_path('mfile2', newdir, mfile2)
276
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
277
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
278
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
279
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
281
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
282
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
283
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
284
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
285
self.assertEqual(file(mfile2_path).read(), 'later2')
286
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
287
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
288
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
289
self.assertEqual(file(newfile_path).read(), 'hello3')
290
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
291
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
292
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
294
def test_both_rename(self):
295
create_tree,root = self.get_transform()
296
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
297
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
299
mangle_tree,root = self.get_transform()
300
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
301
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
302
mangle_tree.adjust_path('test', root, selftest)
303
mangle_tree.adjust_path('test_too_much', root, selftest)
304
mangle_tree.set_executability(True, blackbox)
307
def test_both_rename2(self):
308
create_tree,root = self.get_transform()
309
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
310
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
311
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
312
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
315
mangle_tree,root = self.get_transform()
316
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
317
tests = mangle_tree.trans_id_tree_file_id('tests-id')
318
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
319
mangle_tree.adjust_path('selftest', bzrlib, tests)
320
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
321
mangle_tree.set_executability(True, test_too_much)
324
def test_both_rename3(self):
325
create_tree,root = self.get_transform()
326
tests = create_tree.new_directory('tests', root, 'tests-id')
327
create_tree.new_file('test_too_much.py', tests, 'hello1',
330
mangle_tree,root = self.get_transform()
331
tests = mangle_tree.trans_id_tree_file_id('tests-id')
332
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
333
mangle_tree.adjust_path('selftest', root, tests)
334
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
335
mangle_tree.set_executability(True, test_too_much)
338
def test_move_dangling_ie(self):
339
create_tree, root = self.get_transform()
341
root = create_tree.root
342
create_tree.new_file('name1', root, 'hello1', 'name1')
344
delete_contents, root = self.get_transform()
345
file = delete_contents.trans_id_tree_file_id('name1')
346
delete_contents.delete_contents(file)
347
delete_contents.apply()
348
move_id, root = self.get_transform()
349
name1 = move_id.trans_id_tree_file_id('name1')
350
newdir = move_id.new_directory('dir', root, 'newdir')
351
move_id.adjust_path('name2', newdir, name1)
354
def test_replace_dangling_ie(self):
355
create_tree, root = self.get_transform()
357
root = create_tree.root
358
create_tree.new_file('name1', root, 'hello1', 'name1')
360
delete_contents = TreeTransform(self.wt)
361
self.addCleanup(delete_contents.finalize)
362
file = delete_contents.trans_id_tree_file_id('name1')
363
delete_contents.delete_contents(file)
364
delete_contents.apply()
365
delete_contents.finalize()
366
replace = TreeTransform(self.wt)
367
self.addCleanup(replace.finalize)
368
name2 = replace.new_file('name2', root, 'hello2', 'name1')
369
conflicts = replace.find_conflicts()
370
name1 = replace.trans_id_tree_file_id('name1')
371
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
372
resolve_conflicts(replace)
375
def test_symlinks(self):
376
if not has_symlinks():
377
raise TestSkipped('Symlinks are not supported on this platform')
378
transform,root = self.get_transform()
379
oz_id = transform.new_directory('oz', root, 'oz-id')
380
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
382
wiz_id = transform.create_path('wizard2', oz_id)
383
transform.create_symlink('behind_curtain', wiz_id)
384
transform.version_file('wiz-id2', wiz_id)
385
transform.set_executability(True, wiz_id)
386
self.assertEqual(transform.find_conflicts(),
387
[('non-file executability', wiz_id)])
388
transform.set_executability(None, wiz_id)
390
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
391
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
392
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
394
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
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')
404
conflicts,root = self.get_transform()
405
# set up duplicate entry, duplicate id
406
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
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')
418
conflicts.adjust_path('emeraldcity', emerald, emerald)
419
return conflicts, emerald, oz, old_dorothy, new_dorothy
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)
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,
442
self.assertEqual(cooked_conflicts[1], duplicate_id)
443
missing_parent = MissingParent('Created directory', 'munchkincity',
445
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
446
self.assertEqual(cooked_conflicts[2], missing_parent)
447
unversioned_parent = UnversionedParent('Versioned directory',
450
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
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)
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)
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 '
471
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
472
'Unversioned existing file '
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.')
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')
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)
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')
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)
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',
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)
525
def test_find_interesting(self):
526
create, root = self.get_transform()
528
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
529
create.new_file('uvfile', root, 'othertext')
531
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
532
find_interesting, wt, wt, ['vfile'])
533
self.assertEqual(result, set(['myfile-id']))
535
def test_set_executability_order(self):
536
"""Ensure that executability behaves the same, no matter what order.
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)
544
transform, root = self.get_transform()
546
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
548
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
549
transform.set_executability(True, sac)
550
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
551
self.assertRaises(KeyError, transform.set_executability, None, uws)
553
self.assertTrue(wt.is_executable('soc'))
554
self.assertTrue(wt.is_executable('sac'))
556
def test_preserve_mode(self):
557
"""File mode is preserved when replacing content"""
558
if sys.platform == 'win32':
559
raise TestSkipped('chmod has no effect on win32')
560
transform, root = self.get_transform()
561
transform.new_file('file1', root, 'contents', 'file1-id', True)
563
self.assertTrue(self.wt.is_executable('file1-id'))
564
transform, root = self.get_transform()
565
file1_id = transform.trans_id_tree_file_id('file1-id')
566
transform.delete_contents(file1_id)
567
transform.create_file('contents2', file1_id)
569
self.assertTrue(self.wt.is_executable('file1-id'))
571
def test__set_mode_stats_correctly(self):
572
"""_set_mode stats to determine file mode."""
573
if sys.platform == 'win32':
574
raise TestSkipped('chmod has no effect on win32')
578
def instrumented_stat(path):
579
stat_paths.append(path)
580
return real_stat(path)
582
transform, root = self.get_transform()
584
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
585
file_id='bar-id-1', executable=False)
588
transform, root = self.get_transform()
589
bar1_id = transform.trans_id_tree_path('bar')
590
bar2_id = transform.trans_id_tree_path('bar2')
592
os.stat = instrumented_stat
593
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
598
bar1_abspath = self.wt.abspath('bar')
599
self.assertEqual([bar1_abspath], stat_paths)
601
def test_iter_changes(self):
602
self.wt.set_root_id('eert_toor')
603
transform, root = self.get_transform()
604
transform.new_file('old', root, 'blah', 'id-1', True)
606
transform, root = self.get_transform()
608
self.assertEqual([], list(transform._iter_changes()))
609
old = transform.trans_id_tree_file_id('id-1')
610
transform.unversion_file(old)
611
self.assertEqual([('id-1', ('old', None), False, (True, False),
612
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
613
(True, True))], list(transform._iter_changes()))
614
transform.new_directory('new', root, 'id-1')
615
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
616
('eert_toor', 'eert_toor'), ('old', 'new'),
617
('file', 'directory'),
618
(True, False))], list(transform._iter_changes()))
622
def test_iter_changes_new(self):
623
self.wt.set_root_id('eert_toor')
624
transform, root = self.get_transform()
625
transform.new_file('old', root, 'blah')
627
transform, root = self.get_transform()
629
old = transform.trans_id_tree_path('old')
630
transform.version_file('id-1', old)
631
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
632
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
633
(False, False))], list(transform._iter_changes()))
637
def test_iter_changes_modifications(self):
638
self.wt.set_root_id('eert_toor')
639
transform, root = self.get_transform()
640
transform.new_file('old', root, 'blah', 'id-1')
641
transform.new_file('new', root, 'blah')
642
transform.new_directory('subdir', root, 'subdir-id')
644
transform, root = self.get_transform()
646
old = transform.trans_id_tree_path('old')
647
subdir = transform.trans_id_tree_file_id('subdir-id')
648
new = transform.trans_id_tree_path('new')
649
self.assertEqual([], list(transform._iter_changes()))
652
transform.delete_contents(old)
653
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
654
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
655
(False, False))], list(transform._iter_changes()))
658
transform.create_file('blah', old)
659
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
660
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
661
(False, False))], list(transform._iter_changes()))
662
transform.cancel_deletion(old)
663
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
664
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
665
(False, False))], list(transform._iter_changes()))
666
transform.cancel_creation(old)
668
# move file_id to a different file
669
self.assertEqual([], list(transform._iter_changes()))
670
transform.unversion_file(old)
671
transform.version_file('id-1', new)
672
transform.adjust_path('old', root, new)
673
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
674
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
675
(False, False))], list(transform._iter_changes()))
676
transform.cancel_versioning(new)
677
transform._removed_id = set()
680
self.assertEqual([], list(transform._iter_changes()))
681
transform.set_executability(True, old)
682
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
683
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
684
(False, True))], list(transform._iter_changes()))
685
transform.set_executability(None, old)
688
self.assertEqual([], list(transform._iter_changes()))
689
transform.adjust_path('new', root, old)
690
transform._new_parent = {}
691
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
692
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
693
(False, False))], list(transform._iter_changes()))
694
transform._new_name = {}
697
self.assertEqual([], list(transform._iter_changes()))
698
transform.adjust_path('new', subdir, old)
699
transform._new_name = {}
700
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
701
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
702
('file', 'file'), (False, False))],
703
list(transform._iter_changes()))
704
transform._new_path = {}
709
def test_iter_changes_modified_bleed(self):
710
self.wt.set_root_id('eert_toor')
711
"""Modified flag should not bleed from one change to another"""
712
# unfortunately, we have no guarantee that file1 (which is modified)
713
# will be applied before file2. And if it's applied after file2, it
714
# obviously can't bleed into file2's change output. But for now, it
716
transform, root = self.get_transform()
717
transform.new_file('file1', root, 'blah', 'id-1')
718
transform.new_file('file2', root, 'blah', 'id-2')
720
transform, root = self.get_transform()
722
transform.delete_contents(transform.trans_id_file_id('id-1'))
723
transform.set_executability(True,
724
transform.trans_id_file_id('id-2'))
725
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
726
('eert_toor', 'eert_toor'), ('file1', u'file1'),
727
('file', None), (False, False)),
728
('id-2', (u'file2', u'file2'), False, (True, True),
729
('eert_toor', 'eert_toor'), ('file2', u'file2'),
730
('file', 'file'), (False, True))],
731
list(transform._iter_changes()))
735
def test_iter_changes_pointless(self):
736
"""Ensure that no-ops are not treated as modifications"""
737
self.wt.set_root_id('eert_toor')
738
transform, root = self.get_transform()
739
transform.new_file('old', root, 'blah', 'id-1')
740
transform.new_directory('subdir', root, 'subdir-id')
742
transform, root = self.get_transform()
744
old = transform.trans_id_tree_path('old')
745
subdir = transform.trans_id_tree_file_id('subdir-id')
746
self.assertEqual([], list(transform._iter_changes()))
747
transform.delete_contents(subdir)
748
transform.create_directory(subdir)
749
transform.set_executability(False, old)
750
transform.unversion_file(old)
751
transform.version_file('id-1', old)
752
transform.adjust_path('old', root, old)
753
self.assertEqual([], list(transform._iter_changes()))
757
class TransformGroup(object):
758
def __init__(self, dirname, root_id):
761
self.wt = BzrDir.create_standalone_workingtree(dirname)
762
self.wt.set_root_id(root_id)
763
self.b = self.wt.branch
764
self.tt = TreeTransform(self.wt)
765
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
768
def conflict_text(tree, merge):
769
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
770
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
773
class TestTransformMerge(TestCaseInTempDir):
774
def test_text_merge(self):
775
root_id = generate_ids.gen_root_id()
776
base = TransformGroup("base", root_id)
777
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
778
base.tt.new_file('b', base.root, 'b1', 'b')
779
base.tt.new_file('c', base.root, 'c', 'c')
780
base.tt.new_file('d', base.root, 'd', 'd')
781
base.tt.new_file('e', base.root, 'e', 'e')
782
base.tt.new_file('f', base.root, 'f', 'f')
783
base.tt.new_directory('g', base.root, 'g')
784
base.tt.new_directory('h', base.root, 'h')
786
other = TransformGroup("other", root_id)
787
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
788
other.tt.new_file('b', other.root, 'b2', 'b')
789
other.tt.new_file('c', other.root, 'c2', 'c')
790
other.tt.new_file('d', other.root, 'd', 'd')
791
other.tt.new_file('e', other.root, 'e2', 'e')
792
other.tt.new_file('f', other.root, 'f', 'f')
793
other.tt.new_file('g', other.root, 'g', 'g')
794
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
795
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
797
this = TransformGroup("this", root_id)
798
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
799
this.tt.new_file('b', this.root, 'b', 'b')
800
this.tt.new_file('c', this.root, 'c', 'c')
801
this.tt.new_file('d', this.root, 'd2', 'd')
802
this.tt.new_file('e', this.root, 'e2', 'e')
803
this.tt.new_file('f', this.root, 'f', 'f')
804
this.tt.new_file('g', this.root, 'g', 'g')
805
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
806
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
808
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
810
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
811
# three-way text conflict
812
self.assertEqual(this.wt.get_file('b').read(),
813
conflict_text('b', 'b2'))
815
self.assertEqual(this.wt.get_file('c').read(), 'c2')
817
self.assertEqual(this.wt.get_file('d').read(), 'd2')
818
# Ambigious clean merge
819
self.assertEqual(this.wt.get_file('e').read(), 'e2')
821
self.assertEqual(this.wt.get_file('f').read(), 'f')
822
# Correct correct results when THIS == OTHER
823
self.assertEqual(this.wt.get_file('g').read(), 'g')
824
# Text conflict when THIS & OTHER are text and BASE is dir
825
self.assertEqual(this.wt.get_file('h').read(),
826
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
827
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
829
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
831
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
832
self.assertEqual(this.wt.get_file('i').read(),
833
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
834
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
836
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
838
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
839
modified = ['a', 'b', 'c', 'h', 'i']
840
merge_modified = this.wt.merge_modified()
841
self.assertSubset(merge_modified, modified)
842
self.assertEqual(len(merge_modified), len(modified))
843
file(this.wt.id2abspath('a'), 'wb').write('booga')
845
merge_modified = this.wt.merge_modified()
846
self.assertSubset(merge_modified, modified)
847
self.assertEqual(len(merge_modified), len(modified))
851
def test_file_merge(self):
852
if not has_symlinks():
853
raise TestSkipped('Symlinks are not supported on this platform')
854
root_id = generate_ids.gen_root_id()
855
base = TransformGroup("BASE", root_id)
856
this = TransformGroup("THIS", root_id)
857
other = TransformGroup("OTHER", root_id)
858
for tg in this, base, other:
859
tg.tt.new_directory('a', tg.root, 'a')
860
tg.tt.new_symlink('b', tg.root, 'b', 'b')
861
tg.tt.new_file('c', tg.root, 'c', 'c')
862
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
863
targets = ((base, 'base-e', 'base-f', None, None),
864
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
865
(other, 'other-e', None, 'other-g', 'other-h'))
866
for tg, e_target, f_target, g_target, h_target in targets:
867
for link, target in (('e', e_target), ('f', f_target),
868
('g', g_target), ('h', h_target)):
869
if target is not None:
870
tg.tt.new_symlink(link, tg.root, target, link)
872
for tg in this, base, other:
874
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
875
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
876
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
877
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
878
for suffix in ('THIS', 'BASE', 'OTHER'):
879
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
880
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
881
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
882
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
883
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
884
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
885
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
886
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
887
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
888
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
889
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
890
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
891
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
892
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
894
def test_filename_merge(self):
895
root_id = generate_ids.gen_root_id()
896
base = TransformGroup("BASE", root_id)
897
this = TransformGroup("THIS", root_id)
898
other = TransformGroup("OTHER", root_id)
899
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
900
for t in [base, this, other]]
901
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
902
for t in [base, this, other]]
903
base.tt.new_directory('c', base_a, 'c')
904
this.tt.new_directory('c1', this_a, 'c')
905
other.tt.new_directory('c', other_b, 'c')
907
base.tt.new_directory('d', base_a, 'd')
908
this.tt.new_directory('d1', this_b, 'd')
909
other.tt.new_directory('d', other_a, 'd')
911
base.tt.new_directory('e', base_a, 'e')
912
this.tt.new_directory('e', this_a, 'e')
913
other.tt.new_directory('e1', other_b, 'e')
915
base.tt.new_directory('f', base_a, 'f')
916
this.tt.new_directory('f1', this_b, 'f')
917
other.tt.new_directory('f1', other_b, 'f')
919
for tg in [this, base, other]:
921
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
922
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
923
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
924
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
925
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
927
def test_filename_merge_conflicts(self):
928
root_id = generate_ids.gen_root_id()
929
base = TransformGroup("BASE", root_id)
930
this = TransformGroup("THIS", root_id)
931
other = TransformGroup("OTHER", root_id)
932
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
933
for t in [base, this, other]]
934
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
935
for t in [base, this, other]]
937
base.tt.new_file('g', base_a, 'g', 'g')
938
other.tt.new_file('g1', other_b, 'g1', 'g')
940
base.tt.new_file('h', base_a, 'h', 'h')
941
this.tt.new_file('h1', this_b, 'h1', 'h')
943
base.tt.new_file('i', base.root, 'i', 'i')
944
other.tt.new_directory('i1', this_b, 'i')
946
for tg in [this, base, other]:
948
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
950
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
951
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
952
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
953
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
954
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
955
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
956
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
959
class TestBuildTree(tests.TestCaseWithTransport):
961
def test_build_tree(self):
962
if not has_symlinks():
963
raise TestSkipped('Test requires symlink support')
965
a = BzrDir.create_standalone_workingtree('a')
967
file('a/foo/bar', 'wb').write('contents')
968
os.symlink('a/foo/bar', 'a/foo/baz')
969
a.add(['foo', 'foo/bar', 'foo/baz'])
970
a.commit('initial commit')
971
b = BzrDir.create_standalone_workingtree('b')
972
basis = a.basis_tree()
974
self.addCleanup(basis.unlock)
976
self.assertIs(os.path.isdir('b/foo'), True)
977
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
978
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
980
def test_build_with_references(self):
981
tree = self.make_branch_and_tree('source',
982
format='dirstate-with-subtree')
983
subtree = self.make_branch_and_tree('source/subtree',
984
format='dirstate-with-subtree')
985
tree.add_reference(subtree)
986
tree.commit('a revision')
987
tree.branch.create_checkout('target')
988
self.failUnlessExists('target')
989
self.failUnlessExists('target/subtree')
991
def test_file_conflict_handling(self):
992
"""Ensure that when building trees, conflict handling is done"""
993
source = self.make_branch_and_tree('source')
994
target = self.make_branch_and_tree('target')
995
self.build_tree(['source/file', 'target/file'])
996
source.add('file', 'new-file')
997
source.commit('added file')
998
build_tree(source.basis_tree(), target)
999
self.assertEqual([DuplicateEntry('Moved existing file to',
1000
'file.moved', 'file', None, 'new-file')],
1002
target2 = self.make_branch_and_tree('target2')
1003
target_file = file('target2/file', 'wb')
1005
source_file = file('source/file', 'rb')
1007
target_file.write(source_file.read())
1012
build_tree(source.basis_tree(), target2)
1013
self.assertEqual([], target2.conflicts())
1015
def test_symlink_conflict_handling(self):
1016
"""Ensure that when building trees, conflict handling is done"""
1017
if not has_symlinks():
1018
raise TestSkipped('Test requires symlink support')
1019
source = self.make_branch_and_tree('source')
1020
os.symlink('foo', 'source/symlink')
1021
source.add('symlink', 'new-symlink')
1022
source.commit('added file')
1023
target = self.make_branch_and_tree('target')
1024
os.symlink('bar', 'target/symlink')
1025
build_tree(source.basis_tree(), target)
1026
self.assertEqual([DuplicateEntry('Moved existing file to',
1027
'symlink.moved', 'symlink', None, 'new-symlink')],
1029
target = self.make_branch_and_tree('target2')
1030
os.symlink('foo', 'target2/symlink')
1031
build_tree(source.basis_tree(), target)
1032
self.assertEqual([], target.conflicts())
1034
def test_directory_conflict_handling(self):
1035
"""Ensure that when building trees, conflict handling is done"""
1036
source = self.make_branch_and_tree('source')
1037
target = self.make_branch_and_tree('target')
1038
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1039
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1040
source.commit('added file')
1041
build_tree(source.basis_tree(), target)
1042
self.assertEqual([], target.conflicts())
1043
self.failUnlessExists('target/dir1/file')
1045
# Ensure contents are merged
1046
target = self.make_branch_and_tree('target2')
1047
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1048
build_tree(source.basis_tree(), target)
1049
self.assertEqual([], target.conflicts())
1050
self.failUnlessExists('target2/dir1/file2')
1051
self.failUnlessExists('target2/dir1/file')
1053
# Ensure new contents are suppressed for existing branches
1054
target = self.make_branch_and_tree('target3')
1055
self.make_branch('target3/dir1')
1056
self.build_tree(['target3/dir1/file2'])
1057
build_tree(source.basis_tree(), target)
1058
self.failIfExists('target3/dir1/file')
1059
self.failUnlessExists('target3/dir1/file2')
1060
self.failUnlessExists('target3/dir1.diverted/file')
1061
self.assertEqual([DuplicateEntry('Diverted to',
1062
'dir1.diverted', 'dir1', 'new-dir1', None)],
1065
target = self.make_branch_and_tree('target4')
1066
self.build_tree(['target4/dir1/'])
1067
self.make_branch('target4/dir1/file')
1068
build_tree(source.basis_tree(), target)
1069
self.failUnlessExists('target4/dir1/file')
1070
self.assertEqual('directory', file_kind('target4/dir1/file'))
1071
self.failUnlessExists('target4/dir1/file.diverted')
1072
self.assertEqual([DuplicateEntry('Diverted to',
1073
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1076
def test_mixed_conflict_handling(self):
1077
"""Ensure that when building trees, conflict handling is done"""
1078
source = self.make_branch_and_tree('source')
1079
target = self.make_branch_and_tree('target')
1080
self.build_tree(['source/name', 'target/name/'])
1081
source.add('name', 'new-name')
1082
source.commit('added file')
1083
build_tree(source.basis_tree(), target)
1084
self.assertEqual([DuplicateEntry('Moved existing file to',
1085
'name.moved', 'name', None, 'new-name')], target.conflicts())
1087
def test_raises_in_populated(self):
1088
source = self.make_branch_and_tree('source')
1089
self.build_tree(['source/name'])
1091
source.commit('added name')
1092
target = self.make_branch_and_tree('target')
1093
self.build_tree(['target/name'])
1095
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1096
build_tree, source.basis_tree(), target)
1099
class MockTransform(object):
1101
def has_named_child(self, by_parent, parent_id, name):
1102
for child_id in by_parent[parent_id]:
1106
elif name == "name.~%s~" % child_id:
1110
class MockEntry(object):
1112
object.__init__(self)
1115
class TestGetBackupName(TestCase):
1116
def test_get_backup_name(self):
1117
tt = MockTransform()
1118
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1119
self.assertEqual(name, 'name.~1~')
1120
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1121
self.assertEqual(name, 'name.~2~')
1122
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1123
self.assertEqual(name, 'name.~1~')
1124
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1125
self.assertEqual(name, 'name.~1~')
1126
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1127
self.assertEqual(name, 'name.~4~')