cli/.github/workflows/scripts/bump-go.sh
copilot-swe-agent[bot] d2cc91bdd4 Remove unnecessary null checks in jq output handling
The jq -r flag with // "" operator already returns empty string
for null/missing values, never the literal string "null".
The checks for == "null" are unnecessary.

Co-authored-by: babakks <36728931+babakks@users.noreply.github.com>
2026-05-13 14:41:02 +02:00

177 lines
6.9 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# bump-go.sh — Update go.mod `go` directive and toolchain to latest stable Go release.
#
# Usage:
# ./bump-go.sh [--apply|-a] <path/to/go.mod>
#
# By default the script runs in *dryrun* mode: it creates a local branch,
# commits the version bump, shows the exact patch, **checks for an existing PR**
# with the same title, and exits. Nothing is pushed. The temporary branch is
# deleted automatically on exit, so your working tree stays clean. Pass
# --apply (or -a) to push the branch and open a new PR *only if one doesnt
# already exist*.
# -----------------------------------------------------------------------------
set -euo pipefail
usage() {
echo "Usage: $0 [--apply|-a] <path/to/go.mod>" >&2
exit 1
}
# ---- Argument parsing -------------------------------------------------------
APPLY=0
GO_MOD=""
while [[ $# -gt 0 ]]; do
case "$1" in
--apply|-a) APPLY=1 ;;
-h|--help) usage ;;
*) [[ -z "$GO_MOD" ]] && GO_MOD="$1" || usage ;;
esac
shift
done
[[ -z "$GO_MOD" ]] && usage
[[ -f "$GO_MOD" ]] || { echo "Error: '$GO_MOD' not found" >&2; exit 1; }
# ---- Discover latest stable Go release --------------------------------------
echo "Fetching latest stable Go version…"
LATEST_JSON=$(curl -fsSL https://go.dev/dl/?mode=json | jq -c '[.[] | select(.stable==true)][0]')
FULL_VERSION=$(jq -r '.version' <<< "$LATEST_JSON") # e.g. go1.23.4
TOOLCHAIN_VERSION="${FULL_VERSION#go}" # e.g. 1.23.4
# The go directive can be either X.Y.0 (minor version) or X.Y.Z (patch version)
# We accept both forms as "latest" if they match the toolchain's major.minor
LATEST_MAJOR_MINOR="$(cut -d. -f1-2 <<< "$TOOLCHAIN_VERSION")"
echo " → latest toolchain : $TOOLCHAIN_VERSION"
# ---- Read current go.mod state using go mod edit ----------------------------
GO_MOD_JSON=$(go mod edit -json "$GO_MOD")
CURRENT_GO_DIRECTIVE=$(jq -r '.Go // ""' <<< "$GO_MOD_JSON")
CURRENT_TOOLCHAIN_DIRECTIVE=$(jq -r '.Toolchain // ""' <<< "$GO_MOD_JSON")
CURRENT_MAJOR_MINOR="$(cut -d. -f1-2 <<< "$CURRENT_GO_DIRECTIVE")"
BRANCH="bump-go-$TOOLCHAIN_VERSION"
BRANCH_CREATED=0
# Set up cleanup trap early (before any potential exits)
cleanup() {
if [[ $BRANCH_CREATED -eq 1 ]]; then
git checkout - >/dev/null 2>&1 || true
git branch -D "$BRANCH" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
# Check if we're already up to date
# Note: toolchain directive may be missing when go directive matches latest toolchain.
# This is expected behavior - `go mod tidy` removes the toolchain line when
# the minimum Go version matches the latest toolchain, as it's redundant.
if [[ "$CURRENT_MAJOR_MINOR" = "$LATEST_MAJOR_MINOR" ]]; then
# Current go directive is at the same major.minor as latest
if [[ -z "$CURRENT_TOOLCHAIN_DIRECTIVE" ]]; then
# No toolchain directive present - this is expected when go version matches latest
echo "Already on latest Go version: $CURRENT_GO_DIRECTIVE (latest toolchain: $TOOLCHAIN_VERSION)"
echo " → Note: No toolchain directive (expected when go version matches latest toolchain)"
exit 0
elif [[ "$CURRENT_TOOLCHAIN_DIRECTIVE" = "go$TOOLCHAIN_VERSION" ]]; then
echo "Already on latest Go version: $CURRENT_GO_DIRECTIVE (toolchain: $CURRENT_TOOLCHAIN_DIRECTIVE)"
exit 0
fi
# Current go directive is latest but toolchain is outdated - continue to update toolchain
fi
echo "Creating branch $BRANCH"
git switch -c "$BRANCH" >/dev/null 2>&1
BRANCH_CREATED=1
# ---- Patch go.mod using go mod edit -----------------------------------------
# Only update go directive if we're not already at the latest major.minor version
if [[ "$CURRENT_MAJOR_MINOR" != "$LATEST_MAJOR_MINOR" ]]; then
# Bump to the latest major.minor.0 (preserves the convention of X.Y.0 for go directive)
NEW_GO_DIRECTIVE="$LATEST_MAJOR_MINOR.0"
go mod edit -go="$NEW_GO_DIRECTIVE" "$GO_MOD"
echo " • go directive $CURRENT_GO_DIRECTIVE$NEW_GO_DIRECTIVE"
# After updating, the current go directive is now the new one for toolchain logic
CURRENT_GO_DIRECTIVE="$NEW_GO_DIRECTIVE"
fi
# Handle toolchain directive - may need to add, update, or skip
if [[ -z "$CURRENT_TOOLCHAIN_DIRECTIVE" ]]; then
# No toolchain directive exists
CURRENT_MAJOR_MINOR="$(cut -d. -f1-2 <<< "$CURRENT_GO_DIRECTIVE")"
if [[ "$CURRENT_MAJOR_MINOR" = "$LATEST_MAJOR_MINOR" ]]; then
# go directive is at latest major.minor - toolchain line is redundant
echo " • toolchain directive not needed (go version matches latest toolchain)"
else
# go directive is older than latest toolchain - add toolchain directive
go mod edit -toolchain="go$TOOLCHAIN_VERSION" "$GO_MOD"
echo " • toolchain directive added: go$TOOLCHAIN_VERSION"
fi
elif [[ "$CURRENT_TOOLCHAIN_DIRECTIVE" != "go$TOOLCHAIN_VERSION" ]]; then
# Toolchain directive exists but needs updating
go mod edit -toolchain="go$TOOLCHAIN_VERSION" "$GO_MOD"
echo " • toolchain $CURRENT_TOOLCHAIN_DIRECTIVE → go$TOOLCHAIN_VERSION"
fi
git add "$GO_MOD"
# ---- Commit -----------------------------------------------------------------
COMMIT_MSG="Bump Go to $TOOLCHAIN_VERSION"
git commit -m "$COMMIT_MSG" >/dev/null
COMMIT_HASH=$(git rev-parse --short HEAD)
PR_TITLE="$COMMIT_MSG"
# ---- Check for existing PR --------------------------------------------------
existing_pr=$(gh search prs --repo cli/cli --match title "$PR_TITLE" --json title --jq "map(select(.title == \"$PR_TITLE\") | .title) | length > 0")
if [[ "$existing_pr" == "true" ]]; then
echo "Found an existing open PR titled '$PR_TITLE'. Skipping push/PR creation."
if [[ $APPLY -eq 0 ]]; then
echo -e "\n=== DRYRUN DIFF (commit $COMMIT_HASH):\n"
git --no-pager show --color "$COMMIT_HASH"
fi
exit 0
fi
# ---- Dryrun handling -------------------------------------------------------
if [[ $APPLY -eq 0 ]]; then
echo -e "\n=== DRYRUN DIFF (commit $COMMIT_HASH):\n"
git --no-pager show --color "$COMMIT_HASH"
echo -e "\nIf --apply were provided, script would continue with:\n git push -u origin $BRANCH\n gh pr create --title \"$PR_TITLE\" --body <body>\n"
exit 0
fi
# ---- Push & PR --------------------------------------------------------------
# Get the actual go directive from the updated go.mod using go mod edit
FINAL_GO_MOD_JSON=$(go mod edit -json "$GO_MOD")
FINAL_GO_DIRECTIVE=$(jq -r '.Go // ""' <<< "$FINAL_GO_MOD_JSON")
FINAL_TOOLCHAIN_DIRECTIVE=$(jq -r '.Toolchain // ""' <<< "$FINAL_GO_MOD_JSON")
if [[ -n "$FINAL_TOOLCHAIN_DIRECTIVE" ]]; then
PR_BODY=$(cat <<EOF
This PR updates Go to the latest stable release.
* **go directive:** \`$FINAL_GO_DIRECTIVE\`
* **toolchain:** \`$FINAL_TOOLCHAIN_DIRECTIVE\`
EOF
)
else
PR_BODY=$(cat <<EOF
This PR updates Go to the latest stable release.
* **go directive:** \`$FINAL_GO_DIRECTIVE\`
* **toolchain:** (not specified - go version matches latest toolchain \`$TOOLCHAIN_VERSION\`)
EOF
)
fi
git push -u origin "$BRANCH"
gh pr create --title "$PR_TITLE" --body "$PR_BODY" --fill
echo "Done!"