Repository: scala-steward-org/scala-steward
Pull request #2777 (2022-11-18 01:16:32)

Bump GitHub actions


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index ac27a848..00000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-version: 2
-updates:
-  # Maintain dependencies for GitHub Actions
-  - package-ecosystem: "github-actions"
-    directory: "/"
-    schedule:
-      # Check for updates to GitHub Actions every week
-      interval: "weekly"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8a62de38..e3df1aac 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        scala: [2.12.17, 2.13.10]
+        scala: [2.12.17, 2.13.8]
         java: [adopt-hotspot@8, adopt-hotspot@11]
     runs-on: ${{ matrix.os }}
     steps:
@@ -65,7 +65,7 @@ jobs:
         run: sbt ++${{ matrix.scala }} validate
 
       - name: Codecov
-        uses: codecov/codecov-action@v2
+        uses: codecov/codecov-action@v3
 
       - name: Compress target directories
         run: tar cf targets.tar target modules/docs/.jvm/target modules/mill-plugin/.jvm/target modules/sbt-plugin/.jvm/target modules/core/.jvm/target modules/benchmark/.jvm/target project/target
@@ -83,7 +83,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        scala: [2.12.17]
+        scala: [2.12.16]
         java: [adopt-hotspot@8]
     runs-on: ${{ matrix.os }}
     steps:
@@ -128,12 +128,12 @@ jobs:
           tar xf targets.tar
           rm targets.tar
 
-      - name: Download target directories (2.13.10)
+      - name: Download target directories (2.13.8)
         uses: actions/download-artifact@v2
         with:
-          name: target-${{ matrix.os }}-2.13.10-${{ matrix.java }}
+          name: target-${{ matrix.os }}-2.13.8-${{ matrix.java }}
 
-      - name: Inflate target directories (2.13.10)
+      - name: Inflate target directories (2.13.8)
         run: |
           tar xf targets.tar
           rm targets.tar
diff --git a/.mergify.yml b/.mergify.yml
index 41bf9e92..de81d406 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -12,8 +12,8 @@ pull_request_rules:
       - author=scala-steward
       - status-success=Build and Test (ubuntu-latest, 2.12.17, adopt-hotspot@8)
       - status-success=Build and Test (ubuntu-latest, 2.12.17, adopt-hotspot@11)
-      - status-success=Build and Test (ubuntu-latest, 2.13.10, adopt-hotspot@8)
-      - status-success=Build and Test (ubuntu-latest, 2.13.10, adopt-hotspot@11)
+      - status-success=Build and Test (ubuntu-latest, 2.13.8, adopt-hotspot@8)
+      - status-success=Build and Test (ubuntu-latest, 2.13.8, adopt-hotspot@11)
     actions:
       merge:
         method: squash
diff --git a/.scala-steward.conf b/.scala-steward.conf
index 7907b9c0..f615e727 100644
--- a/.scala-steward.conf
+++ b/.scala-steward.conf
@@ -1,6 +1,2 @@
 updatePullRequests = "always"
 
-updates.pin = [
-  # We may drop this when we drop JDK 8 since minimum JDK is 11 from logback-classic 1.4.0
-  { groupId = "ch.qos.logback", artifactId="logback-classic", version="1.3." }
-]
diff --git a/.scalafmt.conf b/.scalafmt.conf
index 33060fce..f2849cfe 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,4 +1,4 @@
-version = 3.6.1
+version = 3.5.9
 runner.dialect = scala213
 assumeStandardLibraryStripMargin = true
 align.openParenCallSite = false
diff --git a/build.sbt b/build.sbt
index 40569e41..c651fd23 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,5 +1,3 @@
-import scala.util.Properties
-import scala.reflect.io.Path
 import com.typesafe.sbt.packager.docker._
 import sbtcrossproject.{CrossProject, CrossType, Platform}
 import sbtghactions.JavaSpec.Distribution.Adopt
@@ -23,7 +21,7 @@ val moduleCrossPlatformMatrix: Map[String, List[Platform]] = Map(
 )
 
 val Scala212 = "2.12.17"
-val Scala213 = "2.13.10"
+val Scala213 = "2.13.8"
 
 /// sbt-github-actions configuration
 
@@ -57,7 +55,7 @@ ThisBuild / githubWorkflowBuild :=
   Seq(
     WorkflowStep.Sbt(List("validate"), name = Some("Build project")),
     WorkflowStep.Use(
-      UseRef.Public("codecov", "codecov-action", "v2"),
+      UseRef.Public("codecov", "codecov-action", "v3"),
       name = Some("Codecov")
     )
   )
@@ -198,8 +196,6 @@ lazy val core = myCrossProject("core")
     }.taskValue,
     run / fork := true,
     Test / fork := true,
-    Test / testOptions +=
-      Tests.Cleanup(() => Path(file(Properties.tmpDir) / "scala-steward").deleteRecursively()),
     Compile / unmanagedResourceDirectories ++= (`sbt-plugin`.jvm / Compile / unmanagedSourceDirectories).value
   )
 
diff --git a/docs/help.md b/docs/help.md
index 55b715e4..d9e264d2 100644
--- a/docs/help.md
+++ b/docs/help.md
@@ -3,9 +3,7 @@
 All command line arguments for the `scala-steward` application.
 
 ```
-Usage:
-    scala-steward validate-repo-config
-    scala-steward --workspace <file> --repos-file <file> [--git-author-name <string>] --git-author-email <string> [--git-author-signing-key <string>] --git-ask-pass <file> [--sign-commits] [--vcs-type <vcs-type>] [--vcs-api-host <uri>] --vcs-login <string> [--do-not-fork] [--add-labels] [--ignore-opts-files] [--env-var <name=value>]... [--process-timeout <duration>] [--whitelist <string>]... [--read-only <string>]... [--enable-sandbox | --disable-sandbox] [--max-buffer-size <integer>] [--repo-config <uri>]... [--disable-default-repo-config] [--scalafix-migrations <uri>]... [--disable-default-scalafix-migrations] [--artifact-migrations <uri>]... [--disable-default-artifact-migrations] [--cache-ttl <duration>] [--bitbucket-server-use-default-reviewers] [--gitlab-merge-when-pipeline-succeeds] [--gitlab-required-reviewers <integer>] [--github-app-id <integer> --github-app-key-file <file>] [--url-checker-test-url <uri>] [--default-maven-repo <string>] [--refresh-backoff-period <duration>]
+Usage: scala-steward --workspace <file> --repos-file <file> [--git-author-name <string>] --git-author-email <string> [--git-author-signing-key <string>] --git-ask-pass <file> [--sign-commits] [--vcs-type <vcs-type>] [--vcs-api-host <uri>] --vcs-login <string> [--do-not-fork] [--add-labels] [--ignore-opts-files] [--env-var <name=value>]... [--process-timeout <duration>] [--whitelist <string>]... [--read-only <string>]... [--enable-sandbox | --disable-sandbox] [--max-buffer-size <integer>] [--repo-config <uri>]... [--disable-default-repo-config] [--scalafix-migrations <uri>]... [--disable-default-scalafix-migrations] [--artifact-migrations <uri>]... [--disable-default-artifact-migrations] [--cache-ttl <duration>] [--bitbucket-server-use-default-reviewers] [--gitlab-merge-when-pipeline-succeeds] [--github-app-id <integer> --github-app-key-file <file>] [--url-checker-test-url <uri>] [--default-maven-repo <string>] [--refresh-backoff-period <duration>]
 
 
 
@@ -70,8 +68,6 @@ Options and flags:
         Whether to assign the default reviewers to a bitbucket pull request; default: false
     --gitlab-merge-when-pipeline-succeeds
         Whether to merge a gitlab merge request when the pipeline succeeds
-    --gitlab-required-reviewers <integer>
-        When set, the number of required reviewers for a merge request will be set to this number (non-negative integer).  Is only used in the context of gitlab-merge-when-pipeline-succeeds being enabled, and requires that the configured access token have the appropriate privileges.
     --github-app-id <integer>
         GitHub application id
     --github-app-key-file <file>
@@ -82,8 +78,4 @@ Options and flags:
         default: https://repo1.maven.org/maven2/
     --refresh-backoff-period <duration>
         Period of time a failed build won't be triggered again; default: 0days
-
-Subcommands:
-    validate-repo-config
-        Validate the repo config file and exit; report errors if any
 ```
diff --git a/docs/repo-specific-configuration.md b/docs/repo-specific-configuration.md
index d3a0d7a3..2fb49775 100644
--- a/docs/repo-specific-configuration.md
+++ b/docs/repo-specific-configuration.md
@@ -32,43 +32,6 @@ You can add `<YOUR_REPO>/.scala-steward.conf` to configure how Scala Steward upd
 #pullRequests.frequency = "0 0 ? * 3" # every thursday on midnight
 pullRequests.frequency = "7 days"
 
-# pullRequests.grouping allows you to specify how Scala Steward should group
-# your updates in order to reduce the number of pull-requests.
-#
-# Updates will be placed in the first group with which they match, starting
-# from the first in the array. Those that do not match any group will follow
-# 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 
-#                         if an update falls into this group.
-#
-# `filter` properties would have this format:
-#   
-#    {
-#       version = "major" | "minor" | "patch" | "pre-release" | "build-metadata",
-#       group = "{group}",
-#       artifact = "{artifact}"
-#    }
-#
-# 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.
-#
-# Default: []
-pullRequests.grouping = [
-  { name = "patches", "title" = "Patch updates", "filter" = [{"version" = "patch"}] },
-  { name = "minor_major", "title" = "Minor/major updates", "filter" = [{"version" = "minor"}, {"version" = "major"}] },
-  { name = "typelevel", "title" = "Typelevel updates", "filter" = [{"group" = "org.typelevel"}, {"group" = "org.http4s"}] },
-  { name = "my_libraries", "filter" = [{"artifact" = "my-library"}, {"artifact" = "my-other-library", "group" = "my-org"}] },
-  { name = "all", title = "Dependency updates", "filter" = [{"group" = "*"}] }
-]
-
 # 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 ".*".
