Skip to content

Commit d6b5b90

Browse files
authored
Merge pull request #2982 from daddykotex/dfrancoeur/2942-gitignore
git check-ignore before adding
2 parents e265087 + 10b400a commit d6b5b90

File tree

9 files changed

+131
-51
lines changed

9 files changed

+131
-51
lines changed

modules/core/src/main/scala/org/scalasteward/core/edit/hooks/HookExecutor.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,21 @@ final class HookExecutor[F[_]](implicit
9595
oldContent <- fileAlg.readFile(file)
9696
newContent = oldContent.fold(newLines)(_ + "\n" + newLines)
9797
_ <- fileAlg.writeFile(file, newContent)
98-
_ <- gitAlg.add(repo, file.pathAsString)
99-
blameIgnoreCommitMsg = CommitMsg(s"Add '${commitMsg.title}' to $gitBlameIgnoreRevsName")
100-
maybeBlameIgnoreCommit <- gitAlg.commitAllIfDirty(repo, blameIgnoreCommitMsg)
98+
pathAsString = file.pathAsString
99+
100+
addAndCommit = gitAlg.add(repo, pathAsString).flatMap { _ =>
101+
val blameIgnoreCommitMsg =
102+
CommitMsg(s"Add '${commitMsg.title}' to $gitBlameIgnoreRevsName")
103+
gitAlg.commitAllIfDirty(repo, blameIgnoreCommitMsg)
104+
}
105+
maybeBlameIgnoreCommit <- gitAlg
106+
.checkIgnore(repo, pathAsString)
107+
.ifM(
108+
logger
109+
.warn(s"Impossible to add '$pathAsString' because it is git ignored.")
110+
.as(Option.empty[Commit]),
111+
addAndCommit
112+
)
101113
} yield maybeBlameIgnoreCommit
102114
} else F.pure(None)
103115
}

modules/core/src/main/scala/org/scalasteward/core/git/FileGitAlg.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import cats.syntax.all._
2222
import org.http4s.Uri
2323
import org.scalasteward.core.application.Config.GitCfg
2424
import org.scalasteward.core.git.FileGitAlg.{dotdot, gitCmd}
25-
import org.scalasteward.core.io.process.SlurpOptions
25+
import org.scalasteward.core.io.process.{ProcessFailedException, SlurpOptions}
2626
import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg}
2727
import org.scalasteward.core.util.Nel
2828

