Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 76 additions & 6 deletions scripts/propagate-workflow-pins.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@
# PATH may be a single consumer repo (has .github/workflows) or a parent
# directory of many repos. Defaults to the current directory.
# --to <sha> target standards SHA to pin to. If omitted, resolved from
# (in order) $STANDARDS_TARGET_SHA, a local standards checkout
# ($STANDARDS_DIR or ~/standards), then `git ls-remote` HEAD.
# (in order) $STANDARDS_TARGET_SHA, the main ref of a local
# standards checkout ($STANDARDS_DIR or ~/standards), then the
# remote main ref. A feature-worktree HEAD is never selected.
#
# Output: tab-separated audit lines suitable for review.

Expand Down Expand Up @@ -77,13 +78,22 @@
if [ -n "$TARGET_SHA" ]; then printf '%s' "$TARGET_SHA"; return 0; fi

local sd="${STANDARDS_DIR:-$HOME/standards}"
if git -C "$sd" rev-parse HEAD >/dev/null 2>&1; then
git -C "$sd" rev-parse HEAD; return 0
fi
# The old implementation used HEAD. When invoked from a standards feature
# worktree, that propagated a PR-intermediate commit before it had landed on
# main. GitHub's contents API could read that commit, but cross-repository
# reusable workflows rejected it as `workflow was not found` (251 active
# workflow files / 70 repos in the 2026-09-04 incident).
local ref
for ref in refs/remotes/origin/main refs/heads/main; do
if git -C "$sd" rev-parse --verify "${ref}^{commit}" >/dev/null 2>&1; then
git -C "$sd" rev-parse "${ref}^{commit}"
return 0
fi
done

