| 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 e37ac36e2b0ae21e5719e15a8a71d4d106ce9297..cca0c31d8939b42b18b9cd0db87ec8e9c1230eb8 100644
|
| --- a/chrome/browser/sync/engine/conflict_resolver.cc
|
| +++ b/chrome/browser/sync/engine/conflict_resolver.cc
|
| @@ -24,7 +24,9 @@ using std::set;
|
| using syncable::BaseTransaction;
|
| using syncable::Directory;
|
| using syncable::Entry;
|
| +using syncable::GetModelTypeFromSpecifics;
|
| using syncable::Id;
|
| +using syncable::IsRealDataType;
|
| using syncable::MutableEntry;
|
| using syncable::ScopedDirLookup;
|
| using syncable::WriteTransaction;
|
| @@ -38,17 +40,6 @@ namespace {
|
|
|
| const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8;
|
|
|
| -// Enumeration of different conflict resolutions. Used for histogramming.
|
| -enum SimpleConflictResolutions {
|
| - OVERWRITE_LOCAL, // Resolved by overwriting local changes.
|
| - OVERWRITE_SERVER, // Resolved by overwriting server changes.
|
| - UNDELETE, // Resolved by undeleting local item.
|
| - IGNORE_ENCRYPTION, // Resolved by ignoring an encryption-only server
|
| - // change. TODO(zea): implement and use this.
|
| - NIGORI_MERGE, // Resolved by merging nigori nodes.
|
| - CONFLICT_RESOLUTION_SIZE,
|
| -};
|
| -
|
| } // namespace
|
|
|
| ConflictResolver::ConflictResolver() {
|
| @@ -61,7 +52,6 @@ void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) {
|
| // An update matches local actions, merge the changes.
|
| // This is a little fishy because we don't actually merge them.
|
| // In the future we should do a 3-way merge.
|
| - DVLOG(1) << "Resolving conflict by ignoring local changes:" << entry;
|
| // With IS_UNSYNCED false, changes should be merged.
|
| entry->Put(syncable::IS_UNSYNCED, false);
|
| }
|
| @@ -73,7 +63,6 @@ void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans,
|
| // made our local client changes.
|
| // TODO(chron): This is really a general property clobber. We clobber
|
| // the server side property. Perhaps we should actually do property merging.
|
| - DVLOG(1) << "Resolving conflict by ignoring server changes:" << entry;
|
| entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION));
|
| entry->Put(syncable::IS_UNAPPLIED_UPDATE, false);
|
| }
|
| @@ -114,8 +103,30 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| return NO_SYNC_PROGRESS;
|
| }
|
|
|
| + // This logic determines "client wins" vs. "server wins" strategy picking.
|
| + // By the time we get to this point, we rely on the following to be true:
|
| + // a) We can decrypt both the local and server data (else we'd be in
|
| + // conflict encryption and not attempting to resolve).
|
| + // 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
|
| + // 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
|
| + // encrypted with the default key, and all other visible properties match,
|
| + // then we can safely ignore the local changes as redundant.
|
| + // e) Otherwise if the base_server_specifics match the server_specifics, no
|
| + // functional change must have been made server-side (else
|
| + // base_server_specifics would have been cleared), and we can therefore
|
| + // safely ignore the server changes as redundant.
|
| + // 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)) {
|
| - // This logic determines "client wins" vs. "server wins" strategy picking.
|
| // TODO(nick): The current logic is arbitrary; instead, it ought to be made
|
| // consistent with the ModelAssociator behavior for a datatype. It would
|
| // be nice if we could route this back to ModelAssociator code to pick one
|
| @@ -127,6 +138,49 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| bool parent_matches = entry.Get(syncable::PARENT_ID) ==
|
| entry.Get(syncable::SERVER_PARENT_ID);
|
| bool entry_deleted = entry.Get(syncable::IS_DEL);
|
| + const sync_pb::EntitySpecifics& specifics =
|
| + entry.Get(syncable::SPECIFICS);
|
| + const sync_pb::EntitySpecifics& server_specifics =
|
| + entry.Get(syncable::SERVER_SPECIFICS);
|
| + const sync_pb::EntitySpecifics& base_server_specifics =
|
| + entry.Get(syncable::BASE_SERVER_SPECIFICS);
|
| + std::string decrypted_specifics, decrypted_server_specifics;
|
| + bool specifics_match = false;
|
| + bool server_encrypted_with_default_key = false;
|
| + if (specifics.has_encrypted()) {
|
| + DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted()));
|
| + decrypted_specifics = cryptographer->DecryptToString(
|
| + specifics.encrypted());
|
| + } else {
|
| + decrypted_specifics = specifics.SerializeAsString();
|
| + }
|
| + if (server_specifics.has_encrypted()) {
|
| + server_encrypted_with_default_key =
|
| + cryptographer->CanDecryptUsingDefaultKey(
|
| + server_specifics.encrypted());
|
| + decrypted_server_specifics = cryptographer->DecryptToString(
|
| + server_specifics.encrypted());
|
| + } else {
|
| + decrypted_server_specifics = server_specifics.SerializeAsString();
|
| + }
|
| + if (decrypted_server_specifics == decrypted_specifics &&
|
| + server_encrypted_with_default_key == specifics.has_encrypted()) {
|
| + specifics_match = true;
|
| + }
|
| + bool base_server_specifics_match = false;
|
| + if (server_specifics.has_encrypted() &&
|
| + IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) {
|
| + std::string decrypted_base_server_specifics;
|
| + if (!base_server_specifics.has_encrypted()) {
|
| + decrypted_base_server_specifics =
|
| + base_server_specifics.SerializeAsString();
|
| + } else {
|
| + decrypted_base_server_specifics = cryptographer->DecryptToString(
|
| + base_server_specifics.encrypted());
|
| + }
|
| + if (decrypted_server_specifics == decrypted_base_server_specifics)
|
| + base_server_specifics_match = true;
|
| + }
|
|
|
| // We manually merge nigori data.
|
| if (entry.GetModelType() == syncable::NIGORI) {
|
| @@ -154,6 +208,8 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| .sync_tabs()) {
|
| nigori->set_sync_tabs(true);
|
| }
|
| + // We deliberately leave the server's device information. This client will
|
| + // add it's own device information on restart.
|
| entry.Put(syncable::SPECIFICS, specifics);
|
| DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry;
|
| status->increment_num_server_overwrites();
|
| @@ -161,25 +217,48 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| NIGORI_MERGE,
|
| CONFLICT_RESOLUTION_SIZE);
|
| - } else if (!entry_deleted && name_matches && parent_matches) {
|
| - // TODO(zea): We may prefer to choose the local changes over the server
|
| - // if we know the local changes happened before (or vice versa).
|
| - // See http://crbug.com/76596
|
| - DVLOG(1) << "Resolving simple conflict, ignoring local changes for:"
|
| - << entry;
|
| + } else if (!entry_deleted && name_matches && parent_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).
|
| + OverwriteServerChanges(trans, &entry);
|
| IgnoreLocalChanges(&entry);
|
| - status->increment_num_local_overwrites();
|
| UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| - OVERWRITE_LOCAL,
|
| + CHANGES_MATCH,
|
| CONFLICT_RESOLUTION_SIZE);
|
| - } else {
|
| - DVLOG(1) << "Resolving simple conflict, overwriting server changes for:"
|
| - << entry;
|
| + } else if (base_server_specifics_match) {
|
| + DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
|
| + << " changes for: " << entry;
|
| + status->increment_num_server_overwrites();
|
| + OverwriteServerChanges(trans, &entry);
|
| + 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();
|
| + DVLOG(1) << "Resolving simple conflict, overwriting server changes "
|
| + << "for: " << entry;
|
| UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| OVERWRITE_SERVER,
|
| CONFLICT_RESOLUTION_SIZE);
|
| + } else {
|
| + DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
|
| + << entry;
|
| + IgnoreLocalChanges(&entry);
|
| + status->increment_num_local_overwrites();
|
| + UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| + OVERWRITE_LOCAL,
|
| + CONFLICT_RESOLUTION_SIZE);
|
| }
|
| return SYNC_PROGRESS;
|
| } else { // SERVER_IS_DEL is true
|
| @@ -205,6 +284,8 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
|
| "when server-deleted.";
|
| OverwriteServerChanges(trans, &entry);
|
| status->increment_num_server_overwrites();
|
| + DVLOG(1) << "Resolving simple conflict, undeleting server entry: "
|
| + << entry;
|
| UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
|
| OVERWRITE_SERVER,
|
| CONFLICT_RESOLUTION_SIZE);
|
|
|