Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
ignore:
- dependency-name: "*"
update-types:
- "version-update:semver-major"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
ignore:
- dependency-name: "*"
update-types:
- "version-update:semver-major"
91 changes: 91 additions & 0 deletions .github/workflows/dependabot_auto_merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Auto Merge Dependabot PR

"on":
workflow_run:
workflows: ["CI"]
types: [completed]

jobs:
auto-merge:
if: github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'dependabot/')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write

steps:
- name: Resolve Dependabot PR
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
BRANCH_NAME="${{ github.event.workflow_run.head_branch }}"
PR_PAYLOAD=$(gh pr list --repo "${GITHUB_REPOSITORY}" --state open --head "${BRANCH_NAME}" --json number,headRefOid --jq '.[0] // {}')
PR_NUMBER=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("number", ""))' <<<"${PR_PAYLOAD}")
PR_HEAD_SHA=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("headRefOid", ""))' <<<"${PR_PAYLOAD}")
if [ -z "${PR_NUMBER}" ]; then
echo "No open Dependabot PR found for ${BRANCH_NAME}." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if [ "${PR_HEAD_SHA}" != "${{ github.event.workflow_run.head_sha }}" ]; then
echo "Skipping auto-merge: PR #${PR_NUMBER} head ${PR_HEAD_SHA} does not match completed CI head ${{ github.event.workflow_run.head_sha }}." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
echo "head_sha=${PR_HEAD_SHA}" >> "$GITHUB_OUTPUT"

- name: Evaluate merge eligibility
id: merge_guard
if: steps.pr.outputs.pr_number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
gh pr view "${{ steps.pr.outputs.pr_number }}" --repo "${GITHUB_REPOSITORY}" --json number,isDraft,author,url,body,labels > pr.json
python3 - <<'PY'
import json
import os
from pathlib import Path

pr = json.loads(Path("pr.json").read_text(encoding="utf-8"))
author = (pr.get("author") or {}).get("login")
labels = {item.get("name", "") for item in pr.get("labels", [])}
body = pr.get("body") or ""
is_major = "update-type: version-update:semver-major" in body
dependabot_authors = {"dependabot[bot]", "app/dependabot"}
is_dependabot = author in dependabot_authors and "dependencies" in labels
should_merge = is_dependabot and not pr.get("isDraft") and not is_major
if should_merge:
reason = "ready"
elif is_major:
reason = "major_update"
else:
reason = "not_eligible_dependabot_pr"

summary_lines = [
"## Auto-Merge Gate",
f"- PR: {pr['url']}",
f"- Author: `{author or '<unknown>'}`",
f"- Draft: `{'yes' if pr.get('isDraft') else 'no'}`",
f"- Dependabot label: `{'yes' if 'dependencies' in labels else 'no'}`",
f"- Major update: `{'yes' if is_major else 'no'}`",
f"- Final merge decision: `{'merge' if should_merge else 'skip'}`",
f"- Reason: `{reason}`",
]
Path("pr-summary.md").write_text("\n".join(summary_lines).strip() + "\n", encoding="utf-8")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
print(f"should_merge={'true' if should_merge else 'false'}", file=output)
print(f"reason={reason}", file=output)
PY

- name: Append merge summary
if: steps.pr.outputs.pr_number != ''
run: cat pr-summary.md >> "$GITHUB_STEP_SUMMARY"

- name: Merge Dependabot PR
if: steps.merge_guard.outputs.should_merge == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge "${{ steps.pr.outputs.pr_number }}" --repo "${GITHUB_REPOSITORY}" --rebase --delete-branch --match-head-commit "${{ steps.pr.outputs.head_sha }}"
51 changes: 51 additions & 0 deletions .github/workflows/dependabot_review_request_cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Dismiss Dependabot Review Request

"on":
pull_request_target:
types: [review_requested]

permissions:
contents: read
pull-requests: write

jobs:
dismiss-review-request:
if: >-
(github.event.pull_request.user.login == 'dependabot[bot]' ||
github.event.pull_request.user.login == 'app/dependabot') &&
github.event.requested_reviewer.login == 'Pigbibi'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dismiss Pigbibi review request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REVIEWER: Pigbibi
run: |
set -euo pipefail
current_review_requests() {
gh pr view "${PR_NUMBER}" \
--repo "${GITHUB_REPOSITORY}" \
--json reviewRequests \
--jq '.reviewRequests[].login'
}

review_requests="$(current_review_requests)"
if ! grep -Fqx "${REVIEWER}" <<<"${review_requests}"; then
echo "No review request for ${REVIEWER}; nothing to dismiss." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi

if ! gh api --method DELETE \
"repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/requested_reviewers" \
-f "reviewers[]=${REVIEWER}"; then
review_requests="$(current_review_requests)"
if ! grep -Fqx "${REVIEWER}" <<<"${review_requests}"; then
echo "Review request was removed concurrently; nothing to dismiss." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
exit 1
fi

echo "Dismissed Dependabot review request for ${REVIEWER}." >> "${GITHUB_STEP_SUMMARY}"
Loading