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
20 changes: 19 additions & 1 deletion scripts/channel-tag.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,25 @@ version_at() {

push_tag() { # push_tag <tag> [--force]
[ "$push" = 1 ] || return 0
git ${remote_opts[@]+"${remote_opts[@]}"} push ${2:+"$2"} "$remote" "refs/tags/$1:refs/tags/$1"
local err rc=0
err="$(git ${remote_opts[@]+"${remote_opts[@]}"} push ${2:+"$2"} "$remote" "refs/tags/$1:refs/tags/$1" 2>&1 >/dev/null)" || rc=$?
[ -z "$err" ] || printf '%s\n' "$err" >&2
[ "$rc" = 0 ] || explain_refusal "$1" "$err"
return "$rc"
}

# Puts GitHub's own refusal on stdout, which the workflows add to the step
# summary, so a red run explains itself without its log. The hint after it
# covers the two refusals these workflows have actually met.
explain_refusal() { # explain_refusal <tag> <push stderr>
echo "push: GitHub refused $1, so it did not move:"
sed -nE '/^(remote|fatal|error):|\[remote rejected\]/{s/[[:space:]]*$//; s/^/push: /; p;}' <<<"$2"
case "$2" in
*"denied to github-actions[bot]"*)
echo "push: that is GITHUB_TOKEN, which may not push workflow-file changes. Set GH_APP_CLIENT_ID and GH_APP_PRIVATE_KEY so the App pushes instead." ;;
*"Permission to "*" denied to "*|*"workflows"*" permission"*)
echo "push: check that the App installation still has contents: write and workflows: write, then re-run the job for a fresh token. If a fresh token is refused too, an org owner re-saves the installation's permissions." ;;
esac
}

move_tag() { # move_tag <tag> <commit>
Expand Down
25 changes: 25 additions & 0 deletions scripts/channel-tag.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ eq() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (expected '$3', got '$2')"
ne() { if [ "$2" != "$3" ]; then ok "$1"; else bad "$1 (both '$2')"; fi; }
yes() { local d="$1"; shift; if "$@" >/dev/null 2>&1; then ok "$d"; else bad "$d"; fi; }
no() { local d="$1"; shift; if "$@" >/dev/null 2>&1; then bad "$d"; else ok "$d"; fi; }
has() { case "$2" in *"$3"*) ok "$1" ;; *) bad "$1 (no '$3' in '$2')" ;; esac; }

export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@example.com GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@example.com
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
Expand Down Expand Up @@ -288,6 +289,30 @@ eq "a token push clears the persisted auth header" "$(grep -c '^-c http.https://
(cd "$R4" && PATH="$SHIM:$PATH" "$SCRIPT" promote --version 1.5.0 >/dev/null 2>&1)
eq "a plain push leaves the header alone" "$(grep -c 'extraheader' "$TMP/git.log")" 0

# ---------------------------------------------------------------- refused push
# A pre-receive hook stands in for GitHub refusing the push the way it did on
# 2026-10-27, so the summary text is checked against the real git output.
R6="$TMP/r6"; make_repo "$R6" 1; release "$R6" 1.0.0; git -C "$R6" tag v1 v1.0.0
git -C "$R6" push -q origin v1
release "$R6" 1.1.0
run "$R6" pin >/dev/null
refuse() { printf '#!/bin/sh\necho \047%s\047\nexit 1\n' "$1" > "$R6.git/hooks/pre-receive"; chmod +x "$R6.git/hooks/pre-receive"; }
refuse "Permission to o/r.git denied to some-app[bot]."
out="$(run "$R6" promote)"; rc=$?
ne "a refused push fails the promotion" "$rc" 0
eq "and the remote v1 stays" "$(remote_sha "$R6" v1)" "$(sha "$R6" v1.0.0)"
has "the summary names the refused tag" "$out" "push: GitHub refused v1, so it did not move:"
has "and quotes GitHub's own words" "$out" "push: remote: Permission to o/r.git denied to some-app[bot]."
has "an App refusal points at the installation" "$out" "re-run the job for a fresh token"
refuse "Permission to o/r.git denied to github-actions[bot]."
has "a GITHUB_TOKEN refusal points at the App secrets" "$(run "$R6" promote)" "push: that is GITHUB_TOKEN"
refuse 'refusing to allow a GitHub App to create or update workflow .github/workflows/wf.yml without "workflows" permission'
has "a missing workflows permission points at the installation" "$(run "$R6" promote)" "contents: write and workflows: write"
refuse "some other rejection"
out="$(run "$R6" promote)"
has "an unknown rejection is still quoted" "$out" "push: remote: some other rejection"
no "and gets no guessed hint" grep -qE 'fresh token|GITHUB_TOKEN' <<<"$out"

echo
echo "$pass passed, $fail failed"
[ "$fail" -eq 0 ]
Loading