diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5db0ec..41ccb4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,58 @@ on: workflow_dispatch: jobs: + Changes: + runs-on: ubuntu-latest + outputs: + run_ci: ${{ steps.changes.outputs.run_ci }} + steps: + - name: 📥 checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: 🔎 detect CI-relevant changes + id: changes + env: + BEFORE_SHA: ${{ github.event.before }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + if [[ "$GITHUB_EVENT_NAME" == "push" && \ + "$GITHUB_REF_NAME" == "$DEFAULT_BRANCH" && \ + -n "$BEFORE_SHA" && ! "$BEFORE_SHA" =~ ^0+$ ]]; then + base_sha="$BEFORE_SHA" + else + git fetch --no-tags origin "$DEFAULT_BRANCH" + base_sha=$(git merge-base "origin/$DEFAULT_BRANCH" HEAD) + fi + + run_ci=false + while IFS= read -r -d '' changed_file; do + case "$changed_file" in + *.py|uv.lock|tox.ini|.github/uv/*|.github/tox-uv/*|.github/workflows/ci.yml) + run_ci=true + ;; + pyproject.toml) + non_version_changes=$(git diff --unified=0 "$base_sha" HEAD -- pyproject.toml | + awk ' + /^[+-]/ && !/^(---|\+\+\+)/ { + line = substr($0, 2) + if (line !~ /^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$/) { + print + } + } + ') + if [[ -n "$non_version_changes" ]]; then + run_ci=true + fi + ;; + esac + done < <(git diff --name-only -z "$base_sha" HEAD) + + echo "run_ci=$run_ci" >> "$GITHUB_OUTPUT" + Tests: + needs: Changes + if: needs.Changes.outputs.run_ci == 'true' strategy: matrix: tox_env: [fastapi-pre121, fastapi-post121, sqlmodel] @@ -25,6 +76,8 @@ jobs: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} Ruff: + needs: Changes + if: needs.Changes.outputs.run_ci == 'true' runs-on: ubuntu-latest steps: - name: 📥 checkout @@ -33,3 +86,31 @@ jobs: uses: ./.github/uv - name: 🐶 ruff run: uv run ruff check + + CI: + name: CI gate + needs: [Changes, Tests, Ruff] + if: always() + runs-on: ubuntu-latest + steps: + - name: ✅ verify CI results + env: + CHANGES_RESULT: ${{ needs.Changes.result }} + RUFF_RESULT: ${{ needs.Ruff.result }} + RUN_CI: ${{ needs.Changes.outputs.run_ci }} + TESTS_RESULT: ${{ needs.Tests.result }} + run: | + if [[ "$CHANGES_RESULT" != "success" ]]; then + echo "Change detection concluded: $CHANGES_RESULT" >&2 + exit 1 + fi + + if [[ "$RUN_CI" == "true" ]]; then + if [[ "$TESTS_RESULT" != "success" || "$RUFF_RESULT" != "success" ]]; then + echo "Tests concluded: $TESTS_RESULT; Ruff concluded: $RUFF_RESULT" >&2 + exit 1 + fi + elif [[ "$TESTS_RESULT" != "skipped" || "$RUFF_RESULT" != "skipped" ]]; then + echo "Unexpected skipped-check results: Tests=$TESTS_RESULT; Ruff=$RUFF_RESULT" >&2 + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb3b068..9808640 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,7 @@ jobs: contents: write id-token: write pull-requests: write + statuses: write steps: - name: 📥 checkout uses: actions/checkout@v7 @@ -64,6 +65,24 @@ jobs: fi gh run watch "$ci_run_id" --exit-status + ci_run_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${ci_run_id}" + successful_jobs=$(gh run view "$ci_run_id" \ + --json jobs \ + --jq '.jobs[] | select(.conclusion == "success") | .name') + if [[ -z "$successful_jobs" ]]; then + echo "CI run $ci_run_id had no successful jobs" >&2 + exit 1 + fi + while IFS= read -r context; do + gh api \ + --method POST \ + "repos/${GITHUB_REPOSITORY}/statuses/${release_sha}" \ + -f state=success \ + -f context="$context" \ + -f description="Passed in CI run ${ci_run_id}" \ + -f target_url="$ci_run_url" >/dev/null + done <<< "$successful_jobs" + merge_response=$(gh api \ --method PUT \ "repos/${GITHUB_REPOSITORY}/pulls/${release_pr_number}/merge" \