72
74
from ..transform import (
84
TransformRenameFailed,
88
class TestTreeTransform(tests.TestCaseWithTransport):
91
super(TestTreeTransform, self).setUp()
92
self.wt = self.make_branch_and_tree('.', format='development-subtree')
95
def get_transform(self):
96
transform = TreeTransform(self.wt)
97
self.addCleanup(transform.finalize)
98
return transform, transform.root
100
def get_transform_for_sha1_test(self):
101
trans, root = self.get_transform()
102
self.wt.lock_tree_write()
103
self.addCleanup(self.wt.unlock)
104
contents = ['just some content\n']
105
sha1 = osutils.sha_strings(contents)
106
# Roll back the clock
107
trans._creation_mtime = time.time() - 20.0
108
return trans, root, contents, sha1
110
def test_existing_limbo(self):
111
transform, root = self.get_transform()
112
limbo_name = transform._limbodir
113
deletion_path = transform._deletiondir
114
os.mkdir(pathjoin(limbo_name, 'hehe'))
115
self.assertRaises(ImmortalLimbo, transform.apply)
116
self.assertRaises(LockError, self.wt.unlock)
117
self.assertRaises(ExistingLimbo, self.get_transform)
118
self.assertRaises(LockError, self.wt.unlock)
119
os.rmdir(pathjoin(limbo_name, 'hehe'))
121
os.rmdir(deletion_path)
122
transform, root = self.get_transform()
125
def test_existing_pending_deletion(self):
126
transform, root = self.get_transform()
127
deletion_path = self._limbodir = urlutils.local_path_from_url(
128
transform._tree._transport.abspath('pending-deletion'))
129
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
130
self.assertRaises(ImmortalPendingDeletion, transform.apply)
131
self.assertRaises(LockError, self.wt.unlock)
132
self.assertRaises(ExistingPendingDeletion, self.get_transform)
134
def test_build(self):
135
transform, root = self.get_transform()
136
self.wt.lock_tree_write()
137
self.addCleanup(self.wt.unlock)
138
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
139
imaginary_id = transform.trans_id_tree_path('imaginary')
140
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
141
self.assertEqual(imaginary_id, imaginary_id2)
142
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
143
self.assertEqual('directory', transform.final_kind(root))
144
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
145
trans_id = transform.create_path('name', root)
146
self.assertIs(transform.final_file_id(trans_id), None)
147
self.assertIs(None, transform.final_kind(trans_id))
148
transform.create_file('contents', trans_id)
149
transform.set_executability(True, trans_id)
150
transform.version_file('my_pretties', trans_id)
151
self.assertRaises(DuplicateKey, transform.version_file,
152
'my_pretties', trans_id)
153
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
154
self.assertEqual(transform.final_parent(trans_id), root)
155
self.assertIs(transform.final_parent(root), ROOT_PARENT)
156
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
157
oz_id = transform.create_path('oz', root)
158
transform.create_directory(oz_id)
159
transform.version_file('ozzie', oz_id)
160
trans_id2 = transform.create_path('name2', root)
161
transform.create_file('contents', trans_id2)
162
transform.set_executability(False, trans_id2)
163
transform.version_file('my_pretties2', trans_id2)
164
modified_paths = transform.apply().modified_paths
165
self.assertEqual('contents', self.wt.get_file_byname('name').read())
166
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
167
self.assertIs(self.wt.is_executable('my_pretties'), True)
168
self.assertIs(self.wt.is_executable('my_pretties2'), False)
169
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
170
self.assertEqual(len(modified_paths), 3)
171
tree_mod_paths = [self.wt.id2abspath(f) for f in
172
('ozzie', 'my_pretties', 'my_pretties2')]
173
self.assertSubset(tree_mod_paths, modified_paths)
174
# is it safe to finalize repeatedly?
178
def test_apply_informs_tree_of_observed_sha1(self):
179
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
180
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
183
orig = self.wt._observed_sha1
184
def _observed_sha1(*args):
187
self.wt._observed_sha1 = _observed_sha1
189
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
192
def test_create_file_caches_sha1(self):
193
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
194
trans_id = trans.create_path('file1', root)
195
trans.create_file(contents, trans_id, sha1=sha1)
196
st_val = osutils.lstat(trans._limbo_name(trans_id))
197
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
198
self.assertEqual(o_sha1, sha1)
199
self.assertEqualStat(o_st_val, st_val)
201
def test__apply_insertions_updates_sha1(self):
202
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
203
trans_id = trans.create_path('file1', root)
204
trans.create_file(contents, trans_id, sha1=sha1)
205
st_val = osutils.lstat(trans._limbo_name(trans_id))
206
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
207
self.assertEqual(o_sha1, sha1)
208
self.assertEqualStat(o_st_val, st_val)
209
creation_mtime = trans._creation_mtime + 10.0
210
# We fake a time difference from when the file was created until now it
211
# is being renamed by using os.utime. Note that the change we actually
212
# want to see is the real ctime change from 'os.rename()', but as long
213
# as we observe a new stat value, we should be fine.
214
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
216
new_st_val = osutils.lstat(self.wt.abspath('file1'))
217
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
218
self.assertEqual(o_sha1, sha1)
219
self.assertEqualStat(o_st_val, new_st_val)
220
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
222
def test_new_file_caches_sha1(self):
223
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
224
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
226
st_val = osutils.lstat(trans._limbo_name(trans_id))
227
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
228
self.assertEqual(o_sha1, sha1)
229
self.assertEqualStat(o_st_val, st_val)
231
def test_cancel_creation_removes_observed_sha1(self):
232
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
233
trans_id = trans.new_file('file1', root, contents, file_id='file1-id',
235
self.assertTrue(trans_id in trans._observed_sha1s)
236
trans.cancel_creation(trans_id)
237
self.assertFalse(trans_id in trans._observed_sha1s)
239
def test_create_files_same_timestamp(self):
240
transform, root = self.get_transform()
241
self.wt.lock_tree_write()
242
self.addCleanup(self.wt.unlock)
243
# Roll back the clock, so that we know everything is being set to the
245
transform._creation_mtime = creation_mtime = time.time() - 20.0
246
transform.create_file('content-one',
247
transform.create_path('one', root))
248
time.sleep(1) # *ugly*
249
transform.create_file('content-two',
250
transform.create_path('two', root))
252
fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
254
fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
256
# We only guarantee 2s resolution
257
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
258
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
259
# But if we have more than that, all files should get the same result
260
self.assertEqual(st1.st_mtime, st2.st_mtime)
262
def test_change_root_id(self):
263
transform, root = self.get_transform()
264
self.assertNotEqual('new-root-id', self.wt.get_root_id())
265
transform.new_directory('', ROOT_PARENT, 'new-root-id')
266
transform.delete_contents(root)
267
transform.unversion_file(root)
268
transform.fixup_new_roots()
270
self.assertEqual('new-root-id', self.wt.get_root_id())
272
def test_change_root_id_add_files(self):
273
transform, root = self.get_transform()
274
self.assertNotEqual('new-root-id', self.wt.get_root_id())
275
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
276
transform.new_file('file', new_trans_id, ['new-contents\n'],
278
transform.delete_contents(root)
279
transform.unversion_file(root)
280
transform.fixup_new_roots()
282
self.assertEqual('new-root-id', self.wt.get_root_id())
283
self.assertEqual('new-file-id', self.wt.path2id('file'))
284
self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
286
def test_add_two_roots(self):
287
transform, root = self.get_transform()
288
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
289
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
290
self.assertRaises(ValueError, transform.fixup_new_roots)
292
def test_retain_existing_root(self):
293
tt, root = self.get_transform()
295
tt.new_directory('', ROOT_PARENT, 'new-root-id')
297
self.assertNotEqual('new-root-id', tt.final_file_id(tt.root))
299
def test_retain_existing_root_added_file(self):
300
tt, root = self.get_transform()
301
new_trans_id = tt.new_directory('', ROOT_PARENT, 'new-root-id')
302
child = tt.new_directory('child', new_trans_id, 'child-id')
304
self.assertEqual(tt.root, tt.final_parent(child))
306
def test_add_unversioned_root(self):
307
transform, root = self.get_transform()
308
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
309
transform.delete_contents(transform.root)
310
transform.fixup_new_roots()
311
self.assertNotIn(transform.root, transform._new_id)
313
def test_remove_root_fixup(self):
314
transform, root = self.get_transform()
315
old_root_id = self.wt.get_root_id()
316
self.assertNotEqual('new-root-id', old_root_id)
317
transform.delete_contents(root)
318
transform.unversion_file(root)
319
transform.fixup_new_roots()
321
self.assertEqual(old_root_id, self.wt.get_root_id())
323
transform, root = self.get_transform()
324
new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
325
new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
326
self.assertRaises(ValueError, transform.fixup_new_roots)
328
def test_fixup_new_roots_permits_empty_tree(self):
329
transform, root = self.get_transform()
330
transform.delete_contents(root)
331
transform.unversion_file(root)
332
transform.fixup_new_roots()
333
self.assertIs(None, transform.final_kind(root))
334
self.assertIs(None, transform.final_file_id(root))
336
def test_apply_retains_root_directory(self):
337
# Do not attempt to delete the physical root directory, because that
339
transform, root = self.get_transform()
341
transform.delete_contents(root)
342
e = self.assertRaises(AssertionError, self.assertRaises,
343
errors.TransformRenameFailed,
345
self.assertContainsRe('TransformRenameFailed not raised', str(e))
347
def test_apply_retains_file_id(self):
348
transform, root = self.get_transform()
349
old_root_id = transform.tree_file_id(root)
350
transform.unversion_file(root)
352
self.assertEqual(old_root_id, self.wt.get_root_id())
354
def test_hardlink(self):
355
self.requireFeature(HardlinkFeature)
356
transform, root = self.get_transform()
357
transform.new_file('file1', root, 'contents')
359
target = self.make_branch_and_tree('target')
360
target_transform = TreeTransform(target)
361
trans_id = target_transform.create_path('file1', target_transform.root)
362
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
363
target_transform.apply()
364
self.assertPathExists('target/file1')
365
source_stat = os.stat(self.wt.abspath('file1'))
366
target_stat = os.stat('target/file1')
367
self.assertEqual(source_stat, target_stat)
369
def test_convenience(self):
370
transform, root = self.get_transform()
371
self.wt.lock_tree_write()
372
self.addCleanup(self.wt.unlock)
373
trans_id = transform.new_file('name', root, 'contents',
375
oz = transform.new_directory('oz', root, 'oz-id')
376
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
377
toto = transform.new_file('toto', dorothy, 'toto-contents',
380
self.assertEqual(len(transform.find_conflicts()), 0)
382
self.assertRaises(ReusingTransform, transform.find_conflicts)
383
self.assertEqual('contents', file(self.wt.abspath('name')).read())
384
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
385
self.assertIs(self.wt.is_executable('my_pretties'), True)
386
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
387
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
388
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
390
self.assertEqual('toto-contents',
391
self.wt.get_file_byname('oz/dorothy/toto').read())
392
self.assertIs(self.wt.is_executable('toto-id'), False)
394
def test_tree_reference(self):
395
transform, root = self.get_transform()
396
tree = transform._tree
397
trans_id = transform.new_directory('reference', root, 'subtree-id')
398
transform.set_tree_reference('subtree-revision', trans_id)
401
self.addCleanup(tree.unlock)
402
self.assertEqual('subtree-revision',
403
tree.root_inventory['subtree-id'].reference_revision)
405
def test_conflicts(self):
406
transform, root = self.get_transform()
407
trans_id = transform.new_file('name', root, 'contents',
409
self.assertEqual(len(transform.find_conflicts()), 0)
410
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
411
self.assertEqual(transform.find_conflicts(),
412
[('duplicate', trans_id, trans_id2, 'name')])
413
self.assertRaises(MalformedTransform, transform.apply)
414
transform.adjust_path('name', trans_id, trans_id2)
415
self.assertEqual(transform.find_conflicts(),
416
[('non-directory parent', trans_id)])
417
tinman_id = transform.trans_id_tree_path('tinman')
418
transform.adjust_path('name', tinman_id, trans_id2)
419
self.assertEqual(transform.find_conflicts(),
420
[('unversioned parent', tinman_id),
421
('missing parent', tinman_id)])
422
lion_id = transform.create_path('lion', root)
423
self.assertEqual(transform.find_conflicts(),
424
[('unversioned parent', tinman_id),
425
('missing parent', tinman_id)])
426
transform.adjust_path('name', lion_id, trans_id2)
427
self.assertEqual(transform.find_conflicts(),
428
[('unversioned parent', lion_id),
429
('missing parent', lion_id)])
430
transform.version_file("Courage", lion_id)
431
self.assertEqual(transform.find_conflicts(),
432
[('missing parent', lion_id),
433
('versioning no contents', lion_id)])
434
transform.adjust_path('name2', root, trans_id2)
435
self.assertEqual(transform.find_conflicts(),
436
[('versioning no contents', lion_id)])
437
transform.create_file('Contents, okay?', lion_id)
438
transform.adjust_path('name2', trans_id2, trans_id2)
439
self.assertEqual(transform.find_conflicts(),
440
[('parent loop', trans_id2),
441
('non-directory parent', trans_id2)])
442
transform.adjust_path('name2', root, trans_id2)
443
oz_id = transform.new_directory('oz', root)
444
transform.set_executability(True, oz_id)
445
self.assertEqual(transform.find_conflicts(),
446
[('unversioned executability', oz_id)])
447
transform.version_file('oz-id', oz_id)
448
self.assertEqual(transform.find_conflicts(),
449
[('non-file executability', oz_id)])
450
transform.set_executability(None, oz_id)
451
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
453
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
454
self.assertEqual('contents', file(self.wt.abspath('name')).read())
455
transform2, root = self.get_transform()
456
oz_id = transform2.trans_id_tree_file_id('oz-id')
457
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
458
result = transform2.find_conflicts()
459
fp = FinalPaths(transform2)
460
self.assertTrue('oz/tip' in transform2._tree_path_ids)
461
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
462
self.assertEqual(len(result), 2)
463
self.assertEqual((result[0][0], result[0][1]),
464
('duplicate', newtip))
465
self.assertEqual((result[1][0], result[1][2]),
466
('duplicate id', newtip))
467
transform2.finalize()
468
transform3 = TreeTransform(self.wt)
469
self.addCleanup(transform3.finalize)
470
oz_id = transform3.trans_id_tree_file_id('oz-id')
471
transform3.delete_contents(oz_id)
472
self.assertEqual(transform3.find_conflicts(),
473
[('missing parent', oz_id)])
474
root_id = transform3.root
475
tip_id = transform3.trans_id_tree_file_id('tip-id')
476
transform3.adjust_path('tip', root_id, tip_id)
479
def test_conflict_on_case_insensitive(self):
480
tree = self.make_branch_and_tree('tree')
481
# Don't try this at home, kids!
482
# Force the tree to report that it is case sensitive, for conflict
484
tree.case_sensitive = True
485
transform = TreeTransform(tree)
486
self.addCleanup(transform.finalize)
487
transform.new_file('file', transform.root, 'content')
488
transform.new_file('FiLe', transform.root, 'content')
489
result = transform.find_conflicts()
490
self.assertEqual([], result)
492
# Force the tree to report that it is case insensitive, for conflict
494
tree.case_sensitive = False
495
transform = TreeTransform(tree)
496
self.addCleanup(transform.finalize)
497
transform.new_file('file', transform.root, 'content')
498
transform.new_file('FiLe', transform.root, 'content')
499
result = transform.find_conflicts()
500
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
502
def test_conflict_on_case_insensitive_existing(self):
503
tree = self.make_branch_and_tree('tree')
504
self.build_tree(['tree/FiLe'])
505
# Don't try this at home, kids!
506
# Force the tree to report that it is case sensitive, for conflict
508
tree.case_sensitive = True
509
transform = TreeTransform(tree)
510
self.addCleanup(transform.finalize)
511
transform.new_file('file', transform.root, 'content')
512
result = transform.find_conflicts()
513
self.assertEqual([], result)
515
# Force the tree to report that it is case insensitive, for conflict
517
tree.case_sensitive = False
518
transform = TreeTransform(tree)
519
self.addCleanup(transform.finalize)
520
transform.new_file('file', transform.root, 'content')
521
result = transform.find_conflicts()
522
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
524
def test_resolve_case_insensitive_conflict(self):
525
tree = self.make_branch_and_tree('tree')
526
# Don't try this at home, kids!
527
# Force the tree to report that it is case insensitive, for conflict
529
tree.case_sensitive = False
530
transform = TreeTransform(tree)
531
self.addCleanup(transform.finalize)
532
transform.new_file('file', transform.root, 'content')
533
transform.new_file('FiLe', transform.root, 'content')
534
resolve_conflicts(transform)
536
self.assertPathExists('tree/file')
537
self.assertPathExists('tree/FiLe.moved')
539
def test_resolve_checkout_case_conflict(self):
540
tree = self.make_branch_and_tree('tree')
541
# Don't try this at home, kids!
542
# Force the tree to report that it is case insensitive, for conflict
544
tree.case_sensitive = False
545
transform = TreeTransform(tree)
546
self.addCleanup(transform.finalize)
547
transform.new_file('file', transform.root, 'content')
548
transform.new_file('FiLe', transform.root, 'content')
549
resolve_conflicts(transform,
550
pass_func=lambda t, c: resolve_checkout(t, c, []))
552
self.assertPathExists('tree/file')
553
self.assertPathExists('tree/FiLe.moved')
555
def test_apply_case_conflict(self):
556
"""Ensure that a transform with case conflicts can always be applied"""
557
tree = self.make_branch_and_tree('tree')
558
transform = TreeTransform(tree)
559
self.addCleanup(transform.finalize)
560
transform.new_file('file', transform.root, 'content')
561
transform.new_file('FiLe', transform.root, 'content')
562
dir = transform.new_directory('dir', transform.root)
563
transform.new_file('dirfile', dir, 'content')
564
transform.new_file('dirFiLe', dir, 'content')
565
resolve_conflicts(transform)
567
self.assertPathExists('tree/file')
568
if not os.path.exists('tree/FiLe.moved'):
569
self.assertPathExists('tree/FiLe')
570
self.assertPathExists('tree/dir/dirfile')
571
if not os.path.exists('tree/dir/dirFiLe.moved'):
572
self.assertPathExists('tree/dir/dirFiLe')
574
def test_case_insensitive_limbo(self):
575
tree = self.make_branch_and_tree('tree')
576
# Don't try this at home, kids!
577
# Force the tree to report that it is case insensitive
578
tree.case_sensitive = False
579
transform = TreeTransform(tree)
580
self.addCleanup(transform.finalize)
581
dir = transform.new_directory('dir', transform.root)
582
first = transform.new_file('file', dir, 'content')
583
second = transform.new_file('FiLe', dir, 'content')
584
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
585
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
587
def test_adjust_path_updates_child_limbo_names(self):
588
tree = self.make_branch_and_tree('tree')
589
transform = TreeTransform(tree)
590
self.addCleanup(transform.finalize)
591
foo_id = transform.new_directory('foo', transform.root)
592
bar_id = transform.new_directory('bar', foo_id)
593
baz_id = transform.new_directory('baz', bar_id)
594
qux_id = transform.new_directory('qux', baz_id)
595
transform.adjust_path('quxx', foo_id, bar_id)
596
self.assertStartsWith(transform._limbo_name(qux_id),
597
transform._limbo_name(bar_id))
599
def test_add_del(self):
600
start, root = self.get_transform()
601
start.new_directory('a', root, 'a')
603
transform, root = self.get_transform()
604
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
605
transform.new_directory('a', root, 'a')
608
def test_unversioning(self):
609
create_tree, root = self.get_transform()
610
parent_id = create_tree.new_directory('parent', root, 'parent-id')
611
create_tree.new_file('child', parent_id, 'child', 'child-id')
613
unversion = TreeTransform(self.wt)
614
self.addCleanup(unversion.finalize)
615
parent = unversion.trans_id_tree_path('parent')
616
unversion.unversion_file(parent)
617
self.assertEqual(unversion.find_conflicts(),
618
[('unversioned parent', parent_id)])
619
file_id = unversion.trans_id_tree_file_id('child-id')
620
unversion.unversion_file(file_id)
623
def test_name_invariants(self):
624
create_tree, root = self.get_transform()
626
root = create_tree.root
627
create_tree.new_file('name1', root, 'hello1', 'name1')
628
create_tree.new_file('name2', root, 'hello2', 'name2')
629
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
630
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
631
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
632
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
635
mangle_tree,root = self.get_transform()
636
root = mangle_tree.root
638
name1 = mangle_tree.trans_id_tree_file_id('name1')
639
name2 = mangle_tree.trans_id_tree_file_id('name2')
640
mangle_tree.adjust_path('name2', root, name1)
641
mangle_tree.adjust_path('name1', root, name2)
643
#tests for deleting parent directories
644
ddir = mangle_tree.trans_id_tree_file_id('ddir')
645
mangle_tree.delete_contents(ddir)
646
dfile = mangle_tree.trans_id_tree_file_id('dfile')
647
mangle_tree.delete_versioned(dfile)
648
mangle_tree.unversion_file(dfile)
649
mfile = mangle_tree.trans_id_tree_file_id('mfile')
650
mangle_tree.adjust_path('mfile', root, mfile)
652
#tests for adding parent directories
653
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
654
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
655
mangle_tree.adjust_path('mfile2', newdir, mfile2)
656
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
657
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
658
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
659
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
661
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
662
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
663
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
664
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
665
self.assertEqual(file(mfile2_path).read(), 'later2')
666
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
667
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
668
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
669
self.assertEqual(file(newfile_path).read(), 'hello3')
670
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
671
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
672
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
674
def test_both_rename(self):
675
create_tree,root = self.get_transform()
676
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
677
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
679
mangle_tree,root = self.get_transform()
680
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
681
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
682
mangle_tree.adjust_path('test', root, selftest)
683
mangle_tree.adjust_path('test_too_much', root, selftest)
684
mangle_tree.set_executability(True, blackbox)
687
def test_both_rename2(self):
688
create_tree,root = self.get_transform()
689
breezy = create_tree.new_directory('breezy', root, 'breezy-id')
690
tests = create_tree.new_directory('tests', breezy, 'tests-id')
691
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
692
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
695
mangle_tree,root = self.get_transform()
696
breezy = mangle_tree.trans_id_tree_file_id('breezy-id')
697
tests = mangle_tree.trans_id_tree_file_id('tests-id')
698
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
699
mangle_tree.adjust_path('selftest', breezy, tests)
700
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
701
mangle_tree.set_executability(True, test_too_much)
704
def test_both_rename3(self):
705
create_tree,root = self.get_transform()
706
tests = create_tree.new_directory('tests', root, 'tests-id')
707
create_tree.new_file('test_too_much.py', tests, 'hello1',
710
mangle_tree,root = self.get_transform()
711
tests = mangle_tree.trans_id_tree_file_id('tests-id')
712
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
713
mangle_tree.adjust_path('selftest', root, tests)
714
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
715
mangle_tree.set_executability(True, test_too_much)
718
def test_move_dangling_ie(self):
719
create_tree, root = self.get_transform()
721
root = create_tree.root
722
create_tree.new_file('name1', root, 'hello1', 'name1')
724
delete_contents, root = self.get_transform()
725
file = delete_contents.trans_id_tree_file_id('name1')
726
delete_contents.delete_contents(file)
727
delete_contents.apply()
728
move_id, root = self.get_transform()
729
name1 = move_id.trans_id_tree_file_id('name1')
730
newdir = move_id.new_directory('dir', root, 'newdir')
731
move_id.adjust_path('name2', newdir, name1)
734
def test_replace_dangling_ie(self):
735
create_tree, root = self.get_transform()
737
root = create_tree.root
738
create_tree.new_file('name1', root, 'hello1', 'name1')
740
delete_contents = TreeTransform(self.wt)
741
self.addCleanup(delete_contents.finalize)
742
file = delete_contents.trans_id_tree_file_id('name1')
743
delete_contents.delete_contents(file)
744
delete_contents.apply()
745
delete_contents.finalize()
746
replace = TreeTransform(self.wt)
747
self.addCleanup(replace.finalize)
748
name2 = replace.new_file('name2', root, 'hello2', 'name1')
749
conflicts = replace.find_conflicts()
750
name1 = replace.trans_id_tree_file_id('name1')
751
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
752
resolve_conflicts(replace)
755
def _test_symlinks(self, link_name1,link_target1,
756
link_name2, link_target2):
758
def ozpath(p): return 'oz/' + p
760
self.requireFeature(SymlinkFeature)
761
transform, root = self.get_transform()
762
oz_id = transform.new_directory('oz', root, 'oz-id')
763
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
765
wiz_id = transform.create_path(link_name2, oz_id)
766
transform.create_symlink(link_target2, wiz_id)
767
transform.version_file('wiz-id2', wiz_id)
768
transform.set_executability(True, wiz_id)
769
self.assertEqual(transform.find_conflicts(),
770
[('non-file executability', wiz_id)])
771
transform.set_executability(None, wiz_id)
773
self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
774
self.assertEqual('symlink',
775
file_kind(self.wt.abspath(ozpath(link_name1))))
776
self.assertEqual(link_target2,
777
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
778
self.assertEqual(link_target1,
779
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
781
def test_symlinks(self):
782
self._test_symlinks('wizard', 'wizard-target',
783
'wizard2', 'behind_curtain')
785
def test_symlinks_unicode(self):
786
self.requireFeature(features.UnicodeFilenameFeature)
787
self._test_symlinks(u'\N{Euro Sign}wizard',
788
u'wizard-targ\N{Euro Sign}t',
789
u'\N{Euro Sign}wizard2',
790
u'b\N{Euro Sign}hind_curtain')
792
def test_unable_create_symlink(self):
794
wt = self.make_branch_and_tree('.')
795
tt = TreeTransform(wt) # TreeTransform obtains write lock
797
tt.new_symlink('foo', tt.root, 'bar')
801
os_symlink = getattr(os, 'symlink', None)
804
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
806
"Unable to create symlink 'foo' on this platform",
810
os.symlink = os_symlink
812
def get_conflicted(self):
813
create,root = self.get_transform()
814
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
815
oz = create.new_directory('oz', root, 'oz-id')
816
create.new_directory('emeraldcity', oz, 'emerald-id')
818
conflicts,root = self.get_transform()
819
# set up duplicate entry, duplicate id
820
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
822
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
823
oz = conflicts.trans_id_tree_file_id('oz-id')
824
# set up DeletedParent parent conflict
825
conflicts.delete_versioned(oz)
826
emerald = conflicts.trans_id_tree_file_id('emerald-id')
827
# set up MissingParent conflict
828
munchkincity = conflicts.trans_id_file_id('munchkincity-id')
829
conflicts.adjust_path('munchkincity', root, munchkincity)
830
conflicts.new_directory('auntem', munchkincity, 'auntem-id')
832
conflicts.adjust_path('emeraldcity', emerald, emerald)
833
return conflicts, emerald, oz, old_dorothy, new_dorothy
835
def test_conflict_resolution(self):
836
conflicts, emerald, oz, old_dorothy, new_dorothy =\
837
self.get_conflicted()
838
resolve_conflicts(conflicts)
839
self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
840
self.assertIs(conflicts.final_file_id(old_dorothy), None)
841
self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
842
self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
843
self.assertEqual(conflicts.final_parent(emerald), oz)
846
def test_cook_conflicts(self):
847
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
848
raw_conflicts = resolve_conflicts(tt)
849
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
850
duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
851
'dorothy', None, 'dorothy-id')
852
self.assertEqual(cooked_conflicts[0], duplicate)
853
duplicate_id = DuplicateID('Unversioned existing file',
854
'dorothy.moved', 'dorothy', None,
856
self.assertEqual(cooked_conflicts[1], duplicate_id)
857
missing_parent = MissingParent('Created directory', 'munchkincity',
859
deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
860
self.assertEqual(cooked_conflicts[2], missing_parent)
861
unversioned_parent = UnversionedParent('Versioned directory',
864
unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
866
self.assertEqual(cooked_conflicts[3], unversioned_parent)
867
parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
868
'oz/emeraldcity', 'emerald-id', 'emerald-id')
869
self.assertEqual(cooked_conflicts[4], deleted_parent)
870
self.assertEqual(cooked_conflicts[5], unversioned_parent2)
871
self.assertEqual(cooked_conflicts[6], parent_loop)
872
self.assertEqual(len(cooked_conflicts), 7)
875
def test_string_conflicts(self):
876
tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
877
raw_conflicts = resolve_conflicts(tt)
878
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
880
conflicts_s = [unicode(c) for c in cooked_conflicts]
881
self.assertEqual(len(cooked_conflicts), len(conflicts_s))
882
self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy. '
883
'Moved existing file to '
885
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
886
'Unversioned existing file '
888
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
889
' munchkincity. Created directory.')
890
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
891
' versioned, but has versioned'
892
' children. Versioned directory.')
893
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
894
" is not empty. Not deleting.")
895
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
896
' versioned, but has versioned'
897
' children. Versioned directory.')
898
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
899
' oz/emeraldcity. Cancelled move.')
901
def prepare_wrong_parent_kind(self):
902
tt, root = self.get_transform()
903
tt.new_file('parent', root, 'contents', 'parent-id')
905
tt, root = self.get_transform()
906
parent_id = tt.trans_id_file_id('parent-id')
907
tt.new_file('child,', parent_id, 'contents2', 'file-id')
910
def test_find_conflicts_wrong_parent_kind(self):
911
tt = self.prepare_wrong_parent_kind()
914
def test_resolve_conflicts_wrong_existing_parent_kind(self):
915
tt = self.prepare_wrong_parent_kind()
916
raw_conflicts = resolve_conflicts(tt)
917
self.assertEqual({('non-directory parent', 'Created directory',
918
'new-3')}, raw_conflicts)
919
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
920
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
921
'parent-id')], cooked_conflicts)
923
self.assertEqual(None, self.wt.path2id('parent'))
924
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
926
def test_resolve_conflicts_wrong_new_parent_kind(self):
927
tt, root = self.get_transform()
928
parent_id = tt.new_directory('parent', root, 'parent-id')
929
tt.new_file('child,', parent_id, 'contents2', 'file-id')
931
tt, root = self.get_transform()
932
parent_id = tt.trans_id_file_id('parent-id')
933
tt.delete_contents(parent_id)
934
tt.create_file('contents', parent_id)
935
raw_conflicts = resolve_conflicts(tt)
936
self.assertEqual({('non-directory parent', 'Created directory',
937
'new-3')}, raw_conflicts)
939
self.assertEqual(None, self.wt.path2id('parent'))
940
self.assertEqual('parent-id', self.wt.path2id('parent.new'))
942
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
943
tt, root = self.get_transform()
944
parent_id = tt.new_directory('parent', root)
945
tt.new_file('child,', parent_id, 'contents2')
947
tt, root = self.get_transform()
948
parent_id = tt.trans_id_tree_path('parent')
949
tt.delete_contents(parent_id)
950
tt.create_file('contents', parent_id)
951
resolve_conflicts(tt)
953
self.assertIs(None, self.wt.path2id('parent'))
954
self.assertIs(None, self.wt.path2id('parent.new'))
956
def test_resolve_conflicts_missing_parent(self):
957
wt = self.make_branch_and_tree('.')
958
tt = TreeTransform(wt)
959
self.addCleanup(tt.finalize)
960
parent = tt.trans_id_file_id('parent-id')
961
tt.new_file('file', parent, 'Contents')
962
raw_conflicts = resolve_conflicts(tt)
963
# Since the directory doesn't exist it's seen as 'missing'. So
964
# 'resolve_conflicts' create a conflict asking for it to be created.
965
self.assertLength(1, raw_conflicts)
966
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
968
# apply fail since the missing directory doesn't exist
969
self.assertRaises(errors.NoFinalPath, tt.apply)
971
def test_moving_versioned_directories(self):
972
create, root = self.get_transform()
973
kansas = create.new_directory('kansas', root, 'kansas-id')
974
create.new_directory('house', kansas, 'house-id')
975
create.new_directory('oz', root, 'oz-id')
977
cyclone, root = self.get_transform()
978
oz = cyclone.trans_id_tree_file_id('oz-id')
979
house = cyclone.trans_id_tree_file_id('house-id')
980
cyclone.adjust_path('house', oz, house)
983
def test_moving_root(self):
984
create, root = self.get_transform()
985
fun = create.new_directory('fun', root, 'fun-id')
986
create.new_directory('sun', root, 'sun-id')
987
create.new_directory('moon', root, 'moon')
989
transform, root = self.get_transform()
990
transform.adjust_root_path('oldroot', fun)
991
new_root = transform.trans_id_tree_path('')
992
transform.version_file('new-root', new_root)
995
def test_renames(self):
996
create, root = self.get_transform()
997
old = create.new_directory('old-parent', root, 'old-id')
998
intermediate = create.new_directory('intermediate', old, 'im-id')
999
myfile = create.new_file('myfile', intermediate, 'myfile-text',
1002
rename, root = self.get_transform()
1003
old = rename.trans_id_file_id('old-id')
1004
rename.adjust_path('new', root, old)
1005
myfile = rename.trans_id_file_id('myfile-id')
1006
rename.set_executability(True, myfile)
1009
def test_rename_fails(self):
1010
self.requireFeature(features.not_running_as_root)
1011
# see https://bugs.launchpad.net/bzr/+bug/491763
1012
create, root_id = self.get_transform()
1013
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1014
myfile = create.new_file('myfile', root_id, 'myfile-text',
1017
if os.name == "posix" and sys.platform != "cygwin":
1018
# posix filesystems fail on renaming if the readonly bit is set
1019
osutils.make_readonly(self.wt.abspath('first-dir'))
1020
elif os.name == "nt":
1021
# windows filesystems fail on renaming open files
1022
self.addCleanup(file(self.wt.abspath('myfile')).close)
1024
self.skipTest("Can't force a permissions error on rename")
1025
# now transform to rename
1026
rename_transform, root_id = self.get_transform()
1027
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1028
dir_id = rename_transform.trans_id_file_id('first-id')
1029
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1030
e = self.assertRaises(errors.TransformRenameFailed,
1031
rename_transform.apply)
1032
# On nix looks like:
1033
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1034
# to .../first-dir/newname: [Errno 13] Permission denied"
1035
# On windows looks like:
1036
# "Failed to rename .../work/myfile to
1037
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1038
# This test isn't concerned with exactly what the error looks like,
1039
# and the strerror will vary across OS and locales, but the assert
1040
# that the exeception attributes are what we expect
1041
self.assertEqual(e.errno, errno.EACCES)
1042
if os.name == "posix":
1043
self.assertEndsWith(e.to_path, "/first-dir/newname")
1045
self.assertEqual(os.path.basename(e.from_path), "myfile")
1047
def test_set_executability_order(self):
1048
"""Ensure that executability behaves the same, no matter what order.
1050
- create file and set executability simultaneously
1051
- create file and set executability afterward
1052
- unsetting the executability of a file whose executability has not been
1053
declared should throw an exception (this may happen when a
1054
merge attempts to create a file with a duplicate ID)
1056
transform, root = self.get_transform()
1057
wt = transform._tree
1059
self.addCleanup(wt.unlock)
1060
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1062
sac = transform.new_file('set_after_creation', root,
1063
'Set after creation', 'sac')
1064
transform.set_executability(True, sac)
1065
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1067
self.assertRaises(KeyError, transform.set_executability, None, uws)
1069
self.assertTrue(wt.is_executable('soc'))
1070
self.assertTrue(wt.is_executable('sac'))
1072
def test_preserve_mode(self):
1073
"""File mode is preserved when replacing content"""
1074
if sys.platform == 'win32':
1075
raise TestSkipped('chmod has no effect on win32')
1076
transform, root = self.get_transform()
1077
transform.new_file('file1', root, 'contents', 'file1-id', True)
1079
self.wt.lock_write()
1080
self.addCleanup(self.wt.unlock)
1081
self.assertTrue(self.wt.is_executable('file1-id'))
1082
transform, root = self.get_transform()
1083
file1_id = transform.trans_id_tree_file_id('file1-id')
1084
transform.delete_contents(file1_id)
1085
transform.create_file('contents2', file1_id)
1087
self.assertTrue(self.wt.is_executable('file1-id'))
1089
def test__set_mode_stats_correctly(self):
1090
"""_set_mode stats to determine file mode."""
1091
if sys.platform == 'win32':
1092
raise TestSkipped('chmod has no effect on win32')
1096
def instrumented_stat(path):
1097
stat_paths.append(path)
1098
return real_stat(path)
1100
transform, root = self.get_transform()
1102
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1103
file_id='bar-id-1', executable=False)
1106
transform, root = self.get_transform()
1107
bar1_id = transform.trans_id_tree_path('bar')
1108
bar2_id = transform.trans_id_tree_path('bar2')
1110
os.stat = instrumented_stat
1111
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1114
transform.finalize()
1116
bar1_abspath = self.wt.abspath('bar')
1117
self.assertEqual([bar1_abspath], stat_paths)
1119
def test_iter_changes(self):
1120
self.wt.set_root_id('eert_toor')
1121
transform, root = self.get_transform()
1122
transform.new_file('old', root, 'blah', 'id-1', True)
1124
transform, root = self.get_transform()
1126
self.assertEqual([], list(transform.iter_changes()))
1127
old = transform.trans_id_tree_file_id('id-1')
1128
transform.unversion_file(old)
1129
self.assertEqual([('id-1', ('old', None), False, (True, False),
1130
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1131
(True, True))], list(transform.iter_changes()))
1132
transform.new_directory('new', root, 'id-1')
1133
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1134
('eert_toor', 'eert_toor'), ('old', 'new'),
1135
('file', 'directory'),
1136
(True, False))], list(transform.iter_changes()))
1138
transform.finalize()
1140
def test_iter_changes_new(self):
1141
self.wt.set_root_id('eert_toor')
1142
transform, root = self.get_transform()
1143
transform.new_file('old', root, 'blah')
1145
transform, root = self.get_transform()
1147
old = transform.trans_id_tree_path('old')
1148
transform.version_file('id-1', old)
1149
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1150
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1151
(False, False))], list(transform.iter_changes()))
1153
transform.finalize()
1155
def test_iter_changes_modifications(self):
1156
self.wt.set_root_id('eert_toor')
1157
transform, root = self.get_transform()
1158
transform.new_file('old', root, 'blah', 'id-1')
1159
transform.new_file('new', root, 'blah')
1160
transform.new_directory('subdir', root, 'subdir-id')
1162
transform, root = self.get_transform()
1164
old = transform.trans_id_tree_path('old')
1165
subdir = transform.trans_id_tree_file_id('subdir-id')
1166
new = transform.trans_id_tree_path('new')
1167
self.assertEqual([], list(transform.iter_changes()))
1170
transform.delete_contents(old)
1171
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1172
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1173
(False, False))], list(transform.iter_changes()))
1176
transform.create_file('blah', old)
1177
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1178
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1179
(False, False))], list(transform.iter_changes()))
1180
transform.cancel_deletion(old)
1181
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1182
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1183
(False, False))], list(transform.iter_changes()))
1184
transform.cancel_creation(old)
1186
# move file_id to a different file
1187
self.assertEqual([], list(transform.iter_changes()))
1188
transform.unversion_file(old)
1189
transform.version_file('id-1', new)
1190
transform.adjust_path('old', root, new)
1191
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1192
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1193
(False, False))], list(transform.iter_changes()))
1194
transform.cancel_versioning(new)
1195
transform._removed_id = set()
1198
self.assertEqual([], list(transform.iter_changes()))
1199
transform.set_executability(True, old)
1200
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1201
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1202
(False, True))], list(transform.iter_changes()))
1203
transform.set_executability(None, old)
1206
self.assertEqual([], list(transform.iter_changes()))
1207
transform.adjust_path('new', root, old)
1208
transform._new_parent = {}
1209
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1210
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1211
(False, False))], list(transform.iter_changes()))
1212
transform._new_name = {}
1215
self.assertEqual([], list(transform.iter_changes()))
1216
transform.adjust_path('new', subdir, old)
1217
transform._new_name = {}
1218
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1219
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1220
('file', 'file'), (False, False))],
1221
list(transform.iter_changes()))
1222
transform._new_path = {}
1225
transform.finalize()
1227
def test_iter_changes_modified_bleed(self):
1228
self.wt.set_root_id('eert_toor')
1229
"""Modified flag should not bleed from one change to another"""
1230
# unfortunately, we have no guarantee that file1 (which is modified)
1231
# will be applied before file2. And if it's applied after file2, it
1232
# obviously can't bleed into file2's change output. But for now, it
1234
transform, root = self.get_transform()
1235
transform.new_file('file1', root, 'blah', 'id-1')
1236
transform.new_file('file2', root, 'blah', 'id-2')
1238
transform, root = self.get_transform()
1240
transform.delete_contents(transform.trans_id_file_id('id-1'))
1241
transform.set_executability(True,
1242
transform.trans_id_file_id('id-2'))
1243
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1244
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1245
('file', None), (False, False)),
1246
('id-2', (u'file2', u'file2'), False, (True, True),
1247
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1248
('file', 'file'), (False, True))],
1249
list(transform.iter_changes()))
1251
transform.finalize()
1253
def test_iter_changes_move_missing(self):
1254
"""Test moving ids with no files around"""
1255
self.wt.set_root_id('toor_eert')
1256
# Need two steps because versioning a non-existant file is a conflict.
1257
transform, root = self.get_transform()
1258
transform.new_directory('floater', root, 'floater-id')
1260
transform, root = self.get_transform()
1261
transform.delete_contents(transform.trans_id_tree_path('floater'))
1263
transform, root = self.get_transform()
1264
floater = transform.trans_id_tree_path('floater')
1266
transform.adjust_path('flitter', root, floater)
1267
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1268
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1269
(None, None), (False, False))], list(transform.iter_changes()))
1271
transform.finalize()
1273
def test_iter_changes_pointless(self):
1274
"""Ensure that no-ops are not treated as modifications"""
1275
self.wt.set_root_id('eert_toor')
1276
transform, root = self.get_transform()
1277
transform.new_file('old', root, 'blah', 'id-1')
1278
transform.new_directory('subdir', root, 'subdir-id')
1280
transform, root = self.get_transform()
1282
old = transform.trans_id_tree_path('old')
1283
subdir = transform.trans_id_tree_file_id('subdir-id')
1284
self.assertEqual([], list(transform.iter_changes()))
1285
transform.delete_contents(subdir)
1286
transform.create_directory(subdir)
1287
transform.set_executability(False, old)
1288
transform.unversion_file(old)
1289
transform.version_file('id-1', old)
1290
transform.adjust_path('old', root, old)
1291
self.assertEqual([], list(transform.iter_changes()))
1293
transform.finalize()
1295
def test_rename_count(self):
1296
transform, root = self.get_transform()
1297
transform.new_file('name1', root, 'contents')
1298
self.assertEqual(transform.rename_count, 0)
1300
self.assertEqual(transform.rename_count, 1)
1301
transform2, root = self.get_transform()
1302
transform2.adjust_path('name2', root,
1303
transform2.trans_id_tree_path('name1'))
1304
self.assertEqual(transform2.rename_count, 0)
1306
self.assertEqual(transform2.rename_count, 2)
1308
def test_change_parent(self):
1309
"""Ensure that after we change a parent, the results are still right.
1311
Renames and parent changes on pending transforms can happen as part
1312
of conflict resolution, and are explicitly permitted by the
1315
This test ensures they work correctly with the rename-avoidance
1318
transform, root = self.get_transform()
1319
parent1 = transform.new_directory('parent1', root)
1320
child1 = transform.new_file('child1', parent1, 'contents')
1321
parent2 = transform.new_directory('parent2', root)
1322
transform.adjust_path('child1', parent2, child1)
1324
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1325
self.assertPathExists(self.wt.abspath('parent2/child1'))
1326
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1327
# no rename for child1 (counting only renames during apply)
1328
self.assertEqual(2, transform.rename_count)
1330
def test_cancel_parent(self):
1331
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1333
This is like the test_change_parent, except that we cancel the parent
1334
before adjusting the path. The transform must detect that the
1335
directory is non-empty, and move children to safe locations.
1337
transform, root = self.get_transform()
1338
parent1 = transform.new_directory('parent1', root)
1339
child1 = transform.new_file('child1', parent1, 'contents')
1340
child2 = transform.new_file('child2', parent1, 'contents')
1342
transform.cancel_creation(parent1)
1344
self.fail('Failed to move child1 before deleting parent1')
1345
transform.cancel_creation(child2)
1346
transform.create_directory(parent1)
1348
transform.cancel_creation(parent1)
1349
# If the transform incorrectly believes that child2 is still in
1350
# parent1's limbo directory, it will try to rename it and fail
1351
# because was already moved by the first cancel_creation.
1353
self.fail('Transform still thinks child2 is a child of parent1')
1354
parent2 = transform.new_directory('parent2', root)
1355
transform.adjust_path('child1', parent2, child1)
1357
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1358
self.assertPathExists(self.wt.abspath('parent2/child1'))
1359
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1360
self.assertEqual(2, transform.rename_count)
1362
def test_adjust_and_cancel(self):
1363
"""Make sure adjust_path keeps track of limbo children properly"""
1364
transform, root = self.get_transform()
1365
parent1 = transform.new_directory('parent1', root)
1366
child1 = transform.new_file('child1', parent1, 'contents')
1367
parent2 = transform.new_directory('parent2', root)
1368
transform.adjust_path('child1', parent2, child1)
1369
transform.cancel_creation(child1)
1371
transform.cancel_creation(parent1)
1372
# if the transform thinks child1 is still in parent1's limbo
1373
# directory, it will attempt to move it and fail.
1375
self.fail('Transform still thinks child1 is a child of parent1')
1376
transform.finalize()
1378
def test_noname_contents(self):
1379
"""TreeTransform should permit deferring naming files."""
1380
transform, root = self.get_transform()
1381
parent = transform.trans_id_file_id('parent-id')
1383
transform.create_directory(parent)
1385
self.fail("Can't handle contents with no name")
1386
transform.finalize()
1388
def test_noname_contents_nested(self):
1389
"""TreeTransform should permit deferring naming files."""
1390
transform, root = self.get_transform()
1391
parent = transform.trans_id_file_id('parent-id')
1393
transform.create_directory(parent)
1395
self.fail("Can't handle contents with no name")
1396
child = transform.new_directory('child', parent)
1397
transform.adjust_path('parent', root, parent)
1399
self.assertPathExists(self.wt.abspath('parent/child'))
1400
self.assertEqual(1, transform.rename_count)
1402
def test_reuse_name(self):
1403
"""Avoid reusing the same limbo name for different files"""
1404
transform, root = self.get_transform()
1405
parent = transform.new_directory('parent', root)
1406
child1 = transform.new_directory('child', parent)
1408
child2 = transform.new_directory('child', parent)
1410
self.fail('Tranform tried to use the same limbo name twice')
1411
transform.adjust_path('child2', parent, child2)
1413
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1414
# child2 is put into top-level limbo because child1 has already
1415
# claimed the direct limbo path when child2 is created. There is no
1416
# advantage in renaming files once they're in top-level limbo, except
1418
self.assertEqual(2, transform.rename_count)
1420
def test_reuse_when_first_moved(self):
1421
"""Don't avoid direct paths when it is safe to use them"""
1422
transform, root = self.get_transform()
1423
parent = transform.new_directory('parent', root)
1424
child1 = transform.new_directory('child', parent)
1425
transform.adjust_path('child1', parent, child1)
1426
child2 = transform.new_directory('child', parent)
1428
# limbo/new-1 => parent
1429
self.assertEqual(1, transform.rename_count)
1431
def test_reuse_after_cancel(self):
1432
"""Don't avoid direct paths when it is safe to use them"""
1433
transform, root = self.get_transform()
1434
parent2 = transform.new_directory('parent2', root)
1435
child1 = transform.new_directory('child1', parent2)
1436
transform.cancel_creation(parent2)
1437
transform.create_directory(parent2)
1438
child2 = transform.new_directory('child1', parent2)
1439
transform.adjust_path('child2', parent2, child1)
1441
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1442
self.assertEqual(2, transform.rename_count)
1444
def test_finalize_order(self):
1445
"""Finalize must be done in child-to-parent order"""
1446
transform, root = self.get_transform()
1447
parent = transform.new_directory('parent', root)
1448
child = transform.new_directory('child', parent)
1450
transform.finalize()
1452
self.fail('Tried to remove parent before child1')
1454
def test_cancel_with_cancelled_child_should_succeed(self):
1455
transform, root = self.get_transform()
1456
parent = transform.new_directory('parent', root)
1457
child = transform.new_directory('child', parent)
1458
transform.cancel_creation(child)
1459
transform.cancel_creation(parent)
1460
transform.finalize()
1462
def test_rollback_on_directory_clash(self):
1464
wt = self.make_branch_and_tree('.')
1465
tt = TreeTransform(wt) # TreeTransform obtains write lock
1467
foo = tt.new_directory('foo', tt.root)
1468
tt.new_file('bar', foo, 'foobar')
1469
baz = tt.new_directory('baz', tt.root)
1470
tt.new_file('qux', baz, 'quux')
1471
# Ask for a rename 'foo' -> 'baz'
1472
tt.adjust_path('baz', tt.root, foo)
1473
# Lie to tt that we've already resolved all conflicts.
1474
tt.apply(no_conflicts=True)
1478
# The rename will fail because the target directory is not empty (but
1479
# raises FileExists anyway).
1480
err = self.assertRaises(errors.FileExists, tt_helper)
1481
self.assertEndsWith(err.path, "/baz")
1483
def test_two_directories_clash(self):
1485
wt = self.make_branch_and_tree('.')
1486
tt = TreeTransform(wt) # TreeTransform obtains write lock
1488
foo_1 = tt.new_directory('foo', tt.root)
1489
tt.new_directory('bar', foo_1)
1490
# Adding the same directory with a different content
1491
foo_2 = tt.new_directory('foo', tt.root)
1492
tt.new_directory('baz', foo_2)
1493
# Lie to tt that we've already resolved all conflicts.
1494
tt.apply(no_conflicts=True)
1498
err = self.assertRaises(errors.FileExists, tt_helper)
1499
self.assertEndsWith(err.path, "/foo")
1501
def test_two_directories_clash_finalize(self):
1503
wt = self.make_branch_and_tree('.')
1504
tt = TreeTransform(wt) # TreeTransform obtains write lock
1506
foo_1 = tt.new_directory('foo', tt.root)
1507
tt.new_directory('bar', foo_1)
1508
# Adding the same directory with a different content
1509
foo_2 = tt.new_directory('foo', tt.root)
1510
tt.new_directory('baz', foo_2)
1511
# Lie to tt that we've already resolved all conflicts.
1512
tt.apply(no_conflicts=True)
1516
err = self.assertRaises(errors.FileExists, tt_helper)
1517
self.assertEndsWith(err.path, "/foo")
1519
def test_file_to_directory(self):
1520
wt = self.make_branch_and_tree('.')
1521
self.build_tree(['foo'])
1524
tt = TreeTransform(wt)
1525
self.addCleanup(tt.finalize)
1526
foo_trans_id = tt.trans_id_tree_path("foo")
1527
tt.delete_contents(foo_trans_id)
1528
tt.create_directory(foo_trans_id)
1529
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1530
tt.create_file(["aa\n"], bar_trans_id)
1531
tt.version_file("bar-1", bar_trans_id)
1533
self.assertPathExists("foo/bar")
1536
self.assertEqual(wt.kind(wt.path2id("foo")), "directory")
1540
changes = wt.changes_from(wt.basis_tree())
1541
self.assertFalse(changes.has_changed(), changes)
1543
def test_file_to_symlink(self):
1544
self.requireFeature(SymlinkFeature)
1545
wt = self.make_branch_and_tree('.')
1546
self.build_tree(['foo'])
1549
tt = TreeTransform(wt)
1550
self.addCleanup(tt.finalize)
1551
foo_trans_id = tt.trans_id_tree_path("foo")
1552
tt.delete_contents(foo_trans_id)
1553
tt.create_symlink("bar", foo_trans_id)
1555
self.assertPathExists("foo")
1557
self.addCleanup(wt.unlock)
1558
self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")
1560
def test_dir_to_file(self):
1561
wt = self.make_branch_and_tree('.')
1562
self.build_tree(['foo/', 'foo/bar'])
1563
wt.add(['foo', 'foo/bar'])
1565
tt = TreeTransform(wt)
1566
self.addCleanup(tt.finalize)
1567
foo_trans_id = tt.trans_id_tree_path("foo")
1568
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1569
tt.delete_contents(foo_trans_id)
1570
tt.delete_versioned(bar_trans_id)
1571
tt.create_file(["aa\n"], foo_trans_id)
1573
self.assertPathExists("foo")
1575
self.addCleanup(wt.unlock)
1576
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1578
def test_dir_to_hardlink(self):
1579
self.requireFeature(HardlinkFeature)
1580
wt = self.make_branch_and_tree('.')
1581
self.build_tree(['foo/', 'foo/bar'])
1582
wt.add(['foo', 'foo/bar'])
1584
tt = TreeTransform(wt)
1585
self.addCleanup(tt.finalize)
1586
foo_trans_id = tt.trans_id_tree_path("foo")
1587
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1588
tt.delete_contents(foo_trans_id)
1589
tt.delete_versioned(bar_trans_id)
1590
self.build_tree(['baz'])
1591
tt.create_hardlink("baz", foo_trans_id)
1593
self.assertPathExists("foo")
1594
self.assertPathExists("baz")
1596
self.addCleanup(wt.unlock)
1597
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1599
def test_no_final_path(self):
1600
transform, root = self.get_transform()
1601
trans_id = transform.trans_id_file_id('foo')
1602
transform.create_file('bar', trans_id)
1603
transform.cancel_creation(trans_id)
1606
def test_create_from_tree(self):
1607
tree1 = self.make_branch_and_tree('tree1')
1608
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1609
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1610
tree2 = self.make_branch_and_tree('tree2')
1611
tt = TreeTransform(tree2)
1612
foo_trans_id = tt.create_path('foo', tt.root)
1613
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1614
bar_trans_id = tt.create_path('bar', tt.root)
1615
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1617
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1618
self.assertFileEqual('baz', 'tree2/bar')
1620
def test_create_from_tree_bytes(self):
1621
"""Provided lines are used instead of tree content."""
1622
tree1 = self.make_branch_and_tree('tree1')
1623
self.build_tree_contents([('tree1/foo', 'bar'),])
1624
tree1.add('foo', 'foo-id')
1625
tree2 = self.make_branch_and_tree('tree2')
1626
tt = TreeTransform(tree2)
1627
foo_trans_id = tt.create_path('foo', tt.root)
1628
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1630
self.assertFileEqual('qux', 'tree2/foo')
1632
def test_create_from_tree_symlink(self):
1633
self.requireFeature(SymlinkFeature)
1634
tree1 = self.make_branch_and_tree('tree1')
1635
os.symlink('bar', 'tree1/foo')
1636
tree1.add('foo', 'foo-id')
1637
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1638
foo_trans_id = tt.create_path('foo', tt.root)
1639
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1641
self.assertEqual('bar', os.readlink('tree2/foo'))
88
1644
class TransformGroup(object):
90
1646
def __init__(self, dirname, root_id):
1117
2690
conflicts.pop())
2693
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2694
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2696
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2697
('', ''), ('directory', 'directory'), (False, False))
2700
class TestTransformPreview(tests.TestCaseWithTransport):
2702
def create_tree(self):
2703
tree = self.make_branch_and_tree('.')
2704
self.build_tree_contents([('a', 'content 1')])
2705
tree.set_root_id('TREE_ROOT')
2706
tree.add('a', 'a-id')
2707
tree.commit('rev1', rev_id='rev1')
2708
return tree.branch.repository.revision_tree('rev1')
2710
def get_empty_preview(self):
2711
repository = self.make_repository('repo')
2712
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2713
preview = TransformPreview(tree)
2714
self.addCleanup(preview.finalize)
2717
def test_transform_preview(self):
2718
revision_tree = self.create_tree()
2719
preview = TransformPreview(revision_tree)
2720
self.addCleanup(preview.finalize)
2722
def test_transform_preview_tree(self):
2723
revision_tree = self.create_tree()
2724
preview = TransformPreview(revision_tree)
2725
self.addCleanup(preview.finalize)
2726
preview.get_preview_tree()
2728
def test_transform_new_file(self):
2729
revision_tree = self.create_tree()
2730
preview = TransformPreview(revision_tree)
2731
self.addCleanup(preview.finalize)
2732
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2733
preview_tree = preview.get_preview_tree()
2734
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2736
preview_tree.get_file('file2-id').read(), 'content B\n')
2738
def test_diff_preview_tree(self):
2739
revision_tree = self.create_tree()
2740
preview = TransformPreview(revision_tree)
2741
self.addCleanup(preview.finalize)
2742
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2743
preview_tree = preview.get_preview_tree()
2745
show_diff_trees(revision_tree, preview_tree, out)
2746
lines = out.getvalue().splitlines()
2747
self.assertEqual(lines[0], "=== added file 'file2'")
2748
# 3 lines of diff administrivia
2749
self.assertEqual(lines[4], "+content B")
2751
def test_transform_conflicts(self):
2752
revision_tree = self.create_tree()
2753
preview = TransformPreview(revision_tree)
2754
self.addCleanup(preview.finalize)
2755
preview.new_file('a', preview.root, 'content 2')
2756
resolve_conflicts(preview)
2757
trans_id = preview.trans_id_file_id('a-id')
2758
self.assertEqual('a.moved', preview.final_name(trans_id))
2760
def get_tree_and_preview_tree(self):
2761
revision_tree = self.create_tree()
2762
preview = TransformPreview(revision_tree)
2763
self.addCleanup(preview.finalize)
2764
a_trans_id = preview.trans_id_file_id('a-id')
2765
preview.delete_contents(a_trans_id)
2766
preview.create_file('b content', a_trans_id)
2767
preview_tree = preview.get_preview_tree()
2768
return revision_tree, preview_tree
2770
def test_iter_changes(self):
2771
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2772
root = revision_tree.get_root_id()
2773
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2774
(root, root), ('a', 'a'), ('file', 'file'),
2776
list(preview_tree.iter_changes(revision_tree)))
2778
def test_include_unchanged_succeeds(self):
2779
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2780
changes = preview_tree.iter_changes(revision_tree,
2781
include_unchanged=True)
2782
root = revision_tree.get_root_id()
2784
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2786
def test_specific_files(self):
2787
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2788
changes = preview_tree.iter_changes(revision_tree,
2789
specific_files=[''])
2790
self.assertEqual([A_ENTRY], list(changes))
2792
def test_want_unversioned(self):
2793
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2794
changes = preview_tree.iter_changes(revision_tree,
2795
want_unversioned=True)
2796
self.assertEqual([A_ENTRY], list(changes))
2798
def test_ignore_extra_trees_no_specific_files(self):
2799
# extra_trees is harmless without specific_files, so we'll silently
2800
# accept it, even though we won't use it.
2801
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2802
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2804
def test_ignore_require_versioned_no_specific_files(self):
2805
# require_versioned is meaningless without specific_files.
2806
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2807
preview_tree.iter_changes(revision_tree, require_versioned=False)
2809
def test_ignore_pb(self):
2810
# pb could be supported, but TT.iter_changes doesn't support it.
2811
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2812
preview_tree.iter_changes(revision_tree)
2814
def test_kind(self):
2815
revision_tree = self.create_tree()
2816
preview = TransformPreview(revision_tree)
2817
self.addCleanup(preview.finalize)
2818
preview.new_file('file', preview.root, 'contents', 'file-id')
2819
preview.new_directory('directory', preview.root, 'dir-id')
2820
preview_tree = preview.get_preview_tree()
2821
self.assertEqual('file', preview_tree.kind('file-id'))
2822
self.assertEqual('directory', preview_tree.kind('dir-id'))
2824
def test_get_file_mtime(self):
2825
preview = self.get_empty_preview()
2826
file_trans_id = preview.new_file('file', preview.root, 'contents',
2828
limbo_path = preview._limbo_name(file_trans_id)
2829
preview_tree = preview.get_preview_tree()
2830
self.assertEqual(os.stat(limbo_path).st_mtime,
2831
preview_tree.get_file_mtime('file-id'))
2833
def test_get_file_mtime_renamed(self):
2834
work_tree = self.make_branch_and_tree('tree')
2835
self.build_tree(['tree/file'])
2836
work_tree.add('file', 'file-id')
2837
preview = TransformPreview(work_tree)
2838
self.addCleanup(preview.finalize)
2839
file_trans_id = preview.trans_id_tree_file_id('file-id')
2840
preview.adjust_path('renamed', preview.root, file_trans_id)
2841
preview_tree = preview.get_preview_tree()
2842
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2843
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2845
def test_get_file_size(self):
2846
work_tree = self.make_branch_and_tree('tree')
2847
self.build_tree_contents([('tree/old', 'old')])
2848
work_tree.add('old', 'old-id')
2849
preview = TransformPreview(work_tree)
2850
self.addCleanup(preview.finalize)
2851
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2853
tree = preview.get_preview_tree()
2854
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2855
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2857
def test_get_file(self):
2858
preview = self.get_empty_preview()
2859
preview.new_file('file', preview.root, 'contents', 'file-id')
2860
preview_tree = preview.get_preview_tree()
2861
tree_file = preview_tree.get_file('file-id')
2863
self.assertEqual('contents', tree_file.read())
2867
def test_get_symlink_target(self):
2868
self.requireFeature(SymlinkFeature)
2869
preview = self.get_empty_preview()
2870
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2871
preview_tree = preview.get_preview_tree()
2872
self.assertEqual('target',
2873
preview_tree.get_symlink_target('symlink-id'))
2875
def test_all_file_ids(self):
2876
tree = self.make_branch_and_tree('tree')
2877
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2878
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2879
preview = TransformPreview(tree)
2880
self.addCleanup(preview.finalize)
2881
preview.unversion_file(preview.trans_id_file_id('b-id'))
2882
c_trans_id = preview.trans_id_file_id('c-id')
2883
preview.unversion_file(c_trans_id)
2884
preview.version_file('c-id', c_trans_id)
2885
preview_tree = preview.get_preview_tree()
2886
self.assertEqual({'a-id', 'c-id', tree.get_root_id()},
2887
preview_tree.all_file_ids())
2889
def test_path2id_deleted_unchanged(self):
2890
tree = self.make_branch_and_tree('tree')
2891
self.build_tree(['tree/unchanged', 'tree/deleted'])
2892
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2893
preview = TransformPreview(tree)
2894
self.addCleanup(preview.finalize)
2895
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2896
preview_tree = preview.get_preview_tree()
2897
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2898
self.assertIs(None, preview_tree.path2id('deleted'))
2900
def test_path2id_created(self):
2901
tree = self.make_branch_and_tree('tree')
2902
self.build_tree(['tree/unchanged'])
2903
tree.add(['unchanged'], ['unchanged-id'])
2904
preview = TransformPreview(tree)
2905
self.addCleanup(preview.finalize)
2906
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2907
'contents', 'new-id')
2908
preview_tree = preview.get_preview_tree()
2909
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2911
def test_path2id_moved(self):
2912
tree = self.make_branch_and_tree('tree')
2913
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2914
tree.add(['old_parent', 'old_parent/child'],
2915
['old_parent-id', 'child-id'])
2916
preview = TransformPreview(tree)
2917
self.addCleanup(preview.finalize)
2918
new_parent = preview.new_directory('new_parent', preview.root,
2920
preview.adjust_path('child', new_parent,
2921
preview.trans_id_file_id('child-id'))
2922
preview_tree = preview.get_preview_tree()
2923
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2924
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2926
def test_path2id_renamed_parent(self):
2927
tree = self.make_branch_and_tree('tree')
2928
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2929
tree.add(['old_name', 'old_name/child'],
2930
['parent-id', 'child-id'])
2931
preview = TransformPreview(tree)
2932
self.addCleanup(preview.finalize)
2933
preview.adjust_path('new_name', preview.root,
2934
preview.trans_id_file_id('parent-id'))
2935
preview_tree = preview.get_preview_tree()
2936
self.assertIs(None, preview_tree.path2id('old_name/child'))
2937
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2939
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2940
preview_tree = tt.get_preview_tree()
2941
preview_result = list(preview_tree.iter_entries_by_dir(
2945
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2946
self.assertEqual(actual_result, preview_result)
2948
def test_iter_entries_by_dir_new(self):
2949
tree = self.make_branch_and_tree('tree')
2950
tt = TreeTransform(tree)
2951
tt.new_file('new', tt.root, 'contents', 'new-id')
2952
self.assertMatchingIterEntries(tt)
2954
def test_iter_entries_by_dir_deleted(self):
2955
tree = self.make_branch_and_tree('tree')
2956
self.build_tree(['tree/deleted'])
2957
tree.add('deleted', 'deleted-id')
2958
tt = TreeTransform(tree)
2959
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2960
self.assertMatchingIterEntries(tt)
2962
def test_iter_entries_by_dir_unversioned(self):
2963
tree = self.make_branch_and_tree('tree')
2964
self.build_tree(['tree/removed'])
2965
tree.add('removed', 'removed-id')
2966
tt = TreeTransform(tree)
2967
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2968
self.assertMatchingIterEntries(tt)
2970
def test_iter_entries_by_dir_moved(self):
2971
tree = self.make_branch_and_tree('tree')
2972
self.build_tree(['tree/moved', 'tree/new_parent/'])
2973
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2974
tt = TreeTransform(tree)
2975
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2976
tt.trans_id_file_id('moved-id'))
2977
self.assertMatchingIterEntries(tt)
2979
def test_iter_entries_by_dir_specific_file_ids(self):
2980
tree = self.make_branch_and_tree('tree')
2981
tree.set_root_id('tree-root-id')
2982
self.build_tree(['tree/parent/', 'tree/parent/child'])
2983
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2984
tt = TreeTransform(tree)
2985
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2987
def test_symlink_content_summary(self):
2988
self.requireFeature(SymlinkFeature)
2989
preview = self.get_empty_preview()
2990
preview.new_symlink('path', preview.root, 'target', 'path-id')
2991
summary = preview.get_preview_tree().path_content_summary('path')
2992
self.assertEqual(('symlink', None, None, 'target'), summary)
2994
def test_missing_content_summary(self):
2995
preview = self.get_empty_preview()
2996
summary = preview.get_preview_tree().path_content_summary('path')
2997
self.assertEqual(('missing', None, None, None), summary)
2999
def test_deleted_content_summary(self):
3000
tree = self.make_branch_and_tree('tree')
3001
self.build_tree(['tree/path/'])
3003
preview = TransformPreview(tree)
3004
self.addCleanup(preview.finalize)
3005
preview.delete_contents(preview.trans_id_tree_path('path'))
3006
summary = preview.get_preview_tree().path_content_summary('path')
3007
self.assertEqual(('missing', None, None, None), summary)
3009
def test_file_content_summary_executable(self):
3010
preview = self.get_empty_preview()
3011
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3012
preview.set_executability(True, path_id)
3013
summary = preview.get_preview_tree().path_content_summary('path')
3014
self.assertEqual(4, len(summary))
3015
self.assertEqual('file', summary[0])
3016
# size must be known
3017
self.assertEqual(len('contents'), summary[1])
3019
self.assertEqual(True, summary[2])
3020
# will not have hash (not cheap to determine)
3021
self.assertIs(None, summary[3])
3023
def test_change_executability(self):
3024
tree = self.make_branch_and_tree('tree')
3025
self.build_tree(['tree/path'])
3027
preview = TransformPreview(tree)
3028
self.addCleanup(preview.finalize)
3029
path_id = preview.trans_id_tree_path('path')
3030
preview.set_executability(True, path_id)
3031
summary = preview.get_preview_tree().path_content_summary('path')
3032
self.assertEqual(True, summary[2])
3034
def test_file_content_summary_non_exec(self):
3035
preview = self.get_empty_preview()
3036
preview.new_file('path', preview.root, 'contents', 'path-id')
3037
summary = preview.get_preview_tree().path_content_summary('path')
3038
self.assertEqual(4, len(summary))
3039
self.assertEqual('file', summary[0])
3040
# size must be known
3041
self.assertEqual(len('contents'), summary[1])
3043
self.assertEqual(False, summary[2])
3044
# will not have hash (not cheap to determine)
3045
self.assertIs(None, summary[3])
3047
def test_dir_content_summary(self):
3048
preview = self.get_empty_preview()
3049
preview.new_directory('path', preview.root, 'path-id')
3050
summary = preview.get_preview_tree().path_content_summary('path')
3051
self.assertEqual(('directory', None, None, None), summary)
3053
def test_tree_content_summary(self):
3054
preview = self.get_empty_preview()
3055
path = preview.new_directory('path', preview.root, 'path-id')
3056
preview.set_tree_reference('rev-1', path)
3057
summary = preview.get_preview_tree().path_content_summary('path')
3058
self.assertEqual(4, len(summary))
3059
self.assertEqual('tree-reference', summary[0])
3061
def test_annotate(self):
3062
tree = self.make_branch_and_tree('tree')
3063
self.build_tree_contents([('tree/file', 'a\n')])
3064
tree.add('file', 'file-id')
3065
tree.commit('a', rev_id='one')
3066
self.build_tree_contents([('tree/file', 'a\nb\n')])
3067
preview = TransformPreview(tree)
3068
self.addCleanup(preview.finalize)
3069
file_trans_id = preview.trans_id_file_id('file-id')
3070
preview.delete_contents(file_trans_id)
3071
preview.create_file('a\nb\nc\n', file_trans_id)
3072
preview_tree = preview.get_preview_tree()
3078
annotation = preview_tree.annotate_iter('file-id', 'me:')
3079
self.assertEqual(expected, annotation)
3081
def test_annotate_missing(self):
3082
preview = self.get_empty_preview()
3083
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3084
preview_tree = preview.get_preview_tree()
3090
annotation = preview_tree.annotate_iter('file-id', 'me:')
3091
self.assertEqual(expected, annotation)
3093
def test_annotate_rename(self):
3094
tree = self.make_branch_and_tree('tree')
3095
self.build_tree_contents([('tree/file', 'a\n')])
3096
tree.add('file', 'file-id')
3097
tree.commit('a', rev_id='one')
3098
preview = TransformPreview(tree)
3099
self.addCleanup(preview.finalize)
3100
file_trans_id = preview.trans_id_file_id('file-id')
3101
preview.adjust_path('newname', preview.root, file_trans_id)
3102
preview_tree = preview.get_preview_tree()
3106
annotation = preview_tree.annotate_iter('file-id', 'me:')
3107
self.assertEqual(expected, annotation)
3109
def test_annotate_deleted(self):
3110
tree = self.make_branch_and_tree('tree')
3111
self.build_tree_contents([('tree/file', 'a\n')])
3112
tree.add('file', 'file-id')
3113
tree.commit('a', rev_id='one')
3114
self.build_tree_contents([('tree/file', 'a\nb\n')])
3115
preview = TransformPreview(tree)
3116
self.addCleanup(preview.finalize)
3117
file_trans_id = preview.trans_id_file_id('file-id')
3118
preview.delete_contents(file_trans_id)
3119
preview_tree = preview.get_preview_tree()
3120
annotation = preview_tree.annotate_iter('file-id', 'me:')
3121
self.assertIs(None, annotation)
3123
def test_stored_kind(self):
3124
preview = self.get_empty_preview()
3125
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3126
preview_tree = preview.get_preview_tree()
3127
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3129
def test_is_executable(self):
3130
preview = self.get_empty_preview()
3131
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3132
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3133
preview_tree = preview.get_preview_tree()
3134
self.assertEqual(True, preview_tree.is_executable('file-id'))
3136
def test_get_set_parent_ids(self):
3137
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3138
self.assertEqual([], preview_tree.get_parent_ids())
3139
preview_tree.set_parent_ids(['rev-1'])
3140
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3142
def test_plan_file_merge(self):
3143
work_a = self.make_branch_and_tree('wta')
3144
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3145
work_a.add('file', 'file-id')
3146
base_id = work_a.commit('base version')
3147
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3148
preview = TransformPreview(work_a)
3149
self.addCleanup(preview.finalize)
3150
trans_id = preview.trans_id_file_id('file-id')
3151
preview.delete_contents(trans_id)
3152
preview.create_file('b\nc\nd\ne\n', trans_id)
3153
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3154
tree_a = preview.get_preview_tree()
3155
tree_a.set_parent_ids([base_id])
3157
('killed-a', 'a\n'),
3158
('killed-b', 'b\n'),
3159
('unchanged', 'c\n'),
3160
('unchanged', 'd\n'),
3163
], list(tree_a.plan_file_merge('file-id', tree_b)))
3165
def test_plan_file_merge_revision_tree(self):
3166
work_a = self.make_branch_and_tree('wta')
3167
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3168
work_a.add('file', 'file-id')
3169
base_id = work_a.commit('base version')
3170
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3171
preview = TransformPreview(work_a.basis_tree())
3172
self.addCleanup(preview.finalize)
3173
trans_id = preview.trans_id_file_id('file-id')
3174
preview.delete_contents(trans_id)
3175
preview.create_file('b\nc\nd\ne\n', trans_id)
3176
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3177
tree_a = preview.get_preview_tree()
3178
tree_a.set_parent_ids([base_id])
3180
('killed-a', 'a\n'),
3181
('killed-b', 'b\n'),
3182
('unchanged', 'c\n'),
3183
('unchanged', 'd\n'),
3186
], list(tree_a.plan_file_merge('file-id', tree_b)))
3188
def test_walkdirs(self):
3189
preview = self.get_empty_preview()
3190
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3191
# FIXME: new_directory should mark root.
3192
preview.fixup_new_roots()
3193
preview_tree = preview.get_preview_tree()
3194
file_trans_id = preview.new_file('a', preview.root, 'contents',
3196
expected = [(('', 'tree-root'),
3197
[('a', 'a', 'file', None, 'a-id', 'file')])]
3198
self.assertEqual(expected, list(preview_tree.walkdirs()))
3200
def test_extras(self):
3201
work_tree = self.make_branch_and_tree('tree')
3202
self.build_tree(['tree/removed-file', 'tree/existing-file',
3203
'tree/not-removed-file'])
3204
work_tree.add(['removed-file', 'not-removed-file'])
3205
preview = TransformPreview(work_tree)
3206
self.addCleanup(preview.finalize)
3207
preview.new_file('new-file', preview.root, 'contents')
3208
preview.new_file('new-versioned-file', preview.root, 'contents',
3210
tree = preview.get_preview_tree()
3211
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3212
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3215
def test_merge_into_preview(self):
3216
work_tree = self.make_branch_and_tree('tree')
3217
self.build_tree_contents([('tree/file','b\n')])
3218
work_tree.add('file', 'file-id')
3219
work_tree.commit('first commit')
3220
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3221
self.build_tree_contents([('child/file','b\nc\n')])
3222
child_tree.commit('child commit')
3223
child_tree.lock_write()
3224
self.addCleanup(child_tree.unlock)
3225
work_tree.lock_write()
3226
self.addCleanup(work_tree.unlock)
3227
preview = TransformPreview(work_tree)
3228
self.addCleanup(preview.finalize)
3229
file_trans_id = preview.trans_id_file_id('file-id')
3230
preview.delete_contents(file_trans_id)
3231
preview.create_file('a\nb\n', file_trans_id)
3232
preview_tree = preview.get_preview_tree()
3233
merger = Merger.from_revision_ids(preview_tree,
3234
child_tree.branch.last_revision(),
3235
other_branch=child_tree.branch,
3236
tree_branch=work_tree.branch)
3237
merger.merge_type = Merge3Merger
3238
tt = merger.make_merger().make_preview_transform()
3239
self.addCleanup(tt.finalize)
3240
final_tree = tt.get_preview_tree()
3241
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3243
def test_merge_preview_into_workingtree(self):
3244
tree = self.make_branch_and_tree('tree')
3245
tree.set_root_id('TREE_ROOT')
3246
tt = TransformPreview(tree)
3247
self.addCleanup(tt.finalize)
3248
tt.new_file('name', tt.root, 'content', 'file-id')
3249
tree2 = self.make_branch_and_tree('tree2')
3250
tree2.set_root_id('TREE_ROOT')
3251
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3253
merger.merge_type = Merge3Merger
3256
def test_merge_preview_into_workingtree_handles_conflicts(self):
3257
tree = self.make_branch_and_tree('tree')
3258
self.build_tree_contents([('tree/foo', 'bar')])
3259
tree.add('foo', 'foo-id')
3261
tt = TransformPreview(tree)
3262
self.addCleanup(tt.finalize)
3263
trans_id = tt.trans_id_file_id('foo-id')
3264
tt.delete_contents(trans_id)
3265
tt.create_file('baz', trans_id)
3266
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3267
self.build_tree_contents([('tree2/foo', 'qux')])
3268
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3270
merger.merge_type = Merge3Merger
3273
def test_has_filename(self):
3274
wt = self.make_branch_and_tree('tree')
3275
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3276
tt = TransformPreview(wt)
3277
removed_id = tt.trans_id_tree_path('removed')
3278
tt.delete_contents(removed_id)
3279
tt.new_file('new', tt.root, 'contents')
3280
modified_id = tt.trans_id_tree_path('modified')
3281
tt.delete_contents(modified_id)
3282
tt.create_file('modified-contents', modified_id)
3283
self.addCleanup(tt.finalize)
3284
tree = tt.get_preview_tree()
3285
self.assertTrue(tree.has_filename('unmodified'))
3286
self.assertFalse(tree.has_filename('not-present'))
3287
self.assertFalse(tree.has_filename('removed'))
3288
self.assertTrue(tree.has_filename('new'))
3289
self.assertTrue(tree.has_filename('modified'))
3291
def test_is_executable(self):
3292
tree = self.make_branch_and_tree('tree')
3293
preview = TransformPreview(tree)
3294
self.addCleanup(preview.finalize)
3295
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3296
preview_tree = preview.get_preview_tree()
3297
self.assertEqual(False, preview_tree.is_executable('baz-id',
3299
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3301
def test_commit_preview_tree(self):
3302
tree = self.make_branch_and_tree('tree')
3303
rev_id = tree.commit('rev1')
3304
tree.branch.lock_write()
3305
self.addCleanup(tree.branch.unlock)
3306
tt = TransformPreview(tree)
3307
tt.new_file('file', tt.root, 'contents', 'file_id')
3308
self.addCleanup(tt.finalize)
3309
preview = tt.get_preview_tree()
3310
preview.set_parent_ids([rev_id])
3311
builder = tree.branch.get_commit_builder([rev_id])
3312
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3313
builder.finish_inventory()
3314
rev2_id = builder.commit('rev2')
3315
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3316
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3318
def test_ascii_limbo_paths(self):
3319
self.requireFeature(features.UnicodeFilenameFeature)
3320
branch = self.make_branch('any')
3321
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3322
tt = TransformPreview(tree)
3323
self.addCleanup(tt.finalize)
3324
foo_id = tt.new_directory('', ROOT_PARENT)
3325
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3326
limbo_path = tt._limbo_name(bar_id)
3327
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
1120
3330
class FakeSerializer(object):
1121
3331
"""Serializer implementation that simply returns the input.