Rev 2413: Most of the integration of dirstate and subtree in http://sourcefrog.net/bzr/dirstate-plus-subtree

Martin Pool mbp at sourcefrog.net
Mon Feb 26 14:24:49 GMT 2007


At http://sourcefrog.net/bzr/dirstate-plus-subtree

------------------------------------------------------------
revno: 2413
revision-id: mbp at sourcefrog.net-20070226142446-sfvznepsaggqnril
parent: mbp at sourcefrog.net-20070226044445-g0o7a0u9qypajqv2
committer: Martin Pool <mbp at sourcefrog.net>
branch nick: dirstate-plus-subtree
timestamp: Tue 2007-02-27 01:24:46 +1100
message:
  Most of the integration of dirstate and subtree
modified:
  bzrlib/bzrdir.py               bzrdir.py-20060131065624-156dfea39c4387cb
  bzrlib/dirstate.py             dirstate.py-20060728012006-d6mvoihjb3je9peu-1
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/inventory.py            inventory.py-20050309040759-6648b84ca2005b37
  bzrlib/mutabletree.py          mutabletree.py-20060906023413-4wlkalbdpsxi2r4y-2
  bzrlib/tests/test_bzrdir.py    test_bzrdir.py-20060131065654-deba40eef51cf220
  bzrlib/tests/tree_implementations/test_tree.py test_tree.py-20061215160206-usu7lwcj8aq2n3br-1
  bzrlib/tests/workingtree_implementations/test_add_reference.py test_add_reference.p-20061211024451-yo9i1691dgbv1eyn-1
  bzrlib/tree.py                 tree.py-20050309040759-9d5f2496be663e77
  bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
=== modified file 'bzrlib/bzrdir.py'
--- a/bzrlib/bzrdir.py	2007-02-22 05:51:57 +0000
+++ b/bzrlib/bzrdir.py	2007-02-26 14:24:46 +0000
@@ -2087,40 +2087,46 @@
     e.g. BzrDirMeta1 with weave repository.  Also, it's more user-oriented.
     """
 
-    def register_metadir(self, key, repo, help, native=True, deprecated=False,
-                         branch_format=None, tree='WorkingTreeFormat3'):
+    def register_metadir(self, key,
+             repository_format, help, native=True, deprecated=False,
+             branch_format=None,
+             tree_format=None):
         """Register a metadir subformat.
 
         These all use a BzrDirMetaFormat1 bzrdir, but can be parameterized
         by the Repository format.
 
