/** * @file Lays out the Changes panel's History as a commit graph (the JetBrains / * `git log --graph` shape): each commit gets a column, and each row lists the * line segments drawn through it — a lane passing by, the lanes converging on * the commit from above, and the lines leaving it for its parents below. * Expects the commits in an order where no parent precedes a child * (`git log --date-order`); a parent outside the list leaves its lane running * off the bottom. * @author Nguyễn Ngọc Trí Vĩ */ /** One line in a row, in column units; y runs 0 (top) → 0.5 (node) → 1 (bottom). */ export interface GraphSeg { x1: number; y1: number; x2: number; y2: number; color: number; } export interface GraphRow { col: number; color: number; segs: GraphSeg[]; } export function commitGraph(commits: { sha: string; parents: string[] }[]): { rows: GraphRow[]; width: number; } { // lanes[j] = the sha the lane in column j is waiting for; colors[j] its hue. const lanes: (string | null)[] = []; const colors: number[] = []; const hue = (j: number) => colors[j] ?? 0; let nextColor = 0; let width = 1; const open = () => { const free = lanes.indexOf(null); const j = free < 0 ? lanes.length : free; colors[j] = nextColor++; return j; }; const rows = commits.map((c) => { const segs: GraphSeg[] = []; let col = lanes.indexOf(c.sha); // A tip nothing above points at: a lane of its own, no line from the top. if (col < 0) { col = open(); lanes[col] = null; } lanes.forEach((s, j) => { if (s === null) return; segs.push( s === c.sha ? { x1: j, y1: 0, x2: col, y2: 0.5, color: hue(j) } : { x1: j, y1: 0, x2: j, y2: 1, color: hue(j) } ); }); const color = hue(col); lanes.forEach((s, j) => { if (s === c.sha) lanes[j] = null; }); c.parents.forEach((p, i) => { let j = i === 0 ? col : lanes.indexOf(p); if (j < 0) j = open(); lanes[j] = p; segs.push({ x1: col, y1: 0.5, x2: j, y2: 1, color: hue(j) }); }); while (lanes.length && lanes[lanes.length - 1] === null) lanes.pop(); width = Math.max(width, lanes.length, col + 1); return { col, color, segs }; }); return { rows, width }; }