Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Only create distinct changesets #2019

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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],
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]] =
Expand Down Expand Up @@ -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))
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions modules/core/src/main/scala/org/scalasteward/core/io/FileAlg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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] = {
Expand Down Expand Up @@ -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
)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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],
Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Loading