diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 695a05c7..e3a396e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -766,6 +766,80 @@ jobs: git push --force --delete origin "${TO_DELETE}" env: TO_DELETE: ${{ steps.setup-test-branch.outputs.branch-name }} + + test-allow-empty: # verify allow-empty: true creates a commit even when there are no file changes + runs-on: ubuntu-latest + needs: [check-not-fork] + permissions: + contents: write + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: true + - name: Setup test branch + id: setup-test-branch + run: | + BRANCH_NAME="test_allow-empty-$(date +%s)" + + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + git checkout -b $BRANCH_NAME + git push --set-upstream origin $BRANCH_NAME + + # output status here to manually verify there are no file changes + git status --porcelain=v2 --branch --untracked-files=no + + PARENT_SHA=$(git rev-parse HEAD) + echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT + echo "parent-sha=$PARENT_SHA" >> $GITHUB_OUTPUT + - uses: ./ + id: test-action + with: + token: ${{ github.token }} + allow-empty: true + commit-message: ${{ steps.setup-test-branch.outputs.branch-name }} + - name: Validate empty commit was created + run: | + if [[ -z "${RESPONSE}" ]]; then + echo "Error: commit-response is empty; expected a commit to be created." + exit 1 + fi + + changedFilesIfAvailable=$(echo "${RESPONSE}" | jq -r '.data.createCommitOnBranch.commit.changedFilesIfAvailable') + if [[ "$changedFilesIfAvailable" -ne 0 ]]; then + echo "Error: changedFilesIfAvailable expected 0 but got $changedFilesIfAvailable." + exit 1 + fi + + # Fetch the branch from the remote and confirm the commit is truly empty + # (its tree SHA equals its parent's tree SHA) and that HEAD advanced. + git fetch origin "${BRANCH}" + NEW_HEAD=$(git rev-parse "origin/${BRANCH}") + if [[ "$NEW_HEAD" == "${PARENT_SHA}" ]]; then + echo "Error: branch HEAD did not advance; no commit was created." + exit 1 + fi + + NEW_TREE=$(git rev-parse "origin/${BRANCH}^{tree}") + PARENT_TREE=$(git rev-parse "origin/${BRANCH}~^{tree}") + if [[ "$NEW_TREE" != "$PARENT_TREE" ]]; then + echo "Error: new commit tree ($NEW_TREE) differs from parent tree ($PARENT_TREE); commit is not empty." + exit 1 + fi + + echo "Validation passed: empty commit created on top of ${PARENT_SHA}." + env: + RESPONSE: ${{ steps.test-action.outputs.commit-response }} + BRANCH: ${{ steps.setup-test-branch.outputs.branch-name }} + PARENT_SHA: ${{ steps.setup-test-branch.outputs.parent-sha }} + - name: Delete test branch + if: ${{ always() }} + run: | + git push --force --delete origin "${TO_DELETE}" + env: + TO_DELETE: ${{ steps.setup-test-branch.outputs.branch-name }} + test-persist-credentials-false-branch-on-remote: runs-on: ubuntu-latest needs: [check-not-fork] diff --git a/action.yml b/action.yml index a5f79326..d29f0a15 100644 --- a/action.yml +++ b/action.yml @@ -16,7 +16,11 @@ inputs: default: 'false' success-if-no-changes: required: true - description: 'Whether to return success if no changes are detected: true/false' + description: 'Whether to return success if no changes are detected. Has no effect when allow-empty is true: true/false' + default: 'false' + allow-empty: + required: true + description: 'Whether to create an empty commit when there are no changes (supersedes success-if-no-changes): true/false' default: 'false' token: required: true @@ -129,13 +133,17 @@ runs: done if [[ "$additions" == "" && "$deletions" == "" ]]; then - echo "No changes to commit" - echo "any_changed=false" >> $GITHUB_OUTPUT - - if [[ "${SUCCESS_IF_NO_CHANGES}" == "true" ]]; then - exit 0 + if [[ "${ALLOW_EMPTY}" == "true" ]]; then + echo "No changes detected; creating an empty commit (allow-empty=true)" else - exit 1 + echo "No changes to commit" + echo "any_changed=false" >> $GITHUB_OUTPUT + + if [[ "${SUCCESS_IF_NO_CHANGES}" == "true" ]]; then + exit 0 + else + exit 1 + fi fi fi @@ -153,6 +161,7 @@ runs: echo "deletions=$deletions" >> $GITHUB_OUTPUT env: SUCCESS_IF_NO_CHANGES: ${{ inputs.success-if-no-changes }} + ALLOW_EMPTY: ${{ inputs.allow-empty }} - name: Commit changes if: ${{ steps.additions-and-deletions.outputs.any_changed == 'true' }} diff --git a/docs/README.tmpl b/docs/README.tmpl index d2364962..b6bde542 100644 --- a/docs/README.tmpl +++ b/docs/README.tmpl @@ -22,6 +22,8 @@ instance, if you create a branch via `git checkout -b my-test-branch` in one of commit-message: "" # Commit message defaults to "Commit performed by grafana/github-api-commit-action" create-branch-on-remote: true | false # Whether to create the branch on the remote if it doesn't exist: Defaults to false stage-all-files: true | false # Whether to additionally stage any changed files in the checkout. Defaults to false + allow-empty: true | false # Whether to create an empty commit when there are no changes (supersedes success-if-no-changes). Defaults to false + success-if-no-changes: true | false # Whether to return success if no changes are detected. Has no effect when allow-empty is true. Defaults to false token: ${{ github.token }} # Token you want to authenticate with ``` @@ -40,6 +42,8 @@ instance, if you create a branch via `git checkout -b my-test-branch` in one of commit-message: "" # Commit message defaults to "Commit performed by grafana/github-api-commit-action" create-branch-on-remote: true | false # Whether to create the branch on the remote if it doesn't exist already: Defaults to false stage-all-files: true | false # Whether to additionally stage any changed files in the checkout. Defaults to false + allow-empty: true | false # Whether to create an empty commit when there are no changes (supersedes success-if-no-changes). Defaults to false + success-if-no-changes: true | false # Whether to return success if no changes are detected. Has no effect when allow-empty is true. Defaults to false token: ${{ steps.get_installation_token.outputs.token }} # Token you want to authenticate with ```