Some checks failed
CI/CD / lint (pull_request) Successful in 1m15s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / typecheck (pull_request) Successful in 2m59s
CI/CD / smoke (pull_request) Failing after 1m57s
CI/CD / build (pull_request) Successful in 4m58s
CI/CD / publish (pull_request) Has been skipped
Same hazard as the package build, with the cluster on the receiving end: a
branch behind main builds images missing whatever landed there, and deploy-k8s.sh
pins that sha in Pulumi — making the stale build the cluster's source of truth.
build-mcpd.sh gets its own call because it is run standalone as well as from
deploy-k8s.sh, so neither can rely on the other having checked.
`--dry-run` is exempt. It builds and cuts over nothing, and blocking a read-only
inspection is exactly what teaches people to export MCPCTL_ALLOW_BEHIND_MAIN=1
permanently — which would disable the gate for the real deploys too.
The failure text is now artifact-agnostic ("produce an artifact" / "shipping
it"), since one helper now speaks for packages, images and deploys.
Verified against a synthetic ref one commit ahead: build-mcpd.sh exits 1 before
any docker work, and deploy-k8s.sh exits 1 before the test gate, the pg_dump,
the image build and pulumi. Neither the working tree nor HEAD was moved to test
this — the ref was built with git commit-tree and deleted afterwards.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wUmrfkVQR6CKcYKxENq7k
102 lines
3.6 KiB
Bash
Executable File
102 lines
3.6 KiB
Bash
Executable File
#!/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
|
|
# Deliberately artifact-agnostic: the same helper gates RPM/DEB packages, the
|
|
# mcpd image, and the k8s deploy.
|
|
echo " Building now would produce an artifact without these, and shipping it" >&2
|
|
echo " would replace 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
|
|
}
|