Add a test for a lock error partway through skipping in a rebase

If a lock stops "git rebase --skip" after git has applied the changes
of the next commit but before it has committed them, git doesn't
reschedule that commit. The changes stay staged. The git command runner
then runs "git rebase --skip" again, and this discards the staged
changes, so the commit is silently lost.

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 d4b11b8cfd
commit 4c18daf933
3 changed files with 89 additions and 0 deletions
@@ -0,0 +1,76 @@
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 LockErrorPartwayThroughSkip = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Skip in a rebase while another git process takes index.lock before the next commit is committed",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
CreateFileAndAdd("file", "original\n").
Commit("base").
UpdateFileAndAdd("file", "changed\n").
Commit("commit to edit").
CreateFileAndAdd("file-y", "y\n").
Commit("add file-y").
CreateFileAndAdd("file-z", "z\n").
Commit("add file-z")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("add file-z").IsSelected(),
Contains("add file-y"),
Contains("commit to edit"),
Contains("base"),
).
NavigateToLine(Contains("commit to edit")).
Press(keys.Universal.Edit).
Lines(
Contains("─── Pending rebase todos"),
Contains("pick").Contains("add file-z"),
Contains("pick").Contains("add file-y"),
Contains("─── Commits"),
Contains("commit to edit").IsSelected(),
Contains("base"),
)
// After amending the commit we stopped at, git has to make a new commit
// when it picks "add file-y", rather than fast-forwarding to it. The
// lock then stops it after it has applied the changes, but before it
// has committed them.
t.Shell().RunCommand([]string{"git", "commit", "--amend", "-m", "edited commit"})
shared.HoldIndexLockBeforeNextPickIsCommitted(t.Shell())
t.GlobalPress(keys.Universal.CreateRebaseOptionsMenu)
t.ExpectPopup().Menu().
Title(Equals("Rebase options")).
Select(Contains("skip")).
Confirm()
/* EXPECTED:
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Contains("index.lock': File exists.")).
Confirm()
t.Views().Files().
Lines(
Contains("A").Contains("file-y"),
)
ACTUAL: */
t.Views().Commits().
Lines(
Contains("add file-z"),
Contains("edited commit"),
Contains("base"),
)
},
})
@@ -30,3 +30,15 @@ func HoldIndexLockAfterNextCommit(shell *Shell) {
shell.CreateFile(".git/hooks/post-commit", "#!/bin/sh\n"+holdIndexLockScript)
shell.MakeExecutable(".git/hooks/post-commit")
}
// HoldIndexLockBeforeNextPickIsCommitted installs a reference-transaction hook
// that takes index.lock (see holdIndexLockScript) the next time a rebase or
// cherry-pick has applied the changes of a commit, but hasn't committed them
// yet. This is when git creates CHERRY_PICK_HEAD.
func HoldIndexLockBeforeNextPickIsCommitted(shell *Shell) {
shell.CreateFile(".git/hooks/reference-transaction", `#!/bin/sh
[ "$1" = committed ] || exit 0
awk '$3 == "CHERRY_PICK_HEAD" && $2 !~ /^0+$/ { found = 1 } END { exit !found }' || exit 0
`+holdIndexLockScript)
shell.MakeExecutable(".git/hooks/reference-transaction")
}
+1
View File
@@ -362,6 +362,7 @@ var tests = []*components.IntegrationTest{
interactive_rebase.InteractiveRebaseOfCopiedBranch,
interactive_rebase.InteractiveRebaseWithConflictForEditCommand,
interactive_rebase.LockErrorPartwayThroughRebase,
interactive_rebase.LockErrorPartwayThroughSkip,
interactive_rebase.MidRebaseRangeSelect,
interactive_rebase.Move,
interactive_rebase.MoveAcrossBranchBoundaryOutsideRebase,