// Merging a pull request, which is the one act in this model that moves the // repository's canonical branch. // // Everything else the page writes goes into the visitor's own repo. This // writes the repository record, so it runs as the owner, on the owner's own // PDS, under the owner's OAuth session. The server accepts it only from a // token whose account is the repository's, so nobody else's session merges, // and the grant reaches no repository but the owner's own. import { XrpcError } from '@pdsjs/git/client'; import { account } from '#/lib/git.js'; import { authedFetch } from '#/lib/oauth.js'; /** * Put a pull request's work on the canonical branch. * * A squash is the change, so it takes the change's title. A merge is a record * of taking the change rather than the change itself, so it names the branch * the way `git merge` does and the title is not its subject. * @param {import('#/lib/oauth.js').Session} session - the owner's own * @param {string} repo - repository name on this site * @param {{author: {did: string, handle: string}, branch: string, title: string}} pull * @param {'merge'|'squash'} mode * @returns {Promise<{sha: string, ref: string, objects: number}>} */ export async function mergePull(session, repo, pull, mode) { const response = await authedFetch( session, `${account.pds}/xrpc/dev.pdsjs.git.mergePull`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: repo, did: pull.author.did, ref: `refs/heads/${pull.branch}`, mode, handle: pull.author.handle, ...(mode === 'squash' && pull.title ? { subject: pull.title } : {}), }), }, ); const body = await response.json().catch(() => ({})); if (!response.ok) { // A grant made before the scope named the repository record cannot // merge; the session provider sends such a session back through consent, // so this reports only what a fresh grant still could not do. throw new XrpcError( response.status, body.error ?? 'Unknown', body.message ?? `The merge failed (${response.status}).`, ); } return body; }