Repository: scala-steward-org/scala-steward (scala)

Issue:
Include changelog AND release notes in PR body
When Scala Steward creates a PR it tries to find a URL for a changelog or the release notes and if found includes this in the PR body. Some projects still have an old changelog around but switched to GitHub release notes ([doobie](https://github.com/tpolecat/doobie/blob/7dd53820309045dc84d103a31cceeb0ddf71fc94/CHANGELOG.md) is one example). For these projects Scala Steward includes the URL to the outdated changelog instead to the more helpful and up-to-date release notes in the PR. I therefore think we should always include both URLs in the PR.

Pull request description:
Adresses #1285

Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/ReleaseRelatedUrl.scala b/modules/core/src/main/scala/org/scalasteward/core/data/ReleaseRelatedUrl.scala
new file mode 100644
index 0000000000..2a16f8ad90
--- /dev/null
+++ b/modules/core/src/main/scala/org/scalasteward/core/data/ReleaseRelatedUrl.scala
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-2020 Scala Steward contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.scalasteward.core.data
+
+import org.http4s.Uri
+
+sealed trait ReleaseRelatedUrl {
+  def url: Uri
+}
+
+object ReleaseRelatedUrl {
+  final case class CustomChangelog(url: Uri) extends ReleaseRelatedUrl
+  final case class CustomReleaseNotes(url: Uri) extends ReleaseRelatedUrl
+  final case class GitHubReleaseNotes(url: Uri) extends ReleaseRelatedUrl
+  final case class VersionDiff(url: Uri) extends ReleaseRelatedUrl
+}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
index 70e96f45da..4712aac183 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
@@ -140,20 +140,16 @@ final class NurtureAlg[F[_]](
       artifactIdToUrl <- coursierAlg.getArtifactIdUrlMapping(
         Scope(data.update.dependencies.toList, resolvers)
       )
-      branchCompareUrl <- artifactIdToUrl
+      releaseRelatedUrls <- artifactIdToUrl
         .get(data.update.mainArtifactId)
-        .flatTraverse(vcsExtraAlg.getBranchCompareUrl(_, data.update))
-      releaseNoteUrl <- artifactIdToUrl
-        .get(data.update.mainArtifactId)
-        .flatTraverse(vcsExtraAlg.getReleaseNoteUrl(_, data.update))
+        .traverse(vcsExtraAlg.getReleaseRelatedUrls(_, data.update))
       branchName = vcs.createBranch(config.vcsType, data.fork, data.update)
       migrations <- migrationAlg.findMigrations(data.update)
       requestData = NewPullRequestData.from(
         data,
         branchName,
         artifactIdToUrl,
-        branchCompareUrl,
-        releaseNoteUrl,
+        releaseRelatedUrls.getOrElse(List.empty),
         migrations
       )
       pr <- vcsApiAlg.createPullRequest(data.repo, requestData)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/vcs/VCSExtraAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/vcs/VCSExtraAlg.scala
index f550190fb0..91f6fbb49a 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/vcs/VCSExtraAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/vcs/VCSExtraAlg.scala
@@ -19,13 +19,12 @@ package org.scalasteward.core.vcs
 import cats.Monad
 import cats.implicits._
 import org.http4s.Uri
-import org.scalasteward.core.data.Update
+import org.scalasteward.core.data.{ReleaseRelatedUrl, Update}
 import org.scalasteward.core.util.HttpExistenceClient
 import org.scalasteward.core.vcs
 
 trait VCSExtraAlg[F[_]] {
-  def getBranchCompareUrl(repoUrl: Uri, update: Update): F[Option[Uri]]
-  def getReleaseNoteUrl(repoUrl: Uri, update: Update): F[Option[Uri]]
+  def getReleaseRelatedUrls(repoUrl: Uri, update: Update): F[List[ReleaseRelatedUrl]]
 }
 
 object VCSExtraAlg {
@@ -34,10 +33,10 @@ object VCSExtraAlg {
       existenceClient: HttpExistenceClient[F],
       F: Monad[F]
   ): VCSExtraAlg[F] = new VCSExtraAlg[F] {
-    override def getBranchCompareUrl(repoUrl: Uri, update: Update): F[Option[Uri]] =
-      vcs.possibleCompareUrls(repoUrl, update).findM(existenceClient.exists)
+    override def getReleaseRelatedUrls(repoUrl: Uri, update: Update): F[List[ReleaseRelatedUrl]] =
+      vcs
+        .possibleReleaseRelatedUrls(repoUrl, update)
+        .filterA(releaseRelatedUrl => existenceClient.exists(releaseRelatedUrl.url))
 
-    override def getReleaseNoteUrl(repoUrl: Uri, update: Update): F[Option[Uri]] =
-      vcs.possibleChangelogUrls(repoUrl, update).findM(existenceClient.exists)
   }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala b/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala
index acaa8b41ba..2332977379 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/vcs/data/NewPullRequestData.scala
@@ -20,7 +20,7 @@ import cats.implicits._
 import io.circe.Encoder
 import io.circe.generic.semiauto._
 import org.http4s.Uri
-import org.scalasteward.core.data.{GroupId, SemVer, Update}
+import org.scalasteward.core.data.{GroupId, ReleaseRelatedUrl, SemVer, Update}
 import org.scalasteward.core.git
 import org.scalasteward.core.git.Branch
 import org.scalasteward.core.nurture.UpdateData
@@ -42,8 +42,7 @@ object NewPullRequestData {
   def bodyFor(
       update: Update,
       artifactIdToUrl: Map[String, Uri],
-      branchCompareUrl: Option[Uri],
-      releaseNoteUrl: Option[Uri],
+      releaseRelatedUrls: List[ReleaseRelatedUrl],
       migrations: List[Migration]
   ): String = {
     val artifacts = artifactsWithOptionalUrl(update, artifactIdToUrl)
@@ -52,8 +51,8 @@ object NewPullRequestData {
     val labels =
       Nel.fromList(List(updateType(update)) ++ semVerLabel(update).toList ++ migrationLabel.toList)
 
-    s"""|Updates $artifacts ${fromTo(update, branchCompareUrl)}.
-        |${releaseNote(releaseNoteUrl).getOrElse("")}
+    s"""|Updates $artifacts ${fromTo(update)}.
+        |${releaseNote(releaseRelatedUrls).getOrElse("")}
         |
         |I'll automatically update this PR to resolve conflicts as long as you don't change it yourself.
         |
@@ -77,18 +76,25 @@ object NewPullRequestData {
       "library-update"
   }
 
-  def releaseNote(releaseNoteUrl: Option[Uri]): Option[String] =
-    releaseNoteUrl.map { url =>
-      s"[Release Notes/Changelog](${url.renderString})"
-    }
+  def releaseNote(releaseRelatedUrls: List[ReleaseRelatedUrl]): Option[String] =
+    if (releaseRelatedUrls.isEmpty) None
+    else
+      releaseRelatedUrls
+        .map { url =>
+          url match {
+            case ReleaseRelatedUrl.CustomChangelog(url) => s"[Changelog](${url.renderString})"
+            case ReleaseRelatedUrl.CustomReleaseNotes(url) =>
+              s"[Release Notes](${url.renderString})"
+            case ReleaseRelatedUrl.GitHubReleaseNotes(url) =>
+              s"[GitHub Release Notes](${url.renderString})"
+            case ReleaseRelatedUrl.VersionDiff(url) => s"[Version Diff](${url.renderString})"
+          }
+        }
+        .mkString(" - ")
+        .some
 
-  def fromTo(update: Update, branchCompareUrl: Option[Uri]): String = {
-    val fromToVersions = s"from ${update.currentVersion} to ${update.nextVersion}"
-    branchCompareUrl match {
-      case None             => fromToVersions
-      case Some(compareUrl) => s"[${fromToVersions}](${compareUrl.renderString})"
-    }
-  }
+  def fromTo(update: Update): String =
+    s"from ${update.currentVersion} to ${update.nextVersion}"
 
   def artifactsWithOptionalUrl(update: Update, artifactIdToUrl: Map[String, Uri]): String =
     update match {
@@ -146,8 +152,7 @@ object NewPullRequestData {
       data: UpdateData,
       branchName: String,
       artifactIdToUrl: Map[String, Uri] = Map.empty,
-      branchCompareUrl: Option[Uri] = None,
-      releaseNoteUrl: Option[Uri] = None,
+      releaseRelatedUrls: List[ReleaseRelatedUrl] = List.empty,
       migrations: List[Migration] = List.empty
   ): NewPullRequestData =
     NewPullRequestData(
@@ -155,8 +160,7 @@ object NewPullRequestData {
       body = bodyFor(
         data.update,
         artifactIdToUrl,
-        branchCompareUrl,
-        releaseNoteUrl,
+        releaseRelatedUrls,
         migrations
       ),
       head = branchName,
diff --git a/modules/core/src/main/scala/org/scalasteward/core/vcs/package.scala b/modules/core/src/main/scala/org/scalasteward/core/vcs/package.scala
index b9faac0d5a..78762d0347 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/vcs/package.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/vcs/package.scala
@@ -20,7 +20,8 @@ import cats.implicits._
 import org.http4s.Uri
 import org.scalasteward.core.application.SupportedVCS
 import org.scalasteward.core.application.SupportedVCS.{Bitbucket, BitbucketServer, GitHub, Gitlab}
-import org.scalasteward.core.data.Update
+import org.scalasteward.core.data.ReleaseRelatedUrl.VersionDiff
+import org.scalasteward.core.data.{ReleaseRelatedUrl, Update}
 import org.scalasteward.core.vcs.data.Repo
 
 package object vcs {
@@ -53,45 +54,53 @@ package object vcs {
     List(s"v$version", version, s"release-$version")
 
   val possibleChangelogFilenames: List[String] = {
-    val basenames = List(
+    val baseNames = List(
       "CHANGELOG",
       "Changelog",
       "changelog",
-      "CHANGES",
+      "CHANGES"
+    )
+    possibleFilenames(baseNames)
+  }
+
+  val possibleReleaseNotesFilenames: List[String] = {
+    val baseNames = List(
       "ReleaseNotes",
       "RELEASES",
       "Releases",
       "releases"
     )
-    val extensions = List("md", "markdown", "rst")
-    (basenames, extensions).mapN { case (base, ext) => s"$base.$ext" }
+    possibleFilenames(baseNames)
   }
 
-  def possibleCompareUrls(repoUrl: Uri, update: Update): List[Uri] = {
+  def possibleCompareUrls(repoUrl: Uri, update: Update): List[VersionDiff] = {
     val host = repoUrl.host.map(_.value)
     val from = update.currentVersion
     val to = update.nextVersion
 
     if (host.exists(Set("github.com", "gitlab.com")))
       possibleTags(from).zip(possibleTags(to)).map {
-        case (from1, to1) => repoUrl / "compare" / s"$from1...$to1"
+        case (from1, to1) => VersionDiff(repoUrl / "compare" / s"$from1...$to1")
       }
     else if (host.contains_("bitbucket.org"))
       possibleTags(from).zip(possibleTags(to)).map {
-        case (from1, to1) => (repoUrl / "compare" / s"$to1..$from1").withFragment("diff")
+        case (from1, to1) =>
+          VersionDiff((repoUrl / "compare" / s"$to1..$from1").withFragment("diff"))
       }
     else
       List.empty
   }
 
-  def possibleChangelogUrls(repoUrl: Uri, update: Update): List[Uri] = {
+  def possibleReleaseRelatedUrls(repoUrl: Uri, update: Update): List[ReleaseRelatedUrl] = {
     val host = repoUrl.host.map(_.value)
-    val vcsSpecific =
+    val github =
       if (host.contains_("github.com"))
-        possibleTags(update.nextVersion).map(tag => repoUrl / "releases" / "tag" / tag)
+        possibleTags(update.nextVersion).map(tag =>
+          ReleaseRelatedUrl.GitHubReleaseNotes(repoUrl / "releases" / "tag" / tag)
+        )
       else
         List.empty
-    val files = {
+    def files(fileNames: List[String]): List[Uri] = {
       val maybeSegments =
         if (host.exists(Set("github.com", "gitlab.com"))) {
           Some(List("blob", "master"))
@@ -102,9 +111,18 @@ package object vcs {
         }
       maybeSegments.toList.flatMap { segments =>
         val base = segments.foldLeft(repoUrl)(_ / _)
-        possibleChangelogFilenames.map(name => base / name)
+        fileNames.map(name => base / name)
       }
     }
-    files ++ vcsSpecific
+    val customChangelog = files(possibleChangelogFilenames).map(ReleaseRelatedUrl.CustomChangelog)
+    val customReleaseNotes =
+      files(possibleReleaseNotesFilenames).map(ReleaseRelatedUrl.CustomReleaseNotes)
+
+    github ++ customReleaseNotes ++ customChangelog ++ possibleCompareUrls(repoUrl, update)
+  }
+
+  private def possibleFilenames(baseNames: List[String]): List[String] = {
+    val extensions = List("md", "markdown", "rst")
+    (baseNames, extensions).mapN { case (base, ext) => s"$base.$ext" }
   }
 }


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSExtraAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSExtraAlgTest.scala
index 983b1976b4..c6dbc68267 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSExtraAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSExtraAlgTest.scala
@@ -7,7 +7,7 @@ import org.http4s.dsl.io._
 import org.http4s.implicits._
 import org.scalasteward.core.TestInstances.ioLogger
 import org.scalasteward.core.TestSyntax._
-import org.scalasteward.core.data.Update
+import org.scalasteward.core.data.{ReleaseRelatedUrl, Update}
 import org.scalasteward.core.mock.MockContext.config
 import org.scalasteward.core.util.{HttpExistenceClient, Nel}
 import org.scalatest.funsuite.AnyFunSuite
@@ -32,13 +32,15 @@ class VCSExtraAlgTest extends AnyFunSuite with Matchers {
 
   test("getBranchCompareUrl") {
     vcsExtraAlg
-      .getBranchCompareUrl(uri"https://github.com/foo/foo", updateFoo)
-      .unsafeRunSync() shouldBe None
+      .getReleaseRelatedUrls(uri"https://github.com/foo/foo", updateFoo)
+      .unsafeRunSync() shouldBe List.empty
     vcsExtraAlg
-      .getBranchCompareUrl(uri"https://github.com/foo/bar", updateBar)
-      .unsafeRunSync() shouldBe Some(uri"https://github.com/foo/bar/compare/v0.1.0...v0.2.0")
+      .getReleaseRelatedUrls(uri"https://github.com/foo/bar", updateBar)
+      .unsafeRunSync() shouldBe List(
+      ReleaseRelatedUrl.VersionDiff(uri"https://github.com/foo/bar/compare/v0.1.0...v0.2.0")
+    )
     vcsExtraAlg
-      .getBranchCompareUrl(uri"https://github.com/foo/buz", updateBuz)
-      .unsafeRunSync() shouldBe None
+      .getReleaseRelatedUrls(uri"https://github.com/foo/buz", updateBuz)
+      .unsafeRunSync() shouldBe List.empty
   }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSPackageTest.scala b/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSPackageTest.scala
index bb52040142..26abd06136 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSPackageTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/vcs/VCSPackageTest.scala
@@ -26,27 +26,27 @@ class VCSPackageTest extends AnyFunSuite with Matchers {
 
   test("possibleCompareUrls") {
     possibleCompareUrls(uri"https://github.com/foo/bar", update)
-      .map(_.renderString) shouldBe List(
+      .map(_.url.renderString) shouldBe List(
       "https://github.com/foo/bar/compare/v1.2.0...v1.2.3",
       "https://github.com/foo/bar/compare/1.2.0...1.2.3",
       "https://github.com/foo/bar/compare/release-1.2.0...release-1.2.3"
     )
     // should canonicalize (drop last slash)
     possibleCompareUrls(uri"https://github.com/foo/bar/", update)
-      .map(_.renderString) shouldBe List(
+      .map(_.url.renderString) shouldBe List(
       "https://github.com/foo/bar/compare/v1.2.0...v1.2.3",
       "https://github.com/foo/bar/compare/1.2.0...1.2.3",
       "https://github.com/foo/bar/compare/release-1.2.0...release-1.2.3"
     )
 
     possibleCompareUrls(uri"https://gitlab.com/foo/bar", update)
-      .map(_.renderString) shouldBe List(
+      .map(_.url.renderString) shouldBe List(
       "https://gitlab.com/foo/bar/compare/v1.2.0...v1.2.3",
       "https://gitlab.com/foo/bar/compare/1.2.0...1.2.3",
       "https://gitlab.com/foo/bar/compare/release-1.2.0...release-1.2.3"
     )
     possibleCompareUrls(uri"https://bitbucket.org/foo/bar", update)
-      .map(_.renderString) shouldBe List(
+      .map(_.url.renderString) shouldBe List(
       "https://bitbucket.org/foo/bar/compare/v1.2.3..v1.2.0#diff",
       "https://bitbucket.org/foo/bar/compare/1.2.3..1.2.0#diff",
       "https://bitbucket.org/foo/bar/compare/release-1.2.3..release-1.2.0#diff"
@@ -56,20 +56,11 @@ class VCSPackageTest extends AnyFunSuite with Matchers {
   }
 
   test("possibleChangelogUrls: github.com") {
-    possibleChangelogUrls(uri"https://github.com/foo/bar", update)
-      .map(_.renderString) shouldBe List(
-      "https://github.com/foo/bar/blob/master/CHANGELOG.md",
-      "https://github.com/foo/bar/blob/master/CHANGELOG.markdown",
-      "https://github.com/foo/bar/blob/master/CHANGELOG.rst",
-      "https://github.com/foo/bar/blob/master/Changelog.md",
-      "https://github.com/foo/bar/blob/master/Changelog.markdown",
-      "https://github.com/foo/bar/blob/master/Changelog.rst",
-      "https://github.com/foo/bar/blob/master/changelog.md",
-      "https://github.com/foo/bar/blob/master/changelog.markdown",
-      "https://github.com/foo/bar/blob/master/changelog.rst",
-      "https://github.com/foo/bar/blob/master/CHANGES.md",
-      "https://github.com/foo/bar/blob/master/CHANGES.markdown",
-      "https://github.com/foo/bar/blob/master/CHANGES.rst",
+    possibleReleaseRelatedUrls(uri"https://github.com/foo/bar", update)
+      .map(_.url.renderString) shouldBe List(
+      "https://github.com/foo/bar/releases/tag/v1.2.3",
+      "https://github.com/foo/bar/releases/tag/1.2.3",
+      "https://github.com/foo/bar/releases/tag/release-1.2.3",
       "https://github.com/foo/bar/blob/master/ReleaseNotes.md",
       "https://github.com/foo/bar/blob/master/ReleaseNotes.markdown",
       "https://github.com/foo/bar/blob/master/ReleaseNotes.rst",
@@ -82,26 +73,50 @@ class VCSPackageTest extends AnyFunSuite with Matchers {
       "https://github.com/foo/bar/blob/master/releases.md",
       "https://github.com/foo/bar/blob/master/releases.markdown",
       "https://github.com/foo/bar/blob/master/releases.rst",
-      "https://github.com/foo/bar/releases/tag/v1.2.3",
-      "https://github.com/foo/bar/releases/tag/1.2.3",
-      "https://github.com/foo/bar/releases/tag/release-1.2.3"
+      "https://github.com/foo/bar/blob/master/CHANGELOG.md",
+      "https://github.com/foo/bar/blob/master/CHANGELOG.markdown",
+      "https://github.com/foo/bar/blob/master/CHANGELOG.rst",
+      "https://github.com/foo/bar/blob/master/Changelog.md",
+      "https://github.com/foo/bar/blob/master/Changelog.markdown",
+      "https://github.com/foo/bar/blob/master/Changelog.rst",
+      "https://github.com/foo/bar/blob/master/changelog.md",
+      "https://github.com/foo/bar/blob/master/changelog.markdown",
+      "https://github.com/foo/bar/blob/master/changelog.rst",
+      "https://github.com/foo/bar/blob/master/CHANGES.md",
+      "https://github.com/foo/bar/blob/master/CHANGES.markdown",
+      "https://github.com/foo/bar/blob/master/CHANGES.rst",
+      "https://github.com/foo/bar/compare/v1.2.0...v1.2.3",
+      "https://github.com/foo/bar/compare/1.2.0...1.2.3",
+      "https://github.com/foo/bar/compare/release-1.2.0...release-1.2.3"
     )
   }
 
   test("possibleChangelogUrls: gitlab.com") {
-    possibleChangelogUrls(uri"https://gitlab.com/foo/bar", update)
-      .map(_.renderString) shouldBe
-      possibleChangelogFilenames.map(name => s"https://gitlab.com/foo/bar/blob/master/$name")
+    possibleReleaseRelatedUrls(uri"https://gitlab.com/foo/bar", update)
+      .map(_.url.renderString) shouldBe
+      possibleReleaseNotesFilenames.map(name => s"https://gitlab.com/foo/bar/blob/master/$name") ++
+        possibleChangelogFilenames.map(name => s"https://gitlab.com/foo/bar/blob/master/$name") ++
+        List(
+          "https://gitlab.com/foo/bar/compare/v1.2.0...v1.2.3",
+          "https://gitlab.com/foo/bar/compare/1.2.0...1.2.3",
+          "https://gitlab.com/foo/bar/compare/release-1.2.0...release-1.2.3"
+        )
   }
 
   test("possibleChangelogUrls: bitbucket.org") {
-    possibleChangelogUrls(uri"https://bitbucket.org/foo/bar", update)
-      .map(_.renderString) shouldBe
-      possibleChangelogFilenames.map(name => s"https://bitbucket.org/foo/bar/master/$name")
+    possibleReleaseRelatedUrls(uri"https://bitbucket.org/foo/bar", update)
+      .map(_.url.renderString) shouldBe
+      possibleReleaseNotesFilenames.map(name => s"https://bitbucket.org/foo/bar/master/$name") ++
+        possibleChangelogFilenames.map(name => s"https://bitbucket.org/foo/bar/master/$name") ++
+        List(
+          "https://bitbucket.org/foo/bar/compare/v1.2.3..v1.2.0#diff",
+          "https://bitbucket.org/foo/bar/compare/1.2.3..1.2.0#diff",
+          "https://bitbucket.org/foo/bar/compare/release-1.2.3..release-1.2.0#diff"
+        )
   }
 
   test("possibleChangelogUrls: homepage") {
-    possibleChangelogUrls(uri"https://scalacenter.github.io/scalafix/", update)
-      .map(_.renderString) shouldBe List()
+    possibleReleaseRelatedUrls(uri"https://scalacenter.github.io/scalafix/", update)
+      .map(_.url.renderString) shouldBe List()
   }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala b/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala
index 4d9c5ee444..f63a5fbffc 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/vcs/data/NewPullRequestDataTest.scala
@@ -3,7 +3,7 @@ package org.scalasteward.core.vcs.data
 import io.circe.syntax._
 import org.http4s.syntax.literals._
 import org.scalasteward.core.TestSyntax._
-import org.scalasteward.core.data.{Update, Version}
+import org.scalasteward.core.data.{ReleaseRelatedUrl, Update, Version}
 import org.scalasteward.core.git.{Branch, Sha1}
 import org.scalasteward.core.nurture.UpdateData
 import org.scalasteward.core.repoconfig.RepoConfig
@@ -39,21 +39,33 @@ class NewPullRequestDataTest extends AnyFunSuite with Matchers {
 
   test("fromTo") {
     NewPullRequestData.fromTo(
-      Update.Single("com.example" % "foo" % "1.2.0", Nel.of("1.2.3")),
-      None
+      Update.Single("com.example" % "foo" % "1.2.0", Nel.of("1.2.3"))
     ) shouldBe "from 1.2.0 to 1.2.3"
-
-    NewPullRequestData.fromTo(
-      Update.Group("com.example" % Nel.of("foo", "bar") % "1.2.0", Nel.of("1.2.3")),
-      Some(uri"http://example.com/compare/v1.2.0...v1.2.3")
-    ) shouldBe "[from 1.2.0 to 1.2.3](http://example.com/compare/v1.2.0...v1.2.3)"
   }
 
   test("links to release notes/changelog") {
-    NewPullRequestData.releaseNote(None) shouldBe None
+    NewPullRequestData.releaseNote(List.empty) shouldBe None
+
+    NewPullRequestData.releaseNote(
+      List(ReleaseRelatedUrl.CustomChangelog(uri"https://github.com/foo/foo/CHANGELOG.rst"))
+    ) shouldBe Some(
+      "[Changelog](https://github.com/foo/foo/CHANGELOG.rst)"
+    )
 
-    NewPullRequestData.releaseNote(Some(uri"https://github.com/foo/foo/CHANGELOG.rst")) shouldBe Some(
-      "[Release Notes/Changelog](https://github.com/foo/foo/CHANGELOG.rst)"
+    NewPullRequestData.releaseNote(
+      List(
+        ReleaseRelatedUrl.CustomChangelog(uri"https://github.com/foo/foo/CHANGELOG.rst"),
+        ReleaseRelatedUrl.GitHubReleaseNotes(uri"https://github.com/foo/foo/releases/tag/v1.2.3"),
+        ReleaseRelatedUrl.CustomReleaseNotes(
+          uri"https://github.com/foo/bar/blob/master/ReleaseNotes.md"
+        ),
+        ReleaseRelatedUrl.CustomReleaseNotes(
+          uri"https://github.com/foo/bar/blob/master/Releases.md"
+        ),
+        ReleaseRelatedUrl.VersionDiff(uri"https://github.com/foo/foo/compare/v1.2.0...v1.2.3")
+      )
+    ) shouldBe Some(
+      "[Changelog](https://github.com/foo/foo/CHANGELOG.rst) - [GitHub Release Notes](https://github.com/foo/foo/releases/tag/v1.2.3) - [Release Notes](https://github.com/foo/bar/blob/master/ReleaseNotes.md) - [Release Notes](https://github.com/foo/bar/blob/master/Releases.md) - [Version Diff](https://github.com/foo/foo/compare/v1.2.0...v1.2.3)"
     )
   }