docs: cover pre-releases and upgrade notes in the release-note skill (#9635)

* docs: cover pre-releases and upgrade notes in the release-note skill

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs: add style and length rules to the release-note skill

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs: drop machine-specific notes from the release-note skill

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs: base the first pre-release on the previous release branch point

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs: subtract the whole previous line when building a minor release note

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
This commit is contained in:
dennis zhuang
2026-10-16 07:36:35 +00:00
committed by GitHub
parent 3de8610e2e
commit a0188c8c41
+199 -36
View File
@@ -1,6 +1,6 @@
---
name: greptimedb-release-note
description: Generate a GreptimeDB release changelog with git cliff (correct range, subtract already-released patch PRs, rebuild contributors, add human-curated highlights), output to a file, and prepare the docs-repo blog PR. Use when asked to write/generate a GreptimeDB release note or changelog.
description: Generate a GreptimeDB release changelog (minor, patch, or alpha/beta/rc pre-release) with git cliff (correct range, subtract already-released patch PRs, rebuild contributors, add human-curated highlights and upgrade notes), output to a file, and prepare the docs-repo blog PR. Use when asked to write/generate a GreptimeDB release note or changelog.
---
# GreptimeDB Release Note / Changelog
@@ -27,41 +27,54 @@ a token it still runs, but may be rate-limited / incomplete.
## 2. Determine the previous version (skip nightlies)
From `gh release list --repo GreptimeTeam/greptimedb`, ignore `*-nightly-*`, `-rc.*`, `-beta.*`,
and build-suffixed tags; focus on formal `vX.Y.Z`.
From `gh release list --repo GreptimeTeam/greptimedb`, ignore `*-nightly-*` and build-suffixed
tags (e.g. `v1.3.0-alpha.1-3c0e2a8d5-20260928-1790639248`).
- Patch `vX.Y.Z` (Z>0): previous = `vX.Y.(Z-1)`.
- New minor `vX.Y.0`: previous = the latest `vX.(Y-1).*` (for `v1.0.0`, the biggest `0.x`).
- Pre-release `vX.Y.Z-{alpha,beta,rc}.N`: previous = the latest pre-release tag of the same
`vX.Y.Z` (e.g. `v1.3.0-beta.1` for `v1.3.0-beta.2`). If there is none (the first alpha),
see "First pre-release" in §3: the previous minor's `.0` may not be published yet, and since
v1.2 a `.0` tag sits on `release/vX.Y`, not on `main`.
Double-check with the user.
## 3. Pick the range and generate
Key topology fact: a **minor tag** (e.g. `v1.0.0`) is an ancestor of `main`; **patch tags**
(`v1.0.1`, `v1.0.2`) live on the `release/v1.0` branch and are **NOT** ancestors of `main`
(they are cherry-picks with different SHAs). Verify with
Key topology fact: **patch tags** (`v1.0.1`, `v1.0.2`) live on `release/vX.Y` and are **NOT**
ancestors of `main` (they are cherry-picks with different SHAs). Up to v1.1 the `.0` tag was
the point where `release/vX.Y` left `main`; from v1.2 the branch is cut before the `.0`, and
pre-releases and the `.0` itself are tagged on the branch with backports. Verify with
`git merge-base --is-ancestor <tag> <remote>/main`.
### New minor (`X.Y.0`, cut from main)
Note the two different tags here: the `git cliff` **base is the previous minor `.0` tag**
(e.g. `v1.0.0`, an ancestor of `main`) — **not** the "previous version" from §2 (the latest
patch, e.g. `v1.0.2`), which is only used below to decide which patch PRs to subtract.
### New minor (`X.Y.0`)
The note compares against the whole previous line: everything new since `release/vX.(Y-1)`
left `main`, minus what any published `vX.(Y-1).*` release (pre-releases, `.0`, patches)
already shipped. The line's own pre-releases (`vX.Y.0-beta.*`) are not subtracted; the `.0`
note covers the whole minor.
Base = the **previous minor tag** (e.g. `v1.0.0`); tip = the release commit (= `release/vX.Y`
tip, usually `<remote>/main`):
Base = the **previous minor tag** (e.g. `v1.1.0`); tip = the tagged `vX.Y.0` commit, resolved as
in "Pre-release" below. Split the range at the point where `release/vX.Y` left `main`:
```
GITHUB_TOKEN=$(gh auth token) git cliff <prev-minor-tag>..<release-commit> --tag vX.Y.0 \
-o /path/CHANGELOG-vX.Y.0.md
bp=$(git merge-base <remote>/release/vX.Y <remote>/main)
```
`cliff.toml`'s `ignore_tags` folds in-range nightly tags into the single section.
- `<prev-minor-tag>..$bp` is on `main`; generate it with `git cliff`:
```
GITHUB_TOKEN=$(gh auth token) git cliff <prev-minor-tag>..$bp --tag vX.Y.0 \
-o /path/CHANGELOG-vX.Y.0.md
```
`cliff.toml`'s `ignore_tags` folds in-range nightly tags into the single section.
- `$bp..<tip>` is the release branch: backports, squashed picks, and commits without a PR.
`git cliff` fails on such commits (`Filter call 'sort' failed`), so build this part as a
pre-release: collect the original PRs with the loop in "Pre-release" and add their bullets.
List the commits that carry no PR with
`git log --format='%h %s' $bp..<tip> | grep -vE '\(#[0-9]+\)$'`; drop mechanical ones, and
for a manual port of a `main` PR (the body says `cherry picked from commit` a SHA that is not
on `main`), find that PR by title and cite it.
**Then subtract PRs already shipped in the intermediate patch releases** (`vX.(Y-1).1`,
`.2`, …) — their main-branch commits are inside the range and would duplicate, and the
audience cares about what's new vs the latest patch. Collect the PR set from the patch release
bodies and remove matching lines:
```
gh release view vX.(Y-1).Z --repo GreptimeTeam/greptimedb | grep -oE 'pull/[0-9]+'
```
Remove every changelog bullet whose `#NNNN` is in that combined set (a small Python script is
the reliable way — match `pull/<n>)` on lines starting with `*`).
**Then subtract PRs already shipped in published `vX.(Y-1).*` releases**: their `main` commits
are inside the range. Build the set as for the first pre-release (below): the PR links in
every published `vX.(Y-1).*` release body, plus the `(#N)` line endings in the history of the
newest published `vX.(Y-1).*` tag. Remove every changelog bullet whose `#NNNN` is in that set (a
small Python script is the reliable way — match `pull/<n>)` on lines starting with `*`).
### Patch (`X.Y.Z`, Z>0, cherry-picks on the release branch)
The previous patch tag is an ancestor of the release branch, so a plain range works **when the
@@ -99,11 +112,78 @@ user which they prefer**):
If the branch history is otherwise messy, the same "identify the picked PRs and keep only those"
rule applies.
### First pre-release (`X.Y.0-alpha.1`, cut from main)
The base is the commit where the previous line's release branch left `main`. It exists when the
alpha is cut, even if `vX.(Y-1).0` does not:
```
base=$(git merge-base <remote>/release/vX.(Y-1) <tag>)
```
If there is no `release/vX.(Y-1)`, use the newest published release tag that is an ancestor of
the tag (`git merge-base --is-ancestor <candidate> <tag>`). Run `git cliff <base>..<tag>`, then
subtract every PR that already shipped in a published `vX.(Y-1).*` release, pre-releases
included. Take the union of the PR links in those release bodies and the `(#N)` line endings in
the history of the newest published `vX.(Y-1).*` tag:
```
gh release view <published-vX.(Y-1)-tag> --repo GreptimeTeam/greptimedb | grep -oE 'pull/[0-9]+'
git log <newest-published-vX.(Y-1)-tag> --format=%B | grep -oE '\(#[0-9]+\)$'
```
Either source alone misses PRs: a release body can omit a backport that its tag contains, and a
backport commit can name only the backport PR.
### Pre-release (`X.Y.Z-{alpha,beta,rc}.N` after an earlier pre-release)
**The tip is the tagged commit, not the release branch tip.** Resolve it from the remote and
write this SHA in the docs PR body (§8) as what the note was checked against:
```
git ls-remote --tags <remote> 'vX.Y.Z-beta.N*'
```
Use the `refs/tags/vX.Y.Z-beta.N` line (its `^{}` line if the tag is annotated), not the
build-suffixed tags it also lists. PRs that land on `release/vX.Y` after the tag
(`git log --oneline <tag>..<remote>/release/vX.Y`) belong to the next release, even if they are
already merged when you write the note.
The range `<prev-pre-release>..<tag>` usually mixes two kinds of commits: individual backports
(`... [Backport release/vX.Y] (#NNNN)`) and squashed pick commits (`chore: backport selected
changes for ... (#NNNN)`). In both, the original PRs are the `(#NN)` at the end of body lines;
a backport commit can also carry prerequisite PRs. Collect the PR set, falling back to the
subject for commits whose body lists none:
```
for c in $(git rev-list <prev-pre-release>..<tag>); do
prs=$(git log -1 --format=%b "$c" | grep -oE '\(#[0-9]+\)$')
[ -n "$prs" ] || prs=$(git log -1 --format=%s "$c" | grep -oE '\(#[0-9]+\)$')
echo "$prs"
done | tr -dc '0-9\n' | sort -un
```
List the bullets under the **original** PRs (title and author of the PR merged to `main`), not
the backport PRs. Build them with patch (a) or (b) above; for (b), fetch
`gh api repos/GreptimeTeam/greptimedb/pulls/<n> --jq '{number,title,author:.user.login,merged_at}'`,
group by the `cliff.toml` parsers (first match wins), order by `merged_at`, and put `!` titles
under Breaking changes (§6).
A squashed pick PR can also contain release-only commits that have no PR. List them:
```
gh api repos/GreptimeTeam/greptimedb/pulls/<squash-pr>/commits --paginate \
--jq '.[].commit.message | split("\n")[0]' | grep -vE '\(#[0-9]+\)$'
```
Mechanical ones (version bump, toolchain or lint adaptations; example:
`fix: adapt release OptionMap iteration to Rust 1.98`) get no bullet. A release-only
commit that changes behavior gets a bullet in its group.
> Flaky-network note for (a): `git cliff` fetches the repo's full commit history from the GitHub
> API for author enrichment (paginates back thousands of commits, even for a tiny range). On a
> flaky connection it can time out mid-pagination; it caches pages, so just **re-run until it
> completes** — each retry resumes from the cache. If it keeps failing, fall back to (b).
### Groups (all paths)
`git cliff` drops PRs whose title isn't a conventional commit (`filter_unconventional`). List
them and add the user-facing ones by hand:
```
git log --format=%s <range> | grep -E '\(#[0-9]+\)$' | grep -vE '^[a-z]+(\([^)]*\))?!?: '
```
Use only the groups in `cliff.toml`. `git cliff` prints a `### Build` section for `build:` commits
because no parser matches them; move a user-facing `build(deps)` fix to Bug Fixes and the rest to
Miscellaneous Tasks. Keep each bullet list free of blank lines; a blank line turns it into a
loose list that renders with different spacing.
## 4. Rebuild contributor sections after subtracting
`cliff` computes `New Contributors` / `All Contributors` over the full range, so after removing
@@ -114,6 +194,21 @@ lines, recompute from the **remaining** commit bullets:
in-range PR shipped in a patch).
Do this in the same script that subtracts the PRs.
When the bullets are built by hand (§3 (b), pre-releases), `git cliff` computes no New
Contributors, and GitHub's generated notes don't either (they only see the backport PRs). An
author is new if none of their merged PRs shipped before, i.e. none appears in the history of the
base tag:
```
git log <base-tag> --format=%B | grep -oE '\(#[0-9]+\)$' | tr -dc '0-9\n' | sort -u > /tmp/shipped.txt
gh search prs --repo GreptimeTeam/greptimedb --author <user> --merged --limit 1000 \
--json number --jq '.[].number' | sort -u > /tmp/author.txt
comm -12 /tmp/author.txt /tmp/shipped.txt # empty => new contributor
```
Run it for every non-bot author in the release, and cite their earliest-merged PR in this
release. "Earliest merged PR is in range" is not the same test: a contributor whose first PR
merged on `main` but was not picked into any release is still new when a later PR of theirs
ships.
## 5. Verify
Cross-check the result against `git log <base>..<tip>`: no subtracted PR remains, and no
@@ -122,17 +217,71 @@ line (ask the user; some prefer to keep it).
## 6. Human-curated sections (insert after the `Release date:` line)
**Style.** Follow the Google developer documentation style guide: sentence-case headings, active voice, present
tense for behavior, American spelling, serial comma, code font for identifiers, bold only for UI
names. Keep it short:
- Intro: two sentences at most, what the release covers and its main theme.
- Highlight: one paragraph of up to four sentences, plus an example if it earns one (see below).
- Upgrade note or breaking change: one bullet, one or two sentences: what changes, who is
affected, what to do.
- Every sentence states a fact the reader acts on or checks. Delete sentences that restate the
heading or the previous sentence, transitions, and summaries.
- No marketing words (`significantly`, `powerful`, `seamless`, `robust`, `enhanced`), no vague
verbs (`supports`, `handles`, `improves`; say what happens), no `several`/`many` when the number
is known. Keep the source's hedges; don't add new ones.
Example:
> Bad: This release significantly enhances write reliability by introducing a robust retry
> mechanism that seamlessly handles transient failures.
>
> Good: In distributed clusters, the frontend retries a failed write up to three times with
> exponential backoff. Set `max_retries = 0` to disable retries.
Mirror past release bodies (`gh release view v1.0.0 --repo GreptimeTeam/greptimedb`):
- **Short intro**, terse, engineer-written tone (no marketing adjectives).
- **`### 👍 Highlights`** — *few, deep* highlights, each with a **working example**
(SQL / TOML config). To get examples right, **read the highlight PR and the related docs
PR/page** in the docs repo (`GreptimeTeam/docs`, often checked out locally) — verify syntax
in `docs/reference/sql/*.md`, `config/*.example.toml`, `config/config.md`. Do **not** mention
implementation details, tiny features, or unfinished/experimental-but-not-ready features.
Always have the user review and edit the highlights; iterate.
- **Short intro** (see Style).
- **`### 👍 Highlights`** — *few, deep* highlights, ranked by user impact. Fixes for silently
wrong query results, lost or invisible data, and security issues rank with new features and
can be highlights.
- Write a fix as its symptom and the affected deployments or usage, not the PR title. Example:
"with no traffic, `errors / requests > 0.05` was true, so the alert fired".
- State whether a feature is on by default. If an example config uses the default values, say
so.
- Give an example (SQL / TOML config) only where it helps readers act. Don't pick highlights,
or the example, by what is easy to demonstrate.
- One highlight covers one change or one user-visible problem. Don't merge unrelated PRs into
one entry because they touch the same component.
- Sources: the PR body's sections on impact, compatibility, and behavior changes, plus the
code and config it touches, not the title alone. Verify examples against the related docs
PR/page in the docs repo (`GreptimeTeam/docs`, often checked out locally),
`docs/reference/sql/*.md`, `config/*.example.toml`, `config/config.md`.
Do **not** mention implementation details, tiny features, or unfinished/experimental-but-not-ready
features. Always have the user review and edit the highlights; iterate.
- **Dashboard** subsection — don't just bump the version. Read the dashboard PRs in the bundled
release (`gh release view <ver> --repo GreptimeTeam/dashboard`; then the linked PRs) and
describe the user-facing change.
describe the user-facing change. Use the names the UI shows, not the PR titles; check them in
the dashboard's locale strings at the bundled tag:
```
gh api 'repos/GreptimeTeam/dashboard/contents/src/locale/en-US/menu.ts?ref=<dashboard-tag>' \
--jq .content | base64 -d
```
Screenshots need absolute `https://` URLs: the body is copied to the GitHub release, where
relative and site-root (`/img/...`) paths don't resolve.
- **`### Upgrade notes`** and **`### Breaking changes`** — check every PR in the release for:
`!` titles; `Compatibility`, `Behavior change`, `Breaking` paragraphs in the body (often a
plain `Compatibility:` line, not a heading); unchecked "API changes are backward compatible" /
"Schema or data changes are backward compatible" boxes; new config options and their defaults;
changed defaults; persisted format changes; rolling-upgrade order; new hard limits. These
signals set reading order, not a filter: a limit can sit in plain prose with every box
checked, so read the body of every user-facing `feat`/`fix` PR. Examples of what is easy to
miss: a 512 MiB cap on decompressed request bodies (#9264); a default step for stepless
subqueries (#9481).
- **Upgrade notes**: steps operators take — upgrade order, index rebuilds, settings to review.
- **Breaking changes**: behavior that used to work and now errors or returns different
results. Put the `!` PR bullets here too, after these entries (see `v1.3.0-beta.1`).
- **Benchmark results** (optional) — state the baseline version (often the previous formal
release, not the previous beta), data size, cache state, and hardware. Don't attribute results
to single PRs. Leave out results the benchmark marks as not for publication, and smoke-level
runs.
## 7. Output
@@ -144,7 +293,8 @@ it**. The release runbook deletes it after the release + docs PR are done.
The release note is also published as a blog post in **`GreptimeTeam/docs`**.
- **Ask the user for their local `GreptimeTeam/docs` checkout path**; if they have none, offer
to clone (`git clone git@github.com:GreptimeTeam/docs.git <path>`).
- File: `blog/release-X-Y-Z.md` (version with dashes; see `blog/release-1-0-0.md`).
- File: `blog/release-X-Y-Z.md` (version with dashes; see `blog/release-1-0-0.md`). Pre-releases
use dashes throughout: `blog/release-1-3-0-beta-2.md`.
- Content = docs frontmatter (below) followed by the GitHub release body, **keeping the
`# vX.Y.Z` H1 directly under the frontmatter**. The blog frontmatter has no `title:`, so
Docusaurus uses that H1 as the page title and sidebar label — omitting it makes the page
@@ -156,11 +306,13 @@ The release note is also published as a blog post in **`GreptimeTeam/docs`**.
description: GreptimeDB vX.Y.Z Changelog
date: YYYY-MM-DD
---
# vX.Y.Z
Release date: ...
```
The docs CI check `check-release-note-sync` requires the post minus its frontmatter to equal
the GitHub release body byte for byte, so the H1 goes on the line right after the closing
`---`, with no blank line.
- The docs working tree may have unrelated WIP — **don't disturb it**: use a `git worktree` off
`origin/main`:
```
@@ -169,8 +321,19 @@ The release note is also published as a blog post in **`GreptimeTeam/docs`**.
```
- **Read and follow the current docs repo PR template**
(`.github/pull_request_template.md`) from the checkout used for the release.
Fill in every required section and leave reviewer-owned checklist boxes unchecked;
do not rely on hard-coded section names from an older template.
Fill in every required section; do not rely on hard-coded section names from an older
template. The checklist items are the author's to confirm: check the ones you did, and for one
that doesn't apply, check it and say why. For pre-releases, state the tagged SHA (§3) under
Verification.
- **Build the site before opening the PR.** A new worktree has no `node_modules`. Install with
the pnpm version pinned in `package.json` (`packageManager`), as CI does:
```
cd /tmp/docs-release-note
pm=$(jq -r .packageManager package.json) # e.g. pnpm@8.6.0
npx -y "$pm" install --frozen-lockfile
npx -y "$pm" build
```
If the build fails, fix it or stop; don't mark the PR ready for review.
- Commit with sign-off, push, open a **draft** PR, then remove the worktree:
```
git -C /tmp/docs-release-note add blog/release-X-Y-Z.md