[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/versioncontrol/ Added queryVersionInfo.

Jim Fulton jim at zope.com
Mon May 23 13:28:29 EDT 2005


Log message for revision 30483:
  Added queryVersionInfo.
  
  Changed "activity" to "branch" in various places -- mostly
  documentation.  Renamed makeActivity to makeBranch and added doctest.
  
  Fixed a bug in copying locations.  The bug caused the entire object
  tree to be copied when making a revision of an object.
  
  Reformed README.txt to use indentation of 4 for examples.
  

Changed:
  U   Zope3/trunk/src/zope/app/versioncontrol/README.txt
  U   Zope3/trunk/src/zope/app/versioncontrol/history.py
  U   Zope3/trunk/src/zope/app/versioncontrol/interfaces.py
  U   Zope3/trunk/src/zope/app/versioncontrol/repository.py
  U   Zope3/trunk/src/zope/app/versioncontrol/tests.py
  U   Zope3/trunk/src/zope/app/versioncontrol/version.py

-=-
Modified: Zope3/trunk/src/zope/app/versioncontrol/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/versioncontrol/README.txt	2005-05-23 17:27:46 UTC (rev 30482)
+++ Zope3/trunk/src/zope/app/versioncontrol/README.txt	2005-05-23 17:27:59 UTC (rev 30483)
@@ -20,172 +20,185 @@
 implementation of INonVersionedData has to deal with these for objects that
 contain their own location information.
 
-  >>> import persistent
-  >>> import zope.interface
-  >>> import zope.app.annotation.attribute
-  >>> import zope.app.annotation.interfaces
-  >>> import zope.app.traversing.interfaces
-  >>> from zope.app.versioncontrol import interfaces
+    >>> import persistent
+    >>> from zope import component, interface
+    >>> import zope.app.annotation.attribute
+    >>> import zope.app.annotation.interfaces
+    >>> import zope.app.traversing.interfaces
+    >>> from zope.app.versioncontrol import interfaces
 
-  >>> marker = object()
+    >>> marker = object()
 
-  >>> class Sample(persistent.Persistent):
-  ...     zope.interface.implements(
-  ...         interfaces.IVersionable,
-  ...         interfaces.INonVersionedData,
-  ...         zope.app.annotation.interfaces.IAttributeAnnotatable,
-  ...         zope.app.traversing.interfaces.IPhysicallyLocatable,
-  ...         )
-  ...   
-  ...     # Methods defined by INonVersionedData
-  ...     # This is a trivial implementation; using INonVersionedData
-  ...     # is discussed later.
-  ...
-  ...     def listNonVersionedObjects(self):
-  ...         return ()
-  ...
-  ...     def removeNonVersionedData(self):
-  ...         if "__name__" in self.__dict__:
-  ...             del self.__name__
-  ...         if "__parent__" in self.__dict__:
-  ...             del self.__parent__
-  ...
-  ...     def getNonVersionedData(self):
-  ...         return (getattr(self, "__name__", marker),
-  ...                 getattr(self, "__parent__", marker))
-  ...
-  ...     def restoreNonVersionedData(self, data):
-  ...         name, parent = data
-  ...         if name is not marker:
-  ...             self.__name__ = name
-  ...         if parent is not marker:
-  ...             self.__parent__ = parent
-  ...
-  ...     # Method from IPhysicallyLocatable that is actually used:
-  ...     def getPath(self):
-  ...         return '/' + self.__name__
+    >>> class Sample(persistent.Persistent):
+    ...     interface.implements(
+    ...         interfaces.IVersionable,
+    ...         interfaces.INonVersionedData,
+    ...         zope.app.annotation.interfaces.IAttributeAnnotatable,
+    ...         zope.app.traversing.interfaces.IPhysicallyLocatable,
+    ...         )
+    ...   
+    ...     # Methods defined by INonVersionedData
+    ...     # This is a trivial implementation; using INonVersionedData
+    ...     # is discussed later.
+    ...
+    ...     def listNonVersionedObjects(self):
+    ...         return ()
+    ...
+    ...     def removeNonVersionedData(self):
+    ...         if "__name__" in self.__dict__:
+    ...             del self.__name__
+    ...         if "__parent__" in self.__dict__:
+    ...             del self.__parent__
+    ...
+    ...     def getNonVersionedData(self):
+    ...         return (getattr(self, "__name__", marker),
+    ...                 getattr(self, "__parent__", marker))
+    ...
+    ...     def restoreNonVersionedData(self, data):
+    ...         name, parent = data
+    ...         if name is not marker:
+    ...             self.__name__ = name
+    ...         if parent is not marker:
+    ...             self.__parent__ = parent
+    ...
+    ...     # Method from IPhysicallyLocatable that is actually used:
+    ...     def getPath(self):
+    ...         return '/' + self.__name__
 
-  >>> from zope.app.testing import ztapi
-  >>> ztapi.provideAdapter(zope.app.annotation.interfaces.IAttributeAnnotatable,
-  ...                      zope.app.annotation.interfaces.IAnnotations,
-  ...                      zope.app.annotation.attribute.AttributeAnnotations)
+    >>> component.provideAdapter(
+    ...     zope.app.annotation.attribute.AttributeAnnotations)
 
 Now we need to create a database with an instance of our sample object to work
 with:
 
-  >>> from ZODB.tests import util
-  >>> db = util.DB()
-  >>> connection = db.open()
-  >>> root = connection.root()
+    >>> from ZODB.tests import util
+    >>> import transaction
 
-  >>> samp = Sample()
-  >>> samp.__name__ = "samp"
-  >>> root["samp"] = samp
-  >>> util.commit()
+    >>> db = util.DB()
+    >>> connection = db.open()
+    >>> root = connection.root()
 
+    >>> samp = Sample()
+    >>> samp.__name__ = "samp"
+    >>> root["samp"] = samp
+    >>> transaction.commit()
+
 Some basic queries may be asked of objects without using an instance of
-`IVersionControl`.  In particular, we can determine whether an object can be
+`IRepository`.  In particular, we can determine whether an object can be
 managed by version control by checking for the `IVersionable` interface:
 
-  >>> interfaces.IVersionable.providedBy(samp)
-  True
-  >>> interfaces.IVersionable.providedBy(42)
-  False
+    >>> interfaces.IVersionable.providedBy(samp)
+    True
+    >>> interfaces.IVersionable.providedBy(42)
+    False
 
 We can also determine whether an object is actually under version
 control using the `IVersioned` interface:
 
-  >>> interfaces.IVersioned.providedBy(samp)
-  False
-  >>> interfaces.IVersioned.providedBy(42)
-  False
+    >>> interfaces.IVersioned.providedBy(samp)
+    False
+    >>> interfaces.IVersioned.providedBy(42)
+    False
 
 Placing an object under version control requires an instance of an
-`IVersionControl` object.  This package provides an implementation of this
+`IRepository` object.  This package provides an implementation of this
 interface on the `Repository` class (from
-`zope.app.versioncontrol.repository`).  Only the `IVersionControl` instance is
+`zope.app.versioncontrol.repository`).  Only the `IRepository` instance is
 responsible for providing version control operations; an instance should never
 be asked to perform operations directly.
 
