You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by ha...@apache.org on 2026/07/31 07:20:12 UTC

(hop) branch main updated: ignore transform and action position in the visual diff, fixes #2099 (#7699)

This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new ceacd3c475 ignore transform and action position in the visual diff, fixes #2099 (#7699)
ceacd3c475 is described below

commit ceacd3c475faca80e84c67b8dc0604fe08c222b7
Author: Bart Maertens <ba...@know.bi>
AuthorDate: Fri Jul 31 09:20:07 2026 +0200

    ignore transform and action position in the visual diff, fixes #2099 (#7699)
    
    A transform or action that was only dragged elsewhere was reported as
    modified, because the visual diff compares the serialized XML and the
    position is part of it. The position is now left out of that comparison,
    controlled by a new "Ignore position in the visual diff" git option that
    is on by default.
    
    Also fixes getPipelineHopName, which used getFromTransform for both ends
    of the hop. Hops leaving the same transform all shared one name, so an
    added or removed hop could be matched against the wrong one and reported
    as unchanged.
---
 .../modules/ROOT/pages/hop-gui/hop-gui-git.adoc    |  5 ++
 .../main/java/org/apache/hop/git/GitGuiPlugin.java | 14 +++-
 .../src/main/java/org/apache/hop/git/HopDiff.java  | 40 ++++++++--
 .../java/org/apache/hop/git/config/GitConfig.java  |  3 +
 .../hop/git/config/GitConfigOptionPlugin.java      | 27 +++++++
 .../git/info/GitInfoExplorerFileTypeHandler.java   | 15 +++-
 .../git/config/messages/messages_en_US.properties  |  4 +-
 .../test/java/org/apache/hop/git/HopDiffTest.java  | 90 +++++++++++++++++-----
 8 files changed, 163 insertions(+), 35 deletions(-)

diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-git.adoc b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-git.adoc
index 2069df7c0f..d39acd7dff 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-git.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-git.adoc
@@ -90,6 +90,9 @@ Hop will show an additional icon in the upper right corner of the action or tran
 
 image::hop-gui/git-visual-diff.png[Git visual diff,width="65%"]
 
+A transform or action that was only moved on the canvas is not marked as changed.
+Uncheck "Ignore position in the visual diff" in the configuration options below to have a move count as a change again.
+
 == Git Configuration options
 
 There are a few global configuration options available for the git GUI plugin.
@@ -100,4 +103,6 @@ You can find these in the configuration perspective under the "Plugins" tab and
 |Option|Description
 |Enable the git plugin|If disabled the plugin will be loaded but will not try to detect a git repository in the project folder.
 |Search parent folders for git repository|this option will search for a git repository (`.git/config` file detection) not just in the project folder but also in all parent folders. Please note that enabling this option can lead to the git plugin having to consider large amounts of files not always associated with the Hop project you're working with.  For this reason the option is disabled by default.
+|Fetch remote changes automatically|Fetches remote updates in the background every 20 minutes.
+|Ignore position in the visual diff|Keeps the position of transforms and actions out of the comparison, so one that was only moved is not reported as changed. Enabled by default.
 |===
diff --git a/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java b/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
index bf47c3a671..694c94f3f4 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/GitGuiPlugin.java
@@ -1040,9 +1040,12 @@ public class GitGuiPlugin
       PipelineMeta pipelineMetaNew =
           new PipelineMeta(xmlStreamNew, hopGui.getMetadataProvider(), hopGui.getVariables());
 
-      pipelineMetaOld = HopDiff.compareTransforms(pipelineMetaOld, pipelineMetaNew, true);
+      boolean ignorePosition = GitConfigSingleton.getConfig().isIgnoringPositionInDiff();
+      pipelineMetaOld =
+          HopDiff.compareTransforms(pipelineMetaOld, pipelineMetaNew, true, ignorePosition);
       pipelineMetaOld = HopDiff.comparePipelineHops(pipelineMetaOld, pipelineMetaNew, true);
-      pipelineMetaNew = HopDiff.compareTransforms(pipelineMetaNew, pipelineMetaOld, false);
+      pipelineMetaNew =
+          HopDiff.compareTransforms(pipelineMetaNew, pipelineMetaOld, false, ignorePosition);
       pipelineMetaNew = HopDiff.comparePipelineHops(pipelineMetaNew, pipelineMetaOld, false);
 
       pipelineMetaOld.setPipelineVersion(CONST_GIT + commitIdOld);
