Files
shopdb-flask/tools/promote-to-main.cmd
cproudlock 28aee355f9 Add Windows versions of the push and promote scripts
Same two operations as the bash pair, for driving git from Windows rather than
from the development box.

CREDENTIALS ARE HANDLED DIFFERENTLY, ON PURPOSE

The bash version reads a token out of secrets.md and feeds it to git through
GIT_ASKPASS, because that box has no credential store. Windows has one: Git for
Windows installs Git Credential Manager and enables it by default, so `git push`
prompts once and Windows Credential Manager keeps it.

So the token handling is deliberately NOT ported. Copying a token into a script,
an environment variable or a remote URL on a machine that already has a
credential store is strictly worse than the thing it would replace, and the
comment in the file says so, because "make it match the other script" is exactly
the tidying somebody would otherwise do later.

THE PROMOTE SCRIPT REFUSES TO RUN WITHOUT BASH

scripts/check-naming-and-style.sh is a shell script, and it carries the ADR-015
site-literal check - the one gate that makes a site branch survivable rather
than a slow leak into the product. Skipping it on a machine without bash would
defeat the entire point of the script, so it stops and says how to fix it
instead of promoting without it.

CRLF

.gitattributes already pinned *.sh to LF so Git Bash can run them; *.cmd is now
pinned to CRLF for the same class of reason in the other direction. cmd.exe
parses a lone LF inconsistently around labels and goto, so an LF-only batch file
can run its happy path and then jump nowhere on the error path - which in a
promote script is the path that matters.

(cherry picked from commit 49a898fc5a)
2026-08-30 11:12:09 -04:00

155 lines
4.4 KiB
Batchfile

@echo off
setlocal enabledelayedexpansion
rem Take specific commits from wj to main, from Windows.
rem
rem tools\promote-to-main.cmd <commit> [<commit> ...]
rem tools\promote-to-main.cmd --dry-run <commit> ...
rem
rem Commits are applied in the order given, so give them oldest first.
rem
rem WHY CHERRY-PICK AND NOT A MERGE
rem
rem wj holds this site's work. Some of it is product - the rest of the world
rem should have it - and some belongs to this site alone. Merging the branch
rem takes both; that is how a site branch quietly becomes the product. Naming
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.."
set "DRYRUN=0"
if "%~1"=="--dry-run" (
set "DRYRUN=1"
shift
)
if "%~1"=="" (
echo usage: tools\promote-to-main.cmd [--dry-run] ^<commit^> [^<commit^> ...]
echo.
echo Give commits oldest first. Find them with:
echo git log --oneline main..wj
exit /b 1
)
rem The ADR-015 site-literal check is a shell script, and it is the one gate
rem that makes a site branch survivable: it catches a hostname or share path
rem riding along in a commit that looked generic. Running the promotion without
rem it would defeat the point, so this refuses rather than skipping it quietly.
where bash >nul 2>&1
if errorlevel 1 (
echo ERROR: bash was not found on PATH.
echo.
echo The naming/style gate ^(scripts\check-naming-and-style.sh^) needs it, and
echo that gate is what stops a site-specific literal reaching main. Promoting
echo without it is not safe.
echo.
echo Git for Windows ships bash. Add its usr\bin to PATH, or run this from
echo Git Bash using tools/promote-to-main.sh instead.
exit /b 1
)
for /f "delims=" %%b in ('git rev-parse --abbrev-ref HEAD') do set "STARTING_BRANCH=%%b"
for /f "delims=" %%s in ('git status --porcelain') do (
echo Working tree is not clean:
git status --short
echo.
echo Cherry-picking onto main from here would mix uncommitted work into the
echo promotion, or fail halfway and leave you on main with a dirty tree.
exit /b 1
)
rem Resolve and describe first, so a typo is caught before anything moves.
echo About to promote onto main, in this order:
for %%r in (%*) do (
git rev-parse --verify --quiet "%%r^^{commit}" >nul
if errorlevel 1 (
echo ERROR: not a commit: %%r
exit /b 1
)
for /f "delims=" %%l in ('git log --oneline -1 %%r') do echo %%l
)
echo.
if "%DRYRUN%"=="1" (
echo --dry-run: files each commit touches, so you can see what reaches main.
for %%r in (%*) do (
echo.
for /f "delims=" %%l in ('git log --oneline -1 %%r') do echo --- %%l ---
git show --stat --oneline %%r
)
echo.
echo Nothing was changed.
exit /b 0
)
git checkout main
if errorlevel 1 goto :restore
for /f "delims=" %%h in ('git rev-parse HEAD') do set "MAIN_BEFORE=%%h"
for %%r in (%*) do (
echo.
for /f "delims=" %%l in ('git log --oneline -1 %%r') do echo ==^> picking %%l
git cherry-pick -x %%r
if errorlevel 1 (
echo.
echo That commit does not apply cleanly to main.
echo Resolve it by hand if it is worth having:
echo git status
echo git cherry-pick --abort
goto :restore
)
)
echo.
echo ==^> main gates
echo --- naming, style, site literals ^(ADR-015^) ---
bash scripts/check-naming-and-style.sh
if errorlevel 1 goto :restore
echo --- generated project map ---
venv\Scripts\python scripts\gen_project_map.py --check >nul 2>&1
if errorlevel 1 (
echo Project map is stale; regenerating.
venv\Scripts\python scripts\gen_project_map.py
git add docs/PROJECT-MAP.md
git commit -q -m "Regenerate the project map after promotion"
)
echo --- python suite ---
venv\Scripts\python -m pytest -q
if errorlevel 1 goto :restore
echo --- frontend ---
pushd frontend
call npm test --silent
set "FRONTEND_STATUS=!errorlevel!"
popd
if not "!FRONTEND_STATUS!"=="0" goto :restore
echo.
echo main is now:
git log --oneline %MAIN_BEFORE%..HEAD
echo.
echo Gates passed. NOT pushed - review the above, then:
echo tools\git-push.cmd main
echo.
echo You are on main. Return to your branch with:
echo git checkout %STARTING_BRANCH%
exit /b 0
:restore
echo.
echo Promotion did NOT complete.
git rev-parse --verify --quiet CHERRY_PICK_HEAD >nul 2>&1
if not errorlevel 1 (
echo Aborting the in-progress cherry-pick ...
git cherry-pick --abort
)
git checkout "%STARTING_BRANCH%" >nul 2>&1
echo Back on %STARTING_BRANCH%. main is unchanged and nothing was pushed.
exit /b 1