Pin upstream-version state file to LF, carving out the JSON CRLF rule - #171
Pin upstream-version state file to LF, carving out the JSON CRLF rule#171ptr727 wants to merge 1 commit into
Conversation
check-upstream-version-task writes the state file LF on the runner, but .editorconfig defaults .json/.jsonc to CRLF, so in a CRLF-default downstream the committed upstream-version.json contradicts the stated rule and an editor save flips it to CRLF, churning a spurious canonicalize bump on the next run. Carve the default upstream-version.json out as canonical LF: a .gitattributes `text eol=lf` rule normalizes an editor CRLF save back on commit (the blanket `* -text` does not), and an .editorconfig stanza makes editors save LF. Both are verbatim carries, so default-named downstreams get the fix automatically; document the canonical-LF contract and the custom-state-file caveat in the task header and AGENTS.md. Closes #170 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR resolves #170 by eliminating line-ending churn for the bot-maintained upstream-version.json state file produced by check-upstream-version-task.yml. It formalizes upstream-version.json as a canonical LF file (an explicit exception to the repository-wide CRLF rule for *.json) via Git- and editor-level carve-outs, and documents the adoption implications for downstreams that rename the state file.
Changes:
- Add a
.gitattributesoverride to enforce LF forupstream-version.jsonat the Git layer. - Add an
.editorconfigstanza to ensure editors saveupstream-version.jsonas LF (overriding the*.jsonCRLF default). - Document the LF exception and the need to update both carve-outs when using a non-default
state-filename.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
AGENTS.md |
Documents upstream-version.json as canonical LF and calls out the required carve-outs when renaming the state file. |
.github/workflows/check-upstream-version-task.yml |
Adds header documentation explaining why the state file is LF and how the repo enforces that deterministically. |
.gitattributes |
Enforces upstream-version.json as text eol=lf to prevent CRLF commits from editor saves. |
.editorconfig |
Carves out upstream-version.json with end_of_line = lf to align editor behavior with the task output. |
|
Replacing this approach. Option 2 (carving the state file out to LF) defeats the reason JSON defaults to CRLF: a Windows vanilla-editor save produces CRLF, so an LF carve-out would churn on exactly the platform the CRLF rule protects. Reworking as option 1 - the task writes the state file as CRLF, honoring the .editorconfig rule with no special case - in a fresh PR. |
Closes #170. Supersedes #171 (which took the wrong approach — see below). ## Problem `check-upstream-version-task.yml` wrote the state file with `printf '%s\n'` on the runner — **LF** — but `.editorconfig` defaults `.json`/`.jsonc` to **CRLF**. The committed `upstream-version.json` therefore contradicted the repo's own rule: open it in an `.editorconfig`-aware (or vanilla Windows) editor and save, and it flips to CRLF, so the next tracker run rewrites it back to LF — a spurious `Canonicalize upstream version state file` PR. ## Why not carve the file out to LF (the closed #171 / issue option 2) The whole point of defaulting JSON to CRLF is that **Windows vanilla-editor saves don't break things**. Carving the state file out to LF inverts that — a Windows save produces CRLF and now violates the carve-out, churning on exactly the platform the CRLF rule protects. So the fix belongs in the task, not in a special case. ## Fix (issue option 1) Write the state file with **CRLF**, honoring `.editorconfig`'s rule for `.json` with no per-file carve-out. `jq` emits LF, so convert line endings on write: ```sh printf '%s\n' "$new" | sed 's/$/\r/' > "$STATE_FILE" ``` `.editorconfig` and `.gitattributes` are untouched. ## Validation - Output is CRLF (`file` → `JSON text data`, `cat -A` → `^M$` line ends) and **idempotent**: rewriting identical content is byte-for-byte identical, so no churn / no spurious PR. - `jq -S '.'` still round-trips the CRLF file, so the old-state read and union diff are unaffected (comparison is on parsed objects, EOL-agnostic). - A Windows vanilla-editor save now produces CRLF that **matches** the committed file. - YAML parses. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #170.
Follow-up found while adopting the multi-key
check-upstream-version-task.yml(#168 / #169) inptr727/ESPHome-NonRoot.Problem
The task writes the state file with
printf '%s\n'on the Ubuntu runner — LF — but.editorconfig(a verbatim downstream carry) defaults.json/.jsoncto CRLF. In a CRLF-default downstream the committedupstream-version.json(LF, to match the task and avoid churn) contradicts the repo's stated rule: open it in an.editorconfig-aware editor and save, and it flips to CRLF, so the next tracker run rewrites it back to LF — a spuriousCanonicalize upstream version state filePR.Fix (issue option 2 — keep the task's LF output deterministic, carve the file out)
.gitattributes:upstream-version.json text eol=lf— enforces LF at the git layer, so even an editor's CRLF save is normalized back on commit. The blanket* -textdoes not do this (it only disables normalization), so this override is the part that actually kills the churn..editorconfig: an[upstream-version.json] end_of_line = lfstanza (placed right after the JSON CRLF stanza so it overrides, above the .NET-only boundary) so editors save LF in the first place.state-filename must be added to both carve-outs.Both governance files are verbatim carries, so default-named downstreams get the fix automatically with no extra adoption step.
Validation
git check-attr text eol -- upstream-version.json→text: set,eol: lf; an unrelatedversion.jsonstaystext: unset(blanket rule untouched).markdownlint-cli2clean on AGENTS.md.🤖 Generated with Claude Code