Rev 2866: Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit. in http://people.ubuntu.com/~robertc/baz2.0/wt.update_to_one_parent_via_delta

Robert Collins robertc at robertcollins.net
Thu Sep 27 10:04:36 BST 2007


At http://people.ubuntu.com/~robertc/baz2.0/wt.update_to_one_parent_via_delta

------------------------------------------------------------
revno: 2866
revision-id: robertc at robertcollins.net-20070927090426-u91r9ki6vrpjqcsd
parent: pqm at pqm.ubuntu.com-20070925205148-yd27v1odc65uql59
committer: Robert Collins <robertc at robertcollins.net>
branch nick: wt.update_to_one_parent_via_delta
timestamp: Thu 2007-09-27 19:04:26 +1000
message:
  Create new mutable tree method update_to_one_parent_via_delta for eventual use by commit.
modified:
  bzrlib/inventory.py            inventory.py-20050309040759-6648b84ca2005b37
  bzrlib/mutabletree.py          mutabletree.py-20060906023413-4wlkalbdpsxi2r4y-2
  bzrlib/tests/workingtree_implementations/test_inv.py test_inv.py-20070311221604-ighlq8tbn5xq0kuo-1
  bzrlib/tests/workingtree_implementations/test_parents.py test_set_parents.py-20060807231740-yicmnlci1mj8smu1-1
=== modified file 'bzrlib/inventory.py'
--- a/bzrlib/inventory.py	2007-09-21 04:22:53 +0000
+++ b/bzrlib/inventory.py	2007-09-27 09:04:26 +0000
@@ -946,6 +946,69 @@
             self._byid = {}
         self.revision_id = revision_id
 
+    def __repr__(self):
+        return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
+
+    def apply_delta(self, delta):
+        """Apply a delta to this inventory.
+
+        :param delta: A list of changes to apply. After all the changes are
+            applied the final inventory must be internally consistent, but it
+            is ok to supply changes which, if only half-applied would have an
+            invalid result - such as supplying two changes which rename two
+            files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
+            ('B', 'A', 'B-id', b_entry)].
+
+            Each change is a tuple, of the form (old_path, new_path, file_id,
+            new_entry).
+            
+            When new_path is None, the change indicates the removal of an entry
+            from the inventory and new_entry will be ignored (using None is
+            appropriate). If new_path is not None, then new_entry must be an
+            InventoryEntry instance, which will be incorporated into the
+            inventory (and replace any existing entry with the same file id).
+            
+            When old_path is None, the change indicates the addition of
+            a new entry to the inventory.
+            
+            When neither new_path nor old_path are None, the change is a
+            modification to an entry, such as a rename, reparent, kind change
+            etc. 
+
+            The children attribute of new_entry is ignored. This is because
+            apply_inventory_delta preserves children automatically across
+            alterations to the parent of the children, and cases where the
+            parent id of a child is changing require the child to be passed in
+            as a separate change regardless. E.g. in the recursive deletion of
+            a directory - the directories children must be included in the
+            delta, or the final inventory will be invalid.
+        """
+        children = {}
+        # Remove all affected items which were in the original inventory,
+        # starting with the longest paths, thus ensuring parents are examined
+        # after their children, which means that everything we examine has no
+        # modified children remaining by the time we examine it.
+        for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
+                                        if op is not None), reverse=True):
+            if file_id not in self:
+                # adds come later
+                continue
+            # Preserve unaltered children of file_id for later reinsertion.
+            children[file_id] = getattr(self[file_id], 'children', {})
+            # Remove file_id and the unaltered children. If file_id is not
+            # being deleted it will be reinserted back later.
+            self.remove_recursive_id(file_id)
+        # Insert all affected which should be in the new inventory, reattaching
+        # their children if they had any. This is done from shortest path to
+        # longest, ensuring that items which were modified and whose parents in
+        # the resulting inventory were also modified, are inserted after their
+        # parents.
+        for new_path, new_entry in sorted((np, e) for op, np, f, e in
+                                          delta if np is not None):
+            if new_entry.kind == 'directory':
+                new_entry.children = children.get(new_entry.file_id, {})
+            self.add(new_entry)
+
     def _set_root(self, ie):
         self.root = ie
         self._byid = {self.root.file_id: self.root}