diff --git a/modules/core/src/main/resources/default.scala-steward.conf b/modules/core/src/main/resources/default.scala-steward.conf
index 119bbbf9..a750d8d5 100644
--- a/modules/core/src/main/resources/default.scala-steward.conf
+++ b/modules/core/src/main/resources/default.scala-steward.conf
@@ -23,9 +23,9 @@ postUpdateHooks = [
 
 updates.ignore = [
   // Ignore the next Scala 3 version until it is announced.
-  { groupId = "org.scala-lang", artifactId = "scala3-compiler",     version = { exact = "3.2.2" } },
-  { groupId = "org.scala-lang", artifactId = "scala3-library",      version = { exact = "3.2.2" } },
-  { groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = { exact = "3.2.2" } },
+  { groupId = "org.scala-lang", artifactId = "scala3-compiler",     version = { exact = "3.2.1" } },
+  { groupId = "org.scala-lang", artifactId = "scala3-library",      version = { exact = "3.2.1" } },
+  { groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = { exact = "3.2.1" } },
 
   // Ignore the next Scala 2.13 version until it is announced.
   { groupId = "org.scala-lang", artifactId = "scala-compiler", version = { exact = "2.13.11" } },
diff --git a/modules/core/src/main/scala/org/scalasteward/core/Main.scala b/modules/core/src/main/scala/org/scalasteward/core/Main.scala
index 7b7dc459..4ce060f0 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/Main.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/Main.scala
@@ -23,7 +23,7 @@ import org.scalasteward.core.application.{Cli, Context}
 object Main extends IOApp {
   override def run(args: List[String]): IO[ExitCode] =
     Cli.parseArgs(args) match {
-      case Cli.ParseResult.Success(config) => Context.step0[IO](config).use(_.runF)
+      case Cli.ParseResult.Success(config) => Context.step0[IO](config).use(_.stewardAlg.runF)
       case Cli.ParseResult.Help(help)      => Console[IO].println(help).as(ExitCode.Success)
       case Cli.ParseResult.Error(error)    => Console[IO].errorln(error).as(ExitCode.Error)
     }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala
index 5219a608..fab87294 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala
@@ -226,14 +226,8 @@ object Cli {
       "Whether to merge a gitlab merge request when the pipeline succeeds"
     ).orFalse
 
-  private val gitlabRequiredReviewers: Opts[Option[Int]] =
-    option[Int](
-      "gitlab-required-reviewers",
-      "When set, the number of required reviewers for a merge request will be set to this number (non-negative integer).  Is only used in the context of gitlab-merge-when-pipeline-succeeds being enabled, and requires that the configured access token have the appropriate privileges."
-    ).validate("Required reviewers must be non-negative")(_ >= 0).orNone
-
   private val gitLabCfg: Opts[GitLabCfg] =
-    (gitlabMergeWhenPipelineSucceeds, gitlabRequiredReviewers).mapN(GitLabCfg.apply)
+    gitlabMergeWhenPipelineSucceeds.map(GitLabCfg.apply)
 
   private val githubAppId: Opts[Long] =
     option[Long]("github-app-id", "GitHub application id")
@@ -263,15 +257,6 @@ object Cli {
       .withDefault(default)
   }
 
-  private val configFile: Opts[File] =
-    Opts.argument[File]()
-
-  private val validateConfigFile: Opts[File] =
-    Opts.subcommand(
-      name = "validate-repo-config",
-      help = "Validate the repo config file and exit; report errors if any"
-    )(configFile)
-
   private val configOpts: Opts[Config] = (
     workspace,
     reposFile,
@@ -291,19 +276,12 @@ object Cli {
     refreshBackoffPeriod
   ).mapN(Config.apply)
 
-  val command: Command[StewardUsage] =
-    Command("scala-steward", "")(
-      validateConfigFile
-        .map(StewardUsage.ValidateRepoConfig(_))
-        .orElse(
-          configOpts
-            .map(StewardUsage.Regular(_))
-        )
-    )
+  val command: Command[Config] =
+    Command("scala-steward", "")(configOpts)
 
   sealed trait ParseResult extends Product with Serializable
   object ParseResult {
-    final case class Success(config: StewardUsage) extends ParseResult
+    final case class Success(config: Config) extends ParseResult
     final case class Help(help: String) extends ParseResult
     final case class Error(error: String) extends ParseResult
   }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala
index 326d715e..5ab62a0c 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/Config.scala
@@ -133,13 +133,6 @@ object Config {
   )
 
   final case class GitLabCfg(
-      mergeWhenPipelineSucceeds: Boolean,
-      requiredReviewers: Option[Int]
+      mergeWhenPipelineSucceeds: Boolean
   )
-
-  sealed trait StewardUsage
-  object StewardUsage {
-    final case class Regular(config: Config) extends StewardUsage
-    final case class ValidateRepoConfig(file: File) extends StewardUsage
-  }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
index ca1fe68d..81994008 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
@@ -16,10 +16,8 @@
 
 package org.scalasteward.core.application
 
-import better.files.File
 import cats.effect._
 import cats.effect.implicits._
-import cats.MonadThrow
 import cats.syntax.all._
 import eu.timepit.refined.auto._
 import org.http4s.Uri
@@ -50,8 +48,6 @@ import org.scalasteward.core.vcs.github.{GitHubAppApiAlg, GitHubAuthAlg}
 import org.scalasteward.core.vcs.{VCSApiAlg, VCSExtraAlg, VCSRepoAlg, VCSSelection}
 import org.typelevel.log4cats.Logger
 import org.typelevel.log4cats.slf4j.Slf4jLogger
-import org.scalasteward.core.application.Config.StewardUsage
-import org.scalasteward.core.repoconfig.ValidateRepoConfigAlg
 
 final class Context[F[_]](implicit
     val buildToolDispatcher: BuildToolDispatcher[F],
@@ -81,29 +77,10 @@ final class Context[F[_]](implicit
 )
 
 object Context {
-
-  sealed trait StewardContext[F[_]] {
-    def runF: F[ExitCode]
-  }
-  object StewardContext {
-    final case class Regular[F[_]](context: Context[F]) extends StewardContext[F] {
-      override def runF: F[ExitCode] = context.stewardAlg.runF
-    }
-
-    final case class ValidateRepoConfig[F[_]](file: File)(implicit
-        val validateRepoConfigAlg: ValidateRepoConfigAlg[F],
-        val logger: Logger[F]
-    ) extends StewardContext[F] {
-      override def runF: F[ExitCode] = validateRepoConfigAlg.validateAndReport(file)
-    }
-  }
-
-  def step0[F[_]](
-      usage: Config.StewardUsage
-  )(implicit F: Async[F]): Resource[F, StewardContext[F]] =
+  def step0[F[_]](config: Config)(implicit F: Async[F]): Resource[F, Context[F]] =
     for {
-      logger0 <- Resource.eval(Slf4jLogger.fromName[F]("org.scalasteward.core"))
-      _ <- Resource.eval(printBanner(logger0))
+      logger <- Resource.eval(Slf4jLogger.fromName[F]("org.scalasteward.core"))
+      _ <- Resource.eval(printBanner(logger))
       _ <- Resource.eval(F.delay(System.setProperty("http.agent", userAgentString)))
       userAgent <- Resource.eval(F.fromEither(`User-Agent`.parse(userAgentString)))
       middleware = ClientConfiguration
@@ -117,46 +94,22 @@ object Context {
         ClientConfiguration.disableFollowRedirect[F],
         middleware
       )
-      fileAlg0 = FileAlg.create(logger0, F)
-      context <- usage match {
-        case StewardUsage.Regular(config) =>
-          initRegular(config)(
-            defaultClient,
-            UrlCheckerClient(urlCheckerClient),
-            fileAlg0,
-            logger0,
-            F
-          ).map(StewardContext.Regular(_))
-
-        case StewardUsage.ValidateRepoConfig(file) =>
-          implicit val fileAlg: FileAlg[F] = fileAlg0
-          implicit val logger: Logger[F] = logger0
-          Resource.pure[F, StewardContext[F]](initValidateRepoConfig(file))
-      }
-
+      fileAlg = FileAlg.create(logger, F)
+      processAlg = ProcessAlg.create(config.processCfg)(logger, F)
+      workspaceAlg = WorkspaceAlg.create(config)(fileAlg, logger, F)
+      context <- Resource.eval(
+        step1(config)(
+          defaultClient,
+          UrlCheckerClient(urlCheckerClient),
+          fileAlg,
+          logger,
+          processAlg,
+          workspaceAlg,
+          F
+        )
+      )
     } yield context
 
-  def initRegular[F[_]](config: Config)(implicit
-      client: Client[F],
-      urlCheckerClient: UrlCheckerClient[F],
-      fileAlg: FileAlg[F],
-      logger: Logger[F],
-      F: Async[F]
-  ): Resource[F, Context[F]] = {
-    implicit val processAlg = ProcessAlg.create(config.processCfg)
-    implicit val workspaceAlg = WorkspaceAlg.create(config)
-    Resource.eval(step1(config))
-  }
-
-  def initValidateRepoConfig[F[_]](file: File)(implicit
-      fileAlg: FileAlg[F],
-      logger: Logger[F],
-      F: MonadThrow[F]
-  ): StewardContext.ValidateRepoConfig[F] = {
-    implicit val validateRepoConfigAlg = new ValidateRepoConfigAlg[F]()
-    StewardContext.ValidateRepoConfig[F](file)
-  }
-
   def step1[F[_]](config: Config)(implicit
       client: Client[F],
       urlCheckerClient: UrlCheckerClient[F],
diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/SemVer.scala b/modules/core/src/main/scala/org/scalasteward/core/data/SemVer.scala
index b17d86ad..60f464f1 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/data/SemVer.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/data/SemVer.scala
@@ -17,7 +17,6 @@
 package org.scalasteward.core.data
 
 import cats.syntax.all._
-import io.circe.{Codec, Decoder, Encoder}
 import org.scalasteward.core.data.SemVer.Change._
 
 import scala.annotation.tailrec
@@ -43,22 +42,6 @@ object SemVer {
     case object Patch extends Change("patch")
     case object PreRelease extends Change("pre-release")
     case object BuildMetadata extends Change("build-metadata")
-
-    final private val allowed = List(Major, Minor, Patch, PreRelease, BuildMetadata)
-      .map(change => s"`${change.render}`")
-      .mkString(", ")
-
-    implicit val ChangeCodec: Codec[Change] =
-      Codec.from(Decoder[String].emap(from), Encoder[String].contramap(_.render))
-
-    def from(string: String): Either[String, Change] = string match {
-      case Major.`render`         => Right(Major)
-      case Minor.`render`         => Right(Minor)
-      case Patch.`render`         => Right(Patch)
-      case PreRelease.`render`    => Right(PreRelease)
-      case BuildMetadata.`render` => Right(BuildMetadata)
-      case other => Left(s"Invalid value for version change: $other. Allowed values are: $allowed")
-    }
   }
 
   def getChangeSpec(from: SemVer, to: SemVer): Option[Change] =
diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala b/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala
index 4f1d3133..37a0caad 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/data/Update.scala
@@ -19,73 +19,13 @@ package org.scalasteward.core.data
 import cats.Order
 import cats.implicits._
 import io.circe.Codec
-import io.circe.syntax._
 import io.circe.generic.semiauto._
-import org.scalasteward.core.data.Update.Group
-import org.scalasteward.core.data.Update.Single
-import org.scalasteward.core.repoconfig.PullRequestGroup
+import org.scalasteward.core.data.Update.{Group, Single}
 import org.scalasteward.core.util
 import org.scalasteward.core.util.Nel
 import org.scalasteward.core.util.string.MinLengthString
-import io.circe.Decoder
 
-sealed trait AnUpdate {
-
-  def on[A](update: Update => A, grouped: GroupedUpdate => A): A = this match {
-    case g: GroupedUpdate => grouped(g)
-    case u: Update        => update(u)
-  }
-
-  def show: String
-
-}
-
-object AnUpdate {
-
-  implicit val AnUpdateCodec: Codec[AnUpdate] = Codec.from(
-    Decoder[GroupedUpdate].widen[AnUpdate].or(Decoder[Update].widen[AnUpdate]),
-    _.on(_.asJson, _.asJson)
-  )
-
-}
-
-final case class GroupedUpdate(name: String, title: Option[String], updates: List[Update])
-    extends AnUpdate {
-
-  override def show: String = name
-
-}
-
-object GroupedUpdate {
-
-  /**
-    * Processes the provided updates using the group configuration. Each update will only be present in the
-    * first group it falls into.
-    *
-    * Updates that do not fall into any group will be returned back in the second return parameter.
-    */
-  /**
-    * Processes the provided updates using the group configuration. Each update will only be present in the
-    * first group it falls into.
-    *
-    * Updates that do not fall into any group will be returned back in the second return parameter.
-    */
-  def from(
-      groups: List[PullRequestGroup],
-      updates: List[Update.Single]
-  ): (List[GroupedUpdate], List[Update.Single]) =
-    groups.foldLeft((List.empty[GroupedUpdate], updates)) { case ((grouped, notGrouped), group) =>
-      notGrouped.partition(group.matches) match {
-        case (Nil, rest)     => (grouped, rest)
-        case (matched, rest) => (grouped :+ GroupedUpdate(group.name, group.title, matched), rest)
-      }
-    }
-
-  implicit val GroupedUpdateCodec: Codec[GroupedUpdate] = deriveCodec
-
-}
-
-sealed trait Update extends Product with Serializable with AnUpdate {
+sealed trait Update extends Product with Serializable {
   def crossDependencies: Nel[CrossDependency]
   def dependencies: Nel[Dependency]
   def groupId: GroupId
@@ -100,7 +40,7 @@ sealed trait Update extends Product with Serializable with AnUpdate {
   final def nextVersion: Version =
     newerVersions.head
 
-  final override def show: String = {
+  final def show: String = {
     val artifacts = this match {
       case s: Single => s.crossDependency.showArtifactNames
       case g: Group  => g.crossDependencies.map(_.showArtifactNames).mkString_("{", ", ", "}")
diff --git a/modules/core/src/main/scala/org/scalasteward/core/data/UpdateData.scala b/modules/core/src/main/scala/org/scalasteward/core/data/UpdateData.scala
index 459cc11b..49bf1b2b 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/data/UpdateData.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/data/UpdateData.scala
@@ -23,7 +23,7 @@ import org.scalasteward.core.vcs.data.Repo
 final case class UpdateData(
     repoData: RepoData,
     fork: Repo,
-    update: AnUpdate,
+    update: Update,
     baseBranch: Branch,
     baseSha1: Sha1,
     updateBranch: Branch
diff --git a/modules/core/src/main/scala/org/scalasteward/core/git/CommitMsg.scala b/modules/core/src/main/scala/org/scalasteward/core/git/CommitMsg.scala
index 360960ae..80b4ba36 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/git/CommitMsg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/git/CommitMsg.scala
@@ -17,7 +17,7 @@
 package org.scalasteward.core.git
 
 import cats.syntax.all._
-import org.scalasteward.core.data.AnUpdate
+import org.scalasteward.core.data.Update
 import org.scalasteward.core.update.show
 import org.scalasteward.core.util.Nel
 
@@ -39,25 +39,17 @@ final case class CommitMsg(
 }
 
 object CommitMsg {
-  def replaceVariables(s: String)(update: AnUpdate, baseBranch: Option[Branch]): CommitMsg =
-    update.on(
-      u => {
-        val artifactNameValue = show.oneLiner(u)
-        val nextVersionValue = u.nextVersion.value
-        val defaultValue = s"Update $artifactNameValue to $nextVersionValue" +
-          baseBranch.fold("")(branch => s" in ${branch.name}")
-        val title = s
-          .replace("${default}", defaultValue)
-          .replace("${artifactName}", artifactNameValue)
-          .replace("${currentVersion}", u.currentVersion.value)
-          .replace("${nextVersion}", nextVersionValue)
-          .replace("${branchName}", baseBranch.map(_.name).orEmpty)
-        CommitMsg(title = title)
-      },
-      g =>
-        CommitMsg(title =
-          g.title.getOrElse(s"Update for group ${g.name}") +
-            baseBranch.fold("")(branch => s" in ${branch.name}")
-        )
-    )
+  def replaceVariables(s: String)(update: Update, baseBranch: Option[Branch]): CommitMsg = {
+    val artifactNameValue = show.oneLiner(update)
+    val nextVersionValue = update.nextVersion.value
+    val defaultValue = s"Update $artifactNameValue to $nextVersionValue" +
+      baseBranch.fold("")(branch => s" in ${branch.name}")
+    val title = s
+      .replace("${default}", defaultValue)
+      .replace("${artifactName}", artifactNameValue)
+      .replace("${currentVersion}", update.currentVersion.value)
+      .replace("${nextVersion}", nextVersionValue)
+      .replace("${branchName}", baseBranch.map(_.name).orEmpty)
+    CommitMsg(title = title)
+  }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/git/package.scala b/modules/core/src/main/scala/org/scalasteward/core/git/package.scala
index 98b5dbd3..0f7f41aa 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/git/package.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/git/package.scala
@@ -16,7 +16,7 @@
 
 package org.scalasteward.core
 
-import org.scalasteward.core.data.{AnUpdate, GroupedUpdate, Update}
+import org.scalasteward.core.data.Update
 import org.scalasteward.core.repoconfig.CommitsConfig
 import org.scalasteward.core.vcs.data.Repo
 
@@ -27,12 +27,9 @@ package object git {
 
   val updateBranchPrefix = "update"
 
-  def branchFor(update: AnUpdate, baseBranch: Option[Branch]): Branch = {
+  def branchFor(update: Update, baseBranch: Option[Branch]): Branch = {
     val base = baseBranch.fold("")(branch => s"${branch.name}/")
-    update match {
-      case g: GroupedUpdate => Branch(s"$updateBranchPrefix/$base${g.name}")
-      case u: Update        => Branch(s"$updateBranchPrefix/$base${u.name}-${u.nextVersion}")
-    }
+    Branch(s"$updateBranchPrefix/$base${update.name}-${update.nextVersion}")
   }
 
   def commitMsgFor(
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 0ee3ffbb..9db26051 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
@@ -32,7 +32,6 @@ import org.scalasteward.core.vcs.data._
 import org.scalasteward.core.vcs.{VCSApiAlg, VCSExtraAlg, VCSRepoAlg}
 import org.scalasteward.core.{git, util, vcs}
 import org.typelevel.log4cats.Logger
-import cats.Applicative
 
 final class NurtureAlg[F[_]](config: VCSCfg)(implicit
     coursierAlg: CoursierAlg[F],
@@ -50,9 +49,7 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
     for {
       _ <- logger.info(s"Nurture ${data.repo.show}")
       baseBranch <- cloneAndSync(data.repo, fork)
-      (grouped, notGrouped) = GroupedUpdate.from(data.config.pullRequests.grouping, updates.toList)
-      finalUpdates = Update.groupByGroupId(notGrouped) ++ grouped
-      _ <- updateDependencies(data, fork.repo, baseBranch, finalUpdates)
+      _ <- updateDependencies(data, fork.repo, baseBranch, Update.groupByGroupId(updates.toList))
     } yield ()
 
   private def cloneAndSync(repo: Repo, fork: RepoOut): F[Branch] =
@@ -65,7 +62,7 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
       data: RepoData,
       fork: Repo,
       baseBranch: Branch,
-      updates: List[AnUpdate]
+      updates: List[Update]
   ): F[Unit] =
     for {
       _ <- logger.info(util.logger.showUpdates(updates))
@@ -92,12 +89,12 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
       head = vcs.listingBranch(config.tpe, data.fork, data.updateBranch)
       pullRequests <- vcsApiAlg.listPullRequests(data.repo, head, data.baseBranch)
       result <- pullRequests.headOption match {
-        case Some(pr) if pr.state.isClosed && data.update.isInstanceOf[Update] =>
+        case Some(pr) if pr.state.isClosed =>
           logger.info(s"PR ${pr.html_url} is closed") >>
             deleteRemoteBranch(data.repo, data.updateBranch).as(Ignored)
-        case Some(pr) if !pr.state.isClosed =>
+        case Some(pr) =>
           logger.info(s"Found PR ${pr.html_url}") >> updatePullRequest(data)
-        case _ =>
+        case None =>
           applyNewUpdate(data).flatTap {
             case Created(newPrNumber) => closeObsoletePullRequests(data, newPrNumber)
             case _                    => F.unit
@@ -117,13 +114,9 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
     } yield result
 
   private def closeObsoletePullRequests(data: UpdateData, newNumber: PullRequestNumber): F[Unit] =
-    data.update.on(
-      update = pullRequestRepository
-        .getObsoleteOpenPullRequests(data.repo, _)
-        .flatMap(_.traverse_(oldPr => closeObsoletePullRequest(data, newNumber, oldPr))),
-      // We don't support closing obsolete PRs for `GroupedUpdate`s
-      grouped = _ => Applicative[F].unit
-    )
+    pullRequestRepository
+      .getObsoleteOpenPullRequests(data.repo, data.update)
+      .flatMap(_.traverse_(oldPr => closeObsoletePullRequest(data, newNumber, oldPr)))
 
   private def closeObsoletePullRequest(
       data: UpdateData,
@@ -159,20 +152,15 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
     gitAlg.returnToCurrentBranch(data.repo) {
       val createBranch = logger.info(s"Create branch ${data.updateBranch.name}") >>
         gitAlg.createBranch(data.repo, data.updateBranch)
-      data.update
-        .on(
-          update = editAlg.applyUpdate(data.repoData, _, createBranch),
-          grouped = createBranch >> _.updates.flatTraverse(editAlg.applyUpdate(data.repoData, _))
-        )
-        .flatMap { edits =>
-          val editCommits = edits.flatMap(_.commits)
-          if (editCommits.isEmpty) logger.warn("No commits created").as(Ignored)
-          else
-            gitAlg.branchesDiffer(data.repo, data.baseBranch, data.updateBranch).flatMap {
-              case true  => pushCommits(data, editCommits) >> createPullRequest(data, edits)
-              case false => logger.warn("No diff between base and update branch").as(Ignored)
-            }
-        }
+      editAlg.applyUpdate(data.repoData, data.update, createBranch).flatMap { edits =>
+        val editCommits = edits.flatMap(_.commits)
+        if (editCommits.isEmpty) logger.warn("No commits created").as(Ignored)
+        else
+          gitAlg.branchesDiffer(data.repo, data.baseBranch, data.updateBranch).flatMap {
+            case true  => pushCommits(data, editCommits) >> createPullRequest(data, edits)
+            case false => logger.warn("No diff between base and update branch").as(Ignored)
+          }
+      }
     }
 
   private def pushCommits(data: UpdateData, commits: List[Commit]): F[ProcessResult] =
@@ -183,51 +171,28 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
         _ <- gitAlg.push(data.repo, data.updateBranch)
       } yield Updated
 
-  private def dependenciesUpdatedWithNextAndCurrentVersion(
-      update: AnUpdate
-  ): List[(Version, Dependency)] =
-    update.on(
-      u => u.dependencies.map(_.copy(version = u.nextVersion)).tupleLeft(u.currentVersion).toList,
-      _.updates.flatMap(dependenciesUpdatedWithNextAndCurrentVersion(_))
-    )
-
   private def createPullRequest(data: UpdateData, edits: List[EditAttempt]): F[ProcessResult] =
     for {
       _ <- logger.info(s"Create PR ${data.updateBranch.name}")
-      dependenciesWithNextVersion = dependenciesUpdatedWithNextAndCurrentVersion(data.update)
+      dependenciesWithNextVersion =
+        data.update.dependencies.map(_.copy(version = data.update.nextVersion)).toList
       resolvers = data.repoData.cache.dependencyInfos.flatMap(_.resolvers)
-      dependencyScope = Scope(
-        value = dependenciesWithNextVersion.map { case (_, dependency) => dependency },
-        resolvers = resolvers
-      )
-      artifactIdToUrl <- coursierAlg.getArtifactIdUrlMapping(dependencyScope)
-      existingArtifactUrlsList <- artifactIdToUrl.toList.filterA { case (_, uri) =>
-        urlChecker.exists(uri)
-      }
+      artifactIdToUrl <-
+        coursierAlg.getArtifactIdUrlMapping(Scope(dependenciesWithNextVersion, resolvers))
+      existingArtifactUrlsList <- artifactIdToUrl.toList.filterA(a => urlChecker.exists(a._2))
       existingArtifactUrlsMap = existingArtifactUrlsList.toMap
-      releaseRelatedUrls <- dependenciesWithNextVersion.flatTraverse {
-        case (currentVersion, dependency) =>
-          existingArtifactUrlsMap
-            .get(dependency.artifactId.name)
-            .toList
-            .traverse(uri =>
-              vcsExtraAlg
-                .getReleaseRelatedUrls(uri, currentVersion, dependency.version)
-                .tupleLeft(dependency.artifactId.name)
-            )
-      }
-      filesWithOldVersion <-
-        data.update
-          .on(u => List(u.currentVersion.value), _.updates.map(_.currentVersion.value))
-          .flatTraverse(gitAlg.findFilesContaining(data.repo, _))
-          .map(_.distinct)
+      releaseRelatedUrls <-
+        existingArtifactUrlsMap
+          .get(data.update.mainArtifactId)
+          .traverse(vcsExtraAlg.getReleaseRelatedUrls(_, data.update))
+      filesWithOldVersion <- gitAlg.findFilesContaining(data.repo, data.update.currentVersion.value)
       branchName = vcs.createBranch(config.tpe, data.fork, data.updateBranch)
       requestData = NewPullRequestData.from(
         data,
         branchName,
         edits,
         existingArtifactUrlsMap,
-        releaseRelatedUrls.toMap,
+        releaseRelatedUrls.getOrElse(List.empty),
         filesWithOldVersion,
         data.repoData.config.pullRequests.includeMatchedLabels
       )
@@ -285,13 +250,9 @@ final class NurtureAlg[F[_]](config: VCSCfg)(implicit
       )
       maybeRevertCommit <- gitAlg.revertChanges(data.repo, data.baseBranch)
       maybeMergeCommit <- gitAlg.mergeTheirs(data.repo, data.baseBranch)
-      edits <- data.update.on(
-        update = editAlg.applyUpdate(data.repoData, _),
-        grouped = _.updates.flatTraverse(editAlg.applyUpdate(data.repoData, _))
-      )
+      edits <- editAlg.applyUpdate(data.repoData, data.update)
       editCommits = edits.flatMap(_.commits)
       commits = maybeRevertCommit.toList ++ maybeMergeCommit.toList ++ editCommits
       result <- pushCommits(data, commits)
     } yield result
-
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestData.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestData.scala
index 4d5aa230..15525283 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestData.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestData.scala
@@ -17,14 +17,14 @@
 package org.scalasteward.core.nurture
 
 import org.http4s.Uri
-import org.scalasteward.core.data.AnUpdate
+import org.scalasteward.core.data.Update
 import org.scalasteward.core.git.{Branch, Sha1}
 import org.scalasteward.core.vcs.data.{PullRequestNumber, PullRequestState}
 
 final case class PullRequestData[F[_]](
     url: Uri,
     baseSha1: Sha1,
-    update: AnUpdate,
+    update: Update,
     state: PullRequestState,
     number: F[PullRequestNumber],
     updateBranch: F[Branch]
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala
index 073687d1..b82a744a 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala
@@ -21,7 +21,7 @@ import cats.{Id, Monad}
 import io.circe.Codec
 import io.circe.generic.semiauto.deriveCodec
 import org.http4s.Uri
-import org.scalasteward.core.data.{AnUpdate, CrossDependency, GroupId, Update, Version}
+import org.scalasteward.core.data.{CrossDependency, GroupId, Update, Version}
 import org.scalasteward.core.git
 import org.scalasteward.core.git.{Branch, Sha1}
 import org.scalasteward.core.nurture.PullRequestRepository.Entry
@@ -69,14 +69,21 @@ final class PullRequestRepository[F[_]](kvStore: KeyValueStore[F, Repo, Map[Uri,
   def getObsoleteOpenPullRequests(repo: Repo, update: Update): F[List[PullRequestData[Id]]] =
     kvStore.getOrElse(repo, Map.empty).map {
       _.collect {
-        case (url, Entry(baseSha1, u: Update, state, _, number, updateBranch))
-            if state === PullRequestState.Open &&
-              u.withNewerVersions(update.newerVersions) === update &&
-              u.nextVersion < update.nextVersion =>
+        case (url, entry)
+            if entry.state === PullRequestState.Open &&
+              entry.update.withNewerVersions(update.newerVersions) === update &&
+              entry.update.nextVersion < update.nextVersion =>
           for {
-            number <- number
-            branch = updateBranch.getOrElse(git.branchFor(u, repo.branch))
-          } yield PullRequestData[Id](url, baseSha1, u, state, number, branch)
+            number <- entry.number
+            updateBranch = entry.updateBranch.getOrElse(git.branchFor(entry.update, repo.branch))
+          } yield PullRequestData[Id](
+            url,
+            entry.baseSha1,
+            entry.update,
+            entry.state,
+            number,
+            updateBranch
+          )
       }.flatten.toList.sortBy(_.number.value)
     }
 
@@ -86,10 +93,9 @@ final class PullRequestRepository[F[_]](kvStore: KeyValueStore[F, Repo, Map[Uri,
       newVersion: Version
   ): F[Option[PullRequestData[Option]]] =
     kvStore.getOrElse(repo, Map.empty).map {
-      _.filter {
-        case (_, Entry(_, u: Update, _, _, _, _)) =>
-          UpdateAlg.isUpdateFor(u, crossDependency) && u.nextVersion === newVersion
-        case _ => false
+      _.filter { case (_, entry) =>
+        UpdateAlg.isUpdateFor(entry.update, crossDependency) &&
+        entry.update.nextVersion === newVersion
       }
         .maxByOption { case (_, entry) => entry.entryCreatedAt.millis }
         .map { case (url, entry) =>
@@ -112,12 +118,9 @@ final class PullRequestRepository[F[_]](kvStore: KeyValueStore[F, Repo, Map[Uri,
       case None => Map.empty
       case Some(pullRequests) =>
         pullRequests.values
-          .collect { case Entry(_, u: Update, _, entryCreatedAt, _, _) =>
-            (u.groupId, u.mainArtifactId, entryCreatedAt)
-          }
-          .groupBy { case (groupId, mainArtifactId, _) => (groupId, mainArtifactId) }
+          .groupBy(entry => (entry.update.groupId, entry.update.mainArtifactId))
           .view
-          .mapValues(_.map { case (_, _, createdAt) => createdAt }.max)
+          .mapValues(_.map(_.entryCreatedAt).max)
           .toMap
     }
 }
@@ -125,7 +128,7 @@ final class PullRequestRepository[F[_]](kvStore: KeyValueStore[F, Repo, Map[Uri,
 object PullRequestRepository {
   final case class Entry(
       baseSha1: Sha1,
-      update: AnUpdate,
+      update: Update,
       state: PullRequestState,
       entryCreatedAt: Timestamp,
       number: Option[PullRequestNumber],
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala
index 0506825f..ab17387f 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/GroupRepoConfig.scala
@@ -19,8 +19,7 @@ package org.scalasteward.core.repoconfig
 import io.circe.Codec
 import io.circe.generic.extras.Configuration
 import io.circe.generic.semiauto.deriveCodec
-import org.scalasteward.core.data.{AnUpdate, Update}
-import org.scalasteward.core.util.string.indentLines
+import org.scalasteward.core.data.Update
 
 final case class GroupRepoConfig(
     pullRequests: PullRequestsConfig = PullRequestsConfig(),
@@ -34,27 +33,17 @@ object GroupRepoConfig {
   implicit val groupPullConfigCodec: Codec[GroupRepoConfig] =
     deriveCodec
 
-  def configToSlowDownUpdatesFrequency(update: AnUpdate): String = {
-    val forUpdate: Update => String = {
+  def configToSlowDownUpdatesFrequency(update: Update): String =
+    update match {
       case s: Update.Single =>
-        s"""{
+        s"""dependencyOverrides = [{
            |  pullRequests = { frequency = "@monthly" },
            |  dependency = { groupId = "${s.groupId}", artifactId = "${s.artifactId.name}" }
-           |}""".stripMargin
+           |}]""".stripMargin
       case g: Update.Group =>
-        s"""{
+        s"""dependencyOverrides = [{
            |  pullRequests = { frequency = "@monthly" },
            |  dependency = { groupId = "${g.groupId}" }
-           |}""".stripMargin
+           |}]""".stripMargin
     }
-
-    update.on(
-      update = u => s"dependencyOverrides = [${forUpdate(u)}]",
-      grouped = _.updates
-        .map(forUpdate(_))
-        .map(_.linesIterator.toList)
-        .map(indentLines(_))
-        .mkString("dependencyOverrides = [\n", ",\n", "\n]")
-    )
-  }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestGroup.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestGroup.scala
deleted file mode 100644
index 5447dbdf..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestGroup.scala
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2018-2022 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.repoconfig
-
-import cats.Eq
-import io.circe.{Decoder, Encoder}
-import io.circe.generic.semiauto._
-import cats.data.NonEmptyList
-import org.scalasteward.core.data.Update
-
-case class PullRequestGroup(
-    name: String,
-    title: Option[String] = None,
-    filter: NonEmptyList[PullRequestUpdateFilter]
-) {
-
-  /**
-    * Returns `true` if an update falls into this group; returns `false` otherwise.
-    */
-  def matches(update: Update.Single): Boolean = filter.exists(_.matches(update))
-
-}
-
-object PullRequestGroup {
-
-  implicit val pullRequestGroupEq: Eq[PullRequestGroup] =
-    Eq.fromUniversalEquals
-
-  implicit val pullRequestGroupDecoder: Decoder[PullRequestGroup] =
-    deriveDecoder
-
-  implicit val pullRequestGroupEncoder: Encoder[PullRequestGroup] =
-    deriveEncoder
-
-}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestUpdateFilter.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestUpdateFilter.scala
deleted file mode 100644
index d4c4170a..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestUpdateFilter.scala
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2018-2022 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.repoconfig
-
-import cats.Eq
-import cats.syntax.all._
-import io.circe._
-import io.circe.syntax._
-import org.scalasteward.core.data.{SemVer, Update}
-
-import scala.util.matching.Regex
-
-final case class PullRequestUpdateFilter private (
-    group: Option[String] = None,
-    artifact: Option[String] = None,
-    version: Option[SemVer.Change] = None
-) {
-
-  /**
-    * Returns `true` if an update falls into this filter; returns `false` otherwise.
-    */
-  def matches(update: Update.Single): Boolean =
-    groupRegex.forall(_.matches(update.groupId.value)) &&
-      artifactRegex.forall(_.matches(update.mainArtifactId)) &&
-      version.forall(isMatchedVersion(_, update))
-
-  private lazy val groupRegex = group.map(wildcardRegex)
-  private lazy val artifactRegex = artifact.map(wildcardRegex)
-
-  private def wildcardRegex(groupOrArtifact: String) = {
-    val pattern = Regex.quote(groupOrArtifact).replaceAll("\\*", "\\\\E.+\\\\Q")
-    new Regex(pattern)
-  }
-
-  private def isMatchedVersion(versionType: SemVer.Change, update: Update.Single): Boolean =
-    (SemVer.parse(update.currentVersion.value), SemVer.parse(update.nextVersion.value)).tupled
-      .flatMap { case (current, next) => SemVer.getChangeEarly(current, next) }
-      .map(_.render === versionType.render)
-      .getOrElse(false)
-
-}
-
-object PullRequestUpdateFilter {
-
-  def apply(
-      group: Option[String] = None,
-      artifact: Option[String] = None,
-      version: Option[SemVer.Change] = None
-  ): Either[String, PullRequestUpdateFilter] =
-    if (group.isEmpty && artifact.isEmpty && version.isEmpty)
-      Left("At least one predicate should be added to the filter")
-    else Right(new PullRequestUpdateFilter(group, artifact, version))
-
-  implicit val pullRequestUpdateDecoder: Decoder[PullRequestUpdateFilter] = { cursor =>
-    for {
-      group <- cursor.get[Option[String]]("group")
-      artifact <- cursor.get[Option[String]]("artifact")
-      version <- cursor.get[Option[SemVer.Change]]("version")
-      filter <- apply(group, artifact, version).leftMap(DecodingFailure(_, Nil))
-    } yield filter
-  }
-
-  implicit val pullRequestUpdateFilterEq: Eq[PullRequestUpdateFilter] =
-    Eq.fromUniversalEquals
-
-  implicit val pullRequestUpdateFilterEncoder: Encoder[PullRequestUpdateFilter] = filter =>
-    Json.obj(
-      "group" := filter.group,
-      "artifact" := filter.artifact,
-      "version" := filter.version
-    )
-
-}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala
index 6ed6ac85..8de1bf4e 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/PullRequestsConfig.scala
@@ -27,7 +27,6 @@ import scala.util.matching.Regex
 
 final case class PullRequestsConfig(
     frequency: Option[PullRequestFrequency] = None,
-    grouping: List[PullRequestGroup] = Nil,
     includeMatchedLabels: Option[Regex] = None
 ) {
   def frequencyOrDefault: PullRequestFrequency =
@@ -57,7 +56,6 @@ object PullRequestsConfig {
       (x, y) =>
         PullRequestsConfig(
           frequency = x.frequency.orElse(y.frequency),
-          grouping = x.grouping |+| y.grouping,
           includeMatchedLabels = x.includeMatchedLabels.orElse(y.includeMatchedLabels)
         )
     )
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 df8f27a2..00007b79 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
@@ -20,7 +20,7 @@ import better.files.File
 import cats.MonadThrow
 import cats.syntax.all._
 import io.circe.config.parser
-import org.scalasteward.core.data.{AnUpdate, Update}
+import org.scalasteward.core.data.Update
 import org.scalasteward.core.io.{FileAlg, WorkspaceAlg}
 import org.scalasteward.core.repoconfig.RepoConfigAlg._
 import org.scalasteward.core.vcs.data.Repo
@@ -60,20 +60,11 @@ object RepoConfigAlg {
   def parseRepoConfig(input: String): Either[io.circe.Error, RepoConfig] =
     parser.decode[RepoConfig](input)
 
-  def configToIgnoreFurtherUpdates(update: AnUpdate): String = {
-    val forUpdate: Update => String = {
+  def configToIgnoreFurtherUpdates(update: Update): String =
+    update match {
       case s: Update.Single =>
-        s"""{ groupId = "${s.groupId}", artifactId = "${s.artifactId.name}" }""".stripMargin
+        s"""updates.ignore = [ { groupId = "${s.groupId}", artifactId = "${s.artifactId.name}" } ]"""
       case g: Update.Group =>
-        s"""{ groupId = "${g.groupId}" }""".stripMargin
+        s"""updates.ignore = [ { groupId = "${g.groupId}" } ]"""
     }
-
-    update.on(
-      update = u => s"updates.ignore = [ ${forUpdate(u)} ]",
-      grouped = g =>
-        g.updates
-          .map("  " + forUpdate(_))
-          .mkString("updates.ignore = [\n", ",\n", "\n]")
-    )
-  }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/ValidateRepoConfigAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/repoconfig/ValidateRepoConfigAlg.scala
deleted file mode 100644
index 4b76b4ee..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/repoconfig/ValidateRepoConfigAlg.scala
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2018-2022 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.repoconfig
-
-import better.files.File
-import cats.MonadThrow
-import cats.syntax.all._
-import cats.effect.ExitCode
-import org.typelevel.log4cats.Logger
-import org.scalasteward.core.io.FileAlg
-import org.scalasteward.core.repoconfig.RepoConfigAlg
-
-import ValidateRepoConfigAlg._
-import io.circe.{CursorOp, DecodingFailure, ParsingFailure}
-
-final class ValidateRepoConfigAlg[F[_]](implicit
-    fileAlg: FileAlg[F],
-    logger: Logger[F],
-    F: MonadThrow[F]
-) {
-
-  def validateConfigFile(configFile: File): F[ConfigValidationResult] =
-    fileAlg.readFile(configFile).map {
-      case Some(content) => validateContent(content)
-      case None          => ConfigValidationResult.FileDoesNotExist
-    }
-
-  def validateAndReport(configFile: File): F[ExitCode] =
-    validateConfigFile(configFile).flatMap { result =>
-      ValidateRepoConfigAlg.presentValidationResult(configFile)(result) match {
-        case Left(errMsg) => logger.error(errMsg).as(ExitCode.Error)
-        case Right(okMsg) => logger.info(okMsg).as(ExitCode.Success)
-      }
-    }
-}
-
-object ValidateRepoConfigAlg {
-  sealed trait ConfigValidationResult
-
-  object ConfigValidationResult {
-    case object FileDoesNotExist extends ConfigValidationResult
-    case class ConfigIsInvalid(err: io.circe.Error) extends ConfigValidationResult
-    case object Ok extends ConfigValidationResult
-  }
-
-  def validateContent(content: String): ConfigValidationResult =
-    RepoConfigAlg.parseRepoConfig(content) match {
-      case Left(err) => ConfigValidationResult.ConfigIsInvalid(err)
-      case Right(_)  => ConfigValidationResult.Ok
-    }
-
-  private def printCirceError(indent: String)(err: io.circe.Error): String =
-    err match {
-      case d: DecodingFailure =>
-        val history =
-          d.history
-            .map {
-              case CursorOp.DownField(k) => k
-              case CursorOp.Field(k)     => k
-              case other                 => other
-            }
-            .mkString(s"${indent * 3}\n")
-        s"""|${indent}Decoding failed with:
-            |${indent * 2}${d.message}:
-            |${indent * 3}${history}""".stripMargin
-      case ParsingFailure(message, _) =>
-        s"""|${indent}Parsing failed with "$message".""".stripMargin
-    }
-
-  def presentValidationResult(
-      configFile: File
-  )(result: ConfigValidationResult): Either[String, String] =
-    result match {
-      case ConfigValidationResult.Ok =>
-        s"Configuration file at $configFile is valid.".asRight
-      case ConfigValidationResult.FileDoesNotExist =>
-        s"Configuration file at $configFile does not exist!".asLeft
-      case ConfigValidationResult.ConfigIsInvalid(err) =>
-        s"""|Configuration file at $configFile contains errors:
-            |${printCirceError(" " * 2)(err)}""".stripMargin.asLeft
-    }
-}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/util/logger.scala b/modules/core/src/main/scala/org/scalasteward/core/util/logger.scala
index f8d80a2a..a2860d9f 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/util/logger.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/util/logger.scala
@@ -17,10 +17,8 @@
 package org.scalasteward.core.util
 
 import cats.syntax.all._
-import cats.{Monad, MonadThrow}
-import org.scalasteward.core.data.GroupedUpdate
+import cats.{Foldable, Functor, Monad, MonadThrow}
 import org.scalasteward.core.data.Update
-import org.scalasteward.core.data.AnUpdate
 import org.typelevel.log4cats.Logger
 import scala.concurrent.duration.FiniteDuration
 
@@ -68,14 +66,8 @@ object logger {
     }
   }
 
-  def showUpdates(allUpdates: List[AnUpdate]): String = {
-    val updates = allUpdates.map {
-      case g: GroupedUpdate =>
-        g.updates.map(_.show).mkString(s"${g.name} (group) {\n    ", "\n    ", "\n  }")
-      case u: Update => u.show
-    }
-
-    val list = string.indentLines(updates)
+  def showUpdates[F[_]: Foldable: Functor](updates: F[Update]): String = {
+    val list = string.indentLines(updates.map(_.show))
     updates.size match {
       case 0 => s"Found 0 updates"
       case 1 => s"Found 1 update:\n$list"
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 842b957e..b7fc0167 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
@@ -20,16 +20,12 @@ import cats.Monad
 import cats.syntax.all._
 import org.http4s.Uri
 import org.scalasteward.core.application.Config.VCSCfg
-import org.scalasteward.core.data.{ReleaseRelatedUrl, Version}
+import org.scalasteward.core.data.{ReleaseRelatedUrl, Update}
 import org.scalasteward.core.util.UrlChecker
 import org.scalasteward.core.vcs
 
 trait VCSExtraAlg[F[_]] {
-  def getReleaseRelatedUrls(
-      repoUrl: Uri,
-      currentVersion: Version,
-      nextVersion: Version
-  ): F[List[ReleaseRelatedUrl]]
+  def getReleaseRelatedUrls(repoUrl: Uri, update: Update): F[List[ReleaseRelatedUrl]]
 }
 
 object VCSExtraAlg {
@@ -38,19 +34,9 @@ object VCSExtraAlg {
       F: Monad[F]
   ): VCSExtraAlg[F] =
     new VCSExtraAlg[F] {
-      override def getReleaseRelatedUrls(
-          repoUrl: Uri,
-          currentVersion: Version,
-          nextVersion: Version
-      ): F[List[ReleaseRelatedUrl]] =
+      override def getReleaseRelatedUrls(repoUrl: Uri, update: Update): F[List[ReleaseRelatedUrl]] =
         vcs
-          .possibleReleaseRelatedUrls(
-            config.tpe,
-            config.apiHost,
-            repoUrl,
-            currentVersion,
-            nextVersion
-          )
+          .possibleReleaseRelatedUrls(config.tpe, config.apiHost, repoUrl, update)
           .filterA(releaseRelatedUrl => urlChecker.exists(releaseRelatedUrl.url))
     }
 }
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 27ec47bc..793bd372 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
@@ -43,14 +43,15 @@ object NewPullRequestData {
     deriveEncoder
 
   def bodyFor(
-      update: AnUpdate,
+      update: Update,
       edits: List[EditAttempt],
       artifactIdToUrl: Map[String, Uri],
-      releaseRelatedUrls: Map[String, List[ReleaseRelatedUrl]],
+      releaseRelatedUrls: List[ReleaseRelatedUrl],
       filesWithOldVersion: List[String],
       configParsingError: Option[String],
       labels: List[String]
   ): String = {
+    val artifacts = artifactsWithOptionalUrl(update, artifactIdToUrl)
     val migrations = edits.collect { case scalafixEdit: ScalafixEdit => scalafixEdit }
     val appliedMigrations = migrationNote(migrations)
     val oldVersionDetails = oldVersionNote(filesWithOldVersion, update)
@@ -61,40 +62,12 @@ object NewPullRequestData {
       configParsingError.map(configParsingErrorDetails)
     ).flatten
 
-    val updatesText = update.on(
-      update = u => {
-        val artifacts = artifactsWithOptionalUrl(u, artifactIdToUrl)
-
-        val relatedUrls = releaseRelatedUrls.get(u.mainArtifactId).getOrElse(Nil)
-
-        s"""|Updates $artifacts ${fromTo(u)}.
-            |${releaseNote(relatedUrls).getOrElse("")}""".stripMargin.trim
-      },
-      grouped = g => {
-        val artifacts = g.updates
-          .fproduct(u => releaseRelatedUrls.get(u.mainArtifactId).orEmpty)
-          .map { case (u, relatedUrls) =>
-            s"* ${artifactsWithOptionalUrl(u, artifactIdToUrl)} ${fromTo(u)}" +
-              releaseNote(relatedUrls).map(urls => s"\n  + $urls").getOrElse("")
-          }
-          .mkString_("\n", "\n", "\n")
-
-        s"""|Updates:
-            |$artifacts""".stripMargin.trim
-      }
-    )
-
-    val skipVersionMessage = update.on(
-      _ => "If you'd like to skip this version, you can just close this PR. ",
-      _ => ""
-    )
-
-    s"""|$updatesText
-        |
+    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.
         |
-        |${skipVersionMessage}If you have any feedback, just mention me in the comments below.
+        |If you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.
         |
         |Configure Scala Steward for your repository with a [`${RepoConfigAlg.repoConfigBasename}`](${org.scalasteward.core.BuildInfo.gitHubUrl}/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.
         |
@@ -147,16 +120,11 @@ object NewPullRequestData {
       case None      => s"$groupId:$artifactId"
     }
 
-  def oldVersionNote(files: List[String], update: AnUpdate): Option[Details] =
+  def oldVersionNote(files: List[String], update: Update): Option[Details] =
     Option.when(files.nonEmpty) {
-      val (number, numberWithVersion) = update.on(
-        update = u => ("number", s"number (${u.currentVersion})"),
-        grouped = _ => ("numbers", "numbers")
-      )
-
       Details(
-        s"Files still referring to the old version $number",
-        s"""The following files still refer to the old version $numberWithVersion.
+        "Files still referring to the old version number",
+        s"""The following files still refer to the old version number (${update.currentVersion}).
            |You might want to review and update them manually.
            |```
            |${files.mkString("\n")}
@@ -165,31 +133,19 @@ object NewPullRequestData {
       )
     }
 
-  def adjustFutureUpdates(update: AnUpdate): Details = Details(
-    "Adjust future updates",
-    update.on(
-      update = u =>
-        s"""|Add this to your `${RepoConfigAlg.repoConfigBasename}` file to ignore future updates of this dependency:
-            |```
-            |${RepoConfigAlg.configToIgnoreFurtherUpdates(u)}
-            |```
-            |Or, add this to slow down future updates of this dependency:
-            |```
-            |${GroupRepoConfig.configToSlowDownUpdatesFrequency(u)}
-            |```
-            |""".stripMargin.trim,
-      grouped = g =>
-        s"""|Add these to your `${RepoConfigAlg.repoConfigBasename}` file to ignore future updates of these dependencies:
-            |```
-            |${RepoConfigAlg.configToIgnoreFurtherUpdates(g)}
-            |```
-            |Or, add these to slow down future updates of these dependencies:
-            |```
-            |${GroupRepoConfig.configToSlowDownUpdatesFrequency(g)}
-            |```
-            |""".stripMargin.trim
+  def adjustFutureUpdates(update: Update): Details =
+    Details(
+      "Adjust future updates",
+      s"""|Add this to your `${RepoConfigAlg.repoConfigBasename}` file to ignore future updates of this dependency:
+          |```
+          |${RepoConfigAlg.configToIgnoreFurtherUpdates(update)}
+          |```
+          |Or, add this to slow down future updates of this dependency:
+          |```
+          |${GroupRepoConfig.configToSlowDownUpdatesFrequency(update)}
+          |```
+          |""".stripMargin.trim
     )
-  )
 
   def configParsingErrorDetails(error: String): Details =
     Details(
@@ -226,7 +182,7 @@ object NewPullRequestData {
       branchName: String,
       edits: List[EditAttempt] = List.empty,
       artifactIdToUrl: Map[String, Uri] = Map.empty,
-      releaseRelatedUrls: Map[String, List[ReleaseRelatedUrl]] = Map.empty,
+      releaseRelatedUrls: List[ReleaseRelatedUrl] = List.empty,
       filesWithOldVersion: List[String] = List.empty,
       includeMatchedLabels: Option[Regex] = None
   ): NewPullRequestData = {
@@ -253,24 +209,20 @@ object NewPullRequestData {
     )
   }
 
-  def updateType(anUpdate: AnUpdate): List[String] = {
-    def forUpdate(update: Update) = {
-      val dependencies = update.dependencies
-      if (dependencies.forall(_.configurations.contains("test")))
-        "test-library-update"
-      else if (dependencies.forall(_.configurations.contains("scalafix-rule")))
-        "scalafix-rule-update"
-      else if (dependencies.forall(_.sbtVersion.isDefined))
-        "sbt-plugin-update"
-      else
-        "library-update"
-    }
-
-    anUpdate.on(u => List(forUpdate(u)), _.updates.map(forUpdate).distinct)
+  def updateType(update: Update): String = {
+    val dependencies = update.dependencies
+    if (dependencies.forall(_.configurations.contains("test")))
+      "test-library-update"
+    else if (dependencies.forall(_.configurations.contains("scalafix-rule")))
+      "scalafix-rule-update"
+    else if (dependencies.forall(_.sbtVersion.isDefined))
+      "sbt-plugin-update"
+    else
+      "library-update"
   }
 
   def labelsFor(
-      update: AnUpdate,
+      update: Update,
       edits: List[EditAttempt],
       filesWithOldVersion: List[String],
       includeMatchedLabels: Option[Regex]
@@ -280,26 +232,19 @@ object NewPullRequestData {
       case n if n <= 1 => s"$n"
       case n           => s"n:$n"
     })
-
-    def semverForUpdate(u: Update): List[String] = {
-      val semVerVersions =
-        (SemVer.parse(u.currentVersion.value), SemVer.parse(u.nextVersion.value)).tupled
-      val earlySemVerLabel = semVerVersions.flatMap { case (curr, next) =>
-        SemVer.getChangeEarly(curr, next).map(c => s"early-semver-${c.render}")
-      }
-      val semVerSpecLabel = semVerVersions.flatMap { case (curr, next) =>
-        SemVer.getChangeSpec(curr, next).map(c => s"semver-spec-${c.render}")
-      }
-      List(earlySemVerLabel, semVerSpecLabel).flatten
+    val semVerVersions =
+      (SemVer.parse(update.currentVersion.value), SemVer.parse(update.nextVersion.value)).tupled
+    val earlySemVerLabel = semVerVersions.flatMap { case (curr, next) =>
+      SemVer.getChangeEarly(curr, next).map(c => s"early-semver-${c.render}")
+    }
+    val semVerSpecLabel = semVerVersions.flatMap { case (curr, next) =>
+      SemVer.getChangeSpec(curr, next).map(c => s"semver-spec-${c.render}")
     }
-    val semver: List[String] =
-      update.on(u => semverForUpdate(u), _.updates.flatMap(semverForUpdate(_)).distinct)
-
     val scalafixLabel = edits.collectFirst { case _: ScalafixEdit => "scalafix-migrations" }
     val oldVersionLabel = Option.when(filesWithOldVersion.nonEmpty)("old-version-remains")
 
-    val allLabels = updateType(update) ++
-      semver ++ List(scalafixLabel, oldVersionLabel).flatten ++
+    val allLabels = updateType(update) ::
+      List(earlySemVerLabel, semVerSpecLabel, scalafixLabel, oldVersionLabel).flatten ++
       List(commitCountLabel)
 
     allLabels.filter(label => includeMatchedLabels.fold(true)(_.matches(label)))
diff --git a/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlg.scala
index 0ccf8d02..05762010 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlg.scala
@@ -62,10 +62,6 @@ final private[gitlab] case class MergeRequestOut(
   val pullRequestOut: PullRequestOut = PullRequestOut(webUrl, state, iid, title)
 }
 
-final private[gitlab] case class MergeRequestApprovalsOut(
-    approvalsRequired: Int
-)
-
 final private[gitlab] case class CommitId(id: Sha1) {
   val commitOut: CommitOut = CommitOut(id)
 }
@@ -113,13 +109,6 @@ private[gitlab] object GitLabJsonCodec {
     } yield MergeRequestOut(webUrl, state, title, iid, mergeStatus)
   }
 
-  implicit val mergeRequestApprovalsOutDecoder: Decoder[MergeRequestApprovalsOut] =
-    Decoder.instance { c =>
-      for {
-        requiredReviewers <- c.downField("approvals_required").as[Int]
-      } yield MergeRequestApprovalsOut(requiredReviewers)
-    }
-
   implicit val projectIdDecoder: Decoder[ProjectId] = deriveDecoder
   implicit val mergeRequestPayloadEncoder: Encoder[MergeRequestPayload] = deriveEncoder
   implicit val updateStateEncoder: Encoder[UpdateState] = Encoder.instance { newState =>
@@ -192,64 +181,34 @@ final class GitLabApiAlg[F[_]](
     val updatedMergeRequest =
       if (!gitLabCfg.mergeWhenPipelineSucceeds)
         mergeRequest
-      else {
-        for {
-          mr <- mergeRequest
-          mrWithStatus <- waitForMergeRequestStatus(mr.iid)
-          _ <- maybeSetReviewers(repo, mrWithStatus)
-          mergedUponSuccess <- mergePipelineUponSuccess(repo, mrWithStatus)
-        } yield mergedUponSuccess
-      }
+      else
+        mergeRequest
+          .flatMap(mr => waitForMergeRequestStatus(mr.iid))
+          .flatMap {
+            case mr if mr.mergeStatus === GitLabMergeStatus.CanBeMerged =>
+              for {
+                _ <- logger.info(s"Setting ${mr.webUrl} to merge when pipeline succeeds")
+                res <-
+                  client
+                    .put[MergeRequestOut](
+                      url.mergeWhenPiplineSucceeds(repo, mr.iid),
+                      modify(repo)
+                    )
+                    // it's possible that our status changed from can be merged already,
+                    // so just handle it gracefully and proceed without setting auto merge.
+                    .recoverWith { case UnexpectedResponse(_, _, _, status, _) =>
+                      logger
+                        .warn(s"Unexpected gitlab response setting auto merge: $status")
+                        .as(mr)
+                    }
+              } yield res
+            case mr =>
+              logger.info(s"Unable to automatically merge ${mr.webUrl}").map(_ => mr)
+          }
 
     updatedMergeRequest.map(_.pullRequestOut)
   }
 
-  private def mergePipelineUponSuccess(repo: Repo, mr: MergeRequestOut): F[MergeRequestOut] =
-    mr match {
-      case mr if mr.mergeStatus === GitLabMergeStatus.CanBeMerged =>
-        for {
-          _ <- logger.info(s"Setting ${mr.webUrl} to merge when pipeline succeeds")
-          res <-
-            client
-              .put[MergeRequestOut](
-                url.mergeWhenPiplineSucceeds(repo, mr.iid),
-                modify(repo)
-              )
-              // it's possible that our status changed from can be merged already,
-              // so just handle it gracefully and proceed without setting auto merge.
-              .recoverWith { case UnexpectedResponse(_, _, _, status, _) =>
-                logger
-                  .warn(s"Unexpected gitlab response setting auto merge: $status")
-                  .as(mr)
-              }
-        } yield res
-      case mr =>
-        logger.info(s"Unable to automatically merge ${mr.webUrl}").map(_ => mr)
-    }
-
-  private def maybeSetReviewers(repo: Repo, mrOut: MergeRequestOut): F[MergeRequestOut] =
-    gitLabCfg.requiredReviewers match {
-      case Some(requiredReviewers) =>
-        for {
-          _ <- logger.info(
-            s"Setting number of required reviewers on ${mrOut.webUrl} to $requiredReviewers"
-          )
-          _ <-
-            client
-              .put[MergeRequestApprovalsOut](
-                url.requiredApprovals(repo, mrOut.iid, requiredReviewers),
-                modify(repo)
-              )
-              .map(_ => ())
-              .recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
-                logger
-                  .warn(s"Unexpected response setting required reviewers: $status:  $body")
-                  .as(())
-              }
-        } yield mrOut
-      case None => F.pure(mrOut)
-    }
-
   override def closePullRequest(repo: Repo, number: PullRequestNumber): F[PullRequestOut] =
     client
       .putWithBody[MergeRequestOut, UpdateState](
diff --git a/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/Url.scala b/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/Url.scala
index 03e41f64..993848a0 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/Url.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/vcs/gitlab/Url.scala
@@ -42,10 +42,6 @@ class Url(apiHost: Uri) {
     (existingMergeRequest(repo, number) / "merge")
       .withQueryParam("merge_when_pipeline_succeeds", "true")
 
-  def requiredApprovals(repo: Repo, number: PullRequestNumber, approvalsRequired: Int): Uri =
-    (existingMergeRequest(repo, number) / "approvals")
-      .withQueryParam("approvals_required", approvalsRequired)
-
   def listMergeRequests(repo: Repo, source: String, target: String): Uri =
     mergeRequest(repo)
       .withQueryParam("source_branch", source)
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 f0f06e96..a0763251 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
@@ -19,7 +19,7 @@ package org.scalasteward.core
 import cats.syntax.all._
 import org.http4s.Uri
 import org.scalasteward.core.data.ReleaseRelatedUrl.VersionDiff
-import org.scalasteward.core.data.{ReleaseRelatedUrl, Version}
+import org.scalasteward.core.data.{ReleaseRelatedUrl, Update, Version}
 import org.scalasteward.core.git.Branch
 import org.scalasteward.core.vcs.VCSType.{Bitbucket, BitbucketServer, GitHub, GitLab}
 import org.scalasteward.core.vcs.data.Repo
@@ -87,32 +87,36 @@ package object vcs {
       vcsType: VCSType,
       vcsUri: Uri,
       repoUrl: Uri,
-      currentVersion: Version,
-      nextVersion: Version
-  ): List[VersionDiff] =
-    extractRepoVCSType(vcsType, vcsUri, repoUrl).map {
-      case GitHub | GitLab =>
-        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"))
-        }
-    }.orEmpty
+      update: Update
+  ): List[VersionDiff] = {
+    val from = update.currentVersion
+    val to = update.nextVersion
+
+    extractRepoVCSType(vcsType, vcsUri, repoUrl)
+      .map {
+        case GitHub | GitLab =>
+          possibleTags(from).zip(possibleTags(to)).map { case (from1, to1) =>
+            VersionDiff(repoUrl / "compare" / s"$from1...$to1")
+          }
+        case Bitbucket | BitbucketServer =>
+          possibleTags(from).zip(possibleTags(to)).map { case (from1, to1) =>
+            VersionDiff((repoUrl / "compare" / s"$to1..$from1").withFragment("diff"))
+          }
+      }
+      .getOrElse(List.empty)
+  }
 
   def possibleReleaseRelatedUrls(
       vcsType: VCSType,
       vcsUri: Uri,
       repoUrl: Uri,
-      currentVersion: Version,
-      nextVersion: Version
+      update: Update
   ): List[ReleaseRelatedUrl] = {
     val repoVCSType = extractRepoVCSType(vcsType, vcsUri, repoUrl)
 
     val github = repoVCSType
       .collect { case GitHub =>
-        possibleTags(nextVersion).map(tag =>
+        possibleTags(update.nextVersion).map(tag =>
           ReleaseRelatedUrl.GitHubReleaseNotes(repoUrl / "releases" / "tag" / tag)
         )
       }
@@ -135,7 +139,7 @@ package object vcs {
       files(possibleReleaseNotesFilenames).map(ReleaseRelatedUrl.CustomReleaseNotes)
 
     github ++ customReleaseNotes ++ customChangelog ++
-      possibleCompareUrls(vcsType, vcsUri, repoUrl, currentVersion, nextVersion)
+      possibleCompareUrls(vcsType, vcsUri, repoUrl, update)
   }
 
   private def possibleFilenames(baseNames: List[String]): List[String] = {
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 56707cb1..f2e15661 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -5,18 +5,18 @@ import sbt.librarymanagement.syntax.ExclusionRule
 object Dependencies {
   val bcprovJdk15to18 = "org.bouncycastle" % "bcprov-jdk15to18" % "1.72"
   val betterFiles = "com.github.pathikrit" %% "better-files" % "3.9.1"
-  val catsEffect = "org.typelevel" %% "cats-effect" % "3.4.0"
-  val catsCore = "org.typelevel" %% "cats-core" % "2.9.0"
+  val catsEffect = "org.typelevel" %% "cats-effect" % "3.3.14"
+  val catsCore = "org.typelevel" %% "cats-core" % "2.8.0"
   val catsLaws = "org.typelevel" %% "cats-laws" % catsCore.revision
   val catsParse = "org.typelevel" %% "cats-parse" % "0.3.8"
   val circeConfig = "io.circe" %% "circe-config" % "0.8.0"
   val circeGeneric = "io.circe" %% "circe-generic" % "0.14.3"
-  val circeGenericExtras = "io.circe" %% "circe-generic-extras" % "0.14.3"
+  val circeGenericExtras = "io.circe" %% "circe-generic-extras" % "0.14.2"
   val circeLiteral = "io.circe" %% "circe-literal" % circeGeneric.revision
   val circeParser = "io.circe" %% "circe-parser" % circeGeneric.revision
   val circeRefined = "io.circe" %% "circe-refined" % circeGeneric.revision
   val commonsIo = "commons-io" % "commons-io" % "2.11.0"
-  val coursierCore = "io.get-coursier" %% "coursier" % "2.1.0-RC2"
+  val coursierCore = "io.get-coursier" %% "coursier" % "2.1.0-M7"
   val cron4sCore = "com.github.alonsodomin.cron4s" %% "cron4s-core" % "0.6.1"
   val decline = "com.monovore" %% "decline" % "2.3.1"
   val disciplineMunit = "org.typelevel" %% "discipline-munit" % "1.0.9"
@@ -29,11 +29,11 @@ object Dependencies {
   val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % "1.0.0-M36"
   val http4sOkhttpClient = "org.http4s" %% "http4s-okhttp-client" % "1.0.0-M32"
   val log4catsSlf4j = "org.typelevel" %% "log4cats-slf4j" % "2.5.0"
-  val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.3.4"
+  val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.2.11"
   val jjwtApi = "io.jsonwebtoken" % "jjwt-api" % "0.11.5"
   val jjwtImpl = "io.jsonwebtoken" % "jjwt-impl" % jjwtApi.revision
   val jjwtJackson = "io.jsonwebtoken" % "jjwt-jackson" % jjwtApi.revision
-  val millVersion = Def.setting(if (scalaBinaryVersion.value == "2.12") "0.6.3" else "0.10.9")
+  val millVersion = Def.setting(if (scalaBinaryVersion.value == "2.12") "0.6.3" else "0.10.7")
   val millScalalib = Def.setting("com.lihaoyi" %% "mill-scalalib" % millVersion.value)
   val monocleCore = "dev.optics" %% "monocle-core" % "3.1.0"
   val munit = "org.scalameta" %% "munit" % "0.7.29"
diff --git a/project/build.properties b/project/build.properties
index 8b9a0b0a..563a014d 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1,1 +1,1 @@
-sbt.version=1.8.0
+sbt.version=1.7.2
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 39b36c56..fe474d0f 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,17 +1,17 @@
 addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2")
-addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.2.0")
 addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0")
-addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.11")
-addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
+addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10")
+addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
 addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.2.16")
 addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.10.0")
-addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.11")
-addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.9.0")
+addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9")
+addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.7.0")
 addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.1")
 addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3")
 addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0")
 addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.6")
-addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6")
+addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.5")
 
 // A workaround until sbt ecosystem migrate to scala-xml 2.x https://github.com/sbt/sbt/issues/6997
 ThisBuild / libraryDependencySchemes ++= Seq(


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala b/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala
index 5211cc7a..30cd5c42 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/application/CliTest.scala
@@ -8,13 +8,12 @@ import org.scalasteward.core.application.Cli.EnvVar
 import org.scalasteward.core.application.Cli.ParseResult._
 import org.scalasteward.core.vcs.VCSType
 import org.scalasteward.core.vcs.github.GitHubApp
-import org.scalasteward.core.application.Config.StewardUsage
 
 import scala.concurrent.duration._
 
 class CliTest extends FunSuite {
   test("parseArgs: example") {
-    val Success(StewardUsage.Regular(obtained)) = Cli.parseArgs(
+    val Success(obtained) = Cli.parseArgs(
       List(
         List("--workspace", "a"),
         List("--repos-file", "b"),
@@ -62,22 +61,17 @@ class CliTest extends FunSuite {
     )
     assertEquals(obtained.githubApp, Some(GitHubApp(12345678L, File("example_app_key"))))
     assertEquals(obtained.refreshBackoffPeriod, 1.day)
-    assert(!obtained.gitLabCfg.mergeWhenPipelineSucceeds)
-    assertEquals(obtained.gitLabCfg.requiredReviewers, None)
   }
 
-  val minimumRequiredParams = List(
-    List("--workspace", "a"),
-    List("--repos-file", "b"),
-    List("--git-author-email", "d"),
-    List("--vcs-login", "e"),
-    List("--git-ask-pass", "f"),
-    List("--disable-sandbox")
-  )
-
   test("parseArgs: minimal example") {
-    val Success(StewardUsage.Regular(obtained)) = Cli.parseArgs(
-      minimumRequiredParams.flatten
+    val Success(obtained) = Cli.parseArgs(
+      List(
+        List("--workspace", "a"),
+        List("--repos-file", "b"),
+        List("--git-author-email", "d"),
+        List("--vcs-login", "e"),
+        List("--git-ask-pass", "f")
+      ).flatten
     )
 
     assert(!obtained.processCfg.sandboxCfg.enableSandbox)
@@ -89,7 +83,7 @@ class CliTest extends FunSuite {
   }
 
   test("parseArgs: enable sandbox") {
-    val Success(StewardUsage.Regular(obtained)) = Cli.parseArgs(
+    val Success(obtained) = Cli.parseArgs(
       List(
         List("--workspace", "a"),
         List("--repos-file", "b"),
@@ -120,7 +114,7 @@ class CliTest extends FunSuite {
   }
 
   test("parseArgs: disable sandbox") {
-    val Success(StewardUsage.Regular(obtained)) = Cli.parseArgs(
+    val Success(obtained) = Cli.parseArgs(
       List(
         List("--workspace", "a"),
         List("--repos-file", "b"),
@@ -149,37 +143,6 @@ class CliTest extends FunSuite {
     assert(clue(obtained).startsWith("Usage"))
   }
 
-  test("parseArgs: non-default GitLab arguments") {
-    val params = minimumRequiredParams ++ List(
-      List("--gitlab-merge-when-pipeline-succeeds"),
-      List("--gitlab-required-reviewers", "5")
-    )
-    val Success(StewardUsage.Regular(obtained)) = Cli.parseArgs(params.flatten)
-
-    assert(obtained.gitLabCfg.mergeWhenPipelineSucceeds)
-    assertEquals(obtained.gitLabCfg.requiredReviewers, Some(5))
-  }
-
-  test("parseArgs: invalid GitLab required reviewers") {
-    val params = minimumRequiredParams ++ List(
-      List("--gitlab-merge-when-pipeline-succeeds"),
-      List("--gitlab-required-reviewers", "-3")
-    )
-    val Error(errorMsg) = Cli.parseArgs(params.flatten)
-
-    assert(clue(errorMsg).startsWith("Required reviewers must be non-negative"))
-  }
-
-  test("parseArgs: validate-repo-config") {
-    val Success(StewardUsage.ValidateRepoConfig(file)) = Cli.parseArgs(
-      List(
-        List("validate-repo-config", "file.conf")
-      ).flatten
-    )
-
-    assertEquals(file, File("file.conf"))
-  }
-
   test("envVarArgument: env-var without equals sign") {
     assert(clue(Cli.envVarArgument.read("SBT_OPTS")).isInvalid)
   }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/data/GroupedUpdateTest.scala b/modules/core/src/test/scala/org/scalasteward/core/data/GroupedUpdateTest.scala
deleted file mode 100644
index 5c876413..00000000
--- a/modules/core/src/test/scala/org/scalasteward/core/data/GroupedUpdateTest.scala
+++ /dev/null
@@ -1,565 +0,0 @@
-package org.scalasteward.core.data
-
-import cats.data.NonEmptyList
-import cats.implicits.catsSyntaxOptionId
-import org.scalasteward.core.TestSyntax._
-import munit.FunSuite
-import org.scalasteward.core.repoconfig.{PullRequestGroup, PullRequestUpdateFilter}
-
-class GroupedUpdateTest extends FunSuite {
-  val updateSingleSpecs2Core: Update.Single =
-    ("org.specs2".g % "specs2-core".a % "3.9.3" %> "3.9.5").single
-
-  val updateSingleSpecs2Scalacheck: Update.Single =
-    ("org.specs2".g % "specs2-scalacheck".a % "3.9.3" %> "3.9.5").single
-
-  val updateSingleTypelevelAlgebra: Update.Single =
-    ("org.typelevel".g % "algebra".a % "3.9.4" %> "3.9.5").single
-
-  val updateSingleCirceCore: Update.Single =
-    ("circe".g % "core".a % "1.4.2" %> "1.5.0").single
-
-  test(
-    "GroupedUpdate.from: Do not group a Update.Single if the groupId does not match the filter"
-  ) {
-    val pullRequestGroupSpecs2: PullRequestGroup = PullRequestGroup(
-      "specs2",
-      Some("update specs2"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.specs2".some).getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(List(pullRequestGroupSpecs2), List(updateSingleTypelevelAlgebra))
-    assertEquals(grouped, List.empty)
-    assertEquals(notGrouped, List(updateSingleTypelevelAlgebra))
-  }
-
-  test(
-    "GroupedUpdate.from: Do not group multiple Update.Single if the groupId does not match the filter"
-  ) {
-    val pullRequestGroupTypeLevel: PullRequestGroup = PullRequestGroup(
-      "typelevel",
-      Some("update typelevel"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.typelevel".some).getOrElse(fail("Should not be called"))
-      )
-    )
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestGroupTypeLevel),
-        List(updateSingleSpecs2Core, updateSingleSpecs2Scalacheck)
-      )
-    assertEquals(grouped, List.empty)
-    assertEquals(notGrouped, List(updateSingleSpecs2Core, updateSingleSpecs2Scalacheck))
-  }
-
-  test("GroupedUpdate.from: Group a Update.Single for a single GroupId filter") {
-    val pullRequestGroupTypeLevel: PullRequestGroup = PullRequestGroup(
-      "typelevel",
-      Some("update typelevel"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.typelevel".some).getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestGroupTypeLevel),
-        List(updateSingleTypelevelAlgebra)
-      )
-
-    assertEquals(
-      grouped,
-      List(GroupedUpdate("typelevel", Some("update typelevel"), List(updateSingleTypelevelAlgebra)))
-    )
-    assertEquals(notGrouped, List.empty)
-  }
-
-  test("GroupedUpdate.from: Group multiple Update.Single for a single GroupId filter") {
-    val pullRequestGroupSpecs2: PullRequestGroup = PullRequestGroup(
-      "specs2",
-      Some("update specs2"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.specs2".some).getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestGroupSpecs2),
-        List(updateSingleSpecs2Core, updateSingleSpecs2Scalacheck)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "specs2",
-          Some("update specs2"),
-          List(updateSingleSpecs2Core, updateSingleSpecs2Scalacheck)
-        )
-      )
-    )
-    assertEquals(notGrouped, List.empty)
-  }
-
-  test("GroupedUpdate.from: Group multiple Updates for multiple GroupId filters") {
-    val pullRequestGroupSpecs2: PullRequestGroup = PullRequestGroup(
-      "specs2",
-      Some("update specs2"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.specs2".some).getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val pullRequestGroupTypeLevel: PullRequestGroup = PullRequestGroup(
-      "typelevel",
-      Some("update typelevel"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.typelevel".some).getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val updates = List(
-      updateSingleSpecs2Core,
-      updateSingleSpecs2Scalacheck,
-      updateSingleTypelevelAlgebra,
-      updateSingleCirceCore
-    )
-    val pullRequestGroups = List(pullRequestGroupSpecs2, pullRequestGroupTypeLevel)
-
-    val (grouped, notGrouped) = GroupedUpdate.from(pullRequestGroups, updates)
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "specs2",
-          Some("update specs2"),
-          List(updateSingleSpecs2Core, updateSingleSpecs2Scalacheck)
-        ),
-        GroupedUpdate("typelevel", Some("update typelevel"), List(updateSingleTypelevelAlgebra))
-      )
-    )
-    assertEquals(notGrouped, List(updateSingleCirceCore))
-  }
-
-  val pullRequestGroupOrgWildcard: PullRequestGroup = PullRequestGroup(
-    "org",
-    Some("update org"),
-    NonEmptyList.one(PullRequestUpdateFilter("org.*".some).getOrElse(fail("Should not be called")))
-  )
-
-  test("GroupedUpdate.from: Group multiple Updates for a GroupId filter with a wildcard") {
-    val updates = List(
-      updateSingleSpecs2Core,
-      updateSingleSpecs2Scalacheck,
-      updateSingleTypelevelAlgebra,
-      updateSingleCirceCore
-    )
-    val pullRequestGroups = List(pullRequestGroupOrgWildcard)
-
-    val (grouped, notGrouped) = GroupedUpdate.from(pullRequestGroups, updates)
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "org",
-          Some("update org"),
-          List(updateSingleSpecs2Core, updateSingleSpecs2Scalacheck, updateSingleTypelevelAlgebra)
-        )
-      )
-    )
-    assertEquals(notGrouped, List(updateSingleCirceCore))
-  }
-
-  test("GroupedUpdate.from: Group everything when using * for OrganisationId") {
-    val pullRequestGroupWildcardAll: PullRequestGroup = PullRequestGroup(
-      "all wildcard",
-      Some("update all wildcard"),
-      NonEmptyList.one(PullRequestUpdateFilter("*".some).getOrElse(fail("Should not be called")))
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestGroupWildcardAll),
-        List(
-          updateSingleTypelevelAlgebra,
-          updateSingleSpecs2Core,
-          updateSingleSpecs2Scalacheck
-        )
-      )
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "all wildcard",
-          Some("update all wildcard"),
-          List(
-            updateSingleTypelevelAlgebra,
-            updateSingleSpecs2Core,
-            updateSingleSpecs2Scalacheck
-          )
-        )
-      )
-    )
-    assertEquals(notGrouped, List.empty)
-  }
-
-  test("GroupedUpdate.from: Group Update.Group for an ArtifactId filter without a group") {
-    val pullRequestArtifactSpecs2CoreWithoutGroup: PullRequestGroup = PullRequestGroup(
-      "specs2 core",
-      Some("update specs2 core"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter(None, "specs2-core".some).getOrElse(fail("Should not be called"))
-      )
-    )
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2CoreWithoutGroup),
-        List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core)
-      )
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "specs2 core",
-          Some("update specs2 core"),
-          List(updateSingleSpecs2Core)
-        )
-      )
-    )
-    assertEquals(notGrouped, List(updateSingleSpecs2Scalacheck))
-  }
-
-  val pullRequestArtifactSpecs2CoreWithGroup: PullRequestGroup = PullRequestGroup(
-    "specs2 core",
-    Some("update specs2 core"),
-    NonEmptyList.one(
-      PullRequestUpdateFilter("org.specs2".some, "specs2-core".some).getOrElse(
-        fail("Should not be called")
-      )
-    )
-  )
-
-  test("GroupedUpdate.from: Group multiple Update.Single for an ArtifactId filter with a group") {
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2CoreWithGroup),
-        List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core)
-      )
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "specs2 core",
-          Some("update specs2 core"),
-          List(updateSingleSpecs2Core)
-        )
-      )
-    )
-    assertEquals(notGrouped, List(updateSingleSpecs2Scalacheck))
-  }
-
-  test(
-    "GroupedUpdate.from: Not group multiple Update.Single for an ArtifactId filter with an invalid group"
-  ) {
-    val pullRequestArtifactSpecs2CoreWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "specs2 core",
-      Some("update specs2 core"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.invalid".some, "specs2-core".some).getOrElse(
-          fail("Should not be called")
-        )
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2CoreWithInvalidGroup),
-        List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core)
-      )
-
-    assertEquals(grouped, List.empty)
-    assertEquals(notGrouped, List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core))
-  }
-
-  test(
-    "GroupedUpdate.from: Group multiple Update.Single for an ArtifactId filter with a wildcard"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithoutGroup: PullRequestGroup = PullRequestGroup(
-      "specs2",
-      Some("update specs2"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter(None, "specs2-*".some).getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithoutGroup),
-        List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core, updateSingleTypelevelAlgebra)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "specs2",
-          Some("update specs2"),
-          List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core)
-        )
-      )
-    )
-    assertEquals(notGrouped, List(updateSingleTypelevelAlgebra))
-  }
-
-  test(
-    "GroupedUpdate.from: Group multiple Update.Single for an ArtifactId filter with a wildcard and a group"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithGroup: PullRequestGroup = PullRequestGroup(
-      "specs2",
-      Some("update specs2"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.specs2".some, "specs2-*".some).getOrElse(
-          fail("Should not be called")
-        )
-      )
-    )
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithGroup),
-        List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core, updateSingleTypelevelAlgebra)
-      )
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate(
-          "specs2",
-          Some("update specs2"),
-          List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core)
-        )
-      )
-    )
-    assertEquals(notGrouped, List(updateSingleTypelevelAlgebra))
-  }
-
-  test(
-    "GroupedUpdate.from: Not group multiple Update.Single for an ArtifactId filter with a wildcard an invalid group"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "specs2",
-      Some("update specs2"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter("org.invalid".some, "specs2-*".some).getOrElse(
-          fail("Should not be called")
-        )
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core, updateSingleTypelevelAlgebra)
-      )
-
-    assertEquals(grouped, List.empty)
-    assertEquals(
-      notGrouped,
-      List(updateSingleSpecs2Scalacheck, updateSingleSpecs2Core, updateSingleTypelevelAlgebra)
-    )
-  }
-
-  val majorUpdate: Update.Single = ("org.major".g % "major".a % "3.1.2" %> "4.0.0").single
-  val minorUpdate: Update.Single = ("org.minor".g % "minor".a % "3.1.2" %> "3.2.0").single
-  val patchUpdate: Update.Single = ("org.patch".g % "patch".a % "3.1.2" %> "3.1.3").single
-
-  test(
-    "GroupedUpdate.from: Group major updates"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "major",
-      Some("update major"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter(version = SemVer.Change.Major.some)
-          .getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(majorUpdate, minorUpdate, patchUpdate)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate("major", Some("update major"), List(majorUpdate))
-      )
-    )
-
-    assertEquals(notGrouped, List(minorUpdate, patchUpdate))
-  }
-
-  test(
-    "GroupedUpdate.from: Group minor updates"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "minor",
-      Some("update minor"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter(version = SemVer.Change.Minor.some)
-          .getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(majorUpdate, minorUpdate, patchUpdate)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate("minor", Some("update minor"), List(minorUpdate))
-      )
-    )
-
-    assertEquals(notGrouped, List(majorUpdate, patchUpdate))
-  }
-
-  test(
-    "GroupedUpdate.from: Group patch updates"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "patch",
-      Some("update patch"),
-      NonEmptyList.one(
-        PullRequestUpdateFilter(version = SemVer.Change.Patch.some)
-          .getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(majorUpdate, minorUpdate, patchUpdate)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate("patch", Some("update patch"), List(patchUpdate))
-      )
-    )
-
-    assertEquals(notGrouped, List(majorUpdate, minorUpdate))
-  }
-
-  test(
-    "GroupedUpdate.from: Group minor & patch updates"
-  ) {
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "patch & minor",
-      Some("update patch & minor"),
-      NonEmptyList.of(
-        PullRequestUpdateFilter(version = SemVer.Change.Patch.some)
-          .getOrElse(fail("Should not be called")),
-        PullRequestUpdateFilter(version = SemVer.Change.Minor.some)
-          .getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(majorUpdate, minorUpdate, patchUpdate)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate("patch & minor", Some("update patch & minor"), List(minorUpdate, patchUpdate))
-      )
-    )
-
-    assertEquals(notGrouped, List(majorUpdate))
-  }
-
-  test(
-    "GroupedUpdate.from: Group major & minor updates with version numbers containing a date and commit hash"
-  ) {
-    val majorUpdate: Update.Single =
-      ("org.major".g % "major".a % "1.0.0-20220920-180024-dd318047" %> "2.0.0-20220922-180024-dd318047").single
-    val minorUpdate: Update.Single =
-      ("org.minor".g % "minor".a % "1.0.0-20220920-180024-dd318047" %> "1.1.0-20220922-180024-dd318047").single
-    val patchUpdate: Update.Single =
-      ("org.patch".g % "patch".a % "1.0.0-20220920-180024-dd318047" %> "1.0.1-20220922-180024-dd318047").single
-
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "major & minor",
-      Some("update major & minor"),
-      NonEmptyList.of(
-        PullRequestUpdateFilter(version = SemVer.Change.Major.some)
-          .getOrElse(fail("Should not be called")),
-        PullRequestUpdateFilter(version = SemVer.Change.Minor.some)
-          .getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(majorUpdate, minorUpdate, patchUpdate)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate("major & minor", Some("update major & minor"), List(majorUpdate, minorUpdate))
-      )
-    )
-
-    assertEquals(notGrouped, List(patchUpdate))
-  }
-
-  test(
-    "GroupedUpdate.from: Group patch updates with version numbers containing a date and commit hash"
-  ) {
-    val majorUpdate: Update.Single =
-      ("org.major".g % "major".a % "1.0.0-20220920-180024-dd318047" %> "2.0.0-20220922-180024-dd318047").single
-    val minorUpdate: Update.Single =
-      ("org.minor".g % "minor".a % "1.0.0-20220920-180024-dd318047" %> "1.1.0-20220922-180024-dd318047").single
-    val patchUpdate: Update.Single =
-      ("org.patch".g % "patch".a % "1.0.0-20220920-180024-dd318047" %> "1.0.1-20220920-180024-dd318047").single
-
-    val pullRequestArtifactSpecs2WildcardWithInvalidGroup: PullRequestGroup = PullRequestGroup(
-      "patch",
-      Some("update patch"),
-      NonEmptyList.of(
-        PullRequestUpdateFilter(version = SemVer.Change.Patch.some)
-          .getOrElse(fail("Should not be called"))
-      )
-    )
-
-    val (grouped, notGrouped) =
-      GroupedUpdate.from(
-        List(pullRequestArtifactSpecs2WildcardWithInvalidGroup),
-        List(majorUpdate, minorUpdate, patchUpdate)
-      )
-
-    assertEquals(
-      grouped,
-      List(
-        GroupedUpdate("patch", Some("update patch"), List(patchUpdate))
-      )
-    )
-
-    assertEquals(notGrouped, List(majorUpdate, minorUpdate))
-  }
-}
diff --git a/modules/core/src/test/scala/org/scalasteward/core/git/FileGitAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/git/FileGitAlgTest.scala
index 833b7910..41cfe4b2 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/git/FileGitAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/git/FileGitAlgTest.scala
@@ -205,7 +205,7 @@ object FileGitAlgTest {
       for {
         _ <- gitAlg.removeClone(repo)
         _ <- fileAlg.ensureExists(repo)
-        _ <- git("init", ".", "--initial-branch", "master")(repo)
+        _ <- git("init", ".")(repo)
         _ <- gitAlg.setAuthor(repo, config.gitCfg.gitAuthor)
         _ <- git("commit", "--allow-empty", "-m", "Initial commit")(repo)
       } yield ()
diff --git a/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala b/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala
index 75f9c7a8..6ea8bd1a 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala
@@ -21,7 +21,7 @@ object MockConfig {
     "--cache-ttl=1hour",
     "--refresh-backoff-period=1hour"
   )
-  val Success(Config.StewardUsage.Regular(config)) = Cli.parseArgs(args)
+  val Success(config: Config) = Cli.parseArgs(args)
   val envVars = List(s"GIT_ASKPASS=${config.gitCfg.gitAskPass}", "VAR1=val1", "VAR2=val2")
   def gitCmd(repoDir: File): List[String] =
     envVars ++ (repoDir.toString :: FileGitAlg.gitCmd.toList)
diff --git a/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala b/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala
index 3fdf24dd..c5f81193 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala
@@ -1,11 +1,9 @@
 package org.scalasteward.core.mock
 
-import better.files.File
 import cats.effect.unsafe.implicits.global
 import org.http4s.HttpApp
 import org.http4s.client.Client
 import org.scalasteward.core.application.Context
-import org.scalasteward.core.application.Context.StewardContext
 import org.scalasteward.core.edit.scalafix.ScalafixMigrationsLoader
 import org.scalasteward.core.io.FileAlgTest.ioFileAlg
 import org.scalasteward.core.io._
@@ -33,6 +31,4 @@ object MockContext {
   )
 
   val context: Context[MockEff] = mockState.toRef.flatMap(Context.step1(config).run).unsafeRunSync()
-  def validateRepoConfigContext(file: File): StewardContext.ValidateRepoConfig[MockEff] =
-    Context.initValidateRepoConfig(file)
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala b/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala
index a9bd8731..0d504497 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala
@@ -5,7 +5,6 @@ import cats.effect.unsafe.implicits.global
 import munit.FunSuite
 import org.http4s.syntax.literals._
 import org.scalasteward.core.TestSyntax._
-import org.scalasteward.core.data.GroupedUpdate
 import org.scalasteward.core.git.Sha1.HexString
 import org.scalasteward.core.git.{Branch, Sha1}
 import org.scalasteward.core.mock.MockConfig.config
@@ -138,58 +137,4 @@ class PullRequestRepositoryTest extends FunSuite {
       )
     )
   }
-
-  test("findLatestPullRequest ignores grouped updates") {
-    val repo = Repo("pr-repo-test", "repo5")
-    val update = portableScala
-    val grouped = GroupedUpdate("group", None, List(update))
-    val data = PullRequestData[Id](url, sha1, grouped, Open, number, branch)
-
-    val p = for {
-      _ <- pullRequestRepository.createOrUpdate(repo, data)
-      result <- pullRequestRepository.findLatestPullRequest(repo, update.crossDependency, "1.0.0".v)
-    } yield result
-
-    val (state, result) = p.runSA(MockState.empty).unsafeRunSync()
-
-    val store =
-      config.workspace / s"store/pull_requests/v2/github/${repo.toPath}/pull_requests.json"
-    assert(result.isEmpty)
-
-    checkTrace(
-      state,
-      Vector(
-        Cmd("read", store.toString),
-        Cmd("write", store.toString)
-      )
-    )
-  }
-
-  test("lastPullRequestCreatedAt returns timestamp for grouped updates") {
-    val repo = Repo("pr-repo-test", "repo7")
-    val update = catsCore
-    val grouped = GroupedUpdate("group", None, List(update))
-    val data = PullRequestData[Id](url, sha1, grouped, Open, number, branch)
-
-    val p = for {
-      emptyCreatedAt <- pullRequestRepository.lastPullRequestCreatedAt(repo)
-      _ <- pullRequestRepository.createOrUpdate(repo, data)
-      createdAt <- pullRequestRepository.lastPullRequestCreatedAt(repo)
-    } yield (emptyCreatedAt, createdAt)
-    val (state, (emptyCreatedAt, createdAt)) = p.runSA(MockState.empty).unsafeRunSync()
-
-    val store =
-      config.workspace / s"store/pull_requests/v2/github/${repo.toPath}/pull_requests.json"
-    assert(emptyCreatedAt.isEmpty)
-    assert(createdAt.isDefined)
-
-    checkTrace(
-      state,
-      Vector(
-        Cmd("read", store.toString),
-        Cmd("write", store.toString)
-      )
-    )
-  }
-
 }
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 fea9ed99..1b4f7b7b 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
@@ -5,7 +5,7 @@ import cats.syntax.all._
 import eu.timepit.refined.types.numeric.NonNegInt
 import munit.FunSuite
 import org.scalasteward.core.TestSyntax._
-import org.scalasteward.core.data.{GroupId, GroupedUpdate, SemVer}
+import org.scalasteward.core.data.GroupId
 import org.scalasteward.core.mock.MockContext.context.repoConfigAlg
 import org.scalasteward.core.mock.MockState.TraceEntry.Log
 import org.scalasteward.core.mock.{MockConfig, MockState}
@@ -13,7 +13,6 @@ import org.scalasteward.core.util.Nel
 import org.scalasteward.core.vcs.data.Repo
 
 import scala.concurrent.duration._
-import cats.data.NonEmptyList
 
 class RepoConfigAlgTest extends FunSuite {
   test("default config is not empty") {
@@ -42,13 +41,6 @@ class RepoConfigAlgTest extends FunSuite {
          |updates.limit = 4
          |updates.fileExtensions = [ ".txt" ]
          |pullRequests.frequency = "@weekly"
-         |pullRequests.grouping = [
-         |  { name = "patches", "title" = "Patch updates", "filter" = [{"version" = "patch"}] },
-         |  { name = "minor_major", "title" = "Minor/major updates", "filter" = [{"version" = "minor"}, {"version" = "major"}] },
-         |  { name = "typelevel", "title" = "Typelevel updates", "filter" = [{"group" = "org.typelevel"}, {"group" = "org.http4s"}] },
-         |  { name = "my_libraries", "filter" = [{"artifact" = "my-library"}, {"artifact" = "my-other-library", "group" = "my-org"}] },
-         |  { name = "all", "filter" = [{"group" = "*"}] }
-         |]
          |dependencyOverrides = [
          |  { pullRequests.frequency = "@daily",   dependency = { groupId = "eu.timepit" } },
          |  { pullRequests.frequency = "@monthly", dependency = { groupId = "eu.timepit", artifactId = "refined.1" } },
@@ -65,52 +57,7 @@ class RepoConfigAlgTest extends FunSuite {
       .unsafeRunSync()
 
     val expected = RepoConfig(
-      pullRequests = PullRequestsConfig(
-        frequency = Some(PullRequestFrequency.Timespan(7.days)),
-        grouping = List(
-          PullRequestGroup(
-            name = "patches",
-            title = "Patch updates".some,
-            filter = NonEmptyList.of(
-              PullRequestUpdateFilter(None, None, SemVer.Change.Patch.some)
-                .getOrElse(fail("Should not be called"))
-            )
-          ),
-          PullRequestGroup(
-            name = "minor_major",
-            title = "Minor/major updates".some,
-            filter = NonEmptyList.of(
-              PullRequestUpdateFilter(None, None, SemVer.Change.Minor.some)
-                .getOrElse(fail("Should not be called")),
-              PullRequestUpdateFilter(None, None, SemVer.Change.Major.some)
-                .getOrElse(fail("Should not be called"))
-            )
-          ),
-          PullRequestGroup(
-            name = "typelevel",
-            title = "Typelevel updates".some,
-            filter = NonEmptyList.of(
-              PullRequestUpdateFilter("org.typelevel".some).getOrElse(fail("Should not be called")),
-              PullRequestUpdateFilter("org.http4s".some).getOrElse(fail("Should not be called"))
-            )
-          ),
-          PullRequestGroup(
-            name = "my_libraries",
-            filter = NonEmptyList.of(
-              PullRequestUpdateFilter(None, "my-library".some)
-                .getOrElse(fail("Should not be called")),
-              PullRequestUpdateFilter("my-org".some, "my-other-library".some)
-                .getOrElse(fail("Should not be called"))
-            )
-          ),
-          PullRequestGroup(
-            name = "all",
-            filter = NonEmptyList.of(
-              PullRequestUpdateFilter("*".some).getOrElse(fail("Should not be called"))
-            )
-          )
-        )
-      ),
+      pullRequests = PullRequestsConfig(frequency = Some(PullRequestFrequency.Timespan(7.days))),
       updates = UpdatesConfig(
         allow = List(UpdatePattern("eu.timepit".g, None, None)),
         pin = List(
@@ -282,24 +229,6 @@ class RepoConfigAlgTest extends FunSuite {
     assertEquals(config, expected)
   }
 
-  test("configToIgnoreFurtherUpdates with grouped update") {
-    val update1 = ("a".g % "b".a % "1" %> "2").single
-    val update2 = ("c".g % "d".a % "1" %> "2").single
-    val update = GroupedUpdate("my-group", None, List(update1, update2))
-    val config = RepoConfigAlg
-      .parseRepoConfig(RepoConfigAlg.configToIgnoreFurtherUpdates(update))
-      .getOrElse(RepoConfig())
-    val expected = RepoConfig(updates =
-      UpdatesConfig(ignore =
-        List(
-          UpdatePattern(groupId = "a".g, artifactId = "b".some, None),
-          UpdatePattern(groupId = "c".g, artifactId = "d".some, None)
-        )
-      )
-    )
-    assertEquals(config, expected)
-  }
-
   test("config with postUpdateHook without group and artifact id") {
     val content =
       """|postUpdateHooks = [{
diff --git a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/ValidateRepoConfigAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/repoconfig/ValidateRepoConfigAlgTest.scala
deleted file mode 100644
index b01a6a2e..00000000
--- a/modules/core/src/test/scala/org/scalasteward/core/repoconfig/ValidateRepoConfigAlgTest.scala
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.scalasteward.core.repoconfig
-
-import better.files.File
-import cats.effect.ExitCode
-import cats.effect.unsafe.implicits.global
-import org.scalasteward.core.mock.{MockContext, MockState}
-
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-
-class ValidateRepoConfigAlgTest extends munit.FunSuite {
-
-  def configFile(content: String) = FunFixture[(File, ExitCode)](
-    setup = { _ =>
-      val tmpFile =
-        File(
-          Files.write(
-            Files.createTempFile(".scala-steward", ".conf"),
-            content.getBytes(StandardCharsets.UTF_8)
-          )
-        )
-
-      val obtained = MockContext
-        .validateRepoConfigContext(tmpFile)
-        .runF
-        .runA(MockState.empty)
-        .unsafeRunSync()
-
-      (tmpFile, obtained)
-    },
-    teardown = { case (file, _) =>
-      file.delete()
-    }
-  )
-
-  configFile(
-    """|updates.pin = [
-       |  { groupId = "org.scala-lang", artifactId="scala3-library", version = "3.1." },
-       |  { groupId = "org.scala-lang", artifactId="scala3-library_sjs1", version = "3.1." },
-       |  { groupId = "org.scala-js", artifactId="sbt-scalajs", version = "1.10." }
-       |]""".stripMargin
-  )
-    .test("accepts valid config") { case (_, obtained) =>
-      assertEquals(obtained, ExitCode.Success)
-    }
-
-  configFile(
-    """|updates.pin  =? [
-       |  { groupId = "org.scala-lang", artifactId="scala3-library", version = "3.1." },
-       |  { groupId = "org.scala-lang", artifactId="scala3-library_sjs1", version = "3.1." },
-       |  { groupId = "org.scala-js", artifactId="sbt-scalajs", version = "1.10." },
-       |]""".stripMargin
-  )
-    .test("rejects config with a parsing failure") { case (_, obtained) =>
-      assertEquals(obtained, ExitCode.Error)
-    }
-
-  configFile(
-    """|updatePullRequests = 123
-       |
-       |""".stripMargin
-  )
-    .test("rejects config with a decoding failure") { case (_, obtained) =>
-      assertEquals(obtained, ExitCode.Error)
-    }
-
-  test("rejects non-existent config file") {
-    val nonExistentFile = File("/", "scripts", "script")
-    val obtained = MockContext
-      .validateRepoConfigContext(nonExistentFile)
-      .runF
-      .runA(MockState.empty)
-      .unsafeRunSync()
-
-    assertEquals(obtained, ExitCode.Error)
-  }
-}
diff --git a/modules/core/src/test/scala/org/scalasteward/core/util/loggerTest.scala b/modules/core/src/test/scala/org/scalasteward/core/util/loggerTest.scala
index cb14cf86..682f9114 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/util/loggerTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/util/loggerTest.scala
@@ -5,9 +5,6 @@ import org.scalasteward.core.mock.MockContext.context._
 import org.scalasteward.core.mock.MockState.TraceEntry.Log
 import org.scalasteward.core.mock.{MockEff, MockState}
 import org.scalasteward.core.util.logger.LoggerOps
-import org.scalasteward.core.util.logger.showUpdates
-import org.scalasteward.core.TestSyntax._
-import org.scalasteward.core.data.GroupedUpdate
 
 class loggerTest extends CatsEffectSuite {
   test("attemptError.label_") {
@@ -29,37 +26,4 @@ class loggerTest extends CatsEffectSuite {
       assertEquals(state.trace.size, 1)
     }
   }
-
-  test("showUpdates") {
-    val a = ("a".g % "a".a % "1.0.0" %> "1.0.1").single
-    val b = ("a".g % "b".a % "1.0.0" %> "1.1.0").single
-    val c = ("a".g % "c".a % "1.0.0" %> "2.0.0").single
-
-    val list = List(
-      GroupedUpdate("all", None, List(a, b, c)),
-      GroupedUpdate("some", None, List(a, b)),
-      a,
-      b,
-      c
-    )
-
-    val result = showUpdates(list)
-
-    val expected = """Found 5 updates:
-                     |  all (group) {
-                     |    a:a : 1.0.0 -> 1.0.1
-                     |    a:b : 1.0.0 -> 1.1.0
-                     |    a:c : 1.0.0 -> 2.0.0
-                     |  }
-                     |  some (group) {
-                     |    a:a : 1.0.0 -> 1.0.1
-                     |    a:b : 1.0.0 -> 1.1.0
-                     |  }
-                     |  a:a : 1.0.0 -> 1.0.1
-                     |  a:b : 1.0.0 -> 1.1.0
-                     |  a:c : 1.0.0 -> 2.0.0""".stripMargin
-
-    assertEquals(result, expected)
-  }
-
 }
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 92059aef..8e59bc72 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
@@ -36,22 +36,14 @@ class VCSExtraAlgTest extends FunSuite {
 
     assertEquals(
       vcsExtraAlg
-        .getReleaseRelatedUrls(
-          repoUrl = uri"https://github.com/foo/foo",
-          currentVersion = updateFoo.currentVersion,
-          nextVersion = updateFoo.nextVersion
-        )
+        .getReleaseRelatedUrls(uri"https://github.com/foo/foo", updateFoo)
         .unsafeRunSync(),
       List.empty
     )
 
     assertEquals(
       vcsExtraAlg
-        .getReleaseRelatedUrls(
-          repoUrl = uri"https://github.com/foo/bar",
-          currentVersion = updateBar.currentVersion,
-          nextVersion = updateBar.nextVersion
-        )
+        .getReleaseRelatedUrls(uri"https://github.com/foo/bar", updateBar)
         .unsafeRunSync(),
       List(
         ReleaseRelatedUrl.VersionDiff(uri"https://github.com/foo/bar/compare/v0.1.0...v0.2.0")
@@ -60,11 +52,7 @@ class VCSExtraAlgTest extends FunSuite {
 
     assertEquals(
       vcsExtraAlg
-        .getReleaseRelatedUrls(
-          repoUrl = uri"https://github.com/foo/buz",
-          currentVersion = updateBuz.currentVersion,
-          nextVersion = updateBuz.nextVersion
-        )
+        .getReleaseRelatedUrls(uri"https://github.com/foo/buz", updateBuz)
         .unsafeRunSync(),
       List.empty
     )
@@ -82,22 +70,14 @@ class VCSExtraAlgTest extends FunSuite {
 
     assertEquals(
       githubOnPremVcsExtraAlg
-        .getReleaseRelatedUrls(
-          repoUrl = uri"https://github.on-prem.com/foo/foo",
-          currentVersion = updateFoo.currentVersion,
-          nextVersion = updateFoo.nextVersion
-        )
+        .getReleaseRelatedUrls(uri"https://github.on-prem.com/foo/foo", updateFoo)
         .unsafeRunSync(),
       List.empty
     )
 
     assertEquals(
       githubOnPremVcsExtraAlg
-        .getReleaseRelatedUrls(
-          repoUrl = uri"https://github.on-prem.com/foo/bar",
-          currentVersion = updateBar.currentVersion,
-          nextVersion = updateBar.nextVersion
-        )
+        .getReleaseRelatedUrls(uri"https://github.on-prem.com/foo/bar", updateBar)
         .unsafeRunSync(),
       List(
         ReleaseRelatedUrl.VersionDiff(
@@ -108,11 +88,7 @@ class VCSExtraAlgTest extends FunSuite {
 
     assertEquals(
       githubOnPremVcsExtraAlg
-        .getReleaseRelatedUrls(
-          repoUrl = uri"https://github.on-prem.com/foo/buz",
-          currentVersion = updateFoo.currentVersion,
-          nextVersion = updateFoo.nextVersion
-        )
+        .getReleaseRelatedUrls(uri"https://github.on-prem.com/foo/buz", updateFoo)
         .unsafeRunSync(),
       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 43763a20..242fa309 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
@@ -5,259 +5,213 @@ import org.http4s.syntax.literals._
 import org.scalasteward.core.TestSyntax._
 import org.scalasteward.core.git
 import org.scalasteward.core.vcs.VCSType.{GitHub, GitLab}
-import org.scalasteward.core.data.GroupedUpdate
 import org.scalasteward.core.vcs.data.Repo
 
 class VCSPackageTest extends FunSuite {
   private val repo = Repo("foo", "bar")
+  private val update = ("ch.qos.logback".g % "logback-classic".a % "1.2.0" %> "1.2.3").single
+  private val updateBranch = git.branchFor(update, None)
 
-  // Single updates
-
-  {
-
-    val update = ("ch.qos.logback".g % "logback-classic".a % "1.2.0" %> "1.2.3").single
-    val updateBranch = git.branchFor(update, None)
-
-    test("listingBranch (single)") {
-      assertEquals(listingBranch(GitHub, repo, updateBranch), s"foo/bar:${updateBranch.name}")
-      assertEquals(listingBranch(GitLab, repo, updateBranch), updateBranch.name)
-    }
-
-    test("createBranch (single)") {
-      assertEquals(createBranch(GitHub, repo, updateBranch), s"foo:${updateBranch.name}")
-      assertEquals(createBranch(GitLab, repo, updateBranch), updateBranch.name)
-    }
-
-    test("possibleCompareUrls") {
-      val onPremVCS = "https://github.onprem.io/"
-      val onPremVCSUri = uri"https://github.onprem.io/"
-
-      assertEquals(
-        possibleCompareUrls(
-          GitHub,
-          onPremVCSUri,
-          uri"https://github.com/foo/bar",
-          update.currentVersion,
-          update.nextVersion
-        ).map(_.url.renderString),
-        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)
-      assertEquals(
-        possibleCompareUrls(
-          GitHub,
-          onPremVCSUri,
-          uri"https://github.com/foo/bar/",
-          update.currentVersion,
-          update.nextVersion
-        ).map(_.url.renderString),
-        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"
-        )
-      )
-
-      assertEquals(
-        possibleCompareUrls(
-          GitHub,
-          onPremVCSUri,
-          uri"https://gitlab.com/foo/bar",
-          update.currentVersion,
-          update.nextVersion
-        ).map(_.url.renderString),
-        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("listingBranch") {
+    assertEquals(listingBranch(GitHub, repo, updateBranch), s"foo/bar:${updateBranch.name}")
+    assertEquals(listingBranch(GitLab, repo, updateBranch), updateBranch.name)
+  }
 
-      assertEquals(
-        possibleCompareUrls(
-          GitHub,
-          onPremVCSUri,
-          uri"https://bitbucket.org/foo/bar",
-          update.currentVersion,
-          update.nextVersion
-        ).map(_.url.renderString),
-        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("createBranch") {
+    assertEquals(createBranch(GitHub, repo, updateBranch), s"foo:${updateBranch.name}")
+    assertEquals(createBranch(GitLab, repo, updateBranch), updateBranch.name)
+  }
 
-      assertEquals(
-        possibleCompareUrls(
-          GitHub,
-          onPremVCSUri,
-          uri"https://scalacenter.github.io/scalafix/",
-          update.currentVersion,
-          update.nextVersion
-        ),
-        List.empty
-      )
+  test("possibleCompareUrls") {
+    val onPremVCS = "https://github.onprem.io/"
+    val onPremVCSUri = uri"https://github.onprem.io/"
 
-      assertEquals(
-        possibleCompareUrls(
-          GitHub,
-          onPremVCSUri,
-          onPremVCSUri.addPath("foo/bar"),
-          update.currentVersion,
-          update.nextVersion
-        ).map(_.url.renderString),
-        List(
-          s"${onPremVCS}foo/bar/compare/v1.2.0...v1.2.3",
-          s"${onPremVCS}foo/bar/compare/1.2.0...1.2.3",
-          s"${onPremVCS}foo/bar/compare/release-1.2.0...release-1.2.3"
-        )
+    assertEquals(
+      possibleCompareUrls(
+        GitHub,
+        onPremVCSUri,
+        uri"https://github.com/foo/bar",
+        update
+      ).map(_.url.renderString),
+      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"
       )
-    }
+    )
 
-    test("possibleChangelogUrls: github.com") {
-      val obtained = possibleReleaseRelatedUrls(
+    // should canonicalize (drop last slash)
+    assertEquals(
+      possibleCompareUrls(
         GitHub,
-        uri"https://github.com",
-        uri"https://github.com/foo/bar",
-        update.currentVersion,
-        update.nextVersion
-      ).map(_.url.renderString)
-      val expected = 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",
-        "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/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/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/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",
+        onPremVCSUri,
+        uri"https://github.com/foo/bar/",
+        update
+      ).map(_.url.renderString),
+      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"
       )
-      assertEquals(obtained, expected)
-    }
+    )
 
-    test("possibleChangelogUrls: gitlab.com") {
-      val obtained = possibleReleaseRelatedUrls(
+    assertEquals(
+      possibleCompareUrls(
         GitHub,
-        uri"https://github.com",
+        onPremVCSUri,
         uri"https://gitlab.com/foo/bar",
-        update.currentVersion,
-        update.nextVersion
-      ).map(_.url.renderString)
-      val expected =
-        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"
-          )
-      assertEquals(obtained, expected)
-    }
-
-    test("possibleChangelogUrls: on-prem gitlab") {
-      val obtained = possibleReleaseRelatedUrls(
-        GitLab,
-        uri"https://gitlab.on-prem.net",
-        uri"https://gitlab.on-prem.net/foo/bar",
-        update.currentVersion,
-        update.nextVersion
-      ).map(_.url.renderString)
-      val expected = possibleReleaseNotesFilenames.map(name =>
-        s"https://gitlab.on-prem.net/foo/bar/blob/master/$name"
-      ) ++ possibleChangelogFilenames.map(name =>
-        s"https://gitlab.on-prem.net/foo/bar/blob/master/$name"
-      ) ++ List(
-        "https://gitlab.on-prem.net/foo/bar/compare/v1.2.0...v1.2.3",
-        "https://gitlab.on-prem.net/foo/bar/compare/1.2.0...1.2.3",
-        "https://gitlab.on-prem.net/foo/bar/compare/release-1.2.0...release-1.2.3"
+        update
+      ).map(_.url.renderString),
+      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"
       )
-      assertEquals(obtained, expected)
-    }
+    )
 
-    test("possibleChangelogUrls: bitbucket.org") {
-      val obtained = possibleReleaseRelatedUrls(
+    assertEquals(
+      possibleCompareUrls(
         GitHub,
-        uri"https://github.com",
+        onPremVCSUri,
         uri"https://bitbucket.org/foo/bar",
-        update.currentVersion,
-        update.nextVersion
-      ).map(_.url.renderString)
-      val expected =
-        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"
-          )
-      assertEquals(obtained, expected)
-    }
+        update
+      ).map(_.url.renderString),
+      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") {
-      val obtained = possibleReleaseRelatedUrls(
+    assertEquals(
+      possibleCompareUrls(
         GitHub,
-        uri"https://github.com",
+        onPremVCSUri,
         uri"https://scalacenter.github.io/scalafix/",
-        update.currentVersion,
-        update.nextVersion
-      ).map(_.url.renderString)
-      assertEquals(obtained, List.empty)
-    }
+        update
+      ),
+      List.empty
+    )
 
+    assertEquals(
+      possibleCompareUrls(
+        GitHub,
+        onPremVCSUri,
+        onPremVCSUri.addPath("foo/bar"),
+        update
+      ).map(_.url.renderString),
+      List(
+        s"${onPremVCS}foo/bar/compare/v1.2.0...v1.2.3",
+        s"${onPremVCS}foo/bar/compare/1.2.0...1.2.3",
+        s"${onPremVCS}foo/bar/compare/release-1.2.0...release-1.2.3"
+      )
+    )
   }
 
-  // Grouped updates
-
-  {
-
-    val update = GroupedUpdate(
-      name = "my-group",
-      title = None,
-      updates = List(("ch.qos.logback".g % "logback-classic".a % "1.2.0" %> "1.2.3").single)
+  test("possibleChangelogUrls: github.com") {
+    val obtained = possibleReleaseRelatedUrls(
+      GitHub,
+      uri"https://github.com",
+      uri"https://github.com/foo/bar",
+      update
+    ).map(_.url.renderString)
+    val expected = 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",
+      "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/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/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/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"
     )
+    assertEquals(obtained, expected)
+  }
 
-    val updateBranch = git.branchFor(update, None)
-
-    test("listingBranch (grouped)") {
-      assertEquals(listingBranch(GitHub, repo, updateBranch), s"foo/bar:update/my-group")
-      assertEquals(listingBranch(GitLab, repo, updateBranch), updateBranch.name)
-    }
+  test("possibleChangelogUrls: gitlab.com") {
+    val obtained = possibleReleaseRelatedUrls(
+      GitHub,
+      uri"https://github.com",
+      uri"https://gitlab.com/foo/bar",
+      update
+    ).map(_.url.renderString)
+    val expected =
+      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"
+        )
+    assertEquals(obtained, expected)
+  }
 
-    test("createBranch (grouped)") {
-      assertEquals(createBranch(GitHub, repo, updateBranch), s"foo:update/my-group")
-      assertEquals(createBranch(GitLab, repo, updateBranch), updateBranch.name)
-    }
+  test("possibleChangelogUrls: on-prem gitlab") {
+    val obtained = possibleReleaseRelatedUrls(
+      GitLab,
+      uri"https://gitlab.on-prem.net",
+      uri"https://gitlab.on-prem.net/foo/bar",
+      update
+    ).map(_.url.renderString)
+    val expected = possibleReleaseNotesFilenames.map(name =>
+      s"https://gitlab.on-prem.net/foo/bar/blob/master/$name"
+    ) ++ possibleChangelogFilenames.map(name =>
+      s"https://gitlab.on-prem.net/foo/bar/blob/master/$name"
+    ) ++ List(
+      "https://gitlab.on-prem.net/foo/bar/compare/v1.2.0...v1.2.3",
+      "https://gitlab.on-prem.net/foo/bar/compare/1.2.0...1.2.3",
+      "https://gitlab.on-prem.net/foo/bar/compare/release-1.2.0...release-1.2.3"
+    )
+    assertEquals(obtained, expected)
+  }
 
+  test("possibleChangelogUrls: bitbucket.org") {
+    val obtained = possibleReleaseRelatedUrls(
+      GitHub,
+      uri"https://github.com",
+      uri"https://bitbucket.org/foo/bar",
+      update
+    ).map(_.url.renderString)
+    val expected =
+      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"
+        )
+    assertEquals(obtained, expected)
   }
 
+  test("possibleChangelogUrls: homepage") {
+    val obtained = possibleReleaseRelatedUrls(
+      GitHub,
+      uri"https://github.com",
+      uri"https://scalacenter.github.io/scalafix/",
+      update
+    ).map(_.url.renderString)
+    assertEquals(obtained, List.empty)
+  }
 }
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 e652f489..8783e266 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
@@ -1,13 +1,12 @@
 package org.scalasteward.core.vcs.data
 
-import cats.syntax.all._
 import io.circe.syntax._
 import munit.FunSuite
 import org.http4s.syntax.literals._
 import org.scalasteward.core.TestInstances._
 import org.scalasteward.core.TestSyntax._
 import org.scalasteward.core.buildtool.sbt.data.SbtVersion
-import org.scalasteward.core.data.{GroupedUpdate, ReleaseRelatedUrl, RepoData, UpdateData, Version}
+import org.scalasteward.core.data.{ReleaseRelatedUrl, RepoData, UpdateData, Version}
 import org.scalasteward.core.edit.EditAttempt.{ScalafixEdit, UpdateEdit}
 import org.scalasteward.core.edit.scalafix.ScalafixMigration
 import org.scalasteward.core.git.{Branch, Commit}
@@ -243,19 +242,19 @@ class NewPullRequestDataTest extends FunSuite {
   test("updateType") {
     val dependency = "com.example".g % "foo".a % "0.1"
     val single = (dependency %> "0.2").single
-    assertEquals(updateType(single), List("library-update"))
+    assertEquals(updateType(single), "library-update")
 
     val group = ("com.example".g % Nel.of("foo".a, "bar".a) % "0.1" %> "0.2").group
-    assertEquals(updateType(group), List("library-update"))
+    assertEquals(updateType(group), "library-update")
 
     val testUpdate = (dependency % "test" %> "0.2").single
-    assertEquals(updateType(testUpdate), List("test-library-update"))
+    assertEquals(updateType(testUpdate), "test-library-update")
 
     val sbtPluginUpdate = (dependency.copy(sbtVersion = Some(SbtVersion("1.0"))) %> "0.2").single
-    assertEquals(updateType(sbtPluginUpdate), List("sbt-plugin-update"))
+    assertEquals(updateType(sbtPluginUpdate), "sbt-plugin-update")
 
     val scalafixRuleUpdate = (dependency % "scalafix-rule" %> "0.2").single
-    assertEquals(updateType(scalafixRuleUpdate), List("scalafix-rule-update"))
+    assertEquals(updateType(scalafixRuleUpdate), "scalafix-rule-update")
   }
 
   test("oldVersionNote without files") {
@@ -319,161 +318,4 @@ class NewPullRequestDataTest extends FunSuite {
     val second = labelsFor(update, List(updateEdit), List.empty, Some("(.*update.*)|(.*count.*)".r))
     assertEquals(clue(second), List("library-update", "commit-count:1"))
   }
-
-  test("label for grouped updates add labels for all update types & version changes") {
-    val update1 = ("a".g % "b".a % "1" -> "2").single
-    val update2 = ("c".g % "d".a % "1.1.0" % "test" %> "1.2.0").single
-    val update = GroupedUpdate("my-group", None, List(update1, update2))
-
-    val labels = labelsFor(update, Nil, Nil, None)
-
-    val expected = List(
-      "library-update",
-      "test-library-update",
-      "early-semver-minor",
-      "semver-spec-minor",
-      "commit-count:0"
-    )
-
-    assertEquals(labels, expected)
-  }
-
-  test("oldVersionNote doesn't show version for grouped updates") {
-    val files = List("Readme.md", "travis.yml")
-    val update1 = ("a".g % "b".a % "1" -> "2").single
-    val update2 = ("c".g % "d".a % "1.1.0" % "test" %> "1.2.0").single
-    val update = GroupedUpdate("my-group", None, List(update1, update2))
-
-    val note = oldVersionNote(files, update)
-
-    assertEquals(
-      note.fold("")(_.toHtml),
-      """<details>
-        |<summary>Files still referring to the old version numbers</summary>
-        |
-        |The following files still refer to the old version numbers.
-        |You might want to review and update them manually.
-        |```
-        |Readme.md
-        |travis.yml
-        |```
-        |</details>
-      """.stripMargin.trim
-    )
-  }
-
-  test("adjustFutureUpdates for grouped udpates shows settings for each update") {
-    val update1 = ("a".g % "b".a % "1" -> "2").single
-    val update2 = ("c".g % "d".a % "1.1.0" % "test" %> "1.2.0").single
-    val update = GroupedUpdate("my-group", None, List(update1, update2))
-
-    val note = adjustFutureUpdates(update)
-
-    assertEquals(
-      note.toHtml,
-      """<details>
-        |<summary>Adjust future updates</summary>
-        |
-        |Add these to your `.scala-steward.conf` file to ignore future updates of these dependencies:
-        |```
-        |updates.ignore = [
-        |  { groupId = "a", artifactId = "b" },
-        |  { groupId = "c", artifactId = "d" }
-        |]
-        |```
-        |Or, add these to slow down future updates of these dependencies:
-        |```
-        |dependencyOverrides = [
-        |  {
-        |    pullRequests = { frequency = "@monthly" },
-        |    dependency = { groupId = "a", artifactId = "b" }
-        |  },
-        |  {
-        |    pullRequests = { frequency = "@monthly" },
-        |    dependency = { groupId = "c", artifactId = "d" }
-        |  }
-        |]
-        |```
-        |</details>
-      """.stripMargin.trim
-    )
-  }
-
-  test("NewPullRequestData.from works for `GroupedUpdate`") {
-    val update1 = ("ch.qos.logback".g % "logback-classic".a % "1.2.0" %> "1.2.3").single
-    val update2 = ("com.example".g % "foo".a % "1.0.0" %> "2.0.0").single
-    val update = GroupedUpdate("my-group", "The PR title".some, List(update1, update2))
-    val data = UpdateData(
-      RepoData(Repo("foo", "bar"), dummyRepoCache, RepoConfig.empty),
-      Repo("scala-steward", "bar"),
-      update,
-      Branch("master"),
-      dummySha1,
-      Branch("update/logback-classic-1.2.3")
-    )
-
-    val obtained = from(data, "scala-steward:update/logback-classic-1.2.3").asJson.spaces2
-
-    val body =
-      raw"""Updates:
-           |
-           |* ch.qos.logback:logback-classic from 1.2.0 to 1.2.3
-           |* com.example:foo from 1.0.0 to 2.0.0
-           |
-           |
-           |I'll automatically update this PR to resolve conflicts as long as you don't change it yourself.
-           |
-           |If you have any feedback, just mention me in the comments below.
-           |
-           |Configure Scala Steward for your repository with a [`.scala-steward.conf`](https://github.com/scala-steward-org/scala-steward/blob/${org.scalasteward.core.BuildInfo.gitHeadCommit}/docs/repo-specific-configuration.md) file.
-           |
-           |Have a fantastic day writing Scala!
-           |
-           |<details>
-           |<summary>Adjust future updates</summary>
-           |
-           |Add these to your `.scala-steward.conf` file to ignore future updates of these dependencies:
-           |```
-           |updates.ignore = [
-           |  { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" },
-           |  { groupId = \"com.example\", artifactId = \"foo\" }
-           |]
-           |```
-           |Or, add these to slow down future updates of these dependencies:
-           |```
-           |dependencyOverrides = [
-           |  {
-           |    pullRequests = { frequency = \"@monthly\" },
-           |    dependency = { groupId = \"ch.qos.logback\", artifactId = \"logback-classic\" }
-           |  },
-           |  {
-           |    pullRequests = { frequency = \"@monthly\" },
-           |    dependency = { groupId = \"com.example\", artifactId = \"foo\" }
-           |  }
-           |]
-           |```
-           |</details>
-           |
-           |labels: library-update, early-semver-patch, semver-spec-patch, early-semver-major, semver-spec-major, commit-count:0""".stripMargin
-
-    val expected =
-      raw"""|{
-            |  "title" : "The PR title",
-            |  "body" : "${body.replace("\n", "\\n")}",
-            |  "head" : "scala-steward:update/logback-classic-1.2.3",
-            |  "base" : "master",
-            |  "labels" : [
-            |    "library-update",
-            |    "early-semver-patch",
-            |    "semver-spec-patch",
-            |    "early-semver-major",
-            |    "semver-spec-major",
-            |    "commit-count:0"
-            |  ],
-            |  "draft" : false
-            |}""".stripMargin
-
-    assertNoDiff(obtained, expected)
-  }
-
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlgTest.scala
index c57ec357..90a75080 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/vcs/gitlab/GitLabApiAlgTest.scala
@@ -29,8 +29,6 @@ class GitLabApiAlgTest extends FunSuite {
   object MergeWhenPipelineSucceedsMatcher
       extends QueryParamDecoderMatcher[Boolean]("merge_when_pipeline_succeeds")
 
-  object RequiredReviewersMatcher extends QueryParamDecoderMatcher[Int]("approvals_required")
-
   val routes: HttpRoutes[IO] =
     HttpRoutes.of[IO] {
       case GET -> Root / "projects" / "foo/bar" =>
@@ -68,14 +66,6 @@ class GitLabApiAlgTest extends FunSuite {
           )
         )
 
-      case PUT -> Root / "projects" / "foo/bar" / "merge_requests" / "150" / "approvals"
-          :? RequiredReviewersMatcher(requiredApprovers) =>
-        Ok(
-          putMrApprovals.deepMerge(
-            json""" { "iid": 150, "approvals_required": $requiredApprovers, "web_url": "https://gitlab.com/foo/bar/merge_requests/150" } """
-          )
-        )
-
       case POST -> Root / "projects" / "foo/bar" / "merge_requests" / "150" / "notes" =>
         Ok(json"""  {
             "body": "Superseded by #1234"
@@ -90,11 +80,7 @@ class GitLabApiAlgTest extends FunSuite {
   implicit val httpJsonClient: HttpJsonClient[IO] = new HttpJsonClient[IO]
   implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
   val gitlabApiAlg =
-    new GitLabApiAlg[IO](
-      config.vcsCfg,
-      GitLabCfg(mergeWhenPipelineSucceeds = false, requiredReviewers = None),
-      _ => IO.pure
-    )
+    new GitLabApiAlg[IO](config.vcsCfg, GitLabCfg(mergeWhenPipelineSucceeds = false), _ => IO.pure)
 
   private val data = UpdateData(
     RepoData(Repo("foo", "bar"), dummyRepoCache, RepoConfig.empty),
@@ -123,7 +109,7 @@ class GitLabApiAlgTest extends FunSuite {
   test("createPullRequest -- no fork") {
     val gitlabApiAlgNoFork = new GitLabApiAlg[IO](
       config.vcsCfg.copy(doNotFork = true),
-      GitLabCfg(mergeWhenPipelineSucceeds = false, requiredReviewers = None),
+      GitLabCfg(mergeWhenPipelineSucceeds = false),
       _ => IO.pure
     )
     val prOut = gitlabApiAlgNoFork
@@ -158,7 +144,7 @@ class GitLabApiAlgTest extends FunSuite {
   test("createPullRequest -- auto merge") {
     val gitlabApiAlgNoFork = new GitLabApiAlg[IO](
       config.vcsCfg.copy(doNotFork = true),
-      GitLabCfg(mergeWhenPipelineSucceeds = true, requiredReviewers = None),
+      GitLabCfg(mergeWhenPipelineSucceeds = true),
       _ => IO.pure
     )
 
@@ -190,7 +176,7 @@ class GitLabApiAlgTest extends FunSuite {
 
     val gitlabApiAlgNoFork = new GitLabApiAlg[IO](
       config.vcsCfg.copy(doNotFork = true),
-      GitLabCfg(mergeWhenPipelineSucceeds = true, requiredReviewers = None),
+      GitLabCfg(mergeWhenPipelineSucceeds = true),
       _ => IO.pure
     )(errorJsonClient, implicitly, implicitly)
 
@@ -208,59 +194,6 @@ class GitLabApiAlgTest extends FunSuite {
     assertEquals(prOut, expected)
   }
 
-  test("createPullRequest -- reduce required reviewers") {
-    val gitlabApiAlgLessReviewersRequired = new GitLabApiAlg[IO](
-      config.vcsCfg.copy(doNotFork = true),
-      GitLabCfg(mergeWhenPipelineSucceeds = true, requiredReviewers = Some(0)),
-      _ => IO.pure
-    )
-
-    val prOut = gitlabApiAlgLessReviewersRequired
-      .createPullRequest(Repo("foo", "bar"), newPRData)
-      .unsafeRunSync()
-
-    val expected = PullRequestOut(
-      uri"https://gitlab.com/foo/bar/merge_requests/150",
-      PullRequestState.Open,
-      PullRequestNumber(150),
-      "title"
-    )
-
-    assertEquals(prOut, expected)
-  }
-
-  test("createPullRequest -- no fail upon required reviewers error") {
-    val errorClient: Client[IO] =
-      Client.fromHttpApp(
-        (HttpRoutes.of[IO] {
-          case PUT -> Root / "projects" / "foo/bar" / "merge_requests" / "150" / "approvals"
-              :? RequiredReviewersMatcher(requiredReviewers) =>
-            BadRequest(s"Cannot set requiredReviewers to $requiredReviewers")
-        } <+> routes).orNotFound
-      )
-    val errorJsonClient: HttpJsonClient[IO] =
-      new HttpJsonClient[IO]()(errorClient, Concurrent[IO])
-
-    val gitlabApiAlgLessReviewersRequiredNoError = new GitLabApiAlg[IO](
-      config.vcsCfg.copy(doNotFork = true),
-      GitLabCfg(mergeWhenPipelineSucceeds = true, requiredReviewers = Some(0)),
-      _ => IO.pure
-    )(errorJsonClient, implicitly, implicitly)
-
-    val prOut = gitlabApiAlgLessReviewersRequiredNoError
-      .createPullRequest(Repo("foo", "bar"), newPRData)
-      .unsafeRunSync()
-
-    val expected = PullRequestOut(
-      uri"https://gitlab.com/foo/bar/merge_requests/150",
-      PullRequestState.Open,
-      PullRequestNumber(150),
-      "title"
-    )
-
-    assertEquals(prOut, expected)
-  }
-
   test("commentPullRequest") {
     val reference = gitlabApiAlg.referencePullRequest(PullRequestNumber(1347))
     assertEquals(reference, "!1347")
@@ -432,33 +365,4 @@ class GitLabApiAlgTest extends FunSuite {
         "packages_enabled": true
       }
     """
-
-  val putMrApprovals =
-    json"""
-      {
-        "id": 138691106,
-        "iid": 4,
-        "project_id": 466,
-        "title": "Standard ScalaSteward configured MR title",
-        "description": "Standard ScalaSteward configured MR description",
-        "state": "opened",
-        "created_at": "2022-02-04T17:43:57.363Z",
-        "updated_at": "2022-02-11T22:38:28.645Z",
-        "merge_status": "can_be_merged",
-        "approved": true,
-        "approvals_required": 0,
-        "approvals_left": 0,
-        "require_password_to_approve": false,
-        "approved_by": [],
-        "suggested_approvers": [],
-        "approvers": [],
-        "approver_groups": [],
-        "user_has_approved": false,
-        "user_can_approve": true,
-        "approval_rules_left": [],
-        "has_approval_rules": true,
-        "merge_request_approvers_available": true,
-        "multiple_approval_rules_available": true
-     }
-     """
 }