-        :param repo: The fully-qualified repository format class name as a
-        string.
+        :param repository_format: The fully-qualified repository format class
+            name as a string.
+        :param branch_format: Fully-qualified branch format class name as
+            a string.
+        :param tree_format: Fully-qualified tree format class name as
+            a string.
         """
         # This should be expanded to support setting WorkingTree and Branch
         # formats, once BzrDirMetaFormat1 supports that.
-        def helper():
-            import bzrlib.branch
-            import bzrlib.repository
-            import bzrlib.workingtree
-            tree_format = getattr(bzrlib.workingtree, tree)
-            mod_name, repo_factory_name = repo.rsplit('.', 1)
+        def _load(full_name):
+            mod_name, factory_name = full_name.rsplit('.', 1)
             try:
                 mod = __import__(mod_name, globals(), locals(),
-                        [repo_factory_name])
+                        [factory_name])
             except ImportError, e:
-                raise ImportError('failed to load repository %s: %s'
-                    % (repo, e))
+                raise ImportError('failed to load %s: %s' % (full_name, e))
             try:
-                repo_format_class = getattr(mod, repo_factory_name)
+                factory = getattr(mod, factory_name)
             except AttributeError:
-                raise AttributeError('no repository format %r in module %r' 
-                    % (repo, mod))
+                raise AttributeError('no factory %s in module %r'
+                    % (full_name, mod))
+            return factory()
+
+        def helper():
             bd = BzrDirMetaFormat1()
-            bd.workingtree_format = tree_format()
-            bd.repository_format = repo_format_class()
             if branch_format is not None:
-                bd.set_branch_format(getattr(bzrlib.branch, branch_format)())
+                bd.set_branch_format(_load(branch_format))
+            if tree_format is not None:
+                bd.workingtree_format = _load(tree_format)
+            if repository_format is not None:
+                bd.repository_format = _load(repository_format)
             return bd
         self.register(key, helper, help, native, deprecated)
 
@@ -2216,7 +2222,7 @@
 format_registry.register_metadir('knit',
     'bzrlib.repofmt.knitrepo.RepositoryFormatKnit1',
     'Format using knits.  Recommended.',
-    branch_format='BzrBranchFormat5')
+    branch_format='bzrlib.branch.BzrBranchFormat5')
 format_registry.set_default('knit')
 format_registry.register_metadir('metaweave',
     'bzrlib.repofmt.weaverepo.RepositoryFormat7',
@@ -2225,16 +2231,24 @@
 format_registry.register_metadir('experimental-knit2',
     'bzrlib.repofmt.knitrepo.RepositoryFormatKnit2',
     'Experimental successor to knit.  Use at your own risk.',
-    branch_format='BzrBranchFormat5',
-    tree='WorkingTreeFormat3'
+    branch_format='bzrlib.branch.BzrBranchFormat5',
+    tree_format='bzrlib.workingtree.WorkingTreeFormat3'
     )
 format_registry.register_metadir('experimental-knit3',
     'bzrlib.repofmt.knitrepo.RepositoryFormatKnit3',
     'Experimental successor to knit2.  Use at your own risk.', 
-    branch_format='BzrBranchFormat5',
-    tree='WorkingTreeFormatAB1')
+    branch_format='bzrlib.branch.BzrBranchFormat5',
+    tree_format='bzrlib.workingtree.WorkingTreeFormatAB1')
 format_registry.register_metadir('experimental-branch6',
     'bzrlib.repofmt.knitrepo.RepositoryFormatKnit1',
     'Experimental successor to knit.  Use at your own risk.',
-    branch_format='BzrBranchFormat6',
-    tree='WorkingTreeFormat3')
+    branch_format='bzrlib.branch.BzrBranchFormat6',
+    tree_format='bzrlib.workingtree.WorkingTreeFormat3')
+
+format_registry.register_metadir('experimental-reference-dirstate',
+    'bzrlib.repofmt.knitrepo.RepositoryFormatKnit3',
+    help='Experimental: dirstate working tree, Branch6, and reference-tree '
+    'support.  Proposed default for bzr 0.15',
+    branch_format='bzrlib.branch.BzrBranchFormat6',
+    tree_format='bzrlib.workingtree_4.WorkingTreeFormat4',
+    )

=== modified file 'bzrlib/dirstate.py'
--- a/bzrlib/dirstate.py	2007-02-26 00:45:31 +0000
+++ b/bzrlib/dirstate.py	2007-02-26 14:24:46 +0000
@@ -86,6 +86,8 @@
     sha1 value.
 'l' is a symlink entry: As for directory, but a symlink. The fingerprint is the
     link target.
+'t' is a reference to a nested subtree; the fingerprint is the referenced
+    revision.
 
 
 --- Format 1 had the following different definition: ---
@@ -226,8 +228,14 @@
     specific, and if it is how we detect/parameterise that.
     """
 
-    _kind_to_minikind = {'absent':'a', 'file':'f', 'directory':'d', 'relocated':'r', 'symlink':'l'}
-    _minikind_to_kind = {'a':'absent', 'f':'file', 'd':'directory', 'l':'symlink', 'r':'relocated'}
+    _kind_to_minikind = {'absent':'a', 'file':'f', 'directory':'d', 'relocated':'r',
+            'symlink': 'l',
+            'tree-reference': 't',
+        }
+    _minikind_to_kind = {'a':'absent', 'f':'file', 'd':'directory', 'l':'symlink',
+            'r': 'relocated',
+            't': 'tree-reference',
+        }
     _to_yesno = {True:'y', False: 'n'} # TODO profile the performance gain
      # of using int conversion rather than a dict here. AND BLAME ANDREW IF
      # it is faster.
