Skip to content

Drop redundant @internal on hasAcceptorTemplateOrLateResolvableParame… #114

Drop redundant @internal on hasAcceptorTemplateOrLateResolvableParame…

Drop redundant @internal on hasAcceptorTemplateOrLateResolvableParame… #114

# Monorepo-style subsplit: replays every commit that touches turbo-ext/ onto
# https://github.com/phpstan/turbo-ext, where the extension is distributed
# standalone (the phpstan/turbo PIE package on Packagist).
#
# Each replayed commit keeps the original message, author, committer and
# dates, and its tree is the commit's turbo-ext/ subtree — plus a
# "Split-From: <monorepo sha>" trailer and a generated VERSION.txt carrying
# the extension version at that commit (the monorepo short SHA the enabler
# pins; builds in the split cannot compute it — replayed commits have
# different SHAs, and PIE tarballs have no git at all). The trailer is what
# makes the replay incremental and idempotent: a run reads the most recent
# trailer from the split repository and replays only the commits made since,
# on top of the split branch's current head. A failed or skipped run is
# simply caught up by the next one (or by a manual workflow_dispatch).
# git subtree split is not used because it walks the entire monorepo history
# on every run, which takes tens of minutes at this repository's size.
#
# Tag pushes (2.2.*) first bring the split branch up to date and then tag
# the split commit replayed from the last turbo-ext-touching commit at or
# before the tagged monorepo commit. Packagist picks the tag up as a
# phpstan/turbo release, and the release workflow in phpstan/phpstan
# attaches the PIE binary assets to a phpstan/turbo-ext release of the same
# name.
#
# When a new maintained branch (2.3.x, ...) starts, update the triggers
# below (like phar.yml) and create the same-named branch in phpstan/turbo-ext
# from the previous branch's head — that head's trailer keeps the replay
# incremental across the branch point.
name: "Subsplit turbo-ext"
on:
workflow_dispatch:
push:
branches:
- "2.2.x"
tags:
- "2.2.*"
# The replay chain builds on the split branch's current head; two runs
# building on the same head would race each other, so queue them instead.
concurrency:
group: subsplit-turbo-ext
cancel-in-progress: false
permissions: {}
jobs:
subsplit:
name: "Replay turbo-ext commits"
runs-on: "ubuntu-latest"
timeout-minutes: 10
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: "Checkout"
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false
fetch-depth: 0 # the replay walks the full history of the branch
- name: "Replay commits touching turbo-ext/ and push them to phpstan/turbo-ext"
env:
PUSH_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }}
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
run: |
PREFIX="turbo-ext"
# A tag lives on its maintained branch (2.2.8 → 2.2.x). The replay
# always follows the branch — a tag run just also marks the split
# commit corresponding to the tagged monorepo commit afterwards.
if [ "$REF_TYPE" = "tag" ]; then
BRANCH="${REF_NAME%.*}.x"
git fetch --no-tags origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH"
replay_tip="$(git rev-parse "refs/remotes/origin/$BRANCH")"
if ! git merge-base --is-ancestor HEAD "$replay_tip"; then
echo "::error::Tag $REF_NAME does not point into branch $BRANCH."
exit 1
fi
else
BRANCH="$REF_NAME"
replay_tip="$(git rev-parse HEAD)"
fi
# The split branch must already exist (see the header comment); the
# fetch fails loudly when it does not.
git fetch --no-tags https://github.com/phpstan/turbo-ext.git "refs/heads/$BRANCH"
target_head="$(git rev-parse FETCH_HEAD)"
# The most recent Split-From trailer marks where the previous
# replay stopped. No trailer anywhere (the split repository only
# has its initial commit) means the full history gets replayed.
last_synced="$(git log FETCH_HEAD --format='%(trailers:key=Split-From,valueonly)' | grep -m1 . || true)"
if [ -n "$last_synced" ] && ! git merge-base --is-ancestor "$last_synced" "$replay_tip"; then
echo "::error::$BRANCH does not contain $last_synced, the most recent Split-From trailer in the split repository. Was the branch history rewritten?"
exit 1
fi
# Every commit touching turbo-ext/ so far sits on the first-parent
# chain (verified July 2026), and first-parent order is append-only,
# so the replay never reorders. A maintained-branch forward merge
# that touches turbo-ext/ replays as the merge commit itself.
mapfile -t commits < <(git rev-list --reverse --first-parent ${last_synced:+"$last_synced.."}"$replay_tip" -- "$PREFIX")
tip="$target_head"
if [ "${#commits[@]}" -gt 0 ]; then
for commit in "${commits[@]}"; do
# The version at this commit, mirroring the Makefile's git
# computation: the last commit touching turbo-ext/src as of
# this commit ("dev" before that layout existed). Commits
# already replayed keep the VERSION.txt committed with them,
# even where an older computation produced a different value.
version="$(git log -1 --format=%H "$commit" -- "$PREFIX/src" | cut -c1-7)"
blob="$(printf '%s\n' "${version:-dev}" | git hash-object -w --stdin)"
# a commit removing the directory altogether replays as an empty tree
if subtree="$(git rev-parse -q --verify "$commit:$PREFIX")"; then
tree="$({ git ls-tree "$subtree" | awk -F'\t' '$2 != "VERSION.txt"'; printf '100644 blob %s\tVERSION.txt\n' "$blob"; } | git mktree)"
else
tree="$(git hash-object -t tree /dev/null)"
fi
tip="$(
git log -1 --format=%B "$commit" \
| git interpret-trailers --trailer "Split-From: $commit" \
| GIT_AUTHOR_NAME="$(git log -1 --format=%an "$commit")" \
GIT_AUTHOR_EMAIL="$(git log -1 --format=%ae "$commit")" \
GIT_AUTHOR_DATE="$(git log -1 --format=%aD "$commit")" \
GIT_COMMITTER_NAME="$(git log -1 --format=%cn "$commit")" \
GIT_COMMITTER_EMAIL="$(git log -1 --format=%ce "$commit")" \
GIT_COMMITTER_DATE="$(git log -1 --format=%cD "$commit")" \
git commit-tree "$tree" -p "$tip"
)"
echo "$commit -> $tip"
done
echo "Replayed ${#commits[@]} commit(s), pushing to $BRANCH"
git push "https://x-access-token:${PUSH_TOKEN}@github.com/phpstan/turbo-ext.git" "$tip:refs/heads/$BRANCH"
else
echo "The split repository is up to date at $target_head."
fi
if [ "$REF_TYPE" != "tag" ]; then
exit 0
fi
# Tag the split commit replayed from the last turbo-ext-touching
# commit at or before the tagged monorepo commit (HEAD on tag runs).
tag_src="$(git rev-list -1 --first-parent HEAD -- "$PREFIX")"
if [ -z "$tag_src" ]; then
echo "::error::No commit touching $PREFIX/ precedes tag $REF_NAME."
exit 1
fi
split_commit="$(git log "$tip" --format='%H%x09%(trailers:key=Split-From,valueonly,separator=)' | awk -F'\t' -v want="$tag_src" '$2 == want {print $1; exit}')"
if [ -z "$split_commit" ]; then
echo "::error::No split commit carries Split-From: $tag_src — was $tag_src replayed?"
exit 1
fi
existing="$(git ls-remote https://github.com/phpstan/turbo-ext.git "refs/tags/$REF_NAME" | cut -f1)"
if [ "$existing" = "$split_commit" ]; then
echo "Tag $REF_NAME already points at $split_commit."
exit 0
fi
if [ -n "$existing" ]; then
echo "::error::Tag $REF_NAME already exists in phpstan/turbo-ext at $existing, expected $split_commit. Delete it there if it must move."
exit 1
fi
echo "Tagging $split_commit (replayed from $tag_src) as $REF_NAME"
git push "https://x-access-token:${PUSH_TOKEN}@github.com/phpstan/turbo-ext.git" "$split_commit:refs/tags/$REF_NAME"