| Index: sync/engine/get_commit_ids.cc
|
| diff --git a/sync/engine/get_commit_ids.cc b/sync/engine/get_commit_ids.cc
|
| index 5f91915d5a0720e018ea333938e1329727c85cc4..146f20f9ecf82355fc3779bd7ecdfe1bcdc2f803 100644
|
| --- a/sync/engine/get_commit_ids.cc
|
| +++ b/sync/engine/get_commit_ids.cc
|
| @@ -250,6 +250,11 @@ class Traversal {
|
| const syncable::Entry& item,
|
| syncable::Directory::Metahandles* result) const;
|
|
|
| + bool AddDeletedParents(const std::set<int64>& ready_unsynced_set,
|
| + const syncable::Entry& item,
|
| + const syncable::Directory::Metahandles& traversed,
|
| + syncable::Directory::Metahandles* result) const;
|
| +
|
| // Returns true if we've collected enough items.
|
| bool IsFull() const;
|
|
|
| @@ -376,6 +381,66 @@ void Traversal::AddPredecessorsThenItem(
|
| result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
|
| }
|
|
|
| +// Traverses the tree from bottom to top, adding the deleted parents of the
|
| +// given |item|. Stops traversing if it encounters a non-deleted node, or
|
| +// a node that was already listed in the |traversed| list. Returns an error
|
| +// (false) if a node along the traversal is in a conflict state.
|
| +//
|
| +// The result list is reversed before it is returned, so the resulting
|
| +// traversal is in top to bottom order. Also note that this function appends
|
| +// to the result list without clearing it.
|
| +bool Traversal::AddDeletedParents(
|
| + const std::set<int64>& ready_unsynced_set,
|
| + const syncable::Entry& item,
|
| + const syncable::Directory::Metahandles& traversed,
|
| + syncable::Directory::Metahandles* result) const {
|
| + syncable::Directory::Metahandles dependencies;
|
| + syncable::Id parent_id = item.GetParentId();
|
| +
|
| + // Climb the tree adding entries leaf -> root.
|
| + while (!parent_id.IsRoot()) {
|
| + syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id);
|
| +
|
| + if (!parent.good()) {
|
| + // This is valid because the parent could have gone away a long time ago
|
| + //
|
| + // Consider the case where a folder is server-unknown and locally
|
| + // deleted, and has a child that is server-known, deleted, and unsynced.
|
| + // The parent could be dropped from memory at any time, but its child
|
| + // needs to be committed first.
|
| + break;
|
| + }
|
| + int64 handle = parent.GetMetahandle();
|
| + if (!parent.GetIsUnsynced()) {
|
| + // In some rare cases, our parent can be both deleted and unsynced.
|
| + // (ie. the server-unknown parent case).
|
| + break;
|
| + }
|
| + if (!parent.GetIsDel()) {
|
| + // We're not intersted in non-deleted parents.
|
| + break;
|
| + }
|
| + if (std::find(traversed.begin(), traversed.end(), handle) !=
|
| + traversed.end()) {
|
| + // We've already added this parent (and therefore all of its parents).
|
| + // We can return early.
|
| + break;
|
| + }
|
| + if (IsEntryInConflict(parent)) {
|
| + // We ignore all entries that are children of a conflicing item. Return
|
| + // false immediately to forget the traversal we've built up so far.
|
| + DVLOG(1) << "Parent was in conflict, omitting " << item;
|
| + return false;
|
| + }
|
| + TryAddItem(ready_unsynced_set, parent, &dependencies);
|
| + parent_id = parent.GetParentId();
|
| + }
|
| +
|
| + // Reverse what we added to get the correct order.
|
| + result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
|
| + return true;
|
| +}
|
| +
|
| bool Traversal::IsFull() const {
|
| return out_->size() >= max_entries_;
|
| }
|
| @@ -429,80 +494,49 @@ void Traversal::AddCreatesAndMoves(
|
| out_->resize(max_entries_);
|
| }
|
|
|
| -void Traversal::AddDeletes(
|
| - const std::set<int64>& ready_unsynced_set) {
|
| - set<syncable::Id> legal_delete_parents;
|
| +void Traversal::AddDeletes(const std::set<int64>& ready_unsynced_set) {
|
| + syncable::Directory::Metahandles deletion_list;
|
|
|
| for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
|
| !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
|
| int64 metahandle = *iter;
|
| +
|
| if (HaveItem(metahandle))
|
| continue;
|
|
|
| + if (std::find(deletion_list.begin(), deletion_list.end(), metahandle) !=
|
| + deletion_list.end()) {
|
| + continue;
|
| + }
|
| +
|
| syncable::Entry entry(trans_, syncable::GET_BY_HANDLE,
|
| metahandle);
|
|
|
| if (entry.GetIsDel()) {
|
| - syncable::Entry parent(trans_, syncable::GET_BY_ID,
|
| - entry.GetParentId());
|
| - // If the parent is deleted and unsynced, then any children of that
|
| - // parent don't need to be added to the delete queue.
|
| - //
|
| - // Note: the parent could be synced if there was an update deleting a
|
| - // folder when we had a deleted all items in it.
|
| - // We may get more updates, or we may want to delete the entry.
|
| - if (parent.good() && parent.GetIsDel() && parent.GetIsUnsynced()) {
|
| - // However, if an entry is moved, these rules can apply differently.
|
| - //
|
| - // If the entry was moved, then the destination parent was deleted,
|
| - // then we'll miss it in the roll up. We have to add it in manually.
|
| - // TODO(chron): Unit test for move / delete cases:
|
| - // Case 1: Locally moved, then parent deleted
|
| - // Case 2: Server moved, then locally issue recursive delete.
|
| - if (entry.GetId().ServerKnows() &&
|
| - entry.GetParentId() != entry.GetServerParentId()) {
|
| - DVLOG(1) << "Inserting moved and deleted entry, will be missed by "
|
| - << "delete roll." << entry.GetId();
|
| -
|
| - AppendToTraversal(metahandle);
|
| - }
|
| -
|
| - // Skip this entry since it's a child of a parent that will be
|
| - // deleted. The server will unroll the delete and delete the
|
| - // child as well.
|
| - continue;
|
| + syncable::Directory::Metahandles parents;
|
| + if (AddDeletedParents(
|
| + ready_unsynced_set, entry, deletion_list, &parents)) {
|
| + // Append parents and chilren in top to bottom order.
|
| + deletion_list.insert(deletion_list.end(),
|
| + parents.begin(),
|
| + parents.end());
|
| + deletion_list.push_back(metahandle);
|
| }
|
| -
|
| - legal_delete_parents.insert(entry.GetParentId());
|
| }
|
| - }
|
|
|
| - // We could store all the potential entries with a particular parent during
|
| - // the above scan, but instead we rescan here. This is less efficient, but
|
| - // we're dropping memory alloc/dealloc in favor of linear scans of recently
|
| - // examined entries.
|
| - //
|
| - // Scan through the UnsyncedMetaHandles again. If we have a deleted
|
| - // entry, then check if the parent is in legal_delete_parents.
|
| - //
|
| - // Parent being in legal_delete_parents means for the child:
|
| - // a recursive delete is not currently happening (no recent deletes in same
|
| - // folder)
|
| - // parent did expect at least one old deleted child
|
| - // parent was not deleted
|
| - for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
|
| - !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
|
| - int64 metahandle = *iter;
|
| - if (HaveItem(metahandle))
|
| - continue;
|
| - syncable::Entry entry(trans_, syncable::GET_BY_HANDLE, metahandle);
|
| - if (entry.GetIsDel()) {
|
| - syncable::Id parent_id = entry.GetParentId();
|
| - if (legal_delete_parents.count(parent_id)) {
|
| - AppendToTraversal(metahandle);
|
| - }
|
| - }
|
| + if (deletion_list.size() + out_->size() > max_entries_)
|
| + break;
|
| }
|
| +
|
| + // We've been gathering deletions in top to bottom order. Now we reverse the
|
| + // order as we prepare the list.
|
| + std::reverse(deletion_list.begin(), deletion_list.end());
|
| + AppendManyToTraversal(deletion_list);
|
| +
|
| + // It's possible that we overcommitted while trying to expand dependent
|
| + // items. If so, truncate the set down to the allowed size.
|
| + if (out_->size() > max_entries_)
|
| + out_->resize(max_entries_);
|
| }
|
|
|
| void OrderCommitIds(
|
|
|