Commit Graph
8643 Commits
Author SHA1 Message Date
Stefan HallerandClaude Opus 5.5 3ffcc0c7b0 Extract worktreeOfFile from markWorktreeFiles
The files panel is going to need to find the worktree of an entry too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 20:51:34 +02:00
Stefan HallerandClaude Opus 5.5 875641557b Move the menu for removing a worktree to BranchesHelper
The files panel is going to show it too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 20:51:34 +02:00
Stefan HallerandClaude Opus 5.5 0380dd4851 Label the directories that git status reports as a whole
The files panel shows such a directory like an untracked file. Nothing
tells you that it is a directory, or that a submodule left it behind
when a commit that you checked out removed the submodule. This is the
case for the directory of a removed submodule, a nested repository, a
linked worktree inside the repo, and any untracked directory if
status.showUntrackedFiles is "normal".

Tell these apart when loading the files, by looking at the directory's
.git. Label removed submodules, nested repositories and worktrees, as we
already do for submodules, and put a slash after the name of a plain
directory, as git status does. With file icons on, a removed submodule
gets the submodule icon, and a nested repository or a plain directory
gets the directory icon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 20:51:34 +02:00
Stefan HallerandClaude Opus 5.5 8a2d9758e0 Move gitDirOfRepo to git_commands
The file loader is going to need it too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 09:13:12 +02:00
Stefan HallerandClaude Opus 5.5 8b79e1dc1f Translate the "submodule" label in the files panel
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 09:13:12 +02:00
Stefan HallerandClaude Opus 5.5 2ddeebca72 Show a directory that git status reports as a whole as a single entry
git status doesn't descend into a nested repository, such as the
directory that a removed submodule leaves behind. It reports the
directory as a whole, with a trailing slash. The file tree splits paths
at slashes, so it showed a directory with a file without a name in it.
Discarding the directory removed it, but then the cleanup of emptied
directories took the parent of "modules/sub/" to be "modules/sub"
itself, and failed to read it.

