/** * Which of a checkout's remotes is the upstream of the copy being pushed. * * A fork is a clone of somebody else's repository pushed to one's own. The * checkout knows both: the remote it was cloned from and the remote it pushes * to. When the push goes to a copy under the same name at another account, * the remote it came from is the upstream, and the pushed record can say so. * * Nothing here runs git; the caller hands in what `git config` listed. */ import { parseAtprotoUrl } from './url.js'; /** * @typedef {Object} Remote * @property {string} name * @property {string} url */ /** * The remotes in `git config --get-regexp '^remote\..*\.url$'` output: one * line each, `remote..url `. * @param {string} text * @returns {Remote[]} */ export function parseRemotes(text) { /** @type {Remote[]} */ const remotes = []; for (const line of text.split('\n')) { const match = /^remote\.(.+)\.url\s+(\S+)\s*$/.exec(line.trim()); if (match) remotes.push({ name: match[1], url: match[2] }); } return remotes; } /** * The remote the pushed copy was taken from: an atproto remote naming the * same repository at another account, outside any space. Where several fit, * `origin` is the one a clone made. * @param {Remote[]} remotes * @param {{authority: string, repoName: string, space?: unknown}} target - * where the push is going * @returns {{name: string, authority: string, repoName: string}|null} */ export function upstreamRemote(remotes, target) { if (target.space) return null; /** @type {Array<{name: string, authority: string, repoName: string}>} */ const fits = []; for (const remote of remotes) { /** @type {ReturnType} */ let parsed; try { parsed = parseAtprotoUrl(remote.url); } catch { continue; } if (parsed.repoName !== target.repoName) continue; if (parsed.authority.toLowerCase() === target.authority.toLowerCase()) { continue; } fits.push({ name: remote.name, authority: parsed.authority, repoName: parsed.repoName, }); } return fits.find((one) => one.name === 'origin') ?? fits[0] ?? null; }