1+ # ------------------------------------------------------------------------------
2+ # Scheduled Dependabot PRs Auto-Merge Workflow
3+ #
4+ # Purpose:
5+ # - Automatically detect, rebase (if needed), and merge Dependabot PRs targeting
6+ # the `dependabotchanges` branch, supporting different merge strategies.
7+ #
8+ # Features:
9+ # ✅ Filters PRs authored by Dependabot and targets the specific base branch
10+ # ✅ Rebases PRs with conflicts and auto-resolves using "prefer-theirs" strategy
11+ # ✅ Attempts all three merge strategies: merge, squash, rebase (first success wins)
12+ # ✅ Handles errors gracefully, logs clearly
13+ #
14+ # Triggers:
15+ # - Scheduled daily run (midnight UTC)
16+ # - Manual trigger (via GitHub UI)
17+ #
18+ # Required Permissions:
19+ # - contents: write
20+ # - pull-requests: write
21+ # ------------------------------------------------------------------------------
22+
23+ name : Scheduled Dependabot PRs Auto-Merge
24+
25+ on :
26+ schedule :
27+ - cron : ' 0 0 * * *' # Runs once a day at midnight UTC
28+ workflow_dispatch :
29+
30+ permissions :
31+ contents : write
32+ pull-requests : write
33+
34+ jobs :
35+ merge-dependabot :
36+ runs-on : ubuntu-latest
37+ steps :
38+ - name : Checkout repository
39+ uses : actions/checkout@v4
40+
41+ - name : Install GitHub CLI
42+ run : |
43+ sudo apt update
44+ sudo apt install -y gh
45+ - name : Fetch & Filter Dependabot PRs
46+ env :
47+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
48+ run : |
49+ echo "🔍 Fetching all Dependabot PRs targeting 'dependabotchanges'..."
50+ > matched_prs.txt
51+ pr_batch=$(gh pr list --state open --json number,title,author,baseRefName,url \
52+ --jq '.[] | "\(.number)|\(.title)|\(.author.login)|\(.baseRefName)|\(.url)"')
53+ while IFS='|' read -r number title author base url; do
54+ author=$(echo "$author" | xargs)
55+ base=$(echo "$base" | xargs)
56+ if [[ "$author" == "app/dependabot" && "$base" == "dependabotchanges" ]]; then
57+ echo "$url" >> matched_prs.txt
58+ echo "✅ Matched PR #$number - $title"
59+ else
60+ echo "❌ Skipped PR #$number - $title (Author: $author, Base: $base)"
61+ fi
62+ done <<< "$pr_batch"
63+ echo "👉 Matched PRs:"
64+ cat matched_prs.txt || echo "None"
65+ - name : Rebase PR if Conflicts Exist
66+ if : success()
67+ env :
68+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
69+ run : |
70+ if [[ ! -s matched_prs.txt ]]; then
71+ echo "⚠️ No matching PRs to process."
72+ exit 0
73+ fi
74+ while IFS= read -r pr_url; do
75+ pr_number=$(basename "$pr_url")
76+ echo "🔁 Checking PR #$pr_number for conflicts..."
77+ mergeable=$(gh pr view "$pr_number" --json mergeable --jq '.mergeable')
78+ if [[ "$mergeable" == "CONFLICTING" ]]; then
79+ echo "⚠️ Merge conflicts detected. Performing manual rebase for PR #$pr_number..."
80+ head_branch=$(gh pr view "$pr_number" --json headRefName --jq '.headRefName')
81+ base_branch=$(gh pr view "$pr_number" --json baseRefName --jq '.baseRefName')
82+ git fetch origin "$base_branch":"$base_branch"
83+ git fetch origin "$head_branch":"$head_branch"
84+ git checkout "$head_branch"
85+ git config user.name "github-actions"
86+ git config user.email "action@github.com"
87+ # Attempt rebase with 'theirs' strategy
88+ if git rebase --strategy=recursive -X theirs "$base_branch"; then
89+ echo "✅ Rebase successful. Pushing..."
90+ git push origin "$head_branch" --force
91+ else
92+ echo "❌ Rebase failed. Aborting..."
93+ git rebase --abort || true
94+ fi
95+ else
96+ echo "✅ PR #$pr_number is mergeable. Skipping rebase."
97+ fi
98+ done < matched_prs.txt
99+
100+ - name : Auto-Merge PRs using available strategy
101+ if : success()
102+ env :
103+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
104+ run : |
105+ if [[ ! -s matched_prs.txt ]]; then
106+ echo "⚠️ No matching PRs to process."
107+ exit 0
108+ fi
109+ while IFS= read -r pr_url; do
110+ pr_number=$(basename "$pr_url")
111+ echo "🔍 Checking mergeability for PR #$pr_number"
112+ attempt=0
113+ max_attempts=8
114+ mergeable=""
115+ sleep 5 # Let GitHub calculate mergeable status
116+ while [[ $attempt -lt $max_attempts ]]; do
117+ mergeable=$(gh pr view "$pr_number" --json mergeable --jq '.mergeable' 2>/dev/null || echo "UNKNOWN")
118+ echo "🔁 Attempt $((attempt+1))/$max_attempts: mergeable=$mergeable"
119+ if [[ "$mergeable" == "MERGEABLE" ]]; then
120+ success=0
121+ for strategy in rebase squash merge; do
122+ echo "🚀 Trying to auto-merge PR #$pr_number using '$strategy' strategy..."
123+ set -x
124+ merge_output=$(gh pr merge --auto --"$strategy" "$pr_url" 2>&1)
125+ merge_status=$?
126+ set +x
127+ echo "$merge_output"
128+ if [[ $merge_status -eq 0 ]]; then
129+ echo "✅ Auto-merge succeeded using '$strategy'."
130+ success=1
131+ break
132+ else
133+ echo "❌ Auto-merge failed using '$strategy'. Trying next strategy..."
134+ fi
135+ done
136+ if [[ $success -eq 0 ]]; then
137+ echo "❌ All merge strategies failed for PR #$pr_number"
138+ fi
139+ break
140+ elif [[ "$mergeable" == "CONFLICTING" ]]; then
141+ echo "❌ Cannot merge due to conflicts. Skipping PR #$pr_number"
142+ break
143+ else
144+ echo "🕒 Waiting for GitHub to determine mergeable status..."
145+ sleep 15
146+ fi
147+ ((attempt++))
148+ done
149+ if [[ "$mergeable" != "MERGEABLE" && "$mergeable" != "CONFLICTING" ]]; then
150+ echo "❌ Mergeability undetermined after $max_attempts attempts. Skipping PR #$pr_number"
151+ fi
152+ done < matched_prs.txt || echo "⚠️ Completed loop with some errors, but continuing gracefully."
0 commit comments