diff --git a/core/modules/migrate/src/Row.php b/core/modules/migrate/src/Row.php
index 4be9712..7b3669e 100644
--- a/core/modules/migrate/src/Row.php
+++ b/core/modules/migrate/src/Row.php
@@ -176,9 +176,12 @@ public function setSourceProperty($property, $data) {
 
   /**
    * Freezes the source.
+   *
+   * @return $this
    */
   public function freezeSource() {
     $this->frozen = TRUE;
+    return $this;
   }
 
   /**
diff --git a/core/modules/migrate/src/Source.php b/core/modules/migrate/src/Source.php
index be4368b..7f30557 100644
--- a/core/modules/migrate/src/Source.php
+++ b/core/modules/migrate/src/Source.php
@@ -32,7 +32,7 @@ class Source implements \Iterator, \Countable {
    *
    * @var array
    */
-  protected $currentIds;
+  protected $currentSourceIds;
 
   /**
    * Number of rows intentionally ignored (prepareRow() returned FALSE)
@@ -122,10 +122,10 @@ class Source implements \Iterator, \Countable {
   protected $highWaterProperty;
 
   /**
-   * Getter for currentIds data member.
+   * Getter for currentSourceIds data member.
    */
   public function getCurrentIds() {
-    return $this->currentIds;
+    return $this->currentSourceIds;
   }
 
   /**
@@ -262,7 +262,7 @@ public function current() {
    * preferable.
    */
   public function key() {
-    return serialize($this->currentIds);
+    return serialize($this->currentSourceIds);
   }
 
   /**
@@ -297,118 +297,79 @@ public function rewind() {
 
   /**
    * {@inheritdoc}
+   *
+   * The migration iterates over a couple of source rows. This method determines
+   * the next row, which will be processed and imported into the system.
+   *
+   * The method tracks the source and destination IDs using the ID map.
+   *
+   * The also takes care about highwater support. Highwater allows to reimport
+   * rows from a previous migration run, which got changed in the meantime.
+   * This is done by specifying a highwater field, which is compared with the
+   * last time, the migration got executed (originalHighWater).
    */
   public function next() {
-    $this->currentIds = NULL;
+    $this->currentSourceIds = NULL;
     $this->currentRow = NULL;
     $source_configuration = $this->migration->get('source');
 
+    // In order to find the next row we want to process, we ask the source
+    // plugin for the next possible row.
     while ($this->getIterator()->valid()) {
       $row_data = $this->getIterator()->current() + $source_configuration;
       $this->getIterator()->next();
       $row = new Row($row_data, $this->migration->getSourcePlugin()->getIds(), $this->migration->get('destinationIds'));
 
       // Populate the source key for this row.
-      $this->currentIds = $row->getSourceIdValues();
+      $this->currentSourceIds = $row->getSourceIdValues();
 
       // Pick up the existing map row, if any, unless getNextRow() did it.
-      if (!$this->mapRowAdded && ($id_map = $this->idMap->getRowBySource($this->currentIds))) {
+      if (!$this->mapRowAdded && ($id_map = $this->idMap->getRowBySource($this->currentSourceIds))) {
         $row->setIdMap($id_map);
       }
 
-      // First, determine if this row should be passed to prepareRow(), or
-      // skipped entirely. The rules are:
-      // 1. If there's an explicit idlist, that's all we care about (ignore
-      //    high waters and map rows).
-      $prepared = FALSE;
-      if (!empty($this->idList)) {
-        if (in_array(reset($this->currentIds), $this->idList)) {
-          // In the list, fall through.
-        }
-        else {
-          // Not in the list, skip it.
-          continue;
-        }
-      }
-      // 2. If the row is not in the map (we have never tried to import it
-      //    before), we always want to try it.
-      elseif (!$row->getIdMap()) {
-        // Fall through
-      }
-      // 3. If the row is marked as needing update, pass it.
-      elseif ($row->needsUpdate()) {
-        // Fall through.
-      }
-      // 4. At this point, we have a row which has previously been imported and
-      //    not marked for update. If we're not using high water marks, then we
-      //    will not take this row. Except, if we're looking for changes in the
-      //    data, we need to go through prepareRow() before we can decide to
-      //    skip it.
-      elseif (!empty($this->highWaterProperty['field'])) {
-        if ($this->trackChanges) {
-          if ($this->prepareRow($row) !== FALSE) {
-            if ($row->changed()) {
-              // This is a keeper
-              $this->currentRow = $row;
-              break;
-            }
-            else {
-              // No change, skip it.
-              continue;
-            }
-          }
-          else {
-            // prepareRow() told us to skip it.
-            continue;
-          }
-        }
-        else {
-          // No high water and not tracking changes, skip.
-          continue;
-        }
-      }
-      // 5. The initial high water mark, before anything is migrated, is ''. We
-      //    want to make sure we don't mistakenly skip rows with a high water
-      //    field value of 0, so explicitly handle '' here.
-      elseif ($this->originalHighWater === '') {
-        // Fall through
+      // If there is an explicit ID list, we know that we want to process the
+      // row.
+      $id_in_the_list = $this->idList && in_array(reset($this->currentSourceIds), $this->idList);
+      // In case we have specified an ID list, but the ID given by the source is
+      // not in there, we skip the row.
+      if ($this->idList && !$id_in_the_list) {
+        continue;
       }
-      // 6. So, we are using high water marks. Take the row if its high water
-      //    field value is greater than the saved mark, otherwise skip it.
-      else {
-        // Call prepareRow() here, in case the highWaterField needs preparation.
-        if ($this->prepareRow($row) !== FALSE) {
-          if ($row->getSourceProperty($this->highWaterProperty['name']) > $this->originalHighWater) {
-            $this->currentRow = $row;
-            break;
-          }
-          else {
-            // Skip.
-            continue;
-          }
-        }
-        $prepared = TRUE;
-      }
-
-      // Allow the Migration to prepare this row. prepareRow() can return
-      // boolean FALSE to ignore this row.
-      if (!$prepared) {
+      if (
+        // Allow explicit specified IDs.
+        $id_in_the_list ||
+        // We haven't imported the row yet, so the ID map is empty.
+        !$row->getIdMap() ||
+        // All checks below are just applied, if the row got imported before.
+
+        // Rows can be marked as required to be updated (MigrateIdMapInterface::STATUS_NEEDS_UPDATE).
+        $row->needsUpdate() ||
+        // For migrations with highwater support,
+        ($this->usesHighWater() && $row->getSourceProperty($this->highWaterProperty['name']) > $this->originalHighWater) || // now let's try to see whether the source has changed, either via highwater which is like node.changed
+        // Without highwater, we figure out a change by using a hash of the row.
+        (!$this->usesHighwater() && $this->trackedRowChanged($row)) ) {
+        // Migrate fires a hook for every row to allow skipping them. On top of
+        // do general house keeping by preparing the row.
         if ($this->prepareRow($row) !== FALSE) {
-          // Finally, we've got a keeper.
-          $this->currentRow = $row;
+          // Stop the searching for the next row.
+          $this->currentRow = $row->freezeSource();
           break;
         }
         else {
+          // The preparation failed, let's try the next one.
           $this->currentRow = NULL;
         }
       }
     }
-    if ($this->currentRow) {
-      $this->currentRow->freezeSource();
-    }
-    else {
-      $this->currentIds = NULL;
-    }
+  }
+
+  protected function trackedRowChanged($row) {
+    return $this->trackChanges && $this->prepareRow($row) !== FALSE && $row->changed();
+  }
+
+  protected function usesHighwater() {
+    return $this->highWaterProperty && $this->originalHighWater !== '';
   }
 
   /**
@@ -426,12 +387,12 @@ protected function prepareRow(Row $row) {
       // Make sure we replace any previous messages for this item with any
       // new ones.
       $id_map = $this->migration->getIdMap();
-      $id_map->delete($this->currentIds, TRUE);
+      $id_map->delete($this->currentSourceIds, TRUE);
       $this->migrateExecutable->saveQueuedMessages();
       $id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->migrateExecutable->rollbackAction);
       $this->numIgnored++;
       $this->currentRow = NULL;
-      $this->currentIds = NULL;
+      $this->currentSourceIds = NULL;
     }
     else {
       // When tracking changed data, We want to quietly skip (rather than
