Add test showing that a rapid second keypress acts on a stale staging panel

Pressing space twice in quick succession in the staging panel is supposed
to stage two hunks: the refresh triggered by the first press rebuilds the
panel's diff and moves the selection to the next stageable hunk, and the
second press stages that.

Since we made UI-thread refreshes non-blocking, the second press is
handled as soon as it arrives, while that refresh is still in flight. It
then reads the stale pre-refresh diff, builds the first hunk's patch
again, and git apply fails with 'patch does not apply' because those
lines are already in the index.

The test documents this currently broken behavior; the fix comes next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-07-21 22:23:58 +02:00
co-authored by Claude Fable 5
parent 7a902b56cc
commit 963db76ab6
2 changed files with 70 additions and 0 deletions
@@ -0,0 +1,69 @@
package staging
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
// The second space is pressed before the refresh triggered by the first one
// has updated the staging panel. That refresh is what moves the selection to
// the next hunk, so the second press must not be handled until it has landed;
// handling it earlier would try to stage the first hunk a second time.
var StageHunksWithRapidKeypresses = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Stage two hunks with two space presses in rapid succession",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = true
},
SetupRepo: func(shell *Shell) {
// Use 7 context lines between the two change blocks so that git creates
// two separate hunks.
shell.CreateFileAndAdd("file1", "1\n2\na\nb\nc\nd\ne\nf\ng\n3\n4\n")
shell.Commit("one")
shell.UpdateFile("file1", "1b\n2b\na\nb\nc\nd\ne\nf\ng\n3b\n4b\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Contains("file1").IsSelected(),
).
PressEnter()
t.Views().Staging().
IsFocused().
PressRapidly(keys.Universal.Select, keys.Universal.Select)
/* EXPECTED:
t.Views().StagingSecondary().
IsFocused().
ContainsLines(
Contains("+1b"),
Contains("+2b"),
).
ContainsLines(
Contains("+3b"),
Contains("+4b"),
)
ACTUAL: */
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Contains("patch does not apply")).
Confirm()
t.Views().Staging().
IsFocused().
ContainsLines(
Contains("+3b"),
Contains("+4b"),
)
t.Views().StagingSecondary().
ContainsLines(
Contains("+1b"),
Contains("+2b"),
)
},
})
+1
View File
@@ -403,6 +403,7 @@ var tests = []*components.IntegrationTest{
staging.SelectNextLineAfterStagingInTwoHunkDiff,
staging.SelectNextLineAfterStagingIsolatedAddedLine,
staging.StageHunks,
staging.StageHunksWithRapidKeypresses,
staging.StageLines,
staging.StagePartialBlockOfChangesFirstLines,
staging.StagePartialBlockOfChangesLastLines,