@@ -285,17 +293,20 @@
         self._end_of_header = None
         self._bisect_page_size = DirState.BISECT_PAGE_SIZE
 
-    def add(self, path, file_id, kind, stat, link_or_sha1):
+    def add(self, path, file_id, kind, stat, fingerprint):
         """Add a path to be tracked.
 
         :param path: The path within the dirstate - '' is the root, 'foo' is the
             path foo within the root, 'foo/bar' is the path bar within foo 
             within the root.
         :param file_id: The file id of the path being added.
-        :param kind: The kind of the path.
+        :param kind: The kind of the path, as a string like 'file', 
+            'directory', etc.
         :param stat: The output of os.lstate for the path.
-        :param link_or_sha1: The sha value of the file, or the target of a
-            symlink. '' for directories.
+        :param fingerprint: The sha value of the file,
+            or the target of a symlink,
+            or the referenced revision id for tree-references,
+            or '' for directories.
         """
         # adding a file:
         # find the block its in. 
@@ -340,7 +351,7 @@
         minikind = DirState._kind_to_minikind[kind]
         if kind == 'file':
             entry_data = entry_key, [
-                (minikind, link_or_sha1, size, False, packed_stat),
+                (minikind, fingerprint, size, False, packed_stat),
                 ] + parent_info
         elif kind == 'directory':
             entry_data = entry_key, [
@@ -348,7 +359,11 @@
                 ] + parent_info
         elif kind == 'symlink':
             entry_data = entry_key, [
-                (minikind, link_or_sha1, size, False, packed_stat),
+                (minikind, fingerprint, size, False, packed_stat),
+                ] + parent_info
+        elif kind == 'tree-reference':
+            entry_data = entry_key, [
+                (minikind, fingerprint, 0, False, packed_stat),
                 ] + parent_info
         else:
             raise errors.BzrError('unknown kind %r' % kind)
@@ -1243,8 +1258,12 @@
             fingerprint = inv_entry.text_sha1 or ''
             size = inv_entry.text_size or 0
             executable = inv_entry.executable
+        elif kind == 'tree-reference':
+            fingerprint = inv_entry.reference_revision or ''
+            size = 0
+            executable = False
         else:
-            raise Exception
+            raise Exception("can't pack %s" % inv_entry)
         return (minikind, fingerprint, size, executable, tree_data)
 
     def _iter_entries(self):

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2007-02-26 02:56:24 +0000
+++ b/bzrlib/errors.py	2007-02-26 14:24:46 +0000
@@ -677,6 +677,9 @@
 
     _fmt = "A write attempt was made in a read only transaction on %(obj)s"
 
+    # TODO: There should also be an error indicating that you need a write
+    # lock and don't have any lock at all... mbp 20070226
+
     def __init__(self, obj):
         self.obj = obj
 

=== modified file 'bzrlib/inventory.py'
--- a/bzrlib/inventory.py	2007-02-25 15:15:16 +0000
+++ b/bzrlib/inventory.py	2007-02-26 14:24:46 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005, 2006 Canonical Ltd
+# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -1361,9 +1361,9 @@
 
 
 entry_factory = {
-    'directory':InventoryDirectory,
-    'file':InventoryFile,
-    'symlink':InventoryLink,
+    'directory': InventoryDirectory,
+    'file': InventoryFile,
+    'symlink': InventoryLink,
     'tree-reference': TreeReference
 }
 

=== modified file 'bzrlib/mutabletree.py'
--- a/bzrlib/mutabletree.py	2007-02-25 15:15:16 +0000
+++ b/bzrlib/mutabletree.py	2007-02-26 14:24:46 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 Canonical Ltd
+# Copyright (C) 2006, 2007 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

