From 5d0040b886000bbf6a141fadac30a7026b5906b5 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 5 May 2026 15:43:36 -0500 Subject: [PATCH] fix: use PR create+merge for cascade (Gitea has no direct merge API) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gitea doesn't support POST /repos/{owner}/{repo}/merges. Rewrites the cascade workflow to: compare branches, create a PR (main → dev), then auto-merge if clean. On conflict the PR stays open for manual resolution. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/cascade-dev.yml | 128 +++++++++++++++++++------------ 1 file changed, 80 insertions(+), 48 deletions(-) diff --git a/.gitea/workflows/cascade-dev.yml b/.gitea/workflows/cascade-dev.yml index 151c6a7..03295f2 100644 --- a/.gitea/workflows/cascade-dev.yml +++ b/.gitea/workflows/cascade-dev.yml @@ -17,8 +17,8 @@ # | Triggers on every push to main (PR merges, bot commits, etc.) | # | | # | 1. Check if a 'dev' branch exists | -# | 2. Attempt API merge (main → dev) | -# | 3. On conflict: create a PR for manual resolution | +# | 2. Create a PR (main → dev) via Gitea API | +# | 3. Auto-merge if clean; leave open for manual resolution on conflict | # | | # +========================================================================+ @@ -68,85 +68,117 @@ jobs: echo "ℹ️ No dev branch found (HTTP ${STATUS}) — skipping cascade" fi - - name: Merge main → dev via API + - name: Check if dev is already up to date if: steps.check.outputs.exists == 'true' - id: merge + id: diff env: GA_TOKEN: ${{ secrets.GA_TOKEN }} run: | API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - COMMIT_MSG="chore: cascade main → dev [skip ci]" - RESPONSE=$(curl -sS -w "\n%{http_code}" \ - -X POST \ + # Compare main..dev — if ahead_by is 0 there's nothing to cascade + RESPONSE=$(curl -sS \ -H "Authorization: token ${GA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{ - \"base\": \"dev\", - \"head\": \"main\", - \"merge_message_field\": \"${COMMIT_MSG}\" - }" \ - "${API}/merges") + "${API}/compare/dev...main") - HTTP_CODE=$(echo "$RESPONSE" | tail -1) - BODY=$(echo "$RESPONSE" | sed '$d') + AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0') - echo "HTTP ${HTTP_CODE}" - - if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then - echo "result=success" >> "$GITHUB_OUTPUT" - echo "✅ main merged into dev successfully" - elif [ "$HTTP_CODE" = "204" ]; then - echo "result=noop" >> "$GITHUB_OUTPUT" + if [ "$AHEAD" -eq 0 ]; then + echo "needs_merge=false" >> "$GITHUB_OUTPUT" echo "✅ dev is already up to date with main" - elif [ "$HTTP_CODE" = "409" ]; then - echo "result=conflict" >> "$GITHUB_OUTPUT" - echo "⚠️ Merge conflict detected — will create PR" else - echo "result=error" >> "$GITHUB_OUTPUT" - echo "❌ Unexpected response: ${BODY}" - exit 1 + echo "needs_merge=true" >> "$GITHUB_OUTPUT" + echo "ℹ️ main is ${AHEAD} commit(s) ahead of dev" fi - - name: Create conflict-resolution PR - if: steps.merge.outputs.result == 'conflict' + - name: Create cascade PR + if: steps.diff.outputs.needs_merge == 'true' + id: pr env: GA_TOKEN: ${{ secrets.GA_TOKEN }} run: | API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" SHORT_SHA="${GITHUB_SHA:0:7}" - # Check if a cascade PR already exists + # Check if a cascade PR already exists (main → dev) EXISTING=$(curl -sS \ -H "Authorization: token ${GA_TOKEN}" \ - "${API}/pulls?state=open&labels=cascade&head=${GITEA_ORG}:main&base=dev&limit=1" \ - | jq 'length') + "${API}/pulls?state=open&head=${GITEA_ORG}:main&base=dev&limit=1") - if [ "$EXISTING" -gt 0 ]; then - echo "ℹ️ Cascade PR already exists — skipping duplicate" + EXISTING_COUNT=$(echo "$EXISTING" | jq 'length') + + if [ "$EXISTING_COUNT" -gt 0 ]; then + PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number') + PR_URL=$(echo "$EXISTING" | jq -r '.[0].html_url') + echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" + echo "pr_exists=true" >> "$GITHUB_OUTPUT" + echo "ℹ️ Cascade PR already exists: ${PR_URL}" + else + RESPONSE=$(curl -sS -w "\n%{http_code}" \ + -X POST \ + -H "Authorization: token ${GA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"title\": \"chore: cascade main → dev (${SHORT_SHA}) [skip ci]\", + \"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`dev\` to keep branches in sync.\\n\\nIf this PR has conflicts, please resolve them manually and merge.\\n\\n> Auto-created by the **Cascade Main → Dev** workflow.\", + \"head\": \"main\", + \"base\": \"dev\" + }" \ + "${API}/pulls") + + HTTP_CODE=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | sed '$d') + PR_NUMBER=$(echo "$BODY" | jq -r '.number // empty') + PR_URL=$(echo "$BODY" | jq -r '.html_url // empty') + + if [ "$HTTP_CODE" = "201" ] && [ -n "$PR_NUMBER" ]; then + echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" + echo "pr_exists=false" >> "$GITHUB_OUTPUT" + echo "✅ Created cascade PR #${PR_NUMBER}: ${PR_URL}" + else + echo "❌ Failed to create PR (HTTP ${HTTP_CODE}): ${BODY}" + exit 1 + fi + fi + + - name: Auto-merge cascade PR + if: steps.pr.outputs.pr_number != '' + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + run: | + API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + PR_NUMBER="${{ steps.pr.outputs.pr_number }}" + + # Check if PR is mergeable + PR_DATA=$(curl -sS \ + -H "Authorization: token ${GA_TOKEN}" \ + "${API}/pulls/${PR_NUMBER}") + + MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false') + + if [ "$MERGEABLE" != "true" ]; then + echo "⚠️ PR #${PR_NUMBER} has conflicts — leaving open for manual resolution" exit 0 fi - # Create the PR + # Merge the PR RESPONSE=$(curl -sS -w "\n%{http_code}" \ -X POST \ -H "Authorization: token ${GA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ - \"title\": \"chore: cascade main → dev (conflicts)\", - \"body\": \"## Automatic cascade\\n\\nMerging \`main\` (${SHORT_SHA}) into \`dev\` hit merge conflicts.\\n\\nPlease resolve conflicts and merge this PR to keep \`dev\` in sync with \`main\`.\\n\\n> Auto-created by the **Cascade Main → Dev** workflow.\", - \"head\": \"main\", - \"base\": \"dev\" + \"Do\": \"merge\", + \"merge_message_field\": \"chore: cascade main → dev [skip ci]\", + \"delete_branch_after_merge\": false }" \ - "${API}/pulls") + "${API}/pulls/${PR_NUMBER}/merge") HTTP_CODE=$(echo "$RESPONSE" | tail -1) - BODY=$(echo "$RESPONSE" | sed '$d') - PR_URL=$(echo "$BODY" | jq -r '.html_url // empty') - if [ "$HTTP_CODE" = "201" ] && [ -n "$PR_URL" ]; then - echo "✅ Created conflict-resolution PR: ${PR_URL}" + if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then + echo "✅ Cascade PR #${PR_NUMBER} merged — dev is now in sync with main" else - echo "❌ Failed to create PR (HTTP ${HTTP_CODE}): ${BODY}" - exit 1 + BODY=$(echo "$RESPONSE" | sed '$d') + echo "⚠️ Merge failed (HTTP ${HTTP_CODE}): ${BODY}" + echo "PR #${PR_NUMBER} left open for manual resolution" fi -- 2.52.0