Commit Graph
42 Commits
Author SHA1 Message Date
Stefan HallerandClaude Opus 5.5 4780c8bb72 Use the detailed commit graph automatically in terminals that draw it
The detailed commit graph is only drawn for users who set
gui.commitGraphStyle to 'detailed'. Many users of terminals that can
draw it will never find out about it. A terminal can't be asked whether
it draws a given character, but it does tell us its name and version
when tcell asks for them with XTVERSION at startup.

Add an 'auto' value and make it the default. It uses the detailed graph
in kitty from 0.36.2 and in Ghostty from 1.0.0 on. These are the first
versions that draw all of the symbols. Everywhere else it stays with
the classic graph. This includes tmux, because tmux answers XTVERSION
itself. So far, WezTerm draws the symbols only in its nightly builds, so
it isn't detected until a release has them. VS Code isn't detected
either, because it only draws the symbols with GPU acceleration.

The expected output of the integration tests has the classic graph, so
pin it in their config. Otherwise they would fail when run with a
visible UI in one of these terminals.

Log the terminal's name and version at startup, to make it possible to
find out why 'auto' picked what it did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:47:01 +02:00
Stefan HallerandClaude Opus 5.5 da5b9b54eb Add an option to draw the commit graph with branch drawing symbols
If a branch forks off a merge commit, the graph can't show both lines
in the merge commit's row. The line from the branch's first commit ends
in the same cell in which the merge's line to its second parent starts.
The box drawing characters have no symbol for a line from above that
bends to the left combined with a line from the left that bends down,
so one of the two gets lost. Today the cell shows a plain vertical
line, and the branch looks as if it sat on the merge's second parent
(#5497).

The branch drawing symbols in the Unicode Private Use Area (U+F5D0 to
U+F60D) have a symbol for every way in which lines meet in a cell.
kitty introduced them for git graph viewers like vim-flog, and Ghostty,
Contour, the terminal of VS Code and nightly builds of WezTerm render
them too. Other terminals need a font that contains them.

Add the gui.commitGraphStyle config. Its 'detailed' style draws the
graph with these symbols, and the commit symbols of the set connect to
their lines. Commits are drawn as hollow circles and merge commits as
filled ones. The default stays the 'classic' style with the box drawing
characters, because not every terminal can display the others.

A cell has only one colour. Where two lines meet, the symbol takes the
colour of the line from above. With the box drawing characters,
highlighting the lines of the selected commit clears the cells they run
through. The branch drawing symbols keep the other lines. A cell in
which a highlighted line bends is highlighted as a whole. Where a
highlighted line crosses the vertical line of another commit, it is
drawn over that line, so that it reads as one line. In a cell in which
another line bends, the symbol keeps the colour of that line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:47:01 +02:00
Stefan HallerandClaude Opus 5.5 74e7572d7a Extract the choice of a graph cell's box drawing characters
This separates choosing the characters from writing them out with their
styles, so that the next commit can add another set of characters.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:47:01 +02:00
Stefan HallerandClaude Opus 5.5 e5c5a62cd9 Record how lines run through the cells of the commit graph
A cell of the commit graph only records which of its edges are touched
by lines. That's all the box drawing characters can show, but it
doesn't say how the lines connect. A vertical line with a horizontal
line passing behind it touches all four edges. So does a cell in which
the line from above bends to the left, the line below comes in from the
left, and a horizontal line passes through.

For the lines at the top and bottom edges, record whether they run
straight on or bend to the left or right, and record whether a line
passes through horizontally. Nothing reads this yet. It prepares for
drawing the graph with symbols that can show these differences.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:47:01 +02:00
Stefan HallerandClaude Opus 5.5 91378b1e8a Stop drawing a line below root commits in the commit graph
If a root commit isn't the last one in the list, the graph draws a
vertical line below it in every row that follows. A root commit's pipe
to the empty tree never terminates, because no commit comes after it,
so it is carried on from row to row. The line also takes up its column,
and an unrelated commit after the root commit is placed to the right of
it.

Drop that pipe after the root commit's row. In its own row, it still
gives the commit symbol the style of the commit. Also, place a commit
that no line leads to next to the lines that go on into its row, not
next to the lines that ended in the row before. Their columns are free
again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:47:01 +02:00
Stefan HallerandClaude Opus 5.5 cee602c029 Demonstrate that the graph continues the line below a root commit
If a root commit isn't the last one in the list, the graph draws a
vertical line below it in every row that follows, although nothing
comes after a root commit. This happens in repos with more than one
root commit, for example when showing all branches of a repo with a
gh-pages branch, or after merging an unrelated history.

Also, a commit that no line leads to is placed to the right of the
lines that ended in the row before it, although their columns are free
again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:47:01 +02:00
Stefan HallerandClaude Opus 4.7 35d3659cce Use more widely-supported Unicode symbols for the commit graph
The commit graph used '⏣' (U+23E3 BENZENE RING WITH CIRCLE) for merge
commits and '◯' (U+25EF LARGE CIRCLE) for regular commits. Both have
very poor coverage in popular monospace fonts:

- '⏣' lives in the Misc Technical block and is essentially absent from
  every common monospace font (Source Code Pro, JetBrains Mono, Fira
  Code, Cascadia Code, Hack, Iosevka, Menlo, Consolas, Monaco, IBM
  Plex Mono, Ubuntu Mono, Noto Sans Mono, Inconsolata). It is always
  drawn from a system fallback font.
- '◯' is the late-addition LARGE CIRCLE codepoint. It is present in
  some fonts (Cascadia, Fira Code, Hack, Iosevka, Menlo, Noto Sans
  Mono) but missing from many others, including Source Code Pro and
  most Nerd Font derivatives based on it.

This is why the graph renders inconsistently across platforms even
when the same monospace font is configured: each OS picks a different
fallback font (Apple Symbols on macOS, Segoe UI Symbol on Windows,
Noto/DejaVu/Symbola on Linux), and the substituted glyphs differ in
shape, weight, and advance width. '◯' is also East Asian Ambiguous
width, so some terminals render it wider than one cell, exaggerating
the misalignment.

Replace the symbols with codepoints from the foundational 1991
Geometric Shapes block, which has far broader font coverage:

- Merge: '◎' U+25CE BULLSEYE -- concentric circles, the visually
  closest cousin to the previous benzene-ring glyph.
- Commit: '○' U+25CB WHITE CIRCLE -- the same hollow-circle silhouette
  as before, just a more universally available codepoint.

The new symbols are present in the font directly in significantly
more cases; and when fallback is still required, they are universally
well-drawn (unlike '⏣', which many fallback fonts also lack).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:06:29 +02:00
phanium d88f95275f Modernize all codes
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
2025-11-15 10:46:23 +01:00
Stefan Haller ca05a2ccea Enable revive linter, and fix a bunch of warnings
I took the set of enabled checks from revive's recommended configuration [1],
and removed some that I didn't like. There might be other useful checks in
revive that we might want to enable, but this is a nice improvement already.

The bulk of the changes here are removing unnecessary else statements after
returns, but there are a few others too.

[1] https://github.com/mgechev/revive?tab=readme-ov-file#recommended-configuration
2025-06-30 19:13:20 +02:00
Stefan Haller 0471dbaa84 Enable intrange linter, and fix warnings 2025-06-30 18:30:11 +02:00
Stefan Haller 6ca627d9d7 Store fromPos/toPos as 16-bit ints, and reorder fields for better packing
Hopefully, graphs will never get wider than 32768 characters. (They would get
kind of hard to navigate if they did...)

This reduces the size of the Pipe struct from 48 to 32 bytes, which makes a
significant difference when there are many millions of instances.
2025-04-29 14:57:15 +02:00
Stefan Haller e63abf89db Store TextStyle in Pipe struct as pointer
The instances are held by the AuthorStyle cache.
2025-04-29 14:57:15 +02:00
Stefan Haller 28aa26f30a Store Pipe objects by value in slice of Pipes
This saves some memory at the cost of a slight performance increase (I suppose
reallocting the slice when adding new Pipes is slightly more expensive now).

Performance of the BenchmarkRenderCommitGraph benchmark is 130μs before, 175μs
after. I'm guessing this is still acceptable.
2025-04-29 14:57:15 +02:00
Stefan Haller 18e5b0a650 Simplify equalHashes
Now that all hashes that we deal with are stored in the same pool, we can simply
compare their addresses.
2025-04-29 14:57:15 +02:00
Stefan Haller 13c21365c0 Store fromHash/toHash in Pipe struct as pointers
Now that commit hashes are stored in a pool and referenced by pointer by the
commits, we can use those same pointers in the pipes.
2025-04-29 14:57:15 +02:00
Stefan Haller 0f1f455edb Make Commit.Parents a getter for an unexported parents field
This is exactly the same as what we did for Hash earlier. And for the same
reason: we want to turn the parents field into a slice of pointers.
2025-04-29 14:57:15 +02:00
Stefan Haller e27bc15bbd Store Commit.Hash by pointer (kept in a pool of hashes)
This in itself is not an improvement, because hashes are unique (they are shared
between real commits and rebase todos, but there are so few of those that it
doesn't matter). However, it becomes an improvement once we also store parent
hashes in the same pool; but the real motivation for this change is to also
reuse the hash pointers in Pipe objects later in the branch. This will be a big
win because in a merge-heavy git repo there are many more Pipe instances than
commits.
2025-04-29 14:57:15 +02:00
Stefan Haller 1037371a44 Make Commit.Hash a getter for an unexported hash field
This is in preparation for turning the hash into pointer to a string.
2025-04-29 14:57:15 +02:00
Stefan Haller 97aa7a04e6 Rewrite generateCommits to avoid write access to commit.Parents
We want to unexport Parents in a later commit.
2025-04-29 14:57:15 +02:00
Stefan Haller cb0c8f39bf Simplify code and fix comment
The "// merge commit" comment was plain wrong, this is any commit that has a
parent, merge or not. The "else if" condition was unnecessary, a plain "else"
would have been enough. But the code in the two blocks was almost identical, so
extract the one thing that was different and unify it.

And while we're at it, use IsFirstCommit() instead of counting parents.
2025-04-29 14:57:15 +02:00
Stefan Haller 9d202cf9ea Remove unused function 2025-04-29 14:57:15 +02:00
Stefan Haller 4cfa6e0c98 Modernize benchmarks
See https://go.dev/blog/testing-b-loop
2025-04-29 14:57:15 +02:00
Kevin Radloff be7583dd40 Update to go 1.24 2025-03-08 14:53:54 -05:00
Jesse Duffield fdff2dec79 Remove redundant variable dedeclarations
In go 1.22, loop variables are redeclared with each iteration of the
loop, rather than simple updated on each iteration. This means that we
no longer need to manually redeclare variables when they're closed over
by a function.
2024-05-19 16:38:21 +10:00
pikomonde 9cf1ca10a2 rename sha to hash 5 2024-04-12 08:33:47 +02:00
pikomonde e6ef1642fa rename sha to hash 2024-04-12 08:33:47 +02:00
pikomonde 84333eebc3 renaming variable to CommitHash 2024-04-12 08:33:47 +02:00
pikomonde 7f6eea2a55 standardize 'Commit Sha' to 'Commit Hash' 2024-04-12 08:31:40 +02:00
Eng Zer Jun f933a2f7ec Replace min/max helpers with built-in min/max
We upgraded our minimum Go version to 1.21 in commit
57ac9c2189. We can now replace our
`utils.Min` and `utils.Max` functions with the built-in `min` and `max`.

Reference: https://go.dev/ref/spec#Min_and_max
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-04-07 23:24:10 +08:00
Stefan Haller c59e6b6451 Cleanup: don't mess with globals in tests without resetting them
Changing globals in the init() function of a test file is a bad idea, as it
affects all other tests that run after it. Do it explicitly in each test
function that needs it, and take care of restoring the previous value
afterwards.
2024-03-29 10:55:33 +01:00
Jesse Duffield e33fe37a99 Standardise on using lo for slice functions
We've been sometimes using lo and sometimes using my slices package, and we need to pick one
for consistency. Lo is more extensive and better maintained so we're going with that.

My slices package was a superset of go's own slices package so in some places I've just used
the official one (the methods were just wrappers anyway).

I've also moved the remaining methods into the utils package.
2023-07-30 18:51:23 +10:00
Jesse Duffield 5df27c61ed Apply correct styling to root commit in graph
The root commit is special in that it has no parents. So we need to add a pipe that's headed for a commit
that doesn't actually exist i.e. the mythical empty tree commit. We're using the actual hash of that
pseudo-commit, but it's not being read anywhere.
2023-06-01 22:20:30 +10:00
Stefan Haller 549ce09f71 Fix deprecated rand.Seed 2023-03-19 10:00:19 +01:00
Jesse Duffield e67fef776b add author email to commits 2022-05-08 14:26:18 +10:00
Jesse Duffield 94a53484a1 would you believe that I'm adding even more generics 2022-03-24 20:14:41 +11:00
Jesse Duffield 1b75ed3740 many more generics 2022-03-24 20:14:41 +11:00
Jesse Duffield eda8f4a5d4 lots more generics 2022-03-24 20:14:41 +11:00
Jesse Duffield a34bdf1a04 update linters 2022-03-19 12:12:57 +11:00
Jesse Duffield 18f48a43d5 add some more linters 2022-01-09 14:09:53 +11:00
Jesse Duffield 9a9e3d506d more consistent rendering 2021-11-05 07:58:21 +11:00
Jesse Duffield 802cfb1a04 render commit graph 2021-11-05 07:58:21 +11:00
Jesse Duffield 7a464ae5b7 add graph algorithm 2021-11-01 10:03:49 +11:00