Repository: jesseduffield/lazygit
File: pkg/gui/controllers/local_commits_controller.go (Go)

Code before:

func (self *LocalCommitsController) quickStartInteractiveRebase() error {
	commitToEdit, err := self.findCommitForQuickStartInteractiveRebase()
	if err != nil {
		return self.c.Error(err)
	}

	return self.startInteractiveRebaseWithEdit(commitToEdit)
}

func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
	commitToEdit *models.Commit,
) error {
	return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
		self.c.LogAction(self.c.Tr.Actions.EditCommit)
		startIdx, endIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
		commits := self.c.Model().Commits
		selectionStartSha := commits[startIdx].Sha
		selectionEndSha := commits[endIdx].Sha
		err := self.c.Git().Rebase.EditRebase(commitToEdit.Sha)
		return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
			err,
			types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
				// We need to select the same commit range again because after starting a rebase,
				// new lines can be added for update-ref commands in the TODO file, due to
				// stacked branches. So the selected commits may be in different positions in the list.
				_, startIndex, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
					return c.Sha == selectionStartSha
				})
				_, endIndex, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
					return c.Sha == selectionEndSha
				})
				if ok1 && ok2 {
					self.context().SetSelectionRangeAndMode(startIndex, endIndex, rangeSelectMode)
				}
			}})
	})
}

func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (*models.Commit, error) {
	commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
		return c.IsMerge() || c.Status == models.StatusMerged
	})

	if !ok || index == 0 {
		errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{
			"editKey": keybindings.Label(self.c.UserConfig.Keybinding.Universal.Edit),
		})

		return nil, errors.New(errorMsg)
	}

Review comment (suggestion):
Looking at the code, I don't believe we're keeping track of which end of the range contains the selected index, meaning if your cursor is at the top of the range it will now move to the bottom. So rather than talking in terms of a start and end index, I'd talk in terms of the selected index and the range start index.

Code after:

func (self *LocalCommitsController) quickStartInteractiveRebase() error {
	commitToEdit, err := self.findCommitForQuickStartInteractiveRebase()
	if err != nil {
		return self.c.Error(err)
	}

	return self.startInteractiveRebaseWithEdit(commitToEdit)
}

func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
	commitToEdit *models.Commit,
) error {
	return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
		self.c.LogAction(self.c.Tr.Actions.EditCommit)
		selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
		commits := self.c.Model().Commits
		selectedSha := commits[selectedIdx].Sha
		rangeStartSha := commits[rangeStartIdx].Sha
		err := self.c.Git().Rebase.EditRebase(commitToEdit.Sha)
		return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
			err,
			types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
				// We need to select the same commit range again because after starting a rebase,
				// new lines can be added for update-ref commands in the TODO file, due to
				// stacked branches. So the selected commits may be in different positions in the list.
				_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
					return c.Sha == selectedSha
				})
				_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
					return c.Sha == rangeStartSha
				})
				if ok1 && ok2 {
					self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, rangeSelectMode)
				}
			}})
	})
}

func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (*models.Commit, error) {
	commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
		return c.IsMerge() || c.Status == models.StatusMerged
	})

	if !ok || index == 0 {
		errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{
			"editKey": keybindings.Label(self.c.UserConfig.Keybinding.Universal.Edit),
		})

		return nil, errors.New(errorMsg)
	}