1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-20 05:19:24 +02:00

move OS commands into their own package

This commit is contained in:
Jesse Duffield 2020-09-29 19:10:57 +10:00
parent f9643448a4
commit 1759ddf247
24 changed files with 175 additions and 151 deletions

View File

@ -16,6 +16,7 @@ import (
"github.com/aybabtme/humanlog" "github.com/aybabtme/humanlog"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui" "github.com/jesseduffield/lazygit/pkg/gui"
@ -30,7 +31,7 @@ type App struct {
Config config.AppConfigurer Config config.AppConfigurer
Log *logrus.Entry Log *logrus.Entry
OSCommand *commands.OSCommand OSCommand *oscommands.OSCommand
GitCommand *commands.GitCommand GitCommand *commands.GitCommand
Gui *gui.Gui Gui *gui.Gui
Tr *i18n.Localizer Tr *i18n.Localizer
@ -106,7 +107,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
return app, nil return app, nil
} }
app.OSCommand = commands.NewOSCommand(app.Log, config) app.OSCommand = oscommands.NewOSCommand(app.Log, config)
app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr) app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr)
if err != nil { if err != nil {

View File

@ -11,6 +11,7 @@ import (
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/models" "github.com/jesseduffield/lazygit/pkg/models"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -31,12 +32,12 @@ const SEPARATION_CHAR = "|"
type CommitListBuilder struct { type CommitListBuilder struct {
Log *logrus.Entry Log *logrus.Entry
GitCommand *GitCommand GitCommand *GitCommand
OSCommand *OSCommand OSCommand *oscommands.OSCommand
Tr *i18n.Localizer Tr *i18n.Localizer
} }
// NewCommitListBuilder builds a new commit list builder // NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer) *CommitListBuilder { func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *oscommands.OSCommand, tr *i18n.Localizer) *CommitListBuilder {
return &CommitListBuilder{ return &CommitListBuilder{
Log: log, Log: log,
GitCommand: gitCommand, GitCommand: gitCommand,
@ -151,7 +152,7 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit
cmd := c.getLogCmd(opts) cmd := c.getLogCmd(opts)
err = RunLineOutputCmd(cmd, func(line string) (bool, error) { err = oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) {
if strings.Split(line, " ")[0] != "gpg:" { if strings.Split(line, " ")[0] != "gpg:" {
commit := c.extractCommitFromLine(line) commit := c.extractCommitFromLine(line)
if commit.Sha == firstPushedCommit { if commit.Sha == firstPushedCommit {

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -13,10 +14,10 @@ func NewDummyCommitListBuilder() *CommitListBuilder {
osCommand := NewDummyOSCommand() osCommand := NewDummyOSCommand()
return &CommitListBuilder{ return &CommitListBuilder{
Log: NewDummyLog(), Log: utils.NewDummyLog(),
GitCommand: NewDummyGitCommandWithOSCommand(osCommand), GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
OSCommand: osCommand, OSCommand: osCommand,
Tr: i18n.NewLocalizer(NewDummyLog()), Tr: i18n.NewLocalizer(utils.NewDummyLog()),
} }
} }

View File

@ -1,61 +1,24 @@
package commands package commands
import ( import (
"io/ioutil" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
) )
// This file exports dummy constructors for use by tests in other packages
// NewDummyOSCommand creates a new dummy OSCommand for testing
func NewDummyOSCommand() *OSCommand {
return NewOSCommand(NewDummyLog(), NewDummyAppConfig())
}
// NewDummyAppConfig creates a new dummy AppConfig for testing
func NewDummyAppConfig() *config.AppConfig {
userConfig := viper.New()
userConfig.SetConfigType("yaml")
if err := config.LoadDefaults(userConfig, config.GetDefaultConfig()); err != nil {
panic(err)
}
appConfig := &config.AppConfig{
Name: "lazygit",
Version: "unversioned",
Commit: "",
BuildDate: "",
Debug: false,
BuildSource: "",
UserConfig: userConfig,
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
return appConfig
}
// NewDummyLog creates a new dummy Log for testing
func NewDummyLog() *logrus.Entry {
log := logrus.New()
log.Out = ioutil.Discard
return log.WithField("test", "test")
}
// NewDummyGitCommand creates a new dummy GitCommand for testing // NewDummyGitCommand creates a new dummy GitCommand for testing
func NewDummyGitCommand() *GitCommand { func NewDummyGitCommand() *GitCommand {
return NewDummyGitCommandWithOSCommand(NewDummyOSCommand()) return NewDummyGitCommandWithOSCommand(oscommands.NewDummyOSCommand())
} }
// NewDummyGitCommandWithOSCommand creates a new dummy GitCommand for testing // NewDummyGitCommandWithOSCommand creates a new dummy GitCommand for testing
func NewDummyGitCommandWithOSCommand(osCommand *OSCommand) *GitCommand { func NewDummyGitCommandWithOSCommand(osCommand *oscommands.OSCommand) *GitCommand {
return &GitCommand{ return &GitCommand{
Log: NewDummyLog(), Log: utils.NewDummyLog(),
OSCommand: osCommand, OSCommand: osCommand,
Tr: i18n.NewLocalizer(NewDummyLog()), Tr: i18n.NewLocalizer(utils.NewDummyLog()),
Config: NewDummyAppConfig(), Config: config.NewDummyAppConfig(),
getGlobalGitConfig: func(string) (string, error) { return "", nil }, getGlobalGitConfig: func(string) (string, error) { return "", nil },
getLocalGitConfig: func(string) (string, error) { return "", nil }, getLocalGitConfig: func(string) (string, error) { return "", nil },
removeFile: func(string) error { return nil }, removeFile: func(string) error { return nil },

View File

@ -16,6 +16,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5" gogit "github.com/go-git/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/env"
@ -42,7 +43,7 @@ func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir f
// we've been given the git directory explicitly so no need to navigate to it // we've been given the git directory explicitly so no need to navigate to it
_, err := stat(gitDir) _, err := stat(gitDir)
if err != nil { if err != nil {
return WrapError(err) return utils.WrapError(err)
} }
return nil return nil
@ -58,11 +59,11 @@ func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir f
} }
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return WrapError(err) return utils.WrapError(err)
} }
if err = chdir(".."); err != nil { if err = chdir(".."); err != nil {
return WrapError(err) return utils.WrapError(err)
} }
} }
} }
@ -112,7 +113,7 @@ func setupRepository(openGitRepository func(string) (*gogit.Repository, error),
// GitCommand is our main git interface // GitCommand is our main git interface
type GitCommand struct { type GitCommand struct {
Log *logrus.Entry Log *logrus.Entry
OSCommand *OSCommand OSCommand *oscommands.OSCommand
Repo *gogit.Repository Repo *gogit.Repository
Tr *i18n.Localizer Tr *i18n.Localizer
Config config.AppConfigurer Config config.AppConfigurer
@ -128,7 +129,7 @@ type GitCommand struct {
} }
// NewGitCommand it runs git commands // NewGitCommand it runs git commands
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, config config.AppConfigurer) (*GitCommand, error) { func NewGitCommand(log *logrus.Entry, osCommand *oscommands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer) (*GitCommand, error) {
var repo *gogit.Repository var repo *gogit.Repository
// see what our default push behaviour is // see what our default push behaviour is
@ -464,7 +465,7 @@ func (c *GitCommand) Fetch(opts FetchOptions) error {
} }
// ResetToCommit reset to commit // ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string, strength string, options RunCommandOptions) error { func (c *GitCommand) ResetToCommit(sha string, strength string, options oscommands.RunCommandOptions) error {
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options) return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options)
} }
@ -604,7 +605,7 @@ func (c *GitCommand) Push(branchName string, force bool, upstream string, args s
// CatFile obtains the content of a file // CatFile obtains the content of a file
func (c *GitCommand) CatFile(fileName string) (string, error) { func (c *GitCommand) CatFile(fileName string) (string, error) {
return c.OSCommand.RunCommandWithOutput("%s %s", c.OSCommand.Platform.catCmd, c.OSCommand.Quote(fileName)) return c.OSCommand.RunCommandWithOutput("%s %s", c.OSCommand.Platform.CatCmd, c.OSCommand.Quote(fileName))
} }
// StageFile stages a file // StageFile stages a file
@ -765,7 +766,7 @@ func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
if options.Force { if options.Force {
forceArg = "--force " forceArg = "--force "
} }
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git checkout %s %s", forceArg, branch), RunCommandOptions{EnvVars: options.EnvVars}) return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git checkout %s %s", forceArg, branch), oscommands.RunCommandOptions{EnvVars: options.EnvVars})
} }
// PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty` // PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty`
@ -972,7 +973,7 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
c.Log.WithField("command", cmdStr).Info("RunCommand") c.Log.WithField("command", cmdStr).Info("RunCommand")
splitCmd := str.ToArgv(cmdStr) splitCmd := str.ToArgv(cmdStr)
cmd := c.OSCommand.command(splitCmd[0], splitCmd[1:]...) cmd := c.OSCommand.Command(splitCmd[0], splitCmd[1:]...)
gitSequenceEditor := ex gitSequenceEditor := ex
if todo == "" { if todo == "" {
@ -1391,7 +1392,7 @@ func (c *GitCommand) GetReflogCommits(lastReflogCommit *models.Commit, filterPat
cmd := c.OSCommand.ExecutableFromString(fmt.Sprintf("git reflog --abbrev=20 --date=unix %s", filterPathArg)) cmd := c.OSCommand.ExecutableFromString(fmt.Sprintf("git reflog --abbrev=20 --date=unix %s", filterPathArg))
onlyObtainedNewReflogCommits := false onlyObtainedNewReflogCommits := false
err := RunLineOutputCmd(cmd, func(line string) (bool, error) { err := oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) {
match := re.FindStringSubmatch(line) match := re.FindStringSubmatch(line)
if len(match) <= 1 { if len(match) <= 1 {
return false, nil return false, nil

View File

@ -12,9 +12,12 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
gogit "github.com/go-git/go-git/v5" gogit "github.com/go-git/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/models" "github.com/jesseduffield/lazygit/pkg/models"
"github.com/jesseduffield/lazygit/pkg/test" "github.com/jesseduffield/lazygit/pkg/test"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -251,7 +254,7 @@ func TestNewGitCommand(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
s.setup() s.setup()
s.test(NewGitCommand(NewDummyLog(), NewDummyOSCommand(), i18n.NewLocalizer(NewDummyLog()), NewDummyAppConfig())) s.test(NewGitCommand(utils.NewDummyLog(), oscommands.NewDummyOSCommand(), i18n.NewLocalizer(utils.NewDummyLog()), config.NewDummyAppConfig()))
}) })
} }
} }

View File

@ -1,20 +0,0 @@
// +build !windows
package commands
import (
"runtime"
)
func getPlatform() *Platform {
return &Platform{
os: runtime.GOOS,
catCmd: "cat",
shell: "bash",
shellArg: "-c",
escapedQuote: "'",
openCommand: "open {{filename}}",
openLinkCommand: "open {{link}}",
fallbackEscapedQuote: "\"",
}
}

View File

@ -0,0 +1,11 @@
package oscommands
import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// NewDummyOSCommand creates a new dummy OSCommand for testing
func NewDummyOSCommand() *OSCommand {
return NewOSCommand(utils.NewDummyLog(), config.NewDummyAppConfig())
}

View File

@ -1,6 +1,6 @@
// +build !windows // +build !windows
package commands package oscommands
import ( import (
"bufio" "bufio"

View File

@ -1,6 +1,6 @@
// +build windows // +build windows
package commands package oscommands
// RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there // RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there
// TODO: Remove this hack and replace it with a proper way to run commands live on windows // TODO: Remove this hack and replace it with a proper way to run commands live on windows

View File

@ -1,4 +1,4 @@
package commands package oscommands
import ( import (
"bufio" "bufio"
@ -24,14 +24,14 @@ import (
// Platform stores the os state // Platform stores the os state
type Platform struct { type Platform struct {
os string OS string
catCmd string CatCmd string
shell string Shell string
shellArg string ShellArg string
escapedQuote string EscapedQuote string
openCommand string OpenCommand string
openLinkCommand string OpenLinkCommand string
fallbackEscapedQuote string FallbackEscapedQuote string
} }
// OSCommand holds all the os commands // OSCommand holds all the os commands
@ -39,10 +39,10 @@ type OSCommand struct {
Log *logrus.Entry Log *logrus.Entry
Platform *Platform Platform *Platform
Config config.AppConfigurer Config config.AppConfigurer
command func(string, ...string) *exec.Cmd Command func(string, ...string) *exec.Cmd
beforeExecuteCmd func(*exec.Cmd) BeforeExecuteCmd func(*exec.Cmd)
getGlobalGitConfig func(string) (string, error) GetGlobalGitConfig func(string) (string, error)
getenv func(string) string Getenv func(string) string
} }
// NewOSCommand os command runner // NewOSCommand os command runner
@ -51,21 +51,21 @@ func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
Log: log, Log: log,
Platform: getPlatform(), Platform: getPlatform(),
Config: config, Config: config,
command: exec.Command, Command: exec.Command,
beforeExecuteCmd: func(*exec.Cmd) {}, BeforeExecuteCmd: func(*exec.Cmd) {},
getGlobalGitConfig: gitconfig.Global, GetGlobalGitConfig: gitconfig.Global,
getenv: os.Getenv, Getenv: os.Getenv,
} }
} }
// SetCommand sets the command function used by the struct. // SetCommand sets the command function used by the struct.
// To be used for testing only // To be used for testing only
func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) { func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) {
c.command = cmd c.Command = cmd
} }
func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) { func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) {
c.beforeExecuteCmd = cmd c.BeforeExecuteCmd = cmd
} }
type RunCommandOptions struct { type RunCommandOptions struct {
@ -102,7 +102,7 @@ func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...inte
// RunExecutableWithOutput runs an executable file and returns its output // RunExecutableWithOutput runs an executable file and returns its output
func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) { func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) {
c.beforeExecuteCmd(cmd) c.BeforeExecuteCmd(cmd)
return sanitisedCommandOutput(cmd.CombinedOutput()) return sanitisedCommandOutput(cmd.CombinedOutput())
} }
@ -115,7 +115,7 @@ func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
// ExecutableFromString takes a string like `git status` and returns an executable command for it // ExecutableFromString takes a string like `git status` and returns an executable command for it
func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd { func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
splitCmd := str.ToArgv(commandStr) splitCmd := str.ToArgv(commandStr)
cmd := c.command(splitCmd[0], splitCmd[1:]...) cmd := c.Command(splitCmd[0], splitCmd[1:]...)
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
return cmd return cmd
} }
@ -124,13 +124,13 @@ func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
func (c *OSCommand) ShellCommandFromString(commandStr string) *exec.Cmd { func (c *OSCommand) ShellCommandFromString(commandStr string) *exec.Cmd {
quotedCommand := "" quotedCommand := ""
// Windows does not seem to like quotes around the command // Windows does not seem to like quotes around the command
if c.Platform.os == "windows" { if c.Platform.OS == "windows" {
quotedCommand = commandStr quotedCommand = commandStr
} else { } else {
quotedCommand = strconv.Quote(commandStr) quotedCommand = strconv.Quote(commandStr)
} }
shellCommand := fmt.Sprintf("%s %s %s", c.Platform.shell, c.Platform.shellArg, quotedCommand) shellCommand := fmt.Sprintf("%s %s %s", c.Platform.Shell, c.Platform.ShellArg, quotedCommand)
return c.ExecutableFromString(shellCommand) return c.ExecutableFromString(shellCommand)
} }
@ -188,7 +188,7 @@ func (c *OSCommand) RunDirectCommand(command string) (string, error) {
c.Log.WithField("command", command).Info("RunDirectCommand") c.Log.WithField("command", command).Info("RunDirectCommand")
return sanitisedCommandOutput( return sanitisedCommandOutput(
c.command(c.Platform.shell, c.Platform.shellArg, command). c.Command(c.Platform.Shell, c.Platform.ShellArg, command).
CombinedOutput(), CombinedOutput(),
) )
} }
@ -199,7 +199,7 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
// errors like 'exit status 1' are not very useful so we'll create an error // errors like 'exit status 1' are not very useful so we'll create an error
// from the combined output // from the combined output
if outputString == "" { if outputString == "" {
return "", WrapError(err) return "", utils.WrapError(err)
} }
return outputString, errors.New(outputString) return outputString, errors.New(outputString)
} }
@ -233,13 +233,13 @@ func (c *OSCommand) OpenLink(link string) error {
// EditFile opens a file in a subprocess using whatever editor is available, // EditFile opens a file in a subprocess using whatever editor is available,
// falling back to core.editor, VISUAL, EDITOR, then vi // falling back to core.editor, VISUAL, EDITOR, then vi
func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) { func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
editor, _ := c.getGlobalGitConfig("core.editor") editor, _ := c.GetGlobalGitConfig("core.editor")
if editor == "" { if editor == "" {
editor = c.getenv("VISUAL") editor = c.Getenv("VISUAL")
} }
if editor == "" { if editor == "" {
editor = c.getenv("EDITOR") editor = c.Getenv("EDITOR")
} }
if editor == "" { if editor == "" {
if err := c.RunCommand("which vi"); err == nil { if err := c.RunCommand("which vi"); err == nil {
@ -258,7 +258,7 @@ func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it // PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it
// TODO: see if this needs to exist, given that ExecutableFromString does the same things // TODO: see if this needs to exist, given that ExecutableFromString does the same things
func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd { func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd {
cmd := c.command(cmdName, commandArgs...) cmd := c.Command(cmdName, commandArgs...)
if cmd != nil { if cmd != nil {
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
} }
@ -268,9 +268,9 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *ex
// Quote wraps a message in platform-specific quotation marks // Quote wraps a message in platform-specific quotation marks
func (c *OSCommand) Quote(message string) string { func (c *OSCommand) Quote(message string) string {
message = strings.Replace(message, "`", "\\`", -1) message = strings.Replace(message, "`", "\\`", -1)
escapedQuote := c.Platform.escapedQuote escapedQuote := c.Platform.EscapedQuote
if strings.Contains(message, c.Platform.escapedQuote) { if strings.Contains(message, c.Platform.EscapedQuote) {
escapedQuote = c.Platform.fallbackEscapedQuote escapedQuote = c.Platform.FallbackEscapedQuote
} }
return escapedQuote + message + escapedQuote return escapedQuote + message + escapedQuote
} }
@ -285,13 +285,13 @@ func (c *OSCommand) Unquote(message string) string {
func (c *OSCommand) AppendLineToFile(filename, line string) error { func (c *OSCommand) AppendLineToFile(filename, line string) error {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil { if err != nil {
return WrapError(err) return utils.WrapError(err)
} }
defer f.Close() defer f.Close()
_, err = f.WriteString("\n" + line) _, err = f.WriteString("\n" + line)
if err != nil { if err != nil {
return WrapError(err) return utils.WrapError(err)
} }
return nil return nil
} }
@ -301,16 +301,16 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
tmpfile, err := ioutil.TempFile("", filename) tmpfile, err := ioutil.TempFile("", filename)
if err != nil { if err != nil {
c.Log.Error(err) c.Log.Error(err)
return "", WrapError(err) return "", utils.WrapError(err)
} }
if _, err := tmpfile.WriteString(content); err != nil { if _, err := tmpfile.WriteString(content); err != nil {
c.Log.Error(err) c.Log.Error(err)
return "", WrapError(err) return "", utils.WrapError(err)
} }
if err := tmpfile.Close(); err != nil { if err := tmpfile.Close(); err != nil {
c.Log.Error(err) c.Log.Error(err)
return "", WrapError(err) return "", utils.WrapError(err)
} }
return tmpfile.Name(), nil return tmpfile.Name(), nil
@ -325,7 +325,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil { if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil {
c.Log.Error(err) c.Log.Error(err)
return WrapError(err) return utils.WrapError(err)
} }
return nil return nil
@ -334,7 +334,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
// Remove removes a file or directory at the specified path // Remove removes a file or directory at the specified path
func (c *OSCommand) Remove(filename string) error { func (c *OSCommand) Remove(filename string) error {
err := os.RemoveAll(filename) err := os.RemoveAll(filename)
return WrapError(err) return utils.WrapError(err)
} }
// FileExists checks whether a file exists at the specified path // FileExists checks whether a file exists at the specified path
@ -352,7 +352,7 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
// this is useful if you need to give your command some environment variables // this is useful if you need to give your command some environment variables
// before running it // before running it
func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error { func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error {
c.beforeExecuteCmd(cmd) c.BeforeExecuteCmd(cmd)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
outString := string(out) outString := string(out)
c.Log.Info(outString) c.Log.Info(outString)
@ -376,7 +376,7 @@ func (c *OSCommand) GetLazygitPath() string {
// RunCustomCommand returns the pointer to a custom command // RunCustomCommand returns the pointer to a custom command
func (c *OSCommand) RunCustomCommand(command string) *exec.Cmd { func (c *OSCommand) RunCustomCommand(command string) *exec.Cmd {
return c.PrepareSubProcess(c.Platform.shell, c.Platform.shellArg, command) return c.PrepareSubProcess(c.Platform.Shell, c.Platform.ShellArg, command)
} }
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C // PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C

View File

@ -0,0 +1,20 @@
// +build !windows
package oscommands
import (
"runtime"
)
func getPlatform() *Platform {
return &Platform{
OS: runtime.GOOS,
CatCmd: "cat",
Shell: "bash",
ShellArg: "-c",
EscapedQuote: "'",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
FallbackEscapedQuote: "\"",
}
}

View File

@ -1,4 +1,4 @@
package commands package oscommands
import ( import (
"io/ioutil" "io/ioutil"
@ -102,7 +102,7 @@ func TestOSCommandOpenFile(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
OSCmd := NewDummyOSCommand() OSCmd := NewDummyOSCommand()
OSCmd.command = s.command OSCmd.Command = s.command
OSCmd.Config.GetUserConfig().Set("os.openCommand", "open {{filename}}") OSCmd.Config.GetUserConfig().Set("os.openCommand", "open {{filename}}")
s.test(OSCmd.OpenFile(s.filename)) s.test(OSCmd.OpenFile(s.filename))
@ -231,9 +231,9 @@ func TestOSCommandEditFile(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
OSCmd := NewDummyOSCommand() OSCmd := NewDummyOSCommand()
OSCmd.command = s.command OSCmd.Command = s.command
OSCmd.getGlobalGitConfig = s.getGlobalGitConfig OSCmd.GetGlobalGitConfig = s.getGlobalGitConfig
OSCmd.getenv = s.getenv OSCmd.Getenv = s.getenv
s.test(OSCmd.EditFile(s.filename)) s.test(OSCmd.EditFile(s.filename))
} }
@ -245,7 +245,7 @@ func TestOSCommandQuote(t *testing.T) {
actual := osCommand.Quote("hello `test`") actual := osCommand.Quote("hello `test`")
expected := osCommand.Platform.escapedQuote + "hello \\`test\\`" + osCommand.Platform.escapedQuote expected := osCommand.Platform.EscapedQuote + "hello \\`test\\`" + osCommand.Platform.EscapedQuote
assert.EqualValues(t, expected, actual) assert.EqualValues(t, expected, actual)
} }
@ -254,11 +254,11 @@ func TestOSCommandQuote(t *testing.T) {
func TestOSCommandQuoteSingleQuote(t *testing.T) { func TestOSCommandQuoteSingleQuote(t *testing.T) {
osCommand := NewDummyOSCommand() osCommand := NewDummyOSCommand()
osCommand.Platform.os = "linux" osCommand.Platform.OS = "linux"
actual := osCommand.Quote("hello 'test'") actual := osCommand.Quote("hello 'test'")
expected := osCommand.Platform.fallbackEscapedQuote + "hello 'test'" + osCommand.Platform.fallbackEscapedQuote expected := osCommand.Platform.FallbackEscapedQuote + "hello 'test'" + osCommand.Platform.FallbackEscapedQuote
assert.EqualValues(t, expected, actual) assert.EqualValues(t, expected, actual)
} }
@ -267,11 +267,11 @@ func TestOSCommandQuoteSingleQuote(t *testing.T) {
func TestOSCommandQuoteDoubleQuote(t *testing.T) { func TestOSCommandQuoteDoubleQuote(t *testing.T) {
osCommand := NewDummyOSCommand() osCommand := NewDummyOSCommand()
osCommand.Platform.os = "linux" osCommand.Platform.OS = "linux"
actual := osCommand.Quote(`hello "test"`) actual := osCommand.Quote(`hello "test"`)
expected := osCommand.Platform.escapedQuote + "hello \"test\"" + osCommand.Platform.escapedQuote expected := osCommand.Platform.EscapedQuote + "hello \"test\"" + osCommand.Platform.EscapedQuote
assert.EqualValues(t, expected, actual) assert.EqualValues(t, expected, actual)
} }

View File

@ -1,4 +1,4 @@
package commands package oscommands
func getPlatform() *Platform { func getPlatform() *Platform {
return &Platform{ return &Platform{
@ -7,6 +7,6 @@ func getPlatform() *Platform {
shell: "cmd", shell: "cmd",
shellArg: "/c", shellArg: "/c",
escapedQuote: `\"`, escapedQuote: `\"`,
fallbackEscapedQuote: "\\'", FallbackEscapedQuote: "\\'",
} }
} }

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/models"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

26
pkg/config/dummies.go Normal file
View File

@ -0,0 +1,26 @@
package config
import (
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
)
// NewDummyAppConfig creates a new dummy AppConfig for testing
func NewDummyAppConfig() *AppConfig {
userConfig := viper.New()
userConfig.SetConfigType("yaml")
if err := LoadDefaults(userConfig, GetDefaultConfig()); err != nil {
panic(err)
}
appConfig := &AppConfig{
Name: "lazygit",
Version: "unversioned",
Commit: "",
BuildDate: "",
Debug: false,
BuildSource: "",
UserConfig: userConfig,
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
return appConfig
}

View File

@ -22,6 +22,7 @@ import (
"github.com/golang-collections/collections/stack" "github.com/golang-collections/collections/stack"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch" "github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
@ -90,7 +91,7 @@ type Gui struct {
g *gocui.Gui g *gocui.Gui
Log *logrus.Entry Log *logrus.Entry
GitCommand *commands.GitCommand GitCommand *commands.GitCommand
OSCommand *commands.OSCommand OSCommand *oscommands.OSCommand
SubProcess *exec.Cmd SubProcess *exec.Cmd
State *guiState State *guiState
Config config.AppConfigurer Config config.AppConfigurer
@ -384,7 +385,7 @@ func (gui *Gui) resetState() {
// for now the split view will always be on // for now the split view will always be on
// NewGui builds a new gui handler // NewGui builds a new gui handler
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater, filterPath string, showRecentRepos bool) (*Gui, error) { func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *oscommands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater, filterPath string, showRecentRepos bool) (*Gui, error) {
gui := &Gui{ gui := &Gui{
Log: log, Log: log,
GitCommand: gitCommand, GitCommand: gitCommand,

View File

@ -4,10 +4,10 @@ import (
"fmt" "fmt"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
) )
func (gui *Gui) resetToRef(ref string, strength string, options commands.RunCommandOptions) error { func (gui *Gui) resetToRef(ref string, strength string, options oscommands.RunCommandOptions) error {
if err := gui.GitCommand.ResetToCommit(ref, strength, options); err != nil { if err := gui.GitCommand.ResetToCommit(ref, strength, options); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
@ -41,7 +41,7 @@ func (gui *Gui) createResetMenu(ref string) error {
), ),
}, },
onPress: func() error { onPress: func() error {
return gui.resetToRef(ref, strength, commands.RunCommandOptions{}) return gui.resetToRef(ref, strength, oscommands.RunCommandOptions{})
}, },
} }
} }

View File

@ -2,7 +2,7 @@ package gui
import ( import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -156,7 +156,7 @@ type handleHardResetWithAutoStashOptions struct {
// only to be used in the undo flow for now // only to be used in the undo flow for now
func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error { func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHardResetWithAutoStashOptions) error {
reset := func() error { reset := func() error {
if err := gui.resetToRef(commitSha, "hard", commands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil { if err := gui.resetToRef(commitSha, "hard", oscommands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
return nil return nil

View File

@ -9,7 +9,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -50,7 +50,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, linesToRead i
return func(stop chan struct{}) error { return func(stop chan struct{}) error {
go func() { go func() {
<-stop <-stop
if err := commands.Kill(cmd); err != nil { if err := oscommands.Kill(cmd); err != nil {
if !strings.Contains(err.Error(), "process already finished") { if !strings.Contains(err.Error(), "process already finished") {
m.Log.Errorf("error when running cmd task: %v", err) m.Log.Errorf("error when running cmd task: %v", err)
} }

View File

@ -1,11 +1,12 @@
package test package test
import ( import (
"github.com/go-errors/errors"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )

View File

@ -15,7 +15,7 @@ import (
"github.com/kardianos/osext" "github.com/kardianos/osext"
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -25,7 +25,7 @@ import (
type Updater struct { type Updater struct {
Log *logrus.Entry Log *logrus.Entry
Config config.AppConfigurer Config config.AppConfigurer
OSCommand *commands.OSCommand OSCommand *oscommands.OSCommand
Tr *i18n.Localizer Tr *i18n.Localizer
} }
@ -40,7 +40,7 @@ const (
) )
// NewUpdater creates a new updater // NewUpdater creates a new updater
func NewUpdater(log *logrus.Entry, config config.AppConfigurer, osCommand *commands.OSCommand, tr *i18n.Localizer) (*Updater, error) { func NewUpdater(log *logrus.Entry, config config.AppConfigurer, osCommand *oscommands.OSCommand, tr *i18n.Localizer) (*Updater, error) {
contextLogger := log.WithField("context", "updates") contextLogger := log.WithField("context", "updates")
return &Updater{ return &Updater{

14
pkg/utils/dummies.go Normal file
View File

@ -0,0 +1,14 @@
package utils
import (
"io/ioutil"
"github.com/sirupsen/logrus"
)
// NewDummyLog creates a new dummy Log for testing
func NewDummyLog() *logrus.Entry {
log := logrus.New()
log.Out = ioutil.Discard
return log.WithField("test", "test")
}

View File

@ -1,4 +1,4 @@
package commands package utils
import "github.com/go-errors/errors" import "github.com/go-errors/errors"