diff --git a/tools/promote-to-main.cmd b/tools/promote-to-main.cmd index b141020d..7f96abfd 100644 --- a/tools/promote-to-main.cmd +++ b/tools/promote-to-main.cmd @@ -17,7 +17,30 @@ rem the commits keeps the decision explicit and per change. rem rem It does NOT push. Read what it prints, then run tools\git-push.cmd main. -cd /d "%~dp0.." +rem RUN FROM A COPY OF OURSELF. +rem +rem This checks out main. The tooling may live only on the site branch, in which +rem case that checkout DELETES this file while cmd.exe is still reading it - and +rem cmd.exe reads a batch file line by line, by file offset, re-seeking after +rem every command. It would carry on reading whatever bytes are at that offset +rem afterwards, while manipulating branches. The bash version has the same +rem hazard and the same fix. +if not "%PROMOTE_SELFCOPY%"=="1" ( + rem The copy runs from %TEMP%, where %~dp0 is the temp directory and not the + rem checkout, so the repository root has to travel with it. + set "PROMOTE_REPO=%~dp0.." + set "PROMOTE_SELFCOPY=1" + copy /y "%~f0" "%TEMP%\promote-to-main-copy.cmd" >nul + call "%TEMP%\promote-to-main-copy.cmd" %* + rem Delayed expansion: a plain %errorlevel% here is expanded when the block is + rem parsed, which is BEFORE the call above has run, so it would always report + rem the code from whatever ran previously. + set "EXITCODE=!errorlevel!" + del /q "%TEMP%\promote-to-main-copy.cmd" >nul 2>&1 + exit /b !EXITCODE! +) + +cd /d "%PROMOTE_REPO%" set "DRYRUN=0" if "%~1"=="--dry-run" ( diff --git a/tools/promote-to-main.sh b/tools/promote-to-main.sh index aef79a66..885ed73c 100755 --- a/tools/promote-to-main.sh +++ b/tools/promote-to-main.sh @@ -26,6 +26,27 @@ # main yourself. set -euo pipefail +# RUN FROM A COPY OF OURSELF. +# +# This script checks out main. The tooling may live only on the site branch, in +# which case that checkout DELETES this file while bash is still reading it - +# and bash reads a script lazily, by offset, so it would carry on executing +# whatever bytes happen to be at that position afterwards. While manipulating +# branches. This is not theoretical: main did not have tools/ when this was +# written, so the very first real use would have hit it. +# +# Copy to a temp file, re-exec from there, and delete the copy immediately. On +# Unix the open file handle keeps working after the unlink, so the running +# script survives whatever git does to the tree. +if [ "${PROMOTE_SELFCOPY:-}" != "1" ]; then + SELF="$(mktemp)" + cat "$0" > "$SELF" + chmod 700 "$SELF" + export PROMOTE_SELFCOPY=1 + exec bash "$SELF" "$@" +fi +rm -f "$0" 2>/dev/null || true + cd "$(git rev-parse --show-toplevel)" DRYRUN=0