// "Resolve in VS Code" (OxygenHub's conflict resolver hands a conflict to // the extension): vscode://oxygenhub.oxygen-vscode/resolve?url=…&bookmark=…&path=… // Parsing and matching live here, apart from VS Code, so they are tested. export interface ResolveRequest { /** The repository's clone URL on the hub. */ url: string; bookmark: string; /** Relative to the repository root. */ path: string; } /** Read a resolve request's query string, or say what is wrong with it. */ export function parseResolveQuery(query: string): ResolveRequest | string { const q = new URLSearchParams(query); const url = (q.get("url") ?? "").trim(); const bookmark = (q.get("bookmark") ?? "").trim(); const path = (q.get("path") ?? "").trim().replace(/^\/+/, ""); if (!/^https?:\/\/[^\s]+$/.test(url)) { return "the link names no repository URL"; } if (!bookmark || /[\s~^:?*[\\]/.test(bookmark) || bookmark.includes("..")) { return "the link names no valid bookmark"; } if (!path || path.split("/").some((c) => c === "" || c === "." || c === ".." || c === ".oxy")) { return "the link names no valid path"; } return { url, bookmark, path }; } /** Whether two remote URLs name the same repository (case of the host and a trailing slash aside). */ export function sameRemote(a: string, b: string): boolean { const norm = (u: string) => { try { const x = new URL(u.trim()); return `${x.protocol}//${x.host.toLowerCase()}${x.pathname.replace(/\/+$/, "").replace(/\.oxy$/, "")}`; } catch { return u.trim().replace(/\/+$/, ""); } }; return norm(a) === norm(b); } export interface PlanRequest { url: string; bookmark: string; /** `squash` (change into target) or `rebase` (change onto target). */ kind: "squash" | "rebase"; change: string; target: string; } /** * A rewrite planned on OxygenHub's swimlanes: * vscode://oxygenhub.oxygen-vscode/plan?url=…&bookmark=…&kind=…&change=…&target=… */ export function parsePlanQuery(query: string): PlanRequest | string { const q = new URLSearchParams(query); const base = parseResolveQuery(new URLSearchParams({ url: q.get("url") ?? "", bookmark: q.get("bookmark") ?? "", path: "x" }).toString()); if (typeof base === "string") { return base; } const kind = q.get("kind"); if (kind !== "squash" && kind !== "rebase") { return "the link names no squash or rebase"; } const hex = /^[0-9a-f]{64}$/; const change = q.get("change") ?? ""; const target = q.get("target") ?? ""; if (!hex.test(change) || !hex.test(target) || change === target) { return "the link names no two changes"; } return { url: base.url, bookmark: base.bookmark, kind, change, target }; }