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
14 changes: 10 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ on:
#
# The reason it exists: #81 and #83 were each green and merged four minutes apart, and their
# combination broke the net48 leg on `main` (#85). Neither pull request was ever built against
# the other. Nothing below is event-specific, so the same jobs cover this trigger unchanged --
# `github.ref` is the queue's own branch, which keeps the concurrency group per entry, and the
# version job already tolerates a git shape GitVersion cannot name.
# the other. Nothing below is event-specific, so the same jobs cover this trigger unchanged.
# On this event `github.ref` is `refs/heads/gh-readonly-queue/<base>/pr-<n>-<sha>`, a different
# ref for each queue entry, which is what keeps the concurrency group above from cancelling a
# neighbouring entry. GitVersion names that ref (`merge-queue` in GitVersion.yml); a ref it
# still cannot name falls back inside the version job rather than failing the build.
merge_group:
workflow_dispatch:

Expand Down Expand Up @@ -49,7 +51,8 @@ jobs:

# Deliberately allowed to fail. GitVersion cannot version every git shape — a branch with no
# merge base against a release branch, for one — and "we could not name this build" is not a
# reason to refuse to compile it.
# reason to refuse to compile it. A merge-queue ref is not one of those shapes; the step
# below checks that it is named rather than falling through to `unknown`.
- name: Run GitVersion
id: gitversion
continue-on-error: true
Expand All @@ -59,6 +62,9 @@ jobs:
semver=$(dotnet gitversion /showvariable FullSemVer)
echo "semver=$semver" >> "$GITHUB_OUTPUT"

- name: Verify merge-queue version
run: bash eng/verify-merge-queue-version.sh

- name: Pick the version for this run
id: pick
run: |
Expand Down
24 changes: 24 additions & 0 deletions GitVersion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ next-version: 0.1.0
# tool looks.

branches:
# GitHub's merge queue builds `gh-readonly-queue/main/pr-<n>-<sha>`, a merge of the queued
# pull requests onto `main`. Nothing else here matches that name, so GitVersion 6.8.2 used to
# fall through to the preset's `unknown` branch and still exit 0 — the version job's
# unversioned fallback never ran. FullSemVer was then
# `1.2.4-gh-readonly-queue-main-pr-<n>-<sha>.1+<commits>`: the whole ref as the prerelease,
# plus build metadata. `dotnet pack -p:Version=` keeps the `+<commits>` in the nuspec and
# drops it from the package filename. `main` and `pull-request` are named without metadata
# (`1.2.4-ci.<n>`, `1.2.4-pr.<n>`). This branch does the same for a queue entry:
# `1.2.4-queue.<n>`.
#
# `[/-]` is there so the regex matches whichever spelling 6.8.2 compares against: the raw ref,
# slashes included, or the escaped one with those slashes already turned into hyphens.
# eng/verify-merge-queue-version.sh builds a two-pull-request queue ref and checks the result.
merge-queue:
mode: ContinuousDelivery
label: queue
increment: Patch
prevent-increment:
of-merged-branch: true
track-merge-target: false
track-merge-message: true
regex: '^gh-readonly-queue[/-]main[/-]'
source-branches:
- main
main:
# `label: ci` rather than '': a CI build of main is not a release, and a bare `1.2.4+12` in an
# artifact list reads exactly like one. With the label it is `1.2.4-ci.12` and says so, which
Expand Down
108 changes: 108 additions & 0 deletions eng/verify-merge-queue-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
#
# Checks that a GitHub merge-queue ref gets a FullSemVer the rest of CI can pass to
# `dotnet build -p:Version=` unchanged.
#
# The queue has never been enabled, so no `merge_group` run has ever exercised this. The ref
# GitHub will build is `gh-readonly-queue/main/pr-<n>-<sha>`, and a group of two pull requests —
# the shape behind #85 — is a chain of merge commits on that ref. Left unmatched, GitVersion
# 6.8.2 still exits 0 and names it `1.2.4-gh-readonly-queue-main-pr-<n>-<sha>.1+<commits>`.
# `GitVersion.yml`'s `merge-queue` branch is what turns that into `1.2.4-queue.<n>`, with no
# build metadata, the same shape as a pull request (`1.2.4-pr.<n>`) and as main between
# releases (`1.2.4-ci.<n>`). A tagged release commit on main is the stable tag instead
# (`1.2.3`); that result is accepted below.
#
# Run from anywhere in the clone. `dotnet tool restore` must already have succeeded (the version
# job does that immediately above this script). The synthetic ref is built in a throwaway clone
# so this does not create branches in the checkout it was launched from.

