| OLD | NEW |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/sync/engine/conflict_resolver.h" | 5 #include "chrome/browser/sync/engine/conflict_resolver.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <map> | 8 #include <map> |
| 9 #include <set> | 9 #include <set> |
| 10 | 10 |
| 11 #include "base/location.h" | 11 #include "base/location.h" |
| 12 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
| 13 #include "chrome/browser/sync/engine/syncer.h" | 13 #include "chrome/browser/sync/engine/syncer.h" |
| 14 #include "chrome/browser/sync/engine/syncer_util.h" | 14 #include "chrome/browser/sync/engine/syncer_util.h" |
| 15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" | 15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" |
| 16 #include "chrome/browser/sync/protocol/service_constants.h" | 16 #include "chrome/browser/sync/protocol/service_constants.h" |
| 17 #include "chrome/browser/sync/sessions/status_controller.h" | 17 #include "chrome/browser/sync/sessions/status_controller.h" |
| 18 #include "chrome/browser/sync/syncable/directory_manager.h" | 18 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 19 #include "chrome/browser/sync/syncable/syncable.h" | 19 #include "chrome/browser/sync/syncable/syncable.h" |
| 20 #include "chrome/browser/sync/util/cryptographer.h" | 20 #include "chrome/browser/sync/util/cryptographer.h" |
| 21 | 21 |
| 22 using std::map; | 22 using std::map; |
| 23 using std::set; | 23 using std::set; |
| 24 using syncable::BaseTransaction; | 24 using syncable::BaseTransaction; |
| 25 using syncable::Directory; | 25 using syncable::Directory; |
| 26 using syncable::Entry; | 26 using syncable::Entry; |
| 27 using syncable::GetModelTypeFromSpecifics; |
| 27 using syncable::Id; | 28 using syncable::Id; |
| 29 using syncable::IsRealDataType; |
| 28 using syncable::MutableEntry; | 30 using syncable::MutableEntry; |
| 29 using syncable::ScopedDirLookup; | 31 using syncable::ScopedDirLookup; |
| 30 using syncable::WriteTransaction; | 32 using syncable::WriteTransaction; |
| 31 | 33 |
| 32 namespace browser_sync { | 34 namespace browser_sync { |
| 33 | 35 |
| 34 using sessions::ConflictProgress; | 36 using sessions::ConflictProgress; |
| 35 using sessions::StatusController; | 37 using sessions::StatusController; |
| 36 | 38 |
| 37 namespace { | 39 namespace { |
| 38 | 40 |
| 39 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; | 41 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; |
| 40 | 42 |
| 41 // Enumeration of different conflict resolutions. Used for histogramming. | |
| 42 enum SimpleConflictResolutions { | |
| 43 OVERWRITE_LOCAL, // Resolved by overwriting local changes. | |
| 44 OVERWRITE_SERVER, // Resolved by overwriting server changes. | |
| 45 UNDELETE, // Resolved by undeleting local item. | |
| 46 IGNORE_ENCRYPTION, // Resolved by ignoring an encryption-only server | |
| 47 // change. TODO(zea): implement and use this. | |
| 48 NIGORI_MERGE, // Resolved by merging nigori nodes. | |
| 49 CONFLICT_RESOLUTION_SIZE, | |
| 50 }; | |
| 51 | |
| 52 } // namespace | 43 } // namespace |
| 53 | 44 |
| 54 ConflictResolver::ConflictResolver() { | 45 ConflictResolver::ConflictResolver() { |
| 55 } | 46 } |
| 56 | 47 |
| 57 ConflictResolver::~ConflictResolver() { | 48 ConflictResolver::~ConflictResolver() { |
| 58 } | 49 } |
| 59 | 50 |
| 60 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { | 51 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { |
| 61 // An update matches local actions, merge the changes. | 52 // An update matches local actions, merge the changes. |
| 62 // This is a little fishy because we don't actually merge them. | 53 // This is a little fishy because we don't actually merge them. |
| 63 // In the future we should do a 3-way merge. | 54 // In the future we should do a 3-way merge. |
| 64 DVLOG(1) << "Resolving conflict by ignoring local changes:" << entry; | |
| 65 // With IS_UNSYNCED false, changes should be merged. | 55 // With IS_UNSYNCED false, changes should be merged. |
| 66 entry->Put(syncable::IS_UNSYNCED, false); | 56 entry->Put(syncable::IS_UNSYNCED, false); |
| 67 } | 57 } |
| 68 | 58 |
| 69 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, | 59 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, |
| 70 MutableEntry * entry) { | 60 MutableEntry * entry) { |
| 71 // This is similar to an overwrite from the old client. | 61 // This is similar to an overwrite from the old client. |
| 72 // This is equivalent to a scenario where we got the update before we'd | 62 // This is equivalent to a scenario where we got the update before we'd |
| 73 // made our local client changes. | 63 // made our local client changes. |
| 74 // TODO(chron): This is really a general property clobber. We clobber | 64 // TODO(chron): This is really a general property clobber. We clobber |
| 75 // the server side property. Perhaps we should actually do property merging. | 65 // the server side property. Perhaps we should actually do property merging. |
| 76 DVLOG(1) << "Resolving conflict by ignoring server changes:" << entry; | |
| 77 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); | 66 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); |
| 78 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); | 67 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); |
| 79 } | 68 } |
| 80 | 69 |
| 81 ConflictResolver::ProcessSimpleConflictResult | 70 ConflictResolver::ProcessSimpleConflictResult |
| 82 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, | 71 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
| 83 const Id& id, | 72 const Id& id, |
| 84 const Cryptographer* cryptographer, | 73 const Cryptographer* cryptographer, |
| 85 StatusController* status) { | 74 StatusController* status) { |
| 86 MutableEntry entry(trans, syncable::GET_BY_ID, id); | 75 MutableEntry entry(trans, syncable::GET_BY_ID, id); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 107 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { | 96 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { |
| 108 // we've both deleted it, so lets just drop the need to commit/update this | 97 // we've both deleted it, so lets just drop the need to commit/update this |
| 109 // entry. | 98 // entry. |
| 110 entry.Put(syncable::IS_UNSYNCED, false); | 99 entry.Put(syncable::IS_UNSYNCED, false); |
| 111 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); | 100 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
| 112 // we've made changes, but they won't help syncing progress. | 101 // we've made changes, but they won't help syncing progress. |
| 113 // METRIC simple conflict resolved by merge. | 102 // METRIC simple conflict resolved by merge. |
| 114 return NO_SYNC_PROGRESS; | 103 return NO_SYNC_PROGRESS; |
| 115 } | 104 } |
| 116 | 105 |
| 106 // This logic determines "client wins" vs. "server wins" strategy picking. |
| 107 // By the time we get to this point, we rely on the following to be true: |
| 108 // a) We can decrypt both the local and server data (else we'd be in |
| 109 // conflict encryption and not attempting to resolve). |
| 110 // b) All unsynced changes have been re-encrypted with the default key ( |
| 111 // occurs either in AttemptToUpdateEntry, SetPassphrase, or |
| 112 // RefreshEncryption). |
| 113 // c) Prev_server_specifics having a valid datatype means that we received |
| 114 // an undecryptable update that only changed specifics, and since then have |
| 115 // not received any further non-specifics-only or decryptable updates. |
| 116 // d) If the server_specifics match specifics, server_specifics are |
| 117 // encrypted with the default key, and all other visible properties match, |
| 118 // then we can safely ignore the local changes as redundant. |
| 119 // e) Otherwise if the base_server_specifics match the server_specifics, no |
| 120 // functional change must have been made server-side (else |
| 121 // base_server_specifics would have been cleared), and we can therefore |
| 122 // safely ignore the server changes as redundant. |
| 123 // f) Otherwise, it's in general safer to ignore local changes, with the |
| 124 // exception of deletion conflicts (choose to undelete) and conflicts |
| 125 // where the non_unique_name or parent don't match. |
| 126 // TODO(sync): We can't compare position here without performing the |
| 127 // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it |
| 128 // into account. |
| 117 if (!entry.Get(syncable::SERVER_IS_DEL)) { | 129 if (!entry.Get(syncable::SERVER_IS_DEL)) { |
| 118 // This logic determines "client wins" vs. "server wins" strategy picking. | |
| 119 // TODO(nick): The current logic is arbitrary; instead, it ought to be made | 130 // TODO(nick): The current logic is arbitrary; instead, it ought to be made |
| 120 // consistent with the ModelAssociator behavior for a datatype. It would | 131 // consistent with the ModelAssociator behavior for a datatype. It would |
| 121 // be nice if we could route this back to ModelAssociator code to pick one | 132 // be nice if we could route this back to ModelAssociator code to pick one |
| 122 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) | 133 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) |
| 123 // are easily mergeable. | 134 // are easily mergeable. |
| 124 // See http://crbug.com/77339. | 135 // See http://crbug.com/77339. |
| 125 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == | 136 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == |
| 126 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); | 137 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); |
| 127 bool parent_matches = entry.Get(syncable::PARENT_ID) == | 138 bool parent_matches = entry.Get(syncable::PARENT_ID) == |
| 128 entry.Get(syncable::SERVER_PARENT_ID); | 139 entry.Get(syncable::SERVER_PARENT_ID); |
| 129 bool entry_deleted = entry.Get(syncable::IS_DEL); | 140 bool entry_deleted = entry.Get(syncable::IS_DEL); |
| 141 const sync_pb::EntitySpecifics& specifics = |
| 142 entry.Get(syncable::SPECIFICS); |
| 143 const sync_pb::EntitySpecifics& server_specifics = |
| 144 entry.Get(syncable::SERVER_SPECIFICS); |
| 145 const sync_pb::EntitySpecifics& base_server_specifics = |
| 146 entry.Get(syncable::BASE_SERVER_SPECIFICS); |
| 147 std::string decrypted_specifics, decrypted_server_specifics; |
| 148 bool specifics_match = false; |
| 149 bool server_encrypted_with_default_key = false; |
| 150 if (specifics.has_encrypted()) { |
| 151 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())); |
| 152 decrypted_specifics = cryptographer->DecryptToString( |
| 153 specifics.encrypted()); |
| 154 } else { |
| 155 decrypted_specifics = specifics.SerializeAsString(); |
| 156 } |
| 157 if (server_specifics.has_encrypted()) { |
| 158 server_encrypted_with_default_key = |
| 159 cryptographer->CanDecryptUsingDefaultKey( |
| 160 server_specifics.encrypted()); |
| 161 decrypted_server_specifics = cryptographer->DecryptToString( |
| 162 server_specifics.encrypted()); |
| 163 } else { |
| 164 decrypted_server_specifics = server_specifics.SerializeAsString(); |
| 165 } |
| 166 if (decrypted_server_specifics == decrypted_specifics && |
| 167 server_encrypted_with_default_key == specifics.has_encrypted()) { |
| 168 specifics_match = true; |
| 169 } |
| 170 bool base_server_specifics_match = false; |
| 171 if (server_specifics.has_encrypted() && |
| 172 IsRealDataType(GetModelTypeFromSpecifics(base_server_specifics))) { |
| 173 std::string decrypted_base_server_specifics; |
| 174 if (!base_server_specifics.has_encrypted()) { |
| 175 decrypted_base_server_specifics = |
| 176 base_server_specifics.SerializeAsString(); |
| 177 } else { |
| 178 decrypted_base_server_specifics = cryptographer->DecryptToString( |
| 179 base_server_specifics.encrypted()); |
| 180 } |
| 181 if (decrypted_server_specifics == decrypted_base_server_specifics) |
| 182 base_server_specifics_match = true; |
| 183 } |
| 130 | 184 |
| 131 // We manually merge nigori data. | 185 // We manually merge nigori data. |
| 132 if (entry.GetModelType() == syncable::NIGORI) { | 186 if (entry.GetModelType() == syncable::NIGORI) { |
| 133 // Create a new set of specifics based on the server specifics (which | 187 // Create a new set of specifics based on the server specifics (which |
| 134 // preserves their encryption keys). | 188 // preserves their encryption keys). |
| 135 sync_pb::EntitySpecifics specifics = | 189 sync_pb::EntitySpecifics specifics = |
| 136 entry.Get(syncable::SERVER_SPECIFICS); | 190 entry.Get(syncable::SERVER_SPECIFICS); |
| 137 sync_pb::NigoriSpecifics* nigori = | 191 sync_pb::NigoriSpecifics* nigori = |
| 138 specifics.MutableExtension(sync_pb::nigori); | 192 specifics.MutableExtension(sync_pb::nigori); |
| 139 // Store the merged set of encrypted types (cryptographer->Update(..) will | 193 // Store the merged set of encrypted types (cryptographer->Update(..) will |
| 140 // have merged the local types already). | 194 // have merged the local types already). |
| 141 cryptographer->UpdateNigoriFromEncryptedTypes(nigori); | 195 cryptographer->UpdateNigoriFromEncryptedTypes(nigori); |
| 142 // The local set of keys is already merged with the server's set within | 196 // The local set of keys is already merged with the server's set within |
| 143 // the cryptographer. If we don't have pending keys we can store the | 197 // the cryptographer. If we don't have pending keys we can store the |
| 144 // merged set back immediately. Else we preserve the server keys and will | 198 // merged set back immediately. Else we preserve the server keys and will |
| 145 // update the nigori when the user provides the pending passphrase via | 199 // update the nigori when the user provides the pending passphrase via |
| 146 // SetPassphrase(..). | 200 // SetPassphrase(..). |
| 147 if (cryptographer->is_ready()) { | 201 if (cryptographer->is_ready()) { |
| 148 cryptographer->GetKeys(nigori->mutable_encrypted()); | 202 cryptographer->GetKeys(nigori->mutable_encrypted()); |
| 149 } | 203 } |
| 150 // TODO(zea): Find a better way of doing this. As it stands, we have to | 204 // TODO(zea): Find a better way of doing this. As it stands, we have to |
| 151 // update this code whenever we add a new non-cryptographer related field | 205 // update this code whenever we add a new non-cryptographer related field |
| 152 // to the nigori node. | 206 // to the nigori node. |
| 153 if (entry.Get(syncable::SPECIFICS).GetExtension(sync_pb::nigori) | 207 if (entry.Get(syncable::SPECIFICS).GetExtension(sync_pb::nigori) |
| 154 .sync_tabs()) { | 208 .sync_tabs()) { |
| 155 nigori->set_sync_tabs(true); | 209 nigori->set_sync_tabs(true); |
| 156 } | 210 } |
| 211 // We deliberately leave the server's device information. This client will |
| 212 // add it's own device information on restart. |
| 157 entry.Put(syncable::SPECIFICS, specifics); | 213 entry.Put(syncable::SPECIFICS, specifics); |
| 158 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; | 214 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; |
| 159 status->increment_num_server_overwrites(); | 215 status->increment_num_server_overwrites(); |
| 160 OverwriteServerChanges(trans, &entry); | 216 OverwriteServerChanges(trans, &entry); |
| 161 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 217 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
| 162 NIGORI_MERGE, | 218 NIGORI_MERGE, |
| 163 CONFLICT_RESOLUTION_SIZE); | 219 CONFLICT_RESOLUTION_SIZE); |
| 164 } else if (!entry_deleted && name_matches && parent_matches) { | 220 } else if (!entry_deleted && name_matches && parent_matches && |
| 165 // TODO(zea): We may prefer to choose the local changes over the server | 221 specifics_match) { |
| 166 // if we know the local changes happened before (or vice versa). | 222 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " |
| 167 // See http://crbug.com/76596 | 223 << "changes for: " << entry; |
| 168 DVLOG(1) << "Resolving simple conflict, ignoring local changes for:" | 224 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the |
| 225 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset |
| 226 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data |
| 227 // when the server update gets applied and the item is re-inserted into |
| 228 // the PREV_ID/NEXT_ID linked list (see above TODO about comparing |
| 229 // positional info). |
| 230 OverwriteServerChanges(trans, &entry); |
| 231 IgnoreLocalChanges(&entry); |
| 232 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
| 233 CHANGES_MATCH, |
| 234 CONFLICT_RESOLUTION_SIZE); |
| 235 } else if (base_server_specifics_match) { |
| 236 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " |
| 237 << " changes for: " << entry; |
| 238 status->increment_num_server_overwrites(); |
| 239 OverwriteServerChanges(trans, &entry); |
| 240 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
| 241 IGNORE_ENCRYPTION, |
| 242 CONFLICT_RESOLUTION_SIZE); |
| 243 // Now that we've resolved the conflict, clear the prev server |
| 244 // specifics. |
| 245 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); |
| 246 } else if (entry_deleted || !name_matches || !parent_matches) { |
| 247 OverwriteServerChanges(trans, &entry); |
| 248 status->increment_num_server_overwrites(); |
| 249 DVLOG(1) << "Resolving simple conflict, overwriting server changes " |
| 250 << "for: " << entry; |
| 251 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
| 252 OVERWRITE_SERVER, |
| 253 CONFLICT_RESOLUTION_SIZE); |
| 254 } else { |
| 255 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " |
| 169 << entry; | 256 << entry; |
| 170 IgnoreLocalChanges(&entry); | 257 IgnoreLocalChanges(&entry); |
| 171 status->increment_num_local_overwrites(); | 258 status->increment_num_local_overwrites(); |
| 172 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 259 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
| 173 OVERWRITE_LOCAL, | 260 OVERWRITE_LOCAL, |
| 174 CONFLICT_RESOLUTION_SIZE); | 261 CONFLICT_RESOLUTION_SIZE); |
| 175 } else { | |
| 176 DVLOG(1) << "Resolving simple conflict, overwriting server changes for:" | |
| 177 << entry; | |
| 178 OverwriteServerChanges(trans, &entry); | |
| 179 status->increment_num_server_overwrites(); | |
| 180 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
| 181 OVERWRITE_SERVER, | |
| 182 CONFLICT_RESOLUTION_SIZE); | |
| 183 } | 262 } |
| 184 return SYNC_PROGRESS; | 263 return SYNC_PROGRESS; |
| 185 } else { // SERVER_IS_DEL is true | 264 } else { // SERVER_IS_DEL is true |
| 186 // If a server deleted folder has local contents we should be in a set. | 265 // If a server deleted folder has local contents we should be in a set. |
| 187 if (entry.Get(syncable::IS_DIR)) { | 266 if (entry.Get(syncable::IS_DIR)) { |
| 188 Directory::ChildHandles children; | 267 Directory::ChildHandles children; |
| 189 trans->directory()->GetChildHandlesById(trans, | 268 trans->directory()->GetChildHandlesById(trans, |
| 190 entry.Get(syncable::ID), | 269 entry.Get(syncable::ID), |
| 191 &children); | 270 &children); |
| 192 if (0 != children.size()) { | 271 if (0 != children.size()) { |
| 193 DVLOG(1) << "Entry is a server deleted directory with local contents, " | 272 DVLOG(1) << "Entry is a server deleted directory with local contents, " |
| 194 << "should be in a set. (race condition)."; | 273 << "should be in a set. (race condition)."; |
| 195 return NO_SYNC_PROGRESS; | 274 return NO_SYNC_PROGRESS; |
| 196 } | 275 } |
| 197 } | 276 } |
| 198 | 277 |
| 199 // The entry is deleted on the server but still exists locally. | 278 // The entry is deleted on the server but still exists locally. |
| 200 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { | 279 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { |
| 201 // If we've got a client-unique tag, we can undelete while retaining | 280 // If we've got a client-unique tag, we can undelete while retaining |
| 202 // our present ID. | 281 // our present ID. |
| 203 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " | 282 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " |
| 204 "know to re-create, client-tagged items should revert to version 0 " | 283 "know to re-create, client-tagged items should revert to version 0 " |
| 205 "when server-deleted."; | 284 "when server-deleted."; |
| 206 OverwriteServerChanges(trans, &entry); | 285 OverwriteServerChanges(trans, &entry); |
| 207 status->increment_num_server_overwrites(); | 286 status->increment_num_server_overwrites(); |
| 287 DVLOG(1) << "Resolving simple conflict, undeleting server entry: " |
| 288 << entry; |
| 208 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 289 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
| 209 OVERWRITE_SERVER, | 290 OVERWRITE_SERVER, |
| 210 CONFLICT_RESOLUTION_SIZE); | 291 CONFLICT_RESOLUTION_SIZE); |
| 211 // Clobber the versions, just in case the above DCHECK is violated. | 292 // Clobber the versions, just in case the above DCHECK is violated. |
| 212 entry.Put(syncable::SERVER_VERSION, 0); | 293 entry.Put(syncable::SERVER_VERSION, 0); |
| 213 entry.Put(syncable::BASE_VERSION, 0); | 294 entry.Put(syncable::BASE_VERSION, 0); |
| 214 } else { | 295 } else { |
| 215 // Otherwise, we've got to undelete by creating a new locally | 296 // Otherwise, we've got to undelete by creating a new locally |
| 216 // uncommitted entry. | 297 // uncommitted entry. |
| 217 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); | 298 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 273 if (progress.ConflictSetsSize() > 0) { | 354 if (progress.ConflictSetsSize() > 0) { |
| 274 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() | 355 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() |
| 275 << " non-simple conflicting entries in " << progress.ConflictSetsSize() | 356 << " non-simple conflicting entries in " << progress.ConflictSetsSize() |
| 276 << " unprocessed conflict sets."; | 357 << " unprocessed conflict sets."; |
| 277 } | 358 } |
| 278 | 359 |
| 279 return ResolveSimpleConflicts(trans, cryptographer, progress, status); | 360 return ResolveSimpleConflicts(trans, cryptographer, progress, status); |
| 280 } | 361 } |
| 281 | 362 |
| 282 } // namespace browser_sync | 363 } // namespace browser_sync |
| OLD | NEW |