Take the slash off when loading the files. This also covers any
untracked directory if status.showUntrackedFiles is "normal", and linked
worktrees inside the repo. Their slash was taken off when they were
marked as worktrees; this is no longer needed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 09:13:12 +02:00
Stefan HallerandClaude Opus 5.5 165b4bb499 Add a test for discarding the directory of a removed submodule
If a commit that you check out or pull removes a submodule, git leaves
the submodule's directory behind, and git status reports it as an
untracked directory. The files panel shows that directory with a file
below it that has no name. Discarding the directory shows an error,
even though the directory is gone afterwards.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 09:09:49 +02:00
Stefan Haller a0fa1becdb Exit gracefully when a repo or worktree gets deleted from under lazygit (#6129)
If the current worktree (or repo) that lazygit has open gets deleted
externally, lazygit used to crash with a panic. Exit gracefully in that
case, showing an error that the directory git deleted.

Also fix a crash for the more exotic case where a linked worktree's
directory is deleted and another worktree is created in the same
location. Users shouldn't do this, but it's still nicer to handle this
more gracefully.

Fixes #5266.
2026-10-15 09:06:13 +02:00
Stefan HallerandClaude Opus 5.5 4b61c4b650 Quit with an error message when the repo or worktree is deleted
If the repo or linked worktree that lazygit has open is deleted while
lazygit is running, every git command fails. lazygit no longer panics
since the previous commit, but it shows empty lists, and there's
nothing useful left to do in it.

Check at the start of every refresh whether the worktree and its git
dir still exist. If one of them is gone, quit and print its path in
the terminal. The background files refresh does this check too, so
lazygit quits within ten seconds even if nobody is using it. To quit
from a refresh, return an error that wraps gocui.ErrQuit; this keeps
it out of the error popup.

Switching to another worktree or to a recent repo would be an
alternative, but it would be more surprising than quitting. Also,
there isn't always a main worktree to switch to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan HallerandClaude Opus 5.5 787a309b3a Begin blocking input after capturing the refresh's baseline
The next commit makes performRefresh return early right after the
capture, and it must not leave the input blocked when it does.

This doesn't let any keypress slip through before the block begins.
Only refreshes on the UI thread block input, and there the capture
runs synchronously.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan HallerandClaude Opus 5.5 4d288579ee Don't panic when listing the branches fails
If the repo that lazygit has open is deleted while lazygit is running,
the next full refresh panics. The same happens for a linked worktree
that is deleted. In both cases `git for-each-ref` fails because its
working directory is gone, and BranchLoader panics when that happens.

Return the error instead. The branches refresh logs it, like it does
with the other errors it gets from loading the branches.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan HallerandClaude Opus 5.5 82207df464 Add a test for loading branches when listing them fails
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan HallerandClaude Opus 5.5 ebee95b847 Fix crash when two worktrees have the same path
If a linked worktree's directory is deleted and another worktree is
moved to the same path, `git worktree list` reports both worktrees with
that path. lazygit then crashes at startup with a stack overflow, and
the terminal is left in a broken state.

To tell the paths in a group apart, getUniqueNamesFromPathsAux keeps
looking at one more parent directory, but identical paths never come
apart, so it recurses forever. A stack overflow is a fatal error rather
than a panic. No deferred function runs, so the terminal is not
restored either.

Name identical paths like a single path instead of recursing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan HallerandClaude Opus 5.5 29e8c79ff5 Extract a helper for trimming the slashes around a worktree path
The next commit compares worktree paths with their slashes trimmed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan HallerandClaude Opus 5.5 1c79c830f8 Cleanup: make WorktreeLoader.pathExists return what its name says
It returns true if the path is missing. It started out as
IsWorktreePathMissing, and was renamed without inverting its result
when it moved into the loader in de57cfd6ff.

Invert the result instead of renaming it back, so that the call site
reads `IsPathMissing: !self.pathExists(path)`. If we can't check the
path, it still counts as existing, as it always did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 08:35:00 +02:00
Stefan Haller 88c339a907 Restore the terminal more reliably after a panic (#6124)
We already have a mechanism in place that takes care of restoring the
terminal if a panic occurs, but it was not consistently used. Use it for
all goroutines, and add a test that guards against forgetting it when we
add new ones.
2026-10-14 20:42:55 +02:00
Stefan HallerandClaude Opus 5.5 cb61dc2432 Add a test that checks that every goroutine is guarded against panics
When starting a new goroutine, it is easy to forget utils.Safe, and
nothing tells us until a panic in it leaves somebody's terminal
broken.

The test parses our source files and reports go statements that don't
run utils.Safe. It also reports functions given to time.AfterFunc or
to a Go method, such as errgroup.Group.Go, that aren't wrapped with
utils.SafeFunc or utils.SafeFuncWithError. It only looks at the
syntax; this is good enough because the names involved are
distinctive.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 20:28:53 +02:00
Stefan HallerandClaude Opus 5.5 7a9e4bee23 Restore the terminal if a timer or errgroup function panics
time.AfterFunc and errgroup.Group.Go run the function they are given
on a goroutine of their own, so a panic in it leaves the terminal in
raw mode, like a panic in any other goroutine that doesn't use
utils.Safe.

Add utils.SafeFunc and utils.SafeFuncWithError, which return a copy
of a function that runs through utils.Safe or utils.SafeWithError, and
use them for the functions given to time.AfterFunc and errgroup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 20:28:53 +02:00
Stefan HallerandClaude Opus 5.5 73fac4a554 Restore the terminal if a goroutine panics
Most goroutines run their function through utils.Safe, which closes
the screen if the function panics. Without it, the panic is printed to
a terminal that is still in raw mode and on the alternate screen, and
the terminal needs a reset afterwards. Go has no process-wide hook for
panics, so every goroutine has to do this itself.

Use utils.Safe for the goroutines that don't use it yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 20:28:53 +02:00
Stefan HallerandClaude Opus 5.5 6042e1c28b Restore the terminal if one of gocui's goroutines panics
A panic in the goroutine that polls the terminal for events leaves the
terminal in raw mode and on the alternate screen. The panic is then
hard to read, and the terminal needs a reset afterwards.

Guard this goroutine the same way as the worker goroutines. Guard the
goroutines that send a resize event too; they are unlikely to panic,
but this way all of gocui's goroutines are guarded, and a check for
unguarded goroutines doesn't need any exceptions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 20:28:53 +02:00
Stefan HallerandClaude Opus 5.5 d8972194d5 Extract gocui's guard against panics in workers into a helper
The next commit uses it for gocui's other goroutines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 20:28:53 +02:00
Stefan Haller b616791c70 Make double-click detection more precise (#6123)
Lazygit decided whether two clicks form a double click based on the
times it handled them. This can lead to false results: if the first
click takes the UI thread more then 500ms to handle, then the second
click is not detected as a double click even when it happened quickly
after the first. Conversely, if two clicks happen a second apart while
the main loop is busy, they are handled quickly once the main loop
unblocks, and got detected as a double-click.

I never actually ran into either of these problems in practice, and we
try hard to prevent lazygit from blocking the UI for longer than
necessary. I only noticed this because an integration test that performs
a double click occasionally fails when the CI runner is under heavy
load.

Fix this by recording the times at which the events were received, and
use those for double-click detection.
2026-10-14 16:49:22 +02:00
Stefan HallerandClaude Opus 5.5 94a3070f02 Time double clicks by when the clicks were read
gocui decides whether two clicks form a double click by the time it
handles them. If handling the first click keeps the main loop busy for
more than 500ms, a quick second click doesn't count as a double click.
The other way round, two separate clicks that wait in the event queue
while the main loop is busy count as a double click.

Compare the times at which tcell read the clicks instead. For events
replayed by integration tests, this is the time the test created them.
This way, a DoubleClick in a test no longer depends on how fast lazygit
handles the first click.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 14:17:47 +02:00
Stefan HallerandClaude Opus 5.5 5bc855415a Add a test for clicks that wait in the event queue
If the main loop is busy, clicks wait in the event queue and are then
handled right after each other. gocui decides whether two clicks form a
double click by the time it handles them, so two clicks read more than
500ms apart count as a double click.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 14:15:25 +02:00
Stefan HallerandClaude Opus 5.5 0855e981cf Remove the unused Timestamp of replayed input events
Every caller passes 0, and nothing reads it. The next commit makes
replayed mouse events carry the time they were created; a second, unused
timestamp next to it would only confuse.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 14:14:08 +02:00
Stefan HallerandClaude Opus 5.5 9e76ed7c03 Send the two clicks of a double click in tests without waiting between
The test enter_and_double_click_focus_file_diff fails occasionally on
CI. After it double-clicks a file, the files panel still has the focus
instead of the main view.

The test double-clicks by calling Click twice. Click waits for lazygit
to become idle after the press and again after the release, and the
first wait includes the git diff that the click starts for the main
view. gocui only counts the second click as a double click if it handles
it within 500ms of the first one. On a busy CI runner, where many
integration tests run in parallel, the waits can take longer than that,
and the second click counts as a single click.

Add DoubleClick to the test driver. It replays both clicks without
waiting in between, as a terminal reports a double click, and waits for
lazygit to become idle only after the second one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 14:12:19 +02:00
Stefan Haller 883c893911 Improve performance with very long diffs (#6121)
Parsing long diffs was very slow, especially for commits that add or
remove files with hundreds of thousands of lines. This caused very
noticeable lags for certain operations such as scrolling down in such a
diff using `>`, or bringing up the `<ctrl-g>` files menu.
2026-10-14 09:48:15 +02:00
Stefan HallerandClaude Opus 5.5 dc4fee296c Leave out the text of the rows of a rendering that states its lines
Under a diff renderer that states which diff line each row shows, such
as delta, the queries about the lines of the main view read the rows by
those records alone. Still, they took the text of every row as well,
and for a diff of 300,000 lines this was most of the time each of them
took. Moving the selection by change block runs two of them for every
key press.

Take the records of the rows first, and their text only if no row
states a diff line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:41:50 +02:00
Stefan HallerandClaude Opus 5.5 3f64902a11 Collect the records of a line when the line has been written
Under a diff renderer that states which diff line each row shows, such
as delta, moving the selection by change block in a diff of 300,000
lines lags noticeably. One reason is that the queries about the lines
of the main view collect the records of every row from the row's
cells, going through every cell of every row. Under delta, a row has
about 130 cells.

Collect the records of a line once, when the write cursor moves on to
the next line, and take them from there. A line that is written to
again is read from its cells until it has been finished again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:41:50 +02:00
Stefan HallerandClaude Opus 5.5 cbfa22cfbe Make the path of a diff's lines absolute once per file
Every query about the lines of the main view, such as moving the
selection to the next change block, finds out which line of which file
each row shows. For every row, this joined the path that the diff
states with the path of the worktree. In a diff of 300,000 lines, this
took more than half of the time of the query.

The lines of a diff come file by file, so remember the last path that
was made absolute and reuse it while the path stays the same. Do the
same in the other places that go through every line of a diff. A query
over that diff now takes about 27 milliseconds instead of 63.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:41:50 +02:00
Stefan HallerandClaude Opus 5.5 185f147db2 Number the lines of a patch in one pass when indexing its change lines
Several actions on the selected lines of a diff look up those lines in
the diff of their file by their line numbers: staging, unstaging and
discarding them, editing the hunk they are in, and adding them to a
custom patch. Showing which lines of a diff are in the custom patch
does the same lookup the other way round. For this, the change lines of
the file's diff are indexed by their line numbers.

The index asked the patch for the number of each change line separately,
and each of these calls counts the lines of the hunk before the line. So
for a new file of 100,000 lines, building the index took 2 seconds.
Number all lines of the patch in one pass instead. Building the index
for that file now takes 4 milliseconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:41:50 +02:00
Stefan HallerandClaude Opus 5.5 80dff3c2b9 Number the lines of a diff in one pass when parsing it
With no diff renderer configured, opening the jump-to-file menu for the
diff of a commit that adds or deletes a file of 100,000 lines takes
more than 20 seconds, and so does picking a file from it. In such a
diff, moving the selection by change block takes as long for each key
press. All this time the UI is frozen.

To find out which line of which file each row of the main view shows,
lazygit parses the view's content as a diff. For every line of a file's
diff it asks the parsed patch for the line's number in the new and in
the old file, and each of these calls counts the lines of the hunk
before it. So parsing takes time quadratic in the length of a hunk, and
the diff of a new or deleted file is a single hunk. For a diff of
200,000 lines, one parse took about 10 seconds, and opening the menu
parses it twice. A diff renderer that states which line each row shows,
such as delta, doesn't go through this parse.

Number all lines of the patch in one pass instead. Parsing that diff
now takes about 40 milliseconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:41:50 +02:00
Stefan Haller ae6639a003 Fix diff rendering after scrolling far down (#6120)
Fix a regression introduced in v0.65.0: if the main view is scrolled
more than about 5000 lines down a diff and another commit with a diff of
more than 5000 lines is selected, the main view kept showing the
previous diff. Switching between the two long commits then didn't update
the main view at all. Only selecting a shorter commit would get this
unstuck.

Along the way, fix another smaller bug and a performance problem; see
commit messages for details.
2026-10-14 08:38:40 +02:00
Stefan HallerandClaude Opus 5.5 f6892ffc94 Read the lines above the view only for a render that stays where it is
A render first reads enough lines to fill the view, plus the lines
above the view, so that a re-render of the diff the user is scrolled
into fills the view where it is. But a render of another diff, such as
the diff of another commit, is shown from the top. So if the view is
scrolled far down a long diff, the next diff only shows up once the
lines above the view have been read as well. For a diff of a few
hundred thousand lines this takes a noticeable while, especially with a
diff renderer such as delta.

ViewBufferManager decides whether a render keeps the view where it is
(see newContentPending). So let linesToReadFromCmdTask count the lines
from the top of the view and pass on how many lines are above it, and
let the task add those only if it keeps the view where it is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 16:31:22 +02:00
Stefan HallerandClaude Opus 5.5 be300454b6 Name the fields of LinesToRead in the task tests
The request for the first lines of a render is going to say how many
lines there are above the view, and the unnamed fields of these
literals would all need another value for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 16:28:17 +02:00
Stefan HallerandClaude Opus 5.5 f91d20f67d Don't let a render stop reading before it has filled the view
If the main view is scrolled more than about 5000 lines down a diff and
another commit with a diff of more than 5000 lines is selected, the
main view keeps showing the previous diff.

A render reads its command's output into an off-screen buffer, and
shows it only once it has read enough lines to fill the view or has
reached the end of the output. It then reads on as far as an accurate
scrollbar needs, and waits for the user to scroll further. Both counts
include the lines above the view, so that a re-render of the diff the
user is scrolled into fills the view where it is. But the 5000-line cap
that is meant for the scrollbar applies to the lines above the view as
well. So if the view is scrolled further down than that, the render
stops reading before it has filled the view, and never shows anything.
The view also counts as loading until the next render starts, so the
layout doesn't keep it from scrolling past the end of the diff in the
meantime.

This has been a problem since 9e23111172 made renders read into an
off-screen buffer. Before that, the lines of a render that stopped early
showed up anyway.

Apply the cap to the lines for the scrollbar only. Also never ask for
fewer lines than fill the view. A view no more than four lines tall
needs fewer lines than that for its scrollbar, even at the top of a
diff.

A render of another diff shows it from the top, so it doesn't need the
lines above the view at all. A later commit stops it from reading them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 16:28:02 +02:00
Stefan HallerandClaude Opus 5.5 56b6c5af31 Stop at the end of the diff when jumping to a file from a panel
With gui.scrollPastBottom set to false, picking the last file of a long
diff from the jump-to-file menu of the commits panel scrolls that file
to the top of the view. This leaves blank space below the end of the
diff.

A jump in a pane that isn't focused puts the file at the top of the view
and leaves it to the layout to stop at the end of the diff. But the
layout doesn't do this while the view is loading. Picking an item from
the menu closes the menu first, and the commits panel starts a re-render
of the diff as it gets the focus back. So the view is loading when the
jump happens, and the layout corrects the position only in a later pass
after the re-render has finished. For a long diff the re-render never
finishes; a later commit fixes this. Clicking a file in the diffstat
doesn't close a menu, so it doesn't have the problem.

Stop the jump at the last screenful of the diff, as a jump in the
focused pane does. Like that one, it stops there whatever
gui.scrollPastBottom is set to.

The diff in click_a_file_in_a_diff_stat_that_comes_first fits on
screen, so the jump doesn't scroll it any more. Make it longer than the
view.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 16:26:38 +02:00
Stefan HallerandClaude Opus 5.5 d198ce56f1 Extract how far a jump can scroll its target to the top of the view
A jump in a pane that isn't focused is going to stop at the last
screenful of the diff as well.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 16:23:36 +02:00
Stefan HallerandClaude Opus 5.5 29942337f5 Add tests for renders that don't show after scrolling far down a diff
If the main view is scrolled more than about 5000 lines down a diff and
another commit is selected, the main view keeps showing the previous
diff, as long as the new one is longer than 5000 lines as well.

Similarly, if the jump-to-file menu of the commits panel is used to go
to the last file of such a diff, the file's header ends up at the top
of the view with blank space below the end of the diff. This happens
even with gui.scrollPastBottom set to false.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 16:18:52 +02:00
Stefan Haller b741725a24 Update docs and schema for release (#6114) v0.66.1 2026-10-11 11:21:51 +02:00
Stefan Haller 72cf6049f1 Update docs and schema for release 2026-10-11 11:18:45 +02:00
Stefan Haller d58920bd57 Edit random tips (#6113) 2026-10-11 11:17:11 +02:00
Stefan Haller 9b353e9dcb Add tip about alt-clicking, which is not very discoverable 2026-10-11 11:12:03 +02:00
Stefan Haller aab6b94a15 Remove obsolete tip 2026-10-11 11:11:45 +02:00
Stefan Haller d6c5e513aa Don't log the diff renderer probe in the command log (#6112)
If a diff renderer is configured, its command shows up in the command
log once after startup, although the user didn't do anything. This is
the probe that asks the renderer whether it emits OSC 1717 metadata. It
changes nothing, so like other read-only commands it shouldn't be
logged.
2026-10-11 11:05:16 +02:00
Stefan Haller aa3da6a20f Keep the diff renderer probe out of the command log
If a diff renderer is configured, its command shows up in the command
log once after startup, although the user didn't do anything. This is
the probe that asks the renderer whether it emits OSC 1717 metadata. It
changes nothing, so like other read-only commands it shouldn't be
logged.
2026-10-11 10:10:41 +02:00
Stefan Haller fc570a6a31 Show the diff view's narrow selection bar on the right edge too (#6105)
This is an improvement for users who use a side-by-side diff renderer on
a very large screen; in this scenario it was hard to see which hunk on
the right side was selected.

See
https://github.com/jesseduffield/lazygit/issues/6098#issuecomment-6064528142.
2026-10-11 09:52:50 +02:00
Stefan Haller 96f93ec79d Show the diff view's narrow selection bar on the right edge too
This is an improvement for users who use a side-by-side diff renderer on
a very large screen; in this scenario it was hard to see which hunk on
the right side was selected.
2026-10-10 14:20:04 +02:00
Stefan Haller a5bf29e092 Open files of a branch's diff from the worktree that has it checked out (#6104)
If you enter a branch in the branches panel and press `e` on a line of a
commit's diff, or on a file in the commit files panel entered from
there, lazygit opens the file in the current worktree. If another
worktree has the branch checked out, this is the wrong copy of the file.
The current worktree may have it in a different state, or not have it at
all. Clicking a lazygit-edit:// hyperlink of a diff renderer in such a
diff has the same problem.

Fix this by opening the file from the worktree that has the branch
checked out instead, and carry the line number forward to the file as
that worktree has it. If no other worktree has the branch checked out,
the file is still opened from the current worktree.

Also, fix the same problem for the `o` command, and for the "Absolute
path" entry in the "Copy to clipboard" menu.

Finally, fix a long-standing annoyance when trying to open a file that
has since been deleted or renamed; in that case, most editors would
create it as a new, empty file, which is not useful. Show an error
message in that case instead.

Fixes #6102.
2026-10-10 14:09:07 +02:00
Stefan HallerandClaude Opus 5.5 3742ac6066 Show an error when editing a file of a diff that doesn't exist
If you press `e` on a file of a commit, and a later commit deleted that
file, the editor is opened on a path that doesn't exist. Most editors
then create it as a new, empty file. This is confusing. The same
happens with `o`, and with any other file that the worktree it is
opened from doesn't have. An example is a file that is added in a branch
that isn't checked out anywhere, since it is opened from the current
worktree.

Show an error instead of opening the editor. For a range selection in
the commit files panel, show it only if none of the selected files
exist. Otherwise open the existing ones and leave out the others
silently; an error shown only after returning from the editor would
come too late to be useful.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:58:05 +02:00