import React from 'dom-chef'; import select from 'select-dom'; import delegate from 'delegate-it'; import AlertIcon from 'octicon/alert.svg'; import * as pageDetect from 'github-url-detection'; import features from '.'; import * as api from '../github-helpers/api'; import observeElement from '../helpers/simplified-element-observer'; import {getConversationNumber} from '../github-helpers'; let observer: MutationObserver; function getBranches(): {base: string; head: string} { return { base: select('.base-ref')!.textContent!.trim(), head: select('.head-ref')!.textContent!.trim() }; } async function mergeBranches(): Promise { return api.v3(`pulls/${getConversationNumber()!}/update-branch`, { method: 'PUT', headers: { Accept: 'application/vnd.github.lydian-preview+json' }, ignoreHTTPStatus: true }); } async function handler({delegateTarget}: delegate.Event): Promise { const {base, head} = getBranches(); if (!confirm(`Merge the ${base} branch into ${head}?`)) { return; } const statusMeta = delegateTarget.parentElement!; statusMeta.textContent = 'Updating branch…'; observer.disconnect(); const response = await mergeBranches(); if (response.ok) { statusMeta.remove(); } else if (response.message?.toLowerCase().startsWith('merge conflict')) { // Only shown on Draft PRs statusMeta.textContent = ''; statusMeta.append( Resolve conflicts ); } else { statusMeta.textContent = response.message ?? 'Error'; statusMeta.prepend(, ' '); throw new api.RefinedGitHubAPIError('update-pr-from-base-branch: ' + JSON.stringify(response)); } } function createButton(): HTMLElement { const button = ; return You can {button}.; } async function addButton(): Promise { if (select.exists('.rgh-update-pr-from-base-branch, .branch-action-btn:not([action$="ready_for_review"]) > .btn')) { return; } const stillLoading = select('#partial-pull-merging poll-include-fragment'); if (stillLoading) { stillLoading.addEventListener('load', addButton); return; } const {base, head} = getBranches(); if (head === 'unknown repository') { return; } // Draft PRs already have this info on the page const outOfDateContainer = select.all('.completeness-indicator-problem + .status-heading') .find(title => title.textContent!.includes('out-of-date')); if (outOfDateContainer) { const meta = outOfDateContainer.nextElementSibling!; meta.after(' ', createButton()); return; } const {status} = await api.v3(`compare/${base}...${head}`); if (status !== 'diverged') { return; } for (const meta of select.all('.mergeability-details > :not(.js-details-container) .status-meta')) { meta.after(' ', createButton()); } } async function init(): Promise { await api.expectToken(); // Button exists when the current user can merge the PR. // Button is disabled when: // - There are conflicts (there's already a native "Resolve conflicts" button) // - Draft PR (show the button anyway) const canMerge = select.exists('[data-details-container=".js-merge-pr"]:not(:disabled)'); const isDraftPR = select.exists('[action$="ready_for_review"]'); if (!canMerge && !isDraftPR) { return false; } observer = observeElement('.discussion-timeline-actions', addButton)!; delegate(document, '.rgh-update-pr-from-base-branch button', 'click', handler); } void features.add(__filebasename, { include: [ pageDetect.isPRConversation ], init });