- Revision
- 11275
- Author
- morgen
- Date
- 2006-07-28 08:02:20 -0700 (Fri, 28 Jul 2006)
Log Message
(0.7alpha3 branch) Andi's fix for the "modified in one view, deleted in the other" repository merge issue, bug 6266. r=morgen
Modified Paths
- branches/0.7alpha3/chandler/parcels/osaf/sharing/Sharing.py
- branches/0.7alpha3/chandler/repository/item/Item.py
- branches/0.7alpha3/chandler/repository/persistence/DBRepositoryView.py
- branches/0.7alpha3/chandler/repository/persistence/RepositoryError.py
- branches/0.7alpha3/chandler/repository/tests/TestMerge.py
Diff
Modified: branches/0.7alpha3/chandler/parcels/osaf/sharing/Sharing.py (11274 => 11275)
--- branches/0.7alpha3/chandler/parcels/osaf/sharing/Sharing.py 2006-07-28 13:38:16 UTC (rev 11274) +++ branches/0.7alpha3/chandler/parcels/osaf/sharing/Sharing.py 2006-07-28 15:02:20 UTC (rev 11275) @@ -26,6 +26,7 @@ from repository.schema.Types import Type from repository.util.Lob import Lob from chandlerdb.item.ItemError import NoSuchAttributeError +from repository.persistence.RepositoryError import MergeError from PyICU import ICUtzinfo import M2Crypto.BIO, WebDAV, twisted.web.http, zanshin.webdav, wx from cStringIO import StringIO @@ -78,6 +79,13 @@ # 'value' is the one from the *this* view # getattr(item, attribute) is the value from a different view + if code == MergeError.DELETE: + logger.debug("Sharing conflict on item %(item)s, " + "deleted locally, modified remotely", + { 'item' : item, } + ) + return True + logger.debug("Sharing conflict on item %(item)s, attribute " "%(attribute)s: %(local)s vs %(remote)s", { 'item' : item,
Modified: branches/0.7alpha3/chandler/repository/item/Item.py (11274 => 11275)
--- branches/0.7alpha3/chandler/repository/item/Item.py 2006-07-28 13:38:16 UTC (rev 11274) +++ branches/0.7alpha3/chandler/repository/item/Item.py 2006-07-28 15:02:20 UTC (rev 11275) @@ -1441,56 +1441,65 @@ self._status &= ~Item.DEFERRING else: - refs = self._references - values = self._values - others = [] + self._delete(view, recursive, deletePolicy, _noMonitors, False) - self.setDirty(Item.NDIRTY) - self._status |= Item.DELETING + def _delete(self, view, recursive, deletePolicy, _noMonitors, _keepRoot): - if hasattr(type(self), 'onItemDelete'): - self.onItemDelete(view, False) + refs = self._references + values = self._values + others = [] - for child in self.iterChildren(): - child.delete(True, deletePolicy) + self.setDirty(Item.NDIRTY) + self._status |= Item.DELETING - if self.isWatched(): - view._notifyChange(self._itemChanged, 'remove', - ('itsKind',)) - self._status &= ~Item.WATCHED + if hasattr(type(self), 'onItemDelete'): + self.onItemDelete(view, False) - if 'watcherDispatch' in values: - del values['watcherDispatch'] - view._unregisterWatches(self) + for child in self.iterChildren(): + child.delete(True, deletePolicy) - if 'monitors' in refs: - for monitor in self.monitors: - monitor.delete(True, None, None, True) + if self.isWatched(): + view._notifyChange(self._itemChanged, 'remove', + ('itsKind',)) + self._status &= ~Item.WATCHED - for name in refs.keys(): - policy = (deletePolicy or - self.getAttributeAspect(name, 'deletePolicy', - False, None, 'remove')) - if policy == 'cascade': - value = refs._getRef(name) - if value is not None: - if value._isRefs(): - others.extend(value) - else: - others.append(value) + if 'watcherDispatch' in values: + del values['watcherDispatch'] + view._unregisterWatches(self) - for other in others: - if other.refCount(True) == 0: - other.delete(recursive, deletePolicy) + if 'monitors' in refs: + for monitor in self.monitors: + monitor.delete(True, None, None, True) - self._setKind(None, _noMonitors) + for name in refs.keys(): + policy = (deletePolicy or + self.getAttributeAspect(name, 'deletePolicy', + False, None, 'remove')) + if policy == 'cascade': + value = refs._getRef(name) + if value is not None: + if value._isRefs(): + others.extend(value) + else: + others.append(value) - self.itsParent._removeItem(self) - self._setRoot(None, view) + for other in others: + if other.refCount(True) == 0: + other.delete(recursive, deletePolicy) - self._status |= Item.DELETED | Item.STALE - self._status &= ~(Item.DELETING | Item.DEFERRED) + self._setKind(None, _noMonitors) + self.itsParent._removeItem(self) + if _keepRoot: # during merge (to delete deferred children) + view._unregisterItem(self, False) + self._status |= Item.DELETED + else: + self._setRoot(None, view) + self._status |= Item.DELETED | Item.STALE + + self._status |= Item.DELETED | Item.STALE + self._status &= ~(Item.DELETING | Item.DEFERRED) + def _copyExport(self, view, cloudAlias, matches): uuid = self._uuid
Modified: branches/0.7alpha3/chandler/repository/persistence/DBRepositoryView.py (11274 => 11275)
--- branches/0.7alpha3/chandler/repository/persistence/DBRepositoryView.py 2006-07-28 13:38:16 UTC (rev 11274) +++ branches/0.7alpha3/chandler/repository/persistence/DBRepositoryView.py 2006-07-28 15:02:20 UTC (rev 11275) @@ -379,15 +379,36 @@ def _refreshForwards(self, mergeFn, newVersion, notify): - history = [] - refreshes = set() - deletes = set() - merges = {} - unloads = {} - dangling = [] + scan = True + while scan: + history = [] + refreshes = set() + deletes = set() + merges = {} + unloads = {} + dangling = [] - self._scanHistory(self.itsVersion, newVersion, - history, refreshes, merges, unloads, deletes) + self._scanHistory(self.itsVersion, newVersion, history, + refreshes, merges, unloads, deletes) + scan = False + + if merges: + # if item is deleted in other view, resolve conflict by + # deleting item locally (with mergeFn approval) + for uItem in deletes: + if uItem in merges: + item = self[uItem] + if not item.isDeferred(): + if (mergeFn is None or + not mergeFn(MergeError.DELETE, item, + None, None)): + self._e_2_delete(item, newVersion) + if not item.isDeleted(): + item.delete(True) + if item.isDeferred(): + item._delete(self, True, None, False, True) + scan = True + oldVersion = self._version self._version = newVersion @@ -695,7 +716,7 @@ elif item.itsVersion < version: unloads[uItem] = item - elif uItem in self._deletedRegistry: + elif uItem in self._deletedRegistry and uItem not in deletes: kind = self.find(uKind, False) if kind is None: self._e_1_delete(uItem, uKind, oldVersion, version) @@ -824,6 +845,10 @@ raise MergeError, ('delete', uItem, 'item %s was deleted in this version (%d) but has later changes in version (%d) where it is of kind %s' %(uItem, oldVersion, newVersion, uKind), MergeError.CHANGE) + def _e_2_delete(self, item, version): + + raise MergeError, ('delete', item, 'item %s was changed in this view but was deleted in version (%d)' %(item._repr_(), version), MergeError.DELETE) + def _e_1_name(self, list, key, newName, name): raise MergeError, ('name', type(self).__name__, 'element %s renamed to %s and %s' %(key, newName, name), MergeError.RENAME)
Modified: branches/0.7alpha3/chandler/repository/persistence/RepositoryError.py (11274 => 11275)
--- branches/0.7alpha3/chandler/repository/persistence/RepositoryError.py 2006-07-28 13:38:16 UTC (rev 11274) +++ branches/0.7alpha3/chandler/repository/persistence/RepositoryError.py 2006-07-28 15:02:20 UTC (rev 11275) @@ -103,6 +103,7 @@ REF = 5 KIND = 6 CHANGE = 7 + DELETE = 8 codeNames = { BUG: 'BUG', RENAME: 'RENAME', @@ -111,7 +112,8 @@ VALUE: 'VALUE', REF: 'REF', KIND: 'KIND', - CHANGE: 'CHANGE' } + CHANGE: 'CHANGE', + DELETE: 'DELETE' } class LoadError(RepositoryError):
Modified: branches/0.7alpha3/chandler/repository/tests/TestMerge.py (11274 => 11275)
--- branches/0.7alpha3/chandler/repository/tests/TestMerge.py 2006-07-28 13:38:16 UTC (rev 11274) +++ branches/0.7alpha3/chandler/repository/tests/TestMerge.py 2006-07-28 15:02:20 UTC (rev 11275) @@ -977,7 +977,48 @@ main.commit() self.assert_(main.check(), 'main view did not check out') + def testMergeChangeDelete(self): + def mergeFn(code, item, attribute, newValue): + if code == MergeError.DELETE: + return True + return newValue + + main = self.rep.view + cineguidePack = os.path.join(self.testdir, 'data', 'packs', + 'cineguide.pack') + main.loadPack(cineguidePack) + main.commit() + + view = self.rep.createView('view') + main = self.rep.setCurrentView(view) + + k = view.findPath('//CineGuide/KHepburn') + c = k.itsParent + m1 = k.movies.first() + m1.delete() + view.commit() + + view = self.rep.setCurrentView(main) + main.deferDelete() + k = main.findPath('//CineGuide/KHepburn') + m1 = k.movies.first() + m1.title = 'Foo' + m1.director = k.movies.next(m1).director + + try: + main.commit(None) + except MergeError, e: + self.assert_(e.getReasonCode() == MergeError.DELETE) + else: + if not m1.isDeleted(): + self.assert_(False, "MergeError not caught") + + main.commit(mergeFn) + self.assert_(m1.isDeleted()) + self.assert_(main.check(), 'main view did not check out') + + if __name__ == "__main__": # import hotshot # profiler = hotshot.Profile('/tmp/TestItems.hotshot')
_______________________________________________ Commits mailing list [email protected] http://lists.osafoundation.org/mailman/listinfo/commits