-  >>> import zope.app.versioncontrol.repository
-  >>> import zope.interface.verify
+    >>> import zope.app.versioncontrol.repository
+    >>> import zope.interface.verify
 
-  >>> repository = zope.app.versioncontrol.repository.Repository()
-  >>> zope.interface.verify.verifyObject(
-  ...     interfaces.IVersionControl,
-  ...	  repository)
-  True
+    >>> repository = zope.app.versioncontrol.repository.Repository()
+    >>> zope.interface.verify.verifyObject(
+    ...     interfaces.IRepository,
+    ...	  repository)
+    True
 
 In order to actually use version control, there must be an
 interaction.  This is needed to allow the framework to determine the
 user making changes.  Let's set up an interaction now. First we need a
 principal. For our purposes, a principal just needs to have an id:
 
-  >>> class FauxPrincipal:
-  ...    def __init__(self, id):
-  ...        self.id = id
-  >>> principal = FauxPrincipal('bob')
+    >>> class FauxPrincipal:
+    ...    def __init__(self, id):
+    ...        self.id = id
+    >>> principal = FauxPrincipal('bob')
 
 Then we need to define an participation for the principal in the
 interaction:
 
-  >>> class FauxParticipation:
-  ...     interaction=None
-  ...     def __init__(self, principal):
-  ...         self.principal = principal
-  >>> participation = FauxParticipation(principal)
+    >>> class FauxParticipation:
+    ...     interaction=None
+    ...     def __init__(self, principal):
+    ...         self.principal = principal
+    >>> participation = FauxParticipation(principal)
 
 Finally, we can create the interaction:
 
-  >>> import zope.security.management
-  >>> zope.security.management.newInteraction(participation)
+    >>> import zope.security.management
+    >>> zope.security.management.newInteraction(participation)
 
 Now, let's put an object under version control and verify that we can
 determine that fact by checking against the interface:
 
-  >>> repository.applyVersionControl(samp)
-  >>> interfaces.IVersioned.providedBy(samp)
-  True
-  >>> util.commit()
+    >>> repository.applyVersionControl(samp)
+    >>> interfaces.IVersioned.providedBy(samp)
+    True
+    >>> transaction.commit()
 
 Once an object is under version control, it's possible to get an
 information object that provides some interesting bits of data:
 
-  >>> info = repository.getVersionInfo(samp)
-  >>> type(info.history_id)
-  <type 'str'>
+    >>> info = repository.getVersionInfo(samp)
+    >>> type(info.history_id)
+    <type 'str'>
 
 It's an error to ask for the version info for an object which isn't
 under revision control:
 
-  >>> samp2 = Sample()
-  >>> repository.getVersionInfo(samp2)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: Object is not under version control.
+    >>> samp2 = Sample()
+    >>> repository.getVersionInfo(samp2)
+    Traceback (most recent call last):
+      ...
+    VersionControlError: Object is not under version control.
 
-  >>> repository.getVersionInfo(42)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: Object is not under version control.
+    >>> repository.getVersionInfo(42)
+    Traceback (most recent call last):
+      ...
+    VersionControlError: Object is not under version control.
 
+The function `queryVersionInfo` returns a default value if an object
+isn't under version control:
+
+    >>> print repository.queryVersionInfo(samp2)
+    None
+
+    >>> print repository.queryVersionInfo(samp2, 0)
+    0
+
+    >>> repository.queryVersionInfo(samp).version_id
+    '1'
+
+
 You can retrieve a version of an object using the `.history_id` and a
 version selector.  A version selector is a string that specifies which
 available version to return.  The value `mainline` tells the
-`IVersionControl` to return the most recent version on the main branch.
+`IRepository` to return the most recent version on the main branch.
 
