| Index: sync/engine/syncer_util.cc
|
| diff --git a/sync/engine/syncer_util.cc b/sync/engine/syncer_util.cc
|
| index 7f63b37ddbdeeeb0a654b54a12ef6a597b67e34a..31ab478cb3dc4ece2021985dd6dae6c9465f193f 100644
|
| --- a/sync/engine/syncer_util.cc
|
| +++ b/sync/engine/syncer_util.cc
|
| @@ -184,7 +184,23 @@ syncable::Id FindLocalIdToUpdate(
|
|
|
| return local_entry.GetId();
|
| }
|
| + } else if (update.has_server_defined_unique_tag() &&
|
| + !update.server_defined_unique_tag().empty()) {
|
| + // The client creates type root folders with a local ID on demand when a
|
| + // progress marker for the given type is initially set.
|
| + // The server might also attempt to send a type root folder for the same
|
| + // type (during the transition period until support for root folders is
|
| + // removed for new client versions).
|
| + // TODO(stanisc): crbug.com/438313: remove this once the transition to
|
| + // implicit root folders on the server is done.
|
| + syncable::Entry local_entry(trans, syncable::GET_BY_SERVER_TAG,
|
| + update.server_defined_unique_tag());
|
| + if (local_entry.good() && !local_entry.GetId().ServerKnows()) {
|
| + DCHECK(local_entry.GetId() != update_id);
|
| + return local_entry.GetId();
|
| + }
|
| }
|
| +
|
| // Fallback: target an entry having the server ID, creating one if needed.
|
| return update_id;
|
| }
|
| @@ -198,6 +214,7 @@ UpdateAttemptResponse AttemptToUpdateEntry(
|
| return SUCCESS; // No work to do.
|
| syncable::Id id = entry->GetId();
|
| const sync_pb::EntitySpecifics& specifics = entry->GetServerSpecifics();
|
| + ModelType type = GetModelTypeFromSpecifics(specifics);
|
|
|
| // Only apply updates that we can decrypt. If we can't decrypt the update, it
|
| // is likely because the passphrase has not arrived yet. Because the
|
| @@ -227,23 +244,30 @@ UpdateAttemptResponse AttemptToUpdateEntry(
|
|
|
| if (!entry->GetServerIsDel()) {
|
| syncable::Id new_parent = entry->GetServerParentId();
|
| - Entry parent(trans, GET_BY_ID, new_parent);
|
| - // A note on non-directory parents:
|
| - // We catch most unfixable tree invariant errors at update receipt time,
|
| - // however we deal with this case here because we may receive the child
|
| - // first then the illegal parent. Instead of dealing with it twice in
|
| - // different ways we deal with it once here to reduce the amount of code and
|
| - // potential errors.
|
| - if (!parent.good() || parent.GetIsDel() || !parent.GetIsDir()) {
|
| - DVLOG(1) << "Entry has bad parent, returning conflict_hierarchy.";
|
| - return CONFLICT_HIERARCHY;
|
| - }
|
| - if (entry->GetParentId() != new_parent) {
|
| - if (!entry->GetIsDel() && !IsLegalNewParent(trans, id, new_parent)) {
|
| - DVLOG(1) << "Not updating item " << id
|
| - << ", illegal new parent (would cause loop).";
|
| + if (!new_parent.IsNull()) {
|
| + // Perform this step only if the parent is specified.
|
| + // The unset parent means that the implicit type root would be used.
|
| + Entry parent(trans, GET_BY_ID, new_parent);
|
| + // A note on non-directory parents:
|
| + // We catch most unfixable tree invariant errors at update receipt time,
|
| + // however we deal with this case here because we may receive the child
|
| + // first then the illegal parent. Instead of dealing with it twice in
|
| + // different ways we deal with it once here to reduce the amount of code
|
| + // and potential errors.
|
| + if (!parent.good() || parent.GetIsDel() || !parent.GetIsDir()) {
|
| + DVLOG(1) << "Entry has bad parent, returning conflict_hierarchy.";
|
| return CONFLICT_HIERARCHY;
|
| }
|
| + if (entry->GetParentId() != new_parent) {
|
| + if (!entry->GetIsDel() && !IsLegalNewParent(trans, id, new_parent)) {
|
| + DVLOG(1) << "Not updating item " << id
|
| + << ", illegal new parent (would cause loop).";
|
| + return CONFLICT_HIERARCHY;
|
| + }
|
| + }
|
| + } else {
|
| + // new_parent is unset.
|
| + DCHECK(!IsTypeWithServerGeneratedRoot(type));
|
| }
|
| } else if (entry->GetIsDir()) {
|
| Directory::Metahandles handles;
|
|
|