43
from bzrlib.diff import show_diff_trees
44
from bzrlib.errors import (
48
from ..controldir import ControlDir
49
from ..diff import show_diff_trees
50
from ..errors import (
47
53
ExistingPendingDeletion,
49
54
ImmortalPendingDeletion,
55
from bzrlib.osutils import (
57
from ..osutils import (
59
from bzrlib.merge import Merge3Merger, Merger
60
from bzrlib.tests import (
61
from ..merge import Merge3Merger, Merger
62
from ..mutabletree import MutableTree
68
from .features import (
67
from bzrlib.transform import (
72
from ..transform import (
85
TransformRenameFailed,
82
class TestTreeTransform(tests.TestCaseWithTransport):
85
super(TestTreeTransform, self).setUp()
86
self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
89
def get_transform(self):
90
transform = TreeTransform(self.wt)
91
self.addCleanup(transform.finalize)
92
return transform, transform.root
94
def test_existing_limbo(self):
95
transform, root = self.get_transform()
96
limbo_name = transform._limbodir
97
deletion_path = transform._deletiondir
98
os.mkdir(pathjoin(limbo_name, 'hehe'))
99
self.assertRaises(ImmortalLimbo, transform.apply)
100
self.assertRaises(LockError, self.wt.unlock)
101
self.assertRaises(ExistingLimbo, self.get_transform)
102
self.assertRaises(LockError, self.wt.unlock)
103
os.rmdir(pathjoin(limbo_name, 'hehe'))
105
os.rmdir(deletion_path)
106
transform, root = self.get_transform()
109
def test_existing_pending_deletion(self):
110
transform, root = self.get_transform()
111
deletion_path = self._limbodir = urlutils.local_path_from_url(
112
transform._tree._transport.abspath('pending-deletion'))
113
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
114
self.assertRaises(ImmortalPendingDeletion, transform.apply)
115
self.assertRaises(LockError, self.wt.unlock)
116
self.assertRaises(ExistingPendingDeletion, self.get_transform)
118
def test_build(self):
119
transform, root = self.get_transform()
120
self.wt.lock_tree_write()
121
self.addCleanup(self.wt.unlock)
122
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
123
imaginary_id = transform.trans_id_tree_path('imaginary')
124
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
125
self.assertEqual(imaginary_id, imaginary_id2)
126
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
127
self.assertEqual(transform.final_kind(root), 'directory')
128
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
129
trans_id = transform.create_path('name', root)
130
self.assertIs(transform.final_file_id(trans_id), None)
131
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
132
transform.create_file('contents', trans_id)
133
transform.set_executability(True, trans_id)
134
transform.version_file('my_pretties', trans_id)
135
self.assertRaises(DuplicateKey, transform.version_file,
136
'my_pretties', trans_id)
137
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
138
self.assertEqual(transform.final_parent(trans_id), root)
139
self.assertIs(transform.final_parent(root), ROOT_PARENT)
140
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
141
oz_id = transform.create_path('oz', root)
142
transform.create_directory(oz_id)
143
transform.version_file('ozzie', oz_id)
144
trans_id2 = transform.create_path('name2', root)
145
transform.create_file('contents', trans_id2)
146
transform.set_executability(False, trans_id2)
147
transform.version_file('my_pretties2', trans_id2)
148
modified_paths = transform.apply().modified_paths
149
self.assertEqual('contents', self.wt.get_file_byname('name').read())
150
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
151
self.assertIs(self.wt.is_executable('my_pretties'), True)
152
self.assertIs(self.wt.is_executable('my_pretties2'), False)
153
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
154
self.assertEqual(len(modified_paths), 3)
155
tree_mod_paths = [self.wt.id2abspath(f) for f in
156
('ozzie', 'my_pretties', 'my_pretties2')]
157
self.assertSubset(tree_mod_paths, modified_paths)
158
# is it safe to finalize repeatedly?
162
def test_create_files_same_timestamp(self):
163
transform, root = self.get_transform()
164
self.wt.lock_tree_write()
165
self.addCleanup(self.wt.unlock)
166
# Roll back the clock, so that we know everything is being set to the
168
transform._creation_mtime = creation_mtime = time.time() - 20.0
169
transform.create_file('content-one',
170
transform.create_path('one', root))
171
time.sleep(1) # *ugly*
172
transform.create_file('content-two',
173
transform.create_path('two', root))
175
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
177
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
179
# We only guarantee 2s resolution
180
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
181
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
182
# But if we have more than that, all files should get the same result
183
self.assertEqual(st1.st_mtime, st2.st_mtime)
185
def test_change_root_id(self):
186
transform, root = self.get_transform()
187
self.assertNotEqual('new-root-id', self.wt.get_root_id())
188
transform.new_directory('', ROOT_PARENT, 'new-root-id')
189
transform.delete_contents(root)
190
transform.unversion_file(root)
191
transform.fixup_new_roots()
193
self.assertEqual('new-root-id', self.wt.get_root_id())
195
def test_change_root_id_add_files(self):
196
transform, root = self.get_transform()
197
self.assertNotEqual('new-root-id', self.wt.get_root_id())
198
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
199
transform.new_file('file', new_trans_id, ['new-contents\n'],
201
transform.delete_contents(root)
202
transform.unversion_file(root)
203
transform.fixup_new_roots()
205
self.assertEqual('new-root-id', self.wt.get_root_id())
206
self.assertEqual('new-file-id', self.wt.path2id('file'))
207
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
209
def test_add_two_roots(self):
210
transform, root = self.get_transform()
211
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
212
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
213
self.assertRaises(ValueError, transform.fixup_new_roots)
215
def test_hardlink(self):
216
self.requireFeature(HardlinkFeature)
217
transform, root = self.get_transform()
218
transform.new_file('file1', root, 'contents')
220
target = self.make_branch_and_tree('target')
221
target_transform = TreeTransform(target)
222
trans_id = target_transform.create_path('file1', target_transform.root)
223
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
224
target_transform.apply()
225
self.failUnlessExists('target/file1')
226
source_stat = os.stat(self.wt.abspath('file1'))
227
target_stat = os.stat('target/file1')
228
self.assertEqual(source_stat, target_stat)
230
def test_convenience(self):
231
transform, root = self.get_transform()
232
self.wt.lock_tree_write()
233
self.addCleanup(self.wt.unlock)
234
trans_id = transform.new_file('name', root, 'contents',
236
oz = transform.new_directory('oz', root, 'oz-id')
237
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
238
toto = transform.new_file('toto', dorothy, 'toto-contents',
241
self.assertEqual(len(transform.find_conflicts()), 0)
243
self.assertRaises(ReusingTransform, transform.find_conflicts)
244
self.assertEqual('contents', file(self.wt.abspath('name')).read())
245
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
246
self.assertIs(self.wt.is_executable('my_pretties'), True)
247
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
248
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
249
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
251
self.assertEqual('toto-contents',
252
self.wt.get_file_byname('oz/dorothy/toto').read())
253
self.assertIs(self.wt.is_executable('toto-id'), False)
255
def test_tree_reference(self):
256
transform, root = self.get_transform()
257
tree = transform._tree
258
trans_id = transform.new_directory('reference', root, 'subtree-id')
259
transform.set_tree_reference('subtree-revision', trans_id)
262
self.addCleanup(tree.unlock)
263
self.assertEqual('subtree-revision',
264
tree.inventory['subtree-id'].reference_revision)
266
def test_conflicts(self):
267
transform, root = self.get_transform()
268
trans_id = transform.new_file('name', root, 'contents',
270
self.assertEqual(len(transform.find_conflicts()), 0)
271
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
272
self.assertEqual(transform.find_conflicts(),
273
[('duplicate', trans_id, trans_id2, 'name')])
274
self.assertRaises(MalformedTransform, transform.apply)
275
transform.adjust_path('name', trans_id, trans_id2)
276
self.assertEqual(transform.find_conflicts(),
277
[('non-directory parent', trans_id)])
278
tinman_id = transform.trans_id_tree_path('tinman')
279
transform.adjust_path('name', tinman_id, trans_id2)
280
self.assertEqual(transform.find_conflicts(),
281
[('unversioned parent', tinman_id),
282
('missing parent', tinman_id)])
283
lion_id = transform.create_path('lion', root)
284
self.assertEqual(transform.find_conflicts(),
285
[('unversioned parent', tinman_id),
286
('missing parent', tinman_id)])
287
transform.adjust_path('name', lion_id, trans_id2)
288
self.assertEqual(transform.find_conflicts(),
289
[('unversioned parent', lion_id),
290
('missing parent', lion_id)])
291
transform.version_file("Courage", lion_id)
292
self.assertEqual(transform.find_conflicts(),
293
[('missing parent', lion_id),
294
('versioning no contents', lion_id)])
295
transform.adjust_path('name2', root, trans_id2)
296
self.assertEqual(transform.find_conflicts(),
297
[('versioning no contents', lion_id)])
298
transform.create_file('Contents, okay?', lion_id)
299
transform.adjust_path('name2', trans_id2, trans_id2)
300
self.assertEqual(transform.find_conflicts(),
301
[('parent loop', trans_id2),
302
('non-directory parent', trans_id2)])
303
transform.adjust_path('name2', root, trans_id2)
304
oz_id = transform.new_directory('oz', root)
305
transform.set_executability(True, oz_id)
306
self.assertEqual(transform.find_conflicts(),
307
[('unversioned executability', oz_id)])
308
transform.version_file('oz-id', oz_id)
309
self.assertEqual(transform.find_conflicts(),
310
[('non-file executability', oz_id)])
311
transform.set_executability(None, oz_id)
312
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
314
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
315
self.assertEqual('contents', file(self.wt.abspath('name')).read())
316
transform2, root = self.get_transform()
317
oz_id = transform2.trans_id_tree_file_id('oz-id')
318
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
319
result = transform2.find_conflicts()
320
fp = FinalPaths(transform2)
321
self.assert_('oz/tip' in transform2._tree_path_ids)
322
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
323
self.assertEqual(len(result), 2)
324
self.assertEqual((result[0][0], result[0][1]),
325
('duplicate', newtip))
326
self.assertEqual((result[1][0], result[1][2]),
327
('duplicate id', newtip))
328
transform2.finalize()
329
transform3 = TreeTransform(self.wt)
330
self.addCleanup(transform3.finalize)
331
oz_id = transform3.trans_id_tree_file_id('oz-id')
332
transform3.delete_contents(oz_id)
333
self.assertEqual(transform3.find_conflicts(),
334
[('missing parent', oz_id)])
335
root_id = transform3.root
336
tip_id = transform3.trans_id_tree_file_id('tip-id')
337
transform3.adjust_path('tip', root_id, tip_id)
340
def test_conflict_on_case_insensitive(self):
341
tree = self.make_branch_and_tree('tree')
342
# Don't try this at home, kids!
343
# Force the tree to report that it is case sensitive, for conflict
345
tree.case_sensitive = True
346
transform = TreeTransform(tree)
347
self.addCleanup(transform.finalize)
348
transform.new_file('file', transform.root, 'content')
349
transform.new_file('FiLe', transform.root, 'content')
350
result = transform.find_conflicts()
351
self.assertEqual([], result)
353
# Force the tree to report that it is case insensitive, for conflict
355
tree.case_sensitive = False
356
transform = TreeTransform(tree)
357
self.addCleanup(transform.finalize)
358
transform.new_file('file', transform.root, 'content')
359
transform.new_file('FiLe', transform.root, 'content')
360
result = transform.find_conflicts()
361
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
363
def test_conflict_on_case_insensitive_existing(self):
364
tree = self.make_branch_and_tree('tree')
365
self.build_tree(['tree/FiLe'])
366
# Don't try this at home, kids!
367
# Force the tree to report that it is case sensitive, for conflict
369
tree.case_sensitive = True
370
transform = TreeTransform(tree)
371
self.addCleanup(transform.finalize)
372
transform.new_file('file', transform.root, 'content')
373
result = transform.find_conflicts()
374
self.assertEqual([], result)
376
# Force the tree to report that it is case insensitive, for conflict
378
tree.case_sensitive = False
379
transform = TreeTransform(tree)
380
self.addCleanup(transform.finalize)
381
transform.new_file('file', transform.root, 'content')
382
result = transform.find_conflicts()
383
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
385
def test_resolve_case_insensitive_conflict(self):
386
tree = self.make_branch_and_tree('tree')
387
# Don't try this at home, kids!
388
# Force the tree to report that it is case insensitive, for conflict
390
tree.case_sensitive = False
391
transform = TreeTransform(tree)
392
self.addCleanup(transform.finalize)
393
transform.new_file('file', transform.root, 'content')
394
transform.new_file('FiLe', transform.root, 'content')
395
resolve_conflicts(transform)
397
self.failUnlessExists('tree/file')
398
self.failUnlessExists('tree/FiLe.moved')
400
def test_resolve_checkout_case_conflict(self):
401
tree = self.make_branch_and_tree('tree')
402
# Don't try this at home, kids!
403
# Force the tree to report that it is case insensitive, for conflict
405
tree.case_sensitive = False
406
transform = TreeTransform(tree)
407
self.addCleanup(transform.finalize)
408
transform.new_file('file', transform.root, 'content')
409
transform.new_file('FiLe', transform.root, 'content')
410
resolve_conflicts(transform,
411
pass_func=lambda t, c: resolve_checkout(t, c, []))
413
self.failUnlessExists('tree/file')
414
self.failUnlessExists('tree/FiLe.moved')
416
def test_apply_case_conflict(self):
417
"""Ensure that a transform with case conflicts can always be applied"""
418
tree = self.make_branch_and_tree('tree')
419
transform = TreeTransform(tree)
420
self.addCleanup(transform.finalize)
421
transform.new_file('file', transform.root, 'content')
422
transform.new_file('FiLe', transform.root, 'content')
423
dir = transform.new_directory('dir', transform.root)
424
transform.new_file('dirfile', dir, 'content')
425
transform.new_file('dirFiLe', dir, 'content')
426
resolve_conflicts(transform)
428
self.failUnlessExists('tree/file')
429
if not os.path.exists('tree/FiLe.moved'):
430
self.failUnlessExists('tree/FiLe')
431
self.failUnlessExists('tree/dir/dirfile')
432
if not os.path.exists('tree/dir/dirFiLe.moved'):
433
self.failUnlessExists('tree/dir/dirFiLe')
435
def test_case_insensitive_limbo(self):
436
tree = self.make_branch_and_tree('tree')
437
# Don't try this at home, kids!
438
# Force the tree to report that it is case insensitive
439
tree.case_sensitive = False
440
transform = TreeTransform(tree)
441
self.addCleanup(transform.finalize)
442
dir = transform.new_directory('dir', transform.root)
443
first = transform.new_file('file', dir, 'content')
444
second = transform.new_file('FiLe', dir, 'content')
445
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
446
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
448
def test_adjust_path_updates_child_limbo_names(self):
449
tree = self.make_branch_and_tree('tree')
450
transform = TreeTransform(tree)
451
self.addCleanup(transform.finalize)
452
foo_id = transform.new_directory('foo', transform.root)
453
bar_id = transform.new_directory('bar', foo_id)
454
baz_id = transform.new_directory('baz', bar_id)
455
qux_id = transform.new_directory('qux', baz_id)
456
transform.adjust_path('quxx', foo_id, bar_id)
457
self.assertStartsWith(transform._limbo_name(qux_id),
458
transform._limbo_name(bar_id))
460
def test_add_del(self):
461
start, root = self.get_transform()
462
start.new_directory('a', root, 'a')
464
transform, root = self.get_transform()
465
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
466
transform.new_directory('a', root, 'a')
469
def test_unversioning(self):
470
create_tree, root = self.get_transform()
471
parent_id = create_tree.new_directory('parent', root, 'parent-id')
472
create_tree.new_file('child', parent_id, 'child', 'child-id')
474
unversion = TreeTransform(self.wt)
475
self.addCleanup(unversion.finalize)
476
parent = unversion.trans_id_tree_path('parent')
477
unversion.unversion_file(parent)
478
self.assertEqual(unversion.find_conflicts(),
479
[('unversioned parent', parent_id)])
480
file_id = unversion.trans_id_tree_file_id('child-id')
481
unversion.unversion_file(file_id)
484
def test_name_invariants(self):
485
create_tree, root = self.get_transform()
487
root = create_tree.root
488
create_tree.new_file('name1', root, 'hello1', 'name1')
489
create_tree.new_file('name2', root, 'hello2', 'name2')
490
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
491
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
492
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
493
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
496
mangle_tree,root = self.get_transform()
497
root = mangle_tree.root
499
name1 = mangle_tree.trans_id_tree_file_id('name1')
500
name2 = mangle_tree.trans_id_tree_file_id('name2')
501
mangle_tree.adjust_path('name2', root, name1)
502
mangle_tree.adjust_path('name1', root, name2)
504
#tests for deleting parent directories
505
ddir = mangle_tree.trans_id_tree_file_id('ddir')
506
mangle_tree.delete_contents(ddir)
507
dfile = mangle_tree.trans_id_tree_file_id('dfile')
508
mangle_tree.delete_versioned(dfile)
509
mangle_tree.unversion_file(dfile)
510
mfile = mangle_tree.trans_id_tree_file_id('mfile')
511
mangle_tree.adjust_path('mfile', root, mfile)
513
#tests for adding parent directories
514
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
515
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
516
mangle_tree.adjust_path('mfile2', newdir, mfile2)
517
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
518
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
519
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
520
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
522
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
523
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
524
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
525
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
526
self.assertEqual(file(mfile2_path).read(), 'later2')
527
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
528
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
529
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
530
self.assertEqual(file(newfile_path).read(), 'hello3')
531
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
532
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
533
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
535
def test_both_rename(self):
536
create_tree,root = self.get_transform()
537
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
538
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
540
mangle_tree,root = self.get_transform()
541
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
542
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
543
mangle_tree.adjust_path('test', root, selftest)
544
mangle_tree.adjust_path('test_too_much', root, selftest)
545
mangle_tree.set_executability(True, blackbox)
548
def test_both_rename2(self):
549
create_tree,root = self.get_transform()
550
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
551
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
552
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
553
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
556
mangle_tree,root = self.get_transform()
557
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
558
tests = mangle_tree.trans_id_tree_file_id('tests-id')
559
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
560
mangle_tree.adjust_path('selftest', bzrlib, tests)
561
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
562
mangle_tree.set_executability(True, test_too_much)
565
def test_both_rename3(self):
566
create_tree,root = self.get_transform()
567
tests = create_tree.new_directory('tests', root, 'tests-id')
568
create_tree.new_file('test_too_much.py', tests, 'hello1',
571
mangle_tree,root = self.get_transform()
572
tests = mangle_tree.trans_id_tree_file_id('tests-id')
573
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
574
mangle_tree.adjust_path('selftest', root, tests)
575
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
576
mangle_tree.set_executability(True, test_too_much)
579
def test_move_dangling_ie(self):
580
create_tree, root = self.get_transform()
582
root = create_tree.root
583
create_tree.new_file('name1', root, 'hello1', 'name1')
585
delete_contents, root = self.get_transform()
586
file = delete_contents.trans_id_tree_file_id('name1')
587
delete_contents.delete_contents(file)
588
delete_contents.apply()
589
move_id, root = self.get_transform()
590
name1 = move_id.trans_id_tree_file_id('name1')
591
newdir = move_id.new_directory('dir', root, 'newdir')
592
move_id.adjust_path('name2', newdir, name1)
595
def test_replace_dangling_ie(self):
596
create_tree, root = self.get_transform()
598
root = create_tree.root
599
create_tree.new_file('name1', root, 'hello1', 'name1')
601
delete_contents = TreeTransform(self.wt)
602
self.addCleanup(delete_contents.finalize)
603
file = delete_contents.trans_id_tree_file_id('name1')
604
delete_contents.delete_contents(file)
605
delete_contents.apply()
606
delete_contents.finalize()
607
replace = TreeTransform(self.wt)
608
self.addCleanup(replace.finalize)
609
name2 = replace.new_file('name2', root, 'hello2', 'name1')
610
conflicts = replace.find_conflicts()
611
name1 = replace.trans_id_tree_file_id('name1')
612
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
613
resolve_conflicts(replace)
616
def _test_symlinks(self, link_name1,link_target1,
617
link_name2, link_target2):
619
def ozpath(p): return 'oz/' + p
621
self.requireFeature(SymlinkFeature)
622
transform, root = self.get_transform()
623
oz_id = transform.new_directory('oz', root, 'oz-id')
624
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
626
wiz_id = transform.create_path(link_name2, oz_id)
627
transform.create_symlink(link_target2, wiz_id)
628
transform.version_file('wiz-id2', wiz_id)
629
transform.set_executability(True, wiz_id)
630
self.assertEqual(transform.find_conflicts(),
631
[('non-file executability', wiz_id)])
632
transform.set_executability(None, wiz_id)
634
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
635
self.assertEqual('symlink',
636
file_kind(self.wt.abspath(ozpath(link_name1))))
637
self.assertEqual(link_target2,
638
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
639
self.assertEqual(link_target1,
640
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
642
def test_symlinks(self):
643
self._test_symlinks('wizard', 'wizard-target',
644
'wizard2', 'behind_curtain')
646
def test_symlinks_unicode(self):
647
self.requireFeature(tests.UnicodeFilenameFeature)
648
self._test_symlinks(u'\N{Euro Sign}wizard',
649
u'wizard-targ\N{Euro Sign}t',
650
u'\N{Euro Sign}wizard2',
651
u'b\N{Euro Sign}hind_curtain')
653
def test_unable_create_symlink(self):
655
wt = self.make_branch_and_tree('.')
656
tt = TreeTransform(wt) # TreeTransform obtains write lock
658
tt.new_symlink('foo', tt.root, 'bar')
662
os_symlink = getattr(os, 'symlink', None)
665
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
667
"Unable to create symlink 'foo' on this platform",
671
os.symlink = os_symlink
673
def get_conflicted(self):
674
create,root = self.get_transform()
675
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
676
oz = create.new_directory('oz', root, 'oz-id')
677
create.new_directory('emeraldcity', oz, 'emerald-id')
679
conflicts,root = self.get_transform()
680
# set up duplicate entry, duplicate id
681
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
683
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
684
oz = conflicts.trans_id_tree_file_id('oz-id')
685
# set up DeletedParent parent conflict
686
conflicts.delete_versioned(oz)
687
emerald = conflicts.trans_id_tree_file_id('emerald-id')
688
# set up MissingParent conflict
689
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
690
conflicts.adjust_path('munchkincity', root, munchkincity)
691
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
693
conflicts.adjust_path('emeraldcity', emerald, emerald)
694
return conflicts, emerald, oz, old_dorothy, new_dorothy
696
def test_conflict_resolution(self):
697
conflicts, emerald, oz, old_dorothy, new_dorothy =\
698
self.get_conflicted()
699
resolve_conflicts(conflicts)
700
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
701
self.assertIs(conflicts.final_file_id(old_dorothy), None)
702
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
703
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
704
self.assertEqual(conflicts.final_parent(emerald), oz)
707
def test_cook_conflicts(self):
708
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
709
raw_conflicts = resolve_conflicts(tt)
710
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
711
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
712
'dorothy', None, 'dorothy-id')
713
self.assertEqual(cooked_conflicts[0], duplicate)
714
duplicate_id = DuplicateID('Unversioned existing file',
715
'dorothy.moved', 'dorothy', None,
717
self.assertEqual(cooked_conflicts[1], duplicate_id)
718
missing_parent = MissingParent('Created directory', 'munchkincity',
720
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
721
self.assertEqual(cooked_conflicts[2], missing_parent)
722
unversioned_parent = UnversionedParent('Versioned directory',
725
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
727
self.assertEqual(cooked_conflicts[3], unversioned_parent)
728
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
729
'oz/emeraldcity', 'emerald-id', 'emerald-id')
730
self.assertEqual(cooked_conflicts[4], deleted_parent)
731
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
732
self.assertEqual(cooked_conflicts[6], parent_loop)
733
self.assertEqual(len(cooked_conflicts), 7)
736
def test_string_conflicts(self):
737
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
738
raw_conflicts = resolve_conflicts(tt)
739
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
741
conflicts_s = [str(c) for c in cooked_conflicts]
742
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
743
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
744
'Moved existing file to '
746
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
747
'Unversioned existing file '
749
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
750
' munchkincity. Created directory.')
751
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
752
' versioned, but has versioned'
753
' children. Versioned directory.')
754
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
755
" is not empty. Not deleting.")
756
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
757
' versioned, but has versioned'
758
' children. Versioned directory.')
759
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
760
' oz/emeraldcity. Cancelled move.')
762
def prepare_wrong_parent_kind(self):
763
tt, root = self.get_transform()
764
tt.new_file('parent', root, 'contents', 'parent-id')
766
tt, root = self.get_transform()
767
parent_id = tt.trans_id_file_id('parent-id')
768
tt.new_file('child,', parent_id, 'contents2', 'file-id')
771
def test_find_conflicts_wrong_parent_kind(self):
772
tt = self.prepare_wrong_parent_kind()
775
def test_resolve_conflicts_wrong_existing_parent_kind(self):
776
tt = self.prepare_wrong_parent_kind()
777
raw_conflicts = resolve_conflicts(tt)
778
self.assertEqual(set([('non-directory parent', 'Created directory',
779
'new-3')]), raw_conflicts)
780
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
781
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
782
'parent-id')], cooked_conflicts)
784
self.assertEqual(None, self.wt.path2id('parent'))
785
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
787
def test_resolve_conflicts_wrong_new_parent_kind(self):
788
tt, root = self.get_transform()
789
parent_id = tt.new_directory('parent', root, 'parent-id')
790
tt.new_file('child,', parent_id, 'contents2', 'file-id')
792
tt, root = self.get_transform()
793
parent_id = tt.trans_id_file_id('parent-id')
794
tt.delete_contents(parent_id)
795
tt.create_file('contents', parent_id)
796
raw_conflicts = resolve_conflicts(tt)
797
self.assertEqual(set([('non-directory parent', 'Created directory',
798
'new-3')]), raw_conflicts)
800
self.assertEqual(None, self.wt.path2id('parent'))
801
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
803
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
804
tt, root = self.get_transform()
805
parent_id = tt.new_directory('parent', root)
806
tt.new_file('child,', parent_id, 'contents2')
808
tt, root = self.get_transform()
809
parent_id = tt.trans_id_tree_path('parent')
810
tt.delete_contents(parent_id)
811
tt.create_file('contents', parent_id)
812
resolve_conflicts(tt)
814
self.assertIs(None, self.wt.path2id('parent'))
815
self.assertIs(None, self.wt.path2id('parent.new'))
817
def test_moving_versioned_directories(self):
818
create, root = self.get_transform()
819
kansas = create.new_directory('kansas', root, 'kansas-id')
820
create.new_directory('house', kansas, 'house-id')
821
create.new_directory('oz', root, 'oz-id')
823
cyclone, root = self.get_transform()
824
oz = cyclone.trans_id_tree_file_id('oz-id')
825
house = cyclone.trans_id_tree_file_id('house-id')
826
cyclone.adjust_path('house', oz, house)
829
def test_moving_root(self):
830
create, root = self.get_transform()
831
fun = create.new_directory('fun', root, 'fun-id')
832
create.new_directory('sun', root, 'sun-id')
833
create.new_directory('moon', root, 'moon')
835
transform, root = self.get_transform()
836
transform.adjust_root_path('oldroot', fun)
837
new_root = transform.trans_id_tree_path('')
838
transform.version_file('new-root', new_root)
841
def test_renames(self):
842
create, root = self.get_transform()
843
old = create.new_directory('old-parent', root, 'old-id')
844
intermediate = create.new_directory('intermediate', old, 'im-id')
845
myfile = create.new_file('myfile', intermediate, 'myfile-text',
848
rename, root = self.get_transform()
849
old = rename.trans_id_file_id('old-id')
850
rename.adjust_path('new', root, old)
851
myfile = rename.trans_id_file_id('myfile-id')
852
rename.set_executability(True, myfile)
855
def test_set_executability_order(self):
856
"""Ensure that executability behaves the same, no matter what order.
858
- create file and set executability simultaneously
859
- create file and set executability afterward
860
- unsetting the executability of a file whose executability has not been
861
declared should throw an exception (this may happen when a
862
merge attempts to create a file with a duplicate ID)
864
transform, root = self.get_transform()
867
self.addCleanup(wt.unlock)
868
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
870
sac = transform.new_file('set_after_creation', root,
871
'Set after creation', 'sac')
872
transform.set_executability(True, sac)
873
uws = transform.new_file('unset_without_set', root, 'Unset badly',
875
self.assertRaises(KeyError, transform.set_executability, None, uws)
877
self.assertTrue(wt.is_executable('soc'))
878
self.assertTrue(wt.is_executable('sac'))
880
def test_preserve_mode(self):
881
"""File mode is preserved when replacing content"""
882
if sys.platform == 'win32':
883
raise TestSkipped('chmod has no effect on win32')
884
transform, root = self.get_transform()
885
transform.new_file('file1', root, 'contents', 'file1-id', True)
888
self.addCleanup(self.wt.unlock)
889
self.assertTrue(self.wt.is_executable('file1-id'))
890
transform, root = self.get_transform()
891
file1_id = transform.trans_id_tree_file_id('file1-id')
892
transform.delete_contents(file1_id)
893
transform.create_file('contents2', file1_id)
895
self.assertTrue(self.wt.is_executable('file1-id'))
897
def test__set_mode_stats_correctly(self):
898
"""_set_mode stats to determine file mode."""
899
if sys.platform == 'win32':
900
raise TestSkipped('chmod has no effect on win32')
904
def instrumented_stat(path):
905
stat_paths.append(path)
906
return real_stat(path)
908
transform, root = self.get_transform()
910
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
911
file_id='bar-id-1', executable=False)
914
transform, root = self.get_transform()
915
bar1_id = transform.trans_id_tree_path('bar')
916
bar2_id = transform.trans_id_tree_path('bar2')
918
os.stat = instrumented_stat
919
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
924
bar1_abspath = self.wt.abspath('bar')
925
self.assertEqual([bar1_abspath], stat_paths)
927
def test_iter_changes(self):
928
self.wt.set_root_id('eert_toor')
929
transform, root = self.get_transform()
930
transform.new_file('old', root, 'blah', 'id-1', True)
932
transform, root = self.get_transform()
934
self.assertEqual([], list(transform.iter_changes()))
935
old = transform.trans_id_tree_file_id('id-1')
936
transform.unversion_file(old)
937
self.assertEqual([('id-1', ('old', None), False, (True, False),
938
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
939
(True, True))], list(transform.iter_changes()))
940
transform.new_directory('new', root, 'id-1')
941
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
942
('eert_toor', 'eert_toor'), ('old', 'new'),
943
('file', 'directory'),
944
(True, False))], list(transform.iter_changes()))
948
def test_iter_changes_new(self):
949
self.wt.set_root_id('eert_toor')
950
transform, root = self.get_transform()
951
transform.new_file('old', root, 'blah')
953
transform, root = self.get_transform()
955
old = transform.trans_id_tree_path('old')
956
transform.version_file('id-1', old)
957
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
958
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
959
(False, False))], list(transform.iter_changes()))
963
def test_iter_changes_modifications(self):
964
self.wt.set_root_id('eert_toor')
965
transform, root = self.get_transform()
966
transform.new_file('old', root, 'blah', 'id-1')
967
transform.new_file('new', root, 'blah')
968
transform.new_directory('subdir', root, 'subdir-id')
970
transform, root = self.get_transform()
972
old = transform.trans_id_tree_path('old')
973
subdir = transform.trans_id_tree_file_id('subdir-id')
974
new = transform.trans_id_tree_path('new')
975
self.assertEqual([], list(transform.iter_changes()))
978
transform.delete_contents(old)
979
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
980
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
981
(False, False))], list(transform.iter_changes()))
984
transform.create_file('blah', old)
985
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
986
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
987
(False, False))], list(transform.iter_changes()))
988
transform.cancel_deletion(old)
989
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
990
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
991
(False, False))], list(transform.iter_changes()))
992
transform.cancel_creation(old)
994
# move file_id to a different file
995
self.assertEqual([], list(transform.iter_changes()))
996
transform.unversion_file(old)
997
transform.version_file('id-1', new)
998
transform.adjust_path('old', root, new)
999
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1000
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1001
(False, False))], list(transform.iter_changes()))
1002
transform.cancel_versioning(new)
1003
transform._removed_id = set()
1006
self.assertEqual([], list(transform.iter_changes()))
1007
transform.set_executability(True, old)
1008
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1009
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1010
(False, True))], list(transform.iter_changes()))
1011
transform.set_executability(None, old)
1014
self.assertEqual([], list(transform.iter_changes()))
1015
transform.adjust_path('new', root, old)
1016
transform._new_parent = {}
1017
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1018
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1019
(False, False))], list(transform.iter_changes()))
1020
transform._new_name = {}
1023
self.assertEqual([], list(transform.iter_changes()))
1024
transform.adjust_path('new', subdir, old)
1025
transform._new_name = {}
1026
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1027
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1028
('file', 'file'), (False, False))],
1029
list(transform.iter_changes()))
1030
transform._new_path = {}
1033
transform.finalize()
1035
def test_iter_changes_modified_bleed(self):
1036
self.wt.set_root_id('eert_toor')
1037
"""Modified flag should not bleed from one change to another"""
1038
# unfortunately, we have no guarantee that file1 (which is modified)
1039
# will be applied before file2. And if it's applied after file2, it
1040
# obviously can't bleed into file2's change output. But for now, it
1042
transform, root = self.get_transform()
1043
transform.new_file('file1', root, 'blah', 'id-1')
1044
transform.new_file('file2', root, 'blah', 'id-2')
1046
transform, root = self.get_transform()
1048
transform.delete_contents(transform.trans_id_file_id('id-1'))
1049
transform.set_executability(True,
1050
transform.trans_id_file_id('id-2'))
1051
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1052
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1053
('file', None), (False, False)),
1054
('id-2', (u'file2', u'file2'), False, (True, True),
1055
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1056
('file', 'file'), (False, True))],
1057
list(transform.iter_changes()))
1059
transform.finalize()
1061
def test_iter_changes_move_missing(self):
1062
"""Test moving ids with no files around"""
1063
self.wt.set_root_id('toor_eert')
1064
# Need two steps because versioning a non-existant file is a conflict.
1065
transform, root = self.get_transform()
1066
transform.new_directory('floater', root, 'floater-id')
1068
transform, root = self.get_transform()
1069
transform.delete_contents(transform.trans_id_tree_path('floater'))
1071
transform, root = self.get_transform()
1072
floater = transform.trans_id_tree_path('floater')
1074
transform.adjust_path('flitter', root, floater)
1075
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1076
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1077
(None, None), (False, False))], list(transform.iter_changes()))
1079
transform.finalize()
1081
def test_iter_changes_pointless(self):
1082
"""Ensure that no-ops are not treated as modifications"""
1083
self.wt.set_root_id('eert_toor')
1084
transform, root = self.get_transform()
1085
transform.new_file('old', root, 'blah', 'id-1')
1086
transform.new_directory('subdir', root, 'subdir-id')
1088
transform, root = self.get_transform()
1090
old = transform.trans_id_tree_path('old')
1091
subdir = transform.trans_id_tree_file_id('subdir-id')
1092
self.assertEqual([], list(transform.iter_changes()))
1093
transform.delete_contents(subdir)
1094
transform.create_directory(subdir)
1095
transform.set_executability(False, old)
1096
transform.unversion_file(old)
1097
transform.version_file('id-1', old)
1098
transform.adjust_path('old', root, old)
1099
self.assertEqual([], list(transform.iter_changes()))
1101
transform.finalize()
1103
def test_rename_count(self):
1104
transform, root = self.get_transform()
1105
transform.new_file('name1', root, 'contents')
1106
self.assertEqual(transform.rename_count, 0)
1108
self.assertEqual(transform.rename_count, 1)
1109
transform2, root = self.get_transform()
1110
transform2.adjust_path('name2', root,
1111
transform2.trans_id_tree_path('name1'))
1112
self.assertEqual(transform2.rename_count, 0)
1114
self.assertEqual(transform2.rename_count, 2)
1116
def test_change_parent(self):
1117
"""Ensure that after we change a parent, the results are still right.
1119
Renames and parent changes on pending transforms can happen as part
1120
of conflict resolution, and are explicitly permitted by the
1123
This test ensures they work correctly with the rename-avoidance
1126
transform, root = self.get_transform()
1127
parent1 = transform.new_directory('parent1', root)
1128
child1 = transform.new_file('child1', parent1, 'contents')
1129
parent2 = transform.new_directory('parent2', root)
1130
transform.adjust_path('child1', parent2, child1)
1132
self.failIfExists(self.wt.abspath('parent1/child1'))
1133
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1134
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1135
# no rename for child1 (counting only renames during apply)
1136
self.failUnlessEqual(2, transform.rename_count)
1138
def test_cancel_parent(self):
1139
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1141
This is like the test_change_parent, except that we cancel the parent
1142
before adjusting the path. The transform must detect that the
1143
directory is non-empty, and move children to safe locations.
1145
transform, root = self.get_transform()
1146
parent1 = transform.new_directory('parent1', root)
1147
child1 = transform.new_file('child1', parent1, 'contents')
1148
child2 = transform.new_file('child2', parent1, 'contents')
1150
transform.cancel_creation(parent1)
1152
self.fail('Failed to move child1 before deleting parent1')
1153
transform.cancel_creation(child2)
1154
transform.create_directory(parent1)
1156
transform.cancel_creation(parent1)
1157
# If the transform incorrectly believes that child2 is still in
1158
# parent1's limbo directory, it will try to rename it and fail
1159
# because was already moved by the first cancel_creation.
1161
self.fail('Transform still thinks child2 is a child of parent1')
1162
parent2 = transform.new_directory('parent2', root)
1163
transform.adjust_path('child1', parent2, child1)
1165
self.failIfExists(self.wt.abspath('parent1'))
1166
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1167
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1168
self.failUnlessEqual(2, transform.rename_count)
1170
def test_adjust_and_cancel(self):
1171
"""Make sure adjust_path keeps track of limbo children properly"""
1172
transform, root = self.get_transform()
1173
parent1 = transform.new_directory('parent1', root)
1174
child1 = transform.new_file('child1', parent1, 'contents')
1175
parent2 = transform.new_directory('parent2', root)
1176
transform.adjust_path('child1', parent2, child1)
1177
transform.cancel_creation(child1)
1179
transform.cancel_creation(parent1)
1180
# if the transform thinks child1 is still in parent1's limbo
1181
# directory, it will attempt to move it and fail.
1183
self.fail('Transform still thinks child1 is a child of parent1')
1184
transform.finalize()
1186
def test_noname_contents(self):
1187
"""TreeTransform should permit deferring naming files."""
1188
transform, root = self.get_transform()
1189
parent = transform.trans_id_file_id('parent-id')
1191
transform.create_directory(parent)
1193
self.fail("Can't handle contents with no name")
1194
transform.finalize()
1196
def test_noname_contents_nested(self):
1197
"""TreeTransform should permit deferring naming files."""
1198
transform, root = self.get_transform()
1199
parent = transform.trans_id_file_id('parent-id')
1201
transform.create_directory(parent)
1203
self.fail("Can't handle contents with no name")
1204
child = transform.new_directory('child', parent)
1205
transform.adjust_path('parent', root, parent)
1207
self.failUnlessExists(self.wt.abspath('parent/child'))
1208
self.assertEqual(1, transform.rename_count)
1210
def test_reuse_name(self):
1211
"""Avoid reusing the same limbo name for different files"""
1212
transform, root = self.get_transform()
1213
parent = transform.new_directory('parent', root)
1214
child1 = transform.new_directory('child', parent)
1216
child2 = transform.new_directory('child', parent)
1218
self.fail('Tranform tried to use the same limbo name twice')
1219
transform.adjust_path('child2', parent, child2)
1221
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1222
# child2 is put into top-level limbo because child1 has already
1223
# claimed the direct limbo path when child2 is created. There is no
1224
# advantage in renaming files once they're in top-level limbo, except
1226
self.assertEqual(2, transform.rename_count)
1228
def test_reuse_when_first_moved(self):
1229
"""Don't avoid direct paths when it is safe to use them"""
1230
transform, root = self.get_transform()
1231
parent = transform.new_directory('parent', root)
1232
child1 = transform.new_directory('child', parent)
1233
transform.adjust_path('child1', parent, child1)
1234
child2 = transform.new_directory('child', parent)
1236
# limbo/new-1 => parent
1237
self.assertEqual(1, transform.rename_count)
1239
def test_reuse_after_cancel(self):
1240
"""Don't avoid direct paths when it is safe to use them"""
1241
transform, root = self.get_transform()
1242
parent2 = transform.new_directory('parent2', root)
1243
child1 = transform.new_directory('child1', parent2)
1244
transform.cancel_creation(parent2)
1245
transform.create_directory(parent2)
1246
child2 = transform.new_directory('child1', parent2)
1247
transform.adjust_path('child2', parent2, child1)
1249
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1250
self.assertEqual(2, transform.rename_count)
1252
def test_finalize_order(self):
1253
"""Finalize must be done in child-to-parent order"""
1254
transform, root = self.get_transform()
1255
parent = transform.new_directory('parent', root)
1256
child = transform.new_directory('child', parent)
1258
transform.finalize()
1260
self.fail('Tried to remove parent before child1')
1262
def test_cancel_with_cancelled_child_should_succeed(self):
1263
transform, root = self.get_transform()
1264
parent = transform.new_directory('parent', root)
1265
child = transform.new_directory('child', parent)
1266
transform.cancel_creation(child)
1267
transform.cancel_creation(parent)
1268
transform.finalize()
1270
def test_rollback_on_directory_clash(self):
1272
wt = self.make_branch_and_tree('.')
1273
tt = TreeTransform(wt) # TreeTransform obtains write lock
1275
foo = tt.new_directory('foo', tt.root)
1276
tt.new_file('bar', foo, 'foobar')
1277
baz = tt.new_directory('baz', tt.root)
1278
tt.new_file('qux', baz, 'quux')
1279
# Ask for a rename 'foo' -> 'baz'
1280
tt.adjust_path('baz', tt.root, foo)
1281
# Lie to tt that we've already resolved all conflicts.
1282
tt.apply(no_conflicts=True)
1286
# The rename will fail because the target directory is not empty (but
1287
# raises FileExists anyway).
1288
err = self.assertRaises(errors.FileExists, tt_helper)
1289
self.assertContainsRe(str(err),
1290
"^File exists: .+/baz")
1292
def test_two_directories_clash(self):
1294
wt = self.make_branch_and_tree('.')
1295
tt = TreeTransform(wt) # TreeTransform obtains write lock
1297
foo_1 = tt.new_directory('foo', tt.root)
1298
tt.new_directory('bar', foo_1)
1299
# Adding the same directory with a different content
1300
foo_2 = tt.new_directory('foo', tt.root)
1301
tt.new_directory('baz', foo_2)
1302
# Lie to tt that we've already resolved all conflicts.
1303
tt.apply(no_conflicts=True)
1307
err = self.assertRaises(errors.FileExists, tt_helper)
1308
self.assertContainsRe(str(err),
1309
"^File exists: .+/foo")
1311
def test_two_directories_clash_finalize(self):
1313
wt = self.make_branch_and_tree('.')
1314
tt = TreeTransform(wt) # TreeTransform obtains write lock
1316
foo_1 = tt.new_directory('foo', tt.root)
1317
tt.new_directory('bar', foo_1)
1318
# Adding the same directory with a different content
1319
foo_2 = tt.new_directory('foo', tt.root)
1320
tt.new_directory('baz', foo_2)
1321
# Lie to tt that we've already resolved all conflicts.
1322
tt.apply(no_conflicts=True)
1326
err = self.assertRaises(errors.FileExists, tt_helper)
1327
self.assertContainsRe(str(err),
1328
"^File exists: .+/foo")
1330
def test_file_to_directory(self):
1331
wt = self.make_branch_and_tree('.')
1332
self.build_tree(['foo'])
1335
tt = TreeTransform(wt)
1336
self.addCleanup(tt.finalize)
1337
foo_trans_id = tt.trans_id_tree_path("foo")
1338
tt.delete_contents(foo_trans_id)
1339
tt.create_directory(foo_trans_id)
1340
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1341
tt.create_file(["aa\n"], bar_trans_id)
1342
tt.version_file("bar-1", bar_trans_id)
1344
self.failUnlessExists("foo/bar")
1347
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1352
changes = wt.changes_from(wt.basis_tree())
1353
self.assertFalse(changes.has_changed(), changes)
1355
def test_file_to_symlink(self):
1356
self.requireFeature(SymlinkFeature)
1357
wt = self.make_branch_and_tree('.')
1358
self.build_tree(['foo'])
1361
tt = TreeTransform(wt)
1362
self.addCleanup(tt.finalize)
1363
foo_trans_id = tt.trans_id_tree_path("foo")
1364
tt.delete_contents(foo_trans_id)
1365
tt.create_symlink("bar", foo_trans_id)
1367
self.failUnlessExists("foo")
1369
self.addCleanup(wt.unlock)
1370
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1373
def test_dir_to_file(self):
1374
wt = self.make_branch_and_tree('.')
1375
self.build_tree(['foo/', 'foo/bar'])
1376
wt.add(['foo', 'foo/bar'])
1378
tt = TreeTransform(wt)
1379
self.addCleanup(tt.finalize)
1380
foo_trans_id = tt.trans_id_tree_path("foo")
1381
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1382
tt.delete_contents(foo_trans_id)
1383
tt.delete_versioned(bar_trans_id)
1384
tt.create_file(["aa\n"], foo_trans_id)
1386
self.failUnlessExists("foo")
1388
self.addCleanup(wt.unlock)
1389
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1392
def test_dir_to_hardlink(self):
1393
self.requireFeature(HardlinkFeature)
1394
wt = self.make_branch_and_tree('.')
1395
self.build_tree(['foo/', 'foo/bar'])
1396
wt.add(['foo', 'foo/bar'])
1398
tt = TreeTransform(wt)
1399
self.addCleanup(tt.finalize)
1400
foo_trans_id = tt.trans_id_tree_path("foo")
1401
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1402
tt.delete_contents(foo_trans_id)
1403
tt.delete_versioned(bar_trans_id)
1404
self.build_tree(['baz'])
1405
tt.create_hardlink("baz", foo_trans_id)
1407
self.failUnlessExists("foo")
1408
self.failUnlessExists("baz")
1410
self.addCleanup(wt.unlock)
1411
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1414
def test_no_final_path(self):
1415
transform, root = self.get_transform()
1416
trans_id = transform.trans_id_file_id('foo')
1417
transform.create_file('bar', trans_id)
1418
transform.cancel_creation(trans_id)
1421
def test_create_from_tree(self):
1422
tree1 = self.make_branch_and_tree('tree1')
1423
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1424
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1425
tree2 = self.make_branch_and_tree('tree2')
1426
tt = TreeTransform(tree2)
1427
foo_trans_id = tt.create_path('foo', tt.root)
1428
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1429
bar_trans_id = tt.create_path('bar', tt.root)
1430
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1432
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1433
self.assertFileEqual('baz', 'tree2/bar')
1435
def test_create_from_tree_bytes(self):
1436
"""Provided lines are used instead of tree content."""
1437
tree1 = self.make_branch_and_tree('tree1')
1438
self.build_tree_contents([('tree1/foo', 'bar'),])
1439
tree1.add('foo', 'foo-id')
1440
tree2 = self.make_branch_and_tree('tree2')
1441
tt = TreeTransform(tree2)
1442
foo_trans_id = tt.create_path('foo', tt.root)
1443
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1445
self.assertFileEqual('qux', 'tree2/foo')
1447
def test_create_from_tree_symlink(self):
1448
self.requireFeature(SymlinkFeature)
1449
tree1 = self.make_branch_and_tree('tree1')
1450
os.symlink('bar', 'tree1/foo')
1451
tree1.add('foo', 'foo-id')
1452
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1453
foo_trans_id = tt.create_path('foo', tt.root)
1454
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1456
self.assertEqual('bar', os.readlink('tree2/foo'))
1459
89
class TransformGroup(object):
1461
91
def __init__(self, dirname, root_id):
1462
92
self.name = dirname
1463
93
os.mkdir(dirname)
1464
self.wt = BzrDir.create_standalone_workingtree(dirname)
94
self.wt = ControlDir.create_standalone_workingtree(dirname)
1465
95
self.wt.set_root_id(root_id)
1466
96
self.b = self.wt.branch
1467
self.tt = TreeTransform(self.wt)
1468
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
97
self.tt = self.wt.transform()
98
self.root = self.tt.trans_id_tree_path('')
1471
101
def conflict_text(tree, merge):
1472
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1473
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
102
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
103
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1476
106
class TestTransformMerge(TestCaseInTempDir):
2255
912
def test_rollback_rename(self):
2256
913
tree = self.make_branch_and_tree('.')
2257
914
self.build_tree(['a/', 'a/b'])
2258
tt = TreeTransform(tree)
915
tt = tree.transform()
2259
916
self.addCleanup(tt.finalize)
2260
917
a_id = tt.trans_id_tree_path('a')
2261
918
tt.adjust_path('c', tt.root, a_id)
2262
919
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2263
920
self.assertRaises(Bogus, tt.apply,
2264
921
_mover=self.ExceptionFileMover(bad_source='a'))
2265
self.failUnlessExists('a')
2266
self.failUnlessExists('a/b')
922
self.assertPathExists('a')
923
self.assertPathExists('a/b')
2268
self.failUnlessExists('c')
2269
self.failUnlessExists('c/d')
925
self.assertPathExists('c')
926
self.assertPathExists('c/d')
2271
928
def test_rollback_rename_into_place(self):
2272
929
tree = self.make_branch_and_tree('.')
2273
930
self.build_tree(['a/', 'a/b'])
2274
tt = TreeTransform(tree)
931
tt = tree.transform()
2275
932
self.addCleanup(tt.finalize)
2276
933
a_id = tt.trans_id_tree_path('a')
2277
934
tt.adjust_path('c', tt.root, a_id)
2278
935
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2279
936
self.assertRaises(Bogus, tt.apply,
2280
937
_mover=self.ExceptionFileMover(bad_target='c/d'))
2281
self.failUnlessExists('a')
2282
self.failUnlessExists('a/b')
938
self.assertPathExists('a')
939
self.assertPathExists('a/b')
2284
self.failUnlessExists('c')
2285
self.failUnlessExists('c/d')
941
self.assertPathExists('c')
942
self.assertPathExists('c/d')
2287
944
def test_rollback_deletion(self):
2288
945
tree = self.make_branch_and_tree('.')
2289
946
self.build_tree(['a/', 'a/b'])
2290
tt = TreeTransform(tree)
947
tt = tree.transform()
2291
948
self.addCleanup(tt.finalize)
2292
949
a_id = tt.trans_id_tree_path('a')
2293
950
tt.delete_contents(a_id)
2294
951
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2295
952
self.assertRaises(Bogus, tt.apply,
2296
953
_mover=self.ExceptionFileMover(bad_target='d'))
2297
self.failUnlessExists('a')
2298
self.failUnlessExists('a/b')
2300
def test_resolve_no_parent(self):
954
self.assertPathExists('a')
955
self.assertPathExists('a/b')
958
class TestFinalizeRobustness(tests.TestCaseWithTransport):
959
"""Ensure treetransform creation errors can be safely cleaned up after"""
961
def _override_globals_in_method(self, instance, method_name, globals):
962
"""Replace method on instance with one with updated globals"""
964
func = getattr(instance, method_name).__func__
965
new_globals = dict(func.__globals__)
966
new_globals.update(globals)
967
new_func = types.FunctionType(func.__code__, new_globals,
968
func.__name__, func.__defaults__)
969
setattr(instance, method_name,
970
types.MethodType(new_func, instance))
971
self.addCleanup(delattr, instance, method_name)
974
def _fake_open_raises_before(name, mode):
975
"""Like open() but raises before doing anything"""
979
def _fake_open_raises_after(name, mode):
980
"""Like open() but raises after creating file without returning"""
981
open(name, mode).close()
984
def create_transform_and_root_trans_id(self):
985
"""Setup a transform creating a file in limbo"""
986
tree = self.make_branch_and_tree('.')
987
tt = tree.transform()
988
return tt, tt.create_path("a", tt.root)
990
def create_transform_and_subdir_trans_id(self):
991
"""Setup a transform creating a directory containing a file in limbo"""
992
tree = self.make_branch_and_tree('.')
993
tt = tree.transform()
994
d_trans_id = tt.create_path("d", tt.root)
995
tt.create_directory(d_trans_id)
996
f_trans_id = tt.create_path("a", d_trans_id)
997
tt.adjust_path("a", d_trans_id, f_trans_id)
998
return tt, f_trans_id
1000
def test_root_create_file_open_raises_before_creation(self):
1001
tt, trans_id = self.create_transform_and_root_trans_id()
1002
self._override_globals_in_method(
1003
tt, "create_file", {"open": self._fake_open_raises_before})
1004
self.assertRaises(RuntimeError, tt.create_file,
1005
[b"contents"], trans_id)
1006
path = tt._limbo_name(trans_id)
1007
self.assertPathDoesNotExist(path)
1009
self.assertPathDoesNotExist(tt._limbodir)
1011
def test_root_create_file_open_raises_after_creation(self):
1012
tt, trans_id = self.create_transform_and_root_trans_id()
1013
self._override_globals_in_method(
1014
tt, "create_file", {"open": self._fake_open_raises_after})
1015
self.assertRaises(RuntimeError, tt.create_file,
1016
[b"contents"], trans_id)
1017
path = tt._limbo_name(trans_id)
1018
self.assertPathExists(path)
1020
self.assertPathDoesNotExist(path)
1021
self.assertPathDoesNotExist(tt._limbodir)
1023
def test_subdir_create_file_open_raises_before_creation(self):
1024
tt, trans_id = self.create_transform_and_subdir_trans_id()
1025
self._override_globals_in_method(
1026
tt, "create_file", {"open": self._fake_open_raises_before})
1027
self.assertRaises(RuntimeError, tt.create_file,
1028
[b"contents"], trans_id)
1029
path = tt._limbo_name(trans_id)
1030
self.assertPathDoesNotExist(path)
1032
self.assertPathDoesNotExist(tt._limbodir)
1034
def test_subdir_create_file_open_raises_after_creation(self):
1035
tt, trans_id = self.create_transform_and_subdir_trans_id()
1036
self._override_globals_in_method(
1037
tt, "create_file", {"open": self._fake_open_raises_after})
1038
self.assertRaises(RuntimeError, tt.create_file,
1039
[b"contents"], trans_id)
1040
path = tt._limbo_name(trans_id)
1041
self.assertPathExists(path)
1043
self.assertPathDoesNotExist(path)
1044
self.assertPathDoesNotExist(tt._limbodir)
1046
def test_rename_in_limbo_rename_raises_after_rename(self):
1047
tt, trans_id = self.create_transform_and_root_trans_id()
1048
parent1 = tt.new_directory('parent1', tt.root)
1049
child1 = tt.new_file('child1', parent1, [b'contents'])
1050
parent2 = tt.new_directory('parent2', tt.root)
1052
class FakeOSModule(object):
1053
def rename(self, old, new):
1056
self._override_globals_in_method(tt, "_rename_in_limbo",
1057
{"os": FakeOSModule()})
1059
RuntimeError, tt.adjust_path, "child1", parent2, child1)
1060
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
1061
self.assertPathExists(path)
1063
self.assertPathDoesNotExist(path)
1064
self.assertPathDoesNotExist(tt._limbodir)
1066
def test_rename_in_limbo_rename_raises_before_rename(self):
1067
tt, trans_id = self.create_transform_and_root_trans_id()
1068
parent1 = tt.new_directory('parent1', tt.root)
1069
child1 = tt.new_file('child1', parent1, [b'contents'])
1070
parent2 = tt.new_directory('parent2', tt.root)
1072
class FakeOSModule(object):
1073
def rename(self, old, new):
1075
self._override_globals_in_method(tt, "_rename_in_limbo",
1076
{"os": FakeOSModule()})
1078
RuntimeError, tt.adjust_path, "child1", parent2, child1)
1079
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
1080
self.assertPathExists(path)
1082
self.assertPathDoesNotExist(path)
1083
self.assertPathDoesNotExist(tt._limbodir)
1086
class TestTransformMissingParent(tests.TestCaseWithTransport):
1088
def make_tt_with_versioned_dir(self):
2301
1089
wt = self.make_branch_and_tree('.')
2302
tt = TreeTransform(wt)
2303
self.addCleanup(tt.finalize)
2304
parent = tt.trans_id_file_id('parent-id')
2305
tt.new_file('file', parent, 'Contents')
2306
resolve_conflicts(tt)
2309
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2310
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2312
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2313
('', ''), ('directory', 'directory'), (False, None))
2316
class TestTransformPreview(tests.TestCaseWithTransport):
2318
def create_tree(self):
2319
tree = self.make_branch_and_tree('.')
2320
self.build_tree_contents([('a', 'content 1')])
2321
tree.set_root_id('TREE_ROOT')
2322
tree.add('a', 'a-id')
2323
tree.commit('rev1', rev_id='rev1')
2324
return tree.branch.repository.revision_tree('rev1')
2326
def get_empty_preview(self):
2327
repository = self.make_repository('repo')
2328
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2329
preview = TransformPreview(tree)
2330
self.addCleanup(preview.finalize)
2333
def test_transform_preview(self):
2334
revision_tree = self.create_tree()
2335
preview = TransformPreview(revision_tree)
2336
self.addCleanup(preview.finalize)
2338
def test_transform_preview_tree(self):
2339
revision_tree = self.create_tree()
2340
preview = TransformPreview(revision_tree)
2341
self.addCleanup(preview.finalize)
2342
preview.get_preview_tree()
2344
def test_transform_new_file(self):
2345
revision_tree = self.create_tree()
2346
preview = TransformPreview(revision_tree)
2347
self.addCleanup(preview.finalize)
2348
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2349
preview_tree = preview.get_preview_tree()
2350
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2352
preview_tree.get_file('file2-id').read(), 'content B\n')
2354
def test_diff_preview_tree(self):
2355
revision_tree = self.create_tree()
2356
preview = TransformPreview(revision_tree)
2357
self.addCleanup(preview.finalize)
2358
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2359
preview_tree = preview.get_preview_tree()
2361
show_diff_trees(revision_tree, preview_tree, out)
2362
lines = out.getvalue().splitlines()
2363
self.assertEqual(lines[0], "=== added file 'file2'")
2364
# 3 lines of diff administrivia
2365
self.assertEqual(lines[4], "+content B")
2367
def test_transform_conflicts(self):
2368
revision_tree = self.create_tree()
2369
preview = TransformPreview(revision_tree)
2370
self.addCleanup(preview.finalize)
2371
preview.new_file('a', preview.root, 'content 2')
2372
resolve_conflicts(preview)
2373
trans_id = preview.trans_id_file_id('a-id')
2374
self.assertEqual('a.moved', preview.final_name(trans_id))
2376
def get_tree_and_preview_tree(self):
2377
revision_tree = self.create_tree()
2378
preview = TransformPreview(revision_tree)
2379
self.addCleanup(preview.finalize)
2380
a_trans_id = preview.trans_id_file_id('a-id')
2381
preview.delete_contents(a_trans_id)
2382
preview.create_file('b content', a_trans_id)
2383
preview_tree = preview.get_preview_tree()
2384
return revision_tree, preview_tree
2386
def test_iter_changes(self):
2387
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2388
root = revision_tree.inventory.root.file_id
2389
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2390
(root, root), ('a', 'a'), ('file', 'file'),
2392
list(preview_tree.iter_changes(revision_tree)))
2394
def test_include_unchanged_succeeds(self):
2395
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2396
changes = preview_tree.iter_changes(revision_tree,
2397
include_unchanged=True)
2398
root = revision_tree.inventory.root.file_id
2400
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2402
def test_specific_files(self):
2403
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2404
changes = preview_tree.iter_changes(revision_tree,
2405
specific_files=[''])
2406
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2408
def test_want_unversioned(self):
2409
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2410
changes = preview_tree.iter_changes(revision_tree,
2411
want_unversioned=True)
2412
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2414
def test_ignore_extra_trees_no_specific_files(self):
2415
# extra_trees is harmless without specific_files, so we'll silently
2416
# accept it, even though we won't use it.
2417
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2418
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2420
def test_ignore_require_versioned_no_specific_files(self):
2421
# require_versioned is meaningless without specific_files.
2422
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2423
preview_tree.iter_changes(revision_tree, require_versioned=False)
2425
def test_ignore_pb(self):
2426
# pb could be supported, but TT.iter_changes doesn't support it.
2427
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2428
preview_tree.iter_changes(revision_tree)
2430
def test_kind(self):
2431
revision_tree = self.create_tree()
2432
preview = TransformPreview(revision_tree)
2433
self.addCleanup(preview.finalize)
2434
preview.new_file('file', preview.root, 'contents', 'file-id')
2435
preview.new_directory('directory', preview.root, 'dir-id')
2436
preview_tree = preview.get_preview_tree()
2437
self.assertEqual('file', preview_tree.kind('file-id'))
2438
self.assertEqual('directory', preview_tree.kind('dir-id'))
2440
def test_get_file_mtime(self):
2441
preview = self.get_empty_preview()
2442
file_trans_id = preview.new_file('file', preview.root, 'contents',
2444
limbo_path = preview._limbo_name(file_trans_id)
2445
preview_tree = preview.get_preview_tree()
2446
self.assertEqual(os.stat(limbo_path).st_mtime,
2447
preview_tree.get_file_mtime('file-id'))
2449
def test_get_file_mtime_renamed(self):
2450
work_tree = self.make_branch_and_tree('tree')
2451
self.build_tree(['tree/file'])
2452
work_tree.add('file', 'file-id')
2453
preview = TransformPreview(work_tree)
2454
self.addCleanup(preview.finalize)
2455
file_trans_id = preview.trans_id_tree_file_id('file-id')
2456
preview.adjust_path('renamed', preview.root, file_trans_id)
2457
preview_tree = preview.get_preview_tree()
2458
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2459
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2461
def test_get_file(self):
2462
preview = self.get_empty_preview()
2463
preview.new_file('file', preview.root, 'contents', 'file-id')
2464
preview_tree = preview.get_preview_tree()
2465
tree_file = preview_tree.get_file('file-id')
2467
self.assertEqual('contents', tree_file.read())
2471
def test_get_symlink_target(self):
2472
self.requireFeature(SymlinkFeature)
2473
preview = self.get_empty_preview()
2474
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2475
preview_tree = preview.get_preview_tree()
2476
self.assertEqual('target',
2477
preview_tree.get_symlink_target('symlink-id'))
2479
def test_all_file_ids(self):
2480
tree = self.make_branch_and_tree('tree')
2481
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2482
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2483
preview = TransformPreview(tree)
2484
self.addCleanup(preview.finalize)
2485
preview.unversion_file(preview.trans_id_file_id('b-id'))
2486
c_trans_id = preview.trans_id_file_id('c-id')
2487
preview.unversion_file(c_trans_id)
2488
preview.version_file('c-id', c_trans_id)
2489
preview_tree = preview.get_preview_tree()
2490
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2491
preview_tree.all_file_ids())
2493
def test_path2id_deleted_unchanged(self):
2494
tree = self.make_branch_and_tree('tree')
2495
self.build_tree(['tree/unchanged', 'tree/deleted'])
2496
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2497
preview = TransformPreview(tree)
2498
self.addCleanup(preview.finalize)
2499
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2500
preview_tree = preview.get_preview_tree()
2501
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2502
self.assertIs(None, preview_tree.path2id('deleted'))
2504
def test_path2id_created(self):
2505
tree = self.make_branch_and_tree('tree')
2506
self.build_tree(['tree/unchanged'])
2507
tree.add(['unchanged'], ['unchanged-id'])
2508
preview = TransformPreview(tree)
2509
self.addCleanup(preview.finalize)
2510
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2511
'contents', 'new-id')
2512
preview_tree = preview.get_preview_tree()
2513
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2515
def test_path2id_moved(self):
2516
tree = self.make_branch_and_tree('tree')
2517
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2518
tree.add(['old_parent', 'old_parent/child'],
2519
['old_parent-id', 'child-id'])
2520
preview = TransformPreview(tree)
2521
self.addCleanup(preview.finalize)
2522
new_parent = preview.new_directory('new_parent', preview.root,
2524
preview.adjust_path('child', new_parent,
2525
preview.trans_id_file_id('child-id'))
2526
preview_tree = preview.get_preview_tree()
2527
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2528
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2530
def test_path2id_renamed_parent(self):
2531
tree = self.make_branch_and_tree('tree')
2532
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2533
tree.add(['old_name', 'old_name/child'],
2534
['parent-id', 'child-id'])
2535
preview = TransformPreview(tree)
2536
self.addCleanup(preview.finalize)
2537
preview.adjust_path('new_name', preview.root,
2538
preview.trans_id_file_id('parent-id'))
2539
preview_tree = preview.get_preview_tree()
2540
self.assertIs(None, preview_tree.path2id('old_name/child'))
2541
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2543
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2544
preview_tree = tt.get_preview_tree()
2545
preview_result = list(preview_tree.iter_entries_by_dir(
2549
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2550
self.assertEqual(actual_result, preview_result)
2552
def test_iter_entries_by_dir_new(self):
2553
tree = self.make_branch_and_tree('tree')
2554
tt = TreeTransform(tree)
2555
tt.new_file('new', tt.root, 'contents', 'new-id')
2556
self.assertMatchingIterEntries(tt)
2558
def test_iter_entries_by_dir_deleted(self):
2559
tree = self.make_branch_and_tree('tree')
2560
self.build_tree(['tree/deleted'])
2561
tree.add('deleted', 'deleted-id')
2562
tt = TreeTransform(tree)
2563
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2564
self.assertMatchingIterEntries(tt)
2566
def test_iter_entries_by_dir_unversioned(self):
2567
tree = self.make_branch_and_tree('tree')
2568
self.build_tree(['tree/removed'])
2569
tree.add('removed', 'removed-id')
2570
tt = TreeTransform(tree)
2571
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2572
self.assertMatchingIterEntries(tt)
2574
def test_iter_entries_by_dir_moved(self):
2575
tree = self.make_branch_and_tree('tree')
2576
self.build_tree(['tree/moved', 'tree/new_parent/'])
2577
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2578
tt = TreeTransform(tree)
2579
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2580
tt.trans_id_file_id('moved-id'))
2581
self.assertMatchingIterEntries(tt)
2583
def test_iter_entries_by_dir_specific_file_ids(self):
2584
tree = self.make_branch_and_tree('tree')
2585
tree.set_root_id('tree-root-id')
2586
self.build_tree(['tree/parent/', 'tree/parent/child'])
2587
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2588
tt = TreeTransform(tree)
2589
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2591
def test_symlink_content_summary(self):
2592
self.requireFeature(SymlinkFeature)
2593
preview = self.get_empty_preview()
2594
preview.new_symlink('path', preview.root, 'target', 'path-id')
2595
summary = preview.get_preview_tree().path_content_summary('path')
2596
self.assertEqual(('symlink', None, None, 'target'), summary)
2598
def test_missing_content_summary(self):
2599
preview = self.get_empty_preview()
2600
summary = preview.get_preview_tree().path_content_summary('path')
2601
self.assertEqual(('missing', None, None, None), summary)
2603
def test_deleted_content_summary(self):
2604
tree = self.make_branch_and_tree('tree')
2605
self.build_tree(['tree/path/'])
2607
preview = TransformPreview(tree)
2608
self.addCleanup(preview.finalize)
2609
preview.delete_contents(preview.trans_id_tree_path('path'))
2610
summary = preview.get_preview_tree().path_content_summary('path')
2611
self.assertEqual(('missing', None, None, None), summary)
2613
def test_file_content_summary_executable(self):
2614
preview = self.get_empty_preview()
2615
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2616
preview.set_executability(True, path_id)
2617
summary = preview.get_preview_tree().path_content_summary('path')
2618
self.assertEqual(4, len(summary))
2619
self.assertEqual('file', summary[0])
2620
# size must be known
2621
self.assertEqual(len('contents'), summary[1])
2623
self.assertEqual(True, summary[2])
2624
# will not have hash (not cheap to determine)
2625
self.assertIs(None, summary[3])
2627
def test_change_executability(self):
2628
tree = self.make_branch_and_tree('tree')
2629
self.build_tree(['tree/path'])
2631
preview = TransformPreview(tree)
2632
self.addCleanup(preview.finalize)
2633
path_id = preview.trans_id_tree_path('path')
2634
preview.set_executability(True, path_id)
2635
summary = preview.get_preview_tree().path_content_summary('path')
2636
self.assertEqual(True, summary[2])
2638
def test_file_content_summary_non_exec(self):
2639
preview = self.get_empty_preview()
2640
preview.new_file('path', preview.root, 'contents', 'path-id')
2641
summary = preview.get_preview_tree().path_content_summary('path')
2642
self.assertEqual(4, len(summary))
2643
self.assertEqual('file', summary[0])
2644
# size must be known
2645
self.assertEqual(len('contents'), summary[1])
2647
self.assertEqual(False, summary[2])
2648
# will not have hash (not cheap to determine)
2649
self.assertIs(None, summary[3])
2651
def test_dir_content_summary(self):
2652
preview = self.get_empty_preview()
2653
preview.new_directory('path', preview.root, 'path-id')
2654
summary = preview.get_preview_tree().path_content_summary('path')
2655
self.assertEqual(('directory', None, None, None), summary)
2657
def test_tree_content_summary(self):
2658
preview = self.get_empty_preview()
2659
path = preview.new_directory('path', preview.root, 'path-id')
2660
preview.set_tree_reference('rev-1', path)
2661
summary = preview.get_preview_tree().path_content_summary('path')
2662
self.assertEqual(4, len(summary))
2663
self.assertEqual('tree-reference', summary[0])
2665
def test_annotate(self):
2666
tree = self.make_branch_and_tree('tree')
2667
self.build_tree_contents([('tree/file', 'a\n')])
2668
tree.add('file', 'file-id')
2669
tree.commit('a', rev_id='one')
2670
self.build_tree_contents([('tree/file', 'a\nb\n')])
2671
preview = TransformPreview(tree)
2672
self.addCleanup(preview.finalize)
2673
file_trans_id = preview.trans_id_file_id('file-id')
2674
preview.delete_contents(file_trans_id)
2675
preview.create_file('a\nb\nc\n', file_trans_id)
2676
preview_tree = preview.get_preview_tree()
2682
annotation = preview_tree.annotate_iter('file-id', 'me:')
2683
self.assertEqual(expected, annotation)
2685
def test_annotate_missing(self):
2686
preview = self.get_empty_preview()
2687
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2688
preview_tree = preview.get_preview_tree()
2694
annotation = preview_tree.annotate_iter('file-id', 'me:')
2695
self.assertEqual(expected, annotation)
2697
def test_annotate_rename(self):
2698
tree = self.make_branch_and_tree('tree')
2699
self.build_tree_contents([('tree/file', 'a\n')])
2700
tree.add('file', 'file-id')
2701
tree.commit('a', rev_id='one')
2702
preview = TransformPreview(tree)
2703
self.addCleanup(preview.finalize)
2704
file_trans_id = preview.trans_id_file_id('file-id')
2705
preview.adjust_path('newname', preview.root, file_trans_id)
2706
preview_tree = preview.get_preview_tree()
2710
annotation = preview_tree.annotate_iter('file-id', 'me:')
2711
self.assertEqual(expected, annotation)
2713
def test_annotate_deleted(self):
2714
tree = self.make_branch_and_tree('tree')
2715
self.build_tree_contents([('tree/file', 'a\n')])
2716
tree.add('file', 'file-id')
2717
tree.commit('a', rev_id='one')
2718
self.build_tree_contents([('tree/file', 'a\nb\n')])
2719
preview = TransformPreview(tree)
2720
self.addCleanup(preview.finalize)
2721
file_trans_id = preview.trans_id_file_id('file-id')
2722
preview.delete_contents(file_trans_id)
2723
preview_tree = preview.get_preview_tree()
2724
annotation = preview_tree.annotate_iter('file-id', 'me:')
2725
self.assertIs(None, annotation)
2727
def test_stored_kind(self):
2728
preview = self.get_empty_preview()
2729
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2730
preview_tree = preview.get_preview_tree()
2731
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2733
def test_is_executable(self):
2734
preview = self.get_empty_preview()
2735
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2736
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2737
preview_tree = preview.get_preview_tree()
2738
self.assertEqual(True, preview_tree.is_executable('file-id'))
2740
def test_get_set_parent_ids(self):
2741
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2742
self.assertEqual([], preview_tree.get_parent_ids())
2743
preview_tree.set_parent_ids(['rev-1'])
2744
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2746
def test_plan_file_merge(self):
2747
work_a = self.make_branch_and_tree('wta')
2748
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2749
work_a.add('file', 'file-id')
2750
base_id = work_a.commit('base version')
2751
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2752
preview = TransformPreview(work_a)
2753
self.addCleanup(preview.finalize)
2754
trans_id = preview.trans_id_file_id('file-id')
2755
preview.delete_contents(trans_id)
2756
preview.create_file('b\nc\nd\ne\n', trans_id)
2757
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2758
tree_a = preview.get_preview_tree()
2759
tree_a.set_parent_ids([base_id])
2761
('killed-a', 'a\n'),
2762
('killed-b', 'b\n'),
2763
('unchanged', 'c\n'),
2764
('unchanged', 'd\n'),
2767
], list(tree_a.plan_file_merge('file-id', tree_b)))
2769
def test_plan_file_merge_revision_tree(self):
2770
work_a = self.make_branch_and_tree('wta')
2771
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2772
work_a.add('file', 'file-id')
2773
base_id = work_a.commit('base version')
2774
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2775
preview = TransformPreview(work_a.basis_tree())
2776
self.addCleanup(preview.finalize)
2777
trans_id = preview.trans_id_file_id('file-id')
2778
preview.delete_contents(trans_id)
2779
preview.create_file('b\nc\nd\ne\n', trans_id)
2780
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2781
tree_a = preview.get_preview_tree()
2782
tree_a.set_parent_ids([base_id])
2784
('killed-a', 'a\n'),
2785
('killed-b', 'b\n'),
2786
('unchanged', 'c\n'),
2787
('unchanged', 'd\n'),
2790
], list(tree_a.plan_file_merge('file-id', tree_b)))
2792
def test_walkdirs(self):
2793
preview = self.get_empty_preview()
2794
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2795
# FIXME: new_directory should mark root.
2796
preview.fixup_new_roots()
2797
preview_tree = preview.get_preview_tree()
2798
file_trans_id = preview.new_file('a', preview.root, 'contents',
2800
expected = [(('', 'tree-root'),
2801
[('a', 'a', 'file', None, 'a-id', 'file')])]
2802
self.assertEqual(expected, list(preview_tree.walkdirs()))
2804
def test_extras(self):
2805
work_tree = self.make_branch_and_tree('tree')
2806
self.build_tree(['tree/removed-file', 'tree/existing-file',
2807
'tree/not-removed-file'])
2808
work_tree.add(['removed-file', 'not-removed-file'])
2809
preview = TransformPreview(work_tree)
2810
self.addCleanup(preview.finalize)
2811
preview.new_file('new-file', preview.root, 'contents')
2812
preview.new_file('new-versioned-file', preview.root, 'contents',
2814
tree = preview.get_preview_tree()
2815
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2816
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2819
def test_merge_into_preview(self):
2820
work_tree = self.make_branch_and_tree('tree')
2821
self.build_tree_contents([('tree/file','b\n')])
2822
work_tree.add('file', 'file-id')
2823
work_tree.commit('first commit')
2824
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2825
self.build_tree_contents([('child/file','b\nc\n')])
2826
child_tree.commit('child commit')
2827
child_tree.lock_write()
2828
self.addCleanup(child_tree.unlock)
2829
work_tree.lock_write()
2830
self.addCleanup(work_tree.unlock)
2831
preview = TransformPreview(work_tree)
2832
self.addCleanup(preview.finalize)
2833
file_trans_id = preview.trans_id_file_id('file-id')
2834
preview.delete_contents(file_trans_id)
2835
preview.create_file('a\nb\n', file_trans_id)
2836
preview_tree = preview.get_preview_tree()
2837
merger = Merger.from_revision_ids(None, preview_tree,
2838
child_tree.branch.last_revision(),
2839
other_branch=child_tree.branch,
2840
tree_branch=work_tree.branch)
2841
merger.merge_type = Merge3Merger
2842
tt = merger.make_merger().make_preview_transform()
2843
self.addCleanup(tt.finalize)
2844
final_tree = tt.get_preview_tree()
2845
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2847
def test_merge_preview_into_workingtree(self):
2848
tree = self.make_branch_and_tree('tree')
2849
tree.set_root_id('TREE_ROOT')
2850
tt = TransformPreview(tree)
2851
self.addCleanup(tt.finalize)
2852
tt.new_file('name', tt.root, 'content', 'file-id')
2853
tree2 = self.make_branch_and_tree('tree2')
2854
tree2.set_root_id('TREE_ROOT')
2855
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2856
None, tree.basis_tree())
2857
merger.merge_type = Merge3Merger
2860
def test_merge_preview_into_workingtree_handles_conflicts(self):
2861
tree = self.make_branch_and_tree('tree')
2862
self.build_tree_contents([('tree/foo', 'bar')])
2863
tree.add('foo', 'foo-id')
2865
tt = TransformPreview(tree)
2866
self.addCleanup(tt.finalize)
2867
trans_id = tt.trans_id_file_id('foo-id')
2868
tt.delete_contents(trans_id)
2869
tt.create_file('baz', trans_id)
2870
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2871
self.build_tree_contents([('tree2/foo', 'qux')])
2873
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2874
pb, tree.basis_tree())
2875
merger.merge_type = Merge3Merger
2878
def test_is_executable(self):
2879
tree = self.make_branch_and_tree('tree')
2880
preview = TransformPreview(tree)
2881
self.addCleanup(preview.finalize)
2882
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2883
preview_tree = preview.get_preview_tree()
2884
self.assertEqual(False, preview_tree.is_executable('baz-id',
2886
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2888
def test_commit_preview_tree(self):
2889
tree = self.make_branch_and_tree('tree')
2890
rev_id = tree.commit('rev1')
2891
tree.branch.lock_write()
2892
self.addCleanup(tree.branch.unlock)
2893
tt = TransformPreview(tree)
2894
tt.new_file('file', tt.root, 'contents', 'file_id')
2895
self.addCleanup(tt.finalize)
2896
preview = tt.get_preview_tree()
2897
preview.set_parent_ids([rev_id])
2898
builder = tree.branch.get_commit_builder([rev_id])
2899
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2900
builder.finish_inventory()
2901
rev2_id = builder.commit('rev2')
2902
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2903
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2905
def test_ascii_limbo_paths(self):
2906
self.requireFeature(tests.UnicodeFilenameFeature)
2907
branch = self.make_branch('any')
2908
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2909
tt = TransformPreview(tree)
2910
self.addCleanup(tt.finalize)
2911
foo_id = tt.new_directory('', ROOT_PARENT)
2912
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2913
limbo_path = tt._limbo_name(bar_id)
2914
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
1090
self.build_tree(['dir/', ])
1091
wt.add(['dir'], [b'dir-id'])
1092
wt.commit('Create dir')
1094
self.addCleanup(tt.finalize)
1097
def test_resolve_create_parent_for_versioned_file(self):
1098
wt, tt = self.make_tt_with_versioned_dir()
1099
dir_tid = tt.trans_id_tree_path('dir')
1100
tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
1101
tt.delete_contents(dir_tid)
1102
tt.unversion_file(dir_tid)
1103
conflicts = resolve_conflicts(tt)
1104
# one conflict for the missing directory, one for the unversioned
1106
self.assertLength(2, conflicts)
1108
def test_non_versioned_file_create_conflict(self):
1109
wt, tt = self.make_tt_with_versioned_dir()
1110
dir_tid = tt.trans_id_tree_path('dir')
1111
tt.new_file('file', dir_tid, [b'Contents'])
1112
tt.delete_contents(dir_tid)
1113
tt.unversion_file(dir_tid)
1114
conflicts = resolve_conflicts(tt)
1115
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
1116
self.assertLength(1, conflicts)
1117
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2917
1121
class FakeSerializer(object):