-  >>> ob = repository.getVersionOfResource(info.history_id, 'mainline')
-  >>> type(ob)
-  <class 'zope.app.versioncontrol.README.Sample'>
-  >>> ob is samp
-  False
-  >>> root["ob"] = ob
-  >>> ob.__name__ = "ob"
-  >>> ob_info = repository.getVersionInfo(ob)
-  >>> ob_info.history_id == info.history_id
-  True
-  >>> ob_info is info
-  False
+    >>> ob = repository.getVersionOfResource(info.history_id, 'mainline')
+    >>> type(ob)
+    <class 'zope.app.versioncontrol.README.Sample'>
+    >>> ob is samp
+    False
+    >>> root["ob"] = ob
+    >>> ob.__name__ = "ob"
+    >>> ob_info = repository.getVersionInfo(ob)
+    >>> ob_info.history_id == info.history_id
+    True
+    >>> ob_info is info
+    False
 
 Once version control has been applied, the object can be "checked
 out", modified and "checked in" to create new versions.  For many
@@ -195,58 +208,58 @@
 Let's save some information about the current version of the object so
 we can see that it changes:
 
-  >>> orig_history_id = info.history_id
-  >>> orig_version_id = info.version_id
+    >>> orig_history_id = info.history_id
+    >>> orig_version_id = info.version_id
 
 Now, let's check out the object and add an attribute:
 
-  >>> repository.checkoutResource(ob)
-  >>> ob.value = 42
-  >>> repository.checkinResource(ob)
-  >>> util.commit()
+    >>> repository.checkoutResource(ob)
+    >>> ob.value = 42
+    >>> repository.checkinResource(ob)
+    >>> transaction.commit()
 
 We can now compare information about the updated version with the
 original information:
 
-  >>> newinfo = repository.getVersionInfo(ob)
-  >>> newinfo.history_id == orig_history_id
-  True
-  >>> newinfo.version_id != orig_version_id
-  True
+    >>> newinfo = repository.getVersionInfo(ob)
+    >>> newinfo.history_id == orig_history_id
+    True
+    >>> newinfo.version_id != orig_version_id
+    True
 
 Retrieving both versions of the object allows use to see the
 differences between the two:
 
-  >>> o1 = repository.getVersionOfResource(orig_history_id,
-  ...                                      orig_version_id)
-  >>> o2 = repository.getVersionOfResource(orig_history_id,
-  ...                                      newinfo.version_id)
-  >>> o1.value
-  Traceback (most recent call last):
-    ...
-  AttributeError: 'Sample' object has no attribute 'value'
-  >>> o2.value
-  42
+    >>> o1 = repository.getVersionOfResource(orig_history_id,
+    ...                                      orig_version_id)
+    >>> o2 = repository.getVersionOfResource(orig_history_id,
+    ...                                      newinfo.version_id)
+    >>> o1.value
+    Traceback (most recent call last):
+      ...
+    AttributeError: 'Sample' object has no attribute 'value'
+    >>> o2.value
+    42
 
 We can determine whether an object that's been checked out is
 up-to-date with the most recent version from the repository:
 
-  >>> repository.isResourceUpToDate(o1)
-  False
-  >>> repository.isResourceUpToDate(o2)
-  True
+    >>> repository.isResourceUpToDate(o1)
+    False
+    >>> repository.isResourceUpToDate(o2)
+    True
 
 Asking whether a non-versioned object is up-to-date produces an error:
 
-  >>> repository.isResourceUpToDate(42)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: Object is not under version control.
+    >>> repository.isResourceUpToDate(42)
+    Traceback (most recent call last):
+      ...
+    VersionControlError: Object is not under version control.
 
-  >>> repository.isResourceUpToDate(samp2)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: Object is not under version control.
+    >>> repository.isResourceUpToDate(samp2)
+    Traceback (most recent call last):
+      ...
+    VersionControlError: Object is not under version control.
 
 It's also possible to check whether an object has been changed since
 it was checked out.  Since we're only looking at changes that have
@@ -254,99 +267,99 @@
 committing it without checking a new version into the version control
 repository.
 
-  >>> repository.updateResource(samp)
-  >>> repository.checkoutResource(samp)
-  >>> util.commit()
+    >>> repository.updateResource(samp)
+    >>> repository.checkoutResource(samp)
+    >>> transaction.commit()
 
-  >>> repository.isResourceChanged(samp)
-  False
-  >>> samp.value += 1
-  >>> util.commit()
+    >>> repository.isResourceChanged(samp)
+    False
+    >>> samp.value += 1
+    >>> transaction.commit()
 
 We can now see that the object has been changed since it was last
 checked in::
 
-  >>> repository.isResourceChanged(samp)
-  True
+    >>> repository.isResourceChanged(samp)
+    True
 
 Checking in the object and commiting shows that we can now veryify
 that the object is considered up-to-date after a subsequent checkout.
 We'll also demonstrate that `checkinResource()` can take an optional
 message argument; we'll see later how this can be used.
 
-  >>> repository.checkinResource(samp, 'sample checkin')
-  >>> util.commit()
+    >>> repository.checkinResource(samp, 'sample checkin')
+    >>> transaction.commit()
 
-  >>> repository.checkoutResource(samp)
-  >>> util.commit()
+    >>> repository.checkoutResource(samp)
+    >>> transaction.commit()
 
-  >>> repository.isResourceUpToDate(samp)
-  True
-  >>> repository.isResourceChanged(samp)
-  False
-  >>> repository.getVersionInfo(samp).version_id
-  '3'
+    >>> repository.isResourceUpToDate(samp)
+    True
+    >>> repository.isResourceChanged(samp)
+    False
+    >>> repository.getVersionInfo(samp).version_id
+    '3'
 
 It's also possible to use version control to discard changes that
 haven't been checked in yet, even though they've been committed to the
 database for the "working copy".  This is done using the
-`uncheckoutResource()` method of the `IVersionControl` object:
+`uncheckoutResource()` method of the `IRepository` object:
 
