-
Notifications
You must be signed in to change notification settings - Fork 0
169 lines (155 loc) · 7.35 KB
/
Copy pathchangelog-prestage.yml
File metadata and controls
169 lines (155 loc) · 7.35 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
name: Pre-stage CHANGELOG before release
# Triggered manually (`workflow_dispatch`) before opening a release PR.
# Inserts the `## [<tag>] - <date>` heading + footer link into develop's
# CHANGELOG ahead of time, and merges main into develop on the prestage
# branch so develop's CHANGELOG already has the structural shape main
# carries. The release PR opened afterwards is conflict-free by
# construction.
#
# Why: release PRs commonly hit a same-line CHANGELOG conflict on the
# release-branch push because git sees both develop and main editing
# the region right after `## [Unreleased]`. The pre-#168 pattern was for
# the operator to manually `git merge origin/main` on the release branch
# every time; this workflow packages the same merge plus the heading
# insertion as a reviewable PR.
#
# Distinct from `changelog-rollup.yml`: rollup runs *after* a release
# tag is cut, bumps the version, and is auto-triggered by release.yml
# success. Prestage runs *before* the tag, doesn't bump version (the
# post-release rollup does), and is operator-triggered. Both share
# `.github/scripts/rollup_changelog.py` — prestage uses the `--no-bump`
# flag.
on:
workflow_dispatch:
inputs:
tag:
description: "Tag about to be released (e.g. v1.11.0)"
required: true
type: string
prior_tag:
description: "Prior release tag for the compare link (auto-resolves if empty)"
required: false
type: string
default: ""
date:
description: "Release date YYYY-MM-DD (defaults to today UTC)"
required: false
type: string
default: ""
permissions:
contents: write
pull-requests: write
jobs:
prestage:
name: Open prestage PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: develop
# Full history so `git describe --abbrev=0 --tags` can resolve
# the prior tag, and the `git merge origin/main` step has a
# complete merge-base.
fetch-depth: 0
# Prefer RELEASE_BOT_TOKEN so the prestage branch's push fires
# `pull_request` workflows on the auto-PR. Falls back to
# GITHUB_TOKEN when the secret isn't set — the auto-PR still
# opens, but its CI doesn't run until a user pushes on top.
token: ${{ secrets.RELEASE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
- name: Resolve inputs
id: resolve
run: |
set -euo pipefail
TAG="${{ inputs.tag }}"
if [ -z "${TAG}" ]; then
echo "::error::tag input is required"
exit 1
fi
# Auto-resolve prior tag if not provided (mirrors changelog-rollup.yml).
PRIOR="${{ inputs.prior_tag }}"
if [ -z "${PRIOR}" ]; then
if PRIOR=$(git describe --abbrev=0 --tags --match 'v*.*.*' "${TAG}^" 2>/dev/null); then
echo "prior tag (resolved): ${PRIOR}"
else
# Try the most recent tag on main if `<TAG>^` doesn't resolve
# (the tag isn't cut yet — that's the whole point of prestage).
if PRIOR=$(git describe --abbrev=0 --tags --match 'v*.*.*' origin/main 2>/dev/null); then
echo "prior tag (resolved from main): ${PRIOR}"
else
PRIOR=""
echo "no prior tag (first release)"
fi
fi
fi
DATE="${{ inputs.date }}"
if [ -z "${DATE}" ]; then
DATE=$(date -u +%Y-%m-%d)
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "prior=${PRIOR}" >> "$GITHUB_OUTPUT"
echo "date=${DATE}" >> "$GITHUB_OUTPUT"
echo "branch=chore/changelog-prestage-${TAG}" >> "$GITHUB_OUTPUT"
- name: Create prestage branch + merge main
env:
BRANCH: ${{ steps.resolve.outputs.branch }}
run: |
set -euo pipefail
# Idempotent: if the branch already exists from a previous replay,
# bail rather than force-push.
if git ls-remote --exit-code --heads origin "${BRANCH}" >/dev/null 2>&1; then
echo "::warning::branch ${BRANCH} already exists; skipping push to avoid clobbering an in-flight prestage PR"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "${BRANCH}"
# Bring main's CHANGELOG state into develop. Prefer auto-merge;
# if there's a real conflict (rare in steady-state — develop and
# main usually share their post-rollup history), fail fast with
# a clear message so the operator can resolve manually.
if ! git merge --no-edit origin/main; then
echo "::error::main → develop merge hit a conflict the prestage can't auto-resolve. Resolve manually on a local branch and re-run, or open the release PR by hand and merge main into the release branch (the pre-#168 pattern)."
git merge --abort
exit 1
fi
- name: Run rollup script in pre-stage mode
run: |
python .github/scripts/rollup_changelog.py \
--tag "${{ steps.resolve.outputs.tag }}" \
--prior-tag "${{ steps.resolve.outputs.prior }}" \
--date "${{ steps.resolve.outputs.date }}" \
--no-bump
- name: Open prestage PR
env:
# Same fallback as the checkout step (#174) so the auto-PR
# fires `pull_request` workflows on creation when the secret
# is provisioned; falls back to GITHUB_TOKEN otherwise.
GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
BRANCH: ${{ steps.resolve.outputs.branch }}
TAG: ${{ steps.resolve.outputs.tag }}
DATE: ${{ steps.resolve.outputs.date }}
run: |
set -euo pipefail
git add CHANGELOG.md
git commit -m "chore: pre-stage CHANGELOG for ${TAG} - ${DATE}"
git push origin "${BRANCH}"
gh pr create \
--base develop \
--head "${BRANCH}" \
--title "chore: pre-stage CHANGELOG for ${TAG} - ${DATE}" \
--body "$(cat <<EOF
Auto-opened by [.github/workflows/changelog-prestage.yml](.github/workflows/changelog-prestage.yml) before the ${TAG} release PR.
## What this PR does
- Merges \`origin/main\` into develop (so develop's CHANGELOG has the same structural shape main carries).
- Inserts \`## [${TAG#v}] - ${DATE}\` heading after \`## [Unreleased]\` in CHANGELOG.md.
- Updates \`[Unreleased]: …/compare/${TAG}...HEAD\` footer link.
- Adds \`[${TAG#v}]: …/compare/<prior>...${TAG}\` footer link.
- **Does not** bump \`pyproject.toml\` / \`uv.lock\` — that's the post-release rollup's job.
## Operator next step
Merge this PR into develop, then open the release PR develop → main with title \`release: ${TAG}\`. The release PR will be conflict-free because develop and main now agree on the CHANGELOG's structural shape.
See [docs/DEVELOPMENT.md#creating-a-release](docs/DEVELOPMENT.md#creating-a-release) for the full cycle.
EOF
)"