From 6cd818db1ce716202b423a782eb199eb6bf58371 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 15:29:02 -0500 Subject: [PATCH 1/6] Add RELEASE.md, copied verbatim from cat_tools cat_tools (Postgres-Extensions/cat_tools) already has a written release process; copy it here as-is before adapting it to count_nulls's own conventions in a follow-up commit, so the diff between "what cat_tools does" and "what count_nulls actually needs" stays easy to review. --- RELEASE.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..e1c27fd --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,97 @@ +# Releasing cat_tools + +cat_tools builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool); +the release machinery (`make tag`, `make dist`) lives in `pgxntool/base.mk`. These +steps cut a new release. + +## 1. Pre-release checks +- [ ] Open issues/PRs for this release reviewed, merged or deferred. +- [ ] CI green on all supported PostgreSQL versions (the `all-checks-passed` job on master). +- [ ] Locally: `make verify-results` passes. It depends on `test` (so it runs the suite + first, then gates on the results). `make test` alone is non-gating — pgxntool marks + `installcheck` `.IGNORE`, so it never returns non-zero on a regression; only + `verify-results` (which inspects `test/regression.diffs`) is a real gate. + +## 2. Decide the version and what to track +- [ ] Pick the new version (semantic versioning). +- [ ] **Minor change? Consider NOT committing the generated versioned install script.** + If this release makes only fairly minor changes (unlikely to cross a PostgreSQL + supported-version boundary), decide whether to omit the generated + `sql/cat_tools--.sql.in` — it has little update-test-coverage value and + omitting it keeps the repo smaller (it is regenerated from `sql/cat_tools.sql.in` + at build time). The update script (`sql/cat_tools----.sql.in`) is + ALWAYS committed. See CLAUDE.md "SQL file conventions". + +## 3. Update version + changelog + +> **⚠️ CRITICAL — you are temporarily leaving the `stable` pseudo-version.** Master's +> `default_version` normally sits at the `stable` pseudo-version so that source edits +> regenerate `cat_tools--stable.sql` and never a frozen released file. Stamping a real +> version here points the generated current-version file at `cat_tools--`. The +> moment this release is merged you **MUST** flip `default_version` back to `stable` on +> master (step 6). If you forget, the next source edit on master will regenerate — and +> corrupt — the just-released version's install file. + +- [ ] Bump `default_version` in `cat_tools.control` (bumped by hand). +- [ ] Bump the version in `META.in.json` — the source of truth is + `provides.cat_tools.version` (also update the top-level `version`); `META.json`, + `control.mk`, and `meta.mk` (which feeds `PGXNVERSION`) regenerate via `make`. +- [ ] Advance `release_status` in `META.in.json` as appropriate (unstable → testing → + stable). +- [ ] Add/finish the update script `sql/cat_tools----.sql.in`; confirm + `ALTER EXTENSION cat_tools UPDATE` from the previous version reaches the new version, + on multiple PG majors. +- [ ] Stamp `HISTORY.asc`: the top `STABLE` section accumulates user-facing changes as + PRs land; at release, rename that header to the new version number. + +## 4. Verify +- [ ] `make verify-results` green (it runs `test` first, then gates on the results). +- [ ] From a clean checkout (or `git archive` of the tag): `make && make install` + regenerates and installs cleanly and `CREATE EXTENSION cat_tools;` reports the new + version — confirms a PGXN consumer can build from the tracked sources alone. (This + mirrors what `make dist` ships, since it archives the tag: committed files only, so + any omitted generated install script is regenerated on the consumer's side.) + +## 5. Tag and distribute +- [ ] Commit the release changes; working tree must be clean — `make tag` aborts with + "Untracked changes!" on a dirty tree. +- [ ] `make tag` — creates a git tag named exactly the version, UNPREFIXED (e.g. `0.2.3`, + matching the existing `0.2.2` tag; no `v` prefix), taken from `PGXNVERSION`, and + pushes it to `origin`. It is idempotent when the tag already points at HEAD, and + errors if the tag exists on a different commit. To move an existing tag use + `make forcetag` (= `make rmtag` then `make tag`); `make rmtag` deletes the tag + locally and on `origin`. +- [ ] `make dist` — depends on `tag` (and builds the HTML docs), then + `git archive`s the tag into `../cat_tools-.zip` (parent directory). + Because it archives the tag, only committed files are included. If a `.gitattributes` + exists it must be committed, or `dist` aborts (git archive only honors + `export-ignore` for committed files). `make forcedist` = `forcetag` + `dist`. +- [ ] Upload the `../cat_tools-.zip` to PGXN (manual). + +## 6. Return master to `stable` (CRITICAL — do not skip) +- [ ] As soon as the release is merged, flip `default_version` back to the `stable` + pseudo-version on master (`cat_tools.control` + `META.in.json`), open a new top + `STABLE` section in `HISTORY.asc`, and re-seed a fresh + `sql/cat_tools----stable.sql.in` update script for the next cycle. + Leaving master stamped at the real version means the next source edit regenerates + and corrupts the released version's install file. +- [ ] `rm` any stale generated `sql/cat_tools--.sql.in` / + `cat_tools--.sql` left in the tree — once `default_version` is + `stable`, `make` no longer regenerates them, so they are one-time cleanup. + +> The persistent `stable` pseudo-version (a permanent version literally named `stable`, +> with a live `sql/cat_tools----stable.sql.in` update script that every +> source fix targets) decouples fixes from version bumps. The machinery is built into +> `sql.mk`; it lands immediately after the 0.2.3 release, so 0.2.3 itself is the last +> release cut before the scheme exists — steps 3/6 above describe the flow from the +> next release onward. + +## Notes and caveats + +- **0.2.3 catalog-view repair / `pg_upgrade` caveat.** Databases updated from + 0.2.0/0.2.1 hold broken catalog views that fail binary `pg_upgrade` to PostgreSQL + 12+ until the extension is updated to 0.2.3 (the update rebuilds them, dropping and + recreating the public `pg_class_v`/`column`/`pg_class()` objects without `CASCADE`). + Surface this in the release notes when people may cross the PG 11 → 12+ boundary. See + README.asc "Updating the extension" and + https://github.com/Postgres-Extensions/cat_tools/pull/42. From 43499259a423cdb87b5bd40ca82aa41366040ee0 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 15:29:44 -0500 Subject: [PATCH 2/6] Adapt RELEASE.md to count_nulls's own conventions count_nulls doesn't share several pieces of cat_tools's release machinery: there's no .sql.in generation (install scripts are committed directly, so there's no generated-script-omission choice to make), no HISTORY.asc changelog file, and no `stable` pseudo-version scheme (cat_tools's sql.mk feature) to leave and return to around a release, so the whole warning box and the "return master to stable" step drop out. It also has no prior release yet, so the tag-naming example uses count_nulls's own version instead of referencing cat_tools's existing 0.2.2 tag, and the removed 0.2.3-specific catalog-view caveat goes with it. Added a `make test-update` check in the verify steps, since that's how count_nulls actually exercises the update path end to end. --- RELEASE.md | 97 ++++++++++++++++-------------------------------------- 1 file changed, 29 insertions(+), 68 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index e1c27fd..a7755fd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,6 @@ -# Releasing cat_tools +# Releasing count_nulls -cat_tools builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool); +count_nulls builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool); the release machinery (`make tag`, `make dist`) lives in `pgxntool/base.mk`. These steps cut a new release. @@ -14,84 +14,45 @@ steps cut a new release. ## 2. Decide the version and what to track - [ ] Pick the new version (semantic versioning). -- [ ] **Minor change? Consider NOT committing the generated versioned install script.** - If this release makes only fairly minor changes (unlikely to cross a PostgreSQL - supported-version boundary), decide whether to omit the generated - `sql/cat_tools--.sql.in` — it has little update-test-coverage value and - omitting it keeps the repo smaller (it is regenerated from `sql/cat_tools.sql.in` - at build time). The update script (`sql/cat_tools----.sql.in`) is - ALWAYS committed. See CLAUDE.md "SQL file conventions". +- [ ] Version-specific install scripts (`sql/count_nulls--.sql`) are committed + directly, not generated from a `.sql.in` source — count_nulls doesn't use pgxntool's + `.sql.in` machinery, so there's no generated-script-omission choice to make here. + The update script (`sql/count_nulls----.sql`) is ALWAYS committed; + it's the only thing that makes the update path (`make test-update`) testable. ## 3. Update version + changelog - -> **⚠️ CRITICAL — you are temporarily leaving the `stable` pseudo-version.** Master's -> `default_version` normally sits at the `stable` pseudo-version so that source edits -> regenerate `cat_tools--stable.sql` and never a frozen released file. Stamping a real -> version here points the generated current-version file at `cat_tools--`. The -> moment this release is merged you **MUST** flip `default_version` back to `stable` on -> master (step 6). If you forget, the next source edit on master will regenerate — and -> corrupt — the just-released version's install file. - -- [ ] Bump `default_version` in `cat_tools.control` (bumped by hand). +- [ ] Bump `default_version` in `count_nulls.control` (bumped by hand). - [ ] Bump the version in `META.in.json` — the source of truth is - `provides.cat_tools.version` (also update the top-level `version`); `META.json`, + `provides.count_nulls.version` (also update the top-level `version`); `META.json`, `control.mk`, and `meta.mk` (which feeds `PGXNVERSION`) regenerate via `make`. - [ ] Advance `release_status` in `META.in.json` as appropriate (unstable → testing → stable). -- [ ] Add/finish the update script `sql/cat_tools----.sql.in`; confirm - `ALTER EXTENSION cat_tools UPDATE` from the previous version reaches the new version, - on multiple PG majors. -- [ ] Stamp `HISTORY.asc`: the top `STABLE` section accumulates user-facing changes as - PRs land; at release, rename that header to the new version number. +- [ ] Add the update script `sql/count_nulls----.sql`; confirm + `ALTER EXTENSION count_nulls UPDATE` from the previous version reaches the new + version, on multiple PG majors. `make test-update` exercises this directly — it + installs the oldest still-tracked version (currently `0.9.6`) and updates to + current, running the full suite against the result. ## 4. Verify - [ ] `make verify-results` green (it runs `test` first, then gates on the results). +- [ ] `make test-update` green — confirms the update path, not just a fresh install. - [ ] From a clean checkout (or `git archive` of the tag): `make && make install` - regenerates and installs cleanly and `CREATE EXTENSION cat_tools;` reports the new - version — confirms a PGXN consumer can build from the tracked sources alone. (This - mirrors what `make dist` ships, since it archives the tag: committed files only, so - any omitted generated install script is regenerated on the consumer's side.) + regenerates and installs cleanly and `CREATE EXTENSION count_nulls;` reports the + new version — confirms a PGXN consumer can build from the tracked sources alone. + (This mirrors what `make dist` ships, since it archives the tag: committed files + only.) ## 5. Tag and distribute - [ ] Commit the release changes; working tree must be clean — `make tag` aborts with "Untracked changes!" on a dirty tree. -- [ ] `make tag` — creates a git tag named exactly the version, UNPREFIXED (e.g. `0.2.3`, - matching the existing `0.2.2` tag; no `v` prefix), taken from `PGXNVERSION`, and - pushes it to `origin`. It is idempotent when the tag already points at HEAD, and - errors if the tag exists on a different commit. To move an existing tag use - `make forcetag` (= `make rmtag` then `make tag`); `make rmtag` deletes the tag - locally and on `origin`. +- [ ] `make tag` — creates a git tag named exactly the version, UNPREFIXED (e.g. `1.0.1`, + no `v` prefix), taken from `PGXNVERSION`, and pushes it to `origin`. It is + idempotent when the tag already points at HEAD, and errors if the tag exists on a + different commit. To move an existing tag use `make forcetag` (= `make rmtag` then + `make tag`); `make rmtag` deletes the tag locally and on `origin`. - [ ] `make dist` — depends on `tag` (and builds the HTML docs), then - `git archive`s the tag into `../cat_tools-.zip` (parent directory). - Because it archives the tag, only committed files are included. If a `.gitattributes` - exists it must be committed, or `dist` aborts (git archive only honors - `export-ignore` for committed files). `make forcedist` = `forcetag` + `dist`. -- [ ] Upload the `../cat_tools-.zip` to PGXN (manual). - -## 6. Return master to `stable` (CRITICAL — do not skip) -- [ ] As soon as the release is merged, flip `default_version` back to the `stable` - pseudo-version on master (`cat_tools.control` + `META.in.json`), open a new top - `STABLE` section in `HISTORY.asc`, and re-seed a fresh - `sql/cat_tools----stable.sql.in` update script for the next cycle. - Leaving master stamped at the real version means the next source edit regenerates - and corrupts the released version's install file. -- [ ] `rm` any stale generated `sql/cat_tools--.sql.in` / - `cat_tools--.sql` left in the tree — once `default_version` is - `stable`, `make` no longer regenerates them, so they are one-time cleanup. - -> The persistent `stable` pseudo-version (a permanent version literally named `stable`, -> with a live `sql/cat_tools----stable.sql.in` update script that every -> source fix targets) decouples fixes from version bumps. The machinery is built into -> `sql.mk`; it lands immediately after the 0.2.3 release, so 0.2.3 itself is the last -> release cut before the scheme exists — steps 3/6 above describe the flow from the -> next release onward. - -## Notes and caveats - -- **0.2.3 catalog-view repair / `pg_upgrade` caveat.** Databases updated from - 0.2.0/0.2.1 hold broken catalog views that fail binary `pg_upgrade` to PostgreSQL - 12+ until the extension is updated to 0.2.3 (the update rebuilds them, dropping and - recreating the public `pg_class_v`/`column`/`pg_class()` objects without `CASCADE`). - Surface this in the release notes when people may cross the PG 11 → 12+ boundary. See - README.asc "Updating the extension" and - https://github.com/Postgres-Extensions/cat_tools/pull/42. + `git archive`s the tag into `../count_nulls-.zip` (parent directory). + Because it archives the tag, only committed files are included. If a + `.gitattributes` exists it must be committed, or `dist` aborts (git archive only + honors `export-ignore` for committed files). `make forcedist` = `forcetag` + `dist`. +- [ ] Upload the `../count_nulls-.zip` to PGXN (manual). From b7e0022599f954cc9ed1376be3edf0957cbb428b Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 16:27:05 -0500 Subject: [PATCH 3/6] Fix RELEASE.md's version model and add HISTORY.md The previous commit dropped the stable-pseudo-version dance and the changelog step entirely, on the wrong assumption that neither applied to count_nulls - checking cat_tools' actual control-file history showed that scheme was never really built there either (its own RELEASE.md describes a feature that doesn't exist), and count_nulls didn't have a HISTORY file to reference. Both are worth having for real here, so this adds them properly instead: default_version now distinguishes from the PGXN distribution version (count_nulls has already shipped one release, 0.9.7, where only the distribution version moved), a new safety-check step verifies committed version files haven't been touched after their own release before trusting the stable dance to prevent that going forward, and HISTORY.md gives the changelog step somewhere to write to. Sized the "commit every version file" guidance to count_nulls' own scale (~90-line source, half a dozen versions) rather than copying cat_tools' size-driven trade-off wholesale. Also switched count_nulls itself from release branches to tags to match: 0.1.0 through 0.9.7 were previously tracked as branches (a leftover pre-tag convention), now backfilled as annotated tags at the same commits and the branches deleted, so `make tag` is the one real release mechanism referenced in this doc. --- HISTORY.md | 12 +++++ RELEASE.md | 142 +++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 HISTORY.md diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..3309427 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,12 @@ +# count_nulls history + +stable +------ + +No user-facing changes yet. + +--- + +Releases before this file existed (`0.1.0` through `0.9.7`) aren't detailed here; +see https://pgxn.org/dist/count_nulls/ and the corresponding git tags for what +shipped in each. diff --git a/RELEASE.md b/RELEASE.md index a7755fd..ba717e8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -4,55 +4,135 @@ count_nulls builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool) the release machinery (`make tag`, `make dist`) lives in `pgxntool/base.mk`. These steps cut a new release. -## 1. Pre-release checks +Two version numbers matter here and they can differ: the **distribution version** +(`META.in.json`'s top-level `version`, feeds `PGXNVERSION`, what PGXN.org lists a +release under) and the **extension version** (`count_nulls.control`'s +`default_version`, what `CREATE EXTENSION count_nulls;` installs by default and +what `pg_extension.extversion` reports). They're usually bumped together, but +count_nulls has already shipped a release where they weren't: `0.9.7` on PGXN +(2017-01-26) was a distribution-only bump (packaging/CI fixes, no SQL changes) — +the extension version stayed at `0.9.6`, unchanged since 2016. `0.9.7` the +*extension* version has never existed. + +## 1. Safety check: verify committed version files haven't drifted + +Before anything else, confirm every committed versioned install script +(`sql/count_nulls--.sql`) still matches what that version actually +shipped — this is the check that the `stable`-vs-real-version dance (step 4/7 +below) exists to make routine, but it's worth a direct look before relying on it. + +- [ ] For each `sql/count_nulls--.sql`, find its last-touching commit + (`git log -1 --format='%H %ad' -- sql/count_nulls--.sql`). +- [ ] Confirm that commit is no later than when that EXTENSION version actually + shipped. `git tag` is now the authoritative source for this — `0.1.0` through + `0.9.7` (the releases published before this project switched to tags) were + backfilled as annotated tags pointing at the commits their old, now-deleted + release branches used to point to. Cross-check against + https://pgxn.org/dist/count_nulls/ if anything looks off, remembering that + page lists *distribution* versions, which can lag the extension version they + contain (see `0.9.7` above). +- [ ] A version file touched by a commit LATER than its own release is a red + flag — it likely means `default_version` was left pointing at a real + (non-`stable`) version and a later source edit silently regenerated + (corrupted) it. Investigate before proceeding. +- [ ] **Known exception, not a corruption:** `sql/count_nulls--0.9.6.sql`'s + last-touching commit is from 2026 (the pgxntool 2.1.0 update), a decade + after `0.9.6` itself shipped in 2016. That's a straight backfill of the + historically-shipped content (needed once pgxntool started requiring + committed version files), not evidence the file's content is wrong — + don't flag re-added-later historical files as suspicious on their own; + only worry about ones whose *content* might have changed after release. + +## 2. Pre-release checks - [ ] Open issues/PRs for this release reviewed, merged or deferred. -- [ ] CI green on all supported PostgreSQL versions (the `all-checks-passed` job on master). +- [ ] CI green on all supported PostgreSQL versions. - [ ] Locally: `make verify-results` passes. It depends on `test` (so it runs the suite first, then gates on the results). `make test` alone is non-gating — pgxntool marks `installcheck` `.IGNORE`, so it never returns non-zero on a regression; only `verify-results` (which inspects `test/regression.diffs`) is a real gate. -## 2. Decide the version and what to track -- [ ] Pick the new version (semantic versioning). -- [ ] Version-specific install scripts (`sql/count_nulls--.sql`) are committed - directly, not generated from a `.sql.in` source — count_nulls doesn't use pgxntool's - `.sql.in` machinery, so there's no generated-script-omission choice to make here. - The update script (`sql/count_nulls----.sql`) is ALWAYS committed; - it's the only thing that makes the update path (`make test-update`) testable. - -## 3. Update version + changelog -- [ ] Bump `default_version` in `count_nulls.control` (bumped by hand). -- [ ] Bump the version in `META.in.json` — the source of truth is - `provides.count_nulls.version` (also update the top-level `version`); `META.json`, - `control.mk`, and `meta.mk` (which feeds `PGXNVERSION`) regenerate via `make`. +## 3. Decide the version and what to track +- [ ] Pick the new version (semantic versioning). Decide whether the extension + version needs to move at all, or (per the `0.9.7` precedent above) only the + distribution version does, if this release has no SQL changes. +- [ ] **Default to committing every versioned install script.** count_nulls is + small (~90-line source, half a dozen tracked versions, versus e.g. + cat_tools' ~2000-line source and two dozen versions) — the storage cost + of keeping every version's file is negligible here, so there's little + reason to skip it purely to save space. The update-test-coverage value + (being able to install any prior version and `ALTER EXTENSION UPDATE` + from it) is the same regardless of size; only skip committing a + version's install script for a truly trivial change where you've + already decided that coverage isn't worth even the small cost. + Update scripts (`sql/count_nulls----.sql`) are ALWAYS + committed regardless — they're the only thing that makes the update + path testable at all. + +## 4. Update version + changelog + +> **⚠️ CRITICAL — you are temporarily leaving the `stable` pseudo-version.** Master's +> `default_version` (in `count_nulls.control`) normally sits at the literal string +> `'stable'`, so that ordinary source edits regenerate `sql/count_nulls--stable.sql` +> (via the existing rule in `control.mk`, driven by whatever `default_version` says) +> and never touch a frozen, already-shipped version's file. Stamping a real version +> number here points that same generation rule at `sql/count_nulls--.sql` +> instead. The moment this release is merged you **MUST** flip `default_version` +> back to `stable` (step 7) if the extension version moved. If you forget, the next +> source edit on master will regenerate — and corrupt — the just-released version's +> install file. + +- [ ] If the extension version is moving, bump `default_version` in + `count_nulls.control` (bumped by hand). If only the distribution version is + moving (no SQL changes — see step 3), leave `default_version` alone. +- [ ] Bump the version in `META.in.json` — the source of truth is the top-level + `version` (the distribution version; always bump this) and + `provides.count_nulls.version` (the extension version; only bump if it's + actually moving, per step 3). `META.json`, `control.mk`, and `meta.mk` + (which feeds `PGXNVERSION`) regenerate via `make`. - [ ] Advance `release_status` in `META.in.json` as appropriate (unstable → testing → stable). -- [ ] Add the update script `sql/count_nulls----.sql`; confirm - `ALTER EXTENSION count_nulls UPDATE` from the previous version reaches the new - version, on multiple PG majors. `make test-update` exercises this directly — it - installs the oldest still-tracked version (currently `0.9.6`) and updates to - current, running the full suite against the result. +- [ ] If the extension version moved: add the update script + `sql/count_nulls----.sql`; confirm `ALTER EXTENSION count_nulls + UPDATE` from the previous version reaches the new version, on multiple PG + majors. +- [ ] Stamp `HISTORY.md`: the top `stable` section accumulates user-facing changes as + PRs land; at release, rename that header to the new (distribution) version + number. -## 4. Verify +## 5. Verify - [ ] `make verify-results` green (it runs `test` first, then gates on the results). -- [ ] `make test-update` green — confirms the update path, not just a fresh install. - [ ] From a clean checkout (or `git archive` of the tag): `make && make install` regenerates and installs cleanly and `CREATE EXTENSION count_nulls;` reports the - new version — confirms a PGXN consumer can build from the tracked sources alone. - (This mirrors what `make dist` ships, since it archives the tag: committed files - only.) + expected version — confirms a PGXN consumer can build from the tracked sources + alone. (This mirrors what `make dist` ships, since it archives the tag: + committed files only.) -## 5. Tag and distribute +## 6. Tag and distribute - [ ] Commit the release changes; working tree must be clean — `make tag` aborts with "Untracked changes!" on a dirty tree. -- [ ] `make tag` — creates a git tag named exactly the version, UNPREFIXED (e.g. `1.0.1`, - no `v` prefix), taken from `PGXNVERSION`, and pushes it to `origin`. It is - idempotent when the tag already points at HEAD, and errors if the tag exists on a - different commit. To move an existing tag use `make forcetag` (= `make rmtag` then - `make tag`); `make rmtag` deletes the tag locally and on `origin`. +- [ ] `make tag` — creates a git tag named exactly the DISTRIBUTION version, + UNPREFIXED (e.g. `1.0.0`, no `v` prefix), taken from `PGXNVERSION`, and pushes + it to `origin`. **Make sure `origin` in your checkout actually points at + `Postgres-Extensions/pg_count_nulls`, not a personal fork.** count_nulls used + release branches for every release through `0.9.7`; those have since been + replaced with tags (`0.1.0`, `0.9.0`..`0.9.7`) and the branches deleted, so + `make tag` is now the one and only release mechanism going forward. It's + idempotent when the tag already points at HEAD, and errors if the tag exists + on a different commit. To move an existing tag use `make forcetag` + (= `make rmtag` then `make tag`); `make rmtag` deletes the tag locally and on + `origin`. - [ ] `make dist` — depends on `tag` (and builds the HTML docs), then `git archive`s the tag into `../count_nulls-.zip` (parent directory). Because it archives the tag, only committed files are included. If a `.gitattributes` exists it must be committed, or `dist` aborts (git archive only honors `export-ignore` for committed files). `make forcedist` = `forcetag` + `dist`. - [ ] Upload the `../count_nulls-.zip` to PGXN (manual). + +## 7. Return master to `stable` (CRITICAL — do not skip, if the extension version moved) +- [ ] As soon as the release is merged, flip `default_version` back to `stable` in + `count_nulls.control`, open a new top `stable` section in `HISTORY.md`, and + re-seed a fresh `sql/count_nulls----stable.sql` update script + (content-identical to the source at this point — it exists purely so the update + path to `stable` is always available) for the next cycle. Leaving master + stamped at the real version means the next source edit regenerates and corrupts + the released version's install file. From 505076d858d50ef8a623dd84df7a9e478feca148 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 16:37:47 -0500 Subject: [PATCH 4/6] Populate HISTORY.md from git history Reconstructed one entry per real release (0.1.0 through 0.9.7) from each tag's actual diff, at pgxntool/HISTORY.asc's level of detail - a paragraph or == per-change section, not just a version bump line. Also credits the one real fix since 0.9.7 (dropping a redundant, unconditional client_min_messages override in the install script) under the pending "stable" section. --- HISTORY.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3309427..2b9dea1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,10 +3,88 @@ stable ------ -No user-facing changes yet. +== Fix client_min_messages handling in the install script +CREATE EXTENSION already raises client_min_messages to WARNING for the install +script (only-raising, so a stricter caller is respected) and restores it +afterward - the extension's own `SET client_min_messages = WARNING` was +redundant and, being unconditional, could lower a stricter caller's level +during install. Removed. + +0.9.7 +----- + +Distribution-only release - the extension version itself stayed at `0.9.6` +(see RELEASE.md's note on distribution vs. extension versions). Raised the +minimum supported PostgreSQL version to 9.4 (from 9.3), matching the jsonb +functions added in 0.9.5, and enabled Travis CI. + +0.9.6 +----- + +== Fix functions failing when installed outside the search_path +`null_count()`/`not_null_count()` and their trigger counterparts called each +other unqualified, so installing count_nulls into a schema not on the +caller's search_path broke them. All internal calls are now qualified with +`@extschema@`. Because that qualification is baked into the install script at +`CREATE EXTENSION` time, count_nulls is no longer relocatable +(`relocatable = false`). + +0.9.5 +----- + +== Change null_count()/not_null_count() to return int instead of bigint +Reverted the anyarray variants' return type from bigint (introduced in 0.1.0) +back to int - a count of NULL arguments realistically never approaches +bigint's range. + +== Add jsonb support +Added jsonb overloads of `null_count()`/`not_null_count()`; the existing json +overloads now delegate to them via a cast instead of duplicating the +`json_each_text` logic. + +0.9.4 +----- + +Metadata-only release: PostgreSQL's control-file parser rejects comments +inside `prereqs`, which the previous release's `META.in.json` had; fixed, and +documented the PostgreSQL 9.3 requirement. + +0.9.3 +----- + +Adopted pgxntool (https://github.com/decibel/pgxntool) as the project's build +system. No functional changes. + +0.9.2 +----- + +No functional changes - added the first upgrade script (`0.9.0` -> `0.9.2`) +and adjusted where `make dist` writes its distribution zip. + +0.9.1 +----- + +== Add null_count_trigger()/not_null_count_trigger() +Trigger functions for enforcing "this row must have exactly N NULL (or NOT +NULL) columns" as a table constraint, usable via +`CREATE TRIGGER ... EXECUTE PROCEDURE null_count_trigger(N)`. + +0.9.0 +----- + +== Add json support +Added `null_count(json)`/`not_null_count(json)`, counting NULL values across +a JSON object's top-level keys. + +0.1.0 +----- + +Initial release. `null_count()`/`not_null_count()` variadic functions, +counting NULL (or NOT NULL) arguments passed to them. --- -Releases before this file existed (`0.1.0` through `0.9.7`) aren't detailed here; -see https://pgxn.org/dist/count_nulls/ and the corresponding git tags for what -shipped in each. +Every entry above from `0.1.0` through `0.9.7` was reconstructed from git +history when this file was created (2026-07-24), not written contemporaneously +with each release - see the corresponding git tags and +https://pgxn.org/dist/count_nulls/ for the authoritative record. From f8dc4e77bb569e83de8ca8b7cbf88bb591e54618 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 16:44:09 -0500 Subject: [PATCH 5/6] Genericize RELEASE.md for reuse across repos; stamp HISTORY.md at 1.0.0 Replaced count_nulls-specific naming throughout RELEASE.md with an placeholder so other repos can adopt this doc with minimal find-and-replace, keeping only the intro paragraph's 0.9.7 anecdote as a concrete, repo-specific illustration of the distribution-vs-extension-version split. Also generalized the version-file-drift safety check, the size-based "commit every version file" guidance, and the tag-migration note in step 6, replacing count_nulls' own specific historical details with the general pattern they illustrate. HISTORY.md's pending section is now headed 1.0.0 instead of stable, since count_nulls.control is already stamped at 1.0.0 - an intentional exception for this first release while adopting the full process; every release after this one goes through the real stable dance. --- HISTORY.md | 4 +-- RELEASE.md | 96 ++++++++++++++++++++++++++---------------------------- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 2b9dea1..985b923 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,7 +1,7 @@ # count_nulls history -stable ------- +1.0.0 +----- == Fix client_min_messages handling in the install script CREATE EXTENSION already raises client_min_messages to WARNING for the install diff --git a/RELEASE.md b/RELEASE.md index ba717e8..b21f0e9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,13 +1,13 @@ -# Releasing count_nulls +# Releasing `` -count_nulls builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool); +`` builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool); the release machinery (`make tag`, `make dist`) lives in `pgxntool/base.mk`. These steps cut a new release. Two version numbers matter here and they can differ: the **distribution version** (`META.in.json`'s top-level `version`, feeds `PGXNVERSION`, what PGXN.org lists a -release under) and the **extension version** (`count_nulls.control`'s -`default_version`, what `CREATE EXTENSION count_nulls;` installs by default and +release under) and the **extension version** (`.control`'s +`default_version`, what `CREATE EXTENSION ;` installs by default and what `pg_extension.extversion` reports). They're usually bumped together, but count_nulls has already shipped a release where they weren't: `0.9.7` on PGXN (2017-01-26) was a distribution-only bump (packaging/CI fixes, no SQL changes) — @@ -17,31 +17,30 @@ the extension version stayed at `0.9.6`, unchanged since 2016. `0.9.7` the ## 1. Safety check: verify committed version files haven't drifted Before anything else, confirm every committed versioned install script -(`sql/count_nulls--.sql`) still matches what that version actually +(`sql/--.sql`) still matches what that version actually shipped — this is the check that the `stable`-vs-real-version dance (step 4/7 below) exists to make routine, but it's worth a direct look before relying on it. -- [ ] For each `sql/count_nulls--.sql`, find its last-touching commit - (`git log -1 --format='%H %ad' -- sql/count_nulls--.sql`). +- [ ] For each `sql/--.sql`, find its last-touching commit + (`git log -1 --format='%H %ad' -- sql/--.sql`). - [ ] Confirm that commit is no later than when that EXTENSION version actually - shipped. `git tag` is now the authoritative source for this — `0.1.0` through - `0.9.7` (the releases published before this project switched to tags) were - backfilled as annotated tags pointing at the commits their old, now-deleted - release branches used to point to. Cross-check against - https://pgxn.org/dist/count_nulls/ if anything looks off, remembering that - page lists *distribution* versions, which can lag the extension version they - contain (see `0.9.7` above). + shipped. `git tag` is the authoritative source for this once the project + is on a tag-based release history. If any releases predate that (tracked + some other way, e.g. release branches, or not tracked at all), fall back + to cross-checking https://pgxn.org/dist/``/, remembering that + page lists *distribution* versions, which can lag the extension version + they contain (see `0.9.7` above). - [ ] A version file touched by a commit LATER than its own release is a red flag — it likely means `default_version` was left pointing at a real (non-`stable`) version and a later source edit silently regenerated (corrupted) it. Investigate before proceeding. -- [ ] **Known exception, not a corruption:** `sql/count_nulls--0.9.6.sql`'s - last-touching commit is from 2026 (the pgxntool 2.1.0 update), a decade - after `0.9.6` itself shipped in 2016. That's a straight backfill of the - historically-shipped content (needed once pgxntool started requiring - committed version files), not evidence the file's content is wrong — - don't flag re-added-later historical files as suspicious on their own; - only worry about ones whose *content* might have changed after release. +- [ ] **Known exception, not necessarily a corruption:** a version file whose + last-touching commit is much later than its version's real release can + also mean the file was legitimately backfilled after the fact (e.g. a + newer pgxntool version started requiring committed version files that + weren't tracked before). A late add-date alone isn't suspicious — only + worry about a file whose *content* looks like it might differ from what + actually shipped. ## 2. Pre-release checks - [ ] Open issues/PRs for this release reviewed, merged or deferred. @@ -55,44 +54,44 @@ below) exists to make routine, but it's worth a direct look before relying on it - [ ] Pick the new version (semantic versioning). Decide whether the extension version needs to move at all, or (per the `0.9.7` precedent above) only the distribution version does, if this release has no SQL changes. -- [ ] **Default to committing every versioned install script.** count_nulls is - small (~90-line source, half a dozen tracked versions, versus e.g. - cat_tools' ~2000-line source and two dozen versions) — the storage cost - of keeping every version's file is negligible here, so there's little - reason to skip it purely to save space. The update-test-coverage value - (being able to install any prior version and `ALTER EXTENSION UPDATE` - from it) is the same regardless of size; only skip committing a - version's install script for a truly trivial change where you've - already decided that coverage isn't worth even the small cost. - Update scripts (`sql/count_nulls----.sql`) are ALWAYS - committed regardless — they're the only thing that makes the update - path testable at all. +- [ ] **Default to committing every versioned install script.** For a small + extension (a handful of tracked versions, a source file measured in + dozens of lines rather than thousands), the storage cost of keeping + every version's file is negligible — there's little reason to skip it + purely to save space. The update-test-coverage value (being able to + install any prior version and `ALTER EXTENSION UPDATE` from it) is the + same regardless of size; only skip committing a version's install + script for a truly trivial change where you've already decided that + coverage isn't worth even the small cost. Update scripts + (`sql/----.sql`) are ALWAYS committed + regardless — they're the only thing that makes the update path + testable at all. ## 4. Update version + changelog > **⚠️ CRITICAL — you are temporarily leaving the `stable` pseudo-version.** Master's -> `default_version` (in `count_nulls.control`) normally sits at the literal string -> `'stable'`, so that ordinary source edits regenerate `sql/count_nulls--stable.sql` +> `default_version` (in `.control`) normally sits at the literal string +> `'stable'`, so that ordinary source edits regenerate `sql/--stable.sql` > (via the existing rule in `control.mk`, driven by whatever `default_version` says) > and never touch a frozen, already-shipped version's file. Stamping a real version -> number here points that same generation rule at `sql/count_nulls--.sql` +> number here points that same generation rule at `sql/--.sql` > instead. The moment this release is merged you **MUST** flip `default_version` > back to `stable` (step 7) if the extension version moved. If you forget, the next > source edit on master will regenerate — and corrupt — the just-released version's > install file. - [ ] If the extension version is moving, bump `default_version` in - `count_nulls.control` (bumped by hand). If only the distribution version is + `.control` (bumped by hand). If only the distribution version is moving (no SQL changes — see step 3), leave `default_version` alone. - [ ] Bump the version in `META.in.json` — the source of truth is the top-level `version` (the distribution version; always bump this) and - `provides.count_nulls.version` (the extension version; only bump if it's + `provides..version` (the extension version; only bump if it's actually moving, per step 3). `META.json`, `control.mk`, and `meta.mk` (which feeds `PGXNVERSION`) regenerate via `make`. - [ ] Advance `release_status` in `META.in.json` as appropriate (unstable → testing → stable). - [ ] If the extension version moved: add the update script - `sql/count_nulls----.sql`; confirm `ALTER EXTENSION count_nulls + `sql/----.sql`; confirm `ALTER EXTENSION UPDATE` from the previous version reaches the new version, on multiple PG majors. - [ ] Stamp `HISTORY.md`: the top `stable` section accumulates user-facing changes as @@ -102,7 +101,7 @@ below) exists to make routine, but it's worth a direct look before relying on it ## 5. Verify - [ ] `make verify-results` green (it runs `test` first, then gates on the results). - [ ] From a clean checkout (or `git archive` of the tag): `make && make install` - regenerates and installs cleanly and `CREATE EXTENSION count_nulls;` reports the + regenerates and installs cleanly and `CREATE EXTENSION ;` reports the expected version — confirms a PGXN consumer can build from the tracked sources alone. (This mirrors what `make dist` ships, since it archives the tag: committed files only.) @@ -112,26 +111,25 @@ below) exists to make routine, but it's worth a direct look before relying on it "Untracked changes!" on a dirty tree. - [ ] `make tag` — creates a git tag named exactly the DISTRIBUTION version, UNPREFIXED (e.g. `1.0.0`, no `v` prefix), taken from `PGXNVERSION`, and pushes - it to `origin`. **Make sure `origin` in your checkout actually points at - `Postgres-Extensions/pg_count_nulls`, not a personal fork.** count_nulls used - release branches for every release through `0.9.7`; those have since been - replaced with tags (`0.1.0`, `0.9.0`..`0.9.7`) and the branches deleted, so - `make tag` is now the one and only release mechanism going forward. It's + it to `origin`. **Make sure `origin` in your checkout actually points at the + real upstream repo, not a personal fork.** If this project has used a + different release-tracking scheme before (release branches, no tracking at + all, etc.), `make tag` is the sole mechanism going forward once migrated. It's idempotent when the tag already points at HEAD, and errors if the tag exists on a different commit. To move an existing tag use `make forcetag` (= `make rmtag` then `make tag`); `make rmtag` deletes the tag locally and on `origin`. - [ ] `make dist` — depends on `tag` (and builds the HTML docs), then - `git archive`s the tag into `../count_nulls-.zip` (parent directory). + `git archive`s the tag into `../-.zip` (parent directory). Because it archives the tag, only committed files are included. If a `.gitattributes` exists it must be committed, or `dist` aborts (git archive only honors `export-ignore` for committed files). `make forcedist` = `forcetag` + `dist`. -- [ ] Upload the `../count_nulls-.zip` to PGXN (manual). +- [ ] Upload the `../-.zip` to PGXN (manual). ## 7. Return master to `stable` (CRITICAL — do not skip, if the extension version moved) - [ ] As soon as the release is merged, flip `default_version` back to `stable` in - `count_nulls.control`, open a new top `stable` section in `HISTORY.md`, and - re-seed a fresh `sql/count_nulls----stable.sql` update script + `.control`, open a new top `stable` section in `HISTORY.md`, and + re-seed a fresh `sql/----stable.sql` update script (content-identical to the source at this point — it exists purely so the update path to `stable` is always available) for the next cycle. Leaving master stamped at the real version means the next source edit regenerates and corrupts From bb9afc10543edecb736768f7205bfab4534f5a37 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 17:07:55 -0500 Subject: [PATCH 6/6] Re-evaluate RELEASE.md's genericization: prefer plain English over The previous pass over-mechanically substituted for every mention of count_nulls, including in plain-English prose where no placeholder was needed at all, and split the L7 paragraph awkwardly between generic placeholder text and a specific count_nulls anecdote mid-paragraph. Reworded throughout: the title and opening sentence no longer need the extension name at all ("This extension makes use of pgxntool" rather than claiming to build on/extend it), and every other prose reference became plain English ("the extension's .control file", "the extension's own entry under provides") instead of a placeholder. Kept only where it's a literal, copy-pasteable file-naming pattern (shown once each, not repeated per bullet). Reverted the L7 paragraph to name count_nulls throughout, not just its second half - the whole paragraph is a real, specific illustration of the distribution-vs-extension-version split, not generic instructional text. --- RELEASE.md | 78 +++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index b21f0e9..78cbac9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,13 +1,13 @@ -# Releasing `` +# Release -`` builds on pgxntool (https://github.com/Postgres-Extensions/pgxntool); +This extension makes use of pgxntool (https://github.com/Postgres-Extensions/pgxntool); the release machinery (`make tag`, `make dist`) lives in `pgxntool/base.mk`. These steps cut a new release. Two version numbers matter here and they can differ: the **distribution version** (`META.in.json`'s top-level `version`, feeds `PGXNVERSION`, what PGXN.org lists a -release under) and the **extension version** (`.control`'s -`default_version`, what `CREATE EXTENSION ;` installs by default and +release under) and the **extension version** (`count_nulls.control`'s +`default_version`, what `CREATE EXTENSION count_nulls;` installs by default and what `pg_extension.extversion` reports). They're usually bumped together, but count_nulls has already shipped a release where they weren't: `0.9.7` on PGXN (2017-01-26) was a distribution-only bump (packaging/CI fixes, no SQL changes) — @@ -16,20 +16,20 @@ the extension version stayed at `0.9.6`, unchanged since 2016. `0.9.7` the ## 1. Safety check: verify committed version files haven't drifted -Before anything else, confirm every committed versioned install script -(`sql/--.sql`) still matches what that version actually -shipped — this is the check that the `stable`-vs-real-version dance (step 4/7 -below) exists to make routine, but it's worth a direct look before relying on it. +Before anything else, confirm every committed versioned install script still +matches what that version actually shipped — this is the check that the +`stable`-vs-real-version dance (step 4/7 below) exists to make routine, but +it's worth a direct look before relying on it. -- [ ] For each `sql/--.sql`, find its last-touching commit - (`git log -1 --format='%H %ad' -- sql/--.sql`). +- [ ] For each versioned install script, find its last-touching commit: + `git log -1 --format='%H %ad' -- sql/--.sql`. - [ ] Confirm that commit is no later than when that EXTENSION version actually shipped. `git tag` is the authoritative source for this once the project is on a tag-based release history. If any releases predate that (tracked some other way, e.g. release branches, or not tracked at all), fall back - to cross-checking https://pgxn.org/dist/``/, remembering that - page lists *distribution* versions, which can lag the extension version - they contain (see `0.9.7` above). + to cross-checking the extension's PGXN.org listing, remembering that it + lists *distribution* versions, which can lag the extension version they + contain (see the `0.9.7` example above). - [ ] A version file touched by a commit LATER than its own release is a red flag — it likely means `default_version` was left pointing at a real (non-`stable`) version and a later source edit silently regenerated @@ -52,7 +52,7 @@ below) exists to make routine, but it's worth a direct look before relying on it ## 3. Decide the version and what to track - [ ] Pick the new version (semantic versioning). Decide whether the extension - version needs to move at all, or (per the `0.9.7` precedent above) only the + version needs to move at all, or (per the `0.9.7` example above) only the distribution version does, if this release has no SQL changes. - [ ] **Default to committing every versioned install script.** For a small extension (a handful of tracked versions, a source file measured in @@ -62,37 +62,37 @@ below) exists to make routine, but it's worth a direct look before relying on it install any prior version and `ALTER EXTENSION UPDATE` from it) is the same regardless of size; only skip committing a version's install script for a truly trivial change where you've already decided that - coverage isn't worth even the small cost. Update scripts - (`sql/----.sql`) are ALWAYS committed + coverage isn't worth even the small cost. Update scripts (following the + `sql/----.sql` naming) are ALWAYS committed regardless — they're the only thing that makes the update path testable at all. ## 4. Update version + changelog > **⚠️ CRITICAL — you are temporarily leaving the `stable` pseudo-version.** Master's -> `default_version` (in `.control`) normally sits at the literal string -> `'stable'`, so that ordinary source edits regenerate `sql/--stable.sql` -> (via the existing rule in `control.mk`, driven by whatever `default_version` says) -> and never touch a frozen, already-shipped version's file. Stamping a real version -> number here points that same generation rule at `sql/--.sql` -> instead. The moment this release is merged you **MUST** flip `default_version` +> `default_version` (in the extension's `.control` file) normally sits at the literal +> string `'stable'`, so that ordinary source edits regenerate the current install +> script (via the existing rule in `control.mk`, driven by whatever `default_version` +> says) and never touch a frozen, already-shipped version's file. Stamping a real +> version number here points that same generation rule at the real version's install +> script instead. The moment this release is merged you **MUST** flip `default_version` > back to `stable` (step 7) if the extension version moved. If you forget, the next > source edit on master will regenerate — and corrupt — the just-released version's > install file. -- [ ] If the extension version is moving, bump `default_version` in - `.control` (bumped by hand). If only the distribution version is - moving (no SQL changes — see step 3), leave `default_version` alone. +- [ ] If the extension version is moving, bump `default_version` in the `.control` + file (bumped by hand). If only the distribution version is moving (no SQL + changes — see step 3), leave `default_version` alone. - [ ] Bump the version in `META.in.json` — the source of truth is the top-level - `version` (the distribution version; always bump this) and - `provides..version` (the extension version; only bump if it's + `version` (the distribution version; always bump this) and the extension's + own entry under `provides` (the extension version; only bump if it's actually moving, per step 3). `META.json`, `control.mk`, and `meta.mk` (which feeds `PGXNVERSION`) regenerate via `make`. - [ ] Advance `release_status` in `META.in.json` as appropriate (unstable → testing → stable). -- [ ] If the extension version moved: add the update script - `sql/----.sql`; confirm `ALTER EXTENSION - UPDATE` from the previous version reaches the new version, on multiple PG +- [ ] If the extension version moved: add the update script from the previous + version to the new one; confirm the `ALTER EXTENSION UPDATE` path + actually reaches the new version from the previous one, on multiple PG majors. - [ ] Stamp `HISTORY.md`: the top `stable` section accumulates user-facing changes as PRs land; at release, rename that header to the new (distribution) version @@ -101,7 +101,7 @@ below) exists to make routine, but it's worth a direct look before relying on it ## 5. Verify - [ ] `make verify-results` green (it runs `test` first, then gates on the results). - [ ] From a clean checkout (or `git archive` of the tag): `make && make install` - regenerates and installs cleanly and `CREATE EXTENSION ;` reports the + regenerates and installs cleanly, and creating the extension reports the expected version — confirms a PGXN consumer can build from the tracked sources alone. (This mirrors what `make dist` ships, since it archives the tag: committed files only.) @@ -120,17 +120,17 @@ below) exists to make routine, but it's worth a direct look before relying on it (= `make rmtag` then `make tag`); `make rmtag` deletes the tag locally and on `origin`. - [ ] `make dist` — depends on `tag` (and builds the HTML docs), then - `git archive`s the tag into `../-.zip` (parent directory). + `git archive`s the tag into a distribution zip in the parent directory. Because it archives the tag, only committed files are included. If a `.gitattributes` exists it must be committed, or `dist` aborts (git archive only honors `export-ignore` for committed files). `make forcedist` = `forcetag` + `dist`. -- [ ] Upload the `../-.zip` to PGXN (manual). +- [ ] Upload the resulting zip to PGXN (manual). ## 7. Return master to `stable` (CRITICAL — do not skip, if the extension version moved) - [ ] As soon as the release is merged, flip `default_version` back to `stable` in - `.control`, open a new top `stable` section in `HISTORY.md`, and - re-seed a fresh `sql/----stable.sql` update script - (content-identical to the source at this point — it exists purely so the update - path to `stable` is always available) for the next cycle. Leaving master - stamped at the real version means the next source edit regenerates and corrupts - the released version's install file. + the `.control` file, open a new top `stable` section in `HISTORY.md`, and + re-seed a fresh update script from this release to `stable` (content-identical + to the source at this point — it exists purely so the update path to `stable` + is always available) for the next cycle. Leaving master stamped at the real + version means the next source edit regenerates and corrupts the released + version's install file.