-  >>> samp.value
-  43
-  >>> samp.value += 2
-  >>> samp.value
-  45
-  >>> util.commit()
-  >>> repository.isResourceChanged(samp)
-  True
-  >>> repository.uncheckoutResource(samp)
-  >>> util.commit()
+    >>> samp.value
+    43
+    >>> samp.value += 2
+    >>> samp.value
+    45
+    >>> transaction.commit()
+    >>> repository.isResourceChanged(samp)
+    True
+    >>> repository.uncheckoutResource(samp)
+    >>> transaction.commit()
 
-  >>> samp.value
-  43
-  >>> repository.isResourceChanged(samp)
-  False
-  >>> version_id = repository.getVersionInfo(samp).version_id
-  >>> version_id
-  '3'
+    >>> samp.value
+    43
+    >>> repository.isResourceChanged(samp)
+    False
+    >>> version_id = repository.getVersionInfo(samp).version_id
+    >>> version_id
+    '3'
 
 An old copy of an object can be "updated" to the most recent version
 of an object:
 
-  >>> ob = repository.getVersionOfResource(orig_history_id, orig_version_id)
-  >>> ob.__name__ = "foo"
-  >>> repository.isResourceUpToDate(ob)
-  False
-  >>> repository.getVersionInfo(ob).version_id
-  '1'
-  >>> repository.updateResource(ob, version_id)
-  >>> repository.getVersionInfo(ob).version_id == version_id
-  True
-  >>> ob.value
-  43
+    >>> ob = repository.getVersionOfResource(orig_history_id, orig_version_id)
+    >>> ob.__name__ = "foo"
+    >>> repository.isResourceUpToDate(ob)
+    False
+    >>> repository.getVersionInfo(ob).version_id
+    '1'
+    >>> repository.updateResource(ob, version_id)
+    >>> repository.getVersionInfo(ob).version_id == version_id
+    True
+    >>> ob.value
+    43
 
 It's possible to get a list of all the versions of a particular object
 from the repository as well.  We can use any copy of the object to
 make the request:
 
-  >>> list(repository.getVersionIds(samp))
-  ['1', '2', '3']
-  >>> list(repository.getVersionIds(ob))
-  ['1', '2', '3']
+    >>> list(repository.getVersionIds(samp))
+    ['1', '2', '3']
+    >>> list(repository.getVersionIds(ob))
+    ['1', '2', '3']
 
 No version information is available for objects that have not had
 version control applied::
 
-  >>> repository.getVersionIds(samp2)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: Object is not under version control.
+    >>> repository.getVersionIds(samp2)
+    Traceback (most recent call last):
+      ...
+    VersionControlError: Object is not under version control.
 
-  >>> repository.getVersionIds(42)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: Object is not under version control.
+    >>> repository.getVersionIds(42)
+    Traceback (most recent call last):
+      ...
+    VersionControlError: Object is not under version control.
 
 
 Naming specific revisions
@@ -360,50 +373,53 @@
 Labels can be assigned to objects that are checked into the
 repository:
 
-  >>> repository.labelResource(samp, 'my-first-label')
-  >>> repository.labelResource(samp, 'my-second-label')
+    >>> repository.labelResource(samp, 'my-first-label')
+    >>> repository.labelResource(samp, 'my-second-label')
 
 The list of labels assigned to some version of an object can be
 retrieved using the repository's `getLabelsForResource()` method::
 
-  >>> list(repository.getLabelsForResource(samp))
-  ['my-first-label', 'my-second-label']
+    >>> list(repository.getLabelsForResource(samp))
+    ['my-first-label', 'my-second-label']
 
 The labels can be retrieved using any object that refers to the same
 line of history in the repository:
 
-  >>> list(repository.getLabelsForResource(ob))
-  ['my-first-label', 'my-second-label']
+    >>> list(repository.getLabelsForResource(ob))
+    ['my-first-label', 'my-second-label']
 
 Labels can be used to retrieve specific versions of an object from the
 repository:
 
-  >>> repository.getVersionInfo(samp).version_id
-  '3'
-  >>> ob = repository.getVersionOfResource(orig_history_id, 'my-first-label')
-  >>> repository.getVersionInfo(ob).version_id
-  '3'
+    >>> repository.getVersionInfo(samp).version_id
+    '3'
+    >>> ob = repository.getVersionOfResource(orig_history_id, 'my-first-label')
+    >>> repository.getVersionInfo(ob).version_id
+    '3'
 
 It's also possible to move a label from one version to another, but
 only when this is specifically indicated as allowed:
 
-  >>> ob = repository.getVersionOfResource(orig_history_id, orig_version_id)
-  >>> ob.__name__ = "bar"
-  >>> repository.labelResource(ob, 'my-second-label')
-  Traceback (most recent call last):
-    ...
-  VersionControlError: The label my-second-label is already associated with a version.
-  >>> repository.labelResource(ob, 'my-second-label', force=True)
+    >>> ob = repository.getVersionOfResource(orig_history_id, orig_version_id)
+    >>> ob.__name__ = "bar"
+    >>> repository.labelResource(ob, 'my-second-label')
+    ... # doctest: +NORMALIZE_WHITESPACE
+    Traceback (most recent call last):
+      ...
+    VersionControlError: The label my-second-label is already associated 
+    with a version.
 
+    >>> repository.labelResource(ob, 'my-second-label', force=True)
+
 Labels can also be used to update an object to a specific version:
 
-  >>> repository.getVersionInfo(ob).version_id
-  '1'
-  >>> repository.updateResource(ob, 'my-first-label')
-  >>> repository.getVersionInfo(ob).version_id
-  '3'
-  >>> ob.value
-  43
+    >>> repository.getVersionInfo(ob).version_id
+    '1'
+    >>> repository.updateResource(ob, 'my-first-label')
+    >>> repository.getVersionInfo(ob).version_id
+    '3'
+    >>> ob.value
+    43
 
 
 Sticky settings