=== modified file 'bzrlib/mutabletree.py'
--- a/bzrlib/mutabletree.py	2007-08-07 20:45:21 +0000
+++ b/bzrlib/mutabletree.py	2007-09-27 09:04:26 +0000
@@ -29,6 +29,7 @@
     bzrdir,
     )
 from bzrlib.osutils import dirname
+from bzrlib.revisiontree import RevisionTree
 from bzrlib.trace import mutter, warning
 """)
 
@@ -159,37 +160,14 @@
     def apply_inventory_delta(self, changes):
         """Apply changes to the inventory as an atomic operation.
 
-        The argument is a set of changes to apply.  It must describe a
-        valid result, but the order is not important.  Specifically,
-        intermediate stages *may* be invalid, such as when two files
-        swap names.
-
-        The changes should be structured as a list of tuples, of the form
-        (old_path, new_path, file_id, new_entry).  For creation, old_path
-        must be None.  For deletion, new_path and new_entry must be None.
-        file_id is always non-None.  For renames and other mutations, all
-        values must be non-None.
-
-        If the new_entry is a directory, its children should be an empty
-        dict.  Children are handled by apply_inventory_delta itself.
-
-        :param changes: A list of tuples for the change to apply:
-            [(old_path, new_path, file_id, new_inventory_entry), ...]
+        :param changes: An inventory delta to apply to the working tree's
+            inventory.
+        :return None:
+        :seealso Inventory.apply_delta: For details on the changes parameter.
         """
         self.flush()
         inv = self.inventory
-        children = {}
-        for old_path, file_id in sorted(((op, f) for op, np, f, e in changes
-                                        if op is not None), reverse=True):
-            if file_id not in inv:
-                continue
-            children[file_id] = getattr(inv[file_id], 'children', {})
-            inv.remove_recursive_id(file_id)
-        for new_path, new_entry in sorted((np, e) for op, np, f, e in
-                                          changes if np is not None):
-            if getattr(new_entry, 'children', None) is not None:
-                new_entry.children = children.get(new_entry.file_id, {})
-            inv.add(new_entry)
+        inv.apply_delta(changes)
         self._write_inventory(inv)
 
     @needs_write_lock
@@ -453,6 +431,35 @@
                 self.read_working_inventory()
         return added, ignored
 
+    def update_to_one_parent_via_delta(self, new_revid, delta):
+        """Update the parents of this tree after a commit.
+
+        This gives the tree one parent, with revision id new_revid. The
+        inventory delta delta is applied ot the current basis tree to generate
+        the inventory for the parent new_revid, and all other parent trees are
+        discarded.
+
+        :param new_revid: The new revision id for the trees parent.
+        :param delta: An inventory delta (see apply_inventory_delta) describing
+            the changes from the current left most parent revision to new_revid.
+        """
+        # if the tree is updated by a pull to the branch, as happens in
+        # WorkingTree2, when there was no separation between branch and tree,
+        # then just clear merges, efficiency is not a concern for now as this
+        # is legacy environments only, and they are slow regardless.
+        if self.last_revision() == new_revid:
+            self.set_parent_ids([new_revid])
+            return
+        # generic implementation based on Inventory manipulation. See
+        # WorkingTree classes for optimised versions for specific format trees.
+        basis = self.basis_tree()
+        basis.lock_read()
+        inventory = basis.inventory
+        basis.unlock()
+        inventory.apply_delta(delta)
+        rev_tree = RevisionTree(self.branch.repository, inventory, new_revid)
+        self.set_parent_trees([(new_revid, rev_tree)])
+
 
 class _FastPath(object):
     """A path object with fast accessors for things like basename."""

=== modified file 'bzrlib/tests/workingtree_implementations/test_inv.py'
--- a/bzrlib/tests/workingtree_implementations/test_inv.py	2007-08-29 16:09:51 +0000
+++ b/bzrlib/tests/workingtree_implementations/test_inv.py	2007-09-27 09:04:26 +0000
@@ -154,10 +154,36 @@
                                   ('foo/bar', None, 'bar-id', None)])
         self.assertIs(None, wt.path2id('foo'))
 
+    def test_rename_dir_with_children(self):
+        wt = self.make_branch_and_tree('.')
+        wt.lock_write()
+        root_id = wt.get_root_id()
+        self.addCleanup(wt.unlock)
+        self.build_tree(['foo/', 'foo/bar'])
+        wt.add(['foo', 'foo/bar'],
+               ['foo-id', 'bar-id'])
+        wt.apply_inventory_delta([('foo', 'baz', 'foo-id',
+            inventory.InventoryDirectory('foo-id', 'baz', root_id))])
+        # foo/bar should have been followed the rename of its parent to baz/bar
+        self.assertEqual('baz/bar', wt.id2path('bar-id'))
+
+    def test_rename_dir_with_children_with_children(self):
+        wt = self.make_branch_and_tree('.')
+        wt.lock_write()
+        root_id = wt.get_root_id()
+        self.addCleanup(wt.unlock)
+        self.build_tree(['foo/', 'foo/bar/', 'foo/bar/baz'])
+        wt.add(['foo', 'foo/bar', 'foo/bar/baz'],
+               ['foo-id', 'bar-id', 'baz-id'])
+        wt.apply_inventory_delta([('foo', 'quux', 'foo-id',
+            inventory.InventoryDirectory('foo-id', 'quux', root_id))])
+        # foo/bar/baz should have been followed the rename of its parent's
+        # parent to quux/bar/baz
+        self.assertEqual('quux/bar/baz', wt.id2path('baz-id'))
+
     def test_rename_file(self):
         wt = self.make_branch_and_tree('.')
         wt.lock_write()
-        root_id = wt.get_root_id()
         self.addCleanup(wt.unlock)
         self.build_tree(['foo/', 'foo/bar', 'baz/'])
         wt.add(['foo', 'foo/bar', 'baz'],
@@ -197,6 +223,8 @@
         self.build_tree(['dir/', 'dir/child', 'other/'])
         wt.add(['dir', 'dir/child', 'other'],
                ['dir-id', 'child-id', 'other-id'])
+        # this delta moves dir-id to dir2 and reparents 
+        # child-id wto a parent of other-id
         wt.apply_inventory_delta([('dir', 'dir2', 'dir-id',
             inventory.InventoryDirectory('dir-id', 'dir2', root_id)),
             ('dir/child', 'other/child', 'child-id',

=== modified file 'bzrlib/tests/workingtree_implementations/test_parents.py'
--- a/bzrlib/tests/workingtree_implementations/test_parents.py	2007-07-12 07:22:52 +0000
+++ b/bzrlib/tests/workingtree_implementations/test_parents.py	2007-09-27 09:04:26 +0000
@@ -16,13 +16,23 @@
 
 """Tests of the parent related functions of WorkingTrees."""
 
+from errno import EEXIST
 import os
 
 from bzrlib import (
     errors,
+    osutils,
     revision as _mod_revision,
     symbol_versioning,
     )
+from bzrlib.inventory import (
+    Inventory,
+    InventoryFile,
+    InventoryDirectory,
+    InventoryLink,
+    )
+from bzrlib.revision import Revision
+from bzrlib.tests import SymlinkFeature, TestNotApplicable
 from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
 from bzrlib.uncommit import uncommit
 
@@ -237,3 +247,328 @@
         first_revision = tree.commit('first post')
         tree.add_parent_tree(('second', None))
         self.assertConsistentParents([first_revision, 'second'], tree)
+
+
+class UpdateToOneParentViaDeltaTests(TestParents):
+    """Tests for the update_to_one_parent_via_delta call.
+    
+    This is intuitively defined as 'apply an inventory delta to the basis and
+    discard other parents', but for trees that have an inventory that is not
+    managed as a tree-by-id, the implementation requires roughly duplicated
+    tests with those for apply_inventory_delta on the main tree.
+    """
+
+    def assertDeltaApplicationResultsInExpectedBasis(self, tree, revid, delta,
+        expected_inventory):
+        tree.update_to_one_parent_via_delta(revid, delta)
+        # check the last revision was adjusted to rev_id
+        self.assertEqual(revid, tree.last_revision())
+        # check the parents are what we expect
+        self.assertEqual([revid], tree.get_parent_ids())
+        # check that the basis tree has the inventory we expect from applying
+        # the delta.
+        result_basis = tree.basis_tree()
+        result_basis.lock_read()
+        self.addCleanup(result_basis.unlock)
+        self.assertEqual(expected_inventory, result_basis.inventory)
+
+    def make_inv_delta(self, old, new):
+        """Make an inventory delta from two inventories."""
+        old_ids = set(old._byid.iterkeys())
+        new_ids = set(new._byid.iterkeys())
+        adds = new_ids - old_ids
+        deletes = old_ids - new_ids
+        common = old_ids.intersection(new_ids)
+        delta = []
+        for file_id in deletes:
+            delta.append((old.id2path(file_id), None, file_id, None))
+        for file_id in adds:
+            delta.append((None, new.id2path(file_id), file_id, new[file_id]))
+        for file_id in common:
+            if old[file_id] != new[file_id]:
+                delta.append((old.id2path(file_id), new.id2path(file_id),
+                    file_id, new[file_id]))
+        return delta
+
+    def fake_up_revision(self, tree, revid, shape):
+        tree.lock_write()
+        try:
+            tree.branch.repository.start_write_group()
+            try:
+                if shape.root.revision is None:
+                    shape.root.revision = revid
+                sha1 = tree.branch.repository.add_inventory(revid, shape, [])
+                rev = Revision(timestamp=0,
+                               timezone=None,
+                               committer="Foo Bar <foo at example.com>",
+                               message="Message",
+                               inventory_sha1=sha1,
+                               revision_id=revid)
+                tree.branch.repository.add_revision(revid, rev)
+            except:
+                tree.branch.repository.abort_write_group()
+                raise
+            else:
+                tree.branch.repository.commit_write_group()
+        finally:
+            tree.unlock()
+
+    def add_entry(self, inv, rev_id, entry):
+        entry.revision = rev_id
+        inv.add(entry)
+
+    def add_dir(self, inv, rev_id, file_id, parent_id, name):
+        new_dir = InventoryDirectory(file_id, name, parent_id)
+        self.add_entry(inv, rev_id, new_dir)
+
+    def add_file(self, inv, rev_id, file_id, parent_id, name, sha, size):
+        new_file = InventoryFile(file_id, name, parent_id)
+        new_file.text_sha1 = sha
+        new_file.text_size = size
+        self.add_entry(inv, rev_id, new_file)
+
+    def add_link(self, inv, rev_id, file_id, parent_id, name, target):
+        new_link = InventoryLink(file_id, name, parent_id)
+        new_link.symlink_target = target
+        self.add_entry(inv, rev_id, new_link)
+
+    def add_new_root(self, new_shape, old_revid, new_revid):
+        if self.bzrdir_format.repository_format.rich_root_data:
+            self.add_dir(new_shape, old_revid, 'root-id', None, '')
+        else:
+            self.add_dir(new_shape, new_revid, 'root-id', None, '')
+
+    def assertTransitionFromBasisToShape(self, basis_shape, basis_revid,
+        new_shape, new_revid, extra_parent=None):
+        delta = self.make_inv_delta(basis_shape, new_shape)
+        tree = self.make_branch_and_tree('tree')
+        # the shapes need to be in the tree's repository to be able to set them
+        # as a parent, but the file content is not needed.
+        if basis_revid is not None:
+            self.fake_up_revision(tree, basis_revid, basis_shape)
+            parents = [basis_revid]
+            if extra_parent is not None:
+                parents.append(extra_parent)
+            tree.set_parent_ids(parents)
+        self.fake_up_revision(tree, new_revid, new_shape)
+        self.assertDeltaApplicationResultsInExpectedBasis(tree, new_revid,
+            delta, new_shape)
+        osutils.rmtree('tree')
+
+    def test_no_parents_just_root(self):
+        """Test doing an empty commit - no parent, set a root only."""
+        basis_shape = Inventory(root_id=None) # empty tree
+        new_shape = Inventory() # tree with a root
+        self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
+            'new_parent')
+
+    def test_no_parents_full_tree(self):
+        """Test doing a regular initial commit with files and dirs."""
+        basis_shape = Inventory(root_id=None) # empty tree
+        revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_dir(new_shape, revid, 'root-id', None, '')
+        self.add_link(new_shape, revid, 'link-id', 'root-id', 'link', 'target')
+        self.add_file(new_shape, revid, 'file-id', 'root-id', 'file', '1' * 32,
+            12)
+        self.add_dir(new_shape, revid, 'dir-id', 'root-id', 'dir')
+        self.add_file(new_shape, revid, 'subfile-id', 'dir-id', 'subfile',
+            '2' * 32, 24)
+        self.assertTransitionFromBasisToShape(basis_shape, None, new_shape,
+            revid)
+
+    def test_file_content_change(self):
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_file(basis_shape, old_revid, 'file-id', 'root-id', 'file',
+            '1' * 32, 12)
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_file(new_shape, new_revid, 'file-id', 'root-id', 'file',
+            '2' * 32, 24)
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_link_content_change(self):
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_link(basis_shape, old_revid, 'link-id', 'root-id', 'link',
+            'old-target')
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
+            'new-target')
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_kind_changes(self):
+        def do_file(inv, revid):
+            self.add_file(inv, revid, 'path-id', 'root-id', 'path', '1' * 32,
+                12)
+        def do_link(inv, revid):
+            self.add_link(inv, revid, 'path-id', 'root-id', 'path', 'target')
+        def do_dir(inv, revid):
+            self.add_dir(inv, revid, 'path-id', 'root-id', 'path')
+        for old_factory in (do_file, do_link, do_dir):
+            for new_factory in (do_file, do_link, do_dir):
+                if old_factory == new_factory:
+                    continue
+                old_revid = 'old-parent'
+                basis_shape = Inventory(root_id=None)
+                self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+                old_factory(basis_shape, old_revid)
+                new_revid = 'new-parent'
+                new_shape = Inventory(root_id=None)
+                self.add_new_root(new_shape, old_revid, new_revid)
+                new_factory(new_shape, new_revid)
+                self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+                    new_shape, new_revid)
+
+    def test_content_from_second_parent_is_dropped(self):
+        left_revid = 'left-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, left_revid, 'root-id', None, '')
+        self.add_link(basis_shape, left_revid, 'link-id', 'root-id', 'link',
+            'left-target')
+        # the right shape has content - file, link, subdir with a child,
+        # that should all be discarded by the call.
+        right_revid = 'right-parent'
+        right_shape = Inventory(root_id=None)
+        self.add_dir(right_shape, left_revid, 'root-id', None, '')
+        self.add_link(right_shape, right_revid, 'link-id', 'root-id', 'link',
+            'left-target')
+        self.add_dir(right_shape, right_revid, 'subdir-id', 'root-id', 'dir')
+        self.add_file(right_shape, right_revid, 'file-id', 'subdir-id', 'file',
+            '2' * 32, 24)
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, left_revid, new_revid)
+        self.add_link(new_shape, new_revid, 'link-id', 'root-id', 'link',
+            'new-target')
+        self.assertTransitionFromBasisToShape(basis_shape, left_revid,
+            new_shape, new_revid, right_revid)
+
+    def test_parent_id_changed(self):
+        # test that when the only change to a entry is its parent id changing
+        # that it is handled correctly (that is it keeps the same path)
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_dir(basis_shape, old_revid, 'orig-parent-id', 'root-id', 'dir')
+        self.add_dir(basis_shape, old_revid, 'dir-id', 'orig-parent-id', 'dir')
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_dir(new_shape, new_revid, 'new-parent-id', 'root-id', 'dir')
+        self.add_dir(new_shape, new_revid, 'dir-id', 'new-parent-id', 'dir')
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_name_changed(self):
+        # test that when the only change to a entry is its name changing
+        # that it is handled correctly (that is it keeps the same parent id)
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_dir(basis_shape, old_revid, 'parent-id', 'root-id', 'origdir')
+        self.add_dir(basis_shape, old_revid, 'dir-id', 'parent-id', 'olddir')
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_dir(new_shape, new_revid, 'parent-id', 'root-id', 'newdir')
+        self.add_dir(new_shape, new_revid, 'dir-id', 'parent-id', 'newdir')
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_path_swap(self):
+        # test a A->B and B->A path swap.
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
+        self.add_dir(basis_shape, old_revid, 'dir-id-B', 'root-id', 'B')
+        self.add_link(basis_shape, old_revid, 'link-id-C', 'root-id', 'C', 'C')
+        self.add_link(basis_shape, old_revid, 'link-id-D', 'root-id', 'D', 'D')
+        self.add_file(basis_shape, old_revid, 'file-id-E', 'root-id', 'E',
+            '1' * 32, 12)
+        self.add_file(basis_shape, old_revid, 'file-id-F', 'root-id', 'F',
+            '2' * 32, 24)
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'B')
+        self.add_dir(new_shape, new_revid, 'dir-id-B', 'root-id', 'A')
+        self.add_link(new_shape, new_revid, 'link-id-C', 'root-id', 'D', 'C')
+        self.add_link(new_shape, new_revid, 'link-id-D', 'root-id', 'C', 'D')
+        self.add_file(new_shape, new_revid, 'file-id-E', 'root-id', 'F',
+            '1' * 32, 12)
+        self.add_file(new_shape, new_revid, 'file-id-F', 'root-id', 'E',
+            '2' * 32, 24)
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_adds(self):
+        # test adding paths and dirs, including adding to a newly added dir.
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        # with a root, so its a commit after the first.
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
+        self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
+        self.add_file(new_shape, new_revid, 'file-id-C', 'root-id', 'C',
+            '1' * 32, 12)
+        self.add_file(new_shape, new_revid, 'file-id-D', 'dir-id-A', 'D',
+            '2' * 32, 24)
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_removes(self):
+        # test removing paths, including paths that are within other also
+        # removed paths.
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
+        self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
+        self.add_file(basis_shape, old_revid, 'file-id-C', 'root-id', 'C',
+            '1' * 32, 12)
+        self.add_file(basis_shape, old_revid, 'file-id-D', 'dir-id-A', 'D',
+            '2' * 32, 24)
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_move_to_added_dir(self):
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_link(basis_shape, old_revid, 'link-id-B', 'root-id', 'B', 'C')
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_dir(new_shape, new_revid, 'dir-id-A', 'root-id', 'A')
+        self.add_link(new_shape, new_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)
+
+    def test_move_from_removed_dir(self):
+        old_revid = 'old-parent'
+        basis_shape = Inventory(root_id=None)
+        self.add_dir(basis_shape, old_revid, 'root-id', None, '')
+        self.add_dir(basis_shape, old_revid, 'dir-id-A', 'root-id', 'A')
+        self.add_link(basis_shape, old_revid, 'link-id-B', 'dir-id-A', 'B', 'C')
+        new_revid = 'new-parent'
+        new_shape = Inventory(root_id=None)
+        self.add_new_root(new_shape, old_revid, new_revid)
+        self.add_link(new_shape, new_revid, 'link-id-B', 'root-id', 'B', 'C')
+        self.assertTransitionFromBasisToShape(basis_shape, old_revid,
+            new_shape, new_revid)



More information about the bazaar-commits mailing list