Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/scripts/merge_conflict_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// .github/scripts/merge_conflict_helpers.js

const BOT_SIGNATURE = '[MergeConflictBotSignature-v1]';

module.exports = async ({ github, context, core }) => {
const { owner, repo } = context.repo;

// fetch PR with retry logic for unknown state
async function getPrWithRetry(prNumber) {
for (let i = 0; i < 10; i++) {
const { data: pr } = await github.rest.pulls.get({
owner, repo, pull_number: prNumber
});

if (pr.mergeable_state !== 'unknown') return pr;

console.log(`PR #${prNumber} state is 'unknown'. Retrying (${i+1}/10)...`);
await new Promise(r => setTimeout(r, 2000));
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
if (pr.mergeable_state === 'unknown') {
console.warn(`PR #${prNumber} state still 'unknown' after 10 retries.`);
}
return pr;
}

// post comment
async function notifyUser(prNumber) {
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number: prNumber,
});

if (comments.some(c => c.body.includes(BOT_SIGNATURE))) {
console.log(`Already commented on PR #${prNumber}. Skipping.`);
return;
}

const body = `Hi, this is MergeConflictBot.\nYour pull request cannot be merged because it contains **merge conflicts**.\n\nPlease resolve these conflicts locally and push the changes.\n\nTo assist you, please read:\n- [Resolving Merge Conflicts](https://github.com/${owner}/${repo}/blob/main/docs/sdk_developers/merge_conflicts.md)\n- [Rebasing Guide](https://github.com/${owner}/${repo}/blob/main/docs/sdk_developers/rebasing.md)\n\nThank you for contributing!\nFrom the Hiero Python SDK Team\n\n${BOT_SIGNATURE}`;

await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body: body
});
}

//set commit status
async function setCommitStatus(sha, state, description) {
await github.rest.repos.createCommitStatus({
owner, repo, sha: sha, state: state,
context: 'Merge Conflict Detector',
description: description,
target_url: `${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${context.runId}`
});
}

//main
let prsToCheck = [];

//push to main
if (context.eventName === 'push') {
console.log("Triggered by Push to Main. Fetching all open PRs...");
const openPrs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', base: 'main', per_page: 100
});
prsToCheck = openPrs.map(pr => pr.number);
}
//PR update
else {
console.log("Triggered by PR update.");
prsToCheck.push(context.payload.pull_request.number);
}

for (const prNumber of prsToCheck) {
try {
console.log(`Checking PR #${prNumber}...`);
const pr = await getPrWithRetry(prNumber);

if (pr.mergeable_state === 'unknown') {
console.log(`PR #${prNumber} state is still 'unknown'. Skipping conflict check.`);
continue;
}

if (pr.mergeable_state === 'dirty') {
console.log(`Conflict detected in PR #${prNumber}`);
await notifyUser(prNumber);

// Push events: set commit status on PR head SHA
// PR events: fail the workflow run (creates a check on the PR)
if (context.eventName === 'push') {
await setCommitStatus(pr.head.sha, 'failure', 'Conflicts detected with main');
} else {
core.setFailed(`Merge conflicts detected in PR #${prNumber}.`);
}
} else {
console.log(`PR #${prNumber} is clean.`);
// For push events, set success status; PR events rely on workflow run success
if (context.eventName === 'push') {
await setCommitStatus(pr.head.sha, 'success', 'No conflicts detected');
}
}
} catch (error) {
console.error(`Error checking PR #${prNumber}: ${error.message}`);
if (context.eventName !== 'push') {
throw error; // Re-throw for PR events to fail the workflow
}
// For push events, log and continue to check remaining PRs
}
}
};
71 changes: 0 additions & 71 deletions .github/workflows/bot-merge-conflict.yml

This file was deleted.

42 changes: 42 additions & 0 deletions .github/workflows/merge-conflict-bot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Merge Conflict Bot

on:
pull_request_target:
types: [opened, synchronize, reopened]
push:
branches:
- main

permissions:
contents: read
pull-requests: write
issues: write
statuses: write

concurrency:
group: "check-conflicts-${{ github.event.pull_request.number || github.sha }}"
cancel-in-progress: true

jobs:
check-conflicts:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: ${{ github.event.repository.default_branch }}

- name: Harden the runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
egress-policy: audit

- name: Check for merge conflicts
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const path = require('path')
const scriptPath = path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge_conflict_helpers.js')
console.log(`Loading script from: ${scriptPath}`)
const script = require(scriptPath)
await script({github, context, core})
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Move `account_allowance_delete_transaction_hbar.py` from `examples/` to `examples/account/` for better organization (#1003)
- Improved consistency of transaction examples (#1120)
- Refactored `account_create_transaction_with_fallback_alias.py` by splitting the monolithic `create_account_with_fallback_alias` function into modular functions: `generate_fallback_key`, `fetch_account_info`, and `print_account_summary`. The existing `setup_client()` function was reused for improved readability and structure (#1018)
- not creating a merge conflict
- Allow `PublicKey` for batch_key in `Transaction`, enabling both `PrivateKey` and `PublicKey` for batched transactions
- Allow `PublicKey` for `TokenUpdateKeys` in `TokenUpdateTransaction`, enabling non-custodial workflows where operators can build transactions using only public keys (#934).
- Bump protobuf toml to protobuf==6.33.2
Expand Down
Loading