-
Notifications
You must be signed in to change notification settings - Fork 1
200 lines (172 loc) · 7.87 KB
/
Copy pathauto-rebase.yml
File metadata and controls
200 lines (172 loc) · 7.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
name: Auto Rebase PRs
on:
push:
branches: [master]
workflow_dispatch:
inputs:
pr_number:
description: 'Specific PR number to rebase (leave empty for all open PRs)'
required: false
default: ''
jobs:
rebase:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout master
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Configure git and GitHub auth
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
gh auth setup-git
- name: Rebase open PRs onto master
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER_INPUT: ${{ inputs.pr_number }}
run: |
set -euo pipefail
MASTER_SHA=$(git rev-parse HEAD)
echo "Master HEAD: $MASTER_SHA"
# Determine which PRs to process
if [ -n "${PR_NUMBER_INPUT:-}" ]; then
PR_LIST="${PR_NUMBER_INPUT}"
else
PR_LIST=$(gh pr list --state open --json number --jq '.[].number' | tr '\n' ' ')
fi
echo "PRs to process: $PR_LIST"
for PR_NUMBER in $PR_LIST; do
echo ""
echo "=========================================="
echo "Processing PR #$PR_NUMBER"
echo "=========================================="
# Get PR details
PR_JSON=$(gh pr view "$PR_NUMBER" --json number,headRefName,headRepository,maintainerCanModify,baseRefName 2>/dev/null) || {
echo "Failed to get PR #$PR_NUMBER details, skipping"
continue
}
HEAD_BRANCH=$(echo "$PR_JSON" | jq -r '.headRefName')
FORK_REPO=$(echo "$PR_JSON" | jq -r '.headRepository.nameWithOwner')
MAINTAINER_CAN_MODIFY=$(echo "$PR_JSON" | jq -r '.maintainerCanModify')
BASE_REF=$(echo "$PR_JSON" | jq -r '.baseRefName')
echo " Branch: $HEAD_BRANCH"
echo " Fork: $FORK_REPO"
echo " Base: $BASE_REF"
echo " Maintainer can modify: $MAINTAINER_CAN_MODIFY"
if [ "$MAINTAINER_CAN_MODIFY" != "true" ]; then
echo " Skipping: maintainerCanModify is not enabled"
continue
fi
if [ "$BASE_REF" != "master" ]; then
echo " Skipping: base branch is '$BASE_REF', not master"
continue
fi
# Fetch the PR head
git fetch origin "refs/pull/$PR_NUMBER/head:pr-temp-$PR_NUMBER" 2>/dev/null || {
echo " Failed to fetch PR #$PR_NUMBER, skipping"
continue
}
# Check if rebase is needed
MERGE_BASE=$(git merge-base "pr-temp-$PR_NUMBER" HEAD)
if [ "$MERGE_BASE" = "$MASTER_SHA" ]; then
echo " Already up to date, skipping"
git branch -D "pr-temp-$PR_NUMBER" 2>/dev/null || true
continue
fi
echo " Merge base: ${MERGE_BASE:0:8} (behind master, rebasing...)"
# Create a working branch for the rebase
git checkout -b "rebase-work-$PR_NUMBER" "pr-temp-$PR_NUMBER"
REBASE_SUCCESS=false
CONFLICT_FILES=""
# Attempt rebase — prefer PR changes on conflict (-X theirs)
if git rebase -X theirs master 2>&1; then
REBASE_SUCCESS=true
echo " Rebase succeeded (auto-resolved conflicts with PR changes)"
else
echo " Rebase failed, checking conflict types..."
CONFLICT_FILES=$(git diff --name-only --diff-filter=U 2>/dev/null || true)
echo " Conflicting files: $CONFLICT_FILES"
# Resolve known auto-resolvable conflicts
RESOLVED=true
for file in $CONFLICT_FILES; do
case "$file" in
pnpm-lock.yaml)
echo " Resolving pnpm-lock.yaml by regenerating..."
git checkout --theirs pnpm-lock.yaml 2>/dev/null || true
git add pnpm-lock.yaml
;;
package-lock.json|yarn.lock|bun.lockb)
echo " Resolving lockfile $file by taking PR version..."
git checkout --theirs "$file" 2>/dev/null || true
git add "$file"
;;
*)
echo " Cannot auto-resolve: $file"
RESOLVED=false
;;
esac
done
if $RESOLVED; then
if GIT_EDITOR=true git rebase --continue 2>&1; then
REBASE_SUCCESS=true
else
echo " Rebase --continue failed even after resolving known conflicts"
git rebase --abort 2>/dev/null || true
REBASE_SUCCESS=false
fi
else
echo " Unresolvable conflicts remain, aborting rebase"
git rebase --abort 2>/dev/null || true
fi
fi
if $REBASE_SUCCESS; then
# Regenerate pnpm-lock.yaml if package.json changed
if git diff master..HEAD --name-only | grep -q "package.json"; then
echo " package.json changed, regenerating pnpm-lock.yaml..."
pnpm install --no-frozen-lockfile 2>/dev/null || true
if ! git diff --quiet pnpm-lock.yaml 2>/dev/null; then
git add pnpm-lock.yaml
git commit --amend --no-edit 2>/dev/null || git commit -m "chore: regenerate pnpm-lock.yaml after rebase" 2>/dev/null || true
fi
fi
echo " Pushing rebased branch to $FORK_REPO/$HEAD_BRANCH..."
if git push "https://github.com/${FORK_REPO}.git" "HEAD:refs/heads/$HEAD_BRANCH" --force-with-lease 2>&1; then
echo " ✅ PR #$PR_NUMBER successfully rebased onto master"
gh pr comment "$PR_NUMBER" \
--body "🤖 **Auto-rebase:** This branch has been automatically rebased onto \`master\` by the CI bot. No conflicts were detected." \
2>/dev/null || true
else
echo " ❌ Push to fork failed for PR #$PR_NUMBER (fork may not allow maintainer pushes)"
gh pr comment "$PR_NUMBER" \
--body "🤖 **Auto-rebase:** The branch was rebased successfully locally but could not be pushed to the fork. Please enable **'Allow edits from maintainers'** in the PR settings, or rebase manually: \`git fetch upstream master && git rebase upstream/master\`." \
2>/dev/null || true
fi
else
echo " ❌ PR #$PR_NUMBER has unresolvable conflicts"
CONFLICT_LIST=$(echo "$CONFLICT_FILES" | tr '\n' ',' | sed 's/,$//')
gh pr comment "$PR_NUMBER" \
--body "🤖 **Auto-rebase failed:** This branch has conflicts with \`master\` that cannot be resolved automatically. Conflicting files: \`${CONFLICT_LIST}\`. Please rebase manually: \`git fetch upstream master && git rebase upstream/master\`." \
2>/dev/null || true
fi
# Cleanup
git checkout master 2>/dev/null || git checkout -
git branch -D "rebase-work-$PR_NUMBER" 2>/dev/null || true
git branch -D "pr-temp-$PR_NUMBER" 2>/dev/null || true
done
echo ""
echo "Done processing all PRs."