diff --git a/src/org/openstreetmap/josm/actions/MergeSelectionAction.java b/src/org/openstreetmap/josm/actions/MergeSelectionAction.java
index 4c63a0b..c5c4888 100644
--- a/src/org/openstreetmap/josm/actions/MergeSelectionAction.java
+++ b/src/org/openstreetmap/josm/actions/MergeSelectionAction.java
@@ -8,10 +8,11 @@ import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.util.Collection;
 import java.util.List;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.MergeCommand;
 
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.visitor.MergeSourceBuildingVisitor;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
@@ -44,10 +45,12 @@ public class MergeSelectionAction extends AbstractMergeAction {
                 return;
             }
         }
-        MergeSourceBuildingVisitor builder = new MergeSourceBuildingVisitor(getEditLayer().data);
-        ((OsmDataLayer)targetLayer).mergeFrom(builder.build());
+        
+        MergeCommand cmd = new MergeCommand((OsmDataLayer)targetLayer, getEditLayer().data, true);
+        Main.main.undoRedo.add(cmd);
     }
 
+    @Override
     public void actionPerformed(ActionEvent e) {
         if (getEditLayer() == null || getEditLayer().data.getSelected().isEmpty())
             return;
diff --git a/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java b/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
index 09156c7..19b5381 100644
--- a/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
+++ b/src/org/openstreetmap/josm/actions/UpdateSelectionAction.java
@@ -13,6 +13,7 @@ import java.util.Collections;
 import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.DownloadOsmCommand;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
@@ -39,7 +40,7 @@ public class UpdateSelectionAction extends JosmAction {
         reader.append(getCurrentDataSet(),id, type);
         try {
             DataSet ds = reader.parseOsm(NullProgressMonitor.INSTANCE);
-            Main.map.mapView.getEditLayer().mergeFrom(ds);
+            Main.main.undoRedo.add(new DownloadOsmCommand(tr("Update selected primitives"), Main.map.mapView.getEditLayer(), ds));
         } catch(Exception e) {
             ExceptionDialogUtil.explainException(e);
         }
diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java
index 991b381..17879aa 100644
--- a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java
+++ b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java
@@ -10,6 +10,8 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.command.DownloadOsmCommand;
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.DataSet;
@@ -207,9 +209,8 @@ public class DownloadOsmTask extends AbstractDownloadTask {
                 if (targetLayer == null) {
                     targetLayer = getFirstDataLayer();
                 }
-                targetLayer.mergeFrom(dataSet);
-                computeBboxAndCenterScale();
-                targetLayer.onPostDownloadFromServer();
+                Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download primitives from bounding box"), targetLayer, dataSet));
+                AutoScaleAction.zoomTo(dataSet.allPrimitives());
             }
         }
         
diff --git a/src/org/openstreetmap/josm/command/DownloadOsmCommand.java b/src/org/openstreetmap/josm/command/DownloadOsmCommand.java
new file mode 100644
index 0000000..7836f70
--- /dev/null
+++ b/src/org/openstreetmap/josm/command/DownloadOsmCommand.java
@@ -0,0 +1,108 @@
+// License: GPL. Copyright 2012 by Josh Doe and others
+package org.openstreetmap.josm.command;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import javax.swing.Icon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A command that merges a downloaded dataset to a layer.
+ * 
+ * @author joshdoe
+ */
+public class DownloadOsmCommand extends Command {
+    private OsmDataLayer targetLayer;
+    private DataSet sourceDataSet;
+    private MergeCommand mergeCommand;
+    private boolean requiresSaveToFile;
+    private boolean requiresUploadToServer;
+    private String commandSummary;
+
+    public DownloadOsmCommand(String commandSummary, OsmDataLayer targetLayer, DataSet sourceDataSet) {
+        super(targetLayer);
+        this.commandSummary = commandSummary;
+        this.targetLayer = targetLayer;
+        this.sourceDataSet = sourceDataSet;
+        mergeCommand = new MergeCommand(targetLayer, sourceDataSet, false);
+    }
+
+    @Override
+    public boolean executeCommand() {
+        if (!mergeCommand.executeCommand()) {
+            return false;
+        }
+        requiresSaveToFile = targetLayer.requiresSaveToFile();
+        requiresUploadToServer = targetLayer.requiresUploadToServer();
+        targetLayer.setRequiresSaveToFile(true);
+        targetLayer.setRequiresUploadToServer(sourceDataSet.isModified());
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        mergeCommand.undoCommand();
+        boolean oldValue;
+        oldValue = targetLayer.requiresSaveToFile();
+        targetLayer.setRequiresSaveToFile(requiresSaveToFile);
+        requiresSaveToFile = oldValue;
+        oldValue = targetLayer.requiresUploadToServer();
+        targetLayer.setRequiresUploadToServer(requiresUploadToServer);
+        requiresUploadToServer = oldValue;
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return commandSummary;
+    }
+
+    @Override
+    public Icon getDescriptionIcon() {
+        return ImageProvider.get("dialogs", "down");
+    }
+    
+    @Override
+    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
+        return sourceDataSet.allPrimitives();
+    }
+    
+    private class DownloadPseudoCommand extends PseudoCommand {
+
+        @Override
+        public String getDescriptionText() {
+            return tr(marktr("Download {0} nodes, {1} ways, {2} relations"),
+                    sourceDataSet.getNodes().size(),
+                    sourceDataSet.getWays().size(),
+                    sourceDataSet.getRelations().size());
+        }
+
+        @Override
+        public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
+            return sourceDataSet.allPrimitives();
+        }
+        
+        @Override
+        public Icon getDescriptionIcon() {
+            return ImageProvider.get("dialogs", "down");
+        }
+        
+    }
+    @Override
+    public Collection<PseudoCommand> getChildren() {
+        ArrayList<PseudoCommand> children = new ArrayList<PseudoCommand>();
+        children.add(new DownloadPseudoCommand());
+        children.add(mergeCommand);
+        return children;
+    }
+    
+}
diff --git a/src/org/openstreetmap/josm/command/MergeCommand.java b/src/org/openstreetmap/josm/command/MergeCommand.java
new file mode 100644
index 0000000..c36ee77
--- /dev/null
+++ b/src/org/openstreetmap/josm/command/MergeCommand.java
@@ -0,0 +1,199 @@
+// License: GPL. Copyright 2012 by Josh Doe and others
+package org.openstreetmap.josm.command;
+
+import java.awt.geom.Area;
+import java.util.Collection;
+import java.util.HashSet;
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.conflict.Conflict;
+import org.openstreetmap.josm.data.osm.*;
+import org.openstreetmap.josm.data.osm.visitor.MergeSourceBuildingVisitor;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor;
+import org.openstreetmap.josm.tools.CheckParameterUtil;
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * A command that merges objects from one layer to another.
+ *
+ * @author joshdoe
+ */
+public class MergeCommand extends Command {
+
+    private DataSetMerger merger;
+    private DataSet sourceDataSet;
+    private DataSet targetDataSet;
+    private OsmDataLayer targetLayer;
+    private Collection<DataSource> addedDataSources;
+    private String otherVersion;
+    private Collection<Conflict> addedConflicts;
+
+    /**
+     * Create command to merge all or only currently selected objects from
+     * sourceDataSet to targetLayer.
+     *
+     * @param targetLayer
+     * @param sourceDataSet
+     * @param onlySelected true to only merge objects selected in the
+     * sourceDataSet
+     */
+    public MergeCommand(OsmDataLayer targetLayer, DataSet sourceDataSet, boolean onlySelected) {
+        this(targetLayer, sourceDataSet, onlySelected ? sourceDataSet.getSelected() : null);
+    }
+
+    /**
+     * Create command to merge the selection from the sourceDataSet to the
+     * targetLayer.
+     *
+     * @param targetLayer
+     * @param sourceDataSet
+     * @param selection
+     */
+    public MergeCommand(OsmDataLayer targetLayer, DataSet sourceDataSet, Collection<OsmPrimitive> selection) {
+        super(targetLayer);
+        CheckParameterUtil.ensureParameterNotNull(targetLayer, "targetLayer");
+        CheckParameterUtil.ensureParameterNotNull(sourceDataSet, "sourceDataSet");
+        this.targetLayer = targetLayer;
+        this.targetDataSet = targetLayer.data;
+
+        // if selection present, create new dataset with just selected objects
+        // and their "hull" (otherwise use entire dataset)
+        if (selection != null && !selection.isEmpty()) {
+            Collection<OsmPrimitive> origSelection = sourceDataSet.getSelected();
+            sourceDataSet.setSelected(selection);
+            MergeSourceBuildingVisitor builder = new MergeSourceBuildingVisitor(sourceDataSet);
+            this.sourceDataSet = builder.build();
+            sourceDataSet.setSelected(origSelection);
+        } else {
+            this.sourceDataSet = sourceDataSet;
+        }
+        
+
+        addedConflicts = new HashSet<Conflict>();
+        addedDataSources = new HashSet<DataSource>();
+    }
+
+    @Override
+    public boolean executeCommand() {
+        PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging data"));
+        monitor.setCancelable(false);
+        if (merger == null) {
+            //first time command is executed
+            merger = new DataSetMerger(targetDataSet, sourceDataSet);
+            try {
+                merger.merge(monitor);
+            } catch (DataIntegrityProblemException e) {
+                JOptionPane.showMessageDialog(
+                        Main.parent,
+                        e.getHtmlMessage() != null ? e.getHtmlMessage() : e.getMessage(),
+                        tr("Error"),
+                        JOptionPane.ERROR_MESSAGE);
+                return false;
+
+            }
+
+            Area a = targetDataSet.getDataSourceArea();
+
+            // copy the merged layer's data source info;
+            // only add source rectangles if they are not contained in the
+            // layer already.
+            for (DataSource src : sourceDataSet.dataSources) {
+                if (a == null || !a.contains(src.bounds.asRect())) {
+                    targetDataSet.dataSources.add(src);
+                    addedDataSources.add(src);
+                }
+            }
+
+            otherVersion = targetDataSet.getVersion();
+            // copy the merged layer's API version, downgrade if required
+            if (targetDataSet.getVersion() == null) {
+                targetDataSet.setVersion(sourceDataSet.getVersion());
+            } else if ("0.5".equals(targetDataSet.getVersion()) ^ "0.5".equals(sourceDataSet.getVersion())) {
+                System.err.println(tr("Warning: mixing 0.6 and 0.5 data results in version 0.5"));
+                targetDataSet.setVersion("0.5");
+            }
+
+
+            // FIXME: allow conflicts to be retrieved rather than added to layer?
+            if (targetLayer != null) {
+                for (Conflict<?> c : merger.getConflicts()) {
+                    if (!targetLayer.getConflicts().hasConflict(c)) {
+                        targetLayer.getConflicts().add(c);
+                        addedConflicts.add(c);
+                    }
+                }
+            }
+        } else {
+            // command is being "redone"
+            
+            merger.remerge();
+            targetDataSet.dataSources.addAll(addedDataSources);
+
+            String version = otherVersion;
+            otherVersion = targetDataSet.getVersion();
+            targetDataSet.setVersion(version);
+
+            for (Conflict c : addedConflicts) {
+                targetLayer.getConflicts().add(c);
+            }
+        }
+        
+        if (addedConflicts.size() > 0) {
+            targetLayer.warnNumNewConflicts(addedConflicts.size());
+        }
+        
+        // repaint to make sure new data is displayed properly.
+        Main.map.mapView.repaint();
+        
+        monitor.close();
+        
+        return true;
+    }
+
+    @Override
+    public void undoCommand() {
+        merger.unmerge();
+
+        // restore data source area
+        targetDataSet.dataSources.removeAll(addedDataSources);
+
+        String version = otherVersion;
+        otherVersion = targetDataSet.getVersion();
+        targetDataSet.setVersion(version);
+
+        for (Conflict c : addedConflicts) {
+            targetLayer.getConflicts().remove(c);
+        }
+
+        Main.map.mapView.repaint();
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr(marktr("Merged objects: {0} added, {1} modified"),
+                merger.getAddedObjects().size(),
+                merger.getChangedObjectsMap().size());
+    }
+
+    @Override
+    public Icon getDescriptionIcon() {
+        return ImageProvider.get("dialogs", "mergedown");
+    }
+    
+    @Override
+    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
+        HashSet<OsmPrimitive> prims = new HashSet<OsmPrimitive>();
+        prims.addAll(merger.getAddedObjects());
+        prims.addAll(merger.getChangedObjectsMap().keySet());
+        return prims;
+    }
+}
\ No newline at end of file
diff --git a/src/org/openstreetmap/josm/data/osm/DataSetMerger.java b/src/org/openstreetmap/josm/data/osm/DataSetMerger.java
index e66bda8..7c3d276 100644
--- a/src/org/openstreetmap/josm/data/osm/DataSetMerger.java
+++ b/src/org/openstreetmap/josm/data/osm/DataSetMerger.java
@@ -43,6 +43,14 @@ public class DataSetMerger {
      */
     private final Set<PrimitiveId> objectsWithChildrenToMerge;
     private final Set<OsmPrimitive> objectsToDelete;
+    
+    private final Map<OsmPrimitive, PrimitiveData> changedObjectsMap;
+    private final Set<OsmPrimitive> addedObjects;
+    
+    private enum UndoState {
+        INIT, MERGED, UNDONE, REDONE
+    }
+    private UndoState undoState;
 
     /**
      * constructor
@@ -61,6 +69,9 @@ public class DataSetMerger {
         mergedMap = new HashMap<PrimitiveId, PrimitiveId>();
         objectsWithChildrenToMerge = new HashSet<PrimitiveId>();
         objectsToDelete = new HashSet<OsmPrimitive>();
+        changedObjectsMap = new HashMap<OsmPrimitive, PrimitiveData>();
+        addedObjects = new HashSet<OsmPrimitive>();
+        undoState = UndoState.INIT;
     }
 
     /**
@@ -77,7 +88,7 @@ public class DataSetMerger {
      * @param <P>  the type of the other primitive
      * @param source  the other primitive
      */
-    protected void mergePrimitive(OsmPrimitive source, Collection<? extends OsmPrimitive> candidates) {
+        protected void mergePrimitive(OsmPrimitive source, Collection<? extends OsmPrimitive> candidates) {
         if (!source.isNew() ) {
             // try to merge onto a matching primitive with the same
             // defined id
@@ -107,6 +118,7 @@ public class DataSetMerger {
                     target.setTimestamp(source.getTimestamp());
                     target.setModified(source.isModified());
                     objectsWithChildrenToMerge.add(source.getPrimitiveId());
+                    changedObjectsMap.put(target, source.save());
                     return;
                 }
             }
@@ -126,6 +138,7 @@ public class DataSetMerger {
         targetDataSet.addPrimitive(target);
         mergedMap.put(source.getPrimitiveId(), target.getPrimitiveId());
         objectsWithChildrenToMerge.add(source.getPrimitiveId());
+        addedObjects.add(target);
     }
 
     protected OsmPrimitive getMergeTarget(OsmPrimitive mergeSource) throws IllegalStateException {
@@ -179,7 +192,8 @@ public class DataSetMerger {
                 List<OsmPrimitive> referrers = target.getReferrers();
                 if (referrers.isEmpty()) {
                     target.setDeleted(true);
-                    target.mergeFrom(source);
+                    if (target.mergeFrom(source))
+                        changedObjectsMap.put(target, source.save());
                     it.remove();
                     flag = true;
                 } else {
@@ -208,9 +222,11 @@ public class DataSetMerger {
                     ((Relation) osm).setMembers(null);
                 }
             }
-            for (OsmPrimitive osm: objectsToDelete) {
-                osm.setDeleted(true);
-                osm.mergeFrom(sourceDataSet.getPrimitiveById(osm.getPrimitiveId()));
+            for (OsmPrimitive target: objectsToDelete) {
+                OsmPrimitive source = sourceDataSet.getPrimitiveById(target.getPrimitiveId());
+                target.setDeleted(true);
+                if (target.mergeFrom(source))
+                    changedObjectsMap.put(target, source.save());
             }
         }
     }
@@ -292,7 +308,8 @@ public class DataSetMerger {
             // target is incomplete, source completes it
             // => merge source into target
             //
-            target.mergeFrom(source);
+            if (target.mergeFrom(source))
+                changedObjectsMap.put(target, source.save());
             objectsWithChildrenToMerge.add(source.getPrimitiveId());
         } else if (!target.isIncomplete() && source.isIncomplete()) {
             // target is complete and source is incomplete
@@ -318,6 +335,7 @@ public class DataSetMerger {
                 if (targetDataSet.getPrimitiveById(referrer.getPrimitiveId()) == null) {
                     conflicts.add(new Conflict<OsmPrimitive>(target, source, true));
                     target.setDeleted(false);
+                    changedObjectsMap.put(target, source.save());
                     break;
                 }
             }
@@ -329,23 +347,27 @@ public class DataSetMerger {
         } else if (! target.isModified() && source.isModified()) {
             // target not modified. We can assume that source is the most recent version.
             // clone it into target.
-            target.mergeFrom(source);
+            if (target.mergeFrom(source))
+                changedObjectsMap.put(target, source.save());
             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
-            target.mergeFrom(source);
+            if (target.mergeFrom(source))
+                changedObjectsMap.put(target, source.save());
             objectsWithChildrenToMerge.add(source.getPrimitiveId());
         } else if (! target.isModified() && !source.isModified() && target.getVersion() < source.getVersion()) {
             // my not modified but other is newer. clone other onto mine.
             //
-            target.mergeFrom(source);
+            if (target.mergeFrom(source))
+                changedObjectsMap.put(target, source.save());
             objectsWithChildrenToMerge.add(source.getPrimitiveId());
         } else if (target.isModified() && ! source.isModified() && target.getVersion() == source.getVersion()) {
             // target is same as source but target is modified
             // => keep target and reset modified flag if target and source are semantically equal
             if (target.hasEqualSemanticAttributes(source)) {
                 target.setModified(false);
+                changedObjectsMap.put(target, source.save());
             }
         } else if (source.isDeleted() != target.isDeleted()) {
             // target is modified and deleted state differs.
@@ -362,7 +384,8 @@ public class DataSetMerger {
             // technical attributes like timestamp or user information. Semantic
             // attributes should already be equal if we get here.
             //
-            target.mergeFrom(source);
+            if (target.mergeFrom(source))
+                changedObjectsMap.put(target, source.save());
             objectsWithChildrenToMerge.add(source.getPrimitiveId());
         }
         return true;
@@ -423,6 +446,8 @@ public class DataSetMerger {
         if (progressMonitor != null) {
             progressMonitor.finishTask();
         }
+        
+        undoState = UndoState.MERGED;
     }
 
     /**
@@ -442,4 +467,70 @@ public class DataSetMerger {
     public ConflictCollection getConflicts() {
         return conflicts;
     }
+    
+    /**
+     * Undos the merge operation.
+     */
+    public void unmerge() {
+        if (undoState != UndoState.MERGED && undoState != UndoState.REDONE) {
+            throw new AssertionError();
+        }
+        
+        targetDataSet.beginUpdate();
+        
+        for (PrimitiveId osm : addedObjects) {
+            targetDataSet.removePrimitive(osm);
+        }
+        
+        for (Map.Entry<OsmPrimitive, PrimitiveData> e : changedObjectsMap.entrySet()) {
+            // restore previous state and save current state for opposite undo action
+            PrimitiveData old = e.getKey().save();
+            e.getKey().load(e.getValue());
+            e.setValue(old);
+        }
+        
+        targetDataSet.endUpdate();
+        undoState = UndoState.UNDONE;
+    }
+    
+    /**
+     * Re-merge objects with dataset. Identical results as {@see #merge()}, but performed
+     * after {@see #unmerge()} has been called.
+     */
+    public void remerge() {
+        if (undoState != UndoState.UNDONE) {
+            throw new AssertionError();
+        }
+        
+        targetDataSet.beginUpdate();
+        
+        // add back objects in order that they can be referenced by other objects
+        for (OsmPrimitive osm : OsmPrimitive.getFilteredList(addedObjects, Node.class)) {
+            targetDataSet.addPrimitive(osm);
+        }
+        for (OsmPrimitive osm : OsmPrimitive.getFilteredList(addedObjects, Way.class)) {
+            targetDataSet.addPrimitive(osm);
+        }
+        for (OsmPrimitive osm : OsmPrimitive.getFilteredList(addedObjects, Relation.class)) {
+            targetDataSet.addPrimitive(osm);
+        }
+        
+        for (Map.Entry<OsmPrimitive, PrimitiveData> e : changedObjectsMap.entrySet()) {
+            // restore previous state and save current state for opposite undo action
+            PrimitiveData old = e.getKey().save();
+            e.getKey().load(e.getValue());
+            e.setValue(old);
+        }
+        
+        targetDataSet.endUpdate();
+        undoState = UndoState.REDONE;
+    }
+    
+    public Map<OsmPrimitive, PrimitiveData> getChangedObjectsMap() {
+        return changedObjectsMap;
+    }
+    
+    public Set<OsmPrimitive> getAddedObjects() {
+        return addedObjects;
+    }
 }
diff --git a/src/org/openstreetmap/josm/data/osm/Node.java b/src/org/openstreetmap/josm/data/osm/Node.java
index cefa750..b292609 100644
--- a/src/org/openstreetmap/josm/data/osm/Node.java
+++ b/src/org/openstreetmap/josm/data/osm/Node.java
@@ -206,21 +206,25 @@ public final class Node extends OsmPrimitive implements INode {
      * have an assigend OSM id, the IDs have to be the same.
      *
      * @param other the other primitive. Must not be null.
+     * @return true if the semantic or technical attributes were changed
      * @throws IllegalArgumentException thrown if other is null.
      * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
      * @throws DataIntegrityProblemException thrown if other is new and other.getId() != this.getId()
      */
     @Override
-    public void mergeFrom(OsmPrimitive other) {
+    public boolean mergeFrom(OsmPrimitive other) {
+        boolean changed;
         boolean locked = writeLock();
         try {
-            super.mergeFrom(other);
-            if (!other.isIncomplete()) {
+            changed = super.mergeFrom(other);
+            if (!other.isIncomplete() && !getCoor().equals(((Node)other).getCoor())) {
                 setCoor(((Node)other).getCoor());
+                changed = true;
             }
         } finally {
             writeUnlock(locked);
         }
+        return changed;
     }
 
     @Override public void load(PrimitiveData data) {
diff --git a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
index 2f06c76..a2e07b7 100644
--- a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
+++ b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java
@@ -926,14 +926,18 @@ abstract public class OsmPrimitive extends AbstractPrimitive implements Comparab
      * Merges the technical and semantical attributes from <code>other</code> onto this.
      *
      * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
-     * have an assigend OSM id, the IDs have to be the same.
+     * have an assigned OSM id, the IDs have to be the same.
      *
      * @param other the other primitive. Must not be null.
+     * @return true if the semantic or technical attributes were changed
      * @throws IllegalArgumentException thrown if other is null.
      * @throws DataIntegrityProblemException thrown if either this is new and other is not, or other is new and this is not
      * @throws DataIntegrityProblemException thrown if other isn't new and other.getId() != this.getId()
      */
-    public void mergeFrom(OsmPrimitive other) {
+    public boolean mergeFrom(OsmPrimitive other) {
+        if (hasEqualSemanticAttributes(other) && hasEqualTechnicalAttributes(other))
+            return false;
+
         boolean locked = writeLock();
         try {
             CheckParameterUtil.ensureParameterNotNull(other, "other");
@@ -952,6 +956,7 @@ abstract public class OsmPrimitive extends AbstractPrimitive implements Comparab
         } finally {
             writeUnlock(locked);
         }
+        return true;
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java b/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java
index 0c1ad81..9e68c3a 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java
@@ -13,6 +13,7 @@ import java.util.Set;
 import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.DownloadOsmCommand;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Relation;
@@ -135,8 +136,7 @@ public class DownloadRelationMemberTask extends PleaseWaitRunnable {
             SwingUtilities.invokeLater(
                     new Runnable() {
                         public void run() {
-                            curLayer.mergeFrom(dataSet);
-                            curLayer.onPostDownloadFromServer();
+                            Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download relation members"), curLayer, dataSet));
                         }
                     }
             );
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java b/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java
index ec140ec..d524a4f 100644
--- a/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java
+++ b/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java
@@ -9,6 +9,7 @@ import java.util.Collection;
 import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.DownloadOsmCommand;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.DataSetMerger;
 import org.openstreetmap.josm.data.osm.Relation;
@@ -98,8 +99,7 @@ public class DownloadRelationTask extends PleaseWaitRunnable {
             SwingUtilities.invokeAndWait(
                     new Runnable() {
                         public void run() {
-                            layer.mergeFrom(allDownloads);
-                            layer.onPostDownloadFromServer();
+                            Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download relation(s)"), layer, allDownloads));
                             Main.map.repaint();
                         }
                     }
diff --git a/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java b/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java
index c1433d6..03fd341 100644
--- a/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java
+++ b/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java
@@ -10,8 +10,10 @@ import java.util.List;
 import java.util.Set;
 
 import javax.swing.SwingUtilities;
+import org.openstreetmap.josm.Main;
 
 import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.command.DownloadOsmCommand;
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.DataSetMerger;
 import org.openstreetmap.josm.data.osm.Node;
@@ -83,9 +85,8 @@ public class DownloadPrimitivesTask extends PleaseWaitRunnable {
         }
         Runnable r = new Runnable() {
             public void run() {
-                layer.mergeFrom(ds);
+                Main.main.undoRedo.add(new DownloadOsmCommand(tr("Download primitives"), layer, ds));
                 AutoScaleAction.zoomTo(ds.allPrimitives());
-                layer.onPostDownloadFromServer();
             }
         };
 
diff --git a/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java b/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java
index 59cb47c..59f0bd6 100644
--- a/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java
+++ b/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java
@@ -10,6 +10,8 @@ import java.util.Collection;
 import java.util.Collections;
 
 import javax.swing.SwingUtilities;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.DownloadOsmCommand;
 
 import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.osm.DataSetMerger;
@@ -81,8 +83,7 @@ public class UpdatePrimitivesTask extends PleaseWaitRunnable {
         }
         Runnable r = new Runnable() {
             public void run() {
-                layer.mergeFrom(ds);
-                layer.onPostDownloadFromServer();
+                Main.main.undoRedo.add(new DownloadOsmCommand(tr("Update primitives"), layer, ds));
             }
         };
 
diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
index 5a0f76f..db43ba5 100644
--- a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
+++ b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
@@ -99,7 +99,7 @@ public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis
 
     public List<TestError> validationErrors = new ArrayList<TestError>();
 
-    protected void setRequiresSaveToFile(boolean newValue) {
+    public void setRequiresSaveToFile(boolean newValue) {
         boolean oldValue = requiresSaveToFile;
         requiresSaveToFile = newValue;
         if (oldValue != newValue) {
@@ -107,7 +107,7 @@ public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis
         }
     }
 
-    protected void setRequiresUploadToServer(boolean newValue) {
+    public void setRequiresUploadToServer(boolean newValue) {
         boolean oldValue = requiresUploadToServer;
         requiresUploadToServer = newValue;
         if (oldValue != newValue) {
@@ -299,6 +299,7 @@ public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis
     }
 
     @Override public void mergeFrom(final Layer from) {
+        // TODO: make undo-able
         final PleaseWaitProgressMonitor monitor = new PleaseWaitProgressMonitor(tr("Merging layers"));
         monitor.setCancelable(false);
         if (from instanceof OsmDataLayer && ((OsmDataLayer)from).isUploadDiscouraged()) {
@@ -314,6 +315,7 @@ public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis
      *
      * @param from  the source data set
      */
+    @Deprecated
     public void mergeFrom(final DataSet from) {
         mergeFrom(from, null);
     }
@@ -324,6 +326,7 @@ public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis
      *
      * @param from  the source data set
      */
+    @Deprecated
     public void mergeFrom(final DataSet from, ProgressMonitor progressMonitor) {
         final DataSetMerger visitor = new DataSetMerger(data,from);
         try {
@@ -375,7 +378,7 @@ public class OsmDataLayer extends Layer implements Listener, SelectionChangedLis
      *
      * @param numNewConflicts the number of detected conflicts
      */
-    protected void warnNumNewConflicts(int numNewConflicts) {
+    public void warnNumNewConflicts(int numNewConflicts) {
         if (numNewConflicts == 0) return;
 
         String msg1 = trn(
