What I'm seeing
In the history view, a branch does not keep a stable horizontal position. As soon as a
path to its left ends, every path to the right shifts one column left. On a repository
with many short-lived branches, following a single branch means tracking it across
several columns as you scroll.
| SourceGit |
Another client, same repository |
 |
 |
Why it happens
Models/CommitGraph.Generate() derives a path's X coordinate from its rank in the
unsolved list, and that rank is recomputed on every row:
var offsetX = 4 - halfWidth;
foreach (var l in unsolved)
{
if (l.Next.Equals(commit.SHA, StringComparison.Ordinal)) { /* ... */ }
else
{
offsetX += unitWidth; // position derives from rank in the list
l.Pass(offsetX, offsetY, halfHeight);
}
}
So a lane is a side effect of iteration order rather than a property of the path. When a
path is removed from unsolved, everything after it slides one column left for the rest
of its life. The drift is a direct consequence, not a rendering artefact.
What I would like to propose
Two independent changes, both behind an opt-in setting:
1. Stable lanes. Give PathHelper an immutable Lane assigned when the path is
created and released when it ends — the same recycling pattern ColorPicker.Recycle()
already uses for colours, applied to positions instead. A released lane is only reused
after a quarantine of ~25 rows, so two unrelated branches never appear back to back in
the same column.
2. A dedicated BRANCH / TAG column. Move CommitRefsPresenter out of the
graph-and-subject cell into its own column, and give the graph its own column too. Commit
subjects then align on a single X instead of each row carrying its own LeftMargin.
CommitRefsPresenter is already self-contained (it measures itself, draws its own chips
and hit-tests through DecoratorAt), so this is mostly a XAML move plus a horizontal
offset on the graph overlay, which currently assumes it starts at x = 0.
Current behaviour would remain the default. The setting would sit in the history options
menu next to Commit order, as Compact (default) / Stable lanes.
What it costs
Measured on a synthetic history of 2,000 commits with power-law branch lifetimes, so
mostly short branches and a few long ones:
| Branches in window |
Compact (current) |
Stable lanes |
| 10 |
4 lanes |
4 lanes |
| 100 |
26 lanes |
30 lanes |
| 200 |
51 lanes |
54 lanes |
The extra width does not scale with the branch count: it is bounded by how many branches
happen to end inside the quarantine window, which stays small regardless of repository
size. Worth noting that at 200 concurrent branches the current compact layout already
needs ~600 px of graph, so a width budget for the graph column would help both modes.
Rendering gets slightly cheaper rather than more expensive: with a fixed lane, Pass()
stops emitting a point per row, so a straight branch is two points instead of one per
commit.
How I would split it
If the direction is acceptable, I would send this as separate PRs against develop
rather than one large change:
- Add
StartX to CommitGraphLayout and stop hard-coding the graph column index.
No behaviour change at all — StartX is 0 until a graph column exists.
- Separate graph column, aligned subjects.
BRANCH / TAG column with the existing refs presenter.
- Stable lane allocation and the settings entry.
Each one builds and is testable on its own, and the first is small enough to judge the
whole direction cheaply.
Questions before I write anything
- Is this something you would consider, or is the compact layout a deliberate design
choice you would rather keep?
- If you are open to it, is an opt-in setting the right call, or would you prefer the
new layout to simply replace the current one?
- Would separate PRs be welcome, or is that more review churn than you want for this?
Happy to drop it if the direction does not fit — I would rather ask before writing the
code than after.
What I'm seeing
In the history view, a branch does not keep a stable horizontal position. As soon as a
path to its left ends, every path to the right shifts one column left. On a repository
with many short-lived branches, following a single branch means tracking it across
several columns as you scroll.
Why it happens
Models/CommitGraph.Generate()derives a path's X coordinate from its rank in theunsolvedlist, and that rank is recomputed on every row:So a lane is a side effect of iteration order rather than a property of the path. When a
path is removed from
unsolved, everything after it slides one column left for the restof its life. The drift is a direct consequence, not a rendering artefact.
What I would like to propose
Two independent changes, both behind an opt-in setting:
1. Stable lanes. Give
PathHelperan immutableLaneassigned when the path iscreated and released when it ends — the same recycling pattern
ColorPicker.Recycle()already uses for colours, applied to positions instead. A released lane is only reused
after a quarantine of ~25 rows, so two unrelated branches never appear back to back in
the same column.
2. A dedicated
BRANCH / TAGcolumn. MoveCommitRefsPresenterout of thegraph-and-subject cell into its own column, and give the graph its own column too. Commit
subjects then align on a single X instead of each row carrying its own
LeftMargin.CommitRefsPresenteris already self-contained (it measures itself, draws its own chipsand hit-tests through
DecoratorAt), so this is mostly a XAML move plus a horizontaloffset on the graph overlay, which currently assumes it starts at x = 0.
Current behaviour would remain the default. The setting would sit in the history options
menu next to
Commit order, asCompact(default) /Stable lanes.What it costs
Measured on a synthetic history of 2,000 commits with power-law branch lifetimes, so
mostly short branches and a few long ones:
The extra width does not scale with the branch count: it is bounded by how many branches
happen to end inside the quarantine window, which stays small regardless of repository
size. Worth noting that at 200 concurrent branches the current compact layout already
needs ~600 px of graph, so a width budget for the graph column would help both modes.
Rendering gets slightly cheaper rather than more expensive: with a fixed lane,
Pass()stops emitting a point per row, so a straight branch is two points instead of one per
commit.
How I would split it
If the direction is acceptable, I would send this as separate PRs against
developrather than one large change:
StartXtoCommitGraphLayoutand stop hard-coding the graph column index.No behaviour change at all —
StartXis 0 until a graph column exists.BRANCH / TAGcolumn with the existing refs presenter.Each one builds and is testable on its own, and the first is small enough to judge the
whole direction cheaply.
Questions before I write anything
choice you would rather keep?
new layout to simply replace the current one?
Happy to drop it if the direction does not fit — I would rather ask before writing the
code than after.