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
149 lines
4.5 KiB
Bash
Executable File
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
|