67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
import type { Commit, FilterState, Ref, RefKind } from "./types";
|
|
|
|
export function refKind(name: string): RefKind {
|
|
if (name.startsWith("refs/remotes/")) return "remote";
|
|
if (name.startsWith("refs/tags/")) return "tag";
|
|
return "local";
|
|
}
|
|
|
|
export function shortName(name: string): string {
|
|
return name.replace(/^refs\/(heads|remotes|tags)\//, "");
|
|
}
|
|
|
|
/** `*` matches any run of characters (including `/`), `?` matches one character. */
|
|
export function globMatch(pattern: string, text: string): boolean {
|
|
const re = pattern
|
|
.split("")
|
|
.map((ch) => (ch === "*" ? ".*" : ch === "?" ? "." : ch.replace(/[.+^${}()|[\]\\]/g, "\\$&")))
|
|
.join("");
|
|
return new RegExp(`^${re}$`).test(text);
|
|
}
|
|
|
|
export function isRefVisible(name: string, state: FilterState): boolean {
|
|
if (state.hidden.includes(name)) return false;
|
|
if (state.hideTags && name.startsWith("refs/tags/")) return false;
|
|
const short = shortName(name);
|
|
const only = state.rules.filter((r) => r.mode === "only");
|
|
if (only.length > 0 && !only.some((r) => globMatch(r.pattern, short))) return false;
|
|
return !state.rules.some((r) => r.mode === "hide" && globMatch(r.pattern, short));
|
|
}
|
|
|
|
/** Commits reachable from `starts`, in the original order. */
|
|
export function reachable(commits: Commit[], starts: Iterable<string>): Commit[] {
|
|
const byHash = new Map(commits.map((c) => [c.hash, c]));
|
|
const seen = new Set<string>();
|
|
const stack = [...starts];
|
|
while (stack.length > 0) {
|
|
const h = stack.pop()!;
|
|
if (seen.has(h)) continue;
|
|
const c = byHash.get(h);
|
|
if (!c) continue;
|
|
seen.add(h);
|
|
stack.push(...c.parents);
|
|
}
|
|
return commits.filter((c) => seen.has(c.hash));
|
|
}
|
|
|
|
const kindOrder: Record<RefKind, number> = { local: 0, remote: 1, tag: 2 };
|
|
|
|
/** For each commit, the ref whose first-parent line it lies on. */
|
|
export function lineOwners(commits: Commit[], refs: Ref[], headRef: string | null): Map<string, string> {
|
|
const byHash = new Map(commits.map((c) => [c.hash, c]));
|
|
const ordered = [...refs].sort(
|
|
(a, b) =>
|
|
Number(b.name === headRef) - Number(a.name === headRef) ||
|
|
kindOrder[refKind(a.name)] - kindOrder[refKind(b.name)],
|
|
);
|
|
const owners = new Map<string, string>();
|
|
for (const ref of ordered) {
|
|
let c = byHash.get(ref.target);
|
|
while (c && !owners.has(c.hash)) {
|
|
owners.set(c.hash, ref.name);
|
|
c = byHash.get(c.parents[0]);
|
|
}
|
|
}
|
|
return owners;
|
|
}
|