1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-02 23:27:32 +02:00

updating specs

This commit is contained in:
Jesse Duffield 2021-12-30 13:11:58 +11:00
parent 1fc0d786ae
commit b028f37ba8
15 changed files with 416 additions and 241 deletions

View File

@ -55,43 +55,6 @@ func NewCommitListBuilder(
} }
} }
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
// then puts them into a commit object
// example input:
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
func (self *CommitListBuilder) extractCommitFromLine(line string) *models.Commit {
split := strings.Split(line, SEPARATION_CHAR)
sha := split[0]
unixTimestamp := split[1]
author := split[2]
extraInfo := strings.TrimSpace(split[3])
parentHashes := split[4]
message := strings.Join(split[5:], SEPARATION_CHAR)
tags := []string{}
if extraInfo != "" {
re := regexp.MustCompile(`tag: ([^,\)]+)`)
tagMatch := re.FindStringSubmatch(extraInfo)
if len(tagMatch) > 1 {
tags = append(tags, tagMatch[1])
}
}
unitTimestampInt, _ := strconv.Atoi(unixTimestamp)
return &models.Commit{
Sha: sha,
Name: message,
Tags: tags,
ExtraInfo: extraInfo,
UnixTimestamp: int64(unitTimestampInt),
Author: author,
Parents: strings.Split(parentHashes, " "),
}
}
type GetCommitsOptions struct { type GetCommitsOptions struct {
Limit bool Limit bool
FilterPath string FilterPath string
@ -101,37 +64,6 @@ type GetCommitsOptions struct {
All bool All bool
} }
func (self *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*models.Commit, error) {
// chances are we have as many commits as last time so we'll set the capacity to be the old length
result := make([]*models.Commit, 0, len(commits))
for i, commit := range commits {
if commit.Status != "rebasing" { // removing the existing rebase commits so we can add the refreshed ones
result = append(result, commits[i:]...)
break
}
}
rebaseMode, err := self.getRebaseMode()
if err != nil {
return nil, err
}
if rebaseMode == "" {
// not in rebase mode so return original commits
return result, nil
}
rebasingCommits, err := self.getHydratedRebasingCommits(rebaseMode)
if err != nil {
return nil, err
}
if len(rebasingCommits) > 0 {
result = append(rebasingCommits, result...)
}
return result, nil
}
// GetCommits obtains the commits of the current branch // GetCommits obtains the commits of the current branch
func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) { func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) {
commits := []*models.Commit{} commits := []*models.Commit{}
@ -172,7 +104,11 @@ func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Com
return nil, err return nil, err
} }
if rebaseMode != "" { if len(commits) == 0 {
return commits, nil
}
if rebaseMode != REBASE_MODE_NONE {
currentCommit := commits[len(rebasingCommits)] currentCommit := commits[len(rebasingCommits)]
youAreHere := style.FgYellow.Sprintf("<-- %s ---", self.Tr.YouAreHere) youAreHere := style.FgYellow.Sprintf("<-- %s ---", self.Tr.YouAreHere)
currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name) currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
@ -186,6 +122,74 @@ func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Com
return commits, nil return commits, nil
} }
func (self *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*models.Commit, error) {
// chances are we have as many commits as last time so we'll set the capacity to be the old length
result := make([]*models.Commit, 0, len(commits))
for i, commit := range commits {
if commit.Status != "rebasing" { // removing the existing rebase commits so we can add the refreshed ones
result = append(result, commits[i:]...)
break
}
}
rebaseMode, err := self.getRebaseMode()
if err != nil {
return nil, err
}
if rebaseMode == REBASE_MODE_NONE {
// not in rebase mode so return original commits
return result, nil
}
rebasingCommits, err := self.getHydratedRebasingCommits(rebaseMode)
if err != nil {
return nil, err
}
if len(rebasingCommits) > 0 {
result = append(rebasingCommits, result...)
}
return result, nil
}
// extractCommitFromLine takes a line from a git log and extracts the sha, message, date, and tag if present
// then puts them into a commit object
// example input:
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
func (self *CommitListBuilder) extractCommitFromLine(line string) *models.Commit {
split := strings.Split(line, SEPARATION_CHAR)
sha := split[0]
unixTimestamp := split[1]
author := split[2]
extraInfo := strings.TrimSpace(split[3])
parentHashes := split[4]
message := strings.Join(split[5:], SEPARATION_CHAR)
tags := []string{}
if extraInfo != "" {
re := regexp.MustCompile(`tag: ([^,\)]+)`)
tagMatch := re.FindStringSubmatch(extraInfo)
if len(tagMatch) > 1 {
tags = append(tags, tagMatch[1])
}
}
unitTimestampInt, _ := strconv.Atoi(unixTimestamp)
return &models.Commit{
Sha: sha,
Name: message,
Tags: tags,
ExtraInfo: extraInfo,
UnixTimestamp: int64(unitTimestampInt),
Author: author,
Parents: strings.Split(parentHashes, " "),
}
}
func (self *CommitListBuilder) getHydratedRebasingCommits(rebaseMode RebaseMode) ([]*models.Commit, error) { func (self *CommitListBuilder) getHydratedRebasingCommits(rebaseMode RebaseMode) ([]*models.Commit, error) {
commits, err := self.getRebasingCommits(rebaseMode) commits, err := self.getRebasingCommits(rebaseMode)
if err != nil { if err != nil {
@ -409,7 +413,7 @@ func (self *CommitListBuilder) getFirstPushedCommit(refName string) (string, err
func (self *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj { func (self *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
limitFlag := "" limitFlag := ""
if opts.Limit { if opts.Limit {
limitFlag = "-300" limitFlag = " -300"
} }
filterFlag := "" filterFlag := ""
@ -427,7 +431,7 @@ func (self *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmd
return self.cmd.New( return self.cmd.New(
fmt.Sprintf( fmt.Sprintf(
"git log %s %s %s --oneline %s %s --abbrev=%d %s", "git log %s %s %s --oneline %s%s --abbrev=%d%s",
self.cmd.Quote(opts.RefName), self.cmd.Quote(opts.RefName),
orderFlag, orderFlag,
allFlag, allFlag,
@ -449,5 +453,5 @@ var prettyFormat = fmt.Sprintf(
) )
func canExtractCommit(line string) bool { func canExtractCommit(line string) bool {
return strings.Split(line, " ")[0] != "gpg:" return line != "" && strings.Split(line, " ")[0] != "gpg:"
} }

View File

@ -1,11 +1,11 @@
package commands package commands
import ( import (
"os/exec"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -18,7 +18,7 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
Common: cmn, Common: cmn,
cmd: nil, cmd: nil,
getCurrentBranchName: func() (string, string, error) { return "master", "master", nil }, getCurrentBranchName: func() (string, string, error) { return "master", "master", nil },
getRebaseMode: func() (string, error) { return REBASE_MODE_NORMAL, nil }, getRebaseMode: func() (RebaseMode, error) { return REBASE_MODE_NONE, nil },
dotGitDir: ".git", dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) { readFile: func(filename string) ([]byte, error) {
return []byte(""), nil return []byte(""), nil
@ -29,92 +29,184 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
} }
} }
// TestCommitListBuilderGetMergeBase is a function. const commitsOutput = `0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode
func TestCommitListBuilderGetMergeBase(t *testing.T) { b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield| (origin/better-tests)|e94e8fc5b6fab4cb755f|fix logging
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield||d8084cd558925eb7c9c3|refactor
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield||65f910ebd85283b5cce9|WIP
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield||26c07b1ab33860a1a759|WIP
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield||3d4470a6c072208722e5|WIP
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield||053a66a7be3da43aacdc|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield||985fe482e806b172aea4|refactoring the config struct`
func TestGetCommits(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
command func(string, ...string) *exec.Cmd runner oscommands.ICmdObjRunner
test func(string, error) expectedCommits []*models.Commit
expectedError error
rebaseMode RebaseMode
currentBranchName string
opts GetCommitsOptions
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
"swallows an error if the call to merge-base returns an error", testName: "should return no commits if there are none",
func(cmd string, args ...string) *exec.Cmd { rebaseMode: REBASE_MODE_NONE,
assert.EqualValues(t, "git", cmd) currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H|%at|%aN|%d|%p|%s" --abbrev=20`, "", nil),
switch args[0] { expectedCommits: []*models.Commit{},
case "symbolic-ref": expectedError: nil,
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
return secureexec.Command("echo", "master")
case "merge-base":
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
return secureexec.Command("test")
}
return nil
},
func(output string, err error) {
assert.NoError(t, err)
assert.EqualValues(t, "", output)
},
}, },
{ {
"returns the commit when master", testName: "should return commits if they are present",
func(cmd string, args ...string) *exec.Cmd { rebaseMode: REBASE_MODE_NONE,
assert.EqualValues(t, "git", cmd) currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H|%at|%aN|%d|%p|%s" --abbrev=20`, commitsOutput, nil).
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
switch args[0] { expectedCommits: []*models.Commit{
case "symbolic-ref": {
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
return secureexec.Command("echo", "master") Name: "better typing for rebase mode",
case "merge-base": Status: "unpushed",
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) Action: "",
return secureexec.Command("echo", "blah") Tags: []string{},
} ExtraInfo: "(HEAD -> better-tests)",
return nil Author: "Jesse Duffield",
}, UnixTimestamp: 1640826609,
func(output string, err error) { Parents: []string{
assert.NoError(t, err) "b21997d6b4cbdf84b149",
assert.Equal(t, "blah", output) },
}, },
}, {
{ Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164",
"checks against develop when a feature branch", Name: "fix logging",
func(cmd string, args ...string) *exec.Cmd { Status: "pushed",
assert.EqualValues(t, "git", cmd) Action: "",
Tags: []string{},
switch args[0] { ExtraInfo: "(origin/better-tests)",
case "symbolic-ref": Author: "Jesse Duffield",
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) UnixTimestamp: 1640824515,
return secureexec.Command("echo", "feature/test") Parents: []string{
case "merge-base": "e94e8fc5b6fab4cb755f",
assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args) },
return secureexec.Command("echo", "blah") },
} {
return nil Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c",
}, Name: "refactor",
func(output string, err error) { Status: "pushed",
assert.NoError(t, err) Action: "",
assert.Equal(t, "blah", output) Tags: []string{},
}, ExtraInfo: "",
}, Author: "Jesse Duffield",
{ UnixTimestamp: 1640823749,
"bubbles up error if there is one", Parents: []string{
func(cmd string, args ...string) *exec.Cmd { "d8084cd558925eb7c9c3",
return secureexec.Command("test") },
}, },
func(output string, err error) { {
assert.Error(t, err) Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90",
assert.Equal(t, "", output) Name: "WIP",
Status: "pushed",
Action: "",
Tags: []string{},
ExtraInfo: "",
Author: "Jesse Duffield",
UnixTimestamp: 1640821426,
Parents: []string{
"65f910ebd85283b5cce9",
},
},
{
Sha: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a",
Name: "WIP",
Status: "pushed",
Action: "",
Tags: []string{},
ExtraInfo: "",
Author: "Jesse Duffield",
UnixTimestamp: 1640821275,
Parents: []string{
"26c07b1ab33860a1a759",
},
},
{
Sha: "26c07b1ab33860a1a7591a0638f9925ccf497ffa",
Name: "WIP",
Status: "merged",
Action: "",
Tags: []string{},
ExtraInfo: "",
Author: "Jesse Duffield",
UnixTimestamp: 1640750752,
Parents: []string{
"3d4470a6c072208722e5",
},
},
{
Sha: "3d4470a6c072208722e5ae9a54bcb9634959a1c5",
Name: "WIP",
Status: "merged",
Action: "",
Tags: []string{},
ExtraInfo: "",
Author: "Jesse Duffield",
UnixTimestamp: 1640748818,
Parents: []string{
"053a66a7be3da43aacdc",
},
},
{
Sha: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2",
Name: "refactoring the config struct",
Status: "merged",
Action: "",
Tags: []string{},
ExtraInfo: "",
Author: "Jesse Duffield",
UnixTimestamp: 1640739815,
Parents: []string{
"985fe482e806b172aea4",
},
},
}, },
expectedError: nil,
}, },
} }
for _, s := range scenarios { for _, scenario := range scenarios {
t.Run(s.testName, func(t *testing.T) { t.Run(scenario.testName, func(t *testing.T) {
c := NewDummyCommitListBuilder() builder := &CommitListBuilder{
c.OSCommand.SetCommand(s.command) Common: utils.NewDummyCommon(),
s.test(c.getMergeBase("HEAD")) cmd: oscommands.NewCmdObjBuilderDummy(scenario.runner),
getCurrentBranchName: func() (string, string, error) {
return scenario.currentBranchName, scenario.currentBranchName, nil
},
getRebaseMode: func() (RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
return []byte(""), nil
},
walkFiles: func(root string, fn filepath.WalkFunc) error {
return nil
},
}
commits, err := builder.GetCommits(scenario.opts)
assert.Equal(t, scenario.expectedCommits, commits)
assert.Equal(t, scenario.expectedError, err)
}) })
} }
} }

View File

@ -8,6 +8,9 @@ import (
// command line. // command line.
type ICmdObj interface { type ICmdObj interface {
GetCmd() *exec.Cmd GetCmd() *exec.Cmd
// outputs string representation of command. Note that if the command was built
// using NewFromArgs, the output won't be quite the same as what you would type
// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"'
ToString() string ToString() string
AddEnvVars(...string) ICmdObj AddEnvVars(...string) ICmdObj
GetEnvVars() []string GetEnvVars() []string

View File

@ -19,9 +19,6 @@ type ICmdObjBuilder interface {
Quote(str string) string Quote(str string) string
} }
// poor man's version of explicitly saying that struct X implements interface Y
var _ ICmdObjBuilder = &CmdObjBuilder{}
type CmdObjBuilder struct { type CmdObjBuilder struct {
runner ICmdObjRunner runner ICmdObjRunner
logCmdObj func(ICmdObj) logCmdObj func(ICmdObj)
@ -31,6 +28,9 @@ type CmdObjBuilder struct {
platform *Platform platform *Platform
} }
// poor man's version of explicitly saying that struct X implements interface Y
var _ ICmdObjBuilder = &CmdObjBuilder{}
func (self *CmdObjBuilder) New(cmdStr string) ICmdObj { func (self *CmdObjBuilder) New(cmdStr string) ICmdObj {
args := str.ToArgv(cmdStr) args := str.ToArgv(cmdStr)
cmd := self.command(args[0], args[1:]...) cmd := self.command(args[0], args[1:]...)

View File

@ -14,19 +14,19 @@ type ICmdObjRunner interface {
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
} }
type RunExpectation func(ICmdObj) (string, error) type cmdObjRunner struct {
type Runner struct {
log *logrus.Entry log *logrus.Entry
logCmdObj func(ICmdObj) logCmdObj func(ICmdObj)
} }
func (self *Runner) Run(cmdObj ICmdObj) error { var _ ICmdObjRunner = &cmdObjRunner{}
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
_, err := self.RunWithOutput(cmdObj) _, err := self.RunWithOutput(cmdObj)
return err return err
} }
func (self *Runner) RunWithOutput(cmdObj ICmdObj) (string, error) { func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
self.logCmdObj(cmdObj) self.logCmdObj(cmdObj)
output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput()) output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput())
if err != nil { if err != nil {
@ -35,7 +35,7 @@ func (self *Runner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return output, err return output, err
} }
func (self *Runner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error { func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
cmd := cmdObj.GetCmd() cmd := cmdObj.GetCmd()
stdoutPipe, err := cmd.StdoutPipe() stdoutPipe, err := cmd.StdoutPipe()
if err != nil { if err != nil {

View File

@ -1,6 +1,7 @@
package oscommands package oscommands
import ( import (
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -8,3 +9,18 @@ import (
func NewDummyOSCommand() *OSCommand { func NewDummyOSCommand() *OSCommand {
return NewOSCommand(utils.NewDummyCommon()) return NewOSCommand(utils.NewDummyCommon())
} }
func NewCmdObjBuilderDummy(runner ICmdObjRunner) ICmdObjBuilder {
return &CmdObjBuilder{
runner: runner,
logCmdObj: func(ICmdObj) {},
command: secureexec.Command,
platform: &Platform{
OS: "darwin",
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
},
}
}

View File

@ -0,0 +1,82 @@
package oscommands
import (
"bufio"
"fmt"
"strings"
"testing"
"github.com/go-errors/errors"
"github.com/stretchr/testify/assert"
)
type FakeCmdObjRunner struct {
t *testing.T
expectedCmds []func(ICmdObj) (string, error)
expectedCmdIndex int
}
var _ ICmdObjRunner = &FakeCmdObjRunner{}
func NewFakeRunner(t *testing.T) *FakeCmdObjRunner {
return &FakeCmdObjRunner{t: t}
}
func (self *FakeCmdObjRunner) Run(cmdObj ICmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
if self.expectedCmdIndex > len(self.expectedCmds)-1 {
self.t.Errorf("ran too many commands. Unexpected command: `%s`", cmdObj.ToString())
return "", errors.New("ran too many commands")
}
expectedCmd := self.expectedCmds[self.expectedCmdIndex]
self.expectedCmdIndex++
return expectedCmd(cmdObj)
}
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
output, err := self.RunWithOutput(cmdObj)
if err != nil {
return err
}
scanner := bufio.NewScanner(strings.NewReader(output))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
stop, err := onLine(line)
if err != nil {
return err
}
if stop {
break
}
}
return nil
}
func (self *FakeCmdObjRunner) ExpectFunc(fn func(cmdObj ICmdObj) (string, error)) *FakeCmdObjRunner {
self.expectedCmds = append(self.expectedCmds, fn)
return self
}
func (self *FakeCmdObjRunner) Expect(expectedCmdStr string, output string, err error) *FakeCmdObjRunner {
self.ExpectFunc(func(cmdObj ICmdObj) (string, error) {
cmdStr := cmdObj.ToString()
if cmdStr != expectedCmdStr {
assert.Equal(self.t, expectedCmdStr, cmdStr, fmt.Sprintf("expected command %d to be %s, but was %s", self.expectedCmdIndex+1, expectedCmdStr, cmdStr))
return "", errors.New("expected cmd")
}
return output, err
})
return self
}

View File

@ -17,27 +17,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
// Platform stores the os state
type Platform struct {
OS string
Shell string
ShellArg string
OpenCommand string
OpenLinkCommand string
}
type ICommander interface {
Run(ICmdObj) error
RunWithOutput(ICmdObj) (string, error)
}
type RealCommander struct {
}
func (self *RealCommander) Run(cmdObj ICmdObj) error {
return cmdObj.GetCmd().Run()
}
// OSCommand holds all the os commands // OSCommand holds all the os commands
type OSCommand struct { type OSCommand struct {
*common.Common *common.Common
@ -56,6 +35,15 @@ type OSCommand struct {
Cmd *CmdObjBuilder Cmd *CmdObjBuilder
} }
// Platform stores the os state
type Platform struct {
OS string
Shell string
ShellArg string
OpenCommand string
OpenLinkCommand string
}
// TODO: make these fields private // TODO: make these fields private
type CmdLogEntry struct { type CmdLogEntry struct {
// e.g. 'git commit -m "haha"' // e.g. 'git commit -m "haha"'
@ -99,7 +87,7 @@ func NewOSCommand(common *common.Common) *OSCommand {
removeFile: os.RemoveAll, removeFile: os.RemoveAll,
} }
runner := &Runner{log: common.Log, logCmdObj: c.LogCmdObj} runner := &cmdObjRunner{log: common.Log, logCmdObj: c.LogCmdObj}
c.Cmd = &CmdObjBuilder{runner: runner, command: command, logCmdObj: c.LogCmdObj, platform: platform} c.Cmd = &CmdObjBuilder{runner: runner, command: command, logCmdObj: c.LogCmdObj, platform: platform}
return c return c
@ -117,7 +105,7 @@ func (c *OSCommand) WithSpan(span string) *OSCommand {
*newOSCommand = *c *newOSCommand = *c
newOSCommand.CmdLogSpan = span newOSCommand.CmdLogSpan = span
newOSCommand.Cmd.logCmdObj = newOSCommand.LogCmdObj newOSCommand.Cmd.logCmdObj = newOSCommand.LogCmdObj
newOSCommand.Cmd.runner = &Runner{log: c.Log, logCmdObj: newOSCommand.LogCmdObj} newOSCommand.Cmd.runner = &cmdObjRunner{log: c.Log, logCmdObj: newOSCommand.LogCmdObj}
return newOSCommand return newOSCommand
} }

View File

@ -2,10 +2,8 @@ package commands
import ( import (
"os/exec" "os/exec"
"regexp"
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/test" "github.com/jesseduffield/lazygit/pkg/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -58,40 +56,40 @@ func TestGitCommandRebaseBranch(t *testing.T) {
} }
} }
// TestGitCommandSkipEditorCommand confirms that SkipEditorCommand injects // // TestGitCommandSkipEditorCommand confirms that SkipEditorCommand injects
// environment variables that suppress an interactive editor // // environment variables that suppress an interactive editor
func TestGitCommandSkipEditorCommand(t *testing.T) { // func TestGitCommandSkipEditorCommand(t *testing.T) {
cmd := NewDummyGitCommand() // cmd := NewDummyGitCommand()
cmd.OSCommand.SetBeforeExecuteCmd(func(cmdObj oscommands.ICmdObj) { // cmd.OSCommand.SetBeforeExecuteCmd(func(cmdObj oscommands.ICmdObj) {
test.AssertContainsMatch( // test.AssertContainsMatch(
t, // t,
cmdObj.GetEnvVars(), // cmdObj.GetEnvVars(),
regexp.MustCompile("^VISUAL="), // regexp.MustCompile("^VISUAL="),
"expected VISUAL to be set for a non-interactive external command", // "expected VISUAL to be set for a non-interactive external command",
) // )
test.AssertContainsMatch( // test.AssertContainsMatch(
t, // t,
cmdObj.GetEnvVars(), // cmdObj.GetEnvVars(),
regexp.MustCompile("^EDITOR="), // regexp.MustCompile("^EDITOR="),
"expected EDITOR to be set for a non-interactive external command", // "expected EDITOR to be set for a non-interactive external command",
) // )
test.AssertContainsMatch( // test.AssertContainsMatch(
t, // t,
cmdObj.GetEnvVars(), // cmdObj.GetEnvVars(),
regexp.MustCompile("^GIT_EDITOR="), // regexp.MustCompile("^GIT_EDITOR="),
"expected GIT_EDITOR to be set for a non-interactive external command", // "expected GIT_EDITOR to be set for a non-interactive external command",
) // )
test.AssertContainsMatch( // test.AssertContainsMatch(
t, // t,
cmdObj.GetEnvVars(), // cmdObj.GetEnvVars(),
regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"), // regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"),
"expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command", // "expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command",
) // )
}) // })
_ = cmd.runSkipEditorCommand("true") // _ = cmd.runSkipEditorCommand("true")
} // }

View File

@ -6,13 +6,17 @@ import (
gogit "github.com/jesseduffield/go-git/v5" gogit "github.com/jesseduffield/go-git/v5"
) )
type RebaseMode string type RebaseMode int
const ( const (
REBASE_MODE_NORMAL RebaseMode = "normal" // this means we're neither rebasing nor merging
REBASE_MODE_INTERACTIVE = "interactive" REBASE_MODE_NONE RebaseMode = iota
REBASE_MODE_REBASING = "rebasing" // this means normal rebase as opposed to interactive rebase
REBASE_MODE_MERGING = "merging" 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
) )
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase // RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
@ -20,7 +24,7 @@ const (
func (c *GitCommand) RebaseMode() (RebaseMode, error) { func (c *GitCommand) RebaseMode() (RebaseMode, error) {
exists, err := c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-apply")) exists, err := c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-apply"))
if err != nil { if err != nil {
return "", err return REBASE_MODE_NONE, err
} }
if exists { if exists {
return REBASE_MODE_NORMAL, nil return REBASE_MODE_NORMAL, nil
@ -29,20 +33,20 @@ func (c *GitCommand) RebaseMode() (RebaseMode, error) {
if exists { if exists {
return REBASE_MODE_INTERACTIVE, err return REBASE_MODE_INTERACTIVE, err
} else { } else {
return "", err return REBASE_MODE_NONE, err
} }
} }
func (c *GitCommand) WorkingTreeState() RebaseMode { func (c *GitCommand) WorkingTreeState() RebaseMode {
rebaseMode, _ := c.RebaseMode() rebaseMode, _ := c.RebaseMode()
if rebaseMode != "" { if rebaseMode != REBASE_MODE_NONE {
return REBASE_MODE_REBASING return REBASE_MODE_REBASING
} }
merging, _ := c.IsInMergeState() merging, _ := c.IsInMergeState()
if merging { if merging {
return REBASE_MODE_MERGING return REBASE_MODE_MERGING
} }
return REBASE_MODE_NORMAL return REBASE_MODE_NONE
} }
// IsInMergeState states whether we are still mid-merge // IsInMergeState states whether we are still mid-merge

View File

@ -262,7 +262,7 @@ func (gui *Gui) handleCompleteMerge() error {
} }
// if we got conflicts after unstashing, we don't want to call any git // if we got conflicts after unstashing, we don't want to call any git
// commands to continue rebasing/merging here // commands to continue rebasing/merging here
if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_NORMAL { if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_NONE {
return gui.handleEscapeMerge() return gui.handleEscapeMerge()
} }
// if there are no more files with merge conflicts, we should ask whether the user wants to continue // if there are no more files with merge conflicts, we should ask whether the user wants to continue

View File

@ -61,7 +61,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
}, },
{ {
isActive: func() bool { isActive: func() bool {
return gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL return gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NONE
}, },
description: func() string { description: func() string {
workingTreeState := gui.GitCommand.WorkingTreeState() workingTreeState := gui.GitCommand.WorkingTreeState()

View File

@ -26,7 +26,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
}, },
} }
if gui.GitCommand.PatchManager.CanRebase && gui.workingTreeState() == commands.REBASE_MODE_NORMAL { if gui.GitCommand.PatchManager.CanRebase && gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_NONE {
menuItems = append(menuItems, []*menuItem{ menuItems = append(menuItems, []*menuItem{
{ {
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.To), displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.To),
@ -74,7 +74,7 @@ func (gui *Gui) getPatchCommitIndex() int {
} }
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) { func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL { if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NONE {
return false, gui.createErrorPanel(gui.Tr.CantPatchWhileRebasingError) return false, gui.createErrorPanel(gui.Tr.CantPatchWhileRebasingError)
} }
return true, nil return true, nil

View File

@ -144,7 +144,7 @@ func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
func (gui *Gui) workingTreeStateNoun() string { func (gui *Gui) workingTreeStateNoun() string {
workingTreeState := gui.GitCommand.WorkingTreeState() workingTreeState := gui.GitCommand.WorkingTreeState()
switch workingTreeState { switch workingTreeState {
case commands.REBASE_MODE_NORMAL: case commands.REBASE_MODE_NONE:
return "" return ""
case commands.REBASE_MODE_MERGING: case commands.REBASE_MODE_MERGING:
return "merge" return "merge"

View File

@ -28,7 +28,7 @@ func (gui *Gui) refreshStatus() {
status += presentation.ColoredBranchStatus(currentBranch) + " " status += presentation.ColoredBranchStatus(currentBranch) + " "
} }
if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL { if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NONE {
status += style.FgYellow.Sprintf("(%s) ", gui.GitCommand.WorkingTreeState()) status += style.FgYellow.Sprintf("(%s) ", gui.GitCommand.WorkingTreeState())
} }
@ -156,15 +156,3 @@ func lazygitTitle() string {
__/ | __/ | __/ | __/ |
|___/ |___/ ` |___/ |___/ `
} }
func (gui *Gui) workingTreeState() commands.RebaseMode {
rebaseMode, _ := gui.GitCommand.RebaseMode()
if rebaseMode != "" {
return commands.REBASE_MODE_REBASING
}
merging, _ := gui.GitCommand.IsInMergeState()
if merging {
return commands.REBASE_MODE_MERGING
}
return commands.REBASE_MODE_NORMAL
}