=== modified file 'bzrlib/tests/test_bzrdir.py'
--- a/bzrlib/tests/test_bzrdir.py	2007-02-21 06:06:06 +0000
+++ b/bzrlib/tests/test_bzrdir.py	2007-02-26 14:24:46 +0000
@@ -78,7 +78,7 @@
         my_format_registry.register_metadir('experimental-knit3', 
             'bzrlib.repofmt.knitrepo.RepositoryFormatKnit3',
             'Format using knits', 
-            tree='WorkingTreeFormatAB1')
+            tree_format='bzrlib.workingtree.WorkingTreeFormatAB1')
         my_format_registry.set_default('knit')
         my_format_registry.register_metadir(
             'experimental-knit2',
@@ -89,7 +89,7 @@
             'branch6',
             'bzrlib.repofmt.knitrepo.RepositoryFormatKnit2',
             'Experimental successor to knit.  Use at your own risk.',
-            branch_format='BzrBranchFormat6')
+            branch_format='bzrlib.branch.BzrBranchFormat6')
         return my_format_registry
 
     def test_format_registry(self):

=== modified file 'bzrlib/tests/tree_implementations/test_tree.py'
--- a/bzrlib/tests/tree_implementations/test_tree.py	2007-02-22 05:51:57 +0000
+++ b/bzrlib/tests/tree_implementations/test_tree.py	2007-02-26 14:24:46 +0000
@@ -40,23 +40,35 @@
 
     def create_nested(self):
         work_tree = self.make_branch_and_tree('wt')
-        self.skip_if_no_reference(work_tree)
-        subtree = self.make_branch_and_tree('wt/subtree')
-        subtree.set_root_id('sub-root')
-        subtree.commit('foo', rev_id='sub-1')
-        work_tree.add_reference(subtree)
+        work_tree.lock_write()
+        try:
+            self.skip_if_no_reference(work_tree)
+            subtree = self.make_branch_and_tree('wt/subtree')
+            subtree.set_root_id('sub-root')
+            subtree.commit('foo', rev_id='sub-1')
+            work_tree.add_reference(subtree)
+        finally:
+            work_tree.unlock()
         tree = self._convert_tree(work_tree)
         self.skip_if_no_reference(tree)
         return tree
 
     def test_get_reference_revision(self):
         tree = self.create_nested()
-        entry = tree.inventory['sub-root']
+        tree.lock_read()
+        try:
+            entry = tree.inventory['sub-root']
+        finally:
+            tree.unlock()
         path = tree.id2path('sub-root')
         self.assertEqual('sub-1', tree.get_reference_revision(entry, path))
 
     def test_iter_reference_entries(self):
         tree = self.create_nested()
-        entry = tree.inventory['sub-root']
+        tree.lock_read()
+        try:
+            entry = tree.inventory['sub-root']
+        finally:
+            tree.unlock()
         self.assertEqual([entry], [e for p, e in
                                    tree.iter_reference_entries()])

=== modified file 'bzrlib/tests/workingtree_implementations/test_add_reference.py'
--- a/bzrlib/tests/workingtree_implementations/test_add_reference.py	2007-02-25 15:33:42 +0000
+++ b/bzrlib/tests/workingtree_implementations/test_add_reference.py	2007-02-26 14:24:46 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006 Canonical Ltd
+# Copyright (C) 2006, 2007 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -58,6 +58,7 @@
             basis.lock_read()
             try:
                 entry = basis.inventory['sub-tree-root-id']
+                self.assertEqual(entry.kind, 'tree-reference')
                 sub_tree = tree.get_nested_tree(entry)
                 self.assertEqual(sub_tree.last_revision(),
                                  entry.reference_revision)
@@ -65,7 +66,6 @@
                 basis.unlock()
         finally:
             tree.unlock()
