If gui.authorColors names no color for an author, lazygit picks one
from a hash of their name, at an HSL lightness between 0.4 and 0.6.
HSL lightness says nothing about how bright a color looks, though. At
0.5 a yellow is glaring and a blue is nearly black, so whether an
author's initials can be read comes down to where their name happens
to hash to. Against a background of #1e1e1e, 45% of four thousand
names fall below a contrast ratio of 4.5:1 and 21% below 3:1, with the
worst at 1.46:1. Several issues have been raised about author colors
being too dark to read.
Use HSLuv instead. Its lightness tracks perceived brightness, so the
range can be narrow now that a number in it means something. Against
#1e1e1e, every author now lands between 4.7:1 and 7.7:1, so no name is
unlucky any more.
This changes the color of every author who isn't named in
gui.authorColors. On a light background, the new colors are uniformly
too pale, where before they were mostly too pale. The next commit gives
a light background a range of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If gui.authorColors names no color for an author, lazygit derives one
from a hash of their name. Checking whether these colors are readable
means looking at a repository, but the authors of a real one rarely
land near the edges of the range, so the worst cases go unseen.
Add cmd/author_colors_repo. It searches for names at the lowest and
highest lightness and saturation, at twelve hues, and commits once as
each of them. The names depend only on the hashing, so the same
repository shows the colors of any lazygit build and can be kept
around to compare them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The next commit adds a tool that creates a repository of authors at
the edges of the range their colors are picked from. To find such
names, it needs to know where a name lands in the range, and it
shouldn't keep a copy of the hashing that could drift from this one.
Later commits test the colors, and need the colors themselves for that
rather than the styles made from them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the config is reloaded, SetCustomAuthors replaces the styles of
authors, but the initials and names that were rendered with the old
styles stay cached. So do the pipes of the commit graph; each of them
carries the style of the author of the commit it starts at.
Drop these whenever the colors of authors change. The graph's cache
finds out by itself, by comparing a version number, so that nothing
has to remember to reset it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
If gui.authorColors changes while lazygit is running, the authors that
are already on screen keep their old colors, both in the author column
and in the commit graph.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The styles from gui.authorColors and the ones derived from the names
of the other authors share a map, so the derived ones can't be dropped
without the configured ones. A later commit needs to drop the derived
ones on their own, when the terminal switches between dark and light.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lazygit doesn't know whether the terminal it runs in has a dark or a
light background. The colors of authors, for example, have to suit one
or the other, and are too pale to read on a light background.
Ask the terminal. Many terminals answer CSI ? 996 n with whether they
are dark or light, and send the same report again whenever that changes
while mode 2031 is on. Terminals differ in what they base this on,
though. kitty goes by its background color, but Ghostty goes by the dark
or light mode of the operating system, even with a theme that doesn't
follow it. So also ask for the background color with OSC 11. If the
terminal answers that, the background decides, and a report only makes
us ask for the background again. Some terminals answer OSC 11 but send
no reports; ask these for the background again whenever the terminal
gains focus.
Turn mode 2031 off whenever lazygit hands the terminal to another
program, so that the program doesn't receive the reports as typed text.
For the same reason, first wait for the answers to any queries that are
still outstanding, but for no longer than half a second. Locally, the
answers take a few milliseconds at most, but over ssh they take a
network round trip, and focusing the terminal and then pressing a key
that starts an editor fits into that.
Leave out the terminals that tcell doesn't send its own queries to,
except for Terminal.app and WezTerm; these are only asked for the
background color.
This doesn't make startup any slower. tcell sends its own queries in
Screen.Init and waits for the terminal to answer them. Ours go out just
before, so their answers arrive before tcell stops waiting, and the
color scheme is known before the first layout.
tcell has no support for any of this, and drops the answers when it
parses its input. Instead of adding support to tcell, wrap the tty that
tcell reads from and watch the input as it goes by.
For now, lazygit only writes the color scheme to the debug log.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the Windows CI job,
TestStartPipelineReadsWhatTheCommandsComplainAbout fails now and then
because the output it reads has an extra line after the expected one.
The line starts with "error: coverage meta-data emit failed" and ends
with "The process cannot access the file because it is being used by
another process."
The pipeline tests run the test binary itself as the members of the
pipeline. CI builds it with -cover, and the members inherit GOCOVERDIR
from go test, so each of them writes coverage data to that directory
when it calls os.Exit. The meta-data file has the same name for every
process of one binary, and every process replaces it by renaming a new
copy onto it. (Go's check for an existing file compares its size against
the wrong length, so it never finds one.) On Windows this rename fails
if another process holds the file open. In this test both members exit
at the same time, and the runtime prints the failure to stderr.
StartPipeline puts every member's stderr into the output that the test
compares.
Exit the members with syscall.Exit instead. It skips the runtime's exit
hooks, so the members write no coverage data at all. Nothing is lost by
this. Their coverage data went to a temporary directory of go test, not
to the directory that CI uploads.
This fixes a regression introduced with #6025.
On the Windows CI job,
TestStartPipelineReadsWhatTheCommandsComplainAbout fails now and then
because the output it reads has an extra line after the expected one.
The line starts with "error: coverage meta-data emit failed" and ends
with "The process cannot access the file because it is being used by
another process."
The pipeline tests run the test binary itself as the members of the
pipeline. CI builds it with -cover, and the members inherit GOCOVERDIR
from go test, so each of them writes coverage data to that directory
when it calls os.Exit. The meta-data file has the same name for every
process of one binary, and every process replaces it by renaming a new
copy onto it. (Go's check for an existing file compares its size against
the wrong length, so it never finds one.) On Windows this rename fails
if another process holds the file open. In this test both members exit
at the same time, and the runtime prints the failure to stderr.
StartPipeline puts every member's stderr into the output that the test
compares.
Exit the members with syscall.Exit instead. It skips the runtime's exit
hooks, so the members write no coverage data at all. Nothing is lost by
this. Their coverage data went to a temporary directory of go test, not
to the directory that CI uploads.
The test was added in dfd6a7dbf2.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In #5740 we implemented PTY support for Windows; back then we thought
this is a prerequisite for supporting custom diff renderers (which were
still called "custom pagers" back then), because git will only use the
GIT_PAGER env var when it is running in a PTY. The problem is that the
Windows PTY, being based on ConPTY, does not behave like a Unix PTY,
which basically just passes through all data from the client. ConPTY
renders what it receives from the client into its own screen buffer, and
then re-encodes it from there for the terminal side. This has already
caused problems that are awkward to work around (e.g. ConPTY will
convert a series of multiple blank lines to a cursor positioning escape
sequence, so we need to parse that and convert it back, see
180fe0cd26); but now, with the upcoming OSC 1717 work, it turns out
that it's impossible to attach OSC 1717 metadata records to the cells
they belong to, because ConPTY sends those immediately to the terminal,
but the rest of the cell data some time later, and it's impossible to
reconstruct the original stream.
So use an ordinary pipe on Windows, where we start git and the diff
renderer on our side instead of telling git to drive the renderer. It's
a shame that we didn't realize it's possible; we could have done this
years ago without having to wait for a working Windows PTY.
One downside is that the diff renderer can no longer ask the terminal
how wide it is, so if it needs to know that (e.g. for a side-by-side
diff, or for horizontal lines that should be as wide as the view), then
it needs another way to find out. We set the `COLUMNS` environment
variable, which delta, difftastic and diff-so-fancy all support in their
latest versions, and for those renderers that don't, we provide a
`{{width}}` template variable that can be used in a diff renderer
command to pass it as a command-line argument.
Renders were kept from refreshing git's index because a pty-rendered
command on Windows is terminated at an arbitrary instruction when its
task stops, and one landing in the window where git holds index.lock to
write back refreshed stat information leaves that lock behind.
A render no longer runs in a pty there, and nothing kills it any more
either: the pipe to the renderer breaks, git's write fails, and git dies
through its own die path with its lock files cleaned up. So let the
refresh happen, and let renders heal stale stat info the way they do
everywhere else.
The teardown of a pseudoconsole took its reassurance about index.lock
from this, and renders were the reason it held. What still runs in a pty
there is a custom command asking for logWithPty and a command that may
be asked for a credential, so the claim no longer follows; drop it
rather than restate it for clients it was never about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ConPTY doesn't carry a diff renderer's output to us as the renderer
wrote it. It parses the output into a screen buffer and re-encodes that
for the terminal side, and a sequence it can't represent there goes out
the moment it is parsed, separately from the text around it. The OSC
1717 records a renderer states its diff lines in therefore arrive
detached from the rows they describe, and the identity layer attributes
rows to the wrong diff line or to none.
Feed the renderer through a pipe there instead, so that its bytes reach
us unaltered. A stdin filter becomes a command of our own, since git
only invokes the one named by GIT_PAGER when it talks to a terminal; an
external diff renderer is git's own business either way and needs
nothing but the pipe.
Unix keeps the pty. A renderer reads the width to lay out to off it, so
taking it away would leave every configuration that doesn't name a
width rendering at whatever the renderer falls back to, and diff
renderers have worked on Unix far too long for that.
LAZYGIT_RENDER_WITHOUT_PTY asks for the piped path anyway, which is how
the integration tests cover it on a platform where they run at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LAZYGIT_COLUMNS told a diff renderer script the width of the view at a
time when a render on Windows ran without a pty, so that the script had
nowhere else to read it from. It was documented for that case only, and
the documentation went when Windows gained a pty; it has not been
mentioned since.
COLUMNS now tells every command rendering into a view the same thing,
in the variable git and the common renderers already read, and the
{{width}} template variable covers a renderer that reads neither. The
value LAZYGIT_COLUMNS carried was also the width before the layout
pass, which is not always the width the view ends up with.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tell a command that renders into a view how wide that view is, through
COLUMNS. git reads it in preference to the size of the terminal it is
talking to, so the diffstat now fills the view whether or not the render
has a terminal to offer.
A diff renderer that can't ask a terminal gets the width from it too;
difftastic, diff-so-fancy and delta all support COLUMNS, so we can stop
running git in a PTY and these will still work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
git scales the graph of a diffstat to the width of the terminal it is
talking to. A render that talks to no terminal tells it no width, so the
stat comes out narrower than the view it is shown in, wasting most of a
wide one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The task runs a command with its output shown through the configured
diff renderer. A pty is one way of getting the output to the renderer,
and is about to become one of two, so the task can no longer be named
after it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A render is about to have a second way of giving the diff renderer the
command's output, so the part that decides how it runs needs to be
apart from the part that does.
Move the setting up to newRenderTask, named for what it does now that a
pty is one of two ways of doing it. Leave the pty with the pair of
functions a task drives it by, and with naming the stdin filter to git
as its pager, since git only runs a pager when it talks to a terminal.
What a way needs to know about the render it runs travels as a
renderSpec.
Running the command plainly, with its output going straight into a
pipe, is a way of its own already, and newCmdTask ends as newRenderTask
does, by creating the task that reads the output into the view. Put the
plain way on the same seam as plainRender, and share the ending as
newTaskForRender.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Like a stdin filter, an external diff program lays out its rendering to
the width it reads off the terminal, and a render through a pipe leaves
it nothing to read. difftastic in side-by-side mode is the case that
shows it.
Offer the width as the {{width}} template variable, the same name the
stdin filter command takes it by. Since difftastic supports the COLUMNS
variable, which we will set later in this branch, and I don't know of
any other external-diff renderer that doesn't, we don't document this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An external diff command rides on the git command as a diff.external
config, which means it is fixed when the command is built. That is
before the layout pass, so the width the renderer is to lay out for
isn't known yet, and the command can't be told about it.
Pass it as GIT_EXTERNAL_DIFF from the render instead, where the width
is known and where a stdin filter is already handed to git the same
way. git ranks the variable exactly as it ranks the config, behind a
per-path diff driver from .gitattributes, so a repository that defines
one still gets it (verified on git 2.22.5 and 2.55). An empty command
means the user wants their own git config to apply, so leave the
variable unset for that; git takes it being set at all as an
instruction.
The renderer command leaves the git arguments, so it also leaves the key
that says which diff a render is of. Cycling between two external diff
renderers now keeps the view's place, the way cycling between two stdin
filters already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A stdin filter finds out how wide to lay out its rendering by asking the
terminal. That is one of the two jobs the pty around a render does.
Rendering through a pipe instead, as Windows is about to do, leaves the
renderer to pick a width of its own, and a side-by-side rendering comes
out at the wrong size.
Offer the width as the {{width}} template variable, so that a
configuration can name it on the command line where the renderer can no
longer ask for it. {{columnWidth}} is derived from the same number.
This is only needed by diff renderers which don't support the COLUMNS
variable, which we will set later in this branch; delta and
diff-so-fancy both do in their latest versions, so we don't document
this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rendering a diff through a renderer on Windows means running the
renderer ourselves, since ConPTY mangles the metadata records it emits
and git only invokes a renderer of its own when it talks to a terminal.
That needs a chain of commands whose output a view can be filled from
while it runs, where PipeCommands runs a chain to completion and reports
what it said afterwards.
StartPipeline starts such a chain and hands back the reader for its
output, along with a handle offering exactly what a render task asks of
a command: something to wait for, something to name it by, and a way to
stop it. Every command's stderr joins the output, so a renderer that
objects to its input says so where the diff would have been.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
If a command in a pipeline exits before reading all of its input, the
command feeding it keeps running and PipeCommands never returns.
The parent holds on to the read end of every pipe it wires between two
commands. A pipe with a reader is a pipe worth writing to, so the
command writing into it is never told that nobody is listening.
Close the parent's ends once the commands are running, since each of
them holds its own by then. The write ends have to go as well, or the
command reading a link never reaches the end of its input.
No caller reaches this today. The one chain lazygit pipes is
`git stash show -p` into `git apply -R`, and apply reads its whole input
before it does anything with it, so it never leaves the show writing to
nobody. The chain about to be added for diff renderers is a different
matter: a renderer can fail at any point of its input, and a render that
is no longer wanted is stopped part way through by design.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PipeCommands runs a chain of commands to completion and gathers what
they wrote to stderr. Rendering a diff through a renderer needs the same
chain, but has to read the last command's output as it arrives, so it
can't use PipeCommands as it stands.
Pull out what both need: naming the chain for a log, wiring each
command's output to the next one's input, and starting them all with the
cleanup a failure to start requires.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When lazygit starts up in a repo with many remote branches, the pull
request icons in the branches panel showed up a good while after the
branch list itself, even though the pull requests come from the cache
file and are in the model before the first render. In the repo where
this showed up (5400 remote branches) the icons were up to half a second
late.
The icons are rendered from Model.PullRequestsMap, and that map is built
from the remotes' URLs, because a branch's upstream remote tells us
which repo owner's pull requests to look for. The map therefore stays
empty until the remotes are in the model, and the remotes refresh
doesn't put them there until it has also enumerated and sorted all
remote branches. In a big repo that takes hundreds of milliseconds;
reading the remotes themselves takes ten.
Load the two separately, and put the remotes in the model as soon as
they have been read from the git config. The branches refresh then finds
them there, and the pull request icons are part of the first render of
the branch list.
The pull request icons in the branches panel come from the remotes, so a
branches render that happens before the remotes are in the model shows no icons,
and the render that follows once the remotes land has to add them. The branches
refresh already waits for the worktrees for exactly this reason.
Wait for the remotes as well. They are read from the git config before their
branches are loaded, so the wait is over well before the branch load itself is
done, and the first render of the branch list shows the icons no matter which of
the two finishes first.
Start the remotes scope before the branches scope, so that an early return in
performRefresh can't leave the branches refresh waiting for a scope that was
never started. The worktrees scope goes first for the same reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When lazygit starts up in a repo with many remote branches, the pull request
icons in the branches panel show up a good while after the branch list itself,
even though the pull requests come from the cache file and are in the model
before the first render. In the repo where this showed up (5400 remote
branches) the icons were up to half a second late.
The icons are rendered from Model.PullRequestsMap, and that map is built from
the remotes' URLs, because a branch's upstream remote tells us which repo
owner's pull requests to look for. The map therefore stays empty until the
remotes are in the model, and the remotes refresh doesn't put them there until
it has also enumerated and sorted all remote branches. In a big repo that takes
hundreds of milliseconds; reading the remotes themselves takes ten.
Load the two separately, and put the remotes in the model as soon as they have
been read from the git config. The branches refresh then finds them there, and
the pull request icons are part of the first render of the branch list.
Carry over the branches of the remotes we already have in the model in that
first update, so that the remote branches, and the branch counts in the remotes
panel, stay in place until the fresh ones are loaded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When only one file inside a nested folder has changes, the files panel
shows the whole path to it on a single line:
```
▼ a/b/c
M file.txt
```
Selecting that line shows the diff of everything below it, in this case
the entire working tree. As soon as a second file changes in a different
folder, the panel splits that line up:
```
▼ a/b
▼ c
M file.txt
▼ d
M other_file.txt
```
Until now, the selection moved down to `c` in this situation, so the
main view suddenly showed only the diff of that folder. Now the
selection stays on the outer folder, `a/b` here. That is the same line
as before, and it still shows the diff of everything. This is most
useful if you like to keep the top folder selected to always see the
diff of the whole working tree; the same rule applies to folders further
down the tree. The reverse case, where two folders fold back into a
single line, already kept the selection on that line.
Along the way we also fixed two small bugs in how the selection follows
a rename after a refresh; see the individual commit messages for
details.
When a single file is modified inside a nested directory, the file tree
compresses the whole chain of directories into one line, such as
"pkg/gui/controllers/helpers". Selecting that line shows the diff of the
entire working tree. When a second file is then modified in another
subdirectory of pkg/gui, the tree splits the line into "pkg/gui" with
"context" and "controllers/helpers" below it, and the refresh moves the
selection down to "controllers/helpers". Users who keep the top
directory selected to see the diff of everything lose that view and
have to move the cursor back up after every such refresh.
This happens because the selection is re-found by the node's own path,
and a compressed node's path is the deepest directory in its chain. The
node stood for every directory in that chain, though, and the topmost
piece of the split is the one that stays on the same line.
Match a compressed directory node against any new node that stands for
at least one of the same directories. The list is in depth-first order,
so the topmost piece wins and the cursor stays on its line. Files are
never compressed, so their handling doesn't change. The reverse case,
where two directories fold back into one compressed line, already
selected the merged line and still does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When the deletion of the selected file is staged and git then reports
it as the old half of a rename whose new half sits in a collapsed
directory, the selection is meant to move to the rename. With
gui.showRootItemInFileTree turned on, the directory stays collapsed and
the selection lands on it. With the option turned off, the directory
expands, but if another file in it sorts before the rename, that file
gets selected.
The loop that expands the directory runs before the tree is rebuilt and
works on the file list instead of on tree nodes. It compares the
rename's previous path, a user-facing path, against the selected node's
internal path, so the two never match while the root item is shown. The
path it hands to ExpandToPath is user-facing as well, so the directory
would stay collapsed either way. And because the loop runs before the
old node list is captured, expanding a directory above the selection
shifts that list, and the search for the new selection starts from
whatever node moved into the selected line.
Rebuild the tree first, capture the old node list before anything
expands, and then look for the rename among the leaves of the new tree.
ExpandToPath gets the leaf's own internal path, so the two kinds of
paths never need converting.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When the deletion of the selected file is staged and git then reports
it as the old half of a rename whose new half sits in a collapsed
directory, the selection is meant to move to the rename, expanding the
directory on the way. With gui.showRootItemInFileTree turned on, the
directory stays collapsed and the selection lands on it instead. With
the option turned off, the directory does expand, but if another file
in it sorts before the rename, that file gets selected.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When the selected rename splits into its two halves, e.g. because it was
unstaged, the selection is meant to move to the new half. With the
default setting of gui.showRootItemInFileTree, it lands on the file that
follows the rename in the list instead.
findNewSelectedIdx identifies a rename by the names of its two halves.
These are user-facing paths, without the "./" prefix that the root item
adds to every internal path, but they were compared against internal
paths. The comparison never matched while the root item was shown.
Compare user-facing paths throughout findNewSelectedIdx. Node.ID already
identifies list items by their user-facing path.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When a rename is selected in the files panel and then splits into its
two halves, e.g. because it was unstaged, the selection is meant to move
to the new half. This only works with gui.showRootItemInFileTree turned
off. With the default setting, the selection lands on the file that
follows the rename in the list instead.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
It was added in #3751 with the explicit reservation that we remove it
again when it turns out to be annoying. This is actually the case now; I
don't find catching spelling errors valuable enough to warrant the
nuisance of having to silence false positives. (And AI agents don't make
spelling mistakes anyway :-)
It was added in #3751 with the explicit reservation that we remove it
again when it turns out to be annoying. This is actually the case now; I
don't find catching spelling errors valuable enough to warrant the
nuisance of having to silence false positives. (And AI agents don't make
spelling mistakes anyway :-)
With stacked branches it is common to rebase the whole stack in one go,
and when doing that it is likely that all branch tips get the same
committer date (because git's time stamps have a granularity of one
second). We sort branches by committer date by default, and git sorts
branches with the same committer date alphabetically, which causes
stacked branches to be listed out of order.
To improve this, detect groups of branches that share the same committer
date, and sort them by ancestry, so that a branch comes before the
branches it is based on. This takes one extra for-each-ref call (only
when such a group exists), and it requires git 2.41 or later.
Force-pushing a stack of branches puts the same commits on the remote,
so the remote branches panel shows the same alphabetical order for a
stack that the local branches panel did. Sort them by ancestry too,
which needs the tip hash and committer date of each remote branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sorting the branches of a remote by ancestry works on the whole list, so
build the list first and group it by remote once it is in the right
order.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It needs the git version to decide whether the ahead-behind format is
available, and GitCommon already carries the common state and the command
builder that the loader used to take separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
In date order, the branches panel lists a stack of branches
alphabetically rather than from the top of the stack down. Rebasing a
stack creates all of its commits within the same second, so all of its
branch tips end up carrying the same committer date, and git sorts refs
with equal committer dates by name.
Sort each group of branches that share a committer date by ancestry
instead, so that a branch comes before the branches it is based on.
Branches that are not descended from one another keep the alphabetical
order git gave them. Determining the ancestry takes one more
for-each-ref call, and it only runs when there is a group to sort.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parseAheadBehindForEachRefOutput dropped fields it couldn't parse, which
left the remaining ones of that line pointing at the wrong bases. The
caller that picks the closest base doesn't care, but sorting refs by
ancestry has to know which base a pair of numbers belongs to. Return an
entry per base and mark the ones that were malformed, as the function's
comment has claimed all along.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The function always asked about every ref under refs/heads. Sorting refs
by ancestry needs the ahead-behind values of a handful of named refs, and
for remote branches those live under refs/remotes, so take the patterns
as an argument. The bases can be commit hashes as well as ref names.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Building and parsing `git for-each-ref --format=%(ahead-behind:<base>)`
is not specific to loading local branches; the commits below use it to
sort refs by ancestry, from the remote branch loader too. Give these
helpers and their tests a file of their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The commit list only renders the visible rows for performance reasons
(see #3687), which means it also uses only the visible rows for
determining the widths of the list's columns. In an interactive rebase,
when you scroll down so that no rebase todos are visible any more, the
author and subject columns would snap to the left, which looks ugly and
distracting. A similar thing happened in half-screen mode when only
commits from today are visible: today's commits use a shorter date
format, so as you scroll down to make an older commit visible, the
columns to the right of the date would move to the right to make room
for the longer date.
Fix this by taking the width requirements of _all_ commits into account,
not just the visible ones, and pad the column texts to those widths. For
the date we take a shortcut: measuring the widths of all commits would
be too expensive, so we only format the oldest commit's date, on the
assumption that this one is the least likely to be from today. This only
works when the long time format is actually longer than the short one,
and when the long format always results in the same width for all dates;
both of these assumptions might be false when users reconfigure their
`gui.timeFormat` or `gui.shortTimeFormat` configs (e.g. to include a
week day), but for the default values of these it works.
Pad the date of every line to the width that the oldest entry in the
reflog asks for, so that the column keeps its width as the user scrolls.
This is the same treatment the commits panel's date column gets, with the
same limits; getReservedColumnWidths spells them out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reflog panel renders only the lines that are on screen too, so the
expanded panel's date column is only as wide as gui.shortTimeFormat while
nothing but entries from today is visible, and the message of every entry
jumps to the left. A reflog reaches back in time, so scrolling crosses
that boundary soon enough.
GetReflogCommitListDisplayStrings had no tests at all, so cover the two
shapes it can return along the way.
This file is deliberately left un-gofumpt'd: gofumpt indents the body of
the commented-out block by a tab, which would fill the diff of the commit
that swaps the two blocks with whitespace-only changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reflog commits context slices the visible lines out itself and passes
only those, so the function has no way to look at the rest of the list. A
following commit needs the oldest entry to work out how much width the
date column needs. Take the whole list along with the range to render, the
way GetCommitListDisplayStrings already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>