|
1 | | -name: Single PR for Closed Milestone PRs |
| 1 | +name: Create Individual PRs from Milestone |
| 2 | + |
| 3 | +permissions: |
| 4 | + contents: write |
| 5 | + pull-requests: write |
| 6 | + issues: write |
2 | 7 |
|
3 | 8 | on: |
4 | | - workflow_dispatch: # Manual trigger |
| 9 | + workflow_dispatch: |
5 | 10 | inputs: |
6 | | - milestone: |
7 | | - description: 'Milestone name to collect closed PRs from' |
| 11 | + milestone_name: |
| 12 | + description: "Milestone name to collect closed PRs from" |
8 | 13 | required: true |
9 | | - default: 'v3.8.2' # Default milestone set to v3.8.2 |
| 14 | + default: "v3.8.4" |
10 | 15 | target_branch: |
11 | | - description: 'Target branch to merge the consolidated PR' |
| 16 | + description: "Target branch to merge the consolidated PR" |
12 | 17 | required: true |
13 | | - default: 'pre-release-v3.8.2' # Default target branch with pre-release-v*.* format |
14 | | - schedule: |
15 | | - - cron: '0 0 * * 0' # Scheduled to run every Sunday at 00:00 UTC |
| 18 | + default: "pre-release-v3.8.4" |
16 | 19 |
|
17 | 20 | env: |
| 21 | + MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.4' }} |
| 22 | + TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.4' }} |
18 | 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 24 | + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} |
| 25 | + LABEL_NAME: cherry-picked |
| 26 | + TEMP_DIR: /tmp |
19 | 27 |
|
20 | 28 | jobs: |
21 | | - cherry_pick_closed_milestone_prs: |
| 29 | + merge_milestone_prs: |
22 | 30 | runs-on: ubuntu-latest |
23 | 31 | steps: |
24 | | - # Step 1: Checkout the repository code |
| 32 | + - name: Setup temp directory |
| 33 | + run: | |
| 34 | + # Create the temporary directory and initialize necessary files |
| 35 | + mkdir -p ${{ env.TEMP_DIR }} |
| 36 | + touch ${{ env.TEMP_DIR }}/pr_numbers.txt |
| 37 | + touch ${{ env.TEMP_DIR }}/commit_hashes.txt |
| 38 | + touch ${{ env.TEMP_DIR }}/pr_title.txt |
| 39 | + touch ${{ env.TEMP_DIR }}/pr_body.txt |
| 40 | + touch ${{ env.TEMP_DIR }}/created_pr_number.txt |
| 41 | +
|
25 | 42 | - name: Checkout repository |
26 | | - uses: actions/checkout@v2 |
| 43 | + uses: actions/checkout@v4 |
| 44 | + with: |
| 45 | + fetch-depth: 0 |
| 46 | + token: ${{ secrets.BOT_TOKEN }} |
27 | 47 |
|
28 | | - # Step 2: Set up Git user details for making commits |
29 | | - - name: Setup Git |
| 48 | + - name: Setup Git User for OpenIM-Robot |
30 | 49 | run: | |
31 | | - git config --global user.name "github-actions[bot]" |
32 | | - git config --global user.email "github-actions[bot]@users.noreply.github.com" |
| 50 | + git config --global user.email "OpenIM-Robot@users.noreply.github.com" |
| 51 | + git config --global user.name "OpenIM-Robot" |
33 | 52 |
|
34 | | - # Step 3: Install GitHub CLI to interact with milestones and PRs |
35 | | - - name: Install GitHub CLI |
36 | | - run: sudo apt install gh |
| 53 | + - name: Fetch Milestone ID and Filter PR Numbers |
| 54 | + env: |
| 55 | + MILESTONE_NAME: ${{ env.MILESTONE_NAME }} |
| 56 | + run: | |
| 57 | + # Fetch milestone details and extract milestone ID |
| 58 | + milestones=$(curl -s -H "Authorization: token $BOT_TOKEN" \ |
| 59 | + -H "Accept: application/vnd.github+json" \ |
| 60 | + "https://api.github.com/repos/${{ github.repository }}/milestones") |
| 61 | + milestone_id=$(echo "$milestones" | grep -B3 "\"title\": \"$MILESTONE_NAME\"" | grep '"number":' | head -n1 | grep -o '[0-9]\+') |
| 62 | + if [ -z "$milestone_id" ]; then |
| 63 | + echo "Milestone '$MILESTONE_NAME' not found. Exiting." |
| 64 | + exit 1 |
| 65 | + fi |
| 66 | + echo "Milestone ID: $milestone_id" |
| 67 | + echo "MILESTONE_ID=$milestone_id" >> $GITHUB_ENV |
37 | 68 |
|
38 | | - # Step 4: Authenticate GitHub CLI using GITHUB_TOKEN |
39 | | - - name: Authenticate GitHub CLI |
40 | | - run: echo "${{ env.GITHUB_TOKEN }}" | gh auth login --with-token |
| 69 | + # Fetch issues for the milestone |
| 70 | + issues=$(curl -s -H "Authorization: token $BOT_TOKEN" \ |
| 71 | + -H "Accept: application/vnd.github+json" \ |
| 72 | + "https://api.github.com/repos/${{ github.repository }}/issues?milestone=$milestone_id&state=closed&per_page=100") |
41 | 73 |
|
42 | | - # Step 5: Fetch closed PRs from the specified milestone |
43 | | - - name: Fetch Closed PRs from Milestone |
44 | | - id: fetch_prs |
45 | | - run: | |
46 | | - milestone="${{ github.event.inputs.milestone }}" |
47 | | - prs=$(gh pr list --search "milestone:$milestone is:closed" --json number,headRefName,mergeCommit) |
48 | | - echo "::set-output name=prs::$prs" |
| 74 | + > ${{ env.TEMP_DIR }}/pr_numbers.txt |
49 | 75 |
|
50 | | - # Step 6: Create a new branch from main, cherry-pick PRs, and push to remote |
51 | | - - name: Create and Cherry-pick Branch |
52 | | - if: ${{ steps.fetch_prs.outputs.prs != '[]' }} |
53 | | - run: | |
54 | | - target_branch="${{ github.event.inputs.target_branch }}" |
55 | | - cherry_pick_branch="milestone-cherry-pick-$(date +%Y%m%d%H%M%S)" |
| 76 | + # Filter PRs that do not have the 'cherry-picked' label |
| 77 | + for pr_number in $(echo "$issues" | jq -r '.[] | select(.pull_request != null) | .number'); do |
| 78 | + labels=$(curl -s -H "Authorization: token $BOT_TOKEN" \ |
| 79 | + -H "Accept: application/vnd.github+json" \ |
| 80 | + "https://api.github.com/repos/${{ github.repository }}/issues/$pr_number/labels" | jq -r '.[].name') |
| 81 | +
|
| 82 | + if ! echo "$labels" | grep -q "${LABEL_NAME}"; then |
| 83 | + echo "PR #$pr_number does not have the 'cherry-picked' label. Adding to the list." |
| 84 | + echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt |
| 85 | + fi |
| 86 | + done |
56 | 87 |
|
57 | | - # Switch to main branch, pull latest changes, then create a new cherry-pick branch |
58 | | - git checkout main |
59 | | - git pull origin main |
60 | | - git checkout -b $cherry_pick_branch |
| 88 | + sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt |
61 | 89 |
|
62 | | - # Iterate over closed PRs in the milestone and cherry-pick each merge commit |
63 | | - echo "${{ steps.fetch_prs.outputs.prs }}" | jq -c '.[]' | while read -r pr; do |
64 | | - pr_number=$(echo "$pr" | jq '.number') |
65 | | - merge_commit=$(echo "$pr" | jq -r '.mergeCommit') |
| 90 | + - name: Create Individual PRs |
| 91 | + run: | |
| 92 | + for pr_number in $(cat ${{ env.TEMP_DIR }}/pr_numbers.txt); do |
| 93 | + pr_details=$(curl -s -H "Authorization: token $BOT_TOKEN" \ |
| 94 | + -H "Accept: application/vnd.github+json" \ |
| 95 | + "https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number") |
| 96 | + pr_title=$(echo "$pr_details" | jq -r '.title') |
| 97 | + pr_body=$(echo "$pr_details" | jq -r '.body') |
| 98 | + pr_creator=$(echo "$pr_details" | jq -r '.user.login') |
| 99 | + merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha') |
| 100 | + short_commit_hash=$(echo "$merge_commit" | cut -c 1-7) |
66 | 101 |
|
67 | 102 | if [ "$merge_commit" != "null" ]; then |
68 | | - # Check if the merge commit is already in the target branch |
69 | | - if git merge-base --is-ancestor $merge_commit $target_branch; then |
70 | | - echo "Skipping PR #$pr_number - Commit $merge_commit already in $target_branch" |
| 103 | + git fetch origin |
| 104 | + |
| 105 | + echo "Checking out target branch: $TARGET_BRANCH" |
| 106 | + git checkout $TARGET_BRANCH |
| 107 | +
|
| 108 | + echo "Pulling latest changes from target branch: $TARGET_BRANCH" |
| 109 | + git pull origin $TARGET_BRANCH |
| 110 | + |
| 111 | + cherry_pick_branch="cherry-pick-${short_commit_hash}" |
| 112 | + git checkout -b $cherry_pick_branch |
| 113 | +
|
| 114 | + echo "Cherry-picking commit: $merge_commit" |
| 115 | + if ! git cherry-pick "$merge_commit" --strategy=recursive -X theirs; then |
| 116 | + echo "Conflict detected for $merge_commit. Resolving with incoming changes." |
| 117 | + conflict_files=$(git diff --name-only --diff-filter=U) |
| 118 | + echo "Conflicting files:" |
| 119 | + echo "$conflict_files" |
| 120 | +
|
| 121 | + for file in $conflict_files; do |
| 122 | + if [ -f "$file" ]; then |
| 123 | + echo "Resolving conflict for $file" |
| 124 | + git add "$file" |
| 125 | + else |
| 126 | + echo "File $file has been deleted. Skipping." |
| 127 | + git rm "$file" |
| 128 | + fi |
| 129 | + done |
| 130 | +
|
| 131 | + echo "Conflicts resolved. Continuing cherry-pick." |
| 132 | + git cherry-pick --continue || { echo "Cherry-pick failed, but continuing to create PR."; } |
71 | 133 | else |
72 | | - echo "Cherry-picking PR #$pr_number with commit $merge_commit" |
73 | | - git cherry-pick $merge_commit || git cherry-pick --abort |
| 134 | + echo "Cherry-pick successful for commit $merge_commit." |
| 135 | + fi |
| 136 | +
|
| 137 | + git remote set-url origin "https://${BOT_TOKEN}@github.com/${{ github.repository }}.git" |
| 138 | + |
| 139 | + echo "Pushing branch: $cherry_pick_branch" |
| 140 | + if ! git push origin $cherry_pick_branch --force; then |
| 141 | + echo "Push failed, but continuing to create PR..." |
74 | 142 | fi |
75 | | - else |
76 | | - echo "Skipping PR #$pr_number as it does not have a merge commit." |
77 | | - fi |
78 | | - done |
79 | 143 |
|
80 | | - # Push the cherry-pick branch to the remote repository |
81 | | - git push origin $cherry_pick_branch |
| 144 | + new_pr_title="$pr_title [Created by @$pr_creator from #$pr_number]" |
| 145 | + new_pr_body="$pr_body |
| 146 | + > This PR is created from original PR #$pr_number." |
82 | 147 |
|
83 | | - # Step 7: Create a single pull request to merge the cherry-pick branch into the target branch |
84 | | - - name: Create Pull Request to Target Branch |
85 | | - if: steps.fetch_prs.outputs.prs != "[]" |
86 | | - uses: peter-evans/create-pull-request@v4 |
87 | | - with: |
88 | | - token: ${{ secrets.GITHUB_TOKEN }} |
89 | | - branch: $cherry_pick_branch |
90 | | - base: ${{ github.event.inputs.target_branch }} |
91 | | - title: "Consolidated Closed Milestone PRs for ${{ github.event.inputs.target_branch }}" |
92 | | - body: "This PR includes all cherry-picked changes from closed PRs in milestone '${{ github.event.inputs.milestone }}' to ${{ github.event.inputs.target_branch }}." |
| 148 | + response=$(curl -s -X POST -H "Authorization: token $BOT_TOKEN" \ |
| 149 | + -H "Accept: application/vnd.github+json" \ |
| 150 | + https://api.github.com/repos/${{ github.repository }}/pulls \ |
| 151 | + -d "$(jq -n --arg title "$new_pr_title" \ |
| 152 | + --arg head "$cherry_pick_branch" \ |
| 153 | + --arg base "$TARGET_BRANCH" \ |
| 154 | + --arg body "$new_pr_body" \ |
| 155 | + '{title: $title, head: $head, base: $base, body: $body}')") |
| 156 | +
|
| 157 | + new_pr_number=$(echo "$response" | jq -r '.number') |
| 158 | + echo "Created PR #$new_pr_number" |
| 159 | +
|
| 160 | + curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \ |
| 161 | + -H "Accept: application/vnd.github+json" \ |
| 162 | + -d '{"labels": ["milestone-merge"]}' \ |
| 163 | + "https://api.github.com/repos/${{ github.repository }}/issues/$new_pr_number/labels" |
| 164 | + fi |
| 165 | + done |
0 commit comments