diff --git a/.forgejo/kcl-ci/main.k b/.forgejo/kcl-ci/main.k index dd02c93..a99dbba 100644 --- a/.forgejo/kcl-ci/main.k +++ b/.forgejo/kcl-ci/main.k @@ -233,6 +233,8 @@ _merge_branch_ref = h.gha_steps("prepare", "branch") _merge_behind_ref = h.gha_needs("check-upstream", "behind") _merge_ahead_ref = h.gha_needs("check-upstream", "ahead") _merge_issue_ref = h.gha_needs("check-upstream", "issue_number") +_merge_existing_pr_ref = h.gha_steps("existing-pr", "pr_exists") +_merge_existing_pr_number_ref = h.gha_steps("existing-pr", "pr_number") _merge_clean_ref = h.gha_steps("prepare", "merge_clean") _merge_affected_ref = h.gha_steps("prepare", "affected_files") @@ -243,6 +245,7 @@ You are automating an upstream sync for our Forgejo fork of anthropics/claude-co - Branch: ${_merge_branch_ref} - ${_merge_behind_ref} commits behind upstream, ${_merge_ahead_ref} commits ahead (our Forgejo changes) - Tracking issue: #${_merge_issue_ref} +- Existing PR: ${_merge_existing_pr_ref} (if true, PR #${_merge_existing_pr_number_ref} already exists — update it instead of creating a new one) - Merge was attempted. Status: ${_merge_clean_ref} (true = clean, false = has conflicts) ## Impact analysis @@ -273,8 +276,10 @@ The fewer upstream files we modify, the fewer conflicts in the next sync. 2. Run typecheck: bun run typecheck 3. Check formatting: bun run format:check — if issues, fix with bun run format 4. Commit any remaining changes and push the branch -5. Create a PR targeting main via the Forgejo API: - POST ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls +5. Push the branch, then create or update the PR: + - If existing PR is "true": PR #${_merge_existing_pr_number_ref} already exists. Just push the branch — the PR updates automatically. + Optionally update the PR body via PATCH ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls/${_merge_existing_pr_number_ref} + - If existing PR is "false": Create a new PR via POST ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls Auth: token ${"$"}GITHUB_TOKEN Title: "chore: merge ${_merge_behind_ref} upstream commits from anthropics/claude-code-action" Body should include: @@ -491,6 +496,29 @@ fi""" token = h.gha_secret("CLAUDE_BOT_TOKEN") } } + { + name = "Check for existing sync PR" + id = "existing-pr" + run = """\ +# Search for open PRs from chore/upstream-sync-* branches +RESPONSE=${"$"}(curl -s \\ + -H "Authorization: token ${h.gha_secret("CLAUDE_BOT_TOKEN")}" \\ + -H "Content-Type: application/json" \\ + "${h.gha_env("GITHUB_API_URL")}/repos/${h.gha_github("repository")}/pulls?state=open&limit=50") + +PR_BRANCH=${"$"}(echo "${"$"}RESPONSE" | jq -r '[.[] | select(.head.ref | startswith("chore/upstream-sync-"))] | .[0].head.ref // ""') + +if [ -n "${"$"}PR_BRANCH" ]; then + PR_NUMBER=${"$"}(echo "${"$"}RESPONSE" | jq -r '[.[] | select(.head.ref | startswith("chore/upstream-sync-"))] | .[0].number') + echo "Found existing sync PR #${"$"}PR_NUMBER on branch ${"$"}PR_BRANCH" + echo "pr_exists=true" >> "${"$"}GITHUB_OUTPUT" + echo "pr_branch=${"$"}PR_BRANCH" >> "${"$"}GITHUB_OUTPUT" + echo "pr_number=${"$"}PR_NUMBER" >> "${"$"}GITHUB_OUTPUT" +else + echo "No existing sync PR found" + echo "pr_exists=false" >> "${"$"}GITHUB_OUTPUT" +fi""" + } { name = "Prepare upstream merge" id = "prepare" @@ -501,6 +529,29 @@ git config user.email "claude@wxs.ro" git remote add upstream https://github.com/anthropics/claude-code-action.git git fetch upstream main --quiet +# Check if existing PR branch is already up to date with upstream +if [ "${h.gha_steps("existing-pr", "pr_exists")}" = "true" ]; then + PR_BRANCH="${h.gha_steps("existing-pr", "pr_branch")}" + git checkout "${"$"}PR_BRANCH" + + # Check if upstream/main is already merged into the PR branch + BEHIND=${"$"}(git rev-list "${"$"}PR_BRANCH"..upstream/main --count) + if [ "${"$"}BEHIND" -eq 0 ]; then + echo "Existing PR branch ${"$"}PR_BRANCH is already up to date with upstream" + echo "skip=true" >> "${"$"}GITHUB_OUTPUT" + exit 0 + fi + + echo "Existing PR branch ${"$"}PR_BRANCH is ${"$"}BEHIND commits behind upstream — updating" + BRANCH="${"$"}PR_BRANCH" +else + BRANCH="chore/upstream-sync-${"$"}(date +%Y%m%d)" + git checkout -b "${"$"}BRANCH" +fi + +echo "skip=false" >> "${"$"}GITHUB_OUTPUT" +echo "branch=${"$"}BRANCH" >> "${"$"}GITHUB_OUTPUT" + # Files our fork has modified vs upstream FORK_FILES=${"$"}(git log upstream/main..main --name-only --pretty=format: | sort -u | grep -v '^${"$"}') echo "=== Fork-modified files ===" @@ -531,10 +582,6 @@ echo "affected_files<> "${"$"}GITHUB_OUTPUT" echo "${"$"}{AFFECTED:-(none)}" >> "${"$"}GITHUB_OUTPUT" echo "EOF" >> "${"$"}GITHUB_OUTPUT" -BRANCH="chore/upstream-sync-${"$"}(date +%Y%m%d)" -git checkout -b "${"$"}BRANCH" -echo "branch=${"$"}BRANCH" >> "${"$"}GITHUB_OUTPUT" - if git merge upstream/main --no-edit; then echo "merge_clean=true" >> "${"$"}GITHUB_OUTPUT" echo "Merge completed cleanly" @@ -543,10 +590,11 @@ else echo "Merge has conflicts — Claude will resolve them" fi""" } - _bun_install() - _bun_deps() + _bun_install() | {"if" = "steps.prepare.outputs.skip != 'true'"} + _bun_deps() | {"if" = "steps.prepare.outputs.skip != 'true'"} { name = "Claude: review merge and create PR" + "if" = "steps.prepare.outputs.skip != 'true'" uses = _claude_action_url "with" = { prompt = _merge_prompt diff --git a/.forgejo/workflows/upstream-sync.yml b/.forgejo/workflows/upstream-sync.yml index 7d04b0d..3705f06 100644 --- a/.forgejo/workflows/upstream-sync.yml +++ b/.forgejo/workflows/upstream-sync.yml @@ -187,6 +187,27 @@ jobs: with: fetch-depth: '0' token: ${{ secrets.CLAUDE_BOT_TOKEN }} + - name: Check for existing sync PR + id: existing-pr + run: |- + # Search for open PRs from chore/upstream-sync-* branches + RESPONSE=$(curl -s \ + -H "Authorization: token ${{ secrets.CLAUDE_BOT_TOKEN }}" \ + -H "Content-Type: application/json" \ + "${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/pulls?state=open&limit=50") + + PR_BRANCH=$(echo "$RESPONSE" | jq -r '[.[] | select(.head.ref | startswith("chore/upstream-sync-"))] | .[0].head.ref // ""') + + if [ -n "$PR_BRANCH" ]; then + PR_NUMBER=$(echo "$RESPONSE" | jq -r '[.[] | select(.head.ref | startswith("chore/upstream-sync-"))] | .[0].number') + echo "Found existing sync PR #$PR_NUMBER on branch $PR_BRANCH" + echo "pr_exists=true" >> "$GITHUB_OUTPUT" + echo "pr_branch=$PR_BRANCH" >> "$GITHUB_OUTPUT" + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + else + echo "No existing sync PR found" + echo "pr_exists=false" >> "$GITHUB_OUTPUT" + fi - name: Prepare upstream merge id: prepare run: |- @@ -196,6 +217,29 @@ jobs: git remote add upstream https://github.com/anthropics/claude-code-action.git git fetch upstream main --quiet + # Check if existing PR branch is already up to date with upstream + if [ "${{ steps.existing-pr.outputs.pr_exists }}" = "true" ]; then + PR_BRANCH="${{ steps.existing-pr.outputs.pr_branch }}" + git checkout "$PR_BRANCH" + + # Check if upstream/main is already merged into the PR branch + BEHIND=$(git rev-list "$PR_BRANCH"..upstream/main --count) + if [ "$BEHIND" -eq 0 ]; then + echo "Existing PR branch $PR_BRANCH is already up to date with upstream" + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Existing PR branch $PR_BRANCH is $BEHIND commits behind upstream — updating" + BRANCH="$PR_BRANCH" + else + BRANCH="chore/upstream-sync-$(date +%Y%m%d)" + git checkout -b "$BRANCH" + fi + + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + # Files our fork has modified vs upstream FORK_FILES=$(git log upstream/main..main --name-only --pretty=format: | sort -u | grep -v '^$') echo "=== Fork-modified files ===" @@ -226,10 +270,6 @@ jobs: echo "${AFFECTED:-(none)}" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" - BRANCH="chore/upstream-sync-$(date +%Y%m%d)" - git checkout -b "$BRANCH" - echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" - if git merge upstream/main --no-edit; then echo "merge_clean=true" >> "$GITHUB_OUTPUT" echo "Merge completed cleanly" @@ -252,9 +292,12 @@ jobs: curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" echo "$HOME/.bun/bin" >> "$GITHUB_PATH" fi + if: steps.prepare.outputs.skip != 'true' - name: Install dependencies run: bun install + if: steps.prepare.outputs.skip != 'true' - name: 'Claude: review merge and create PR' + if: steps.prepare.outputs.skip != 'true' uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/actions/claude-code-action@main with: prompt: |- @@ -264,6 +307,7 @@ jobs: - Branch: ${{ steps.prepare.outputs.branch }} - ${{ needs.check-upstream.outputs.behind }} commits behind upstream, ${{ needs.check-upstream.outputs.ahead }} commits ahead (our Forgejo changes) - Tracking issue: #${{ needs.check-upstream.outputs.issue_number }} + - Existing PR: ${{ steps.existing-pr.outputs.pr_exists }} (if true, PR #${{ steps.existing-pr.outputs.pr_number }} already exists — update it instead of creating a new one) - Merge was attempted. Status: ${{ steps.prepare.outputs.merge_clean }} (true = clean, false = has conflicts) ## Impact analysis @@ -294,8 +338,10 @@ jobs: 2. Run typecheck: bun run typecheck 3. Check formatting: bun run format:check — if issues, fix with bun run format 4. Commit any remaining changes and push the branch - 5. Create a PR targeting main via the Forgejo API: - POST $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls + 5. Push the branch, then create or update the PR: + - If existing PR is "true": PR #${{ steps.existing-pr.outputs.pr_number }} already exists. Just push the branch — the PR updates automatically. + Optionally update the PR body via PATCH $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls/${{ steps.existing-pr.outputs.pr_number }} + - If existing PR is "false": Create a new PR via POST $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls Auth: token $GITHUB_TOKEN Title: "chore: merge ${{ needs.check-upstream.outputs.behind }} upstream commits from anthropics/claude-code-action" Body should include: