Rev 2491: Teach _iter_changes to gather unversioned path details upon request. in http://bazaar.launchpad.net/~bzr/bzr/dirstate

Robert Collins robertc at robertcollins.net
Fri Mar 2 01:07:16 GMT 2007


At http://bazaar.launchpad.net/~bzr/bzr/dirstate

------------------------------------------------------------
revno: 2491
revision-id: robertc at robertcollins.net-20070302010612-v4zb59puoc5b0ai5
parent: john at arbash-meinel.com-20070301215930-ivnmomd61iek19bn
committer: Robert Collins <robertc at robertcollins.net>
branch nick: dirstate.dogfood
timestamp: Fri 2007-03-02 12:06:12 +1100
message:
  Teach _iter_changes to gather unversioned path details upon request.
modified:
  bzrlib/tests/intertree_implementations/test_compare.py test_compare.py-20060724101752-09ysswo1a92uqyoz-2
  bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
  bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
  bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
=== modified file 'bzrlib/tests/intertree_implementations/test_compare.py'
--- a/bzrlib/tests/intertree_implementations/test_compare.py	2007-02-28 23:36:39 +0000
+++ b/bzrlib/tests/intertree_implementations/test_compare.py	2007-03-02 01:06:12 +0000
@@ -398,6 +398,17 @@
                 (entry.name, None), (entry.kind, None),
                 (entry.executable, None))
 
+    def renamed(self, from_tree, to_tree, file_id, content_changed):
+        from_entry = from_tree.inventory[file_id]
+        to_entry = to_tree.inventory[file_id]
+        from_path = from_tree.id2path(file_id)
+        to_path = to_tree.id2path(file_id)
+        return (file_id, to_path, content_changed, (True, True),
+            (from_entry.parent_id, to_entry.parent_id),
+            (from_entry.name, to_entry.name),
+            (from_entry.kind, to_entry.kind),
+            (from_entry.executable, to_entry.executable))
+
     def unchanged(self, tree, file_id):
         entry = tree.inventory[file_id]
         parent = entry.parent_id
@@ -609,56 +620,130 @@
             (False, False)), self.unchanged(tree1, 'c-id')]),
             self.do_iter_changes(tree1, tree2, include_unchanged=True))
 
-    def _todo_test_unversioned_paths_in_tree(self):
+    def test_default_ignores_unversioned_files(self):
         tree1 = self.make_branch_and_tree('tree1')
         tree2 = self.make_to_branch_and_tree('tree2')
-        self.build_tree(['tree2/file', 'tree2/dir/'])
-        # try:
-        os.symlink('target', 'tree2/link')
-        links_supported = True
-        # except ???:
-        #   links_supported = False
+        self.build_tree(['tree1/a', 'tree1/c',
+                         'tree2/a', 'tree2/b', 'tree2/c'])
+        tree1.add(['a', 'c'], ['a-id', 'c-id'])
+        tree2.add(['a', 'c'], ['a-id', 'c-id'])
+
         tree1, tree2 = self.mutable_trees_to_test_trees(tree1, tree2)
-        root_id = tree1.path2id('')
         tree1.lock_read()
         self.addCleanup(tree1.unlock)
         tree2.lock_read()
         self.addCleanup(tree2.unlock)
-        expected = [
-            self.unversioned(tree2, 'file'),
-            self.unversioned(tree2, 'dir'),
-            ]
-        if links_supported:
-            expected.append(self.unversioned(tree2, 'link'))
-        expected = sorted(expected)
+
+        # We should ignore the fact that 'b' exists in tree-2
+        # because the want_unversioned parameter was not given.
+        expected = sorted([
+            self.content_changed(tree2, 'a-id'),
+            self.content_changed(tree2, 'c-id'),
+            ])
         self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
 
