Version Number Sync Adjustment
Nucleic-Session: 6D0F2A29-65AB-40D8-BD83-A863DEE7E4AF Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/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; }
|
||||
|
||||
# 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)."
|
||||
Reference in New Issue
Block a user