From bf586ff0c6b0f22a25c0d7ad8c09778383ba9390 Mon Sep 17 00:00:00 2001 From: Mohammed Faizan Mohiuddin Date: Mon, 27 Jul 2026 15:43:35 +0530 Subject: [PATCH] Fix panic when moving a custom patch after its commit leaves the view When a custom patch is built from a commit and you then check out a branch that doesn't contain that commit, getPatchCommitIndex() returns -1, which was passed straight into commits[commitIndex], panicking with 'index out of range [-1]' (#5802). This affected all five custom-patch menu actions. Make getPatchCommitIndex() return an error when the patch's commit is no longer present, and have each action surface it as a friendly message instead of crashing. Add an integration test reproducing the scenario. --- .../custom_patch_options_menu_action.go | 31 +++++++--- pkg/i18n/english.go | 2 + ...index_when_commit_not_in_current_branch.go | 58 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 4 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 pkg/integration/tests/patch_building/move_to_index_when_commit_not_in_current_branch.go diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go index 2882ab808fd..9d3ebe734c3 100644 --- a/pkg/gui/controllers/custom_patch_options_menu_action.go +++ b/pkg/gui/controllers/custom_patch_options_menu_action.go @@ -114,13 +114,13 @@ func (self *CustomPatchOptionsMenuAction) Call() error { return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.PatchOptionsTitle, Items: menuItems}) } -func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int { +func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() (int, error) { for index, commit := range self.c.Model().Commits { if commit.Hash() == self.c.Git().Patch.PatchBuilder.To { - return index + return index, nil } } - return -1 + return -1, errors.New(self.c.Tr.PatchCommitNotInCommitsErr) } func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessary() { @@ -133,7 +133,10 @@ func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error { self.returnFocusFromPatchExplorerIfNecessary() commits := self.c.Model().Commits - commitIndex := self.getPatchCommitIndex() + commitIndex, err := self.getPatchCommitIndex() + if err != nil { + return err + } return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit) err := self.c.Git().Patch.DeletePatchesFromCommit(commits, commitIndex) @@ -145,7 +148,10 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() erro self.returnFocusFromPatchExplorerIfNecessary() commits := self.c.Model().Commits - commitIndex := self.getPatchCommitIndex() + commitIndex, err := self.getPatchCommitIndex() + if err != nil { + return err + } toCommitIndex := self.c.Contexts().LocalCommits.GetSelectedLineIdx() return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit) @@ -163,7 +169,10 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error Prompt: self.c.Tr.MustStashWarning, HandleConfirm: func() error { commits := self.c.Model().Commits - commitIndex := self.getPatchCommitIndex() + commitIndex, err := self.getPatchCommitIndex() + if err != nil { + return err + } return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex) err := self.c.Git().Patch.MovePatchIntoIndex(commits, commitIndex, mustStash) @@ -176,7 +185,10 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error { self.returnFocusFromPatchExplorerIfNecessary() - commitIndex := self.getPatchCommitIndex() + commitIndex, err := self.getPatchCommitIndex() + if err != nil { + return err + } self.c.Helpers().Commits.OpenCommitMessagePanel( &helpers.OpenCommitMessagePanelOpts{ // Pass a commit index of one less than the moved-from commit, so that @@ -211,7 +223,10 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error { func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() error { self.returnFocusFromPatchExplorerIfNecessary() - commitIndex := self.getPatchCommitIndex() + commitIndex, err := self.getPatchCommitIndex() + if err != nil { + return err + } self.c.Helpers().Commits.OpenCommitMessagePanel( &helpers.OpenCommitMessagePanelOpts{ // Pass a commit index of one less than the moved-from commit, so that diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 2e83fed9bde..f85256eefad 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -461,6 +461,7 @@ type TranslationSet struct { AutoStashForCheckout string AutoStashForNewBranch string AutoStashForMovingPatchToIndex string + PatchCommitNotInCommitsErr string AutoStashForCherryPicking string AutoStashForReverting string Discard string @@ -1611,6 +1612,7 @@ func EnglishTranslationSet() *TranslationSet { AutoStashForCheckout: "Auto-stashing changes for checking out %s", AutoStashForNewBranch: "Auto-stashing changes for creating new branch %s", AutoStashForMovingPatchToIndex: "Auto-stashing changes for moving custom patch to index from %s", + PatchCommitNotInCommitsErr: "Cannot find the commit this custom patch was created from in the current commits list. This can happen after switching branches; recreate the patch to continue.", AutoStashForCherryPicking: "Auto-stashing changes for cherry-picking commits", AutoStashForReverting: "Auto-stashing changes for reverting commits", Discard: "Discard", diff --git a/pkg/integration/tests/patch_building/move_to_index_when_commit_not_in_current_branch.go b/pkg/integration/tests/patch_building/move_to_index_when_commit_not_in_current_branch.go new file mode 100644 index 00000000000..06f40e2ccab --- /dev/null +++ b/pkg/integration/tests/patch_building/move_to_index_when_commit_not_in_current_branch.go @@ -0,0 +1,58 @@ +package patch_building + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var MoveToIndexWhenCommitNotInCurrentBranch = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Move a patch into the index after checking out a branch that doesn't contain the patch's commit; expect a friendly error rather than a crash", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("base commit") + shell.NewBranch("feature") + shell.CreateFileAndAdd("file1", "file1 content\n") + shell.Commit("feature commit") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // Build a custom patch from a commit that only exists on the feature branch + t.Views().Commits(). + Focus(). + Lines( + Contains("feature commit").IsSelected(), + Contains("base commit"), + ). + PressEnter() + + t.Views().CommitFiles(). + IsFocused(). + PressPrimaryAction() + + t.Views().Information().Content(Contains("Building patch")) + + // Check out a branch that does not contain the patch's commit, so the + // commit the patch was built from is no longer in the commits list. + t.Views().Branches(). + Focus(). + NavigateToLine(Contains("master")). + PressPrimaryAction() + + t.Views().Commits(). + Focus(). + Lines( + Contains("base commit").IsSelected(), + ) + + // Previously this panicked with "index out of range [-1]" because the + // patch's commit could not be found in the current commits list. It + // should now report a friendly error instead. + t.Common().SelectPatchOption(Contains("Move patch out into index")) + + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Contains("Cannot find the commit this custom patch was created from")). + Confirm() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 07a12e2be1c..674c1644dca 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -364,6 +364,7 @@ var tests = []*components.IntegrationTest{ patch_building.MoveToIndexFromAddedFileWithConflict, patch_building.MoveToIndexPartOfAdjacentAddedLines, patch_building.MoveToIndexPartial, + patch_building.MoveToIndexWhenCommitNotInCurrentBranch, patch_building.MoveToIndexWithConflict, patch_building.MoveToIndexWithModifiedFile, patch_building.MoveToIndexWorksEvenIfNoprefixIsSet,