[Checkins] SVN: z3c.vcsync/trunk/src/z3c/vcsync/svn.txt Massive rewrite for readability.

Martijn Faassen faassen at infrae.com
Mon Apr 21 13:57:34 EDT 2008


Log message for revision 85557:
  Massive rewrite for readability.
  

Changed:
  U   z3c.vcsync/trunk/src/z3c/vcsync/svn.txt

-=-
Modified: z3c.vcsync/trunk/src/z3c/vcsync/svn.txt
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/svn.txt	2008-04-21 17:53:56 UTC (rev 85556)
+++ z3c.vcsync/trunk/src/z3c/vcsync/svn.txt	2008-04-21 17:57:34 UTC (rev 85557)
@@ -1,20 +1,26 @@
 SVN integration
 ===============
 
-z3c.vcsync can work with SVN as a backend. Here we test the SVN
-backend in particular.
+z3c.vcsync can work with SVN as a backend. At the time of writing,
+this is the only version control backend that is supported. Here we
+show how to integrate your aplication with SVN.
 
-Let's grok this package first::
+The tree to synchronize
+-----------------------
 
-  >>> import grok.testing
-  >>> grok.testing.grok('z3c.vcsync')
+Content objects need to track the revision number after which it was
+last changed, so that later we can find all objects that have
+changed. In a real application we would typically track this in some
+content object (such as the application root), but here we will just
+track it globally::
 
-We will track the current SVN revision number globally, starting at
-0::
-
   >>> last_revision_nr = 0
 
-Let's define a simple item::
+An item contains some payload data, and maintains the SVN revision
+after which it was changed. In a real program you would typically
+maintain the revision number of objects by using an annotation and
+listening to ``IObjectModifiedEvent``, but we will use a property
+here::
 
   >>> class Item(object):
   ...   def __init__(self, payload):
@@ -25,8 +31,75 @@
   ...     self._payload = value
   ...     self.revision_nr = last_revision_nr
   ...   payload = property(_get_payload, _set_payload)
+
+We also have a ``Container`` class, set up before this test
+started. It is a class that implements enough of the dictionary API
+and implements the ``IContainer`` interface. A normal Zope 3 folder or
+Grok container will work. Let's now set up the tree::
+
+  >>> data = Container()
+  >>> data.__name__ = 'root'
+  >>> data['foo'] = Item(payload=1)
+  >>> data['bar'] = Item(payload=2)
+  >>> data['sub'] = Container()
+  >>> data['sub']['qux'] = Item(payload=3)
+
+The tree is represented by a special ``IState`` object, which allows
+the synchronizer to ask which objects have changed (or been added)
+since the last synchronization (when ``last_revision_nr`` is updated),
+and which objects have been removed (or moved from their original
+location). In a real application you could implement this using an
+index on the revision nr, so that they can be looked up quickly. Event
+handlers for ``IObjectMovedEvent`` and ``IObjectRemovedEvent`` can be
+used to track which objects were removed. Here we will use a simpler,
+less efficient, implementation that goes through the entire tree to
+find changes::
+
+  >>> from zope.interface import implements
+  >>> from z3c.vcsync.interfaces import IState
+  >>> class TestState(object):
+  ...     implements(IState)
+  ...     def __init__(self, root):
+  ...         self.root = root
+  ...     def removed(self, revision_nr):
+  ...         return []
+  ...     def objects(self, revision_nr):
+  ...         for container in self._containers(revision_nr):
+  ...             for value in container.values():
+  ...                 if isinstance(value, Container):
+  ...                     continue
+  ...                 if value.revision_nr >= revision_nr:
+  ...                     yield value
+  ...     def _containers(self, revision_nr):
+  ...         return self._containers_helper(self.root)
+  ...     def _containers_helper(self, container):
+  ...         yield container
+  ...         for obj in container.values():
+  ...             if not isinstance(obj, Container):
+  ...                 continue
+  ...             for sub_container in self._containers_helper(obj):
+  ...                 yield sub_container
+
+Now that we have an implementation of ``IState`` that works for our
+state, let's create our ``state`` object::
+
+  >>> state = TestState(data)
+
+Reading from and writing to the filesystem
+------------------------------------------
+
+To integrate with the synchronization machinery, we need a way to dump
+a Python object to the filesystem (to an SVN working copy), and to
+parse it back to an object again.
+
+Let's grok this package first, as it provides some of the required
+infrastructure::
+
+  >>> import grok.testing
+  >>> grok.testing.grok('z3c.vcsync')
   
-For the item w define a serializer of an item to the filesystem::
+We need to provide a serializer for the Item class that takes an item
+and writes it to the filesystem::
 
   >>> import grok
   >>> from z3c.vcsync.interfaces import ISerializer
@@ -39,8 +112,8 @@
   ...     def name(self):
   ...         return self.context.__name__ + '.test'
 
