Repository: jesseduffield/lazygit (language: go)

Issue:
Continuing a revert after fixing a merge conflict does not work
When attempting to revert a commit that results in a conflict, there is no way to continue the revert after resolving the conflict (ie. `git revert --continue`):

![iTerm2 2022-03-14 at 08 58 52](https://user-images.githubusercontent.com/191335/158138759-7040b255-9920-4e35-9b64-5f5f1c5f04e4.gif)

Given that this works in lazygit for other similar flows like rebasing, I'd expect this one to also be feasible.



Discussion / hints:
interesting, it never occurred to me that that git revert would have its own continue/abort/skip commands. This should be easy to support by extending the `genericMergeCommand` function in `pkg/gui/controllers/helpers/merge_and_rebase_helper.go` but that's on my refactor branch which I still haven't quite merged
Just ran into this as well. Not a huge deal to break out of lazygit every now and then, and now have my $0.02 FWIW 😄 
I have run into this too in the past. And it's not just revert, there are other commands that have the same issue (`cherry-pick` and `am` are the ones I can think of off the top of my head). To be fair, these can't be invoked from within lazygit itself, so they are maybe less important to support.
The code I referred to in my last comment is now merged so this is unblocked.

Fleshing out what's specifically required here: we need a way of knowing whether we're mid-cherry-pick or mid-revert so that we can issue the right command to continue.

The important code is in `pkg/gui/controllers/helpers/merge_and_rebase_helper.go`, specifically `genericMergeCommand`:
```go
	commandType := ""
	switch status {
	case enums.REBASE_MODE_MERGING:
		commandType = "merge"
	case enums.REBASE_MODE_REBASING:
		commandType = "rebase"
	default:
		// shouldn't be possible to land here
	}
```

In `pkg/commands/git_commands/status.go` we have:

```go
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
}

func (self *StatusCommands) IsInInteractiveRebase() (bool, error) {
	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
}

// IsInMergeState states whether we are still mid-merge
func (self *StatusCommands) IsInMergeState() (bool, error) {
	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
}
```

And now we need to add one for cherry-pick/revert i.e. looking for CHERRY_PICK_HEAD and REVERT_HEAD.

In `pkg/commands/types/enums/enums.go` we have our `RebaseMode` enum:

```go
type RebaseMode int

const (
	// this means we're neither rebasing nor merging
	REBASE_MODE_NONE RebaseMode = iota
	// this means normal rebase as opposed to interactive rebase
	REBASE_MODE_NORMAL
	REBASE_MODE_INTERACTIVE
	// REBASE_MODE_REBASING is a general state that captures both REBASE_MODE_NORMAL and REBASE_MODE_INTERACTIVE
	REBASE_MODE_REBASING
	REBASE_MODE_MERGING
)
```

And as you can see it's being used for two different things in `pkg/commands/git_commands/status.go`:

```go
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
	ok, err := self.IsInNormalRebase()
	if err == nil && ok {
		return enums.REBASE_MODE_NORMAL, nil
	}
	ok, err = self.IsInInteractiveRebase()
	if err == nil && ok {
		return enums.REBASE_MODE_INTERACTIVE, err
	}

	return enums.REBASE_MODE_NONE, err
}

func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
	rebaseMode, _ := self.RebaseMode()
	if rebaseMode != enums.REBASE_MODE_NONE {
		return enums.REBASE_MODE_REBASING
	}
	merging, _ := self.IsInMergeState()
	if merging {
		return enums.REBASE_MODE_MERGING
	}
	return enums.REBASE_MODE_NONE
}
```

These are used in a few places so we would need to go to each place and see if it needs to be updated to support new enum values. I think we should be encapsulating some details e.g. 

```go
func (self RebaseMode) CanContinue() bool {
	return self == REBASE_MODE_NORMAL || self == REBASE_MODE_INTERACTIVE || self == REBASE_MODE_CHERRY_PICK || self == REBASE_MODE_REVERT
}

func (self RebaseMode) CanAbort() bool {
	...
}

func (self RebaseMode) Command() string {
	switch self {
	case REBASE_MODE_NORMAL, REBASE_MODE_INTERACTIVE:
		return "rebase"
	case REBASE_MODE_REVERT:
		return "revert"
	case REBASE_MODE_CHERRY_PICK:
		return "cherry-pick"
	case REBASE_MODE_MERGING:
		return "merge"
	}
}
```

So that we don't need to be listing all the different variants throughout the code.
I would suggest to support `git am` as well, while we're at it.

Fix patch:
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 7e7d9354fe7..4aba9a7a845 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -136,7 +136,7 @@ func NewGitCommandAux(
 
 	branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
 	commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
-	commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
+	commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.WorkingTreeState, gitCommon)
 	reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
 	remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
 	worktreeLoader := git_commands.NewWorktreeLoader(gitCommon)
diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go
index f9cfff1419e..22050b38472 100644
--- a/pkg/commands/git_commands/commit_loader.go
+++ b/pkg/commands/git_commands/commit_loader.go
@@ -13,7 +13,6 @@ import (
 
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
 	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/common"
 	"github.com/jesseduffield/lazygit/pkg/utils"
 	"github.com/samber/lo"
@@ -31,10 +30,10 @@ type CommitLoader struct {
 	*common.Common
 	cmd oscommands.ICmdObjBuilder
 
-	getRebaseMode func() (enums.RebaseMode, error)
-	readFile      func(filename string) ([]byte, error)
-	walkFiles     func(root string, fn filepath.WalkFunc) error
-	dotGitDir     string
+	getWorkingTreeState func() models.WorkingTreeState
+	readFile            func(filename string) ([]byte, error)
+	walkFiles           func(root string, fn filepath.WalkFunc) error
+	dotGitDir           string
 	*GitCommon
 }
 
@@ -42,16 +41,16 @@ type CommitLoader struct {
 func NewCommitLoader(
 	cmn *common.Common,
 	cmd oscommands.ICmdObjBuilder,
-	getRebaseMode func() (enums.RebaseMode, error),
+	getWorkingTreeState func() models.WorkingTreeState,
 	gitCommon *GitCommon,
 ) *CommitLoader {
 	return &CommitLoader{
-		Common:        cmn,
-		cmd:           cmd,
-		getRebaseMode: getRebaseMode,
-		readFile:      os.ReadFile,
-		walkFiles:     filepath.Walk,
-		GitCommon:     gitCommon,
+		Common:              cmn,
+		cmd:                 cmd,
+		getWorkingTreeState: getWorkingTreeState,
+		readFile:            os.ReadFile,
+		walkFiles:           filepath.Walk,
+		GitCommon:           gitCommon,
 	}
 }
 
@@ -172,17 +171,12 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
 		}
 	}
 
-	rebaseMode, err := self.getRebaseMode()
-	if err != nil {
-		return nil, err
-	}
-
-	if rebaseMode == enums.REBASE_MODE_NONE {
+	if !self.getWorkingTreeState().Rebasing {
 		// not in rebase mode so return original commits
 		return result, nil
 	}
 
-	rebasingCommits, err := self.getHydratedRebasingCommits(rebaseMode)
+	rebasingCommits, err := self.getHydratedRebasingCommits()
 	if err != nil {
 		return nil, err
 	}
@@ -248,8 +242,8 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
 	}
 }
 
