[Zope-CVS] CVS: Products/ZopeVersionControl - IVersionControl.py:1.2 Repository.py:1.2 Utility.py:1.2 Version.py:1.5 VersionHistory.py:1.3 VersionSupport.py:1.2 ZopeRepository.py:1.2 ZopeVersion.py:1.2 ZopeVersionHistory.py:1.2 __init__.py:1.2 notes.py:NONE

Shane Hathaway shane@cvs.zope.org
Thu, 9 May 2002 13:44:11 -0400


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

Modified Files:
	IVersionControl.py Repository.py Utility.py Version.py 
	VersionHistory.py VersionSupport.py ZopeRepository.py 
	ZopeVersion.py ZopeVersionHistory.py __init__.py 
Removed Files:
	notes.py 
Log Message:
Brought in line with Brian's current work


=== Products/ZopeVersionControl/IVersionControl.py 1.1.1.1 => 1.2 ===


=== Products/ZopeVersionControl/Repository.py 1.1 => 1.2 === (453/553 lines abridged)
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+# 
+##############################################################################
+
 __version__='$Revision$'[11:-2]
 
-from Globals import DTMLFile, InitializeClass
-from OFS.ObjectManager import ObjectManager
-from VersionHistory import VersionHistory
-import OFS, AccessControl, Acquisition
-
-
-class Repository(
-    OFS.ObjectManager.ObjectManager,
-    AccessControl.Role.RoleManager,
-    OFS.SimpleItem.Item,
-    ):
-    """ """
-    __non_versionable__ = 1
-
-    security = AccessControl.ClassSecurityInfo()
-    
-    meta_type = 'Repository'
-
-    manage_options=(
-        ( {'label': 'Contents',    'action':'manage_main',
-           'help': ('Zope Help', 'Repository-Manage.stx')},
-          {'label': 'Properties', 'action':'manage_properties_form',
-           'help': ('Zope Help', 'Repository-Properties.stx')},
-        ) +
-        AccessControl.Role.RoleManager.manage_options +
-        OFS.SimpleItem.Item.manage_options
-        )
-
-    security.declareProtected('View management screens',
-                              'manage_main', 'manage_properties_form'
-                              )
-
-    manage_main = DTMLFile('dtml/RepositoryManageMain', globals())
-    manage_properties_form = DTMLFile('dtml/RepositoryProperties', globals())
-    manage_main._setName('manage_main')
-    manage = manage_main

[-=- -=- -=- 453 lines omitted -=- -=- -=-]

+        else:
+            if history.hasVersionId(selector):
+                version = history.getVersionById(selector)
+                sticky = ('V', selector)
+
+            elif self._labels.has_key(selector):
+                version = history.getVersionByLabel(selector)
+                sticky = ('L', selector)
+
+            elif self._branches.has_key(selector):
+                version = history.getLatestVersion(selector)
+                sticky = ('B', selector)
+            else:
+                try: date = DateTime(selector)
+                except:
+                    raise VersionControlError(
+                        'Invalid version selector: %s' % selector
+                        )
+                else:
+                    timestamp = date.timeTime()
+                    sticky = ('D', timestamp)
+                    version = history.getVersionByDate('mainline', timestamp)
+
+        object = version.copyState()
+
+        info = VersionInfo(history_id, version.getId(), VersionInfo.CHECKED_IN)
+        if sticky is not None:
+            info.sticky = sticky
+        object.__vc_info__ = info
+        return object
+
+    security.declareProtected(use_vc_permission, 'getVersionIds')
+    def getVersionIds(self, object):
+        info = self.getVersionInfo(object)
+        history = self.getVersionHistory(info.history_id)
+        return history.getVersionIds()
+
+    security.declareProtected(use_vc_permission, 'getLabelsForResource')
+    def getLabelsForResource(self, object):
+        info = self.getVersionInfo(object)
+        history = self.getVersionHistory(info.history_id)
+        return history.getLabels()
+
+    security.declareProtected(use_vc_permission, 'getLogEntries')
+    def getLogEntries(self, object):
+        info = self.getVersionInfo(object)
+        history = self.getVersionHistory(info.history_id)
+        return history.getLogEntries()
+
+InitializeClass(Repository)


=== Products/ZopeVersionControl/Utility.py 1.1 => 1.2 ===
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+# 
+##############################################################################
 
 from Globals import InitializeClass, Persistent
