| OLD | NEW |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 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/syncer_util.h" | 5 #include "chrome/browser/sync/engine/syncer_util.h" |
| 6 | 6 |
| 7 #include <set> | 7 #include <set> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <vector> | 9 #include <vector> |
| 10 | 10 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 25 using syncable::CHANGES_VERSION; | 25 using syncable::CHANGES_VERSION; |
| 26 using syncable::CREATE; | 26 using syncable::CREATE; |
| 27 using syncable::CREATE_NEW_UPDATE_ITEM; | 27 using syncable::CREATE_NEW_UPDATE_ITEM; |
| 28 using syncable::CTIME; | 28 using syncable::CTIME; |
| 29 using syncable::ComparePathNames; | 29 using syncable::ComparePathNames; |
| 30 using syncable::Directory; | 30 using syncable::Directory; |
| 31 using syncable::Entry; | 31 using syncable::Entry; |
| 32 using syncable::ExtendedAttributeKey; | 32 using syncable::ExtendedAttributeKey; |
| 33 using syncable::GET_BY_HANDLE; | 33 using syncable::GET_BY_HANDLE; |
| 34 using syncable::GET_BY_ID; | 34 using syncable::GET_BY_ID; |
| 35 using syncable::GET_BY_PARENTID_AND_DBNAME; | |
| 36 using syncable::ID; | 35 using syncable::ID; |
| 37 using syncable::IS_BOOKMARK_OBJECT; | 36 using syncable::IS_BOOKMARK_OBJECT; |
| 38 using syncable::IS_DEL; | 37 using syncable::IS_DEL; |
| 39 using syncable::IS_DIR; | 38 using syncable::IS_DIR; |
| 40 using syncable::IS_UNAPPLIED_UPDATE; | 39 using syncable::IS_UNAPPLIED_UPDATE; |
| 41 using syncable::IS_UNSYNCED; | 40 using syncable::IS_UNSYNCED; |
| 42 using syncable::Id; | 41 using syncable::Id; |
| 43 using syncable::META_HANDLE; | 42 using syncable::META_HANDLE; |
| 44 using syncable::MTIME; | 43 using syncable::MTIME; |
| 45 using syncable::MutableEntry; | 44 using syncable::MutableEntry; |
| 46 using syncable::MutableExtendedAttribute; | 45 using syncable::MutableExtendedAttribute; |
| 47 using syncable::NEXT_ID; | 46 using syncable::NEXT_ID; |
| 48 using syncable::Name; | 47 using syncable::NON_UNIQUE_NAME; |
| 49 using syncable::PARENT_ID; | 48 using syncable::PARENT_ID; |
| 50 using syncable::PREV_ID; | 49 using syncable::PREV_ID; |
| 51 using syncable::ReadTransaction; | 50 using syncable::ReadTransaction; |
| 52 using syncable::SERVER_BOOKMARK_FAVICON; | 51 using syncable::SERVER_BOOKMARK_FAVICON; |
| 53 using syncable::SERVER_BOOKMARK_URL; | 52 using syncable::SERVER_BOOKMARK_URL; |
| 54 using syncable::SERVER_CTIME; | 53 using syncable::SERVER_CTIME; |
| 55 using syncable::SERVER_IS_BOOKMARK_OBJECT; | 54 using syncable::SERVER_IS_BOOKMARK_OBJECT; |
| 56 using syncable::SERVER_IS_DEL; | 55 using syncable::SERVER_IS_DEL; |
| 57 using syncable::SERVER_IS_DIR; | 56 using syncable::SERVER_IS_DIR; |
| 58 using syncable::SERVER_MTIME; | 57 using syncable::SERVER_MTIME; |
| 59 using syncable::SERVER_NAME; | 58 using syncable::SERVER_NON_UNIQUE_NAME; |
| 60 using syncable::SERVER_PARENT_ID; | 59 using syncable::SERVER_PARENT_ID; |
| 61 using syncable::SERVER_POSITION_IN_PARENT; | 60 using syncable::SERVER_POSITION_IN_PARENT; |
| 62 using syncable::SERVER_VERSION; | 61 using syncable::SERVER_VERSION; |
| 63 using syncable::SINGLETON_TAG; | 62 using syncable::SINGLETON_TAG; |
| 64 using syncable::SYNCER; | 63 using syncable::SYNCER; |
| 65 using syncable::SyncName; | |
| 66 using syncable::UNSANITIZED_NAME; | |
| 67 using syncable::WriteTransaction; | 64 using syncable::WriteTransaction; |
| 68 | 65 |
| 69 namespace browser_sync { | 66 namespace browser_sync { |
| 70 | 67 |
| 71 using std::string; | 68 using std::string; |
| 72 using std::vector; | 69 using std::vector; |
| 73 | 70 |
| 74 // TODO(ncarter): Remove unique-in-parent title support and name conflicts. | |
| 75 // static | |
| 76 syncable::Id SyncerUtil::GetNameConflictingItemId( | |
| 77 syncable::BaseTransaction* trans, | |
| 78 const syncable::Id& parent_id, | |
| 79 const PathString& server_name) { | |
| 80 | |
| 81 Entry same_path(trans, GET_BY_PARENTID_AND_DBNAME, parent_id, server_name); | |
| 82 if (same_path.good() && !same_path.GetName().HasBeenSanitized()) | |
| 83 return same_path.Get(ID); | |
| 84 Name doctored_name(server_name); | |
| 85 doctored_name.db_value().MakeOSLegal(); | |
| 86 if (!doctored_name.HasBeenSanitized()) | |
| 87 return syncable::kNullId; | |
| 88 Directory::ChildHandles children; | |
| 89 trans->directory()->GetChildHandles(trans, parent_id, &children); | |
| 90 Directory::ChildHandles::iterator i = children.begin(); | |
| 91 while (i != children.end()) { | |
| 92 Entry child_entry(trans, GET_BY_HANDLE, *i++); | |
| 93 CHECK(child_entry.good()); | |
| 94 if (0 == ComparePathNames(child_entry.Get(UNSANITIZED_NAME), server_name)) | |
| 95 return child_entry.Get(ID); | |
| 96 } | |
| 97 return syncable::kNullId; | |
| 98 } | |
| 99 | |
| 100 // Returns the number of unsynced entries. | 71 // Returns the number of unsynced entries. |
| 101 // static | 72 // static |
| 102 int SyncerUtil::GetUnsyncedEntries(syncable::BaseTransaction* trans, | 73 int SyncerUtil::GetUnsyncedEntries(syncable::BaseTransaction* trans, |
| 103 vector<int64> *handles) { | 74 vector<int64> *handles) { |
| 104 trans->directory()->GetUnsyncedMetaHandles(trans, handles); | 75 trans->directory()->GetUnsyncedMetaHandles(trans, handles); |
| 105 LOG_IF(INFO, handles->size() > 0) | 76 LOG_IF(INFO, handles->size() > 0) |
| 106 << "Have " << handles->size() << " unsynced items."; | 77 << "Have " << handles->size() << " unsynced items."; |
| 107 return handles->size(); | 78 return handles->size(); |
| 108 } | 79 } |
| 109 | 80 |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 213 // of this system and just have client side generated IDs as a whole. | 184 // of this system and just have client side generated IDs as a whole. |
| 214 } | 185 } |
| 215 } | 186 } |
| 216 | 187 |
| 217 // static | 188 // static |
| 218 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry( | 189 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntry( |
| 219 syncable::WriteTransaction* const trans, | 190 syncable::WriteTransaction* const trans, |
| 220 syncable::MutableEntry* const entry, | 191 syncable::MutableEntry* const entry, |
| 221 ConflictResolver* resolver) { | 192 ConflictResolver* resolver) { |
| 222 | 193 |
| 223 syncable::Id conflicting_id; | |
| 224 UpdateAttemptResponse result = | |
| 225 AttemptToUpdateEntryWithoutMerge(trans, entry, &conflicting_id); | |
| 226 if (result != NAME_CONFLICT) { | |
| 227 return result; | |
| 228 } | |
| 229 syncable::MutableEntry same_path(trans, syncable::GET_BY_ID, conflicting_id); | |
| 230 CHECK(same_path.good()); | |
| 231 | |
| 232 if (resolver && | |
| 233 resolver->AttemptItemMerge(trans, &same_path, entry)) { | |
| 234 return SUCCESS; | |
| 235 } | |
| 236 LOG(INFO) << "Not updating item, path collision. Update:\n" << *entry | |
| 237 << "\nSame Path:\n" << same_path; | |
| 238 return CONFLICT; | |
| 239 } | |
| 240 | |
| 241 // static | |
| 242 UpdateAttemptResponse SyncerUtil::AttemptToUpdateEntryWithoutMerge( | |
| 243 syncable::WriteTransaction* const trans, | |
| 244 syncable::MutableEntry* const entry, | |
| 245 syncable::Id* const conflicting_id) { | |
| 246 | |
| 247 CHECK(entry->good()); | 194 CHECK(entry->good()); |
| 248 if (!entry->Get(IS_UNAPPLIED_UPDATE)) | 195 if (!entry->Get(IS_UNAPPLIED_UPDATE)) |
| 249 return SUCCESS; // No work to do. | 196 return SUCCESS; // No work to do. |
| 250 syncable::Id id = entry->Get(ID); | 197 syncable::Id id = entry->Get(ID); |
| 251 | 198 |
| 252 if (entry->Get(IS_UNSYNCED)) { | 199 if (entry->Get(IS_UNSYNCED)) { |
| 253 LOG(INFO) << "Skipping update, returning conflict for: " << id | 200 LOG(INFO) << "Skipping update, returning conflict for: " << id |
| 254 << " ; it's unsynced."; | 201 << " ; it's unsynced."; |
| 255 return CONFLICT; | 202 return CONFLICT; |
| 256 } | 203 } |
| 257 if (!entry->Get(SERVER_IS_DEL)) { | 204 if (!entry->Get(SERVER_IS_DEL)) { |
| 258 syncable::Id new_parent = entry->Get(SERVER_PARENT_ID); | 205 syncable::Id new_parent = entry->Get(SERVER_PARENT_ID); |
| 259 Entry parent(trans, GET_BY_ID, new_parent); | 206 Entry parent(trans, GET_BY_ID, new_parent); |
| 260 // A note on non-directory parents: | 207 // A note on non-directory parents: |
| 261 // We catch most unfixable tree invariant errors at update receipt time, | 208 // We catch most unfixable tree invariant errors at update receipt time, |
| 262 // however we deal with this case here because we may receive the child | 209 // however we deal with this case here because we may receive the child |
| 263 // first then the illegal parent. Instead of dealing with it twice in | 210 // first then the illegal parent. Instead of dealing with it twice in |
| 264 // different ways we deal with it once here to reduce the amount of code and | 211 // different ways we deal with it once here to reduce the amount of code and |
| 265 // potential errors. | 212 // potential errors. |
| 266 if (!parent.good() || parent.Get(IS_DEL) || !parent.Get(IS_DIR)) { | 213 if (!parent.good() || parent.Get(IS_DEL) || !parent.Get(IS_DIR)) { |
| 267 return CONFLICT; | 214 return CONFLICT; |
| 268 } | 215 } |
| 269 if (entry->Get(PARENT_ID) != new_parent) { | 216 if (entry->Get(PARENT_ID) != new_parent) { |
| 270 if (!entry->Get(IS_DEL) && !IsLegalNewParent(trans, id, new_parent)) { | 217 if (!entry->Get(IS_DEL) && !IsLegalNewParent(trans, id, new_parent)) { |
| 271 LOG(INFO) << "Not updating item " << id << ", illegal new parent " | 218 LOG(INFO) << "Not updating item " << id << ", illegal new parent " |
| 272 "(would cause loop)."; | 219 "(would cause loop)."; |
| 273 return CONFLICT; | 220 return CONFLICT; |
| 274 } | 221 } |
| 275 } | 222 } |
| 276 PathString server_name = entry->Get(SERVER_NAME); | |
| 277 syncable::Id conflict_id = | |
| 278 SyncerUtil::GetNameConflictingItemId(trans, | |
| 279 entry->Get(SERVER_PARENT_ID), | |
| 280 server_name); | |
| 281 if (conflict_id != syncable::kNullId && conflict_id != id) { | |
| 282 if (conflicting_id) | |
| 283 *conflicting_id = conflict_id; | |
| 284 return NAME_CONFLICT; | |
| 285 } | |
| 286 } else if (entry->Get(IS_DIR)) { | 223 } else if (entry->Get(IS_DIR)) { |
| 287 Directory::ChildHandles handles; | 224 Directory::ChildHandles handles; |
| 288 trans->directory()->GetChildHandles(trans, id, &handles); | 225 trans->directory()->GetChildHandles(trans, id, &handles); |
| 289 if (!handles.empty()) { | 226 if (!handles.empty()) { |
| 290 // If we have still-existing children, then we need to deal with | 227 // If we have still-existing children, then we need to deal with |
| 291 // them before we can process this change. | 228 // them before we can process this change. |
| 292 LOG(INFO) << "Not deleting directory; it's not empty " << *entry; | 229 LOG(INFO) << "Not deleting directory; it's not empty " << *entry; |
| 293 return CONFLICT; | 230 return CONFLICT; |
| 294 } | 231 } |
| 295 } | 232 } |
| 296 | 233 |
| 297 SyncerUtil::UpdateLocalDataFromServerData(trans, entry); | 234 SyncerUtil::UpdateLocalDataFromServerData(trans, entry); |
| 298 | 235 |
| 299 return SUCCESS; | 236 return SUCCESS; |
| 300 } | 237 } |
| 301 | 238 |
| 302 // Pass in name and checksum because of UTF8 conversion. | 239 // Pass in name and checksum because of UTF8 conversion. |
| 303 // static | 240 // static |
| 304 void SyncerUtil::UpdateServerFieldsFromUpdate( | 241 void SyncerUtil::UpdateServerFieldsFromUpdate( |
| 305 MutableEntry* local_entry, | 242 MutableEntry* local_entry, |
| 306 const SyncEntity& server_entry, | 243 const SyncEntity& server_entry, |
| 307 const SyncName& name) { | 244 const PathString& name) { |
| 308 if (server_entry.deleted()) { | 245 if (server_entry.deleted()) { |
| 309 // The server returns very lightweight replies for deletions, so we don't | 246 // The server returns very lightweight replies for deletions, so we don't |
| 310 // clobber a bunch of fields on delete. | 247 // clobber a bunch of fields on delete. |
| 311 local_entry->Put(SERVER_IS_DEL, true); | 248 local_entry->Put(SERVER_IS_DEL, true); |
| 312 local_entry->Put(SERVER_VERSION, | 249 local_entry->Put(SERVER_VERSION, |
| 313 std::max(local_entry->Get(SERVER_VERSION), | 250 std::max(local_entry->Get(SERVER_VERSION), |
| 314 local_entry->Get(BASE_VERSION)) + 1L); | 251 local_entry->Get(BASE_VERSION)) + 1L); |
| 315 local_entry->Put(IS_UNAPPLIED_UPDATE, true); | 252 local_entry->Put(IS_UNAPPLIED_UPDATE, true); |
| 316 return; | 253 return; |
| 317 } | 254 } |
| 318 | 255 |
| 319 CHECK(local_entry->Get(ID) == server_entry.id()) | 256 CHECK(local_entry->Get(ID) == server_entry.id()) |
| 320 << "ID Changing not supported here"; | 257 << "ID Changing not supported here"; |
| 321 local_entry->Put(SERVER_PARENT_ID, server_entry.parent_id()); | 258 local_entry->Put(SERVER_PARENT_ID, server_entry.parent_id()); |
| 322 local_entry->PutServerName(name); | 259 local_entry->Put(SERVER_NON_UNIQUE_NAME, name); |
| 323 local_entry->Put(SERVER_VERSION, server_entry.version()); | 260 local_entry->Put(SERVER_VERSION, server_entry.version()); |
| 324 local_entry->Put(SERVER_CTIME, | 261 local_entry->Put(SERVER_CTIME, |
| 325 ServerTimeToClientTime(server_entry.ctime())); | 262 ServerTimeToClientTime(server_entry.ctime())); |
| 326 local_entry->Put(SERVER_MTIME, | 263 local_entry->Put(SERVER_MTIME, |
| 327 ServerTimeToClientTime(server_entry.mtime())); | 264 ServerTimeToClientTime(server_entry.mtime())); |
| 328 local_entry->Put(SERVER_IS_BOOKMARK_OBJECT, server_entry.has_bookmarkdata()); | 265 local_entry->Put(SERVER_IS_BOOKMARK_OBJECT, server_entry.has_bookmarkdata()); |
| 329 local_entry->Put(SERVER_IS_DIR, server_entry.IsFolder()); | 266 local_entry->Put(SERVER_IS_DIR, server_entry.IsFolder()); |
| 330 if (server_entry.has_singleton_tag()) { | 267 if (server_entry.has_singleton_tag()) { |
| 331 const PathString& tag = server_entry.singleton_tag(); | 268 const PathString& tag = server_entry.singleton_tag(); |
| 332 local_entry->Put(SINGLETON_TAG, tag); | 269 local_entry->Put(SINGLETON_TAG, tag); |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 414 // static | 351 // static |
| 415 bool SyncerUtil::ServerAndLocalEntriesMatch(syncable::Entry* entry) { | 352 bool SyncerUtil::ServerAndLocalEntriesMatch(syncable::Entry* entry) { |
| 416 if (!ClientAndServerTimeMatch( | 353 if (!ClientAndServerTimeMatch( |
| 417 entry->Get(CTIME), ClientTimeToServerTime(entry->Get(SERVER_CTIME)))) { | 354 entry->Get(CTIME), ClientTimeToServerTime(entry->Get(SERVER_CTIME)))) { |
| 418 LOG(WARNING) << "Client and server time mismatch"; | 355 LOG(WARNING) << "Client and server time mismatch"; |
| 419 return false; | 356 return false; |
| 420 } | 357 } |
| 421 if (entry->Get(IS_DEL) && entry->Get(SERVER_IS_DEL)) | 358 if (entry->Get(IS_DEL) && entry->Get(SERVER_IS_DEL)) |
| 422 return true; | 359 return true; |
| 423 // Name should exactly match here. | 360 // Name should exactly match here. |
| 424 if (!entry->SyncNameMatchesServerName()) { | 361 if (!(entry->Get(NON_UNIQUE_NAME) == entry->Get(SERVER_NON_UNIQUE_NAME))) { |
| 425 LOG(WARNING) << "Unsanitized name mismatch"; | 362 LOG(WARNING) << "Unsanitized name mismatch"; |
| 426 return false; | 363 return false; |
| 427 } | 364 } |
| 428 | 365 |
| 429 if (entry->Get(PARENT_ID) != entry->Get(SERVER_PARENT_ID) || | 366 if (entry->Get(PARENT_ID) != entry->Get(SERVER_PARENT_ID) || |
| 430 entry->Get(IS_DIR) != entry->Get(SERVER_IS_DIR) || | 367 entry->Get(IS_DIR) != entry->Get(SERVER_IS_DIR) || |
| 431 entry->Get(IS_DEL) != entry->Get(SERVER_IS_DEL)) { | 368 entry->Get(IS_DEL) != entry->Get(SERVER_IS_DEL)) { |
| 432 LOG(WARNING) << "Metabit mismatch"; | 369 LOG(WARNING) << "Metabit mismatch"; |
| 433 return false; | 370 return false; |
| 434 } | 371 } |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 479 // static | 416 // static |
| 480 void SyncerUtil::UpdateLocalDataFromServerData( | 417 void SyncerUtil::UpdateLocalDataFromServerData( |
| 481 syncable::WriteTransaction* trans, | 418 syncable::WriteTransaction* trans, |
| 482 syncable::MutableEntry* entry) { | 419 syncable::MutableEntry* entry) { |
| 483 CHECK(!entry->Get(IS_UNSYNCED)); | 420 CHECK(!entry->Get(IS_UNSYNCED)); |
| 484 CHECK(entry->Get(IS_UNAPPLIED_UPDATE)); | 421 CHECK(entry->Get(IS_UNAPPLIED_UPDATE)); |
| 485 LOG(INFO) << "Updating entry : " << *entry; | 422 LOG(INFO) << "Updating entry : " << *entry; |
| 486 entry->Put(IS_BOOKMARK_OBJECT, entry->Get(SERVER_IS_BOOKMARK_OBJECT)); | 423 entry->Put(IS_BOOKMARK_OBJECT, entry->Get(SERVER_IS_BOOKMARK_OBJECT)); |
| 487 // This strange dance around the IS_DEL flag avoids problems when setting | 424 // This strange dance around the IS_DEL flag avoids problems when setting |
| 488 // the name. | 425 // the name. |
| 426 // TODO(chron): Is this still an issue? Unit test this codepath. |
| 489 if (entry->Get(SERVER_IS_DEL)) { | 427 if (entry->Get(SERVER_IS_DEL)) { |
| 490 entry->Put(IS_DEL, true); | 428 entry->Put(IS_DEL, true); |
| 491 } else { | 429 } else { |
| 492 Name name = Name::FromSyncName(entry->GetServerName()); | 430 entry->Put(NON_UNIQUE_NAME, entry->Get(SERVER_NON_UNIQUE_NAME)); |
| 493 name.db_value().MakeOSLegal(); | 431 entry->Put(PARENT_ID, entry->Get(SERVER_PARENT_ID)); |
| 494 bool was_doctored_before_made_noncolliding = name.HasBeenSanitized(); | |
| 495 name.db_value().MakeNoncollidingForEntry(trans, | |
| 496 entry->Get(SERVER_PARENT_ID), | |
| 497 entry); | |
| 498 bool was_doctored = name.HasBeenSanitized(); | |
| 499 if (was_doctored) { | |
| 500 // If we're changing the name of entry, either its name should be | |
| 501 // illegal, or some other entry should have an unsanitized name. | |
| 502 // There's should be a CHECK in every code path. | |
| 503 Entry blocking_entry(trans, GET_BY_PARENTID_AND_DBNAME, | |
| 504 entry->Get(SERVER_PARENT_ID), | |
| 505 name.value()); | |
| 506 if (blocking_entry.good()) | |
| 507 CHECK(blocking_entry.GetName().HasBeenSanitized()); | |
| 508 else | |
| 509 CHECK(was_doctored_before_made_noncolliding); | |
| 510 } | |
| 511 CHECK(entry->PutParentIdAndName(entry->Get(SERVER_PARENT_ID), name)) | |
| 512 << "Name Clash in UpdateLocalDataFromServerData: " | |
| 513 << *entry; | |
| 514 CHECK(entry->Put(IS_DEL, false)); | 432 CHECK(entry->Put(IS_DEL, false)); |
| 515 Id new_predecessor = ComputePrevIdFromServerPosition(trans, entry, | 433 Id new_predecessor = ComputePrevIdFromServerPosition(trans, entry, |
| 516 entry->Get(SERVER_PARENT_ID)); | 434 entry->Get(SERVER_PARENT_ID)); |
| 517 CHECK(entry->PutPredecessor(new_predecessor)) | 435 CHECK(entry->PutPredecessor(new_predecessor)) |
| 518 << " Illegal predecessor after converting from server position."; | 436 << " Illegal predecessor after converting from server position."; |
| 519 } | 437 } |
| 520 | 438 |
| 521 entry->Put(CTIME, entry->Get(SERVER_CTIME)); | 439 entry->Put(CTIME, entry->Get(SERVER_CTIME)); |
| 522 entry->Put(MTIME, entry->Get(SERVER_MTIME)); | 440 entry->Put(MTIME, entry->Get(SERVER_MTIME)); |
| 523 entry->Put(BASE_VERSION, entry->Get(SERVER_VERSION)); | 441 entry->Put(BASE_VERSION, entry->Get(SERVER_VERSION)); |
| (...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 825 | 743 |
| 826 // |update_entry| is considered to be somewhere after |candidate|, so store | 744 // |update_entry| is considered to be somewhere after |candidate|, so store |
| 827 // it as the upper bound. | 745 // it as the upper bound. |
| 828 closest_sibling = candidate.Get(ID); | 746 closest_sibling = candidate.Get(ID); |
| 829 } | 747 } |
| 830 | 748 |
| 831 return closest_sibling; | 749 return closest_sibling; |
| 832 } | 750 } |
| 833 | 751 |
| 834 } // namespace browser_sync | 752 } // namespace browser_sync |
| OLD | NEW |