293
291
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
296
class TestBuildTree(tests.TestCaseWithTransport):
298
def test_build_tree_with_symlinks(self):
299
self.requireFeature(SymlinkFeature)
301
a = ControlDir.create_standalone_workingtree('a')
303
with open('a/foo/bar', 'wb') as f:
305
os.symlink('a/foo/bar', 'a/foo/baz')
306
a.add(['foo', 'foo/bar', 'foo/baz'])
307
a.commit('initial commit')
308
b = ControlDir.create_standalone_workingtree('b')
309
basis = a.basis_tree()
311
self.addCleanup(basis.unlock)
313
self.assertIs(os.path.isdir('b/foo'), True)
314
with open('b/foo/bar', 'rb') as f:
315
self.assertEqual(f.read(), b"contents")
316
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
318
def test_build_with_references(self):
319
tree = self.make_branch_and_tree('source',
320
format='development-subtree')
321
subtree = self.make_branch_and_tree('source/subtree',
322
format='development-subtree')
323
tree.add_reference(subtree)
324
tree.commit('a revision')
325
tree.branch.create_checkout('target')
326
self.assertPathExists('target')
327
self.assertPathExists('target/subtree')
329
def test_file_conflict_handling(self):
330
"""Ensure that when building trees, conflict handling is done"""
331
source = self.make_branch_and_tree('source')
332
target = self.make_branch_and_tree('target')
333
self.build_tree(['source/file', 'target/file'])
334
source.add('file', b'new-file')
335
source.commit('added file')
336
build_tree(source.basis_tree(), target)
338
[DuplicateEntry('Moved existing file to', 'file.moved',
339
'file', None, 'new-file')],
341
target2 = self.make_branch_and_tree('target2')
342
with open('target2/file', 'wb') as target_file, \
343
open('source/file', 'rb') as source_file:
344
target_file.write(source_file.read())
345
build_tree(source.basis_tree(), target2)
346
self.assertEqual([], target2.conflicts())
348
def test_symlink_conflict_handling(self):
349
"""Ensure that when building trees, conflict handling is done"""
350
self.requireFeature(SymlinkFeature)
351
source = self.make_branch_and_tree('source')
352
os.symlink('foo', 'source/symlink')
353
source.add('symlink', b'new-symlink')
354
source.commit('added file')
355
target = self.make_branch_and_tree('target')
356
os.symlink('bar', 'target/symlink')
357
build_tree(source.basis_tree(), target)
359
[DuplicateEntry('Moved existing file to', 'symlink.moved',
360
'symlink', None, 'new-symlink')],
362
target = self.make_branch_and_tree('target2')
363
os.symlink('foo', 'target2/symlink')
364
build_tree(source.basis_tree(), target)
365
self.assertEqual([], target.conflicts())
367
def test_directory_conflict_handling(self):
368
"""Ensure that when building trees, conflict handling is done"""
369
source = self.make_branch_and_tree('source')
370
target = self.make_branch_and_tree('target')
371
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
372
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
373
source.commit('added file')
374
build_tree(source.basis_tree(), target)
375
self.assertEqual([], target.conflicts())
376
self.assertPathExists('target/dir1/file')
378
# Ensure contents are merged
379
target = self.make_branch_and_tree('target2')
380
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
381
build_tree(source.basis_tree(), target)
382
self.assertEqual([], target.conflicts())
383
self.assertPathExists('target2/dir1/file2')
384
self.assertPathExists('target2/dir1/file')
386
# Ensure new contents are suppressed for existing branches
387
target = self.make_branch_and_tree('target3')
388
self.make_branch('target3/dir1')
389
self.build_tree(['target3/dir1/file2'])
390
build_tree(source.basis_tree(), target)
391
self.assertPathDoesNotExist('target3/dir1/file')
392
self.assertPathExists('target3/dir1/file2')
393
self.assertPathExists('target3/dir1.diverted/file')
395
[DuplicateEntry('Diverted to', 'dir1.diverted',
396
'dir1', 'new-dir1', None)],
399
target = self.make_branch_and_tree('target4')
400
self.build_tree(['target4/dir1/'])
401
self.make_branch('target4/dir1/file')
402
build_tree(source.basis_tree(), target)
403
self.assertPathExists('target4/dir1/file')
404
self.assertEqual('directory', file_kind('target4/dir1/file'))
405
self.assertPathExists('target4/dir1/file.diverted')
407
[DuplicateEntry('Diverted to', 'dir1/file.diverted',
408
'dir1/file', 'new-file', None)],
411
def test_mixed_conflict_handling(self):
412
"""Ensure that when building trees, conflict handling is done"""
413
source = self.make_branch_and_tree('source')
414
target = self.make_branch_and_tree('target')
415
self.build_tree(['source/name', 'target/name/'])
416
source.add('name', b'new-name')
417
source.commit('added file')
418
build_tree(source.basis_tree(), target)
420
[DuplicateEntry('Moved existing file to',
421
'name.moved', 'name', None, 'new-name')],
424
def test_raises_in_populated(self):
425
source = self.make_branch_and_tree('source')
426
self.build_tree(['source/name'])
428
source.commit('added name')
429
target = self.make_branch_and_tree('target')
430
self.build_tree(['target/name'])
432
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
433
build_tree, source.basis_tree(), target)
435
def test_build_tree_rename_count(self):
436
source = self.make_branch_and_tree('source')
437
self.build_tree(['source/file1', 'source/dir1/'])
438
source.add(['file1', 'dir1'])
439
source.commit('add1')
440
target1 = self.make_branch_and_tree('target1')
441
transform_result = build_tree(source.basis_tree(), target1)
442
self.assertEqual(2, transform_result.rename_count)
444
self.build_tree(['source/dir1/file2'])
445
source.add(['dir1/file2'])
446
source.commit('add3')
447
target2 = self.make_branch_and_tree('target2')
448
transform_result = build_tree(source.basis_tree(), target2)
449
# children of non-root directories should not be renamed
450
self.assertEqual(2, transform_result.rename_count)
452
def create_ab_tree(self):
453
"""Create a committed test tree with two files"""
454
source = self.make_branch_and_tree('source')
455
self.build_tree_contents([('source/file1', b'A')])
456
self.build_tree_contents([('source/file2', b'B')])
457
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
458
source.commit('commit files')
460
self.addCleanup(source.unlock)
463
def test_build_tree_accelerator_tree(self):
464
source = self.create_ab_tree()
465
self.build_tree_contents([('source/file2', b'C')])
467
real_source_get_file = source.get_file
471
return real_source_get_file(path)
472
source.get_file = get_file
473
target = self.make_branch_and_tree('target')
474
revision_tree = source.basis_tree()
475
revision_tree.lock_read()
476
self.addCleanup(revision_tree.unlock)
477
build_tree(revision_tree, target, source)
478
self.assertEqual(['file1'], calls)
480
self.addCleanup(target.unlock)
481
self.assertEqual([], list(target.iter_changes(revision_tree)))
483
def test_build_tree_accelerator_tree_observes_sha1(self):
484
source = self.create_ab_tree()
485
sha1 = osutils.sha_string(b'A')
486
target = self.make_branch_and_tree('target')
488
self.addCleanup(target.unlock)
489
state = target.current_dirstate()
490
state._cutoff_time = time.time() + 60
491
build_tree(source.basis_tree(), target, source)
492
entry = state._get_entry(0, path_utf8=b'file1')
493
self.assertEqual(sha1, entry[1][0][1])
495
def test_build_tree_accelerator_tree_missing_file(self):
496
source = self.create_ab_tree()
497
os.unlink('source/file1')
498
source.remove(['file2'])
499
target = self.make_branch_and_tree('target')
500
revision_tree = source.basis_tree()
501
revision_tree.lock_read()
502
self.addCleanup(revision_tree.unlock)
503
build_tree(revision_tree, target, source)
505
self.addCleanup(target.unlock)
506
self.assertEqual([], list(target.iter_changes(revision_tree)))
508
def test_build_tree_accelerator_wrong_kind(self):
509
self.requireFeature(SymlinkFeature)
510
source = self.make_branch_and_tree('source')
511
self.build_tree_contents([('source/file1', b'')])
512
self.build_tree_contents([('source/file2', b'')])
513
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
514
source.commit('commit files')
515
os.unlink('source/file2')
516
self.build_tree_contents([('source/file2/', b'C')])
517
os.unlink('source/file1')
518
os.symlink('file2', 'source/file1')
520
real_source_get_file = source.get_file
524
return real_source_get_file(path)
525
source.get_file = get_file
526
target = self.make_branch_and_tree('target')
527
revision_tree = source.basis_tree()
528
revision_tree.lock_read()
529
self.addCleanup(revision_tree.unlock)
530
build_tree(revision_tree, target, source)
531
self.assertEqual([], calls)
533
self.addCleanup(target.unlock)
534
self.assertEqual([], list(target.iter_changes(revision_tree)))
536
def test_build_tree_hardlink(self):
537
self.requireFeature(HardlinkFeature)
538
source = self.create_ab_tree()
539
target = self.make_branch_and_tree('target')
540
revision_tree = source.basis_tree()
541
revision_tree.lock_read()
542
self.addCleanup(revision_tree.unlock)
543
build_tree(revision_tree, target, source, hardlink=True)
545
self.addCleanup(target.unlock)
546
self.assertEqual([], list(target.iter_changes(revision_tree)))
547
source_stat = os.stat('source/file1')
548
target_stat = os.stat('target/file1')
549
self.assertEqual(source_stat, target_stat)
551
# Explicitly disallowing hardlinks should prevent them.
552
target2 = self.make_branch_and_tree('target2')
553
build_tree(revision_tree, target2, source, hardlink=False)
555
self.addCleanup(target2.unlock)
556
self.assertEqual([], list(target2.iter_changes(revision_tree)))
557
source_stat = os.stat('source/file1')
558
target2_stat = os.stat('target2/file1')
559
self.assertNotEqual(source_stat, target2_stat)
561
def test_build_tree_accelerator_tree_moved(self):
562
source = self.make_branch_and_tree('source')
563
self.build_tree_contents([('source/file1', b'A')])
564
source.add(['file1'], [b'file1-id'])
565
source.commit('commit files')
566
source.rename_one('file1', 'file2')
568
self.addCleanup(source.unlock)
569
target = self.make_branch_and_tree('target')
570
revision_tree = source.basis_tree()
571
revision_tree.lock_read()
572
self.addCleanup(revision_tree.unlock)
573
build_tree(revision_tree, target, source)
575
self.addCleanup(target.unlock)
576
self.assertEqual([], list(target.iter_changes(revision_tree)))
578
def test_build_tree_hardlinks_preserve_execute(self):
579
self.requireFeature(HardlinkFeature)
580
source = self.create_ab_tree()
581
tt = source.transform()
582
trans_id = tt.trans_id_tree_path('file1')
583
tt.set_executability(True, trans_id)
585
self.assertTrue(source.is_executable('file1'))
586
target = self.make_branch_and_tree('target')
587
revision_tree = source.basis_tree()
588
revision_tree.lock_read()
589
self.addCleanup(revision_tree.unlock)
590
build_tree(revision_tree, target, source, hardlink=True)
592
self.addCleanup(target.unlock)
593
self.assertEqual([], list(target.iter_changes(revision_tree)))
594
self.assertTrue(source.is_executable('file1'))
596
def install_rot13_content_filter(self, pattern):
598
# self.addCleanup(filters._reset_registry, filters._reset_registry())
599
# below, but that looks a bit... hard to read even if it's exactly
601
original_registry = filters._reset_registry()
603
def restore_registry():
604
filters._reset_registry(original_registry)
605
self.addCleanup(restore_registry)
607
def rot13(chunks, context=None):
609
codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
611
rot13filter = filters.ContentFilter(rot13, rot13)
612
filters.filter_stacks_registry.register(
613
'rot13', {'yes': [rot13filter]}.get)
614
os.mkdir(self.test_home_dir + '/.bazaar')
615
rules_filename = self.test_home_dir + '/.bazaar/rules'
616
with open(rules_filename, 'wb') as f:
617
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
619
def uninstall_rules():
620
os.remove(rules_filename)
622
self.addCleanup(uninstall_rules)
625
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
626
"""build_tree will not hardlink files that have content filtering rules
627
applied to them (but will still hardlink other files from the same tree
630
self.requireFeature(HardlinkFeature)
631
self.install_rot13_content_filter(b'file1')
632
source = self.create_ab_tree()
633
target = self.make_branch_and_tree('target')
634
revision_tree = source.basis_tree()
635
revision_tree.lock_read()
636
self.addCleanup(revision_tree.unlock)
637
build_tree(revision_tree, target, source, hardlink=True)
639
self.addCleanup(target.unlock)
640
self.assertEqual([], list(target.iter_changes(revision_tree)))
641
source_stat = os.stat('source/file1')
642
target_stat = os.stat('target/file1')
643
self.assertNotEqual(source_stat, target_stat)
644
source_stat = os.stat('source/file2')
645
target_stat = os.stat('target/file2')
646
self.assertEqualStat(source_stat, target_stat)
648
def test_case_insensitive_build_tree_inventory(self):
649
if (features.CaseInsensitiveFilesystemFeature.available()
650
or features.CaseInsCasePresFilenameFeature.available()):
651
raise tests.UnavailableFeature('Fully case sensitive filesystem')
652
source = self.make_branch_and_tree('source')
653
self.build_tree(['source/file', 'source/FILE'])
654
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
655
source.commit('added files')
656
# Don't try this at home, kids!
657
# Force the tree to report that it is case insensitive
658
target = self.make_branch_and_tree('target')
659
target.case_sensitive = False
660
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
661
self.assertEqual('file.moved', target.id2path(b'lower-id'))
662
self.assertEqual('FILE', target.id2path(b'upper-id'))
664
def test_build_tree_observes_sha(self):
665
source = self.make_branch_and_tree('source')
666
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
667
source.add(['file1', 'dir', 'dir/file2'],
668
[b'file1-id', b'dir-id', b'file2-id'])
669
source.commit('new files')
670
target = self.make_branch_and_tree('target')
672
self.addCleanup(target.unlock)
673
# We make use of the fact that DirState caches its cutoff time. So we
674
# set the 'safe' time to one minute in the future.
675
state = target.current_dirstate()
676
state._cutoff_time = time.time() + 60
677
build_tree(source.basis_tree(), target)
678
entry1_sha = osutils.sha_file_by_name('source/file1')
679
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
680
# entry[1] is the state information, entry[1][0] is the state of the
681
# working tree, entry[1][0][1] is the sha value for the current working
683
entry1 = state._get_entry(0, path_utf8=b'file1')
684
self.assertEqual(entry1_sha, entry1[1][0][1])
685
# The 'size' field must also be set.
686
self.assertEqual(25, entry1[1][0][2])
687
entry1_state = entry1[1][0]
688
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
689
self.assertEqual(entry2_sha, entry2[1][0][1])
690
self.assertEqual(29, entry2[1][0][2])
691
entry2_state = entry2[1][0]
692
# Now, make sure that we don't have to re-read the content. The
693
# packed_stat should match exactly.
694
self.assertEqual(entry1_sha, target.get_file_sha1('file1'))
695
self.assertEqual(entry2_sha, target.get_file_sha1('dir/file2'))
696
self.assertEqual(entry1_state, entry1[1][0])
697
self.assertEqual(entry2_state, entry2[1][0])
700
294
class TestCommitTransform(tests.TestCaseWithTransport):
702
296
def get_branch(self):