Index: core/src/org/openstreetmap/josm/actions/MergeLayerAction.java
===================================================================
--- core/src/org/openstreetmap/josm/actions/MergeLayerAction.java	(revision 4666)
+++ core/src/org/openstreetmap/josm/actions/MergeLayerAction.java	(working copy)
@@ -38,7 +38,7 @@
         Main.map.mapView.setActiveLayer(targetLayer);
     }
 
-    public void merge(Layer sourceLayer) {
+    public void merge(final Layer sourceLayer) {
         if (sourceLayer == null)
             return;
         List<Layer> targetLayers = LayerListDialog.getInstance().getModel().getPossibleMergeTargets(sourceLayer);
@@ -46,12 +46,17 @@
             warnNoTargetLayersForSourceLayer(sourceLayer);
             return;
         }
-        Layer targetLayer = askTargetLayer(targetLayers);
+        final Layer targetLayer = askTargetLayer(targetLayers);
         if (targetLayer == null)
             return;
-        targetLayer.mergeFrom(sourceLayer);
-        Main.map.mapView.removeLayer(sourceLayer);
-        Main.map.mapView.setActiveLayer(targetLayer);
+        Main.worker.submit(new Runnable() {
+            @Override
+            public void run() {
+                targetLayer.mergeFrom(sourceLayer);
+                Main.map.mapView.removeLayer(sourceLayer);
+                Main.map.mapView.setActiveLayer(targetLayer);
+            }
+        });
     }
 
     public void actionPerformed(ActionEvent e) {
Index: core/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java
===================================================================
--- core/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(revision 4666)
+++ core/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java	(working copy)
@@ -646,7 +646,11 @@
      * @return true if other isn't null and has the same tags (key/value-pairs) as this.
      */
     public boolean hasSameTags(OsmPrimitive other) {
-        return getKeys().equals(other.getKeys());
+        // We cannot directly use Arrays.equals(keys, other.keys) as keys is not ordered by key
+        // but we can at least check if both arrays are null or of the same size before creating 
+        // and comparing the key maps (costly operation, see #7159)
+        return (keys == null && other.keys == null) 
+            || (keys != null && other.keys != null && keys.length == other.keys.length && (keys.length == 0 || getKeys().equals(other.getKeys())));
     }
 
     /**
Index: core/src/org/openstreetmap/josm/data/osm/DataSetMerger.java
===================================================================
--- core/src/org/openstreetmap/josm/data/osm/DataSetMerger.java	(revision 4666)
+++ core/src/org/openstreetmap/josm/data/osm/DataSetMerger.java	(working copy)
@@ -15,6 +15,7 @@
 
 import org.openstreetmap.josm.data.conflict.Conflict;
 import org.openstreetmap.josm.data.conflict.ConflictCollection;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 
 /**
@@ -76,7 +77,7 @@
      * @param <P>  the type of the other primitive
      * @param source  the other primitive
      */
-    protected void mergePrimitive(OsmPrimitive source) {
+    protected void mergePrimitive(OsmPrimitive source, Collection<? extends OsmPrimitive> candidates) {
         if (!source.isNew() ) {
             // try to merge onto a matching primitive with the same
             // defined id
@@ -93,13 +94,6 @@
             // try to merge onto a primitive  which has no id assigned
             // yet but which is equal in its semantic attributes
             //
-            Collection<? extends OsmPrimitive> candidates = null;
-            switch (source.getType()) {
-            case NODE: candidates = targetDataSet.getNodes(); break;
-            case WAY: candidates  = targetDataSet.getWays(); break;
-            case RELATION: candidates = targetDataSet.getRelations(); break;
-            default: throw new AssertionError();
-            }
             for (OsmPrimitive target : candidates) {
                 if (!target.isNew() || target.isDeleted()) {
                     continue;
@@ -381,23 +375,54 @@
      * See {@see #getConflicts()} for a map of conflicts after the merge operation.
      */
     public void merge() {
+        merge(null);
+    }
+
+    /**
+     * Runs the merge operation. Successfully merged {@see OsmPrimitive}s are in
+     * {@see #getMyDataSet()}.
+     *
+     * See {@see #getConflicts()} for a map of conflicts after the merge operation.
+     */
+    public void merge(ProgressMonitor progressMonitor) {
         if (sourceDataSet == null)
             return;
+        if (progressMonitor != null) {
+            progressMonitor.beginTask(tr("Merging data..."), sourceDataSet.allPrimitives().size());
+        }
         targetDataSet.beginUpdate();
         try {
+        	ArrayList<? extends OsmPrimitive> candidates = new ArrayList<Node>(targetDataSet.getNodes());
             for (Node node: sourceDataSet.getNodes()) {
-                mergePrimitive(node);
+                mergePrimitive(node, candidates);
+                if (progressMonitor != null) {
+                    progressMonitor.worked(1);
+                }
             }
+            candidates.clear();
+            candidates = new ArrayList<Way>(targetDataSet.getWays());
             for (Way way: sourceDataSet.getWays()) {
-                mergePrimitive(way);
+                mergePrimitive(way, candidates);
+                if (progressMonitor != null) {
+                    progressMonitor.worked(1);
+                }
             }
+            candidates.clear();
+            candidates = new ArrayList<Relation>(targetDataSet.getRelations());
             for (Relation relation: sourceDataSet.getRelations()) {
-                mergePrimitive(relation);
+                mergePrimitive(relation, candidates);
+                if (progressMonitor != null) {
+                    progressMonitor.worked(1);
+                }
             }
+            candidates.clear();
             fixReferences();
         } finally {
             targetDataSet.endUpdate();
         }
+        if (progressMonitor != null) {
+            progressMonitor.finishTask();
+        }
     }
 
     /**
Index: core/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
===================================================================
--- core/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(revision 4666)
+++ core/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java	(working copy)
@@ -965,7 +965,8 @@
     public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
         if (!isNew() &&  id != other.id)
             return false;
-        if (isIncomplete() && ! other.isIncomplete() || !isIncomplete()  && other.isIncomplete())
+//        if (isIncomplete() && ! other.isIncomplete() || !isIncomplete()  && other.isIncomplete())
+        if (isIncomplete() ^ other.isIncomplete()) // exclusive or operator for performance (see #7159)
             return false;
         // can't do an equals check on the internal keys array because it is not ordered
         //
Index: core/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(revision 4666)
+++ core/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java	(working copy)
@@ -70,6 +70,8 @@
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.tools.DateUtils;
 import org.openstreetmap.josm.tools.FilteredCollection;
 import org.openstreetmap.josm.tools.GBC;
@@ -281,7 +283,10 @@
     }
 
     @Override public void mergeFrom(final Layer from) {
-        mergeFrom(((OsmDataLayer)from).data);
+        final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging layers"));
+        monitor.setCancelable(false);
+        mergeFrom(((OsmDataLayer)from).data, monitor);
+        monitor.close();
     }
 
     /**
@@ -291,9 +296,19 @@
      * @param from  the source data set
      */
     public void mergeFrom(final DataSet from) {
+        mergeFrom(from, null);
+    }
+    
+    /**
+     * merges the primitives in dataset <code>from</code> into the dataset of
+     * this layer
+     *
+     * @param from  the source data set
+     */
+    public void mergeFrom(final DataSet from, ProgressMonitor progressMonitor) {
         final DataSetMerger visitor = new DataSetMerger(data,from);
         try {
-            visitor.merge();
+            visitor.merge(progressMonitor);
         } catch (DataIntegrityProblemException e) {
             JOptionPane.showMessageDialog(
                     Main.parent,
Index: core/src/org/openstreetmap/josm/gui/progress/PleaseWaitProgressMonitor.java
===================================================================
--- core/src/org/openstreetmap/josm/gui/progress/PleaseWaitProgressMonitor.java	(revision 4666)
+++ core/src/org/openstreetmap/josm/gui/progress/PleaseWaitProgressMonitor.java	(working copy)
@@ -25,6 +25,8 @@
 
     private PleaseWaitDialog dialog;
     private String windowTitle;
+    
+    private boolean cancelable;
 
     public PleaseWaitProgressMonitor() {
         this("");
@@ -38,6 +40,7 @@
     public PleaseWaitProgressMonitor(Component dialogParent) {
         super(new CancelHandler());
         this.dialogParent = JOptionPane.getFrameForComponent(dialogParent);
+        this.cancelable = true;
     }
 
     public PleaseWaitProgressMonitor(Component dialogParent, String windowTitle) {
@@ -57,6 +60,14 @@
         }
     };
 
+    public final boolean isCancelable() {
+        return cancelable;
+    }
+
+    public final void setCancelable(boolean cancelable) {
+        this.cancelable = cancelable;
+    }
+
     private void doInEDT(Runnable runnable) {
         EventQueue.invokeLater(runnable);
     }
@@ -75,7 +86,7 @@
                 if (windowTitle != null) {
                     dialog.setTitle(windowTitle);
                 }
-                dialog.setCancelEnabled(true);
+                dialog.setCancelEnabled(cancelable);
                 dialog.setCancelCallback(cancelListener);
                 dialog.setCustomText("");
                 dialog.addWindowListener(windowListener);
