-
Notifications
You must be signed in to change notification settings - Fork 489
add git pre push hook #2553
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
Merged
Merged
add git pre push hook #2553
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e6e9a07
add git pre push hook
lazer-dev 6528b0b
spotlessApply
lazer-dev b9f4880
Placeholders for docs.
a790659
maven wrapper support
lazer-dev 525e70e
reinstall support
lazer-dev 259ec38
Modify git-hook docs, with an eye towards a future where there is bot…
2dadeb6
uninstall fix
lazer-dev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
212 changes: 212 additions & 0 deletions
212
lib/src/main/java/com/diffplug/spotless/GitPrePushHookInstaller.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| /* | ||
| * Copyright 2025 DiffPlug | ||
| * | ||
| * 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 com.diffplug.spotless; | ||
|
|
||
| import static java.nio.charset.StandardCharsets.UTF_8; | ||
| import static java.util.Objects.requireNonNull; | ||
|
|
||
| import java.io.File; | ||
| import java.io.FileWriter; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
|
|
||
| /** | ||
| * Abstract class responsible for installing a Git pre-push hook in a repository. | ||
| * This class ensures that specific checks and logic are run before a push operation in Git. | ||
| * | ||
| * Subclasses should define specific behavior for hook installation by implementing the required abstract methods. | ||
| */ | ||
| public abstract class GitPrePushHookInstaller { | ||
|
|
||
| private static final String HOOK_HEADLINE = "##### SPOTLESS HOOK START #####"; | ||
| private static final String HOOK_FOOTER = "##### SPOTLESS HOOK END #####"; | ||
|
|
||
| /** | ||
| * Logger for recording informational and error messages during the installation process. | ||
| */ | ||
| protected final GitPreHookLogger logger; | ||
|
|
||
| /** | ||
| * The root directory of the Git repository where the hook will be installed. | ||
| */ | ||
| protected final File root; | ||
|
|
||
| /** | ||
| * Constructor to initialize the GitPrePushHookInstaller with a logger and repository root path. | ||
| * | ||
| * @param logger The logger for recording messages. | ||
| * @param root The root directory of the Git repository. | ||
| */ | ||
| public GitPrePushHookInstaller(GitPreHookLogger logger, File root) { | ||
| this.logger = requireNonNull(logger, "logger can not be null"); | ||
| this.root = requireNonNull(root, "root file can not be null"); | ||
| } | ||
|
|
||
| /** | ||
| * Installs the Git pre-push hook into the repository. | ||
| * | ||
| * <p>This method checks for the following: | ||
| * <ul> | ||
| * <li>Ensures Git is installed and the `.git/config` file exists.</li> | ||
| * <li>Checks if an executor required by the hook is available.</li> | ||
| * <li>Creates and writes the pre-push hook file if it does not exist.</li> | ||
| * <li>Skips installation if the hook is already installed.</li> | ||
| * </ul> | ||
| * If an issue occurs during installation, error messages are logged. | ||
| * | ||
| * @throws Exception if any error occurs during installation. | ||
| */ | ||
| public void install() throws Exception { | ||
| logger.info("Installing git pre-push hook"); | ||
|
|
||
| if (!isGitInstalled()) { | ||
| logger.error("Git not found in root directory"); | ||
| return; | ||
| } | ||
|
|
||
| var hookContent = ""; | ||
| final var gitHookFile = root.toPath().resolve(".git/hooks/pre-push").toFile(); | ||
| if (!gitHookFile.exists()) { | ||
| logger.info("Git pre-push hook not found, creating it"); | ||
| if (!gitHookFile.getParentFile().exists() && !gitHookFile.getParentFile().mkdirs()) { | ||
| logger.error("Failed to create pre-push hook directory"); | ||
| return; | ||
| } | ||
|
|
||
| if (!gitHookFile.createNewFile()) { | ||
| logger.error("Failed to create pre-push hook file"); | ||
| return; | ||
| } | ||
|
|
||
| if (!gitHookFile.setExecutable(true, false)) { | ||
| logger.error("Can not make file executable"); | ||
| return; | ||
| } | ||
|
|
||
| hookContent += "#!/bin/sh\n"; | ||
| } | ||
|
|
||
| if (isGitHookInstalled(gitHookFile)) { | ||
| logger.info("Git pre-push hook already installed, reinstalling it"); | ||
| uninstall(gitHookFile); | ||
| } | ||
|
|
||
| hookContent += preHookContent(); | ||
| writeFile(gitHookFile, hookContent, true); | ||
|
|
||
| logger.info("Git pre-push hook installed successfully to the file %s", gitHookFile.getAbsolutePath()); | ||
| } | ||
|
|
||
| /** | ||
| * Uninstalls the Spotless Git pre-push hook from the specified hook file by removing | ||
| * the custom hook content between the defined hook markers. | ||
| * | ||
| * <p>This method: | ||
| * <ul> | ||
| * <li>Reads the entire content of the pre-push hook file</li> | ||
| * <li>Identifies the Spotless hook section using predefined markers</li> | ||
| * <li>Removes the Spotless hook content while preserving other hook content</li> | ||
| * <li>Writes the modified content back to the hook file</li> | ||
| * </ul> | ||
| * | ||
| * @param gitHookFile The Git pre-push hook file from which to remove the Spotless hook | ||
| * @throws Exception if any error occurs during the uninstallation process, | ||
| * such as file reading or writing errors | ||
| */ | ||
| private void uninstall(File gitHookFile) throws Exception { | ||
| final var hook = Files.readString(gitHookFile.toPath(), UTF_8); | ||
| final var hookStart = hook.indexOf(HOOK_HEADLINE); | ||
| final var hookEnd = hook.indexOf(HOOK_FOOTER) + HOOK_FOOTER.length(); | ||
|
|
||
| final var hookScript = hook.substring(hookStart, hookEnd); | ||
|
|
||
| final var uninstalledHook = hook.replace(hookScript, ""); | ||
|
|
||
| writeFile(gitHookFile, uninstalledHook, false); | ||
| } | ||
|
|
||
| /** | ||
| * Provides the content of the hook that should be inserted into the pre-push script. | ||
| * | ||
| * @return A string representing the content to include in the pre-push script. | ||
| */ | ||
| protected abstract String preHookContent(); | ||
|
|
||
| /** | ||
| * Generates a pre-push template script that defines the commands to check and apply changes | ||
| * using an executor and Spotless. | ||
| * | ||
| * @param executor The tool to execute the check and apply commands. | ||
| * @param commandCheck The command to check for issues. | ||
| * @param commandApply The command to apply corrections. | ||
| * @return A string template representing the Spotless Git pre-push hook content. | ||
| */ | ||
| protected String preHookTemplate(String executor, String commandCheck, String commandApply) { | ||
| var spotlessHook = "\n"; | ||
| spotlessHook += "\n" + HOOK_HEADLINE; | ||
| spotlessHook += "\nSPOTLESS_EXECUTOR=" + executor; | ||
| spotlessHook += "\nif ! $SPOTLESS_EXECUTOR " + commandCheck + " ; then"; | ||
| spotlessHook += "\n echo 1>&2 \"spotless found problems, running " + commandApply + "; commit the result and re-push\""; | ||
| spotlessHook += "\n $SPOTLESS_EXECUTOR " + commandApply; | ||
| spotlessHook += "\n exit 1"; | ||
| spotlessHook += "\nfi"; | ||
| spotlessHook += "\n" + HOOK_FOOTER; | ||
| spotlessHook += "\n"; | ||
| return spotlessHook; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if Git is installed by validating the existence of `.git/config` in the repository root. | ||
| * | ||
| * @return {@code true} if Git is installed, {@code false} otherwise. | ||
| */ | ||
| private boolean isGitInstalled() { | ||
| return root.toPath().resolve(".git/config").toFile().exists(); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies if the pre-push hook file already contains the custom Spotless hook content. | ||
| * | ||
| * @param gitHookFile The file representing the Git hook. | ||
| * @return {@code true} if the hook is already installed, {@code false} otherwise. | ||
| * @throws Exception if an error occurs when reading the file. | ||
| */ | ||
| private boolean isGitHookInstalled(File gitHookFile) throws Exception { | ||
| final var hook = Files.readString(gitHookFile.toPath(), UTF_8); | ||
| return hook.contains(HOOK_HEADLINE); | ||
| } | ||
|
|
||
| /** | ||
| * Writes the specified content into a file. | ||
| * | ||
| * @param file The file to which the content should be written. | ||
| * @param content The content to write into the file. | ||
| * @throws IOException if an error occurs while writing to the file. | ||
| */ | ||
| private void writeFile(File file, String content, boolean append) throws IOException { | ||
| try (final var writer = new FileWriter(file, UTF_8, append)) { | ||
| writer.write(content); | ||
| } | ||
| } | ||
|
|
||
| public interface GitPreHookLogger { | ||
| void info(String format, Object... arguments); | ||
|
|
||
| void warn(String format, Object... arguments); | ||
|
|
||
| void error(String format, Object... arguments); | ||
| } | ||
| } | ||
52 changes: 52 additions & 0 deletions
52
lib/src/main/java/com/diffplug/spotless/GitPrePushHookInstallerGradle.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /* | ||
| * Copyright 2025 DiffPlug | ||
| * | ||
| * 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 com.diffplug.spotless; | ||
|
|
||
| import java.io.File; | ||
|
|
||
| /** | ||
| * Implementation of {@link GitPrePushHookInstaller} specifically for Gradle-based projects. | ||
| * This class installs a Git pre-push hook that uses Gradle's `gradlew` executable to check and apply Spotless formatting. | ||
| */ | ||
| public class GitPrePushHookInstallerGradle extends GitPrePushHookInstaller { | ||
|
|
||
| /** | ||
| * The Gradle wrapper file (`gradlew`) located in the root directory of the project. | ||
| */ | ||
| private final File gradlew; | ||
|
|
||
| public GitPrePushHookInstallerGradle(GitPreHookLogger logger, File root) { | ||
| super(logger, root); | ||
| this.gradlew = root.toPath().resolve("gradlew").toFile(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| protected String preHookContent() { | ||
| return preHookTemplate(executor(), "spotlessCheck", "spotlessApply"); | ||
| } | ||
|
|
||
| private String executor() { | ||
| if (gradlew.exists()) { | ||
| return gradlew.getAbsolutePath(); | ||
| } | ||
|
|
||
| logger.info("Gradle wrapper is not installed, using global gradle"); | ||
| return "gradle"; | ||
| } | ||
| } |
49 changes: 49 additions & 0 deletions
49
lib/src/main/java/com/diffplug/spotless/GitPrePushHookInstallerMaven.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* | ||
| * Copyright 2025 DiffPlug | ||
| * | ||
| * 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 com.diffplug.spotless; | ||
|
|
||
| import java.io.File; | ||
|
|
||
| /** | ||
| * Implementation of {@link GitPrePushHookInstaller} specifically for Maven-based projects. | ||
| * This class installs a Git pre-push hook that uses Maven to check and apply Spotless formatting. | ||
| */ | ||
| public class GitPrePushHookInstallerMaven extends GitPrePushHookInstaller { | ||
|
|
||
| private final File mvnw; | ||
|
|
||
| public GitPrePushHookInstallerMaven(GitPreHookLogger logger, File root) { | ||
| super(logger, root); | ||
| this.mvnw = root.toPath().resolve("mvnw").toFile(); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| protected String preHookContent() { | ||
| return preHookTemplate(executor(), "spotless:check", "spotless:apply"); | ||
| } | ||
|
|
||
| private String executor() { | ||
| if (mvnw.exists()) { | ||
| return mvnw.getAbsolutePath(); | ||
| } | ||
|
|
||
| logger.info("Maven wrapper is not installed, using global maven"); | ||
| return "mvn"; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.