Files
orca/docs/reference/line-endings.md
T
Merge Sim 82de6adbbf fix(repo): pin the Windows CLI shim to CRLF, the bytes it already ships
The parent commit pins the whole tree to LF and leaves one question open:
whether resources/win32/bin/orca.cmd should be an exception. Measured against
the shipped v1.4.192 installer, it should -- though for a narrower reason than
the open question assumed. See the finding below.

The release Windows runner pins nothing and sets no core.autocrlf, so it
converts this file on checkout. The committed blob is 644 bytes with 0 CR; the
copy inside orca-windows-setup.exe is 665 bytes with 21 CR, and is sha256-
identical to that blob with every LF doubled. 665 - 644 = 21, the file's line
count. The same installer carries its own control: the seven files under
resources/plugins/ were already pinned eol=lf and shipped unconverted, byte-
identical to their blobs. One artifact proves both that the conversion is live
and that a pin overrides it.

That reverses the framing. Pinning CRLF reproduces today's artifact exactly;
it is leaving the file to the blanket rule that flips a shipped byte. Verified
across every checkout config: with the pin the working tree is 665 B / 21 CR
under core.autocrlf=true, false and input alike; without it, 644 B / 0 CR under
all three, and a real windows-2022 runner confirms `i/lf w/crlf` with the pin
and `i/lf w/lf` without. The index blob is unchanged either way.

FINDING: LF is not broken. check-windows-launcher-line-endings.ps1 ran the shim
both ways on windows-2022 and both encodings passed both arms -- the goto-label
hazard did not reproduce. So this pin is a consistency choice, not a
correctness fix: it reproduces the bytes v1.4.192 shipped and stops them being
decided by a build-image default nobody controls. A uniform-LF decision would
now be defensible; what should survive either way is that something finally
executes this file. Until now nothing did -- smoke-packaged-cli.mjs resolves
the packaged CLI to resources/bin/orca.exe on win32, never the .cmd beside it.

The shim sits outside the parent gate's population (no shebang, mode 100644),
so the two rules cannot contradict each other; a test asserts that.

check-line-ending-policy.mjs now asserts the exception rather than merely
permitting it: the blob must still be LF and eol must resolve to crlf. An
exception nothing checks is how this file's encoding became a runner-image
accident in the first place.

check-windows-launcher-line-endings.ps1 adds the empirical half on the existing
package (windows) job, first step after checkout, one second. It confirms the
runner really wrote CRLF, then runs the shim's guard path and its fall-through
under a real cmd.exe, using a copy of cmd.exe as the orca.exe the shim requires
-- without one the shim exits 1 before reaching either arm. It reports the LF
verdict without ever failing the job on it. Both checks were watched failing
with the pin removed before this landed.
2026-08-29 19:40:04 -07:00

5.6 KiB
Raw Blame History

Why every file in this repo is LF

The short version

.gitattributes starts with one line:

* text=auto eol=lf

That means Git stores every text file with LF and writes it to disk with LF, on macOS, Linux and Windows alike. One file is deliberately checked out with CRLF — the Windows CLI shim, below. Everything else is LF.

What this fixes

Git for Windows ships with core.autocrlf=true. On a stock Windows clone Git used to rewrite the working tree on the way out, and Orca ships executable shell scripts straight out of this repo — so that rewrite reached users, not just contributors:

File Ships as Before (Windows clone) After
resources/linux/bin/orca-ide the Linux orca-ide command env: bash\r: No such file or directory, exit 127 — the script never runs runs
resources/darwin/bin/orca the macOS orca command same runs
resources/linux/packaging/after-install.sh deb/rpm post-install hook same runs
.husky/pre-commit the commit hook same runs

The contributor-facing half was the visible symptom (pnpm lint could not pass on a fresh Windows clone, because generated-artifact checks compare bytes and the checkout had \r\n where the generator emits \n), but the shipped scripts were the real cost.

Why it did not rewrite anything

text=auto leaves the binary/text decision to Git, and the repository was already LF-only — every blob in the index, all 20,050 of them, was unchanged by git add --renormalize .. The policy pins existing behaviour; it does not convert files. That is what makes it safe to apply in one commit.

If you already have a Windows clone

Changing .gitattributes does not rewrite files already on disk — git pull leaves a CRLF working tree exactly as it was, because the blobs did not change. Repair it once:

git rm --cached -r .
git reset --hard

A fresh clone needs nothing.

The one CRLF exception

resources/win32/bin/orca.cmd is pinned the other way:

/resources/win32/bin/orca.cmd text eol=crlf

This is not a hygiene lapse. It is the byte that already ships. The release Windows runner pins nothing and sets no core.autocrlf, so it converted this file on checkout long before the blanket rule existed:

committed blob inside v1.4.192's orca-windows-setup.exe
size 644 B 665 B
CR 0 21

665 − 644 = 21, exactly the file's line count — the same bytes with every \n doubled. The same installer carries its own control: the seven files under resources/plugins/ were already pinned to LF and shipped unconverted, byte-identical to their blobs. One artifact, both directions.

So the pin reproduces today's shipped launcher rather than changing it, and makes it deterministic instead of dependent on a runner-image default nobody controls. Leaving the file to the blanket rule is what would flip a shipped byte — on a batch file that uses goto with labels, into an encoding no smoke test executes (config/scripts/smoke-packaged-cli.mjs resolves the packaged CLI to resources/bin/orca.exe on win32, never the .cmd beside it).

Whether LF actually breaks cmd.exe here is measured, not assumed — config/scripts/check-windows-launcher-line-endings.ps1 runs the shim both ways on windows-2022 and reports which. The pin stands either way, because reproducing the shipped bytes is the point.

The pin changes the working tree only. The stored blob stays LF, so git add --renormalize still stages nothing:

$ git ls-files --eol -- resources/win32/bin/orca.cmd
i/lf    w/crlf  attr/text eol=crlf      resources/win32/bin/orca.cmd

The three exemptions

  • config/patches/*.patch are -text: pnpm hashes each patch byte-for-byte, so any normalization breaks pnpm install.
  • config/patches/@xterm__xterm@*.patch are -diff: the bundle hunks are unreadable in a diff. Review config/patches/xterm-src/ instead.
  • src/main/__fixtures__/shell-wrapper-snapshots/*.txt are linguist-generated, so they collapse in a PR diff. They are still diffable — the shell diff is the review surface.

What keeps it true

pnpm lint runs config/scripts/check-line-ending-policy.mjs. For every tracked file that the OS has to execute — anything starting with #!, plus anything carrying the executable bit, 132 files today — it asserts both halves independently:

  1. the committed blob contains no CRLF (what ships, on every platform), and
  2. git check-attr eol resolves to lf (what a core.autocrlf=true clone writes).

Neither implies the other: a clean blob still breaks Windows under a stray eol=crlf, and a correct attribute still ships CRLF if the blob itself carries it.

The same script asserts the CRLF exception with the mirrored rules — the blob must still be LF, and eol must resolve to crlf — so the exception cannot be silently reclaimed by the blanket rule or quietly widened. On Windows, check-windows-launcher-line-endings.ps1 adds the empirical half: it reads git ls-files --eol to confirm the runner really wrote CRLF, then executes the shim's guard path and its fall-through under a real cmd.exe.

The population is derived from file content, not hand-listed. A curated list would have had to name 122 files on the day this landed and would silently miss the 123rd — which is exactly how the broken launcher shipped. Equally, the gate is not a repo-wide "no CRLF anywhere" rule: that would over-fire on the byte-pinned pnpm patches. There is no exemption list, because CRLF is never correct for a file the OS has to exec.