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)
This commit is contained in:
63
tools/git-push.cmd
Normal file
63
tools/git-push.cmd
Normal file
@@ -0,0 +1,63 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
rem Push a branch to the internal git server, from Windows.
|
||||
rem
|
||||
rem tools\git-push.cmd push the current branch
|
||||
rem tools\git-push.cmd wj push a named branch
|
||||
rem
|
||||
rem CREDENTIALS ARE NOT THIS SCRIPT'S JOB HERE.
|
||||
rem
|
||||
rem The bash version of this script reads a token out of secrets.md and feeds it
|
||||
rem to git through GIT_ASKPASS, because that box has no credential store. Windows
|
||||
rem does: Git for Windows installs Git Credential Manager and enables it by
|
||||
rem default, so `git push` prompts once and Windows Credential Manager keeps it
|
||||
rem after that.
|
||||
rem
|
||||
rem So do NOT port the token handling here. Copying a token into a script, an
|
||||
rem environment variable or a remote URL on a machine that already has a
|
||||
rem credential store is strictly worse than the thing it replaces.
|
||||
rem
|
||||
rem If a push here asks for a password every time, the fix is to check the
|
||||
rem credential helper, not to embed a token:
|
||||
rem git config --global credential.helper manager
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
set "BRANCH=%~1"
|
||||
if "%BRANCH%"=="" (
|
||||
for /f "delims=" %%b in ('git rev-parse --abbrev-ref HEAD') do set "BRANCH=%%b"
|
||||
)
|
||||
|
||||
if "%BRANCH%"=="HEAD" (
|
||||
echo ERROR: detached HEAD. Check out a branch, or name one.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
git show-ref --verify --quiet "refs/heads/%BRANCH%"
|
||||
if errorlevel 1 (
|
||||
echo ERROR: no such local branch: %BRANCH%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
rem A push of a dirty tree pushes the LAST commit, not what is in front of you.
|
||||
rem That reads as a successful push and is found out much later.
|
||||
for /f "delims=" %%s in ('git status --porcelain') do (
|
||||
echo Working tree is not clean:
|
||||
git status --short
|
||||
echo.
|
||||
echo Commit or stash first - a push of a dirty tree pushes the LAST commit,
|
||||
echo not what you are looking at.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Pushing %BRANCH% ...
|
||||
git push origin "%BRANCH%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo Push failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Pushed %BRANCH%.
|
||||
endlocal
|
||||
154
tools/promote-to-main.cmd
Normal file
154
tools/promote-to-main.cmd
Normal file
@@ -0,0 +1,154 @@
|
||||
@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
|
||||
Reference in New Issue
Block a user