-    def _todo_test_unversioned_paths_in_tree_specific_files(self):
-        tree1 = self.make_branch_and_tree('tree1')
-        tree2 = self.make_to_branch_and_tree('tree2')
-        self.build_tree(['tree2/file', 'tree2/dir/'])
-        # try:
-        os.symlink('target', 'tree2/link')
-        links_supported = True
-        # except ???:
-        #   links_supported = False
-        tree1, tree2 = self.mutable_trees_to_test_trees(tree1, tree2)
-        root_id = tree1.path2id('')
-        tree1.lock_read()
-        self.addCleanup(tree1.unlock)
-        tree2.lock_read()
-        self.addCleanup(tree2.unlock)
-        expected = [
-            self.unversioned(tree2, 'file'),
-            self.unversioned(tree2, 'dir'),
-            ]
-        specific_files=['file', 'dir']
-        if links_supported:
-            expected.append(self.unversioned(tree2, 'link'))
-            specific_files.append('link')
-        expected = sorted(expected)
-        self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
-            specific_files=specific_files))
+    def test_unversioned_paths_in_tree(self):
+        tree1 = self.make_branch_and_tree('tree1')
+        tree2 = self.make_to_branch_and_tree('tree2')
+        self.build_tree(['tree2/file', 'tree2/dir/'])
+        # try:
+        os.symlink('target', 'tree2/link')
+        links_supported = True
+        # except ???:
+        #   links_supported = False
+        tree1, tree2 = self.mutable_trees_to_test_trees(tree1, tree2)
+        root_id = tree1.path2id('')
+        tree1.lock_read()
+        self.addCleanup(tree1.unlock)
+        tree2.lock_read()
+        self.addCleanup(tree2.unlock)
+        expected = [
+            self.unversioned(tree2, 'file'),
+            self.unversioned(tree2, 'dir'),
+            ]
+        if links_supported:
+            expected.append(self.unversioned(tree2, 'link'))
+        expected = sorted(expected)
+        self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+            want_unversioned=True))
+
+    def test_unversioned_paths_in_tree_specific_files(self):
+        tree1 = self.make_branch_and_tree('tree1')
+        tree2 = self.make_to_branch_and_tree('tree2')
+        self.build_tree(['tree2/file', 'tree2/dir/'])
+        # try:
+        os.symlink('target', 'tree2/link')
+        links_supported = True
+        # except ???:
+        #   links_supported = False
+        tree1, tree2 = self.mutable_trees_to_test_trees(tree1, tree2)
+        root_id = tree1.path2id('')
+        tree1.lock_read()
+        self.addCleanup(tree1.unlock)
+        tree2.lock_read()
+        self.addCleanup(tree2.unlock)
+        expected = [
+            self.unversioned(tree2, 'file'),
+            self.unversioned(tree2, 'dir'),
+            ]
+        specific_files=['file', 'dir']
+        if links_supported:
+            expected.append(self.unversioned(tree2, 'link'))
+            specific_files.append('link')
+        expected = sorted(expected)
+        self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+            specific_files=specific_files, require_versioned=False,
+            want_unversioned=True))
+
+    def test_unversioned_paths_in_target_matching_source_old_names(self):
+        # its likely that naive implementations of unversioned file support
+        # will fail if the path was versioned, but is not any more, 
+        # due to a rename, not due to unversioning it.
+        # That is, if the old tree has a versioned file 'foo', and
+        # the new tree has the same file but versioned as 'bar', and also
+        # has an unknown file 'foo', we should get back output for
+        # both foo and bar.
+        tree1 = self.make_branch_and_tree('tree1')
+        tree2 = self.make_to_branch_and_tree('tree2')
+        self.build_tree(['tree2/file', 'tree2/dir/',
+            'tree1/file', 'tree2/movedfile',
+            'tree1/dir/', 'tree2/moveddir/'])
+        # try:
+        os.symlink('target', 'tree1/link')
+        os.symlink('target', 'tree2/link')
+        os.symlink('target', 'tree2/movedlink')
+        links_supported = True
+        # except ???:
+        #   links_supported = False
+        tree1.add(['file', 'dir', 'link'], ['file-id', 'dir-id', 'link-id'])
+        tree2.add(['movedfile', 'moveddir', 'movedlink'],
+            ['file-id', 'dir-id', 'link-id'])
+        tree1, tree2 = self.mutable_trees_to_test_trees(tree1, tree2)
+        root_id = tree1.path2id('')
+        tree1.lock_read()
+        self.addCleanup(tree1.unlock)
+        tree2.lock_read()
+        self.addCleanup(tree2.unlock)
+        expected = [
+            self.renamed(tree1, tree2, 'dir-id', False),
+            self.renamed(tree1, tree2, 'file-id', True),
+            self.unversioned(tree2, 'file'),
+            self.unversioned(tree2, 'dir'),
+            ]
+        specific_files=['file', 'dir']
+        if links_supported:
+            expected.append(self.renamed(tree1, tree2, 'link-id', False))
+            expected.append(self.unversioned(tree2, 'link'))
+            specific_files.append('link')
+        expected = sorted(expected)
+        # run once with, and once without specific files, to catch
+        # potentially different code paths.
+        self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+            require_versioned=False,
+            want_unversioned=True))
+        self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
+            specific_files=specific_files, require_versioned=False,
+            want_unversioned=True))
 
     def make_trees_with_symlinks(self):
         tree1 = self.make_branch_and_tree('tree1')