@@ -47,6 +47,11 @@ final class FileGitAlg[F[_]](config: GitCfg)(implicit
4747
override def checkoutBranch(repo: File, branch: Branch): F[Unit] =
4848
git_("checkout", branch.name)(repo).void
4949

50+
override def checkIgnore(repo: File, file: String): F[Boolean] =
51+
git_("check-ignore", file)(repo)
52+
.as(true)
53+
.recover { case ex: ProcessFailedException if ex.exitValue === 1 => false }
54+
5055
override def clone(repo: File, url: Uri): F[Unit] =
5156
for {
5257
rootDir <- workspaceAlg.rootDir

modules/core/src/main/scala/org/scalasteward/core/git/GenGitAlg.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ trait GenGitAlg[F[_], Repo] {
3434

3535
def checkoutBranch(repo: Repo, branch: Branch): F[Unit]
3636

37+
def checkIgnore(repo: Repo, file: String): F[Boolean]
38+
3739
def clone(repo: Repo, url: Uri): F[Unit]
3840

3941
def cloneExists(repo: Repo): F[Boolean]
@@ -105,6 +107,9 @@ trait GenGitAlg[F[_], Repo] {
105107
override def checkoutBranch(repo: A, branch: Branch): F[Unit] =
106108
f(repo).flatMap(self.checkoutBranch(_, branch))
107109

110+
override def checkIgnore(repo: A, file: String): F[Boolean] =
111+
f(repo).flatMap(self.checkIgnore(_, file))
112+
108113
override def clone(repo: A, url: Uri): F[Unit] =
109114
f(repo).flatMap(self.clone(_, url))
110115

modules/core/src/main/scala/org/scalasteward/core/io/process.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ object process {
148148
final class ProcessFailedException(
149149
args: Args,
150150
buffer: ListBuffer[String],
151-
exitValue: Int
151+
val exitValue: Int
152152
) extends IOException(makeMessage(args, buffer)(s"exited with code $exitValue."))
153153

154154
final class ProcessTimedOutException(

modules/core/src/test/scala/org/scalasteward/core/buildtool/mill/MillAlgTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class MillAlgTest extends FunSuite {
2828
"show",
2929
extractDeps
3030
)
31-
val initial = MockState.empty.copy(commandOutputs = Map(millCmd -> List("""{"modules":[]}""")))
31+
val initial =
32+
MockState.empty.copy(commandOutputs = Map(millCmd -> Right(List("""{"modules":[]}"""))))
3233
val state = millAlg.getDependencies(buildRoot).runS(initial).unsafeRunSync()
3334
val expected = initial.copy(
3435
trace = Vector(

modules/core/src/test/scala/org/scalasteward/core/edit/hooks/HookExecutorTest.scala

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.scalasteward.core.edit.hooks
22

3+
import cats.data.NonEmptyList
34
import cats.syntax.all._
45
import munit.CatsEffectSuite
56
import org.scalasteward.core.TestInstances.{dummyRepoCache, dummySha1}
@@ -15,6 +16,9 @@ import org.scalasteward.core.repoconfig.{PostUpdateHookConfig, RepoConfig, Scala
1516
import org.scalasteward.core.scalafmt.ScalafmtAlg.opts
1617
import org.scalasteward.core.scalafmt.{scalafmtArtifactId, scalafmtBinary, scalafmtGroupId}
1718
import org.scalasteward.core.util.Nel
19+
import org.scalasteward.core.io.process.ProcessFailedException
20+
import org.scalasteward.core.io.process
21+
import scala.collection.mutable.ListBuffer
1822

1923
class HookExecutorTest extends CatsEffectSuite {
2024
private val repo = Repo("scala-steward-org", "scala-steward")
@@ -27,23 +31,51 @@ class HookExecutorTest extends CatsEffectSuite {
2731
state.map(assertEquals(_, MockState.empty))
2832
}
2933

30-
test("scalafmt: enabled by config") {
31-
val update = (scalafmtGroupId % scalafmtArtifactId % "2.7.4" %> "2.7.5").single
32-
val initial = MockState.empty.copy(commandOutputs =
33-
Map(
34-
FileGitAlg.gitCmd.toList ++
35-
List("status", "--porcelain", "--untracked-files=no", "--ignore-submodules") ->
36-
List("build.sbt"),
37-
FileGitAlg.gitCmd.toList ++ List("rev-parse", "--verify", "HEAD") ->
38-
List(dummySha1.value.value)
34+
Seq(true, false).foreach { case blameRevIgnored =>
35+
val isIgnored = if (blameRevIgnored) "ignored" else "not ignored"
36+
37+
test(s"scalafmt: enabled by config / $gitBlameIgnoreRevsName $isIgnored") {
38+
val gitBlameIgnoreRevs = repoDir / gitBlameIgnoreRevsName
39+
val update = (scalafmtGroupId % scalafmtArtifactId % "2.7.4" %> "2.7.5").single
40+
val initial = MockState.empty.copy(commandOutputs =
41+
Map(
42+
FileGitAlg.gitCmd.toList ++
43+
List("status", "--porcelain", "--untracked-files=no", "--ignore-submodules") ->
44+
Right(List("build.sbt")),
45+
FileGitAlg.gitCmd.toList ++ List("rev-parse", "--verify", "HEAD") ->
46+
Right(List(dummySha1.value.value)),
47+
FileGitAlg.gitCmd.toList ++ List("check-ignore", gitBlameIgnoreRevs.pathAsString) ->
48+
(if (blameRevIgnored) Right(List.empty) else Left(dummyProcessError))
49+
)
3950
)
40-
)
41-
val gitBlameIgnoreRevs = repoDir / gitBlameIgnoreRevsName
42-
val state = FileAlgTest.ioFileAlg.deleteForce(gitBlameIgnoreRevs) >>
43-
hookExecutor.execPostUpdateHooks(data, update).runS(initial)
51+
val state = FileAlgTest.ioFileAlg.deleteForce(gitBlameIgnoreRevs) >>
52+
hookExecutor.execPostUpdateHooks(data, update).runS(initial)
4453

45-
val expected = initial.copy(
46-
trace = Vector(
54+
val logIfIgnored =
55+
if (blameRevIgnored)
56+
Vector(Log(s"Impossible to add '$gitBlameIgnoreRevs' because it is git ignored."))
57+
else
58+
Vector(
59+
Cmd(gitCmd(repoDir), "add", gitBlameIgnoreRevs.pathAsString),
60+
Cmd(
61+
gitCmd(repoDir),
62+
"status",
63+
"--porcelain",
64+
"--untracked-files=no",
65+
"--ignore-submodules"
66+
),
67+
Cmd(
68+
gitCmd(repoDir),
69+
"commit",
70+
"--all",
71+
"--no-gpg-sign",
72+
"-m",
73+
s"Add 'Reformat with scalafmt 2.7.5' to $gitBlameIgnoreRevsName"
74+
),
75+
Cmd(gitCmd(repoDir), "rev-parse", "--verify", "HEAD")
76+
)
77+
78+
val traces = Vector(
4779
Log(
4880
"Executing post-update hook for org.scalameta:scalafmt-core with command 'scalafmt --non-interactive'"
4981
),
@@ -70,30 +102,19 @@ class HookExecutorTest extends CatsEffectSuite {
70102
Cmd(gitCmd(repoDir), "rev-parse", "--verify", "HEAD"),
71103
Cmd("read", gitBlameIgnoreRevs.pathAsString),
72104
Cmd("write", gitBlameIgnoreRevs.pathAsString),
73-
Cmd(gitCmd(repoDir), "add", gitBlameIgnoreRevs.pathAsString),
74-
Cmd(
75-
gitCmd(repoDir),
76-
"status",
77-
"--porcelain",
78-
"--untracked-files=no",
79-
"--ignore-submodules"
80-
),
81-
Cmd(
82-
gitCmd(repoDir),
83-
"commit",
84-
"--all",
85-
"--no-gpg-sign",
86-
"-m",
87-
s"Add 'Reformat with scalafmt 2.7.5' to $gitBlameIgnoreRevsName"
88-
),
89-
Cmd(gitCmd(repoDir), "rev-parse", "--verify", "HEAD")
90-
),
91-
files = Map(
92-
gitBlameIgnoreRevs -> s"# Scala Steward: Reformat with scalafmt 2.7.5\n${dummySha1.value.value}\n"
105+
Cmd(gitCmd(repoDir), "check-ignore", gitBlameIgnoreRevs.pathAsString)
106+
) ++
107+
logIfIgnored ++ Vector(
108+
)
109+
val expected = initial.copy(
110+
trace = traces,
111+
files = Map(
112+
gitBlameIgnoreRevs -> s"# Scala Steward: Reformat with scalafmt 2.7.5\n${dummySha1.value.value}\n"
113+
)
93114
)
94-
)
95115

96-
state.map(assertEquals(_, expected))
116+
state.map(assertEquals(_, expected))
117+
}
97118
}
98119

99120
test("scalafmt: disabled by config") {
@@ -166,4 +187,7 @@ class HookExecutorTest extends CatsEffectSuite {
166187

167188
state.map(assertEquals(_, expected))
168189
}
190+
191+
private val dummyProcessError =
192+
new ProcessFailedException(process.Args(NonEmptyList.of("cmd")), ListBuffer.empty, 1)
169193
}

modules/core/src/test/scala/org/scalasteward/core/git/FileGitAlgTest.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,32 @@ import org.scalasteward.core.util.Nel
1616
class FileGitAlgTest extends CatsEffectSuite {
1717
private val rootDir = mockRoot / "git-tests"
1818

19+
test("add with .gitignore") {
20+
val repo = rootDir / "branchgitignpreAuthors"
21+
for {
22+
_ <- ioAuxGitAlg.createRepo(repo)
23+
24+
gitIgnoreFile = repo / ".gitignore"
25+
_ <- ioFileAlg.writeFile(gitIgnoreFile, "ignored.txt")
26+
_ <- ioAuxGitAlg.addFiles(repo, gitIgnoreFile)
27+
28+
ignoredFile = repo / "ignored.txt"
29+
_ <- ioFileAlg.writeFile(ignoredFile, "irrelevant")
30+
ignoredFileCheck <- ioGitAlg.checkIgnore(repo, ignoredFile.pathAsString)
31+
32+
notIgnoredFile = repo / "not-ignored.txt"
33+
_ <- ioFileAlg.writeFile(notIgnoredFile, "irrelevant")
34+
notIgnoredFileCheck <- ioGitAlg.checkIgnore(repo, notIgnoredFile.pathAsString)
35+
36+
} yield {
37+
assert(ignoredFileCheck, "The file is in .gitignore, checkIgnore should return true.")
38+
assert(
39+
!notIgnoredFileCheck,
40+
"The file is not in .gitignore, checkIgnore should return false."
41+
)
42+
}
43+
}
44+
1945
test("branchAuthors") {
2046
val repo = rootDir / "branchAuthors"
2147
for {
Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
package org.scalasteward.core.io
22

33
import cats.data.Kleisli
4+
import cats.effect.IO
45
import org.scalasteward.core.application.Config.ProcessCfg
56
import org.scalasteward.core.mock.MockEff
67

78
object MockProcessAlg {
89
def create(config: ProcessCfg): ProcessAlg[MockEff] =
910
new ProcessAlg(config)({ args =>
10-
Kleisli {
11-
_.modify { s =>
12-
val cmd = args.workingDirectory.map(_.toString).toList ++ args.command.toList
13-
val s1 = s.exec(cmd, args.extraEnv: _*)
14-
val a = s.commandOutputs.getOrElse(args.command.toList, List.empty)
15-
(s1, a)
16-
}
11+
Kleisli { x =>
12+
for {
13+
state <- x.get
14+
cmd = args.workingDirectory.map(_.toString).toList ++ args.command.toList
15+
newState = state.exec(cmd, args.extraEnv: _*)
16+
res <- x
17+
.set(newState)
18+
.flatMap { _ =>
19+
state.commandOutputs
20+
.getOrElse(args.command.toList, Right(List.empty))
21+
.fold(err => IO.raiseError(err), a => IO.pure(a))
22+
}
23+
} yield res
1724
}
1825
})
1926
}

modules/core/src/test/scala/org/scalasteward/core/mock/MockState.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.scalasteward.core.mock.MockState.TraceEntry.{Cmd, Log}
1010

1111
final case class MockState(
1212
trace: Vector[TraceEntry],
13-
commandOutputs: Map[List[String], List[String]],
13+
commandOutputs: Map[List[String], Either[Throwable, List[String]]],
1414
files: Map[File, String],
1515
uris: Map[Uri, String],
1616
clientResponses: HttpApp[MockEff]

0 commit comments

Comments
 (0)