Files
shopdb-flask/tools/wj-status.sh
cproudlock faace01f2d
All checks were successful
CI / naming (push) Successful in 4s
CI / frontend (push) Successful in 12s
CI / migrations-mysql (push) Successful in 41s
CI / backend (push) Successful in 4m4s
Do not offer to promote work that already landed
Promotion cherry-picks, which gives the commit a new sha on main. wj-status
compared by sha via main..wj, so after promoting ten commits it still listed all
ten as unpromoted - and would have invited promoting the same work twice.

git cherry compares patch content instead and marks an already-present commit
with a leading '-'. When nothing is genuinely outstanding it now says so, counts
what has already landed, and shows how far the branch is BEHIND main with the
rebase to run. Behind was the state that mattered here and the tool could not
express it at all.

Scope: product
2026-08-30 12:48:56 -04:00

149 lines
4.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# What is on wj that is not on main, and where each commit belongs.
#
# tools/wj-status.sh classify everything unpromoted
# tools/wj-status.sh --product print just the promotable SHAs
#
# The second form is meant to be read by a person and then pasted, oldest
# first:
#
# tools/promote-to-main.sh $(tools/wj-status.sh --product)
#
# HOW A COMMIT IS CLASSIFIED
#
# By a trailer you write at commit time:
#
# Scope: product belongs on main, the rest of the world should have it
# Scope: site belongs to this site alone, keep it on wj
#
# Intent is the only thing that can actually decide this, and you have it when
# you write the commit, not months later when you are staring at a list of
# subject lines trying to remember.
#
# Where there is no trailer this falls back to paths, but the fallback is
# deliberately weak, and says so. Under ADR-015 the only site-specific home in
# the tree is scripts/site_imports/<site>/ - everything else is meant to be
# product with settings and seed data carrying the differences. So "touches
# only site_imports" is the one confident site verdict available, and anything
# else is reported as UNMARKED rather than guessed into a bucket.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
MODE="${1:-report}"
if ! git show-ref --verify --quiet refs/heads/main; then
echo "ERROR: no main branch here." >&2
exit 1
fi
RANGE="main..wj"
if ! git rev-parse --verify --quiet refs/heads/wj >/dev/null; then
echo "ERROR: no wj branch here." >&2
exit 1
fi
# git cherry, not rev-list. Promotion CHERRY-PICKS, which gives the commit a new
# sha on main, so `main..wj` keeps listing work that already landed - and this
# tool would then invite you to promote it a second time. `git cherry` compares
# patch content and marks an already-present commit with a leading '-'.
COMMITS="$(git cherry main wj | awk '$1 == "+" { print $2 }')"
ALREADY="$(git cherry main wj | awk '$1 == "-" { print $2 }' | wc -l)"
if [ -z "$COMMITS" ]; then
echo "Nothing on wj that main does not have."
if [ "$ALREADY" -gt 0 ]; then
echo "($ALREADY commit(s) here have already landed on main as cherry-picks.)"
fi
echo
echo "wj is behind main by:"
git log --oneline wj..main | sed 's/^/ /' | head -10
echo
echo "Rebase it before starting new work:"
echo " git checkout wj && git rebase main"
exit 0
fi
classify() {
local sha="$1"
local scope
scope="$(git log -1 --format='%(trailers:key=Scope,valueonly)' "$sha" | tr -d '[:space:]')"
case "$(printf '%s' "$scope" | tr '[:upper:]' '[:lower:]')" in
product) echo product; return ;;
site) echo site; return ;;
esac
# No trailer. The only confident verdict from paths alone.
local files outside
files="$(git show --pretty=format: --name-only "$sha" | sed '/^$/d')"
outside="$(printf '%s\n' "$files" | grep -v '^scripts/site_imports/' || true)"
if [ -n "$files" ] && [ -z "$outside" ]; then
echo site-by-path
else
echo unmarked
fi
}
if [ "$MODE" = "--product" ]; then
for sha in $COMMITS; do
if [ "$(classify "$sha")" = "product" ]; then
printf '%s ' "$sha"
fi
done
echo
exit 0
fi
product_shas=""
unmarked_count=0
echo "On wj, not on main - oldest first"
echo
for sha in $COMMITS; do
verdict="$(classify "$sha")"
subject="$(git log -1 --format='%h %s' "$sha")"
case "$verdict" in
product)
echo " [PRODUCT ] $subject"
product_shas="$product_shas $sha"
;;
site)
echo " [SITE ] $subject"
;;
site-by-path)
echo " [site? ] $subject"
echo " touches only scripts/site_imports/ - looks site-only, not marked"
;;
*)
echo " [UNMARKED] $subject"
unmarked_count=$((unmarked_count + 1))
;;
esac
done
echo
if [ -n "$product_shas" ]; then
echo "Marked for main:"
echo " tools/promote-to-main.sh$product_shas"
echo
fi
if [ "$unmarked_count" -gt 0 ]; then
echo "$unmarked_count commit(s) carry no Scope trailer."
echo
echo "That is not a failure - it just means nobody decided yet. Under ADR-015"
echo "most work is product: site differences are meant to live in settings and"
echo "seed data, not in a branch. So an unmarked commit is more likely to"
echo "belong on main than not, and leaving it unmarked is how it ends up"
echo "stranded here."
echo
echo "Mark future ones as you go:"
echo " git commit -m \"...\" -m \"Scope: product\""
echo
echo "Or decide now and promote the ones that belong:"
echo " tools/promote-to-main.sh --dry-run <sha>"
fi