From 963db76ab62700ddd32ddfd14e0e16efe30601be Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Tue, 21 Jul 2026 12:54:24 +0200 Subject: [PATCH] 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 --- .../stage_hunks_with_rapid_keypresses.go | 69 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 2 files changed, 70 insertions(+) create mode 100644 pkg/integration/tests/staging/stage_hunks_with_rapid_keypresses.go diff --git a/pkg/integration/tests/staging/stage_hunks_with_rapid_keypresses.go b/pkg/integration/tests/staging/stage_hunks_with_rapid_keypresses.go new file mode 100644 index 000000000..74dbb4b6f --- /dev/null +++ b/pkg/integration/tests/staging/stage_hunks_with_rapid_keypresses.go @@ -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"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 8213d5159..2189d3506 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -403,6 +403,7 @@ var tests = []*components.IntegrationTest{ staging.SelectNextLineAfterStagingInTwoHunkDiff, staging.SelectNextLineAfterStagingIsolatedAddedLine, staging.StageHunks, + staging.StageHunksWithRapidKeypresses, staging.StageLines, staging.StagePartialBlockOfChangesFirstLines, staging.StagePartialBlockOfChangesLastLines,