-func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) {
-	commits := self.getRebasingCommits(rebaseMode)
+func (self *CommitLoader) getHydratedRebasingCommits() ([]*models.Commit, error) {
+	commits := self.getRebasingCommits()
 
 	if len(commits) == 0 {
 		return nil, nil
@@ -310,11 +304,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
 // git-rebase-todo example:
 // pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae
 // pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931
-func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) []*models.Commit {
-	if rebaseMode != enums.REBASE_MODE_INTERACTIVE {
-		return nil
-	}
-
+func (self *CommitLoader) getRebasingCommits() []*models.Commit {
 	bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"))
 	if err != nil {
 		self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go
index 36e094337c4..77563ab88bb 100644
--- a/pkg/commands/git_commands/patch.go
+++ b/pkg/commands/git_commands/patch.go
@@ -8,7 +8,6 @@ import (
 	"github.com/jesseduffield/lazygit/pkg/app/daemon"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
 	"github.com/jesseduffield/lazygit/pkg/commands/patch"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/stefanhaller/git-todo-parser/todo"
 )
 
@@ -229,7 +228,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
 	}
 
 	if err := self.ApplyCustomPatch(true, true); err != nil {
-		if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+		if self.status.WorkingTreeState().Rebasing {
 			_ = self.rebase.AbortRebase()
 		}
 		return err
@@ -253,7 +252,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
 	self.rebase.onSuccessfulContinue = func() error {
 		// add patches to index
 		if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
-			if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+			if self.status.WorkingTreeState().Rebasing {
 				_ = self.rebase.AbortRebase()
 			}
 			return err
diff --git a/pkg/commands/git_commands/status.go b/pkg/commands/git_commands/status.go
index 0e0ef37fcc5..ff09e22bc31 100644
--- a/pkg/commands/git_commands/status.go
+++ b/pkg/commands/git_commands/status.go
@@ -5,7 +5,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
+	"github.com/jesseduffield/lazygit/pkg/commands/models"
 )
 
 type StatusCommands struct {
@@ -20,50 +20,68 @@ func NewStatusCommands(
 	}
 }
 
-// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
-// and "interactive" for interactive rebase
-func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
-	ok, err := self.IsInNormalRebase()
-	if err == nil && ok {
-		return enums.REBASE_MODE_NORMAL, nil
-	}
-	ok, err = self.IsInInteractiveRebase()
-	if err == nil && ok {
-		return enums.REBASE_MODE_INTERACTIVE, err
-	}
-
-	return enums.REBASE_MODE_NONE, err
-}
-
-func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
-	rebaseMode, _ := self.RebaseMode()
-	if rebaseMode != enums.REBASE_MODE_NONE {
-		return enums.REBASE_MODE_REBASING
-	}
-	merging, _ := self.IsInMergeState()
-	if merging {
-		return enums.REBASE_MODE_MERGING
-	}
-	return enums.REBASE_MODE_NONE
+func (self *StatusCommands) WorkingTreeState() models.WorkingTreeState {
+	result := models.WorkingTreeState{}
+	result.Rebasing, _ = self.IsInRebase()
+	result.Merging, _ = self.IsInMergeState()
+	result.CherryPicking, _ = self.IsInCherryPick()
+	result.Reverting, _ = self.IsInRevert()
+	return result
 }
 
 func (self *StatusCommands) IsBareRepo() bool {
 	return self.repoPaths.isBareRepo
 }
 
-func (self *StatusCommands) IsInNormalRebase() (bool, error) {
+func (self *StatusCommands) IsInRebase() (bool, error) {
+	exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
+	if err == nil && exists {
+		return true, nil
+	}
 	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
 }
 
-func (self *StatusCommands) IsInInteractiveRebase() (bool, error) {
-	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
-}
-
 // IsInMergeState states whether we are still mid-merge
 func (self *StatusCommands) IsInMergeState() (bool, error) {
 	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
 }
 
+func (self *StatusCommands) IsInCherryPick() (bool, error) {
+	exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "CHERRY_PICK_HEAD"))
+	if err != nil || !exists {
+		return exists, err
+	}
+	// Sometimes, CHERRY_PICK_HEAD is present during rebases even if no
+	// cherry-pick is in progress. I suppose this is because rebase used to be
+	// implemented as a series of cherry-picks, so this could be remnants of
+	// code that is shared between cherry-pick and rebase, or something. The way
+	// to tell if this is the case is to check for the presence of the
+	// stopped-sha file, which records the sha of the last pick that was
+	// executed before the rebase stopped, and seeing if the sha in that file is
+	// the same as the one in CHERRY_PICK_HEAD.
+	cherryPickHead, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "CHERRY_PICK_HEAD"))
+	if err != nil {
+		return false, err
+	}
+	stoppedSha, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge", "stopped-sha"))
+	if err != nil {
+		// If we get an error we assume the file doesn't exist
+		return true, nil
+	}
+	cherryPickHeadStr := strings.TrimSpace(string(cherryPickHead))
+	stoppedShaStr := strings.TrimSpace(string(stoppedSha))
+	// Need to use HasPrefix here because the cherry-pick HEAD is a full sha1,
+	// but stopped-sha is an abbreviated sha1
+	if strings.HasPrefix(cherryPickHeadStr, stoppedShaStr) {
+		return false, nil
+	}
+	return true, nil
+}
+
+func (self *StatusCommands) IsInRevert() (bool, error) {
+	return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "REVERT_HEAD"))
+}
+
 // Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
 // being rebased, or empty string when we're not in a rebase
 func (self *StatusCommands) BranchBeingRebased() string {
diff --git a/pkg/commands/models/working_tree_state.go b/pkg/commands/models/working_tree_state.go
new file mode 100644
index 00000000000..3e7bdbf1b40
--- /dev/null
+++ b/pkg/commands/models/working_tree_state.go
@@ -0,0 +1,112 @@
+package models
+
+import "github.com/jesseduffield/lazygit/pkg/i18n"
+
+// The state of the working tree. Several of these can be true at once.
+// In particular, the concrete multi-state combinations that can occur in
+// practice are Rebasing+CherryPicking, and Rebasing+Reverting. Theoretically, I
+// guess Rebasing+Merging could also happen, but it probably won't in practice.
+type WorkingTreeState struct {
+	Rebasing      bool
+	Merging       bool
+	CherryPicking bool
+	Reverting     bool
+}
+
+func (self WorkingTreeState) Any() bool {
+	return self.Rebasing || self.Merging || self.CherryPicking || self.Reverting
+}
+
+func (self WorkingTreeState) None() bool {
+	return !self.Any()
+}
+
+type EffectiveWorkingTreeState int
+
+const (
+	// this means we're neither rebasing nor merging, cherry-picking, or reverting
+	WORKING_TREE_STATE_NONE EffectiveWorkingTreeState = iota
+	WORKING_TREE_STATE_REBASING
+	WORKING_TREE_STATE_MERGING
+	WORKING_TREE_STATE_CHERRY_PICKING
+	WORKING_TREE_STATE_REVERTING
+)
+
+// Effective returns the "current" state; if several states are true at once,
+// this is the one that should be displayed in status views, and it's the one
+// that the user can continue or abort.
+//
+// As an example, if you are stopped in an interactive rebase, and then you
+// perform a cherry-pick, and the cherry-pick conflicts, then both
+// WorkingTreeState.Rebasing and WorkingTreeState.CherryPicking are true.
+// The effective state is cherry-picking, because that's the one you can
+// continue or abort. It is not possible to continue the rebase without first
+// aborting the cherry-pick.
+func (self WorkingTreeState) Effective() EffectiveWorkingTreeState {
+	if self.Reverting {
+		return WORKING_TREE_STATE_REVERTING
+	}
+	if self.CherryPicking {
+		return WORKING_TREE_STATE_CHERRY_PICKING
+	}
+	if self.Merging {
+		return WORKING_TREE_STATE_MERGING
+	}
+	if self.Rebasing {
+		return WORKING_TREE_STATE_REBASING
+	}
+	return WORKING_TREE_STATE_NONE
+}
+
+func (self WorkingTreeState) Title(tr *i18n.TranslationSet) string {
+	return map[EffectiveWorkingTreeState]string{
+		WORKING_TREE_STATE_REBASING:       tr.RebasingStatus,
+		WORKING_TREE_STATE_MERGING:        tr.MergingStatus,
+		WORKING_TREE_STATE_CHERRY_PICKING: tr.CherryPickingStatus,
+		WORKING_TREE_STATE_REVERTING:      tr.RevertingStatus,
+	}[self.Effective()]
+}
+
+func (self WorkingTreeState) LowerCaseTitle(tr *i18n.TranslationSet) string {
+	return map[EffectiveWorkingTreeState]string{
+		WORKING_TREE_STATE_REBASING:       tr.LowercaseRebasingStatus,
+		WORKING_TREE_STATE_MERGING:        tr.LowercaseMergingStatus,
+		WORKING_TREE_STATE_CHERRY_PICKING: tr.LowercaseCherryPickingStatus,
+		WORKING_TREE_STATE_REVERTING:      tr.LowercaseRevertingStatus,
+	}[self.Effective()]
+}
+
+func (self WorkingTreeState) OptionsMenuTitle(tr *i18n.TranslationSet) string {
+	return map[EffectiveWorkingTreeState]string{
+		WORKING_TREE_STATE_REBASING:       tr.RebaseOptionsTitle,
+		WORKING_TREE_STATE_MERGING:        tr.MergeOptionsTitle,
+		WORKING_TREE_STATE_CHERRY_PICKING: tr.CherryPickOptionsTitle,
+		WORKING_TREE_STATE_REVERTING:      tr.RevertOptionsTitle,
+	}[self.Effective()]
+}
+
+func (self WorkingTreeState) OptionsMapTitle(tr *i18n.TranslationSet) string {
+	return map[EffectiveWorkingTreeState]string{
+		WORKING_TREE_STATE_REBASING:       tr.ViewRebaseOptions,
+		WORKING_TREE_STATE_MERGING:        tr.ViewMergeOptions,
+		WORKING_TREE_STATE_CHERRY_PICKING: tr.ViewCherryPickOptions,
+		WORKING_TREE_STATE_REVERTING:      tr.ViewRevertOptions,
+	}[self.Effective()]
+}
+
+func (self WorkingTreeState) CommandName() string {
+	return map[EffectiveWorkingTreeState]string{
+		WORKING_TREE_STATE_REBASING:       "rebase",
+		WORKING_TREE_STATE_MERGING:        "merge",
+		WORKING_TREE_STATE_CHERRY_PICKING: "cherry-pick",
+		WORKING_TREE_STATE_REVERTING:      "revert",
+	}[self.Effective()]
+}
+
+func (self WorkingTreeState) CanShowTodos() bool {
+	return self.Rebasing || self.CherryPicking || self.Reverting
+}
+
+func (self WorkingTreeState) CanSkip() bool {
+	return self.Rebasing || self.CherryPicking || self.Reverting
+}
diff --git a/pkg/commands/types/enums/enums.go b/pkg/commands/types/enums/enums.go
deleted file mode 100644
index 68a29d8f0ab..00000000000
--- a/pkg/commands/types/enums/enums.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package enums
-
-type RebaseMode int
-
-const (
-	// this means we're neither rebasing nor merging
-	REBASE_MODE_NONE RebaseMode = iota
-	// this means normal rebase as opposed to interactive rebase
-	REBASE_MODE_NORMAL
-	REBASE_MODE_INTERACTIVE
-	// REBASE_MODE_REBASING is a general state that captures both REBASE_MODE_NORMAL and REBASE_MODE_INTERACTIVE
-	REBASE_MODE_REBASING
-	REBASE_MODE_MERGING
-)
-
-func (self RebaseMode) IsMerging() bool {
-	return self == REBASE_MODE_MERGING
-}
-
-func (self RebaseMode) IsRebasing() bool {
-	return self == REBASE_MODE_INTERACTIVE || self == REBASE_MODE_NORMAL || self == REBASE_MODE_REBASING
-}
diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go
index 4f2f797e5da..f40f7f0ff49 100644
--- a/pkg/gui/context/local_commits_context.go
+++ b/pkg/gui/context/local_commits_context.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/presentation"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/samber/lo"
@@ -41,7 +40,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
 			}
 		}
 
