Rev 2359: Add WorkingTree4.paths2ids which is inventory-usage free if the trees being examined are in the dirstate. in sftp://bazaar.launchpad.net/%7Ebzr/bzr/dirstate/

Robert Collins robertc at robertcollins.net
Thu Feb 22 05:08:04 GMT 2007


At sftp://bazaar.launchpad.net/%7Ebzr/bzr/dirstate/

------------------------------------------------------------
revno: 2359
revision-id: robertc at robertcollins.net-20070222050658-m9gdlj1ypv2gqvjx
parent: robertc at robertcollins.net-20070222024924-bokuavlg2yglgtja
committer: Robert Collins <robertc at robertcollins.net>
branch nick: dirstate
timestamp: Thu 2007-02-22 16:06:58 +1100
message:
  Add WorkingTree4.paths2ids which is inventory-usage free if the trees being examined are in the dirstate.
modified:
  bzrlib/tests/workingtree_implementations/test_paths2ids.py test_paths2ids.py-20070222011621-kesvovdwm69nndtx-1
  bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
  bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
=== modified file 'bzrlib/tests/workingtree_implementations/test_paths2ids.py'
--- a/bzrlib/tests/workingtree_implementations/test_paths2ids.py	2007-02-22 02:49:24 +0000
+++ b/bzrlib/tests/workingtree_implementations/test_paths2ids.py	2007-02-22 05:06:58 +0000
@@ -36,6 +36,8 @@
 # TODO: test that supplying paths with duplication - i.e. foo, foo, foo/bar -
 # does not result in garbage out.
 
+# TODO: Are we meant to raise the precise unversioned paths when some are
+# unversioned - if so, test this.
 
 class TestPaths2Ids(TestCaseWithWorkingTree):
 
@@ -54,6 +56,12 @@
         self.assertEqual(set(ids), result)
         tree.unlock()
 
+    def test_paths_none_result_none(self):
+        tree = self.make_branch_and_tree('tree')
+        tree.lock_read()
+        self.assertEqual(None, tree.paths2ids(None))
+        tree.unlock()
+
     def test_find_single_root(self):
         tree = self.make_branch_and_tree('tree')
         self.assertExpectedIds([tree.path2id('')], tree, [''])
@@ -152,13 +160,24 @@
         self.assertRaises(errors.PathsNotVersionedError, tree.paths2ids, ['unversioned'])
         tree.unlock()
 
-    def test_unversioned_multiple_trees(self):
-        # in this test, the path is unversioned in only one tree, but it should
-        # still raise an error.
-        tree = self.make_branch_and_tree('tree')
-        tree.commit('make basis')
-        basis = tree.basis_tree()
-        self.build_tree(['tree/unversioned'])
+    def test_unversioned_in_one_of_multiple_trees(self):
+        # in this test, the path is unversioned in only one tree, and thus
+        # should not raise an error: it must be unversioned in *all* trees to
+        # error.
+        tree = self.make_branch_and_tree('tree')
+        tree.commit('make basis')
+        basis = tree.basis_tree()
+        self.build_tree(['tree/in-one'])
+        tree.add(['in-one'], ['in-one'])
+        self.assertExpectedIds(['in-one'], tree, ['in-one'], [basis])
+
+    def test_unversioned_all_of_multiple_trees(self):
+        # in this test, the path is unversioned in every tree, and thus
+        # should not raise an error: it must be unversioned in *all* trees to
+        # error.
+        tree = self.make_branch_and_tree('tree')
+        tree.commit('make basis')
+        basis = tree.basis_tree()
         self.assertExpectedIds([], tree, ['unversioned'], [basis],
             require_versioned=False)
         tree.lock_read()

=== modified file 'bzrlib/tree.py'
--- a/bzrlib/tree.py	2007-02-22 02:45:55 +0000
+++ b/bzrlib/tree.py	2007-02-22 05:06:58 +0000
@@ -226,6 +226,9 @@
         tree, and vice versa.
 
         :param paths: An iterable of paths to start converting to ids from.
+            Alternatively, if paths is None, no ids should be calculated and None
+            will be returned. This is offered to make calling the api unconditional
+            for code that *might* take a list of files.
         :param trees: Additional trees to consider.
         :param require_versioned: If False, do not raise NotVersionedError if
             an element of paths is not versioned in this tree and all of trees.

=== modified file 'bzrlib/workingtree_4.py'
--- a/bzrlib/workingtree_4.py	2007-02-22 01:10:12 +0000
+++ b/bzrlib/workingtree_4.py	2007-02-22 05:06:58 +0000
@@ -550,6 +550,119 @@
             return None
         return entry[0][2].decode('utf8')
 
