[Checkins] SVN: z3c.vcsync/trunk/src/z3c/vcsync/ Refactor,
splitting out the synchronizer from the checkout.
Martijn Faassen
faassen at infrae.com
Thu Jul 5 14:20:05 EDT 2007
Log message for revision 77474:
Refactor, splitting out the synchronizer from the checkout.
Changed:
U z3c.vcsync/trunk/src/z3c/vcsync/README.txt
U z3c.vcsync/trunk/src/z3c/vcsync/__init__.py
U z3c.vcsync/trunk/src/z3c/vcsync/interfaces.py
U z3c.vcsync/trunk/src/z3c/vcsync/svn.py
U z3c.vcsync/trunk/src/z3c/vcsync/tests.py
U z3c.vcsync/trunk/src/z3c/vcsync/vc.py
-=-
Modified: z3c.vcsync/trunk/src/z3c/vcsync/README.txt
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/README.txt 2007-07-05 16:41:06 UTC (rev 77473)
+++ z3c.vcsync/trunk/src/z3c/vcsync/README.txt 2007-07-05 18:20:04 UTC (rev 77474)
@@ -9,11 +9,11 @@
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):
+The synchronization sequence (ISequences) 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.
+ 1) save persistent state (IState) to svn checkout (ICheckout) 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.
@@ -175,13 +175,19 @@
>>> sorted([obj.__name__ for obj in state.objects(None)])
['bar', 'foo', 'qux', 'root', 'sub']
-The object structure can now be saved into that checkout::
+Now let's synchronize. For this, we need a synchronizer initialized
+with the checkout and the state::
+
+ >>> from z3c.vcsync import Synchronizer
+ >>> s = Synchronizer(checkout, state)
- >>> checkout.save(state, None)
+We now save the state into that checkout. We are passing ``None`` for
+the dt for the time being::
-The filesystem should now contain the right objects.
+ >>> s.save(None)
-Everything is always saved in a directory called ``root``:
+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)
@@ -232,9 +238,12 @@
>>> removed_paths = ['/root/bar']
>>> state.removed_paths = removed_paths
+The added object always will return with ``objects``, but in your
+application you may also need to let the state know.
+
Let's save the object structure again to the same checkout::
-
- >>> checkout.save(state, None)
+
+ >>> s.save(None)
We expect the ``hoi.test`` file to be added::
@@ -269,7 +278,7 @@
We export again into the existing checkout (which still has 'hoi' as a
file)::
- >>> checkout.save(state, None)
+ >>> s.save(None)
Let's check the filesystem state::
@@ -288,7 +297,7 @@
>>> del data['hoi']
>>> data['hoi'] = Item(payload=16)
- >>> checkout.save(state, None)
+ >>> s.save(None)
This means we need to mark the path to the container to be removed::
@@ -350,7 +359,7 @@
Let's serialize::
- >>> checkout.save(state, None)
+ >>> s.save(None)
We expect to see a ``hoi.other`` item now::
@@ -362,7 +371,7 @@
>>> del data['hoi']
>>> state.removed_paths = ['/root/hoi']
>>> data['hoi'] = Item(payload=16)
- >>> checkout.save(state, None)
+ >>> s.save(None)
We expect to see a ``hoi.test`` item again::
@@ -407,11 +416,22 @@
>>> checkout._deleted = []
>>> checkout._modified = []
-Let's load up the contents from the filesystem now::
+Let's load up the contents from the filesystem now, into a new container::
>>> container2 = Container()
>>> container2.__name__ = 'root'
- >>> checkout.load(container2)
+
+In order to load into a different container, we need to set up a new
+synchronizer with a new state::
+
+ >>> s = Synchronizer(checkout, TestState(container2))
+
+We can now do the loading::
+
+ >>> s.load(None)
+
+We expect the proper objects to be in the new container::
+
>>> sorted(container2.keys())
['foo', 'hoi', 'sub']
@@ -462,14 +482,13 @@
We will reload the checkout into Python objects::
- >>> checkout.load(container2)
+ >>> s.load(None)
We expect the ``hoi`` object to be modified::
>>> container2['hoi'].payload
200
-
version control adds a file
---------------------------
@@ -490,7 +509,7 @@
We will reload the checkout into Python objects again::
- >>> checkout.load(container2)
+ >>> s.load(None)
We expect there to be a new object ``hallo``::
@@ -515,9 +534,9 @@
>>> checkout._modified = []
We will reload the checkout into Python objects::
+
+ >>> s.load(None)
- >>> checkout.load(container2)
-
We expect the object ``hallo`` to be gone again::
>>> 'hallo' in container2.keys()
@@ -546,8 +565,7 @@
Reloading this will cause a new container to exist::
- >>> checkout.load(container2)
-
+ >>> s.load(None)
>>> 'newdir' in container2.keys()
True
>>> isinstance(container2['newdir'], Container)
@@ -574,11 +592,10 @@
And reload the data::
- >>> checkout.load(container2)
+ >>> s.load(None)
Reloading this will cause the new container to be gone again::
- >>> checkout.load(container2)
>>> 'newdir' in container2.keys()
False
@@ -606,7 +623,7 @@
Reloading this will cause a new container to be there instead of the file::
- >>> checkout.load(container2)
+ >>> s.load(None)
>>> isinstance(container2['hoi'], Container)
True
>>> container2['hoi']['some'].payload
@@ -635,7 +652,7 @@
Reloading this will cause a new item to be there instead of the
container::
- >>> checkout.load(container2)
+ >>> s.load(None)
>>> isinstance(container2['hoi'], Item)
True
>>> container2['hoi'].payload
@@ -665,8 +682,7 @@
Now we'll synchronize with the memory structure::
- >>> state = TestState(container2)
- >>> checkout.sync(state, None)
+ >>> s.sync(None)
We expect the checkout to reflect the changed state of the ``hoi`` object::
Modified: z3c.vcsync/trunk/src/z3c/vcsync/__init__.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/__init__.py 2007-07-05 16:41:06 UTC (rev 77473)
+++ z3c.vcsync/trunk/src/z3c/vcsync/__init__.py 2007-07-05 18:20:04 UTC (rev 77474)
@@ -1,2 +1,2 @@
-#
+from vc import Synchronizer
Modified: z3c.vcsync/trunk/src/z3c/vcsync/interfaces.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/interfaces.py 2007-07-05 16:41:06 UTC (rev 77473)
+++ z3c.vcsync/trunk/src/z3c/vcsync/interfaces.py 2007-07-05 18:20:04 UTC (rev 77474)
@@ -33,48 +33,36 @@
"""Create new instance of object.
"""
-class IState(Interface):
- """Information about Python object state.
+class ISynchronizer(Interface):
+ """Synchronizer between state and version control.
"""
- root = Attribute('The root container')
+ checkout = Attribute('Version control system checkout')
+ state = Attribute('Persistent state')
+
+ def sync(dt, message=''):
+ """Synchronize persistent Python state with version control system.
- def objects(dt):
- """Objects present in state.
+ dt - date since when to look for state changes
+ message - message to commit any version control changes.
+ """
- Not all objects have to be returned. At a minimum, only those
- objects that have been modified or added since dt need to
- be returned.
+ def save(dt):
+ """Save state to filesystem location of checkout.
+
+ dt - timestamp after which to look for state changes.
"""
- def removed(dt):
- """Paths removed.
+ def load(dt):
+ """Load the filesystem information into persistent state.
- The path is a path from the state root object to the actual
- object that was removed. It is therefore not the same as the
- physically locatable path.
-
- Any path that has been removed since dt should be returned. This
- path might have been added again later, so it is safe to return
- paths of objects returned by the 'objects' method.
+ dt - timestamp after which to look for filesystem changes.
"""
-
+
class ICheckout(Interface):
"""A version control system checkout.
- """
- def sync(state, dt, message=''):
- """Synchronize persistent Python state with remove version control.
+ """
+ path = Attribute('Path to checkout root')
- dt is date since when to look for state changes.
- """
-
- def save(state, dt):
- """Save state 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.
"""
@@ -87,15 +75,40 @@
"""Commit checkout to version control system.
"""
- def added():
- """A list of those files that have been added after 'up'.
+ def files(dt):
+ """Files added/modified in state since dt.
+
+ Returns paths to files that were added/modified.
"""
- def deleted():
- """A list of those files that have been deleted after 'up'.
+ def removed(dt):
+ """Files removed in state since dt.
+
+ Returns paths to files that were removed.
"""
- def modified():
- """A list of those files that have been modified after 'up'.
+class IState(Interface):
+ """Information about Python object state.
+ """
+ root = Attribute('The root container')
+
+ def objects(dt):
+ """Objects present in state.
+
+ Not all objects have to be returned. At a minimum, only those
+ objects that have been modified or added since dt need to
+ be returned.
"""
-
+
+ def removed(dt):
+ """Paths removed.
+
+ The path is a path from the state root object to the actual
+ object that was removed. It is therefore not the same as the
+ physically locatable path.
+
+ Any path that has been removed since dt should be returned. This
+ path might have been added again later, so it is safe to return
+ paths of objects returned by the 'objects' method.
+ """
+
Modified: z3c.vcsync/trunk/src/z3c/vcsync/svn.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/svn.py 2007-07-05 16:41:06 UTC (rev 77473)
+++ z3c.vcsync/trunk/src/z3c/vcsync/svn.py 2007-07-05 18:20:04 UTC (rev 77474)
@@ -1,8 +1,6 @@
import py
-from z3c.vcsync.vc import CheckoutBase
-
-class SvnCheckout(CheckoutBase):
+class SvnCheckout(object):
"""A checkout for SVN.
This is a simplistic implementation. Advanced implementations
@@ -15,9 +13,9 @@
"""
def __init__(self, path):
- super(SvnCheckout, self).__init__(path)
- self._log_info = {'R':[], 'A':[], 'M':[]}
-
+ self.path = path
+ self._log_info = log_info()
+
def _repository_url(self):
prefix = 'Repository Root: '
lines = self.path._svn('info').splitlines()
@@ -34,23 +32,27 @@
return checkout_url[len(repos_url):]
def up(self):
- original_rev = int(self.path.status().rev)
+ original_rev = int(self.path.status().rev) - 10
self.path.update()
now_rev = int(self.path.status().rev)
- logs = self.path.log(now_rev, original_rev, verbose=True)
+
+ if original_rev == now_rev:
+ return
+
+ logs = self.path.log(original_rev + 1, now_rev, verbose=True)
checkout_path = self._checkout_path()
- log_info = {'R': [], 'A': [], 'M':[]}
+ info = log_info()
for log in logs:
for p in log.strpaths:
rel_path = p.strpath[len(checkout_path):]
steps = rel_path.split(self.path.sep)
# construct py.path to file
path = self.path.join(*steps)
- log_info[p.action].append(path)
- self._log_info = log_info
+ info[p.action].add(path)
+ self._log_info = info
def resolve(self):
pass
@@ -59,12 +61,13 @@
self.path.commit(message)
def added(self):
- return self._log_info['A']
+ return list(self._log_info['A'])
def deleted(self):
- return self._log_info['R']
+ return list(self._log_info['D'])
def modified(self):
- return self._log_info['M']
+ return list(self._log_info['M'].union(self._log_info['R']))
-
+def log_info():
+ return {'D': set(), 'R': set(), 'A': set(), 'M': set()}
Modified: z3c.vcsync/trunk/src/z3c/vcsync/tests.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/tests.py 2007-07-05 16:41:06 UTC (rev 77473)
+++ z3c.vcsync/trunk/src/z3c/vcsync/tests.py 2007-07-05 18:20:04 UTC (rev 77474)
@@ -10,12 +10,15 @@
from zope.app.container.interfaces import IContainer
from zope.exceptions.interfaces import DuplicationError
-from z3c.vcsync.interfaces import ISerializer, IVcDump, IVcFactory
+from z3c.vcsync.interfaces import (ISerializer, IVcDump, IVcFactory,
+ IState, ICheckout)
from z3c.vcsync import vc
-class TestCheckout(vc.CheckoutBase):
+class TestCheckout(object):
+ grok.implements(ICheckout)
+
def __init__(self, path):
- super(TestCheckout, self).__init__(path)
+ self.path = path
self.update_function = None
self._added = []
self._deleted = []
@@ -43,6 +46,8 @@
return self._modified
class TestState(object):
+ grok.implements(IState)
+
def __init__(self, root):
self.root = root
self.removed_paths = []
Modified: z3c.vcsync/trunk/src/z3c/vcsync/vc.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/vc.py 2007-07-05 16:41:06 UTC (rev 77473)
+++ z3c.vcsync/trunk/src/z3c/vcsync/vc.py 2007-07-05 18:20:04 UTC (rev 77474)
@@ -6,7 +6,7 @@
from zope.app.container.interfaces import IContainer
from zope.traversing.interfaces import IPhysicallyLocatable
-from z3c.vcsync.interfaces import IVcDump, ISerializer, IVcFactory, ICheckout
+from z3c.vcsync.interfaces import IVcDump, ISerializer, IVcFactory, ISynchronizer
import grok
@@ -62,37 +62,24 @@
return None
return obj
-class CheckoutBase(object):
- """Checkout base class.
+class Synchronizer(object):
+ grok.implements(ISynchronizer)
- (hopefully) version control system agnostic.
- """
- grok.implements(ICheckout)
-
- def __init__(self, path):
- self.path = path
+ def __init__(self, checkout, state):
+ self.checkout = checkout
+ self.state = state
- def sync(self, state, dt, message=''):
- self.save(state, dt)
- self.up()
- self.resolve()
- self.load(state.root)
- self.commit(message)
+ def sync(self, dt, message=''):
+ self.save(dt)
+ self.checkout.up()
+ self.checkout.resolve()
+ self.load(dt)
+ self.checkout.commit(message)
- def get_container_path(self, root, obj):
- steps = []
- while obj is not root:
- obj = obj.__parent__
- steps.append(obj.__name__)
- steps.reverse()
- return self.path.join(*steps)
-
- def save(self, state, dt):
- root = state.root
-
+ def save(self, dt):
# remove all files that have been removed in the database
- path = self.path
- for removed_path in state.removed(dt):
+ path = self.checkout.path
+ for removed_path in self.state.removed(dt):
# construct path to directory containing file/dir to remove
steps = removed_path.split('/')
container_dir_path = path.join(*steps[:-1])
@@ -109,46 +96,39 @@
str('%s.*' % name)))
assert len(file_paths) == 1
file_paths[0].remove()
+
# now save all files that have been modified/added
- for obj in state.objects(dt):
+ root = self.state.root
+ for obj in self.state.objects(dt):
IVcDump(obj).save(self,
- self.get_container_path(root, obj))
+ self._get_container_path(root, obj))
- def load(self, object):
- root = object
- for deleted_path in self.deleted():
- obj = resolve(root, self.path, deleted_path)
+ def load(self, dt):
+ root = self.state.root
+
+ for deleted_path in self.checkout.deleted():
+ obj = resolve(root, self.checkout.path, deleted_path)
if obj is not None:
del obj.__parent__[obj.__name__]
- added_paths = self.added()
+ added_paths = self.checkout.added()
# to ensure that containers are created before items we sort them
sorted(added_paths)
for added_path in added_paths:
- obj = resolve_container(root, self.path, added_path)
+ obj = resolve_container(root, self.checkout.path, added_path)
factory = getUtility(IVcFactory, name=added_path.ext)
obj[added_path.purebasename] = factory(self, added_path)
- for modified_path in self.modified():
- obj = resolve(root, self.path, modified_path)
+ for modified_path in self.checkout.modified():
+ obj = resolve(root, self.checkout.path, modified_path)
factory = getUtility(IVcFactory, name=modified_path.ext)
container = obj.__parent__
name = obj.__name__
del container[name]
container[name] = factory(self, modified_path)
- def up(self):
- raise NotImplementedError
-
- def resolve(self):
- raise NotImplementedError
-
- def commit(self, message):
- raise NotImplementedError
-
- def added(self):
- raise NotImplementedError
-
- def deleted(self):
- raise NotImplementedError
-
- def modified(self):
- raise NotImplementedError
+ def _get_container_path(self, root, obj):
+ steps = []
+ while obj is not root:
+ obj = obj.__parent__
+ steps.append(obj.__name__)
+ steps.reverse()
+ return self.checkout.path.join(*steps)
More information about the Checkins
mailing list