@@ -413,28 +429,31 @@
 an object is updated to a specific date, determination of whether
 it is up-to-date or changed is based on the version it was updated to.
 
-  >>> repository.updateResource(samp, orig_version_id)
-  >>> util.commit()
+    >>> repository.updateResource(samp, orig_version_id)
+    >>> transaction.commit()
 
-  >>> samp.value
-  Traceback (most recent call last):
-    ...
-  AttributeError: 'Sample' object has no attribute 'value'
+    >>> samp.value
+    Traceback (most recent call last):
+      ...
+    AttributeError: 'Sample' object has no attribute 'value'
 
-  >>> repository.getVersionInfo(samp).version_id == orig_version_id
-  True
-  >>> repository.isResourceChanged(samp)
-  False
-  >>> repository.isResourceUpToDate(samp)
-  False
+    >>> repository.getVersionInfo(samp).version_id == orig_version_id
+    True
+    >>> repository.isResourceChanged(samp)
+    False
+    >>> repository.isResourceUpToDate(samp)
+    False
 
 The `isResourceUpToDate()` method indicates whether
 `checkoutResource()` will succeed or raise an exception::
 
-  >>> repository.checkoutResource(samp)
-  Traceback (most recent call last):
-    ...
-  VersionControlError: The selected resource has been updated to a particular version, label or date. The resource must be updated to the mainline or a branch before it may be checked out.
+    >>> repository.checkoutResource(samp)
+    ... # doctest: +NORMALIZE_WHITESPACE
+    Traceback (most recent call last):
+      ...
+    VersionControlError: The selected resource has been updated to a
+    particular version, label or date. The resource must be updated to
+    the mainline or a branch before it may be checked out.
 
 
 TODO: Figure out how to write date-based tests.  Perhaps the
@@ -445,67 +464,67 @@
 Examining the change history
 ----------------------------
 
-  >>> actions = {
-  ...	  interfaces.ACTION_CHECKIN: "Check in",
-  ...	  interfaces.ACTION_CHECKOUT: "Check out",
-  ...	  interfaces.ACTION_UNCHECKOUT: "Uncheckout",
-  ...	  interfaces.ACTION_UPDATE: "Update",
-  ... }
+    >>> actions = {
+    ...	  interfaces.ACTION_CHECKIN: "Check in",
+    ...	  interfaces.ACTION_CHECKOUT: "Check out",
+    ...	  interfaces.ACTION_UNCHECKOUT: "Uncheckout",
+    ...	  interfaces.ACTION_UPDATE: "Update",
+    ... }
 
-  >>> entries = repository.getLogEntries(samp)
-  >>> for entry in entries:
-  ...	  print "Action:", actions[entry.action]
-  ...	  print "Version:", entry.version_id
-  ...	  print "Path:", entry.path
-  ...	  if entry.message:
-  ...	      print "Message:", entry.message
-  ...	  print "--"
-  Action: Update
-  Version: 1
-  Path: /samp
-  --
-  Action: Update
-  Version: 3
-  Path: /bar
-  --
-  Action: Update
-  Version: 3
-  Path: /foo
-  --
-  Action: Uncheckout
-  Version: 3
-  Path: /samp
-  --
-  Action: Check out
-  Version: 3
-  Path: /samp
-  --
-  Action: Check in
-  Version: 3
-  Path: /samp
-  Message: sample checkin
-  --
-  Action: Check out
-  Version: 2
-  Path: /samp
-  --
-  Action: Update
-  Version: 2
-  Path: /samp
-  --
-  Action: Check in
-  Version: 2
-  Path: /ob
-  --
-  Action: Check out
-  Version: 1
-  Path: /ob
-  --
-  Action: Check in
-  Version: 1
-  Path: /samp
-  Message: Initial checkin.
-  --
+    >>> entries = repository.getLogEntries(samp)
+    >>> for entry in entries:
+    ...	  print "Action:", actions[entry.action]
+    ...	  print "Version:", entry.version_id
+    ...	  print "Path:", entry.path
+    ...	  if entry.message:
+    ...	      print "Message:", entry.message
+    ...	  print "--"
+    Action: Update
+    Version: 1
+    Path: /samp
+    --
+    Action: Update
+    Version: 3
+    Path: /bar
+    --
+    Action: Update
+    Version: 3
+    Path: /foo
+    --
+    Action: Uncheckout
+    Version: 3
+    Path: /samp
+    --
+    Action: Check out
+    Version: 3
+    Path: /samp
+    --
+    Action: Check in
+    Version: 3
+    Path: /samp
+    Message: sample checkin
+    --
+    Action: Check out
+    Version: 2
+    Path: /samp
+    --
+    Action: Update
+    Version: 2
+    Path: /samp
+    --
+    Action: Check in
+    Version: 2
+    Path: /ob
+    --
+    Action: Check out
+    Version: 1
+    Path: /ob
+    --
+    Action: Check in
+    Version: 1
+    Path: /samp
+    Message: Initial checkin.
+    --
 
 Note that the entry with the checkin entry for version 3 includes the
 comment passed to `checkinResource()`.
@@ -513,19 +532,49 @@
 The version history also contains the principal id related to each
 entry::
 
-  >>> entries[0].user_id
-  'bob'
+    >>> entries[0].user_id
+    'bob'
 
-
 Branches
 --------
 
-The implementation contains some support for branching, but it's not
-fully exposed in the interface at this time.  It's too early to
-document at this time.  Branches will interact heavily with
-"stickiness".
+We may wish to create parallel versions of objects.  For example, we 
+might want to create a version of content from an old version. We can
+do this by making a branch. To make a branch, we need to get an object
+for the version we want to branch from.  Here's we'll get an object
+for revision 2:
 
