From ba7579616c090853327ecc4daefa9ebce6d2e84d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:39:38 +0000 Subject: [PATCH 1/2] Initial plan From 161aac28d1583bdbb6db3dc1cad333fedf2f7fe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:43:04 +0000 Subject: [PATCH 2/2] Add create-upstream-pr workflow, fix rebase-upstream merge-base bug, improve sync-upstream Co-authored-by: carstenartur <3164220+carstenartur@users.noreply.github.com> --- .github/fork-specific-files.txt | 10 ++ .github/workflows/create-upstream-pr.yml | 126 +++++++++++++++++++++++ .github/workflows/rebase-upstream.yml | 30 ++---- .github/workflows/sync-upstream.yml | 76 ++++++++------ 4 files changed, 189 insertions(+), 53 deletions(-) create mode 100644 .github/fork-specific-files.txt create mode 100644 .github/workflows/create-upstream-pr.yml diff --git a/.github/fork-specific-files.txt b/.github/fork-specific-files.txt new file mode 100644 index 00000000000..ae527d989ce --- /dev/null +++ b/.github/fork-specific-files.txt @@ -0,0 +1,10 @@ +.github/dependabot.yml +.github/workflows/codacy.yml +.github/workflows/codeql.yml +.github/workflows/maven.yml +.github/workflows/rebase.yml +.github/workflows/rebase-upstream.yml +.github/workflows/sync-upstream.yml +.github/workflows/create-upstream-pr.yml +.github/fork-specific-files.txt +README.md diff --git a/.github/workflows/create-upstream-pr.yml b/.github/workflows/create-upstream-pr.yml new file mode 100644 index 00000000000..3540542cff2 --- /dev/null +++ b/.github/workflows/create-upstream-pr.yml @@ -0,0 +1,126 @@ +name: Create Upstream PR Branch + +on: + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number (used for branch name: upstream-pr/issue-{number})' + required: true + type: string + source_branch: + description: 'Source branch containing the commits to cherry-pick' + required: true + type: string + commit_shas: + description: 'Comma-separated list of commit SHAs to cherry-pick onto upstream/master' + required: true + type: string + pr_title: + description: 'Title for the upstream PR (informational only)' + required: false + type: string + +jobs: + create-upstream-pr-branch: + name: Create Clean Upstream PR Branch + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Fetch upstream master + run: | + git remote add upstream https://github.com/eclipse-jdt/eclipse.jdt.ui.git + git fetch upstream master + + - name: Create branch based on upstream/master + run: | + BRANCH_NAME="upstream-pr/issue-${{ inputs.issue_number }}" + git checkout -b "$BRANCH_NAME" upstream/master + echo "Created branch: $BRANCH_NAME" + + - name: Cherry-pick specified commits + id: cherry_pick + run: | + BRANCH_NAME="upstream-pr/issue-${{ inputs.issue_number }}" + COMMIT_SHAS="${{ inputs.commit_shas }}" + + # Convert comma-separated list to space-separated + SHAS=$(echo "$COMMIT_SHAS" | tr ',' ' ' | tr -s ' ') + + echo "Cherry-picking commits: $SHAS" + + SUCCESS=true + for SHA in $SHAS; do + SHA=$(echo "$SHA" | xargs) # trim whitespace + if [ -z "$SHA" ]; then + continue + fi + echo "Cherry-picking $SHA..." + if ! git cherry-pick "$SHA"; then + echo "Cherry-pick failed for $SHA" + git cherry-pick --abort 2>/dev/null || true + SUCCESS=false + break + fi + done + + if [ "$SUCCESS" = "true" ]; then + echo "success=true" >> $GITHUB_OUTPUT + else + echo "success=false" >> $GITHUB_OUTPUT + fi + + - name: Verify branch contents + if: steps.cherry_pick.outputs.success == 'true' + run: | + echo "=== Commits on this branch (not in upstream/master) ===" + git log upstream/master..HEAD --oneline + echo "" + echo "=== Files changed vs upstream/master ===" + git diff upstream/master..HEAD --stat + + - name: Force-push branch to fork + if: steps.cherry_pick.outputs.success == 'true' + run: | + BRANCH_NAME="upstream-pr/issue-${{ inputs.issue_number }}" + git push --force origin "$BRANCH_NAME" + echo "✅ Branch '$BRANCH_NAME' pushed to fork" + + - name: Output PR creation link + if: steps.cherry_pick.outputs.success == 'true' + run: | + BRANCH_NAME="upstream-pr/issue-${{ inputs.issue_number }}" + REPO_OWNER="${{ github.repository_owner }}" + REPO_NAME="${{ github.event.repository.name }}" + PR_LINK="https://github.com/eclipse-jdt/eclipse.jdt.ui/compare/master...${REPO_OWNER}:${REPO_NAME}:${BRANCH_NAME}" + + echo "" + echo "==========================================" + echo "✅ Branch ready for upstream PR!" + echo "==========================================" + echo "" + echo "Branch: $BRANCH_NAME" + if [ -n "${{ inputs.pr_title }}" ]; then + echo "PR Title: ${{ inputs.pr_title }}" + fi + echo "" + echo "Create PR at:" + echo "$PR_LINK" + echo "" + + - name: Fail if cherry-pick failed + if: steps.cherry_pick.outputs.success == 'false' + run: | + echo "❌ Cherry-pick failed. Check the logs above for details." + exit 1 diff --git a/.github/workflows/rebase-upstream.yml b/.github/workflows/rebase-upstream.yml index 0062ddda3ea..d4aa9219044 100644 --- a/.github/workflows/rebase-upstream.yml +++ b/.github/workflows/rebase-upstream.yml @@ -114,17 +114,6 @@ jobs: core.setOutput('head_ref', pr.head.ref); core.setOutput('head_repo', pr.head.repo.full_name); core.setOutput('head_clone_url', pr.head.repo.clone_url); - - // Get the number of commits in the PR - // Note: per_page is set to 250, which covers most PRs. - // For PRs with >250 commits, manual rebasing would be required. - const { data: commits } = await github.rest.pulls.listCommits({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - per_page: 250 - }); - core.setOutput('commit_count', commits.length); - name: Checkout PR branch uses: actions/checkout@v4 @@ -145,19 +134,22 @@ jobs: git remote add upstream https://github.com/eclipse-jdt/eclipse.jdt.ui.git git fetch upstream master - # Get the number of commits in the PR - PR_COMMIT_COUNT=${{ steps.pr.outputs.commit_count }} - echo "PR has $PR_COMMIT_COUNT commits" + # Find the actual fork point with upstream + MERGE_BASE=$(git merge-base HEAD upstream/master) + echo "Merge base with upstream: $MERGE_BASE" + + # Count actual commits to rebase (between merge-base and HEAD) + ACTUAL_COMMITS=$(git rev-list --count $MERGE_BASE..HEAD) + echo "Actual commits to rebase: $ACTUAL_COMMITS" - # Validate commit count - if [ "$PR_COMMIT_COUNT" -eq 0 ]; then - echo "Error: PR has 0 commits" + if [ "$ACTUAL_COMMITS" -eq 0 ]; then + echo "Error: No commits to rebase" echo "success=false" >> $GITHUB_OUTPUT exit 0 fi - # Rebase only the PR commits onto upstream/master - if git rebase --onto upstream/master HEAD~${PR_COMMIT_COUNT}; then + # Rebase using merge-base (correct: only replays commits after the fork point) + if git rebase --onto upstream/master $MERGE_BASE; then echo "success=true" >> $GITHUB_OUTPUT else echo "success=false" >> $GITHUB_OUTPUT diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 85e518cfacb..072ad1bf8f8 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -55,24 +55,28 @@ jobs: # Create temporary directory for fork-specific files mkdir -p /tmp/fork-specific - # Identify fork-specific workflow files (files that don't exist in upstream) - git fetch upstream master - - # Get list of workflow files in fork - FORK_WORKFLOWS=$(git ls-tree -r HEAD --name-only .github/workflows/ 2>/dev/null || echo "") + # Read the list of fork-specific files from the manifest + MANIFEST=".github/fork-specific-files.txt" + if [ ! -f "$MANIFEST" ]; then + echo "Warning: $MANIFEST not found, no files to backup" + echo "has_fork_files=false" >> $GITHUB_OUTPUT + exit 0 + fi - # For each workflow file in fork, check if it exists in upstream - for file in $FORK_WORKFLOWS; do - if ! git cat-file -e upstream/master:"$file" 2>/dev/null; then - echo "Fork-specific file: $file" - # Create directory structure and copy file + HAS_FILES=false + while IFS= read -r file || [ -n "$file" ]; do + # Skip empty lines and comments + [ -z "$file" ] && continue + case "$file" in \#*) continue ;; esac + if [ -f "$file" ]; then + echo "Backing up fork-specific file: $file" mkdir -p "/tmp/fork-specific/$(dirname "$file")" cp "$file" "/tmp/fork-specific/$file" + HAS_FILES=true fi - done + done < "$MANIFEST" - # Check if any fork-specific files were found - if [ -d "/tmp/fork-specific/.github" ]; then + if [ "$HAS_FILES" = "true" ]; then echo "has_fork_files=true" >> $GITHUB_OUTPUT echo "Found fork-specific files:" find /tmp/fork-specific -type f @@ -90,13 +94,13 @@ jobs: - name: Restore fork-specific files if: steps.check_sync.outputs.needs_sync == 'true' && steps.backup.outputs.has_fork_files == 'true' run: | - # Copy fork-specific files back + # Restore all backed-up fork-specific files if [ -d "/tmp/fork-specific" ]; then - cp -r /tmp/fork-specific/.github ./ 2>/dev/null || true + cp -r /tmp/fork-specific/. ./ fi - # Add all fork-specific changes - git add .github/ + # Stage all restored files + git add -A - name: Commit fork-specific changes if: steps.check_sync.outputs.needs_sync == 'true' && steps.backup.outputs.has_fork_files == 'true' @@ -214,24 +218,28 @@ jobs: # Create temporary directory for fork-specific files mkdir -p /tmp/fork-specific - # Identify fork-specific workflow files (files that don't exist in upstream) - git fetch upstream master - - # Get list of workflow files in fork - FORK_WORKFLOWS=$(git ls-tree -r HEAD --name-only .github/workflows/ 2>/dev/null || echo "") + # Read the list of fork-specific files from the manifest + MANIFEST=".github/fork-specific-files.txt" + if [ ! -f "$MANIFEST" ]; then + echo "Warning: $MANIFEST not found, no files to backup" + echo "has_fork_files=false" >> $GITHUB_OUTPUT + exit 0 + fi - # For each workflow file in fork, check if it exists in upstream - for file in $FORK_WORKFLOWS; do - if ! git cat-file -e upstream/master:"$file" 2>/dev/null; then - echo "Fork-specific file: $file" - # Create directory structure and copy file + HAS_FILES=false + while IFS= read -r file || [ -n "$file" ]; do + # Skip empty lines and comments + [ -z "$file" ] && continue + case "$file" in \#*) continue ;; esac + if [ -f "$file" ]; then + echo "Backing up fork-specific file: $file" mkdir -p "/tmp/fork-specific/$(dirname "$file")" cp "$file" "/tmp/fork-specific/$file" + HAS_FILES=true fi - done + done < "$MANIFEST" - # Check if any fork-specific files were found - if [ -d "/tmp/fork-specific/.github" ]; then + if [ "$HAS_FILES" = "true" ]; then echo "has_fork_files=true" >> $GITHUB_OUTPUT echo "Found fork-specific files:" find /tmp/fork-specific -type f @@ -251,13 +259,13 @@ jobs: - name: Restore fork-specific files if: steps.check_sync.outputs.needs_sync == 'true' && steps.backup.outputs.has_fork_files == 'true' run: | - # Copy fork-specific files back + # Restore all backed-up fork-specific files if [ -d "/tmp/fork-specific" ]; then - cp -r /tmp/fork-specific/.github ./ 2>/dev/null || true + cp -r /tmp/fork-specific/. ./ fi - # Add all fork-specific changes - git add .github/ + # Stage all restored files + git add -A - name: Commit fork-specific changes if: steps.check_sync.outputs.needs_sync == 'true' && steps.backup.outputs.has_fork_files == 'true'