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 "[email protected] "
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