+    def paths2ids(self, paths, trees=[], require_versioned=True):
+        """See Tree.paths2ids().
+        
+        This specialisation fast-paths the case where all the trees are in the
+        dirstate.
+        """
+        if paths is None:
+            return None
+        parents = self.get_parent_ids()
+        for tree in trees:
+            if not (isinstance(tree, DirStateRevisionTree) and tree._revision_id in
+                parents):
+                return super(WorkingTree4, self).paths2ids(paths, trees, require_versioned)
+        search_indexes = [0] + [1 + parents.index(tree._revision_id) for tree in trees]
+        # -- make all paths utf8 --
+        paths_utf8 = set()
+        for path in paths:
+            paths_utf8.add(path.encode('utf8'))
+        paths = paths_utf8
+        # -- paths is now a utf8 path set --
+        # -- get the state object and prepare it.
+        state = self.current_dirstate()
+        state._read_dirblocks_if_needed()
+        def _entries_for_path(path):
+            """Return a list with all the entries that match path for all ids.
+            """
+            dirname, basename = os.path.split(path)
+            key = (dirname, basename, '')
+            block_index, present = state._find_block_index_from_key(key)
+            if not present:
+                # the block which should contain path is absent.
+                return []
+            result = []
+            block = state._dirblocks[block_index][1]
+            entry_index, _ = state._find_entry_index(key, block)
+            # we may need to look at multiple entries at this path: walk while the paths match.
+            while (entry_index < len(block) and
+                block[entry_index][0][0:2] == key[0:2]):
+                result.append(block[entry_index])
+                entry_index += 1
+            return result
+        if require_versioned:
+            # -- check all supplied paths are versioned in all search trees. --
+            all_versioned = True
+            for path in paths:
+                path_entries = _entries_for_path(path)
+                if not path_entries:
+                    # this specified path is not present at all: error
+                    all_versioned = False
+                    break
+                found_versioned = False
+                # for each id at this path
+                for entry in path_entries:
+                    # for each tree.
+                    for index in search_indexes:
+                        if entry[1][index][0] != 'absent':
+                            found_versioned = True
+                            # all good: found a versioned cell
+                            break
+                if not found_versioned:
+                    # non of the indexes was not 'absent' at all ids for this
+                    # path.
+                    all_versioned = False
+                    break
+            if not all_versioned:
+                raise errors.PathsNotVersionedError(paths)
+        # -- remove redundancy in supplied paths to prevent over-scanning --
+        search_paths = set()
+        for path in paths:
+            other_paths = paths.difference(set([path]))
+            if not osutils.is_inside_any(other_paths, path):
+                # this is a top level path, we must check it.
+                search_paths.add(path)
+        # sketch: 
+        # for all search_indexs in each path at or under each element of
+        # search_paths, if the detail is relocated: add the id, and add the
+        # relocated path as one to search if its not searched already. If the
+        # detail is not relocated, add the id.
+        searched_paths = set()
+        found_ids = set()
+        def _process_entry(entry):
+            """Look at search_indexes within entry.
+
+            If a specific tree's details are relocated, add the relocation
+            target to search_paths if not searched already. If it is absent, do
+            nothing. Otherwise add the id to found_ids.
+            """
+            for index in search_indexes:
+                if entry[1][index][0] == 'relocated':
+                    if not osutils.is_inside_any(searched_paths, entry[1][index][1]):
+                        search_paths.add(entry[1][index][1])
+                elif entry[1][index][0] != 'absent':
+                    found_ids.add(entry[0][2])
+        while search_paths:
+            current_root = search_paths.pop()
+            searched_paths.add(current_root)
+            # process the entries for this containing directory: the rest will be
+            # found by their parents recursively.
+            root_entries = _entries_for_path(current_root)
+            if not root_entries:
+                # this specified path is not present at all, skip it.
+                continue
+            for entry in root_entries:
+                _process_entry(entry)
+            initial_key = (current_root, '', '')
+            block_index, _ = state._find_block_index_from_key(initial_key)
+            while (block_index < len(state._dirblocks) and
+                osutils.is_inside(current_root, state._dirblocks[block_index][0])):
+                for entry in state._dirblocks[block_index][1]:
+                    _process_entry(entry)
+                block_index += 1
+        return found_ids
+
     def read_working_inventory(self):
         """Read the working inventory.
         
@@ -976,7 +1089,7 @@
         return self._repository.get_revision(self._revision_id).parent_ids
 
     def has_filename(self, filename):
-        return bool(self.inventory.path2id(filename))
+        return bool(self.path2id(filename))
 
     def kind(self, file_id):
         return self.inventory[file_id].kind



More information about the bazaar-commits mailing list