// "Resolve in VS Code" (OxygenHub's conflict resolver hands a conflict to // the extension): vscode://oxygen.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); }