-import ExtensionClass, AccessControl
-import string
+from AccessControl import ClassSecurityInfo
+from ZODB.referencesf import referencesf
+from ZODB.TimeStamp import TimeStamp
+from App.Common import package_home
+import os, time, AccessControl
+from DateTime import DateTime
+from types import FloatType
 
 
-class VersionInfo(Persistent):
-    """ """
-
-    security = AccessControl.ClassSecurityInfo()
-    
-    def __init__(self, object):
-        pass
-
-    security.declarePublic('getRepositoryId')
-    def getRepositoryId(self):
-        return self._repository_id
-
-    security.declarePrivate('setRepositoryId')
-    def setRepositoryId(self, id):
-        self._repository_id = id
-
-    security.declarePublic('getVersionHistoryId')
-    def getVersionHistoryId(self):
-        return self._history_id
-
-    security.declarePrivate('setVersionHistoryId')
-    def setVersionHistoryId(self, id):
-        self._history_id = id
-
-    security.declarePublic('getSourceVersionId')
-    def getSourceVersionId(self):
-        return self._version_id
-
-    security.declarePrivate('setSourceVersionId')
-    def setSourceVersionId(self, id):
-        self._version_id = id
-
-    security.declarePublic('getVersionPath')
-    def getVersionPath(self):
-        return self._version_path
+_dtmldir = os.path.join( package_home( globals() ), 'dtml' )
 
-    security.declarePrivate('setVersionPath')
-    def setVersionPath(self, path):
-        self._version_path = path
-
-    security.declarePublic('getResourceStatus')
-    def getResourceStatus(self):
-        return getattr(self, '_status', 'checked-in')
-
-    security.declarePrivate('setResourceStatus')
-    def setResourceStatus(self, status):
-        if not status in ('checked-in', 'checked-out'):
-            raise ValueError, status
-        self._status = status
+use_vc_permission = 'Use version control'
 
 
+class VersionInfo(Persistent):
+    """A VersionInfo object contains bookkeeping information for version
+       controlled objects. The bookkeeping information can be read (but
+       not changed) by restricted code."""
+
+    def __init__(self, history_id, version_id, status):
+        self.timestamp = time.time()
+        self.history_id = history_id
+        self.version_id = version_id
+        self.status = status
+        self.user_id = _findUserId()
+
+    sticky = None
+
+    CHECKED_OUT = 0
+    CHECKED_IN = 1
+
+    def branchName(self):
+        if self.sticky is not None and self.sticky[0] == 'B':
+            return self.sticky[1]
+        return 'mainline'
 
-    security.declarePrivate('clone')
-    def clone(self):
-        info = VersionInfo(None)
+    def clone(self, clear_sticky=0):
+        info = VersionInfo(self.history_id, self.version_id, self.status)
         dict = info.__dict__
         for name, value in self.__dict__.items():
             dict[name] = value
+        if clear_sticky:
+            if dict.has_key('sticky'):
+                del dict['sticky']
+        info.user_id = _findUserId()
+        info.timestamp = time.time()
         return info
 
-
-class ObjectMetadata(Persistent):
-    """ """
-
-    security = AccessControl.ClassSecurityInfo()
-    
-    def __init__(self, object):
-        # Save some basic information about the object, so that we
-        # don't have to wake it up for simple things like listing
-        # the available version histories in the repository.
-        self._ob_meta_type = getattr_ex(object, 'meta_type', None)
-        self._ob_icon = getattr_ex(object, 'icon', None)
-        path = object.getPhysicalPath()
-        self._ob_path = string.join(path, '/')
-        self._ob_id = find_id(object)
-
-    security.declarePublic('getItemIcon')
-    def getItemIcon(self):
-        """Return the icon of the underlying version controlled resource."""
-        return self._ob_icon
-
-    security.declarePublic('getItemType')
-    def getItemType(self):
-        """Return the type name of the version controlled resource."""
-        return self._ob_meta_type
-
-    security.declarePublic('getItemPath')
-    def getItemPath(self):
-        """Return the original path of the resource that intially was
-           used to create the version history in the repository."""
-        return self._ob_path
-
-    security.declarePublic('getItemId')
-    def getItemId(self):
-        """Return the original id of the version controlled resource."""
-        return self._ob_id
+InitializeClass(VersionInfo)
 
 
-InitializeClass(ObjectMetadata)
 
+class ReadOnlyJar:
+    """A read-only ZODB connection-like object that prevents changes."""
 
+    def __init__(self, base):
+        self.__base__ = base
 
-from DateTime.DateTime import DateTime
-import time
+    _invalidating = []
 
-class AuditRecord(Persistent):
-    """ """
+    def __getattr__(self, name):
+        return getattr(self.__base__, name)
 
