포매터 신경 더 이상 쓰지 않아도 되는, Spotless Git Hook pre commit 적용하기 - DDD-Community/DDD-12-MOYORAK-API GitHub Wiki
Java나 Groovy 등 여러 언어의 코드 스타일을 자동으로 포매팅해 주는 Gradle 플러그인 입니다.
(🔗 Github : https://github.com/diffplug/spotless)
Spotless의 장점은 아래와 같습니다.
- 일관 된 코드 기준을 코드로 명시할 수 있습니다.
- 개발자마다 다른 포매터 설정이나, IDE 스타일이 달라 발생하는 불필요한 커뮤니케이션 비용을 줄일 수 있습니다.
-
editorconfig
는 IDE 설정일 뿐, 직접 코드를 수정해 주지는 않으나 Spotless는 코드를 직접 수정을 해준다는 차이점이 있습니다.
이를 통해 여러 명이 협업할 때, 일관된 코드 스타일을 유지하는 데 유용하게 활용할 수 있습니다.
plugins {
id("com.diffplug.spotless") version "7.0.3"
}
Spotless는 다양한 포매터와 여러 기능을 지원하고 있습니다.
대표적으로 많이 사용되거나, 유용한 항목들에 대해 아래와 같이 정리해 보았습니다.
어떤 스타일의 포매터를 사용할 것인지 명시합니다.
- googlejavaformat(
"버전"
)- 구글에서 제공하는 Java 코드 포매터
- 버전 명시하면 고정되며, 생략 시 최신 버전으로 적용
-
.aosp()
옵션으로 안드로이드 스타일 적용 가능
- eclipse
- Eclipse IDE의 코드 포맷 설정을 Gradle에서도 사용
- configFile을 통해서 직접
.xml
파일 지정 가능
- importOrder(...)
- 패키지 상단에 import를 특정 순서대로 지정
- removeUnusedImports
- 사용하지 않는 import 제거
- trimTrailingWhitespace
- 줄 끝 공백 제거
- endWithNewline
- 파일 끝에 개행 추가 (POSIX 표준 맞춤)
- licenseHeader("문자")
- licenseHeaderFile("파일 경로") 파일 상단에 공통 라이선스 문구 삽입 가능
- custom("이름", "정규식", "치환 문자열")
- toggleOffOn()
- 주석으로 포맷팅 적용/비적용 범위 설정 가능
public class Sample {
// spotless:off
public void messyMethod(){
System.out.println( "해당 부분 포맷팅 ❌" );
}
// spotless:on
public void cleanMethod() {
System.out.println("해당 부분 포맷팅 ✅”");
}
}
format("json") {
target("**/*.json")
prettier().config(mapOf("tabWidth" to 2, "useTabs" to false))
}
format("yaml") {
target("**/*.yml")
trimTrailingWhitespace()
endWithNewline()
}
configure<SpotlessExtension> {
java {
googleJavaFormat().aosp()
trimTrailingWhitespace()
endWithNewline()
removeUnusedImports()
}
}
현재 위와 같이 설정하여 사용 중이며, 혹시 더 좋은 방식이 있다면 공유 주세요. 😁
설정된 Spotless
는 아래 명령어로 실행할 수 있습니다.
./gradlew spotlessApply`
해당 명령어를 실행하면, 설정한 포매터 규칙에 따라 코드가 자동 정리가 됩니다.
단, 이 작업은 개발자가 직접 수동으로 명령어를 실행해야만 적용됩니다.
포매팅 체크를 하는 명령어는 아래와 같습니다.
./gradlew spotlessCheck
포매팅 체크에 실패한다면,아래와 같이 오류 메세지가 출력됩니다. 🚨
> spotless: Format violations were found. Run 'spotlessApply' to fix.
이처럼 spotlessCheck
를 이용해, CI 파이프라인에 포함 시킨다면,
Spotless
를 통해 정리가 된 코드만 통과될 수 있도록 강제할 수도 있습니다.
매번 직접 명령어를 실행하는 방식보단, 커밋할 때마다 자동으로 실행된다면 좋지 않을까? 생각이 들었습니다.
그렇다면 정리되지 않은 코드가 커밋되는 것을 사전에 방지할 수 있게 됩니다.
이러한 자동화를 구성하기 위해 GitHook을 활용하기로 하였습니다.
GitHook이 실행되기 위해서는 스크립트 파일이 .git/hooks
디렉토리에 존재해야 합니다.
하지만, 해당 디렉터리는 형상 관리 대상이 아니기 때문에 직접 옮겨줘야 하고
이 작업 역시 자동으로 풀어보고자 합니다.
- 형상 관리 범위의 위치(`/githooks)에서 pre-commit 스크립트를 작성합니다.
-
pre-commit
을.git/hooks
로 복사해 주는 Gradle task를 생성합니다. - clean 명령어에 만든 task를 의존시켜, 프로젝트 최초 설정 시 자동으로 적용되게 합니다.
./githooks/pre-commit
생성
#!/bin/sh
set -e
echo "▶ 스테이지된 Java 파일만 spotlessApply 실행 중..."
# 1. 현재 스테이지에 올라간 파일 목록만 추출
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.java$' || true)
if [ -z "$files" ]; then
echo "✅ 포맷할 Java 파일이 없습니다. 커밋을 진행합니다."
exit 0
fi
# 2. Spotless는 지정된 파일만 직접 지정 불가 → 대신 전체 실행 후, 수정된 파일 중 대상만 git add./gradlew spotlessApply --quiet
echo "▶ 다시 git add 중..."
echo "$files" | xargs git add
echo "▶ spotlessCheck 실행 중..."
./gradlew spotlessCheck --quiet
echo "✅ 포맷 완료. 커밋 진행 가능!"
-
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.java$' || true)
- 프로젝트 전체 코드를 대상으로 포매팅을 하기 때문에, 스테이지 파일만 조건으로 주고자 작성
- 스테이지에 올린 파일만 커밋하더라도, 스테이지에 올라가지 않은 파일까지 함께 포매팅 되게 됩니다.
위와 같은 상황에서 두 파일 모두 포매팅이 됩니다.
build.gradle.kts
tasks.register<Copy>("initGitHooks") {
from(file("$rootDir/githooks")) {
include("pre-commit")
rename("pre-commit", "pre-commit")
}
into(file("$rootDir/.git/hooks"))
doLast {
val hookFile = file("$rootDir/.git/hooks/pre-commit")
if (hookFile.exists()) {
hookFile.setExecutable(true)
}
}
}
단계 | 내용 |
---|---|
register<Copy> |
initGitHooks 라는 이름의 복사 task 생성 |
from(...) |
복사할 원본 파일 지정 |
into(...) |
복사 대상 지정 |
doLast |
복사된 후, 실행 권한(chmod +x ) 부여 |
새로 등록된 task는 아래와 같이 확인 가능합니다.
대부분의 프로젝트 초기화를 할 때, .gradlew clean
을 실행하므로,
clean
작업 전에 만들어놓은 initGitHooks
task가 자동으로 실행되도록 의존성을 설정합니다.
tasks.named("clean") {
dependsOn("initGitHooks")
}
이렇게 설정해 두면, 프로젝트를 처음 설정하는 개발자라도 별도의 작업 없이
clean 명령만으로 GitHook이 자동으로 구성됩니다.
즉, Spotless 설정을 별도로 인지하지 못하더라도 자동으로 적용될 수 있는 구조가 됩니다.
-
/githooks
의pre-commit
스크립트가 변경될 때 자동으로./git/hooks
까지 반영되지 않습니다. - 해당 부분 역시 자동화를 고민해 볼 수 있지 않을까 싶습니다.