[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