+    >>> obranch = repository.getVersionOfResource(orig_history_id, '2')
+    >>> obranch.__name__ = "obranch"
+    >>> root["obranch"] = obranch
+    >>> repository.getVersionInfo(obranch).version_id
+    '2'
 
+Now we can use this object to make a branch:
+
+    >>> repository.makeBranch(obranch, '2-branch')
+
+To create a new version on the branch, we first have to check out the
+object on the branch:
+
+    >>> repository.updateResource(obranch, '2-branch')
+    >>> repository.checkoutResource(obranch)
+
+    >>> repository.getVersionInfo(obranch).version_id
+    '2'
+
+    >>> obranch.value
+    42
+
+    >>> obranch.value = 100
+
+    >>> repository.checkinResource(obranch)
+    >>> transaction.commit()
+
+    >>> repository.getVersionInfo(obranch).version_id
+    '2-branch.1'
+
+
 Supporting separately versioned subobjects
 ------------------------------------------
 
@@ -542,64 +591,64 @@
 `Sample` class above, we're still careful to consider the values for
 `__name__` and `__parent__` to be non-versioned:
 
-  >>> def ignored_item(name, ob):
-  ...     """Return True for non-versioned items."""
-  ...     return (interfaces.IVersionable.providedBy(ob)
-  ...             or name.startswith("bob")
-  ...             or (name in ["__name__", "__parent__"]))
+    >>> def ignored_item(name, ob):
+    ...     """Return True for non-versioned items."""
+    ...     return (interfaces.IVersionable.providedBy(ob)
+    ...             or name.startswith("bob")
+    ...             or (name in ["__name__", "__parent__"]))
 
-  >>> class SampleContainer(Sample):
-  ...   
-  ...     # Methods defined by INonVersionedData
-  ...     def listNonVersionedObjects(self):
-  ...         return [ob for (name, ob) in self.__dict__.items()
-  ...                 if ignored_item(name, ob)
-  ...                 ]
-  ...
-  ...     def removeNonVersionedData(self):
-  ...         for name, value in self.__dict__.items():
-  ...             if ignored_item(name, value):
-  ...                 del self.__dict__[name]
-  ...
-  ...     def getNonVersionedData(self):
-  ...         return [(name, ob) for (name, ob) in self.__dict__.items()
-  ...                 if ignored_item(name, ob)
-  ...                 ]
-  ...
-  ...     def restoreNonVersionedData(self, data):
-  ...         for name, value in data:
-  ...             if name not in self.__dict__:
-  ...                 self.__dict__[name] = value
+    >>> class SampleContainer(Sample):
+    ...   
+    ...     # Methods defined by INonVersionedData
+    ...     def listNonVersionedObjects(self):
+    ...         return [ob for (name, ob) in self.__dict__.items()
+    ...                 if ignored_item(name, ob)
+    ...                 ]
+    ...
+    ...     def removeNonVersionedData(self):
+    ...         for name, value in self.__dict__.items():
+    ...             if ignored_item(name, value):
+    ...                 del self.__dict__[name]
+    ...
+    ...     def getNonVersionedData(self):
+    ...         return [(name, ob) for (name, ob) in self.__dict__.items()
+    ...                 if ignored_item(name, ob)
+    ...                 ]
+    ...
+    ...     def restoreNonVersionedData(self, data):
+    ...         for name, value in data:
+    ...             if name not in self.__dict__:
+    ...                 self.__dict__[name] = value
 
 Let's take a look at how the `INonVersionedData` interface is used.
 We'll start by creating an instance of our sample container and
 storing it in the database:
 
-  >>> box = SampleContainer()
-  >>> box.__name__ = "box"
-  >>> root[box.__name__] = box
+    >>> box = SampleContainer()
+    >>> box.__name__ = "box"
+    >>> root[box.__name__] = box
 
 We'll also add some contained objects:
 
-  >>> box.aList = [1, 2, 3]
+    >>> box.aList = [1, 2, 3]
 
-  >>> samp1 = Sample()
-  >>> samp1.__name__ = "box/samp1"
-  >>> samp1.__parent__ = box
-  >>> box.samp1 = samp1
+    >>> samp1 = Sample()
+    >>> samp1.__name__ = "box/samp1"
+    >>> samp1.__parent__ = box
+    >>> box.samp1 = samp1
 
-  >>> box.bob_list = [3, 2, 1]
+    >>> box.bob_list = [3, 2, 1]
 
-  >>> bob_samp = Sample()
-  >>> bob_samp.__name__ = "box/bob_samp"
-  >>> bob_samp.__parent__ = box
-  >>> box.bob_samp = bob_samp
+    >>> bob_samp = Sample()
+    >>> bob_samp.__name__ = "box/bob_samp"
+    >>> bob_samp.__parent__ = box
+    >>> box.bob_samp = bob_samp
 
-  >>> util.commit()
+    >>> transaction.commit()
 
 Let's apply version control to the container:
 
-  >>> repository.applyVersionControl(box)
+    >>> repository.applyVersionControl(box)
 
 We'll start by showing some basics of how the INonVersionedData
 interface is used.  
@@ -628,32 +677,32 @@
 This is fairly simple to see in an example.  Step 1 is to save the
 non-versioned data:
 
-  >>> saved = box.getNonVersionedData()
+    >>> saved = box.getNonVersionedData()
 
 While the version control framework treats this as an opaque value, we
 can take a closer look to make sure we got what we expected (since we
 know our implementation):
 
-  >>> names = [name for (name, ob) in saved]
-  >>> names.sort()
-  >>> names
-  ['__name__', 'bob_list', 'bob_samp', 'samp1']
+    >>> names = [name for (name, ob) in saved]
+    >>> names.sort()
+    >>> names
+    ['__name__', 'bob_list', 'bob_samp', 'samp1']
 
 Step 2 is to remove the data from the object:
 
