Blame SOURCES/0001-Don-t-fail-merges-when-default-streams-differ.patch

9b3a95
From 774f36026f1d3a4215be67845c2873135ceab6e4 Mon Sep 17 00:00:00 2001
9b3a95
From: Stephen Gallagher <sgallagh@redhat.com>
9b3a95
Date: Wed, 16 Jan 2019 13:21:44 -0500
9b3a95
Subject: [PATCH] Don't fail merges when default streams differ
9b3a95
9b3a95
Instead of failing the merge on a default stream conflict, we will
9b3a95
instead treat it as having no default set. This is safe because if
9b3a95
the module is not yet installed, then this just means its packages
9b3a95
aren't visible and it needs to be selected explicitly.
9b3a95
9b3a95
If some packages from it are already installed, then the module
9b3a95
stream is already set, and thus changing the default won't matter.
9b3a95
9b3a95
Update documentation to reflect modified field in Defaults
9b3a95
9b3a95
Resolves: rhbz#1666871
9b3a95
9b3a95
Signed-off-by: Stephen Gallagher <sgallagh@redhat.com>
9b3a95
---
9b3a95
 .../modulemd-1.0/private/modulemd-private.h   |  2 +
9b3a95
 modulemd/v1/modulemd-defaults.c               | 39 +++++++----
9b3a95
 modulemd/v1/tests/test-modulemd-defaults.c    | 66 ++++++++++++++++++-
9b3a95
 .../modulemd-module-index-merger.h            | 18 +++--
9b3a95
 .../private/modulemd-defaults-v1-private.h    |  2 +
9b3a95
 modulemd/v2/modulemd-defaults-v1.c            | 59 +++++++++++------
9b3a95
 modulemd/v2/tests/ModulemdTests/merger.py     | 33 +++++++---
9b3a95
 .../v2/tests/test_data/overriding-nodejs.yaml | 11 ++++
9b3a95
 test_data/defaults/overriding-nodejs.yaml     | 10 +++
9b3a95
 9 files changed, 191 insertions(+), 49 deletions(-)
9b3a95
 create mode 100644 modulemd/v2/tests/test_data/overriding-nodejs.yaml
9b3a95
 create mode 100644 test_data/defaults/overriding-nodejs.yaml
9b3a95
9b3a95
diff --git a/modulemd/v1/include/modulemd-1.0/private/modulemd-private.h b/modulemd/v1/include/modulemd-1.0/private/modulemd-private.h
9b3a95
index 20604efab843de6ac64cd9e181ce93d7e3a798fe..e9a785aed0d38598ce08d58e7e507d01698788a0 100644
9b3a95
--- a/modulemd/v1/include/modulemd-1.0/private/modulemd-private.h
9b3a95
+++ b/modulemd/v1/include/modulemd-1.0/private/modulemd-private.h
9b3a95
@@ -24,10 +24,12 @@ enum
9b3a95
   MD_VERSION_MAX = G_MAXUINT64
9b3a95
 };
9b3a95
 
9b3a95
 #define MD_VERSION_LATEST MD_VERSION_2
9b3a95
 
9b3a95
+#define DEFAULT_MERGE_CONFLICT "__merge_conflict__"
9b3a95
+
9b3a95
 ModulemdModule *
9b3a95
 modulemd_module_new_from_modulestream (ModulemdModuleStream *stream);
9b3a95
 
9b3a95
 ModulemdModuleStream *
9b3a95
 modulemd_module_peek_modulestream (ModulemdModule *self);
9b3a95
diff --git a/modulemd/v1/modulemd-defaults.c b/modulemd/v1/modulemd-defaults.c
9b3a95
index 77ce30733945e30ffb8368986fa8c54c45697b5a..d44076eef0ac3bffae05f74b852fb6b51c2aee64 100644
9b3a95
--- a/modulemd/v1/modulemd-defaults.c
9b3a95
+++ b/modulemd/v1/modulemd-defaults.c
9b3a95
@@ -12,10 +12,11 @@
9b3a95
  */
9b3a95
 
9b3a95
 #include "modulemd.h"
9b3a95
 #include "modulemd-defaults.h"