@@ -768,27 +853,6 @@
                           if f_id.endswith('_f-id'))
         self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
 
-    def test_trees_with_unknown(self):
-        tree1 = self.make_branch_and_tree('tree1')
-        tree2 = self.make_to_branch_and_tree('tree2')
-        self.build_tree(['tree1/a', 'tree1/c',
-                         'tree2/a', 'tree2/b', 'tree2/c'])
-        tree1.add(['a', 'c'], ['a-id', 'c-id'])
-        tree2.add(['a', 'c'], ['a-id', 'c-id'])
-
-        tree1, tree2 = self.mutable_trees_to_test_trees(tree1, tree2)
-        tree1.lock_read()
-        self.addCleanup(tree1.unlock)
-        tree2.lock_read()
-        self.addCleanup(tree2.unlock)
-
-        # We should ignore the fact that 'b' exists in tree-2
-        expected = sorted([
-            self.content_changed(tree2, 'a-id'),
-            self.content_changed(tree2, 'c-id'),
-            ])
-        self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
-
     def test_trees_with_missing_dir(self):
         tree1 = self.make_branch_and_tree('tree1')
         tree2 = self.make_to_branch_and_tree('tree2')

=== modified file 'bzrlib/tree.py'
--- a/bzrlib/tree.py	2007-03-01 06:19:44 +0000
+++ b/bzrlib/tree.py	2007-03-02 01:06:12 +0000
@@ -18,6 +18,7 @@
 """
 
 import os
+from collections import deque
 from cStringIO import StringIO
 
 import bzrlib
@@ -29,7 +30,7 @@
 from bzrlib.decorators import needs_read_lock
 from bzrlib.errors import BzrError, BzrCheckError
 from bzrlib import errors
-from bzrlib.inventory import Inventory
+from bzrlib.inventory import Inventory, InventoryFile
 from bzrlib.inter import InterObject
 from bzrlib.osutils import fingerprint_file
 import bzrlib.revision
@@ -89,7 +90,7 @@
 
     def _iter_changes(self, from_tree, include_unchanged=False,
                      specific_files=None, pb=None, extra_trees=None,
-                     require_versioned=True):
+                     require_versioned=True, want_unversioned=False):
         intertree = InterTree.get(from_tree, self)
         return intertree._iter_changes(include_unchanged, specific_files, pb,
             extra_trees, require_versioned)
@@ -557,7 +558,7 @@
 
     def _iter_changes(self, include_unchanged=False,
                       specific_files=None, pb=None, extra_trees=[],
-                      require_versioned=True):
+                      require_versioned=True, want_unversioned=False):
         """Generate an iterator of changes between trees.
 
         A tuple is returned:
@@ -581,12 +582,22 @@
         :param require_versioned: Raise errors.PathsNotVersionedError if a
             path in the specific_files list is not versioned in one of
             source, target or extra_trees.
