| 68 | } |
| 69 | |
| 70 | function computeLayout(commits: Commit[]): GraphRow[] { |
| 71 | const rows: GraphRow[] = [] |
| 72 | // Active rails: each slot holds the hash of the commit it's "waiting for" |
| 73 | const rails: (string | null)[] = [] |
| 74 | |
| 75 | for (const commit of commits) { |
| 76 | const hash = commit.hash |
| 77 | |
| 78 | // Find which rail this commit occupies (if any rail is waiting for it) |
| 79 | let commitRail = rails.indexOf(hash) |
| 80 | |
| 81 | if (commitRail === -1) { |
| 82 | // New branch — find first empty slot or append |
| 83 | const emptySlot = rails.indexOf(null) |
| 84 | if (emptySlot !== -1) { |
| 85 | commitRail = emptySlot |
| 86 | rails[commitRail] = hash |
| 87 | } else { |
| 88 | commitRail = rails.length |
| 89 | rails.push(hash) |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | const commitColor = color(commitRail) |
| 94 | const edges: Edge[] = [] |
| 95 | |
| 96 | // Draw straight lines for all other active rails (pass-through) |
| 97 | for (let r = 0; r < rails.length; r++) { |
| 98 | if (r !== commitRail && rails[r] !== null) { |
| 99 | edges.push({ |
| 100 | fromRail: r, |
| 101 | toRail: r, |
| 102 | color: color(r), |
| 103 | type: "straight", |
| 104 | }) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // Clear this rail — the commit has been rendered |
| 109 | rails[commitRail] = null |
| 110 | |
| 111 | // Process parents |
| 112 | const parents = commit.parents |
| 113 | if (parents.length >= 1) { |
| 114 | const firstParent = parents[0] |
| 115 | // First parent continues on the same rail |
| 116 | const existingRail = rails.indexOf(firstParent) |
| 117 | if (existingRail !== -1) { |
| 118 | // Parent already expected on another rail — merge line |
| 119 | edges.push({ |
| 120 | fromRail: commitRail, |
| 121 | toRail: existingRail, |
| 122 | color: commitColor, |
| 123 | type: "merge-in", |
| 124 | }) |
| 125 | } else { |
| 126 | // Parent takes this commit's rail |
| 127 | rails[commitRail] = firstParent |