Files
Stefan HallerandClaude Fable 5 0a92529b14 Prevent stale index.lock files from diffs rendered through a pty on Windows
At the end of a diff against the worktree, git re-reads and refreshes
the index and writes it back if it found stale stat information
(diff.autoRefreshIndex, on by default). It holds index.lock for the
whole refresh; GIT_OPTIONAL_LOCKS does not cover this lock, and the
window scales with the size of the repository (~150ms for a 6k-file
repository with a warm stat cache).

On Windows, a pty task that is stopped because the user moved on
terminates its git process at an arbitrary point: tearing down the
pseudoconsole delivers CTRL_CLOSE_EVENT, which git leaves to the
default handler, which simply calls ExitProcess. If that lands inside
the refresh, a stale index.lock is left behind and the next git
command chokes on it. This is the same problem that 98801da106 fixed
by no longer killing git processes; the ConPTY support added in 0.63
reintroduced it through the close event.

Disable the automatic refresh for pty-rendered commands. They can
afford it: the refresh only persists refreshed stat information, and
lazygit's foreground git status refreshes -- which never run in a pty
and are never killed -- already write that back on every user action
and on terminal focus-in. The cost is that while the on-disk stat
cache is stale, an external differ is invoked even for files whose
stat information changed but whose content didn't, showing them as
empty diffs; this heals with the next foreground refresh, which also
re-renders the view.

Unix keeps the refresh: a stopped pty child gets SIGTERM there, and
git's signal handlers remove its lock files, so the lock window is
harmless. The rawGit renderer keeps it too: its tasks don't run in a
pty and are never killed on Windows -- they either run to completion
or die on a broken pipe mid-output, before the refresh begins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 06:53:53 +02:00

30 lines
939 B
Go

package gui
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithPtyGitConfig(t *testing.T) {
args := []string{"git", "-C", "/repo", "diff", "--color=always"}
assert.Equal(t,
[]string{"git", "-c", "diff.autoRefreshIndex=false", "-C", "/repo", "diff", "--color=always"},
withPtyGitConfig(args, "windows"))
assert.Equal(t, args, withPtyGitConfig(args, "linux"))
assert.Equal(t, args, withPtyGitConfig(args, "darwin"))
// A user-configured command that wraps git in a shell must not have git
// flags injected into it.
shellArgs := []string{"sh", "-c", "git log --graph {{branchName}} -- | sed -e s/x/y/"}
assert.Equal(t, shellArgs, withPtyGitConfig(shellArgs, "windows"))
// The guard recognizes git regardless of case and extension.
exeArgs := []string{"GIT.EXE", "diff"}
assert.Equal(t,
[]string{"GIT.EXE", "-c", "diff.autoRefreshIndex=false", "diff"},
withPtyGitConfig(exeArgs, "windows"))
}