11#! /usr/bin/env bash
22#
3- # bump-go.sh — Update go.mod `go` directive and toolchain to latest stable Go release.
3+ # bump-go.sh -- Update go.mod `go` directive and toolchain to latest stable Go release.
44#
55# Usage:
66# ./bump-go.sh [--apply|-a] <path/to/go.mod>
77#
8- # By default the script runs in *dry‑ run* mode: it creates a local branch,
8+ # By default the script runs in *dry- run* mode: it creates a local branch,
99# commits the version bump, shows the exact patch, **checks for an existing PR**
1010# with the same title, and exits. Nothing is pushed. The temporary branch is
1111# deleted automatically on exit, so your working tree stays clean. Pass
12- # --apply (or -a) to push the branch and open a new PR *only if one doesn’ t
12+ # --apply (or -a) to push the branch and open a new PR *only if one doesn' t
1313# already exist*.
1414# -----------------------------------------------------------------------------
1515set -euo pipefail
3535[[ -z " $GO_MOD " ]] && usage
3636[[ -f " $GO_MOD " ]] || { echo " Error: '$GO_MOD ' not found" >&2 ; exit 1; }
3737
38+ REPO=" cli/cli"
39+ MODULE_DIR=$( dirname " $GO_MOD " )
40+ GO_SUM=" $MODULE_DIR /go.sum"
41+
3842# ---- Discover latest stable Go release --------------------------------------
39- echo " Fetching latest stable Go version… "
43+ echo " Fetching latest stable Go version... "
4044LATEST_JSON=$( curl -fsSL https://go.dev/dl/? mode=json | jq -c ' [.[] | select(.stable==true)][0]' )
4145FULL_VERSION=$( jq -r ' .version' <<< " $LATEST_JSON" ) # e.g. go1.23.4
4246TOOLCHAIN_VERSION=" ${FULL_VERSION# go} " # e.g. 1.23.4
43- # `go mod tidy` will always add `.0` if there is no minor version
44- # so let's just ensure .0 is suffixed to the go directive.
4547GO_DIRECTIVE_VERSION=" $( cut -d. -f1-2 <<< " $TOOLCHAIN_VERSION" ) .0"
4648
47- echo " → go : $GO_DIRECTIVE_VERSION "
48- echo " → toolchain : $TOOLCHAIN_VERSION "
49+ echo " → go directive : $GO_DIRECTIVE_VERSION "
50+ echo " → toolchain : go $TOOLCHAIN_VERSION "
4951
50- # ---- Prepare Git branch ---------------------------------------------------
51- CURRENT_GO_DIRECTIVE=$( grep -E ' ^go ' " $GO_MOD " | cut -d ' ' -f2)
52- CURRENT_TOOLCHAIN_DIRECTIVE=$( grep -E ' ^toolchain ' " $GO_MOD " | cut -d ' ' -f2)
52+ # ---- Read current go.mod state using go mod edit ----------------------------
53+ GO_MOD_JSON=$( go mod edit -json " $GO_MOD " )
54+ CURRENT_GO_DIRECTIVE=$( jq -r ' .Go // ""' <<< " $GO_MOD_JSON" )
55+ CURRENT_TOOLCHAIN=$( jq -r ' .Toolchain // ""' <<< " $GO_MOD_JSON" )
5356
54- if [[ " $CURRENT_GO_DIRECTIVE " = " $GO_DIRECTIVE_VERSION " && \
55- " $CURRENT_TOOLCHAIN_DIRECTIVE " = " go$TOOLCHAIN_VERSION " ]]; then
56- echo " Already on latest Go version: $CURRENT_GO_DIRECTIVE (toolchain: $CURRENT_TOOLCHAIN_DIRECTIVE )"
57- exit 0
58- fi
57+ echo " → current go : $CURRENT_GO_DIRECTIVE "
58+ echo " → current tc : ${CURRENT_TOOLCHAIN:- (none)} "
5959
60+ # ---- Prepare Git branch -----------------------------------------------------
6061BRANCH=" bump-go-$TOOLCHAIN_VERSION "
62+ BRANCH_CREATED=0
63+
6164cleanup () {
62- git checkout - > /dev/null 2>&1 || true
63- git branch -D " $BRANCH " > /dev/null 2>&1 || true
65+ if [[ $BRANCH_CREATED -eq 1 ]]; then
66+ git checkout - > /dev/null 2>&1 || true
67+ git branch -D " $BRANCH " > /dev/null 2>&1 || true
68+ fi
6469}
6570trap cleanup EXIT
6671
6772echo " Creating branch $BRANCH "
6873git switch -c " $BRANCH " > /dev/null 2>&1
74+ BRANCH_CREATED=1
6975
7076# ---- Patch go.mod -----------------------------------------------------------
71- if [[ " $CURRENT_GO_DIRECTIVE " != " $GO_DIRECTIVE_VERSION " ]]; then
72- sed -Ei.bak " s/^go [0-9]+\.[0-9]+.*$/go $GO_DIRECTIVE_VERSION /" " $GO_MOD "
73- echo " • go directive $CURRENT_GO_DIRECTIVE → $GO_DIRECTIVE_VERSION "
74- fi
75-
76- if [[ " $CURRENT_TOOLCHAIN_DIRECTIVE " != " go$TOOLCHAIN_VERSION " ]]; then
77- sed -Ei.bak " s/^toolchain go[0-9]+\.[0-9]+\.[0-9]+.*$/toolchain go$TOOLCHAIN_VERSION /" " $GO_MOD "
78- echo " • toolchain $CURRENT_TOOLCHAIN_DIRECTIVE → go$TOOLCHAIN_VERSION "
77+ # Always set both directives and let `go mod tidy` normalize.
78+ # When the go directive version matches the toolchain version, tidy will remove
79+ # the toolchain line because it is redundant -- this is expected Go behavior.
80+ go mod edit -go=" $GO_DIRECTIVE_VERSION " -toolchain=" go$TOOLCHAIN_VERSION " " $GO_MOD "
81+ echo " • set go directive → $GO_DIRECTIVE_VERSION "
82+ echo " • set toolchain → go$TOOLCHAIN_VERSION "
83+
84+ # Let go mod tidy reconcile dependencies and normalize directives.
85+ echo " • running go mod tidy..."
86+ pushd " $MODULE_DIR " > /dev/null
87+ go mod tidy
88+ popd > /dev/null
89+
90+ # ---- Check if anything actually changed -------------------------------------
91+ if git diff --quiet -- " $GO_MOD " " $GO_SUM " 2> /dev/null; then
92+ echo " Already on latest Go version -- no changes needed."
93+ exit 0
7994fi
8095
81- rm -f " $GO_MOD .bak"
82-
8396git add " $GO_MOD "
97+ [[ -f " $GO_SUM " ]] && git add " $GO_SUM "
8498
8599# ---- Commit -----------------------------------------------------------------
86100COMMIT_MSG=" Bump Go to $TOOLCHAIN_VERSION "
@@ -90,31 +104,43 @@ COMMIT_HASH=$(git rev-parse --short HEAD)
90104PR_TITLE=" $COMMIT_MSG "
91105
92106# ---- Check for existing PR --------------------------------------------------
93- existing_pr=$( gh search prs --repo cli/cli --match title " $PR_TITLE " --json title --jq " map(select(.title == \" $PR_TITLE \" ) | .title) | length > 0" )
107+ existing_pr=$( gh search prs --repo " $REPO " --state open --match title " $PR_TITLE " \
108+ --json title --jq " map(select(.title == \" $PR_TITLE \" ) | .title) | length > 0" )
94109
95110if [[ " $existing_pr " == " true" ]]; then
96111 echo " Found an existing open PR titled '$PR_TITLE '. Skipping push/PR creation."
97112 if [[ $APPLY -eq 0 ]]; then
98- echo -e " \n=== DRY‑ RUN DIFF (commit $COMMIT_HASH ):\n"
113+ echo -e " \n=== DRY- RUN DIFF (commit $COMMIT_HASH ):\n"
99114 git --no-pager show --color " $COMMIT_HASH "
100115 fi
101116 exit 0
102117fi
103118
104- # ---- Dry‑ run handling -------------------------------------------------------
119+ # ---- Dry- run handling -------------------------------------------------------
105120if [[ $APPLY -eq 0 ]]; then
106- echo -e " \n=== DRY‑ RUN DIFF (commit $COMMIT_HASH ):\n"
121+ echo -e " \n=== DRY- RUN DIFF (commit $COMMIT_HASH ):\n"
107122 git --no-pager show --color " $COMMIT_HASH "
108123 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"
109124 exit 0
110125fi
111126
112127# ---- Push & PR --------------------------------------------------------------
128+ FINAL_GO_MOD_JSON=$( go mod edit -json " $GO_MOD " )
129+ FINAL_GO=$( jq -r ' .Go // ""' <<< " $FINAL_GO_MOD_JSON" )
130+ FINAL_TC=$( jq -r ' .Toolchain // ""' <<< " $FINAL_GO_MOD_JSON" )
131+
132+ # Build PR body reflecting final state after tidy
133+ if [[ -n " $FINAL_TC " ]]; then
134+ TC_LINE=" * **toolchain:** \` $FINAL_TC \` "
135+ else
136+ TC_LINE=" * **toolchain:** _(none -- \` go mod tidy\` removed it because the go directive already implies go$TOOLCHAIN_VERSION )_"
137+ fi
138+
113139PR_BODY=$( cat << EOF
114140This PR updates Go to the latest stable release.
115141
116- * **go directive:** \` $GO_DIRECTIVE_VERSION \`
117- * **toolchain:** \` $TOOLCHAIN_VERSION \`
142+ * **go directive:** \` $FINAL_GO \`
143+ $TC_LINE
118144EOF
119145)
120146
0 commit comments