| f532c426 Andy McCall ↶ |
1 |
// "Resolve in VS Code" (OxygenHub's conflict resolver hands a conflict to |
| 26410312 Andy McCall ↶ |
2 |
// the extension): vscode://oxygenhub.oxygen/resolve?url=…&bookmark=…&path=… |
| f532c426 Andy McCall ↶ |
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 |
} |
| 19639205 Andy McCall ↶ |
43 |
|
| 44 |
export interface PlanRequest { |
| 45 |
url: string; |
| 46 |
bookmark: string; |
| 47 |
/** `squash` (change into target) or `rebase` (change onto target). */ |
| 48 |
kind: "squash" | "rebase"; |
| 49 |
change: string; |
| 50 |
target: string; |
| 51 |
} |
| 52 |
|
| 53 |
/** |
| 54 |
* A rewrite planned on OxygenHub's swimlanes: |
| 26410312 Andy McCall ↶ |
55 |
* vscode://oxygenhub.oxygen/plan?url=…&bookmark=…&kind=…&change=…&target=… |
| 19639205 Andy McCall ↶ |
56 |
*/ |
| 57 |
export function parsePlanQuery(query: string): PlanRequest | string { |
| 58 |
const q = new URLSearchParams(query); |
| 59 |
const base = parseResolveQuery(new URLSearchParams({ url: q.get("url") ?? "", bookmark: q.get("bookmark") ?? "", path: "x" }).toString()); |
| 60 |
if (typeof base === "string") { |
| 61 |
return base; |
| 62 |
} |
| 63 |
const kind = q.get("kind"); |
| 64 |
if (kind !== "squash" && kind !== "rebase") { |
| 65 |
return "the link names no squash or rebase"; |
| 66 |
} |
| 67 |
const hex = /^[0-9a-f]{64}$/; |
| 68 |
const change = q.get("change") ?? ""; |
| 69 |
const target = q.get("target") ?? ""; |
| 70 |
if (!hex.test(change) || !hex.test(target) || change === target) { |
| 71 |
return "the link names no two changes"; |
| 72 |
} |
| 73 |
return { url: base.url, bookmark: base.bookmark, kind, change, target }; |
| 74 |
} |