forked from heavy-duty/ceremony
Merge pull request #99 from codex-bot-andresmgsl/build/98-publishable-changelog-sections
feat: validate publishable changelog sections
This commit is contained in:
commit
ad04eaf990
13 changed files with 291 additions and 27 deletions
24
.github/workflows/release-exercise.yml
vendored
24
.github/workflows/release-exercise.yml
vendored
|
|
@ -171,12 +171,32 @@ jobs:
|
|||
run: |
|
||||
# shellcheck source=/dev/null
|
||||
. "$CEREMONY_DIR/lib/changelog.sh"
|
||||
changelog_section CHANGELOG.md "$VER" > "$RUNNER_TEMP/notes.md"
|
||||
if [ ! -s "$RUNNER_TEMP/notes.md" ]; then
|
||||
if ! diagnosis="$(changelog_section_problem CHANGELOG.md "$VER")"; then
|
||||
echo "CHANGELOG.md has no '## $VER' section at the merge commit — the ceremony PR must stamp it; refusing to publish an empty release" >&2
|
||||
printf '%s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
fi
|
||||
changelog_section CHANGELOG.md "$VER" > "$RUNNER_TEMP/notes.md"
|
||||
cat "$RUNNER_TEMP/notes.md"
|
||||
- name: an entry-less stamped fixture is refused by the notes predicate
|
||||
working-directory: ${{ runner.temp }}/fixture
|
||||
env:
|
||||
VER: ${{ steps.facts.outputs.ver }}
|
||||
run: |
|
||||
cp CHANGELOG.md "$RUNNER_TEMP/CHANGELOG.good.md"
|
||||
awk -v ver="$VER" '
|
||||
/^## / { in_section = ($2 == ver) }
|
||||
in_section && /^[[:space:]]*[-*][[:space:]]/ { next }
|
||||
{ print }
|
||||
' "$RUNNER_TEMP/CHANGELOG.good.md" > CHANGELOG.md
|
||||
# shellcheck source=/dev/null
|
||||
. "$CEREMONY_DIR/lib/changelog.sh"
|
||||
if diagnosis="$(changelog_section_problem CHANGELOG.md "$VER")"; then
|
||||
echo "entry-less stamped section unexpectedly passed" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$diagnosis" | grep -F "section '$VER' has no entries"
|
||||
cp "$RUNNER_TEMP/CHANGELOG.good.md" CHANGELOG.md
|
||||
- name: the chain must land where the fixture says it lands
|
||||
env:
|
||||
CEREMONY: ${{ steps.decide.outputs.ceremony }}
|
||||
|
|
|
|||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
|
@ -198,11 +198,12 @@ jobs:
|
|||
run: |
|
||||
# shellcheck source=/dev/null
|
||||
. "$CEREMONY_DIR/lib/changelog.sh"
|
||||
changelog_section CHANGELOG.md "$VER" > "$RUNNER_TEMP/notes.md"
|
||||
if [ ! -s "$RUNNER_TEMP/notes.md" ]; then
|
||||
if ! diagnosis="$(changelog_section_problem CHANGELOG.md "$VER")"; then
|
||||
echo "CHANGELOG.md has no '## $VER' section at the merge commit — the ceremony PR must stamp it; refusing to publish an empty release" >&2
|
||||
printf '%s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
fi
|
||||
changelog_section CHANGELOG.md "$VER" > "$RUNNER_TEMP/notes.md"
|
||||
cat "$RUNNER_TEMP/notes.md"
|
||||
- name: nothing may exist yet — no tag, no release (re-runs refuse loudly)
|
||||
if: steps.decide.outputs.ceremony == 'yes'
|
||||
|
|
@ -342,11 +343,12 @@ jobs:
|
|||
run: |
|
||||
# shellcheck source=/dev/null
|
||||
. "$CEREMONY_DIR/lib/changelog.sh"
|
||||
changelog_section CHANGELOG.md "$VER" > "$RUNNER_TEMP/notes.md"
|
||||
if [ ! -s "$RUNNER_TEMP/notes.md" ]; then
|
||||
if ! diagnosis="$(changelog_section_problem CHANGELOG.md "$VER")"; then
|
||||
echo "CHANGELOG.md has no '## $VER' section — stamp the Unreleased section in the release PR before tagging; refusing to publish an empty release" >&2
|
||||
printf '%s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
fi
|
||||
changelog_section CHANGELOG.md "$VER" > "$RUNNER_TEMP/notes.md"
|
||||
cat "$RUNNER_TEMP/notes.md"
|
||||
- name: artifact hook — the consumer's own release-artifact action
|
||||
# After the tag exists (it fired this door), before the publish —
|
||||
|
|
|
|||
|
|
@ -84,8 +84,9 @@ triage bug, and the move is to say so on the issue, not to guess.
|
|||
one turns out to be wrong or unreachable, say so on the issue and get it
|
||||
amended by triage — do not silently ship less than the issue says.
|
||||
- Every behavior change adds one line to `CHANGELOG.md` under
|
||||
`## Unreleased` — insert **above** the heading below it, never over it
|
||||
(the monotonic guard's whole reason to exist).
|
||||
`## Unreleased` — in a grouped changelog, append under an existing heading
|
||||
and create one only when the kind is genuinely new; insert **above** the
|
||||
heading below, never over it (the monotonic guard's whole reason to exist).
|
||||
- Follow the repo's conventions file and match the code you touch. Tests are
|
||||
not optional: the issue's test plan is the floor, not the ceiling.
|
||||
- **Scope discipline: the PR does the issue — whole, and nothing else.**
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ so entries say what changed, cite the issue, and stop.
|
|||
|
||||
## Unreleased
|
||||
|
||||
- Changelog publication — count entries instead of bytes, refuse dangling grouped headings, and seed grouped re-arms with Added/Changed/Fixed (#98).
|
||||
- `labels-reconcile` — grant callers private-repo check reads and warn when an entire PR sweep is blind (#95).
|
||||
- `labels-reconcile` — the bootstrap now retires the six GitHub defaults `LABELS.md` publishes as deleted, tolerating both an already-absent label and a refused delete (#93).
|
||||
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ already proven:
|
|||
1. **One issue, one PR**, opened as a **draft** while building, with
|
||||
`Closes #N` in the body. Drafts are invisible to the reviewer panel on
|
||||
purpose. Every behavior change adds one line to `CHANGELOG.md` under
|
||||
`## Unreleased` (insert **above** the heading below — never type over it;
|
||||
the monotonic guard exists because of exactly that edit).
|
||||
`## Unreleased` (in a grouped changelog, append under an existing heading
|
||||
and create one only when the kind is genuinely new; insert **above** the
|
||||
heading below — never type over it; the monotonic guard exists because of
|
||||
exactly that edit).
|
||||
2. **When it's ready**: mark ready-for-review and request the whole panel.
|
||||
3. **Rounds are answered whole.** Wait until every reviewer has a verdict in,
|
||||
then answer the entire round in a **single reply**, push the fixes, and
|
||||
|
|
|
|||
25
README.md
25
README.md
|
|
@ -23,11 +23,18 @@ stamps:
|
|||
([lib/version.sh](lib/version.sh)).
|
||||
2. **The changelog is stamped *and re-armed* — two edits, not one**
|
||||
(box#108). `## Unreleased` becomes `## X.Y.Z — DATE`, and an **empty
|
||||
`## Unreleased` goes back on top**, immediately above it:
|
||||
`## Unreleased` goes back on top**, immediately above it. When the repo
|
||||
groups entries, the ceremony PR seeds its three standing headings:
|
||||
|
||||
```markdown
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## 0.7.1 — 2026-07-19
|
||||
|
||||
### Fixed
|
||||
|
|
@ -136,15 +143,19 @@ keyed on the tree's version:
|
|||
- `-dev` tree → the top section **must** be `## Unreleased`.
|
||||
- bare tree (the ceremony PR and its merge) → the top section may be
|
||||
`## Unreleased` (re-armed) *or* the stamped section for exactly that
|
||||
version — **and** that version's section must exist and carry prose,
|
||||
because it is the one about to ship (the half-ceremony refusal, rig#67:
|
||||
version bumped, stamp missing — asserted through the very extractor the
|
||||
publisher uses, so the two cannot disagree about what a section is).
|
||||
version — **and** that version's section must exist, carry at least one
|
||||
`-` or `*` entry, and have no `### ` heading without an entry before the
|
||||
next heading or section end. A heading is not an entry. These publication
|
||||
rules do not apply to `Unreleased`: the empty three-heading template is
|
||||
deliberately valid there (the half-ceremony refusal, rig#67: version
|
||||
bumped, stamp missing — asserted through the very extractor the publisher
|
||||
uses, so the two cannot disagree about what a section is).
|
||||
|
||||
**The incident**: box#108 / rig#66 — the silent mislanding described
|
||||
[above](#what-a-release-is). **Red means** a PR entry has nowhere safe to
|
||||
land; **the fix** is to re-arm: add an empty `## Unreleased` above the top
|
||||
stamped section.
|
||||
land, or a stamped version would publish no entries or a dangling grouped
|
||||
heading; **the fix** is to re-arm the top or delete/populate the named
|
||||
heading before publishing.
|
||||
|
||||
**Do not "simplify" this to "always require `## Unreleased`".** The
|
||||
unconditional form is false by construction on the ceremony PR's own tree
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ EOF
|
|||
# the very extractor the publisher uses — changelog_section (#4) — so the
|
||||
# guard and the publisher cannot disagree about what a section is or when
|
||||
# one counts as empty (rig#67).
|
||||
if [ -z "$(changelog_section "$changelog" "$ver")" ]; then
|
||||
if ! diagnosis="$(changelog_section_problem "$changelog" "$ver")"; then
|
||||
cat >&2 <<EOF
|
||||
changelog-armed: the version is '$ver' but $changelog has no non-empty
|
||||
section for '$ver'. The top section is:
|
||||
|
|
@ -150,6 +150,8 @@ changelog-armed: the version is '$ver' but $changelog has no non-empty
|
|||
|
||||
The fix is the ceremony's first edit: stamp '## Unreleased' into
|
||||
'## $ver — DATE', then put an empty '## Unreleased' back above it.
|
||||
|
||||
$diagnosis
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ fi
|
|||
exit 1
|
||||
}
|
||||
|
||||
notes="$(changelog_section "$changelog" "$ver")"
|
||||
[ -n "$notes" ] || {
|
||||
if ! diagnosis="$(changelog_section_problem "$changelog" "$ver")"; then
|
||||
echo "changelog-section: $changelog has no section for '$ver' — the release PR stamps the Unreleased section with version + date BEFORE the tag" >&2
|
||||
printf 'changelog-section: %s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
notes="$(changelog_section "$changelog" "$ver")"
|
||||
printf '%s\n' "$notes"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,18 @@ the machinery at all:
|
|||
keeps the repo clear of it entirely. (`package-json` backend: the
|
||||
`version` field, same rule.)
|
||||
2. **An armed `CHANGELOG.md`**: a preamble plus an empty `## Unreleased`
|
||||
section for the first entries to land under.
|
||||
section for the first entries to land under. If the repo groups entries,
|
||||
seed the shape the ceremony PR will restore at every re-arm:
|
||||
|
||||
```markdown
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
```
|
||||
3. **`drills/README.md`** defining what a drill *means* in this repo —
|
||||
each repo names its own
|
||||
([the drill doctrine](../README.md#the-drill-doctrine)). Plain
|
||||
|
|
@ -382,6 +393,11 @@ The portable version of the family's contributor rule — the repo's own
|
|||
CONTRIBUTING may sharpen it, but this is the floor the guards assume:
|
||||
|
||||
- **Every PR that changes behavior adds one line** under `## Unreleased`.
|
||||
- **Grouped changelogs keep three standing headings:** `### Added`,
|
||||
`### Changed`, and `### Fixed`. The ceremony PR's hand-edited re-arm seeds
|
||||
all three; append under one instead of creating a heading at the top
|
||||
anchor. Create `Deprecated`, `Removed`, or `Security` only when a change
|
||||
genuinely needs that rarer kind.
|
||||
- **Insert above the heading below — never type over it.** Replacing a
|
||||
shipped `## X.Y.Z` heading with your entry deletes that release's
|
||||
section, silently; this exact edit is why the
|
||||
|
|
@ -393,7 +409,8 @@ CONTRIBUTING may sharpen it, but this is the floor the guards assume:
|
|||
belong in the PR body, where anyone chasing the reasoning already goes.
|
||||
- **Cite the issue or PR** — `(#141)`.
|
||||
- **Mark a breaking change** with a leading `BREAKING:`.
|
||||
- Group under `### Added` / `### Changed` / `### Fixed` / `### Removed`.
|
||||
- Flat changelogs remain flat; the standing-heading rule applies only when a
|
||||
repo already groups its entries.
|
||||
|
||||
## Adopting the agent team flow
|
||||
|
||||
|
|
|
|||
|
|
@ -20,3 +20,48 @@ changelog_section() {
|
|||
found { body = 1; print }
|
||||
' "$1"
|
||||
}
|
||||
|
||||
# changelog_section_problem <file> <version>
|
||||
#
|
||||
# Print the first reason a version section cannot be published. Unreleased is
|
||||
# a work-in-progress template, so its headings may deliberately be empty.
|
||||
# A printed problem returns 1; silence returns 0.
|
||||
changelog_section_problem() {
|
||||
local file="$1" ver="$2" notes problem
|
||||
|
||||
if ! awk -v ver="$ver" '/^## / && $2 == ver { found = 1; exit } END { exit !found }' "$file"; then
|
||||
printf "no section for '%s'\n" "$ver"
|
||||
return 1
|
||||
fi
|
||||
|
||||
[ "$ver" = "Unreleased" ] && return 0
|
||||
|
||||
notes="$(changelog_section "$file" "$ver")"
|
||||
if ! printf '%s\n' "$notes" | awk '/^[[:space:]]*[-*][[:space:]]/ { found = 1; exit } END { exit !found }'; then
|
||||
printf "section '%s' has no entries — a heading is not an entry\n" "$ver"
|
||||
return 1
|
||||
fi
|
||||
|
||||
problem="$(
|
||||
printf '%s\n' "$notes" | awk '
|
||||
/^### / {
|
||||
if (heading != "" && !entry) {
|
||||
reported = 1
|
||||
print heading
|
||||
exit
|
||||
}
|
||||
heading = $0
|
||||
entry = 0
|
||||
next
|
||||
}
|
||||
heading != "" && /^[[:space:]]*[-*][[:space:]]/ { entry = 1 }
|
||||
END {
|
||||
if (!reported && heading != "" && !entry) print heading
|
||||
}
|
||||
'
|
||||
)"
|
||||
if [ -n "$problem" ]; then
|
||||
printf "section '%s' has an empty heading: '%s'\n" "$ver" "$problem"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,25 @@ tree dev-armed 1.2.4-dev <<'EOF'
|
|||
EOF
|
||||
check "-dev + Unreleased on top passes" 0 "agrees" in_tree dev-armed
|
||||
|
||||
tree dev-seeded 1.2.4-dev <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## 1.2.3 — 2026-07-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- The shipped entry.
|
||||
EOF
|
||||
check "-dev + seeded empty Unreleased headings passes" 0 "agrees" in_tree dev-seeded
|
||||
|
||||
tree dev-stamped 1.2.4-dev <<'EOF'
|
||||
# Changelog
|
||||
|
||||
|
|
@ -90,6 +109,34 @@ tree bare-stamped 1.2.3 <<'EOF'
|
|||
EOF
|
||||
check "bare + own stamped section on top passes" 0 "agrees" in_tree bare-stamped
|
||||
|
||||
tree bare-dangling-heading 1.2.3 <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## 1.2.3 — 2026-07-20
|
||||
|
||||
### Added
|
||||
EOF
|
||||
check "bare + dangling heading fails with the heading diagnosis" 1 \
|
||||
"section '1.2.3' has no entries — a heading is not an entry" \
|
||||
in_tree bare-dangling-heading
|
||||
check "bare + dangling heading keeps the half-ceremony remedy" 1 \
|
||||
"HALF-DONE ceremony" in_tree bare-dangling-heading
|
||||
|
||||
tree bare-partly-dangling 1.2.3 <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## 1.2.3 — 2026-07-20
|
||||
|
||||
### Added
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed entry.
|
||||
EOF
|
||||
check "bare + one empty grouped heading names the first empty heading" 1 \
|
||||
"section '1.2.3' has an empty heading: '### Added'" \
|
||||
in_tree bare-partly-dangling
|
||||
|
||||
tree bare-empty-stamp 1.2.3 <<'EOF'
|
||||
# Changelog
|
||||
|
||||
|
|
|
|||
|
|
@ -57,11 +57,101 @@ check "date-less version heading parses" 0 "" assert_section 0.4.0 '- A date-les
|
|||
check "empty stamped section returns empty output" 0 "" assert_section 0.5.0 ""
|
||||
check "missing section returns empty output" 0 "" assert_section 9.9.9 ""
|
||||
|
||||
PROBLEM_FIXTURE="$TMP/CHANGELOG.problems.md"
|
||||
assert_problem() {
|
||||
local version="$1" expected_status="$2" expected="$3"
|
||||
check "predicate: $version / $expected" "$expected_status" "$expected" \
|
||||
changelog_section_problem "$PROBLEM_FIXTURE" "$version"
|
||||
}
|
||||
|
||||
cat >"$PROBLEM_FIXTURE" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Flat dash entry.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
* Flat star entry.
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed entry.
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Added
|
||||
|
||||
- Added entry.
|
||||
|
||||
### Changed
|
||||
|
||||
* Changed entry.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed entry.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Added
|
||||
|
||||
## 1.6.0
|
||||
|
||||
### Added
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed entry.
|
||||
EOF
|
||||
|
||||
assert_problem Unreleased 0 ""
|
||||
assert_problem 1.0.0 0 ""
|
||||
assert_problem 1.1.0 0 ""
|
||||
assert_problem 1.2.0 0 ""
|
||||
assert_problem 1.3.0 0 ""
|
||||
assert_problem 1.4.0 1 "section '1.4.0' has no entries — a heading is not an entry"
|
||||
assert_problem 1.5.0 1 "section '1.5.0' has no entries — a heading is not an entry"
|
||||
assert_problem 1.6.0 1 "section '1.6.0' has an empty heading: '### Added'"
|
||||
assert_problem 9.9.9 1 "no section for '9.9.9'"
|
||||
|
||||
MISSING_UNRELEASED_FIXTURE="$TMP/CHANGELOG.missing-unreleased.md"
|
||||
cat >"$MISSING_UNRELEASED_FIXTURE" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Released entry.
|
||||
EOF
|
||||
check "predicate: absent Unreleased still refuses" 1 "no section for 'Unreleased'" \
|
||||
changelog_section_problem "$MISSING_UNRELEASED_FIXTURE" Unreleased
|
||||
|
||||
WRAPPER="$ROOT/bin/changelog-section"
|
||||
check "wrapper publishes the requested body" 0 "The seven-oh entry" "$WRAPPER" 0.7.0 "$FIXTURE"
|
||||
check "wrapper refuses an empty section" 1 "no section for '0.5.0'" "$WRAPPER" 0.5.0 "$FIXTURE"
|
||||
check "wrapper refuses an absent section" 1 "no section for '9.9.9'" "$WRAPPER" 9.9.9 "$FIXTURE"
|
||||
check "wrapper explains how the release PR fixes refusal" 1 "stamps the Unreleased section" "$WRAPPER" 9.9.9 "$FIXTURE"
|
||||
check "wrapper refuses a heading-only version section" 1 \
|
||||
"section '1.5.0' has no entries — a heading is not an entry" \
|
||||
"$WRAPPER" 1.5.0 "$PROBLEM_FIXTURE"
|
||||
check "wrapper names the first dangling heading" 1 \
|
||||
"section '1.6.0' has an empty heading: '### Added'" \
|
||||
"$WRAPPER" 1.6.0 "$PROBLEM_FIXTURE"
|
||||
check "wrapper prints seeded empty Unreleased without refusing" 0 "### Added" \
|
||||
"$WRAPPER" Unreleased "$PROBLEM_FIXTURE"
|
||||
check "wrapper requires a version" 2 "usage:" "$WRAPPER"
|
||||
check "wrapper refuses a missing file" 1 "no such file" "$WRAPPER" 1.0.0 "$TMP/missing.md"
|
||||
|
||||
|
|
|
|||
|
|
@ -89,11 +89,11 @@ chain() {
|
|||
*ceremony=yes*)
|
||||
# shellcheck source=lib/changelog.sh
|
||||
. "$ROOT/lib/changelog.sh"
|
||||
notes="$(changelog_section CHANGELOG.md "$ver")"
|
||||
if [ -z "$notes" ]; then
|
||||
echo "chain: the changelog section for $ver is empty" >&2
|
||||
diagnosis="$(changelog_section_problem CHANGELOG.md "$ver")" || {
|
||||
printf 'chain: %s\n' "$diagnosis" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
notes="$(changelog_section CHANGELOG.md "$ver")"
|
||||
printf 'notes: %s\n' "$notes"
|
||||
;;
|
||||
esac
|
||||
|
|
@ -105,6 +105,31 @@ check "the ceremony merge decides ceremony=yes" 0 "ceremony=yes" \
|
|||
check "the notes are the stamped section's prose" 0 \
|
||||
"notes: - The entry this release ships." chain "$MERGE_SHA" "$BASE_SHA"
|
||||
|
||||
# The same ceremony facts with an entry-less stamped section must stop at the
|
||||
# notes door, before any tag or release mutation could run.
|
||||
git -C "$TMP/repo" reset -q --hard "$BASE_SHA"
|
||||
printf '0.7.0\n' >"$TMP/repo/VERSION"
|
||||
cat >"$TMP/repo/CHANGELOG.md" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.7.0 — 2026-07-21
|
||||
|
||||
### Added
|
||||
|
||||
## 0.6.8 — 2026-07-01
|
||||
|
||||
- An older entry.
|
||||
EOF
|
||||
git -C "$TMP/repo" add VERSION CHANGELOG.md
|
||||
git -C "$TMP/repo" commit -qm "release: entry-less 0.7.0"
|
||||
EMPTY_MERGE_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
|
||||
check "the notes door refuses an entry-less stamped section" 1 \
|
||||
"section '0.7.0' has no entries" chain "$EMPTY_MERGE_SHA" "$BASE_SHA"
|
||||
|
||||
git -C "$TMP/repo" reset -q --hard "$MERGE_SHA"
|
||||
|
||||
# The same chain on an ordinary merge: -dev, unchanged — a green NOTICE
|
||||
# no-op that never consults the API (the stub would refuse a release view).
|
||||
printf 'ordinary work\n' >"$TMP/repo/notes.txt"
|
||||
|
|
|
|||
Loading…
Reference in a new issue