-    security = AccessControl.ClassSecurityInfo()
+    def commit(*args, **kw):
+        raise VersionWriteError(
+            'Old versions of objects cannot be modified.'
+            )
 
-    def __init__(self, action, username, path, message):
-        self._version_id = None
-        self._action = action
-        self._path = path
-        self._username = username
-        self._message = message
-        self._time = time.time()
-
-    security.declarePublic('getVersionId')
-    def getVersionId(self):
-        return self._version_id
-
-    security.declarePublic('setVersionId')
-    def setVersionId(self, version_id):
-        self._version_id = version_id
-
-    security.declarePublic('getAction')
-    def getAction(self):
-        return self._action
-
-    security.declarePublic('getTargetPath')
-    def getTargetPath(self):
-        return self._path
-
-    security.declarePublic('getUserName')
-    def getUserName(self):
-        return self._username
-
-    security.declarePublic('getMessage')
-    def getMessage(self):
-        return self._message
-
-    security.declarePublic('getDateTime')
-    def getDateTime(self):
-        return DateTime(self._time)
-
-
-
-InitializeClass(AuditRecord)
-
-
-class CheckoutRecord(Persistent):
-    """ """
-
-    security = AccessControl.ClassSecurityInfo()
-
-    def __init__(self, activity_id, version_id, path, username):
-        self._activity_id = activity_id
-        self._version_id = version_id
-        self._username = username
-        self._time = time.time()
-        self._path = path
-
-    security.declarePublic('getActivityId')
-    def getActivityId(self):
-        return self._activity_id
-
-    security.declarePublic('getVersionId')
-    def getVersionId(self):
-        return self._version_id
-
-    security.declarePublic('getTargetPath')
-    def getTargetPath(self):
-        return self._path
+    def abort(*args, **kw):
+        pass
 
-    security.declarePublic('getUserName')
-    def getUserName(self):
-        return self._username
 
-    security.declarePublic('getMessage')
-    def getMessage(self):
-        return self._message
 
-    security.declarePublic('getDateTime')
-    def getDateTime(self):
-        return DateTime(self._time)
+class VersionControlError(Exception):
+    pass
 
-InitializeClass(CheckoutRecord)
 
 
+def _findUserId():
+    user = AccessControl.getSecurityManager().getUser()
+    return user.getUserName()
+
+def _findPath(object):
+    path = object.getPhysicalPath()
+    return '/'.join(path)
+
+def _findModificationTime(object):
+    """Find the last modification time for a version-controlled object.
+       The modification time reflects the latest modification time of
+       the object or any of its persistent subobjects that are not
+       themselves version-controlled objects. Note that this will
+       return None if the object has no modification time."""
+
+    mtime = getattr(object, '_p_mtime', None)
+    if mtime is None:
+        return None
+
+    latest = mtime
+    conn = object._p_jar
+    load = conn._storage.load
+    version = conn._version
+    refs = referencesf
+
+    oids=[object._p_oid]
+    done_oids={}
+    done=done_oids.has_key
+    first = 1
+
+    while oids:
+        oid=oids[0]
+        del oids[0]
+        if done(oid):
+            continue
+        done_oids[oid]=1
+        try: p, serial = load(oid, version)
+        except: pass # invalid reference!
+        else:
+            if first is not None:
+                first = None
+            else:
+                if p.find('U\x0b__vc_info__') == -1:
+                    mtime = TimeStamp(serial).timeTime()
+                    if mtime > latest:
+                        latest = mtime
+            refs(p, oids)
 
-_marker = []
+    return latest
 
-def getattr_ex(object, name, default=_marker):
-    data = getattr(object, name, default)
-    if data is _marker and default is _marker:
-        raise AttributeError, name
-    if callable(data):
-        return data()
-    return data
-    
-def find_id(object):
-    name=getattr(object, 'id', None)
-    if callable(name):
-        return name()
-    if name is not None:
-        return name
-    if hasattr(object, '__name__'):
-        return object.__name__
-    raise AttributeError, 'This object has no id'


=== Products/ZopeVersionControl/Version.py 1.4 => 1.5 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+# 
+##############################################################################
+
 __version__='$Revision$'[11:-2]
 