-		showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == enums.REBASE_MODE_REBASING
+		showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh.CanShowTodos()
 		hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs()
 
 		return presentation.GetCommitListDisplayStrings(
diff --git a/pkg/gui/controllers/custom_patch_options_menu_action.go b/pkg/gui/controllers/custom_patch_options_menu_action.go
index ced98b0725e..f231152db79 100644
--- a/pkg/gui/controllers/custom_patch_options_menu_action.go
+++ b/pkg/gui/controllers/custom_patch_options_menu_action.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/jesseduffield/gocui"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 )
@@ -44,7 +43,7 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
 		},
 	}
 
-	if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
+	if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState().None() {
 		menuItems = append(menuItems, []*types.MenuItem{
 			{
 				Label:   fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, self.c.Git().Patch.PatchBuilder.To),
@@ -115,7 +114,7 @@ func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
 }
 
 func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) {
-	if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+	if self.c.Git().Status.WorkingTreeState().Any() {
 		return false, errors.New(self.c.Tr.CantPatchWhileRebasingError)
 	}
 	return true, nil
diff --git a/pkg/gui/controllers/global_controller.go b/pkg/gui/controllers/global_controller.go
index 6fa6fefef9f..2d7f796a5cf 100644
--- a/pkg/gui/controllers/global_controller.go
+++ b/pkg/gui/controllers/global_controller.go
@@ -35,11 +35,12 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
 			OpensMenu:   true,
 		},
 		{
-			Key:         opts.GetKey(opts.Config.Universal.CreateRebaseOptionsMenu),
-			Handler:     opts.Guards.NoPopupPanel(self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu),
-			Description: self.c.Tr.ViewMergeRebaseOptions,
-			Tooltip:     self.c.Tr.ViewMergeRebaseOptionsTooltip,
-			OpensMenu:   true,
+			Key:               opts.GetKey(opts.Config.Universal.CreateRebaseOptionsMenu),
+			Handler:           opts.Guards.NoPopupPanel(self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu),
+			Description:       self.c.Tr.ViewMergeRebaseOptions,
+			Tooltip:           self.c.Tr.ViewMergeRebaseOptionsTooltip,
+			OpensMenu:         true,
+			GetDisabledReason: self.canShowRebaseOptions,
 		},
 		{
 			Key:         opts.GetKey(opts.Config.Universal.Refresh),
@@ -191,3 +192,12 @@ func (self *GlobalController) escape() error {
 func (self *GlobalController) toggleWhitespace() error {
 	return (&ToggleWhitespaceAction{c: self.c}).Call()
 }
+
+func (self *GlobalController) canShowRebaseOptions() *types.DisabledReason {
+	if self.c.Model().WorkingTreeStateAtLastCommitRefresh.None() {
+		return &types.DisabledReason{
+			Text: self.c.Tr.NotMergingOrRebasing,
+		}
+	}
+	return nil
+}
diff --git a/pkg/gui/controllers/helpers/cherry_pick_helper.go b/pkg/gui/controllers/helpers/cherry_pick_helper.go
index 47108df166e..1cb2285d002 100644
--- a/pkg/gui/controllers/helpers/cherry_pick_helper.go
+++ b/pkg/gui/controllers/helpers/cherry_pick_helper.go
@@ -77,7 +77,7 @@ func (self *CherryPickHelper) Paste() error {
 				"numCommits": strconv.Itoa(len(self.getData().CherryPickedCommits)),
 			}),
 		HandleConfirm: func() error {
-			isInRebase, err := self.c.Git().Status.IsInInteractiveRebase()
+			isInRebase, err := self.c.Git().Status.IsInRebase()
 			if err != nil {
 				return err
 			}
@@ -107,7 +107,7 @@ func (self *CherryPickHelper) Paste() error {
 				// be because there were conflicts. Don't clear the copied
 				// commits in this case, since we might want to abort and
 				// try pasting them again.
-				isInRebase, err = self.c.Git().Status.IsInInteractiveRebase()
+				isInRebase, err = self.c.Git().Status.IsInRebase()
 				if err != nil {
 					return err
 				}
diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
index fba0b9014c8..3f2ced17e5c 100644
--- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
+++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go
@@ -10,7 +10,6 @@ import (
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/jesseduffield/lazygit/pkg/utils"
 	"github.com/samber/lo"
@@ -51,7 +50,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
 		{option: REBASE_OPTION_ABORT, key: 'a'},
 	}
 
-	if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+	if self.c.Git().Status.WorkingTreeState().CanSkip() {
 		options = append(options, optionAndKey{
 			option: REBASE_OPTION_SKIP, key: 's',
 		})
@@ -67,13 +66,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
 		}
 	})
 
-	var title string
-	if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
-		title = self.c.Tr.MergeOptionsTitle
-	} else {
-		title = self.c.Tr.RebaseOptionsTitle
-	}
-
+	title := self.c.Git().Status.WorkingTreeState().OptionsMenuTitle(self.c.Tr)
 	return self.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems})
 }
 
