[Checkins] SVN: z3c.vcsync/ Initial import.
Martijn Faassen
faassen at infrae.com
Mon Jun 25 13:36:05 EDT 2007
Log message for revision 77071:
Initial import.
Changed:
A z3c.vcsync/
A z3c.vcsync/README.txt
A z3c.vcsync/buildout.cfg
A z3c.vcsync/setup.py
A z3c.vcsync/src/
A z3c.vcsync/src/z3c/
A z3c.vcsync/src/z3c/__init__.py
A z3c.vcsync/src/z3c/vcsync/
A z3c.vcsync/src/z3c/vcsync/README.txt
A z3c.vcsync/src/z3c/vcsync/__init__.py
A z3c.vcsync/src/z3c/vcsync/configure.zcml
A z3c.vcsync/src/z3c/vcsync/interfaces.py
A z3c.vcsync/src/z3c/vcsync/svn.py
A z3c.vcsync/src/z3c/vcsync/tests.py
A z3c.vcsync/src/z3c/vcsync/vc.py
A z3c.vcsync/svn-commit.tmp
-=-
Added: z3c.vcsync/README.txt
===================================================================
--- z3c.vcsync/README.txt (rev 0)
+++ z3c.vcsync/README.txt 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,17 @@
+Takes an arbitrary object and syncs it through SVN.
+
+This means a serialization to text, and a deserialization from text.
+
+The text is stored in a SVN checkout. Newly appeared texts are svn added,
+removed texts are svn removed. svn move and svn copy are not supported. They
+will instead cause a svn delete/svn add combination.
+
+An svn up can be performed. Any conflicting files will be noted and
+can be resolved (automatically?).
+
+An svn up always takes place before an svn commit.
+
+An svn sync from the server will main all files that have changed will
+be updated in the ZODB, and all files that have been deleted will be
+removed in the ZODB. Added files will be added in the ZODB.
+
Added: z3c.vcsync/buildout.cfg
===================================================================
--- z3c.vcsync/buildout.cfg (rev 0)
+++ z3c.vcsync/buildout.cfg 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,15 @@
+[buildout]
+develop = . grok martian
+parts = test devpython
+
+[test]
+recipe = zc.recipe.testrunner
+extra-paths=/home/faassen/buildout/z331-lp/lib/python
+eggs = z3c.vcsync
+
+# installs bin/devpython to do simple interpreter tests
+[devpython]
+recipe = zc.recipe.egg
+interpreter = devpython
+eggs = z3c.vcsync
+
Added: z3c.vcsync/setup.py
===================================================================
--- z3c.vcsync/setup.py (rev 0)
+++ z3c.vcsync/setup.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,19 @@
+from setuptools import setup, find_packages
+import sys, os
+
+setup(name='z3c.vcsync',
+ version='0.1',
+ description="Sync ZODB data with version control system, currently SVN",
+ package_dir={'': 'src'},
+ packages=find_packages('src'),
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=[
+ 'setuptools',
+ 'grok',
+ 'py',
+ ],
+ entry_points="""
+ # -*- Entry points: -*-
+ """,
+ )
Added: z3c.vcsync/src/z3c/__init__.py
===================================================================
--- z3c.vcsync/src/z3c/__init__.py (rev 0)
+++ z3c.vcsync/src/z3c/__init__.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1 @@
+#
Added: z3c.vcsync/src/z3c/vcsync/README.txt
===================================================================
--- z3c.vcsync/src/z3c/vcsync/README.txt (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/README.txt 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,568 @@
+Version Control Synchronization
+===============================
+
+This package contains code that helps with handling synchronization of
+persistent content with a version control system. This can be useful
+in software that needs to be able to work offline. The web application
+runs on a user's laptop that may be away from an internet
+connection. When connected again, the user syncs with a version
+control server, receiving updates that may have been made by others,
+and committing their own changes.
+
+The synchronization sequence is as follows (example given with SVN as
+the version control system):
+
+ 1) save persistent state to svn checkout on the same machine as the
+ Zope application.
+
+ 2) ``svn up``. Subversion merges in changed made by others users
+ that were checked into the svn server.
+
+ 3) Any svn conflicts are automatically resolved.
+
+ 4) reload changes in svn checkout into persistent Python objects
+
+ 5) ``svn commit``.
+
+This is all happening in a single step. It can happen over and over
+again in a reasonably safe manner, as after the synchronization has
+concluded, the state of the persistent objects and that of the local
+SVN checkout will always be perfectly in sync.
+
+SVN difficulties
+----------------
+
+Changing a file into a directory with SVN requires the following
+procedure::
+
+ * svn remove file
+
+ * svn commit file
+
+ * svn up
+
+ * mdkir file
+
+ * svn add file
+
+If during the serialization procedure a file changed into a directory,
+it would require an ``svn up`` to be issued during step 1. This is too
+early. As we see later, we instead ask the application developer to
+avoid this situation altogether.
+
+To start
+--------
+
+Let's first grok this package::
+
+ >>> import grok
+ >>> grok.grok('z3c.vcsync')
+
+Serialization
+-------------
+
+In order to export content to a version control system, it first needs
+to be possible to serialize a content object to a text representation.
+
+For the purposes of this document, we have defined a simple item that
+just carries an integer payload attribute::
+
+ >>> class Item(object):
+ ... def __init__(self, payload):
+ ... self.payload = payload
+ >>> item = Item(payload=1)
+ >>> item.payload
+ 1
+
+We will use an ISerializer adapter to serialize it to a file. Let's
+define the adapter::
+
+ >>> from z3c.vcsync.interfaces import ISerializer
+ >>> class ItemSerializer(grok.Adapter):
+ ... grok.provides(ISerializer)
+ ... grok.context(Item)
+ ... def serialize(self, f):
+ ... f.write(str(self.context.payload))
+ ... f.write('\n')
+ ... def name(self):
+ ... return self.context.__name__ + '.test'
+
+Let's test our adapter::
+
+ >>> from StringIO import StringIO
+ >>> f= StringIO()
+ >>> ItemSerializer(item).serialize(f)
+ >>> f.getvalue()
+ '1\n'
+
+Let's register the adapter::
+
+ >>> grok.grok_component('ItemSerializer', ItemSerializer)
+ True
+
+We can now use the adapter::
+
+ >>> f = StringIO()
+ >>> ISerializer(item).serialize(f)
+ >>> f.getvalue()
+ '1\n'
+
+Export persistent state to version control system checkout
+----------------------------------------------------------
+
+As part of the synchronization procedure we need the ability to export
+persistent python objects to the version control checkout directory in
+the form of files and directories.
+
+Content is assumed to consist of two types of objects:
+
+* containers. These are represented as directories on the filesystem.
+
+* items. These are represented as files on the filesystem. The files
+ will have an extension to indicate the type of item.
+
+Let's imagine we have this object structure consisting of a container
+with some items and sub-containers in it::
+
+ >>> data = Container()
+ >>> data.__name__ = 'root'
+ >>> data['foo'] = Item(payload=1)
+ >>> data['bar'] = Item(payload=2)
+ >>> data['sub'] = Container()
+ >>> data['sub']['qux'] = Item(payload=3)
+
+This object structure has some test payload data::
+
+ >>> data['foo'].payload
+ 1
+ >>> data['sub']['qux'].payload
+ 3
+
+We have a checkout in testpath on the filesystem::
+
+ >>> testpath = create_test_dir()
+ >>> checkout = TestCheckout(testpath)
+
+The object structure can now be saved into that checkout::
+
+ >>> checkout.save(data)
+
+The filesystem should now contain the right objects.
+
+Everything is always saved in a directory called ``root``:
+
+ >>> root = testpath.join('root')
+ >>> root.check(dir=True)
+ True
+
+This root directory should contain the right objects::
+
+ >>> sorted([entry.basename for entry in root.listdir()])
+ ['bar.test', 'foo.test', 'sub']
+
+We expect the right contents in ``bar.test`` and ``foo.test``::
+
+ >>> root.join('bar.test').read()
+ '2\n'
+ >>> root.join('foo.test').read()
+ '1\n'
+
+``sub`` is a container so should be represented as a directory::
+
+ >>> sub_path = root.join('sub')
+ >>> sub_path.check(dir=True)
+ True
+
+ >>> sorted([entry.basename for entry in sub_path.listdir()])
+ ['qux.test']
+
+ >>> sub_path.join('qux.test').read()
+ '3\n'
+
+We know that no existing files or directories were deleted by this save,
+as the checkout was empty before this::
+
+ >>> checkout.deleted_by_save()
+ []
+
+We also know that certain files have been added::
+
+ >>> rel_paths(checkout, checkout.added_by_save())
+ ['/root', '/root/bar.test', '/root/foo.test', '/root/sub',
+ '/root/sub/qux.test']
+
+Modifying an existing checkout
+------------------------------
+
+Now let's assume that the version control checkout is that as
+generated by step 1a). We will bring it to its initial state first::
+
+ >>> checkout.clear()
+
+We will now change some data in the ZODB again to test whether we
+detect additions and deletions (we need to inform the version control
+system about these).
+
+Let's add ``hoi``::
+
+ >>> data['hoi'] = Item(payload=4)
+
+And let's delete ``bar``::
+
+ >>> del data['bar']
+
+Let's save the object structure again to the same checkout::
+
+ >>> checkout.save(data)
+
+The checkout will now know which files were added and deleted during
+the save::
+
+ >>> rel_paths(checkout, checkout.added_by_save())
+ ['/root/hoi.test']
+
+We also know which files got deleted::
+
+ >>> rel_paths(checkout, checkout.deleted_by_save())
+ ['/root/bar.test']
+
+Modifying an existing checkout, some edge cases
+-----------------------------------------------
+
+Let's take our checkout as one fully synched up again::
+
+ >>> checkout.clear()
+
+The ZODB has changed again. Item 'hoi' has changed from an item into
+a container::
+
+ >>> data['hoi'] = Container()
+
+We put some things into the container::
+
+ >>> data['hoi']['something'] = Item(payload=15)
+
+We export again into the existing checkout (which still has 'hoi' as a
+file)::
+
+ >>> checkout.save(data)
+
+The file ``hoi.test`` should now be removed::
+
+ >>> rel_paths(checkout, checkout.deleted_by_save())
+ ['/root/hoi.test']
+
+And the directory ``hoi`` should now be added::
+
+ >>> rel_paths(checkout, checkout.added_by_save())
+ ['/root/hoi', '/root/hoi/something.test']
+
+Let's check the filesystem state::
+
+ >>> sorted([entry.basename for entry in root.listdir()])
+ ['foo.test', 'hoi', 'sub']
+
+We expect ``hoi`` to contain ``something.test``::
+
+ >>> hoi_path = root.join('hoi')
+ >>> something_path = hoi_path.join('something.test')
+ >>> something_path.read()
+ '15\n'
+
+Let's now consider the checkout synched up entirely again::
+
+ >>> checkout.clear()
+
+Let's now change the ZODB again and change the ``hoi`` container back
+into a file::
+
+ >>> data['hoi'] = Item(payload=16)
+ >>> checkout.save(data)
+
+The ``hoi`` directory (and everything in it, implicitly) is now
+deleted::
+
+ >>> rel_paths(checkout, checkout.deleted_by_save())
+ ['/root/hoi']
+
+We have added ``hoi.test``::
+
+ >>> rel_paths(checkout, checkout.added_by_save())
+ ['/root/hoi.test']
+
+We expect to see a ``hoi.test`` but no ``hoi`` directory anymore::
+
+ >>> sorted([entry.basename for entry in root.listdir()])
+ ['foo.test', 'hoi.test', 'sub']
+
+Let's be synched-up again::
+
+ >>> checkout.clear()
+
+Note: creating a container with the name ``hoi.test`` (using the
+``.test`` postfix) will lead to trouble now, as we already have a file
+``hoi.test``. ``svn`` doesn't allow a single-step replace of a file
+with a directory - as expressed earlier, an ``svn up`` would need to
+be issued first, but this would be too early in the process. Solving
+this problem is quite involved. Instead, we require the application to
+avoid creating any directories with a postfix in use by items. The
+following should be forbidden::
+
+ data['hoi.test'] = Container()
+
+loading a checkout state into python objects
+--------------------------------------------
+
+Let's load the currentfilesystem layout into python objects. Factories
+are registered as utilities for the different things we can encounter
+on the filesystem. Let's look at items first. A factory is registered
+for the ``.test`` extension::
+
+ >>> from z3c.vcsync.interfaces import IVcFactory
+ >>> class ItemFactory(grok.GlobalUtility):
+ ... grok.provides(IVcFactory)
+ ... grok.name('.test')
+ ... def __call__(self, checkout, path):
+ ... payload = int(path.read())
+ ... return Item(payload)
+ >>> grok.grok_component('ItemFactory', ItemFactory)
+ True
+
+Now for containers. They are registered for an empty extension. They
+are also required to use VcLoad to load their contents::
+
+ >>> from z3c.vcsync.interfaces import IVcLoad
+ >>> class ContainerFactory(grok.GlobalUtility):
+ ... grok.provides(IVcFactory)
+ ... def __call__(self, checkout, path):
+ ... container = Container()
+ ... IVcLoad(container).load(checkout, path)
+ ... return container
+ >>> grok.grok_component('ContainerFactory', ContainerFactory)
+ True
+
+We have registered enough. Let's load up the contents from the
+filesystem now::
+
+ >>> container2 = Container()
+ >>> container2.__name__ = 'root'
+ >>> checkout.load(container2)
+ >>> sorted(container2.keys())
+ ['foo', 'hoi', 'sub']
+
+We check whether the items contains the right information::
+
+ >>> isinstance(container2['foo'], Item)
+ True
+ >>> container2['foo'].payload
+ 1
+ >>> isinstance(container2['hoi'], Item)
+ True
+ >>> container2['hoi'].payload
+ 16
+ >>> isinstance(container2['sub'], Container)
+ True
+ >>> sorted(container2['sub'].keys())
+ ['qux']
+ >>> container2['sub']['qux'].payload
+ 3
+
+version control changes a file
+------------------------------
+
+Now we synchronize our checkout by synchronizing the checkout with the
+central coordinating server (or shared branch in case of a distributed
+version control system). We do a ``checkout.up()`` that causes the
+text in a file to be modified.
+
+The special checkout class we use for example purposes will call
+``update_function`` during an update. This function should then
+simulate what might happen during a version control system ``update``
+operation. Let's define one here that modifies text in a file::
+
+ >>> hoi_path = root.join('hoi.test')
+ >>> def update_function():
+ ... hoi_path.write('200\n')
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+We will reload the checkout into Python objects::
+
+ >>> checkout.load(container2)
+
+We expect the ``hoi`` object to be modified::
+
+ >>> container2['hoi'].payload
+ 200
+
+
+version control adds a file
+---------------------------
+
+We update our checkout again and cause a file to be added::
+
+ >>> hallo = root.join('hallo.test').ensure()
+ >>> def update_function():
+ ... hallo.write('300\n')
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+We will reload the checkout into Python objects again::
+
+ >>> checkout.load(container2)
+
+We expect there to be a new object ``hallo``::
+
+ >>> 'hallo' in container2.keys()
+ True
+
+version control removes a file
+------------------------------
+
+We update our checkout and cause a file to be removed::
+
+ >>> def update_function():
+ ... root.join('hallo.test').remove()
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+We will reload the checkout into Python objects::
+
+ >>> checkout.load(container2)
+
+We expect the object ``hallo`` to be gone again::
+
+ >>> 'hallo' in container2.keys()
+ False
+
+version control adds a directory
+--------------------------------
+
+We update our checkout and cause a directory (with a file inside) to be
+added::
+
+ >>> newdir_path = root.join('newdir')
+ >>> def update_function():
+ ... newdir_path.ensure(dir=True)
+ ... newfile_path = newdir_path.join('newfile.test').ensure()
+ ... newfile_path.write('400\n')
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+Reloading this will cause a new container to exist::
+
+ >>> checkout.load(container2)
+
+ >>> 'newdir' in container2.keys()
+ True
+ >>> isinstance(container2['newdir'], Container)
+ True
+ >>> container2['newdir']['newfile'].payload
+ 400
+
+version control removes a directory
+-----------------------------------
+
+We update our checkout once again and cause a directory to be removed::
+
+ >>> def update_function():
+ ... newdir_path.remove()
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+ >>> checkout.load(container2)
+
+Reloading this will cause the new container to be gone again::
+
+ >>> checkout.load(container2)
+ >>> 'newdir' in container2.keys()
+ False
+
+version control changes a file into a directory
+-----------------------------------------------
+
+Some sequence of actions by other users has caused a name that previously
+referred to a file to now refer to a directory::
+
+ >>> hoi_path2 = root.join('hoi')
+ >>> def update_function():
+ ... hoi_path.remove()
+ ... hoi_path2.ensure(dir=True)
+ ... some_path = hoi_path2.join('some.test').ensure(file=True)
+ ... some_path.write('1000\n')
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+Reloading this will cause a new container to be there instead of the file::
+
+ >>> checkout.load(container2)
+ >>> isinstance(container2['hoi'], Container)
+ True
+ >>> container2['hoi']['some'].payload
+ 1000
+
+version control changes a directory into a file
+-----------------------------------------------
+
+Some sequence of actions by other users has caused a name that
+previously referred to a directory to now refer to a file::
+
+ >>> def update_function():
+ ... hoi_path2.remove()
+ ... hoi_path = root.join('hoi.test').ensure()
+ ... hoi_path.write('2000\n')
+ >>> checkout.update_function = update_function
+
+ >>> checkout.up()
+
+Reloading this will cause a new item to be there instead of the
+container::
+
+ >>> checkout.load(container2)
+ >>> isinstance(container2['hoi'], Item)
+ True
+ >>> container2['hoi'].payload
+ 2000
+
+Complete synchronization
+------------------------
+
+Let's now exercise the ``sync`` method directly. First we'll modify
+the payload of the ``hoi`` item::
+
+ >>> container2['hoi'].payload = 3000
+
+Next, we willl add a new ``alpha`` file to the checkout when we do an
+``up()``, so again we simulate the actions of our version control system::
+
+ >>> def update_function():
+ ... alpha_path = root.join('alpha.test').ensure()
+ ... alpha_path.write('4000\n')
+ >>> checkout.update_function = update_function
+
+Now we'll synchronize with the memory structure::
+
+ >>> checkout.sync(container2)
+
+We expect the checkout to reflect the changed state of the ``hoi`` object::
+
+ >>> root.join('hoi.test').read()
+ '3000\n'
+
+We also expect the database to reflect the creation of the new
+``alpha`` object::
+
+ >>> container2['alpha'].payload
+ 4000
+
+
+
+* are the right events being generated
+
+* check save of unicode names
Added: z3c.vcsync/src/z3c/vcsync/__init__.py
===================================================================
--- z3c.vcsync/src/z3c/vcsync/__init__.py (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/__init__.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,2 @@
+#
+
Added: z3c.vcsync/src/z3c/vcsync/configure.zcml
===================================================================
--- z3c.vcsync/src/z3c/vcsync/configure.zcml (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/configure.zcml 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,3 @@
+<configure xmlns="http://namespaces.zope.org/grok">
+ <grok package="." />
+</configure>
Added: z3c.vcsync/src/z3c/vcsync/interfaces.py
===================================================================
--- z3c.vcsync/src/z3c/vcsync/interfaces.py (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/interfaces.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,104 @@
+from zope.interface import Interface
+
+class IVcDump(Interface):
+ def save(checkout, path):
+ """Save context object to path in checkout.
+
+ checkout - an ICheckout object
+ path - a py.path object referring to directory to save in.
+
+ This might result in the creation of a new file or directory under
+ the path, or alternatively to the modification of an existing file
+ or directory.
+
+ Returns the path just created.
+ """
+
+class IVcLoad(Interface):
+ def load(checkout, path):
+ """Load data in checkout's path into context object.
+ """
+
+class ISerializer(Interface):
+ def serialize(f):
+ """Serialize object to file object.
+ """
+
+class IParser(Interface):
+ def parse(f):
+ """Parse object and load it into new object, returning it.
+ """
+
+ def parse_into(f):
+ """Parse object and replace current object's content with it.
+ """
+
+class IVcFactory(Interface):
+ def __call__():
+ """Create new instance of object.
+ """
+
+class IModified(Interface):
+ def modified_since(dt):
+ """Return True if the object has been modified since dt.
+ """
+
+ def update():
+ """Update modification datetime.
+ """
+
+class ICheckout(Interface):
+ """A version control system checkout.
+ """
+ def sync(object, message=''):
+ """Synchronize persistent Python state with remove version control.
+ """
+
+ def save(object):
+ """Save root object to filesystem location of checkout.
+ """
+
+ def load(object):
+ """Load filesystem state of checkout into object.
+ """
+
+ def up():
+ """Update the checkout with the state of the version control system.
+ """
+
+ def resolve():
+ """Resolve all conflicts that may be in the checkout.
+ """
+
+ def commit(message):
+ """Commit checkout to version control system.
+ """
+
+ def add(path):
+ """Add a file to the checkout (so it gets committed).
+ """
+
+ def delete(path):
+ """Delete a file from the checkout (so the delete gets committed).
+ """
+
+ def added_by_save():
+ """A list of files and directories that have been added by a save.
+ """
+
+ def deleted_by_save():
+ """A list of files and directories that have been deleted by a save.
+ """
+
+ def added_by_up():
+ """A list of those files that have been added after 'up'.
+ """
+
+ def deleted_by_up():
+ """A list of those files that have been deleted after 'up'.
+ """
+
+ def modified_by_up():
+ """A list of those files that have been modified after 'up'.
+ """
+
Added: z3c.vcsync/src/z3c/vcsync/svn.py
===================================================================
--- z3c.vcsync/src/z3c/vcsync/svn.py (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/svn.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,24 @@
+import py
+
+from z3c.vcsync.vc import CheckoutBase
+
+class SvnCheckout(CheckoutBase):
+ """A checkout for SVN.
+
+ This is a simplistic implementation. Advanced implementations
+ might check what has been changed in an SVN update and change the
+ load() method to only bother to load changed (or added or removed)
+ data. Similarly save() could be adjusted to only save changed
+ data.
+
+ It is assumed to be initialized with py.path.svnwc
+ """
+
+ def up(self):
+ self.path.update()
+
+ def resolve(self):
+ pass
+
+ def commit(self, message):
+ self.path.commit(message)
Added: z3c.vcsync/src/z3c/vcsync/tests.py
===================================================================
--- z3c.vcsync/src/z3c/vcsync/tests.py (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/tests.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,105 @@
+import unittest
+from zope.testing import doctest, cleanup
+import tempfile
+import shutil
+import py.path
+from datetime import datetime
+import grok
+
+from zope.interface import implements, Interface
+from zope.app.container.interfaces import IContainer
+
+from z3c.vcsync.interfaces import ISerializer, IVcDump, IVcLoad, IVcFactory, IModified
+from z3c.vcsync import vc
+
+class TestCheckout(vc.CheckoutBase):
+ def __init__(self, path):
+ super(TestCheckout, self).__init__(path)
+ self.update_function = None
+
+ def up(self):
+ # call update_function which will modify the checkout as might
+ # happen in a version control update. Function should be set before
+ # calling this in testing code
+ self.update_function()
+
+ def resolve(self):
+ pass
+
+ def commit(self, message):
+ pass
+
+class Container(object):
+ implements(IContainer)
+
+ def __init__(self):
+ self.__name__ = None
+ self._data = {}
+
+ def keys(self):
+ return self._data.keys()
+
+ def values(self):
+ return self._data.values()
+
+ def __setitem__(self, name, value):
+ self._data[name] = value
+ value.__name__ = name
+
+ def __getitem__(self, name):
+ return self._data[name]
+
+ def __delitem__(self, name):
+ del self._data[name]
+
+
+## class ItemModified(grok.Adapter):
+## grok.context(Item)
+## grok.implements(IModified)
+
+## def modified_since(self, dt):
+## return dt is None or self.context._modified is None or self.context._modified > dt
+
+## def update(self):
+## self.context._modified = datetime.now()
+
+def setUpZope(test):
+ pass
+
+def cleanUpZope(test):
+ for dirpath in _test_dirs:
+ shutil.rmtree(dirpath)
+ cleanup.cleanUp()
+
+_test_dirs = []
+
+def create_test_dir():
+ dirpath = tempfile.mkdtemp()
+ _test_dirs.append(dirpath)
+ return py.path.local(dirpath)
+
+def rel_paths(checkout, paths):
+ result = []
+ start = len(checkout.path.strpath)
+ for path in paths:
+ result.append(path.strpath[start:])
+ return sorted(result)
+
+
+globs = {'Container': Container,
+ 'TestCheckout': TestCheckout,
+ 'create_test_dir': create_test_dir,
+ 'rel_paths': rel_paths}
+
+def test_suite():
+ suite = unittest.TestSuite([
+ doctest.DocFileSuite(
+ 'README.txt',
+ setUp=setUpZope,
+ tearDown=cleanUpZope,
+ globs=globs,
+ optionflags=doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE)])
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Added: z3c.vcsync/src/z3c/vcsync/vc.py
===================================================================
--- z3c.vcsync/src/z3c/vcsync/vc.py (rev 0)
+++ z3c.vcsync/src/z3c/vcsync/vc.py 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,150 @@
+import os
+
+from zope.interface import Interface
+from zope.component import queryUtility
+from zope.app.container.interfaces import IContainer
+
+from z3c.vcsync.interfaces import (IVcDump, IVcLoad,
+ ISerializer, IVcFactory,
+ IModified, ICheckout)
+
+import grok
+
+class VcDump(grok.Adapter):
+ """General VcDump for arbitrary objects.
+
+ Can be overridden for specific objects (such as containers).
+ """
+ grok.provides(IVcDump)
+ grok.context(Interface)
+
+ def save(self, checkout, path):
+ serializer = ISerializer(self.context)
+ path = path.join(serializer.name())
+ if not path.check():
+ checkout.add(path)
+ path.ensure()
+ f = path.open('w')
+ serializer.serialize(f)
+ f.close()
+ return path
+
+class ContainerVcDump(grok.Adapter):
+ grok.provides(IVcDump)
+ grok.context(IContainer)
+
+ def save(self, checkout, path):
+ path = path.join(self.context.__name__)
+ if not path.check():
+ checkout.add(path)
+ path.ensure(dir=True)
+ added_paths = []
+ for value in self.context.values():
+ added_paths.append(IVcDump(value).save(checkout, path))
+ # remove any paths not there anymore
+ for existing_path in path.listdir():
+ if existing_path not in added_paths:
+ checkout.delete(existing_path)
+ existing_path.remove()
+ return path
+
+class ContainerVcLoad(grok.Adapter):
+ grok.provides(IVcLoad)
+ grok.context(IContainer)
+
+ def load(self, checkout, path):
+ loaded = []
+ for sub in path.listdir():
+ if sub.basename.startswith('.'):
+ continue
+ if sub.check(dir=True):
+ object_name = '' # containers are indicated by empty string
+ else:
+ object_name = sub.ext
+ #if sub.read().strip() == '200':
+ # import pdb; pdb.set_trace()
+ factory = queryUtility(IVcFactory, name=object_name, default=None)
+ # we cannot handle this kind of object, so skip it
+ if factory is None:
+ continue
+ # create instance of object and put it into the container
+ # XXX what if object is already there?
+ obj = factory(checkout, sub)
+ # store the newly created object into the container
+ self.context[sub.purebasename] = obj
+ loaded.append(sub.purebasename)
+ # remove any objects not there anymore
+ for name in list(self.context.keys()):
+ if name not in loaded:
+ del self.context[name]
+
+class CheckoutBase(object):
+ """Checkout base class.
+
+ (hopefully) version control system agnostic.
+ """
+ grok.implements(ICheckout)
+
+ def __init__(self, path):
+ self.path = path
+ self.clear()
+
+ def sync(self, object, message=''):
+ self.save(object)
+ self.up()
+ self.resolve()
+ self.load(object)
+ self.commit(message)
+
+ def save(self, object):
+ IVcDump(object).save(self, self.path)
+
+ def load(self, object):
+ # XXX can only load containers here, not items
+ names = [path.purebasename for path in self.path.listdir()
+ if not path.purebasename.startswith('.')]
+ assert len(names) == 1
+ IVcLoad(object).load(self, self.path.join(names[0]))
+
+ def clear(self):
+ self._added_by_save = []
+ self._deleted_by_save = []
+
+ def up(self):
+ raise NotImplementedError
+
+ def resolve(self):
+ raise NotImplementedError
+
+ def commit(self, message):
+ raise NotImplementedError
+
+ def add(self, path):
+ self._added_by_save.append(path)
+
+ def delete(self, path):
+ self._deleted_by_save.append(path)
+
+ def added_by_save(self):
+ return self._added_by_save
+
+ def deleted_by_save(self):
+ return self._deleted_by_save
+
+ def added_by_up(self):
+ raise NotImplementedError
+
+ def deleted_by_up(self):
+ raise NotImplementedError
+
+ def modified_by_up(self):
+ raise NotImplementedError
+
+class ContainerModified(grok.Adapter):
+ grok.provides(IModified)
+ grok.context(IContainer)
+
+ def modified_since(self, dt):
+ # containers themselves are never modified
+ return False
+
Added: z3c.vcsync/svn-commit.tmp
===================================================================
--- z3c.vcsync/svn-commit.tmp (rev 0)
+++ z3c.vcsync/svn-commit.tmp 2007-06-25 17:36:04 UTC (rev 77071)
@@ -0,0 +1,4 @@
+Initial import of z3c.vcsync.
+--This line, and those below, will be ignored--
+
+A .
More information about the Checkins
mailing list