-from Globals import DTMLFile, InitializeClass
-from Globals import PersistentMapping
-from OFS.ObjectManager import ObjectManager
-import OFS, AccessControl, Acquisition
-from Utility import ObjectMetadata
-from Utility import AuditRecord
-import copy
-
-class Version(
-    Acquisition.Implicit,
-    OFS.SimpleItem.Item,
-    AccessControl.Role.RoleManager
-    ):
-    """A "version resource", or simply "version", is a resource that 
-       contains a copy of a particular state (content and dead properties) 
-       of a version-controlled resource.  A version is created by 
-       "checking in" a checked-out resource.  The server allocates a 
-       distinct new URL for each new version, and this URL will never be 
-       used to identify any resource other than that version.  The content 
-       and dead properties of a version never change.
-
-       A "version name" is a string chosen by the server to distinguish 
-       one version of a version history from the other versions of that 
-       version history.  Versions from different version histories may 
-       have the same version name."""
-
-    security = AccessControl.ClassSecurityInfo()
-    __non_versionable__ = 1
-
-    meta_type = 'Version'
-
-    manage_options=(
-        ( {'label': 'Information',    'action':'manage_main',
-           'help': ('Zope Help', 'Version-Manage.stx')},
-          {'label': 'Activity Log',    'action':'manage_activity_form',
-           'help': ('Zope Help', 'Version-ActivityLog.stx')},
-          {'label': 'Properties', 'action':'manage_properties_form',
-           'help': ('Zope Help', 'Version-Properties.stx')},
-        ) +
-        AccessControl.Role.RoleManager.manage_options +
-        OFS.SimpleItem.Item.manage_options
-        )
-
-    icon='misc_/ZopeVersionControl/Version.gif'
-
-    security.declareProtected('View management screens',
-                              'manage_main',
-                              'manage_properties_form',
-                              'manage_activity_form'
-                              )
-
-    manage_main = DTMLFile('dtml/VersionManageMain', globals())
-    manage_main._setName('manage_main')
-    manage = manage_main
-
-    manage_properties_form = DTMLFile('dtml/VersionProperties', globals())
-    manage_activity_form = DTMLFile('dtml/VersionActivityLog', globals())
-
-    security.declareProtected('Manage version resources', 'manage_edit')
-    def manage_edit(self, REQUEST=None):
-        """Change object properties."""
-        if REQUEST is not None:
-            message="Saved changes."
-            return self.manage_properties_form(
-                self, REQUEST, manage_tabs_message=message
-
-                )
-
-    def __init__(self, id, object, parent):
-        self.id = id
-        self.meta = ObjectMetadata(object)
-        self.saveState(object, parent)
-        # xxx - fix this
-        self.records = []
-#        self.co_state = PersistentMapping()
-
-    def addMessageEntry(self, action, username, path, message):
-        record = AuditRecord(action, username, path, message)
-        record.setVersionId(self.id)
-        records = self.records
-        records.append(record)
-        self.records = records
-
-
-    def getMessageEntries(self):
-        return self.records
-
-    def getReadOnlyObject(self):
-        """Return the object corresponding to this version."""
-        object = Acquisition.aq_base(self.data)
-        object._p_jar = ReadOnlyJar(object._p_jar)
-        return object
-
-    def saveState(self, object, parent):
-        baseobj = Acquisition.aq_base(object)
-        # xxx - fix this!
-#        obcopy =  object._getCopy(object)
-        try: obcopy =  object._getCopy(parent)
-        except:
-            # This may happen for the intial version!
-            obcopy = object._getCopy(Acquisition.aq_parent(parent))
-        obcopy.__non_versionable__ = 1
-        self.data = obcopy
-        return
+from Acquisition import Implicit, aq_parent, aq_inner
+from Globals import InitializeClass, Persistent
+from AccessControl import ClassSecurityInfo
+from Utility import VersionControlError
+from BTrees.OOBTree import OOBTree
+import tempfile, time
+
+class Version(Implicit, Persistent):
+    """A Version is a resource that contains a copy of a particular state
+       (content and dead properties) of a version-controlled resource.  A
+       version is created by checking in a checked-out resource. The state
+       of a version of a version-controlled resource never changes."""
+
+    def __init__(self, version_id, object):
+        self.id = version_id
+        self.date_created = time.time()
+        self._data = None
+
+    # These attributes are set by the createVersion method of the version
+    # history at the time the version is created. The branch is the name
+    # of the branch on which the version was created. The prev attribute
+    # is the version id of the predecessor to this version. The next attr
+    # is a sequence of version ids of the successors to this version.
+    branch = 'mainline'
+    prev = None
+    next = ()
 
-    def copyState(self):
-        # xxx - fix this!
-        object = self.data._getCopy(self.data)
-        if hasattr(object, '__non_versionable__'):
-            del object.__non_versionable__
-        return object
+    security = ClassSecurityInfo()
 