# Network fallback — the live standards HEAD.
local remote
remote=$(git ls-remote https://github.com/hyperpolymath/standards.git HEAD 2>/dev/null | awk '{print $1}')
remote=$(git ls-remote https://github.com/hyperpolymath/standards.git refs/heads/main 2>/dev/null | awk '{print $1}')
if [ -n "$remote" ]; then printf '%s' "$remote"; return 0; fi

return 1
Expand All @@ -94,6 +104,66 @@
exit 2
fi

# Prove that the proposed pin is a full commit SHA reachable from standards'
# default branch. Merely proving that `/commits/<sha>` exists is insufficient:
# a squash-merged PR leaves its intermediate commits addressable through the
# API but unusable as cross-repository reusable-workflow refs.
validate_target() {
[[ "$TARGET_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || {
log "ERROR: target must be a full 40-character hexadecimal commit SHA: $TARGET_SHA"
return 1
}

local sd="${STANDARDS_DIR:-$HOME/standards}" ref head shallow
local local_main_seen=false
if git -C "$sd" cat-file -e "${TARGET_SHA}^{commit}" >/dev/null 2>&1; then
for ref in refs/remotes/origin/main refs/heads/main; do
if head=$(git -C "$sd" rev-parse --verify "${ref}^{commit}" 2>/dev/null); then
local_main_seen=true
if git -C "$sd" merge-base --is-ancestor "$TARGET_SHA" "$head"; then
return 0
fi
fi
done

# Only reject after every usable local main ref has been checked. A
# shallow or partial graph is not conclusive, so defer that case to the
# authoritative server comparison below.
shallow=$(git -C "$sd" rev-parse --is-shallow-repository 2>/dev/null || printf 'true')
if [[ "$local_main_seen" == true && "$shallow" != true ]] &&
[[ "$(git -C "$sd" config --get remote.origin.promisor 2>/dev/null || true)" != true ]] &&
[[ -z "$(git -C "$sd" config --get remote.origin.partialclonefilter 2>/dev/null || true)" ]]; then
log "ERROR: target ${TARGET_SHA} exists but is not reachable from any available local standards main ref."
return 1
fi
fi

# Neither usable local main ref proved reachability. Local refs can be stale,
# so only the authoritative server comparison may return a negative result.

local api="${STANDARDS_REACHABILITY_API_BASE:-https://api.github.com}"
local -a auth=()
[ -n "${GITHUB_TOKEN:-}" ] && auth=(-H "Authorization: Bearer ${GITHUB_TOKEN}")

Check failure on line 146 in scripts/propagate-workflow-pins.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBtI5IEMvU2nFaicEVx&open=AaBtI5IEMvU2nFaicEVx&pullRequest=739
local body status
body=$(curl -fsS --max-time 20 \
-H 'Accept: application/vnd.github+json' \
"${auth[@]}" \
"$api/repos/hyperpolymath/standards/compare/${TARGET_SHA}...main") || {
log "ERROR: could not prove target ${TARGET_SHA} is reachable from standards main; refusing propagation."
return 1
}
status=$(printf '%s' "$body" | sed -n 's/.*"status"[[:space:]]*:[[:space:]]*"\([a-z]*\)".*/\1/p' | head -n1)
case "$status" in
identical|ahead) return 0 ;;
*)
log "ERROR: target ${TARGET_SHA} is not reachable from standards main (compare status: ${status:-unknown}); refusing propagation."
return 1
;;
esac
}

validate_target || exit 2

# ---------------------------------------------------------------------------
# Per-file rewrite (the unit-testable core)
# ---------------------------------------------------------------------------
Expand Down
70 changes: 69 additions & 1 deletion scripts/tests/propagate-workflow-pins-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,25 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROP="$SCRIPT_DIR/../propagate-workflow-pins.sh"

OLD="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
TARGET="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"

# A real local commit graph makes reachability part of every test. The
# propagation primitive must reject a SHA that merely exists on a feature
# branch: GitHub cannot resolve such a SHA as a cross-repo reusable workflow.
UPSTREAM="$TEST_DIR/standards"
git init -q -b main "$UPSTREAM"
git -C "$UPSTREAM" config user.email t@t
git -C "$UPSTREAM" config user.name t
touch "$UPSTREAM/reusable.yml"
git -C "$UPSTREAM" add reusable.yml
git -C "$UPSTREAM" commit -q -m main
TARGET=$(git -C "$UPSTREAM" rev-parse HEAD)
git -C "$UPSTREAM" checkout -q -b feature
touch "$UPSTREAM/feature-only"
git -C "$UPSTREAM" add feature-only
git -C "$UPSTREAM" commit -q -m feature
ORPHAN=$(git -C "$UPSTREAM" rev-parse HEAD)
git -C "$UPSTREAM" checkout -q main
export STANDARDS_DIR="$UPSTREAM"

PASS=0; TOTAL=0

Expand Down Expand Up @@ -90,6 +108,56 @@ OUT=$(bash "$PROP" --to "$TARGET" "$ROOT")
try "parent-dir mode sees repoA" contains repoA "$OUT"
try "parent-dir mode sees repoB" contains repoB "$OUT"

# ── 8. Existing but non-mainline target is refused before any rewrite ───────
R="$TEST_DIR/orphan"; mk_consumer "$R"
set +e
OUT=$(bash "$PROP" --fix --to "$ORPHAN" "$R" 2>&1)
RC=$?
set -e
try "feature-only target is rejected" test "$RC" -eq 2
try "rejected target leaves consumer unchanged" file_has "$R/.github/workflows/governance.yml" "governance-reusable.yml@${OLD}"
try "rejection explains default-branch reachability" contains "not reachable" "$OUT"

# ── 9. A stale origin/main must not hide a newer local main ──────────────────
git -C "$UPSTREAM" update-ref refs/remotes/origin/main "$TARGET"
touch "$UPSTREAM/mainline-newer"
git -C "$UPSTREAM" add mainline-newer
git -C "$UPSTREAM" commit -q -m mainline-newer
NEWER_TARGET=$(git -C "$UPSTREAM" rev-parse HEAD)
R="$TEST_DIR/stale-origin"; mk_consumer "$R"
set +e
OUT=$(bash "$PROP" --to "$NEWER_TARGET" "$R" 2>&1)
RC=$?
set -e
try "newer local main target survives stale origin/main" test "$RC" -eq 0
try "stale origin/main does not report unreachable" not_contains "not reachable" "$OUT"

# ── 10. A linked worktree retains the shared clone's shallow status ────────────
SHALLOW_REMOTE="$TEST_DIR/standards-remote.git"
SHALLOW_CLONE="$TEST_DIR/standards-shallow"
SHALLOW_WORKTREE="$TEST_DIR/standards-shallow-worktree"
FAKE_BIN="$TEST_DIR/fake-bin"
git init -q --bare "$SHALLOW_REMOTE"
git -C "$UPSTREAM" push -q "$SHALLOW_REMOTE" main
git -C "$SHALLOW_REMOTE" symbolic-ref HEAD refs/heads/main
git clone -q --depth 1 "file://$SHALLOW_REMOTE" "$SHALLOW_CLONE"
git -C "$SHALLOW_CLONE" fetch -q origin "$TARGET"
git -C "$SHALLOW_CLONE" worktree add -q -b reachability-test "$SHALLOW_WORKTREE" HEAD
mkdir -p "$FAKE_BIN"
cat > "$FAKE_BIN/curl" <<'EOF'
#!/usr/bin/env sh
printf '%s\n' '{"status":"ahead"}'
EOF
chmod +x "$FAKE_BIN/curl"
R="$TEST_DIR/shallow-consumer"; mk_consumer "$R"
set +e
OUT=$(STANDARDS_DIR="$SHALLOW_WORKTREE" PATH="$FAKE_BIN:$PATH" \
bash "$PROP" --to "$TARGET" "$R" 2>&1)
RC=$?
set -e
try "linked shallow worktree defers to server reachability" test "$RC" -eq 0
try "linked shallow worktree does not report unreachable" not_contains "not reachable" "$OUT"

echo "----------------------------------------"
echo "$PASS/$TOTAL test cases passed."
[ "$PASS" -eq "$TOTAL" ] || { echo "Some propagation tests FAILED."; exit 1; }
Expand Down
Loading