[Checkins] SVN: z3c.vcsync/trunk/ Fix a bug where in some scenarios the svn R flag was generated (replaced),
Martijn Faassen
faassen at infrae.com
Tue Aug 19 13:33:27 EDT 2008
Log message for revision 90016:
Fix a bug where in some scenarios the svn R flag was generated (replaced),
which isn't recognized by Py 0.9.1.
Changed:
U z3c.vcsync/trunk/CHANGES.txt
U z3c.vcsync/trunk/setup.py
U z3c.vcsync/trunk/src/z3c/vcsync/__init__.py
U z3c.vcsync/trunk/src/z3c/vcsync/conflict.txt
A z3c.vcsync/trunk/src/z3c/vcsync/monkey.py
U z3c.vcsync/trunk/src/z3c/vcsync/vc.py
-=-
Modified: z3c.vcsync/trunk/CHANGES.txt
===================================================================
--- z3c.vcsync/trunk/CHANGES.txt 2008-08-19 16:54:33 UTC (rev 90015)
+++ z3c.vcsync/trunk/CHANGES.txt 2008-08-19 17:33:26 UTC (rev 90016)
@@ -4,8 +4,16 @@
0.15 (unreleased)
-----------------
-* ...
+* Fix a bug where the SVN "R" status was not recognized by the py lib.
+ Monkey-patch py for now, though a fix should be released with py
+ 0.9.2 eventually. Strictly depend on py 0.9.1 for now to make sure
+ monkey-patch applies cleanly.
+* A bit of refactoring of duplicated code in retrieving the objects
+ modified and objects removed lists. Still not happy that this gets
+ called twice per synchronization, but it was already doing this so
+ doesn't get worse either.
+
0.14 (2008-07-04)
-----------------
Modified: z3c.vcsync/trunk/setup.py
===================================================================
--- z3c.vcsync/trunk/setup.py 2008-08-19 16:54:33 UTC (rev 90015)
+++ z3c.vcsync/trunk/setup.py 2008-08-19 17:33:26 UTC (rev 90016)
@@ -25,7 +25,7 @@
install_requires=[
'setuptools',
'grok',
- 'py >= 0.9.1',
+ 'py == 0.9.1',
],
license="ZPL 2.1",
entry_points="""
Modified: z3c.vcsync/trunk/src/z3c/vcsync/__init__.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/__init__.py 2008-08-19 16:54:33 UTC (rev 90015)
+++ z3c.vcsync/trunk/src/z3c/vcsync/__init__.py 2008-08-19 17:33:26 UTC (rev 90016)
@@ -1,3 +1,7 @@
-from vc import Synchronizer
-from importexport import (export, import_,
- export_zip, import_zip)
+from z3c.vcsync.vc import Synchronizer
+from z3c.vcsync.importexport import (export, import_,
+ export_zip, import_zip)
+
+from z3c.vcsync.monkey import apply_monkey_patches
+apply_monkey_patches()
+
Modified: z3c.vcsync/trunk/src/z3c/vcsync/conflict.txt
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/conflict.txt 2008-08-19 16:54:33 UTC (rev 90015)
+++ z3c.vcsync/trunk/src/z3c/vcsync/conflict.txt 2008-08-19 17:33:26 UTC (rev 90016)
@@ -391,6 +391,55 @@
>>> found2['data']['folder']['content'].payload
15
+Adding an object that was just removed: Py error
+------------------------------------------------
+
+In some cases an object that was removed in a state is later re-added,
+such as in the following scenario:
+
+* there is an object 'testing'
+
+* synchronized
+
+* the object is removed
+
+* a new object is created with the same name, 'testing'
+
+* resynchronization
+
+The result is an error in the Py lib that the svn status code ``R``
+cannot be processed.
+
+The interface of ``IState`` states that it is safe for the ``removed``
+method to return objects that were removed but were since then
+re-added (and thus also appear in the ``objects`` list). Unfortunately
+the
+
+This is however not the underlying error. The true error is that the
+object got added to the removed list of the state while it is also in
+the modified list of the state (as it was re-added). Let's demonstrate
+the error::
+
+ >>> current_synchronizer = s1
+ >>> data1['testing'] = Item(200)
+ >>> info = s1.sync("synchronize")
+
+We claim the object is removed::
+
+ >>> state1._removed.append(
+ ... get_object_path(root1, data1['testing']))
+ >>> del data1['testing']
+
+But we also claim it is modified (the test state declares everything
+modified)::
+
+ >>> data1['testing'] = Item(250)
+
+The result is an error when synchronizing::
+
+ >>> info = s1.sync("synchronize")
+
+
Conflicting directory conflicts
-------------------------------
Added: z3c.vcsync/trunk/src/z3c/vcsync/monkey.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/monkey.py (rev 0)
+++ z3c.vcsync/trunk/src/z3c/vcsync/monkey.py 2008-08-19 17:33:26 UTC (rev 90016)
@@ -0,0 +1,130 @@
+
+def apply_monkey_patches():
+ """Apply all monkey-patches
+ """
+ svn_r_status_support()
+
+def svn_r_status_support():
+ """monkey-patch py 0.9.1 with support for SVN 'R' status flag.
+
+ this should be fixed in py 0.9.2, so the monkey patch should go
+ away when we start using that.
+ """
+ from py.__.path.svn import wccommand
+ wccommand.WCStatus.attrnames = wccommand.WCStatus.attrnames + ('replaced',)
+ wccommand.SvnWCCommandPath.status = status
+
+from py.__.path.svn.wccommand import WCStatus
+
+# taken from py.path.svn.wccommand
+def status(self, updates=0, rec=0, externals=0):
+ """ return (collective) Status object for this file. """
+ # http://svnbook.red-bean.com/book.html#svn-ch-3-sect-4.3.1
+ # 2201 2192 jum test
+ # XXX
+ if externals:
+ raise ValueError("XXX cannot perform status() "
+ "on external items yet")
+ else:
+ #1.2 supports: externals = '--ignore-externals'
+ externals = ''
+ if rec:
+ rec= ''
+ else:
+ rec = '--non-recursive'
+
+ # XXX does not work on all subversion versions
+ #if not externals:
+ # externals = '--ignore-externals'
+
+ if updates:
+ updates = '-u'
+ else:
+ updates = ''
+
+ update_rev = None
+
+ cmd = 'status -v %s %s %s' % (updates, rec, externals)
+ out = self._authsvn(cmd)
+ rootstatus = WCStatus(self)
+ for line in out.split('\n'):
+ if not line.strip():
+ continue
+ #print "processing %r" % line
+ flags, rest = line[:8], line[8:]
+ # first column
+ c0,c1,c2,c3,c4,c5,x6,c7 = flags
+ #if '*' in line:
+ # print "flags", repr(flags), "rest", repr(rest)
+
+ if c0 in '?XI':
+ fn = line.split(None, 1)[1]
+ if c0 == '?':
+ wcpath = self.join(fn, abs=1)
+ rootstatus.unknown.append(wcpath)
+ elif c0 == 'X':
+ wcpath = self.__class__(self.localpath.join(fn, abs=1),
+ auth=self.auth)
+ rootstatus.external.append(wcpath)
+ elif c0 == 'I':
+ wcpath = self.join(fn, abs=1)
+ rootstatus.ignored.append(wcpath)
+
+ continue
+
+ #elif c0 in '~!' or c4 == 'S':
+ # raise NotImplementedError("received flag %r" % c0)
+
+ m = self._rex_status.match(rest)
+ if not m:
+ if c7 == '*':
+ fn = rest.strip()
+ wcpath = self.join(fn, abs=1)
+ rootstatus.update_available.append(wcpath)
+ continue
+ if line.lower().find('against revision:')!=-1:
+ update_rev = int(rest.split(':')[1].strip())
+ continue
+ # keep trying
+ raise ValueError, "could not parse line %r" % line
+ else:
+ rev, modrev, author, fn = m.groups()
+ wcpath = self.join(fn, abs=1)
+ #assert wcpath.check()
+ if c0 == 'M':
+ assert wcpath.check(file=1), "didn't expect a directory with changed content here"
+ rootstatus.modified.append(wcpath)
+ elif c0 == 'A' or c3 == '+' :
+ rootstatus.added.append(wcpath)
+ elif c0 == 'D':
+ rootstatus.deleted.append(wcpath)
+ elif c0 == 'C':
+ rootstatus.conflict.append(wcpath)
+ # XXX following two lines added by monkey-patch
+ elif c0 == 'R':
+ rootstatus.replaced.append(wcpath)
+ elif c0 == '~':
+ rootstatus.kindmismatch.append(wcpath)
+ elif c0 == '!':
+ rootstatus.incomplete.append(wcpath)
+ elif not c0.strip():
+ rootstatus.unchanged.append(wcpath)
+ else:
+ raise NotImplementedError("received flag %r" % c0)
+
+ if c1 == 'M':
+ rootstatus.prop_modified.append(wcpath)
+ # XXX do we cover all client versions here?
+ if c2 == 'L' or c5 == 'K':
+ rootstatus.locked.append(wcpath)
+ if c7 == '*':
+ rootstatus.update_available.append(wcpath)
+
+ if wcpath == self:
+ rootstatus.rev = rev
+ rootstatus.modrev = modrev
+ rootstatus.author = author
+ if update_rev:
+ rootstatus.update_rev = update_rev
+ continue
+ return rootstatus
Modified: z3c.vcsync/trunk/src/z3c/vcsync/vc.py
===================================================================
--- z3c.vcsync/trunk/src/z3c/vcsync/vc.py 2008-08-19 16:54:33 UTC (rev 90015)
+++ z3c.vcsync/trunk/src/z3c/vcsync/vc.py 2008-08-19 17:33:26 UTC (rev 90016)
@@ -104,12 +104,10 @@
def sync(self, message='', modified_function=None):
revision_nr = self.state.get_revision_nr()
- # store these to report in SynchronizationInfo below
- objects_removed = list(self.state.removed(revision_nr))
- root = self.state.root
- objects_changed = [get_object_path(root, obj) for obj in
- self.state.objects(revision_nr)]
- # now save the state to the checkout
+ # store some information for the synchronization info
+ objects_changed, object_paths_changed, object_paths_removed =\
+ self._get_changed_removed(revision_nr)
+ # save the state to the checkout
self.save(revision_nr)
# update the checkout
self.checkout.up()
@@ -141,15 +139,37 @@
# we store the new revision number in the state
self.state.set_revision_nr(revision_nr)
- # and we return some informatino about happened
+ # and we return some information about what happened
return SynchronizationInfo(revision_nr,
- objects_removed, objects_changed,
+ object_paths_removed, object_paths_changed,
files_removed, files_changed)
+ def _get_changed_removed(self, revision_nr):
+ """Construct the true lists of objects that are changed and removed.
+
+ Returns tuple with:
+
+ * actual objects changed
+ * object paths changed
+ * object paths removed
+ """
+ # store these to report in SynchronizationInfo below
+ object_paths_removed = list(self.state.removed(revision_nr))
+ root = self.state.root
+ objects_changed = list(self.state.objects(revision_nr))
+ object_paths_changed = [get_object_path(root, obj) for obj in
+ objects_changed]
+ return objects_changed, object_paths_changed, object_paths_removed
+
def save(self, revision_nr):
+ """Save objects to filesystem.
+ """
+ objects_changed, object_paths_changed, object_paths_removed =\
+ self._get_changed_removed(revision_nr)
+
# remove all files that have been removed in the database
path = self.checkout.path
- for removed_path in self.state.removed(revision_nr):
+ for removed_path in object_paths_removed:
# construct path to directory containing file/dir to remove
# note: this is a state-specific path which always uses /, so we
# shouldn't use os.path.sep here
@@ -178,10 +198,10 @@
# now save all files that have been modified/added
root = self.state.root
- for obj in self.state.objects(revision_nr):
+ for obj in objects_changed:
if obj is not root:
IDump(obj).save(self._get_container_path(root, obj))
-
+
def load(self, revision_nr):
# remove all objects that have been removed in the checkout
root = self.state.root
More information about the Checkins
mailing list