Skip to content

Commit 80865e6

Browse files
authored
Replace release-* GitHub workflows with scripts (#952)
We tried, really tried, to reduce maintenance burden by offloading as many of the repetitive, error-prone manual tasks to automation. That worked for a while, but a recent tally revealed that we're spending more time on adapting those GitHub workflows to ever-changing requirements (even having to go so far as re-converting tediously-converted Azure Pipelines from GitHub workflows _back_ to once again being Azure Pipelines) than we save on running those tasks manually. Rather than repeating that exercise with the release-triggered automation to update winget, Homebrew and VFS for Git, let's write off all those adaptations as irrecoverable sunk cost, then, delete the GitHub workflows, and settle on a hopefully sustainable middle-ground: shell scripts relying on manual invocations.
2 parents c96c03f + cfe415c commit 80865e6

6 files changed

Lines changed: 447 additions & 331 deletions

File tree

.github/release-homebrew.sh

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/bin/sh
2+
#
3+
# Promote a microsoft/git release into the microsoft/homebrew-git tap.
4+
#
5+
# Usage:
6+
# .github/release-homebrew.sh [--force] [<TAG_NAME>]
7+
#
8+
# If TAG_NAME is omitted, the latest microsoft/git release is used.
9+
#
10+
# Downgrades require `--force`.
11+
#
12+
# Prerequisites:
13+
# - `gh` authenticated (via `gh auth login`) as a user with push
14+
# access to microsoft/homebrew-git.
15+
# - `git`, `jq`, and `sed` on PATH.
16+
#
17+
# Given a release tag on microsoft/git (e.g. v2.54.0.vfs.0.4), this
18+
# script looks up the macOS installer asset for that tag, extracts the
19+
# SHA-256 digest reported by the GitHub Releases API (deliberately not
20+
# re-hashed locally; see microsoft/homebrew-git#102), edits the
21+
# `microsoft-git` cask in place preserving its indentation and quote
22+
# style, pushes the update to the tap under a datetime-keyed branch,
23+
# and opens a PR.
24+
#
25+
# This mirrors the behaviour of mjcheetham/update-homebrew@v1.5.1
26+
# invoked with `type: cask`, `alwaysUsePullRequest: true`.
27+
28+
set -eu
29+
30+
die () {
31+
echo "error: $*" >&2
32+
exit 1
33+
}
34+
35+
case "$1" in
36+
--force) force=t; shift;;
37+
*) force=;;
38+
esac
39+
40+
TAG_NAME=${1-}
41+
if [ -z "$TAG_NAME" ]; then
42+
echo "==> No tag given; resolving latest microsoft/git release"
43+
TAG_NAME=$(gh release view -R microsoft/git \
44+
--json tagName --jq .tagName)
45+
test -n "$TAG_NAME" || die "could not determine latest release tag"
46+
fi
47+
48+
echo "==> Tag: $TAG_NAME"
49+
50+
version=${TAG_NAME#v}
51+
echo "==> Version: $version"
52+
53+
# Refuse to downgrade the cask and short-circuit no-op runs. Look up
54+
# the version currently in the tap and compare before fetching the
55+
# release JSON or cloning.
56+
current_content=$(gh api \
57+
repos/microsoft/homebrew-git/contents/Casks/microsoft-git.rb \
58+
-H "Accept: application/vnd.github.raw")
59+
current_version=$(printf '%s\n' "$current_content" | sed -nE \
60+
"s/^[[:space:]]*version *['\"]([^'\"]*)['\"].*/\\1/p")
61+
test -n "$current_version" || die "could not parse current cask version"
62+
echo "==> Current: $current_version"
63+
64+
if [ "$version" = "$current_version" ]; then
65+
echo "warning: cask is already at $version; nothing to do." >&2
66+
exit 0
67+
fi
68+
lowest=$(printf '%s\n%s\n' "$version" "$current_version" |
69+
sort -V | sed 1q)
70+
if [ "$lowest" = "$version" ]; then
71+
test -n "$force" ||
72+
die "regression: cask is at $current_version," \
73+
"refusing to downgrade to $version"
74+
echo "warning: **downgrading** from $current_version to $version" >&2
75+
fi
76+
77+
echo "==> Fetching release metadata"
78+
release_json=$(gh api \
79+
-H "Accept: application/vnd.github+json" \
80+
-H "X-GitHub-Api-Version: 2022-11-28" \
81+
"repos/microsoft/git/releases/tags/$TAG_NAME")
82+
83+
asset_pattern='git-(.*)\.pkg'
84+
asset_json=$(jq -n \
85+
--argjson release "$release_json" \
86+
--arg pat "$asset_pattern" '
87+
[ $release.assets[] | select(.name | test($pat)) ] as $matches
88+
| if ($matches | length) == 0 then
89+
error("no asset matches pattern \($pat)")
90+
elif ($matches | length) > 1 then
91+
error("multiple assets match pattern \($pat): " +
92+
([$matches[].name] | join(", ")))
93+
else $matches[0] end')
94+
95+
digest=$(jq -n -r --argjson a "$asset_json" '$a.digest // ""')
96+
case "$digest" in
97+
sha256:*) sha256=${digest#sha256:} ;;
98+
"") die "asset has no 'digest' field" ;;
99+
*) die "asset digest is not sha256: $digest" ;;
100+
esac
101+
102+
# Enforce 64 lowercase hex chars without spawning grep.
103+
*[!0-9a-f]*|"")
104+
esac
105+
test ${#sha256} -eq 64 ||
106+
die "asset digest is not 64 chars long: $sha256"
107+
108+
echo "==> Asset: $(jq -n -r --argjson a "$asset_json" '$a.name')"
109+
echo "==> SHA-256: $sha256"
110+
111+
workdir=$(mktemp -d)
112+
trap 'rm -rf "$workdir"' EXIT
113+
114+
echo "==> Cloning microsoft/homebrew-git"
115+
REPO=microsoft/homebrew-git
116+
gh repo clone "$REPO" "$workdir/homebrew-git" -- \
117+
--depth=1 --quiet
118+
119+
cd "$workdir/homebrew-git"
120+
121+
# Preserve existing indentation and quote style, replacing only the
122+
# value; matches mjcheetham/update-homebrew's setField regex. The
123+
# `<file >file.new && mv -f file.new file` idiom sidesteps the
124+
# incompatible `sed -i` spellings between GNU and BSD sed.
125+
f=Casks/microsoft-git.rb
126+
# Capture opening quote as \2, match value up to the matching quote.
127+
q='(['\''"])[^'\''"]+\2'
128+
sed -E \
129+
-e "s/^([[:space:]]*)version +$q/\\1version \\2$version\\2/" \
130+
-e "s/^([[:space:]]*)sha256 +$q/\\1sha256 \\2$sha256\\2/" \
131+
<"$f" >"$f.new" &&
132+
mv -f "$f.new" "$f"
133+
134+
if git diff --quiet -- Casks/microsoft-git.rb; then
135+
echo "==> No changes needed; cask is already at $version."
136+
exit 0
137+
fi
138+
139+
git --no-pager diff -- Casks/microsoft-git.rb
140+
141+
BRANCH=update-$(date +%Y-%m-%d-%H-%M-%S)
142+
git switch -c $BRANCH
143+
TITLE="microsoft-git: update to $version"
144+
git commit -m "$TITLE" \
145+
-- Casks/microsoft-git.rb
146+
147+
git push origin HEAD
148+
149+
echo "==> Pushed: $(git log -1 --format='%h %s')"
150+
151+
pr_url=$(gh pr create \
152+
--repo "$REPO" \
153+
--head "$BRANCH" \
154+
--title "$TITLE")
155+
156+
echo "==> Created: $pr_url"