-We also define a parser to load an object from the filesystem into
-Python again::
+We also need to provide a parser to load an object from the filesystem
+back into Python, overwriting the previously existing object::
 
   >>> from z3c.vcsync.interfaces import IParser
   >>> class ItemParser(grok.GlobalUtility):
@@ -49,7 +122,9 @@
   ...   def __call__(self, object, path):
   ...      object.payload = int(path.read())
 
-We define a way to create new items as they appear on the filesystem::
+Sometimes there is no previously existing object in the Python tree,
+and we need to add it. To do this we implement a factory (where we use
+the parser for the real work)::
 
   >>> from z3c.vcsync.interfaces import IVcFactory
   >>> from zope import component
@@ -62,8 +137,11 @@
   ...       parser(item, path)
   ...       return item
 
-We grok those components::
+Both parser and factory are registered per extension, in this case
+``.test``. This is the name of the utility.
 
+We register these components::
+
   >>> grok.testing.grok_component('ItemSerializer', ItemSerializer)
   True
   >>> grok.testing.grok_component('ItemParser', ItemParser)
@@ -71,7 +149,9 @@
   >>> grok.testing.grok_component('ItemFactory', ItemFactory)
   True
 
-We also need a parser and factory for setting up containers::
+We also need a parser and factory for containers, registered for the
+empty extension (thus no special utility name). These can be very
+simple::
 
   >>> class ContainerParser(grok.GlobalUtility):
   ...     grok.provides(IParser)
@@ -88,9 +168,12 @@
   >>> grok.testing.grok_component('ContainerFactory', ContainerFactory)
   True
 
-We create a test SVN repository now and create a svn path to a
-checkout::
+Setting up the SVN repository
+-----------------------------
 
+Now we need an SVN repository to synchronize with. We create a test
+SVN repository now and create a svn path to a checkout::
+
   >>> repo, wc = svn_repo_wc()
 
 We can now initialize the ``SvnCheckout`` object with the SVN path to
@@ -99,60 +182,34 @@
   >>> from z3c.vcsync.svn import SvnCheckout
   >>> checkout = SvnCheckout(wc)
 
-Now that we have the SVN end set up, we'll set up the state of the
-Python objects that we want to synchronize with SVN::
+Constructing the synchronizer
+-----------------------------
 
-  >>> data = Container()
-  >>> data.__name__ = 'root'
-  >>> data['foo'] = Item(payload=1)
-  >>> data['bar'] = Item(payload=2)
-  >>> data['sub'] = Container()
-  >>> data['sub']['qux'] = Item(payload=3)
+Now that we have the checkout and the state, we can set up a synchronizer::
 
-We need to set up a state object for this content::
-
-  >>> class TestState(object):
-  ...     def __init__(self, root):
-  ...         self.root = root
-  ...     def removed(self, revision_nr):
-  ...         return []
-  ...     def objects(self, revision_nr):
-  ...         for container in self._containers(revision_nr):
-  ...             for value in container.values():
-  ...                 if isinstance(value, Container):
-  ...                     continue
-  ...                 if value.revision_nr >= revision_nr:
-  ...                     yield value
-  ...     def _containers(self, revision_nr):
-  ...         return self._containers_helper(self.root)
-  ...     def _containers_helper(self, container):
-  ...         yield container
-  ...         for obj in container.values():
-  ...             if not isinstance(obj, Container):
-  ...                 continue
-  ...             for sub_container in self._containers_helper(obj):
-  ...                 yield sub_container
-
-  >>> state = TestState(data)
-
-Let's now construct a synchronizer from the SVN checkout and the state::
-
   >>> from z3c.vcsync import Synchronizer
   >>> s = Synchronizer(checkout, state)
 
+Synchronization
+---------------
+
 We'll synchronize for the first time now::
 
   >>> info = s.sync(last_revision_nr, "synchronize")
 
-Let's introduce some helper functions that help us present the paths
-in a more readable form, relative to the base::
+We will now examine the SVN checkout to see whether the
+synchronization was success.
 
+We first introduce some helper functions that help us present the
+paths in a more readable form, relative to the base of the checkout::
+
   >>> def pretty_path(path):
   ...     return path.relto(wc)
   >>> def pretty_paths(paths):
   ...     return sorted([pretty_path(path) for path in paths])
 
-The state of the python objects can now be found in the working copy::
+We see that the structure containers and items  has been translated to the
+same structure of directories and ``.test`` files on the filesystem::
 
   >>> pretty_paths(wc.listdir())
   ['root']
@@ -161,4 +218,13 @@
   >>> pretty_paths(wc.join('root').join('sub').listdir())
   ['root/sub/qux.test']
 
+The ``.test`` files have the payload data we expect::
+  
+  >>> print wc.join('root').join('foo.test').read()
+  1
+  >>> print wc.join('root').join('bar.test').read()
+  2
+  >>> print wc.join('root').join('sub').join('qux.test').read()
+  3
+
         
\ No newline at end of file



More information about the Checkins mailing list