@@ -1090,9 +1093,12 @@ public class GitGuiPlugin
       WorkflowMeta workflowMetaNew =
           new WorkflowMeta(xmlStreamNew, hopGui.getMetadataProvider(), hopGui.getVariables());
 
-      workflowMetaOld = HopDiff.compareActions(workflowMetaOld, workflowMetaNew, true);
+      boolean ignorePosition = GitConfigSingleton.getConfig().isIgnoringPositionInDiff();
+      workflowMetaOld =
+          HopDiff.compareActions(workflowMetaOld, workflowMetaNew, true, ignorePosition);
       workflowMetaOld = HopDiff.compareWorkflowHops(workflowMetaOld, workflowMetaNew, true);
-      workflowMetaNew = HopDiff.compareActions(workflowMetaNew, workflowMetaOld, false);
+      workflowMetaNew =
+          HopDiff.compareActions(workflowMetaNew, workflowMetaOld, false, ignorePosition);
       workflowMetaNew = HopDiff.compareWorkflowHops(workflowMetaNew, workflowMetaOld, false);
 
       workflowMetaOld.setWorkflowVersion(CONST_GIT + commitIdOld);
diff --git a/plugins/misc/git/src/main/java/org/apache/hop/git/HopDiff.java b/plugins/misc/git/src/main/java/org/apache/hop/git/HopDiff.java
index 336af6f4c2..3fe70bb134 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/HopDiff.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/HopDiff.java
@@ -20,6 +20,7 @@ package org.apache.hop.git;
 
 import java.util.Map;
 import java.util.Optional;
+import java.util.regex.Pattern;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.pipeline.PipelineHopMeta;
 import org.apache.hop.pipeline.PipelineMeta;