-    def getVersionId(self):
-        """ """
+    security.declarePublic('getId')
+    def getId(self):
         return self.id
 
-InitializeClass(Version)
-
-
-
-
-def historicalRevision(self, serial):
-    state=self._p_jar.oldstate(self, serial)
-    rev=self.__class__.__basicnew__()
-    rev._p_jar=HystoryJar(self._p_jar)
-    rev._p_oid=self._p_oid
-    rev._p_serial=serial
-    rev.__setstate__(state)
-    rev._p_changed=0
-    return rev
+    security.declarePrivate('saveState')
+    def saveState(self, object):
+        """Save the state of object as the state for this version of
+           a version-controlled resource."""
+        self._data = self.stateCopy(object, self)
 
+    security.declarePrivate('copyState')
+    def copyState(self):
+        """Return an independent deep copy of the state of the version."""
+        return self.stateCopy(self._data, self)
 
-class ReadOnlyJar:
-    """A read-only ZODB connection-like object that prevents changes."""
-
-    def __init__(self, base):
-        self.__base__=base
-
-    _invalidating = []
-
-    def __getattr__(self, name):
-        return getattr(self.__base__, name)
-
-    def commit(*args, **kw):
-        raise VersionWriteError(
-            'Old versions of objects cannot be modified.'
-            )
+    security.declarePrivate('stateCopy')
+    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):
+            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
 
-    def abort(*args, **kw):
-        pass
 
 
-class VersionWriteError(Exception):
-    pass
+InitializeClass(Version)
 


=== Products/ZopeVersionControl/VersionHistory.py 1.2 => 1.3 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+# 
+##############################################################################
+
 __version__='$Revision$'[11:-2]
 
