[Zope-CVS] CVS: Products/ZopeVersionControl - Repository.py:1.4 Version.py:1.7 ZopeRepository.py:1.3 ZopeVersion.py:1.3

Shane Hathaway shane@zope.com
Wed, 15 Jan 2003 17:18:19 -0500


Update of /cvs-repository/Products/ZopeVersionControl
In directory cvs.zope.org:/tmp/cvs-serv29711

Modified Files:
	Repository.py Version.py ZopeRepository.py ZopeVersion.py 
Log Message:
Containers are now versioned independently of their ObjectManagerish contents.
This means it is now possible to put versioned objects in a hierarchy.
The current implementation ignores the fact that the user may actually want
to version the item names and item history IDs.  Instead, it only
versions the aspects of the container that are not items.


=== Products/ZopeVersionControl/Repository.py 1.3 => 1.4 ===
--- Products/ZopeVersionControl/Repository.py:1.3	Wed Jun 12 13:57:43 2002
+++ Products/ZopeVersionControl/Repository.py	Wed Jan 15 17:17:46 2003
@@ -63,8 +63,8 @@
         """Internal: replace a persistent object in its container."""
         container = aq_parent(aq_inner(object))
         item_id = object.getId()
-        container._delObject(item_id)
-        container._setObject(item_id, new_object)
+        container._delOb(item_id)
+        container._setOb(item_id, new_object)
         return container._getOb(item_id)
 
     #####################################################################


=== Products/ZopeVersionControl/Version.py 1.6 => 1.7 ===
--- Products/ZopeVersionControl/Version.py:1.6	Wed Jun 12 13:57:43 2002
+++ Products/ZopeVersionControl/Version.py	Wed Jan 15 17:17:46 2003
@@ -13,12 +13,49 @@
 
 __version__='$Revision$'[11:-2]
 
-from Acquisition import Implicit, aq_parent, aq_inner
+import tempfile
+import time
+from cStringIO import StringIO
+from cPickle import Pickler, Unpickler
+
+from Acquisition import Implicit, aq_parent, aq_inner, aq_base
 from Globals import InitializeClass, Persistent
 from AccessControl import ClassSecurityInfo
-from Utility import VersionControlError
 from BTrees.OOBTree import OOBTree
