Index: src/org/openstreetmap/josm/io/session/CommandSessionExporters.java
===================================================================
--- src/org/openstreetmap/josm/io/session/CommandSessionExporters.java	(nonexistent)
+++ src/org/openstreetmap/josm/io/session/CommandSessionExporters.java	(working copy)
@@ -0,0 +1,160 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import java.util.Collection;
+
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.PseudoCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
+import org.openstreetmap.josm.tools.Logging;
+import org.w3c.dom.Element;
+
+/**
+ * Command session exporters.
+ * @since xxx
+ */
+public final class CommandSessionExporters {
+
+    private CommandSessionExporters() {
+        // Hide default constructor
+    }
+
+    abstract static class AbstractCommandSessionExporter<T extends Command> implements SessionCommandExporter {
+        protected final T command;
+
+        AbstractCommandSessionExporter(T command) {
+            this.command = command;
+        }
+
+        @Override
+        public final Element export(ExportSupport support) {
+            Element el = support.createElement("command");
+            el.setAttribute("class", command.getClass().getName());
+            el.setAttribute("layer", Integer.toString(support.getLayerIndex()));
+            doExport(el, support);
+            if (includeParticipatingOsmPrimitives()) {
+                for (OsmPrimitive osm : command.getParticipatingPrimitives()) {
+                    Element osmEl = support.createElement(osm.getType().getAPIName());
+                    osmEl.setAttribute("id", Long.toString(osm.getUniqueId()));
+                    el.appendChild(osmEl);
+                }
+            }
+            return el;
+        }
+
+        protected void doExport(Element el, ExportSupport support) {
+            // To be overriden if needed
+        }
+    }
+
+    /**
+     * A special command exporter used when no exporter can be found for a given command.
+     * This allows us to export as many commands as possible but:<ul>
+     * <li>log errors directly in the session file, for troubleshooting</li>
+     * <li>include an error flag preventing session import action to load commands to ensure data integrity</li>
+     * </ul>
+     */
+    public static class ErrorCommandExporter extends AbstractCommandSessionExporter<Command> {
+
+        /**
+         * Constructs a new {@code ErrorCommandExporter}
+         * @param command unsupported command
+         */
+        public ErrorCommandExporter(Command command) {
+            super(command);
+        }
+
+        @Override
+        protected void doExport(Element el, ExportSupport support) {
+            String message = "No exporter found for " + command;
+            Logging.error(message);
+            el.setAttribute("error", message);
+        }
+    }
+
+    /**
+     * Command exporter for {@link AddCommand}.
+     */
+    public static class AddCommandExporter extends AbstractCommandSessionExporter<AddCommand> {
+
+        /**
+         * Constructs a new {@code AddCommandExporter}
+         * @param command add command
+         */
+        public AddCommandExporter(AddCommand command) {
+            super(command);
+        }
+    }
+
+    /**
+     * Command exporter for {@link ChangeCommand}.
+     */
+    public static class ChangeCommandExporter extends AbstractCommandSessionExporter<ChangeCommand> {
+
+        /**
+         * Constructs a new {@code ChangeCommandExporter}
+         * @param command change command
+         */
+        public ChangeCommandExporter(ChangeCommand command) {
+            super(command);
+        }
+
+        @Override
+        protected void doExport(Element el, ExportSupport support) {
+            // TODO Auto-generated method stub
+        }
+    }
+
+    /**
+     * Command exporter for {@link SequenceCommand}.
+     */
+    public static class SequenceCommandExporter extends AbstractCommandSessionExporter<SequenceCommand> {
+
+        /**
+         * Constructs a new {@code SequenceCommandExporter}
+         * @param command sequence command
+         */
+        public SequenceCommandExporter(SequenceCommand command) {
+            super(command);
+        }
+
+        @Override
+        public boolean includeParticipatingOsmPrimitives() {
+            return false;
+        }
+
+        @Override
+        protected void doExport(Element el, ExportSupport support) {
+            el.setAttribute("name", command.getName());
+            for (PseudoCommand pc : command.getChildren()) {
+                if (pc instanceof Command) {
+                    SessionWriter.addCommand(el, (Command) pc, support);
+                } else {
+                    // Include a fake command to issue an error in the session file (PseudoCommand not supported)
+                    SessionWriter.addCommand(el, new Command(command.getAffectedDataSet()) {
+
+                        @Override
+                        public String getDescriptionText() {
+                            return null;
+                        }
+
+                        @Override
+                        public void fillModifiedData(Collection<OsmPrimitive> modified,
+                                Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+                            // Do nothing
+                        }
+
+                        @Override
+                        public String toString() {
+                            return "No exporter found for " + pc;
+                        }
+                    }, support);
+                }
+            }
+        }
+    }
+}

