gites: colorful git tree viewer
Tauri 2 + Vue app that draws the commit graph of the repository given on the command line. Branches can be hidden one by one or by patterns, runs of plain commits fold into a dotted line, and everything is reachable from the keyboard.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user