Files
project-work/worker-toolkit-potion-polyglot/scripts/lib/resolve-pin.sh

97 lines
4.0 KiB
Bash

# shellcheck shell=bash
#
# resolve_pin — turn a task's pinned commit into a SHA that exists in the repo,
# translating through a commit map when history has been rewritten under it.
#
# A task pins a commit in task.toml. If that repo's history is later rewritten
# (to strip something that should never have shipped, say), every rewritten
# commit gets a new SHA and the pin stops resolving — including on machines we
# cannot reach, holding tasks we cannot edit. A commit map lets those pins keep
# working: `<old-sha> <new-sha>` per line, at task-shared/commit-maps/<member>.map,
# <member> being the task's `repo` key — a standalone toolkit checks its repo out
# at repo/, so the directory name is not the member name and cannot be the key.
#
# The map is only consulted when the pin does not resolve, so it carries only
# rewritten commits — an unchanged commit resolves on its own and its identity
# row could never be read.
#
# Usage (source, then call):
# . "$(dirname "$0")/lib/resolve-pin.sh"
# sha=$(resolve_pin "$REPO_DIR" "$COMMIT" "$TOOLKIT_ROOT/task-shared/commit-maps" "$MEMBER") || exit 1
#
# Writes the resolved SHA to stdout, notes on stderr. Returns non-zero if the
# pin cannot be resolved, having explained why.
RESOLVE_PIN_MAX_HOPS="${RESOLVE_PIN_MAX_HOPS:-25}"
_RESOLVE_PIN_ZERO='0000000000000000000000000000000000000000'
# Look one hop: echo the successor of $1 in map $2, or nothing. Fails if the
# prefix is ambiguous, which would otherwise pick an arbitrary commit.
_resolve_pin_hop() {
local from="$1" map="$2" hits
hits=$(awk -v p="$from" '
/^#/ || NF < 2 { next }
index($1, p) == 1 { print $2 }
' "$map" | sort -u)
[ -z "$hits" ] && return 1
if [ "$(printf '%s\n' "$hits" | wc -l | tr -d ' ')" -gt 1 ]; then
echo " pin $from is ambiguous in $(basename "$map") — use a longer SHA" >&2
return 2
fi
printf '%s\n' "$hits"
}
resolve_pin() {
local repo_dir="$1" commit="$2" map_dir="${3:-}" key="${4:-}" sha map cur hops next rc
# Present in the repo: nothing to translate.
if sha=$(git -C "$repo_dir" rev-parse --quiet --verify "$commit^{commit}" 2>/dev/null); then
printf '%s\n' "$sha"
return 0
fi
map=""
if [ -n "$map_dir" ]; then
if [ -n "$key" ] && [ -f "$map_dir/$key.map" ]; then
map="$map_dir/$key.map"
elif [ -f "$map_dir/$(basename "$repo_dir").map" ]; then
map="$map_dir/$(basename "$repo_dir").map"
fi
fi
if [ -z "$map" ]; then
echo "Error: pinned commit $commit is not in $repo_dir, and no commit map is available." >&2
echo " The repo may be a shallow or partial copy — try a full clone." >&2
return 1
fi
# Follow the chain: a commit rewritten more than once maps forward a hop per
# rewrite, so keep going until the SHA exists or the trail ends.
cur="$commit"
hops=0
while [ "$hops" -lt "$RESOLVE_PIN_MAX_HOPS" ]; do
next=$(_resolve_pin_hop "$cur" "$map"); rc=$?
[ "$rc" -eq 2 ] && return 1
if [ "$rc" -ne 0 ]; then
echo "Error: pinned commit $commit is not in $repo_dir and is not in $(basename "$map")." >&2
echo " It predates the map, or came from a repo copy this toolkit was not built from." >&2
return 1
fi
if [ "$next" = "$_RESOLVE_PIN_ZERO" ]; then
echo "Error: pinned commit $commit was deleted by a history rewrite, not rewritten." >&2
echo " Re-pin this task to a commit that still exists." >&2
return 1
fi
if sha=$(git -C "$repo_dir" rev-parse --quiet --verify "$next^{commit}" 2>/dev/null); then
echo " Pin $commit was rewritten; using $sha" >&2
printf '%s\n' "$sha"
return 0
fi
cur="$next"
hops=$((hops + 1))
done
echo "Error: pinned commit $commit did not settle after $RESOLVE_PIN_MAX_HOPS hops." >&2
echo " $(basename "$map") may contain a cycle." >&2
return 1
}