9b3a95
 #include "modulemd-simpleset.h"
9b3a95
+#include "private/modulemd-private.h"
9b3a95
 #include "private/modulemd-yaml.h"
9b3a95
 
9b3a95
 
9b3a95
 GQuark
9b3a95
 modulemd_defaults_error_quark (void)
9b3a95
@@ -146,19 +147,38 @@ modulemd_defaults_set_default_stream (ModulemdDefaults *self,
9b3a95
 const gchar *
9b3a95
 modulemd_defaults_peek_default_stream (ModulemdDefaults *self)
9b3a95
 {
9b3a95
   g_return_val_if_fail (self, NULL);
9b3a95
 
9b3a95
+  if (self->default_stream &&
9b3a95
+      g_str_equal (self->default_stream, DEFAULT_MERGE_CONFLICT))
9b3a95
+    {
9b3a95
+      /* During an index merge, we determined that this was in conflict
9b3a95
+       * with another set of ModulemdDefaults for the same module. If we
9b3a95
+       * see this, treat it as no default stream when querying for it.
9b3a95
+       */
9b3a95
+      return NULL;
9b3a95
+    }
9b3a95
   return self->default_stream;
9b3a95
 }
9b3a95
 
9b3a95
 
9b3a95
 gchar *
9b3a95
 modulemd_defaults_dup_default_stream (ModulemdDefaults *self)
9b3a95
 {
9b3a95
   g_return_val_if_fail (self, NULL);
9b3a95
 
9b3a95
+  if (self->default_stream &&
9b3a95
+      g_str_equal (self->default_stream, DEFAULT_MERGE_CONFLICT))
9b3a95
+    {
9b3a95
+      /* During an index merge, we determined that this was in conflict
9b3a95
+       * with another set of ModulemdDefaults for the same module. If we
9b3a95
+       * see this, treat it as no default stream when querying for it.
9b3a95
+       */
9b3a95
+      return NULL;
9b3a95
+    }
9b3a95
+
9b3a95
   return g_strdup (self->default_stream);
9b3a95
 }
9b3a95
 
9b3a95
 
9b3a95
 void
9b3a95
@@ -753,28 +773,25 @@ modulemd_defaults_merge (ModulemdDefaults *first,
9b3a95
   /* They had the same 'modified' value (such as both zero, for
9b3a95
    * backwards-compatibility with 1.7.x and older.
9b3a95
    * Merge them as best we can.
9b3a95
    */
9b3a95
 
9b3a95
+  defaults = modulemd_defaults_copy (first);
9b3a95
+
9b3a95
   /* First check for incompatibilities with the streams */
9b3a95
-  if (g_strcmp0 (modulemd_defaults_peek_default_stream (first),
9b3a95
-                 modulemd_defaults_peek_default_stream (second)))
9b3a95
+  if (g_strcmp0 (first->default_stream, second->default_stream))
9b3a95
     {
9b3a95
       /* Default streams don't match and override is not set.
9b3a95
        * Return an error
9b3a95
        */
9b3a95
-      g_set_error (
9b3a95
-        error,
9b3a95
-        MODULEMD_DEFAULTS_ERROR,
9b3a95
-        MODULEMD_DEFAULTS_ERROR_CONFLICTING_STREAMS,
9b3a95
-        "Conflicting default streams when merging defaults for module %s",
9b3a95
-        modulemd_defaults_peek_module_name (first));
9b3a95
-      return NULL;
9b3a95
+      /* They have conflicting default streams */
9b3a95
+      g_info ("Module stream mismatch in merge: %s != %s",
9b3a95
+              first->default_stream,
9b3a95
+              second->default_stream);
9b3a95
+      modulemd_defaults_set_default_stream (defaults, DEFAULT_MERGE_CONFLICT);
9b3a95
     }
9b3a95
 
9b3a95
-  defaults = modulemd_defaults_copy (first);
9b3a95
-
9b3a95
   /* Merge the profile defaults */
9b3a95
   profile_defaults = modulemd_defaults_peek_profile_defaults (defaults);
9b3a95
 
9b3a95
   g_hash_table_iter_init (&iter,
9b3a95
                           modulemd_defaults_peek_profile_defaults (second));
9b3a95
diff --git a/modulemd/v1/tests/test-modulemd-defaults.c b/modulemd/v1/tests/test-modulemd-defaults.c
9b3a95
index 5d032d83cbca4046af13719f68bdbc6778d6879e..35142b885b3ec30f5109d53befe17be3e08d7b5a 100644
9b3a95
--- a/modulemd/v1/tests/test-modulemd-defaults.c
9b3a95
+++ b/modulemd/v1/tests/test-modulemd-defaults.c
9b3a95
@@ -602,10 +602,11 @@ modulemd_defaults_test_prioritizer (DefaultsFixture *fixture,
9b3a95
 {
9b3a95
   g_autofree gchar *yaml_base_path = NULL;
9b3a95
   g_autofree gchar *yaml_override_path = NULL;
9b3a95
   g_autoptr (GPtrArray) base_objects = NULL;
9b3a95
   g_autoptr (GPtrArray) override_objects = NULL;
9b3a95
+  g_autoptr (GPtrArray) override_nodejs_objects = NULL;
9b3a95
   g_autoptr (GPtrArray) merged_objects = NULL;
9b3a95
   g_autoptr (ModulemdPrioritizer) prioritizer = NULL;
9b3a95
   g_autoptr (GError) error = NULL;
9b3a95
   GHashTable *htable = NULL;
9b3a95
   gint64 prio;
9b3a95
@@ -618,10 +619,22 @@ modulemd_defaults_test_prioritizer (DefaultsFixture *fixture,
9b3a95
 
9b3a95
   base_objects = modulemd_objects_from_file (yaml_base_path, &error);
9b3a95
   g_assert_nonnull (base_objects);
9b3a95
   g_assert_cmpint (base_objects->len, ==, 7);
9b3a95
 
9b3a95
+
9b3a95
+  yaml_override_path =
9b3a95
+    g_strdup_printf ("%s/test_data/defaults/overriding-nodejs.yaml",
9b3a95
+                     g_getenv ("MESON_SOURCE_ROOT"));
9b3a95
+  g_assert_nonnull (yaml_override_path);
9b3a95
+
9b3a95
+  override_nodejs_objects =
9b3a95
+    modulemd_objects_from_file (yaml_override_path, &error);
9b3a95
+  g_clear_pointer (&yaml_override_path, g_free);
9b3a95
+  g_assert_nonnull (override_nodejs_objects);
9b3a95
+  g_assert_cmpint (override_nodejs_objects->len, ==, 1);
9b3a95
+
9b3a95
   yaml_override_path = g_strdup_printf (
9b3a95
     "%s/test_data/defaults/overriding.yaml", g_getenv ("MESON_SOURCE_ROOT"));
9b3a95
   g_assert_nonnull (yaml_override_path);
9b3a95
 
9b3a95
   override_objects = modulemd_objects_from_file (yaml_override_path, &error);
9b3a95
@@ -659,10 +672,52 @@ modulemd_defaults_test_prioritizer (DefaultsFixture *fixture,
9b3a95
       fprintf (stderr, "Merge error: %s", error->message);
9b3a95
     }
9b3a95
   g_assert_true (result);
9b3a95
 
9b3a95
 
9b3a95
+  /*
9b3a95
+   * Test that importing the nodejs overrides at the same priority level fails.
9b3a95
+   *
9b3a95
+   * This YAML has a conflicting default stream which should be ignored and set
9b3a95
+   * to "no default stream".
9b3a95
+   */
9b3a95
+
9b3a95
+  result = modulemd_prioritizer_add (
9b3a95
+    prioritizer, override_nodejs_objects, prio, &error);
9b3a95
+  g_assert_true (result);
9b3a95
+
9b3a95
+  merged_objects = modulemd_prioritizer_resolve (prioritizer, &error);
9b3a95
+  g_assert_nonnull (merged_objects);
9b3a95
+  g_assert_cmpint (merged_objects->len, ==, 3);
9b3a95
+
9b3a95
+  for (gint i = 0; i < merged_objects->len; i++)
9b3a95
+    {
9b3a95
+      if (MODULEMD_IS_DEFAULTS (g_ptr_array_index (merged_objects, i)))
9b3a95
+        {
9b3a95
+          defaults = g_ptr_array_index (merged_objects, i);
9b3a95
+          if (!g_strcmp0 (modulemd_defaults_peek_module_name (defaults),
9b3a95
+                          "nodejs"))
9b3a95
+            {
9b3a95
+              g_assert_null (modulemd_defaults_peek_default_stream (defaults));
9b3a95
+            }
9b3a95
+        }
9b3a95
+    }
9b3a95
+
9b3a95
+  g_clear_pointer (&merged_objects, g_ptr_array_unref);
9b3a95
+
9b3a95
+
9b3a95
+  /* Start over and test profile conflicts */
9b3a95
+  g_clear_pointer (&prioritizer, g_object_unref);
9b3a95
+  prioritizer = modulemd_prioritizer_new ();
9b3a95
+  result = modulemd_prioritizer_add (prioritizer, base_objects, prio, &error);
9b3a95
+  if (!result)
9b3a95
+    {
9b3a95
+      fprintf (stderr, "Merge error: %s", error->message);
9b3a95
+    }
9b3a95
+  g_assert_true (result);
9b3a95
+
9b3a95
+
9b3a95
   /*
9b3a95
    * Test that importing the overrides at the same priority level fails.
9b3a95
    *
9b3a95
    * These objects have several conflicts with the base objects that cannot be
9b3a95
    * merged.
9b3a95
@@ -671,11 +726,11 @@ modulemd_defaults_test_prioritizer (DefaultsFixture *fixture,
9b3a95
     modulemd_prioritizer_add (prioritizer, override_objects, prio, &error);
9b3a95
   g_assert_false (result);
9b3a95
   g_assert_cmpstr (
9b3a95
     g_quark_to_string (error->domain), ==, "modulemd-defaults-error-quark");
9b3a95
   g_assert_cmpint (
9b3a95
-    error->code, ==, MODULEMD_DEFAULTS_ERROR_CONFLICTING_STREAMS);
9b3a95
+    error->code, ==, MODULEMD_DEFAULTS_ERROR_CONFLICTING_PROFILES);
9b3a95
   g_clear_pointer (&error, g_error_free);
9b3a95
 
9b3a95
   /* The object's internal state is undefined after an error, so delete it */
9b3a95
   g_clear_pointer (&prioritizer, g_object_unref);
9b3a95
 
9b3a95
@@ -987,11 +1042,11 @@ modulemd_defaults_test_index_prioritizer (DefaultsFixture *fixture,
9b3a95
     modulemd_prioritizer_add_index (prioritizer, override_index, prio, &error);
9b3a95
   g_assert_false (result);
9b3a95
   g_assert_cmpstr (
9b3a95
     g_quark_to_string (error->domain), ==, "modulemd-defaults-error-quark");
9b3a95
   g_assert_cmpint (
9b3a95
-    error->code, ==, MODULEMD_DEFAULTS_ERROR_CONFLICTING_STREAMS);
9b3a95
+    error->code, ==, MODULEMD_DEFAULTS_ERROR_CONFLICTING_PROFILES);
9b3a95
   g_clear_pointer (&error, g_error_free);
9b3a95
 
9b3a95
   /* The object's internal state is undefined after an error, so delete it */
9b3a95
   g_clear_pointer (&prioritizer, g_object_unref);
9b3a95
 
9b3a95
@@ -1138,10 +1193,15 @@ modulemd_regressions_issue44 (DefaultsFixture *fixture,
9b3a95
   g_assert_true (result);
9b3a95
 
9b3a95
 
9b3a95
   /* Add another almost-identical document, except with a conflicting default
9b3a95
    * stream set.
9b3a95
+   *
9b3a95
+   * NOTE: when this was written (for issue 44 on GitHub), this was meant to be
9b3a95
+   * a hard error. As of 1.8.1 we expect this to just result in having no
9b3a95
+   * default stream for this module. This test has been slightly modified so
9b3a95
+   * that the expected result is now a pass.
9b3a95
    */
9b3a95
   yaml_conflicting_path = g_strdup_printf (
9b3a95
     "%s/test_data/defaults/issue44-2.yaml", g_getenv ("MESON_SOURCE_ROOT"));
9b3a95
   g_assert_nonnull (yaml_conflicting_path);
9b3a95
 
9b3a95
@@ -1149,11 +1209,11 @@ modulemd_regressions_issue44 (DefaultsFixture *fixture,
9b3a95
     modulemd_objects_from_file (yaml_conflicting_path, &error);
9b3a95
   g_assert_nonnull (conflicting_objects);
9b3a95
 
9b3a95
   result =
9b3a95
     modulemd_prioritizer_add (prioritizer, conflicting_objects, 0, &error);
9b3a95
-  g_assert_false (result);
9b3a95
+  g_assert_true (result);
9b3a95
 }
9b3a95
 
9b3a95
 
9b3a95
 static void
9b3a95
 modulemd_regressions_issue45 (DefaultsFixture *fixture,
9b3a95
diff --git a/modulemd/v2/include/modulemd-2.0/modulemd-module-index-merger.h b/modulemd/v2/include/modulemd-2.0/modulemd-module-index-merger.h
9b3a95
index 27cccb738dc805724268afa04acc13d4e250eae2..b019f0ed856684a003e9c2f6abefc70e1448246a 100644
9b3a95
--- a/modulemd/v2/include/modulemd-2.0/modulemd-module-index-merger.h
9b3a95
+++ b/modulemd/v2/include/modulemd-2.0/modulemd-module-index-merger.h
9b3a95
@@ -51,22 +51,30 @@ G_BEGIN_DECLS
9b3a95
  * there exists two #ModulemdModuleStream entries that have different content
9b3a95
  * for the same NSVC, the behavior is undefined.
9b3a95
  *
9b3a95
  * Merging #ModulemdDefaults entries behaves as follows:
9b3a95
  *
9b3a95
+ * - Within a ModuleIndex, if two or more default entries reference the same
9b3a95
+ *   module, the one with the highest modified field will be used and the
9b3a95
+ *   others discarded.
9b3a95
+ * - When merging ModuleIndexes, if two or more indexes contain Defaults for
9b3a95
+ *   the same module, but different modified values, the one with the highest
9b3a95
+ *   modified value will be used and the others discarded.
9b3a95
  * - Any module default that is provided by a single repository is
9b3a95
  *   authoritative.
9b3a95
  * - If the repos have different priorities (not common), then the default for
9b3a95
  *   this module and stream name coming from the repo of higher priority will
9b3a95
- *   be used and the default from the lower-priority repo will not be included.
9b3a95
+ *   be used and the default from the lower-priority repo will not be included
9b3a95
+ *   even if it has a higher modified value.
9b3a95
  * - If the repos have the same priority (such as "fedora" and "updates" in the
9b3a95
- *   Fedora Project), the entries will be merged as follows:
9b3a95
- *   - If both repositories specify a default stream for the module, use it.
9b3a95
+ *   Fedora Project) and modified value, the entries will be merged as follows:
9b3a95
+ *   - If both repositories specify the same default stream for the module, use
9b3a95
+ *     it.
9b3a95
  *   - If either repository specifies a default stream for the module and the
9b3a95
  *     other does not, use the one specified.
9b3a95
- *   - If both repositories specify different streams, this is an unresolvable
9b3a95
- *     merge conflict and the merge resolution will fail and report an error.
9b3a95
+ *   - If both repositories specify different default streams, the merge will
9b3a95
+ *     unset the default stream and proceed with the merge.
9b3a95
  *   - If both repositories specify a set of default profiles for a stream and
9b3a95
  *     the sets are equivalent, use that set.
9b3a95
  *   - If one repository specifies a set of default profiles for a stream and
9b3a95
  *     the other does not, use the one specified.
9b3a95
  *   - If both repositories specify a set of default profiles for a stream and
9b3a95
diff --git a/modulemd/v2/include/modulemd-2.0/private/modulemd-defaults-v1-private.h b/modulemd/v2/include/modulemd-2.0/private/modulemd-defaults-v1-private.h
9b3a95
index 9cac6cc53175a0eecca986f4fa96a7b7b1070957..de2ede98e6fb49bfd792b5f2913868fad6ff27db 100644
9b3a95
--- a/modulemd/v2/include/modulemd-2.0/private/modulemd-defaults-v1-private.h
9b3a95
+++ b/modulemd/v2/include/modulemd-2.0/private/modulemd-defaults-v1-private.h
9b3a95
@@ -26,10 +26,12 @@ G_BEGIN_DECLS
9b3a95
  * @stability: private
9b3a95
  * @short_description: #ModulemdDefault methods that should only be used by
9b3a95
  * internal consumers.
9b3a95
  */
9b3a95
 
9b3a95
+#define DEFAULT_MERGE_CONFLICT "__merge_conflict__"
9b3a95
+
9b3a95
 /**
9b3a95
  * modulemd_defaults_v1_parse_yaml:
9b3a95
  * @subdoc: (in): A #ModulemdSubdocumentInfo representing a defaults document
9b3a95
  * of metadata version 1.
9b3a95
  * @strict: (in): Whether the parser should return failure if it encounters an
9b3a95
diff --git a/modulemd/v2/modulemd-defaults-v1.c b/modulemd/v2/modulemd-defaults-v1.c
9b3a95
index c8f983105b529519365d0a653d8d07715f610b97..1c1cdd7b1036734a477b72d12f12526af1de777d 100644
9b3a95
--- a/modulemd/v2/modulemd-defaults-v1.c
9b3a95
+++ b/modulemd/v2/modulemd-defaults-v1.c
9b3a95
@@ -179,11 +179,22 @@ modulemd_defaults_v1_get_default_stream (ModulemdDefaultsV1 *self,
9b3a95
 {
9b3a95
   const gchar *default_stream = NULL;
9b3a95
   g_return_val_if_fail (MODULEMD_IS_DEFAULTS_V1 (self), NULL);
9b3a95
 
9b3a95
   if (!intent)
9b3a95
-    return self->default_stream;
9b3a95
+    {
9b3a95
+      if (self->default_stream &&
9b3a95
+          g_str_equal (self->default_stream, DEFAULT_MERGE_CONFLICT))
9b3a95
+        {
9b3a95
+          /* During an index merge, we determined that this was in conflict
9b3a95
+           * with another set of ModulemdDefaults for the same module. If we
9b3a95
+           * see this, treat it as no default stream when querying for it.
9b3a95
+           */
9b3a95
+          return NULL;
9b3a95
+        }
9b3a95
+      return self->default_stream;
9b3a95
+    }
9b3a95
 
9b3a95
   default_stream = g_hash_table_lookup (self->intent_default_streams, intent);
9b3a95
   if (default_stream)
9b3a95
     {
9b3a95
       if (default_stream[0] == '\0')
9b3a95
@@ -1159,12 +1170,10 @@ ModulemdDefaults *
9b3a95
 modulemd_defaults_v1_merge (const gchar *module_name,
9b3a95
                             ModulemdDefaultsV1 *from,
9b3a95
                             ModulemdDefaultsV1 *into,
9b3a95
                             GError **error)
9b3a95
 {
9b3a95
-  const gchar *into_default_stream;
9b3a95
-  const gchar *from_default_stream;
9b3a95
   g_autoptr (ModulemdDefaultsV1) merged = NULL;
9b3a95
   GHashTableIter iter;
9b3a95
   gpointer key, value;
9b3a95
   GHashTable *intent_profiles = NULL;
9b3a95
   GHashTable *merged_intent_profiles = NULL;
9b3a95
@@ -1175,37 +1184,45 @@ modulemd_defaults_v1_merge (const gchar *module_name,
9b3a95
   g_autoptr (GError) nested_error = NULL;
9b3a95
 
9b3a95
   merged = modulemd_defaults_v1_new (module_name);
9b3a95
 
9b3a95
   /* Merge the default streams */
9b3a95
-  into_default_stream = modulemd_defaults_v1_get_default_stream (into, NULL);
9b3a95
-  from_default_stream = modulemd_defaults_v1_get_default_stream (from, NULL);
9b3a95
-
9b3a95
-  if (into_default_stream && !from_default_stream)
9b3a95
+  if (into->default_stream && !from->default_stream)
9b3a95
     {
9b3a95
       modulemd_defaults_v1_set_default_stream (
9b3a95
-        merged, into_default_stream, NULL);
9b3a95
+        merged, into->default_stream, NULL);
9b3a95
     }
9b3a95
-  else if (from_default_stream && !into_default_stream)
9b3a95
+  else if (from->default_stream && !into->default_stream)
9b3a95
     {
9b3a95
       modulemd_defaults_v1_set_default_stream (
9b3a95
-        merged, from_default_stream, NULL);
9b3a95
+        merged, from->default_stream, NULL);
9b3a95
     }
9b3a95
-  else if (into_default_stream && from_default_stream)
9b3a95
+  else if (into->default_stream && from->default_stream)
9b3a95
     {
9b3a95
-      if (!g_str_equal (into_default_stream, from_default_stream))
9b3a95
+      if (g_str_equal (into->default_stream, DEFAULT_MERGE_CONFLICT))
9b3a95
         {
9b3a95
-          g_set_error (error,
9b3a95
-                       MODULEMD_ERROR,
9b3a95
-                       MODULEMD_ERROR_VALIDATE,
9b3a95
-                       "Module stream mismatch in merge: %s != %s",
9b3a95
-                       into_default_stream,
9b3a95
-                       from_default_stream);
9b3a95
-          return NULL;
9b3a95
+          /* A previous pass over this same module encountered a merge
9b3a95
+           * conflict, so we need to propagate that.
9b3a95
+           */
9b3a95
+          modulemd_defaults_v1_set_default_stream (
9b3a95
+            merged, DEFAULT_MERGE_CONFLICT, NULL);
9b3a95
+        }
9b3a95
+      else if (!g_str_equal (into->default_stream, from->default_stream))
9b3a95
+        {
9b3a95
+          /* They have conflicting default streams */
9b3a95
+          g_info ("Module stream mismatch in merge: %s != %s",
9b3a95
+                  into->default_stream,
9b3a95
+                  from->default_stream);
9b3a95
+          modulemd_defaults_v1_set_default_stream (
9b3a95
+            merged, DEFAULT_MERGE_CONFLICT, NULL);
9b3a95
+        }
9b3a95
+      else
9b3a95
+        {
9b3a95
+          /* They're the same, so store that */
9b3a95
+          modulemd_defaults_v1_set_default_stream (
9b3a95
+            merged, into->default_stream, NULL);
9b3a95
         }
9b3a95
-      modulemd_defaults_v1_set_default_stream (
9b3a95
-        merged, into_default_stream, NULL);
9b3a95
     }
9b3a95
   else
9b3a95
     {
9b3a95
       /* Both values were NULL.
9b3a95
        * Nothing to do, leave it blank.
9b3a95
diff --git a/modulemd/v2/tests/ModulemdTests/merger.py b/modulemd/v2/tests/ModulemdTests/merger.py
9b3a95
index e7cbf4214bb111fbc5732fe983ee5ccee134fd83..49c2722959486aeb250d88daf7f068cacc8c3fe7 100644
9b3a95
--- a/modulemd/v2/tests/ModulemdTests/merger.py
9b3a95
+++ b/modulemd/v2/tests/ModulemdTests/merger.py
9b3a95
@@ -104,26 +104,41 @@ class TestModuleIndexMerger(TestBase):
9b3a95
         self.assertEqual(
9b3a95
             len(httpd_defaults.get_default_profiles_for_stream('2.4', 'workstation')), 1)
9b3a95
         self.assertEqual(
9b3a95
             len(httpd_defaults.get_default_profiles_for_stream('2.6', 'workstation')), 3)
9b3a95
 
9b3a95
+        # Get another set of objects that will override the default stream for
9b3a95
+        # nodejs
9b3a95
+        override_nodejs_index = Modulemd.ModuleIndex()
9b3a95
+        override_nodejs_index.update_from_file(
9b3a95
+            path.join(
9b3a95
+                self.source_root,
9b3a95
+                "modulemd/v2/tests/test_data/overriding-nodejs.yaml"), True)
9b3a95
+
9b3a95
+        # Test that adding both of these at the same priority level results in
9b3a95
+        # the no default stream
9b3a95
+        merger = Modulemd.ModuleIndexMerger()
9b3a95
+        merger.associate_index(base_index, 0)
9b3a95
+        merger.associate_index(override_nodejs_index, 0)
9b3a95
+
9b3a95
+        merged_index = merger.resolve()
9b3a95
+        self.assertIsNotNone(merged_index)
9b3a95
+
9b3a95
+        nodejs = merged_index.get_module('nodejs')
9b3a95
+        self.assertIsNotNone(nodejs)
9b3a95
+
9b3a95
+        nodejs_defaults = nodejs.get_defaults()
9b3a95
+        self.assertIsNotNone(nodejs_defaults)
9b3a95
+        self.assertIsNone(nodejs_defaults.get_default_stream())
9b3a95
+
9b3a95
         # Get another set of objects that will override the above
9b3a95
         override_index = Modulemd.ModuleIndex()
9b3a95
         override_index.update_from_file(
9b3a95
             path.join(
9b3a95
                 self.source_root,
9b3a95
                 "modulemd/v2/tests/test_data/overriding.yaml"), True)
9b3a95
 
9b3a95
-        # Test that adding both of these at the same priority level fails
9b3a95
-        # with a merge conflict.
9b3a95
-        merger = Modulemd.ModuleIndexMerger()
9b3a95
-        merger.associate_index(base_index, 0)
9b3a95
-        merger.associate_index(override_index, 0)
9b3a95
-
9b3a95
-        with self.assertRaisesRegex(GLib.GError, 'Module stream mismatch in merge'):
9b3a95
-            merged_index = merger.resolve()
9b3a95
-
9b3a95
         # Test that override_index at a higher priority level succeeds
9b3a95
         # Test that adding both of these at the same priority level fails
9b3a95
         # with a merge conflict.
9b3a95
         # Use randomly-selected high and low values to make sure we don't have
9b3a95
         # sorting issues.
9b3a95
diff --git a/modulemd/v2/tests/test_data/overriding-nodejs.yaml b/modulemd/v2/tests/test_data/overriding-nodejs.yaml
9b3a95
new file mode 100644
9b3a95
index 0000000000000000000000000000000000000000..b588f5f542d84755fa74fc1dbf059c0766d2a712
9b3a95
--- /dev/null
9b3a95
+++ b/modulemd/v2/tests/test_data/overriding-nodejs.yaml
9b3a95
@@ -0,0 +1,11 @@
9b3a95
+---
9b3a95
+# Override the default stream
9b3a95
+document: modulemd-defaults
9b3a95
+version: 1
9b3a95
+data:
9b3a95
+    module: nodejs
9b3a95
+    stream: '9.0'
9b3a95
+    profiles:
9b3a95
+        '6.0': [default]
9b3a95
+        '8.0': [super]
9b3a95
+        '9.0': [supermegaultra]
9b3a95
\ No newline at end of file
9b3a95
diff --git a/test_data/defaults/overriding-nodejs.yaml b/test_data/defaults/overriding-nodejs.yaml
9b3a95
new file mode 100644
9b3a95
index 0000000000000000000000000000000000000000..937a2b899afc6b5254fd3b3e61e5b32035a3130a
9b3a95
--- /dev/null
9b3a95
+++ b/test_data/defaults/overriding-nodejs.yaml
9b3a95
@@ -0,0 +1,10 @@
9b3a95
+---
9b3a95
+document: modulemd-defaults
9b3a95
+version: 1
9b3a95
+data:
9b3a95
+    module: nodejs
9b3a95
+    stream: '9.0'
9b3a95
+    profiles:
9b3a95
+        '6.0': [default]
9b3a95
+        '8.0': [super]
9b3a95
+        '9.0': [supermegaultra]
9b3a95
\ No newline at end of file
9b3a95
-- 
9b3a95
2.20.1
9b3a95