diff --git a/CHANGELOG.md b/CHANGELOG.md index f1dcb234..3beb92ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +- fix [#374](https://github.com/JLLeitschuh/ktlint-gradle/issues/374): fix pre-commit hook path handling for projects in subdirectories [#996](https://github.com/JLLeitschuh/ktlint-gradle/pull/996) + ## [14.0.1] - 2025-11-10 - Update build to work with gradle 9.1 and Java 25 [#962](https://github.com/JLLeitschuh/ktlint-gradle/pull/962) diff --git a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/GitHook.kt b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/GitHook.kt index 4b4368b1..d10fb48f 100644 --- a/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/GitHook.kt +++ b/plugin/src/main/kotlin/org/jlleitschuh/gradle/ktlint/GitHook.kt @@ -44,16 +44,18 @@ private fun generateGitCommand( ): String = if (gradleRootDirPrefix.isEmpty()) { "git --no-pager diff --name-status --no-color --cached" } else { - "git --no-pager diff --name-status --no-color --cached -- $gradleRootDirPrefix/" + "git --no-pager diff --name-status --no-color --cached --relative=$gradleRootDirPrefix -- $gradleRootDirPrefix/" } private fun postCheck( - shouldUpdateCommit: Boolean + shouldUpdateCommit: Boolean, + gradleRootDirPrefix: String ): String = if (shouldUpdateCommit) { + val filePrefix = if (gradleRootDirPrefix.isNotEmpty()) "$gradleRootDirPrefix/" else "" """ echo "${'$'}CHANGED_FILES" | while read -r file; do - if [ -f ${'$'}file ]; then - git add ${'$'}file + if [ -f $filePrefix${'$'}file ]; then + git add $filePrefix${'$'}file fi done """ @@ -91,7 +93,7 @@ internal fun generateGitHook( gradle_command_exit_code=${'$'}? echo "Completed ktlint run." - ${postCheck(shouldUpdateCommit)} + ${postCheck(shouldUpdateCommit, gradleRootDirPrefix)} if [ -s ${'$'}diff ]; then git apply --ignore-whitespace ${'$'}diff @@ -188,7 +190,7 @@ open class KtlintInstallGitHookTask @Inject constructor( gitHookFile.createNewFile() gitHookFile.setExecutable(true) } - val gradleRootDirPrefix = File(rootDirectory.get()).relativeTo(repo.workTree).path + val gradleRootDirPrefix = File(rootDirectory.get()).relativeTo(repo.workTree).path.replace(File.separator, "/") if (gitHookFile.length() == 0L) { gitHookFile.writeText( diff --git a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/GitHookTasksTest.kt b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/GitHookTasksTest.kt index dd158274..98c79f85 100644 --- a/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/GitHookTasksTest.kt +++ b/plugin/src/test/kotlin/org/jlleitschuh/gradle/ktlint/GitHookTasksTest.kt @@ -153,6 +153,75 @@ class GitHookTasksTest : AbstractPluginTest() { } } + @DisplayName("Check hook should use --relative flag when Gradle project is in subdirectory") + @CommonTest + fun checkHookUsesRelativeFlagInSubdirectory(gradleVersion: GradleVersion) { + val gradleRoot = projectRoot.resolve("project/submodule/").also { it.mkdirs() } + val gitDir = projectRoot.initGit() + + project(gradleVersion, projectPath = gradleRoot) { + build(":$INSTALL_GIT_HOOK_CHECK_TASK") { + assertThat(task(":$INSTALL_GIT_HOOK_CHECK_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + val hookText = gitDir.preCommitGitHook().readText() + assertThat(hookText).contains("--relative=project/submodule") + assertThat(hookText).contains("-- project/submodule/") + } + } + } + + @DisplayName("Format hook should use --relative flag and correct file paths when Gradle project is in subdirectory") + @CommonTest + fun formatHookUsesRelativeFlagInSubdirectory(gradleVersion: GradleVersion) { + val gradleRoot = projectRoot.resolve("project/submodule/").also { it.mkdirs() } + val gitDir = projectRoot.initGit() + + project(gradleVersion, projectPath = gradleRoot) { + build(":$INSTALL_GIT_HOOK_FORMAT_TASK") { + assertThat(task(":$INSTALL_GIT_HOOK_FORMAT_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + val hookText = gitDir.preCommitGitHook().readText() + // Should use --relative flag for git diff + assertThat(hookText).contains("--relative=project/submodule") + assertThat(hookText).contains("-- project/submodule/") + // Should prefix file paths in git add command + assertThat(hookText).contains("if [ -f project/submodule/\$file ]; then") + assertThat(hookText).contains("git add project/submodule/\$file") + } + } + } + + @DisplayName("Check hook should not use --relative flag when Gradle project is at git root") + @CommonTest + fun checkHookDoesNotUseRelativeFlagAtGitRoot(gradleVersion: GradleVersion) { + project(gradleVersion) { + val gitDir = projectPath.initGit() + + build(":$INSTALL_GIT_HOOK_CHECK_TASK") { + assertThat(task(":$INSTALL_GIT_HOOK_CHECK_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + val hookText = gitDir.preCommitGitHook().readText() + // Should NOT use --relative flag when at git root + assertThat(hookText).doesNotContain("--relative=") + } + } + } + + @DisplayName("Format hook should not use --relative flag when Gradle project is at git root") + @CommonTest + fun formatHookDoesNotUseRelativeFlagAtGitRoot(gradleVersion: GradleVersion) { + project(gradleVersion) { + val gitDir = projectPath.initGit() + + build(":$INSTALL_GIT_HOOK_FORMAT_TASK") { + assertThat(task(":$INSTALL_GIT_HOOK_FORMAT_TASK")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + val hookText = gitDir.preCommitGitHook().readText() + // Should NOT use --relative flag when at git root + assertThat(hookText).doesNotContain("--relative=") + // Should NOT prefix file paths + assertThat(hookText).contains("if [ -f \$file ]; then") + assertThat(hookText).doesNotContain("if [ -f /\$file ]; then") + } + } + } + @DisplayName("Check hook should not include files into git commit") @CommonTest fun checkHookShouldNotIncludeFilesIntoGitCommit(gradleVersion: GradleVersion) {