@@ -38,10 +39,36 @@ public class HopDiff {
   public static final String REMOVED = "DELETE";
   public static final String ADDED = "INSERT";
 
+  /**
+   * The location of a transform or an action, as written by TransformMeta and ActionMeta. Only
+   * those two write an xloc/yloc pair, so this does not reach into what a plugin serialized.
+   */
+  private static final Pattern POSITION_TAGS =
+      Pattern.compile("[ \\t]*<(xloc|yloc)>.*?</\\1>\\r?\\n?");
+
   private HopDiff() {}
 
+  /**
+   * Drop the position from serialized XML so that a transform or action which was only dragged
+   * somewhere else does not read as modified. The surrounding GUI tag is left in place: it is
+   * identical on both sides once the coordinates are gone.
+   */
+  private static String removePosition(String xml) {
+    return POSITION_TAGS.matcher(xml).replaceAll("");
+  }
+
+  private static boolean sameXml(String xml1, String xml2, boolean ignorePosition) {
+    if (ignorePosition) {
+      return removePosition(xml1).equals(removePosition(xml2));
+    }
+    return xml1.equals(xml2);
+  }
+
   public static PipelineMeta compareTransforms(
-      PipelineMeta pipelineMeta1, PipelineMeta pipelineMeta2, boolean isForward) {
+      PipelineMeta pipelineMeta1,
+      PipelineMeta pipelineMeta2,
+      boolean isForward,
+      boolean ignorePosition) {
     pipelineMeta1
         .getTransforms()
         .forEach(
@@ -58,7 +85,7 @@ public class HopDiff {
                   // AttributeMap("Git") cannot affect the XML comparison
                   tmp = transform.getAttributesMap().remove(ATTR_GIT);
                   tmp2 = transform2.get().getAttributesMap().remove(ATTR_GIT);
-                  if (transform.getXml().equals(transform2.get().getXml())) {
+                  if (sameXml(transform.getXml(), transform2.get().getXml(), ignorePosition)) {
                     status = UNCHANGED;
                   } else {
                     status = CHANGED;
@@ -119,7 +146,7 @@ public class HopDiff {
       name += from.getName();
     }
     name += " - ";
-    TransformMeta to = hopMeta.getFromTransform();
+    TransformMeta to = hopMeta.getToTransform();
     if (to != null) {
       name += to.getName();
     }
@@ -127,7 +154,10 @@ public class HopDiff {
   }
 
   public static WorkflowMeta compareActions(
-      WorkflowMeta workflowMeta1, WorkflowMeta workflowMeta2, boolean isForward) {
+      WorkflowMeta workflowMeta1,
+      WorkflowMeta workflowMeta2,
+      boolean isForward,
+      boolean ignorePosition) {
     workflowMeta1
         .getActions()
         .forEach(
@@ -143,7 +173,7 @@ public class HopDiff {
                 // AttributeMap("Git") cannot affect the XML comparison
                 tmp = je.getAttributesMap().remove(ATTR_GIT);
                 tmp2 = je2.get().getAttributesMap().remove(ATTR_GIT);
-                if (je.getXml().equals(je2.get().getXml())) {
+                if (sameXml(je.getXml(), je2.get().getXml(), ignorePosition)) {
                   status = UNCHANGED;
                 } else {
                   status = CHANGED;
diff --git a/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfig.java b/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfig.java
index cc0b8e48fa..005fa91b47 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfig.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfig.java
@@ -28,16 +28,19 @@ public class GitConfig {
   private boolean enabled;
   private boolean searchingParentFolders;
   private boolean fetchAutomatic;
+  private boolean ignoringPositionInDiff;
 
   public GitConfig() {
     this.enabled = true;
     this.searchingParentFolders = false;
     this.fetchAutomatic = false;
+    this.ignoringPositionInDiff = true;
   }
 
   public GitConfig(GitConfig config) {
     this.enabled = config.enabled;
     this.searchingParentFolders = config.searchingParentFolders;
     this.fetchAutomatic = config.fetchAutomatic;
+    this.ignoringPositionInDiff = config.ignoringPositionInDiff;
   }
 }
diff --git a/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfigOptionPlugin.java b/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfigOptionPlugin.java
index d6dab48e23..f9c1036120 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfigOptionPlugin.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/config/GitConfigOptionPlugin.java
@@ -54,6 +54,8 @@ public class GitConfigOptionPlugin implements IConfigOptions, IGuiPluginComposit
   private static final String WIDGET_ID_GIT_SEARCH_PARENT_FOLDERS =
       "10010-git-search-parent-folders";
   private static final String WIDGET_ID_GIT_FETCH_AUTOMATIC = "10020-git-fetch-automatic";
+  private static final String WIDGET_ID_GIT_IGNORE_POSITION_IN_DIFF =
+      "10030-git-ignore-position-in-diff";
 
   @GuiWidgetElement(
       id = WIDGET_ID_GIT_ENABLE,
@@ -83,12 +85,24 @@ public class GitConfigOptionPlugin implements IConfigOptions, IGuiPluginComposit
       toolTip = "i18n::GitConfig.FetchAutomatic.Tooltip")
   private Boolean fetchAutomatic;
 
+  @GuiWidgetElement(
+      id = WIDGET_ID_GIT_IGNORE_POSITION_IN_DIFF,
+      parentId = ConfigPluginOptionsTab.GUI_WIDGETS_PARENT_ID,
+      type = GuiElementType.CHECKBOX,
+      label = "i18n::GitConfig.IgnorePositionInDiff.Message",
+      toolTip = "i18n::GitConfig.IgnorePositionInDiff.Tooltip")
+  @CommandLine.Option(
+      names = {"--git-gui-diff-ignore-position"},
+      description = "Do not mark a moved transform or action as changed in the visual diff")
+  private Boolean ignoringPositionInDiff;
+
   public static GitConfigOptionPlugin getInstance() {
     GitConfigOptionPlugin instance = new GitConfigOptionPlugin();
     GitConfig config = GitConfigSingleton.getConfig();
     instance.gitEnabled = config.isEnabled();
     instance.searchingParentFolders = config.isSearchingParentFolders();
     instance.fetchAutomatic = config.isFetchAutomatic();
+    instance.ignoringPositionInDiff = config.isIgnoringPositionInDiff();
     return instance;
   }
 
@@ -122,6 +136,15 @@ public class GitConfigOptionPlugin implements IConfigOptions, IGuiPluginComposit
         config.setFetchAutomatic(fetchAutomatic);
         changed = true;
       }
+      if (ignoringPositionInDiff != null) {
+        config.setIgnoringPositionInDiff(ignoringPositionInDiff);
+        if (ignoringPositionInDiff) {
+          log.logBasic("The visual diff ignores the position of transforms and actions.");
+        } else {
+          log.logBasic("The visual diff marks a moved transform or action as changed.");
+        }
+        changed = true;
+      }
 
       // Save to file if anything changed
       //
@@ -167,6 +190,10 @@ public class GitConfigOptionPlugin implements IConfigOptions, IGuiPluginComposit
           fetchAutomatic = ((Button) control).getSelection();
           GitConfigSingleton.getConfig().setFetchAutomatic(fetchAutomatic);
           break;
+        case WIDGET_ID_GIT_IGNORE_POSITION_IN_DIFF:
+          ignoringPositionInDiff = ((Button) control).getSelection();
+          GitConfigSingleton.getConfig().setIgnoringPositionInDiff(ignoringPositionInDiff);
+          break;
         default:
           break;
       }
diff --git a/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java b/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
index dbe3fb7bdb..fa030118cf 100644
--- a/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
+++ b/plugins/misc/git/src/main/java/org/apache/hop/git/info/GitInfoExplorerFileTypeHandler.java
@@ -39,6 +39,7 @@ import org.apache.hop.core.logging.LogChannel;
 import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.git.GitGuiPlugin;
 import org.apache.hop.git.HopDiff;
+import org.apache.hop.git.config.GitConfigSingleton;
 import org.apache.hop.git.model.UIFile;
 import org.apache.hop.git.model.UIGit;
 import org.apache.hop.git.model.VCS;
@@ -441,9 +442,12 @@ public class GitInfoExplorerFileTypeHandler extends BaseExplorerFileTypeHandler
       PipelineMeta pipelineMetaNew =
           new PipelineMeta(xmlStreamNew, hopGui.getMetadataProvider(), hopGui.getVariables());
 
-      pipelineMetaOld = HopDiff.compareTransforms(pipelineMetaOld, pipelineMetaNew, true);
+      boolean ignorePosition = GitConfigSingleton.getConfig().isIgnoringPositionInDiff();
+      pipelineMetaOld =
+          HopDiff.compareTransforms(pipelineMetaOld, pipelineMetaNew, true, ignorePosition);
       pipelineMetaOld = HopDiff.comparePipelineHops(pipelineMetaOld, pipelineMetaNew, true);
-      pipelineMetaNew = HopDiff.compareTransforms(pipelineMetaNew, pipelineMetaOld, false);
+      pipelineMetaNew =
+          HopDiff.compareTransforms(pipelineMetaNew, pipelineMetaOld, false, ignorePosition);
       pipelineMetaNew = HopDiff.comparePipelineHops(pipelineMetaNew, pipelineMetaOld, false);
 
       pipelineMetaOld.setPipelineVersion(CONST_GIT + commitIdOld);
@@ -490,9 +494,12 @@ public class GitInfoExplorerFileTypeHandler extends BaseExplorerFileTypeHandler
       WorkflowMeta workflowMetaNew =
           new WorkflowMeta(xmlStreamNew, hopGui.getMetadataProvider(), hopGui.getVariables());
 
-      workflowMetaOld = HopDiff.compareActions(workflowMetaOld, workflowMetaNew, true);
+      boolean ignorePosition = GitConfigSingleton.getConfig().isIgnoringPositionInDiff();
+      workflowMetaOld =
+          HopDiff.compareActions(workflowMetaOld, workflowMetaNew, true, ignorePosition);
       workflowMetaOld = HopDiff.compareWorkflowHops(workflowMetaOld, workflowMetaNew, true);
-      workflowMetaNew = HopDiff.compareActions(workflowMetaNew, workflowMetaOld, false);
+      workflowMetaNew =
+          HopDiff.compareActions(workflowMetaNew, workflowMetaOld, false, ignorePosition);
       workflowMetaNew = HopDiff.compareWorkflowHops(workflowMetaNew, workflowMetaOld, false);
 
       workflowMetaOld.setWorkflowVersion(CONST_GIT + commitIdOld);
diff --git a/plugins/misc/git/src/main/resources/org/apache/hop/git/config/messages/messages_en_US.properties b/plugins/misc/git/src/main/resources/org/apache/hop/git/config/messages/messages_en_US.properties
index fb684fe17a..de3dc9da3a 100644
--- a/plugins/misc/git/src/main/resources/org/apache/hop/git/config/messages/messages_en_US.properties
+++ b/plugins/misc/git/src/main/resources/org/apache/hop/git/config/messages/messages_en_US.properties
@@ -21,4 +21,6 @@ GitConfig.SearchParentFolders.Message=Search parent folders for git repository
 GitConfig.SavingOption.ErrorDialog.Header=Error
 GitConfig.SavingOption.ErrorDialog.Message=Error saving option
 GitConfig.FetchAutomatic.Message=Fetch remote changes automatically
-GitConfig.FetchAutomatic.Tooltip=Enables background fetching of remote updates every 20 minutes.
\ No newline at end of file
+GitConfig.FetchAutomatic.Tooltip=Enables background fetching of remote updates every 20 minutes.
+GitConfig.IgnorePositionInDiff.Message=Ignore position in the visual diff
+GitConfig.IgnorePositionInDiff.Tooltip=Do not mark a transform or action as changed when it was only moved.
\ No newline at end of file
diff --git a/plugins/misc/git/src/test/java/org/apache/hop/git/HopDiffTest.java b/plugins/misc/git/src/test/java/org/apache/hop/git/HopDiffTest.java
index 42d6c0226b..44ac074811 100644
--- a/plugins/misc/git/src/test/java/org/apache/hop/git/HopDiffTest.java
+++ b/plugins/misc/git/src/test/java/org/apache/hop/git/HopDiffTest.java
@@ -26,6 +26,7 @@ import static org.apache.hop.git.HopDiff.REMOVED;
 import static org.apache.hop.git.HopDiff.UNCHANGED;
 import static org.apache.hop.git.HopDiff.compareActions;
 import static org.apache.hop.git.HopDiff.compareTransforms;
+import static org.apache.hop.git.HopDiff.getPipelineHopName;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.io.File;
@@ -42,6 +43,7 @@ import org.apache.hop.core.variables.Variables;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.pipeline.transforms.calculator.CalculatorMeta;
 import org.apache.hop.pipeline.transforms.checksum.CheckSumMeta;
 import org.apache.hop.pipeline.transforms.csvinput.CsvInputMeta;
@@ -82,41 +84,87 @@ class HopDiffTest {
     metadataProvider = new MemoryMetadataProvider();
   }
 
+  private PipelineMeta loadPipeline(String name) throws Exception {
+    try (InputStream xmlStream = new FileInputStream(new File("src/test/resources/" + name))) {
+      return new PipelineMeta(xmlStream, metadataProvider, Variables.getADefaultVariableSpace());
+    }
+  }
+
+  private WorkflowMeta loadWorkflow(String name) throws Exception {
+    try (InputStream xmlStream = new FileInputStream(new File("src/test/resources/" + name))) {
+      return new WorkflowMeta(xmlStream, metadataProvider, new Variables());
+    }
+  }
+
   @Test
   void diffPipelineTest() throws Exception {
-    File file = new File("src/test/resources/r1.hpl");
-    InputStream xmlStream = new FileInputStream(file);
-    PipelineMeta pipelineMeta1 =
-        new PipelineMeta(xmlStream, metadataProvider, Variables.getADefaultVariableSpace());
-
-    File file2 = new File("src/test/resources/r2.hpl");
-    InputStream xmlStream2 = new FileInputStream(file2);
-    PipelineMeta pipelineMeta2 =
-        new PipelineMeta(xmlStream2, metadataProvider, Variables.getADefaultVariableSpace());
-
-    PipelineMeta resultForward = compareTransforms(pipelineMeta1, pipelineMeta2, true);
-    PipelineMeta resultBackward = compareTransforms(pipelineMeta2, pipelineMeta1, false);
+    PipelineMeta pipelineMeta1 = loadPipeline("r1.hpl");
+    PipelineMeta pipelineMeta2 = loadPipeline("r2.hpl");
+
+    PipelineMeta resultForward = compareTransforms(pipelineMeta1, pipelineMeta2, true, false);
+    PipelineMeta resultBackward = compareTransforms(pipelineMeta2, pipelineMeta1, false, false);
     assertEquals(CHANGED, resultForward.getTransform(0).getAttribute(ATTR_GIT, ATTR_STATUS));
     assertEquals(UNCHANGED, resultForward.getTransform(1).getAttribute(ATTR_GIT, ATTR_STATUS));
     assertEquals(REMOVED, resultForward.getTransform(2).getAttribute(ATTR_GIT, ATTR_STATUS));
     assertEquals(ADDED, resultBackward.getTransform(2).getAttribute(ATTR_GIT, ATTR_STATUS));
   }
 
+  /** The same pipeline with one transform dragged elsewhere, and nothing else touched. */
+  private PipelineMeta movedPipeline() throws Exception {
+    PipelineMeta pipelineMeta = loadPipeline("r1.hpl");
+    TransformMeta transform = pipelineMeta.getTransform(0);
+    transform.setLocation(transform.getLocation().x + 64, transform.getLocation().y + 32);
+    return pipelineMeta;
+  }
+
   @Test
-  void diffWorkflowTest() throws Exception {
-    File file = new File("src/test/resources/r1.hwf");
-    InputStream xmlStream = new FileInputStream(file);
-    WorkflowMeta jobMeta = new WorkflowMeta(xmlStream, metadataProvider, new Variables());
+  void movedTransformIsChangedWithoutTheOption() throws Exception {
+    PipelineMeta result = compareTransforms(loadPipeline("r1.hpl"), movedPipeline(), true, false);
+
+    assertEquals(CHANGED, result.getTransform(0).getAttribute(ATTR_GIT, ATTR_STATUS));
+  }
 
-    File file2 = new File("src/test/resources/r2.hwf");
-    InputStream xmlStream2 = new FileInputStream(file2);
-    WorkflowMeta jobMeta2 = new WorkflowMeta(xmlStream2, metadataProvider, new Variables());
+  @Test
+  void movedTransformIsUnchangedWithTheOption() throws Exception {
+    PipelineMeta result = compareTransforms(loadPipeline("r1.hpl"), movedPipeline(), true, true);
 
-    jobMeta = compareActions(jobMeta, jobMeta2, true);
-    jobMeta2 = compareActions(jobMeta2, jobMeta, false);
+    assertEquals(UNCHANGED, result.getTransform(0).getAttribute(ATTR_GIT, ATTR_STATUS));
+  }
+
+  @Test
+  void diffWorkflowTest() throws Exception {
+    WorkflowMeta jobMeta = loadWorkflow("r1.hwf");
+    WorkflowMeta jobMeta2 = loadWorkflow("r2.hwf");
+
+    jobMeta = compareActions(jobMeta, jobMeta2, true, false);
+    jobMeta2 = compareActions(jobMeta2, jobMeta, false, false);
     assertEquals(CHANGED, jobMeta.getAction(0).getAttribute(ATTR_GIT, ATTR_STATUS));
     assertEquals(UNCHANGED, jobMeta.getAction(1).getAttribute(ATTR_GIT, ATTR_STATUS));
     assertEquals(REMOVED, jobMeta.getAction(2).getAttribute(ATTR_GIT, ATTR_STATUS));
     assertEquals(ADDED, jobMeta2.getAction(2).getAttribute(ATTR_GIT, ATTR_STATUS));
   }
+
+  /** START only moved between r1 and r2, so it is unchanged once the position is left out. */
+  @Test
+  void diffWorkflowIgnoringPositionTest() throws Exception {
+    WorkflowMeta jobMeta = loadWorkflow("r1.hwf");
+    WorkflowMeta jobMeta2 = loadWorkflow("r2.hwf");
+
+    jobMeta = compareActions(jobMeta, jobMeta2, true, true);
+    jobMeta2 = compareActions(jobMeta2, jobMeta, false, true);
+    assertEquals("START", jobMeta.getAction(0).getName());
+    assertEquals(UNCHANGED, jobMeta.getAction(0).getAttribute(ATTR_GIT, ATTR_STATUS));
+    assertEquals(UNCHANGED, jobMeta.getAction(1).getAttribute(ATTR_GIT, ATTR_STATUS));
+    assertEquals(REMOVED, jobMeta.getAction(2).getAttribute(ATTR_GIT, ATTR_STATUS));
+    assertEquals(ADDED, jobMeta2.getAction(2).getAttribute(ATTR_GIT, ATTR_STATUS));
+  }
+
+  /** The name has to name both ends, otherwise hops sharing a source transform collide. */
+  @Test
+  void pipelineHopNameTest() throws Exception {
+    PipelineMeta pipelineMeta = loadPipeline("r1.hpl");
+
+    assertEquals(
+        "CSV file input - Add a checksum", getPipelineHopName(pipelineMeta.getPipelineHop(0)));
+  }
 }