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