From 6c6996c9a13c5ceb5013d60cba5700b4363b4ad1 Mon Sep 17 00:00:00 2001 From: Stefan Zweifel Date: Tue, 16 Sep 2025 19:16:07 +0200 Subject: [PATCH] WIP Hooks --- .github/workflows/hook-examples.yml | 242 ++++++++++++++++++++++++++++ README.md | 159 ++++++++++++++++++ action.yml | 39 +++-- entrypoint.sh | 45 ++++++ tests/git-auto-commit.bats | 238 +++++++++++++++++++++++++++ 5 files changed, 711 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/hook-examples.yml diff --git a/.github/workflows/hook-examples.yml b/.github/workflows/hook-examples.yml new file mode 100644 index 0000000..d7939b3 --- /dev/null +++ b/.github/workflows/hook-examples.yml @@ -0,0 +1,242 @@ +name: Hook Examples + +on: + workflow_dispatch: + push: + branches: [ main ] + +jobs: + # Example 1: Use pre_status_hook to unshallow repository + unshallow-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 # Create shallow clone + + - name: Make some changes + run: | + echo "$(date): Updated by workflow" >> updates.log + + - name: Commit with unshallow hook + uses: ./ + with: + commit_message: "Update logs with unshallow support" + pre_status_hook: | + echo "Checking if repository is shallow..." + if git rev-parse --is-shallow-repository 2>/dev/null | grep -q true; then + echo "Repository is shallow, running git fetch --unshallow" + git fetch --unshallow + else + echo "Repository is not shallow" + fi + + # Example 2: Use pre_commit_hook for validation + validation-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - name: Make changes to JavaScript files + run: | + echo "console.log('Hello World');" > example.js + + - name: Commit with validation hook + uses: ./ + with: + commit_message: "Add example JavaScript file" + pre_commit_hook: | + echo "Running pre-commit validation..." + # Validate JavaScript syntax + find . -name "*.js" -not -path "./node_modules/*" -exec node -c {} \; + echo "JavaScript validation passed!" + + # Run tests if they exist + if [ -f "package.json" ] && npm run test --if-present; then + echo "Tests passed!" + fi + + # Example 3: Use pre_commit_hook to generate additional files + file-generation-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Make some changes + run: | + echo "New feature added" > feature.txt + + - name: Commit with file generation hook + uses: ./ + with: + commit_message: "Add feature with generated manifest" + pre_commit_hook: | + echo "Generating build manifest..." + + # Create build info file + cat > build-info.json << EOF + { + "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "commit": "$GITHUB_SHA", + "workflow": "$GITHUB_WORKFLOW", + "run_id": "$GITHUB_RUN_ID", + "actor": "$GITHUB_ACTOR" + } + EOF + + echo "Generated build-info.json" + cat build-info.json + + # Example 4: Use pre_push_hook for final validation + pre-push-validation-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Make changes + run: | + echo "Important data: $(date)" > important-file.txt + + - name: Commit with pre-push validation + uses: ./ + with: + commit_message: "Add important file with validation" + pre_push_hook: | + echo "Running final validation before push..." + + # Check if important file exists and has content + if [ ! -f "important-file.txt" ] || [ ! -s "important-file.txt" ]; then + echo "ERROR: important-file.txt is missing or empty!" + exit 1 + fi + + # Check git log for the commit we're about to push + echo "Latest commit details:" + git log -1 --oneline + + echo "Pre-push validation passed!" + + # Example 5: Use post_push_hook for notifications + notification-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Update documentation + run: | + echo "# Documentation Updated" > README.md + echo "Last updated: $(date)" >> README.md + + - name: Commit with notification hook + uses: ./ + with: + commit_message: "Update documentation" + post_push_hook: | + echo "Changes successfully pushed!" + + # Get the commit hash that was just pushed + COMMIT_HASH=$(git rev-parse HEAD) + echo "Pushed commit: $COMMIT_HASH" + + # Create a summary + echo "📝 Documentation updated successfully" >> $GITHUB_STEP_SUMMARY + echo "- Commit: \`$COMMIT_HASH\`" >> $GITHUB_STEP_SUMMARY + echo "- Time: $(date)" >> $GITHUB_STEP_SUMMARY + + # In a real scenario, you might send notifications to Slack, Discord, etc. + echo "This is where you would send notifications to your team!" + + # Example 6: Multiple hooks working together + comprehensive-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup environment + run: | + echo "Setting up project..." + mkdir -p logs + + - name: Make changes + run: | + echo "Feature implementation" > feature.txt + echo "$(date): Feature added" > logs/changes.log + + - name: Commit with all hooks + uses: ./ + with: + commit_message: "Add comprehensive feature" + pre_status_hook: | + echo "🔍 Pre-status: Preparing repository..." + git status --porcelain + + pre_commit_hook: | + echo "🛠️ Pre-commit: Generating metadata..." + + # Generate changelog entry + echo "## $(date +%Y-%m-%d)" > CHANGELOG_ENTRY.md + echo "- Added comprehensive feature" >> CHANGELOG_ENTRY.md + + # Update version file + echo "1.0.$(date +%s)" > VERSION + + pre_push_hook: | + echo "✅ Pre-push: Final validation..." + + # Validate all required files exist + required_files=("feature.txt" "logs/changes.log" "CHANGELOG_ENTRY.md" "VERSION") + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + echo "ERROR: Required file $file is missing!" + exit 1 + fi + done + + echo "All validations passed!" + + post_push_hook: | + echo "🎉 Post-push: Cleanup and notification..." + + # Clean up temporary files if any + rm -f /tmp/build-* + + # Log success + echo "Deployment completed at $(date)" >> logs/deployment.log + + echo "Comprehensive feature deployment completed!" + + # Example 7: Error handling demonstration + error-handling-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Make changes + run: | + echo "Some changes" > test-file.txt + + - name: Demonstrate hook failure (this will fail) + uses: ./ + continue-on-error: true + with: + commit_message: "This commit should fail" + pre_commit_hook: | + echo "Running validation that will fail..." + + # This will cause the hook to fail + if [ "$(cat test-file.txt)" = "Some changes" ]; then + echo "ERROR: File content not allowed!" + exit 1 + fi + + - name: Show that workflow continues after failure + run: | + echo "This step runs even if the previous step failed due to continue-on-error: true" diff --git a/README.md b/README.md index 69fba9f..e8a7b60 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,165 @@ If you would like to prevent this, you can add `skip-checks:true` to the commit Does your workflow change a file, but "git-auto-commit" does not detect the change? Check the `.gitignore` that applies to the respective file. You might have accidentally marked the file to be ignored by git. +## Hooks + +The action supports custom bash scripts that can be executed at various points during the git workflow. This allows for custom preparation, validation, or post-processing steps. + +### Available Hooks + +- **`pre_status_hook`** - Executed before checking git status. Useful for repository preparation like `git fetch --unshallow`. +- **`pre_commit_hook`** - Executed after detecting changes but before adding files. Allows modification of files that will be included in the commit. +- **`pre_push_hook`** - Executed after committing but before pushing to remote. Useful for final validation. +- **`post_push_hook`** - Executed after successfully pushing changes. Useful for notifications or cleanup. + +### Hook Examples + +#### Unshallow Repository Before Checking Status + +This example addresses the common issue where workflows use shallow checkouts (`fetch-depth: 1`) for performance, but need to unshallow before merging or when full history is required for certain operations. + +```yaml +name: Update Submodules +on: [push] + +jobs: + update-submodules: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 # Shallow checkout for performance + submodules: recursive + + - name: Update submodule to latest + run: | + git submodule update --remote + + - name: Commit submodule updates + uses: stefanzweifel/git-auto-commit-action@v6 + with: + file_pattern: .gitmodules * + commit_message: "Update submodules to latest versions" + pre_status_hook: | + # Only unshallow if we have changes and need to merge + if git status --porcelain | grep -q .; then + echo "Changes detected, checking if repository is shallow..." + if git rev-parse --is-shallow-repository 2>/dev/null | grep -q true; then + echo "Repository is shallow, running git fetch --unshallow" + git fetch --unshallow + fi + fi +``` + +#### Addressing "refusing to merge unrelated histories" Error + +This specific example addresses the use case discussed in [GitHub Issue #365](https://github.com/stefanzweifel/git-auto-commit-action/discussions/365), where shallow repositories can cause merge conflicts: + +```yaml +name: Update Dependencies with Unshallow +on: + schedule: + - cron: '0 2 * * 1' # Weekly updates + workflow_dispatch: + +jobs: + update-deps: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 # Start with shallow clone for performance + + - name: Update dependencies + run: | + # Your dependency update commands here + npm update + # or pip install -r requirements.txt --upgrade + # or bundle update + + - name: Commit dependency updates + uses: stefanzweifel/git-auto-commit-action@v6 + with: + file_pattern: 'package*.json yarn.lock requirements.txt Gemfile.lock' + commit_message: 'Update dependencies' + pre_status_hook: | + # Check if repository is shallow and unshallow if changes are detected + if git status --porcelain | grep -q .; then + echo "Dependencies have been updated, checking repository depth..." + if git rev-parse --is-shallow-repository 2>/dev/null | grep -q true; then + echo "Repository is shallow ($(git rev-list --count HEAD) commits), running git fetch --unshallow" + git fetch --unshallow + echo "Repository unshallowed successfully" + else + echo "Repository is not shallow ($(git rev-list --count HEAD) commits)" + fi + else + echo "No dependency changes detected, skipping unshallow" + fi +``` + +#### Validate Files Before Committing + +```yaml +- name: Commit with validation + uses: stefanzweifel/git-auto-commit-action@v6 + with: + commit_message: Apply automatic changes + pre_commit_hook: | + echo "Running validation..." + npm run lint + npm run test +``` + +#### Generate Additional Files in Pre-Commit Hook + +```yaml +- name: Commit with file generation + uses: stefanzweifel/git-auto-commit-action@v6 + with: + commit_message: Update files and manifest + pre_commit_hook: | + echo "Generating manifest..." + echo "Build timestamp: $(date)" > build-info.txt + echo "Commit: $GITHUB_SHA" >> build-info.txt +``` + +#### Notify After Successful Push + +```yaml +- name: Commit and notify + uses: stefanzweifel/git-auto-commit-action@v6 + with: + commit_message: Apply automatic changes + post_push_hook: | + echo "Changes successfully pushed!" + curl -X POST -H 'Content-type: application/json' \ + --data '{"text":"Code changes have been committed and pushed"}' \ + "$SLACK_WEBHOOK_URL" +``` + +### Hook Error Handling + +If any hook fails (exits with non-zero status), the entire action will fail and stop execution. This ensures that validation hooks can prevent commits/pushes when issues are detected. + +```yaml +- name: Commit with strict validation + uses: stefanzweifel/git-auto-commit-action@v6 + with: + commit_message: Apply automatic changes + pre_commit_hook: | + # This will fail the action if any .js files have syntax errors + find . -name "*.js" -exec node -c {} \; +``` + +### Hook Execution Context + +- Hooks are executed in the repository directory +- Hooks have access to all git commands and repository state +- Hooks can access GitHub Actions environment variables +- Files created/modified by `pre_commit_hook` will be included in the commit +- Hooks run in bash and support multi-line scripts + ## Advanced Uses ### Multiline Commit Messages diff --git a/action.yml b/action.yml index 9df496a..2ab6144 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ name: Git Auto Commit -description: 'Automatically commits files which have been changed during the workflow run and push changes back to remote repository.' +description: "Automatically commits files which have been changed during the workflow run and push changes back to remote repository." author: Stefan Zweifel @@ -15,23 +15,23 @@ inputs: commit_options: description: Commit options (eg. --no-verify) required: false - default: '' + default: "" add_options: description: Add options (eg. -u) required: false - default: '' + default: "" status_options: description: Status options (eg. --untracked-files=no) required: false - default: '' + default: "" file_pattern: description: File pattern used for `git add`. For example `src/*.js` required: false - default: '.' + default: "." repository: description: Local file path to the git repository. Defaults to the current directory (`.`) required: false - default: '.' + default: "." commit_user_name: description: Name used for the commit user required: false @@ -47,11 +47,11 @@ inputs: tagging_message: description: Message used to create a new git tag with the commit. Keep this empty, if no tag should be created. required: false - default: '' + default: "" push_options: description: Push options (eg. --force) required: false - default: '' + default: "" skip_dirty_check: description: Skip the check if the git repository is dirty and always try to create a commit. required: false @@ -66,6 +66,22 @@ inputs: internal_git_binary: description: Internal use only! Path to git binary used to check if git is available. (Don't change this!) default: git + pre_status_hook: + description: Bash script to execute before checking git status. Useful for git fetch --unshallow or other repository preparation. + required: false + default: "" + pre_commit_hook: + description: Bash script to execute before committing changes. Useful for validation or last-minute file modifications. + required: false + default: "" + pre_push_hook: + description: Bash script to execute before pushing changes to remote. Useful for final validation. + required: false + default: "" + post_push_hook: + description: Bash script to execute after successfully pushing changes. Useful for notifications or cleanup. + required: false + default: "" skip_fetch: description: "Deprecated: skip_fetch has been removed in v6. It does not have any effect anymore." required: false @@ -78,7 +94,6 @@ inputs: description: "Deprecated: create_branch has been removed in v6. It does not have any effect anymore." default: false - outputs: changes_detected: description: Value is "true", if the repository was dirty and file changes have been detected. Value is "false", if no changes have been detected. @@ -88,9 +103,9 @@ outputs: description: Value is "true", if a git tag was created using the `create_git_tag_only`-input. runs: - using: 'node20' - main: 'index.js' + using: "node20" + main: "index.js" branding: - icon: 'git-commit' + icon: "git-commit" color: orange diff --git a/entrypoint.sh b/entrypoint.sh index b55b351..b125399 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -19,6 +19,32 @@ _set_github_output() { fi } +_execute_hook() { + local hook_name=${1} + local hook_script=${2} + + if [ -n "$hook_script" ]; then + _log "debug" "Executing $hook_name hook"; + echo "::group::$hook_name hook" + + # Execute the hook script and capture exit code + # Temporarily disable errexit to handle hook failures gracefully + set +e + eval "$hook_script" + local exit_code=$? + set -e + + echo "::endgroup::" + + if [ $exit_code -ne 0 ]; then + _log "error" "$hook_name hook failed with exit code $exit_code"; + exit 1; + fi + + _log "debug" "$hook_name hook completed successfully"; + fi +} + _log() { local level=${1} local message=${2} @@ -47,15 +73,28 @@ _main() { # _check_if_repository_is_in_detached_state + # Execute pre-status hook before checking repository state + _execute_hook "pre_status" "$INPUT_PRE_STATUS_HOOK" + if "$INPUT_CREATE_GIT_TAG_ONLY"; then _log "debug" "Create git tag only"; _set_github_output "create_git_tag_only" "true" _tag_commit + + # Execute pre-push hook before pushing (tag-only mode) + _execute_hook "pre_push" "$INPUT_PRE_PUSH_HOOK" + _push_to_github + + # Execute post-push hook after successful push (tag-only mode) + _execute_hook "post_push" "$INPUT_POST_PUSH_HOOK" elif _git_is_dirty || "$INPUT_SKIP_DIRTY_CHECK"; then _set_github_output "changes_detected" "true" + # Execute pre-commit hook before adding files so hook can modify files + _execute_hook "pre_commit" "$INPUT_PRE_COMMIT_HOOK" + _add_files # Check dirty state of repo again using git-diff. @@ -67,7 +106,13 @@ _main() { _tag_commit + # Execute pre-push hook before pushing + _execute_hook "pre_push" "$INPUT_PRE_PUSH_HOOK" + _push_to_github + + # Execute post-push hook after successful push + _execute_hook "post_push" "$INPUT_POST_PUSH_HOOK" else _set_github_output "changes_detected" "false" diff --git a/tests/git-auto-commit.bats b/tests/git-auto-commit.bats index c64bf19..7072658 100644 --- a/tests/git-auto-commit.bats +++ b/tests/git-auto-commit.bats @@ -38,6 +38,12 @@ setup() { export INPUT_DISABLE_GLOBBING=false export INPUT_INTERNAL_GIT_BINARY=git + # Hook variables + export INPUT_PRE_STATUS_HOOK="" + export INPUT_PRE_COMMIT_HOOK="" + export INPUT_PRE_PUSH_HOOK="" + export INPUT_POST_PUSH_HOOK="" + # Deprecated variables. Will be removed in future versions export INPUT_CREATE_BRANCH=false export INPUT_SKIP_FETCH=false @@ -1181,3 +1187,235 @@ END assert_line "::warning::git-auto-commit: skip_checkout has been removed in v6. It does not have any effect anymore." assert_line "::warning::git-auto-commit: create_branch has been removed in v6. It does not have any effect anymore." } + +@test "it executes pre_status_hook when provided" { + # Create a dummy file and setup the hook to create a marker file + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-created-by-hook.txt + + INPUT_PRE_STATUS_HOOK="echo 'pre-status-hook-executed' > hook-marker.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_status hook" + assert_line "::debug::pre_status hook completed successfully" + + # Verify the hook actually executed + assert [ -f "${FAKE_LOCAL_REPOSITORY}/hook-marker.txt" ] + run cat "${FAKE_LOCAL_REPOSITORY}/hook-marker.txt" + assert_output "pre-status-hook-executed" +} + +@test "it executes pre_commit_hook when changes are detected" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file-created-by-hook.txt + + INPUT_PRE_COMMIT_HOOK="echo 'pre-commit-hook-executed' > pre-commit-marker.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_commit hook" + assert_line "::debug::pre_commit hook completed successfully" + + # Verify the hook actually executed + assert [ -f "${FAKE_LOCAL_REPOSITORY}/pre-commit-marker.txt" ] + run cat "${FAKE_LOCAL_REPOSITORY}/pre-commit-marker.txt" + assert_output "pre-commit-hook-executed" +} + +@test "it executes pre_push_hook when changes are detected" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_PRE_PUSH_HOOK="echo 'pre-push-hook-executed' > pre-push-marker.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_push hook" + assert_line "::debug::pre_push hook completed successfully" + + # Verify the hook actually executed + assert [ -f "${FAKE_LOCAL_REPOSITORY}/pre-push-marker.txt" ] + run cat "${FAKE_LOCAL_REPOSITORY}/pre-push-marker.txt" + assert_output "pre-push-hook-executed" +} + +@test "it executes post_push_hook when changes are detected" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_POST_PUSH_HOOK="echo 'post-push-hook-executed' > post-push-marker.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing post_push hook" + assert_line "::debug::post_push hook completed successfully" + + # Verify the hook actually executed + assert [ -f "${FAKE_LOCAL_REPOSITORY}/post-push-marker.txt" ] + run cat "${FAKE_LOCAL_REPOSITORY}/post-push-marker.txt" + assert_output "post-push-hook-executed" +} + +@test "it executes all hooks in correct order when changes are detected" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_PRE_STATUS_HOOK="echo '1' > execution-order.txt" + INPUT_PRE_COMMIT_HOOK="echo '2' >> execution-order.txt" + INPUT_PRE_PUSH_HOOK="echo '3' >> execution-order.txt" + INPUT_POST_PUSH_HOOK="echo '4' >> execution-order.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_status hook" + assert_line "::debug::Executing pre_commit hook" + assert_line "::debug::Executing pre_push hook" + assert_line "::debug::Executing post_push hook" + + # Verify all hooks executed in the correct order + run cat "${FAKE_LOCAL_REPOSITORY}/execution-order.txt" + assert_line --index 0 "1" + assert_line --index 1 "2" + assert_line --index 2 "3" + assert_line --index 3 "4" +} + +@test "it executes pre_status_hook even when no changes are detected" { + INPUT_PRE_STATUS_HOOK="echo 'pre-status-hook-executed' > /tmp/hook-marker.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_status hook" + assert_line "Working tree clean. Nothing to commit." + + # Verify the hook actually executed + assert [ -f "/tmp/hook-marker.txt" ] + run cat "/tmp/hook-marker.txt" + assert_output "pre-status-hook-executed" + rm -f "/tmp/hook-marker.txt" +} + +@test "it does not execute commit/push hooks when no changes are detected" { + INPUT_PRE_COMMIT_HOOK="echo 'should-not-execute' > pre-commit-marker.txt" + INPUT_PRE_PUSH_HOOK="echo 'should-not-execute' > pre-push-marker.txt" + INPUT_POST_PUSH_HOOK="echo 'should-not-execute' > post-push-marker.txt" + + run git_auto_commit + + assert_success + assert_line "Working tree clean. Nothing to commit." + + # Verify the hooks did not execute + assert [ ! -f "${FAKE_LOCAL_REPOSITORY}/pre-commit-marker.txt" ] + assert [ ! -f "${FAKE_LOCAL_REPOSITORY}/pre-push-marker.txt" ] + assert [ ! -f "${FAKE_LOCAL_REPOSITORY}/post-push-marker.txt" ] +} + +@test "it executes hooks in tag-only mode" { + INPUT_CREATE_GIT_TAG_ONLY=true + INPUT_TAGGING_MESSAGE="v1.0.0" + INPUT_PRE_STATUS_HOOK="echo 'pre-status-tag-only' > pre-status-marker.txt" + INPUT_PRE_PUSH_HOOK="echo 'pre-push-tag-only' > pre-push-marker.txt" + INPUT_POST_PUSH_HOOK="echo 'post-push-tag-only' > post-push-marker.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_status hook" + assert_line "::debug::Executing pre_push hook" + assert_line "::debug::Executing post_push hook" + + # Verify hooks executed + assert [ -f "${FAKE_LOCAL_REPOSITORY}/pre-status-marker.txt" ] + assert [ -f "${FAKE_LOCAL_REPOSITORY}/pre-push-marker.txt" ] + assert [ -f "${FAKE_LOCAL_REPOSITORY}/post-push-marker.txt" ] +} + +@test "it fails when pre_status_hook fails" { + INPUT_PRE_STATUS_HOOK="false" + + run git_auto_commit + + assert_failure + assert_line "::debug::Executing pre_status hook" + assert_line "::error::pre_status hook failed with exit code 1" +} + +@test "it fails when pre_commit_hook fails" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_PRE_COMMIT_HOOK="false" + + run git_auto_commit + + assert_failure + assert_line "::debug::Executing pre_commit hook" + assert_line "::error::pre_commit hook failed with exit code 1" +} + +@test "it fails when pre_push_hook fails" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_PRE_PUSH_HOOK="false" + + run git_auto_commit + + assert_failure + assert_line "::debug::Executing pre_push hook" + assert_line "::error::pre_push hook failed with exit code 1" +} + +@test "it fails when post_push_hook fails" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_POST_PUSH_HOOK="false" + + run git_auto_commit + + assert_failure + assert_line "::debug::Executing post_push hook" + assert_line "::error::post_push hook failed with exit code 1" +} + +@test "hook can access git commands and repository state" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_PRE_COMMIT_HOOK="git log --oneline | head -1 > git-log-output.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_commit hook" + + # Verify the hook could access git commands + assert [ -f "${FAKE_LOCAL_REPOSITORY}/git-log-output.txt" ] + run cat "${FAKE_LOCAL_REPOSITORY}/git-log-output.txt" + assert_line --partial "Init Remote Repository" +} + +@test "hook can modify files that get included in commit" { + # Create a dummy file to trigger commit process + touch "${FAKE_LOCAL_REPOSITORY}"/new-file.txt + + INPUT_PRE_COMMIT_HOOK="echo 'modified by hook' > hook-modified-file.txt" + + run git_auto_commit + + assert_success + assert_line "::debug::Executing pre_commit hook" + + # Verify the file created by the hook was committed + run git log --name-only -1 + assert_line "hook-modified-file.txt" + assert_line "new-file.txt" +}