set -euo pipefail

root=$(git rev-parse --show-toplevel)
tmp=$(mktemp -d)
cleanup() {
rm -rf "$tmp"
}
trap cleanup EXIT

git -c advice.detachedHead=false clone --shared --quiet "$root" "$tmp/repo"
cp "$root/GitVersion.yml" "$tmp/repo/GitVersion.yml"

# `actions/checkout` on a pull request leaves the workspace detached, with `main` only as
# `origin/main`. `git clone` does not copy remote-tracking refs, so the throwaway clone would
# then have no `main` to build the queue ref on. Take the commit from whichever spelling the
# workspace has and check it out as a local `main` in the clone.
if git -C "$root" show-ref --verify --quiet refs/heads/main; then
main_sha=$(git -C "$root" rev-parse refs/heads/main)
elif git -C "$root" show-ref --verify --quiet refs/remotes/origin/main; then
main_sha=$(git -C "$root" rev-parse refs/remotes/origin/main)
else
echo "cannot find main (refs/heads/main or refs/remotes/origin/main)" >&2
exit 1
fi

cd "$tmp/repo"
git checkout -q -B main "$main_sha"
git checkout -q -B mq-sim-a main
echo a > mq-sim-a.txt
git add mq-sim-a.txt
git -c user.email="merge-queue-verify@example.com" -c user.name="merge-queue-verify" \
commit -q -m "sim: first queued pull request"

git checkout -q -B mq-sim-b main
echo b > mq-sim-b.txt
git add mq-sim-b.txt
git -c user.email="merge-queue-verify@example.com" -c user.name="merge-queue-verify" \
commit -q -m "sim: second queued pull request"

# Forty hex characters: the longest sha GitHub puts in the ref.
suffix=$(printf 'a%.0s' {1..40})
queue="gh-readonly-queue/main/pr-0-${suffix}"
git checkout -q -B "$queue" main
git -c user.email="merge-queue-verify@example.com" -c user.name="merge-queue-verify" \
merge --no-ff mq-sim-a -q -m "Merge pull request #1"
git -c user.email="merge-queue-verify@example.com" -c user.name="merge-queue-verify" \
merge --no-ff mq-sim-b -q -m "Merge pull request #2"

queue_sha=$(git rev-parse HEAD)

# GitVersion on a GitHub-hosted runner reads GITHUB_REF rather than the checked-out branch, and
# this script is itself running inside that environment on a pull request. Point the variables at
# the synthetic ref for the queue case, and strip them for main, or both calls version whichever
# pull request launched CI.
queue_full=$(
GITHUB_ACTIONS=true \
GITHUB_EVENT_NAME=merge_group \
GITHUB_REF="refs/heads/${queue}" \
GITHUB_REF_NAME="${queue}" \
GITHUB_SHA="${queue_sha}" \
GITHUB_WORKSPACE="${tmp}/repo" \
dotnet gitversion "${tmp}/repo" /config "${root}/GitVersion.yml" /showvariable FullSemVer
)

git checkout -q -B main "$main_sha"
main_full=$(
env -u GITHUB_ACTIONS -u GITHUB_REF -u GITHUB_REF_NAME -u GITHUB_SHA \
-u GITHUB_HEAD_REF -u GITHUB_BASE_REF -u GITHUB_EVENT_NAME \
dotnet gitversion "${tmp}/repo" /config "${root}/GitVersion.yml" /showvariable FullSemVer
)

queue_pattern='^[0-9]+\.[0-9]+\.[0-9]+-queue\.[0-9]+$'
# Between releases main is `1.2.4-ci.<n>`. The release commit itself is the tag, and GitVersion
# reports that stable version (`1.2.3` on `v1.2.3`) because the main branch config does not
# increment a tagged commit. This script runs in every version job, so refusing the tag would
# fail every pull request for as long as that commit stayed at the tip of main. A queue label
# still does not match.
main_pattern='^[0-9]+\.[0-9]+\.[0-9]+(-ci\.[0-9]+)?$'

if [[ ! "$queue_full" =~ $queue_pattern ]]; then
echo "merge-queue ref versioned as '${queue_full}', expected ${queue_pattern}" >&2
exit 1
fi
if [[ ! "$main_full" =~ $main_pattern ]]; then
echo "main versioned as '${main_full}', expected X.Y.Z or X.Y.Z-ci.N" >&2
exit 1
Comment thread
cursor[bot] marked this conversation as resolved.
fi

echo "merge-queue ref: ${queue_full}"
echo "main: ${main_full}"
Loading