| Index: chrome/browser/sync/engine/conflict_resolver.cc
|
| diff --git a/chrome/browser/sync/engine/conflict_resolver.cc b/chrome/browser/sync/engine/conflict_resolver.cc
|
| index cca0c31d8939b42b18b9cd0db87ec8e9c1230eb8..a0dc796d8fd18f0df63ccfbe02a75c45720faff4 100644
|
| --- a/chrome/browser/sync/engine/conflict_resolver.cc
|
| +++ b/chrome/browser/sync/engine/conflict_resolver.cc
|
| @@ -5,6 +5,7 @@
|
| #include "chrome/browser/sync/engine/conflict_resolver.h"
|
|
|
| #include <algorithm>
|
| +#include <list>
|
| #include <map>
|
| #include <set>
|
|
|
| @@ -19,6 +20,7 @@
|
| #include "chrome/browser/sync/syncable/syncable.h"
|
| #include "chrome/browser/sync/util/cryptographer.h"
|
|
|
| +using std::list;
|
| using std::map;
|
| using std::set;
|
| using syncable::BaseTransaction;
|
| @@ -110,7 +112,7 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| // b) All unsynced changes have been re-encrypted with the default key (
|
| // occurs either in AttemptToUpdateEntry, SetPassphrase, or
|
| // RefreshEncryption).
|
| - // c) Prev_server_specifics having a valid datatype means that we received
|
| + // c) Base_server_specifics having a valid datatype means that we received
|
| // an undecryptable update that only changed specifics, and since then have
|
| // not received any further non-specifics-only or decryptable updates.
|
| // d) If the server_specifics match specifics, server_specifics are
|
| @@ -123,9 +125,6 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| // f) Otherwise, it's in general safer to ignore local changes, with the
|
| // exception of deletion conflicts (choose to undelete) and conflicts
|
| // where the non_unique_name or parent don't match.
|
| - // TODO(sync): We can't compare position here without performing the
|
| - // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it
|
| - // into account.
|
| if (!entry.Get(syncable::SERVER_IS_DEL)) {
|
| // TODO(nick): The current logic is arbitrary; instead, it ought to be made
|
| // consistent with the ModelAssociator behavior for a datatype. It would
|
| @@ -136,8 +135,58 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) ==
|
| entry.Get(syncable::SERVER_NON_UNIQUE_NAME);
|
| bool parent_matches = entry.Get(syncable::PARENT_ID) ==
|
| - entry.Get(syncable::SERVER_PARENT_ID);
|
| + entry.Get(syncable::SERVER_PARENT_ID);
|
| bool entry_deleted = entry.Get(syncable::IS_DEL);
|
| +
|
| + // This positional check is meant to be necessary but not sufficient. As a
|
| + // result, it may be false even when the position hasn't changed, possibly
|
| + // resulting in unnecessary commits, but if it's true the position has
|
| + // definitely not changed. The check works by verifying that the prev id
|
| + // as calculated from the server position (which will ignore any
|
| + // unsynced/unapplied predecessors and be root for non-bookmark datatypes)
|
| + // matches the client prev id. Because we traverse chains of conflicting
|
| + // items in predecessor -> successor order, we don't need to also verify the
|
| + // successor matches (If it's in conflict, we'll verify it next. If it's
|
| + // not, then it should be taken into account already in the
|
| + // ComputePrevIdFromServerPosition calculation). This works even when there
|
| + // are chains of conflicting items.
|
| + //
|
| + // Example: Original sequence was abcde. Server changes to aCDbe, while
|
| + // client changes to aDCbe (C and D are in conflict). Locally, D's prev id
|
| + // is a, while C's prev id is D. On the other hand, the server prev id will
|
| + // ignore unsynced/unapplied items, so D's server prev id will also be a,
|
| + // just like C's. Because we traverse in client predecessor->successor
|
| + // order, we evaluate D first. Since prev id and server id match, we
|
| + // consider the position to have remained the same for D, and will unset
|
| + // it's UNSYNCED/UNAPPLIED bits. When we evaluate C though, we'll see that
|
| + // the prev id is D locally while the server's prev id is a. C will
|
| + // therefore count as a positional conflict (and the local data will be
|
| + // overwritten by the server data typically). The final result will be
|
| + // aCDbe (the same as the server's view). Even though both C and D were
|
| + // modified, only one counted as being in actual conflict and was resolved
|
| + // with local/server wins.
|
| + //
|
| + // In general, when there are chains of positional conflicts, only the first
|
| + // item in chain (based on the clients point of view) will have both it's
|
| + // server prev id and local prev id match. For all the rest the server prev
|
| + // id will be the predecessor of the first item in the chain, and therefore
|
| + // not match the local prev id.
|
| + //
|
| + // Similarly, chains of conflicts where the server and client info are the
|
| + // same are supported due to the predecessor->successor ordering. In this
|
| + // case, from the first item onward, we unset the UNSYNCED/UNAPPLIED bits as
|
| + // we decide that nothing changed. The subsequent item's server prev id will
|
| + // accurately match the local prev id because the predecessor is no longer
|
| + // UNSYNCED/UNAPPLIED.
|
| + // TODO(zea): simplify all this once we can directly compare server position
|
| + // to client position.
|
| + syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition(
|
| + entry.Get(syncable::SERVER_PARENT_ID));
|
| + bool position_matches = parent_matches &&
|
| + server_prev_id == entry.Get(syncable::PREV_ID);
|
| + DVLOG_IF(1, !position_matches) << "Position doesn't match, server prev id "
|
| + << " is " << server_prev_id << ", local prev id is "
|
| + << entry.Get(syncable::PREV_ID);
|
| const sync_pb::EntitySpecifics& specifics =
|
| entry.Get(syncable::SPECIFICS);
|
| const sync_pb::EntitySpecifics& server_specifics =
|
| @@ -218,15 +267,14 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| NIGORI_MERGE,
|
| CONFLICT_RESOLUTION_SIZE);
|
| } else if (!entry_deleted && name_matches && parent_matches &&
|
| - specifics_match) {
|
| + position_matches && specifics_match) {
|
| DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
|
| << "changes for: " << entry;
|
| // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the
|
| // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset
|
| - // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data
|
| - // when the server update gets applied and the item is re-inserted into
|
| - // the PREV_ID/NEXT_ID linked list (see above TODO about comparing
|
| - // positional info).
|
| + // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data from
|
| + // adjacent entries when the server update gets applied and the item is
|
| + // re-inserted into the PREV_ID/NEXT_ID linked list.
|
| OverwriteServerChanges(trans, &entry);
|
| IgnoreLocalChanges(&entry);
|
| UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| @@ -240,9 +288,6 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| IGNORE_ENCRYPTION,
|
| CONFLICT_RESOLUTION_SIZE);
|
| - // Now that we've resolved the conflict, clear the prev server
|
| - // specifics.
|
| - entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
|
| } else if (entry_deleted || !name_matches || !parent_matches) {
|
| OverwriteServerChanges(trans, &entry);
|
| status->increment_num_server_overwrites();
|
| @@ -260,6 +305,9 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| OVERWRITE_LOCAL,
|
| CONFLICT_RESOLUTION_SIZE);
|
| }
|
| + // Now that we've resolved the conflict, clear the prev server
|
| + // specifics.
|
| + entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
|
| return SYNC_PROGRESS;
|
| } else { // SERVER_IS_DEL is true
|
| // If a server deleted folder has local contents we should be in a set.
|
| @@ -318,21 +366,46 @@ bool ConflictResolver::ResolveSimpleConflicts(
|
| bool forward_progress = false;
|
| // First iterate over simple conflict items (those that belong to no set).
|
| set<Id>::const_iterator conflicting_item_it;
|
| + set<Id> processed_items;
|
| for (conflicting_item_it = progress.ConflictingItemsBegin();
|
| conflicting_item_it != progress.ConflictingItemsEnd();
|
| ++conflicting_item_it) {
|
| Id id = *conflicting_item_it;
|
| + if (processed_items.count(id) > 0)
|
| + continue;
|
| map<Id, ConflictSet*>::const_iterator item_set_it =
|
| progress.IdToConflictSetFind(id);
|
| if (item_set_it == progress.IdToConflictSetEnd() ||
|
| 0 == item_set_it->second) {
|
| - // We have a simple conflict.
|
| - switch (ProcessSimpleConflict(trans, id, cryptographer, status)) {
|
| - case NO_SYNC_PROGRESS:
|
| - break;
|
| - case SYNC_PROGRESS:
|
| - forward_progress = true;
|
| + // We have a simple conflict. In order check if positions have changed,
|
| + // we need to process conflicting predecessors before successors. Traverse
|
| + // backwards through all continuous conflicting predecessors, building a
|
| + // stack of items to resolve in predecessor->successor order, then process
|
| + // each item individually.
|
| + list<Id> predecessors;
|
| + Id prev_id = id;
|
| + do {
|
| + predecessors.push_back(prev_id);
|
| + Entry entry(trans, syncable::GET_BY_ID, prev_id);
|
| + // Any entry in conflict must be valid.
|
| + CHECK(entry.good());
|
| + Id new_prev_id = entry.Get(syncable::PREV_ID);
|
| + if (new_prev_id == prev_id)
|
| break;
|
| + prev_id = new_prev_id;
|
| + } while (processed_items.count(prev_id) == 0 &&
|
| + progress.HasSimpleConflictItem(prev_id)); // Excludes root.
|
| + while (!predecessors.empty()) {
|
| + id = predecessors.back();
|
| + predecessors.pop_back();
|
| + switch (ProcessSimpleConflict(trans, id, cryptographer, status)) {
|
| + case NO_SYNC_PROGRESS:
|
| + break;
|
| + case SYNC_PROGRESS:
|
| + forward_progress = true;
|
| + break;
|
| + }
|
| + processed_items.insert(id);
|
| }
|
| }
|
| }
|
|
|