Skip to content

Commit 5425257

Browse files
committed
Make WorkingTreeState a struct, and add cherry-picking and reverting states
1 parent 8af8f77 commit 5425257

17 files changed

+172
-91
lines changed

pkg/commands/git_commands/commit_loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
171171
}
172172
}
173173

174-
if !self.getWorkingTreeState().IsRebasing() {
174+
if !self.getWorkingTreeState().Rebasing {
175175
// not in rebase mode so return original commits
176176
return result, nil
177177
}

pkg/commands/git_commands/commit_loader_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func TestGetCommits(t *testing.T) {
303303
builder := &CommitLoader{
304304
Common: common,
305305
cmd: cmd,
306-
getWorkingTreeState: func() models.WorkingTreeState { return models.WORKING_TREE_STATE_NONE },
306+
getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{} },
307307
dotGitDir: ".git",
308308
readFile: func(filename string) ([]byte, error) {
309309
return []byte(""), nil
@@ -486,7 +486,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
486486
builder := &CommitLoader{
487487
Common: common,
488488
cmd: oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
489-
getWorkingTreeState: func() models.WorkingTreeState { return models.WORKING_TREE_STATE_REBASING },
489+
getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{Rebasing: true} },
490490
dotGitDir: ".git",
491491
readFile: func(filename string) ([]byte, error) {
492492
return []byte(""), nil

pkg/commands/git_commands/patch.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
228228
}
229229

230230
if err := self.ApplyCustomPatch(true, true); err != nil {
231-
if self.status.WorkingTreeState() == models.WORKING_TREE_STATE_REBASING {
231+
if self.status.WorkingTreeState().Rebasing {
232232
_ = self.rebase.AbortRebase()
233233
}
234234
return err
@@ -252,7 +252,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
252252
self.rebase.onSuccessfulContinue = func() error {
253253
// add patches to index
254254
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
255-
if self.status.WorkingTreeState() == models.WORKING_TREE_STATE_REBASING {
255+
if self.status.WorkingTreeState().Rebasing {
256256
_ = self.rebase.AbortRebase()
257257
}
258258
return err

pkg/commands/git_commands/status.go

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@ func NewStatusCommands(
2121
}
2222

2323
func (self *StatusCommands) WorkingTreeState() models.WorkingTreeState {
24-
isInRebase, _ := self.IsInRebase()
25-
if isInRebase {
26-
return models.WORKING_TREE_STATE_REBASING
27-
}
28-
merging, _ := self.IsInMergeState()
29-
if merging {
30-
return models.WORKING_TREE_STATE_MERGING
31-
}
32-
return models.WORKING_TREE_STATE_NONE
24+
result := models.WorkingTreeState{}
25+
result.Rebasing, _ = self.IsInRebase()
26+
result.Merging, _ = self.IsInMergeState()
27+
result.CherryPicking, _ = self.IsInCherryPick()
28+
result.Reverting, _ = self.IsInRevert()
29+
return result
3330
}
3431

3532
func (self *StatusCommands) IsBareRepo() bool {
@@ -49,6 +46,42 @@ func (self *StatusCommands) IsInMergeState() (bool, error) {
4946
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
5047
}
5148

49+
func (self *StatusCommands) IsInCherryPick() (bool, error) {
50+
exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "CHERRY_PICK_HEAD"))
51+
if err != nil || !exists {
52+
return exists, err
53+
}
54+
// Sometimes, CHERRY_PICK_HEAD is present during rebases even if no
55+
// cherry-pick is in progress. I suppose this is because rebase used to be
56+
// implemented as a series of cherry-picks, so this could be remnants of
57+
// code that is shared between cherry-pick and rebase, or something. The way
58+
// to tell if this is the case is to check for the presence of the
59+
// stopped-sha file, which records the sha of the last pick that was
60+
// executed before the rebase stopped, and seeing if the sha in that file is
61+
// the same as the one in CHERRY_PICK_HEAD.
62+
cherryPickHead, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "CHERRY_PICK_HEAD"))
63+
if err != nil {
64+
return false, err
65+
}
66+
stoppedSha, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge", "stopped-sha"))
67+
if err != nil {
68+
// If we get an error we assume the file doesn't exist
69+
return true, nil
70+
}
71+
cherryPickHeadStr := strings.TrimSpace(string(cherryPickHead))
72+
stoppedShaStr := strings.TrimSpace(string(stoppedSha))
73+
// Need to use HasPrefix here because the cherry-pick HEAD is a full sha1,
74+
// but stopped-sha is an abbreviated sha1
75+
if strings.HasPrefix(cherryPickHeadStr, stoppedShaStr) {
76+
return false, nil
77+
}
78+
return true, nil
79+
}
80+
81+
func (self *StatusCommands) IsInRevert() (bool, error) {
82+
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "REVERT_HEAD"))
83+
}
84+
5285
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
5386
// being rebased, or empty string when we're not in a rebase
5487
func (self *StatusCommands) BranchBeingRebased() string {

pkg/commands/models/working_tree_state.go

Lines changed: 89 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,111 @@ package models
22

33
import "github.com/jesseduffield/lazygit/pkg/i18n"
44

5-
type WorkingTreeState int
5+
// The state of the working tree. Several of these can be true at once.
6+
// In particular, the concrete multi-state combinations that can occur in
7+
// practice are Rebasing+CherryPicking, and Rebasing+Reverting. Theoretically, I
8+
// guess Rebasing+Merging could also happen, but it probably won't in practice.
9+
type WorkingTreeState struct {
10+
Rebasing bool
11+
Merging bool
12+
CherryPicking bool
13+
Reverting bool
14+
}
15+
16+
func (self WorkingTreeState) Any() bool {
17+
return self.Rebasing || self.Merging || self.CherryPicking || self.Reverting
18+
}
19+
20+
func (self WorkingTreeState) None() bool {
21+
return !self.Any()
22+
}
23+
24+
type EffectiveWorkingTreeState int
625

726
const (
8-
// this means we're neither rebasing nor merging
9-
WORKING_TREE_STATE_NONE WorkingTreeState = iota
27+
// this means we're neither rebasing nor merging, cherry-picking, or reverting
28+
WORKING_TREE_STATE_NONE EffectiveWorkingTreeState = iota
1029
WORKING_TREE_STATE_REBASING
1130
WORKING_TREE_STATE_MERGING
31+
WORKING_TREE_STATE_CHERRY_PICKING
32+
WORKING_TREE_STATE_REVERTING
1233
)
1334

14-
func (self WorkingTreeState) IsMerging() bool {
15-
return self == WORKING_TREE_STATE_MERGING
16-
}
17-
18-
func (self WorkingTreeState) IsRebasing() bool {
19-
return self == WORKING_TREE_STATE_REBASING
35+
// Effective returns the "current" state; if several states are true at once,
36+
// this is the one that should be displayed in status views, and it's the one
37+
// that the user can continue or abort.
38+
//
39+
// As an example, if you are stopped in an interactive rebase, and then you
40+
// perform a cherry-pick, and the cherry-pick conflicts, then both
41+
// WorkingTreeState.Rebasing and WorkingTreeState.CherryPicking are true.
42+
// The effective state is cherry-picking, because that's the one you can
43+
// continue or abort. It is not possible to continue the rebase without first
44+
// aborting the cherry-pick.
45+
func (self WorkingTreeState) Effective() EffectiveWorkingTreeState {
46+
if self.Reverting {
47+
return WORKING_TREE_STATE_REVERTING
48+
}
49+
if self.CherryPicking {
50+
return WORKING_TREE_STATE_CHERRY_PICKING
51+
}
52+
if self.Merging {
53+
return WORKING_TREE_STATE_MERGING
54+
}
55+
if self.Rebasing {
56+
return WORKING_TREE_STATE_REBASING
57+
}
58+
return WORKING_TREE_STATE_NONE
2059
}
2160

2261
func (self WorkingTreeState) Title(tr *i18n.TranslationSet) string {
23-
switch self {
24-
case WORKING_TREE_STATE_REBASING:
25-
return tr.RebasingStatus
26-
case WORKING_TREE_STATE_MERGING:
27-
return tr.MergingStatus
28-
default:
29-
// should never actually display this
30-
return "none"
31-
}
62+
return map[EffectiveWorkingTreeState]string{
63+
WORKING_TREE_STATE_REBASING: tr.RebasingStatus,
64+
WORKING_TREE_STATE_MERGING: tr.MergingStatus,
65+
WORKING_TREE_STATE_CHERRY_PICKING: tr.CherryPickingStatus,
66+
WORKING_TREE_STATE_REVERTING: tr.RevertingStatus,
67+
}[self.Effective()]
3268
}
3369

3470
func (self WorkingTreeState) LowerCaseTitle(tr *i18n.TranslationSet) string {
35-
switch self {
36-
case WORKING_TREE_STATE_REBASING:
37-
return tr.LowercaseRebasingStatus
38-
case WORKING_TREE_STATE_MERGING:
39-
return tr.LowercaseMergingStatus
40-
default:
41-
// should never actually display this
42-
return "none"
43-
}
71+
return map[EffectiveWorkingTreeState]string{
72+
WORKING_TREE_STATE_REBASING: tr.LowercaseRebasingStatus,
73+
WORKING_TREE_STATE_MERGING: tr.LowercaseMergingStatus,
74+
WORKING_TREE_STATE_CHERRY_PICKING: tr.LowercaseCherryPickingStatus,
75+
WORKING_TREE_STATE_REVERTING: tr.LowercaseRevertingStatus,
76+
}[self.Effective()]
4477
}
4578

4679
func (self WorkingTreeState) OptionsMenuTitle(tr *i18n.TranslationSet) string {
47-
if self == WORKING_TREE_STATE_MERGING {
48-
return tr.MergeOptionsTitle
49-
}
50-
return tr.RebaseOptionsTitle
80+
return map[EffectiveWorkingTreeState]string{
81+
WORKING_TREE_STATE_REBASING: tr.RebaseOptionsTitle,
82+
WORKING_TREE_STATE_MERGING: tr.MergeOptionsTitle,
83+
WORKING_TREE_STATE_CHERRY_PICKING: tr.CherryPickOptionsTitle,
84+
WORKING_TREE_STATE_REVERTING: tr.RevertOptionsTitle,
85+
}[self.Effective()]
86+
}
87+
88+
func (self WorkingTreeState) OptionsMapTitle(tr *i18n.TranslationSet) string {
89+
return map[EffectiveWorkingTreeState]string{
90+
WORKING_TREE_STATE_REBASING: tr.ViewRebaseOptions,
91+
WORKING_TREE_STATE_MERGING: tr.ViewMergeOptions,
92+
WORKING_TREE_STATE_CHERRY_PICKING: tr.ViewCherryPickOptions,
93+
WORKING_TREE_STATE_REVERTING: tr.ViewRevertOptions,
94+
}[self.Effective()]
5195
}
5296

5397
func (self WorkingTreeState) CommandName() string {
54-
switch self {
55-
case WORKING_TREE_STATE_MERGING:
56-
return "merge"
57-
case WORKING_TREE_STATE_REBASING:
58-
return "rebase"
59-
default:
60-
// shouldn't be possible to land here
61-
return ""
62-
}
98+
return map[EffectiveWorkingTreeState]string{
99+
WORKING_TREE_STATE_REBASING: "rebase",
100+
WORKING_TREE_STATE_MERGING: "merge",
101+
WORKING_TREE_STATE_CHERRY_PICKING: "cherry-pick",
102+
WORKING_TREE_STATE_REVERTING: "revert",
103+
}[self.Effective()]
104+
}
105+
106+
func (self WorkingTreeState) CanShowTodos() bool {
107+
return self.Rebasing || self.CherryPicking || self.Reverting
108+
}
109+
110+
func (self WorkingTreeState) CanSkip() bool {
111+
return self.Rebasing || self.CherryPicking || self.Reverting
63112
}

pkg/gui/context/local_commits_context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
4040
}
4141
}
4242

43-
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == models.WORKING_TREE_STATE_REBASING
43+
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh.CanShowTodos()
4444
hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs()
4545

4646
return presentation.GetCommitListDisplayStrings(

pkg/gui/controllers/custom_patch_options_menu_action.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66

77
"github.com/jesseduffield/gocui"
8-
"github.com/jesseduffield/lazygit/pkg/commands/models"
98
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
109
"github.com/jesseduffield/lazygit/pkg/gui/types"
1110
)
@@ -44,7 +43,7 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
4443
},
4544
}
4645

