100 lines
3.4 KiB
Bash
100 lines
3.4 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# Refuse to build a package from a branch that main has already moved past.
|
||
|
|
#
|
||
|
|
# WHY
|
||
|
|
#
|
||
|
|
# Everyone branches off main and builds from their own branch. A branch that is
|
||
|
|
# behind main still builds and installs perfectly — it just quietly ships a
|
||
|
|
# binary missing whatever landed on main in the meantime, and `rpm -U --force`
|
||
|
|
# overwrites the good one with it.
|
||
|
|
#
|
||
|
|
# That is not hypothetical: on 2026-08-10 a build from a stale checkout replaced
|
||
|
|
# /usr/bin/mcpctl with one that had no `statusline` command at all, months after
|
||
|
|
# the status line landed on main. Nothing reported an error — the release
|
||
|
|
# succeeded, the feature just vanished from the installed CLI.
|
||
|
|
#
|
||
|
|
# So this is a hard failure rather than a warning. A warning scrolls past in a
|
||
|
|
# build log; the whole point is to stop before the artifact exists.
|
||
|
|
#
|
||
|
|
# ESCAPE HATCH
|
||
|
|
#
|
||
|
|
# MCPCTL_ALLOW_BEHIND_MAIN=1 build anyway (deliberate build of an old tree)
|
||
|
|
# MCPCTL_BASE_BRANCH=<name> compare against something other than main
|
||
|
|
#
|
||
|
|
# Skips itself entirely outside a git checkout, so tarball builds still work.
|
||
|
|
|
||
|
|
# Resolve the ref to compare against, echoing it on stdout. Prefers a fresh
|
||
|
|
# fetch; falls back to whatever is already on disk so an offline build is
|
||
|
|
# degraded rather than blocked. Returns 1 when there is nothing to compare to.
|
||
|
|
_main_sync_ref() {
|
||
|
|
local base="$1" remote="$2"
|
||
|
|
|
||
|
|
if [ -n "$remote" ] && git fetch --quiet "$remote" "$base" 2>/dev/null; then
|
||
|
|
# FETCH_HEAD rather than refs/remotes/<remote>/<base>: it is what this fetch
|
||
|
|
# just wrote, so it cannot be a stale opportunistic update.
|
||
|
|
echo "FETCH_HEAD"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -n "$remote" ] && git rev-parse --verify --quiet "refs/remotes/$remote/$base" >/dev/null; then
|
||
|
|
echo " (could not reach $remote — comparing against the last fetched $remote/$base)" >&2
|
||
|
|
echo "refs/remotes/$remote/$base"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
if git rev-parse --verify --quiet "refs/heads/$base" >/dev/null; then
|
||
|
|
echo " (no reachable remote — comparing against local $base)" >&2
|
||
|
|
echo "refs/heads/$base"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
check_main_sync() {
|
||
|
|
local base="${MCPCTL_BASE_BRANCH:-main}"
|
||
|
|
|
||
|
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||
|
|
return 0 # not a checkout; nothing to be behind
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "${MCPCTL_ALLOW_BEHIND_MAIN:-}" = "1" ]; then
|
||
|
|
echo "==> Skipping the '$base' sync check (MCPCTL_ALLOW_BEHIND_MAIN=1)"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "==> Checking this branch is not behind '$base'..."
|
||
|
|
|
||
|
|
local remote ref
|
||
|
|
remote="$(git remote | head -1)"
|
||
|
|
if ! ref="$(_main_sync_ref "$base" "$remote")"; then
|
||
|
|
echo " (no '$base' branch found anywhere — skipping)"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
local behind
|
||
|
|
behind="$(git rev-list --count "HEAD..$ref" 2>/dev/null || echo 0)"
|
||
|
|
if [ "$behind" -eq 0 ]; then
|
||
|
|
echo " up to date with $base"
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
local branch
|
||
|
|
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
|
||
|
|
echo "" >&2
|
||
|
|
echo "ERROR: '$branch' is $behind commit(s) behind $base — refusing to build." >&2
|
||
|
|
echo "" >&2
|
||
|
|
echo " Building now would package a binary without these, and installing it" >&2
|
||
|
|
echo " would overwrite a good one with a version missing them:" >&2
|
||
|
|
echo "" >&2
|
||
|
|
git log --oneline --no-decorate "HEAD..$ref" | head -15 | sed 's/^/ /' >&2
|
||
|
|
if [ "$behind" -gt 15 ]; then
|
||
|
|
echo " … and $((behind - 15)) more" >&2
|
||
|
|
fi
|
||
|
|
echo "" >&2
|
||
|
|
echo " Fix it: git merge $base # or: git rebase $base" >&2
|
||
|
|
echo " Anyway: MCPCTL_ALLOW_BEHIND_MAIN=1 $0" >&2
|
||
|
|
echo "" >&2
|
||
|
|
return 1
|
||
|
|
}
|