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 8d4499451c..e01d27f89b 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 @@ -32,7 +32,7 @@ import org.scalasteward.core.edit.hooks.HookExecutor import org.scalasteward.core.edit.scalafix.{ScalafixMigrationsFinder, ScalafixMigrationsLoader} import org.scalasteward.core.git.{GenGitAlg, GitAlg} import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg} -import org.scalasteward.core.nurture.{NurtureAlg, PullRequestData, PullRequestRepository} +import org.scalasteward.core.nurture.{ApplyAlg, NurtureAlg, PullRequestData, PullRequestRepository} import org.scalasteward.core.persistence.{CachingKeyValueStore, JsonKeyValueStore} import org.scalasteward.core.repocache._ import org.scalasteward.core.repoconfig.RepoConfigAlg @@ -47,6 +47,7 @@ import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger final class Context[F[_]](implicit + val applyAlg: ApplyAlg[F], val buildToolDispatcher: BuildToolDispatcher[F], val coursierAlg: CoursierAlg[F], val dateTimeAlg: DateTimeAlg[F], @@ -146,6 +147,7 @@ object Context { implicit val refreshErrorAlg: RefreshErrorAlg[F] = new RefreshErrorAlg[F](refreshErrorStore) implicit val repoCacheAlg: RepoCacheAlg[F] = new RepoCacheAlg[F](config) implicit val editAlg: EditAlg[F] = new EditAlg[F] + implicit val applyAlg: ApplyAlg[F] = new ApplyAlg[F] implicit val nurtureAlg: NurtureAlg[F] = new NurtureAlg[F](config) implicit val pruningAlg: PruningAlg[F] = new PruningAlg[F] implicit val gitHubAppApiAlg: GitHubAppApiAlg[F] = new GitHubAppApiAlg[F](config.vcsApiHost) diff --git a/modules/core/src/main/scala/org/scalasteward/core/git/FileGitAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/git/FileGitAlg.scala index d6e696a5ad..b870b6d0dd 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/git/FileGitAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/git/FileGitAlg.scala @@ -138,6 +138,9 @@ final class FileGitAlg[F[_]](config: GitCfg)(implicit override def version: F[String] = workspaceAlg.rootDir.flatMap(git("--version")).map(_.mkString.trim) + override def diff(repo: File, branch: Branch): F[List[String]] = + git("diff", branch.name)(repo) + private def git(args: String*)(repo: File): F[List[String]] = processAlg.exec(Nel.of("git", args: _*), repo, "GIT_ASKPASS" -> config.gitAskPass.pathAsString) diff --git a/modules/core/src/main/scala/org/scalasteward/core/git/GenGitAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/git/GenGitAlg.scala index bba23a1db3..595681b4d9 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/git/GenGitAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/git/GenGitAlg.scala @@ -69,6 +69,8 @@ trait GenGitAlg[F[_], Repo] { def version: F[String] + def diff(repo: Repo, branch: Branch): F[List[String]] + final def commitAllIfDirty(repo: Repo, message: String, messages: String*)(implicit F: Monad[F] ): F[Option[Commit]] = @@ -142,6 +144,9 @@ trait GenGitAlg[F[_], Repo] { override def version: F[String] = self.version + + override def diff(repo: A, branch: Branch): F[List[String]] = + f(repo).flatMap(self.diff(_, branch)) } } } diff --git a/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala index 5f90e89493..5141cd1803 100644 --- a/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala +++ b/modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala @@ -26,6 +26,7 @@ import org.http4s.Uri import org.http4s.implicits.http4sLiteralsSyntax import org.typelevel.log4cats.Logger import scala.io.Source +import java.nio.file.attribute.PosixFilePermission trait FileAlg[F[_]] { def deleteForce(file: File): F[Unit] @@ -50,6 +51,8 @@ trait FileAlg[F[_]] { def writeFile(file: File, content: String): F[Unit] + def ensureExecutable(file: File): F[Unit] + final def createTemporarily[A, E](file: File, content: String)( fa: F[A] )(implicit F: Bracket[F, E]): F[A] = { @@ -147,5 +150,16 @@ object FileAlg { logger.debug(s"Write $file") >> file.parentOption.fold(F.unit)(ensureExists(_).void) >> F.delay(file.write(content)).void + + override def ensureExecutable(file: File): F[Unit] = + F.delay( + file.setPermissions( + file.permissions ++ Set( + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_EXECUTE + ) + ) + ) } } diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/ApplyAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/ApplyAlg.scala new file mode 100644 index 0000000000..260849ff21 --- /dev/null +++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/ApplyAlg.scala @@ -0,0 +1,73 @@ +/* + * Copyright 2018-2021 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.nurture + +import cats.effect.BracketThrow +import cats.implicits._ +import org.typelevel.log4cats.Logger +import org.scalasteward.core.data.ProcessResult.Ignored +import org.scalasteward.core.data._ +import org.scalasteward.core.edit.EditAlg +import org.scalasteward.core.git.{Branch, Commit, GitAlg} + +final class ApplyAlg[F[_]](implicit + editAlg: EditAlg[F], + gitAlg: GitAlg[F], + logger: Logger[F], + F: BracketThrow[F] +) { + def applyNewUpdate( + data: UpdateData, + seenBranches: List[Branch], + pushCommits: (UpdateData, List[Commit]) => F[ProcessResult], + createPullRequest: UpdateData => F[ProcessResult] + ): F[ProcessResult] = + gitAlg.returnToCurrentBranch(data.repo) { + val createBranch = logger.info(s"Create branch ${data.updateBranch.name}") >> + gitAlg.createBranch(data.repo, data.updateBranch) + editAlg.applyUpdate(data.repoData, data.update, createBranch).flatMap { editCommits => + if (editCommits.isEmpty) logger.warn("No commits created").as(Ignored) + else + seenBranches + .forallM(gitAlg.diff(data.repo, _).map(_.nonEmpty)) + .ifM( + pushCommits(data, editCommits) >> createPullRequest(data), + logger.warn("Discovered a duplicate branch, not pushing").as[ProcessResult](Ignored) + ) + } + } + + def mergeAndApplyAgain( + data: UpdateData, + seenBranches: List[Branch], + pushCommits: (UpdateData, List[Commit]) => F[ProcessResult] + ): F[ProcessResult] = + for { + _ <- logger.info( + s"Merge branch ${data.baseBranch.name} into ${data.updateBranch.name} and apply again" + ) + maybeMergeCommit <- gitAlg.mergeTheirs(data.repo, data.baseBranch) + editCommits <- editAlg.applyUpdate(data.repoData, data.update) + result <- + seenBranches + .forallM(gitAlg.diff(data.repo, _).map(_.nonEmpty)) + .ifM( + pushCommits(data, maybeMergeCommit.toList ++ editCommits), + logger.warn("Discovered a duplicate branch, not pushing").as[ProcessResult](Ignored) + ) + } yield result +} 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 1ef56fe630..9f8069eabf 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 @@ -17,7 +17,8 @@ package org.scalasteward.core.nurture import cats.Applicative -import cats.effect.BracketThrow +import cats.effect.{BracketThrow, Sync} +import cats.effect.concurrent.Ref import cats.implicits._ import eu.timepit.refined.types.numeric.NonNegInt import fs2.Stream @@ -27,7 +28,6 @@ import org.scalasteward.core.application.Config import org.scalasteward.core.coursier.CoursierAlg import org.scalasteward.core.data.ProcessResult.{Created, Ignored, Updated} import org.scalasteward.core.data._ -import org.scalasteward.core.edit.EditAlg import org.scalasteward.core.git.{Branch, Commit, GitAlg} import org.scalasteward.core.repoconfig.PullRequestUpdateStrategy import org.scalasteward.core.edit.scalafix.ScalafixMigrationsFinder @@ -39,7 +39,6 @@ import org.scalasteward.core.{git, util, vcs} final class NurtureAlg[F[_]](config: Config)(implicit coursierAlg: CoursierAlg[F], - editAlg: EditAlg[F], gitAlg: GitAlg[F], logger: Logger[F], pullRequestRepository: PullRequestRepository[F], @@ -49,7 +48,9 @@ final class NurtureAlg[F[_]](config: Config)(implicit vcsRepoAlg: VCSRepoAlg[F], streamCompiler: Stream.Compiler[F, F], urlChecker: UrlChecker[F], - F: BracketThrow[F] + applyAlg: ApplyAlg[F], + F: BracketThrow[F], + FS: Sync[F] ) { def nurture(data: RepoData, fork: RepoOut, updates: List[Update.Single]): F[Unit] = for { @@ -75,22 +76,33 @@ final class NurtureAlg[F[_]](config: Config)(implicit grouped = Update.groupByGroupId(updates) _ <- logger.info(util.logger.showUpdates(grouped)) baseSha1 <- gitAlg.latestSha1(data.repo, baseBranch) + seenBranches <- Ref[F].of(List.empty[Branch]) _ <- NurtureAlg.processUpdates( grouped, update => { val updateData = UpdateData(data, fork, update, baseBranch, baseSha1, git.branchFor(update)) - processUpdate(updateData).flatMap { - case result @ Created(newPrNumber) => - closeObsoletePullRequests(updateData, newPrNumber).as[ProcessResult](result) - case result @ _ => F.pure(result) - } + seenBranches + .getAndUpdate( + identity + ) // Suppress Codacity's faulty `.get` detection, https://twitter.com/blast_hardchese/status/1373376444827508737 + .flatMap(processUpdate(updateData, _)) + .flatMap { + case result @ Created(newPrNumber) => + (for { + _ <- closeObsoletePullRequests(updateData, newPrNumber) + _ <- seenBranches.update(updateData.updateBranch :: _) + } yield ()).as[ProcessResult](result) + case result @ Updated => + seenBranches.update(updateData.updateBranch :: _).as[ProcessResult](result) + case result @ Ignored => F.pure(result) + } }, data.config.updates.limit ) } yield () - def processUpdate(data: UpdateData): F[ProcessResult] = + def processUpdate(data: UpdateData, seenBranches: List[Branch]): F[ProcessResult] = for { _ <- logger.info(s"Process update ${data.update.show}") head = vcs.listingBranch(config.vcsType, data.fork, data.update) @@ -100,9 +112,9 @@ final class NurtureAlg[F[_]](config: Config)(implicit logger.info(s"PR ${pr.html_url} is closed") >> removeRemoteBranch(data.repo, data.updateBranch).as(Ignored) case Some(pr) => - logger.info(s"Found PR ${pr.html_url}") >> updatePullRequest(data) + logger.info(s"Found PR ${pr.html_url}") >> updatePullRequest(data, seenBranches) case None => - applyNewUpdate(data) + applyAlg.applyNewUpdate(data, seenBranches, pushCommits, createPullRequest) } _ <- pullRequests.headOption.traverse_ { pr => pullRequestRepository.createOrUpdate( @@ -146,16 +158,6 @@ final class NurtureAlg[F[_]](config: Config)(implicit gitAlg.removeBranch(repo, branch) } - def applyNewUpdate(data: UpdateData): F[ProcessResult] = - gitAlg.returnToCurrentBranch(data.repo) { - val createBranch = logger.info(s"Create branch ${data.updateBranch.name}") >> - gitAlg.createBranch(data.repo, data.updateBranch) - editAlg.applyUpdate(data.repoData, data.update, createBranch).flatMap { editCommits => - if (editCommits.isEmpty) logger.warn("No commits created").as(Ignored) - else pushCommits(data, editCommits) >> createPullRequest(data) - } - } - def pushCommits(data: UpdateData, commits: List[Commit]): F[ProcessResult] = if (commits.isEmpty) F.pure[ProcessResult](Ignored) else @@ -201,13 +203,15 @@ final class NurtureAlg[F[_]](config: Config)(implicit _ <- logger.info(s"Created PR ${pr.html_url}") } yield Created(pr.number) - def updatePullRequest(data: UpdateData): F[ProcessResult] = + def updatePullRequest(data: UpdateData, seenBranches: List[Branch]): F[ProcessResult] = if (data.repoConfig.updatePullRequestsOrDefault =!= PullRequestUpdateStrategy.Never) gitAlg.returnToCurrentBranch(data.repo) { for { _ <- gitAlg.checkoutBranch(data.repo, data.updateBranch) update <- shouldBeUpdated(data) - result <- if (update) mergeAndApplyAgain(data) else F.pure[ProcessResult](Ignored) + result <- + if (update) applyAlg.mergeAndApplyAgain(data, seenBranches, pushCommits) + else F.pure[ProcessResult](Ignored) } yield result } else @@ -233,15 +237,6 @@ final class NurtureAlg[F[_]](config: Config)(implicit result.flatMap { case (update, msg) => logger.info(msg).as(update) } } - def mergeAndApplyAgain(data: UpdateData): F[ProcessResult] = - for { - _ <- logger.info( - s"Merge branch ${data.baseBranch.name} into ${data.updateBranch.name} and apply again" - ) - maybeMergeCommit <- gitAlg.mergeTheirs(data.repo, data.baseBranch) - editCommits <- editAlg.applyUpdate(data.repoData, data.update) - result <- pushCommits(data, maybeMergeCommit.toList ++ editCommits) - } yield result } object NurtureAlg { 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 0a5bff0207..a15ef6fd4d 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 @@ -143,6 +143,23 @@ class FileGitAlgTest extends FunSuite { test("version") { assert(ioGitAlg.version.unsafeRunSync().nonEmpty) } + + test("diff") { + val repo = rootDir / "diff" + val wip = Branch("wip") + val p = for { + _ <- supplement.createRepo(repo) + _ <- ioFileAlg.writeFile(repo / "test.txt", "hello") + _ <- supplement.git("add", "test.txt")(repo) + _ <- ioGitAlg.commitAll(repo, "Add test.txt") + // work on wip + _ <- ioGitAlg.createBranch(repo, wip) + c1 <- ioGitAlg.diff(repo, master) + _ <- ioFileAlg.writeFile(repo / "test.txt", "hello world") + c2 <- ioGitAlg.diff(repo, master) + } yield (c1.isEmpty, c2.isEmpty) + assertEquals(p.unsafeRunSync(), (true, false)) + } } object FileGitAlgTest { diff --git a/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala b/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala index 7f7ba79c51..56e42f441f 100644 --- a/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala +++ b/modules/core/src/test/scala/org/scalasteward/core/io/MockFileAlg.scala @@ -66,4 +66,9 @@ class MockFileAlg extends FileAlg[MockEff] { StateT.modifyF[IO, MockState]( _.exec(List("write", file.pathAsString)).addFiles(file -> content) ) + + override def ensureExecutable(file: File): MockEff[Unit] = + StateT.modify[IO, MockState]( + _.exec(List("chmod", "u+x,g+x,o+x", file.pathAsString)) + ) >> StateT.liftF(ioFileAlg.ensureExecutable(file)) } diff --git a/modules/core/src/test/scala/org/scalasteward/core/nurture/ApplyAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/nurture/ApplyAlgTest.scala new file mode 100644 index 0000000000..8a33507807 --- /dev/null +++ b/modules/core/src/test/scala/org/scalasteward/core/nurture/ApplyAlgTest.scala @@ -0,0 +1,193 @@ +package org.scalasteward.core.nurture + +import munit.ScalaCheckSuite +import org.scalasteward.core.TestInstances._ +import org.scalasteward.core.data.{ProcessResult, RepoData, Update, UpdateData} + +import better.files.File +import cats.Applicative +import cats.effect._ +import cats.effect.concurrent.Ref +import org.http4s.HttpApp +import org.http4s.client.Client +import org.scalasteward.core.TestSyntax._ +import org.scalasteward.core.application.{Config, Context} +import org.scalasteward.core.git.FileGitAlgTest.{master, Supplement} +import org.scalasteward.core.git.{Branch, Commit, FileGitAlg, GenGitAlg, Sha1} +import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg} +import org.scalasteward.core.mock.MockContext +import org.scalasteward.core.mock.MockContext.{config, mockRoot} +import org.scalasteward.core.repocache._ +import org.scalasteward.core.repoconfig.RepoConfig +import org.scalasteward.core.util.Nel +import org.scalasteward.core.vcs.data.Repo + +class ApplyAlgTest extends ScalaCheckSuite { + def step0(implicit + CE: ConcurrentEffect[IO] + ): Resource[IO, (ProcessAlg[IO], FileAlg[IO], WorkspaceAlg[IO], Context[IO])] = for { + blocker <- Blocker[IO] + config = Config.from(MockContext.args) + implicit0(client: Client[IO]) = Client.fromHttpApp[IO](HttpApp.notFound) + implicit0(fileAlg: FileAlg[IO]) = FileAlg.create[IO] + _ <- Resource.eval(fileAlg.ensureExists(config.gitCfg.gitAskPass.parent)) + _ <- Resource.eval( + fileAlg.writeFile( + config.gitCfg.gitAskPass, + """ echo bogus-password """ + ) + ) + _ <- Resource.eval(fileAlg.ensureExecutable(config.gitCfg.gitAskPass)) + implicit0(processAlg: ProcessAlg[IO]) = ProcessAlg.create[IO](blocker, config.processCfg) + implicit0(workspaceAlg: WorkspaceAlg[IO]) = WorkspaceAlg.create[IO](config) + context <- Resource.eval(Context.step1[IO](config)) + } yield (processAlg, fileAlg, workspaceAlg, context) + + def setupRepo(repo: Repo, identicalBranch: (Branch, Update.Single)): IO[Unit] = + step0.use { + case ( + implicit0(processAlg: ProcessAlg[IO]), + implicit0(fileAlg: FileAlg[IO]), + implicit0(workspaceAlg: WorkspaceAlg[IO]), + context + ) => + val (branch, update) = identicalBranch + implicit val ioGitAlg: GenGitAlg[IO, File] = + new FileGitAlg[IO](config.gitCfg).contramapRepoF(Applicative[IO].pure) + val supplement = new Supplement[IO] + val repoDir = mockRoot / "workspace" / "repos" / repo.owner / repo.repo + for { + _ <- supplement.createRepo(repoDir) + _ <- fileAlg.writeFile( + repoDir / "build.sbt", + """libraryDependency += "foo" % "bar" % "1.2.3" """ + ) + _ <- supplement.git("add", "build.sbt")(repoDir) + _ <- context.gitAlg.commitAll(repo, "Initial commit") + // Create another simulated curated update branch with + _ <- context.gitAlg.createBranch(repo, branch) + _ <- fileAlg.writeFile( + repoDir / "build.sbt", + s"""libraryDependency += "foo" % "bar" % "${update.newerVersions.head}" """ + ) + _ <- supplement.git("add", "build.sbt")(repoDir) + _ <- context.gitAlg.commitAll(repo, "Update bar to 1.2.4") + _ <- context.gitAlg.checkoutBranch(repo, master) + } yield () + } + + test("Ensure unique patchesets are pushed") { + val firstBranch = Branch("update/foo-1.2.4") + val duplicateBranch = Branch("update/foo-duplicate-1.2.4") + val update = Update.Single("foo" % "bar" % "1.2.3", Nel.one("1.2.4")) + val firstChangeset = (firstBranch, update) + val res = ({ + def pushCommits( + seenBranchesRef: Ref[IO, List[Branch]] + ): (UpdateData, List[Commit]) => IO[ProcessResult] = { (data, _) => + for { + _ <- seenBranchesRef.update(data.updateBranch :: _) + } yield ProcessResult.Updated + } + + val createPullRequest: UpdateData => IO[ProcessResult] = _ => IO.pure(ProcessResult.Updated) + + val repo = Repo("myorg", "myrepo") + val fork = Repo("myfork", "myrepo") + step0.use { + case ( + implicit0(processAlg: ProcessAlg[IO]), + implicit0(fileAlg: FileAlg[IO]), + implicit0(workspaceAlg: WorkspaceAlg[IO]), + context + ) => + for { + _ <- setupRepo(repo, firstChangeset) + seenBranchesRef <- Ref[IO].of(List.empty[Branch]) + sha1 <- IO.fromEither(Sha1.from("adc83b19e793491b1c6ea0fd8b46cd9f32e592fc")) + firstData = UpdateData( + RepoData( + repo, + RepoCache(sha1, List.empty, Option.empty), + RepoConfig() + ), + fork, + update, + master, + sha1, + Branch("bump") + ) + secondData = firstData.copy( + updateBranch = duplicateBranch, + update = update + ) + seenBranches <- seenBranchesRef.getAndUpdate(identity _) + res1 <- context.applyAlg.applyNewUpdate( + firstData, + seenBranches, + pushCommits(seenBranchesRef), + createPullRequest + ) + seenBranches <- seenBranchesRef.getAndUpdate(identity _) + res2 <- context.applyAlg.applyNewUpdate( + secondData, + seenBranches, + pushCommits(seenBranchesRef), + createPullRequest + ) + } yield (res1, res2) + } + }).unsafeRunSync() + + assertEquals(res, (ProcessResult.Updated, ProcessResult.Ignored)) + } + + test("Ensure non-unique patchesets are not pushed") { + val branch = Branch("update/foo-1.2.4") + val update = Update.Single("foo" % "bar" % "1.2.3", Nel.one("1.2.4")) + val identicalBranch = (branch, update) + val res = ({ + val pushCommits: (UpdateData, List[Commit]) => IO[ProcessResult] = + (_, _) => IO.pure(ProcessResult.Updated) + + val createPullRequest: UpdateData => IO[ProcessResult] = _ => IO.pure(ProcessResult.Updated) + + val repo = Repo("myorg", "myrepo") + val fork = Repo("myfork", "myrepo") + step0.use { + case ( + implicit0(processAlg: ProcessAlg[IO]), + implicit0(fileAlg: FileAlg[IO]), + implicit0(workspaceAlg: WorkspaceAlg[IO]), + context + ) => + for { + _ <- setupRepo(repo, identicalBranch) + seenBranchesRef <- Ref[IO].of(List(branch)) + sha1 <- IO.fromEither(Sha1.from("adc83b19e793491b1c6ea0fd8b46cd9f32e592fc")) + data = UpdateData( + RepoData( + repo, + RepoCache(sha1, List.empty, Option.empty), + RepoConfig() + ), + fork, + update, + master, + sha1, + Branch("bump") + ) + seenBranches <- seenBranchesRef.getAndUpdate(identity _) + res <- context.applyAlg.applyNewUpdate( + data, + seenBranches, + pushCommits, + createPullRequest + ) + } yield res + } + }).unsafeRunSync() + + assertEquals(res, ProcessResult.Ignored) + } +}