.github/release-vfsforgit.sh

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/bin/sh
2+
#
3+
# Promote a microsoft/git release into the microsoft/VFSForGit repo.
4+
#
5+
# Usage:
6+
# .github/release-vfsforgit.sh [--force] [<TAG_NAME>]
7+
#
8+
# If TAG_NAME is omitted, the latest microsoft/git release is used.
9+
#
10+
# Downgrades require `--force`.
11+
#
12+
# Prerequisites:
13+
# - `gh` authenticated (via `gh auth login`) as a user with push
14+
# access to microsoft/VFSForGit.
15+
# - `git` and `sed` on PATH.
16+
#
17+
# Given a release tag on microsoft/git (e.g. v2.54.0.vfs.0.4), this
18+
# script opens a pull request against microsoft/VFSForGit that bumps
19+
# the `GIT_VERSION` default in `.github/workflows/build.yaml` so that
20+
# VFSForGit builds pick up the newly promoted release by default.
21+
22+
set -eu
23+
24+
die () {
25+
echo "error: $*" >&2
26+
exit 1
27+
}
28+
29+
case "$1" in
30+
--force) force=t; shift;;
31+
*) force=;;
32+
esac
33+
34+
TAG_NAME=${1-}
35+
if [ -z "$TAG_NAME" ]; then
36+
echo "==> No tag given; resolving latest microsoft/git release"
37+
TAG_NAME=$(gh release view -R microsoft/git \
38+
--json tagName --jq .tagName)
39+
test -n "$TAG_NAME" || die "could not determine latest release tag"
40+
fi
41+
42+
echo "==> Tag: $TAG_NAME"
43+
44+
REPO=microsoft/VFSForGit
45+
BRANCH="automation/gitrelease-$TAG_NAME"
46+
FILE=.github/workflows/build.yaml
47+
RELEASE_URL="https://github.com/microsoft/git/releases/tag/$TAG_NAME"
48+
49+
# Refuse to downgrade and short-circuit no-op runs. Read the current
50+
# GIT_VERSION default straight from build.yaml on the VFSForGit
51+
# default branch before cloning anything.
52+
current_content=$(gh api "repos/$REPO/contents/$FILE" \
53+
-H "Accept: application/vnd.github.raw")
54+
current_tag=$(printf '%s\n' "$current_content" | sed -nE \
55+
"/GIT_VERSION/s/.*\\|\\| *'([^']*)' *\\}\\}.*/\\1/p")
56+
test -n "$current_tag" || die "could not parse current GIT_VERSION"
57+
echo "==> Current: $current_tag"
58+
59+
if [ "$TAG_NAME" = "$current_tag" ]; then
60+
echo "warning: GIT_VERSION is already $TAG_NAME; nothing to do." >&2
61+
exit 0
62+
fi
63+
lowest=$(printf '%s\n%s\n' "$TAG_NAME" "$current_tag" |
64+
sort -V | sed 1q)
65+
if [ "$lowest" = "$TAG_NAME" ]; then
66+
test -n "$force" ||
67+
die "regression: GIT_VERSION is $current_tag," \
68+
"refusing to downgrade to $TAG_NAME"
69+
echo "warning: **downgrading** from $current_tag to $TAG_NAME" >&2
70+
fi
71+
72+
workdir=$(mktemp -d)
73+
trap 'rm -rf "$workdir"' EXIT
74+
75+
echo "==> Sparse-cloning $REPO"
76+
gh repo clone "$REPO" "$workdir/vfsforgit" -- \
77+
--filter=blob:none --no-checkout --depth=1 --quiet
78+
cd "$workdir/vfsforgit"
79+
git sparse-checkout set "$FILE"
80+
git checkout -b "$BRANCH" --quiet
81+
82+
echo "==> Bumping GIT_VERSION in $FILE"
83+
sed "/GIT_VERSION/s/|| '[^']*' }}/|| '$TAG_NAME' }}/" \
84+
<"$FILE" >"$FILE.new" &&
85+
mv -f "$FILE.new" "$FILE"
86+
87+
git --no-pager diff -- "$FILE"
88+
89+
git commit -m "Update default Microsoft Git version to $TAG_NAME" \
90+
-- "$FILE"
91+
92+
git push origin "$BRANCH"
93+
94+
pr_body="Update the default Microsoft Git version used by VFS for Git
95+
to the newly promoted [\`$TAG_NAME\`]($RELEASE_URL) release."
96+
97+
pr_url=$(gh pr create \
98+
--repo "$REPO" \
99+
--head "$BRANCH" \
100+
--title "Update default Microsoft Git version to $TAG_NAME" \
101+
--body "$pr_body")
102+
103+
echo "==> Created: $pr_url"

0 commit comments

Comments
 (0)