Property changes on: src\org\openstreetmap\josm\io\session\CommandSessionExporters.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: src/org/openstreetmap/josm/io/session/CommandSessionImporters.java
===================================================================
--- src/org/openstreetmap/josm/io/session/CommandSessionImporters.java	(nonexistent)
+++ src/org/openstreetmap/josm/io/session/CommandSessionImporters.java	(working copy)
@@ -0,0 +1,106 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.tools.XmlUtils;
+import org.w3c.dom.Element;
+
+/**
+ * Command session importers.
+ * @since xxx
+ */
+public final class CommandSessionImporters {
+
+    private CommandSessionImporters() {
+        // Hide default constructor
+    }
+
+    abstract static class AbstractCommandSessionImporter<T extends Command> implements SessionCommandImporter {
+
+        @Override
+        public Command load(Element elem, OsmDataLayer dataLayer, ProgressMonitor progressMonitor) throws IllegalDataException {
+            return doImport(elem, dataLayer, elem.getAttribute("name"));
+        }
+
+        /**
+         * @param el Command element
+         * @param dataLayer data layer
+         * @param name command name
+         * @return the resulting command
+         * @throws IllegalDataException in case of illegal data
+         */
+        protected abstract Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException;
+    }
+
+    /**
+     * A special command importer used when no exporter was found for a given command.
+     * This allows us to export as many commands as possible but:<ul>
+     * <li>log errors directly in the session file, for troubleshooting</li>
+     * <li>include an error flag preventing session import action to load commands to ensure data integrity</li>
+     * </ul>
+     */
+    public static class ErrorCommandImporter extends AbstractCommandSessionImporter<Command> {
+
+        @Override
+        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
+            throw new IllegalDataException(name + ": " + el.getAttribute("error"));
+        }
+    }
+
+    /**
+     * Command importer for {@link AddCommand}.
+     */
+    public static class AddCommandImporter extends AbstractCommandSessionImporter<AddCommand> {
+
+        @Override
+        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
+            DataSet dataSet = dataLayer.data;
+            Element osm = XmlUtils.getFirstChildElement(el);
+            return new AddCommand(dataSet, dataSet.getPrimitiveById(
+                    Long.parseLong(osm.getAttribute("id")),
+                    OsmPrimitiveType.from(osm.getLocalName())));
+        }
+    }
+
+    /**
+     * Command importer for {@link ChangeCommand}.
+     */
+    public static class ChangeCommandImporter extends AbstractCommandSessionImporter<ChangeCommand> {
+
+        @Override
+        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+    }
+
+    /**
+     * Command importer for {@link SequenceCommand}.
+     */
+    public static class SequenceCommandImporter extends AbstractCommandSessionImporter<SequenceCommand> {
+
+        @Override
+        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
+            try {
+                int layerIndex = Integer.parseInt(el.getAttribute("layer"));
+                List<Layer> layers = Collections.nCopies(layerIndex, dataLayer);
+                return new SequenceCommand(name, SessionReader.readCommands(el, new ArrayList<>(), layers));
+            } catch (ClassNotFoundException e) {
+                throw new IllegalDataException(e);
+            }
+        }
+    }
+}

