Skip to content

Commit 525e70e

Browse files
committed
reinstall support
1 parent a790659 commit 525e70e

File tree

10 files changed

+206
-57
lines changed

10 files changed

+206
-57
lines changed

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

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,74 @@ public void install() throws Exception {
100100
}
101101

102102
if (isGitHookInstalled(gitHookFile)) {
103-
logger.warn("Skipping, git pre-push hook already installed %s", gitHookFile.getAbsolutePath());
104-
return;
103+
logger.info("Git pre-push hook already installed, reinstalling it");
104+
uninstall(gitHookFile);
105105
}
106106

107107
hookContent += preHookContent();
108-
writeFile(gitHookFile, hookContent);
108+
writeFile(gitHookFile, hookContent, true);
109109

110110
logger.info("Git pre-push hook installed successfully to the file %s", gitHookFile.getAbsolutePath());
111111
}
112112

113+
/**
114+
* Uninstalls the Spotless Git pre-push hook from the specified hook file by removing
115+
* the custom hook content between the defined hook markers.
116+
*
117+
* <p>This method:
118+
* <ul>
119+
* <li>Reads the entire content of the pre-push hook file</li>
120+
* <li>Identifies the Spotless hook section using predefined markers</li>
121+
* <li>Removes the Spotless hook content while preserving other hook content</li>
122+
* <li>Writes the modified content back to the hook file</li>
123+
* </ul>
124+
*
125+
* @param gitHookFile The Git pre-push hook file from which to remove the Spotless hook
126+
* @throws Exception if any error occurs during the uninstallation process,
127+
* such as file reading or writing errors
128+
*/
129+
private void uninstall(File gitHookFile) throws Exception {
130+
final var hook = Files.readString(gitHookFile.toPath(), UTF_8);
131+
final var hookStart = hook.indexOf(HOOK_HEADLINE);
132+
final var hookEnd = hook.indexOf(HOOK_FOOTER) + HOOK_FOOTER.length();
133+
134+
final var hookScript = hook.substring(hookStart, hookEnd);
135+
136+
final var uninstalledHook = hook.replace(hookScript, "");
137+
138+
writeFile(gitHookFile, uninstalledHook, false);
139+
}
140+
113141
/**
114142
* Provides the content of the hook that should be inserted into the pre-push script.
115143
*
116144
* @return A string representing the content to include in the pre-push script.
117145
*/
118146
protected abstract String preHookContent();
119147