-        
 
     def test_add_reference_same_root(self):
         tree = self.make_branch_and_tree('tree')

=== modified file 'bzrlib/tree.py'
--- a/bzrlib/tree.py	2007-02-26 02:56:24 +0000
+++ b/bzrlib/tree.py	2007-02-26 14:24:46 +0000
@@ -166,8 +166,9 @@
         raise NotImplementedError("subclasses must implement kind")
 
     def get_reference_revision(self, entry, path=None):
-        raise NotImplementedError("subclasses must implement "
-                                  "get_reference_revision")
+        raise NotImplementedError("Tree subclass %s must implement "
+                                  "get_reference_revision"
+            % self.__class__.__name__)
 
     def _comparison_data(self, entry, path):
         """Return a tuple of kind, executable, stat_value for a file.
@@ -634,102 +635,115 @@
             path in the specific_files list is not versioned in one of
             source, target or extra_trees.
         """
-        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)
-        to_paths = {}
-        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
-            specific_file_ids=specific_file_ids))
-        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
-        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
-            specific_file_ids=specific_file_ids))
-        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
-        entry_count = 0
-        for to_path, to_entry in to_entries_by_dir:
-            file_id = to_entry.file_id
-            to_paths[file_id] = to_path
-            entry_count += 1
-            changed_content = False
-            from_path, from_entry = from_data.get(file_id, (None, None))
-            from_versioned = (from_entry is not None)
-            if from_entry is not None:
-                from_versioned = True
-                from_name = from_entry.name
-                from_parent = from_entry.parent_id
-                from_kind, from_executable, from_stat = \
-                    self.source._comparison_data(from_entry, from_path)
-                entry_count += 1
-            else:
-                from_versioned = False
-                from_kind = None
-                from_parent = None
-                from_name = None
-                from_executable = None
-            versioned = (from_versioned, True)
-            to_kind, to_executable, to_stat = \
-                self.target._comparison_data(to_entry, to_path)
-            kind = (from_kind, to_kind)
-            if kind[0] != kind[1]:
+        # this must return a sequence rather than a list so that it can hold a
+        # read-lock for the whole time.
+        #
+        # TODO: this really only needs to lock the trees not the branches, so
+        # could do with lock_tree_read() -- mbp 20070227
+        result = []
+        self.source.lock_read()
+        self.target.lock_read()
+        try:
+            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)
+            to_paths = {}
+            from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
+                specific_file_ids=specific_file_ids))
+            from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
+            to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
+                specific_file_ids=specific_file_ids))
+            num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
+            entry_count = 0
+            for to_path, to_entry in to_entries_by_dir:
+                file_id = to_entry.file_id
+                to_paths[file_id] = to_path
+                entry_count += 1
+                changed_content = False
+                from_path, from_entry = from_data.get(file_id, (None, None))
+                from_versioned = (from_entry is not None)
+                if from_entry is not None:
+                    from_versioned = True
+                    from_name = from_entry.name
+                    from_parent = from_entry.parent_id
+                    from_kind, from_executable, from_stat = \
+                        self.source._comparison_data(from_entry, from_path)
+                    entry_count += 1
+                else:
+                    from_versioned = False
+                    from_kind = None
+                    from_parent = None
+                    from_name = None
+                    from_executable = None
+                versioned = (from_versioned, True)
+                to_kind, to_executable, to_stat = \
+                    self.target._comparison_data(to_entry, to_path)
+                kind = (from_kind, to_kind)
+                if kind[0] != kind[1]:
+                    changed_content = True
+                elif from_kind == 'file':
+                    from_size = self.source._file_size(from_entry, from_stat)
+                    to_size = self.target._file_size(to_entry, to_stat)
+                    if from_size != to_size:
+                        changed_content = True
+                    elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
+                        self.target.get_file_sha1(file_id, to_path, to_stat)):
+                        changed_content = True
+                elif from_kind == 'symlink':
+                    if (self.source.get_symlink_target(file_id) != 
+                        self.target.get_symlink_target(file_id)):
+                        changed_content = True
+                elif from_kind == 'tree-reference':
+                    if (self.source.get_reference_revision(from_entry, from_path)
+                        != self.target.get_reference_revision(to_entry, to_path)):
+                        changed_content = True 
+                parent = (from_parent, to_entry.parent_id)
+                name = (from_name, to_entry.name)
+                executable = (from_executable, to_executable)
+                if pb is not None:
+                    pb.update('comparing files', entry_count, num_entries)
+                if (changed_content is not False or versioned[0] != versioned[1] 
+                    or parent[0] != parent[1] or name[0] != name[1] or 
+                    executable[0] != executable[1] or include_unchanged):
+                    result.append((file_id, to_path, changed_content, versioned, parent,
+                           name, kind, executable))
+            def get_to_path(from_entry):
+                if from_entry.parent_id is None:
+                    to_path = ''
+                else:
+                    if from_entry.parent_id not in to_paths:
+                        get_to_path(self.source.inventory[from_entry.parent_id])
+                    to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
+                                               from_entry.name)
+                to_paths[from_entry.file_id] = to_path
+                return to_path
+
+            for path, from_entry in from_entries_by_dir:
+                file_id = from_entry.file_id
+                if file_id in to_paths:
+                    continue
+                to_path = get_to_path(from_entry)
+                entry_count += 1
+                if pb is not None:
+                    pb.update('comparing files', entry_count, num_entries)
+                versioned = (True, False)
+                parent = (from_entry.parent_id, None)
+                name = (from_entry.name, None)
+                from_kind, from_executable, stat_value = \
+                    self.source._comparison_data(from_entry, path)
+                kind = (from_kind, None)
+                executable = (from_executable, None)
                 changed_content = True