47-
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState() == models.WORKING_TREE_STATE_NONE {
46+
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState().None() {
4847
menuItems = append(menuItems, []*types.MenuItem{
4948
{
5049
Label: fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, self.c.Git().Patch.PatchBuilder.To),
@@ -115,7 +114,7 @@ func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
115114
}
116115

117116
func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) {
118-
if self.c.Git().Status.WorkingTreeState() != models.WORKING_TREE_STATE_NONE {
117+
if self.c.Git().Status.WorkingTreeState().Any() {
119118
return false, errors.New(self.c.Tr.CantPatchWhileRebasingError)
120119
}
121120
return true, nil

pkg/gui/controllers/helpers/merge_and_rebase_helper.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
5050
{option: REBASE_OPTION_ABORT, key: 'a'},
5151
}
5252

53-
if self.c.Git().Status.WorkingTreeState() == models.WORKING_TREE_STATE_REBASING {
53+
if self.c.Git().Status.WorkingTreeState().CanSkip() {
5454
options = append(options, optionAndKey{
5555
option: REBASE_OPTION_SKIP, key: 's',
5656
})
@@ -77,12 +77,13 @@ func (self *MergeAndRebaseHelper) ContinueRebase() error {
7777
func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
7878
status := self.c.Git().Status.WorkingTreeState()
7979

80-
if status != models.WORKING_TREE_STATE_MERGING && status != models.WORKING_TREE_STATE_REBASING {
80+
if status.None() {
8181
return errors.New(self.c.Tr.NotMergingOrRebasing)
8282
}
8383

8484
self.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
85-
if status == models.WORKING_TREE_STATE_REBASING {
85+
effectiveStatus := status.Effective()
86+
if effectiveStatus == models.WORKING_TREE_STATE_REBASING {
8687
todoFile, err := os.ReadFile(
8788
filepath.Join(self.c.Git().RepoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"),
8889
)
@@ -101,10 +102,10 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
101102
// we should end up with a command like 'git merge --continue'
102103

103104
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
104-
needsSubprocess := (status == models.WORKING_TREE_STATE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig().Git.Merging.ManualCommit) ||
105+
needsSubprocess := (effectiveStatus == models.WORKING_TREE_STATE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig().Git.Merging.ManualCommit) ||
105106
// but we'll also use a subprocess if we have exec todos; those are likely to be lengthy build
106107
// tasks whose output the user will want to see in the terminal
107-
(status == models.WORKING_TREE_STATE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos())
108+
(effectiveStatus == models.WORKING_TREE_STATE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos())
108109

109110
if needsSubprocess {
110111
// TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction

pkg/gui/controllers/helpers/mode_helper.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"strings"
66

7-
"github.com/jesseduffield/lazygit/pkg/commands/models"
87
"github.com/jesseduffield/lazygit/pkg/gui/style"
98
"github.com/jesseduffield/lazygit/pkg/gui/types"
109
"github.com/samber/lo"
@@ -115,7 +114,7 @@ func (self *ModeHelper) Statuses() []ModeStatus {
115114
},
116115
{
117116
IsActive: func() bool {
118-
return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState() != models.WORKING_TREE_STATE_NONE
117+
return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState().Any()
119118
},
120119
Description: func() string {
121120
workingTreeState := self.c.Git().Status.WorkingTreeState()

pkg/gui/controllers/helpers/patch_building_helper.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package helpers
33
import (
44
"errors"
55

6-
"github.com/jesseduffield/lazygit/pkg/commands/models"
76
"github.com/jesseduffield/lazygit/pkg/commands/patch"
87
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
98
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -22,7 +21,7 @@ func NewPatchBuildingHelper(
2221
}
2322

2423
func (self *PatchBuildingHelper) ValidateNormalWorkingTreeState() (bool, error) {
25-
if self.c.Git().Status.WorkingTreeState() != models.WORKING_TREE_STATE_NONE {
24+
if self.c.Git().Status.WorkingTreeState().Any() {
2625
return false, errors.New(self.c.Tr.CantPatchWhileRebasingError)
2726
}
2827
return true, nil

0 commit comments

Comments
 (0)