diff --git a/js/id/actions/merge_remote_changes.js b/js/id/actions/merge_remote_changes.js index 768c6089a..326070b6d 100644 --- a/js/id/actions/merge_remote_changes.js +++ b/js/id/actions/merge_remote_changes.js @@ -63,31 +63,44 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph, formatUser } - function mergeChildNodes(target, children, updates, graph) { + function mergeChildren(target, children, updates, graph) { + function isUsed(node) { + return node.hasInterestingTags() || + graph.parentWays(node).length > 0 || + graph.parentRelations(node).length > 0; + } + var ccount = conflicts.length; for (var i = 0; i < children.length; i++) { var id = children[i], node = graph.hasEntity(id); - // remove unwanted. + // remove unused childNodes. if (target.nodes.indexOf(id) === -1) { - if (node && !node.hasInterestingTags()) updates.removeIds.push(id); + if (node && !isUsed(node)) { + updates.removeIds.push(id); + } continue; } + // restore used childNodes.. var localNode = localGraph.hasEntity(id), - remoteNode = remoteGraph.hasEntity(id); + remoteNode = remoteGraph.hasEntity(id), + targetNode; - // restore wanted.. - if (option === 'force_remote') { - if (remoteNode) updates.replacements.push(remoteNode); - } else if (localNode && remoteNode) { - var targetNode = iD.Entity(localNode, { version: remoteNode.version }); - if (option !== 'force_local') { - targetNode = mergeLocation(remoteNode, targetNode); - if (conflicts.length !== ccount) break; - } + if (remoteNode && option === 'force_remote') { + updates.replacements.push(remoteNode); + + } else if (localNode && option === 'force_local') { + targetNode = iD.Entity(localNode, + { version: (remoteNode ? remoteNode.version : localNode.version + 1) }); + updates.replacements.push(targetNode); + + } else if (localNode && remoteNode && option === 'safe') { + targetNode = iD.Entity(localNode, { version: remoteNode.version }); + targetNode = mergeLocation(remoteNode, targetNode); + if (conflicts.length !== ccount) break; updates.replacements.push(targetNode); } } @@ -96,6 +109,17 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph, formatUser } + function updateChildren(updates, graph) { + for (var i = 0; i < updates.replacements.length; i++) { + graph = graph.replace(updates.replacements[i]); + } + if (updates.removeIds.length) { + graph = iD.actions.DeleteMultiple(updates.removeIds)(graph); + } + return graph; + } + + function mergeMembers(remote, target) { if (option === 'force_local' || _.isEqual(target.members, remote.members)) { return target; @@ -157,19 +181,42 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph, formatUser // `graph.base()` --- ... --- `remoteGraph` // var action = function(graph) { - var base = graph.base().entities[id], + var updates = { replacements: [], removeIds: [] }, + base = graph.base().entities[id], local = localGraph.entity(id), - remote = remoteGraph.entity(id), - target = iD.Entity(local, { version: remote.version }), - updates = { replacements: [], removeIds: [] }; + remote = remoteGraph.hasEntity(id), + target; + + // delete/undelete + if (!remote) { + if (option === 'force_remote') { + return iD.actions.DeleteMultiple([id])(graph); + + } else if (option === 'force_local') { + target = iD.Entity(local, { version: local.version + 1 }); + if (target.type === 'way') { + target = mergeChildren(target, _.uniq(local.nodes), updates, graph); + graph = updateChildren(updates, graph); + } + return graph.replace(target); + + } else { + return graph; // do nothing + } + } + + // merge + target = iD.Entity(local, { version: remote.version }); if (target.type === 'node') { target = mergeLocation(remote, target); + } else if (target.type === 'way') { // pull in any child nodes that may not be present locally.. graph.rebase(remoteGraph.childNodes(remote), [graph], false); target = mergeNodes(base, remote, target); - target = mergeChildNodes(target, _.union(local.nodes, remote.nodes), updates, graph); + target = mergeChildren(target, _.union(local.nodes, remote.nodes), updates, graph); + } else if (target.type === 'relation') { target = mergeMembers(remote, target); } @@ -177,13 +224,7 @@ iD.actions.MergeRemoteChanges = function(id, localGraph, remoteGraph, formatUser target = mergeTags(base, remote, target); if (!conflicts.length) { - graph = graph.replace(target); - for (var i = 0; i < updates.replacements.length; i++) { - graph = graph.replace(updates.replacements[i]); - } - if (updates.removeIds.length) { - graph = iD.actions.DeleteMultiple(updates.removeIds)(graph); - } + graph = updateChildren(updates, graph).replace(target); } return graph; diff --git a/js/id/modes/save.js b/js/id/modes/save.js index 74435d401..2a391b62c 100644 --- a/js/id/modes/save.js +++ b/js/id/modes/save.js @@ -15,7 +15,6 @@ iD.modes.Save = function(context) { remoteGraph = iD.Graph(history.base(), true), modified = _.filter(history.difference().summary(), {changeType: 'modified'}), toCheck = _.pluck(_.pluck(modified, 'entity'), 'id'), - deletedIds = [], conflicts = [], errors = []; @@ -36,6 +35,7 @@ iD.modes.Save = function(context) { if (err) { if (err.status === 410) { // Status: Gone (contains no responseText) + remoteGraph.remove(remoteGraph.hasEntity(id)); addDeleteConflict(id); } else { errors.push({ @@ -58,10 +58,10 @@ iD.modes.Save = function(context) { function addDeleteConflict(id) { - if (deletedIds.indexOf(id) !== -1) return; - else deletedIds.push(id); - - var local = localGraph.entity(id); + var local = localGraph.entity(id), + action = iD.actions.MergeRemoteChanges, + forceLocal = action(id, localGraph, remoteGraph).withOption('force_local'), + forceRemote = action(id, localGraph, remoteGraph).withOption('force_remote'); conflicts.push({ id: id, @@ -69,17 +69,10 @@ iD.modes.Save = function(context) { details: [ t('save.conflict.deleted') ], chosen: 1, choices: [ - choice(id, t('save.conflict.restore'), undelete(local)), - choice(id, t('save.conflict.delete'), iD.actions.DeleteMultiple([id])) + choice(id, t('save.conflict.restore'), forceLocal), + choice(id, t('save.conflict.delete'), forceRemote) ], }); - - function undelete(entity) { - return function(graph) { - var target = iD.Entity(entity, { version: +entity.version + 1 }); - return graph.replace(target); - }; - } } @@ -95,8 +88,8 @@ iD.modes.Save = function(context) { if (diff.length()) return; // merged safely - var forceLocal = action(id, localGraph, remoteGraph, formatUser).withOption('force_local'), - forceRemote = action(id, localGraph, remoteGraph, formatUser).withOption('force_remote'); + var forceLocal = action(id, localGraph, remoteGraph).withOption('force_local'), + forceRemote = action(id, localGraph, remoteGraph).withOption('force_remote'); conflicts.push({ id: id, @@ -308,7 +301,7 @@ iD.modes.Save = function(context) { (i === 1 && index === conflicts.length - 1) || null; }) .on('click', function(d, i) { - var container = parent.select('.conflict-container'), //d3.select(this.parentElement.parentElement.parentElement.parentElement), + var container = parent.select('.conflict-container'), sign = (i === 0 ? -1 : 1); container