-            elif from_kind == 'file':
-                from_size = self.source._file_size(from_entry, from_stat)
-                to_size = self.target._file_size(to_entry, to_stat)
-                if from_size != to_size:
-                    changed_content = True
-                elif (self.source.get_file_sha1(file_id, from_path, from_stat) !=
-                    self.target.get_file_sha1(file_id, to_path, to_stat)):
-                    changed_content = True
-            elif from_kind == 'symlink':
-                if (self.source.get_symlink_target(file_id) != 
-                    self.target.get_symlink_target(file_id)):
-                    changed_content = True
-            elif from_kind == 'tree-reference':
-                if (self.source.get_reference_revision(from_entry, from_path)
-                    != self.target.get_reference_revision(to_entry, to_path)):
-                    changed_content = True 
-            parent = (from_parent, to_entry.parent_id)
-            name = (from_name, to_entry.name)
-            executable = (from_executable, to_executable)
-            if pb is not None:
-                pb.update('comparing files', entry_count, num_entries)
-            if (changed_content is not False or versioned[0] != versioned[1] 
-                or parent[0] != parent[1] or name[0] != name[1] or 
-                executable[0] != executable[1] or include_unchanged):
-                yield (file_id, to_path, changed_content, versioned, parent,
-                       name, kind, executable)
-
-        def get_to_path(from_entry):
-            if from_entry.parent_id is None:
-                to_path = ''
-            else:
-                if from_entry.parent_id not in to_paths:
-                    get_to_path(self.source.inventory[from_entry.parent_id])
-                to_path = osutils.pathjoin(to_paths[from_entry.parent_id],
-                                           from_entry.name)
-            to_paths[from_entry.file_id] = to_path
-            return to_path
-
-        for path, from_entry in from_entries_by_dir:
-            file_id = from_entry.file_id
-            if file_id in to_paths:
-                continue
-            to_path = get_to_path(from_entry)
-            entry_count += 1
-            if pb is not None:
-                pb.update('comparing files', entry_count, num_entries)
-            versioned = (True, False)
-            parent = (from_entry.parent_id, None)
-            name = (from_entry.name, None)
-            from_kind, from_executable, stat_value = \
-                self.source._comparison_data(from_entry, path)
-            kind = (from_kind, None)
-            executable = (from_executable, None)
-            changed_content = True
-            # the parent's path is necessarily known at this point.
-            yield(file_id, to_path, changed_content, versioned, parent,
-                  name, kind, executable)
+                # the parent's path is necessarily known at this point.
+                result.append((file_id, to_path, changed_content, versioned, parent,
+                      name, kind, executable))
+        finally:
+            self.source.unlock()
+            self.target.unlock()
+        return result
+
 
 
 # This was deprecated before 0.12, but did not have an official warning

