diff --git a/README.md b/README.md index aeb08e6..236bb93 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,9 @@ The package includes an install hook that prints the same guidance after install ### Foundry CLI AUR recovery -The real-actor package PR merge triggers `publish.yml` on main. Missing AUR +The real-actor package PR merge triggers `publish.yml` on main. Foundry CLI +publishing compares version and package revision against AUR after cloning; +equal or older retries are no-ops, and concurrent stale pushes fail without force. Missing AUR credentials fail the Foundry CLI publish job. To retry publishing an already merged version without touching other packages: diff --git a/scripts/publish-foundry-cli.test.sh b/scripts/publish-foundry-cli.test.sh new file mode 100755 index 0000000..71f83ba --- /dev/null +++ b/scripts/publish-foundry-cli.test.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Exercise real local git pushes; never connect to AUR. +set -euo pipefail +repo_root="$(cd "$(dirname "$0")/.." && pwd)" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +export REAL_GIT +REAL_GIT="$(command -v git)" +mkdir -p "$tmp/bin" +cat > "$tmp/bin/git" <<'GIT' +#!/usr/bin/env bash +set -euo pipefail +if [[ "$1" == clone ]]; then + [[ "$2" == ssh://aur@aur.archlinux.org/foundry-cli-bin.git ]] + exec "$REAL_GIT" clone "$FAKE_AUR" "$3" +fi +exec "$REAL_GIT" "$@" +GIT +chmod +x "$tmp/bin/git" +export PATH="$tmp/bin:$PATH" AUR_USERNAME=test AUR_EMAIL=test@example.com +write_package() { + local directory="$1" version="$2" revision="$3" + mkdir -p "$directory" + printf 'pkgver=%s\npkgrel=%s\n' "$version" "$revision" > "$directory/PKGBUILD" + printf 'pkgbase = foundry-cli-bin\n\tpkgver = %s\n\tpkgrel = %s\n' "$version" "$revision" > "$directory/.SRCINFO" +} +run_case() { + local name="$1" version="$2" revision="$3" expected="$4" + local work="$tmp/$name" + export FAKE_AUR="$work/aur.git" + mkdir -p "$work/source/scripts" + git init -q --bare --initial-branch=master "$FAKE_AUR" + git init -q -b master "$work/seed" + write_package "$work/seed" 0.0.56 2 + git -C "$work/seed" add . + git -C "$work/seed" -c user.name=test -c user.email=test@example.com commit -qm initial + git -C "$work/seed" push -q "$FAKE_AUR" master + local before + before="$(git --git-dir="$FAKE_AUR" rev-parse HEAD)" + git init -q -b main "$work/source" + write_package "$work/source/foundry-cli-bin" "$version" "$revision" + git -C "$work/source" add . + cp "${PUBLISHER_UNDER_TEST:-$repo_root/scripts/publish-package.sh}" "$work/source/scripts/publish-package.sh" + "$work/source/scripts/publish-package.sh" foundry-cli-bin > "$work/output" 2>&1 + if [[ "$expected" == noop ]]; then + [[ "$(git --git-dir="$FAKE_AUR" rev-parse HEAD)" == "$before" ]] || { + echo "Publisher changed AUR for $name; expected a no-op" >&2 + exit 1 + } + else + [[ "$(git --git-dir="$FAKE_AUR" rev-parse HEAD)" != "$before" ]] + git --git-dir="$FAKE_AUR" show HEAD:.SRCINFO > "$work/published" + diff -u "$work/source/foundry-cli-bin/.SRCINFO" "$work/published" + fi + echo "Passed publish guard: $name" +} +run_case older-version 0.0.55 99 noop +run_case older-revision 0.0.56 1 noop +run_case equal 0.0.56 2 noop +run_case forward-version 0.0.57 1 publish +run_case forward-revision 0.0.56 3 publish diff --git a/scripts/publish-package.sh b/scripts/publish-package.sh index f302cef..1d355c0 100755 --- a/scripts/publish-package.sh +++ b/scripts/publish-package.sh @@ -20,10 +20,33 @@ trap 'rm -rf "${workdir}"' EXIT git clone "ssh://aur@aur.archlinux.org/${package_name}.git" "${workdir}/${package_name}" +# A retry can refer to an older main commit than the version already in AUR. +# Compare after cloning; a concurrent newer push after this point is rejected +# by the ordinary non-force git push below. +if [[ "$package_name" == foundry-cli-bin && -f "$workdir/$package_name/.SRCINFO" ]]; then + read_release() { + local file="$1" version revision + version="$(sed -n 's/^[[:space:]]*pkgver = //p' "$file")" + revision="$(sed -n 's/^[[:space:]]*pkgrel = //p' "$file")" + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ && "$revision" =~ ^[0-9]+$ ]] || { + echo "Invalid Foundry CLI AUR version metadata" >&2 + return 1 + } + printf '%s-%s\n' "$version" "$revision" + } + candidate="$(read_release "$repo_root/$package_dir/.SRCINFO")" + published="$(read_release "$workdir/$package_name/.SRCINFO")" + newest="$(printf '%s\n' "$candidate" "$published" | sort -V | tail -n1)" + if [[ "$candidate" == "$published" || "$newest" != "$candidate" ]]; then + echo "No AUR changes: Foundry CLI $published is already published; refusing replay of $candidate." + exit 0 + fi +fi + find "${workdir}/${package_name}" -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + while IFS= read -r tracked_file; do - relative_path="${tracked_file#${package_dir}/}" + relative_path="${tracked_file#"${package_dir}"/}" install -d "${workdir}/${package_name}/$(dirname "${relative_path}")" cp -a "${repo_root}/${tracked_file}" "${workdir}/${package_name}/${relative_path}" done < <(git -C "${repo_root}" ls-files -- "${package_dir}") diff --git a/scripts/validate-packages.sh b/scripts/validate-packages.sh index ef47191..33ce3ed 100755 --- a/scripts/validate-packages.sh +++ b/scripts/validate-packages.sh @@ -34,3 +34,4 @@ bash -n "${repo_root}/scripts/publish-tabex-release.sh" "${repo_root}/scripts/bump-foundry-cli-release.test.sh" "${repo_root}/scripts/validate-foundry-cli-checksum.test.sh" +"${repo_root}/scripts/publish-foundry-cli.test.sh"