+        :param want_unversioned: Should unversioned files be returned in the
+            output. An unversioned file is defined as one with (False, False)
+            for the versioned pair.
         """
         lookup_trees = [self.source]
         if extra_trees:
              lookup_trees.extend(extra_trees)
         specific_file_ids = self.target.paths2ids(specific_files,
             lookup_trees, require_versioned=require_versioned)
+        if want_unversioned:
+            all_unversioned = sorted([(p.split('/'), p) for p in self.target.extras()
+                if not specific_files or
+                    osutils.is_inside_any(specific_files, p)])
+            all_unversioned = deque(all_unversioned)
+        else:
+            all_unversioned = deque()
         to_paths = {}
         from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
             specific_file_ids=specific_file_ids))
@@ -595,7 +606,20 @@
             specific_file_ids=specific_file_ids))
         num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
         entry_count = 0
+        # the unversioned path lookup only occurs on real trees - where there 
+        # can be extras. So the fake_entry is solely used to look up
+        # executable it values when execute is not supported.
+        fake_entry = InventoryFile('unused', 'unused', 'unused')
         for to_path, to_entry in to_entries_by_dir:
+            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
+                unversioned_path = all_unversioned.popleft()
+                to_kind, to_executable, to_stat = \
+                    self.target._comparison_data(fake_entry, unversioned_path[1])
+                yield (None, unversioned_path[1], True, (False, False),
+                    (None, None),
+                    (None, unversioned_path[0][-1]),
+                    (None, to_kind),
+                    (None, to_executable))
             file_id = to_entry.file_id
             to_paths[file_id] = to_path
             entry_count += 1
@@ -643,6 +667,16 @@
                 executable[0] != executable[1] or include_unchanged):
                 yield (file_id, to_path, changed_content, versioned, parent,
                        name, kind, executable)
+        while all_unversioned:
+            # yield any trailing unversioned paths
+            unversioned_path = all_unversioned.popleft()
+            to_kind, to_executable, to_stat = \
+                self.target._comparison_data(fake_entry, unversioned_path[1])
+            yield (None, unversioned_path[1], True, (False, False),
+                (None, None),
+                (None, unversioned_path[0][-1]),
+                (None, to_kind),
+                (None, to_executable))
 
         def get_to_path(from_entry):
             if from_entry.parent_id is None:

=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py	2007-03-01 16:48:50 +0000
+++ b/bzrlib/workingtree.py	2007-03-02 01:06:12 +0000
@@ -1402,13 +1402,14 @@
         # TODO: update the hashcache here ?
 
     def extras(self):
-        """Yield all unknown files in this WorkingTree.
+        """Yield all unversioned files in this WorkingTree.
 
-        If there are any unknown directories then only the directory is
-        returned, not all its children.  But if there are unknown files
+        If there are any unversioned directories then only the directory is
+        returned, not all its children.  But if there are unversioned files
         under a versioned subdirectory, they are returned.
 
         Currently returned depth-first, sorted by name within directories.
+        This is the same order used by 'osutils.walkdirs'.
         """
         ## TODO: Work from given directory downwards
         for path, dir_entry in self.inventory.directories():

=== modified file 'bzrlib/workingtree_4.py'
--- a/bzrlib/workingtree_4.py	2007-03-01 21:56:19 +0000
+++ b/bzrlib/workingtree_4.py	2007-03-02 01:06:12 +0000
@@ -1437,7 +1437,7 @@
 
     def _iter_changes(self, include_unchanged=False,
                       specific_files=None, pb=None, extra_trees=[],
-                      require_versioned=True):
+                      require_versioned=True, want_unversioned=False):
         """Return the changes from source to target.
 
         :return: An iterator that yields tuples. See InterTree._iter_changes
@@ -1453,6 +1453,9 @@
         :param require_versioned: If True, all files in specific_files must be
             versioned in one of source, target, extra_trees or
             PathsNotVersionedError is raised.
+        :param want_unversioned: Should unversioned files be returned in the
+            output. An unversioned file is defined as one with (False, False)
+            for the versioned pair.
         """
         utf8_decode = cache_utf8._utf8_decode
         _minikind_to_kind = dirstate.DirState._minikind_to_kind
