166 lines
6.6 KiB
Bash
Executable File
166 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Sync the release version (./VERSION) DOWN the channel chain so no less-stable
|
|
# branch ever trails a more-stable one.
|
|
#
|
|
# scripts/sync-version.sh # dry run: print what would change
|
|
# scripts/sync-version.sh --apply # write a VERSION-bump commit on each branch that is behind
|
|
#
|
|
# Channels flow feature -> dev -> canary -> staging -> rc -> main (least -> most stable;
|
|
# see BUILD.md). Each branch feeds one channel and is released independently, so a more-stable
|
|
# branch can ship a newer version than a less-stable one that hasn't been released in a while.
|
|
# That is the case this fixes: a branch's marketing version must never be *behind* a more-stable
|
|
# branch above it.
|
|
#
|
|
# staging released 0.3.1 while dev sat at 0.2.1 -> dev is fast-forwarded to 0.3.1
|
|
# (build number raised too, so
|
|
# CFBundleVersion stays monotonic)
|
|
# dev already ahead at 0.3.2 vs staging 0.3.1 -> dev is left untouched (never downgraded)
|
|
#
|
|
# Each branch is fast-forwarded to the highest marketing version among every branch listed
|
|
# before it (more stable) in the chain. Only ./VERSION is rewritten, one commit per branch:
|
|
# a non-checked-out branch is updated in place via git plumbing (no working-tree churn); the
|
|
# currently checked-out branch, if it needs a bump, is committed normally (needs a clean tree).
|
|
# Nothing is pushed — push the updated branches with your usual pushes.
|
|
#
|
|
# Branch order (most -> least stable) can be overridden with NUCLEIC_CHANNEL_BRANCHES.
|
|
# Missing branches, and branches without a readable ./VERSION, are skipped.
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
APPLY=0
|
|
case "${1:-}" in
|
|
""|-n|--dry-run) APPLY=0 ;;
|
|
--apply) APPLY=1 ;;
|
|
-h|--help)
|
|
echo "usage: $0 [--dry-run|--apply]"
|
|
echo " (no args / --dry-run) show which branches would be fast-forwarded"
|
|
echo " --apply write the VERSION-bump commits"
|
|
exit 0 ;;
|
|
*) echo "usage: $0 [--dry-run|--apply]" >&2; exit 2 ;;
|
|
esac
|
|
|
|
# Most-stable -> least-stable. Each branch is fast-forwarded up to the max marketing version
|
|
# of every branch before it in this list.
|
|
# shellcheck disable=SC2206
|
|
BRANCHES=(${NUCLEIC_CHANNEL_BRANCHES:-main rc staging canary dev})
|
|
|
|
CUR="$(git symbolic-ref --quiet --short HEAD || true)"
|
|
|
|
branch_exists() { git show-ref --verify --quiet "refs/heads/$1"; }
|
|
read_field() { git show "refs/heads/$1:VERSION" 2>/dev/null | sed -n "s/^$2=[[:space:]]*//p" | tail -n1 || true; }
|
|
|
|
# Compare two major.minor.patch strings; echo 1 (a>b), 0 (a==b) or -1 (a<b).
|
|
ver_cmp() {
|
|
local IFS=.
|
|
# shellcheck disable=SC2206
|
|
local -a A=($1) B=($2)
|
|
local i x y
|
|
for i in 0 1 2; do
|
|
x=${A[i]:-0}; y=${B[i]:-0}
|
|
if ((10#$x > 10#$y)); then echo 1; return; fi
|
|
if ((10#$x < 10#$y)); then echo -1; return; fi
|
|
done
|
|
echo 0
|
|
}
|
|
|
|
# Build the plan: for each branch, if its marketing version trails the running-max ("floor")
|
|
# of the more-stable branches above it, record a fast-forward. Never lowers a branch that is
|
|
# already ahead, and never touches the floor-setting (more-stable) branches themselves.
|
|
PLAN="" # rows: branch|oldmkt|oldbuild|newmkt|newbuild|src
|
|
CUR_IN_PLAN=0
|
|
FMKT=""; FBUILD=""; FSRC="" # floor: highest marketing version + build seen so far, and its source branch
|
|
|
|
for b in "${BRANCHES[@]}"; do
|
|
if ! branch_exists "$b"; then
|
|
echo " . $b: no local branch, skipping" >&2
|
|
continue
|
|
fi
|
|
mkt="$(read_field "$b" NUCLEIC_MARKETING_VERSION)"
|
|
build="$(read_field "$b" NUCLEIC_BUILD_NUMBER)"
|
|
if [ -z "$mkt" ] || [ -z "$build" ]; then
|
|
echo " . $b: no readable VERSION, skipping" >&2
|
|
continue
|
|
fi
|
|
|
|
if [ -z "$FMKT" ]; then # seed the floor from the most-stable branch that has a VERSION
|
|
FMKT="$mkt"; FBUILD="$build"; FSRC="$b"
|
|
continue
|
|
fi
|
|
|
|
if [ "$(ver_cmp "$mkt" "$FMKT")" = "-1" ]; then # behind the floor -> fast-forward up to it
|
|
newmkt="$FMKT"
|
|
if [ "$build" -gt "$FBUILD" ]; then newbuild="$build"; else newbuild="$FBUILD"; fi
|
|
PLAN="${PLAN}${b}|${mkt}|${build}|${newmkt}|${newbuild}|${FSRC}
|
|
"
|
|
if [ "$b" = "$CUR" ]; then CUR_IN_PLAN=1; fi
|
|
fi
|
|
|
|
# Advance the floor to the running max (a branch that is ahead becomes the new floor for those below it).
|
|
if [ "$(ver_cmp "$mkt" "$FMKT")" = "1" ]; then FMKT="$mkt"; FSRC="$b"; fi
|
|
if [ "$build" -gt "$FBUILD" ]; then FBUILD="$build"; fi
|
|
done
|
|
|
|
if [ -z "$PLAN" ]; then
|
|
echo "OK All channel branches are in version sync — nothing to do."
|
|
exit 0
|
|
fi
|
|
|
|
printf '%-9s %-18s %-18s %s\n' "branch" "current" "-> new" "(catch up to)"
|
|
while IFS='|' read -r b omkt obuild nmkt nbuild src; do
|
|
[ -n "$b" ] || continue
|
|
printf '%-9s %-18s -> %-18s %s\n' "$b" "$omkt ($obuild)" "$nmkt ($nbuild)" "$src"
|
|
done <<EOF
|
|
$PLAN
|
|
EOF
|
|
|
|
if [ "$APPLY" != "1" ]; then
|
|
echo
|
|
echo "Dry run. Re-run with --apply (or 'make sync-versions APPLY=1') to write these commits."
|
|
exit 0
|
|
fi
|
|
|
|
# Applying. If the checked-out branch itself needs a bump we commit it through the working
|
|
# tree, so require a clean tree in that case.
|
|
if [ "$CUR_IN_PLAN" = "1" ] && ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
echo "sync-version: working tree is dirty and the current branch ($CUR) needs a bump — commit or stash first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
while IFS='|' read -r b omkt obuild nmkt nbuild src; do
|
|
[ -n "$b" ] || continue
|
|
msg="sync($b): VERSION $omkt ($obuild) -> $nmkt ($nbuild) — catch up to $src"
|
|
if [ "$b" = "$CUR" ]; then
|
|
sed -E -i.bak \
|
|
-e "s/^NUCLEIC_MARKETING_VERSION=.*/NUCLEIC_MARKETING_VERSION=$nmkt/" \
|
|
-e "s/^NUCLEIC_BUILD_NUMBER=.*/NUCLEIC_BUILD_NUMBER=$nbuild/" VERSION
|
|
rm -f VERSION.bak
|
|
git add VERSION
|
|
git commit -q -m "$msg"
|
|
else
|
|
# Rewrite VERSION on the branch tip without touching the working tree or index.
|
|
mode="$(git ls-tree "refs/heads/$b" VERSION | awk '{print $1}')"; [ -n "$mode" ] || mode=100644
|
|
blob="$(git show "refs/heads/$b:VERSION" \
|
|
| sed -E -e "s/^NUCLEIC_MARKETING_VERSION=.*/NUCLEIC_MARKETING_VERSION=$nmkt/" \
|
|
-e "s/^NUCLEIC_BUILD_NUMBER=.*/NUCLEIC_BUILD_NUMBER=$nbuild/" \
|
|
| git hash-object -w --stdin)"
|
|
tmpidx="$(mktemp)"
|
|
GIT_INDEX_FILE="$tmpidx" git read-tree "refs/heads/$b"
|
|
GIT_INDEX_FILE="$tmpidx" git update-index --cacheinfo "$mode,$blob,VERSION"
|
|
tree="$(GIT_INDEX_FILE="$tmpidx" git write-tree)"
|
|
rm -f "$tmpidx"
|
|
commit="$(git commit-tree "$tree" -p "refs/heads/$b" -m "$msg")"
|
|
git update-ref "refs/heads/$b" "$commit" "refs/heads/$b"
|
|
fi
|
|
echo " OK $b -> $nmkt ($nbuild)"
|
|
done <<EOF
|
|
$PLAN
|
|
EOF
|
|
|
|
echo
|
|
echo "Version-sync commits written. Push the updated branches to publish them (e.g. git push origin dev canary)."
|