No .gitattributes: every Windows clone gets CRLF in scripts/gate.sh and the gate dies with bash\r (exit 127) #19

Closed
opened 2026-08-26 04:00:02 +02:00 by pbwraith · 1 comment
Owner

scripts/gate.sh breaks in any Windows clone, because this repo has no .gitattributes

Small, cheap, and worth fixing now that scripts/gate.sh is the repo's keystone - eight open PRs are gated by it, so anything that makes it fail confusingly costs more here than it would elsewhere.

The measurement

$ git ls-tree origin/main --name-only | grep gitattributes
(nothing)

No .gitattributes on main, and none on issue-16 either. The repo ships three shell scripts:

data/icon/generate.sh
data/shader/output.sh
scripts/gate.sh

The committed blobs are clean - git cat-file -p origin/issue-16:scripts/gate.sh | tr -cd '\r' | wc -c is 0. The problem is not what is stored, it is what gets written on checkout.

Why it bites

Git for Windows defaults to core.autocrlf=true. With no .gitattributes to override it, cloning this repo on Windows writes CRLF into every .sh working copy, and the script then dies at exec:

$ ./scripts/gate.sh
/usr/bin/env: 'bash\r': No such file or directory
EXIT=127

This already happened during review of !17: the first gate run failed exactly this way, and it took a round of investigation to establish it was a clone artifact rather than a defect in the PR. env: 'bash\r' does not name line endings, and the natural reading is "the script is broken" - which is the wrong conclusion about a script whose blob is byte-clean.

The workaround is git config core.autocrlf false plus a fresh checkout, but that is per-clone tribal knowledge. The repo should assert its own requirement.

What to do

Add .gitattributes at the repo root:

*.sh text eol=lf

Consider * text=auto alongside it if you want the general rule, but the .sh line is the one that fixes the observed failure.

The part that is easy to get wrong

.gitattributes alone does not fix an already-checked-out working copy. Git does not rewrite a file on branch switch when its blob is unchanged, so a clone that already has CRLF in scripts/gate.sh will still have CRLF after merging this change - on a branch whose blobs are perfectly clean. Existing clones need a forced re-checkout:

rm -f .git/index && git reset --hard HEAD

If any committed blob did contain CR (none do here, so this should be a no-op), fixing it needs git add --renormalize . as a separate commit - the attributes file does not retroactively clean stored objects.

