|
| 1 | +name: 'Gitflow: Merge Conflict Issue' |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + types: [opened] |
| 6 | + branches: |
| 7 | + - develop |
| 8 | + |
| 9 | +jobs: |
| 10 | + check-merge-conflicts: |
| 11 | + name: Detect merge conflicts in gitflow PRs |
| 12 | + runs-on: ubuntu-24.04 |
| 13 | + if: | |
| 14 | + ${{ contains(github.event.pull_request.labels.*.name, 'Dev: Gitflow') }} |
| 15 | + permissions: |
| 16 | + issues: write |
| 17 | + steps: |
| 18 | + - name: Check for merge conflicts with retry |
| 19 | + uses: actions/github-script@v8 |
| 20 | + with: |
| 21 | + script: | |
| 22 | + const retryInterval = 30_000; |
| 23 | + const maxRetries = 10; // (30 seconds * 10 retries) = 5 minutes |
| 24 | +
|
| 25 | + async function isMergeable() { |
| 26 | + const { data: pr } = await github.rest.pulls.get({ |
| 27 | + owner: context.repo.owner, |
| 28 | + repo: context.repo.repo, |
| 29 | + pull_number: context.payload.pull_request.number |
| 30 | + }); |
| 31 | +
|
| 32 | + return pr.mergeable; |
| 33 | + } |
| 34 | +
|
| 35 | + async function sleep(ms) { |
| 36 | + return new Promise(resolve => setTimeout(resolve, ms)); |
| 37 | + } |
| 38 | +
|
| 39 | + let attempt = 0; |
| 40 | + let mergeable = null; |
| 41 | +
|
| 42 | + while (attempt < maxRetries) { |
| 43 | + attempt++; |
| 44 | + console.log(`Attempt ${attempt}/${maxRetries}: Checking if PR is mergeable...`); |
| 45 | +
|
| 46 | + mergeable = await isMergeable(); |
| 47 | + console.log(`Mergeable: ${mergeable}`); |
| 48 | +
|
| 49 | + // If mergeable is not null, GitHub has finished computing merge state |
| 50 | + if (mergeable !== null) { |
| 51 | + break; |
| 52 | + } |
| 53 | +
|
| 54 | + if (attempt < maxRetries) { |
| 55 | + console.log(`Waiting ${retryInterval/1000} seconds before retry...`); |
| 56 | + await sleep(retryInterval); |
| 57 | + } |
| 58 | + } |
| 59 | +
|
| 60 | + // Check if we have merge conflicts |
| 61 | + if (mergeable === false) { |
| 62 | + const issueTitle = '[Gitflow] Merge Conflict'; |
| 63 | +
|
| 64 | + // Check for existing open issues with the same title |
| 65 | + const { data: existingIssues } = await github.rest.issues.listForRepo({ |
| 66 | + owner: context.repo.owner, |
| 67 | + repo: context.repo.repo, |
| 68 | + state: 'open', |
| 69 | + labels: 'Dev: Gitflow' |
| 70 | + }); |
| 71 | +
|
| 72 | + const existingOpenIssue = existingIssues.find(issue => |
| 73 | + issue.title === issueTitle && !issue.pull_request |
| 74 | + ); |
| 75 | +
|
| 76 | + if (!existingOpenIssue) { |
| 77 | + const issueBody = [ |
| 78 | + '## Gitflow Merge Conflict Detected', |
| 79 | + '', |
| 80 | + `The automated gitflow PR #${context.payload.pull_request.number} has merge conflicts and cannot be merged automatically.`, |
| 81 | + '', |
| 82 | + '### How to resolve', |
| 83 | + '', |
| 84 | + `Follow the steps documented in [docs/gitflow.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/develop/docs/gitflow.md#what-to-do-if-there-is-a-merge-conflict):`, |
| 85 | + '', |
| 86 | + `1. Close the automated PR #${context.payload.pull_request.number}`, |
| 87 | + '2. Create a new branch on top of `master` (e.g., `manual-develop-sync`)', |
| 88 | + '3. Merge `develop` into this branch with a **merge commit** (fix any merge conflicts)', |
| 89 | + '4. Create a PR against `develop` from your branch', |
| 90 | + '5. Merge that PR with a **merge commit**' |
| 91 | + ].join('\n'); |
| 92 | +
|
| 93 | + await github.rest.issues.create({ |
| 94 | + owner: context.repo.owner, |
| 95 | + repo: context.repo.repo, |
| 96 | + title: issueTitle, |
| 97 | + body: issueBody, |
| 98 | + labels: ['Dev: Gitflow'] |
| 99 | + }); |
| 100 | +
|
| 101 | + console.log('Created new issue for merge conflict'); |
| 102 | + } |
| 103 | + } else if (mergeable === null) { |
| 104 | + console.log('Could not determine mergeable state after maximum retries'); |
| 105 | + } else { |
| 106 | + console.log('No merge conflicts detected - PR can be merged'); |
| 107 | + } |
0 commit comments