-  >>> box.removeNonVersionedData()
+    >>> box.removeNonVersionedData()
 
 The non-versioned data should no longer be part of the object:
 
-  >>> box.bob_samp
-  Traceback (most recent call last):
-    ...
-  AttributeError: 'SampleContainer' object has no attribute 'bob_samp'
+    >>> box.bob_samp
+    Traceback (most recent call last):
+      ...
+    AttributeError: 'SampleContainer' object has no attribute 'bob_samp'
 
 While versioned data should remain present:
 
-  >>> box.aList
-  [1, 2, 3]
+    >>> box.aList
+    [1, 2, 3]
 
 At this point, the version control framework will perform any
 appropriate state copies are needed.
@@ -661,15 +710,15 @@
 Once that's done, `restoreNonVersionedData()` will be called with the
 saved data to perform the restore operation:
 
-  >>> box.restoreNonVersionedData(saved)
+    >>> box.restoreNonVersionedData(saved)
 
 We can verify that the restoraion has been performed by checking the
 non-versioned data:
 
-  >>> box.bob_list
-  [3, 2, 1]
-  >>> type(box.samp1)
-  <class 'zope.app.versioncontrol.README.Sample'>
+    >>> box.bob_list
+    [3, 2, 1]
+    >>> type(box.samp1)
+    <class 'zope.app.versioncontrol.README.Sample'>
 
 We can see how this is affects object state by making some changes to
 the container object's versioned and non-versioned data and watching
@@ -678,25 +727,25 @@
 `getVersionOfResource()`.  Let's start by generating some new
 revisions in the repository:
 
-  >>> repository.checkoutResource(box)
-  >>> util.commit()
-  >>> version_id = repository.getVersionInfo(box).version_id
+    >>> repository.checkoutResource(box)
+    >>> transaction.commit()
+    >>> version_id = repository.getVersionInfo(box).version_id
 
-  >>> box.aList.append(4)
-  >>> box.bob_list.append(0)
-  >>> repository.checkinResource(box)
-  >>> util.commit()
+    >>> box.aList.append(4)
+    >>> box.bob_list.append(0)
+    >>> repository.checkinResource(box)
+    >>> transaction.commit()
 
-  >>> box.aList
-  [1, 2, 3, 4]
-  >>> box.bob_list
-  [3, 2, 1, 0]
+    >>> box.aList
+    [1, 2, 3, 4]
+    >>> box.bob_list
+    [3, 2, 1, 0]
 
-  >>> repository.updateResource(box, version_id)
-  >>> box.aList
-  [1, 2, 3]
-  >>> box.bob_list
-  [3, 2, 1, 0]
+    >>> repository.updateResource(box, version_id)
+    >>> box.aList
+    [1, 2, 3]
+    >>> box.bob_list
+    [3, 2, 1, 0]
 
 The list-remaining method of the `INonVersionedData` interface is a
 little different, but remains very tightly tied to the details of the

Modified: Zope3/trunk/src/zope/app/versioncontrol/history.py
===================================================================
--- Zope3/trunk/src/zope/app/versioncontrol/history.py	2005-05-23 17:27:46 UTC (rev 30482)
+++ Zope3/trunk/src/zope/app/versioncontrol/history.py	2005-05-23 17:27:59 UTC (rev 30483)
@@ -82,7 +82,7 @@
            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 already exists: %s' % branch_id
                 )
         branch = BranchInfo(branch_id, version_id)
         branch.__parent__ = self

Modified: Zope3/trunk/src/zope/app/versioncontrol/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/versioncontrol/interfaces.py	2005-05-23 17:27:46 UTC (rev 30482)
+++ Zope3/trunk/src/zope/app/versioncontrol/interfaces.py	2005-05-23 17:27:59 UTC (rev 30483)
@@ -32,7 +32,7 @@
     pass
 
 
-class IVersionControl(zope.interface.Interface):
+class IRepository(zope.interface.Interface):
     """Main API for version control operations.
 
     This interface hides most of the details of version data storage
@@ -54,7 +54,7 @@
     def isResourceUpToDate(object, require_branch=False):
         """
         Returns true if a resource is based on the latest version. Note
-        that the latest version is in the context of any activity (branch).
+        that the latest version is in the context of any branch.
 
         If the require_branch flag is true, this method returns false if
         the resource is updated to a particular version, label, or date.
@@ -130,7 +130,7 @@
         """
         Update the state of the given object to that of a specific version
         of the object. The object must be in the checked-in state to be
-        updated. The selector must be a string (version id, activity id,
+        updated. The selector must be a string (version id, branch id,
         label or date) that is used to select a version from the version
         history.
 
@@ -153,7 +153,7 @@
         Given a version history id and a version selector, return the
         object as of that version. Note that the returned object has no
         acquisition context. The selector must be a string (version id,
-        activity id, label or date) that is used to select a version
+        branch id, label or date) that is used to select a version
         from the version history.
 
         Permission: Use version control
@@ -186,6 +186,12 @@
         Permission: Use version control
         """
 
+    def makeBranch(object, branch_id):
+        """Create a new branch with the given branch id
+
+        The branch is created from the object's version.
+        """
+
 CHECKED_OUT = 0
 CHECKED_IN = 1
 

Modified: Zope3/trunk/src/zope/app/versioncontrol/repository.py
===================================================================
--- Zope3/trunk/src/zope/app/versioncontrol/repository.py	2005-05-23 17:27:46 UTC (rev 30482)
+++ Zope3/trunk/src/zope/app/versioncontrol/repository.py	2005-05-23 17:27:59 UTC (rev 30483)
@@ -33,7 +33,7 @@
 from zope.app.versioncontrol.history import VersionHistory
 from zope.app.versioncontrol.interfaces import VersionControlError
 from zope.app.versioncontrol.interfaces import IVersionable, IVersioned