Property changes on: src\org\openstreetmap\josm\io\session\CommandSessionImporters.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: src/org/openstreetmap/josm/io/session/GenericSessionExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(revision 14336)
+++ src/org/openstreetmap/josm/io/session/GenericSessionExporter.java	(working copy)
@@ -35,7 +35,7 @@
 import org.w3c.dom.Element;
 
 /**
- * Generic superclass of {@link OsmDataSessionExporter} and {@link GpxTracksSessionExporter} layer exporters.
+ * Generic superclass of {@link OsmDataSessionExporter}, {@link GpxTracksSessionExporter} and {@code NoteSessionExporter} layer exporters.
  * @param <T> Type of exported layer
  * @since 9470
  */
Index: src/org/openstreetmap/josm/io/session/SessionCommandExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/SessionCommandExporter.java	(nonexistent)
+++ src/org/openstreetmap/josm/io/session/SessionCommandExporter.java	(working copy)
@@ -0,0 +1,30 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
+import org.w3c.dom.Element;
+
+/**
+ * Session command exporter.
+ * @since xxx
+ */
+public interface SessionCommandExporter {
+
+    /**
+     * Save meta data to the .jos file. Return a command XML element.
+     * Use <code>support</code> to save files in the zip archive as needed.
+     * @param support support class providing export utilities
+     * @return the resulting XML element
+     */
+    Element export(ExportSupport support);
+
+    /**
+     * Whether to include participating OSM primitives as inner child elements.
+     * @return {@code true} to include participating OSM primitives
+     * @see Command#getParticipatingPrimitives
+     */
+    default boolean includeParticipatingOsmPrimitives() {
+        return true;
+    }
+}

Property changes on: src\org\openstreetmap\josm\io\session\SessionCommandExporter.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: src/org/openstreetmap/josm/io/session/SessionCommandImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/SessionCommandImporter.java	(nonexistent)
+++ src/org/openstreetmap/josm/io/session/SessionCommandImporter.java	(working copy)
@@ -0,0 +1,26 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.session;
+
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.IllegalDataException;
+import org.w3c.dom.Element;
+
+/**
+ * Session command importer.
+ * @since xxx
+ */
+@FunctionalInterface
+public interface SessionCommandImporter {
+
+    /**
+     * Load the command from xml meta-data.
+     * @param elem XML element
+     * @param dataLayer data layer
+     * @param progressMonitor progress monitor
+     * @return the resulting command
+     * @throws IllegalDataException in case of illegal data
+     */
+    Command load(Element elem, OsmDataLayer dataLayer, ProgressMonitor progressMonitor) throws IllegalDataException;
+}

Property changes on: src\org\openstreetmap\josm\io\session\SessionCommandImporter.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: src/org/openstreetmap/josm/io/session/SessionReader.java
===================================================================
--- src/org/openstreetmap/josm/io/session/SessionReader.java	(revision 14336)
+++ src/org/openstreetmap/josm/io/session/SessionReader.java	(working copy)
@@ -31,6 +31,23 @@
 import javax.swing.SwingUtilities;
 import javax.xml.parsers.ParserConfigurationException;
 
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.AddPrimitivesCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.ChangeNodesCommand;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
+import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.MoveCommand;
+import org.openstreetmap.josm.command.RemoveNodesCommand;
+import org.openstreetmap.josm.command.RotateCommand;
+import org.openstreetmap.josm.command.ScaleCommand;
+import org.openstreetmap.josm.command.SelectCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.command.SplitWayCommand;
+import org.openstreetmap.josm.command.TransformNodesCommand;
 import org.openstreetmap.josm.data.ViewportData;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -38,10 +55,14 @@
 import org.openstreetmap.josm.gui.ExtendedDialog;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.io.Compression;
 import org.openstreetmap.josm.io.IllegalDataException;
+import org.openstreetmap.josm.io.session.CommandSessionImporters.AddCommandImporter;
+import org.openstreetmap.josm.io.session.CommandSessionImporters.ChangeCommandImporter;
+import org.openstreetmap.josm.io.session.CommandSessionImporters.SequenceCommandImporter;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
@@ -148,7 +169,35 @@
         }
     }
 