-from Globals import DTMLFile, InitializeClass
-from Globals import PersistentMapping
-from OFS.ObjectManager import ObjectManager
-import OFS, AccessControl, Acquisition
-from Utility import ObjectMetadata
-from Utility import CheckoutRecord
-from Version import Version
-import copy
-
-
-class VersionHistory(
-    OFS.ObjectManager.ObjectManager,
-    AccessControl.Role.RoleManager,
-    OFS.SimpleItem.Item,
-    ):
-    """A version history resource, or simply version history, is a 
-       resource that contains all the versions of a particular version
-       controlled resource."""
-    __non_versionable__ = 1
-
-    security = AccessControl.ClassSecurityInfo()
-
-    meta_type = 'Version History'
-
-    manage_options=(
-        ( {'label': 'Contents',    'action':'manage_main',
-           'help': ('Zope Help', 'VersionHistory-Manage.stx')},
-          {'label': 'Activity Log',    'action':'manage_activity_form',
-           'help': ('Zope Help', 'VersionHistory-ActivityLog.stx')},
-          {'label': 'Properties', 'action':'manage_properties_form',
-           'help': ('Zope Help', 'VersionHistory-Properties.stx')},
-        ) +
-        AccessControl.Role.RoleManager.manage_options +
-        OFS.SimpleItem.Item.manage_options
-        )
-
-    icon='misc_/ZopeVersionControl/VersionHistory.gif'
-
-    security.declareProtected('View management screens',
-                              'manage_main',
-                              'manage_properties_form',
-                              'manage_activity_form'
-                              )
-
-    manage_main = DTMLFile('dtml/VersionHistoryManageMain', globals())
-    manage_main._setName('manage_main')
-    manage = manage_main
-
-    manage_properties_form = DTMLFile('dtml/VersionHistoryProperties',
-                                      globals())
-    manage_activity_form = DTMLFile('dtml/VersionHistoryActivityLog',
-                                    globals())
-
-
-    def __init__(self, id, object):
-        self.id = id
-        self.meta = ObjectMetadata(object)
-        self.co_state = PersistentMapping()
-
-    security.declareProtected('Manage version histories', 'manage_edit')
-    def manage_edit(self, REQUEST=None):
-        """Change object properties."""
-        if REQUEST is not None:
-            message="Saved changes."
-            return self.manage_properties_form(
-                self, REQUEST, manage_tabs_message=message
+from Globals import InitializeClass, Persistent
+from AccessControl import ClassSecurityInfo
+from EventLog import EventLog, LogEntry
+from Utility import VersionControlError
+from ZopeVersion import ZopeVersion
+from BTrees.IOBTree import IOBTree
+from BTrees.IIBTree import IIBTree
+from BTrees.OOBTree import OOBTree
+from Acquisition import Implicit
+import sys, time
+
+
+class VersionHistory(Implicit, Persistent):
+    """A version history maintains the information about the changes
+       to a particular version-controlled resource over time."""
+
+    def __init__(self, history_id, object):
+        # The _versions mapping maps version ids to version objects. All
+        # of the actual version data is looked up there. The _labels
+        # mapping maps labels to specific version ids. The _branches map
+        # manages BranchInfo objects that maintain branch information.
+        self._eventLog = EventLog()
+        self._versions = OOBTree()
+        self._branches = OOBTree()
+        self._labels = OOBTree()
+        mainline = self.createBranch('mainline', None)
+        self.id = history_id
+
+    security = ClassSecurityInfo()
+
+    security.declarePublic('getId')
+    def getId(self):
+        return self.id
+
+    security.declarePrivate('addLogEntry')
+    def addLogEntry(self, version_id, action, path=None, message=''):
+        """Add a new log entry associated with this version history."""
+        entry = LogEntry(version_id, action, path, message)
+        self._eventLog.addEntry(entry)
+
+    security.declarePrivate('getLogEntries')
+    def getLogEntries(self):
+        """Return a sequence of the log entries for this version history."""
+        return self._eventLog.getEntries()
+
+    security.declarePrivate('getLabels')
+    def getLabels(self):
+        return self._labels.keys()
+
+    security.declarePrivate('labelVersion')
+    def labelVersion(self, version_id, label, force=0):
+        """Associate a particular version in a version history with the
+           given label, removing any existing association with that label
+           if force is true, or raising an error if force is false and
+           an association with the given label already exists."""
+        current = self._labels.get(label)
+        if current is not None:
+            if current == version_id:
+                return
+            if not force:
+                raise VersionControlError(
+                    'The label %s is already associated with a version.' % (
+                     label
+                    ))
+            del self._labels[label]
+        self._labels[label] = version_id
+
+    security.declarePrivate('createBranch')
+    def createBranch(self, branch_id, version_id):
+        """Create a new branch associated with the given branch_id. The
+           new branch is rooted at the version named by version_id."""
+        if self._branches.has_key(branch_id):
+            raise VersionControlError(
+                'Activity already exists: %s' % branch_id
                 )
+        branch = BranchInfo(branch_id, version_id)
+        self._branches[branch_id] = branch
+        return branch
+
+    security.declarePrivate('createVersion')
+    def createVersion(self, object, branch_id):
+        """Create a new version in the line of descent named by the given
+           branch_id, returning the newly created version object."""
+        branch = self._branches.get(branch_id)
+        if branch is None:
+            branch = self.createBranch(branch_id, None)
+        if branch.name != 'mainline':
+            version_id = '%s.%d' % (branch.name, len(branch) + 1)
+        else:
+            version_id = '%d' % (len(branch) + 1)
+        version = ZopeVersion(version_id, object)
+        version._p_jar = self._p_jar
+        version.saveState(object)
+
+        # Update the  predecessor, successor and branch relationships.
+        # This is something of a hedge against the future. Versions will
+        # always know enough to reconstruct their lineage without the help
+        # of optimized data structures, which will make it easier to change
+        # internals in the future if we need to.
+        latest = branch.latest()
+        if latest is not None:
+            last = self._versions[latest]
+            last.next = last.next + (version_id,)
+            version.prev = latest
+
+        # If the branch is not the mainline, store the branch name in the
+        # version. Versions have 'mainline' as the default class attribute
+        # which is the common case and saves a minor bit of storage space.
+        if branch.name != 'mainline':
+            version.branch = branch.name
+
+        branch.append(version)
+        self._versions[version_id] = version
+        return version.__of__(self)
+
+    security.declarePrivate('hasVersionId')
+    def hasVersionId(self, version_id):
+        """Return true if history contains a version with the given id."""
+        return self._versions.has_key(version_id)
+
+    security.declarePrivate('isLatestVersion')
+    def isLatestVersion(self, version_id, branch_id):
+        """Return true if version id is the latest in its branch."""
+        branch = self._branches[branch_id]
+        return version_id == branch.latest()
+
+    security.declarePrivate('getLatestVersion')
+    def getLatestVersion(self, branch_id):
+        """Return the latest version object within the given branch, or
+           None if the branch contains no versions."""
+        branch = self._branches[branch_id]
+        version = self._versions[branch.latest()]
+        return version.__of__(self)
+
+    security.declarePrivate('findBranchId')
+    def findBranchId(self, version_id):
+        """Given a version id, return the id of the branch of the version.
+           Note that we cheat, since we can find this out from the id."""
+        parts = version_id.split('.')
+        if len(parts) > 1:
+            return parts[-2]
+        return 'mainline'
+
+    security.declarePrivate('getVersionById')
+    def getVersionById(self, version_id):
+        """Return the version object named by the given version id, or
+           raise a VersionControlError if the version is not found."""
+        version = self._versions.get(version_id)
+        if version is None:
+            raise VersionControlError(
+                'Unknown version id: %s' % version_id
+                )
+        return version.__of__(self)
 
-    def getDescription(self):
-        return getattr(self, '_description', '')
-
-    def setDescription(self, description):
-        self._description = description
+    security.declarePrivate('getVersionByLabel')
+    def getVersionByLabel(self, label):
+        """Return the version associated with the given label, or None
+           if no version matches the given label."""
+        version_id = self._labels.get(label)
+        version = self._versions.get(version_id)
+        if version is None:
+            return None
+        return version.__of__(self)
+
+    security.declarePrivate('getVersionByDate')
+    def getVersionByDate(self, branch_id, timestamp):
+        """Return the last version committed in the given branch on or
+           before the given time value. The timestamp should be a float
+           (time.time format) value in UTC."""
+        branch = self._branches[branch_id]
+        tvalue = int(timestamp / 60.0)
+        while 1:
+            # Try to find a version with a commit date <= the given time
+            # using the timestamp index in the branch information.
+            if branch.m_order:
+                try:
+                    match = branch.m_date.maxKey(tvalue)
+                    match = branch.m_order[branch.m_date[match]]
+                    return self._versions[match].__of__(self)
+                except ValueError:
+                    pass
+
+            # If we've run out of lineage without finding a version with
+            # a commit date <= the given timestamp, we return None. It is
+            # up to the caller to decide what to do in this situation.
+            if branch.root is None:
+                return None
+            
+            # If the branch has a root (a version in another branch), then
+            # we check the root and do it again with the ancestor branch.
+            rootver = self._versions[branch.root]
+            if int(rootver.date_created / 60.0) < tvalue:
+                return rootver.__of__(self)
+            branch = self._branches[rootver.branch]
+
+    security.declarePrivate('getVersionIds')
+    def getVersionIds(self, branch_id=None):
+        """Return a sequence of version ids for the versions in this
+           version history. If a branch_id is given, only version ids
+           from that branch will be returned. Note that the sequence
+           of ids returned does not include the id of the branch root."""
+        if branch_id is not None:
+            return self._branches[branch_id].versionIds()
+        return self._versions.keys()
 
-    def nextVersionId(self, object):
-        # xxx - fix this
-        next_id = getattr(self, '_next_id', 0)
-        self._next_id = next_id + 1
-        return'1.%d' % self._next_id
-
-    def lastVersionId(self):
-        ids = self.objectIds()
-        ids.sort()
-        return ids[-1]
-
-    def createVersion(self, object):
-        ver_id = self.nextVersionId(object)
-        version = Version(ver_id, object, self)
-        self._setObject(ver_id, version)
-        return self.getVersion(ver_id)
-
-    def getVersionIds(self):
-        """ """
-        return self.objectIds()
-
-    def getVersions(self):
-        """Return a sequence containing the versions of the resource."""
-        # make this a tricky sequence! :)
-        return self.objectValues()
-
-    def getVersion(self, id):
-        """ """
-        return self._getOb(id, None)
-
-    def delVersion(self, id):
-        """ """
-        self._delObject(id)
-
-
-    def registerCheckout(self, activity, version_id, path, username):
-        """ """
-        if self.co_state.has_key(activity):
-            raise ValueError, 'Already checked out in activity!'
-
-        co_record = CheckoutRecord(activity, version_id, path, username)
-        self.co_state[activity] = co_record
-
-    def unregisterCheckout(self, activity):
-        """ """
-        if self.co_state.has_key(activity):
-            del self.co_state[activity]
-        return
-
-    def getCheckoutRecords(self):
-        """ """
-        return self.co_state.values()
-
-    def getCurrentCheckout(self, activity):
-        return self.co_state.get(activity)
-
-    def getAllMessageEntries(self):
-        """ """
-        entries = {}
-        for item in self.getVersions():
-            for message in item.getMessageEntries():
-                entries[message.getDateTime()] = message
-        keys = entries.keys()
-        keys.sort()
-        keys.reverse()
-        result = []
-        for key in keys:
-            result.append(entries[key])
-        return result
+InitializeClass(VersionHistory)
 
 
-InitializeClass(VersionHistory)
+class BranchInfo(Implicit, Persistent):
+    """A utility class to hold branch (line-of-descent) information. It
+       maintains the name of the branch, the version id of the root of
+       the branch and indices to allow for efficient lookups."""
+
+    def __init__(self, name, root):
+        # m_order maintains a newest-first mapping of int -> version id.
+        # m_date maintains a mapping of a packed date (int # of minutes
+        # since the epoch) to a lookup key in m_order. The two structures
+        # are separate because we only support minute precision for date
+        # lookups (and multiple versions could be added in a minute).
+        self.date_created = time.time()
+        self.m_order = IOBTree()
+        self.m_date = IIBTree()
+        self.name = name
+        self.root = root
+
+    def append(self, version):
+        """Append a version to the branch information. Note that this
+           does not store the actual version, but metadata about the
+           version to support ordering and date lookups."""
+        if len(self.m_order):
+            key = self.m_order.minKey() - 1
+        else: key = sys.maxint
+        self.m_order[key] = version.id
+        timestamp = int(version.date_created / 60.0)
+        self.m_date[timestamp] = key
+
+    def versionIds(self):
+        """Return a newest-first sequence of version ids in the branch."""
+        return self.m_order.values()
+
+    def latest(self):
+        """Return the version id of the latest version in the branch."""
+        mapping = self.m_order
+        if not len(mapping):
+            return self.root
+        return mapping[mapping.keys()[0]]
 
+    def __len__(self):
+        return len(self.m_order)
 
+InitializeClass(BranchInfo)


=== Products/ZopeVersionControl/VersionSupport.py 1.1.1.1 => 1.2 ===


=== Products/ZopeVersionControl/ZopeRepository.py 1.1.1.1 => 1.2 ===


=== Products/ZopeVersionControl/ZopeVersion.py 1.1.1.1 => 1.2 ===


=== Products/ZopeVersionControl/ZopeVersionHistory.py 1.1.1.1 => 1.2 ===


=== Products/ZopeVersionControl/__init__.py 1.1 => 1.2 ===
-
-__version__='$Revision$'[11:-2]
-
-import Repository, OFS, App
-
-def initialize(context):
-
-    context.registerClass(
-        instance_class = Repository.Repository,
-        meta_type      = 'Repository',
-        permission     = 'Add Repositories',
-        constructors   = Repository.constructors,
-        icon           = 'www/Repository.gif'
-      )
-
-    context.registerHelp()
-    context.registerHelpTitle('Zope Help')
-
-    registerIcon('VersionHistory.gif')
-    registerIcon('Version.gif')
-
-
-def registerIcon(filename):
-    setattr(OFS.misc_.misc_.ZopeVersionControl, filename, 
-            App.ImageFile.ImageFile('www/%s' % filename, globals())
-            )
-
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+# 
+##############################################################################
+
+__version__='$Revision$'[11:-2]
+
+import ZopeRepository, OFS, App, Globals
+
+
+def initialize(context):
+
+    context.registerClass(
+        instance_class = ZopeRepository.ZopeRepository,
+        meta_type      = 'Repository',
+        permission     = 'Add Repositories',
+        constructors   = ZopeRepository.constructors,
+        icon           = 'www/Repository.gif'
+      )
+
+    context.registerHelp()
+    context.registerHelpTitle('Zope Help')
+
+    registerIcon('VersionHistory.gif')
+    registerIcon('Version.gif')
+
+
+    # Hackery - don't try this at home, kids. :) This is temporary for
+    # testing purposes only.
+    from VersionSupport import VersionSupport
+    import OFS.SimpleItem, App.Management
+
+    method = App.Management.Tabs.filtered_manage_options
+    def filtered_manage_options(self, REQUEST=None, method = method,
+                                options = VersionSupport.manage_options):
+        result = method(self, REQUEST)
+        for item in result:
+            if item.get('label') == 'Version Control':
+                return result
+        for option in options:
+            result.append(option)
+        return result
+    App.Management.Tabs.filtered_manage_options = filtered_manage_options
+
+    for _class in (OFS.SimpleItem.Item, OFS.SimpleItem.Item_w__name__):
+        dict = _class.__dict__
+        for name, value in VersionSupport.__dict__.items():
+            if name != 'manage_options':
+                dict[name] = value
+        Globals.InitializeClass(_class)
+
+
+def registerIcon(filename):
+    setattr(OFS.misc_.misc_.ZopeVersionControl, filename, 
+            App.ImageFile.ImageFile('www/%s' % filename, globals())
+            )

=== Removed File Products/ZopeVersionControl/notes.py ===