Skip to content

Commit 9ed45fb

Browse files
Auto approve dependabot (#36)
* update dependabot job Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com> * auto apporve dependabot PRs Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3dc1837 commit 9ed45fb

2 files changed

Lines changed: 212 additions & 0 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Auto Merge Dependabot PRs
2+
3+
on:
4+
schedule:
5+
# Run daily at 04:00 UTC since dependabot runs at 03:00 UTC
6+
- cron: '0 4 * * *'
7+
workflow_dispatch: # Allow manual trigger
8+
9+
# This workflow uses a GitHub App token to approve and merge Dependabot PRs
10+
# The token is created using the `actions/create-github-app-token` action
11+
# The token is used so that the updates are made by the GitHub App instead of GitHub Actions
12+
# and will show up as such in the PR comments and history
13+
# In addition, the token is scoped to only the permissions needed for this workflow
14+
# see https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow for details
15+
16+
jobs:
17+
auto-merge-dependabot:
18+
permissions:
19+
contents: read
20+
runs-on: ubuntu-latest
21+
steps:
22+
23+
# Gets the GitHub App token
24+
- uses: actions/create-github-app-token@v3
25+
id: get-app-token
26+
with:
27+
# required
28+
app-id: ${{ secrets.DEPENDABOT_APP_ID }}
29+
private-key: ${{ secrets.DEPENDABOT_APP_KEY }}
30+
permission-pull-requests: write
31+
permission-contents: write
32+
permission-checks: read
33+
permission-actions: read
34+
35+
- name: Checkout code
36+
uses: actions/checkout@v6
37+
with:
38+
token: ${{ steps.get-app-token.outputs.token }}
39+
persist-credentials: false
40+
41+
- name: Setup GitHub CLI
42+
run: |
43+
# GitHub CLI is pre-installed on GitHub-hosted runners
44+
gh --version
45+
46+
- name: Make script executable
47+
run: chmod +x ./scripts/auto-approve-dependabot.sh
48+
49+
- name: Run auto approve script
50+
env:
51+
GITHUB_TOKEN: ${{ steps.get-app-token.outputs.token }}
52+
run: ./scripts/auto-approve-dependabot.sh ${{ github.repository }}

scripts/auto-approve-dependabot.sh

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/bin/bash
2+
set -e
3+
set -o pipefail
4+
5+
# This script checks for open PRs from dependabot that have all checks passing and have not been
6+
# modified by another user, and approves+merges them automatically.
7+
# To be run as a GitHub action.
8+
9+
# Check if repository argument is provided
10+
if [ -z "$1" ]; then
11+
echo "Error: Repository name not provided."
12+
echo "Usage: $0 <owner/repo>"
13+
echo "Example: $0 hyperlight-dev/hyperlight"
14+
exit 1
15+
fi
16+
17+
REPO="$1"
18+
echo "Checking for open Dependabot PRs to approve and merge in $REPO..."
19+
20+
# Get all open PRs from dependabot
21+
# We filter so that only PRs that are not from forks and are in branches starting with "dependabot/cargo" are included.
22+
dependabot_prs=$(gh pr list -R "$REPO" --author "dependabot[bot]" --state open --json number,title,reviews,headRepositoryOwner,headRefName | jq --arg repo_owner "$(echo "$REPO" | cut -d'/' -f1)" '[.[] | select(.headRepositoryOwner.login == $repo_owner and (.headRefName | startswith("dependabot/cargo")))]')
23+
# Exit early if no PRs found
24+
if [ -z "$dependabot_prs" ] || [ "$dependabot_prs" = "[]" ]; then
25+
echo "No open Dependabot PRs found in $REPO"
26+
exit 0
27+
fi
28+
29+
# Count how many PRs we found
30+
pr_count=$(echo "$dependabot_prs" | jq 'length')
31+
echo "Found $pr_count open Dependabot PRs in $REPO"
32+
33+
# Process each PR
34+
echo "$dependabot_prs" | jq -c '.[]' | while read -r pr; do
35+
pr_number=$(echo "$pr" | jq -r '.number')
36+
pr_title=$(echo "$pr" | jq -r '.title')
37+
38+
echo "Processing PR #$pr_number: $pr_title"
39+
40+
# Check if PR only modifies allowed files
41+
pr_files=$(gh pr view "$pr_number" -R "$REPO" --json files)
42+
invalid_files=$(echo "$pr_files" | jq -r '.files[].path' | grep -v -E '(Cargo\.toml|Cargo\.lock)' || true)
43+
44+
if [ -n "$invalid_files" ]; then
45+
echo " ❌ PR #$pr_number modifies files that are not allowed for auto-merge:"
46+
printf '%s\n' "$invalid_files" | sed 's/^/ - /'
47+
echo " ℹ️ Only changes to Cargo.toml and Cargo.lock are allowed"
48+
continue
49+
fi
50+
51+
echo " ✅ PR #$pr_number only modifies allowed files (Cargo.toml and Cargo.lock)"
52+
53+
# First, get detailed PR information including all checks
54+
pr_details=$(gh pr view "$pr_number" -R "$REPO" --json statusCheckRollup,state)
55+
56+
# Check if all status checks have passed (regardless of required or not)
57+
all_checks_pass=true
58+
has_pending_checks=false
59+
failed_checks=""
60+
61+
# First identify checks that are still in progress
62+
pending_checks=$(echo "$pr_details" | jq -r '.statusCheckRollup[] | select(.status == "IN_PROGRESS" or .status == "QUEUED" or .status == "PENDING") | .name')
63+
64+
# Check for permission-required checks
65+
permission_required_checks=$(echo "$pr_details" | jq -r '.statusCheckRollup[] | select(.status == "WAITING" or .status == "ACTION_REQUIRED" or (.status == "QUEUED" and .conclusion == null and .detailsUrl != null and (.detailsUrl | contains("waiting-for-approval")))) | .name')
66+
67+
# Don't approve if there are checks required that need permission to run
68+
if [ -n "$permission_required_checks" ]; then
69+
echo " 🔐 PR #$pr_number has checks waiting for permission:"
70+
echo "$permission_required_checks" | sed 's/^/ - /'
71+
echo " ❌ Skipping auto-approval due to permission-required checks"
72+
continue
73+
fi
74+
75+
if [ -n "$pending_checks" ]; then
76+
echo " ⏳ PR #$pr_number has pending checks:"
77+
echo "$pending_checks" | sed 's/^/ - /'
78+
echo " ℹ️ We will still approve the PR so it can merge automatically once all checks pass"
79+
has_pending_checks=true
80+
fi
81+
82+
# Check for failed checks - only include checks that have a conclusion and are not still running
83+
# Explicitly exclude checks with status IN_PROGRESS, QUEUED, or PENDING
84+
failed_checks=$(echo "$pr_details" | jq -r '.statusCheckRollup[] |
85+
select(.conclusion != null and
86+
.conclusion != "SUCCESS" and
87+
.conclusion != "NEUTRAL" and
88+
.conclusion != "SKIPPED" and
89+
.status != "IN_PROGRESS" and
90+
.status != "QUEUED" and
91+
.status != "PENDING") | .name')
92+
93+
if [ -n "$failed_checks" ]; then
94+
echo " ❌ PR #$pr_number has failed checks:"
95+
echo "$failed_checks" | sed 's/^/ - /'
96+
all_checks_pass=false
97+
continue
98+
fi
99+
100+
# If we've reached here, either all checks have passed or some are pending
101+
if [ "$has_pending_checks" = false ]; then
102+
echo " ✅ All status checks passed for PR #$pr_number"
103+
fi
104+
105+
# Check if PR has been modified by someone other than dependabot
106+
pr_commits=$(gh pr view "$pr_number" -R "$REPO" --json commits)
107+
non_dependabot_authors=$(echo "$pr_commits" | jq -r '.commits[].authors[].login' | grep -v -e "dependabot\[bot\]" -e "^$" || true)
108+
109+
if [ -n "$non_dependabot_authors" ]; then
110+
echo " ❌ PR #$pr_number has been modified by users other than dependabot: $non_dependabot_authors"
111+
continue
112+
fi
113+
114+
# Check if PR needs approval (i.e., hasn't been approved already)
115+
already_approved=$(echo "$pr" | jq -r '.reviews[] | select(.state == "APPROVED") | .state' | grep -c "APPROVED" || true)
116+
117+
if [ "$already_approved" -eq 0 ]; then
118+
echo " ✅ Approving PR #$pr_number"
119+
gh pr review "$pr_number" -R "$REPO" --approve -b "Automatically approved by dependabot auto-approve workflow"
120+
else
121+
echo " ℹ️ PR #$pr_number is already approved"
122+
fi
123+
124+
if [ "$has_pending_checks" = true ] || [ "$all_checks_pass" = true ]; then
125+
# Check if PR is draft and whether it is up-to-date with base branch
126+
pr_merge_info=$(gh pr view "$pr_number" -R "$REPO" --json isDraft,mergeStateStatus)
127+
is_draft=$(echo "$pr_merge_info" | jq -r '.isDraft')
128+
merge_status=$(echo "$pr_merge_info" | jq -r '.mergeStateStatus')
129+
130+
if [ "$is_draft" = "true" ]; then
131+
echo " ⚠️ PR #$pr_number is a draft PR; skipping merge"
132+
continue
133+
fi
134+
135+
if [ "$merge_status" != "CLEAN" ]; then
136+
echo " ⚠️ PR #$pr_number is not up to date (status: $merge_status)"
137+
# Enable auto-merge to merge once checks pass
138+
echo " ✅ Enabling auto-merge (squash strategy) for PR #$pr_number"
139+
if gh pr merge "$pr_number" -R "$REPO" --auto --squash; then
140+
echo " ✅ Auto-merge enabled for PR #$pr_number"
141+
else
142+
echo " ⚠️ Failed to enable auto-merge for PR #$pr_number; continuing to next PR"
143+
continue
144+
fi
145+
else
146+
echo " ✅ PR #$pr_number is up to date with base branch"
147+
# PR is already clean/mergeable - merge directly instead of enabling auto-merge
148+
echo " ✅ Merging PR #$pr_number directly (squash strategy)"
149+
if gh pr merge "$pr_number" -R "$REPO" --squash; then
150+
echo " ✅ PR #$pr_number merged successfully"
151+
else
152+
echo " ⚠️ Failed to merge PR #$pr_number; continuing to next PR"
153+
continue
154+
fi
155+
fi
156+
fi
157+
158+
done
159+
160+
echo "Finished processing Dependabot PRs for $REPO"

0 commit comments

Comments
 (0)