Skip to content

Commit 6991270

Browse files
authored
git pre push hook, task compatible with gradle configuration cache + parallel execution handling (#2570)
2 parents a8b5587 + 368f4be commit 6991270

File tree

9 files changed

+172
-48
lines changed

9 files changed

+172
-48
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1616
* Adds support for worktrees (fixes [#1765](https://github.com/diffplug/spotless/issues/1765))
1717
* Bump default `google-java-format` version to latest `1.24.0` -> `1.28.0`. ([#2345](https://github.com/diffplug/spotless/pull/2345))
1818
* Bump default `ktlint` version to latest `1.5.0` -> `1.7.1`. ([#2555](https://github.com/diffplug/spotless/pull/2555))
19+
* `GitPrePushHookInstaller` uses a lock to run gracefully if it is called many times in parallel. ([#2570](https://github.com/diffplug/spotless/pull/2570))
1920
* Bump default `jackson` version to latest `2.19.2` -> `2.20.0`. ([#2606](https://github.com/diffplug/spotless/pull/2606))
2021
* Bump default `gson` version to latest `2.13.1` -> `2.13.2`. ([#2615](https://github.com/diffplug/spotless/pull/2615))
2122
* **BREAKING** Bump default `ktfmt` version to latest `0.53` -> `0.58` ([#2613](https://github.com/diffplug/spotless/pull/2613))

lib/src/main/java/com/diffplug/spotless/GitPrePushHookInstaller.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.nio.file.Files;
2525
import java.util.Locale;
2626

27+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28+
2729
/**
2830
* Abstract class responsible for installing a Git pre-push hook in a repository.
2931
* This class ensures that specific checks and logic are run before a push operation in Git.
@@ -35,6 +37,10 @@ public abstract class GitPrePushHookInstaller {
3537
private static final String HOOK_HEADER = "##### SPOTLESS HOOK START #####";
3638
private static final String HOOK_FOOTER = "##### SPOTLESS HOOK END #####";
3739

40+
private static final Object LOCK = new Object();
41+
42+
private static volatile boolean installing = false;
43+
3844
/**
3945
* Logger for recording informational and error messages during the installation process.
4046
*/
@@ -56,6 +62,44 @@ public GitPrePushHookInstaller(GitPreHookLogger logger, File root) {
5662
this.root = requireNonNull(root, "root file can not be null");
5763
}
5864

65+
/**
66+
* Installs the Git pre-push hook while ensuring thread safety and preventing parallel installations.
67+
*
68+
* The method:
69+
* 1. Uses a thread-safe mechanism to prevent concurrent installations
70+
* 2. If a parallel installation is detected, logs a warning and skips the installation
71+
* 3. Uses a synchronized block with a static lock object to ensure thread safety
72+
*
73+
* The installation process sets a flag during installation and resets it afterwards,
74+
* using the {@link #doInstall()} method to perform the actual installation.
75+
*
76+
* @throws Exception if any error occurs during installation
77+
*/
78+
@SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", justification = "This is a safe usage of a static lock object")
79+
public void install() throws Exception {
80+
if (installing) {
81+
logger.warn("Parallel Spotless Git pre-push hook installation detected, skipping installation");
82+
return;
83+
}
84+
85+
// Since hook installation is a manual task triggered by the user,
86+
// using synchronized locking is acceptable. It ensures thread safety
87+
// without affecting overall performance, and provides a simple and reliable solution.
88+
synchronized (LOCK) {
89+
if (installing) {
90+
logger.warn("Parallel Spotless Git pre-push hook installation detected, skipping installation");
91+
return;
92+
}
93+
94+
try {
95+
installing = true;
96+
doInstall();
97+
} finally {
98+
installing = false;
99+
}
100+
}
101+
}
102+
59103
/**
60104
* Installs the Git pre-push hook into the repository.
61105
*
@@ -70,7 +114,7 @@ public GitPrePushHookInstaller(GitPreHookLogger logger, File root) {
70114
*
71115
* @throws Exception if any error occurs during installation.
72116
*/
73-
public void install() throws Exception {
117+
private void doInstall() throws Exception {
74118
logger.info("Installing git pre-push hook");
75119

76120
if (!isGitInstalled()) {

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
55
## [Unreleased]
66
### Changed
77
* **BREAKING** Bump the required Gradle to `7.3` and required Java to `17`. ([#2375](https://github.com/diffplug/spotless/issues/2375), [#2540](https://github.com/diffplug/spotless/pull/2540))
8+
* **BREAKING** `spotlessInstallGitPrePushHook` task is now installed only on the root project. ([#2570](https://github.com/diffplug/spotless/pull/2570))
89
* Bump JGit from `6.10.1` to `7.3.0` ([#2257](https://github.com/diffplug/spotless/pull/2257))
910
* Adds support for worktrees (fixes [#1765](https://github.com/diffplug/spotless/issues/1765))
1011
* Bump default `google-java-format` version to latest `1.24.0` -> `1.28.0`. ([#2345](https://github.com/diffplug/spotless/pull/2345))
@@ -16,6 +17,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1617

1718
### Fixed
1819
* Respect system gitconfig when performing git operations ([#2404](https://github.com/diffplug/spotless/issues/2404))
20+
* `spotlessInstallGitPrePushHook` is now compatible with configuration cache. ([#2570](https://github.com/diffplug/spotless/pull/2570))
1921

2022
## [7.2.1] - 2025-07-21
2123
### Fixed

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public SpotlessExtensionImpl(Project project) {
4141
rootInstallPreHook = project.getTasks().register(EXTENSION + INSTALL_GIT_PRE_PUSH_HOOK, SpotlessInstallPrePushHookTask.class, task -> {
4242
task.setGroup(BUILD_SETUP_TASK_GROUP);
4343
task.setDescription(INSTALL_GIT_PRE_PUSH_HOOK_DESCRIPTION);
44+
task.getRootDir().set(project.getRootDir());
45+
task.getIsRootExecution().set(project.equals(project.getRootProject()));
4446
});
4547

4648
project.afterEvaluate(unused -> {

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessInstallPrePushHookTask.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
*/
1616
package com.diffplug.gradle.spotless;
1717

18+
import java.io.File;
19+
1820
import org.gradle.api.DefaultTask;
21+
import org.gradle.api.provider.Property;
22+
import org.gradle.api.tasks.Internal;
1923
import org.gradle.api.tasks.TaskAction;
2024
import org.gradle.work.DisableCachingByDefault;
2125

@@ -30,7 +34,16 @@
3034
* <p>The task leverages {@link GitPrePushHookInstallerGradle} to implement the installation process.
3135
*/
3236
@DisableCachingByDefault(because = "not worth caching")
33-
public class SpotlessInstallPrePushHookTask extends DefaultTask {
37+
public abstract class SpotlessInstallPrePushHookTask extends DefaultTask {
38+
39+
@Internal
40+
abstract Property<File> getRootDir();
41+
42+
/**
43+
* Determines whether this task is being executed from the root project.
44+
*/
45+
@Internal
46+
abstract Property<Boolean> getIsRootExecution();
3447

3548
/**
3649
* Executes the task to install the Git pre-push hook.
@@ -43,6 +56,12 @@ public class SpotlessInstallPrePushHookTask extends DefaultTask {
4356
*/
4457
@TaskAction
4558
public void performAction() throws Exception {
59+
// if is not root project, skip it
60+
if (!getIsRootExecution().get()) {
61+
getLogger().debug("Skipping Spotless pre-push hook installation because it is not being executed from the root project.");
62+
return;
63+
}
64+
4665
final var logger = new GitPreHookLogger() {
4766
@Override
4867
public void info(String format, Object... arguments) {
@@ -60,7 +79,7 @@ public void error(String format, Object... arguments) {
6079
}
6180
};
6281

63-
final var installer = new GitPrePushHookInstallerGradle(logger, getProject().getRootDir());
82+
final var installer = new GitPrePushHookInstallerGradle(logger, getRootDir().get());
6483
installer.install();
6584
}
6685
}

plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessInstallPrePushHookTaskTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public void should_create_pre_hook_file_when_hook_file_does_not_exists() throws
3535

3636
// when
3737
var output = gradleRunner()
38-
.withArguments("spotlessInstallGitPrePushHook")
38+
.withArguments("spotlessInstallGitPrePushHook", "--system-prop=org.gradle.configuration-cache=true")
3939
.build()
4040
.getOutput();
4141

@@ -65,7 +65,7 @@ public void should_append_to_existing_pre_hook_file_when_hook_file_exists() thro
6565

6666
// when
6767
final var output = gradleRunner()
68-
.withArguments("spotlessInstallGitPrePushHook")
68+
.withArguments("spotlessInstallGitPrePushHook", "--system-prop=org.gradle.configuration-cache=true")
6969
.build()
7070
.getOutput();
7171

plugin-maven/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
55
## [Unreleased]
66
### Changes
77
* **BREAKING** Bump the required Java to `17`. ([#2375](https://github.com/diffplug/spotless/issues/2375), [#2540](https://github.com/diffplug/spotless/pull/2540))
8+
* **BREAKING** `spotless:install-git-pre-push-hook` task is now always installed in the root `.git/hooks` directory by resolving the top-level project base directory. ([#2570](https://github.com/diffplug/spotless/pull/2570))
89
* Bump JGit from `6.10.1` to `7.3.0` ([#2257](https://github.com/diffplug/spotless/pull/2257))
910
* Adds support for worktrees (fixes [#1765](https://github.com/diffplug/spotless/issues/1765))
1011
* Bump default `google-java-format` version to latest `1.24.0` -> `1.28.0`. ([#2345](https://github.com/diffplug/spotless/pull/2345))

plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessInstallPrePushHookMojo.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@
1515
*/
1616
package com.diffplug.spotless.maven;
1717

18-
import java.io.File;
19-
2018
import org.apache.maven.plugin.AbstractMojo;
2119
import org.apache.maven.plugin.MojoExecutionException;
2220
import org.apache.maven.plugin.MojoFailureException;
2321
import org.apache.maven.plugins.annotations.Mojo;
2422
import org.apache.maven.plugins.annotations.Parameter;
23+
import org.apache.maven.project.MavenProject;
2524

2625
import com.diffplug.spotless.GitPrePushHookInstaller.GitPreHookLogger;
2726
import com.diffplug.spotless.GitPrePushHookInstallerMaven;
@@ -37,12 +36,8 @@
3736
@Mojo(name = AbstractSpotlessMojo.GOAL_PRE_PUSH_HOOK, threadSafe = true)
3837
public class SpotlessInstallPrePushHookMojo extends AbstractMojo {
3938

40-
/**
41-
* The base directory of the Maven project where the Git pre-push hook will be installed.
42-
* This parameter is automatically set to the root directory of the current project.
43-
*/
44-
@Parameter(defaultValue = "${project.basedir}", readonly = true, required = true)
45-
private File baseDir;
39+
@Parameter(defaultValue = "${project}", readonly = true, required = true)
40+
private MavenProject project;
4641

4742
/**
4843
* Executes the Mojo, installing the Git pre-push hook for the Spotless plugin.
@@ -56,6 +51,12 @@ public class SpotlessInstallPrePushHookMojo extends AbstractMojo {
5651
*/
5752
@Override
5853
public void execute() throws MojoExecutionException, MojoFailureException {
54+
// if is not root project, skip it
55+
if (!project.isExecutionRoot()) {
56+
getLog().debug("Skipping Spotless pre-push hook installation for non-root project: " + project.getName());
57+
return;
58+
}
59+
5960
final var logger = new GitPreHookLogger() {
6061
@Override
6162
public void info(String format, Object... arguments) {
@@ -74,7 +75,7 @@ public void error(String format, Object... arguments) {
7475
};
7576

7677
try {
77-
final var installer = new GitPrePushHookInstallerMaven(logger, baseDir);
78+
final var installer = new GitPrePushHookInstallerMaven(logger, project.getBasedir());
7879
installer.install();
7980
} catch (Exception e) {
8081
throw new MojoExecutionException("Unable to install pre-push hook", e);

0 commit comments

Comments
 (0)