Skip to content

Commit 43f4b9f

Browse files
authored
feat: add git pre-commit hook for automatic code formatting (#23384)
Add a Husky-based pre-commit hook that runs Spotless on staged files, so code is automatically formatted before each commit. - Use Husky (via npx) for cross-platform git hook management - Auto-install hooks via Maven profile activated when .husky/_ is missing - Use ratchetFrom with the upstream merge-base to only format changed files - Stash unstaged changes before formatting to preserve partial staging - Use a filesystem lock to prevent races during parallel Maven builds - Skip formatting during rebase when no upstream is set
1 parent 39c2387 commit 43f4b9f

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed

.husky/pre-commit

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Get staged files that spotless can format
2+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(java|js|ts|css)$|pom\.xml$' || true)
3+
4+
if [ -n "$STAGED_FILES" ]; then
5+
# Skip formatting during rebase (detached HEAD, no upstream)
6+
UPSTREAM_REF="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)" || exit 0
7+
BASE_SHA="$(git merge-base HEAD "$UPSTREAM_REF")"
8+
9+
# Stash unstaged changes to avoid re-staging partial hunks
10+
git stash push --keep-index --quiet --message "pre-commit-hook-unstaged"
11+
12+
echo "Formatting with Spotless..."
13+
if ! mvn spotless:apply -q -Dspotless.ratchetFrom="$BASE_SHA"; then
14+
echo "Spotless formatting failed."
15+
git stash pop --quiet 2>/dev/null || true
16+
exit 1
17+
fi
18+
19+
echo "$STAGED_FILES" | xargs git add
20+
21+
# Restore unstaged changes
22+
git stash pop --quiet 2>/dev/null || true
23+
24+
echo "Formatting complete."
25+
fi

pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
<maven-plugin-annotations.version>3.15.2</maven-plugin-annotations.version>
114114
<spotless.plugin.version>3.2.1</spotless.plugin.version>
115115
<spotless.license-header>${maven.multiModuleProjectDirectory}/eclipse/apache2-license-header.txt</spotless.license-header>
116+
<spotless.ratchetFrom/>
116117

117118
<!-- Frontend -->
118119
<flow.dev.dependencies.folder>generatedDeps</flow.dev.dependencies.folder>
@@ -593,6 +594,7 @@
593594
<artifactId>spotless-maven-plugin</artifactId>
594595
<version>${spotless.plugin.version}</version>
595596
<configuration>
597+
<ratchetFrom>${spotless.ratchetFrom}</ratchetFrom>
596598
<java>
597599
<eclipse>
598600
<file>${maven.multiModuleProjectDirectory}/eclipse/VaadinJavaConventions.xml</file>
@@ -769,6 +771,39 @@
769771
</extensions>
770772
</build>
771773
<profiles>
774+
<profile>
775+
<id>install-git-hooks</id>
776+
<activation>
777+
<file>
778+
<missing>${maven.multiModuleProjectDirectory}/.husky/_</missing>
779+
</file>
780+
</activation>
781+
<build>
782+
<plugins>
783+
<plugin>
784+
<groupId>org.codehaus.mojo</groupId>
785+
<artifactId>exec-maven-plugin</artifactId>
786+
<version>${maven.exec.plugin.version}</version>
787+
<executions>
788+
<execution>
789+
<id>install-git-hooks</id>
790+
<goals>
791+
<goal>exec</goal>
792+
</goals>
793+
<phase>initialize</phase>
794+
<configuration>
795+
<executable>node</executable>
796+
<workingDirectory>${maven.multiModuleProjectDirectory}</workingDirectory>
797+
<arguments>
798+
<argument>${maven.multiModuleProjectDirectory}/scripts/installHusky.js</argument>
799+
</arguments>
800+
</configuration>
801+
</execution>
802+
</executions>
803+
</plugin>
804+
</plugins>
805+
</build>
806+
</profile>
772807
<profile>
773808
<id>default</id>
774809
<activation>

scripts/installHusky.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env node
2+
const fs = require("fs");
3+
const { execSync } = require("child_process");
4+
5+
const lockDir = ".husky/_lock";
6+
try {
7+
fs.mkdirSync(lockDir);
8+
} catch (e) {
9+
return; // another process or already set up
10+
}
11+
try {
12+
execSync("npx husky@9.1.7", { stdio: "inherit" });
13+
} finally {
14+
fs.rmSync(lockDir, { recursive: true });
15+
}

0 commit comments

Comments
 (0)