Skip to content

Commit a8c1b95

Browse files
ci(stlc): promote via pull request; back-sync fast-forwards to descendants
Production `main` is protected (PR + review) and should stay that way, so the promote no longer force-pushes main. It pushes staging's tip to `stlc/promote` on the production repo and opens/refreshes a PR listing the commits; the PR is the human review gate before publishing and must be merged with a merge commit. Back-sync now also fast-forwards when production is a strict descendant of staging with an identical tree (the merged promote PR), so codegen isn't held between the promote merge and the release commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WL7jhZTnnSYDo5vECWpXUw
1 parent cf3fe11 commit a8c1b95

2 files changed

Lines changed: 41 additions & 9 deletions

File tree

‎.github/workflows/stlc-promote.yml‎

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
name: Promote SDKs
22

3-
# Promote staging to production by fast-forwarding production main up to staging,
4-
# preserving SHAs so the trunks stay identical and linear (nothing to heal on the
5-
# next stlc build). Manual dispatch by design: a maintainer promotes the accumulated
6-
# batch to cut a release; release-please then opens its version PR on production.
3+
# Promote staging to production by opening a pull request on the production repo
4+
# whose head is staging main (pushed to the `stlc/promote` branch). Production `main`
5+
# stays fully protected; the PR is the human review gate before publishing. It must
6+
# be merged with a MERGE COMMIT (never squash/rebase) so the trunks keep shared
7+
# history; back-sync then fast-forwards staging to the merge commit. Manual dispatch
8+
# by design: a maintainer promotes the accumulated batch to cut a release;
9+
# release-please then opens its version PR on production.
710
on:
811
workflow_dispatch: {}
912

@@ -47,17 +50,40 @@ jobs:
4750
echo "synced=false" >> "$GITHUB_OUTPUT"
4851
fi
4952
50-
- name: Promote staging to production (fast-forward)
53+
- name: Promote staging to production (pull request)
5154
if: steps.diff.outputs.synced == 'false'
55+
env:
56+
PROMOTE_BRANCH: stlc/promote
5257
run: |
5358
# Refuse unless production is an ancestor of staging: otherwise the trunks
54-
# have forked (production advanced without a back-sync) and FF is unsafe.
59+
# have forked (production advanced without a back-sync) and the PR would be
60+
# a real merge. Back-sync production into staging first.
5561
if ! git merge-base --is-ancestor production/main origin/main; then
5662
echo "::error title=Promote blocked::production/main is not an ancestor of staging main. Back-sync production into staging first."
5763
exit 1
5864
fi
59-
git push production origin/main:refs/heads/main
60-
echo "Fast-forwarded production/main to staging/main."
65+
# Publish staging's tip as a branch on production and open (or refresh) the
66+
# promote PR. `main` is protected on production; the PR is the review gate.
67+
git push --force production "origin/main:refs/heads/${PROMOTE_BRANCH}"
68+
tip=$(git rev-parse --short origin/main)
69+
{
70+
echo "Promotes \`${PRODUCTION_REPO}\` \`main\` to the staging trunk (\`${tip}\`)."
71+
echo
72+
echo "**Merge with a merge commit — do not squash or rebase.** Squashing rewrites the commits and forks the trunks; the next back-sync and promote refuse until repaired by hand."
73+
echo
74+
echo "After merging, release-please opens the version PR on this repo; merging that one tags and publishes."
75+
echo
76+
echo "### Commits"
77+
git log --format='- %h %s' production/main..origin/main
78+
} > /tmp/promote-body.md
79+
existing=$(gh pr list -R "$PRODUCTION_REPO" --head "$PROMOTE_BRANCH" --state open --json number --jq '.[0].number // empty')
80+
if [ -n "$existing" ]; then
81+
gh pr edit "$existing" -R "$PRODUCTION_REPO" --title "Promote staging to production (${tip})" --body-file /tmp/promote-body.md
82+
echo "Refreshed promote PR #${existing} on ${PRODUCTION_REPO}."
83+
else
84+
gh pr create -R "$PRODUCTION_REPO" --base main --head "$PROMOTE_BRANCH" \
85+
--title "Promote staging to production (${tip})" --body-file /tmp/promote-body.md
86+
fi
6187
6288
- name: Alert on failure
6389
if: failure()

‎.github/workflows/stlc-sync.yml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ jobs:
6161
# If not, staging already has production's content (release-please commits).
6262
MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict
6363
STAGING_TREE=$(git rev-parse 'origin/main^{tree}')
64-
if [ "$MERGED" = "$STAGING_TREE" ]; then
64+
if [ "$(git rev-parse origin/main)" != "$(git rev-parse production/main)" ] \
65+
&& git merge-base --is-ancestor origin/main production/main; then
66+
# Production is a strict descendant of staging — e.g. the merged promote PR
67+
# (a merge commit with an identical tree) or a release commit. Fast-forward
68+
# even when the trees match, so the trunks share history and codegen isn't held.
69+
echo "behind=true" >> "$GITHUB_OUTPUT"
70+
elif [ "$MERGED" = "$STAGING_TREE" ]; then
6571
echo "Staging already has production's content. Nothing to pull back."
6672
echo "behind=false" >> "$GITHUB_OUTPUT"
6773
else

0 commit comments

Comments
 (0)