diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index f40f7fa6f..1cdd55f63 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -88,8 +88,8 @@ func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) { } // DeletePatchesFromCommit applies a patch in reverse for a commit -func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int) error { - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex, false); err != nil { +func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int, parentIdx int) error { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex, parentIdx, false); err != nil { return err } @@ -113,12 +113,12 @@ func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, com return self.rebase.ContinueRebase() } -func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int) error { +func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int, parentIdx int) error { if sourceCommitIdx < destinationCommitIdx { // Passing true for keepCommitsThatBecomeEmpty: if the moved-from // commit becomes empty, we want to keep it, mainly for consistency with // moving the patch to a *later* commit, which behaves the same. - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx, true); err != nil { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx, parentIdx, true); err != nil { return err } @@ -217,14 +217,14 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s return self.rebase.ContinueRebase() } -func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error { +func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, parentIdx int, stash bool) error { if stash { if err := self.stash.Push(fmt.Sprintf(self.Tr.AutoStashForMovingPatchToIndex, commits[commitIdx].ShortHash())); err != nil { return err } } - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, false); err != nil { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, parentIdx, false); err != nil { return err } @@ -275,10 +275,11 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId func (self *PatchCommands) PullPatchIntoNewCommit( commits []*models.Commit, commitIdx int, + parentIdx int, commitSummary string, commitDescription string, ) error { - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, false); err != nil { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx, parentIdx, false); err != nil { return err } @@ -318,10 +319,11 @@ func (self *PatchCommands) PullPatchIntoNewCommit( func (self *PatchCommands) PullPatchIntoNewCommitBefore( commits []*models.Commit, commitIdx int, + parentIdx int, commitSummary string, commitDescription string, ) error { - if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx+1, true); err != nil { + if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx+1, parentIdx, true); err != nil { return err } diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index 74278b18d..aebfc635f 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -34,14 +34,14 @@ func NewRebaseCommands( } } -func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error { +func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, parentIdx int, summary string, description string) error { // This check is currently unreachable (handled in LocalCommitsController.reword), // but kept as a safeguard in case this method is used elsewhere. if self.config.NeedsGpgSubprocessForCommit() { return errors.New(self.Tr.DisabledForGPG) } - err := self.BeginInteractiveRebaseForCommit(commits, index, false) + err := self.BeginInteractiveRebaseForCommit(commits, index, parentIdx, false) if err != nil { return err } @@ -55,7 +55,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su return self.ContinueRebase() } -func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (*oscommands.CmdObj, error) { +func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int, parentIdx int) (*oscommands.CmdObj, error) { changes := []daemon.ChangeTodoAction{{ Hash: commits[index].Hash(), NewAction: todo.Reword, @@ -63,36 +63,36 @@ func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index self.os.LogCommand(logTodoChanges(changes), false) return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, index+1), + baseHashOrRoot: getBaseHashOrRoot(commits, parentIdx), instruction: daemon.NewChangeTodoActionsInstruction(changes), }), nil } -func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, start, end int) error { - return self.GenericAmend(commits, start, end, func(_ *models.Commit) error { +func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, start, end int, parentIdx int) error { + return self.GenericAmend(commits, start, end, parentIdx, func(_ *models.Commit) error { return self.commit.ResetAuthor() }) } -func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, start, end int, value string) error { - return self.GenericAmend(commits, start, end, func(_ *models.Commit) error { +func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, start, end int, parentIdx int, value string) error { + return self.GenericAmend(commits, start, end, parentIdx, func(_ *models.Commit) error { return self.commit.SetAuthor(value) }) } -func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, start, end int, value string) error { - return self.GenericAmend(commits, start, end, func(commit *models.Commit) error { +func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, start, end int, parentIdx int, value string) error { + return self.GenericAmend(commits, start, end, parentIdx, func(commit *models.Commit) error { return self.commit.AddCoAuthor(commit.Hash(), value) }) } -func (self *RebaseCommands) GenericAmend(commits []*models.Commit, start, end int, f func(commit *models.Commit) error) error { +func (self *RebaseCommands) GenericAmend(commits []*models.Commit, start, end int, parentIdx int, f func(commit *models.Commit) error) error { if start == end && models.IsHeadCommit(commits, start) { // we've selected the top commit so no rebase is required return f(commits[start]) } - err := self.BeginInteractiveRebaseForCommitRange(commits, start, end, false) + err := self.BeginInteractiveRebaseForCommitRange(commits, start, end, parentIdx, false) if err != nil { return err } @@ -140,13 +140,8 @@ func (self *RebaseCommands) MoveCommits(commits []*models.Commit, startIdx int, }).Run() } -func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand, flag string) error { - baseIndex := endIdx + 1 - if action == todo.Squash || action == todo.Fixup { - baseIndex++ - } - - baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex) +func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, parentIdx int, action todo.TodoCommand, flag string) error { + baseHashOrRoot := getBaseHashOrRoot(commits, parentIdx) changes := lo.FilterMap(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) (daemon.ChangeTodoAction, bool) { return daemon.ChangeTodoAction{ @@ -296,7 +291,7 @@ func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) { } // AmendTo amends the given commit with whatever files are staged -func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error { +func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int, parentIdx int) error { commit := commits[commitIndex] if err := self.commit.CreateFixupCommit(commit.Hash()); err != nil { @@ -309,7 +304,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e } return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1), + baseHashOrRoot: getBaseHashOrRoot(commits, parentIdx), overrideEditor: true, instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash(), fixupHash, true), }).Run() @@ -403,7 +398,7 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er // BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current // commit and pick all others. After this you'll want to call `self.ContinueRebase() func (self *RebaseCommands) BeginInteractiveRebaseForCommit( - commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool, + commits []*models.Commit, commitIndex int, parentIdx int, keepCommitsThatBecomeEmpty bool, ) error { if commitIndex < len(commits) && commits[commitIndex].IsMerge() { if self.config.NeedsGpgSubprocessForCommit() { @@ -417,11 +412,11 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit( }).Run() } - return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty) + return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, parentIdx, keepCommitsThatBecomeEmpty) } func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange( - commits []*models.Commit, start, end int, keepCommitsThatBecomeEmpty bool, + commits []*models.Commit, start, end int, parentIdx int, keepCommitsThatBecomeEmpty bool, ) error { if len(commits)-1 < end { return errors.New("index outside of range of commits") @@ -444,7 +439,7 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange( self.os.LogCommand(logTodoChanges(changes), false) return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{ - baseHashOrRoot: getBaseHashOrRoot(commits, end+1), + baseHashOrRoot: getBaseHashOrRoot(commits, parentIdx), overrideEditor: true, keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty, instruction: daemon.NewChangeTodoActionsInstruction(changes), @@ -517,8 +512,8 @@ func (self *RebaseCommands) runSkipEditorCommand(cmdObj *oscommands.CmdObj) erro } // DiscardOldFileChanges discards changes to a file from an old commit -func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, filePaths []string) error { - if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex, false); err != nil { +func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, parentIdx int, filePaths []string) error { + if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex, parentIdx, false); err != nil { return err } diff --git a/pkg/commands/git_commands/rebase_test.go b/pkg/commands/git_commands/rebase_test.go index 46f1fcc1c..b6e42d215 100644 --- a/pkg/commands/git_commands/rebase_test.go +++ b/pkg/commands/git_commands/rebase_test.go @@ -100,6 +100,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) { gitConfigMockResponses map[string]string commitOpts []models.NewCommitOpts commitIndex int + parentIndex int fileName []string runner *oscommands.FakeCmdObjRunner test func(error) @@ -111,6 +112,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) { gitConfigMockResponses: nil, commitOpts: []models.NewCommitOpts{}, commitIndex: 0, + parentIndex: 0, fileName: []string{"test999.txt"}, runner: oscommands.NewFakeRunner(t), test: func(err error) { @@ -122,6 +124,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) { gitConfigMockResponses: map[string]string{"commit.gpgSign": "true"}, commitOpts: []models.NewCommitOpts{{Name: "commit", Hash: "123456"}}, commitIndex: 0, + parentIndex: 0, fileName: []string{"test999.txt"}, runner: oscommands.NewFakeRunner(t), test: func(err error) { @@ -136,6 +139,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) { {Name: "commit2", Hash: "abcdef"}, }, commitIndex: 0, + parentIndex: 1, fileName: []string{"test999.txt"}, runner: oscommands.NewFakeRunner(t). ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil). @@ -163,7 +167,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) { commits := lo.Map(s.commitOpts, func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) }) - s.test(instance.DiscardOldFileChanges(commits, s.commitIndex, s.fileName)) + s.test(instance.DiscardOldFileChanges(commits, s.commitIndex, s.parentIndex, s.fileName)) s.runner.CheckForMissingCalls() }) } diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 4aa46a28c..588b5b6b8 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -339,6 +339,8 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN HandleConfirm: func() error { commits := self.c.Model().Commits selectedLineIdx := self.c.Contexts().LocalCommits.GetSelectedLineIdx() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{ Message: self.c.Tr.RebasingStatus, HideWorkingTreeState: true, @@ -361,7 +363,7 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN }) } - err := self.c.Git().Rebase.DiscardOldFileChanges(commits, selectedLineIdx, filePaths) + err := self.c.Git().Rebase.DiscardOldFileChanges(commits, selectedLineIdx, parentIdx, filePaths) if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { return err } diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go index 2882ab808..af35a285d 100644 --- a/pkg/gui/controllers/custom_patch_options_menu_action.go +++ b/pkg/gui/controllers/custom_patch_options_menu_action.go @@ -134,9 +134,11 @@ func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error { commits := self.c.Model().Commits commitIndex := self.getPatchCommitIndex() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) 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) + err := self.c.Git().Patch.DeletePatchesFromCommit(commits, commitIndex, parentIdx) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) }) } @@ -147,9 +149,11 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() erro commits := self.c.Model().Commits commitIndex := self.getPatchCommitIndex() toCommitIndex := self.c.Contexts().LocalCommits.GetSelectedLineIdx() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit) - err := self.c.Git().Patch.MovePatchToSelectedCommit(commits, commitIndex, toCommitIndex) + err := self.c.Git().Patch.MovePatchToSelectedCommit(commits, commitIndex, toCommitIndex, parentIdx) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) }) } @@ -164,9 +168,11 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error HandleConfirm: func() error { commits := self.c.Model().Commits commitIndex := self.getPatchCommitIndex() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) 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) + err := self.c.Git().Patch.MovePatchIntoIndex(commits, commitIndex, parentIdx, mustStash) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) }) }, @@ -177,6 +183,8 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error { self.returnFocusFromPatchExplorerIfNecessary() commitIndex := self.getPatchCommitIndex() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) self.c.Helpers().Commits.OpenCommitMessagePanel( &helpers.OpenCommitMessagePanelOpts{ // Pass a commit index of one less than the moved-from commit, so that @@ -191,7 +199,7 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error { self.c.Helpers().Commits.CloseCommitMessagePanel() return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit) - err := self.c.Git().Patch.PullPatchIntoNewCommit(commits, commitIndex, summary, description) + err := self.c.Git().Patch.PullPatchIntoNewCommit(commits, commitIndex, parentIdx, summary, description) if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { return err } @@ -212,6 +220,8 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() e self.returnFocusFromPatchExplorerIfNecessary() commitIndex := self.getPatchCommitIndex() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 2) self.c.Helpers().Commits.OpenCommitMessagePanel( &helpers.OpenCommitMessagePanelOpts{ // Pass a commit index of one less than the moved-from commit, so that @@ -226,7 +236,7 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() e self.c.Helpers().Commits.CloseCommitMessagePanel() return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit) - err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(commits, commitIndex, summary, description) + err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(commits, commitIndex, parentIdx, summary, description) if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil { return err } diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 5e50ba7b2..3f1a67511 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -7,6 +7,7 @@ import ( "time" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" + "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/samber/lo" @@ -252,6 +253,39 @@ func (self *CommitsHelper) OpenCommitMenu(suggestionFunc func(string) []*types.S }) } +func (self *CommitsHelper) GetParentCommit(selectedCommits []*models.Commit, endIdx int, depth int) (*models.Commit, int) { + commits := self.c.Model().Commits + + currentCommit := selectedCommits[len(selectedCommits)-1] + parentIndex := endIdx + var parentCommit *models.Commit + + for d := 0; d < depth; d++ { + selectedParents := currentCommit.Parents() + if len(selectedParents) == 0 { + self.c.Log.Warn("Selected commit has no parents, the nearest commit is used") + return commits[parentIndex], parentIndex + 1 + } + parentHash := selectedParents[0] + found := false + for i, commit := range commits { + if commit.Hash() == parentHash { + parentIndex = i + parentCommit = commits[parentIndex] + currentCommit = parentCommit + found = true + break + } + } + if !found { + self.c.Log.Warnf("Parent commit %s not found in visible commit list, the nearest commit is used ", parentHash) + return commits[parentIndex], parentIndex + 1 + } + } + + return parentCommit, parentIndex +} + func (self *CommitsHelper) addCoAuthor(suggestionFunc func(string) []*types.Suggestion) error { self.c.Prompt(types.PromptOpts{ Title: self.c.Tr.AddCoAuthorPromptTitle, diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index 1e1a01427..3e4626610 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -740,6 +740,8 @@ func (self *LocalCommitsController) squashDown(selectedCommits []*models.Commit, return self.updateTodos(todo.Squash, selectedCommits) } + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + self.c.Confirm(types.ConfirmOpts{ Title: self.c.Tr.Squash, Prompt: self.c.Tr.SureSquashThisCommit, @@ -751,7 +753,7 @@ func (self *LocalCommitsController) squashDown(selectedCommits []*models.Commit, HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.SquashCommitDown) - return self.interactiveRebase(commits, todo.Squash, startIdx, endIdx) + return self.interactiveRebase(commits, todo.Squash, startIdx, endIdx, parentIdx) }) }, }) @@ -763,6 +765,7 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star if self.isRebasing() { return self.updateTodos(todo.Fixup, selectedCommits) } + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) return self.c.Menu(types.CreateMenuOptions{ Title: self.c.Tr.Fixup, @@ -778,7 +781,7 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.FixupCommit) - return self.interactiveRebase(commits, todo.Fixup, startIdx, endIdx) + return self.interactiveRebase(commits, todo.Fixup, startIdx, endIdx, parentIdx) }) }, Tooltip: self.c.Tr.FixupTooltip, @@ -794,7 +797,7 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage) - return self.interactiveRebaseWithFlag(commits, todo.Fixup, startIdx, endIdx, "-C") + return self.interactiveRebaseWithFlag(commits, todo.Fixup, startIdx, endIdx, parentIdx, "-C") }) }, Tooltip: self.c.Tr.FixupKeepMessageTooltip, @@ -872,7 +875,10 @@ func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepat self.c.Git().Commit.RewordLastCommitInEditorWithMessageFileCmdObj(filepath)) } - err := self.c.Git().Rebase.BeginInteractiveRebaseForCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx(), false) + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + + err := self.c.Git().Rebase.BeginInteractiveRebaseForCommit(self.c.Model().Commits, self.context().GetSelectedLineIdx(), parentIdx, false) if err != nil { return err } @@ -903,11 +909,14 @@ func (self *LocalCommitsController) handleReword(summary string, description str self.c.Tr.RewordingStatus, nil, nil) } + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{ Message: self.c.Tr.RewordingStatus, HideWorkingTreeState: true, }, func(gocui.Task) error { - err := self.c.Git().Rebase.RewordCommit(commits, selectedIdx, summary, description) + err := self.c.Git().Rebase.RewordCommit(commits, selectedIdx, parentIdx, summary, description) if err != nil { return err } @@ -923,8 +932,11 @@ func (self *LocalCommitsController) doRewordEditor() error { return self.c.RunSubprocessAndRefresh(self.c.Git().Commit.RewordLastCommitInEditorCmdObj()) } + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + subProcess, err := self.c.Git().Rebase.RewordCommitInEditor( - self.c.Model().Commits, self.context().GetSelectedLineIdx(), + self.c.Model().Commits, self.context().GetSelectedLineIdx(), parentIdx, ) if err != nil { return err @@ -984,6 +996,8 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start isMerge := selectedCommits[0].IsMerge() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + self.c.Confirm(types.ConfirmOpts{ Title: self.c.Tr.DropCommitTitle, Prompt: lo.Ternary(isMerge, self.c.Tr.DropMergeCommitPrompt, self.c.Tr.DropCommitPrompt), @@ -1000,7 +1014,7 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start if isMerge { return self.dropMergeCommit(commits, startIdx) } - return self.interactiveRebase(commits, todo.Drop, startIdx, endIdx) + return self.interactiveRebase(commits, todo.Drop, startIdx, endIdx, parentIdx) }) }, }) @@ -1018,13 +1032,15 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start return self.updateTodos(todo.Edit, selectedCommits) } + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + commits := self.c.Model().Commits if !commits[endIdx].IsMerge() { return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{ Message: self.c.Tr.RebasingStatus, HideWorkingTreeState: true, }, func(gocui.Task) error { - err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "") + err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, parentIdx, todo.Edit, "") return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions( err, types.RefreshOptions{BatchUIUpdates: true}) }) @@ -1093,12 +1109,12 @@ func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error panic("should be disabled when not rebasing") } -func (self *LocalCommitsController) interactiveRebase(commits []*models.Commit, action todo.TodoCommand, startIdx int, endIdx int) error { - return self.interactiveRebaseWithFlag(commits, action, startIdx, endIdx, "") +func (self *LocalCommitsController) interactiveRebase(commits []*models.Commit, action todo.TodoCommand, startIdx int, endIdx int, parentIdx int) error { + return self.interactiveRebaseWithFlag(commits, action, startIdx, endIdx, parentIdx, "") } -func (self *LocalCommitsController) interactiveRebaseWithFlag(commits []*models.Commit, action todo.TodoCommand, startIdx int, endIdx int, flag string) error { - err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, action, flag) +func (self *LocalCommitsController) interactiveRebaseWithFlag(commits []*models.Commit, action todo.TodoCommand, startIdx int, endIdx int, parentIdx int, flag string) error { + err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, parentIdx, action, flag) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) } @@ -1226,6 +1242,9 @@ func (self *LocalCommitsController) move( func (self *LocalCommitsController) amendTo(commit *models.Commit) error { var handleCommit func() error + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) + if self.isSelectedHeadCommit() { handleCommit = func() error { return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error { @@ -1246,7 +1265,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error { HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.AmendCommit) - err := self.c.Git().Rebase.AmendTo(commits, selectedIdx) + err := self.c.Git().Rebase.AmendTo(commits, selectedIdx, parentIdx) return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err) }) }) @@ -1274,30 +1293,31 @@ func (self *LocalCommitsController) canAmend(_ *models.Commit) *types.DisabledRe return self.canAmendRange(self.c.Model().Commits, idx, idx) } -func (self *LocalCommitsController) amendAttribute(_ []*models.Commit, start, end int) error { +func (self *LocalCommitsController) amendAttribute(selectedCommits []*models.Commit, start, end int) error { // The author operations index into the full commit list by absolute // start/end, so capture that here on the UI thread rather than reading // Model().Commits from the worker the menu items dispatch to. commits := self.c.Model().Commits + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, end, 1) opts := self.c.KeybindingsOpts() return self.c.Menu(types.CreateMenuOptions{ Title: "Amend commit attribute", Items: []*types.MenuItem{ { Label: self.c.Tr.ResetAuthor, - OnPress: func() error { return self.resetAuthor(commits, start, end) }, + OnPress: func() error { return self.resetAuthor(commits, start, end, parentIdx) }, Keys: opts.GetKeys(opts.Config.AmendAttribute.ResetAuthor), Tooltip: self.c.Tr.ResetAuthorTooltip, }, { Label: self.c.Tr.SetAuthor, - OnPress: func() error { return self.setAuthor(commits, start, end) }, + OnPress: func() error { return self.setAuthor(commits, start, end, parentIdx) }, Keys: opts.GetKeys(opts.Config.AmendAttribute.SetAuthor), Tooltip: self.c.Tr.SetAuthorTooltip, }, { Label: self.c.Tr.AddCoAuthor, - OnPress: func() error { return self.addCoAuthor(commits, start, end) }, + OnPress: func() error { return self.addCoAuthor(commits, start, end, parentIdx) }, Keys: opts.GetKeys(opts.Config.AmendAttribute.AddCoAuthor), Tooltip: self.c.Tr.AddCoAuthorTooltip, }, @@ -1305,13 +1325,13 @@ func (self *LocalCommitsController) amendAttribute(_ []*models.Commit, start, en }) } -func (self *LocalCommitsController) resetAuthor(commits []*models.Commit, start, end int) error { +func (self *LocalCommitsController) resetAuthor(commits []*models.Commit, start, end int, parentIdx int) error { return self.c.WithWaitingStatusBlockingInput(types.WaitingStatusOpts{ Message: self.c.Tr.AmendingStatus, HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor) - if err := self.c.Git().Rebase.ResetCommitAuthor(commits, start, end); err != nil { + if err := self.c.Git().Rebase.ResetCommitAuthor(commits, start, end, parentIdx); err != nil { return err } @@ -1320,7 +1340,7 @@ func (self *LocalCommitsController) resetAuthor(commits []*models.Commit, start, }) } -func (self *LocalCommitsController) setAuthor(commits []*models.Commit, start, end int) error { +func (self *LocalCommitsController) setAuthor(commits []*models.Commit, start, end int, parentIdx int) error { self.c.Prompt(types.PromptOpts{ Title: self.c.Tr.SetAuthorPromptTitle, FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), @@ -1330,7 +1350,7 @@ func (self *LocalCommitsController) setAuthor(commits []*models.Commit, start, e HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor) - if err := self.c.Git().Rebase.SetCommitAuthor(commits, start, end, value); err != nil { + if err := self.c.Git().Rebase.SetCommitAuthor(commits, start, end, parentIdx, value); err != nil { return err } @@ -1343,7 +1363,7 @@ func (self *LocalCommitsController) setAuthor(commits []*models.Commit, start, e return nil } -func (self *LocalCommitsController) addCoAuthor(commits []*models.Commit, start, end int) error { +func (self *LocalCommitsController) addCoAuthor(commits []*models.Commit, start, end int, parentIdx int) error { self.c.Prompt(types.PromptOpts{ Title: self.c.Tr.AddCoAuthorPromptTitle, FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(), @@ -1353,7 +1373,7 @@ func (self *LocalCommitsController) addCoAuthor(commits []*models.Commit, start, HideWorkingTreeState: true, }, func(gocui.Task) error { self.c.LogAction(self.c.Tr.Actions.AddCommitCoAuthor) - if err := self.c.Git().Rebase.AddCommitCoAuthor(commits, start, end, value); err != nil { + if err := self.c.Git().Rebase.AddCommitCoAuthor(commits, start, end, parentIdx, value); err != nil { return err } self.c.RefreshFromWorker(types.RefreshOptions{}) diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go index e1405463a..ed0ce0eb4 100644 --- a/pkg/gui/controllers/patch_building_controller.go +++ b/pkg/gui/controllers/patch_building_controller.go @@ -225,9 +225,11 @@ func (self *PatchBuildingController) discardSelectionFromCommit() error { commits := self.c.Model().Commits commitIndex := self.getPatchCommitIndex() + selectedCommits, _, endIdx := self.c.Contexts().LocalCommits.GetSelectedItems() + _, parentIdx := self.c.Helpers().Commits.GetParentCommit(selectedCommits, endIdx, 1) 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) + err := self.c.Git().Patch.DeletePatchesFromCommit(commits, commitIndex, parentIdx) // Escape pops the patch-building context, so run it on the UI thread // before the refresh below. _ = self.c.GocuiGui().OnUIThreadAndWait(func() { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 9c72b53df..ade270fb6 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -1332,7 +1332,7 @@ func EnglishTranslationSet() *TranslationSet { SetFixupMessageTooltip: "Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message.", FixupDiscardMessage: "Fixup and discard this commit's message", FixupDiscardMessageTooltip: "Squash the selected commit into the commit below, discarding this commit's message.", - SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?", + SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the parent commit below?", Squash: "Squash", PickCommitTooltip: "Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase.", Pick: "Pick", diff --git a/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go b/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go index fe9d2b762..7a0379766 100644 --- a/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go +++ b/pkg/integration/tests/interactive_rebase/outside_rebase_range_select.go @@ -49,7 +49,7 @@ var OutsideRebaseRangeSelect = NewIntegrationTest(NewIntegrationTestArgs{ Tap(func() { t.ExpectPopup().Confirmation(). Title(Equals("Squash")). - Content(Contains("Are you sure you want to squash the selected commit(s) into the commit below?")). + Content(Contains("Are you sure you want to squash the selected commit(s) into the parent commit below?")). Confirm() }). TopLines( diff --git a/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go b/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go index ba5f33705..545860e6d 100644 --- a/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go +++ b/pkg/integration/tests/interactive_rebase/squash_down_second_commit.go @@ -27,7 +27,7 @@ var SquashDownSecondCommit = NewIntegrationTest(NewIntegrationTestArgs{ Tap(func() { t.ExpectPopup().Confirmation(). Title(Equals("Squash")). - Content(Equals("Are you sure you want to squash the selected commit(s) into the commit below?")). + Content(Equals("Are you sure you want to squash the selected commit(s) into the parent commit below?")). Confirm() }). Lines(