Acceptance criteria

  1. .gitattributes exists on main and contains *.sh text eol=lf.
  2. On a fresh Windows clone made with core.autocrlf=true (the default - set it explicitly for the test rather than relying on the box's config), tr -cd '\r' < scripts/gate.sh | wc -c returns 0. State the command and the number.
  3. head -c 24 scripts/gate.sh | od -c shows a clean #!/usr/bin/env bash shebang with no \r.
  4. All three .sh files are covered, not just scripts/gate.sh.

Measurement warning

Do not verify this with grep -c $'\r'. It is broken in this environment's Git Bash and returns the line count, not the CR count - it has produced false findings on three separate occasions here, and the tell is that the number always equals wc -l. Use tr -cd '\r' < file | wc -c, and treat actually running ./scripts/gate.sh --help (or any invocation that reaches the interpreter) as the deciding test.

Out of scope

  • Any change to scripts/gate.sh's contents or to the build configuration. This is line endings only.
  • The disk-space blocker currently preventing the gate from completing (gitea-worker-ops#24). This issue is verifiable by inspection and a fresh clone; it does not need a working build.
## `scripts/gate.sh` breaks in any Windows clone, because this repo has no `.gitattributes` Small, cheap, and worth fixing now that `scripts/gate.sh` is the repo's keystone - eight open PRs are gated by it, so anything that makes it fail confusingly costs more here than it would elsewhere. ## The measurement ``` $ git ls-tree origin/main --name-only | grep gitattributes (nothing) ``` No `.gitattributes` on `main`, and none on `issue-16` either. The repo ships three shell scripts: ``` data/icon/generate.sh data/shader/output.sh scripts/gate.sh ``` The committed blobs are clean - `git cat-file -p origin/issue-16:scripts/gate.sh | tr -cd '\r' | wc -c` is **0**. The problem is not what is stored, it is what gets written on checkout. ## Why it bites Git for Windows defaults to `core.autocrlf=true`. With no `.gitattributes` to override it, cloning this repo on Windows writes CRLF into every `.sh` working copy, and the script then dies at exec: ``` $ ./scripts/gate.sh /usr/bin/env: 'bash\r': No such file or directory EXIT=127 ``` This already happened during review of `!17`: the first gate run failed exactly this way, and it took a round of investigation to establish it was a clone artifact rather than a defect in the PR. `env: 'bash\r'` does not name line endings, and the natural reading is "the script is broken" - which is the wrong conclusion about a script whose blob is byte-clean. The workaround is `git config core.autocrlf false` plus a fresh checkout, but that is per-clone tribal knowledge. The repo should assert its own requirement. ## What to do Add `.gitattributes` at the repo root: ``` *.sh text eol=lf ``` Consider `* text=auto` alongside it if you want the general rule, but the `.sh` line is the one that fixes the observed failure. ## The part that is easy to get wrong **`.gitattributes` alone does not fix an already-checked-out working copy.** Git does not rewrite a file on branch switch when its blob is unchanged, so a clone that already has CRLF in `scripts/gate.sh` will still have CRLF after merging this change - on a branch whose blobs are perfectly clean. Existing clones need a forced re-checkout: ``` rm -f .git/index && git reset --hard HEAD ``` If any committed blob *did* contain CR (none do here, so this should be a no-op), fixing it needs `git add --renormalize .` as a separate commit - the attributes file does not retroactively clean stored objects. ## Acceptance criteria 1. `.gitattributes` exists on `main` and contains `*.sh text eol=lf`. 2. On a **fresh Windows clone made with `core.autocrlf=true`** (the default - set it explicitly for the test rather than relying on the box's config), `tr -cd '\r' < scripts/gate.sh | wc -c` returns **0**. State the command and the number. 3. `head -c 24 scripts/gate.sh | od -c` shows a clean `#!/usr/bin/env bash` shebang with no `\r`. 4. All three `.sh` files are covered, not just `scripts/gate.sh`. ## Measurement warning Do **not** verify this with `grep -c $'\r'`. It is broken in this environment's Git Bash and returns the *line count*, not the CR count - it has produced false findings on three separate occasions here, and the tell is that the number always equals `wc -l`. Use `tr -cd '\r' < file | wc -c`, and treat actually running `./scripts/gate.sh --help` (or any invocation that reaches the interpreter) as the deciding test. ## Out of scope - Any change to `scripts/gate.sh`'s contents or to the build configuration. This is line endings only. - The disk-space blocker currently preventing the gate from completing (gitea-worker-ops#24). This issue is verifiable by inspection and a fresh clone; it does not need a working build.
pbwraith added the
p2
label 2026-08-26 04:00:02 +02:00
pbwraith added the
in-progress
label 2026-08-26 04:30:18 +02:00
pbwraith added
in-review
and removed
in-progress
labels 2026-08-26 04:46:58 +02:00
Author
Owner

[worker] lane3 implemented — PR #21 (branch issue-19, base main).

Commit: cfa4694 "Add .gitattributes: *.sh text eol=lf for Windows clones" (rebased onto current main 8a46ac5; diff = new .gitattributes only, git diff --check clean).

Verification (fresh clones with core.autocrlf=true set explicitly):

  • BEFORE (main, no .gitattributes): tr -cd '\r' < scripts/gate.sh | wc -c = 81; generate.sh = 6, output.sh = 5; shebang had bash\r and ./scripts/gate.sh failed with /usr/bin/env: 'bash\r': No such file or directory, EXIT=127 (reproduced).
  • AFTER (issue-19): tr -cd '\r' < scripts/gate.sh | wc -c = 0 (and 0 for the other two .sh files); head -c 24 scripts/gate.sh | od -c shows a clean #!/usr/bin/env bash\n shebang with no \r; bash -n parse check passes for all three scripts.
  • Committed blobs were already byte-clean (0 CR each), so no renormalization commit was needed.

Full cargo gate not run: blocked by the known disk-space issue (gitea-worker-ops#24); per the issue this change is verifiable by inspection and a fresh clone.

[worker] lane3 implemented — PR #21 (branch `issue-19`, base `main`). Commit: `cfa4694` "Add .gitattributes: *.sh text eol=lf for Windows clones" (rebased onto current main `8a46ac5`; diff = new `.gitattributes` only, `git diff --check` clean). Verification (fresh clones with `core.autocrlf=true` set explicitly): - BEFORE (main, no `.gitattributes`): `tr -cd '\r' < scripts/gate.sh | wc -c` = **81**; generate.sh = **6**, output.sh = **5**; shebang had `bash\r` and `./scripts/gate.sh` failed with `/usr/bin/env: 'bash\r': No such file or directory`, EXIT=127 (reproduced). - AFTER (issue-19): `tr -cd '\r' < scripts/gate.sh | wc -c` = **0** (and 0 for the other two .sh files); `head -c 24 scripts/gate.sh | od -c` shows a clean `#!/usr/bin/env bash\n` shebang with no `\r`; `bash -n` parse check passes for all three scripts. - Committed blobs were already byte-clean (0 CR each), so no renormalization commit was needed. Full cargo gate not run: blocked by the known disk-space issue (gitea-worker-ops#24); per the issue this change is verifiable by inspection and a fresh clone.
This repo is archived. You cannot comment on issues.
No Milestone
No project
No Assignees
1 Participants
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/gopher64-netplay#19
No description provided.