[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