When the user marks the last commit to test, the commits view keeps it
selected, and the user has to look for the result. The same happens
when lazygit starts after a bisect has finished.
After a mark, git checks out the next commit to test, unless there's
nothing left to test. In that case, the commit that the user marked
last is still the current commit, and the commit marked bad is the
first bad commit. So once the current commit is marked, select the
commit marked bad instead. Do the same if git never checked out a
commit to test, because the good commit is a parent of the bad one.
If only skipped commits are left to test, the first bad commit is
either one of them or the commit marked bad. Select the commit marked
bad then too; it's the newest of the candidates.
The commits view loads the commits down to the commit that it selects,
so this one is always loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the user marks the last commit to test as good, git reports the
commit that is marked bad as the first bad commit. The commits view
keeps the commit that the user just marked selected, though, so the
user has to look for the result. The same happens when lazygit starts
after a bisect has finished.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lazygit starts in the files panel. During a bisect, this is rarely what
the user needs. The commits panel shows how far the bisect has got, and
the user marks the commits to test there. So start in the commits panel
if a bisect is in progress, even if git doesn't know both a good and a
bad commit yet. The same applies the first time the user switches to
such a repo during a session.
A filter path or a "status", "branch", "log" or "stash" argument still
decides which panel to focus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
To start a bisect, users often know that one branch or tag is good and
another one is bad. The bisect menu was available in the commits panel
only, though. To mark the head of another branch, the user had to check
it out first, or find its head commit in the commits panel. The commits
panel shows only the checked-out branch.
Offer the menu in the local branches, remote branches, tags, sub-commits
and reflog views too. There it marks the selected item as good or bad,
and its entries name the item ("branch 'dev'", "tag 'v1.0'"), so that
it's clear what they act on. Once git knows both a good and a bad
commit, the bisect continues in the commits panel. It shows the good and
bad commits and the one to test next, and its menu acts on that one. So
focus the commits panel when a mark in another view gives git the second
of the two, and disable the key in the other views from then on. They
don't offer skipping either.
Name the selected commit "commit <hash>" in the commits panel's menu
too, so that it reads the same in all views.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It worked with the commits of the local commits context only. The next
commit offers the bisect menu in other views too, such as branches and
tags, so let it take any context and act on its selected ref.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After marking the first commit as good or bad, the bisect menu's
entries act on the selected commit until git knows both a good and a
bad commit. They called it the "current commit" though, as if it was
the commit that git wants the user to test.
Call it "commit <hash>" instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One function built the menu both for when git still waits for a good or
a bad commit, and for while bisecting. Its entries act on the selected
commit in the first case, and on the commit that git wants the user to
test in the second case. Several conditions in the function picked
between the two.
The next commits change the first menu only, so make it a function of
its own. Extract the menu entries that both menus have, so that they
don't get duplicated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After marking the first commit as good or bad, the bisect menu acts on
the selected commit until git knows both a good and a bad commit. Its
entries call that commit the "current commit" though, as if it was the
commit that git wants the user to test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commits view loads the first 300 commits only. If the range to
bisect is longer than about 600 commits, the commit that git wants the
user to test is further down than that, so it isn't shown, and we can't
select it.
While bisecting, count the commits in the log above the current commit,
and load that many more than usual.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commits view loads the first 300 commits only. If the range to
bisect is longer than about 600 commits, the commit that git wants the
user to test is further down than that. It isn't loaded, so we can't
select it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commits and sub-commits views load the first 300 commits, and load
all of them once the user selects a commit beyond the 200th. A later
commit loads more than 300 commits while bisecting, so that the commit
to test is among them. If it is beyond the 200th, it would cause all
commits to be loaded as soon as it gets selected.
Remember how many commits the views were loaded with, and load all of
them only when the selection gets within 100 commits of that number.
For the views loaded with 300 commits, nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commit loader took a flag for whether to limit the number of
commits it loads, and the limit of 300 was hard-coded in it. A later
commit needs to load more than that while bisecting, so let the caller
pass the number.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commits and the sub-commits views had identical copies of it. A
later commit changes when it loads all commits, so make it one function
first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once a bisect had started, the commits view showed the log of the
branch that was checked out when it started, even while git was still
waiting for a good or a bad commit. If the user checked out a different
branch to mark its head as bad, its commits weren't shown.
Keep showing the log of HEAD until git starts checking out commits to
test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once a bisect has started, the commits view shows the log of the branch
that was checked out when it started. This happens even while we don't
know both a good and a bad commit yet, and git hasn't checked out
anything for the user to test. If the user checks out a different
branch to mark its head as bad, the commits view keeps showing the old
branch, so the new branch's head isn't there to be marked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If lazygit starts while a bisect is in progress, the commits view
selects the top commit, and the user has to look for the commit that
git wants them to test. The same happens when a git bisect command run
outside of lazygit moves the bisect on.
The bisect menu selected the new current commit after a good, bad or
skip, but only if the selected commit was the current one. It also
needed extra logic to wait for the refresh in case the log was going to
start from a different ref.
Remove that, and let the commits refresh select the current commit
whenever it differs from the one at the previous refresh, like it does
for a newly conflicted commit. A refresh that doesn't change it leaves
the selection alone. This way a background fetch doesn't move the
selection back to the current commit after the user navigated away from
it.
Marking the current commit as good or bad while a different commit is
selected now selects the new current commit too.
Skipping the selected commit when it isn't the current one is an
exception. Git often picks a different commit to test after that, but
we keep the skipped commit selected, so that the user can go on
skipping commits near it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a refresh finds a new conflicted commit, it selects it so that the
user sees where the rebase stopped. A background refresh keeps the
scroll position of the views it updates though. If the conflicted
commit is outside the visible part of the commits view, the selection
moves to a commit that the user can't see.
Scroll the selection into view whenever the refresh moves it to a newly
conflicted commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If a rebase started outside of lazygit stops with a conflict, the next
background refresh selects the conflicted commit. It keeps the scroll
position though, so if there are enough pending todos above the
conflicted commit, the selection is off-screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commits refresh updated the model and the selection in one hop to
the UI thread, and re-rendered the view in a second one. The scroll
behavior of the re-render was fixed when the refresh started.
The next commit makes the scroll behavior depend on how the refresh
changed the selection. Do both in one hop, so that the re-render can
see that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`git bisect start` takes the bad commit first and the good one second.
The parameters were named the other way round. The only caller already
passes the bad commit first, so only the names were wrong.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codacy hasn't analysed this repository since April 2026, because status
checks are disabled for it in Codacy's settings. The uploads from CI
still succeed, but the commits never show up in Codacy, and the coverage
badge in the README shows 0%.
We decided not to revive the integration. Remove the upload job and the
two Codacy badges.
If LAZYGIT_GOCOVERDIR is set, run_integration_tests.sh collects the
coverage of the integration tests. Only the lazygit binary writes its
data to that directory, though. The test runner always writes to
/tmp/code_coverage, so the data ends up in two places unless the
variable points there. On CI it always did.
Also, the run fails if the directory doesn't exist yet. With a relative
path, the binary's data goes missing, because the binary runs in the
directory of the test's repo.
Pass the directory to the test runner too, create it if needed, and turn
it into an absolute path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Go Report Card has shut down. The badge now reads "go report: retired",
and the report page only shows a farewell note. That note recommends
golangci-lint as the replacement, and we already show a badge for it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CI collected coverage data from the unit and integration tests only for
the Codacy upload. Nothing else reads it.
Remove the coverage flags, the LAZYGIT_GOCOVERDIR env var and the
artifact uploads from the CI workflow. Keep the support for
LAZYGIT_GOCOVERDIR in run_integration_tests.sh and in the integration
test runner, so that we can still measure the coverage of the
integration tests locally.
Remove the step in run_integration_tests.sh that merges the coverage
files, though. It only existed to reduce the number of artifacts to
upload.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codacy hasn't analysed this repository since April 2026, because status
checks are disabled for it in Codacy's settings. The uploads from CI
still succeed, but the commits never show up in Codacy, and the coverage
badge in the README shows 0%. On Dependabot PRs the upload fails
outright. These runs don't get the CODACY_PROJECT_TOKEN secret.
We decided not to revive the integration. Remove the upload job and the
two Codacy badges.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With Go 1.27 installed, `just lint` fails with typecheck errors like
"export data version 5 is greater than maximum supported version 4".
The shim builds golangci-lint with the local Go, but v2.12.2 can't read
the export data of Go 1.27's standard library. Support for Go 1.27 came
in v2.13.0.
v2.15.0 still builds with Go 1.26, so linting keeps working there too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TestClosingAPipelinesOutputBringsItDown receives the error of
Pipeline.Wait from a channel and ignores it. errcheck v1.30.0 reports
such a receive as an unchecked error. golangci-lint 2.15.0 comes with
this errcheck version, and the next commit bumps to it.
The test ignores the error on purpose. It only checks that Wait returns,
and the error depends on the platform. Make that explicit by discarding
the error in the goroutine, and use the channel only to signal that
Wait has returned.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Releases are run from stefanhaller's fork, but if that fork is not up to
date with jesseduffield's, bad things can happen. Guard against this by
checking that the release.yml file in both is identical.
Also take the go version to build with from the go.mod file in the
branch that is being released from, rather than hard-code it in
release.yml; this could be relevant when making a release from a 0.66.3
branch when the go version has already been bumped on master.
release.yml installs a fixed Go version for building a release, and it
must match the release.yml on master. A patch release from a branch off
an older tag is therefore built with master's Go version, not with the
one that branch uses. For example, once master moves to Go 1.27, a
v0.66.3 built from a branch off v0.66.2 would no longer run on macOS
12, although v0.66.2 does.
Read the version from the go line of the checked-out go.mod instead,
and add ".x" so that setup-go installs the latest patch release.
setup-go's go-version-file input can't do this; for "go 1.26.0" it
installs exactly 1.26.0. Read the version before pushing the tag, so
that a go.mod without a go line stops the release early.
This also removes release.yml from the files to edit when updating Go.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Releases run the release workflow of the stefanhaller/lazygit fork, but
build the code of jesseduffield/lazygit. If the fork's master is behind,
its copy of release.yml can differ from upstream's, and the release
runs with outdated steps. This may only show up after the new tag has
already been pushed.
Stop the release right after checking out the code if the running
release.yml differs from the one on jesseduffield/lazygit's master.
Compare with master rather than with the branch being released. A
patch release from a branch off an older tag carries an older
release.yml, and it should still be released with the current workflow.
This means that changes to release.yml can no longer be tried out in
the fork before they land upstream.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dependabot PRs that update golang.org/x modules fail on CI. These
modules now require go 1.26, but CI runs 1.25. The golang.org/x modules
require the oldest Go release that is still supported, and since Go
1.27 came out, this is 1.26.
Stop at 1.26 rather than going to 1.27. The go version in go.mod is the
minimum for everyone who builds lazygit from source, and Go 1.27 is
only two months old. Also, Go 1.27 requires macOS 13, so release
binaries built with it would no longer run on macOS 12.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Updating Go means editing the go-version of six setup-go steps in
ci.yml. The workflow defines a GO_VERSION variable for this, but no
step has used it since it was added in a34bdf1a04.
Use it in all six steps. Give its value the ".x" suffix so that
setup-go keeps picking the latest patch release. This also keeps YAML
from reading a version like 1.30 as the number 1.3.
release.yml still needs its own update, because env variables don't
carry over to other workflows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two places pass plain text as the format string of TextStyle.Sprintf
without further arguments: the prefix of a disabled menu item's
tooltip, and the key binding entries of the options bar. If the text
contains a "%", for example in a translation or in a key label, it is
mangled into something like "%!(NOVERB)".
go vet doesn't report these calls yet. It will once go.mod says go
1.26, because vet then also finds printf wrappers through interface
methods. TextStyle.Sprintf forwards to the Sprintf method of the
Sprinter interface.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When opening the `<ctrl+g>` files menu exactly at the moment where a
background fetch finishes, it could sometimes only show some of its
entries. The same could happen when pressing `>` to jump to the end of a
long diff at that moment.
Some actions read the content of the main view to the end before they
act on it: the jump-to-file menu, going to the bottom, searching, and
jumping to a file from the diffstat. If a re-render replaces the task
while it reads, these actions are told that the content has been read
although only a part of it has, so the menu lists only the files in
that part. A background fetch re-renders the main view once a minute,
so now and then the menu is incomplete. The same happens if a read is
asked for after a render has been asked for but before its task has
started.
These actions act on the content of the task asked for last, so leave
the reads waiting for content to that task. Hold them from the moment
the task is asked for, hand over the ones a replaced task hasn't
answered, including the one it was reading for, and answer them once
the task asked for last has given the view all of its content. The
replacing task still reads its own first lines first, so that the reads
it takes over don't delay its first paint.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>