Repository: scala-steward-org/scala-steward
Pull request #3305 (2024-02-29 11:49:04)

Fix the merging order updates.pin
The merging of the default Scala Steward configuration for pinned artifacts was reversed.

In contrast to the comment and intend:
```Scala
//  Strategy: union with repo preference in terms of revision
```

This pull-request fixes the merging order and adapts the test. In addition it adds tests that show how `updates.pin` can  be used to control Scala LTS release updates.
Discussed in 
- #3302 
- #2467 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/scala-steward-org/scala-steward/pull/3305?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 91.18%. Comparing base [(`7ad5462`)](https://app.codecov.io/gh/scala-steward-org/scala-steward/commit/7ad54620e03939f4541efe0d5b0443c9154cec8c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org) to head [(`c926cd6`)](https://app.codecov.io/gh/scala-steward-org/scala-steward/pull/3305?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org).


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #3305   +/-   ##
=======================================
  Coverage   91.18%   91.18%           
=======================================
  Files         166      166           
  Lines        3404     3404           
  Branches      317      317           
=======================================
  Hits         3104     3104           
  Misses        300      300           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/scala-steward-org/scala-steward/pull/3305?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scala-steward-org).


Patch:
diff --git a/modules/core/src/main/resources/default.scala-steward.conf b/modules/core/src/main/resources/default.scala-steward.conf
index 9d5dff3e..d36c72d4 100644
--- a/modules/core/src/main/resources/default.scala-steward.conf
+++ b/modules/core/src/main/resources/default.scala-steward.conf
@@ -34,9 +34,9 @@ updates.ignore = [
   { groupId = "org.scala-lang", artifactId = "scala3-compiler",     version = { exact = "3.4.0" } },
   { groupId = "org.scala-lang", artifactId = "scala3-library",      version = { exact = "3.4.0" } },
   { groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = { exact = "3.4.0" } },
-  { groupId = "org.scala-lang", artifactId = "scala3-compiler",     version = { exact = "3.3.3" } },
-  { groupId = "org.scala-lang", artifactId = "scala3-library",      version = { exact = "3.3.3" } },
-  { groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = { exact = "3.3.3" } },
+  { groupId = "org.scala-lang", artifactId = "scala3-compiler",     version = { exact = "3.3.4" } },
+  { groupId = "org.scala-lang", artifactId = "scala3-library",      version = { exact = "3.3.4" } },
+  { groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = { exact = "3.3.4" } },
 
 
   // Ignore the 3.3.2 version as it is abandonned due to broken compatibility
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
index 12000369..c01f03e5 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/UpdatesConfig.scala
@@ -133,9 +133,9 @@ object UpdatesConfig {
       x: List[UpdatePattern],
       y: List[UpdatePattern]
   ): List[UpdatePattern] =
-    x ::: y.filterNot { p1 =>
-      x.exists(p2 => p1.groupId === p2.groupId && p1.artifactId === p2.artifactId)
-    }
+    x.filterNot { p1 =>
+      y.exists(p2 => p1.groupId === p2.groupId && p1.artifactId === p2.artifactId)
+    } ::: y
 
   private[repoconfig] val nonExistingUpdatePattern: List[UpdatePattern] =
     List(UpdatePattern(GroupId("non-exist"), None, None))


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigLoaderTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigLoaderTest.scala
new file mode 100644
index 00000000..fd3ce988
--- /dev/null
+++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigLoaderTest.scala
@@ -0,0 +1,42 @@
+package org.scalasteward.core.repoconfig
+
+import cats.effect.unsafe.implicits.global
+import munit.FunSuite
+import org.http4s.Uri
+import org.scalasteward.core.application.Config.RepoConfigCfg
+import org.scalasteward.core.mock.MockConfig.mockRoot
+import org.scalasteward.core.mock.MockContext.context.{fileAlg, logger}
+import org.scalasteward.core.mock.MockContext.mockState
+
+class RepoConfigLoaderTest extends FunSuite {
+  test("config file merging order") {
+    val config1 =
+      """
+        |updates.pin = [
+        |  { groupId = "org.scala-lang", artifactId="scala3-compiler", version = "3.3." },
+        |  { groupId = "org.scala-lang", artifactId="scala3-compiler", version = "3.5." },
+        |]
+        |""".stripMargin
+    val config2 =
+      """
+        |updates.pin = [
+        |  { groupId = "org.scala-lang", artifactId="scala3-compiler", version = "3.4." },
+        |]
+        |""".stripMargin
+
+    val uri1 = Uri.unsafeFromString(s"$mockRoot/test1.scala-steward.conf")
+    val uri2 = Uri.unsafeFromString(s"$mockRoot/test2.scala-steward.conf")
+
+    val initialState = mockState.addUris(uri1 -> config1, uri2 -> config2)
+
+    val repoConfigLoader = new RepoConfigLoader
+    val repoConfig = repoConfigLoader
+      .loadGlobalRepoConfig(RepoConfigCfg(repoConfigs = List(uri1, uri2), disableDefault = true))
+      .runA(initialState)
+      .attempt
+      .unsafeRunSync()
+      .getOrElse(None)
+    assert(clue(repoConfig).isDefined)
+    assertEquals(repoConfig.get.updates.pin.head.version, Some(VersionPattern(Some("3.4."))))
+  }
+}
diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala
index 368e54bd..3c35d8de 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/UpdatesConfigTest.scala
@@ -30,8 +30,31 @@ class UpdatesConfigTest extends DisciplineSuite {
 
     assertEquals(UpdatesConfig.mergePin(List(aa1), List(aa1, ac3)), List(aa1, ac3))
 
-    assertEquals(UpdatesConfig.mergePin(List(aa1), List(aa2)), List(aa1))
-    assertEquals(UpdatesConfig.mergePin(List(aa2), List(aa1)), List(aa2))
+    assertEquals(UpdatesConfig.mergePin(List(aa1), List(aa2)), List(aa2))
+    assertEquals(UpdatesConfig.mergePin(List(aa2), List(aa1)), List(aa1))
+  }
+
+  test("mergePin: scala 3 LTS") {
+    val groupIdScala = GroupId("org.scala-lang")
+    val scala = Some("scala3-compiler")
+    val s33 = UpdatePattern(groupIdScala, scala, Some(VersionPattern(Some("3.3."))))
+    val s34 = UpdatePattern(groupIdScala, scala, Some(VersionPattern(Some("3.4."))))
+    val s35 = UpdatePattern(groupIdScala, scala, Some(VersionPattern(Some("3.5."))))
+
+    def mergeDefaultWithLocal(
+        default: List[UpdatePattern],
+        local: List[UpdatePattern]
+    ): List[UpdatePattern] =
+      UpdatesConfig.mergePin(default, local)
+
+    assertEquals(mergeDefaultWithLocal(default = List(s33), local = Nil), List(s33))
+    assertEquals(mergeDefaultWithLocal(default = List(s33), local = List(s34)), List(s34))
+    assertEquals(mergeDefaultWithLocal(default = List(s33, s35), local = Nil), List(s33, s35))
+    assertEquals(mergeDefaultWithLocal(default = List(s33, s35), local = List(s34)), List(s34))
+    assertEquals(
+      mergeDefaultWithLocal(default = List(s33, s35), local = List(s34, s35)),
+      List(s34, s35)
+    )
   }
 
   test("mergeAllow: basic checks") {