Merge pull request #289 from cndgrr/build/262-fragment-cite
feat(changelog): the terminal issue cite joins the fragment guard
This commit is contained in:
commit
2e11855839
14 changed files with 415 additions and 106 deletions
2
.github/workflows/release-exercise.yml
vendored
2
.github/workflows/release-exercise.yml
vendored
|
|
@ -119,7 +119,7 @@ jobs:
|
||||||
EOF
|
EOF
|
||||||
mkdir changelog.d
|
mkdir changelog.d
|
||||||
printf '# changelog.d/ — assembled at release (heavy-duty/ceremony#112); the marker keeps the directory tracked.\n' > changelog.d/README.md
|
printf '# changelog.d/ — assembled at release (heavy-duty/ceremony#112); the marker keeps the directory tracked.\n' > changelog.d/README.md
|
||||||
printf -- '- The entry this release ships.\n' > changelog.d/42.md
|
printf -- '- The entry this release ships (#42).\n' > changelog.d/42.md
|
||||||
git add VERSION CHANGELOG.md changelog.d
|
git add VERSION CHANGELOG.md changelog.d
|
||||||
git commit -qm "base"
|
git commit -qm "base"
|
||||||
printf '0.7.0\n' > VERSION
|
printf '0.7.0\n' > VERSION
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,13 @@ triage bug, and the move is to say so on the issue, not to guess.
|
||||||
a change genuinely is one. An entry is at most 300 characters — the
|
a change genuinely is one. An entry is at most 300 characters — the
|
||||||
fragment guard reds longer (#167) — so a genuinely long change ships
|
fragment guard reds longer (#167) — so a genuinely long change ships
|
||||||
several short entries, never one long one; wrapping an entry over
|
several short entries, never one long one; wrapping an entry over
|
||||||
continuation lines is fine and never counts against it. Never edit
|
continuation lines is fine and never counts against it. Every entry
|
||||||
|
**ends with its issue citation**, and the same guard reds an entry
|
||||||
|
without one: a single `(` group of `#N`, `repo#N` or `owner/repo#N`
|
||||||
|
references separated by `, `, then `)`, then the final `.` and nothing
|
||||||
|
after it — `(#262).` locally, `(#236, #250).` when one entry honestly
|
||||||
|
lands two. The citation need not name the fragment's own issue, because
|
||||||
|
the filename already carries the authorizing one (#262). Never edit
|
||||||
`CHANGELOG.md` for an entry — the
|
`CHANGELOG.md` for an entry — the
|
||||||
release PR assembles the section from the fragments (#112); the monotonic
|
release PR assembles the section from the fragments (#112); the monotonic
|
||||||
guard still refuses anything that deletes a shipped heading.
|
guard still refuses anything that deletes a shipped heading.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ published verbatim as that release's body (lib/changelog.sh extracts it),
|
||||||
so entries say what changed, cite the issue, and stop — at most 300
|
so entries say what changed, cite the issue, and stop — at most 300
|
||||||
characters each, guard-enforced on the PR that writes the fragment (#167);
|
characters each, guard-enforced on the PR that writes the fragment (#167);
|
||||||
a genuinely long change ships several short entries, never one long one.
|
a genuinely long change ships several short entries, never one long one.
|
||||||
|
The citation is guard-enforced too, and it closes the entry: one `(#N)`
|
||||||
|
group, then the final `.` and nothing after it (#262). Sections published
|
||||||
|
before that rule keep their prose; the guard reads fragments only.
|
||||||
Entries arrive as fragments — one `changelog.d/<issue>.md` per PR, never
|
Entries arrive as fragments — one `changelog.d/<issue>.md` per PR, never
|
||||||
an edit to this file — and the release PR assembles them into the next
|
an edit to this file — and the release PR assembles them into the next
|
||||||
section here (`bin/changelog-assemble`, #112).
|
section here (`bin/changelog-assemble`, #112).
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
- Define the doors-unchanged drill record and an executable release-path list,
|
- Define the doors-unchanged drill record and an executable release-path list,
|
||||||
so a release may reuse live evidence only when its door bytes are unchanged
|
so a release may reuse live evidence only when its door bytes are unchanged
|
||||||
since the last rehearsed tag. (#237)
|
since the last rehearsed tag (#237).
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Preserve active claims when an open local pull request links them with `Refs #N`.
|
- Preserve active claims when an open local pull request links them with `Refs #N` (#241).
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Document the optional, operator-ruled release-epic flow for governed repositories. (#248)
|
- Document the optional, operator-ruled release-epic flow for governed repositories (#248).
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Release epics now announce release initialization when their declared dependency gates clear. (#253)
|
- Release epics now announce release initialization when their declared dependency gates clear (#253).
|
||||||
|
|
|
||||||
20
changelog.d/262.md
Normal file
20
changelog.d/262.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The fragment guard now requires each entry to end with its issue
|
||||||
|
citation: one `(#N)` group — local, `repo#N` or `owner/repo#N`
|
||||||
|
references separated by `, ` — then the final `.` and nothing after it
|
||||||
|
(#262).
|
||||||
|
- The refusal distinguishes an entry carrying no reference at all from one
|
||||||
|
whose reference is present but not terminal, and names the shape to
|
||||||
|
write in both (#262).
|
||||||
|
- The 300-character bound still outranks the citation across the whole
|
||||||
|
fragment, and the outranked problem stays out of the message it lost
|
||||||
|
to: one fragment, one diagnosis, wherever in the file it sits (#262).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `BUILDER.md` and `CHANGELOG.md` state the citation as guard-enforced
|
||||||
|
rather than as house style, beside the 300-character bound it now sits
|
||||||
|
next to (#262).
|
||||||
|
- Four fragments in flight gained a terminal citation; published sections
|
||||||
|
are untouched, so no shipped prose is re-opened (#262).
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
- CONTRIBUTING.md now keeps vendored doctrine self-contained: state the rule,
|
- CONTRIBUTING.md now keeps vendored doctrine self-contained: state the rule,
|
||||||
retain at most one sentence of why, cite the local record bare, and leave the
|
retain at most one sentence of why, cite the local record bare, and leave the
|
||||||
incident narrative in that record. (#280)
|
incident narrative in that record (#280).
|
||||||
|
|
|
||||||
|
|
@ -115,8 +115,22 @@ changelog_fragments() {
|
||||||
# splits the measured history: every healthy entry passes untouched,
|
# splits the measured history: every healthy entry passes untouched,
|
||||||
# the drift cluster does not. mawk's length() counts bytes; prose here
|
# the drift cluster does not. mawk's length() counts bytes; prose here
|
||||||
# is ASCII and the fuzz is acceptable.
|
# is ASCII and the fuzz is acceptable.
|
||||||
|
# - every entry ends with its issue citation (#262): one '(' group of
|
||||||
|
# '#N', 'repo#N' or 'owner/repo#N' references separated by ', ', then
|
||||||
|
# ')', then the final '.' and nothing after it. Stated as style and
|
||||||
|
# enforced by nobody, this rule cost #255 a full four-bot round on a
|
||||||
|
# missing '(#248)'; the fragment rules that live in this guard drew no
|
||||||
|
# review comment at all across the same fifteen PRs. Measured on the
|
||||||
|
# same normalized entry as the bound above, so a citation that wraps
|
||||||
|
# onto a continuation line still counts. The repo token is the one the
|
||||||
|
# filename rule already admits, so '<repo>-<issue>.md' and its cite
|
||||||
|
# cannot drift apart; the two halves of one convention. A single group
|
||||||
|
# is what makes 'terminal' checkable — '(#236, #250).' lands two issues
|
||||||
|
# in one entry, '(#236) and (#250).' does not. The citation need not
|
||||||
|
# name the file's own issue: the filename already carries the
|
||||||
|
# authorizing one, so a fragment may cite the incident beside it.
|
||||||
changelog_fragment_problem() {
|
changelog_fragment_problem() {
|
||||||
local file="$1" base problem
|
local file="$1" base problem kind detail rest
|
||||||
base="${file##*/}"
|
base="${file##*/}"
|
||||||
|
|
||||||
if ! printf '%s\n' "$base" | grep -qE '^([a-z][a-z0-9-]*-)?[0-9]+\.md$'; then
|
if ! printf '%s\n' "$base" | grep -qE '^([a-z][a-z0-9-]*-)?[0-9]+\.md$'; then
|
||||||
|
|
@ -157,9 +171,35 @@ changelog_fragment_problem() {
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# One walk of the entries, two rules, and the order between them is
|
||||||
|
# deliberate: an over-long entry anywhere outranks a citation problem
|
||||||
|
# anywhere, so the length diagnosis a fragment already draws is the same
|
||||||
|
# one it drew before the citation rule existed. Both read the entry the
|
||||||
|
# same normalizer produces, which is the whole reason they share a pass.
|
||||||
problem="$(
|
problem="$(
|
||||||
awk -v max=300 '
|
awk -v max=300 '
|
||||||
function flush( len, e) {
|
# cite_problem <entry> — "", "uncited" or "misplaced". Counting the
|
||||||
|
# groups is what distinguishes the two admitted shapes: one group
|
||||||
|
# closing the entry passes however many references it carries, and a
|
||||||
|
# second group anywhere means no single group is terminal.
|
||||||
|
function cite_problem(e, rest, groups, consumed, group_end) {
|
||||||
|
rest = e
|
||||||
|
groups = 0
|
||||||
|
consumed = 0
|
||||||
|
while (match(rest, /\((([A-Za-z0-9._-]+\/)?[a-z][a-z0-9-]*)?#[0-9]+(, (([A-Za-z0-9._-]+\/)?[a-z][a-z0-9-]*)?#[0-9]+)*\)/)) {
|
||||||
|
groups++
|
||||||
|
group_end = consumed + RSTART + RLENGTH - 1
|
||||||
|
consumed = group_end
|
||||||
|
rest = substr(rest, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
if (groups == 0) return e ~ /#[0-9]/ ? "misplaced" : "uncited"
|
||||||
|
if (groups > 1) return "misplaced"
|
||||||
|
return (group_end == length(e) - 1 && substr(e, group_end + 1) == ".") ? "" : "misplaced"
|
||||||
|
}
|
||||||
|
function excerpt(e) {
|
||||||
|
return length(e) > 60 ? substr(e, 1, 60) "…" : e
|
||||||
|
}
|
||||||
|
function flush( len, e, kind) {
|
||||||
if (entry == "") return 0
|
if (entry == "") return 0
|
||||||
e = entry
|
e = entry
|
||||||
entry = ""
|
entry = ""
|
||||||
|
|
@ -168,9 +208,15 @@ changelog_fragment_problem() {
|
||||||
sub(/ $/, "", e)
|
sub(/ $/, "", e)
|
||||||
len = length(e)
|
len = length(e)
|
||||||
if (len > max) {
|
if (len > max) {
|
||||||
printf "%d\t%s\n", len, substr(e, 1, 60)
|
reported = 1
|
||||||
|
printf "long\t%d\t%s\n", len, excerpt(e)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
kind = cite_problem(e)
|
||||||
|
if (kind != "" && cite_kind == "") {
|
||||||
|
cite_kind = kind
|
||||||
|
cite_excerpt = excerpt(e)
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
/^### / { if (flush()) exit; next }
|
/^### / { if (flush()) exit; next }
|
||||||
|
|
@ -182,12 +228,37 @@ changelog_fragment_problem() {
|
||||||
}
|
}
|
||||||
/^[[:space:]]*$/ { next }
|
/^[[:space:]]*$/ { next }
|
||||||
entry != "" { entry = entry " " $0 }
|
entry != "" { entry = entry " " $0 }
|
||||||
END { flush() }
|
# An exit from a main rule still runs END, so a length row printed
|
||||||
|
# mid-file would be followed by the citation row it outranks — two
|
||||||
|
# lines spliced into one diagnosis, the internal protocol row landing
|
||||||
|
# inside the human-facing excerpt (#262 round 1). The reported flag is
|
||||||
|
# the same guard the empty-heading walk above uses, for the same
|
||||||
|
# reason: one diagnosis per fragment is the contract.
|
||||||
|
END {
|
||||||
|
if (flush()) exit
|
||||||
|
if (!reported && cite_kind != "") printf "%s\t\t%s\n", cite_kind, cite_excerpt
|
||||||
|
}
|
||||||
' "$file"
|
' "$file"
|
||||||
)"
|
)"
|
||||||
if [ -n "$problem" ]; then
|
if [ -n "$problem" ]; then
|
||||||
printf "fragment '%s' has a %s-character entry — '%s…' — the bound is 300: split it into multiple '- ' entries in this same fragment\n" \
|
kind="${problem%%$'\t'*}"
|
||||||
"$file" "${problem%%$'\t'*}" "${problem#*$'\t'}"
|
rest="${problem#*$'\t'}"
|
||||||
|
detail="${rest%%$'\t'*}"
|
||||||
|
rest="${rest#*$'\t'}"
|
||||||
|
case "$kind" in
|
||||||
|
long)
|
||||||
|
printf "fragment '%s' has a %s-character entry — '%s' — the bound is 300: split it into multiple '- ' entries in this same fragment\n" \
|
||||||
|
"$file" "$detail" "$rest"
|
||||||
|
;;
|
||||||
|
uncited)
|
||||||
|
printf "fragment '%s' has an entry with no issue citation — '%s' — end it with the issue it comes from: '(#N).'\n" \
|
||||||
|
"$file" "$rest"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "fragment '%s' has an entry whose issue citation is not terminal — '%s' — exactly one '(#N)' group ends the entry, the final '.' after it\n" \
|
||||||
|
"$file" "$rest"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ fragment_tree fragments-dev-flat 1.2.4-dev <<'EOF'
|
||||||
|
|
||||||
- The shipped entry.
|
- The shipped entry.
|
||||||
EOF
|
EOF
|
||||||
printf '%s\n' "- Added fragment mode." >"$TMP/fragments-dev-flat/changelog.d/115.md"
|
printf '%s\n' "- Added fragment mode (#115)." >"$TMP/fragments-dev-flat/changelog.d/115.md"
|
||||||
check "fragment -dev + well-formed flat fragment passes" 0 "fragment mode" \
|
check "fragment -dev + well-formed flat fragment passes" 0 "fragment mode" \
|
||||||
in_tree fragments-dev-flat
|
in_tree fragments-dev-flat
|
||||||
|
|
||||||
|
|
@ -298,6 +298,38 @@ check "fragment mode over-bound refusal names the bound and the split fix" 1 \
|
||||||
"the bound is 300: split it into multiple '- ' entries in this same fragment" \
|
"the bound is 300: split it into multiple '- ' entries in this same fragment" \
|
||||||
in_tree fragments-dev-over-bound
|
in_tree fragments-dev-over-bound
|
||||||
|
|
||||||
|
# The terminal cite (#262) reds the PR that writes the fragment, through the
|
||||||
|
# same shared predicate — which is the whole point of the rule living there
|
||||||
|
# rather than in prose a reviewer has to remember.
|
||||||
|
fragment_tree fragments-dev-uncited 1.2.4-dev <<'EOF'
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.2.3 — 2026-07-20
|
||||||
|
|
||||||
|
- The shipped entry.
|
||||||
|
EOF
|
||||||
|
printf '%s\n' "- An entry that never learned to cite its issue." \
|
||||||
|
>"$TMP/fragments-dev-uncited/changelog.d/115.md"
|
||||||
|
check "fragment mode refuses an uncited entry, fragment named" 1 \
|
||||||
|
"115.md' has an entry with no issue citation" \
|
||||||
|
in_tree fragments-dev-uncited
|
||||||
|
check "fragment mode uncited refusal names the shape to write" 1 \
|
||||||
|
"end it with the issue it comes from: '(#N).'" \
|
||||||
|
in_tree fragments-dev-uncited
|
||||||
|
|
||||||
|
fragment_tree fragments-dev-misplaced-cite 1.2.4-dev <<'EOF'
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.2.3 — 2026-07-20
|
||||||
|
|
||||||
|
- The shipped entry.
|
||||||
|
EOF
|
||||||
|
printf '%s\n' "- The citation trails the period. (#115)" \
|
||||||
|
>"$TMP/fragments-dev-misplaced-cite/changelog.d/115.md"
|
||||||
|
check "fragment mode refuses a non-terminal citation, fragment named" 1 \
|
||||||
|
"115.md' has an entry whose issue citation is not terminal" \
|
||||||
|
in_tree fragments-dev-misplaced-cite
|
||||||
|
|
||||||
fragment_tree fragments-dev-grouped 1.2.4-dev <<'EOF'
|
fragment_tree fragments-dev-grouped 1.2.4-dev <<'EOF'
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
@ -310,7 +342,7 @@ EOF
|
||||||
cat >"$TMP/fragments-dev-grouped/changelog.d/115.md" <<'EOF'
|
cat >"$TMP/fragments-dev-grouped/changelog.d/115.md" <<'EOF'
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Added fragment mode.
|
- Added fragment mode (#115).
|
||||||
EOF
|
EOF
|
||||||
check "fragment -dev + well-formed grouped fragment passes" 0 "fragment mode" \
|
check "fragment -dev + well-formed grouped fragment passes" 0 "fragment mode" \
|
||||||
in_tree fragments-dev-grouped
|
in_tree fragments-dev-grouped
|
||||||
|
|
@ -322,11 +354,11 @@ fragment_tree fragments-dev-mixed 1.2.4-dev <<'EOF'
|
||||||
|
|
||||||
- The shipped entry.
|
- The shipped entry.
|
||||||
EOF
|
EOF
|
||||||
printf '%s\n' "- Flat fragment." >"$TMP/fragments-dev-mixed/changelog.d/114.md"
|
printf '%s\n' "- Flat fragment (#115)." >"$TMP/fragments-dev-mixed/changelog.d/114.md"
|
||||||
cat >"$TMP/fragments-dev-mixed/changelog.d/115.md" <<'EOF'
|
cat >"$TMP/fragments-dev-mixed/changelog.d/115.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#115).
|
||||||
EOF
|
EOF
|
||||||
check "fragment mode refuses mixed shapes with the shared assembler diagnosis" 1 \
|
check "fragment mode refuses mixed shapes with the shared assembler diagnosis" 1 \
|
||||||
"fragment 'changelog.d/115.md' is grouped but fragment 'changelog.d/114.md' is not" \
|
"fragment 'changelog.d/115.md' is grouped but fragment 'changelog.d/114.md' is not" \
|
||||||
|
|
@ -342,7 +374,7 @@ EOF
|
||||||
cat >"$TMP/fragments-dev-all-grouped-over-flat/changelog.d/115.md" <<'EOF'
|
cat >"$TMP/fragments-dev-all-grouped-over-flat/changelog.d/115.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#115).
|
||||||
EOF
|
EOF
|
||||||
check "fragment mode refuses an all-grouped set over a flat published section" 1 \
|
check "fragment mode refuses an all-grouped set over a flat published section" 1 \
|
||||||
"changelog.d/115.md' is grouped but newest published section '1.2.3'" \
|
"changelog.d/115.md' is grouped but newest published section '1.2.3'" \
|
||||||
|
|
@ -357,7 +389,7 @@ fragment_tree fragments-dev-flat-over-grouped 1.2.4-dev <<'EOF'
|
||||||
|
|
||||||
- The shipped entry.
|
- The shipped entry.
|
||||||
EOF
|
EOF
|
||||||
printf '%s\n' "- Flat fragment." >"$TMP/fragments-dev-flat-over-grouped/changelog.d/115.md"
|
printf '%s\n' "- Flat fragment (#115)." >"$TMP/fragments-dev-flat-over-grouped/changelog.d/115.md"
|
||||||
check "fragment mode refuses a flat set over a grouped published section" 1 \
|
check "fragment mode refuses a flat set over a grouped published section" 1 \
|
||||||
"changelog.d/115.md' is flat but newest published section '1.2.3'" \
|
"changelog.d/115.md' is flat but newest published section '1.2.3'" \
|
||||||
in_tree fragments-dev-flat-over-grouped
|
in_tree fragments-dev-flat-over-grouped
|
||||||
|
|
@ -376,14 +408,14 @@ printf '%s\n' "grouped" >"$TMP/fragments-dev-flip/changelog.d/shape"
|
||||||
cat >"$TMP/fragments-dev-flip/changelog.d/115.md" <<'EOF'
|
cat >"$TMP/fragments-dev-flip/changelog.d/115.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#115).
|
||||||
EOF
|
EOF
|
||||||
check "fragment mode: 'grouped' sentinel admits the flip tree over a flat published section" 0 \
|
check "fragment mode: 'grouped' sentinel admits the flip tree over a flat published section" 0 \
|
||||||
"fragment mode" in_tree fragments-dev-flip
|
"fragment mode" in_tree fragments-dev-flip
|
||||||
|
|
||||||
# Post-flip drift is refused on its own PR: a flat probe fragment atop the
|
# Post-flip drift is refused on its own PR: a flat probe fragment atop the
|
||||||
# flip tree goes red — beside grouped fragments the mix rule names it first.
|
# flip tree goes red — beside grouped fragments the mix rule names it first.
|
||||||
printf '%s\n' "- Flat probe." >"$TMP/fragments-dev-flip/changelog.d/116.md"
|
printf '%s\n' "- Flat probe (#116)." >"$TMP/fragments-dev-flip/changelog.d/116.md"
|
||||||
check "fragment mode: a flat probe atop the flip tree is refused" 1 \
|
check "fragment mode: a flat probe atop the flip tree is refused" 1 \
|
||||||
"changelog.d/115.md' is grouped but fragment 'changelog.d/116.md' is not" \
|
"changelog.d/115.md' is grouped but fragment 'changelog.d/116.md' is not" \
|
||||||
in_tree fragments-dev-flip
|
in_tree fragments-dev-flip
|
||||||
|
|
@ -393,7 +425,7 @@ rm "$TMP/fragments-dev-flip/changelog.d/116.md"
|
||||||
# holds the shape: an all-flat set under 'grouped' is refused, sentinel
|
# holds the shape: an all-flat set under 'grouped' is refused, sentinel
|
||||||
# named — the published-section inference never gets a say.
|
# named — the published-section inference never gets a say.
|
||||||
rm "$TMP/fragments-dev-flip/changelog.d/115.md"
|
rm "$TMP/fragments-dev-flip/changelog.d/115.md"
|
||||||
printf '%s\n' "- Flat probe." >"$TMP/fragments-dev-flip/changelog.d/116.md"
|
printf '%s\n' "- Flat probe (#116)." >"$TMP/fragments-dev-flip/changelog.d/116.md"
|
||||||
check "fragment mode: a flat set under the 'grouped' sentinel refused, sentinel named" 1 \
|
check "fragment mode: a flat set under the 'grouped' sentinel refused, sentinel named" 1 \
|
||||||
"changelog.d/116.md' is flat but 'changelog.d/shape' declares grouped" \
|
"changelog.d/116.md' is flat but 'changelog.d/shape' declares grouped" \
|
||||||
in_tree fragments-dev-flip
|
in_tree fragments-dev-flip
|
||||||
|
|
@ -417,7 +449,7 @@ EOF
|
||||||
cat >"$TMP/fragments-dev-no-published/changelog.d/115.md" <<'EOF'
|
cat >"$TMP/fragments-dev-no-published/changelog.d/115.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#115).
|
||||||
EOF
|
EOF
|
||||||
check "fragment mode accepts a consistent set with no published section" 0 \
|
check "fragment mode accepts a consistent set with no published section" 0 \
|
||||||
"fragment mode" in_tree fragments-dev-no-published
|
"fragment mode" in_tree fragments-dev-no-published
|
||||||
|
|
@ -484,7 +516,7 @@ check "fragment bare + stamped section + consumed directory passes" 0 \
|
||||||
"fragment mode" in_tree fragments-bare-stamped
|
"fragment mode" in_tree fragments-bare-stamped
|
||||||
|
|
||||||
cp -R "$TMP/fragments-bare-stamped" "$TMP/fragments-bare-survivor"
|
cp -R "$TMP/fragments-bare-stamped" "$TMP/fragments-bare-survivor"
|
||||||
printf '%s\n' "- This entry was not consumed." \
|
printf '%s\n' "- This entry was not consumed (#115)." \
|
||||||
>"$TMP/fragments-bare-survivor/changelog.d/115.md"
|
>"$TMP/fragments-bare-survivor/changelog.d/115.md"
|
||||||
check "fragment bare refuses and lists surviving fragments" 1 \
|
check "fragment bare refuses and lists surviving fragments" 1 \
|
||||||
"these fragments were not consumed: changelog.d/115.md" \
|
"these fragments were not consumed: changelog.d/115.md" \
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,13 @@ tree flat-one <<EOF
|
||||||
$BASE_CHANGELOG
|
$BASE_CHANGELOG
|
||||||
EOF
|
EOF
|
||||||
frag flat-one 12.md <<'EOF'
|
frag flat-one 12.md <<'EOF'
|
||||||
- Twelve landed.
|
- Twelve landed (#12).
|
||||||
EOF
|
EOF
|
||||||
check "flat: one fragment assembles and stamps" 0 "consumed 1 fragment" \
|
check "flat: one fragment assembles and stamps" 0 "consumed 1 fragment" \
|
||||||
in_tree flat-one 0.2.0 2026-07-24
|
in_tree flat-one 0.2.0 2026-07-24
|
||||||
check "flat: preamble and shipped section stay byte-identical around the insert" 0 "" \
|
check "flat: preamble and shipped section stay byte-identical around the insert" 0 "" \
|
||||||
assert_file "$TMP/flat-one/CHANGELOG.md" \
|
assert_file "$TMP/flat-one/CHANGELOG.md" \
|
||||||
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n- Twelve landed.\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
|
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n- Twelve landed (#12).\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
|
||||||
check "flat: the consumed fragment is deleted" 1 "" \
|
check "flat: the consumed fragment is deleted" 1 "" \
|
||||||
test -e "$TMP/flat-one/changelog.d/12.md"
|
test -e "$TMP/flat-one/changelog.d/12.md"
|
||||||
check "flat: README.md survives consumption" 0 "" \
|
check "flat: README.md survives consumption" 0 "" \
|
||||||
|
|
@ -73,17 +73,17 @@ tree flat-many <<EOF
|
||||||
$BASE_CHANGELOG
|
$BASE_CHANGELOG
|
||||||
EOF
|
EOF
|
||||||
frag flat-many 2.md <<'EOF'
|
frag flat-many 2.md <<'EOF'
|
||||||
- Two.
|
- Two (#2).
|
||||||
EOF
|
EOF
|
||||||
frag flat-many 9.md <<'EOF'
|
frag flat-many 9.md <<'EOF'
|
||||||
- Nine.
|
- Nine (#9).
|
||||||
EOF
|
EOF
|
||||||
frag flat-many 10.md <<'EOF'
|
frag flat-many 10.md <<'EOF'
|
||||||
- Ten.
|
- Ten (#10).
|
||||||
EOF
|
EOF
|
||||||
frag flat-many ceremony-14.md <<'EOF'
|
frag flat-many ceremony-14.md <<'EOF'
|
||||||
- Fourteen crossed over — naïve reflows would mangle this café's
|
- Fourteen crossed over — naïve reflows would mangle this café's
|
||||||
continuation line, so it must survive verbatim.
|
continuation line, so it must survive verbatim (#14).
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
assert_check() {
|
assert_check() {
|
||||||
|
|
@ -95,7 +95,7 @@ assert_check() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check "flat: numeric-descending order (10.md before 9.md), cross-repo name beside local" 0 "" \
|
check "flat: numeric-descending order (10.md before 9.md), cross-repo name beside local" 0 "" \
|
||||||
assert_check flat-many $'- Fourteen crossed over — naïve reflows would mangle this café'"'"$'s\n continuation line, so it must survive verbatim.\n- Ten.\n- Nine.\n- Two.'
|
assert_check flat-many $'- Fourteen crossed over — naïve reflows would mangle this café'"'"$'s\n continuation line, so it must survive verbatim (#14).\n- Ten (#10).\n- Nine (#9).\n- Two (#2).'
|
||||||
|
|
||||||
# --- grouped write: canonical order, unnamed group appended ------------------
|
# --- grouped write: canonical order, unnamed group appended ------------------
|
||||||
|
|
||||||
|
|
@ -113,28 +113,28 @@ EOF
|
||||||
frag grouped 21.md <<'EOF'
|
frag grouped 21.md <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed twenty-one.
|
- Fixed twenty-one (#21).
|
||||||
EOF
|
EOF
|
||||||
frag grouped 20.md <<'EOF'
|
frag grouped 20.md <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added twenty.
|
- Added twenty (#20).
|
||||||
- Added twenty, second bullet.
|
- Added twenty, second bullet (#20).
|
||||||
|
|
||||||
### Docs
|
### Docs
|
||||||
|
|
||||||
- Docs twenty.
|
- Docs twenty (#20).
|
||||||
EOF
|
EOF
|
||||||
frag grouped 19.md <<'EOF'
|
frag grouped 19.md <<'EOF'
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Security nineteen.
|
- Security nineteen (#19).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added nineteen.
|
- Added nineteen (#19).
|
||||||
EOF
|
EOF
|
||||||
GROUPED_BODY=$'### Added\n\n- Added twenty.\n- Added twenty, second bullet.\n- Added nineteen.\n\n### Fixed\n\n- Fixed twenty-one.\n\n### Security\n\n- Security nineteen.\n\n### Docs\n\n- Docs twenty.'
|
GROUPED_BODY=$'### Added\n\n- Added twenty (#20).\n- Added twenty, second bullet (#20).\n- Added nineteen (#19).\n\n### Fixed\n\n- Fixed twenty-one (#21).\n\n### Security\n\n- Security nineteen (#19).\n\n### Docs\n\n- Docs twenty (#20).'
|
||||||
check "grouped: --check shows canonical order, multi-bullet group, unnamed group last" 0 "" \
|
check "grouped: --check shows canonical order, multi-bullet group, unnamed group last" 0 "" \
|
||||||
assert_check grouped "$GROUPED_BODY"
|
assert_check grouped "$GROUPED_BODY"
|
||||||
check "grouped: write mode assembles the same section" 0 "consumed 3 fragment" \
|
check "grouped: write mode assembles the same section" 0 "consumed 3 fragment" \
|
||||||
|
|
@ -156,13 +156,13 @@ printf 'grouped\n' >"$TMP/flip/changelog.d/shape"
|
||||||
frag flip 40.md <<'EOF'
|
frag flip 40.md <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Forty landed.
|
- Forty landed (#40).
|
||||||
EOF
|
EOF
|
||||||
check "sentinel: the flip release assembles grouped over a flat published section" 0 \
|
check "sentinel: the flip release assembles grouped over a flat published section" 0 \
|
||||||
"consumed 1 fragment" in_tree flip 0.2.0 2026-07-24
|
"consumed 1 fragment" in_tree flip 0.2.0 2026-07-24
|
||||||
check "sentinel: the written flip section is exact" 0 "" \
|
check "sentinel: the written flip section is exact" 0 "" \
|
||||||
assert_file "$TMP/flip/CHANGELOG.md" \
|
assert_file "$TMP/flip/CHANGELOG.md" \
|
||||||
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n### Added\n\n- Forty landed.\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
|
$'# Changelog\n\nPreamble prose belongs to no section.\n\n## 0.2.0 — 2026-07-24\n\n### Added\n\n- Forty landed (#40).\n\n## 0.1.0 — 2026-07-01\n\n- The shipped entry.'
|
||||||
check "sentinel: changelog.d/shape survives consumption" 0 "" \
|
check "sentinel: changelog.d/shape survives consumption" 0 "" \
|
||||||
test -e "$TMP/flip/changelog.d/shape"
|
test -e "$TMP/flip/changelog.d/shape"
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ $BASE_CHANGELOG
|
||||||
EOF
|
EOF
|
||||||
printf 'grouped\n' >"$TMP/flip-flat-frag/changelog.d/shape"
|
printf 'grouped\n' >"$TMP/flip-flat-frag/changelog.d/shape"
|
||||||
frag flip-flat-frag 41.md <<'EOF'
|
frag flip-flat-frag 41.md <<'EOF'
|
||||||
- Flat forty-one.
|
- Flat forty-one (#41).
|
||||||
EOF
|
EOF
|
||||||
check "sentinel: a flat fragment under 'grouped' refuses, sentinel named" 1 \
|
check "sentinel: a flat fragment under 'grouped' refuses, sentinel named" 1 \
|
||||||
"changelog.d/shape' declares grouped" in_tree flip-flat-frag 0.2.0 2026-07-24
|
"changelog.d/shape' declares grouped" in_tree flip-flat-frag 0.2.0 2026-07-24
|
||||||
|
|
@ -183,7 +183,7 @@ printf 'Grouped\n' >"$TMP/flip-malformed/changelog.d/shape"
|
||||||
frag flip-malformed 42.md <<'EOF'
|
frag flip-malformed 42.md <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Forty-two.
|
- Forty-two (#42).
|
||||||
EOF
|
EOF
|
||||||
check "sentinel: a malformed sentinel refuses, file named" 1 \
|
check "sentinel: a malformed sentinel refuses, file named" 1 \
|
||||||
"changelog.d/shape' declares neither shape" in_tree flip-malformed 0.2.0 2026-07-24
|
"changelog.d/shape' declares neither shape" in_tree flip-malformed 0.2.0 2026-07-24
|
||||||
|
|
@ -196,13 +196,45 @@ tree preamble-only <<'EOF'
|
||||||
Only preamble so far.
|
Only preamble so far.
|
||||||
EOF
|
EOF
|
||||||
frag preamble-only 1.md <<'EOF'
|
frag preamble-only 1.md <<'EOF'
|
||||||
- The first entry ever.
|
- The first entry ever (#1).
|
||||||
EOF
|
EOF
|
||||||
check "a changelog with no section yet gets the section after the preamble" 0 "" \
|
check "a changelog with no section yet gets the section after the preamble" 0 "" \
|
||||||
in_tree preamble-only 0.1.0 2026-07-24
|
in_tree preamble-only 0.1.0 2026-07-24
|
||||||
check "preamble-only write is exact" 0 "" \
|
check "preamble-only write is exact" 0 "" \
|
||||||
assert_file "$TMP/preamble-only/CHANGELOG.md" \
|
assert_file "$TMP/preamble-only/CHANGELOG.md" \
|
||||||
$'# Changelog\n\nOnly preamble so far.\n\n## 0.1.0 — 2026-07-24\n\n- The first entry ever.'
|
$'# Changelog\n\nOnly preamble so far.\n\n## 0.1.0 — 2026-07-24\n\n- The first entry ever (#1).'
|
||||||
|
|
||||||
|
# --- the fragment predicate at release time (#262) ---------------------------
|
||||||
|
|
||||||
|
# The cite rule joins changelog_fragment_problem, so it binds both callers:
|
||||||
|
# the arming guard at PR time and this assembler at release time. Asserted
|
||||||
|
# rather than assumed — a release that publishes an uncited entry is the
|
||||||
|
# failure the PR-time guard exists to have already caught.
|
||||||
|
|
||||||
|
tree uncited-release <<EOF
|
||||||
|
$BASE_CHANGELOG
|
||||||
|
EOF
|
||||||
|
frag uncited-release 60.md <<'EOF'
|
||||||
|
- An entry that never learned to cite its issue.
|
||||||
|
EOF
|
||||||
|
check "release time: an uncited fragment refuses the release, fragment named" 1 \
|
||||||
|
"changelog.d/60.md' has an entry with no issue citation" \
|
||||||
|
in_tree uncited-release 0.2.0 2026-07-24
|
||||||
|
check "release time: the uncited refusal survives --check too" 1 \
|
||||||
|
"has an entry with no issue citation" \
|
||||||
|
in_tree uncited-release 0.2.0 2026-07-24 --check
|
||||||
|
check "release time: the refused release wrote nothing" 0 "" \
|
||||||
|
test -e "$TMP/uncited-release/changelog.d/60.md"
|
||||||
|
|
||||||
|
tree misplaced-release <<EOF
|
||||||
|
$BASE_CHANGELOG
|
||||||
|
EOF
|
||||||
|
frag misplaced-release 61.md <<'EOF'
|
||||||
|
- The citation trails the period. (#61)
|
||||||
|
EOF
|
||||||
|
check "release time: a non-terminal citation refuses the release" 1 \
|
||||||
|
"changelog.d/61.md' has an entry whose issue citation is not terminal" \
|
||||||
|
in_tree misplaced-release 0.2.0 2026-07-24
|
||||||
|
|
||||||
# --- --check is provably read-only -------------------------------------------
|
# --- --check is provably read-only -------------------------------------------
|
||||||
|
|
||||||
|
|
@ -210,10 +242,10 @@ tree check-readonly <<EOF
|
||||||
$BASE_CHANGELOG
|
$BASE_CHANGELOG
|
||||||
EOF
|
EOF
|
||||||
frag check-readonly 5.md <<'EOF'
|
frag check-readonly 5.md <<'EOF'
|
||||||
- Five.
|
- Five (#5).
|
||||||
EOF
|
EOF
|
||||||
cp -R "$TMP/check-readonly" "$TMP/check-readonly.before"
|
cp -R "$TMP/check-readonly" "$TMP/check-readonly.before"
|
||||||
check "--check prints the assembled body" 0 "Five." \
|
check "--check prints the assembled body" 0 "Five (#5)." \
|
||||||
in_tree check-readonly 0.2.0 2026-07-24 --check
|
in_tree check-readonly 0.2.0 2026-07-24 --check
|
||||||
check "--check is read-only: the tree is byte-identical before and after" 0 "" \
|
check "--check is read-only: the tree is byte-identical before and after" 0 "" \
|
||||||
diff -r "$TMP/check-readonly.before" "$TMP/check-readonly"
|
diff -r "$TMP/check-readonly.before" "$TMP/check-readonly"
|
||||||
|
|
@ -229,11 +261,11 @@ check "the defaulted stamp is a UTC date" 0 "" \
|
||||||
|
|
||||||
mkdir -p "$TMP/flagged/frags"
|
mkdir -p "$TMP/flagged/frags"
|
||||||
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped.\n' >"$TMP/flagged/NOTES.md"
|
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped.\n' >"$TMP/flagged/NOTES.md"
|
||||||
printf -- '- Flagged entry.\n' >"$TMP/flagged/frags/2.md"
|
printf -- '- Flagged entry (#2).\n' >"$TMP/flagged/frags/2.md"
|
||||||
check "--changelog and --dir override the defaults" 0 "" \
|
check "--changelog and --dir override the defaults" 0 "" \
|
||||||
"$TOOL" 0.2.0 2026-07-24 --changelog "$TMP/flagged/NOTES.md" --dir "$TMP/flagged/frags"
|
"$TOOL" 0.2.0 2026-07-24 --changelog "$TMP/flagged/NOTES.md" --dir "$TMP/flagged/frags"
|
||||||
check "the flag-driven write landed in the named changelog" 0 "" \
|
check "the flag-driven write landed in the named changelog" 0 "" \
|
||||||
grep -qF -- "- Flagged entry." "$TMP/flagged/NOTES.md"
|
grep -qF -- "- Flagged entry (#2)." "$TMP/flagged/NOTES.md"
|
||||||
|
|
||||||
# --- refusals: each names the file responsible -------------------------------
|
# --- refusals: each names the file responsible -------------------------------
|
||||||
|
|
||||||
|
|
@ -292,7 +324,7 @@ tree stray-txt <<EOF
|
||||||
$BASE_CHANGELOG
|
$BASE_CHANGELOG
|
||||||
EOF
|
EOF
|
||||||
frag stray-txt 7.md <<'EOF'
|
frag stray-txt 7.md <<'EOF'
|
||||||
- Seven.
|
- Seven (#7).
|
||||||
EOF
|
EOF
|
||||||
frag stray-txt notes.txt <<'EOF'
|
frag stray-txt notes.txt <<'EOF'
|
||||||
A stray scratchpad.
|
A stray scratchpad.
|
||||||
|
|
@ -322,12 +354,12 @@ tree mixed <<EOF
|
||||||
$BASE_CHANGELOG
|
$BASE_CHANGELOG
|
||||||
EOF
|
EOF
|
||||||
frag mixed 5.md <<'EOF'
|
frag mixed 5.md <<'EOF'
|
||||||
- Flat five.
|
- Flat five (#5).
|
||||||
EOF
|
EOF
|
||||||
frag mixed 6.md <<'EOF'
|
frag mixed 6.md <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Grouped six.
|
- Grouped six (#6).
|
||||||
EOF
|
EOF
|
||||||
check "grouped + flat mixed refuses, both files named" 1 "6.md" \
|
check "grouped + flat mixed refuses, both files named" 1 "6.md" \
|
||||||
in_tree mixed 0.2.0
|
in_tree mixed 0.2.0
|
||||||
|
|
@ -340,7 +372,7 @@ EOF
|
||||||
frag grouped-over-flat 6.md <<'EOF'
|
frag grouped-over-flat 6.md <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Grouped six.
|
- Grouped six (#6).
|
||||||
EOF
|
EOF
|
||||||
check "an all-grouped set over a flat published section refuses before assembly" 1 \
|
check "an all-grouped set over a flat published section refuses before assembly" 1 \
|
||||||
"fragment 'changelog.d/6.md' is grouped but newest published section '0.1.0'" \
|
"fragment 'changelog.d/6.md' is grouped but newest published section '0.1.0'" \
|
||||||
|
|
@ -354,7 +386,7 @@ tree already <<'EOF'
|
||||||
- Already shipped.
|
- Already shipped.
|
||||||
EOF
|
EOF
|
||||||
frag already 4.md <<'EOF'
|
frag already 4.md <<'EOF'
|
||||||
- A late fragment.
|
- A late fragment (#4).
|
||||||
EOF
|
EOF
|
||||||
check "an already-present section refuses — the ceremony was already run" 1 \
|
check "an already-present section refuses — the ceremony was already run" 1 \
|
||||||
"already has a section for '0.2.0'" \
|
"already has a section for '0.2.0'" \
|
||||||
|
|
@ -370,13 +402,13 @@ tree rc-present <<'EOF'
|
||||||
- The candidate's entry.
|
- The candidate's entry.
|
||||||
EOF
|
EOF
|
||||||
frag rc-present 8.md <<'EOF'
|
frag rc-present 8.md <<'EOF'
|
||||||
- The real release entry.
|
- The real release entry (#8).
|
||||||
EOF
|
EOF
|
||||||
check "an rc section does not block assembling the bare version" 0 "" \
|
check "an rc section does not block assembling the bare version" 0 "" \
|
||||||
in_tree rc-present 0.2.0 2026-07-24
|
in_tree rc-present 0.2.0 2026-07-24
|
||||||
|
|
||||||
mkdir -p "$TMP/no-changelog/changelog.d"
|
mkdir -p "$TMP/no-changelog/changelog.d"
|
||||||
printf -- '- Entry.\n' >"$TMP/no-changelog/changelog.d/2.md"
|
printf -- '- Entry (#2).\n' >"$TMP/no-changelog/changelog.d/2.md"
|
||||||
check "a missing changelog refuses" 1 "no such file" \
|
check "a missing changelog refuses" 1 "no such file" \
|
||||||
in_tree no-changelog 0.2.0
|
in_tree no-changelog 0.2.0
|
||||||
|
|
||||||
|
|
@ -404,12 +436,12 @@ frag round-trip 30.md <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Thirty — wraps onto a
|
- Thirty — wraps onto a
|
||||||
continuation line with a naïve café.
|
continuation line with a naïve café (#30).
|
||||||
EOF
|
EOF
|
||||||
frag round-trip 29.md <<'EOF'
|
frag round-trip 29.md <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed twenty-nine.
|
- Fixed twenty-nine (#29).
|
||||||
EOF
|
EOF
|
||||||
CHECKED="$(in_tree round-trip 0.2.0 2026-07-24 --check)"
|
CHECKED="$(in_tree round-trip 0.2.0 2026-07-24 --check)"
|
||||||
check "round trip: write mode succeeds after --check" 0 "" \
|
check "round trip: write mode succeeds after --check" 0 "" \
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,8 @@ Preamble prose belongs to no section.
|
||||||
- The shipped entry.
|
- The shipped entry.
|
||||||
EOF
|
EOF
|
||||||
printf '0.1.1-dev\n' >"$dir/VERSION"
|
printf '0.1.1-dev\n' >"$dir/VERSION"
|
||||||
printf -- '- Twelve landed.\n' >"$dir/changelog.d/12.md"
|
printf -- '- Twelve landed (#12).\n' >"$dir/changelog.d/12.md"
|
||||||
printf -- '- Nine landed, and its prose wraps onto a\n continuation line.\n' >"$dir/changelog.d/9.md"
|
printf -- '- Nine landed, and its prose wraps onto a\n continuation line (#9).\n' >"$dir/changelog.d/9.md"
|
||||||
commit_base "$name"
|
commit_base "$name"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,8 +86,8 @@ check "faithful flat ceremony: the section is byte-for-byte the assembly" 0 \
|
||||||
|
|
||||||
seed_flat faithful-grouped
|
seed_flat faithful-grouped
|
||||||
sed -i '/^- The shipped entry/i ### Fixed\\\n' "$TMP/faithful-grouped/CHANGELOG.md"
|
sed -i '/^- The shipped entry/i ### Fixed\\\n' "$TMP/faithful-grouped/CHANGELOG.md"
|
||||||
printf -- '### Fixed\n\n- Fixed twenty-one.\n' >"$TMP/faithful-grouped/changelog.d/21.md"
|
printf -- '### Fixed\n\n- Fixed twenty-one (#21).\n' >"$TMP/faithful-grouped/changelog.d/21.md"
|
||||||
printf -- '### Added\n\n- Added twenty.\n\n### Docs\n\n- Docs twenty.\n' >"$TMP/faithful-grouped/changelog.d/20.md"
|
printf -- '### Added\n\n- Added twenty (#20).\n\n### Docs\n\n- Docs twenty (#20).\n' >"$TMP/faithful-grouped/changelog.d/20.md"
|
||||||
rm "$TMP/faithful-grouped/changelog.d/12.md" "$TMP/faithful-grouped/changelog.d/9.md"
|
rm "$TMP/faithful-grouped/changelog.d/12.md" "$TMP/faithful-grouped/changelog.d/9.md"
|
||||||
git -C "$TMP/faithful-grouped" add -A
|
git -C "$TMP/faithful-grouped" add -A
|
||||||
git -C "$TMP/faithful-grouped" commit -qm regroup
|
git -C "$TMP/faithful-grouped" commit -qm regroup
|
||||||
|
|
@ -108,7 +108,7 @@ check "the stamp's date never enters the comparison" 0 "byte-for-byte" \
|
||||||
# --- inapplicable trees: green NOTICE, never a silent skip -------------------
|
# --- inapplicable trees: green NOTICE, never a silent skip -------------------
|
||||||
|
|
||||||
seed_flat ordinary-add
|
seed_flat ordinary-add
|
||||||
printf -- '- Thirteen incoming.\n' >"$TMP/ordinary-add/changelog.d/13.md"
|
printf -- '- Thirteen incoming (#13).\n' >"$TMP/ordinary-add/changelog.d/13.md"
|
||||||
commit_head ordinary-add
|
commit_head ordinary-add
|
||||||
check "-dev PR adding a fragment: green NOTICE" 0 "NOTICE" run ordinary-add base
|
check "-dev PR adding a fragment: green NOTICE" 0 "NOTICE" run ordinary-add base
|
||||||
|
|
||||||
|
|
@ -195,8 +195,8 @@ Preamble prose belongs to no section.
|
||||||
## 0.2.0 — 2026-07-24
|
## 0.2.0 — 2026-07-24
|
||||||
|
|
||||||
- Nine landed, and its prose wraps onto a
|
- Nine landed, and its prose wraps onto a
|
||||||
continuation line.
|
continuation line (#9).
|
||||||
- Twelve landed.
|
- Twelve landed (#12).
|
||||||
|
|
||||||
## 0.1.0 — 2026-07-01
|
## 0.1.0 — 2026-07-01
|
||||||
|
|
||||||
|
|
@ -210,7 +210,7 @@ check "re-ordered entries fail" 1 "NOT what the fragments" run reordered base
|
||||||
# directory is not — only the survivor refusal fires.
|
# directory is not — only the survivor refusal fires.
|
||||||
seed_flat survivor
|
seed_flat survivor
|
||||||
ceremony survivor 0.2.0 2026-07-24
|
ceremony survivor 0.2.0 2026-07-24
|
||||||
printf -- '- Nine landed, and its prose wraps onto a\n continuation line.\n' >"$TMP/survivor/changelog.d/9.md"
|
printf -- '- Nine landed, and its prose wraps onto a\n continuation line (#9).\n' >"$TMP/survivor/changelog.d/9.md"
|
||||||
commit_head survivor
|
commit_head survivor
|
||||||
check "a surviving fragment with its entry present fails" 1 "STILL PRESENT" \
|
check "a surviving fragment with its entry present fails" 1 "STILL PRESENT" \
|
||||||
run survivor base
|
run survivor base
|
||||||
|
|
@ -318,7 +318,7 @@ init_repo env-tree
|
||||||
mkdir -p "$TMP/env-tree/frags"
|
mkdir -p "$TMP/env-tree/frags"
|
||||||
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped.\n' >"$TMP/env-tree/NOTES.md"
|
printf '# Changelog\n\n## 0.1.0 — 2026-07-01\n\n- Shipped.\n' >"$TMP/env-tree/NOTES.md"
|
||||||
printf '0.1.1-dev\n' >"$TMP/env-tree/VERSION"
|
printf '0.1.1-dev\n' >"$TMP/env-tree/VERSION"
|
||||||
printf -- '- Flagged entry.\n' >"$TMP/env-tree/frags/2.md"
|
printf -- '- Flagged entry (#2).\n' >"$TMP/env-tree/frags/2.md"
|
||||||
git -C "$TMP/env-tree" add -A
|
git -C "$TMP/env-tree" add -A
|
||||||
git -C "$TMP/env-tree" commit -qm base
|
git -C "$TMP/env-tree" commit -qm base
|
||||||
git -C "$TMP/env-tree" branch fixture-base
|
git -C "$TMP/env-tree" branch fixture-base
|
||||||
|
|
|
||||||
|
|
@ -182,11 +182,11 @@ printf 'marker\n' >"$FRAG/README.md"
|
||||||
check "fragments: README.md is the directory marker, never a fragment" 0 "" \
|
check "fragments: README.md is the directory marker, never a fragment" 0 "" \
|
||||||
changelog_fragments "$FRAG"
|
changelog_fragments "$FRAG"
|
||||||
|
|
||||||
printf -- '- Two.\n' >"$FRAG/2.md"
|
printf -- '- Two (#2).\n' >"$FRAG/2.md"
|
||||||
printf -- '- Nine.\n' >"$FRAG/9.md"
|
printf -- '- Nine (#9).\n' >"$FRAG/9.md"
|
||||||
printf -- '- Ten.\n' >"$FRAG/10.md"
|
printf -- '- Ten (#10).\n' >"$FRAG/10.md"
|
||||||
printf -- '- Cross.\n' >"$FRAG/ceremony-14.md"
|
printf -- '- Cross (#14).\n' >"$FRAG/ceremony-14.md"
|
||||||
printf -- '- Local fourteen.\n' >"$FRAG/14.md"
|
printf -- '- Local fourteen (#14).\n' >"$FRAG/14.md"
|
||||||
|
|
||||||
assert_fragments_order() {
|
assert_fragments_order() {
|
||||||
local expected="$1" actual
|
local expected="$1" actual
|
||||||
|
|
@ -205,19 +205,19 @@ check "fragments: issue number descending (numeric, 10 before 9), filename tie-b
|
||||||
PF="$TMP/frag-problems"
|
PF="$TMP/frag-problems"
|
||||||
mkdir -p "$PF"
|
mkdir -p "$PF"
|
||||||
|
|
||||||
printf -- '- Fine.\n' >"$PF/7.md"
|
printf -- '- Fine (#7).\n' >"$PF/7.md"
|
||||||
check "fragment predicate: a flat fragment passes" 0 "" \
|
check "fragment predicate: a flat fragment passes" 0 "" \
|
||||||
changelog_fragment_problem "$PF/7.md"
|
changelog_fragment_problem "$PF/7.md"
|
||||||
|
|
||||||
cat >"$PF/8.md" <<'EOF'
|
cat >"$PF/8.md" <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Grouped fine.
|
- Grouped fine (#8).
|
||||||
EOF
|
EOF
|
||||||
check "fragment predicate: a grouped fragment passes" 0 "" \
|
check "fragment predicate: a grouped fragment passes" 0 "" \
|
||||||
changelog_fragment_problem "$PF/8.md"
|
changelog_fragment_problem "$PF/8.md"
|
||||||
|
|
||||||
printf -- '- Cross-repo.\n' >"$PF/ceremony-14.md"
|
printf -- '- Cross-repo (#14).\n' >"$PF/ceremony-14.md"
|
||||||
check "fragment predicate: a cross-repo name passes" 0 "" \
|
check "fragment predicate: a cross-repo name passes" 0 "" \
|
||||||
changelog_fragment_problem "$PF/ceremony-14.md"
|
changelog_fragment_problem "$PF/ceremony-14.md"
|
||||||
|
|
||||||
|
|
@ -274,14 +274,14 @@ check "length bound: the refusal names the bound and the split fix" 1 \
|
||||||
"the bound is 300: split it into multiple '- ' entries in this same fragment" \
|
"the bound is 300: split it into multiple '- ' entries in this same fragment" \
|
||||||
changelog_fragment_problem "$PF/30.md"
|
changelog_fragment_problem "$PF/30.md"
|
||||||
|
|
||||||
printf -- '- %s\n' "$(mkchars 300)" >"$PF/31.md"
|
printf -- '- %s (#31).\n' "$(mkchars 293)" >"$PF/31.md"
|
||||||
check "length bound: an entry of exactly 300 passes" 0 "" \
|
check "length bound: an entry of exactly 300 passes" 0 "" \
|
||||||
changelog_fragment_problem "$PF/31.md"
|
changelog_fragment_problem "$PF/31.md"
|
||||||
|
|
||||||
{
|
{
|
||||||
printf -- '- %s\n' "$(mkchars 150)"
|
printf -- '- %s (#32).\n' "$(mkchars 143)"
|
||||||
printf -- '- %s\n' "$(mkchars 150)"
|
printf -- '- %s (#32).\n' "$(mkchars 143)"
|
||||||
printf -- '- %s\n' "$(mkchars 150)"
|
printf -- '- %s (#32).\n' "$(mkchars 143)"
|
||||||
} >"$PF/32.md"
|
} >"$PF/32.md"
|
||||||
check "length bound: several within-bound entries pass though the file totals over 300" 0 "" \
|
check "length bound: several within-bound entries pass though the file totals over 300" 0 "" \
|
||||||
changelog_fragment_problem "$PF/32.md"
|
changelog_fragment_problem "$PF/32.md"
|
||||||
|
|
@ -291,14 +291,14 @@ check "length bound: several within-bound entries pass though the file totals ov
|
||||||
printf ' %s\n' "$(mkchars 50)"
|
printf ' %s\n' "$(mkchars 50)"
|
||||||
printf ' %s\n' "$(mkchars 50)"
|
printf ' %s\n' "$(mkchars 50)"
|
||||||
printf ' %s\n' "$(mkchars 50)"
|
printf ' %s\n' "$(mkchars 50)"
|
||||||
printf ' %s\n' "$(mkchars 50)"
|
printf ' %s (#33).\n' "$(mkchars 50)"
|
||||||
} >"$PF/33.md"
|
} >"$PF/33.md"
|
||||||
check "length bound: a ~250-character entry wrapped over four continuation lines passes" 0 "" \
|
check "length bound: a ~250-character entry wrapped over four continuation lines passes" 0 "" \
|
||||||
changelog_fragment_problem "$PF/33.md"
|
changelog_fragment_problem "$PF/33.md"
|
||||||
|
|
||||||
{
|
{
|
||||||
printf '### Added\n\n'
|
printf '### Added\n\n'
|
||||||
printf -- '- %s\n' "$(mkchars 300)"
|
printf -- '- %s (#34).\n' "$(mkchars 293)"
|
||||||
} >"$PF/34.md"
|
} >"$PF/34.md"
|
||||||
check "length bound: a '### ' heading counts toward no entry — 300 under it still passes" 0 "" \
|
check "length bound: a '### ' heading counts toward no entry — 300 under it still passes" 0 "" \
|
||||||
changelog_fragment_problem "$PF/34.md"
|
changelog_fragment_problem "$PF/34.md"
|
||||||
|
|
@ -331,6 +331,151 @@ check "length bound: published sections stay unvalidated — 0.3.0's over-bound
|
||||||
check "length bound: published sections stay unvalidated — 0.2.0 reds nothing either" 0 "" \
|
check "length bound: published sections stay unvalidated — 0.2.0 reds nothing either" 0 "" \
|
||||||
changelog_section_problem "$ROOT/CHANGELOG.md" 0.2.0
|
changelog_section_problem "$ROOT/CHANGELOG.md" 0.2.0
|
||||||
|
|
||||||
|
# --- the terminal issue cite (#262) ------------------------------------------
|
||||||
|
|
||||||
|
# cite_case <number> <entry-line...> — a fragment holding exactly the given
|
||||||
|
# lines, so a case reads as the entry it is about.
|
||||||
|
cite_case() {
|
||||||
|
local num="$1"
|
||||||
|
shift
|
||||||
|
printf '%s\n' "$@" >"$PF/$num.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
cite_case 40 '- Local (#262).'
|
||||||
|
check "cite: the canonical '(#N).' passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/40.md"
|
||||||
|
cite_case 41 '- Sibling repo (crew#309).'
|
||||||
|
check "cite: a sibling-repo reference passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/41.md"
|
||||||
|
cite_case 42 '- Fully qualified (heavy-duty/crew#309).'
|
||||||
|
check "cite: an owner/repo reference passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/42.md"
|
||||||
|
cite_case 43 '- Two issues, one entry (#236, #250).'
|
||||||
|
check "cite: one group carrying two references passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/43.md"
|
||||||
|
|
||||||
|
# The cite is measured on the normalized entry, so a citation that lands on
|
||||||
|
# a continuation line still closes the entry — the #167 lesson, repeated:
|
||||||
|
# wrapping alone must never red a compliant entry.
|
||||||
|
cite_case 44 '- An entry whose prose wraps onto a' ' continuation line, cite and all (#262).'
|
||||||
|
check "cite: a citation on a continuation line passes — the entry is normalized first" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/44.md"
|
||||||
|
|
||||||
|
cite_case 45 '### Added' '' '- Added one (#101).' '- Added two (#102).' '' \
|
||||||
|
'### Changed' '' '- Changed one (#103).' '' '### Fixed' '' '- Fixed one (#104).'
|
||||||
|
check "cite: a grouped fragment, three headings, every entry compliant, passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/45.md"
|
||||||
|
|
||||||
|
# The two diagnoses are distinct by construction (D5): a builder who reads
|
||||||
|
# one must not be told the other's fix.
|
||||||
|
cite_case 50 '- No cite here.'
|
||||||
|
check "cite: an entry with no reference at all is refused" 1 \
|
||||||
|
"50.md' has an entry with no issue citation" \
|
||||||
|
changelog_fragment_problem "$PF/50.md"
|
||||||
|
check "cite: the uncited refusal names the shape to write" 1 \
|
||||||
|
"end it with the issue it comes from: '(#N).'" \
|
||||||
|
changelog_fragment_problem "$PF/50.md"
|
||||||
|
|
||||||
|
cite_case 51 '- Cite before the period. (#262)'
|
||||||
|
check "cite: a citation trailing the period is refused — the 248.md shape" 1 \
|
||||||
|
"51.md' has an entry whose issue citation is not terminal" \
|
||||||
|
changelog_fragment_problem "$PF/51.md"
|
||||||
|
check "cite: the misplaced refusal names the shape to write" 1 \
|
||||||
|
"exactly one '(#N)' group ends the entry, the final '.' after it" \
|
||||||
|
changelog_fragment_problem "$PF/51.md"
|
||||||
|
|
||||||
|
cite_case 52 '- Trailing prose (#262) and then more.'
|
||||||
|
check "cite: a citation with prose after it is refused" 1 \
|
||||||
|
"has an entry whose issue citation is not terminal" \
|
||||||
|
changelog_fragment_problem "$PF/52.md"
|
||||||
|
|
||||||
|
cite_case 53 '- Two groups (#236) and (#250).'
|
||||||
|
check "cite: two citation groups are refused — one terminal group, or none (D2)" 1 \
|
||||||
|
"has an entry whose issue citation is not terminal" \
|
||||||
|
changelog_fragment_problem "$PF/53.md"
|
||||||
|
|
||||||
|
cite_case 54 '- Bad token (#abc).'
|
||||||
|
check "cite: a reference with no digits is no reference" 1 \
|
||||||
|
"has an entry with no issue citation" \
|
||||||
|
changelog_fragment_problem "$PF/54.md"
|
||||||
|
cite_case 55 '- Bad token (#).'
|
||||||
|
check "cite: an empty reference is no reference" 1 \
|
||||||
|
"has an entry with no issue citation" \
|
||||||
|
changelog_fragment_problem "$PF/55.md"
|
||||||
|
|
||||||
|
# The citation need not name the file's own issue (D3): the filename already
|
||||||
|
# carries the authorizing one, so an entry may cite the incident beside it.
|
||||||
|
cite_case 56 '- Cites another issue entirely (#101).'
|
||||||
|
check "cite: the reference need not match the filename" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/56.md"
|
||||||
|
|
||||||
|
# Ordering: the bound outranks the cite across the whole fragment, so a
|
||||||
|
# fragment that reds today draws the diagnosis it drew before this rule
|
||||||
|
# existed. The uncited entry comes FIRST here on purpose — the other order
|
||||||
|
# would pass whatever the precedence is.
|
||||||
|
{
|
||||||
|
printf -- '- Uncited, and it comes first.\n'
|
||||||
|
printf -- '- %s\n' "$(mkchars 301)"
|
||||||
|
} >"$PF/57.md"
|
||||||
|
check "cite: an over-bound entry outranks an earlier uncited one" 1 \
|
||||||
|
"57.md' has a 301-character entry" \
|
||||||
|
changelog_fragment_problem "$PF/57.md"
|
||||||
|
assert_one_diagnosis() {
|
||||||
|
local count
|
||||||
|
count="$(changelog_fragment_problem "$PF/$1.md" | wc -l)"
|
||||||
|
[ "$count" = 1 ] || {
|
||||||
|
printf 'wanted one diagnosis, got %s\n' "$count"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check "cite: the outranked citation problem is not reported beside it" 0 "" \
|
||||||
|
assert_one_diagnosis 57
|
||||||
|
|
||||||
|
# The axis 57.md cannot test: its over-bound entry is LAST, so the only
|
||||||
|
# flush that can print is END's, which exits immediately. A flush from a
|
||||||
|
# main rule exits too — but awk runs END on the way out, so the citation
|
||||||
|
# row a mid-file length row outranks would print after it unless END is
|
||||||
|
# guarded. Both ways out of the walk, a bullet and a heading.
|
||||||
|
{
|
||||||
|
printf -- '- Uncited, and it comes first.\n'
|
||||||
|
printf -- '- %s\n' "$(mkchars 301)"
|
||||||
|
printf -- '- A later entry the walk never reaches (#57).\n'
|
||||||
|
} >"$PF/58.md"
|
||||||
|
check "cite: an over-bound entry that is not the last one still reports the bound" 1 \
|
||||||
|
"58.md' has a 301-character entry" \
|
||||||
|
changelog_fragment_problem "$PF/58.md"
|
||||||
|
check "cite: and it is still one diagnosis, not the protocol row spliced into it" 0 "" \
|
||||||
|
assert_one_diagnosis 58
|
||||||
|
{
|
||||||
|
printf '### Fixed\n'
|
||||||
|
printf -- '- Misplaced, and it comes first. (#59)\n'
|
||||||
|
printf -- '- %s\n' "$(mkchars 301)"
|
||||||
|
printf '### Changed\n'
|
||||||
|
printf -- '- The heading is the other way out of the walk (#59).\n'
|
||||||
|
} >"$PF/59.md"
|
||||||
|
check "cite: a heading after the over-bound entry is the same one diagnosis" 1 \
|
||||||
|
"59.md' has a 301-character entry" \
|
||||||
|
changelog_fragment_problem "$PF/59.md"
|
||||||
|
check "cite: the misplaced row does not ride along with it either" 0 "" \
|
||||||
|
assert_one_diagnosis 59
|
||||||
|
|
||||||
|
# Published sections keep their pre-rule prose (D4): reddening history is a
|
||||||
|
# wall, not a guard. Every shipped section predates the cite.
|
||||||
|
check "cite: a published section with uncited entries still reds nothing" 0 "" \
|
||||||
|
changelog_section_problem "$ROOT/CHANGELOG.md" 0.3.0
|
||||||
|
|
||||||
|
# The fragments this repo carries right now are the rule's own first
|
||||||
|
# constituency — the guard is worth nothing if the tree it ships in fails it.
|
||||||
|
assert_tree_fragments() {
|
||||||
|
local f
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
changelog_fragment_problem "$f" || return 1
|
||||||
|
done <<<"$(changelog_fragments "$ROOT/changelog.d")"
|
||||||
|
}
|
||||||
|
check "cite: every fragment in this tree passes the rule it ships" 0 "" \
|
||||||
|
assert_tree_fragments
|
||||||
|
|
||||||
# --- the assembler (#114) ----------------------------------------------------
|
# --- the assembler (#114) ----------------------------------------------------
|
||||||
|
|
||||||
assert_assemble() {
|
assert_assemble() {
|
||||||
|
|
@ -347,11 +492,11 @@ mkdir -p "$AF"
|
||||||
printf 'marker\n' >"$AF/README.md"
|
printf 'marker\n' >"$AF/README.md"
|
||||||
cat >"$AF/3.md" <<'EOF'
|
cat >"$AF/3.md" <<'EOF'
|
||||||
- Three — an em dash, and prose that
|
- Three — an em dash, and prose that
|
||||||
wraps onto a continuation line.
|
wraps onto a continuation line (#3).
|
||||||
EOF
|
EOF
|
||||||
printf -- '- Ten.\n- Ten again.\n' >"$AF/10.md"
|
printf -- '- Ten (#10).\n- Ten again (#10).\n' >"$AF/10.md"
|
||||||
check "assemble: flat fragments, newest issue first, prose verbatim" 0 "" \
|
check "assemble: flat fragments, newest issue first, prose verbatim" 0 "" \
|
||||||
assert_assemble "$AF" $'- Ten.\n- Ten again.\n- Three — an em dash, and prose that\n wraps onto a continuation line.'
|
assert_assemble "$AF" $'- Ten (#10).\n- Ten again (#10).\n- Three — an em dash, and prose that\n wraps onto a continuation line (#3).'
|
||||||
|
|
||||||
check "assemble: an empty directory is empty output — refusing is the caller's stance" 0 "" \
|
check "assemble: an empty directory is empty output — refusing is the caller's stance" 0 "" \
|
||||||
changelog_assemble "$TMP/no-such-dir"
|
changelog_assemble "$TMP/no-such-dir"
|
||||||
|
|
@ -361,36 +506,36 @@ mkdir -p "$AG"
|
||||||
cat >"$AG/21.md" <<'EOF'
|
cat >"$AG/21.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed twenty-one.
|
- Fixed twenty-one (#21).
|
||||||
EOF
|
EOF
|
||||||
cat >"$AG/20.md" <<'EOF'
|
cat >"$AG/20.md" <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added twenty.
|
- Added twenty (#20).
|
||||||
|
|
||||||
### Docs
|
### Docs
|
||||||
|
|
||||||
- Docs twenty.
|
- Docs twenty (#20).
|
||||||
EOF
|
EOF
|
||||||
cat >"$AG/19.md" <<'EOF'
|
cat >"$AG/19.md" <<'EOF'
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Security nineteen.
|
- Security nineteen (#19).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added nineteen.
|
- Added nineteen (#19).
|
||||||
EOF
|
EOF
|
||||||
check "assemble: canonical group order, unnamed group appended, fragment order inside a group" 0 "" \
|
check "assemble: canonical group order, unnamed group appended, fragment order inside a group" 0 "" \
|
||||||
assert_assemble "$AG" $'### Added\n\n- Added twenty.\n- Added nineteen.\n\n### Fixed\n\n- Fixed twenty-one.\n\n### Security\n\n- Security nineteen.\n\n### Docs\n\n- Docs twenty.'
|
assert_assemble "$AG" $'### Added\n\n- Added twenty (#20).\n- Added nineteen (#19).\n\n### Fixed\n\n- Fixed twenty-one (#21).\n\n### Security\n\n- Security nineteen (#19).\n\n### Docs\n\n- Docs twenty (#20).'
|
||||||
|
|
||||||
AM="$TMP/assemble-mixed"
|
AM="$TMP/assemble-mixed"
|
||||||
mkdir -p "$AM"
|
mkdir -p "$AM"
|
||||||
printf -- '- Flat five.\n' >"$AM/5.md"
|
printf -- '- Flat five (#5).\n' >"$AM/5.md"
|
||||||
cat >"$AM/6.md" <<'EOF'
|
cat >"$AM/6.md" <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Grouped six.
|
- Grouped six (#6).
|
||||||
EOF
|
EOF
|
||||||
check "assemble: mixed shapes refused, grouped side named" 1 "6.md" \
|
check "assemble: mixed shapes refused, grouped side named" 1 "6.md" \
|
||||||
changelog_assemble "$AM"
|
changelog_assemble "$AM"
|
||||||
|
|
@ -400,11 +545,11 @@ check "assemble: mixed shapes refused, flat side named too" 1 "5.md" \
|
||||||
AX="$TMP/assemble-selfmixed"
|
AX="$TMP/assemble-selfmixed"
|
||||||
mkdir -p "$AX"
|
mkdir -p "$AX"
|
||||||
cat >"$AX/7.md" <<'EOF'
|
cat >"$AX/7.md" <<'EOF'
|
||||||
- Ungrouped lead.
|
- Ungrouped lead (#7).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Grouped follow.
|
- Grouped follow (#7).
|
||||||
EOF
|
EOF
|
||||||
check "assemble: one fragment mixing both shapes is refused, file named" 1 \
|
check "assemble: one fragment mixing both shapes is refused, file named" 1 \
|
||||||
"'$AX/7.md' mixes grouped headings and ungrouped bullets" \
|
"'$AX/7.md' mixes grouped headings and ungrouped bullets" \
|
||||||
|
|
@ -429,14 +574,14 @@ cat >"$SHAPE_CHANGELOG" <<'EOF'
|
||||||
|
|
||||||
- Older section is grouped.
|
- Older section is grouped.
|
||||||
EOF
|
EOF
|
||||||
printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md"
|
printf -- '- Flat fragment (#1).\n' >"$SHAPE_DIR/1.md"
|
||||||
check "shape: flat set matches newest flat published section" 0 "" \
|
check "shape: flat set matches newest flat published section" 0 "" \
|
||||||
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
||||||
|
|
||||||
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#1).
|
||||||
EOF
|
EOF
|
||||||
check "shape: grouped set names its conflict with newest flat published section" 1 \
|
check "shape: grouped set names its conflict with newest flat published section" 1 \
|
||||||
"fragment '$SHAPE_DIR/1.md' is grouped but newest published section '2.0.0' in '$SHAPE_CHANGELOG' is flat" \
|
"fragment '$SHAPE_DIR/1.md' is grouped but newest published section '2.0.0' in '$SHAPE_CHANGELOG' is flat" \
|
||||||
|
|
@ -451,7 +596,7 @@ cat >"$SHAPE_CHANGELOG" <<'EOF'
|
||||||
|
|
||||||
- Newest section is grouped.
|
- Newest section is grouped.
|
||||||
EOF
|
EOF
|
||||||
printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md"
|
printf -- '- Flat fragment (#1).\n' >"$SHAPE_DIR/1.md"
|
||||||
check "shape: flat set names its conflict with newest grouped published section" 1 \
|
check "shape: flat set names its conflict with newest grouped published section" 1 \
|
||||||
"fragment '$SHAPE_DIR/1.md' is flat but newest published section '2.0.0' in '$SHAPE_CHANGELOG' is grouped" \
|
"fragment '$SHAPE_DIR/1.md' is flat but newest published section '2.0.0' in '$SHAPE_CHANGELOG' is grouped" \
|
||||||
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
||||||
|
|
@ -459,7 +604,7 @@ check "shape: flat set names its conflict with newest grouped published section"
|
||||||
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#1).
|
||||||
EOF
|
EOF
|
||||||
check "shape: grouped set matches newest grouped published section" 0 "" \
|
check "shape: grouped set matches newest grouped published section" 0 "" \
|
||||||
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
||||||
|
|
@ -484,7 +629,7 @@ EOF
|
||||||
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#1).
|
||||||
EOF
|
EOF
|
||||||
printf 'grouped\n' >"$SHAPE_DIR/shape"
|
printf 'grouped\n' >"$SHAPE_DIR/shape"
|
||||||
check "shape: 'grouped' sentinel admits a grouped set over a flat published section" 0 "" \
|
check "shape: 'grouped' sentinel admits a grouped set over a flat published section" 0 "" \
|
||||||
|
|
@ -492,7 +637,7 @@ check "shape: 'grouped' sentinel admits a grouped set over a flat published sect
|
||||||
check "shape: the sentinel binds with no changelog at all — the assembler's call" 0 "" \
|
check "shape: the sentinel binds with no changelog at all — the assembler's call" 0 "" \
|
||||||
changelog_shape_problem "" "$SHAPE_DIR"
|
changelog_shape_problem "" "$SHAPE_DIR"
|
||||||
|
|
||||||
printf -- '- Flat fragment.\n' >"$SHAPE_DIR/1.md"
|
printf -- '- Flat fragment (#1).\n' >"$SHAPE_DIR/1.md"
|
||||||
check "shape: flat fragment under a 'grouped' sentinel refused, fragment and sentinel named" 1 \
|
check "shape: flat fragment under a 'grouped' sentinel refused, fragment and sentinel named" 1 \
|
||||||
"fragment '$SHAPE_DIR/1.md' is flat but '$SHAPE_DIR/shape' declares grouped" \
|
"fragment '$SHAPE_DIR/1.md' is flat but '$SHAPE_DIR/shape' declares grouped" \
|
||||||
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
||||||
|
|
@ -512,14 +657,14 @@ check "shape: 'flat' sentinel admits a flat set over a grouped published section
|
||||||
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#1).
|
||||||
EOF
|
EOF
|
||||||
check "shape: grouped fragment under a 'flat' sentinel refused, fragment and sentinel named" 1 \
|
check "shape: grouped fragment under a 'flat' sentinel refused, fragment and sentinel named" 1 \
|
||||||
"fragment '$SHAPE_DIR/1.md' is grouped but '$SHAPE_DIR/shape' declares flat" \
|
"fragment '$SHAPE_DIR/1.md' is grouped but '$SHAPE_DIR/shape' declares flat" \
|
||||||
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
||||||
|
|
||||||
printf 'grouped\n' >"$SHAPE_DIR/shape"
|
printf 'grouped\n' >"$SHAPE_DIR/shape"
|
||||||
printf -- '- Flat two.\n' >"$SHAPE_DIR/2.md"
|
printf -- '- Flat two (#2).\n' >"$SHAPE_DIR/2.md"
|
||||||
check "shape: a mixed set is refused regardless of the sentinel" 1 \
|
check "shape: a mixed set is refused regardless of the sentinel" 1 \
|
||||||
"fragment '$SHAPE_DIR/1.md' is grouped but fragment '$SHAPE_DIR/2.md' is not" \
|
"fragment '$SHAPE_DIR/1.md' is grouped but fragment '$SHAPE_DIR/2.md' is not" \
|
||||||
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
changelog_shape_problem "$SHAPE_CHANGELOG" "$SHAPE_DIR"
|
||||||
|
|
@ -558,7 +703,7 @@ printf 'grouped\n' >"$SHAPE_DIR/shape"
|
||||||
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
cat >"$SHAPE_DIR/1.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Grouped fragment.
|
- Grouped fragment (#1).
|
||||||
EOF
|
EOF
|
||||||
assert_fragments_exclude_sentinel() {
|
assert_fragments_exclude_sentinel() {
|
||||||
local out
|
local out
|
||||||
|
|
@ -579,17 +724,17 @@ printf 'grouped\n' >"$AS/shape"
|
||||||
cat >"$AS/30.md" <<'EOF'
|
cat >"$AS/30.md" <<'EOF'
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed thirty.
|
- Fixed thirty (#30).
|
||||||
EOF
|
EOF
|
||||||
cat >"$AS/31.md" <<'EOF'
|
cat >"$AS/31.md" <<'EOF'
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added thirty-one.
|
- Added thirty-one (#31).
|
||||||
EOF
|
EOF
|
||||||
check "assemble: the sentinel never assembles, and canonical order holds under it" 0 "" \
|
check "assemble: the sentinel never assembles, and canonical order holds under it" 0 "" \
|
||||||
assert_assemble "$AS" $'### Added\n\n- Added thirty-one.\n\n### Fixed\n\n- Fixed thirty.'
|
assert_assemble "$AS" $'### Added\n\n- Added thirty-one (#31).\n\n### Fixed\n\n- Fixed thirty (#30).'
|
||||||
rm "$AS/30.md" "$AS/31.md"
|
rm "$AS/30.md" "$AS/31.md"
|
||||||
printf -- '- Flat probe.\n' >"$AS/29.md"
|
printf -- '- Flat probe (#29).\n' >"$AS/29.md"
|
||||||
check "assemble: a flat set under a 'grouped' sentinel refuses to assemble" 1 \
|
check "assemble: a flat set under a 'grouped' sentinel refuses to assemble" 1 \
|
||||||
"declares grouped" \
|
"declares grouped" \
|
||||||
changelog_assemble "$AS"
|
changelog_assemble "$AS"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue