From 21bbcc957a94f84c77a73025540db6ad9c855556 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 7 Nov 2026 09:35:32 +0100 Subject: [PATCH] Add a test for a lock error partway through a rebase If another git process takes index.lock while a rebase is replaying commits (for example an IDE running "git status" in the background), git stops the rebase and reschedules the command that failed. Lazygit then runs the whole "git rebase --interactive" command again. This fails with "It seems that there is already a rebase-merge directory", and the user is left with a rebase that stopped halfway. Co-Authored-By: Claude Opus 5.5 --- .../lock_error_partway_through_rebase.go | 59 +++++++++++++++++++ pkg/integration/tests/shared/index_lock.go | 32 ++++++++++ pkg/integration/tests/test_list.go | 1 + 3 files changed, 92 insertions(+) create mode 100644 pkg/integration/tests/interactive_rebase/lock_error_partway_through_rebase.go create mode 100644 pkg/integration/tests/shared/index_lock.go diff --git a/pkg/integration/tests/interactive_rebase/lock_error_partway_through_rebase.go b/pkg/integration/tests/interactive_rebase/lock_error_partway_through_rebase.go new file mode 100644 index 000000000..0d0b9cf92 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/lock_error_partway_through_rebase.go @@ -0,0 +1,59 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" +) + +var LockErrorPartwayThroughRebase = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Drops a commit while another git process takes index.lock partway through the rebase", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateNCommits(4) + // The rebase makes a commit when it picks commit-03; the lock then + // stops it when it tries to pick commit-04. + shared.HoldIndexLockAfterNextCommit(shell) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("commit-04").IsSelected(), + Contains("commit-03"), + Contains("commit-02"), + Contains("commit-01"), + ). + NavigateToLine(Contains("commit-02")). + Press(keys.Universal.Remove). + Tap(func() { + t.ExpectPopup().Confirmation(). + Title(Equals("Drop commit")). + Content(Contains("Are you sure you want to drop the selected commit(s)?")). + Confirm() + }). + /* EXPECTED: + IsFocused(). + Lines( + Contains("commit-04"), + Contains("commit-03"), + Contains("commit-01"), + ) + ACTUAL: */ + Tap(func() { + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Contains("It seems that there is already a rebase-merge directory")). + Confirm() + }). + Lines( + Contains("─── Pending rebase todos"), + Contains("pick").Contains("commit-04"), + Contains("─── Commits"), + Contains("commit-03"), + Contains("commit-01"), + ) + }, +}) diff --git a/pkg/integration/tests/shared/index_lock.go b/pkg/integration/tests/shared/index_lock.go new file mode 100644 index 000000000..29e513f6f --- /dev/null +++ b/pkg/integration/tests/shared/index_lock.go @@ -0,0 +1,32 @@ +package shared + +import ( + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +// holdIndexLockScript is the part of a git hook that takes index.lock the +// first time the hook runs, and holds it until the git command that ran the +// hook has exited. This simulates another git process (for example an IDE +// running "git status" in the background) that takes the lock while a command +// of several steps, such as a rebase, is partway through. +const holdIndexLockScript = `if [ ! -e .git/index-lock-taken ]; then + touch .git/index-lock-taken .git/index.lock + ( + # If lazygit runs the command in a pty (as it does for a pull), we + # get SIGHUP when the command exits + trap '' HUP + while kill -0 $PPID 2>/dev/null; do sleep 0.01; done + rm -f .git/index.lock + ) >/dev/null 2>&1