-from zope.app.versioncontrol.interfaces import IVersionControl
+from zope.app.versioncontrol.interfaces import IRepository
 from zope.app.versioncontrol.interfaces import CHECKED_IN, CHECKED_OUT
 from zope.app.versioncontrol.interfaces import ACTION_CHECKIN, ACTION_CHECKOUT
 from zope.app.versioncontrol.interfaces import ACTION_UNCHECKOUT, ACTION_UPDATE
@@ -48,7 +48,7 @@
     """The repository implementation manages the actual data of versions
     and version histories. It does not handle user interface issues."""
 
-    zope.interface.implements(IVersionControl)
+    zope.interface.implements(IRepository)
 
     def __init__(self):
         # These keep track of symbolic label and branch names that
@@ -142,6 +142,12 @@
         raise VersionControlError(
             "Object is not under version control.")
 
+    def queryVersionInfo(self, object, default=None):
+        if IVersioned.providedBy(object):
+            annotations = IAnnotations(object)
+            return annotations[VERSION_INFO_KEY]
+        return default
+
     def applyVersionControl(self, object, message=None):
         if IVersioned.providedBy(object):
             raise VersionControlError(
@@ -160,7 +166,7 @@
         if parent is None:
             p_info = None
         else:
-            p_info = self.getVersionInfo(parent)
+            p_info = self.queryVersionInfo(parent)
         if p_info is not None:
             sticky = p_info.sticky
             if sticky and sticky[0] == 'B':
@@ -376,7 +382,7 @@
         # Make sure that labels and branch ids do not collide.
         if self._branches.has_key(label) or label == 'mainline':
             raise VersionControlError(
-                'The label value given is already in use as an activity id.'
+                'The label value given is already in use as a branch id.'
                 )
         if not self._labels.has_key(label):
             self._labels[label] = 1
@@ -384,7 +390,7 @@
         history = self.getVersionHistory(info.history_id)
         history.labelVersion(info.version_id, label, force)
 
-    def makeActivity(self, object, branch_id):
+    def makeBranch(self, object, branch_id):
         # Note - this is not part of the official version control API yet.
         # It is here to allow unit testing of the architectural aspects
         # that are already in place to support activities in the future.
@@ -397,7 +403,7 @@
 
         branch_id = branch_id or None
 
-        # Make sure that activity ids and labels do not collide.
+        # Make sure that branch ids and labels do not collide.
         if self._labels.has_key(branch_id) or branch_id == 'mainline':
             raise VersionControlError(
                 'The value given is already in use as a version label.'
@@ -410,11 +416,10 @@
 
         if history._branches.has_key(branch_id):
             raise VersionControlError(
-                'The resource is already associated with the given activity.'
+                'The resource is already associated with the given branch.'
                 )
 
         history.createBranch(branch_id, info.version_id)
-        return object
 
     def getVersionOfResource(self, history_id, selector):
         history = self.getVersionHistory(history_id)

Modified: Zope3/trunk/src/zope/app/versioncontrol/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/versioncontrol/tests.py	2005-05-23 17:27:46 UTC (rev 30482)
+++ Zope3/trunk/src/zope/app/versioncontrol/tests.py	2005-05-23 17:27:59 UTC (rev 30483)
@@ -21,6 +21,8 @@
 from zope.component.tests.placelesssetup import PlacelessSetup
 from zope.testing import doctest, module
 from transaction import abort
+import zope.app.location
+import zope.app.versioncontrol.version
 
 name = 'zope.app.versioncontrol.README'
 
@@ -38,10 +40,39 @@
         db.close()
     ps.tearDown()
 
+
+
+def testLocationSanity_for_cloneByPickle():
+    """\
+cloneByPickle should not go outside a location
+
+    >>> parent = zope.app.location.Location()
+    >>> parent.poison = lambda: None
+    >>> ob = zope.app.location.Location()
+    >>> ob.__parent__ = parent
+    >>> x = zope.app.location.Location()
+    >>> x.poison = lambda: None
+    >>> ob.x = x
+    >>> ob.y = zope.app.location.Location()
+    >>> ob.y.__parent__ = ob
+    >>> clone = zope.app.versioncontrol.version.cloneByPickle(ob)
+    >>> clone.__parent__ is ob.__parent__
+    True
+    >>> clone.x is ob.x
+    True
+    >>> clone.y is ob.y
+    False
+
+"""
+    
+
 def test_suite():
-    return doctest.DocFileSuite('README.txt',
-                                setUp=setUp, tearDown=tearDown,
-                                )
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+                             setUp=setUp, tearDown=tearDown,
+                             ),
+        doctest.DocTestSuite(),
+        ))
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Modified: Zope3/trunk/src/zope/app/versioncontrol/version.py
===================================================================
--- Zope3/trunk/src/zope/app/versioncontrol/version.py	2005-05-23 17:27:46 UTC (rev 30482)
+++ Zope3/trunk/src/zope/app/versioncontrol/version.py	2005-05-23 17:27:59 UTC (rev 30483)
@@ -44,7 +44,9 @@
     def persistent_id(ob):
         if ignore_dict.has_key(id(ob)):
             return 'ignored'
-        if (zope.app.location.ILocation.providedBy(object)
+
+        # XXX obviously no test for this
+        if (zope.app.location.ILocation.providedBy(ob)
             and not zope.app.location.inside(ob, obj)):
             myid = id(ob)
             ids[myid] = ob



More information about the Zope3-Checkins mailing list