148+
/**
149+
* Generates a pre-push template script that defines the commands to check and apply changes
150+
* using an executor and Spotless.
151+
*
152+
* @param executor The tool to execute the check and apply commands.
153+
* @param commandCheck The command to check for issues.
154+
* @param commandApply The command to apply corrections.
155+
* @return A string template representing the Spotless Git pre-push hook content.
156+
*/
157+
protected String preHookTemplate(String executor, String commandCheck, String commandApply) {
158+
var spotlessHook = "\n";
159+
spotlessHook += "\n" + HOOK_HEADLINE;
160+
spotlessHook += "\nSPOTLESS_EXECUTOR=" + executor;
161+
spotlessHook += "\nif ! $SPOTLESS_EXECUTOR " + commandCheck + " ; then";
162+
spotlessHook += "\n echo 1>&2 \"spotless found problems, running " + commandApply + "; commit the result and re-push\"";
163+
spotlessHook += "\n $SPOTLESS_EXECUTOR " + commandApply;
164+
spotlessHook += "\n exit 1";
165+
spotlessHook += "\nfi";
166+
spotlessHook += "\n" + HOOK_FOOTER;
167+
spotlessHook += "\n";
168+
return spotlessHook;
169+
}
170+
120171
/**
121172
* Checks if Git is installed by validating the existence of `.git/config` in the repository root.
122173
*
@@ -145,38 +196,17 @@ private boolean isGitHookInstalled(File gitHookFile) throws Exception {
145196
* @param content The content to write into the file.
146197
* @throws IOException if an error occurs while writing to the file.
147198
*/
148-
private void writeFile(File file, String content) throws IOException {
149-
try (final var writer = new FileWriter(file, UTF_8, true)) {
199+
private void writeFile(File file, String content, boolean append) throws IOException {
200+
try (final var writer = new FileWriter(file, UTF_8, append)) {
150201
writer.write(content);
151202
}
152203
}
153204

154-
/**
155-
* Generates a pre-push template script that defines the commands to check and apply changes
156-
* using an executor and Spotless.
157-
*
158-
* @param executor The tool to execute the check and apply commands.
159-
* @param commandCheck The command to check for issues.
160-
* @param commandApply The command to apply corrections.
161-
* @return A string template representing the Spotless Git pre-push hook content.
162-
*/
163-
protected String preHookTemplate(String executor, String commandCheck, String commandApply) {
164-
var spotlessHook = "\n";
165-
spotlessHook += "\n" + HOOK_HEADLINE;
166-
spotlessHook += "\nSPOTLESS_EXECUTOR=" + executor;
167-
spotlessHook += "\nif ! $SPOTLESS_EXECUTOR " + commandCheck + " ; then";
168-
spotlessHook += "\n echo 1>&2 \"spotless found problems, running " + commandApply + "; commit the result and re-push\"";
169-
spotlessHook += "\n $SPOTLESS_EXECUTOR " + commandApply;
170-
spotlessHook += "\n exit 1";
171-
spotlessHook += "\nfi";
172-
spotlessHook += "\n" + HOOK_FOOTER;
173-
spotlessHook += "\n\n";
174-
return spotlessHook;
175-
}
176-
177205
public interface GitPreHookLogger {
178206
void info(String format, Object... arguments);
207+
179208
void warn(String format, Object... arguments);
209+
180210
void error(String format, Object... arguments);
181211
}
182212
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public GitPrePushHookInstallerGradle(GitPreHookLogger logger, File root) {
3333
this.gradlew = root.toPath().resolve("gradlew").toFile();
3434
}
3535

36-
3736
/**
3837
* {@inheritDoc}
3938
*/

plugin-gradle/README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
5353

5454
- [**Quickstart**](#quickstart)
5555
- [Requirements](#requirements)
56-
- [Git hook](#git-hook)
56+
- [Git pre-push hook](#git-pre-push-hook)
5757
- [Linting](#linting)
5858
- **Languages**
5959
- [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat), [IntelliJ IDEA](#intellij-idea))
@@ -142,9 +142,34 @@ Spotless requires JRE 11+ and Gradle 6.1.1 or newer.
142142
- If you're stuck on JRE 8, use [`id 'com.diffplug.spotless' version '6.13.0'` or older](https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md#6130---2023-01-14).
143143
- If you're stuck on an older version of Gradle, [`id 'com.diffplug.gradle.spotless' version '4.5.1'` supports all the way back to Gradle 2.x](https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md#451---2020-07-04).
144144

145-
### Git hook
145+
### Git pre-push hook
146146

147-
TODO
147+
You can install a Git pre-push hook that ensures code is properly formatted before being pushed to a remote repository.
148+
This helps catch formatting issues early — before CI fails — and is especially useful for teams not using IDE integrations or pre-commit tools.
149+
150+
#### What the hook does
151+
152+
When installed, the Git `pre-push` hook will:
153+
154+
1. Run `spotlessCheck`
155+
2. If formatting issues are found:
156+
- It automatically runs `spotlessApply` to fix them
157+
- Aborts the push with a message
158+
- You can then commit the changes and push again
159+
160+
This ensures your code is always clean before it leaves your machine.
161+
162+
#### Installation
163+
164+
Run the following task once in your project:
165+
```console
166+
gradle spotlessInstallGitPrePushHook
167+
```
168+
169+
This installs a `.git/hooks/pre-push` script that runs `spotlessCheck`, and runs `spotlessApply` if needed.
170+
171+
> [!WARNING]
172+
> The hook will not install automatically — you must run the install command manually.
148173
149174
### Linting
150175

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
@@ -44,7 +44,7 @@ public void should_create_pre_hook_file_when_hook_file_does_not_exists() throws
4444
assertThat(output).contains("Git pre-push hook not found, creating it");
4545
assertThat(output).contains("Git pre-push hook installed successfully to the file " + newFile(".git/hooks/pre-push"));
4646

47-
final var content = getTestResource("git_pre_hook/pre-push.created")
47+
final var content = getTestResource("git_pre_hook/pre-push.created-tpl")
4848
.replace("${executor}", "gradle")
4949
.replace("${checkCommand}", "spotlessCheck")
5050
.replace("${applyCommand}", "spotlessApply");
@@ -73,7 +73,7 @@ public void should_append_to_existing_pre_hook_file_when_hook_file_exists() thro
7373
assertThat(output).contains("Installing git pre-push hook");
7474
assertThat(output).contains("Git pre-push hook installed successfully to the file " + newFile(".git/hooks/pre-push"));
7575

76-
final var content = getTestResource("git_pre_hook/pre-push.existing-added")
76+
final var content = getTestResource("git_pre_hook/pre-push.existing-added-tpl")
7777
.replace("${executor}", "gradle")
7878
.replace("${checkCommand}", "spotlessCheck")
7979
.replace("${applyCommand}", "spotlessApply");

plugin-maven/README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ user@machine repo % mvn spotless:check
3737

3838
- [**Quickstart**](#quickstart)
3939
- [Requirements](#requirements)
40-
- [Git hook](#git-hook)
40+
- [Git pre-push hook](#git-pre-push-hook)
4141
- [Binding to maven phase](#binding-to-maven-phase)
4242
- **Languages**
4343
- [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat), [IntelliJ IDEA](#intellij-idea))
@@ -147,9 +147,34 @@ Spotless consists of a list of formats (in the example above, `misc` and `java`)
147147
Spotless requires Maven to be running on JRE 11+. To use JRE 8, go back to [`2.30.0` or older](https://github.com/diffplug/spotless/blob/main/plugin-maven/CHANGES.md#2300---2023-01-13).
148148

149149

150-
### Git hook
150+
### Git pre-push hook
151151

152-
TODO
152+
You can install a Git pre-push hook that ensures code is properly formatted before being pushed to a remote repository.
153+
This helps catch formatting issues early — before CI fails — and is especially useful for teams not using IDE integrations or pre-commit tools.
154+
155+
#### What the hook does
156+
157+
When installed, the Git `pre-push` hook will:
158+
159+
1. Run `spotless:check`
160+
2. If formatting issues are found:
161+
- It automatically runs `spotless:apply` to fix them
162+
- Aborts the push with a message
163+
- You can then commit the changes and push again
164+
165+
This ensures your code is always clean before it leaves your machine.
166+
167+
#### Installation
168+
169+
Run the following task once in your project:
170+
```console
171+
mvn spotless:install-git-pre-push-hook
172+
```
173+
174+
This installs a `.git/hooks/pre-push` script that runs `spotless:check`, and runs `spotless:apply` if needed.
175+
176+
> [!WARNING]
177+
> The hook will not install automatically — you must run the install command manually.
153178
154179
### Binding to maven phase
155180

plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessInstallPrePushHookMojoTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public void should_create_pre_hook_file_when_hook_file_does_not_exists() throws
4141
assertThat(output).contains("Git pre-push hook not found, creating it");
4242
assertThat(output).contains("Git pre-push hook installed successfully to the file " + newFile(".git/hooks/pre-push"));
4343

44-
final var content = getTestResource("git_pre_hook/pre-push.created")
45-
.replace("${executor}", "mvn")
44+
final var content = getTestResource("git_pre_hook/pre-push.created-tpl")
45+
.replace("${executor}", newFile("mvnw").getAbsolutePath())
4646
.replace("${checkCommand}", "spotless:check")
4747
.replace("${applyCommand}", "spotless:apply");
4848
assertFile(".git/hooks/pre-push").hasContent(content);
@@ -67,8 +67,8 @@ public void should_append_to_existing_pre_hook_file_when_hook_file_exists() thro
6767
assertThat(output).contains("Installing git pre-push hook");
6868
assertThat(output).contains("Git pre-push hook installed successfully to the file " + newFile(".git/hooks/pre-push"));
6969

70-
final var content = getTestResource("git_pre_hook/pre-push.existing-added")
71-
.replace("${executor}", "mvn")
70+
final var content = getTestResource("git_pre_hook/pre-push.existing-added-tpl")
71+
.replace("${executor}", newFile("mvnw").getAbsolutePath())
7272
.replace("${checkCommand}", "spotless:check")
7373
.replace("${applyCommand}", "spotless:apply");
7474
assertFile(".git/hooks/pre-push").hasContent(content);

testlib/src/main/resources/git_pre_hook/pre-push.created renamed to testlib/src/main/resources/git_pre_hook/pre-push.created-tpl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ if ! $SPOTLESS_EXECUTOR ${checkCommand} ; then
99
exit 1
1010
fi
1111
##### SPOTLESS HOOK END #####
12-

testlib/src/main/resources/git_pre_hook/pre-push.existing-added renamed to testlib/src/main/resources/git_pre_hook/pre-push.existing-added-tpl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,3 @@ if ! $SPOTLESS_EXECUTOR ${checkCommand} ; then
5959
exit 1
6060
fi
6161
##### SPOTLESS HOOK END #####
62-
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/bin/sh
2+
3+
# An example hook script to verify what is about to be pushed. Called by "git
4+
# push" after it has checked the remote status, but before anything has been
5+
# pushed. If this script exits with a non-zero status nothing will be pushed.
6+
#
7+
# This hook is called with the following parameters:
8+
#
9+
# $1 -- Name of the remote to which the push is being done
10+
# $2 -- URL to which the push is being done
11+
#
12+
# If pushing without using a named remote those arguments will be equal.
13+
#
14+
# Information about the commits which are being pushed is supplied as lines to
15+
# the standard input in the form:
16+
#
17+
# <local ref> <local oid> <remote ref> <remote oid>
18+
#
19+
# This sample shows how to prevent push of commits where the log message starts
20+
# with "WIP" (work in progress).
21+
22+
remote="$1"
23+
url="$2"
24+
25+
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
26+
27+
while read local_ref local_oid remote_ref remote_oid
28+
do
29+
if test "$local_oid" = "$zero"
30+
then
31+
# Handle delete
32+
:
33+
else
34+
if test "$remote_oid" = "$zero"
35+
then
36+
# New branch, examine all commits
37+
range="$local_oid"
38+
else
39+
# Update to existing branch, examine new commits
40+
range="$remote_oid..$local_oid"
41+
fi
42+
43+
# Check for WIP commit
44+
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
45+
if test -n "$commit"
46+
then
47+
echo >&2 "Found WIP commit in $local_ref, not pushing"
48+
exit 1
49+
fi
50+
fi
51+
done
52+
53+
54+
55+
56+
57+
##### SPOTLESS HOOK START #####
58+
SPOTLESS_EXECUTOR=${executor}
59+
if ! $SPOTLESS_EXECUTOR ${checkCommand} ; then
60+
echo 1>&2 "spotless found problems, running ${applyCommand}; commit the result and re-push"
61+
$SPOTLESS_EXECUTOR ${applyCommand}
62+
exit 1
63+
fi
64+
##### SPOTLESS HOOK END #####

0 commit comments

Comments
 (0)