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 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-11-08 14:54:41 +01:00
co-authored by Claude Opus 5.5
parent 3dcad25058
commit 21bbcc957a
3 changed files with 92 additions and 0 deletions
@@ -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"),
)
},
})
@@ -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 </dev/null &
fi
`
// HoldIndexLockAfterNextCommit installs a post-commit hook that takes
// index.lock after the next commit (see holdIndexLockScript).
//
// Install it after creating the commits of the test setup, since it acts on
// the first commit that is made after it.
func HoldIndexLockAfterNextCommit(shell *Shell) {
shell.CreateFile(".git/hooks/post-commit", "#!/bin/sh\n"+holdIndexLockScript)
shell.MakeExecutable(".git/hooks/post-commit")
}
+1
View File
@@ -358,6 +358,7 @@ var tests = []*components.IntegrationTest{
interactive_rebase.FixupSecondCommit,
interactive_rebase.InteractiveRebaseOfCopiedBranch,
interactive_rebase.InteractiveRebaseWithConflictForEditCommand,
interactive_rebase.LockErrorPartwayThroughRebase,
interactive_rebase.MidRebaseRangeSelect,
interactive_rebase.Move,
interactive_rebase.MoveAcrossBranchBoundaryOutsideRebase,