Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor UpdateInfoUrlFinder, enhance ForgeType #3145

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import eu.timepit.refined.auto._
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.headers.`User-Agent`
import org.scalasteward.core.application.Config.StewardUsage
import org.scalasteward.core.application.Config.{ForgeCfg, StewardUsage}
import org.scalasteward.core.buildtool.BuildToolDispatcher
import org.scalasteward.core.buildtool.maven.MavenAlg
import org.scalasteward.core.buildtool.mill.MillAlg
Expand Down Expand Up @@ -211,8 +211,8 @@ object Context {
implicit val forgeApiAlg: ForgeApiAlg[F] =
ForgeSelection.forgeApiAlg[F](config.forgeCfg, config.forgeSpecificCfg, forgeUser)
implicit val forgeRepoAlg: ForgeRepoAlg[F] = new ForgeRepoAlg[F](config)
implicit val updateInfoUrlFinder: UpdateInfoUrlFinder[F] =
new UpdateInfoUrlFinder[F](config.forgeCfg)
implicit val forgeCfg: ForgeCfg = config.forgeCfg
implicit val updateInfoUrlFinder: UpdateInfoUrlFinder[F] = new UpdateInfoUrlFinder[F]
implicit val pullRequestRepository: PullRequestRepository[F] =
new PullRequestRepository[F](pullRequestsStore)
implicit val scalafixCli: ScalafixCli[F] = new ScalafixCli[F]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ final case class Version(value: String) {
}

object Version {
val tagNames: List[Version => String] = List("v" + _, _.value, "release-" + _)

implicit val versionCodec: Codec[Version] =
deriveUnwrappedCodec

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ package org.scalasteward.core.forge

import cats.Eq
import cats.syntax.all._
import org.http4s.Uri
import org.http4s.syntax.literals._
import org.scalasteward.core.application.Config.ForgeCfg
import org.scalasteward.core.forge.ForgeType._
import org.scalasteward.core.util.unexpectedString

sealed trait ForgeType extends Product with Serializable {
def publicWebHost: Option[String]
val diffs: DiffUriPattern
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quibble: Can we add some ScalaDocs here to explain what this method should do?

This comment follows the conventionalcomments.org standard

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done with 905250c !

val files: FileUriPattern
def supportsForking: Boolean = true
def supportsLabels: Boolean = true

Expand All @@ -38,35 +42,64 @@ sealed trait ForgeType extends Product with Serializable {
}

object ForgeType {
trait DiffUriPattern { def forDiff(from: String, to: String): Uri => Uri }
trait FileUriPattern { def forFile(fileName: String): Uri => Uri }
Comment on lines +55 to +56
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileUriPattern & DiffUriPattern have been designed so that they can be concisely implemented using the lambda syntax for Single Abstract Method (SAM) types, eg:

val diffs: DiffUriPattern = (from, to) => _ / "compare" / s"$from...$to"
val files: FileUriPattern = fileName => _ / "blob" / "master" / fileName

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


case object AzureRepos extends ForgeType {
override val publicWebHost: Some[String] = Some("dev.azure.com")
override def supportsForking: Boolean = false
val diffs: DiffUriPattern = (from, to) =>
_ / "branchCompare" withQueryParams Map(
"baseVersion" -> s"GT$from",
"targetVersion" -> s"GT$to"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: You can also use the operator to add query params +?

_ / "branchCompare" +? ("baseVersion", s"GT$from") +? ("targetVersion", s"GT$to")

This comment follows the conventionalcomments.org standard

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, nice one! Updated with fca39d4 👍

)
val files: FileUriPattern =
fileName =>
_.withQueryParam(
"path",
fileName
) // Azure's canonical value for the path is prefixed with a slash?
}

case object Bitbucket extends ForgeType {
override val publicWebHost: Some[String] = Some("bitbucket.org")
override def supportsLabels: Boolean = false
val publicApiBaseUrl = uri"https://api.bitbucket.org/2.0"
val diffs: DiffUriPattern = (from, to) => _ / "compare" / s"$to..$from" withFragment "diff"
val files: FileUriPattern = fileName => _ / "src" / "master" / fileName
}

/** Note Bitbucket Server will be End Of Service Life on 15th February 2024:
*
* https://www.atlassian.com/software/bitbucket/enterprise
* https://www.atlassian.com/migration/assess/journey-to-cloud
*/
case object BitbucketServer extends ForgeType {
override val publicWebHost: None.type = None
override def supportsForking: Boolean = false
override def supportsLabels: Boolean = false
val diffs: DiffUriPattern = Bitbucket.diffs
val files: FileUriPattern = fileName => _ / "browse" / fileName
}

case object GitHub extends ForgeType {
override val publicWebHost: Some[String] = Some("github.com")
val publicApiBaseUrl = uri"https://api.github.com"
val diffs: DiffUriPattern = (from, to) => _ / "compare" / s"$from...$to"
val files: FileUriPattern = fileName => _ / "blob" / "master" / fileName
}

case object GitLab extends ForgeType {
override val publicWebHost: Some[String] = Some("gitlab.com")
val publicApiBaseUrl = uri"https://gitlab.com/api/v4"
val diffs: DiffUriPattern = GitHub.diffs
val files: FileUriPattern = GitHub.files
}

case object Gitea extends ForgeType {
override val publicWebHost: Option[String] = None
val diffs: DiffUriPattern = GitHub.diffs
val files: FileUriPattern = fileName => _ / "src" / "branch" / "master" / fileName
}

val all: List[ForgeType] = List(AzureRepos, Bitbucket, BitbucketServer, GitHub, GitLab, Gitea)
Expand All @@ -83,6 +116,16 @@ object ForgeType {
def fromPublicWebHost(host: String): Option[ForgeType] =
all.find(_.publicWebHost.contains_(host))

/** Attempts to guess, based on the uri host and the config used to launch Scala Steward, what
* type of forge hosts the repo at the supplied uri.
*/
def fromRepoUrl(repoUrl: Uri)(implicit config: ForgeCfg): Option[ForgeType] =
repoUrl.host.flatMap { repoHost =>
Option
.when(config.apiHost.host.contains(repoHost))(config.tpe)
.orElse(fromPublicWebHost(repoHost.value))
}

implicit val forgeTypeEq: Eq[ForgeType] =
Eq.fromUniversalEquals
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import org.scalasteward.core.nurture.UpdateInfoUrl._
import org.scalasteward.core.nurture.UpdateInfoUrlFinder.possibleUpdateInfoUrls
import org.scalasteward.core.util.UrlChecker

final class UpdateInfoUrlFinder[F[_]](config: ForgeCfg)(implicit
final class UpdateInfoUrlFinder[F[_]](implicit
config: ForgeCfg,
urlChecker: UrlChecker[F],
F: Monad[F]
) {
Expand All @@ -40,7 +41,9 @@ final class UpdateInfoUrlFinder[F[_]](config: ForgeCfg)(implicit
val updateInfoUrls =
metadata.releaseNotesUrl.toList.map(CustomReleaseNotes.apply) ++
metadata.repoUrl.toList.flatMap { repoUrl =>
possibleUpdateInfoUrls(config.tpe, config.apiHost, repoUrl, currentVersion, nextVersion)
ForgeType.fromRepoUrl(repoUrl).toSeq.flatMap { forgeType =>
possibleUpdateInfoUrls(forgeType, repoUrl, currentVersion, nextVersion)
}
}

updateInfoUrls
Expand All @@ -51,8 +54,6 @@ final class UpdateInfoUrlFinder[F[_]](config: ForgeCfg)(implicit
}

object UpdateInfoUrlFinder {
private def possibleTags(version: Version): List[String] =
List(s"v$version", version.value, s"release-$version")

private[nurture] val possibleChangelogFilenames: List[String] = {
val baseNames = List(
Expand All @@ -74,84 +75,44 @@ object UpdateInfoUrlFinder {
possibleFilenames(baseNames)
}

private def forgeTypeFromRepoUrl(
forgeType: ForgeType,
forgeUrl: Uri,
repoUrl: Uri
): Option[ForgeType] =
repoUrl.host.flatMap { repoHost =>
if (forgeUrl.host.contains(repoHost)) Some(forgeType)
else ForgeType.fromPublicWebHost(repoHost.value)
}

private[nurture] def possibleVersionDiffs(
forgeType: ForgeType,
forgeUrl: Uri,
repoUrl: Uri,
currentVersion: Version,
nextVersion: Version
): List[VersionDiff] =
forgeTypeFromRepoUrl(forgeType, forgeUrl, repoUrl).map {
case GitHub | GitLab | Gitea =>
possibleTags(currentVersion).zip(possibleTags(nextVersion)).map { case (from1, to1) =>
VersionDiff(repoUrl / "compare" / s"$from1...$to1")
}
case Bitbucket | BitbucketServer =>
possibleTags(currentVersion).zip(possibleTags(nextVersion)).map { case (from1, to1) =>
VersionDiff((repoUrl / "compare" / s"$to1..$from1").withFragment("diff"))
}
case AzureRepos =>
possibleTags(currentVersion).zip(possibleTags(nextVersion)).map { case (from1, to1) =>
VersionDiff(
(repoUrl / "branchCompare")
.withQueryParams(Map("baseVersion" -> from1, "targetVersion" -> to1))
)
}
}.orEmpty
): List[VersionDiff] = for {
tagName <- Version.tagNames
} yield VersionDiff(
forgeType.diffs.forDiff(tagName(currentVersion), tagName(nextVersion))(repoUrl)
)

private[nurture] def possibleUpdateInfoUrls(
forgeType: ForgeType,
forgeUrl: Uri,
repoUrl: Uri,
currentVersion: Version,
nextVersion: Version
): List[UpdateInfoUrl] = {
val repoForgeType = forgeTypeFromRepoUrl(forgeType, forgeUrl, repoUrl)

val githubReleaseNotes = repoForgeType
.collect { case GitHub =>
possibleTags(nextVersion).map(tag => GitHubReleaseNotes(repoUrl / "releases" / "tag" / tag))
}
.getOrElse(List.empty)

def files(fileNames: List[String]): List[Uri] = {
val maybeSegments = repoForgeType.collect {
case GitHub | GitLab => List("blob", "master")
case Bitbucket => List("master")
case BitbucketServer => List("browse")
case Gitea => List("src", "branch", "master")
}
def customUrls(wrap: Uri => UpdateInfoUrl, fileNames: List[String]): List[UpdateInfoUrl] =
fileNames.map(f => wrap(forgeType.files.forFile(f)(repoUrl)))

val repoFiles = maybeSegments.toList.flatMap { segments =>
val base = segments.foldLeft(repoUrl)(_ / _)
fileNames.map(name => base / name)
}

val azureRepoFiles = repoForgeType
.collect { case AzureRepos => fileNames.map(name => repoUrl.withQueryParam("path", name)) }
.toList
.flatten
gitHubReleaseNotesFor(forgeType, repoUrl, nextVersion) ++
customUrls(CustomReleaseNotes, possibleReleaseNotesFilenames) ++
customUrls(CustomChangelog, possibleChangelogFilenames) ++
possibleVersionDiffs(forgeType, repoUrl, currentVersion, nextVersion)
}

repoFiles ++ azureRepoFiles
private def gitHubReleaseNotesFor(
forgeType: ForgeType,
repoUrl: Uri,
version: Version
): List[UpdateInfoUrl] =
forgeType match {
case GitHub =>
Version.tagNames
.map(tagName => GitHubReleaseNotes(repoUrl / "releases" / "tag" / tagName(version)))
case _ => Nil
}

val customChangelog = files(possibleChangelogFilenames).map(CustomChangelog)
val customReleaseNotes = files(possibleReleaseNotesFilenames).map(CustomReleaseNotes)

githubReleaseNotes ++ customReleaseNotes ++ customChangelog ++
possibleVersionDiffs(forgeType, forgeUrl, repoUrl, currentVersion, nextVersion)
}

private def possibleFilenames(baseNames: List[String]): List[String] = {
val extensions = List("md", "markdown", "rst")
(baseNames, extensions).mapN { case (base, ext) => s"$base.$ext" }
Expand Down
Loading