@@ -84,12 +77,13 @@ func (self *MergeAndRebaseHelper) ContinueRebase() error {
 func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
 	status := self.c.Git().Status.WorkingTreeState()
 
-	if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
+	if status.None() {
 		return errors.New(self.c.Tr.NotMergingOrRebasing)
 	}
 
 	self.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
-	if status == enums.REBASE_MODE_REBASING {
+	effectiveStatus := status.Effective()
+	if effectiveStatus == models.WORKING_TREE_STATE_REBASING {
 		todoFile, err := os.ReadFile(
 			filepath.Join(self.c.Git().RepoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
 		)
@@ -103,23 +97,15 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
 		}
 	}
 
-	commandType := ""
-	switch status {
-	case enums.REBASE_MODE_MERGING:
-		commandType = "merge"
-	case enums.REBASE_MODE_REBASING:
-		commandType = "rebase"
-	default:
-		// shouldn't be possible to land here
-	}
+	commandType := status.CommandName()
 
 	// we should end up with a command like 'git merge --continue'
 
 	// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
-	needsSubprocess := (status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig().Git.Merging.ManualCommit) ||
+	needsSubprocess := (effectiveStatus == models.WORKING_TREE_STATE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig().Git.Merging.ManualCommit) ||
 		// but we'll also use a subprocess if we have exec todos; those are likely to be lengthy build
 		// tasks whose output the user will want to see in the terminal
-		(status == enums.REBASE_MODE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos())
+		(effectiveStatus == models.WORKING_TREE_STATE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos())
 
 	if needsSubprocess {
 		// TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction
@@ -152,6 +138,8 @@ var conflictStrings = []string{
 	"fix conflicts",
 	"Resolve all conflicts manually",
 	"Merge conflict in file",
+	"hint: after resolving the conflicts",
+	"CONFLICT (content):",
 }
 
 func isMergeConflictErr(errStr string) bool {
@@ -199,7 +187,7 @@ func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error {
 }
 
 func (self *MergeAndRebaseHelper) PromptForConflictHandling() error {
-	mode := self.workingTreeStateNoun()
+	mode := self.c.Git().Status.WorkingTreeState().CommandName()
 	return self.c.Menu(types.CreateMenuOptions{
 		Title: self.c.Tr.FoundConflictsTitle,
 		Items: []*types.MenuItem{
@@ -224,7 +212,7 @@ func (self *MergeAndRebaseHelper) PromptForConflictHandling() error {
 
 func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error {
 	// prompt user to confirm that they want to abort, then do it
-	mode := self.workingTreeStateNoun()
+	mode := self.c.Git().Status.WorkingTreeState().CommandName()
 	self.c.Confirm(types.ConfirmOpts{
 		Title:  fmt.Sprintf(self.c.Tr.AbortTitle, mode),
 		Prompt: fmt.Sprintf(self.c.Tr.AbortPrompt, mode),
@@ -236,23 +224,11 @@ func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error {
 	return nil
 }
 
-func (self *MergeAndRebaseHelper) workingTreeStateNoun() string {
-	workingTreeState := self.c.Git().Status.WorkingTreeState()
-	switch workingTreeState {
-	case enums.REBASE_MODE_NONE:
-		return ""
-	case enums.REBASE_MODE_MERGING:
-		return "merge"
-	default:
-		return "rebase"
-	}
-}
-
 // PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
 func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
 	self.c.Confirm(types.ConfirmOpts{
 		Title:  self.c.Tr.Continue,
-		Prompt: self.c.Tr.ConflictsResolved,
+		Prompt: fmt.Sprintf(self.c.Tr.ConflictsResolved, self.c.Git().Status.WorkingTreeState().CommandName()),
 		HandleConfirm: func() error {
 			// By the time we get here, we might have unstaged changes again,
 			// e.g. if the user had to fix build errors after resolving the
diff --git a/pkg/gui/controllers/helpers/mode_helper.go b/pkg/gui/controllers/helpers/mode_helper.go
index 5798621552f..8c6b46eb662 100644
--- a/pkg/gui/controllers/helpers/mode_helper.go
+++ b/pkg/gui/controllers/helpers/mode_helper.go
@@ -4,8 +4,6 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
-	"github.com/jesseduffield/lazygit/pkg/gui/presentation"
 	"github.com/jesseduffield/lazygit/pkg/gui/style"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/samber/lo"
@@ -116,12 +114,12 @@ func (self *ModeHelper) Statuses() []ModeStatus {
 		},
 		{
 			IsActive: func() bool {
-				return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE
+				return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState().Any()
 			},
 			Description: func() string {
 				workingTreeState := self.c.Git().Status.WorkingTreeState()
 				return self.withResetButton(
-					presentation.FormatWorkingTreeStateTitle(self.c.Tr, workingTreeState), style.FgYellow,
+					workingTreeState.Title(self.c.Tr), style.FgYellow,
 				)
 			},
 			Reset: self.mergeAndRebaseHelper.AbortMergeOrRebaseWithConfirm,
diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go
index 1cf64b3f8b4..73d0fb608b6 100644
--- a/pkg/gui/controllers/helpers/patch_building_helper.go
+++ b/pkg/gui/controllers/helpers/patch_building_helper.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 
 	"github.com/jesseduffield/lazygit/pkg/commands/patch"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 )
@@ -22,7 +21,7 @@ func NewPatchBuildingHelper(
 }
 
 func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error) {
-	if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+	if self.c.Git().Status.WorkingTreeState().Any() {
 		return false, errors.New(self.c.Tr.CantPatchWhileRebasingError)
 	}
 	return true, nil
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index 33347a363c7..93ad110f304 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -9,7 +9,6 @@ import (
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/filetree"
 	"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
@@ -583,7 +582,7 @@ func (self *RefreshHelper) refreshStateFiles() error {
 		}
 	}
 
-	if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
+	if self.c.Git().Status.WorkingTreeState().Any() && conflictFileCount == 0 && prevConflictFileCount > 0 {
 		self.c.OnUIThread(func() error { return self.mergeAndRebaseHelper.PromptToContinueRebase() })
 	}
 
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index 79e3a6549da..0b1b81e72af 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -8,7 +8,6 @@ import (
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/context"
 	"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
 	"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
@@ -682,7 +681,7 @@ func (self *LocalCommitsController) rewordEnabled(commit *models.Commit) *types.
 }
 
 func (self *LocalCommitsController) isRebasing() bool {
-	return self.c.Model().WorkingTreeStateAtLastCommitRefresh != enums.REBASE_MODE_NONE
+	return self.c.Model().WorkingTreeStateAtLastCommitRefresh.Any()
 }
 
 func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
@@ -869,7 +868,8 @@ func (self *LocalCommitsController) revert(commit *models.Commit) error {
 		HandleConfirm: func() error {
 			self.c.LogAction(self.c.Tr.Actions.RevertCommit)
 			return self.c.WithWaitingStatusSync(self.c.Tr.RevertingStatus, func() error {
-				if err := self.c.Git().Commit.Revert(commit.Hash); err != nil {
+				result := self.c.Git().Commit.Revert(commit.Hash)
+				if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(result); err != nil {
 					return err
 				}
 				return self.afterRevertCommit()
@@ -976,7 +976,7 @@ func (self *LocalCommitsController) moveFixupCommitToOwnerStackedBranch(targetCo
 		return nil
 	}
 
-	if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
+	if self.c.Git().Status.WorkingTreeState().Any() {
 		// Can't move commits while rebasing
 		return nil
 	}
diff --git a/pkg/gui/controllers/status_controller.go b/pkg/gui/controllers/status_controller.go
index d517b870c85..8d173f46cf3 100644
--- a/pkg/gui/controllers/status_controller.go
+++ b/pkg/gui/controllers/status_controller.go
@@ -7,7 +7,6 @@ import (
 	"time"
 
 	"github.com/jesseduffield/gocui"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/constants"
 	"github.com/jesseduffield/lazygit/pkg/gui/presentation"
 	"github.com/jesseduffield/lazygit/pkg/gui/style"
@@ -109,19 +108,16 @@ func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
 	upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig()))
 	repoName := self.c.Git().RepoPaths.RepoName()
 	workingTreeState := self.c.Git().Status.WorkingTreeState()
-	switch workingTreeState {
-	case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
-		workingTreeStatus := fmt.Sprintf("(%s)", presentation.FormatWorkingTreeStateLower(self.c.Tr, workingTreeState))
+	if workingTreeState.Any() {
+		workingTreeStatus := fmt.Sprintf("(%s)", workingTreeState.LowerCaseTitle(self.c.Tr))
 		if cursorInSubstring(opts.X, upstreamStatus+" ", workingTreeStatus) {
 			return self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu()
 		}
 		if cursorInSubstring(opts.X, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
 			return self.c.Helpers().Repos.CreateRecentReposMenu()
 		}
-	default:
-		if cursorInSubstring(opts.X, upstreamStatus+" ", repoName) {
-			return self.c.Helpers().Repos.CreateRecentReposMenu()
-		}
+	} else if cursorInSubstring(opts.X, upstreamStatus+" ", repoName) {
+		return self.c.Helpers().Repos.CreateRecentReposMenu()
 	}
 
 	return nil
diff --git a/pkg/gui/controllers/undo_controller.go b/pkg/gui/controllers/undo_controller.go
index 198aa7d7bcb..9922f73d6b1 100644
--- a/pkg/gui/controllers/undo_controller.go
+++ b/pkg/gui/controllers/undo_controller.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/jesseduffield/gocui"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/gui/types"
 	"github.com/jesseduffield/lazygit/pkg/utils"
 )
@@ -78,7 +77,7 @@ func (self *UndoController) reflogUndo() error {
 	undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
 	undoingStatus := self.c.Tr.UndoingStatus
 
-	if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+	if self.c.Git().Status.WorkingTreeState().Any() {
 		return errors.New(self.c.Tr.CantUndoWhileRebasing)
 	}
 
@@ -142,7 +141,7 @@ func (self *UndoController) reflogRedo() error {
 	redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
 	redoingStatus := self.c.Tr.RedoingStatus
 
-	if self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
+	if self.c.Git().Status.WorkingTreeState().Any() {
 		return errors.New(self.c.Tr.CantRedoWhileRebasing)
 	}
 
diff --git a/pkg/gui/options_map.go b/pkg/gui/options_map.go
index 4f983655188..347db75898e 100644
--- a/pkg/gui/options_map.go
+++ b/pkg/gui/options_map.go
@@ -82,16 +82,10 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
 	}
 
 	// Mode-specific global keybindings
-	if self.c.Model().WorkingTreeStateAtLastCommitRefresh.IsRebasing() {
+	if state := self.c.Model().WorkingTreeStateAtLastCommitRefresh; state.Any() {
 		optionsMap = utils.Prepend(optionsMap, bindingInfo{
 			key:         keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu),
-			description: self.c.Tr.ViewRebaseOptions,
-			style:       style.FgYellow,
-		})
-	} else if self.c.Model().WorkingTreeStateAtLastCommitRefresh.IsMerging() {
-		optionsMap = utils.Prepend(optionsMap, bindingInfo{
-			key:         keybindings.Label(self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu),
-			description: self.c.Tr.ViewMergeOptions,
+			description: state.OptionsMapTitle(self.c.Tr),
 			style:       style.FgYellow,
 		})
 	}
diff --git a/pkg/gui/presentation/status.go b/pkg/gui/presentation/status.go
index b3b2100673f..b636df6471d 100644
--- a/pkg/gui/presentation/status.go
+++ b/pkg/gui/presentation/status.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/config"
 	"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
 	"github.com/jesseduffield/lazygit/pkg/gui/style"
@@ -18,7 +17,7 @@ func FormatStatus(
 	currentBranch *models.Branch,
 	itemOperation types.ItemOperation,
 	linkedWorktreeName string,
-	workingTreeState enums.RebaseMode,
+	workingTreeState models.WorkingTreeState,
 	tr *i18n.TranslationSet,
 	userConfig *config.UserConfig,
 ) string {
@@ -31,8 +30,8 @@ func FormatStatus(
 		}
 	}
 
-	if workingTreeState != enums.REBASE_MODE_NONE {
-		status += style.FgYellow.Sprintf("(%s) ", FormatWorkingTreeStateLower(tr, workingTreeState))
+	if workingTreeState.Any() {
+		status += style.FgYellow.Sprintf("(%s) ", workingTreeState.LowerCaseTitle(tr))
 	}
 
 	name := GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name)
diff --git a/pkg/gui/presentation/working_tree.go b/pkg/gui/presentation/working_tree.go
deleted file mode 100644
index 1e1954bc985..00000000000
--- a/pkg/gui/presentation/working_tree.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package presentation
-
-import (
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
-	"github.com/jesseduffield/lazygit/pkg/i18n"
-)
-
-func FormatWorkingTreeStateTitle(tr *i18n.TranslationSet, rebaseMode enums.RebaseMode) string {
-	switch rebaseMode {
-	case enums.REBASE_MODE_REBASING:
-		return tr.RebasingStatus
-	case enums.REBASE_MODE_MERGING:
-		return tr.MergingStatus
-	default:
-		// should never actually display this
-		return "none"
-	}
-}
-
-func FormatWorkingTreeStateLower(tr *i18n.TranslationSet, rebaseMode enums.RebaseMode) string {
-	switch rebaseMode {
-	case enums.REBASE_MODE_REBASING:
-		return tr.LowercaseRebasingStatus
-	case enums.REBASE_MODE_MERGING:
-		return tr.LowercaseMergingStatus
-	default:
-		// should never actually display this
-		return "none"
-	}
-}
diff --git a/pkg/gui/types/common.go b/pkg/gui/types/common.go
index 82a309fb113..e01c664230b 100644
--- a/pkg/gui/types/common.go
+++ b/pkg/gui/types/common.go
@@ -6,7 +6,6 @@ import (
 	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
 	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/common"
 	"github.com/jesseduffield/lazygit/pkg/config"
 	"github.com/jesseduffield/lazygit/pkg/utils"
@@ -288,7 +287,7 @@ type Model struct {
 	ReflogCommits []*models.Commit
 
 	BisectInfo                          *git_commands.BisectInfo
-	WorkingTreeStateAtLastCommitRefresh enums.RebaseMode
+	WorkingTreeStateAtLastCommitRefresh models.WorkingTreeState
 	RemoteBranches                      []*models.RemoteBranch
 	Tags                                []*models.Tag
 
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index b2deb7ab11f..aa413c6b5b0 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -306,11 +306,15 @@ type TranslationSet struct {
 	ViewMergeRebaseOptionsTooltip         string
 	ViewMergeOptions                      string
 	ViewRebaseOptions                     string
+	ViewCherryPickOptions                 string
+	ViewRevertOptions                     string
 	NotMergingOrRebasing                  string
 	AlreadyRebasing                       string
 	RecentRepos                           string
 	MergeOptionsTitle                     string
 	RebaseOptionsTitle                    string
+	CherryPickOptionsTitle                string
+	RevertOptionsTitle                    string
 	CommitSummaryTitle                    string
 	CommitDescriptionTitle                string
 	CommitDescriptionSubTitle             string
@@ -392,6 +396,8 @@ type TranslationSet struct {
 	MergingStatus                         string
 	LowercaseRebasingStatus               string
 	LowercaseMergingStatus                string
+	LowercaseCherryPickingStatus          string
+	LowercaseRevertingStatus              string
 	AmendingStatus                        string
 	CherryPickingStatus                   string
 	UndoingStatus                         string
@@ -1359,11 +1365,15 @@ func EnglishTranslationSet() *TranslationSet {
 		ViewMergeRebaseOptionsTooltip:        "View options to abort/continue/skip the current merge/rebase.",
 		ViewMergeOptions:                     "View merge options",
 		ViewRebaseOptions:                    "View rebase options",
+		ViewCherryPickOptions:                "View cherry-pick options",
+		ViewRevertOptions:                    "View revert options",
 		NotMergingOrRebasing:                 "You are currently neither rebasing nor merging",
 		AlreadyRebasing:                      "Can't perform this action during a rebase",
 		RecentRepos:                          "Recent repositories",
 		MergeOptionsTitle:                    "Merge options",
 		RebaseOptionsTitle:                   "Rebase options",
+		CherryPickOptionsTitle:               "Cherry-pick options",
+		RevertOptionsTitle:                   "Revert options",
 		CommitSummaryTitle:                   "Commit summary",
 		CommitDescriptionTitle:               "Commit description",
 		CommitDescriptionSubTitle:            "Press {{.togglePanelKeyBinding}} to toggle focus, {{.commitMenuKeybinding}} to open menu",
@@ -1381,7 +1391,7 @@ func EnglishTranslationSet() *TranslationSet {
 		SecondaryTitle:                       "Secondary",
 		ReflogCommitsTitle:                   "Reflog",
 		GlobalTitle:                          "Global keybindings",
-		ConflictsResolved:                    "All merge conflicts resolved. Continue?",
+		ConflictsResolved:                    "All merge conflicts resolved. Continue the %s?",
 		Continue:                             "Continue",
 		UnstagedFilesAfterConflictsResolved:  "Files have been modified since conflicts were resolved. Auto-stage them and continue?",
 		Keybindings:                          "Keybindings",
@@ -1451,8 +1461,10 @@ func EnglishTranslationSet() *TranslationSet {
 		MovingStatus:                         "Moving",
 		RebasingStatus:                       "Rebasing",
 		MergingStatus:                        "Merging",
-		LowercaseRebasingStatus:              "rebasing", // lowercase because it shows up in parentheses
-		LowercaseMergingStatus:               "merging",  // lowercase because it shows up in parentheses
+		LowercaseRebasingStatus:              "rebasing",       // lowercase because it shows up in parentheses
+		LowercaseMergingStatus:               "merging",        // lowercase because it shows up in parentheses
+		LowercaseCherryPickingStatus:         "cherry-picking", // lowercase because it shows up in parentheses
+		LowercaseRevertingStatus:             "reverting",      // lowercase because it shows up in parentheses
 		AmendingStatus:                       "Amending",
 		CherryPickingStatus:                  "Cherry-picking",
 		UndoingStatus:                        "Undoing",
diff --git a/pkg/integration/clients/injector/main.go b/pkg/integration/clients/injector/main.go
index 1f4c845e83d..96a4fb1f084 100644
--- a/pkg/integration/clients/injector/main.go
+++ b/pkg/integration/clients/injector/main.go
@@ -31,7 +31,7 @@ func main() {
 
 	integrationTest := getIntegrationTest()
 
-	if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" {
+	if os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" && !daemon.InDaemonMode() {
 		println("Waiting for debugger to attach...")
 		for !isDebuggerAttached() {
 			time.Sleep(time.Millisecond * 100)
diff --git a/pkg/integration/components/common.go b/pkg/integration/components/common.go
index 2033a9442e8..2d62e9ea3c9 100644
--- a/pkg/integration/components/common.go
+++ b/pkg/integration/components/common.go
@@ -1,5 +1,7 @@
 package components
 
+import "fmt"
+
 // for running common actions
 type Common struct {
 	t *TestDriver
@@ -43,10 +45,10 @@ func (self *Common) AcknowledgeConflicts() {
 		Confirm()
 }
 
-func (self *Common) ContinueOnConflictsResolved() {
+func (self *Common) ContinueOnConflictsResolved(command string) {
 	self.t.ExpectPopup().Confirmation().
 		Title(Equals("Continue")).
-		Content(Contains("All merge conflicts resolved. Continue?")).
+		Content(Contains(fmt.Sprintf("All merge conflicts resolved. Continue the %s?", command))).
 		Confirm()
 }
 


Test patch:
diff --git a/pkg/commands/git_commands/commit_loader_test.go b/pkg/commands/git_commands/commit_loader_test.go
index f9c4cdff6d2..f7b07513454 100644
--- a/pkg/commands/git_commands/commit_loader_test.go
+++ b/pkg/commands/git_commands/commit_loader_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/go-errors/errors"
 	"github.com/jesseduffield/lazygit/pkg/commands/models"
 	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
-	"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
 	"github.com/jesseduffield/lazygit/pkg/config"
 	"github.com/jesseduffield/lazygit/pkg/utils"
 	"github.com/stefanhaller/git-todo-parser/todo"
@@ -33,17 +32,15 @@ func TestGetCommits(t *testing.T) {
 		expectedCommits []*models.Commit
 		expectedError   error
 		logOrder        string
-		rebaseMode      enums.RebaseMode
 		opts            GetCommitsOptions
 		mainBranches    []string
 	}
 
 	scenarios := []scenario{
 		{
-			testName:   "should return no commits if there are none",
-			logOrder:   "topo-order",
-			rebaseMode: enums.REBASE_MODE_NONE,
-			opts:       GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
+			testName: "should return no commits if there are none",
+			logOrder: "topo-order",
+			opts:     GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
 			runner: oscommands.NewFakeRunner(t).
 				ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
 				ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
@@ -52,10 +49,9 @@ func TestGetCommits(t *testing.T) {
 			expectedError:   nil,
 		},
 		{
-			testName:   "should use proper upstream name for branch",
-			logOrder:   "topo-order",
-			rebaseMode: enums.REBASE_MODE_NONE,
-			opts:       GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
+			testName: "should use proper upstream name for branch",
+			logOrder: "topo-order",
+			opts:     GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
 			runner: oscommands.NewFakeRunner(t).
 				ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
 				ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
@@ -66,7 +62,6 @@ func TestGetCommits(t *testing.T) {
 		{
 			testName:     "should return commits if they are present",
 			logOrder:     "topo-order",
-			rebaseMode:   enums.REBASE_MODE_NONE,
 			opts:         GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
 			mainBranches: []string{"master", "main", "develop"},
 			runner: oscommands.NewFakeRunner(t).
@@ -203,7 +198,6 @@ func TestGetCommits(t *testing.T) {
 		{
 			testName:     "should not call merge-base for mainBranches if none exist",
 			logOrder:     "topo-order",
-			rebaseMode:   enums.REBASE_MODE_NONE,
 			opts:         GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
 			mainBranches: []string{"master", "main"},
 			runner: oscommands.NewFakeRunner(t).
@@ -240,7 +234,6 @@ func TestGetCommits(t *testing.T) {
 		{
 			testName:     "should call merge-base for all main branches that exist",
 			logOrder:     "topo-order",
-			rebaseMode:   enums.REBASE_MODE_NONE,
 			opts:         GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
 			mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
 			runner: oscommands.NewFakeRunner(t).
@@ -277,10 +270,9 @@ func TestGetCommits(t *testing.T) {
 			expectedError: nil,
 		},
 		{
-			testName:   "should not specify order if `log.order` is `default`",
-			logOrder:   "default",
-			rebaseMode: enums.REBASE_MODE_NONE,
-			opts:       GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
+			testName: "should not specify order if `log.order` is `default`",
+			logOrder: "default",
+			opts:     GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
 			runner: oscommands.NewFakeRunner(t).
 				ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
 				ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
@@ -289,10 +281,9 @@ func TestGetCommits(t *testing.T) {
 			expectedError:   nil,
 		},
 		{
-			testName:   "should set filter path",
-			logOrder:   "default",
-			rebaseMode: enums.REBASE_MODE_NONE,
-			opts:       GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
+			testName: "should set filter path",
+			logOrder: "default",
+			opts:     GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
 			runner: oscommands.NewFakeRunner(t).
 				ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
 				ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
@@ -310,10 +301,10 @@ func TestGetCommits(t *testing.T) {
 			cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner)
 
 			builder := &CommitLoader{
-				Common:        common,
-				cmd:           cmd,
-				getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
-				dotGitDir:     ".git",
+				Common:              common,
+				cmd:                 cmd,
+				getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{} },
+				dotGitDir:           ".git",
 				readFile: func(filename string) ([]byte, error) {
 					return []byte(""), nil
 				},
@@ -493,10 +484,10 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
 			common := utils.NewDummyCommon()
 
 			builder := &CommitLoader{
-				Common:        common,
-				cmd:           oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
-				getRebaseMode: func() (enums.RebaseMode, error) { return enums.REBASE_MODE_INTERACTIVE, nil },
-				dotGitDir:     ".git",
+				Common:              common,
+				cmd:                 oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
+				getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{Rebasing: true} },
+				dotGitDir:           ".git",
 				readFile: func(filename string) ([]byte, error) {
 					return []byte(""), nil
 				},
diff --git a/pkg/integration/tests/branch/rebase.go b/pkg/integration/tests/branch/rebase.go
index c7dc028afa4..6bc2083c42a 100644
--- a/pkg/integration/tests/branch/rebase.go
+++ b/pkg/integration/tests/branch/rebase.go
@@ -48,7 +48,7 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{
 
 		t.Views().Information().Content(Contains("Rebasing"))
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Information().Content(DoesNotContain("Rebasing"))
 
diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go
index 594ae485457..d6c655cf7e5 100644
--- a/pkg/integration/tests/branch/rebase_and_drop.go
+++ b/pkg/integration/tests/branch/rebase_and_drop.go
@@ -77,7 +77,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
 			IsFocused().
 			PressPrimaryAction()
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Information().Content(DoesNotContain("Rebasing"))
 
diff --git a/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go b/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go
index 9cd42cd2ee9..2ae75dafbe6 100644
--- a/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go
+++ b/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors.go
@@ -51,7 +51,7 @@ var RebaseConflictsFixBuildErrors = NewIntegrationTest(NewIntegrationTestArgs{
 
 		popup := t.ExpectPopup().Confirmation().
 			Title(Equals("Continue")).
-			Content(Contains("All merge conflicts resolved. Continue?"))
+			Content(Contains("All merge conflicts resolved. Continue the rebase?"))
 
 		// While the popup is showing, fix some build errors
 		t.Shell().UpdateFile("file", "make it compile again")
diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go b/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go
index cd968adf820..2f001ebe448 100644
--- a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go
+++ b/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go
@@ -69,7 +69,7 @@ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{
 			SelectNextItem().
 			PressPrimaryAction()
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Files().IsEmpty()
 
diff --git a/pkg/integration/tests/commit/revert_with_conflict_single_commit.go b/pkg/integration/tests/commit/revert_with_conflict_single_commit.go
new file mode 100644
index 00000000000..949bf4b3427
--- /dev/null
+++ b/pkg/integration/tests/commit/revert_with_conflict_single_commit.go
@@ -0,0 +1,80 @@
+package commit
+
+import (
+	"github.com/jesseduffield/lazygit/pkg/config"
+	. "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var RevertWithConflictSingleCommit = NewIntegrationTest(NewIntegrationTestArgs{
+	Description:  "Reverts a commit that conflicts",
+	ExtraCmdArgs: []string{},
+	Skip:         false,
+	SetupConfig:  func(config *config.AppConfig) {},
+	SetupRepo: func(shell *Shell) {
+		shell.CreateFileAndAdd("myfile", "")
+		shell.Commit("add empty file")
+		shell.CreateFileAndAdd("myfile", "first line\n")
+		shell.Commit("add first line")
+		shell.UpdateFileAndAdd("myfile", "first line\nsecond line\n")
+		shell.Commit("add second line")
+	},
+	Run: func(t *TestDriver, keys config.KeybindingConfig) {
+		t.Views().Commits().
+			Focus().
+			Lines(
+				Contains("CI ◯ add second line").IsSelected(),
+				Contains("CI ◯ add first line"),
+				Contains("CI ◯ add empty file"),
+			).
+			SelectNextItem().
+			Press(keys.Commits.RevertCommit).
+			Tap(func() {
+				t.ExpectPopup().Confirmation().
+					Title(Equals("Revert commit")).
+					Content(MatchesRegexp(`Are you sure you want to revert \w+?`)).
+					Confirm()
+				t.ExpectPopup().Menu().
+					Title(Equals("Conflicts!")).
+					Select(Contains("View conflicts")).
+					Confirm()
+			}).
+			Lines(
+				/* EXPECTED:
+				Proper display of revert commits is not implemented yet; we'll do this in the next PR
+				Contains("revert").Contains("CI <-- CONFLICT --- add first line"),
+				Contains("CI ◯ add second line"),
+				Contains("CI ◯ add first line"),
+				Contains("CI ◯ add empty file"),
+				ACTUAL: */
+				Contains("CI ◯ <-- YOU ARE HERE --- add second line"),
+				Contains("CI ◯ add first line"),
+				Contains("CI ◯ add empty file"),
+			)
+
+		t.Views().Options().Content(Contains("View revert options: m"))
+		t.Views().Information().Content(Contains("Reverting (Reset)"))
+
+		t.Views().Files().IsFocused().
+			Lines(
+				Contains("UU myfile").IsSelected(),
+			).
+			PressEnter()
+
+		t.Views().MergeConflicts().IsFocused().
+			SelectNextItem().
+			PressPrimaryAction()
+
+		t.ExpectPopup().Alert().
+			Title(Equals("Continue")).
+			Content(Contains("All merge conflicts resolved. Continue the revert?")).
+			Confirm()
+
+		t.Views().Commits().
+			Lines(
+				Contains(`CI ◯ Revert "add first line"`),
+				Contains("CI ◯ add second line"),
+				Contains("CI ◯ add first line"),
+				Contains("CI ◯ add empty file"),
+			)
+	},
+})
diff --git a/pkg/integration/tests/commit/shared.go b/pkg/integration/tests/commit/shared.go
index 072cc8154dd..b6861f13565 100644
--- a/pkg/integration/tests/commit/shared.go
+++ b/pkg/integration/tests/commit/shared.go
@@ -61,7 +61,7 @@ func doTheRebaseForAmendTests(t *TestDriver, keys config.KeybindingConfig) {
 
 	t.ExpectPopup().Confirmation().
 		Title(Equals("Continue")).
-		Content(Contains("All merge conflicts resolved. Continue?")).
+		Content(Contains("All merge conflicts resolved. Continue the rebase?")).
 		Cancel()
 }
 
diff --git a/pkg/integration/tests/conflicts/resolve_externally.go b/pkg/integration/tests/conflicts/resolve_externally.go
index a8d717351b2..ab045f23397 100644
--- a/pkg/integration/tests/conflicts/resolve_externally.go
+++ b/pkg/integration/tests/conflicts/resolve_externally.go
@@ -25,7 +25,7 @@ var ResolveExternally = NewIntegrationTest(NewIntegrationTestArgs{
 			}).
 			Press(keys.Universal.Refresh)
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("merge")
 
 		t.Views().Files().
 			IsEmpty()
diff --git a/pkg/integration/tests/conflicts/resolve_multiple_files.go b/pkg/integration/tests/conflicts/resolve_multiple_files.go
index 542a74d54b7..7dd88e02c50 100644
--- a/pkg/integration/tests/conflicts/resolve_multiple_files.go
+++ b/pkg/integration/tests/conflicts/resolve_multiple_files.go
@@ -51,6 +51,6 @@ var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{
 			).
 			PressPrimaryAction()
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("merge")
 	},
 })
diff --git a/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go b/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go
index 2af1eac5445..3deafb28852 100644
--- a/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go
+++ b/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go
@@ -43,7 +43,7 @@ var ResolveWithoutTrailingLf = NewIntegrationTest(NewIntegrationTestArgs{
 
 		t.ExpectPopup().Alert().
 			Title(Equals("Continue")).
-			Content(Contains("All merge conflicts resolved. Continue?")).
+			Content(Contains("All merge conflicts resolved. Continue the merge?")).
 			Cancel()
 
 		t.Views().Files().
diff --git a/pkg/integration/tests/file/discard_all_dir_changes.go b/pkg/integration/tests/file/discard_all_dir_changes.go
index e0381853462..6caa3d51993 100644
--- a/pkg/integration/tests/file/discard_all_dir_changes.go
+++ b/pkg/integration/tests/file/discard_all_dir_changes.go
@@ -93,7 +93,7 @@ var DiscardAllDirChanges = NewIntegrationTest(NewIntegrationTestArgs{
 					Confirm()
 			}).
 			Tap(func() {
-				t.Common().ContinueOnConflictsResolved()
+				t.Common().ContinueOnConflictsResolved("merge")
 				t.ExpectPopup().Confirmation().
 					Title(Equals("Continue")).
 					Content(Contains("Files have been modified since conflicts were resolved. Auto-stage them and continue?")).
diff --git a/pkg/integration/tests/file/discard_various_changes.go b/pkg/integration/tests/file/discard_various_changes.go
index 0cc188f6572..bc68fd218ec 100644
--- a/pkg/integration/tests/file/discard_various_changes.go
+++ b/pkg/integration/tests/file/discard_various_changes.go
@@ -53,7 +53,7 @@ var DiscardVariousChanges = NewIntegrationTest(NewIntegrationTestArgs{
 
 		t.ExpectPopup().Confirmation().
 			Title(Equals("Continue")).
-			Content(Contains("All merge conflicts resolved. Continue?")).
+			Content(Contains("All merge conflicts resolved. Continue the merge?")).
 			Cancel()
 
 		discardOneByOne([]statusFile{
diff --git a/pkg/integration/tests/file/discard_various_changes_range_select.go b/pkg/integration/tests/file/discard_various_changes_range_select.go
index 403ece7f74f..937c50114b8 100644
--- a/pkg/integration/tests/file/discard_various_changes_range_select.go
+++ b/pkg/integration/tests/file/discard_various_changes_range_select.go
@@ -40,7 +40,7 @@ var DiscardVariousChangesRangeSelect = NewIntegrationTest(NewIntegrationTestArgs
 
 				t.ExpectPopup().Confirmation().
 					Title(Equals("Continue")).
-					Content(Contains("All merge conflicts resolved. Continue?")).
+					Content(Contains("All merge conflicts resolved. Continue the merge?")).
 					Cancel()
 			}).
 			Lines(
diff --git a/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go b/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go
index 2226b519646..c20ab51e0f8 100644
--- a/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go
+++ b/pkg/integration/tests/interactive_rebase/amend_commit_with_conflict.go
@@ -60,7 +60,7 @@ var AmendCommitWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
 			SelectNextItem().
 			PressPrimaryAction() // pick "4"
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Common().AcknowledgeConflicts()
 
diff --git a/pkg/integration/tests/interactive_rebase/rebase_with_commit_that_becomes_empty.go b/pkg/integration/tests/interactive_rebase/rebase_with_commit_that_becomes_empty.go
new file mode 100644
index 00000000000..e9b743856d0
--- /dev/null
+++ b/pkg/integration/tests/interactive_rebase/rebase_with_commit_that_becomes_empty.go
@@ -0,0 +1,45 @@
+package interactive_rebase
+
+import (
+	"github.com/jesseduffield/lazygit/pkg/config"
+	. "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+var RebaseWithCommitThatBecomesEmpty = NewIntegrationTest(NewIntegrationTestArgs{
+	Description:  "Performs a rebase involving a commit that becomes empty during the rebase, and gets dropped.",
+	ExtraCmdArgs: []string{},
+	Skip:         false,
+	SetupConfig:  func(config *config.AppConfig) {},
+	SetupRepo: func(shell *Shell) {
+		shell.EmptyCommit("initial commit")
+		// It is important that we create two separate commits for the two
+		// changes to the file, but only one commit for the same changes on our
+		// branch; otherwise, the commit would be discarded at the start of the
+		// rebase already.
+		shell.CreateFileAndAdd("file", "change 1\n")
+		shell.Commit("master change 1")
+		shell.UpdateFileAndAdd("file", "change 1\nchange 2\n")
+		shell.Commit("master change 2")
+		shell.NewBranchFrom("branch", "HEAD^^")
+		shell.CreateFileAndAdd("file", "change 1\nchange 2\n")
+		shell.Commit("branch change")
+	},
+	Run: func(t *TestDriver, keys config.KeybindingConfig) {
+		t.Views().Branches().
+			Focus().
+			NavigateToLine(Contains("master")).
+			Press(keys.Branches.RebaseBranch)
+
+		t.ExpectPopup().Menu().
+			Title(Equals("Rebase 'branch'")).
+			Select(Contains("Simple rebase")).
+			Confirm()
+
+		t.Views().Commits().
+			Lines(
+				Contains("master change 2"),
+				Contains("master change 1"),
+				Contains("initial commit"),
+			)
+	},
+})
diff --git a/pkg/integration/tests/interactive_rebase/shared.go b/pkg/integration/tests/interactive_rebase/shared.go
index 2e42c7f76f3..f34cbd7e22c 100644
--- a/pkg/integration/tests/interactive_rebase/shared.go
+++ b/pkg/integration/tests/interactive_rebase/shared.go
@@ -33,7 +33,7 @@ func handleConflictsFromSwap(t *TestDriver) {
 		SelectNextItem().
 		PressPrimaryAction() // pick "three"
 
-	t.Common().ContinueOnConflictsResolved()
+	t.Common().ContinueOnConflictsResolved("rebase")
 
 	t.Common().AcknowledgeConflicts()
 
@@ -56,7 +56,7 @@ func handleConflictsFromSwap(t *TestDriver) {
 		SelectNextItem().
 		PressPrimaryAction() // pick "two"
 
-	t.Common().ContinueOnConflictsResolved()
+	t.Common().ContinueOnConflictsResolved("rebase")
 
 	t.Views().Commits().
 		Focus().
diff --git a/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go b/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go
index 122be38842e..a619128faca 100644
--- a/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go
+++ b/pkg/integration/tests/patch_building/move_to_earlier_commit_from_added_file.go
@@ -71,7 +71,7 @@ var MoveToEarlierCommitFromAddedFile = NewIntegrationTest(NewIntegrationTestArgs
 			SelectNextItem().
 			PressPrimaryAction() // choose the version with all three lines
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Commits().
 			Focus().
diff --git a/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go b/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go
index c7acfcd5021..177e76e04eb 100644
--- a/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go
+++ b/pkg/integration/tests/patch_building/move_to_index_from_added_file_with_conflict.go
@@ -67,7 +67,7 @@ var MoveToIndexFromAddedFileWithConflict = NewIntegrationTest(NewIntegrationTest
 			SelectNextItem().
 			PressPrimaryAction()
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.ExpectPopup().Alert().
 			Title(Equals("Error")).
diff --git a/pkg/integration/tests/patch_building/move_to_index_with_conflict.go b/pkg/integration/tests/patch_building/move_to_index_with_conflict.go
index 51b6f4766f0..bdf0765d9ef 100644
--- a/pkg/integration/tests/patch_building/move_to_index_with_conflict.go
+++ b/pkg/integration/tests/patch_building/move_to_index_with_conflict.go
@@ -62,7 +62,7 @@ var MoveToIndexWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
 			).
 			PressPrimaryAction()
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.ExpectPopup().Alert().
 			Title(Equals("Error")).
diff --git a/pkg/integration/tests/sync/pull_merge_conflict.go b/pkg/integration/tests/sync/pull_merge_conflict.go
index 2161f6abd97..45ca39b1fbc 100644
--- a/pkg/integration/tests/sync/pull_merge_conflict.go
+++ b/pkg/integration/tests/sync/pull_merge_conflict.go
@@ -60,7 +60,7 @@ var PullMergeConflict = NewIntegrationTest(NewIntegrationTestArgs{
 			).
 			PressPrimaryAction() // choose 'content4'
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("merge")
 
 		t.Views().Status().Content(Equals("↑2 repo → master"))
 
diff --git a/pkg/integration/tests/sync/pull_rebase_conflict.go b/pkg/integration/tests/sync/pull_rebase_conflict.go
index d9541e0edfa..1d41b2ac351 100644
--- a/pkg/integration/tests/sync/pull_rebase_conflict.go
+++ b/pkg/integration/tests/sync/pull_rebase_conflict.go
@@ -61,7 +61,7 @@ var PullRebaseConflict = NewIntegrationTest(NewIntegrationTestArgs{
 			SelectNextItem().
 			PressPrimaryAction() // choose 'content4'
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Status().Content(Equals("↑1 repo → master"))
 
diff --git a/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go b/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go
index bf0fc050b92..eb767a1a340 100644
--- a/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go
+++ b/pkg/integration/tests/sync/pull_rebase_interactive_conflict.go
@@ -74,7 +74,7 @@ var PullRebaseInteractiveConflict = NewIntegrationTest(NewIntegrationTestArgs{
 			SelectNextItem().
 			PressPrimaryAction() // choose 'content4'
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Status().Content(Equals("↑2 repo → master"))
 
diff --git a/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go b/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go
index 3eee12efda0..4aff8b4e0df 100644
--- a/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go
+++ b/pkg/integration/tests/sync/pull_rebase_interactive_conflict_drop.go
@@ -83,7 +83,7 @@ var PullRebaseInteractiveConflictDrop = NewIntegrationTest(NewIntegrationTestArg
 			SelectNextItem().
 			PressPrimaryAction() // choose 'content4'
 
-		t.Common().ContinueOnConflictsResolved()
+		t.Common().ContinueOnConflictsResolved("rebase")
 
 		t.Views().Status().Content(Equals("↑1 repo → master"))
 
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index cbcf4ab5a94..2c1e2d1697c 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -128,6 +128,7 @@ var tests = []*components.IntegrationTest{
 	commit.ResetAuthorRange,
 	commit.Revert,
 	commit.RevertMerge,
+	commit.RevertWithConflictSingleCommit,
 	commit.Reword,
 	commit.Search,
 	commit.SetAuthor,
@@ -260,6 +261,7 @@ var tests = []*components.IntegrationTest{
 	interactive_rebase.QuickStartKeepSelection,
 	interactive_rebase.QuickStartKeepSelectionRange,
 	interactive_rebase.Rebase,
+	interactive_rebase.RebaseWithCommitThatBecomesEmpty,
 	interactive_rebase.RewordCommitWithEditorAndFail,
 	interactive_rebase.RewordFirstCommit,
 	interactive_rebase.RewordLastCommit,