Skip to content

Rebase Dependabot

Rebase Dependabot #279

name: Rebase Dependabot
# After a merge, strict required checks leave other Dependabot PRs BEHIND.
# Dependabot rebase-strategy: auto is slow, so auto-merge sits armed forever.
# Replay each behind branch onto origin/main. Do not comment
# @dependabot rebase: github-actions[bot] and the auto-approve App are
# both rejected ("only users with push access", #1508 / #1509).
#
# Push the rebase with GITHUB_TOKEN (not the App). require_last_push_approval
# dismisses the last pusher's review. If the App pushed, it could not approve
# (#1520). That GITHUB_TOKEN push leaves CI/Security/Auto-approve at
# action_required with zero jobs (#1516 / #1522). The following step uses
# the App token to approve those stubs and re-approve the PR.
on:
push:
branches: [main]
schedule:
- cron: "17 * * * *"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
permissions:
contents: read
jobs:
rebase:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
pull-requests: write
steps:
- name: Harden runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
# App is not last pusher (rebase uses GITHUB_TOKEN). That is required
# so this token can approve the PR under require_last_push_approval
# and start action_required stub runs (#1516 / #1522).
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
continue-on-error: true
with:
client-id: ${{ vars.AUTO_APPROVE_CLIENT_ID }}
private-key: ${{ secrets.AUTO_APPROVE_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
permission-workflows: write
- name: Rebase Dependabot PRs that are behind main
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
nums=$(gh pr list --repo "${REPO}" --author "app/dependabot" --state open \
--json number,mergeStateStatus \
--jq '[.[] | select(.mergeStateStatus == "BEHIND") | .number] | .[]')
if [ -z "${nums}" ]; then
echo "No Dependabot PRs behind main."
exit 0
fi
git fetch origin main
remote_url="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"
for n in ${nums}; do
branch=$(gh pr view "${n}" --repo "${REPO}" --json headRefName --jq .headRefName)
echo "Rebasing #${n} (${branch}) onto origin/main"
# Named-remote lease needs a tracking SHA. Pushing to a URL has none,
# so bare --force-with-lease reports stale info and aborts (#1513).
git fetch origin "refs/heads/${branch}:refs/remotes/origin/${branch}"
expected=$(git rev-parse "origin/${branch}")
git checkout -B "${branch}" "origin/${branch}"
if ! git rebase origin/main; then
echo "Conflict on #${n}; leaving it for a human."
git rebase --abort
continue
fi
if ! git push --force-with-lease="refs/heads/${branch}:${expected}" \
"${remote_url}" "HEAD:refs/heads/${branch}"; then
echo "Lease rejected on #${n}; refetch and retry once."
git fetch origin "refs/heads/${branch}:refs/remotes/origin/${branch}"
expected=$(git rev-parse "origin/${branch}")
git checkout -B "${branch}" "origin/${branch}"
if ! git rebase origin/main; then
echo "Conflict on #${n} after retry; leaving it for a human."
git rebase --abort
continue
fi
git push --force-with-lease="refs/heads/${branch}:${expected}" \
"${remote_url}" "HEAD:refs/heads/${branch}"
fi
done
- name: Start CI and re-approve after GITHUB_TOKEN rebase
if: steps.app-token.outputs.token != ''
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# GITHUB_TOKEN rebase leaves pull_request runs at action_required
# with zero jobs. User tokens 404 on run-approve; App token works
# (start-cleanup-pr-ci.sh). App is not last pusher, so it can also
# satisfy require_last_push_approval. Only touch PRs that already
# have auto-merge armed (skip unreviewed majors).
echo "PLAN: unstick Dependabot PRs with action_required stubs"
for _try in 1 2 3 4 5; do
found=0
nums=$(gh pr list --repo "${REPO}" --author "app/dependabot" \
--state open --json number,autoMergeRequest \
--jq '.[] | select(.autoMergeRequest != null) | .number')
for n in ${nums}; do
branch=$(gh pr view "${n}" --repo "${REPO}" --json headRefName \
--jq .headRefName)
ids=$(gh run list --repo "${REPO}" --branch "${branch}" --limit 15 \
--json databaseId,conclusion \
--jq '.[] | select(.conclusion == "action_required") | .databaseId')
if [ -z "${ids}" ]; then
continue
fi
found=1
echo "DO: approve stubs on #${n}"
while read -r id; do
[ -z "${id}" ] && continue
gh api -X POST "repos/${REPO}/actions/runs/${id}/approve" \
>/dev/null && echo "OK: approved run ${id}" \
|| echo "FAIL: approve ${id} (continuing)"
done <<<"${ids}"
gh pr review --approve "${n}" --repo "${REPO}" \
|| echo "Approve #${n} failed or already approved"
gh pr merge --auto --squash "${n}" --repo "${REPO}" \
|| echo "auto-merge already armed or blocked on #${n}"
done
if [ "${found}" -eq 0 ]; then
echo "OK: no action_required Dependabot stubs"
break
fi
sleep 4
done
echo "DONE: Dependabot unstick pass"