153
160
self.assertConsistentParents(
154
161
[first_revision, second_revision, third_revision], t)
163
def test_set_duplicate_parent_ids(self):
164
t = self.make_branch_and_tree('.')
165
rev1 = t.commit('first post')
166
uncommit(t.branch, tree=t)
167
rev2 = t.commit('second post')
168
uncommit(t.branch, tree=t)
169
rev3 = t.commit('third post')
170
uncommit(t.branch, tree=t)
171
t.set_parent_ids([rev1, rev2, rev2, rev3])
172
# We strip the duplicate, but preserve the ordering
173
self.assertConsistentParents([rev1, rev2, rev3], t)
175
def test_set_duplicate_parent_trees(self):
176
t = self.make_branch_and_tree('.')
177
rev1 = t.commit('first post')
178
uncommit(t.branch, tree=t)
179
rev2 = t.commit('second post')
180
uncommit(t.branch, tree=t)
181
rev3 = t.commit('third post')
182
uncommit(t.branch, tree=t)
183
rev_tree1 = t.branch.repository.revision_tree(rev1)
184
rev_tree2 = t.branch.repository.revision_tree(rev2)
185
rev_tree3 = t.branch.repository.revision_tree(rev3)
186
t.set_parent_trees([(rev1, rev_tree1), (rev2, rev_tree2),
187
(rev2, rev_tree2), (rev3, rev_tree3)])
188
# We strip the duplicate, but preserve the ordering
189
self.assertConsistentParents([rev1, rev2, rev3], t)
191
def test_set_parent_ids_in_ancestry(self):
192
t = self.make_branch_and_tree('.')
193
rev1 = t.commit('first post')
194
rev2 = t.commit('second post')
195
rev3 = t.commit('third post')
196
# Reset the tree, back to rev1
197
t.set_parent_ids([rev1])
198
t.branch.set_last_revision_info(1, rev1)
199
self.assertConsistentParents([rev1], t)
200
t.set_parent_ids([rev1, rev2, rev3])
201
# rev2 is in the ancestry of rev3, so it will be filtered out
202
self.assertConsistentParents([rev1, rev3], t)
203
# Order should be preserved, and the first revision should always be
205
t.set_parent_ids([rev2, rev3, rev1])
206
self.assertConsistentParents([rev2, rev3], t)
208
def test_set_parent_trees_in_ancestry(self):
209
t = self.make_branch_and_tree('.')
210
rev1 = t.commit('first post')
211
rev2 = t.commit('second post')
212
rev3 = t.commit('third post')
213
# Reset the tree, back to rev1
214
t.set_parent_ids([rev1])
215
t.branch.set_last_revision_info(1, rev1)
216
self.assertConsistentParents([rev1], t)
217
rev_tree1 = t.branch.repository.revision_tree(rev1)
218
rev_tree2 = t.branch.repository.revision_tree(rev2)
219
rev_tree3 = t.branch.repository.revision_tree(rev3)
220
t.set_parent_trees([(rev1, rev_tree1), (rev2, rev_tree2),
222
# rev2 is in the ancestry of rev3, so it will be filtered out
223
self.assertConsistentParents([rev1, rev3], t)
224
# Order should be preserved, and the first revision should always be
226
t.set_parent_trees([(rev2, rev_tree2), (rev1, rev_tree1),
228
self.assertConsistentParents([rev2, rev3], t)
230
def test_unicode_symlink(self):
231
# this tests bug #272444
232
self.requireFeature(tests.SymlinkFeature)
233
self.requireFeature(tests.UnicodeFilenameFeature)
235
tree = self.make_branch_and_tree('tree1')
237
# The link points to a file whose name is an omega
238
# U+03A9 GREEK CAPITAL LETTER OMEGA
239
# UTF-8: ce a9 UTF-16BE: 03a9 Decimal: Ω
241
link_name = u'\N{Euro Sign}link'
242
os.symlink(target, 'tree1/' + link_name)
243
tree.add([link_name],['link-id'])
245
revision1 = tree.commit('added a link to a Unicode target')
246
revision2 = tree.commit('this revision will be discarded')
247
tree.set_parent_ids([revision1])
249
self.addCleanup(tree.unlock)
250
# Check that the symlink target is safely round-tripped in the trees.
251
self.assertEqual(target, tree.get_symlink_target('link-id'))
252
basis = tree.basis_tree()
253
self.assertEqual(target, basis.get_symlink_target('link-id'))
157
256
class TestAddParent(TestParents):
230
329
tree.add_parent_tree((first_revision,
231
330
tree.branch.repository.revision_tree(first_revision)))
232
331
self.assertConsistentParents([second_revision, first_revision], tree)
234
333
def test_add_second_parent_tree_ghost(self):
235
334
"""Test adding the second parent id - as a ghost"""
236
335
tree = self.make_branch_and_tree('.')
237
336
first_revision = tree.commit('first post')
238
337
tree.add_parent_tree(('second', None))
239
338
self.assertConsistentParents([first_revision, 'second'], tree)
341
class UpdateToOneParentViaDeltaTests(TestCaseWithWorkingTree):
342
"""Tests for the update_basis_by_delta call.
344
This is intuitively defined as 'apply an inventory delta to the basis and
345
discard other parents', but for trees that have an inventory that is not
346
managed as a tree-by-id, the implementation requires roughly duplicated
347
tests with those for apply_inventory_delta on the main tree.
350
def assertDeltaApplicationResultsInExpectedBasis(self, tree, revid, delta,
354
tree.update_basis_by_delta(revid, delta)
357
# check the last revision was adjusted to rev_id
358
self.assertEqual(revid, tree.last_revision())
359
# check the parents are what we expect
360
self.assertEqual([revid], tree.get_parent_ids())
361
# check that the basis tree has the inventory we expect from applying
363
result_basis = tree.basis_tree()
364
result_basis.lock_read()
366
self.assertEqual(expected_inventory, result_basis.inventory)
368
result_basis.unlock()
370
def make_inv_delta(self, old, new):
371
"""Make an inventory delta from two inventories."""
372
old_ids = set(old._byid.iterkeys())
373
new_ids = set(new._byid.iterkeys())
374
adds = new_ids - old_ids
375
deletes = old_ids - new_ids
376
common = old_ids.intersection(new_ids)
378
for file_id in deletes:
379
delta.append((old.id2path(file_id), None, file_id, None))
381
delta.append((None, new.id2path(file_id), file_id, new[file_id]))
382
for file_id in common:
383
if old[file_id] != new[file_id]:
384
delta.append((old.id2path(file_id), new.id2path(file_id),
385
file_id, new[file_id]))
388
def fake_up_revision(self, tree, revid, shape):
391
tree.branch.repository.start_write_group()
393
if shape.root.revision is None:
394
shape.root.revision = revid
395
sha1 = tree.branch.repository.add_inventory(revid, shape, [])
396
rev = Revision(timestamp=0,
398
committer="Foo Bar <foo@example.com>",
402
tree.branch.repository.add_revision(revid, rev)
404
tree.branch.repository.abort_write_group()
407
tree.branch.repository.commit_write_group()
411
def add_entry(self, inv, rev_id, entry):
412
entry.revision = rev_id
415
def add_dir(self, inv, rev_id, file_id, parent_id, name):
416
new_dir = InventoryDirectory(file_id, name, parent_id)
417
self.add_entry(inv, rev_id, new_dir)
419
def add_file(self, inv, rev_id, file_id, parent_id, name, sha, size):
420
new_file = InventoryFile(file_id, name, parent_id)
421
new_file.text_sha1 = sha
422
new_file.text_size = size
423
self.add_entry(inv, rev_id, new_file)
425
def add_link(self, inv, rev_id, file_id, parent_id, name, target):
426
new_link = InventoryLink(file_id, name, parent_id)
427
new_link.symlink_target = target
428
self.add_entry(inv, rev_id, new_link)
430
def add_new_root(self, new_shape, old_revid, new_revid):
431
if self.bzrdir_format.repository_format.rich_root_data:
432
self.add_dir(new_shape, old_revid, 'root-id', None, '')
434
self.add_dir(new_shape, new_revid, 'root-id', None, '')
436
def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,
437
new_shape, new_revid, extra_parent=None):
438
# set the inventory revision ids.
439
basis_shape.revision_id = basis_revid
440
new_shape.revision_id = new_revid
441
delta = self.make_inv_delta(basis_shape, new_shape)
442
tree = self.make_branch_and_tree('tree')
443
# the shapes need to be in the tree's repository to be able to set them
444
# as a parent, but the file content is not needed.
445
if basis_revid is not None:
446
self.fake_up_revision(tree, basis_revid, basis_shape)
447
parents = [basis_revid]
448
if extra_parent is not None:
449
parents.append(extra_parent)
450
tree.set_parent_ids(parents)
451
self.fake_up_revision(tree, new_revid, new_shape)
452
# give tree an inventory of new_shape
453
tree._write_inventory(new_shape)
454
self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,
456
# The tree should be internally consistent; while this is a moderately
457
# large hammer, this is a particularly sensitive area of code, so the
458
# extra assurance is well worth it.
460
osutils.rmtree('tree')
462
def test_no_parents_just_root(self):
463
"""Test doing an empty commit - no parent, set a root only."""
464
basis_shape = Inventory(root_id=None) # empty tree
465
new_shape = Inventory() # tree with a root
466
self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
469
def test_no_parents_full_tree(self):
470
"""Test doing a regular initial commit with files and dirs."""
471
basis_shape = Inventory(root_id=None) # empty tree
473
new_shape = Inventory(root_id=None)
474
self.add_dir(new_shape, revid, 'root-id', None, '')
475
self.add_link(new_shape, revid, 'link-id', 'root-id', 'link', 'target')
476
self.add_file(new_shape, revid, 'file-id', 'root-id', 'file', '1' * 32,
478
self.add_dir(new_shape, revid, 'dir-id', 'root-id', 'dir')
479
self.add_file(new_shape, revid, 'subfile-id', 'dir-id', 'subfile',
481
self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
484
def test_file_content_change(self):
485
old_revid = 'old-parent'
486
basis_shape = Inventory(root_id=None)
487
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
488
self.add_file(basis_shape, old_revid, 'file-id', 'root-id', 'file',
490
new_revid = 'new-parent'
491
new_shape = Inventory(root_id=None)
492
self.add_new_root(new_shape, old_revid, new_revid)
493
self.add_file(new_shape, new_revid, 'file-id', 'root-id', 'file',
495
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
496
new_shape, new_revid)
498
def test_link_content_change(self):
499
old_revid = 'old-parent'
500
basis_shape = Inventory(root_id=None)
501
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
502
self.add_link(basis_shape, old_revid, 'link-id', 'root-id', 'link',
504
new_revid = 'new-parent'
505
new_shape = Inventory(root_id=None)
506
self.add_new_root(new_shape, old_revid, new_revid)
507
self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
509
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
510
new_shape, new_revid)
512
def test_kind_changes(self):
513
def do_file(inv, revid):
514
self.add_file(inv, revid, 'path-id', 'root-id', 'path', '1' * 32,
516
def do_link(inv, revid):
517
self.add_link(inv, revid, 'path-id', 'root-id', 'path', 'target')
518
def do_dir(inv, revid):
519
self.add_dir(inv, revid, 'path-id', 'root-id', 'path')
520
for old_factory in (do_file, do_link, do_dir):
521
for new_factory in (do_file, do_link, do_dir):
522
if old_factory == new_factory:
524
old_revid = 'old-parent'
525
basis_shape = Inventory(root_id=None)
526
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
527
old_factory(basis_shape, old_revid)
528
new_revid = 'new-parent'
529
new_shape = Inventory(root_id=None)
530
self.add_new_root(new_shape, old_revid, new_revid)
531
new_factory(new_shape, new_revid)
532
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
533
new_shape, new_revid)
535
def test_content_from_second_parent_is_dropped(self):
536
left_revid = 'left-parent'
537
basis_shape = Inventory(root_id=None)
538
self.add_dir(basis_shape, left_revid, 'root-id', None, '')
539
self.add_link(basis_shape, left_revid, 'link-id', 'root-id', 'link',
541
# the right shape has content - file, link, subdir with a child,
542
# that should all be discarded by the call.
543
right_revid = 'right-parent'
544
right_shape = Inventory(root_id=None)
545
self.add_dir(right_shape, left_revid, 'root-id', None, '')
546
self.add_link(right_shape, right_revid, 'link-id', 'root-id', 'link',
548
self.add_dir(right_shape, right_revid, 'subdir-id', 'root-id', 'dir')
549
self.add_file(right_shape, right_revid, 'file-id', 'subdir-id', 'file',
551
new_revid = 'new-parent'
552
new_shape = Inventory(root_id=None)
553
self.add_new_root(new_shape, left_revid, new_revid)
554
self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
556
self.assertTransitionFromBasisToShape(basis_shape, left_revid,
557
new_shape, new_revid, right_revid)
559
def test_parent_id_changed(self):
560
# test that when the only change to an entry is its parent id changing
561
# that it is handled correctly (that is it keeps the same path)
562
old_revid = 'old-parent'
563
basis_shape = Inventory(root_id=None)
564
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
565
self.add_dir(basis_shape, old_revid, 'orig-parent-id', 'root-id', 'dir')
566
self.add_dir(basis_shape, old_revid, 'dir-id', 'orig-parent-id', 'dir')
567
new_revid = 'new-parent'
568
new_shape = Inventory(root_id=None)
569
self.add_new_root(new_shape, old_revid, new_revid)
570
self.add_dir(new_shape, new_revid, 'new-parent-id', 'root-id', 'dir')
571
self.add_dir(new_shape, new_revid, 'dir-id', 'new-parent-id', 'dir')
572
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
573
new_shape, new_revid)
575
def test_name_changed(self):
576
# test that when the only change to an entry is its name changing that
577
# it is handled correctly (that is it keeps the same parent id)
578
old_revid = 'old-parent'
579
basis_shape = Inventory(root_id=None)
580
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
581
self.add_dir(basis_shape, old_revid, 'parent-id', 'root-id', 'origdir')
582
self.add_dir(basis_shape, old_revid, 'dir-id', 'parent-id', 'olddir')
583
new_revid = 'new-parent'
584
new_shape = Inventory(root_id=None)
585
self.add_new_root(new_shape, old_revid, new_revid)
586
self.add_dir(new_shape, new_revid, 'parent-id', 'root-id', 'newdir')
587
self.add_dir(new_shape, new_revid, 'dir-id', 'parent-id', 'newdir')
588
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
589
new_shape, new_revid)
591
def test_parent_child_swap(self):
592
# test a A->A/B and A/B->A path swap.
593
old_revid = 'old-parent'
594
basis_shape = Inventory(root_id=None)
595
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
596
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
597
self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
598
self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
599
new_revid = 'new-parent'
600
new_shape = Inventory(root_id=None)
601
self.add_new_root(new_shape, old_revid, new_revid)
602
self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
603
self.add_dir(new_shape, new_revid, 'dir-id-A', 'dir-id-B', 'B')
604
self.add_link(new_shape, new_revid, 'link-id-C', 'dir-id-A', 'C', 'C')
605
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
606
new_shape, new_revid)
608
def test_parent_deleted_child_renamed(self):
609
# test a A->None and A/B->A.
610
old_revid = 'old-parent'
611
basis_shape = Inventory(root_id=None)
612
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
613
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
614
self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
615
self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
616
new_revid = 'new-parent'
617
new_shape = Inventory(root_id=None)
618
self.add_new_root(new_shape, old_revid, new_revid)
619
self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
620
self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'C')
621
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
622
new_shape, new_revid)
624
def test_dir_to_root(self):
626
old_revid = 'old-parent'
627
basis_shape = Inventory(root_id=None)
628
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
629
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
630
self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'B')
631
new_revid = 'new-parent'
632
new_shape = Inventory(root_id=None)
633
self.add_dir(new_shape, new_revid, 'dir-id-A', None, '')
634
self.add_link(new_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'B')
635
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
636
new_shape, new_revid)
638
def test_path_swap(self):
639
# test a A->B and B->A path swap.
640
old_revid = 'old-parent'
641
basis_shape = Inventory(root_id=None)
642
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
643
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
644
self.add_dir(basis_shape, old_revid, 'dir-id-B', 'root-id', 'B')
645
self.add_link(basis_shape, old_revid, 'link-id-C', 'root-id', 'C', 'C')
646
self.add_link(basis_shape, old_revid, 'link-id-D', 'root-id', 'D', 'D')
647
self.add_file(basis_shape, old_revid, 'file-id-E', 'root-id', 'E',
649
self.add_file(basis_shape, old_revid, 'file-id-F', 'root-id', 'F',
651
new_revid = 'new-parent'
652
new_shape = Inventory(root_id=None)
653
self.add_new_root(new_shape, old_revid, new_revid)
654
self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
655
self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
656
self.add_link(new_shape, new_revid, 'link-id-C', 'root-id', 'D', 'C')
657
self.add_link(new_shape, new_revid, 'link-id-D', 'root-id', 'C', 'D')
658
self.add_file(new_shape, new_revid, 'file-id-E', 'root-id', 'F',
660
self.add_file(new_shape, new_revid, 'file-id-F', 'root-id', 'E',
662
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
663
new_shape, new_revid)
666
# test adding paths and dirs, including adding to a newly added dir.
667
old_revid = 'old-parent'
668
basis_shape = Inventory(root_id=None)
669
# with a root, so its a commit after the first.
670
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
671
new_revid = 'new-parent'
672
new_shape = Inventory(root_id=None)
673
self.add_new_root(new_shape, old_revid, new_revid)
674
self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
675
self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
676
self.add_file(new_shape, new_revid, 'file-id-C', 'root-id', 'C',
678
self.add_file(new_shape, new_revid, 'file-id-D', 'dir-id-A', 'D',
680
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
681
new_shape, new_revid)
683
def test_removes(self):
684
# test removing paths, including paths that are within other also
686
old_revid = 'old-parent'
687
basis_shape = Inventory(root_id=None)
688
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
689
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
690
self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
691
self.add_file(basis_shape, old_revid, 'file-id-C', 'root-id', 'C',
693
self.add_file(basis_shape, old_revid, 'file-id-D', 'dir-id-A', 'D',
695
new_revid = 'new-parent'
696
new_shape = Inventory(root_id=None)
697
self.add_new_root(new_shape, old_revid, new_revid)
698
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
699
new_shape, new_revid)
701
def test_move_to_added_dir(self):
702
old_revid = 'old-parent'
703
basis_shape = Inventory(root_id=None)
704
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
705
self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
706
new_revid = 'new-parent'
707
new_shape = Inventory(root_id=None)
708
self.add_new_root(new_shape, old_revid, new_revid)
709
self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
710
self.add_link(new_shape, new_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
711
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
712
new_shape, new_revid)
714
def test_move_from_removed_dir(self):
715
old_revid = 'old-parent'
716
basis_shape = Inventory(root_id=None)
717
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
718
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
719
self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
720
new_revid = 'new-parent'
721
new_shape = Inventory(root_id=None)
722
self.add_new_root(new_shape, old_revid, new_revid)
723
self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
724
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
725
new_shape, new_revid)
727
def test_move_moves_children_recursively(self):
728
old_revid = 'old-parent'
729
basis_shape = Inventory(root_id=None)
730
self.add_dir(basis_shape, old_revid, 'root-id', None, '')
731
self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
732
self.add_dir(basis_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
733
self.add_link(basis_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
734
new_revid = 'new-parent'
735
new_shape = Inventory(root_id=None)
736
self.add_new_root(new_shape, old_revid, new_revid)
738
self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
740
self.add_dir(new_shape, old_revid, 'dir-id-B', 'dir-id-A', 'B')
741
self.add_link(new_shape, old_revid, 'link-id-C', 'dir-id-B', 'C', 'D')
742
self.assertTransitionFromBasisToShape(basis_shape, old_revid,
743
new_shape, new_revid)