Skip to content

Commit 3dc045b

Browse files
authored
Continue/abort a conflicted cherry-pick or revert (#4441)
- **PR Description** This is part one of a four part series of PRs that improve the cherry-pick and revert experience. With this first PR we make it possible to continue or abort a cherry-pick or revert operation, in the same way you can continue or abort a rebase or merge. Currently this is only relevant for revert, because lazygit doesn't use git cherry-pick for copying/pasting commits. This will change in a later PR in this series though, so here we are already preparing for that. Fixes #1807
2 parents 89ba3f2 + 90db796 commit 3dc045b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+421
-255
lines changed

pkg/commands/git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func NewGitCommandAux(
136136

137137
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
138138
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
139-
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
139+
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.WorkingTreeState, gitCommon)
140140
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
141141
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
142142
worktreeLoader := git_commands.NewWorktreeLoader(gitCommon)

pkg/commands/git_commands/commit_loader.go

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
"github.com/jesseduffield/lazygit/pkg/commands/models"
1515
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
16-
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
1716
"github.com/jesseduffield/lazygit/pkg/common"
1817
"github.com/jesseduffield/lazygit/pkg/utils"
1918
"github.com/samber/lo"
@@ -31,27 +30,27 @@ type CommitLoader struct {
3130
*common.Common
3231
cmd oscommands.ICmdObjBuilder
3332

34-
getRebaseMode func() (enums.RebaseMode, error)
35-
readFile func(filename string) ([]byte, error)
36-
walkFiles func(root string, fn filepath.WalkFunc) error
37-
dotGitDir string
33+
getWorkingTreeState func() models.WorkingTreeState
34+
readFile func(filename string) ([]byte, error)
35+
walkFiles func(root string, fn filepath.WalkFunc) error
36+
dotGitDir string
3837
*GitCommon
3938
}
4039

4140
// making our dependencies explicit for the sake of easier testing
4241
func NewCommitLoader(
4342
cmn *common.Common,
4443
cmd oscommands.ICmdObjBuilder,
45-
getRebaseMode func() (enums.RebaseMode, error),
44+
getWorkingTreeState func() models.WorkingTreeState,
4645
gitCommon *GitCommon,
4746
) *CommitLoader {
4847
return &CommitLoader{
49-
Common: cmn,
50-
cmd: cmd,
51-
getRebaseMode: getRebaseMode,
52-
readFile: os.ReadFile,
53-
walkFiles: filepath.Walk,
54-
GitCommon: gitCommon,
48+
Common: cmn,
49+
cmd: cmd,
50+
getWorkingTreeState: getWorkingTreeState,
51+
readFile: os.ReadFile,
52+
walkFiles: filepath.Walk,
53+
GitCommon: gitCommon,
5554
}
5655
}
5756

@@ -172,17 +171,12 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
172171
}
173172
}
174173

175-
rebaseMode, err := self.getRebaseMode()
176-
if err != nil {
177-
return nil, err
178-
}
179-
180-
if rebaseMode == enums.REBASE_MODE_NONE {
174+
if !self.getWorkingTreeState().Rebasing {
181175
// not in rebase mode so return original commits
182176
return result, nil
183177
}
184178

185-
rebasingCommits, err := self.getHydratedRebasingCommits(rebaseMode)
179+
rebasingCommits, err := self.getHydratedRebasingCommits()
186180
if err != nil {
187181
return nil, err
188182
}
@@ -248,8 +242,8 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
248242
}
249243
}
250244