-    private static final Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters = new HashMap<>();
+    /**
+     * Data class for commands saved in the session file.
+     * @since xxx
+     */
+    public static class CommandStackData {
+        private final List<Command> undo = new ArrayList<>();
+        private final List<Command> redo = new ArrayList<>();
+
+        /**
+         * Returns the undo commands.
+         * @return the undo commands
+         */
+        public final List<Command> getUndo() {
+            return undo;
+        }
+
+        /**
+         * Returns the redo commands.
+         * @return the redo commands
+         */
+        public final List<Command> getRedo() {
+            return redo;
+        }
+    }
+
+    private static final Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters
+        = new HashMap<>();
+    private static final Map<Class<? extends Command>, Class<? extends SessionCommandImporter>> sessionCommandImporters
+        = new HashMap<>();
 
     private URI sessionFileURI;
     private boolean zip; // true, if session file is a .joz file; false if it is a .jos file
@@ -156,16 +205,36 @@
     private List<Layer> layers = new ArrayList<>();
     private int active = -1;
     private final List<Runnable> postLoadTasks = new ArrayList<>();
+    //private final Map<Integer, ImportSupport> supports = new HashMap<>();
     private SessionViewportData viewport;
     private SessionProjectionChoiceData projectionChoice;
+    private CommandStackData commandStack;
 
     static {
+        // Layers
         registerSessionLayerImporter("osm-data", OsmDataSessionImporter.class);
         registerSessionLayerImporter("imagery", ImagerySessionImporter.class);
         registerSessionLayerImporter("tracks", GpxTracksSessionImporter.class);
         registerSessionLayerImporter("geoimage", GeoImageSessionImporter.class);
         registerSessionLayerImporter("markers", MarkerSessionImporter.class);
         registerSessionLayerImporter("osm-notes", NoteSessionImporter.class);
+        // Commands TODO
+        registerSessionCommandImporter(AddCommand.class, AddCommandImporter.class);
+        registerSessionCommandImporter(AddPrimitivesCommand.class, null);
+        registerSessionCommandImporter(ChangeCommand.class, ChangeCommandImporter.class);
+        registerSessionCommandImporter(ChangeNodesCommand.class, null);
+        registerSessionCommandImporter(ChangePropertyCommand.class, null);
+        registerSessionCommandImporter(ChangePropertyKeyCommand.class, null);
+        registerSessionCommandImporter(ChangeRelationMemberRoleCommand.class, null);
+        registerSessionCommandImporter(DeleteCommand.class, null);
+        registerSessionCommandImporter(MoveCommand.class, null);
+        registerSessionCommandImporter(RemoveNodesCommand.class, null);
+        registerSessionCommandImporter(RotateCommand.class, null);
+        registerSessionCommandImporter(ScaleCommand.class, null);
+        registerSessionCommandImporter(SelectCommand.class, null);
+        registerSessionCommandImporter(SequenceCommand.class, SequenceCommandImporter.class);
+        registerSessionCommandImporter(SplitWayCommand.class, null);
+        registerSessionCommandImporter(TransformNodesCommand.class, null);
     }
 
     /**
@@ -179,21 +248,45 @@
     }
 
     /**
+     * Register a session command importer.
+     *
+     * @param commandClass command class
+     * @param importer importer for this command class
+     * @since xxx
+     */
+    public static void registerSessionCommandImporter(Class<? extends Command> commandClass, Class<? extends SessionCommandImporter> importer) {
+        sessionCommandImporters.put(commandClass, importer);
+    }
+
+    /**
      * Returns the session layer importer for the given layer type.
      * @param layerType layer type to import
      * @return session layer importer for the given layer
      */
     public static SessionLayerImporter getSessionLayerImporter(String layerType) {
-        Class<? extends SessionLayerImporter> importerClass = sessionLayerImporters.get(layerType);
+        return getSessionImporter(sessionLayerImporters, layerType);
+    }
+
+    /**
+     * Returns the session command importer for the given command.
+     * @param commandClass command class to import
+     * @return session command importer for the given command, or null
+     * @since xxx
+     */
+    public static SessionCommandImporter getSessionCommandImporter(Class<? extends Command> commandClass) {
+        return getSessionImporter(sessionCommandImporters, commandClass);
+    }
+
+    private static <E, T> E getSessionImporter(Map<T, Class<? extends E>> importers, T key) {
+        Class<? extends E> importerClass = importers.get(key);
         if (importerClass == null)
             return null;
-        SessionLayerImporter importer = null;
         try {
-            importer = importerClass.getConstructor().newInstance();
+            return importerClass.getConstructor().newInstance();
         } catch (ReflectiveOperationException e) {
-            throw new JosmRuntimeException(e);
+            Logging.error(e);
+            return null;
         }
-        return importer;
     }
 
     /**
@@ -236,6 +329,15 @@
     }
 
     /**
+     * Return the command stack data.
+     * @return the command stack; can be null when no command is found in the file
+     * @since xxx
+     */
+    public CommandStackData getCommandStack() {
+        return commandStack;
+    }
+
+    /**
      * A class that provides some context for the individual {@link SessionLayerImporter}
      * when doing the import.
      */
