Skip to content

Commit 8843313

Browse files
committed
build: release.sh has smart checkouts
1 parent fa82a2a commit 8843313

File tree

1 file changed

+189
-22
lines changed

1 file changed

+189
-22
lines changed

release.sh

Lines changed: 189 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,85 @@
22

33
set -euo pipefail
44

5+
export GIT_PAGER=cat
6+
export PAGER=cat
7+
export LESS=-F
8+
9+
ORIG_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
10+
ORIG_COMMIT=""
11+
if [ "$ORIG_BRANCH" = "HEAD" ]; then
12+
ORIG_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "")
13+
fi
14+
AUTO_STASHES=()
15+
RESTORE_COMPLETED=0
16+
ORIG_UNTRACKED=()
17+
while IFS=$'\0' read -r -d '' path; do
18+
ORIG_UNTRACKED+=("$path")
19+
done < <(git ls-files --others --exclude-standard -z 2>/dev/null || printf '')
20+
21+
restore_workspace() {
22+
local restore_messages=()
23+
24+
if [ -n "$ORIG_BRANCH" ] && [ "$ORIG_BRANCH" != "HEAD" ]; then
25+
local current_branch
26+
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
27+
if [ -n "$current_branch" ] && [ "$current_branch" != "$ORIG_BRANCH" ]; then
28+
if git checkout "$ORIG_BRANCH" >/dev/null 2>&1; then
29+
restore_messages+=("Returned to original branch $ORIG_BRANCH.")
30+
else
31+
echo "Warning: failed to return to branch $ORIG_BRANCH. Current branch: $current_branch."
32+
fi
33+
fi
34+
elif [ -n "$ORIG_COMMIT" ]; then
35+
local current_ref
36+
current_ref=$(git rev-parse HEAD 2>/dev/null || echo "")
37+
if [ -n "$current_ref" ] && [ "$current_ref" != "$ORIG_COMMIT" ]; then
38+
if git checkout --detach "$ORIG_COMMIT" >/dev/null 2>&1; then
39+
restore_messages+=("Returned to detached HEAD at $ORIG_COMMIT.")
40+
else
41+
echo "Warning: failed to return to original commit $ORIG_COMMIT."
42+
fi
43+
fi
44+
fi
45+
46+
for (( idx=${#AUTO_STASHES[@]}-1; idx>=0; idx-- )); do
47+
local stash_ref=${AUTO_STASHES[idx]}
48+
if [ -n "$stash_ref" ]; then
49+
if git stash pop "$stash_ref" >/dev/null 2>&1; then
50+
restore_messages+=("Restored auto-stash $stash_ref.")
51+
else
52+
echo "Warning: failed to restore auto-stash $stash_ref. Run 'git stash pop $stash_ref' manually."
53+
fi
54+
fi
55+
done
56+
57+
if [ ${#ORIG_UNTRACKED[@]} -gt 0 ]; then
58+
for path in "${ORIG_UNTRACKED[@]}"; do
59+
if git ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
60+
git update-index --force-remove -- "$path" >/dev/null 2>&1
61+
fi
62+
done
63+
fi
64+
65+
if [ ${#restore_messages[@]} -gt 0 ]; then
66+
printf '%s\n' "${restore_messages[@]}"
67+
fi
68+
69+
RESTORE_COMPLETED=1
70+
}
71+
72+
cleanup_release_context() {
73+
local exit_code=$?
74+
set +e
75+
76+
if [ "$RESTORE_COMPLETED" -ne 1 ]; then
77+
restore_workspace
78+
fi
79+
80+
exit $exit_code
81+
}
82+
trap cleanup_release_context EXIT
83+
584
ask_consent() {
685
local explanation=$1
786
shift
@@ -29,15 +108,72 @@ require_clean_worktree() {
29108
fi
30109
}
31110

111+
32112
ensure_checkout_safe() {
33113
local target=$1
34-
if ! git checkout --dry-run "$target" >/dev/null 2>&1; then
35-
echo "Checkout preview detected conflicts (tracked files or untracked files would be overwritten)."
36-
echo "Resolve or stash those files before continuing."
114+
115+
if ! git rev-parse --verify "$target" >/dev/null 2>&1; then
116+
echo "Unable to verify target ref $target. Ensure the branch exists locally."
117+
exit 1
118+
fi
119+
120+
local tracked_blockers
121+
tracked_blockers=$(git status --porcelain --untracked-files=no)
122+
if [ -n "$tracked_blockers" ]; then
123+
echo "Tracked changes detected that would block switching to $target."
124+
echo "Tracked files (showing up to 20):"
125+
printf '%s\n' "$tracked_blockers" | head -n 20 | sed 's/^/ - /'
126+
if [ "$(printf '%s\n' "$tracked_blockers" | wc -l | tr -d ' ')" -gt 20 ]; then
127+
echo " - ... (additional files omitted)"
128+
fi
129+
echo "Clean or stash the tracked changes before continuing."
130+
exit 1
131+
fi
132+
133+
local untracked_blockers=()
134+
while IFS= read -r -d '' path; do
135+
untracked_blockers+=("$path")
136+
done < <(git ls-files --others --exclude-standard -z)
137+
138+
if [ ${#untracked_blockers[@]} -eq 0 ]; then
139+
return 0
140+
fi
141+
142+
echo "Checkout preview detected untracked files that could be overwritten when switching to $target."
143+
echo "Affected untracked files (showing up to 20):"
144+
local idx=0
145+
for path in "${untracked_blockers[@]}"; do
146+
idx=$((idx + 1))
147+
if [ $idx -le 20 ]; then
148+
echo " - $path"
149+
fi
150+
done
151+
if [ $idx -gt 20 ]; then
152+
echo " - ... ($idx total files)"
153+
fi
154+
155+
echo "Auto-stashing untracked files before retrying checkout."
156+
local stash_marker
157+
stash_marker="$(date +%s)-$$-$RANDOM"
158+
local stash_message="release.sh:auto-stash:$target:$stash_marker"
159+
if git stash push --include-untracked --message "$stash_message" >/dev/null 2>&1; then
160+
local stash_ref
161+
stash_ref=$(git stash list | awk -F: -v msg="$stash_message" '$0 ~ msg {print $1; exit}')
162+
if [ -n "$stash_ref" ]; then
163+
AUTO_STASHES+=("$stash_ref")
164+
else
165+
echo "Warning: auto-stashed files recorded but stash reference could not be determined."
166+
fi
167+
echo "Untracked files stashed temporarily; they will be restored after release.sh completes."
168+
ensure_checkout_safe "$target"
169+
return
170+
else
171+
echo "Failed to auto-stash untracked files. Resolve them manually and rerun the script."
37172
exit 1
38173
fi
39174
}
40175

176+
41177
# Step 1: Get the first argument from the command line
42178
echo "==== Read Tag ===="
43179
TAG=${1:-}
@@ -68,13 +204,34 @@ if [ -z "$TAG" ]; then
68204
MINOR_TAG="v${MAJOR}.${MINOR_SUGGESTED}.0"
69205
PATCH_TAG="v${MAJOR}.${MINOR}.${PATCH_SUGGESTED}"
70206
echo "Suggested tags:"
71-
echo " - Minor bump (includes these features): $MINOR_TAG"
72-
echo " - Patch bump: $PATCH_TAG"
73-
read -r -p "Enter desired tag (copy one of the suggestions or provide a custom value): " TAG
74-
if [ -z "$TAG" ]; then
75-
TAG=$PATCH_TAG
76-
echo "Defaulting to patch bump: $TAG"
77-
fi
207+
echo " 1) Minor bump (includes these features): $MINOR_TAG"
208+
echo " 2) Patch bump: $PATCH_TAG"
209+
echo " 3) Enter custom tag"
210+
while true; do
211+
read -r -p "Selection [1-3, default 2]: " TAG_CHOICE
212+
case "$TAG_CHOICE" in
213+
""|"2")
214+
TAG=$PATCH_TAG
215+
echo "Using patch bump: $TAG"
216+
break
217+
;;
218+
"1")
219+
TAG=$MINOR_TAG
220+
echo "Using minor bump: $TAG"
221+
break
222+
;;
223+
"3")
224+
read -r -p "Enter custom tag (vX.Y.Z): " TAG
225+
if [ -n "$TAG" ]; then
226+
break
227+
fi
228+
echo "Custom tag cannot be empty."
229+
;;
230+
*)
231+
echo "Invalid selection. Enter 1, 2, or 3."
232+
;;
233+
esac
234+
done
78235
else
79236
TAG="v${MAJOR}.${MINOR}.${PATCH_SUGGESTED}"
80237
read -r -p "Suggested tag is $TAG (no feature commits detected). Is this appropriate? (y/n): " CONFIRM
@@ -95,6 +252,23 @@ VERSION=${TAG#v}
95252

96253
require_clean_worktree "Resolve tracked changes before starting the release (common causes: leftover ./version.sh runs or generated dist files)."
97254

255+
# Step 4: Run dependency audit, update dependencies, update versions, and rebuild bundles
256+
echo "==== Dependency audit ===="
257+
ask_consent "Run depcheck across all actions to verify dependency health." ./depcheck.sh
258+
require_clean_worktree "Depcheck introduced tracked changes. Review and commit or stash them before continuing."
259+
260+
echo "==== Dependency updates ===="
261+
ask_consent "Run update-dependencies to refresh package versions before release." ./update-dependencies.sh
262+
require_clean_worktree "Dependency updates introduced tracked changes. Review and commit or stash them before continuing."
263+
264+
echo "==== Update versions ===="
265+
ask_consent "Update package.json versions to $VERSION across all actions." ./version.sh "$VERSION"
266+
require_clean_worktree "Version update introduced tracked changes. Review and commit or stash them before continuing."
267+
268+
echo "==== Rebuild bundles ===="
269+
ask_consent "Regenerate dist outputs so release $TAG contains fresh builds." ./build.sh
270+
require_clean_worktree "Build step introduced tracked changes. Review and commit or stash them before continuing."
271+
98272
# Step 4: Check out the develop branch locally and check if it matches the remote
99273
echo "==== Develop matches remote ===="
100274
ask_consent "Fetch latest refs from origin to ensure develop is up to date before tagging." git fetch origin
@@ -104,6 +278,7 @@ if [ "$LOCAL_DEVELOP" != "$REMOTE_DEVELOP" ]; then
104278
echo "Local develop branch is not up to date with remote. "
105279
echo "Local develop: $LOCAL_DEVELOP."
106280
git log -1 --pretty=format:"%s" refs/heads/develop
281+
echo "\n"
107282
echo "Remote develop: $REMOTE_DEVELOP."
108283
git log -1 --pretty=format:"%s" origin/develop
109284
echo "Exiting"
@@ -134,18 +309,6 @@ fi
134309

135310
require_clean_worktree "Rebase introduced tracked changes (e.g., conflict resolution artifacts). Review and resolve them before proceeding."
136311

137-
echo "==== Dependency audit ===="
138-
ask_consent "Run depcheck across all actions to verify dependency health." ./depcheck.sh
139-
140-
echo "==== Dependency updates ===="
141-
ask_consent "Run update-dependencies to refresh package versions before release." ./update-dependencies.sh
142-
143-
echo "==== Update versions ===="
144-
ask_consent "Update package.json versions to $VERSION across all actions." ./version.sh "$VERSION"
145-
146-
echo "==== Rebuild bundles ===="
147-
ask_consent "Regenerate dist outputs so release $TAG contains fresh builds." ./build.sh
148-
149312
echo "==== Stage release artifacts ===="
150313
if [ -z "$(git status --porcelain)" ]; then
151314
echo "No changes detected after dependency updates and rebuild. Exiting to avoid an empty release commit."
@@ -170,4 +333,8 @@ ask_consent "Create local tag $TAG so the release can be pushed." git tag "$TAG"
170333
echo "==== Push tag ===="
171334
ask_consent "Push tag $TAG to origin to make the release available to users." git push origin "$TAG"
172335

336+
# Step 9: Restore original branch
337+
echo "==== Restore original branch ===="
338+
restore_workspace
339+
173340
echo "Tag $TAG has been created and pushed to remote."

0 commit comments

Comments
 (0)