mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 12:25:47 +02:00
updating specs
This commit is contained in:
parent
1fc0d786ae
commit
b028f37ba8
15 changed files with 416 additions and 241 deletions
|
@ -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 {
|
||||
Limit bool
|
||||
FilterPath string
|
||||
|
@ -101,37 +64,6 @@ type GetCommitsOptions struct {
|
|||
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
|
||||
func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) {
|
||||
commits := []*models.Commit{}
|
||||
|
@ -172,7 +104,11 @@ func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Com
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if rebaseMode != "" {
|
||||
if len(commits) == 0 {
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
if rebaseMode != REBASE_MODE_NONE {
|
||||
currentCommit := commits[len(rebasingCommits)]
|
||||
youAreHere := style.FgYellow.Sprintf("<-- %s ---", self.Tr.YouAreHere)
|
||||
currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
|
||||
|
@ -186,6 +122,74 @@ func (self *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Com
|
|||
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) {
|
||||
commits, err := self.getRebasingCommits(rebaseMode)
|
||||
if err != nil {
|
||||
|
@ -409,7 +413,7 @@ func (self *CommitListBuilder) getFirstPushedCommit(refName string) (string, err
|
|||
func (self *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
limitFlag := ""
|
||||
if opts.Limit {
|
||||
limitFlag = "-300"
|
||||
limitFlag = " -300"
|
||||
}
|
||||
|
||||
filterFlag := ""
|
||||
|
@ -427,7 +431,7 @@ func (self *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmd
|
|||
|
||||
return self.cmd.New(
|
||||
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),
|
||||
orderFlag,
|
||||
allFlag,
|
||||
|
@ -449,5 +453,5 @@ var prettyFormat = fmt.Sprintf(
|
|||
)
|
||||
|
||||
func canExtractCommit(line string) bool {
|
||||
return strings.Split(line, " ")[0] != "gpg:"
|
||||
return line != "" && strings.Split(line, " ")[0] != "gpg:"
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
|
|||
Common: cmn,
|
||||
cmd: 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",
|
||||
readFile: func(filename string) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
|
@ -29,92 +29,184 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetMergeBase is a function.
|
||||
func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
const commitsOutput = `0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode
|
||||
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 {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string, error)
|
||||
testName string
|
||||
runner oscommands.ICmdObjRunner
|
||||
expectedCommits []*models.Commit
|
||||
expectedError error
|
||||
rebaseMode RebaseMode
|
||||
currentBranchName string
|
||||
opts GetCommitsOptions
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"swallows an error if the call to merge-base returns an error",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
testName: "should return no commits if there are none",
|
||||
rebaseMode: REBASE_MODE_NONE,
|
||||
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] {
|
||||
case "symbolic-ref":
|
||||
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)
|
||||
},
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
"returns the commit when master",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
testName: "should return commits if they are present",
|
||||
rebaseMode: REBASE_MODE_NONE,
|
||||
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] {
|
||||
case "symbolic-ref":
|
||||
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("echo", "blah")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"checks against develop when a feature branch",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return secureexec.Command("echo", "feature/test")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args)
|
||||
return secureexec.Command("echo", "blah")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"bubbles up error if there is one",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
return secureexec.Command("test")
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", output)
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
|
||||
Name: "better typing for rebase mode",
|
||||
Status: "unpushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(HEAD -> better-tests)",
|
||||
Author: "Jesse Duffield",
|
||||
UnixTimestamp: 1640826609,
|
||||
Parents: []string{
|
||||
"b21997d6b4cbdf84b149",
|
||||
},
|
||||
},
|
||||
{
|
||||
Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164",
|
||||
Name: "fix logging",
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(origin/better-tests)",
|
||||
Author: "Jesse Duffield",
|
||||
UnixTimestamp: 1640824515,
|
||||
Parents: []string{
|
||||
"e94e8fc5b6fab4cb755f",
|
||||
},
|
||||
},
|
||||
{
|
||||
Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c",
|
||||
Name: "refactor",
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
Author: "Jesse Duffield",
|
||||
UnixTimestamp: 1640823749,
|
||||
Parents: []string{
|
||||
"d8084cd558925eb7c9c3",
|
||||
},
|
||||
},
|
||||
{
|
||||
Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90",
|
||||
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 {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getMergeBase("HEAD"))
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
builder := &CommitListBuilder{
|
||||
Common: utils.NewDummyCommon(),
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import (
|
|||
// command line.
|
||||
type ICmdObj interface {
|
||||
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
|
||||
AddEnvVars(...string) ICmdObj
|
||||
GetEnvVars() []string
|
||||
|
|
|
@ -19,9 +19,6 @@ type ICmdObjBuilder interface {
|
|||
Quote(str string) string
|
||||
}
|
||||
|
||||
// poor man's version of explicitly saying that struct X implements interface Y
|
||||
var _ ICmdObjBuilder = &CmdObjBuilder{}
|
||||
|
||||
type CmdObjBuilder struct {
|
||||
runner ICmdObjRunner
|
||||
logCmdObj func(ICmdObj)
|
||||
|
@ -31,6 +28,9 @@ type CmdObjBuilder struct {
|
|||
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 {
|
||||
args := str.ToArgv(cmdStr)
|
||||
cmd := self.command(args[0], args[1:]...)
|
||||
|
|
|
@ -14,19 +14,19 @@ type ICmdObjRunner interface {
|
|||
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
|
||||
}
|
||||
|
||||
type RunExpectation func(ICmdObj) (string, error)
|
||||
|
||||
type Runner struct {
|
||||
type cmdObjRunner struct {
|
||||
log *logrus.Entry
|
||||
logCmdObj func(ICmdObj)
|
||||
}
|
||||
|
||||
func (self *Runner) Run(cmdObj ICmdObj) error {
|
||||
var _ ICmdObjRunner = &cmdObjRunner{}
|
||||
|
||||
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
||||
_, err := self.RunWithOutput(cmdObj)
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *Runner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
self.logCmdObj(cmdObj)
|
||||
output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput())
|
||||
if err != nil {
|
||||
|
@ -35,7 +35,7 @@ func (self *Runner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
|||
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()
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package oscommands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -8,3 +9,18 @@ import (
|
|||
func NewDummyOSCommand() *OSCommand {
|
||||
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}}",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
82
pkg/commands/oscommands/fake_runner.go
Normal file
82
pkg/commands/oscommands/fake_runner.go
Normal 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
|
||||
}
|
|
@ -17,27 +17,6 @@ import (
|
|||
"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
|
||||
type OSCommand struct {
|
||||
*common.Common
|
||||
|
@ -56,6 +35,15 @@ type OSCommand struct {
|
|||
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
|
||||
type CmdLogEntry struct {
|
||||
// e.g. 'git commit -m "haha"'
|
||||
|
@ -99,7 +87,7 @@ func NewOSCommand(common *common.Common) *OSCommand {
|
|||
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}
|
||||
|
||||
return c
|
||||
|
@ -117,7 +105,7 @@ func (c *OSCommand) WithSpan(span string) *OSCommand {
|
|||
*newOSCommand = *c
|
||||
newOSCommand.CmdLogSpan = span
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,8 @@ package commands
|
|||
|
||||
import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -58,40 +56,40 @@ func TestGitCommandRebaseBranch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestGitCommandSkipEditorCommand confirms that SkipEditorCommand injects
|
||||
// environment variables that suppress an interactive editor
|
||||
func TestGitCommandSkipEditorCommand(t *testing.T) {
|
||||
cmd := NewDummyGitCommand()
|
||||
// // TestGitCommandSkipEditorCommand confirms that SkipEditorCommand injects
|
||||
// // environment variables that suppress an interactive editor
|
||||
// func TestGitCommandSkipEditorCommand(t *testing.T) {
|
||||
// cmd := NewDummyGitCommand()
|
||||
|
||||
cmd.OSCommand.SetBeforeExecuteCmd(func(cmdObj oscommands.ICmdObj) {
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmdObj.GetEnvVars(),
|
||||
regexp.MustCompile("^VISUAL="),
|
||||
"expected VISUAL to be set for a non-interactive external command",
|
||||
)
|
||||
// cmd.OSCommand.SetBeforeExecuteCmd(func(cmdObj oscommands.ICmdObj) {
|
||||
// test.AssertContainsMatch(
|
||||
// t,
|
||||
// cmdObj.GetEnvVars(),
|
||||
// regexp.MustCompile("^VISUAL="),
|
||||
// "expected VISUAL to be set for a non-interactive external command",
|
||||
// )
|
||||
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmdObj.GetEnvVars(),
|
||||
regexp.MustCompile("^EDITOR="),
|
||||
"expected EDITOR to be set for a non-interactive external command",
|
||||
)
|
||||
// test.AssertContainsMatch(
|
||||
// t,
|
||||
// cmdObj.GetEnvVars(),
|
||||
// regexp.MustCompile("^EDITOR="),
|
||||
// "expected EDITOR to be set for a non-interactive external command",
|
||||
// )
|
||||
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmdObj.GetEnvVars(),
|
||||
regexp.MustCompile("^GIT_EDITOR="),
|
||||
"expected GIT_EDITOR to be set for a non-interactive external command",
|
||||
)
|
||||
// test.AssertContainsMatch(
|
||||
// t,
|
||||
// cmdObj.GetEnvVars(),
|
||||
// regexp.MustCompile("^GIT_EDITOR="),
|
||||
// "expected GIT_EDITOR to be set for a non-interactive external command",
|
||||
// )
|
||||
|
||||
test.AssertContainsMatch(
|
||||
t,
|
||||
cmdObj.GetEnvVars(),
|
||||
regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"),
|
||||
"expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command",
|
||||
)
|
||||
})
|
||||
// test.AssertContainsMatch(
|
||||
// t,
|
||||
// cmdObj.GetEnvVars(),
|
||||
// regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"),
|
||||
// "expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command",
|
||||
// )
|
||||
// })
|
||||
|
||||
_ = cmd.runSkipEditorCommand("true")
|
||||
}
|
||||
// _ = cmd.runSkipEditorCommand("true")
|
||||
// }
|
||||
|
|
|
@ -6,13 +6,17 @@ import (
|
|||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
)
|
||||
|
||||
type RebaseMode string
|
||||
type RebaseMode int
|
||||
|
||||
const (
|
||||
REBASE_MODE_NORMAL RebaseMode = "normal"
|
||||
REBASE_MODE_INTERACTIVE = "interactive"
|
||||
REBASE_MODE_REBASING = "rebasing"
|
||||
REBASE_MODE_MERGING = "merging"
|
||||
// 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
|
||||
)
|
||||
|
||||
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
|
||||
|
@ -20,7 +24,7 @@ const (
|
|||
func (c *GitCommand) RebaseMode() (RebaseMode, error) {
|
||||
exists, err := c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-apply"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return REBASE_MODE_NONE, err
|
||||
}
|
||||
if exists {
|
||||
return REBASE_MODE_NORMAL, nil
|
||||
|
@ -29,20 +33,20 @@ func (c *GitCommand) RebaseMode() (RebaseMode, error) {
|
|||
if exists {
|
||||
return REBASE_MODE_INTERACTIVE, err
|
||||
} else {
|
||||
return "", err
|
||||
return REBASE_MODE_NONE, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GitCommand) WorkingTreeState() RebaseMode {
|
||||
rebaseMode, _ := c.RebaseMode()
|
||||
if rebaseMode != "" {
|
||||
if rebaseMode != REBASE_MODE_NONE {
|
||||
return REBASE_MODE_REBASING
|
||||
}
|
||||
merging, _ := c.IsInMergeState()
|
||||
if merging {
|
||||
return REBASE_MODE_MERGING
|
||||
}
|
||||
return REBASE_MODE_NORMAL
|
||||
return REBASE_MODE_NONE
|
||||
}
|
||||
|
||||
// IsInMergeState states whether we are still mid-merge
|
||||
|
|
|
@ -262,7 +262,7 @@ func (gui *Gui) handleCompleteMerge() error {
|
|||
}
|
||||
// if we got conflicts after unstashing, we don't want to call any git
|
||||
// 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()
|
||||
}
|
||||
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
|
||||
|
|
|
@ -61,7 +61,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
|||
},
|
||||
{
|
||||
isActive: func() bool {
|
||||
return gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL
|
||||
return gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NONE
|
||||
},
|
||||
description: func() string {
|
||||
workingTreeState := gui.GitCommand.WorkingTreeState()
|
||||
|
|
|
@ -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{
|
||||
{
|
||||
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) {
|
||||
if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NORMAL {
|
||||
if gui.GitCommand.WorkingTreeState() != commands.REBASE_MODE_NONE {
|
||||
return false, gui.createErrorPanel(gui.Tr.CantPatchWhileRebasingError)
|
||||
}
|
||||
return true, nil
|
||||
|
|
|
@ -144,7 +144,7 @@ func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
|
|||
func (gui *Gui) workingTreeStateNoun() string {
|
||||
workingTreeState := gui.GitCommand.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
case commands.REBASE_MODE_NORMAL:
|
||||
case commands.REBASE_MODE_NONE:
|
||||
return ""
|
||||
case commands.REBASE_MODE_MERGING:
|
||||
return "merge"
|
||||
|
|
|
@ -28,7 +28,7 @@ func (gui *Gui) refreshStatus() {
|
|||
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())
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue