#!/bin/sh
# version-stamp.sh CHANGELOG CHECKIN [TAG...]
# version-stamp.sh CHANGELOG CHECKIN -
#
# The version quarry is stamped with (RFC 0020 §releases). CHANGELOG is
# the tree's CHANGELOG.md: its newest heading naming a version X.Y.Z is
# the release the tree is at or working toward. CHECKIN is the built
# check-in (12 hex characters or more, or "local" for a tree with no
# checkout); the TAGs are the names it carries.
#
#   - A check-in tagged version-X.Y.Z is that release and prints X.Y.Z.
#   - While version-X.Y.Z is not yet a tag anywhere (the release's
#     changelog has landed, or is being written ahead of time), anything
#     else is on its way there: X.Y.Z-dev+<the check-in's first 12>.
#   - Once version-X.Y.Z is released, anything else is on its way to the
#     next patch: X.Y.(Z+1)-dev+<check-in>. Nobody bumps a file after a
#     release; the next release's changelog heading is the bump, and a
#     minor or major one is just a heading that says so.
#
# With TAGs on the command line the repository's releases are not known,
# and the newest heading is taken as released.
#
# With -, the tags are read from stdin in parts split by a line `--`:
# what `zit tag list --raw CHECKIN` prints, the same for its primary
# parent, what plain `zit tag list CHECKIN` prints, and (optional, the
# newest heading is taken as released without it) what plain
# `zit tag list` prints for the whole repository. A tag counts when the
# check-in carries it itself, the rule the CI trigger's tag event uses
# (index.TagsAdded), so what starts a release is what stamps it: a `+`
# name on it (the plain list, which holds exactly those), or a sym- name
# it has that its parent does not (a propagating `*` it carries, never
# one inherited), and never a branch's own name. zit prints no sign, so
# one case reads differently from the trigger: a check-in carrying its
# own `*sym-NAME` while its parent also has NAME stamps -dev. Nobody
# propagates a version tag down a line that already carries it.
#
# build-linux.sh stamps with this when VERSION is unset, and deploy.sh
# runs the tree's copy, so the rule has one home.
set -eu
[ $# -ge 2 ] || { echo "usage: version-stamp.sh CHANGELOG CHECKIN [TAG...]" >&2; exit 2; }
file=$1 checkin=$2
shift 2
released=unknown
if [ "$*" = - ]; then
  # One tag per word: a fossil tag name holds no space. The repository's
  # release names come back as released:NAME, its own tags bare.
  # shellcheck disable=SC2046
  set -- $(awk '$0 == "--" { part++; next }
    part == 1 { inherited[$1] = 1; next }
    part == 2 { if (NF) single["sym-" $1] = 1; next }
    part == 3 { if (NF) print "released:" $1; next }
    $1 == "branch" { branch["sym-" $2] = 1 }
    $1 ~ /^sym-/ { own[$1] = 1 }
    END {
      if (part >= 3) print "released-known"
      for (t in own) if ((t in single || !(t in inherited)) && !(t in branch)) print substr(t, 5)
    }')
fi
# The newest heading naming a release: "## X.Y.Z" with the version as a
# word of its own (v and version- allowed), as release.sh and the
# Releases page read it. A pre-release heading (0.1.0-beta.1) names none.
[ -f "$file" ] || { echo "version-stamp: no $file" >&2; exit 1; }
v=$(awk '/^## / { n = split(substr($0, 4), w, /[][ \t()`*_,:;]+/)
         for (i = 1; i <= n; i++) {
           x = w[i]; sub(/^(v|version-)/, "", x)
           if (x ~ /^[0-9]+\.[0-9]+\.[0-9]+$/) { print x; exit }
         } }' "$file")
[ -n "$v" ] || { echo "version-stamp: $file has no \"## X.Y.Z\" heading" >&2; exit 1; }
case "$checkin" in
  local) ;;
  *[!0-9a-f]* | "") echo "version-stamp: \"$checkin\" is not a check-in hash" >&2; exit 1 ;;
  *) [ "${#checkin}" -ge 12 ] || { echo "version-stamp: \"$checkin\" is shorter than 12 hex characters" >&2; exit 1; }
     checkin=$(printf '%s' "$checkin" | cut -c1-12) ;;
esac
for t in "$@"; do
  case "$t" in
    released-known) [ "$released" = yes ] || released=no ;;
    "released:version-$v") released=yes ;;
    released:*) ;;
    "version-$v") echo "$v"; exit 0 ;;
  esac
done
if [ "$released" = no ]; then
  echo "$v-dev+$checkin"
else
  # Released, or not known: the next patch.
  echo "${v%.*}.$((${v##*.} + 1))-dev+$checkin"
fi
