| 1 |
// "Resolve in VS Code" (OxygenHub's conflict resolver hands a conflict to |
| 2 |
// the extension): vscode://oxygen.oxygen-vscode/resolve?url=…&bookmark=…&path=… |
| 3 |
// Parsing and matching live here, apart from VS Code, so they are tested. |
| 4 |
|
| 5 |
export interface ResolveRequest { |
| 6 |
/** The repository's clone URL on the hub. */ |
| 7 |
url: string; |
| 8 |
bookmark: string; |
| 9 |
/** Relative to the repository root. */ |
| 10 |
path: string; |
| 11 |
} |
| 12 |
|
| 13 |
/** Read a resolve request's query string, or say what is wrong with it. */ |
| 14 |
export function parseResolveQuery(query: string): ResolveRequest | string { |
| 15 |
const q = new URLSearchParams(query); |
| 16 |
const url = (q.get("url") ?? "").trim(); |
| 17 |
const bookmark = (q.get("bookmark") ?? "").trim(); |
| 18 |
const path = (q.get("path") ?? "").trim().replace(/^\/+/, ""); |
| 19 |
if (!/^https?:\/\/[^\s]+$/.test(url)) { |
| 20 |
return "the link names no repository URL"; |
| 21 |
} |
| 22 |
if (!bookmark || /[\s~^:?*[\\]/.test(bookmark) || bookmark.includes("..")) { |
| 23 |
return "the link names no valid bookmark"; |
| 24 |
} |
| 25 |
if (!path || path.split("/").some((c) => c === "" || c === "." || c === ".." || c === ".oxy")) { |
| 26 |
return "the link names no valid path"; |
| 27 |
} |
| 28 |
return { url, bookmark, path }; |
| 29 |
} |
| 30 |
|
| 31 |
/** Whether two remote URLs name the same repository (case of the host and a trailing slash aside). */ |
| 32 |
export function sameRemote(a: string, b: string): boolean { |
| 33 |
const norm = (u: string) => { |
| 34 |
try { |
| 35 |
const x = new URL(u.trim()); |
| 36 |
return `${x.protocol}//${x.host.toLowerCase()}${x.pathname.replace(/\/+$/, "").replace(/\.oxy$/, "")}`; |
| 37 |
} catch { |
| 38 |
return u.trim().replace(/\/+$/, ""); |
| 39 |
} |
| 40 |
}; |
| 41 |
return norm(a) === norm(b); |
| 42 |
} |