=== modified file 'bzrlib/workingtree_4.py'
--- a/bzrlib/workingtree_4.py	2007-02-26 04:44:45 +0000
+++ b/bzrlib/workingtree_4.py	2007-02-26 14:24:46 +0000
@@ -105,10 +105,12 @@
     """This is the Format 4 working tree.
 
     This differs from WorkingTree3 by:
-     - having a consolidated internal dirstate.
-     - not having a regular inventory attribute.
+     - Having a consolidated internal dirstate, stored in a
+       randomly-accessible sorted file on disk.
+     - Not having a regular inventory attribute.  One can be synthesized 
+       on demand but this is expensive and should be avoided.
 
-    This is new in bzr TODO FIXME SETMEBEFORE MERGE.
+    This is new in bzr 0.15.
     """
 
     def __init__(self, basedir,
@@ -180,6 +182,14 @@
             state.add(f, file_id, kind, None, '')
         self._dirty = True
 
+    @needs_tree_write_lock
+    def add_reference(self, sub_tree):
+        # use standard implementation, which calls back to self._add
+        # 
+        # So we don't store the reference_revision in the working dirstate,
+        # it's just recorded at the moment of commit. 
+        self._add_reference(sub_tree)
+
     def break_lock(self):
         """Break a lock if one is present from another instance.
 
@@ -217,6 +227,14 @@
         self._control_files.break_lock()
         self.branch.break_lock()
 
+    def _comparison_data(self, entry, path):
+        kind, executable, stat_value = \
+            WorkingTree3._comparison_data(self, entry, path)
+        # it looks like a plain directory, but it's really a reference
+        if kind == 'directory' and entry.kind == 'tree-reference':
+            kind = 'tree-reference'
+        return kind, executable, stat_value
+
     def current_dirstate(self):
         """Return the current dirstate object. 
 
@@ -225,8 +243,7 @@
 
         :raises errors.NotWriteLocked: when not in a lock. 
         """
-        if not self._control_files._lock_count:
-            raise errors.ObjectNotLocked(self)
+        self._must_be_locked()
         return self._current_dirstate()
 
     def _current_dirstate(self):
@@ -357,6 +374,7 @@
         """Get the inventory for the tree. This is only valid within a lock."""
         if self._inventory is not None:
             return self._inventory
+        self._must_be_locked()
         self._generate_inventory()
         return self._inventory
 
@@ -371,6 +389,15 @@
         """
         return self.current_dirstate().get_parent_ids()
 
+    def get_reference_revision(self, entry, path=None):
+        # referenced tree's revision is whatever's currently there
+        return self.get_nested_tree(entry, path).last_revision()
+
+    def get_nested_tree(self, entry, path=None):
+        if path is None:
+            path = self.id2path(entry.file_id)
+        return WorkingTree.open(self.abspath(path))
+
     @needs_read_lock
     def get_root_id(self):
         """Return the id of this trees root"""
@@ -415,6 +442,21 @@
                 result.append(key[2])
         return iter(result)
 
+    def kind(self, file_id):
+        # The kind of a file is whatever it actually is on disk, except that 
+        # tree-references need to be reported as such rather than as the
+        # directiories
+        #
+        # TODO: Possibly we should check that the directory still really
+        # contains a subtree, at least during commit? mbp 20070227
+        kind = WorkingTree3.kind(self, file_id)
+        if kind == 'directory':
+            # TODO: ask the dirstate not the inventory -- mbp 20060227
+            entry = self.inventory[file_id]
+            if entry.kind == 'tree-reference':
+                kind = 'tree-reference'
+        return kind
+
     @needs_read_lock
     def _last_revision(self):
         """See Mutable.last_revision."""
@@ -659,6 +701,10 @@
 
         return #rename_tuples
 
+    def _must_be_locked(self):
+        if not self._control_files._lock_count:
+            raise errors.ObjectNotLocked(self)
+
     def _new_tree(self):
         """Initialize the state in this tree to be a new tree."""
         self._dirty = True
@@ -1033,6 +1079,8 @@
         - uses a LockDir to guard access to it.
     """
 
+    supports_tree_reference = True
+
     def get_format_string(self):
         """See WorkingTreeFormat.get_format_string()."""
         return "Bazaar Working Tree format 4\n"
@@ -1093,6 +1141,13 @@
                            _bzrdir=a_bzrdir,
                            _control_files=control_files)
 
+    def __get_matchingbzrdir(self):
+        # please test against something that will let us do tree references
+        return bzrdir.format_registry.make_bzrdir(
+            'experimental-reference-dirstate')
+
+    _matchingbzrdir = property(__get_matchingbzrdir)
+
 
 class DirStateRevisionTree(Tree):
     """A revision tree pulling the inventory from a dirstate."""
@@ -1112,7 +1167,6 @@
         return w.annotate_iter(self.inventory[file_id].revision)
 
     def _comparison_data(self, entry, path):
-        """See Tree._comparison_data."""
         if entry is None:
             return None, False, None
         # trust the entry as RevisionTree does, but this may not be
@@ -1156,8 +1210,11 @@
     def _generate_inventory(self):
         """Create and set self.inventory from the dirstate object.
 
+        (So this is only called the first time the inventory is requested for
+        this tree; it then remains in memory.)
+
         This is relatively expensive: we have to walk the entire dirstate.
-        Ideally we would not, and instead would """
+        """
         assert self._locked, 'cannot generate inventory of an unlocked '\
             'dirstate revision tree'
         # separate call for profiling - makes it clear where the costs are.
@@ -1189,7 +1246,7 @@
                 # all the paths in this block are not versioned in this tree
                 continue
             for key, entry in block[1]:
-                minikind, link_or_sha1, size, executable, revid = entry[parent_index]
+                minikind, fingerprint, size, executable, revid = entry[parent_index]
                 if minikind in ('a', 'r'): # absent, relocated
                     # not this tree
                     continue
@@ -1203,15 +1260,18 @@
                 if kind == 'file':
                     inv_entry.executable = executable
                     inv_entry.text_size = size
-                    inv_entry.text_sha1 = link_or_sha1
+                    inv_entry.text_sha1 = fingerprint
                 elif kind == 'directory':
                     parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
                 elif kind == 'symlink':
                     inv_entry.executable = False
                     inv_entry.text_size = size
-                    inv_entry.symlink_target = utf8_decode(link_or_sha1)[0]
+                    inv_entry.symlink_target = utf8_decode(fingerprint)[0]
+                elif kind == 'tree-reference':
+                    inv_entry.reference_revision = fingerprint
                 else:
-                    raise Exception, kind
+                    raise AssertionError("cannot convert entry %r into an InventoryEntry"
+                            % entry)
                 # These checks cost us around 40ms on a 55k entry tree
                 assert file_id not in inv_byid
                 assert name_unicode not in parent_ie.children
@@ -1272,9 +1332,6 @@
     def has_filename(self, filename):
         return bool(self.path2id(filename))
 
-    def kind(self, file_id):
-        return self.inventory[file_id].kind
-
     def is_executable(self, file_id, path=None):
         ie = self.inventory[file_id]
         if ie.kind != "file":




More information about the bazaar-commits mailing list