From 5398bca9eb7f9e2866787b581f8d976deb23cb5e Mon Sep 17 00:00:00 2001 From: Blaz Kranjc Date: Fri, 14 Apr 2023 11:18:03 +0200 Subject: [PATCH 1/5] Add tests for multiple config locations --- .../core/repoconfig/RepoConfigAlgTest.scala | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala index 025ea979b8..ee42f6aa06 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala @@ -264,6 +264,51 @@ class RepoConfigAlgTest extends FunSuite { assert(clue(log).contains(startOfErrorMsg)) } + test("config file in .github/") { + val repo = Repo("test", "dot-github-config") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val rootConfigFile = repoDir / ".scala-steward.conf" + val dotGithubConfigFile = repoDir / ".github" / ".scala-steward.conf" + val initialState = MockState.empty.addFiles(dotGithubConfigFile -> "").unsafeRunSync() + val config = repoConfigAlg.readRepoConfig(repo).runA(initialState).unsafeRunSync() + + assert(!fileAlg.isRegularFile(rootConfigFile).unsafeRunSync()) + assert(fileAlg.isRegularFile(dotGithubConfigFile).unsafeRunSync()) + + assert(config.maybeRepoConfig.isDefined) + } + + test("config file in .config/") { + val repo = Repo("test", "dot-config-config") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val rootConfigFile = repoDir / ".scala-steward.conf" + val dotConfigConfigFile = repoDir / ".config" / ".scala-steward.conf" + val initialState = MockState.empty.addFiles(dotConfigConfigFile -> "").unsafeRunSync() + val config = repoConfigAlg.readRepoConfig(repo).runA(initialState).unsafeRunSync() + + assert(!fileAlg.isRegularFile(rootConfigFile).unsafeRunSync()) + assert(fileAlg.isRegularFile(dotConfigConfigFile).unsafeRunSync()) + + assert(config.maybeRepoConfig.isDefined) + } + + test("log warning on multiple config files") { + val repo = Repo("test", "multiple-config") + val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() + val rootConfigFile = repoDir / ".scala-steward.conf" + val dotConfigConfigFile = repoDir / ".config" / ".scala-steward.conf" + val initialState = MockState.empty.addFiles(rootConfigFile -> "", dotConfigConfigFile -> "").unsafeRunSync() + val (state, config) = repoConfigAlg.readRepoConfig(repo).runSA(initialState).unsafeRunSync() + + assert(fileAlg.isRegularFile(rootConfigFile).unsafeRunSync()) + assert(fileAlg.isRegularFile(dotConfigConfigFile).unsafeRunSync()) + + assert(config.maybeRepoConfig.isDefined) + + val log = state.trace.collectFirst { case Log((_, msg)) => msg }.getOrElse("") + assert(clue(log).contains("""Config file ".config/.scala-steward.conf" ignored""")) + } + test("configToIgnoreFurtherUpdates with single update") { val update = ("a".g % "b".a % "c" %> "d").single val config = RepoConfigAlg From 01631742d098554c87bc0e66d29078e08ad907df Mon Sep 17 00:00:00 2001 From: Blaz Kranjc Date: Fri, 14 Apr 2023 11:18:03 +0200 Subject: [PATCH 2/5] Initial implementation --- .../core/repoconfig/RepoConfigAlg.scala | 28 +++++++++++++++++-- .../core/repoconfig/RepoConfigAlgTest.scala | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala index 8ae9810663..27a87f6256 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala @@ -18,7 +18,7 @@ package org.scalasteward.core.repoconfig import better.files.File import cats.syntax.all._ -import cats.{Functor, MonadThrow} +import cats.{Functor, Monad, MonadThrow} import io.circe.config.parser import org.scalasteward.core.data.{Repo, Update} import org.scalasteward.core.io.{FileAlg, WorkspaceAlg} @@ -37,7 +37,10 @@ final class RepoConfigAlg[F[_]](maybeGlobalRepoConfig: Option[RepoConfig])(impli def readRepoConfig(repo: Repo): F[ConfigParsingResult] = for { repoDir <- workspaceAlg.repoDir(repo) - configParsingResult <- readRepoConfigFromFile(repoDir / repoConfigBasename) + activeConfigFile <- activeConfigFile(repoDir) + configParsingResult <- activeConfigFile.fold( + F.pure[ConfigParsingResult](ConfigParsingResult.FileDoesNotExist) + )(readRepoConfigFromFile(_)) _ <- configParsingResult.fold( F.unit, error => logger.info(s"Failed to parse $repoConfigBasename: ${error.getMessage}"), @@ -77,6 +80,27 @@ object RepoConfigAlg { def parseRepoConfig(input: String): Either[io.circe.Error, RepoConfig] = parser.decode[RepoConfig](input) + private val repoConfigFileSearchPath: List[List[String]] = List(List.empty, List(".github"), List(".config")) + + private def activeConfigFile[F[_]]( + repoDir: File + )(implicit fileAlg: FileAlg[F], logger: Logger[F], F: Monad[F]): F[Option[File]] = { + val configFileCandidates: F[List[File]] = repoConfigFileSearchPath + .map(_ :+ repoConfigBasename) + .map(path => path.foldLeft(repoDir)(_ / _)) + .traverse(file => fileAlg.isRegularFile(file).map { + case true => Some(file) + case false => None + }) + .map(_.flatten) + + configFileCandidates.flatMap { + case Nil => F.pure(None) + case active :: remaining => F.pure(active.some) + .productL(remaining.traverse_(file => logger.warn(s"""Ignored config file "${file.pathAsString}""""))) + } + } + def readRepoConfigFromFile[F[_]]( configFile: File )(implicit fileAlg: FileAlg[F], F: Functor[F]): F[ConfigParsingResult] = diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala index ee42f6aa06..d4f6244b7c 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala @@ -306,7 +306,7 @@ class RepoConfigAlgTest extends FunSuite { assert(config.maybeRepoConfig.isDefined) val log = state.trace.collectFirst { case Log((_, msg)) => msg }.getOrElse("") - assert(clue(log).contains("""Config file ".config/.scala-steward.conf" ignored""")) + assert(clue(log).contains("Ignored config file")) } test("configToIgnoreFurtherUpdates with single update") { From de643f8c1828019f2e1d24c2a85f5d88342696ed Mon Sep 17 00:00:00 2001 From: Blaz Kranjc Date: Fri, 14 Apr 2023 11:18:03 +0200 Subject: [PATCH 3/5] Use filterA --- .../org/scalasteward/core/repoconfig/RepoConfigAlg.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala index 27a87f6256..36737e9ddb 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala @@ -88,11 +88,7 @@ object RepoConfigAlg { val configFileCandidates: F[List[File]] = repoConfigFileSearchPath .map(_ :+ repoConfigBasename) .map(path => path.foldLeft(repoDir)(_ / _)) - .traverse(file => fileAlg.isRegularFile(file).map { - case true => Some(file) - case false => None - }) - .map(_.flatten) + .filterA(fileAlg.isRegularFile) configFileCandidates.flatMap { case Nil => F.pure(None) From 03abf10c40286bca026c7c47e5011d9bf9758aa8 Mon Sep 17 00:00:00 2001 From: Blaz Kranjc Date: Fri, 14 Apr 2023 11:18:03 +0200 Subject: [PATCH 4/5] Run scalafmt --- .../core/repoconfig/RepoConfigAlg.scala | 14 ++++++++++---- .../core/repoconfig/RepoConfigAlgTest.scala | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala index 36737e9ddb..68a5d4ddef 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/RepoConfigAlg.scala @@ -80,10 +80,11 @@ object RepoConfigAlg { def parseRepoConfig(input: String): Either[io.circe.Error, RepoConfig] = parser.decode[RepoConfig](input) - private val repoConfigFileSearchPath: List[List[String]] = List(List.empty, List(".github"), List(".config")) + private val repoConfigFileSearchPath: List[List[String]] = + List(List.empty, List(".github"), List(".config")) private def activeConfigFile[F[_]]( - repoDir: File + repoDir: File )(implicit fileAlg: FileAlg[F], logger: Logger[F], F: Monad[F]): F[Option[File]] = { val configFileCandidates: F[List[File]] = repoConfigFileSearchPath .map(_ :+ repoConfigBasename) @@ -92,8 +93,13 @@ object RepoConfigAlg { configFileCandidates.flatMap { case Nil => F.pure(None) - case active :: remaining => F.pure(active.some) - .productL(remaining.traverse_(file => logger.warn(s"""Ignored config file "${file.pathAsString}""""))) + case active :: remaining => + F.pure(active.some) + .productL( + remaining.traverse_(file => + logger.warn(s"""Ignored config file "${file.pathAsString}"""") + ) + ) } } diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala index d4f6244b7c..76077e33ee 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/RepoConfigAlgTest.scala @@ -297,7 +297,8 @@ class RepoConfigAlgTest extends FunSuite { val repoDir = workspaceAlg.repoDir(repo).unsafeRunSync() val rootConfigFile = repoDir / ".scala-steward.conf" val dotConfigConfigFile = repoDir / ".config" / ".scala-steward.conf" - val initialState = MockState.empty.addFiles(rootConfigFile -> "", dotConfigConfigFile -> "").unsafeRunSync() + val initialState = + MockState.empty.addFiles(rootConfigFile -> "", dotConfigConfigFile -> "").unsafeRunSync() val (state, config) = repoConfigAlg.readRepoConfig(repo).runSA(initialState).unsafeRunSync() assert(fileAlg.isRegularFile(rootConfigFile).unsafeRunSync()) From a29746cdd8ac5161711bf0c1d1f4b0ca62d1e267 Mon Sep 17 00:00:00 2001 From: Blaz Kranjc Date: Fri, 14 Apr 2023 11:18:03 +0200 Subject: [PATCH 5/5] Update documentation --- docs/repo-specific-configuration.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/repo-specific-configuration.md b/docs/repo-specific-configuration.md index cac193ba60..47715924de 100644 --- a/docs/repo-specific-configuration.md +++ b/docs/repo-specific-configuration.md @@ -1,6 +1,8 @@ # Repository-specific configuration -You can add `/.scala-steward.conf` to configure how Scala Steward updates your repository. +You can add a configuration file `.scala-steward.conf` to configure how Scala Steward updates your repository. +The `.scala-steward.conf` configuration file can be located in the root of your repository, in `.github` directory or in `.config` directory (searched in this order). +If a configuration file exists in more than one location, only the first found file is taken into account. ```properties # pullRequests.frequency allows to control how often or when Scala Steward @@ -40,14 +42,14 @@ pullRequests.frequency = "7 days" # the default procedure (one PR per update). # # Each element in the array will have the following schema: -# +# # - name (mandatory): the name of the group, will be used for things like naming the branch # - title (optional): if provided it will be used as the title for the PR -# - filter (mandatory): a non-empty list containing the filters to use to know +# - filter (mandatory): a non-empty list containing the filters to use to know # if an update falls into this group. # # `filter` properties would have this format: -# +# # { # version = "major" | "minor" | "patch" | "pre-release" | "build-metadata", # group = "{group}", @@ -55,7 +57,7 @@ pullRequests.frequency = "7 days" # } # # For more information on the values for the `version` filter visit https://semver.org/ -# +# # Every field in a `filter` is optional but at least one must be provided. # # For grouping every update togeher a filter like {group = "*"} can be # provided. @@ -69,7 +71,7 @@ pullRequests.grouping = [ { name = "all", "title" = "Dependency updates", "filter" = [{"group" = "*"}] } ] -# pullRequests.includeMatchedLabels allows to control which labels are added to PRs +# pullRequests.includeMatchedLabels allows to control which labels are added to PRs # via a regex check each label is checked against. # Defaults to no regex (all labels are added) which is equivalent to ".*". pullRequests.includeMatchedLabels = "(.*semver.*)|(commit-count:n:.*)" @@ -120,10 +122,10 @@ updatePullRequests = "always" | "on-conflicts" | "never" # If set, Scala Steward will use this message template for the commit messages and PR titles. # Supported variables: ${artifactName}, ${currentVersion}, ${nextVersion} and ${default} -# Default: "${default}" which is equivalent to "Update ${artifactName} to ${nextVersion}" +# Default: "${default}" which is equivalent to "Update ${artifactName} to ${nextVersion}" commits.message = "Update ${artifactName} from ${currentVersion} to ${nextVersion}" -# If true and when upgrading version in .scalafmt.conf, Scala Steward will perform scalafmt +# If true and when upgrading version in .scalafmt.conf, Scala Steward will perform scalafmt # and add a separate commit when format changed. So you don't need reformat manually and can merge PR. # If false, Scala Steward will not perform scalafmt, so your CI may abort when reformat needed. # Default: true @@ -144,7 +146,7 @@ postUpdateHooks = [{ }] # You can override some config options for dependencies that matches the given pattern. -# Currently, "pullRequests" can be overridden. +# Currently, "pullRequests" can be overridden. # Each pattern must have `groupId`, and may have `artifactId` and `version`. # First-matched entry is used. # More-specific entry should be placed before less-specific entry. @@ -204,7 +206,7 @@ libraryDependencies ++= Seq( // scala-steward:off "com.github.pathikrit" %% "better-files" % "3.8.0", "com.olegpy" %% "better-monadic-for" % "0.3.1", - // scala-steward:on + // scala-steward:on "org.typelevel" %% "cats-effect" % "1.3.1", // This and subsequent will get updated "org.typelevel" %% "cats-kernel-laws" % "1.6.1" )