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
ExistingPendingDeletion, ImmortalLimbo,
35
ImmortalPendingDeletion, LockError)
36
from bzrlib.osutils import file_kind, pathjoin
37
from bzrlib.merge import Merge3Merger
38
from bzrlib.tests import (
44
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
45
resolve_conflicts, cook_conflicts,
46
find_interesting, build_tree, get_backup_name,
47
change_entry, _FileMover)
50
class TestTreeTransform(tests.TestCaseWithTransport):
53
super(TestTreeTransform, self).setUp()
54
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
57
def get_transform(self):
58
transform = TreeTransform(self.wt)
59
#self.addCleanup(transform.finalize)
60
return transform, transform.root
62
def test_existing_limbo(self):
63
transform, root = self.get_transform()
64
limbo_name = transform._limbodir
65
deletion_path = transform._deletiondir
66
os.mkdir(pathjoin(limbo_name, 'hehe'))
67
self.assertRaises(ImmortalLimbo, transform.apply)
68
self.assertRaises(LockError, self.wt.unlock)
69
self.assertRaises(ExistingLimbo, self.get_transform)
70
self.assertRaises(LockError, self.wt.unlock)
71
os.rmdir(pathjoin(limbo_name, 'hehe'))
73
os.rmdir(deletion_path)
74
transform, root = self.get_transform()
77
def test_existing_pending_deletion(self):
78
transform, root = self.get_transform()
79
deletion_path = self._limbodir = urlutils.local_path_from_url(
80
transform._tree._control_files.controlfilename('pending-deletion'))
81
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
82
self.assertRaises(ImmortalPendingDeletion, transform.apply)
83
self.assertRaises(LockError, self.wt.unlock)
84
self.assertRaises(ExistingPendingDeletion, self.get_transform)
87
transform, root = self.get_transform()
88
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
89
imaginary_id = transform.trans_id_tree_path('imaginary')
90
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
91
self.assertEqual(imaginary_id, imaginary_id2)
92
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
93
self.assertEqual(transform.final_kind(root), 'directory')
94
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
95
trans_id = transform.create_path('name', root)
96
self.assertIs(transform.final_file_id(trans_id), None)
97
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
98
transform.create_file('contents', trans_id)
99
transform.set_executability(True, trans_id)
100
transform.version_file('my_pretties', trans_id)
101
self.assertRaises(DuplicateKey, transform.version_file,
102
'my_pretties', trans_id)
103
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
104
self.assertEqual(transform.final_parent(trans_id), root)
105
self.assertIs(transform.final_parent(root), ROOT_PARENT)
106
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
107
oz_id = transform.create_path('oz', root)
108
transform.create_directory(oz_id)
109
transform.version_file('ozzie', oz_id)
110
trans_id2 = transform.create_path('name2', root)
111
transform.create_file('contents', trans_id2)
112
transform.set_executability(False, trans_id2)
113
transform.version_file('my_pretties2', trans_id2)
114
modified_paths = transform.apply().modified_paths
115
self.assertEqual('contents', self.wt.get_file_byname('name').read())
116
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
117
self.assertIs(self.wt.is_executable('my_pretties'), True)
118
self.assertIs(self.wt.is_executable('my_pretties2'), False)
119
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
120
self.assertEqual(len(modified_paths), 3)
121
tree_mod_paths = [self.wt.id2abspath(f) for f in
122
('ozzie', 'my_pretties', 'my_pretties2')]
123
self.assertSubset(tree_mod_paths, modified_paths)
124
# is it safe to finalize repeatedly?
128
def test_convenience(self):
129
transform, root = self.get_transform()
130
trans_id = transform.new_file('name', root, 'contents',
132
oz = transform.new_directory('oz', root, 'oz-id')
133
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
134
toto = transform.new_file('toto', dorothy, 'toto-contents',
137
self.assertEqual(len(transform.find_conflicts()), 0)
139
self.assertRaises(ReusingTransform, transform.find_conflicts)
140
self.assertEqual('contents', file(self.wt.abspath('name')).read())
141
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
142
self.assertIs(self.wt.is_executable('my_pretties'), True)
143
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
144
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
145
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
147
self.assertEqual('toto-contents',
148
self.wt.get_file_byname('oz/dorothy/toto').read())
149
self.assertIs(self.wt.is_executable('toto-id'), False)
151
def test_tree_reference(self):
152
transform, root = self.get_transform()
153
tree = transform._tree
154
trans_id = transform.new_directory('reference', root, 'subtree-id')
155
transform.set_tree_reference('subtree-revision', trans_id)
158
self.addCleanup(tree.unlock)
159
self.assertEqual('subtree-revision',
160
tree.inventory['subtree-id'].reference_revision)
162
def test_conflicts(self):
163
transform, root = self.get_transform()
164
trans_id = transform.new_file('name', root, 'contents',
166
self.assertEqual(len(transform.find_conflicts()), 0)
167
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
168
self.assertEqual(transform.find_conflicts(),
169
[('duplicate', trans_id, trans_id2, 'name')])
170
self.assertRaises(MalformedTransform, transform.apply)
171
transform.adjust_path('name', trans_id, trans_id2)
172
self.assertEqual(transform.find_conflicts(),
173
[('non-directory parent', trans_id)])
174
tinman_id = transform.trans_id_tree_path('tinman')
175
transform.adjust_path('name', tinman_id, trans_id2)
176
self.assertEqual(transform.find_conflicts(),
177
[('unversioned parent', tinman_id),
178
('missing parent', tinman_id)])
179
lion_id = transform.create_path('lion', root)
180
self.assertEqual(transform.find_conflicts(),
181
[('unversioned parent', tinman_id),
182
('missing parent', tinman_id)])
183
transform.adjust_path('name', lion_id, trans_id2)
184
self.assertEqual(transform.find_conflicts(),
185
[('unversioned parent', lion_id),
186
('missing parent', lion_id)])
187
transform.version_file("Courage", lion_id)
188
self.assertEqual(transform.find_conflicts(),
189
[('missing parent', lion_id),
190
('versioning no contents', lion_id)])
191
transform.adjust_path('name2', root, trans_id2)
192
self.assertEqual(transform.find_conflicts(),
193
[('versioning no contents', lion_id)])
194
transform.create_file('Contents, okay?', lion_id)
195
transform.adjust_path('name2', trans_id2, trans_id2)
196
self.assertEqual(transform.find_conflicts(),
197
[('parent loop', trans_id2),
198
('non-directory parent', trans_id2)])
199
transform.adjust_path('name2', root, trans_id2)
200
oz_id = transform.new_directory('oz', root)
201
transform.set_executability(True, oz_id)
202
self.assertEqual(transform.find_conflicts(),
203
[('unversioned executability', oz_id)])
204
transform.version_file('oz-id', oz_id)
205
self.assertEqual(transform.find_conflicts(),
206
[('non-file executability', oz_id)])
207
transform.set_executability(None, oz_id)
208
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
210
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
211
self.assertEqual('contents', file(self.wt.abspath('name')).read())
212
transform2, root = self.get_transform()
213
oz_id = transform2.trans_id_tree_file_id('oz-id')
214
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
215
result = transform2.find_conflicts()
216
fp = FinalPaths(transform2)
217
self.assert_('oz/tip' in transform2._tree_path_ids)
218
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
219
self.assertEqual(len(result), 2)
220
self.assertEqual((result[0][0], result[0][1]),
221
('duplicate', newtip))
222
self.assertEqual((result[1][0], result[1][2]),
223
('duplicate id', newtip))
224
transform2.finalize()
225
transform3 = TreeTransform(self.wt)
226
self.addCleanup(transform3.finalize)
227
oz_id = transform3.trans_id_tree_file_id('oz-id')
228
transform3.delete_contents(oz_id)
229
self.assertEqual(transform3.find_conflicts(),
230
[('missing parent', oz_id)])
231
root_id = transform3.root
232
tip_id = transform3.trans_id_tree_file_id('tip-id')
233
transform3.adjust_path('tip', root_id, tip_id)
236
def test_add_del(self):
237
start, root = self.get_transform()
238
start.new_directory('a', root, 'a')
240
transform, root = self.get_transform()
241
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
242
transform.new_directory('a', root, 'a')
245
def test_unversioning(self):
246
create_tree, root = self.get_transform()
247
parent_id = create_tree.new_directory('parent', root, 'parent-id')
248
create_tree.new_file('child', parent_id, 'child', 'child-id')
250
unversion = TreeTransform(self.wt)
251
self.addCleanup(unversion.finalize)
252
parent = unversion.trans_id_tree_path('parent')
253
unversion.unversion_file(parent)
254
self.assertEqual(unversion.find_conflicts(),
255
[('unversioned parent', parent_id)])
256
file_id = unversion.trans_id_tree_file_id('child-id')
257
unversion.unversion_file(file_id)
260
def test_name_invariants(self):
261
create_tree, root = self.get_transform()
263
root = create_tree.root
264
create_tree.new_file('name1', root, 'hello1', 'name1')
265
create_tree.new_file('name2', root, 'hello2', 'name2')
266
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
267
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
268
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
269
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
272
mangle_tree,root = self.get_transform()
273
root = mangle_tree.root
275
name1 = mangle_tree.trans_id_tree_file_id('name1')
276
name2 = mangle_tree.trans_id_tree_file_id('name2')
277
mangle_tree.adjust_path('name2', root, name1)
278
mangle_tree.adjust_path('name1', root, name2)
280
#tests for deleting parent directories
281
ddir = mangle_tree.trans_id_tree_file_id('ddir')
282
mangle_tree.delete_contents(ddir)
283
dfile = mangle_tree.trans_id_tree_file_id('dfile')
284
mangle_tree.delete_versioned(dfile)
285
mangle_tree.unversion_file(dfile)
286
mfile = mangle_tree.trans_id_tree_file_id('mfile')
287
mangle_tree.adjust_path('mfile', root, mfile)
289
#tests for adding parent directories
290
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
291
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
292
mangle_tree.adjust_path('mfile2', newdir, mfile2)
293
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
294
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
295
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
296
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
298
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
299
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
300
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
301
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
302
self.assertEqual(file(mfile2_path).read(), 'later2')
303
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
304
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
305
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
306
self.assertEqual(file(newfile_path).read(), 'hello3')
307
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
308
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
309
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
311
def test_both_rename(self):
312
create_tree,root = self.get_transform()
313
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
314
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
316
mangle_tree,root = self.get_transform()
317
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
318
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
319
mangle_tree.adjust_path('test', root, selftest)
320
mangle_tree.adjust_path('test_too_much', root, selftest)
321
mangle_tree.set_executability(True, blackbox)
324
def test_both_rename2(self):
325
create_tree,root = self.get_transform()
326
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
327
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
328
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
329
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
332
mangle_tree,root = self.get_transform()
333
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
334
tests = mangle_tree.trans_id_tree_file_id('tests-id')
335
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
336
mangle_tree.adjust_path('selftest', bzrlib, tests)
337
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
338
mangle_tree.set_executability(True, test_too_much)
341
def test_both_rename3(self):
342
create_tree,root = self.get_transform()
343
tests = create_tree.new_directory('tests', root, 'tests-id')
344
create_tree.new_file('test_too_much.py', tests, 'hello1',
347
mangle_tree,root = self.get_transform()
348
tests = mangle_tree.trans_id_tree_file_id('tests-id')
349
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
350
mangle_tree.adjust_path('selftest', root, tests)
351
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
352
mangle_tree.set_executability(True, test_too_much)
355
def test_move_dangling_ie(self):
356
create_tree, root = self.get_transform()
358
root = create_tree.root
359
create_tree.new_file('name1', root, 'hello1', 'name1')
361
delete_contents, root = self.get_transform()
362
file = delete_contents.trans_id_tree_file_id('name1')
363
delete_contents.delete_contents(file)
364
delete_contents.apply()
365
move_id, root = self.get_transform()
366
name1 = move_id.trans_id_tree_file_id('name1')
367
newdir = move_id.new_directory('dir', root, 'newdir')
368
move_id.adjust_path('name2', newdir, name1)
371
def test_replace_dangling_ie(self):
372
create_tree, root = self.get_transform()
374
root = create_tree.root
375
create_tree.new_file('name1', root, 'hello1', 'name1')
377
delete_contents = TreeTransform(self.wt)
378
self.addCleanup(delete_contents.finalize)
379
file = delete_contents.trans_id_tree_file_id('name1')
380
delete_contents.delete_contents(file)
381
delete_contents.apply()
382
delete_contents.finalize()
383
replace = TreeTransform(self.wt)
384
self.addCleanup(replace.finalize)
385
name2 = replace.new_file('name2', root, 'hello2', 'name1')
386
conflicts = replace.find_conflicts()
387
name1 = replace.trans_id_tree_file_id('name1')
388
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
389
resolve_conflicts(replace)
392
def test_symlinks(self):
393
self.requireFeature(SymlinkFeature)
394
transform,root = self.get_transform()
395
oz_id = transform.new_directory('oz', root, 'oz-id')
396
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
398
wiz_id = transform.create_path('wizard2', oz_id)
399
transform.create_symlink('behind_curtain', wiz_id)
400
transform.version_file('wiz-id2', wiz_id)
401
transform.set_executability(True, wiz_id)
402
self.assertEqual(transform.find_conflicts(),
403
[('non-file executability', wiz_id)])
404
transform.set_executability(None, wiz_id)
406
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
407
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
408
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
410
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
413
def get_conflicted(self):
414
create,root = self.get_transform()
415
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
416
oz = create.new_directory('oz', root, 'oz-id')
417
create.new_directory('emeraldcity', oz, 'emerald-id')
419
conflicts,root = self.get_transform()
420
# set up duplicate entry, duplicate id
421
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
423
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
424
oz = conflicts.trans_id_tree_file_id('oz-id')
425
# set up DeletedParent parent conflict
426
conflicts.delete_versioned(oz)
427
emerald = conflicts.trans_id_tree_file_id('emerald-id')
428
# set up MissingParent conflict
429
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
430
conflicts.adjust_path('munchkincity', root, munchkincity)
431
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
433
conflicts.adjust_path('emeraldcity', emerald, emerald)
434
return conflicts, emerald, oz, old_dorothy, new_dorothy
436
def test_conflict_resolution(self):
437
conflicts, emerald, oz, old_dorothy, new_dorothy =\
438
self.get_conflicted()
439
resolve_conflicts(conflicts)
440
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
441
self.assertIs(conflicts.final_file_id(old_dorothy), None)
442
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
443
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
444
self.assertEqual(conflicts.final_parent(emerald), oz)
447
def test_cook_conflicts(self):
448
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
449
raw_conflicts = resolve_conflicts(tt)
450
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
451
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
452
'dorothy', None, 'dorothy-id')
453
self.assertEqual(cooked_conflicts[0], duplicate)
454
duplicate_id = DuplicateID('Unversioned existing file',
455
'dorothy.moved', 'dorothy', None,
457
self.assertEqual(cooked_conflicts[1], duplicate_id)
458
missing_parent = MissingParent('Created directory', 'munchkincity',
460
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
461
self.assertEqual(cooked_conflicts[2], missing_parent)
462
unversioned_parent = UnversionedParent('Versioned directory',
465
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
467
self.assertEqual(cooked_conflicts[3], unversioned_parent)
468
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
469
'oz/emeraldcity', 'emerald-id', 'emerald-id')
470
self.assertEqual(cooked_conflicts[4], deleted_parent)
471
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
472
self.assertEqual(cooked_conflicts[6], parent_loop)
473
self.assertEqual(len(cooked_conflicts), 7)
476
def test_string_conflicts(self):
477
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
478
raw_conflicts = resolve_conflicts(tt)
479
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
481
conflicts_s = [str(c) for c in cooked_conflicts]
482
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
483
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
484
'Moved existing file to '
486
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
487
'Unversioned existing file '
489
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
490
' munchkincity. Created directory.')
491
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
492
' versioned, but has versioned'
493
' children. Versioned directory.')
494
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
495
" is not empty. Not deleting.")
496
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
497
' versioned, but has versioned'
498
' children. Versioned directory.')
499
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
500
' oz/emeraldcity. Cancelled move.')
502
def test_moving_versioned_directories(self):
503
create, root = self.get_transform()
504
kansas = create.new_directory('kansas', root, 'kansas-id')
505
create.new_directory('house', kansas, 'house-id')
506
create.new_directory('oz', root, 'oz-id')
508
cyclone, root = self.get_transform()
509
oz = cyclone.trans_id_tree_file_id('oz-id')
510
house = cyclone.trans_id_tree_file_id('house-id')
511
cyclone.adjust_path('house', oz, house)
514
def test_moving_root(self):
515
create, root = self.get_transform()
516
fun = create.new_directory('fun', root, 'fun-id')
517
create.new_directory('sun', root, 'sun-id')
518
create.new_directory('moon', root, 'moon')
520
transform, root = self.get_transform()
521
transform.adjust_root_path('oldroot', fun)
522
new_root=transform.trans_id_tree_path('')
523
transform.version_file('new-root', new_root)
526
def test_renames(self):
527
create, root = self.get_transform()
528
old = create.new_directory('old-parent', root, 'old-id')
529
intermediate = create.new_directory('intermediate', old, 'im-id')
530
myfile = create.new_file('myfile', intermediate, 'myfile-text',
533
rename, root = self.get_transform()
534
old = rename.trans_id_file_id('old-id')
535
rename.adjust_path('new', root, old)
536
myfile = rename.trans_id_file_id('myfile-id')
537
rename.set_executability(True, myfile)
540
def test_find_interesting(self):
541
create, root = self.get_transform()
543
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
544
create.new_file('uvfile', root, 'othertext')
546
result = self.applyDeprecated(symbol_versioning.zero_fifteen,
547
find_interesting, wt, wt, ['vfile'])
548
self.assertEqual(result, set(['myfile-id']))
550
def test_set_executability_order(self):
551
"""Ensure that executability behaves the same, no matter what order.
553
- create file and set executability simultaneously
554
- create file and set executability afterward
555
- unsetting the executability of a file whose executability has not been
556
declared should throw an exception (this may happen when a
557
merge attempts to create a file with a duplicate ID)
559
transform, root = self.get_transform()
561
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
563
sac = transform.new_file('set_after_creation', root,
564
'Set after creation', 'sac')
565
transform.set_executability(True, sac)
566
uws = transform.new_file('unset_without_set', root, 'Unset badly',
568
self.assertRaises(KeyError, transform.set_executability, None, uws)
570
self.assertTrue(wt.is_executable('soc'))
571
self.assertTrue(wt.is_executable('sac'))
573
def test_preserve_mode(self):
574
"""File mode is preserved when replacing content"""
575
if sys.platform == 'win32':
576
raise TestSkipped('chmod has no effect on win32')
577
transform, root = self.get_transform()
578
transform.new_file('file1', root, 'contents', 'file1-id', True)
580
self.assertTrue(self.wt.is_executable('file1-id'))
581
transform, root = self.get_transform()
582
file1_id = transform.trans_id_tree_file_id('file1-id')
583
transform.delete_contents(file1_id)
584
transform.create_file('contents2', file1_id)
586
self.assertTrue(self.wt.is_executable('file1-id'))
588
def test__set_mode_stats_correctly(self):
589
"""_set_mode stats to determine file mode."""
590
if sys.platform == 'win32':
591
raise TestSkipped('chmod has no effect on win32')
595
def instrumented_stat(path):
596
stat_paths.append(path)
597
return real_stat(path)
599
transform, root = self.get_transform()
601
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
602
file_id='bar-id-1', executable=False)
605
transform, root = self.get_transform()
606
bar1_id = transform.trans_id_tree_path('bar')
607
bar2_id = transform.trans_id_tree_path('bar2')
609
os.stat = instrumented_stat
610
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
615
bar1_abspath = self.wt.abspath('bar')
616
self.assertEqual([bar1_abspath], stat_paths)
618
def test_iter_changes(self):
619
self.wt.set_root_id('eert_toor')
620
transform, root = self.get_transform()
621
transform.new_file('old', root, 'blah', 'id-1', True)
623
transform, root = self.get_transform()
625
self.assertEqual([], list(transform._iter_changes()))
626
old = transform.trans_id_tree_file_id('id-1')
627
transform.unversion_file(old)
628
self.assertEqual([('id-1', ('old', None), False, (True, False),
629
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
630
(True, True))], list(transform._iter_changes()))
631
transform.new_directory('new', root, 'id-1')
632
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
633
('eert_toor', 'eert_toor'), ('old', 'new'),
634
('file', 'directory'),
635
(True, False))], list(transform._iter_changes()))
639
def test_iter_changes_new(self):
640
self.wt.set_root_id('eert_toor')
641
transform, root = self.get_transform()
642
transform.new_file('old', root, 'blah')
644
transform, root = self.get_transform()
646
old = transform.trans_id_tree_path('old')
647
transform.version_file('id-1', old)
648
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
649
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
650
(False, False))], list(transform._iter_changes()))
654
def test_iter_changes_modifications(self):
655
self.wt.set_root_id('eert_toor')
656
transform, root = self.get_transform()
657
transform.new_file('old', root, 'blah', 'id-1')
658
transform.new_file('new', root, 'blah')
659
transform.new_directory('subdir', root, 'subdir-id')
661
transform, root = self.get_transform()
663
old = transform.trans_id_tree_path('old')
664
subdir = transform.trans_id_tree_file_id('subdir-id')
665
new = transform.trans_id_tree_path('new')
666
self.assertEqual([], list(transform._iter_changes()))
669
transform.delete_contents(old)
670
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
671
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
672
(False, False))], list(transform._iter_changes()))
675
transform.create_file('blah', old)
676
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
677
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
678
(False, False))], list(transform._iter_changes()))
679
transform.cancel_deletion(old)
680
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
681
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
682
(False, False))], list(transform._iter_changes()))
683
transform.cancel_creation(old)
685
# move file_id to a different file
686
self.assertEqual([], list(transform._iter_changes()))
687
transform.unversion_file(old)
688
transform.version_file('id-1', new)
689
transform.adjust_path('old', root, new)
690
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
691
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
692
(False, False))], list(transform._iter_changes()))
693
transform.cancel_versioning(new)
694
transform._removed_id = set()
697
self.assertEqual([], list(transform._iter_changes()))
698
transform.set_executability(True, old)
699
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
700
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
701
(False, True))], list(transform._iter_changes()))
702
transform.set_executability(None, old)
705
self.assertEqual([], list(transform._iter_changes()))
706
transform.adjust_path('new', root, old)
707
transform._new_parent = {}
708
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
709
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
710
(False, False))], list(transform._iter_changes()))
711
transform._new_name = {}
714
self.assertEqual([], list(transform._iter_changes()))
715
transform.adjust_path('new', subdir, old)
716
transform._new_name = {}
717
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
718
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
719
('file', 'file'), (False, False))],
720
list(transform._iter_changes()))
721
transform._new_path = {}
726
def test_iter_changes_modified_bleed(self):
727
self.wt.set_root_id('eert_toor')
728
"""Modified flag should not bleed from one change to another"""
729
# unfortunately, we have no guarantee that file1 (which is modified)
730
# will be applied before file2. And if it's applied after file2, it
731
# obviously can't bleed into file2's change output. But for now, it
733
transform, root = self.get_transform()
734
transform.new_file('file1', root, 'blah', 'id-1')
735
transform.new_file('file2', root, 'blah', 'id-2')
737
transform, root = self.get_transform()
739
transform.delete_contents(transform.trans_id_file_id('id-1'))
740
transform.set_executability(True,
741
transform.trans_id_file_id('id-2'))
742
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
743
('eert_toor', 'eert_toor'), ('file1', u'file1'),
744
('file', None), (False, False)),
745
('id-2', (u'file2', u'file2'), False, (True, True),
746
('eert_toor', 'eert_toor'), ('file2', u'file2'),
747
('file', 'file'), (False, True))],
748
list(transform._iter_changes()))
752
def test_iter_changes_move_missing(self):
753
"""Test moving ids with no files around"""
754
self.wt.set_root_id('toor_eert')
755
# Need two steps because versioning a non-existant file is a conflict.
756
transform, root = self.get_transform()
757
transform.new_directory('floater', root, 'floater-id')
759
transform, root = self.get_transform()
760
transform.delete_contents(transform.trans_id_tree_path('floater'))
762
transform, root = self.get_transform()
763
floater = transform.trans_id_tree_path('floater')
765
transform.adjust_path('flitter', root, floater)
766
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
767
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
768
(None, None), (False, False))], list(transform._iter_changes()))
772
def test_iter_changes_pointless(self):
773
"""Ensure that no-ops are not treated as modifications"""
774
self.wt.set_root_id('eert_toor')
775
transform, root = self.get_transform()
776
transform.new_file('old', root, 'blah', 'id-1')
777
transform.new_directory('subdir', root, 'subdir-id')
779
transform, root = self.get_transform()
781
old = transform.trans_id_tree_path('old')
782
subdir = transform.trans_id_tree_file_id('subdir-id')
783
self.assertEqual([], list(transform._iter_changes()))
784
transform.delete_contents(subdir)
785
transform.create_directory(subdir)
786
transform.set_executability(False, old)
787
transform.unversion_file(old)
788
transform.version_file('id-1', old)
789
transform.adjust_path('old', root, old)
790
self.assertEqual([], list(transform._iter_changes()))
794
def test_rename_count(self):
795
transform, root = self.get_transform()
796
transform.new_file('name1', root, 'contents')
797
self.assertEqual(transform.rename_count, 0)
799
self.assertEqual(transform.rename_count, 1)
800
transform2, root = self.get_transform()
801
transform2.adjust_path('name2', root,
802
transform2.trans_id_tree_path('name1'))
803
self.assertEqual(transform2.rename_count, 0)
805
self.assertEqual(transform2.rename_count, 2)
807
def test_change_parent(self):
808
"""Ensure that after we change a parent, the results are still right.
810
Renames and parent changes on pending transforms can happen as part
811
of conflict resolution, and are explicitly permitted by the
814
This test ensures they work correctly with the rename-avoidance
817
transform, root = self.get_transform()
818
parent1 = transform.new_directory('parent1', root)
819
child1 = transform.new_file('child1', parent1, 'contents')
820
parent2 = transform.new_directory('parent2', root)
821
transform.adjust_path('child1', parent2, child1)
823
self.failIfExists(self.wt.abspath('parent1/child1'))
824
self.failUnlessExists(self.wt.abspath('parent2/child1'))
825
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
826
# no rename for child1 (counting only renames during apply)
827
self.failUnlessEqual(2, transform.rename_count)
829
def test_cancel_parent(self):
830
"""Cancelling a parent doesn't cause deletion of a non-empty directory
832
This is like the test_change_parent, except that we cancel the parent
833
before adjusting the path. The transform must detect that the
834
directory is non-empty, and move children to safe locations.
836
transform, root = self.get_transform()
837
parent1 = transform.new_directory('parent1', root)
838
child1 = transform.new_file('child1', parent1, 'contents')
839
child2 = transform.new_file('child2', parent1, 'contents')
841
transform.cancel_creation(parent1)
843
self.fail('Failed to move child1 before deleting parent1')
844
transform.cancel_creation(child2)
845
transform.create_directory(parent1)
847
transform.cancel_creation(parent1)
848
# If the transform incorrectly believes that child2 is still in
849
# parent1's limbo directory, it will try to rename it and fail
850
# because was already moved by the first cancel_creation.
852
self.fail('Transform still thinks child2 is a child of parent1')
853
parent2 = transform.new_directory('parent2', root)
854
transform.adjust_path('child1', parent2, child1)
856
self.failIfExists(self.wt.abspath('parent1'))
857
self.failUnlessExists(self.wt.abspath('parent2/child1'))
858
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
859
self.failUnlessEqual(2, transform.rename_count)
861
def test_adjust_and_cancel(self):
862
"""Make sure adjust_path keeps track of limbo children properly"""
863
transform, root = self.get_transform()
864
parent1 = transform.new_directory('parent1', root)
865
child1 = transform.new_file('child1', parent1, 'contents')
866
parent2 = transform.new_directory('parent2', root)
867
transform.adjust_path('child1', parent2, child1)
868
transform.cancel_creation(child1)
870
transform.cancel_creation(parent1)
871
# if the transform thinks child1 is still in parent1's limbo
872
# directory, it will attempt to move it and fail.
874
self.fail('Transform still thinks child1 is a child of parent1')
877
def test_noname_contents(self):
878
"""TreeTransform should permit deferring naming files."""
879
transform, root = self.get_transform()
880
parent = transform.trans_id_file_id('parent-id')
882
transform.create_directory(parent)
884
self.fail("Can't handle contents with no name")
887
def test_noname_contents_nested(self):
888
"""TreeTransform should permit deferring naming files."""
889
transform, root = self.get_transform()
890
parent = transform.trans_id_file_id('parent-id')
892
transform.create_directory(parent)
894
self.fail("Can't handle contents with no name")
895
child = transform.new_directory('child', parent)
896
transform.adjust_path('parent', root, parent)
898
self.failUnlessExists(self.wt.abspath('parent/child'))
899
self.assertEqual(1, transform.rename_count)
901
def test_reuse_name(self):
902
"""Avoid reusing the same limbo name for different files"""
903
transform, root = self.get_transform()
904
parent = transform.new_directory('parent', root)
905
child1 = transform.new_directory('child', parent)
907
child2 = transform.new_directory('child', parent)
909
self.fail('Tranform tried to use the same limbo name twice')
910
transform.adjust_path('child2', parent, child2)
912
# limbo/new-1 => parent, limbo/new-3 => parent/child2
913
# child2 is put into top-level limbo because child1 has already
914
# claimed the direct limbo path when child2 is created. There is no
915
# advantage in renaming files once they're in top-level limbo, except
917
self.assertEqual(2, transform.rename_count)
919
def test_reuse_when_first_moved(self):
920
"""Don't avoid direct paths when it is safe to use them"""
921
transform, root = self.get_transform()
922
parent = transform.new_directory('parent', root)
923
child1 = transform.new_directory('child', parent)
924
transform.adjust_path('child1', parent, child1)
925
child2 = transform.new_directory('child', parent)
927
# limbo/new-1 => parent
928
self.assertEqual(1, transform.rename_count)
930
def test_reuse_after_cancel(self):
931
"""Don't avoid direct paths when it is safe to use them"""
932
transform, root = self.get_transform()
933
parent2 = transform.new_directory('parent2', root)
934
child1 = transform.new_directory('child1', parent2)
935
transform.cancel_creation(parent2)
936
transform.create_directory(parent2)
937
child2 = transform.new_directory('child1', parent2)
938
transform.adjust_path('child2', parent2, child1)
940
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
941
self.assertEqual(2, transform.rename_count)
943
def test_finalize_order(self):
944
"""Finalize must be done in child-to-parent order"""
945
transform, root = self.get_transform()
946
parent = transform.new_directory('parent', root)
947
child = transform.new_directory('child', parent)
951
self.fail('Tried to remove parent before child1')
953
def test_cancel_with_cancelled_child_should_succeed(self):
954
transform, root = self.get_transform()
955
parent = transform.new_directory('parent', root)
956
child = transform.new_directory('child', parent)
957
transform.cancel_creation(child)
958
transform.cancel_creation(parent)
961
def test_change_entry(self):
962
txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
963
self.callDeprecated([txt], change_entry, None, None, None, None, None,
967
class TransformGroup(object):
968
def __init__(self, dirname, root_id):
971
self.wt = BzrDir.create_standalone_workingtree(dirname)
972
self.wt.set_root_id(root_id)
973
self.b = self.wt.branch
974
self.tt = TreeTransform(self.wt)
975
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
978
def conflict_text(tree, merge):
979
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
980
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
983
class TestTransformMerge(TestCaseInTempDir):
984
def test_text_merge(self):
985
root_id = generate_ids.gen_root_id()
986
base = TransformGroup("base", root_id)
987
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
988
base.tt.new_file('b', base.root, 'b1', 'b')
989
base.tt.new_file('c', base.root, 'c', 'c')
990
base.tt.new_file('d', base.root, 'd', 'd')
991
base.tt.new_file('e', base.root, 'e', 'e')
992
base.tt.new_file('f', base.root, 'f', 'f')
993
base.tt.new_directory('g', base.root, 'g')
994
base.tt.new_directory('h', base.root, 'h')
996
other = TransformGroup("other", root_id)
997
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
998
other.tt.new_file('b', other.root, 'b2', 'b')
999
other.tt.new_file('c', other.root, 'c2', 'c')
1000
other.tt.new_file('d', other.root, 'd', 'd')
1001
other.tt.new_file('e', other.root, 'e2', 'e')
1002
other.tt.new_file('f', other.root, 'f', 'f')
1003
other.tt.new_file('g', other.root, 'g', 'g')
1004
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1005
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1007
this = TransformGroup("this", root_id)
1008
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1009
this.tt.new_file('b', this.root, 'b', 'b')
1010
this.tt.new_file('c', this.root, 'c', 'c')
1011
this.tt.new_file('d', this.root, 'd2', 'd')
1012
this.tt.new_file('e', this.root, 'e2', 'e')
1013
this.tt.new_file('f', this.root, 'f', 'f')
1014
this.tt.new_file('g', this.root, 'g', 'g')
1015
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
1016
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1018
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1020
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1021
# three-way text conflict
1022
self.assertEqual(this.wt.get_file('b').read(),
1023
conflict_text('b', 'b2'))
1025
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1027
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1028
# Ambigious clean merge
1029
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1031
self.assertEqual(this.wt.get_file('f').read(), 'f')
1032
# Correct correct results when THIS == OTHER
1033
self.assertEqual(this.wt.get_file('g').read(), 'g')
1034
# Text conflict when THIS & OTHER are text and BASE is dir
1035
self.assertEqual(this.wt.get_file('h').read(),
1036
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1037
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1039
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1041
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1042
self.assertEqual(this.wt.get_file('i').read(),
1043
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1044
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1046
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1048
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
1049
modified = ['a', 'b', 'c', 'h', 'i']
1050
merge_modified = this.wt.merge_modified()
1051
self.assertSubset(merge_modified, modified)
1052
self.assertEqual(len(merge_modified), len(modified))
1053
file(this.wt.id2abspath('a'), 'wb').write('booga')
1055
merge_modified = this.wt.merge_modified()
1056
self.assertSubset(merge_modified, modified)
1057
self.assertEqual(len(merge_modified), len(modified))
1061
def test_file_merge(self):
1062
self.requireFeature(SymlinkFeature)
1063
root_id = generate_ids.gen_root_id()
1064
base = TransformGroup("BASE", root_id)
1065
this = TransformGroup("THIS", root_id)
1066
other = TransformGroup("OTHER", root_id)
1067
for tg in this, base, other:
1068
tg.tt.new_directory('a', tg.root, 'a')
1069
tg.tt.new_symlink('b', tg.root, 'b', 'b')
1070
tg.tt.new_file('c', tg.root, 'c', 'c')
1071
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1072
targets = ((base, 'base-e', 'base-f', None, None),
1073
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1074
(other, 'other-e', None, 'other-g', 'other-h'))
1075
for tg, e_target, f_target, g_target, h_target in targets:
1076
for link, target in (('e', e_target), ('f', f_target),
1077
('g', g_target), ('h', h_target)):
1078
if target is not None:
1079
tg.tt.new_symlink(link, tg.root, target, link)
1081
for tg in this, base, other:
1083
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1084
self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
1085
self.assertIs(os.path.islink(this.wt.abspath('b')), True)
1086
self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
1087
for suffix in ('THIS', 'BASE', 'OTHER'):
1088
self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
1089
self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
1090
self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
1091
self.assertEqual(this.wt.id2path('f'), 'f.THIS')
1092
self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
1093
self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
1094
self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
1095
self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
1096
self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
1097
self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
1098
self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
1099
self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
1100
self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
1101
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1103
def test_filename_merge(self):
1104
root_id = generate_ids.gen_root_id()
1105
base = TransformGroup("BASE", root_id)
1106
this = TransformGroup("THIS", root_id)
1107
other = TransformGroup("OTHER", root_id)
1108
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1109
for t in [base, this, other]]
1110
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1111
for t in [base, this, other]]
1112
base.tt.new_directory('c', base_a, 'c')
1113
this.tt.new_directory('c1', this_a, 'c')
1114
other.tt.new_directory('c', other_b, 'c')
1116
base.tt.new_directory('d', base_a, 'd')
1117
this.tt.new_directory('d1', this_b, 'd')
1118
other.tt.new_directory('d', other_a, 'd')
1120
base.tt.new_directory('e', base_a, 'e')
1121
this.tt.new_directory('e', this_a, 'e')
1122
other.tt.new_directory('e1', other_b, 'e')
1124
base.tt.new_directory('f', base_a, 'f')
1125
this.tt.new_directory('f1', this_b, 'f')
1126
other.tt.new_directory('f1', other_b, 'f')
1128
for tg in [this, base, other]:
1130
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1131
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
1132
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
1133
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
1134
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1136
def test_filename_merge_conflicts(self):
1137
root_id = generate_ids.gen_root_id()
1138
base = TransformGroup("BASE", root_id)
1139
this = TransformGroup("THIS", root_id)
1140
other = TransformGroup("OTHER", root_id)
1141
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1142
for t in [base, this, other]]
1143
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1144
for t in [base, this, other]]
1146
base.tt.new_file('g', base_a, 'g', 'g')
1147
other.tt.new_file('g1', other_b, 'g1', 'g')
1149
base.tt.new_file('h', base_a, 'h', 'h')
1150
this.tt.new_file('h1', this_b, 'h1', 'h')
1152
base.tt.new_file('i', base.root, 'i', 'i')
1153
other.tt.new_directory('i1', this_b, 'i')
1155
for tg in [this, base, other]:
1157
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1159
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1160
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
1161
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
1162
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1163
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
1164
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1165
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1168
class TestBuildTree(tests.TestCaseWithTransport):
1170
def test_build_tree(self):
1171
self.requireFeature(SymlinkFeature)
1173
a = BzrDir.create_standalone_workingtree('a')
1175
file('a/foo/bar', 'wb').write('contents')
1176
os.symlink('a/foo/bar', 'a/foo/baz')
1177
a.add(['foo', 'foo/bar', 'foo/baz'])
1178
a.commit('initial commit')
1179
b = BzrDir.create_standalone_workingtree('b')
1180
basis = a.basis_tree()
1182
self.addCleanup(basis.unlock)
1183
build_tree(basis, b)
1184
self.assertIs(os.path.isdir('b/foo'), True)
1185
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1186
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1188
def test_build_with_references(self):
1189
tree = self.make_branch_and_tree('source',
1190
format='dirstate-with-subtree')
1191
subtree = self.make_branch_and_tree('source/subtree',
1192
format='dirstate-with-subtree')
1193
tree.add_reference(subtree)
1194
tree.commit('a revision')
1195
tree.branch.create_checkout('target')
1196
self.failUnlessExists('target')
1197
self.failUnlessExists('target/subtree')
1199
def test_file_conflict_handling(self):
1200
"""Ensure that when building trees, conflict handling is done"""
1201
source = self.make_branch_and_tree('source')
1202
target = self.make_branch_and_tree('target')
1203
self.build_tree(['source/file', 'target/file'])
1204
source.add('file', 'new-file')
1205
source.commit('added file')
1206
build_tree(source.basis_tree(), target)
1207
self.assertEqual([DuplicateEntry('Moved existing file to',
1208
'file.moved', 'file', None, 'new-file')],
1210
target2 = self.make_branch_and_tree('target2')
1211
target_file = file('target2/file', 'wb')
1213
source_file = file('source/file', 'rb')
1215
target_file.write(source_file.read())
1220
build_tree(source.basis_tree(), target2)
1221
self.assertEqual([], target2.conflicts())
1223
def test_symlink_conflict_handling(self):
1224
"""Ensure that when building trees, conflict handling is done"""
1225
self.requireFeature(SymlinkFeature)
1226
source = self.make_branch_and_tree('source')
1227
os.symlink('foo', 'source/symlink')
1228
source.add('symlink', 'new-symlink')
1229
source.commit('added file')
1230
target = self.make_branch_and_tree('target')
1231
os.symlink('bar', 'target/symlink')
1232
build_tree(source.basis_tree(), target)
1233
self.assertEqual([DuplicateEntry('Moved existing file to',
1234
'symlink.moved', 'symlink', None, 'new-symlink')],
1236
target = self.make_branch_and_tree('target2')
1237
os.symlink('foo', 'target2/symlink')
1238
build_tree(source.basis_tree(), target)
1239
self.assertEqual([], target.conflicts())
1241
def test_directory_conflict_handling(self):
1242
"""Ensure that when building trees, conflict handling is done"""
1243
source = self.make_branch_and_tree('source')
1244
target = self.make_branch_and_tree('target')
1245
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1246
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1247
source.commit('added file')
1248
build_tree(source.basis_tree(), target)
1249
self.assertEqual([], target.conflicts())
1250
self.failUnlessExists('target/dir1/file')
1252
# Ensure contents are merged
1253
target = self.make_branch_and_tree('target2')
1254
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1255
build_tree(source.basis_tree(), target)
1256
self.assertEqual([], target.conflicts())
1257
self.failUnlessExists('target2/dir1/file2')
1258
self.failUnlessExists('target2/dir1/file')
1260
# Ensure new contents are suppressed for existing branches
1261
target = self.make_branch_and_tree('target3')
1262
self.make_branch('target3/dir1')
1263
self.build_tree(['target3/dir1/file2'])
1264
build_tree(source.basis_tree(), target)
1265
self.failIfExists('target3/dir1/file')
1266
self.failUnlessExists('target3/dir1/file2')
1267
self.failUnlessExists('target3/dir1.diverted/file')
1268
self.assertEqual([DuplicateEntry('Diverted to',
1269
'dir1.diverted', 'dir1', 'new-dir1', None)],
1272
target = self.make_branch_and_tree('target4')
1273
self.build_tree(['target4/dir1/'])
1274
self.make_branch('target4/dir1/file')
1275
build_tree(source.basis_tree(), target)
1276
self.failUnlessExists('target4/dir1/file')
1277
self.assertEqual('directory', file_kind('target4/dir1/file'))
1278
self.failUnlessExists('target4/dir1/file.diverted')
1279
self.assertEqual([DuplicateEntry('Diverted to',
1280
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1283
def test_mixed_conflict_handling(self):
1284
"""Ensure that when building trees, conflict handling is done"""
1285
source = self.make_branch_and_tree('source')
1286
target = self.make_branch_and_tree('target')
1287
self.build_tree(['source/name', 'target/name/'])
1288
source.add('name', 'new-name')
1289
source.commit('added file')
1290
build_tree(source.basis_tree(), target)
1291
self.assertEqual([DuplicateEntry('Moved existing file to',
1292
'name.moved', 'name', None, 'new-name')], target.conflicts())
1294
def test_raises_in_populated(self):
1295
source = self.make_branch_and_tree('source')
1296
self.build_tree(['source/name'])
1298
source.commit('added name')
1299
target = self.make_branch_and_tree('target')
1300
self.build_tree(['target/name'])
1302
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1303
build_tree, source.basis_tree(), target)
1305
def test_build_tree_rename_count(self):
1306
source = self.make_branch_and_tree('source')
1307
self.build_tree(['source/file1', 'source/dir1/'])
1308
source.add(['file1', 'dir1'])
1309
source.commit('add1')
1310
target1 = self.make_branch_and_tree('target1')
1311
transform_result = build_tree(source.basis_tree(), target1)
1312
self.assertEqual(2, transform_result.rename_count)
1314
self.build_tree(['source/dir1/file2'])
1315
source.add(['dir1/file2'])
1316
source.commit('add3')
1317
target2 = self.make_branch_and_tree('target2')
1318
transform_result = build_tree(source.basis_tree(), target2)
1319
# children of non-root directories should not be renamed
1320
self.assertEqual(2, transform_result.rename_count)
1323
class MockTransform(object):
1325
def has_named_child(self, by_parent, parent_id, name):
1326
for child_id in by_parent[parent_id]:
1330
elif name == "name.~%s~" % child_id:
1335
class MockEntry(object):
1337
object.__init__(self)
1340
class TestGetBackupName(TestCase):
1341
def test_get_backup_name(self):
1342
tt = MockTransform()
1343
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1344
self.assertEqual(name, 'name.~1~')
1345
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1346
self.assertEqual(name, 'name.~2~')
1347
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1348
self.assertEqual(name, 'name.~1~')
1349
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1350
self.assertEqual(name, 'name.~1~')
1351
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1352
self.assertEqual(name, 'name.~4~')
1355
class TestFileMover(tests.TestCaseWithTransport):
1357
def test_file_mover(self):
1358
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1359
mover = _FileMover()
1360
mover.rename('a', 'q')
1361
self.failUnlessExists('q')
1362
self.failIfExists('a')
1363
self.failUnlessExists('q/b')
1364
self.failUnlessExists('c')
1365
self.failUnlessExists('c/d')
1367
def test_pre_delete_rollback(self):
1368
self.build_tree(['a/'])
1369
mover = _FileMover()
1370
mover.pre_delete('a', 'q')
1371
self.failUnlessExists('q')
1372
self.failIfExists('a')
1374
self.failIfExists('q')
1375
self.failUnlessExists('a')
1377
def test_apply_deletions(self):
1378
self.build_tree(['a/', 'b/'])
1379
mover = _FileMover()
1380
mover.pre_delete('a', 'q')
1381
mover.pre_delete('b', 'r')
1382
self.failUnlessExists('q')
1383
self.failUnlessExists('r')
1384
self.failIfExists('a')
1385
self.failIfExists('b')
1386
mover.apply_deletions()
1387
self.failIfExists('q')
1388
self.failIfExists('r')
1389
self.failIfExists('a')
1390
self.failIfExists('b')
1392
def test_file_mover_rollback(self):
1393
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1394
mover = _FileMover()
1395
mover.rename('c/d', 'c/f')
1396
mover.rename('c/e', 'c/d')
1398
mover.rename('a', 'c')
1401
self.failUnlessExists('a')
1402
self.failUnlessExists('c/d')
1405
class Bogus(Exception):
1409
class TestTransformRollback(tests.TestCaseWithTransport):
1411
class ExceptionFileMover(_FileMover):
1413
def __init__(self, bad_source=None, bad_target=None):
1414
_FileMover.__init__(self)
1415
self.bad_source = bad_source
1416
self.bad_target = bad_target
1418
def rename(self, source, target):
1419
if (self.bad_source is not None and
1420
source.endswith(self.bad_source)):
1422
elif (self.bad_target is not None and
1423
target.endswith(self.bad_target)):
1426
_FileMover.rename(self, source, target)
1428
def test_rollback_rename(self):
1429
tree = self.make_branch_and_tree('.')
1430
self.build_tree(['a/', 'a/b'])
1431
tt = TreeTransform(tree)
1432
self.addCleanup(tt.finalize)
1433
a_id = tt.trans_id_tree_path('a')
1434
tt.adjust_path('c', tt.root, a_id)
1435
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1436
self.assertRaises(Bogus, tt.apply,
1437
_mover=self.ExceptionFileMover(bad_source='a'))
1438
self.failUnlessExists('a')
1439
self.failUnlessExists('a/b')
1441
self.failUnlessExists('c')
1442
self.failUnlessExists('c/d')
1444
def test_rollback_rename_into_place(self):
1445
tree = self.make_branch_and_tree('.')
1446
self.build_tree(['a/', 'a/b'])
1447
tt = TreeTransform(tree)
1448
self.addCleanup(tt.finalize)
1449
a_id = tt.trans_id_tree_path('a')
1450
tt.adjust_path('c', tt.root, a_id)
1451
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1452
self.assertRaises(Bogus, tt.apply,
1453
_mover=self.ExceptionFileMover(bad_target='c/d'))
1454
self.failUnlessExists('a')
1455
self.failUnlessExists('a/b')
1457
self.failUnlessExists('c')
1458
self.failUnlessExists('c/d')
1460
def test_rollback_deletion(self):
1461
tree = self.make_branch_and_tree('.')
1462
self.build_tree(['a/', 'a/b'])
1463
tt = TreeTransform(tree)
1464
self.addCleanup(tt.finalize)
1465
a_id = tt.trans_id_tree_path('a')
1466
tt.delete_contents(a_id)
1467
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1468
self.assertRaises(Bogus, tt.apply,
1469
_mover=self.ExceptionFileMover(bad_target='d'))
1470
self.failUnlessExists('a')
1471
self.failUnlessExists('a/b')