OXYGENHUB

Coverage / src/handoff.ts 91.9%

68 of 74 lines run, 5 of 5 functions, 23 of 30 branches · run #26 on main · run missed

10×
// "Resolve in VS Code" (OxygenHub's conflict resolver hands a conflict to
20×
// the extension): vscode://oxygenhub.oxygen-vscode/resolve?url=…&bookmark=…&path=…
30×
// Parsing and matching live here, apart from VS Code, so they are tested.
41×
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
}
121×
130×
/** Read a resolve request's query string, or say what is wrong with it. */
141×
export function parseResolveQuery(query: string): ResolveRequest | string {
151×
  const q = new URLSearchParams(query);
161×
  const url = (q.get("url") ?? "").trim();
171×
  const bookmark = (q.get("bookmark") ?? "").trim();
181×
  const path = (q.get("path") ?? "").trim().replace(/^\/+/, "");
191×
  if (!/^https?:\/\/[^\s]+$/.test(url)) {
201×
    return "the link names no repository URL";
211×
  }
221×
  if (!bookmark || /[\s~^:?*[\\]/.test(bookmark) || bookmark.includes("..")) {
232×
    return "the link names no valid bookmark";
242×
  }
251×
  if (!path || path.split("/").some((c) => c === "" || c === "." || c === ".." || c === ".oxy")) {
262×
    return "the link names no valid path";
272×
  }
281×
  return { url, bookmark, path };
291×
}
301×
311×
/** Whether two remote URLs name the same repository (case of the host and a trailing slash aside). */
321×
export function sameRemote(a: string, b: string): boolean {
333×
  const norm = (u: string) => {
346×
    try {
356×
      const x = new URL(u.trim());
366×
      return `${x.protocol}//${x.host.toLowerCase()}${x.pathname.replace(/\/+$/, "").replace(/\.oxy$/, "")}`;
376×
    } catch {
380×
      return u.trim().replace(/\/+$/, "");
390×
    }
403×
  };
413×
  return norm(a) === norm(b);
425×
}
435×
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
}
525×
535×
/**
545×
 * A rewrite planned on OxygenHub's swimlanes:
555×
 * vscode://oxygenhub.oxygen-vscode/plan?url=…&bookmark=…&kind=…&change=…&target=…
565×
 */
575×
export function parsePlanQuery(query: string): PlanRequest | string {
585×
  const q = new URLSearchParams(query);
595×
  const base = parseResolveQuery(new URLSearchParams({ url: q.get("url") ?? "", bookmark: q.get("bookmark") ?? "", path: "x" }).toString());
605×
  if (typeof base === "string") {
611×
    return base;
621×
  }
634×
  const kind = q.get("kind");
645×
  if (kind !== "squash" && kind !== "rebase") {
651×
    return "the link names no squash or rebase";
661×
  }
673×
  const hex = /^[0-9a-f]{64}$/;
685×
  const change = q.get("change") ?? "";
695×
  const target = q.get("target") ?? "";
705×
  if (!hex.test(change) || !hex.test(target) || change === target) {
712×
    return "the link names no two changes";
722×
  }
735×
  return { url: base.url, bookmark: base.bookmark, kind, change, target };
745×
}