@@ -65,7 +65,44 @@ echo "→ fetching origin/main"
6565git fetch origin main
6666
6767# The single authoritative list, read at run time.
68- mapfile -t regen_paths < <( grep ' merge=os-regen' .gitattributes | awk ' {print $1}' )
68+ #
69+ # READ LOOP, NOT `mapfile` — THE BASH 3.2 FLOOR. `mapfile`/`readarray` are bash
70+ # 4 builtins and `/usr/bin/env bash` is bash 3.2.57 on macOS (Apple ships no
71+ # bash 4+, for licensing reasons). This script is run BY HAND, in an agent's or
72+ # a maintainer's worktree, and it has no CI path at all — nothing in
73+ # `.github/workflows/` invokes it — so a bash-4 builtin here does not fail on a
74+ # fringe host, it fails on the ordinary one, and no CI run can ever say so.
75+ # Measured with the builtin disabled (`enable -n mapfile readarray` via
76+ # `BASH_ENV`, which reproduces the macOS symptom byte for byte):
77+ # `mapfile: command not found`, then `set -e` kills the run at status 127 —
78+ # AFTER `git fetch origin main` and BEFORE step 1, i.e. with the merge sequence
79+ # whose ORDER is this script's entire reason for existing not begun. The
80+ # operator is left to perform steps 1–3 by hand, which is the trap the script
81+ # was written to remove.
82+ #
83+ # Keep this loop bash-3.2-clean: no `mapfile`, no `readarray`, no `declare -A`,
84+ # no `${x^^}`/`${x,,}`. Two details are load-bearing and neither is obvious:
85+ #
86+ # `regen_paths=()` BEFORE the loop. Under `set -u` a loop that appends
87+ # nothing never creates the array at all, so `${#regen_paths[@]}` below
88+ # aborts with `unbound variable` — and "grep matched nothing" is precisely
89+ # the case the refusal below exists to REPORT. Measured: without the
90+ # declaration the empty input dies at `regen_paths: unbound variable`; with
91+ # it, `count=0` and the refusal prints.
92+ #
93+ # `if [[ -n … ]]; then …; fi`, not a trailing `[[ -n … ]] && …`. Measured:
94+ # with the `&&` form the `while` takes status 1 whenever the LAST line read
95+ # fails the test, and under `set -e` that kills the caller the moment such a
96+ # loop is the last command of a function — silently correct today, a landmine
97+ # for the next refactor. The `if` form returns 0 on the same input. (This is
98+ # the form #12142 standardised; its own note attributes the trap to the empty
99+ # list, which measures clean — an all-empty read leaves the body unexecuted
100+ # and the `while` at status 0.)
101+ regen_paths=()
102+ regen_line=' '
103+ while IFS= read -r regen_line; do
104+ if [[ -n " $regen_line " ]]; then regen_paths+=(" $regen_line " ); fi
105+ done < <( grep ' merge=os-regen' .gitattributes | awk ' {print $1}' )
69106if [ " ${# regen_paths[@]} " -eq 0 ]; then
70107 echo " ✗ no merge=os-regen entries found in .gitattributes — refusing to guess" >&2
71108 exit 1
0 commit comments