fix(prep): print the [Unreleased] refusal's lines without a CRLF file's \r (#1507) #1508
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit!1508
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/1507-prep-refusal-crlf"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs #1507
Why
On a CRLF
CHANGELOG.md,rt prep's[Unreleased]refusal (prep.UnreleasedDropError, #1486) printed eachUncarriedLine.Textverbatim,\rincluded. In a terminal the\rreturns the cursor to column 0, so the reason is drawn over the file line and text the refusal exists to point at. Detection was already right. #1493 fixed the same thing in the fold-back refusal with a privateunCR, so the two refusals rendered the same line two different ways.Decision tree
Where the
\ris stripped\rfrom the body beforeUncarriedLines) would be right only if nothing needed the raw record.UncarriedLine.Textis documented as verbatim, and #1493 kept it that way. Mutant M4 below shows the new test refuses this option.Shape of the shared helper
UncarriedLine.Render(where, reason), which renders the whole<where>: <text> <- <reason>line, is right when the thing that drifted is the line format: both refusals carried their own copy of the same format literal. Taken. Each caller keeps its own indent,whereandreason, because those differ.rt prepnumbers lines within the file or within the body. The fold-back refusal overrides the reason for a pending line above its first heading.unCRwould be right if callers needed to strip some field other than the line text. It leaves each caller to remember the call, which is howrt prepmissed it.unCRstays unexported; the fold-back refusal still uses it forPendingHeading.What is stripped
\r(#1493'sstrings.TrimRight(s, "\r")) is right because LF output must stay byte-identical (AC 3). Trailing spaces or tabs the operator wrote print as before. Taken.Reason()applies toHeading) would change LF output for a line ending in spaces. Mutant M3 shows the new unit test refuses it.What's in the PR
internal/changelog/merge.go: addsUncarriedLine.Render. TheTextdoc now says the field keeps a CRLF file's\rand thatRenderstrips it.internal/prep/prep.go:UnreleasedDropError.Errorprints each line throughRender.internal/changelog/foldback.go:FoldBackDropError.Describeprints throughRenderinstead of the inlineunCR(l.Text). Its output is unchanged, and its existing CRLF test passes without edits.internal/prep/unreleased_drop_test.go: addsTestCompose_refusalOnACRLFChangelog.internal/changelog/uncarried_test.go: addsTestUncarriedLine_Render.changelog.d/1507-prep-refusal-crlf.fixed.md.Acceptance criteria (#1507)
\ron CRLF, same file lines as LF.TestCompose_refusalOnACRLFChangelogbuilds its input with the same two readsrt prepmakes,UnreleasedContentandUnreleasedStartLine(cmd/rt/prep.go:195,:298). It runs the issue's CHANGELOG through them in both line endings. It asserts that the CRLF refusal contains no\rand is byte-equal to the LF refusal, so it names the same lines 5, 7 and 9.UncarriedLine.Textstays verbatim. In the same test, each CRLFLines[i]has the LF line number and the LF text plus\r.changelog.AcceptedHeadingsNote()andchangelog.UncarriedRemedy("rt prep")(sincede49186, review 7168). The tail's bytes stay pinned as a literal by the existingTestCompose_refusesUnreleasedTextTheFoldWouldDrop, which is unchanged and passes.Verification
All runs used a fresh LF clone in
golang:1.25.Red on main (
2060ed1code, new tests).TestCompose_refusalOnACRLFChangelogfails. The CRLF refusal reads" CHANGELOG.md:5: Prose.\r <- before the first ### heading\n CHANGELOG.md:7: ### Notes\r ...", with all three lines carrying\r. The LF golden assertion passes on main, so the golden really is main's output.TestUncarriedLine_Renderfails to build on main becauseRenderdoes not exist yet, so its teeth come from the mutants below.Green at head
de4918684856ff3ef364de2381866e5e7576bd42:go test -count=1 ./...passes 29 of 29 packages, with no failures.go vet ./...is clean, andgofmt -l cmd internalis empty.golangci-lint run --max-issues-per-linter=0 --max-same-issues=0(v2.14.0, whole repo) reports0 issues.FRAGMENT_CHECK_COVERAGE=content rt fragment-check changelog.dpasses at5682563.de49186does not touch the fragment.Mutations (AGENTS.md section 8, Axis B). Each mutant ran on a copy, with the clone mounted read-only. M1-M6 were measured at
5682563.de49186changes only how the test buildswant, so I re-ran the red-on-main check, M1 and M5 atde49186, and each went red at the same assertions as before.rt prepprintsl.Textraw again (main's rendering)TestCompose_refusalOnACRLFChangelogRenderkeeps the\rTestUncarriedLine_Render,TestCompose_refusalOnACRLFChangelog,TestFoldBackDescribeOnACRLFChangelogRenderstrips all trailing white spaceTestUncarriedLine_RenderComposestrips\rbefore detection, soTextis no longer verbatimTestCompose_refusalOnACRLFChangelog, at the verbatim-Textassertionrt prep's line indent changes from two spaces to oneTestCompose_refusalOnACRLFChangelog, at the LF golden. The existing prep test stays green under this mutant, which is the gap the whole-string pin closes.l.TextrawTestFoldBackDescribeOnACRLFChangelogM2 is the one that shows both refusals print through
Render: one change turns the CRLF tests of both verbs red.Review fixes
de49186takes the optional nit from review 7168: the CRLF test's LF golden builds its tail from the shared helpers instead of literals. Measured atde49186, with the clone read-only:rt prepreplaces the remedy line:55,:58) and the CRLF test's golden (:98)."Performance"):55), andTestCompose_refusalOnACRLFChangelogstays green. At5682563the same change also turned the CRLF test red, at:98and:106.Re-pin (AGENTS.md section 2)
By mechanism-of-touch this changes
rt prep, a cut verb. Measured at this head, no re-pin is needed:gates.DefaultComposeScripts(internal/gates/selfboot_check.go:234) lists only the reusable workflows andscripts/workflow-parse-check.sh. It lists no Go files, socheck-self-bootstraphas nothing to flag.reusable-release.yml@main(.forgejo/workflows/release.yml:79).rtfrom the workspace commit (reusable-release.yml:405-407), so the next push:main prep runs this code.Verify-at-source request (section 6)
Please verify both halves of the shared mechanism at source, not from this description:
rt prep: on a CRLF changelog, the refusal has no\rand matches LF. RunTestCompose_refusalOnACRLFChangelog, then reproduce M1 (revert theprep.goline tol.Text) and watch it go red.rt recover-pending-cut: the fold-back output is unchanged.TestFoldBackDescribeOnACRLFChangelogis untouched and green, and M2 or M6 turns it red.This changes only an error path, so a well-formed
[Unreleased]takes the same path as before and the next cut is unaffected.Review coordination
The PR follows section 6.2. I will announce
HOLDhere before pushing any review fix. Fixes land as new commits, never an amend or force-push. Before you submit, runrt review-submit-check --owner frankenbit --repo release-toolkit --pr 1508 --reviewed-head <full SHA>.What this PR does NOT do
UncarriedLineswas already right on CRLF, and the new test confirms it finds the same lines.###line inside a fenced block, or for an H2 after an accepted H3. That is #1492.\rinrt prepoutput beyond this refusal. The other text in this refusal carries none:Reason()already trimsHeading, and the header prints the configured path. Other messages were not measured.🤖 Generated with Claude Code
Verdict: APPROVE at
5682563f93e3544838afa758f2fa83bf045b2729. I am posting this as a COMMENT because I can't post the approval itself. This session's forge identity isclaude, the PR's author, and Forgejo refused the APPROVED review with422 approve your own pull is not allowed. That block applies to every repo (AGENTS.md section 8, axis A, instance 7). @alex, the formal approval needs your click. Before submitting,rt review-submit-check --pr 1508 --reviewed-head 5682563f…returned rc=0 (READY), and I re-read the head right before posting.Verify-at-source (section 6)
I checked everything below myself in a fresh LF clone at the head, using
golang:1.25. Each mutant ran on a copy, with the clone mounted read-only.go test -count=1 ./...passes.go vet ./...is clean andgofmt -l cmd internalis empty. All 30 CI contexts on the head aresuccess.prep.go,merge.goandfoldback.goto2060ed1and kept the new tests.TestCompose_refusalOnACRLFChangelogfails on the raw-\rassertion and on the CRLF≠LF assertion, withProse.\r,### Notes\rand- n\ron lines 5, 7 and 9. The LF golden does not fail on main, so the golden really is main's LF output and AC 3 holds.rt prepprintsl.TextrawTestCompose_refusalOnACRLFChangelogRenderkeeps the\rTestUncarriedLine_Render,TestCompose_refusalOnACRLFChangelog,TestFoldBackDescribeOnACRLFChangelogRendertrims" \t\r"TestUncarriedLine_Renderonly\r-stripped bodyTestCompose_refusalOnACRLFChangelog, at the verbatim-Textassertion (:114)TestCompose_refusalOnACRLFChangelog, at the LF golden (:98). The existing tail pin stays green, as claimed.l.TextrawTestFoldBackDescribeOnACRLFChangelogonlyM2 is the one that proves both halves: a single change to
Renderturns both refusals' CRLF tests red, so both print through it.UncarriedLine.Textis rendered in only two places,UnreleasedDropError.ErrorandFoldBackDropError.Describe, and both now go throughRender. The other.Textreaders ininternal/gatesare body-check hits, a different type.DefaultComposeScripts(internal/gates/selfboot_check.go:234) lists no Go files, and toolkit-self pinsreusable-release.yml@main(release.yml:79). So no re-pin is needed, as stated.AC (#1507)
All four hold as written: no
\rand the same file lines on CRLF,Textkept verbatim, LF output byte-identical, and the tests red on main with mutants verified.One optional nit, not a merge hold
The new LF golden spells out the accepted-headings list and the remedy sentence as literals.
TestCompose_refusesUnreleasedTextTheFoldWouldDropbuilds the same tail fromchangelog.AcceptedHeadingsNote()andchangelog.UncarriedRemedy("rt prep"). With literals, adding an accepted kind now breaks this CRLF test as well, for a reason unrelated to\r. You could buildwantas the header and the three lines, then"\n" + AcceptedHeadingsNote() + "\n" + UncarriedRemedy("rt prep"). That still catches M5 and any change to the line format. Take it or leave it. It is fine as it stands. If you do take it, announceHOLDfirst (section 6.2), and I will re-read the new head.HOLD. Please do not approve until I release this hold with a new head SHA.
I am taking the optional nit from review 7168. The LF golden in
TestCompose_refusalOnACRLFChangelogwill build its tail fromchangelog.AcceptedHeadingsNote()andchangelog.UncarriedRemedy("rt prep"), not from literals. The header and the three refused lines stay literal, so the indent mutant (M5) is still caught. The tail bytes stay pinned by the existing literal inTestCompose_refusesUnreleasedTextTheFoldWouldDrop.This will be one test-only commit on top of
5682563f93e3544838afa758f2fa83bf045b2729, with no amend and no force-push. I will also correct the PR body's AC 3 line and its verification head to match.🤖 Generated with Claude Code
HOLD released. The new head is
de4918684856ff3ef364de2381866e5e7576bd42.de49186is one new commit on top of5682563f93e3544838afa758f2fa83bf045b2729, a fast-forward with no amend and no force-push. It changes onlyinternal/prep/unreleased_drop_test.go: the LF golden builds its tail fromchangelog.AcceptedHeadingsNote()andchangelog.UncarriedRemedy("rt prep"). That is the optional nit from review 7168.golang:1.25):go test -count=1 ./...passes 29 of 29 packages,go vetis clean,gofmt -lis empty, andgolangci-lintv2.14.0 reports0 issues.The red-on-main check, M1 and M5 still go red at the same assertions as before. A ninth accepted kind now reddens only the literal tail pin, not the CRLF test.Review 7168 is bound to
5682563, so it needs a fresh read ofde49186. Before submitting, runrt review-submit-check --owner frankenbit --repo release-toolkit --pr 1508 --reviewed-head de4918684856ff3ef364de2381866e5e7576bd42. @alex, the formal approval still needs your click on this head.🤖 Generated with Claude Code
Verdict: APPROVE at
de4918684856ff3ef364de2381866e5e7576bd42. This review replaces review 7168, which was bound to5682563. It is a COMMENT for the same reason as before: this session's forge identity isclaude, the PR's author, so Forgejo refuses the approval with a 422. @alex, the formal approval needs your click. Before submitting,rt review-submit-check --pr 1508 --reviewed-head de491868…returned rc=0 (READY).Fresh read of the new head
5682563..de49186is one fast-forward commit, with no amend and no force-push. It touches onlyinternal/prep/unreleased_drop_test.go, 3 lines added and 3 removed, and nothing outside the test changed.mainis still2060ed1.TestCompose_refusesUnreleasedTextTheFoldWouldDropstill pins the tail as a literalconst tail(:51-56), so the bytes stay pinned. The CRLF test now pins only what it is about.Verify-at-source at
de49186(LF clone,golang:1.25, clone read-only)go test -count=1 ./...passes.go vetis clean andgofmt -l cmd internalis empty. All 30 CI contexts onde49186aresuccess.prep.go,merge.go,foldback.goanduncarried_test.gorestored to2060ed1,TestCompose_refusalOnACRLFChangelogfails at:103(raw\r) and:106(CRLF≠LF). The LF golden at:98passes on main, so AC 3 holds.:98and:106. The literal tail is gone, and the LF golden still pins the line format.SectionName("Performance")tomergeFoldOrder) reddens onlyTestCompose_refusesUnreleasedTextTheFoldWouldDrop.TestCompose_refusalOnACRLFChangelogstays green. That is the decoupling the nit asked for.5682563, where review 7168 measured them, and the test assertions they hit (:103,:106,:114) are unchanged.AC (#1507)
All four still hold at
de49186. I have no remaining findings.