@@ -400,25 +502,46 @@
         }
     }
 
+    /**
+     * Layer dependency.
+     */
     public static class LayerDependency {
         private final Integer index;
         private final Layer layer;
         private final SessionLayerImporter importer;
 
+        /**
+         * Constructs a new {@code LayerDependency}.
+         * @param index layer index
+         * @param layer layer
+         * @param importer layer importer
+         */
         public LayerDependency(Integer index, Layer layer, SessionLayerImporter importer) {
             this.index = index;
             this.layer = layer;
             this.importer = importer;
         }
 
+        /**
+         * Returns the layer importer.
+         * @return the layer importer
+         */
         public SessionLayerImporter getImporter() {
             return importer;
         }
 
+        /**
+         * Returns the layer index.
+         * @return the layer index
+         */
         public Integer getIndex() {
             return index;
         }
 
+        /**
+         * Returns the layer.
+         * @return the layer
+         */
         public Layer getLayer() {
             return layer;
         }
@@ -557,6 +680,7 @@
                     depsImp.add(new LayerDependency(d, layersMap.get(d), dImp));
                 }
                 ImportSupport support = new ImportSupport(name, idx, depsImp);
+                //supports.put(idx, support);
                 Layer layer = null;
                 Exception exception = null;
                 try {
@@ -614,6 +738,48 @@
             layer.setName(names.get(entry.getKey()));
             layers.add(layer);
         }
+        commandStack = readCommandStackData(root);
+        //supports.clear();
+    }
+
+    private CommandStackData readCommandStackData(Element root) {
+        Element commandsEl = getElementByTagName(root, "commands");
+        if (commandsEl == null) return null;
+        CommandStackData result = new CommandStackData();
+        try {
+            readCommands(commandsEl, result.undo, "undo");
+            readCommands(commandsEl, result.redo, "redo");
+        } catch (ClassNotFoundException | IllegalDataException | RuntimeException e) {
+            Logging.error(e);
+            return null;
+        }
+        return result;
+    }
+
+    private void readCommands(Element commandsEl, List<Command> result, String type)
+            throws ClassNotFoundException, IllegalDataException {
+        Element typeEl = getElementByTagName(commandsEl, type);
+        if (typeEl != null) {
+            readCommands(typeEl, result/*, supports*/, layers);
+        }
+    }
+
+    static List<Command> readCommands(Element parentEl, List<Command> result/*, Map<Integer, ImportSupport> supports*/, List<Layer> layers)
+            throws ClassNotFoundException, IllegalDataException {
+        NodeList commandNl = parentEl.getElementsByTagName("command");
+        int length = commandNl.getLength();
+        for (int i = 0; i < length; i++) {
+            Element commandEl = (Element) commandNl.item(i);
+            String klass = commandEl.getAttribute("class");
+            @SuppressWarnings("unchecked")
+            SessionCommandImporter importer = getSessionCommandImporter((Class<? extends Command>) Class.forName(klass));
+            if (importer == null)
+                throw new IllegalStateException("No importer found for " + klass);
+            int layerIndex = Integer.parseInt(commandEl.getAttribute("layer"));
+            OsmDataLayer dataLayer = ((OsmDataLayer) layers.get(layerIndex - 1));
+            result.add(importer.load(commandEl, dataLayer, /*supports.get(layerIndex),*/ NullProgressMonitor.INSTANCE));
+        }
+        return result;
     }
 
     private static SessionViewportData readViewportData(Element root) {
Index: src/org/openstreetmap/josm/io/session/SessionWriter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/SessionWriter.java	(revision 14336)
+++ src/org/openstreetmap/josm/io/session/SessionWriter.java	(working copy)
@@ -25,8 +25,27 @@
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.AddPrimitivesCommand;
+import org.openstreetmap.josm.command.ChangeCommand;
+import org.openstreetmap.josm.command.ChangeNodesCommand;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
+import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.MoveCommand;
+import org.openstreetmap.josm.command.RemoveNodesCommand;
+import org.openstreetmap.josm.command.RotateCommand;
+import org.openstreetmap.josm.command.ScaleCommand;
+import org.openstreetmap.josm.command.SelectCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.command.SplitWayCommand;
+import org.openstreetmap.josm.command.TransformNodesCommand;
+import org.openstreetmap.josm.data.UndoRedoHandler;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MapView;
@@ -40,6 +59,10 @@
 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
+import org.openstreetmap.josm.io.session.CommandSessionExporters.AddCommandExporter;
+import org.openstreetmap.josm.io.session.CommandSessionExporters.ChangeCommandExporter;
+import org.openstreetmap.josm.io.session.CommandSessionExporters.ErrorCommandExporter;
+import org.openstreetmap.josm.io.session.CommandSessionExporters.SequenceCommandExporter;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.MultiMap;
@@ -47,6 +70,7 @@
 import org.openstreetmap.josm.tools.XmlUtils;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 import org.w3c.dom.Text;
 
 /**
@@ -55,7 +79,10 @@
  */
 public class SessionWriter {
 
-    private static Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters = new HashMap<>();
+    private static final Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters
+        = new HashMap<>();
+    private static final Map<Class<? extends Command>, Class<? extends SessionCommandExporter>> sessionCommandExporters
+        = new HashMap<>();
 
     private final List<Layer> layers;
     private final int active;
@@ -66,6 +93,7 @@
     private ZipOutputStream zipOut;
 
     static {
+        // Layers
         registerSessionLayerExporter(OsmDataLayer.class, OsmDataSessionExporter.class);
         registerSessionLayerExporter(TMSLayer.class, ImagerySessionExporter.class);
         registerSessionLayerExporter(WMSLayer.class, ImagerySessionExporter.class);
@@ -74,6 +102,23 @@
         registerSessionLayerExporter(GeoImageLayer.class, GeoImageSessionExporter.class);
         registerSessionLayerExporter(MarkerLayer.class, MarkerSessionExporter.class);
         registerSessionLayerExporter(NoteLayer.class, NoteSessionExporter.class);
+        // Commands TODO
+        registerSessionCommandExporter(AddCommand.class, AddCommandExporter.class);
+        registerSessionCommandExporter(AddPrimitivesCommand.class, null);
+        registerSessionCommandExporter(ChangeCommand.class, ChangeCommandExporter.class);
+        registerSessionCommandExporter(ChangeNodesCommand.class, null);
+        registerSessionCommandExporter(ChangePropertyCommand.class, null);
+        registerSessionCommandExporter(ChangePropertyKeyCommand.class, null);
+        registerSessionCommandExporter(ChangeRelationMemberRoleCommand.class, null);
+        registerSessionCommandExporter(DeleteCommand.class, null);
+        registerSessionCommandExporter(MoveCommand.class, null);
+        registerSessionCommandExporter(RemoveNodesCommand.class, null);
+        registerSessionCommandExporter(RotateCommand.class, null);
+        registerSessionCommandExporter(ScaleCommand.class, null);
+        registerSessionCommandExporter(SelectCommand.class, null);
+        registerSessionCommandExporter(SequenceCommand.class, SequenceCommandExporter.class);
+        registerSessionCommandExporter(SplitWayCommand.class, null);
+        registerSessionCommandExporter(TransformNodesCommand.class, null);
     }
 
     /**
@@ -88,19 +133,45 @@
     }
 
     /**
+     * Register a session layer exporter.
+     *
+     * The exporter class must have a one-argument constructor with commandClass as formal parameter type.
+     * @param commandClass command class
+     * @param exporter exporter for this command class
+     * @since xxx
+     */
+    public static void registerSessionCommandExporter(Class<? extends Command> commandClass, Class<? extends SessionCommandExporter> exporter) {
+        sessionCommandExporters.put(commandClass, exporter);
+    }
+
+    /**
      * Returns the session layer exporter for the given layer.
      * @param layer layer to export
-     * @return session layer exporter for the given layer
+     * @return session layer exporter for the given layer, or null
      */
     public static SessionLayerExporter getSessionLayerExporter(Layer layer) {
-        Class<? extends Layer> layerClass = layer.getClass();
-        Class<? extends SessionLayerExporter> exporterClass = sessionLayerExporters.get(layerClass);
+        return getSessionExporter(sessionLayerExporters, layer.getClass(), layer);
+    }
+
+    /**
+     * Returns the session command exporter for the given command.
+     * @param command command to export
+     * @return session command exporter for the given command, or null
+     * @since xxx
+     */
+    public static SessionCommandExporter getSessionCommandExporter(Command command) {
+        return getSessionExporter(sessionCommandExporters, command.getClass(), command);
+    }
+
+    private static <E, T> E getSessionExporter(Map<Class<? extends T>, Class<? extends E>> exporters, Class<? extends T> klass, T obj) {
+        Class<? extends E> exporterClass = exporters.get(klass);
         if (exporterClass == null)
             return null;
         try {
-            return exporterClass.getConstructor(layerClass).newInstance(layer);
+            return exporterClass.getConstructor(klass).newInstance(obj);
         } catch (ReflectiveOperationException e) {
-            throw new JosmRuntimeException(e);
+            Logging.error(e);
+            return null;
         }
     }
 
@@ -252,9 +323,43 @@
             }
             layersEl.appendChild(el);
         }
