Index: src/org/openstreetmap/josm/data/osm/DataSetMerger.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/DataSetMerger.java	(revision 3405)
+++ src/org/openstreetmap/josm/data/osm/DataSetMerger.java	(working copy)
@@ -6,6 +6,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -39,7 +40,7 @@
      * to relation members) after the first phase of merging
      */
     private final Set<PrimitiveId> objectsWithChildrenToMerge;
-    private final Set<OsmPrimitive> deletedObjectsToUnlink;
+    private final Set<OsmPrimitive> objectsToDelete;
 
     /**
      * constructor
@@ -57,7 +58,7 @@
         conflicts = new ConflictCollection();
         mergedMap = new HashMap<PrimitiveId, PrimitiveId>();
         objectsWithChildrenToMerge = new HashSet<PrimitiveId>();
-        deletedObjectsToUnlink = new HashSet<OsmPrimitive>();
+        objectsToDelete = new HashSet<OsmPrimitive>();
     }
 
     /**
@@ -161,11 +162,61 @@
                 mergeRelationMembers(r);
             }
         }
-        for (OsmPrimitive source: deletedObjectsToUnlink) {
-            OsmPrimitive target = getMergeTarget(source);
-            if (target == null)
-                throw new RuntimeException(tr("Missing merge target for object with id {0}", source.getUniqueId()));
-            targetDataSet.unlinkReferencesToPrimitive(target);
+
+        deleteMarkedObjects();
+    }
+
+    /**
+     * Deleted objects in objectsToDelete set and create conflicts for objects that cannot
+     * be deleted because they're referenced in the target dataset.
+     */
+    protected void deleteMarkedObjects() {
+        boolean flag;
+        do {
+            flag = false;
+            for (Iterator<OsmPrimitive> it = objectsToDelete.iterator();it.hasNext();) {
+                OsmPrimitive target = it.next();
+                OsmPrimitive source = sourceDataSet.getPrimitiveById(target.getPrimitiveId());
+                if (source == null)
+                    throw new RuntimeException(tr("Object of type {0} with id {1} was marked to be deleted, but it's missing in the source dataset",
+                            target.getType(), target.getUniqueId()));
+
+                List<OsmPrimitive> referrers = target.getReferrers();
+                if (referrers.isEmpty()) {
+                    target.setDeleted(true);
+                    target.mergeFrom(source);
+                    it.remove();
+                    flag = true;
+                } else {
+                    for (OsmPrimitive referrer : referrers) {
+                        // If one of object referrers isn't going to be deleted,
+                        // add a conflict and don't delete the object
+                        if (!objectsToDelete.contains(referrer)) {
+                            conflicts.add(target, source);
+                            it.remove();
+                            flag = true;
+                            break;
+                        }
+                    }
+                }
+
+            }
+        } while (flag);
+
+        if (!objectsToDelete.isEmpty()) {
+            // There are some more objects rest in the objectsToDelete set
+            // This can be because of cross-referenced relations.
+            for (OsmPrimitive osm: objectsToDelete) {
+                if (osm instanceof Way) {
+                    ((Way) osm).setNodes(null);
+                } else if (osm instanceof Relation) {
+                    ((Relation) osm).setMembers(null);
+                }
+            }
+            for (OsmPrimitive osm: objectsToDelete) {
+                osm.setDeleted(true);
+                osm.mergeFrom(sourceDataSet.getPrimitiveById(osm.getPrimitiveId()));
+            }
         }
     }
 
@@ -256,7 +307,13 @@
             // target and source are incomplete. Doesn't matter which one to
             // take. We take target.
             //
-        } else if (target.isDeleted() && ! source.isDeleted() && target.getVersion() == source.getVersion()) {
+        } else if (target.isVisible() != source.isVisible() && target.getVersion() == source.getVersion())
+            // Same version, but different "visible" attribute. It indicates a serious problem in datasets.
+            // For example, datasets can be fetched from different OSM servers or badly hand-modified.
+            // We shouldn't merge that datasets.
+            throw new DataIntegrityProblemException(tr("Conflict in 'visible' attribute for object of type {0} with id {1}",
+                    target.getType(), target.getId()));
+        else if (target.isDeleted() && ! source.isDeleted() && target.getVersion() == source.getVersion()) {
             // same version, but target is deleted. Assume target takes precedence
             // otherwise too many conflicts when refreshing from the server
             // but, if source has a referrer that is not in the target dataset there is a conflict
@@ -268,24 +325,16 @@
                     break;
                 }
             }
-        } else if (target.isDeleted() != source.isDeleted()) {
-            // differences in deleted state have to be resolved manually. This can
-            // happen if one layer is merged onto another layer
+        } else if (! target.isModified() && source.isDeleted()) {
+            // target not modified. We can assume that source is the most recent version,
+            // so mark it to be deleted.
             //
-            conflicts.add(target,source);
+            objectsToDelete.add(target);
         } else if (! target.isModified() && source.isModified()) {
             // target not modified. We can assume that source is the most recent version.
-            // clone it into target. But check first, whether source is deleted. if so,
-            // make sure that target is not referenced any more in myDataSet. If it is there
-            // is a conflict
-            if (source.isDeleted()) {
-                if (!target.getReferrers().isEmpty()) {
-                    conflicts.add(target, source);
-                }
-            } else {
-                target.mergeFrom(source);
-                objectsWithChildrenToMerge.add(source.getPrimitiveId());
-            }
+            // clone it into target.
+            target.mergeFrom(source);
+            objectsWithChildrenToMerge.add(source.getPrimitiveId());
         } else if (! target.isModified() && !source.isModified() && target.getVersion() == source.getVersion()) {
             // both not modified. Merge nevertheless.
             // This helps when updating "empty" relations, see #4295
@@ -302,6 +351,11 @@
             if (target.hasEqualSemanticAttributes(source)) {
                 target.setModified(false);
             }
+        } else if (source.isDeleted() != target.isDeleted()) {
+            // target is modified and deleted state differs.
+            // this have to be resolved manually.
+            //
+            conflicts.add(target,source);
         } else if (! target.hasEqualSemanticAttributes(source)) {
             // target is modified and is not semantically equal with source. Can't automatically
             // resolve the differences