251-
func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode) ([]*models.Commit, error) {
252-
commits := self.getRebasingCommits(rebaseMode)
245+
func (self *CommitLoader) getHydratedRebasingCommits() ([]*models.Commit, error) {
246+
commits := self.getRebasingCommits()
253247

254248
if len(commits) == 0 {
255249
return nil, nil
@@ -310,11 +304,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
310304
// git-rebase-todo example:
311305
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae
312306
// pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931
313-
func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) []*models.Commit {
314-
if rebaseMode != enums.REBASE_MODE_INTERACTIVE {
315-
return nil
316-
}
317-
307+
func (self *CommitLoader) getRebasingCommits() []*models.Commit {
318308
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"))
319309
if err != nil {
320310
self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))

pkg/commands/git_commands/commit_loader_test.go

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/go-errors/errors"
99
"github.com/jesseduffield/lazygit/pkg/commands/models"
1010
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
11-
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
1211
"github.com/jesseduffield/lazygit/pkg/config"
1312
"github.com/jesseduffield/lazygit/pkg/utils"
1413
"github.com/stefanhaller/git-todo-parser/todo"
@@ -33,17 +32,15 @@ func TestGetCommits(t *testing.T) {
3332
expectedCommits []*models.Commit
3433
expectedError error
3534
logOrder string
36-
rebaseMode enums.RebaseMode
3735
opts GetCommitsOptions
3836
mainBranches []string
3937
}
4038

4139
scenarios := []scenario{
4240
{
43-
testName: "should return no commits if there are none",
44-
logOrder: "topo-order",
45-
rebaseMode: enums.REBASE_MODE_NONE,
46-
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
41+
testName: "should return no commits if there are none",
42+
logOrder: "topo-order",
43+
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
4744
runner: oscommands.NewFakeRunner(t).
4845
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
4946
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) {
5249
expectedError: nil,
5350
},
5451
{
55-
testName: "should use proper upstream name for branch",
56-
logOrder: "topo-order",
57-
rebaseMode: enums.REBASE_MODE_NONE,
58-
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
52+
testName: "should use proper upstream name for branch",
53+
logOrder: "topo-order",
54+
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
5955
runner: oscommands.NewFakeRunner(t).
6056
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
6157
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) {
6662
{
6763
testName: "should return commits if they are present",
6864
logOrder: "topo-order",
69-
rebaseMode: enums.REBASE_MODE_NONE,
7065
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
7166
mainBranches: []string{"master", "main", "develop"},
7267
runner: oscommands.NewFakeRunner(t).
@@ -203,7 +198,6 @@ func TestGetCommits(t *testing.T) {
203198
{
204199
testName: "should not call merge-base for mainBranches if none exist",
205200
logOrder: "topo-order",
206-
rebaseMode: enums.REBASE_MODE_NONE,
207201
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
208202
mainBranches: []string{"master", "main"},
209203
runner: oscommands.NewFakeRunner(t).
@@ -240,7 +234,6 @@ func TestGetCommits(t *testing.T) {
240234
{
241235
testName: "should call merge-base for all main branches that exist",
242236
logOrder: "topo-order",
243-
rebaseMode: enums.REBASE_MODE_NONE,
244237
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
245238
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
246239
runner: oscommands.NewFakeRunner(t).
@@ -277,10 +270,9 @@ func TestGetCommits(t *testing.T) {
277270
expectedError: nil,
278271
},
279272
{
280-
testName: "should not specify order if `log.order` is `default`",
281-
logOrder: "default",
282-
rebaseMode: enums.REBASE_MODE_NONE,
283-
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
273+
testName: "should not specify order if `log.order` is `default`",
274+
logOrder: "default",
275+
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
284276
runner: oscommands.NewFakeRunner(t).
285277
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
286278
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) {
289281
expectedError: nil,
290282
},
291283
{
292-
testName: "should set filter path",
293-
logOrder: "default",
294-
rebaseMode: enums.REBASE_MODE_NONE,
295-
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
284+
testName: "should set filter path",
285+
logOrder: "default",
286+
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
296287
runner: oscommands.NewFakeRunner(t).
297288
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
298289
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) {
310301
cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner)
311302

312303
builder := &CommitLoader{
313-
Common: common,
314-
cmd: cmd,
315-
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
316-
dotGitDir: ".git",
304+
Common: common,
305+
cmd: cmd,
306+
getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{} },
307+
dotGitDir: ".git",
317308
readFile: func(filename string) ([]byte, error) {
318309
return []byte(""), nil
319310
},
@@ -493,10 +484,10 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
493484
common := utils.NewDummyCommon()
494485

495486
builder := &CommitLoader{
496-
Common: common,
497-
cmd: oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
498-
getRebaseMode: func() (enums.RebaseMode, error) { return enums.REBASE_MODE_INTERACTIVE, nil },
499-
dotGitDir: ".git",
487+
Common: common,
488+
cmd: oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)),
489+
getWorkingTreeState: func() models.WorkingTreeState { return models.WorkingTreeState{Rebasing: true} },
490+
dotGitDir: ".git",
500491
readFile: func(filename string) ([]byte, error) {
501492
return []byte(""), nil
502493
},

pkg/commands/git_commands/patch.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/jesseduffield/lazygit/pkg/app/daemon"
99
"github.com/jesseduffield/lazygit/pkg/commands/models"
1010
"github.com/jesseduffield/lazygit/pkg/commands/patch"
11-
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
1211
"github.com/stefanhaller/git-todo-parser/todo"
1312
)
1413

@@ -229,7 +228,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
229228
}
230229

231230
if err := self.ApplyCustomPatch(true, true); err != nil {
232-
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
231+
if self.status.WorkingTreeState().Rebasing {
233232
_ = self.rebase.AbortRebase()
234233
}
235234
return err
@@ -253,7 +252,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
253252
self.rebase.onSuccessfulContinue = func() error {
254253
// add patches to index
255254
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
256-
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
255+
if self.status.WorkingTreeState().Rebasing {
257256
_ = self.rebase.AbortRebase()
258257
}
259258
return err

pkg/commands/git_commands/status.go

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"path/filepath"
66
"strings"
77

8-
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
8+
"github.com/jesseduffield/lazygit/pkg/commands/models"
99
)
1010

1111
type StatusCommands struct {
@@ -20,50 +20,68 @@ func NewStatusCommands(
2020
}
2121
}
2222

23-
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
24-
// and "interactive" for interactive rebase
25-
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
26-
ok, err := self.IsInNormalRebase()
27-
if err == nil && ok {
28-
return enums.REBASE_MODE_NORMAL, nil
29-
}
30-
ok, err = self.IsInInteractiveRebase()
31-
if err == nil && ok {
32-
return enums.REBASE_MODE_INTERACTIVE, err
33-
}
34-
35-
return enums.REBASE_MODE_NONE, err
36-
}
37-
38-
func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
39-
rebaseMode, _ := self.RebaseMode()
40-
if rebaseMode != enums.REBASE_MODE_NONE {
41-
return enums.REBASE_MODE_REBASING
42-
}
43-
merging, _ := self.IsInMergeState()
44-
if merging {
45-
return enums.REBASE_MODE_MERGING
46-
}
47-
return enums.REBASE_MODE_NONE
23+
func (self *StatusCommands) WorkingTreeState() models.WorkingTreeState {
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
4830
}
4931

5032
func (self *StatusCommands) IsBareRepo() bool {
5133
return self.repoPaths.isBareRepo
5234
}
5335

54-
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
36+
func (self *StatusCommands) IsInRebase() (bool, error) {
37+
exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
38+
if err == nil && exists {
39+
return true, nil
40+
}
5541
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
5642
}
5743

58-
func (self *StatusCommands) IsInInteractiveRebase() (bool, error) {
59-
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
60-
}
61-
6244
// IsInMergeState states whether we are still mid-merge
6345
func (self *StatusCommands) IsInMergeState() (bool, error) {
6446
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
6547
}
6648

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+
6785
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
6886
// being rebased, or empty string when we're not in a rebase
6987
func (self *StatusCommands) BranchBeingRebased() string {

0 commit comments

Comments
 (0)