+        writeCommandStack(root);
         return doc;
     }
 
+    private void writeCommandStack(Element root) {
+        Document doc = root.getOwnerDocument();
+        Element commandsEl = doc.createElement("commands");
+        root.appendChild(commandsEl);
+        addCommands(doc, commandsEl.appendChild(doc.createElement("undo")), UndoRedoHandler.getInstance().getUndoCommands());
+        addCommands(doc, commandsEl.appendChild(doc.createElement("redo")), UndoRedoHandler.getInstance().getRedoCommands());
+    }
+
+    private void addCommands(Document doc, Node commandsEl, List<Command> commands) {
+        Map<DataSet, Integer> layerIndexMap = new HashMap<>();
+        for (Command c : commands) {
+            DataSet ds = c.getAffectedDataSet();
+            Integer layerIndex = layerIndexMap.computeIfAbsent(ds, x -> {
+                for (int i = 0; i < layers.size(); i++) {
+                    Layer l = layers.get(i);
+                    if (l instanceof OsmDataLayer && ((OsmDataLayer) l).getDataSet() == ds) {
+                        return i + 1;
+                    }
+                }
+                return -1;
+            });
+            addCommand(commandsEl, c, new ExportSupport(doc, layerIndex));
+        }
+    }
+
+    static Node addCommand(Node commandsEl, Command c, ExportSupport support) {
+        SessionCommandExporter exporter = getSessionCommandExporter(c);
+        if (exporter == null) {
+            exporter = new ErrorCommandExporter(c);
+        }
+        return commandsEl.appendChild(exporter.export(support));
+    }
+
     private static void writeViewPort(Element root) {
         Document doc = root.getOwnerDocument();
         Element viewportEl = doc.createElement("viewport");
