Files
shopdb-flask/tools/wj-status.sh
cproudlock e838aaf737 Say which wj commits belong on main
The scripts could move a commit to main but nothing said which commits should
go, and that decision does not survive being deferred: months later it is a list
of subject lines and a guess.

So it is recorded at commit time, as a trailer:

    Scope: product     the rest of the world should have it
    Scope: site        this site alone, keep it here

tools/wj-status.sh reads them and prints main..wj in three groups, with the
promote command for the ones marked product.

The path fallback is deliberately weak. 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 what differs between sites. So
"touches only site_imports" is the one confident site verdict paths can give,
and everything else is reported UNMARKED rather than guessed into a bucket. A
tool that quietly sorted commits by directory would be wrong often enough to
stop being read.

The unmarked message says which way to lean, because the failure mode here is
not promoting something site-specific by accident - the ADR-015 gate catches
that on the way in. It is product work sitting on wj forever because nobody
decided, which is how the wilm branch became a port rather than a merge.

Scope: product
(cherry picked from commit 0400a58786)
2026-08-30 11:12:09 -04:00

134 lines
3.8 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
COMMITS="$(git rev-list --reverse "$RANGE")"
if [ -z "$COMMITS" ]; then
echo "Nothing on wj that main does not have."
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