-import tempfile, time
+
+from Utility import VersionControlError
+
+
+def cloneByPickle(object, ignore_list=()):
+    """Makes a copy of a ZODB object, loading ghosts as needed.
+
+    Ignores specified objects along the way, replacing them with None
+    in the copy.
+    """
+    ignore_dict = {}
+    for o in ignore_list:
+        ignore_dict[id(o)] = o
+
+    def persistent_id(ob, ignore_dict=ignore_dict):
+        if ignore_dict.has_key(id(ob)):
+            return 'ignored'
+        if getattr(ob, '_p_changed', 0) is None:
+            ob._p_changed = 0
+        return None
+
+    def persistent_load(ref):
+        assert ref == 'ignored'
+        return None
+
+    stream = StringIO()
+    p = Pickler(stream, 1)
+    p.persistent_id = persistent_id
+    p.dump(object)
+    stream.seek(0)
+    u = Unpickler(stream)
+    u.persistent_load = persistent_load
+    return u.load()
+
 
 class Version(Implicit, Persistent):
     """A Version is a resource that contains a copy of a particular state
@@ -61,19 +98,7 @@
     def stateCopy(self, object, container):
         """Get a deep copy of the state of an object, breaking any database
            identity references."""
-        # TODO: this should probably use Utility._findModificationTime,
-        # though we should gauge the impact on performance.
-        if (getattr(object, '_p_changed', 0) or
-            getattr(object, '_p_jar', None) is None or
-            getattr(container, '_p_jar', None) is None):
-            get_transaction().commit(1)
-        file = tempfile.TemporaryFile()
-        object._p_jar.exportFile(object._p_oid, file)
-        file.seek(0)
-        result = container._p_jar.importFile(file)
-        file.close()
-        return result
-
+        return cloneByPickle(aq_base(object))
 
 
 InitializeClass(Version)


=== Products/ZopeVersionControl/ZopeRepository.py 1.2 => 1.3 ===
--- Products/ZopeVersionControl/ZopeRepository.py:1.2	Thu May  9 13:43:40 2002
+++ Products/ZopeVersionControl/ZopeRepository.py	Wed Jan 15 17:17:46 2003
@@ -17,6 +17,8 @@
 from SequenceWrapper import SequenceWrapper
 import OFS, AccessControl, Acquisition
 import Repository
+from Acquisition import aq_base
+from OFS.ObjectManager import ObjectManager
 
 
 class ZopeRepository(
@@ -83,6 +85,55 @@
     security.declarePrivate('objectItems')
     def objectItems(self, spec=None):
         return SequenceWrapper(self, self._histories.items(), 1)
+
+    security.declarePrivate('listItems')
+    def listContainedItems(self, object):
+        """Internal: list the items of a container as [(id, value)]."""
+        items = []
+        try:
+            v = isinstance(object, ObjectManager)
+        except TypeError:
+            # Python 2.1 isinstance() dislikes ExtensionClass instances.
+            # This is an adequate workaround.
+            v = 0
+        if v:
+            # Return the items.
+            for name, value in object.objectItems():
+                items.append((name, aq_base(value)))
+        return items
+
+    security.declarePrivate('addItems')
+    def restoreContainedItems(self, ob, items):
+        """Internal: restore the items of a container."""
+        # First build "keep", a dictionary listing which
+        # items the object were provided by the version controlled object.
+        # Don't restore over those.
+        keep = {}
+        for name in ob.objectIds():
+            keep[name] = 1
+        # Restore the items.
+        for name, value in items:
+            if not keep.has_key(name):
+                ob._setOb(name, value)
+                if not hasattr(ob, '_tree'):
+                    # Avoid generating events, since nothing was ever really
+                    # removed or added.
+                    ob._objects += ({'meta_type': value.meta_type,
+                                     'id': name},)
+                # If there is a _tree attribute, it's very likely
+                # a BTreeFolder2, which doesn't need or want the
+                # _objects attribute.
+                # XXX This is a hackish way to check for BTreeFolder2s.
+
+    security.declarePrivate('replaceObject')
+    def replaceObject(self, object, new_object):
+        """Internal: replace a persistent object in its container."""
+        items = self.listContainedItems(object)
+        ob = Repository.Repository.replaceObject(self, object, new_object)
+        if items:
+            self.restoreContainedItems(ob, items)
+        return ob
+
 
 
 InitializeClass(ZopeRepository)


=== Products/ZopeVersionControl/ZopeVersion.py 1.2 => 1.3 ===
--- Products/ZopeVersionControl/ZopeVersion.py:1.2	Thu May  9 13:43:40 2002
+++ Products/ZopeVersionControl/ZopeVersion.py	Wed Jan 15 17:17:46 2003
@@ -16,6 +16,21 @@
 from Globals import DTMLFile, InitializeClass
 import OFS, AccessControl, Acquisition
 import Version
+from Acquisition import aq_base
+from OFS.ObjectManager import ObjectManager
+
+
+def cloneObjectManager(object):
+    """Makes a copy of an object manager without its subitems.
+    """
+    ids = object.objectIds()
+    values = object.objectValues()
+    object = Version.cloneByPickle(object, values)
+    for id in ids:
+        object._delOb(id)
+    if object._objects:
+        object._objects = ()
+    return object
 
 
 class ZopeVersion(
@@ -62,6 +77,22 @@
                 self, REQUEST, manage_tabs_message=message
 
                 )
+
+    security.declarePrivate('stateCopy')
+    def stateCopy(self, object, container):
+        """Get a deep copy of the state of an object, breaking any database
+           identity references."""
+        object = aq_base(object)
+        try:
+            v = isinstance(object, ObjectManager)
+        except TypeError:
+            # Python 2.1 isinstance() dislikes ExtensionClass instances.
+            # This is an adequate workaround.
+            v = 0
+        if v:
+            return cloneObjectManager(object)
+        else:
+            return Version.cloneByPickle(object)
 
 
 InitializeClass(ZopeVersion)