From f90c76911a1f09c7df5d616c3847784922dd35cb Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Sun, 31 May 2026 16:16:44 -0500 Subject: [PATCH 1/2] fix(ci): push detached HEAD to master using HEAD:master refspec The changelog job checks out a specific SHA (detached HEAD) then commits CHANGELOG.md and tries to push with 'git push origin master'. Since there is no local branch named 'master', git rejects the push with 'src refspec master does not match any'. Fix: use 'git push origin HEAD:master' which explicitly maps the current detached HEAD to the remote master branch regardless of local branch state. --- .gitea/workflows/auto-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/auto-tag.yml b/.gitea/workflows/auto-tag.yml index 72b47f0e..6a9d035e 100644 --- a/.gitea/workflows/auto-tag.yml +++ b/.gitea/workflows/auto-tag.yml @@ -206,7 +206,7 @@ jobs: fi git add CHANGELOG.md git commit -m "chore: update CHANGELOG.md for ${TAG} [skip ci]" || echo "No changes to commit" - git push origin master + git push origin HEAD:master echo "✓ CHANGELOG.md committed to master" - name: Upload CHANGELOG.md as release asset From 34a69620f52dd40cfccb5daa6d05a619ffc3e949 Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Sun, 31 May 2026 16:26:31 -0500 Subject: [PATCH 2/2] fix(ci): consolidate all auto-tag changelog fixes Three issues addressed together: 1. Race condition (was PR #56): changelog job now CREATES the Gitea release rather than assuming build jobs have already created it. Build jobs continue to use create-or-skip + upload unchanged. 2. Detached HEAD push: 'git push origin master' fails when HEAD is detached (no local branch named master). Changed to 'HEAD:master'. 3. git-cliff tag guard: verify tag is present locally before running git-cliff, to fail fast with a clear message rather than silently generating a wrong changelog. 4. git commit idiom: replaced 'git commit || echo' (swallows all non-zero exit codes including real failures) with an explicit 'git diff --staged --quiet' guard so set -euo pipefail is not undermined. --- .gitea/workflows/auto-tag.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.gitea/workflows/auto-tag.yml b/.gitea/workflows/auto-tag.yml index 6a9d035e..4e043d42 100644 --- a/.gitea/workflows/auto-tag.yml +++ b/.gitea/workflows/auto-tag.yml @@ -125,11 +125,10 @@ jobs: RELEASE_TAG: ${{ needs.autotag.outputs.release_tag }} run: | set -eu - # Use the tag output from autotag — never rely on git describe CURRENT_TAG="${RELEASE_TAG}" echo "Building changelog for $CURRENT_TAG" - # Verify the tag is present locally after fetch before running git-cliff + # Verify the tag is present locally after fetch if ! git rev-parse "refs/tags/${CURRENT_TAG}" >/dev/null 2>&1; then echo "ERROR: tag ${CURRENT_TAG} not found locally after fetch" exit 1 @@ -141,7 +140,7 @@ jobs: if [ -n "$PREV_TAG" ]; then git-cliff --config cliff.toml --tag "$CURRENT_TAG" --strip all > /tmp/release_body.md || true else - echo "=== No previous tag found, generating from git commits ===" + echo "No previous tag found, generating from git commits" git log --pretty=format:"- %s" > /tmp/release_body.md || true fi echo "=== Release body preview ===" @@ -155,16 +154,14 @@ jobs: set -eu TAG="${RELEASE_TAG}" API="http://172.0.0.29:3000/api/v1/repos/$GITHUB_REPOSITORY" - RELEASE_BODY=$(cat /tmp/release_body.md) # Try to find an existing release for this tag RELEASE_ID=$(curl -s "$API/releases/tags/$TAG" \ -H "Authorization: token $RELEASE_TOKEN" | jq -r '.id // empty') if [ -z "$RELEASE_ID" ]; then - # Release doesn't exist yet — create it with the changelog body. - # Build jobs run in parallel and rely on the release existing; - # creating it here ensures no race condition. + # First run: changelog job owns release creation so build jobs + # never race against a missing release object echo "Creating release $TAG..." RELEASE_ID=$(jq -n \ --arg tag "$TAG" \ @@ -178,7 +175,7 @@ jobs: | jq -r '.id') echo "✓ Release created (id=$RELEASE_ID)" else - # Release already exists (e.g. re-run) — patch the body only + # Re-run: patch the body only echo "Updating existing release $TAG (id=$RELEASE_ID)..." jq -n --rawfile body /tmp/release_body.md '{body: $body}' \ | curl -sf -X PATCH "$API/releases/$RELEASE_ID" \ @@ -199,13 +196,20 @@ jobs: run: | set -euo pipefail TAG="${RELEASE_TAG}" - # Validate tag format to prevent shell injection in commit message / JSON if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then echo "ERROR: Unexpected tag format: $TAG" exit 1 fi git add CHANGELOG.md - git commit -m "chore: update CHANGELOG.md for ${TAG} [skip ci]" || echo "No changes to commit" + # Only commit if CHANGELOG.md actually changed — avoids ambiguous + # exit-code handling from 'git commit || echo' with set -e + if git diff --staged --quiet; then + echo "No CHANGELOG.md changes to commit" + else + git commit -m "chore: update CHANGELOG.md for ${TAG} [skip ci]" + fi + # HEAD:master works in detached HEAD state; 'git push origin master' + # would fail because there is no local branch named master git push origin HEAD:master echo "✓ CHANGELOG.md committed to master"