@@ -1793,11 +1796,13 @@
             if not root_entries and not root_dir_info:
                 # this specified path is not present at all, skip it.
                 continue
+            path_handled = False
             for entry in root_entries:
                 for result in _process_entry(entry, root_dir_info):
                     # this check should probably be outside the loop: one
                     # 'iterate two trees' api, and then _iter_changes filters
                     # unchanged pairs. - RBC 20070226
+                    path_handled = True
                     if (include_unchanged
                         or result[2]                    # content change
                         or result[3][0] != result[3][1] # versioned status
@@ -1809,6 +1814,13 @@
                         result = (result[0],
                                   utf8_decode(result[1])[0]) + result[2:]
                         yield result
+            if want_unversioned and not path_handled:
+                new_executable = bool(
+                    stat.S_ISREG(root_dir_info[3].st_mode)
+                    and stat.S_IEXEC & root_dir_info[3].st_mode)
+                yield (None, current_root, True, (False, False), (None, None),
+                    (None, splitpath(current_root)[-1]),
+                    (None, root_dir_info[2]), (None, new_executable))
             dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
             initial_key = (current_root, '', '')
             block_index, _ = state._find_block_index_from_key(initial_key)
@@ -1906,24 +1918,13 @@
                 else:
                     current_path_info = None
                 advance_path = True
+                path_handled = False
                 while (current_entry is not None or
                     current_path_info is not None):
                     if current_entry is None:
-                        # no more entries: yield current_pathinfo as an
-                        # unversioned file: its not the same as a path in any
-                        # tree in the dirstate.
-                        new_executable = bool(
-                            stat.S_ISREG(current_path_info[3].st_mode)
-                            and stat.S_IEXEC & current_path_info[3].st_mode)
-                        pass # unversioned file support not added to the
-                        # _iter_changes api yet - breaks status amongst other
-                        # things.
-#                        yield (None, current_path_info[0], True,
-#                               (False, False),
-#                               (None, None),
-#                               (None, current_path_info[1]),
-#                               (None, current_path_info[2]),
-#                               (None, new_executable))
+                        # the check for path_handled when the path is adnvaced
+                        # will yield this path if needed.
+                        pass
                     elif current_path_info is None:
                         # no path is fine: the per entry code will handle it.
                         for result in _process_entry(current_entry, current_path_info):
@@ -1955,6 +1956,7 @@
                                 # this check should probably be outside the loop: one
                                 # 'iterate two trees' api, and then _iter_changes filters
                                 # unchanged pairs. - RBC 20070226
+                                path_handled = True
                                 if (include_unchanged
                                     or result[2]                    # content change
                                     or result[3][0] != result[3][1] # versioned status
@@ -1972,6 +1974,7 @@
                             # this check should probably be outside the loop: one
                             # 'iterate two trees' api, and then _iter_changes filters
                             # unchanged pairs. - RBC 20070226
+                            path_handled = True
                             if (include_unchanged
                                 or result[2]                    # content change
                                 or result[3][0] != result[3][1] # versioned status
@@ -1992,11 +1995,23 @@
                     else:
                         advance_entry = True # reset the advance flaga
                     if advance_path and current_path_info is not None:
+                        if want_unversioned and not path_handled:
+                            new_executable = bool(
+                                stat.S_ISREG(current_path_info[3].st_mode)
+                                and stat.S_IEXEC & current_path_info[3].st_mode)
+                            if want_unversioned:
+                                yield (None, current_path_info[0], True,
+                                       (False, False),
+                                       (None, None),
+                                       (None, current_path_info[1]),
+                                       (None, current_path_info[2]),
+                                       (None, new_executable))
                         path_index += 1
                         if path_index < len(current_dir_info[1]):
                             current_path_info = current_dir_info[1][path_index]
                         else:
                             current_path_info = None
+                        path_handled = False
                     else:
                         advance_path = True # reset the advance flagg.
                 if current_block is not None:



More information about the bazaar-commits mailing list