diff --git a/.gitattributes b/.gitattributes index a3d7cab..85bc66a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ /.github/ export-ignore +/.githooks/ export-ignore /docs/ export-ignore /tests/ export-ignore /.gitattributes export-ignore @@ -6,7 +7,6 @@ /.php-cs-fixer.dist.php export-ignore /compose.yaml export-ignore /Dockerfile export-ignore -/pre-commit.config.yaml export-ignore /mkdocs.yml export-ignore /phpmd.baseline.xml export-ignore /phpmd.xml export-ignore diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..d2569dc --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,61 @@ +#!/bin/sh + +DOCKER_COMPOSE_RUN="docker compose run -T --rm --no-deps php" + +run_command () { + COMMAND_NAME=$1 + COMMAND_RUN=$2 + + ( + OUTPUT=$(eval "$DOCKER_COMPOSE_RUN $COMMAND_RUN" 2>&1) + status_code=$? + + if [ $status_code -ne 0 ]; then + printf "\r\033[K❌ %s \033[31m%s\033[0m\n" "$COMMAND_NAME" "failed" >&2 + echo "$OUTPUT" > "/tmp/${COMMAND_NAME}_pre-commit" + exit 1 + else + printf "\r\033[K✅ %s \033[32m%s\033[0m\n" "$COMMAND_NAME" "passed" >&2 + fi + ) & +} + +printf "🐚 \033[33m%s\033[0m\n\n" "Executing pre-commit hook..." + +# Staged files +STAGED_FILES=$(git diff --cached --name-only --diff-filter=AMR | grep -E '\.php$|\.php-cs-fixer(\.dist)?\.php|composer\.lock|phpunit\.xml|phpstan(-baseline)?\.neon' | tr '\n' ' ') + +HOOK_EXIT_CODE=0 +if [ "$STAGED_FILES" ]; then + # PHP-CS-Fixer; run on changed files or on whole project if .php-cs-fixer.dist.php/composer.lock has changed + EXTRA_ARGS='' + IFS=' + ' + if ! echo "${STAGED_FILES}" | grep -qE '\.php-cs-fixer(\.dist)?\.php|composer\.lock'; then + EXTRA_ARGS=$(printf -- '--path-mode=intersection %s' "${STAGED_FILES}") + fi + + run_command "PHP-CS-Fixer" "vendor/bin/php-cs-fixer fix --dry-run -v --diff --config=.php-cs-fixer.dist.php --show-progress=none $EXTRA_ARGS" + run_command "PHPStan" "vendor/bin/phpstan --memory-limit=1G --error-format=table" + run_command "PHPMD" "vendor/bin/phpmd src/ text phpmd.xml" + run_command "PHPUnit" "vendor/bin/phpunit" + + wait + + for OUTPUT_FILE in /tmp/*_pre-commit; do + if [ ! -f "$OUTPUT_FILE" ]; then + continue + fi + + HOOK_EXIT_CODE=1 + printf "\n----------------\n❌ %s\n\n" $(basename $OUTPUT_FILE '_output') + cat "$OUTPUT_FILE" + rm "$OUTPUT_FILE" + done + +else + printf "ℹ️ No staged PHP files\n" + HOOK_EXIT_CODE=0 +fi + +exit $HOOK_EXIT_CODE diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index d4c92f5..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -repos: - - repo: local - hooks: - - id: phpcsfixer - name: PHP-CS-Fixer - language: system - files: \.php$ - entry: docker compose run -T --rm php vendor/bin/php-cs-fixer fix --dry-run -v --diff --path-mode=intersection --config=.php-cs-fixer.dist.php --show-progress=none - - id: phpstan - name: PHPStan - language: system - files: \.php$ - pass_filenames: false - entry: docker compose run -T --rm php vendor/bin/phpstan analyse --memory-limit 1G --error-format=table - - id: phpmd - name: PHPMD - language: system - files: \.php$ - pass_filenames: false - entry: docker compose run -T --rm php vendor/bin/phpmd src/ text phpmd.xml - - id: phpunit - name: PHPUnit - language: system - files: \.php$ - pass_filenames: false - entry: docker compose run -T --rm php vendor/bin/phpunit diff --git a/README.md b/README.md index 2f425da..d2bdaaf 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Features and bugfixes should be based on the `master` branch. ### Prerequisites * [Docker Compose](https://docs.docker.com/compose/install/) -* [Pre-commit](https://pre-commit.com/#install) * [Task (optional)](https://taskfile.dev/installation/) ### Install dependencies @@ -51,10 +50,10 @@ Features and bugfixes should be based on the `master` branch. task composer:install ``` -### Enable pre-commit +### Enable pre-commit hook ```shell -task pre-commit:init +task git:hooks ``` > Note: Checks for `phpcs`, `phpstan`, `phpmd` and `phpunit` are executed when committing. diff --git a/Taskfile.yml b/Taskfile.yml index a7ed266..3e7bb08 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -63,13 +63,26 @@ tasks: cmds: - docker compose run --rm $TTY php composer update {{.PACKAGE}} {{.CLI_ARGS | default "--no-cache"}} - # Tools - pre-commit:init: - desc: Init pre-commit + # Git + git:hooks: + desc: Setup git hooks silent: true cmds: - - pre-commit install -f + - | + APP_HOOKS_DIR="{{.ROOT_DIR}}/.githooks" + GIT_HOOKS_DIR="{{.ROOT_DIR}}/.git/hooks" + + # Create the hooks directory if it does not exist + mkdir -p "$GIT_HOOKS_DIR" + + # Symlink hooks + for hook in $(ls "$APP_HOOKS_DIR"); do + ln -sf "$APP_HOOKS_DIR/$hook" "$GIT_HOOKS_DIR/$hook" + done + printf "\033[32m%s\033[0m\n" "Git hooks installed." + + # Tools phpcs: desc: PHPCS dry run cmds: diff --git a/composer.json b/composer.json index e012f17..305a88b 100644 --- a/composer.json +++ b/composer.json @@ -39,9 +39,9 @@ }, "require-dev": { "rector/rector": "^1.0", - "phpmd/phpmd": "^2.13", + "phpmd/phpmd": "^2.15", "friendsofphp/php-cs-fixer": "^3.64", - "phpstan/phpstan": "^1.9", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^10.0" }, "config": {