#!/bin/bash
# Apply the s3-gateway patch series for the target versitygw tag onto
# the checked-out source tree (run ci/clone-source.sh first).
#
# Patch sets live in patches/
/. The directory for a given tag
# comes from ci/support-matrix.yaml (the `patches:` field, defaulting
# to the tag name). Patches are git-format-patch files applied with
# `git am --3way` in filename order — same model as scoutfs-notify.
#
# The matrix is an ALLOWLIST: a tag that is not listed is refused, so a
# stray tag push or a mistyped dispatch cannot publish a build of a
# superseded (and possibly vulnerable) upstream release. Superseded
# series stay on disk under patches/archive// and can be rebuilt
# deliberately by passing PATCH_DIR.
#
# Environment:
# VGW_SRC_DIR source tree (default: src; set by clone-source.sh)
# VGW_TAG upstream tag being built (required)
# PATCH_DIR build this directory instead of consulting the matrix;
# the escape hatch for patches/archive/ (optional)
set -euo pipefail
VGW_SRC_DIR="${VGW_SRC_DIR:-src}"
: "${VGW_TAG:?VGW_TAG must be set}"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC="$(cd "${VGW_SRC_DIR}" && pwd)"
MATRIX="${REPO_ROOT}/ci/support-matrix.yaml"
# Emit " " for every row of the support matrix. Prefer
# yq, but fall back to a small awk parse of the fixed shape this repo
# writes: the stock golang CI container ships no yq, and the matrix is
# the allowlist, so it has to be readable either way.
matrix_entries() {
if command -v yq >/dev/null 2>&1; then
yq -r '.versitygw[] | "\(.tag) \(.patches // .tag)"' "${MATRIX}" 2>/dev/null && return
fi
sed 's/#.*//' "${MATRIX}" | awk '
/^[[:space:]]*-[[:space:]]*tag:[[:space:]]*[^[:space:]]/ {
if (tag != "") print tag, (pat != "" ? pat : tag)
tag = $NF; pat = ""; next
}
/^[[:space:]]*patches:[[:space:]]*[^[:space:]]/ { if (tag != "") pat = $NF }
END { if (tag != "") print tag, (pat != "" ? pat : tag) }
'
}
# An explicit PATCH_DIR is a deliberate override (rebuilding something
# out of patches/archive/). Otherwise the matrix decides, and a tag it
# does not list is refused — never silently built as vanilla upstream.
if [[ -z "${PATCH_DIR:-}" ]]; then
PATCH_NAME="$(matrix_entries | awk -v t="${VGW_TAG}" '$1 == t { print $2; exit }')"
if [[ -z "${PATCH_NAME}" ]]; then
{
echo "ERROR: ${VGW_TAG} is not listed in ci/support-matrix.yaml."
echo " Supported: $(matrix_entries | awk '{print $1}' | tr '\n' ' ')"
echo " Superseded series are archived under patches/archive/ and"
echo " are deliberately not rebuilt — they predate the fixes in"
echo " the supported release. To build one anyway, pass it"
echo " explicitly:"
echo " PATCH_DIR=patches/archive/${VGW_TAG} bash ci/apply-patches.sh"
} >&2
exit 1
fi
PATCH_DIR="${REPO_ROOT}/patches/${PATCH_NAME}"
fi
# git am runs from inside the source tree, so the patch paths have to be
# absolute: a relative PATCH_DIR (the documented archive invocation is
# one) would still glob here but no longer resolve after the cd below.
if [[ ! -d "${PATCH_DIR}" ]]; then
echo "ERROR: patch directory ${PATCH_DIR} does not exist." >&2
exit 1
fi
PATCH_DIR="$(cd "${PATCH_DIR}" && pwd)"
echo ">>> target versitygw tag: ${VGW_TAG}"
echo ">>> patch directory: ${PATCH_DIR}"
shopt -s nullglob
patches=( "${PATCH_DIR}"/*.patch )
shopt -u nullglob
if (( ${#patches[@]} == 0 )); then
echo "ERROR: no *.patch files in ${PATCH_DIR}." >&2
echo " Refusing to continue — that would publish an un-de-branded" >&2
echo " vanilla upstream build under this project's release name." >&2
exit 1
fi
echo ">>> applying ${#patches[@]} patch(es) with git am --3way:"
printf ' %s\n' "${patches[@]##*/}"
cd "${SRC}"
git am --whitespace=nowarn --3way "${patches[@]}"
echo ">>> applied commits:"
git log --oneline "${VGW_TAG}..HEAD"
if [[ -n "${GITHUB_ENV:-}" ]]; then
{
echo "S3GW_PATCH_DIR=${PATCH_DIR}"
echo "S3GW_PATCH_COUNT=${#patches[@]}"
} >> "${GITHUB_ENV}"
fi
echo ">>> patches applied"