Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/scripts/close-issues-on-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# close-issues-on-dev.sh — close the issues named by pull requests that merged into a branch other than
# dev, once their merge commit is part of dev (see .github/workflows/close-issues-on-dev.yml).
#
# DRY_RUN=1 .github/scripts/close-issues-on-dev.sh # list, close nothing
#
# Needs: gh (authenticated; GH_TOKEN in CI), a checkout with full history, and origin/dev or HEAD on dev.
# LOOKBACK: how many recently merged pull requests to consider (default 300).
set -euo pipefail

DRY_RUN="${DRY_RUN:-0}"
LOOKBACK="${LOOKBACK:-300}"
REPO="${GITHUB_REPOSITORY:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
DEV_REF="${DEV_REF:-HEAD}"
[ "$DEV_REF" = HEAD ] && [ "$(git rev-parse --abbrev-ref HEAD)" != dev ] && DEV_REF=origin/dev

closed=0; pending=0
while IFS=$'\t' read -r pr base merge body64; do
[ -n "$merge" ] || continue
[ "$base" = dev ] && continue # GitHub closes these itself
if ! git merge-base --is-ancestor "$merge" "$DEV_REF" 2>/dev/null; then
pending=$((pending + 1)); continue # merged, but not on dev yet
fi
body=$(printf '%s' "$body64" | base64 --decode)
for n in $(printf '%s\n' "$body" | grep -oiE '\b(close[sd]?|fix(e[sd])?|resolve[sd]?)[[:space:]]*:?[[:space:]]+#[0-9]+' \
| grep -oE '[0-9]+$' | sort -un); do
state=$(gh issue view "$n" --repo "$REPO" --json state -q .state 2>/dev/null || echo MISSING)
[ "$state" = OPEN ] || continue
if [ "$DRY_RUN" = 1 ]; then
echo "would close #$n (pull request #$pr into $base, ${merge:0:9} is on dev)"
else
gh issue close "$n" --repo "$REPO" \
--comment "Closed by #$pr, which merged into \`$base\` and reached \`dev\` in ${merge:0:9}."
echo "closed #$n (pull request #$pr into $base)"
fi
closed=$((closed + 1))
done
done < <(gh pr list --repo "$REPO" --state merged --limit "$LOOKBACK" \
--json number,baseRefName,mergeCommit,body \
-q '.[] | [.number, .baseRefName, (.mergeCommit.oid // ""), (.body // "" | @base64)] | @tsv')

verb=closed; [ "$DRY_RUN" = 1 ] && verb="to close"
echo "issues $verb: $closed; merged pull requests not on dev yet: $pending"
44 changes: 44 additions & 0 deletions .github/workflows/close-issues-on-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ─────────────────────────────────────────────────────────────────────────────
# A pull request that merges into a release or integration branch (0.2.1, a
# perf branch) never closes the issues it names: GitHub acts on "Closes #N" only
# when the merge lands on the default branch. This closes them when that work
# reaches `dev`.
#
# On every push to dev it looks at pull requests merged into any other branch,
# keeps those whose merge commit is now part of dev, and closes each open issue
# their description closes ("Closes", "Fixes", "Resolves" #N), with a comment
# naming the pull request. "Part of #N" and "Refs #N" are not closing words, so
# umbrella issues stay open.
# ─────────────────────────────────────────────────────────────────────────────
name: close-issues-on-dev

on:
push:
branches: [dev]
workflow_dispatch:
inputs:
dry_run:
description: list what would be closed, close nothing
type: boolean
default: true

permissions:
contents: read
issues: write
pull-requests: read

jobs:
close:
name: close issues whose pull request reached dev
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: close the issues
env:
GH_TOKEN: ${{ github.token }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '1' || '0' }}
run: .github/scripts/close-issues-on-dev.sh
Loading