diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2022-01-09 12:56:29 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2022-01-09 14:09:53 +1100 |
commit | a936c0592ff879d2c9a194556bae568d7943c5da (patch) | |
tree | 09d1b4810ed05972fbf988dd4e0d9beb3956b929 /pkg | |
parent | 06687c8a59be6fe5466bf44ae2730354c2daa0e1 (diff) |
more refactoring
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/commands/oscommands/cmd_obj_runner.go | 150 | ||||
-rw-r--r-- | pkg/commands/oscommands/cmd_obj_runner_default.go (renamed from pkg/commands/oscommands/exec_live_default.go) | 0 | ||||
-rw-r--r-- | pkg/commands/oscommands/cmd_obj_runner_win.go (renamed from pkg/commands/oscommands/exec_live_win.go) | 0 | ||||
-rw-r--r-- | pkg/commands/oscommands/exec_live.go | 116 | ||||
-rw-r--r-- | pkg/commands/oscommands/gui_io.go | 4 |
5 files changed, 135 insertions, 135 deletions
diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go index bbd3c85cc..24ce5a73b 100644 --- a/pkg/commands/oscommands/cmd_obj_runner.go +++ b/pkg/commands/oscommands/cmd_obj_runner.go @@ -2,6 +2,10 @@ package oscommands import ( "bufio" + "bytes" + "io" + "regexp" + "strings" "github.com/go-errors/errors" "github.com/jesseduffield/lazygit/pkg/utils" @@ -14,6 +18,14 @@ type ICmdObjRunner interface { RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error } +type CredentialType int + +const ( + Password CredentialType = iota + Username + Passphrase +) + type cmdObjRunner struct { log *logrus.Entry guiIO *guiIO @@ -21,20 +33,6 @@ type cmdObjRunner struct { var _ ICmdObjRunner = &cmdObjRunner{} -func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error { - switch cmdObj.GetCredentialStrategy() { - case PROMPT: - return self.RunAndDetectCredentialRequest(cmdObj, self.guiIO.promptForCredentialFn) - case FAIL: - return self.RunAndDetectCredentialRequest(cmdObj, func(CredentialType) string { return "\n" }) - case NONE: - // we should never land here - return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy") - } - - return errors.New("unexpected credential strategy") -} - func (self *cmdObjRunner) Run(cmdObj ICmdObj) error { if cmdObj.GetCredentialStrategy() == NONE { _, err := self.RunWithOutput(cmdObj) @@ -44,10 +42,6 @@ func (self *cmdObjRunner) Run(cmdObj ICmdObj) error { } } -func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) { - self.guiIO.logCommandFn(cmdObj.ToString(), true) -} - func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) { if cmdObj.GetCredentialStrategy() != NONE { err := self.runWithCredentialHandling(cmdObj) @@ -106,6 +100,30 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st return nil } +// Whenever we're asked for a password we just enter a newline, which will +// eventually cause the command to fail. +var failPromptFn = func(CredentialType) string { return "\n" } + +func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error { + var promptFn func(CredentialType) string + + switch cmdObj.GetCredentialStrategy() { + case PROMPT: + promptFn = self.guiIO.promptForCredentialFn + case FAIL: + promptFn = failPromptFn + case NONE: + // we should never land here + return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy") + } + + return self.runAndDetectCredentialRequest(cmdObj, promptFn) +} + +func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) { + self.guiIO.logCommandFn(cmdObj.ToString(), true) +} + func sanitisedCommandOutput(output []byte, err error) (string, error) { outputString := string(output) if err != nil { @@ -118,3 +136,99 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) { } return outputString, nil } + +type cmdHandler struct { + stdoutPipe io.Reader + stdinPipe io.Writer + close func() error +} + +// runAndDetectCredentialRequest detect a username / password / passphrase question in a command +// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase +// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back +func (self *cmdObjRunner) runAndDetectCredentialRequest( + cmdObj ICmdObj, + promptUserForCredential func(CredentialType) string, +) error { + cmdWriter := self.guiIO.newCmdWriterFn() + + if cmdObj.ShouldLog() { + self.logCmdObj(cmdObj) + } + self.log.WithField("command", cmdObj.ToString()).Info("RunCommand") + cmd := cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8").GetCmd() + + var stderr bytes.Buffer + cmd.Stderr = io.MultiWriter(cmdWriter, &stderr) + + handler, err := self.getCmdHandler(cmd) + if err != nil { + return err + } + + defer func() { + if closeErr := handler.close(); closeErr != nil { + self.log.Error(closeErr) + } + }() + + tr := io.TeeReader(handler.stdoutPipe, cmdWriter) + + go utils.Safe(func() { + self.processOutput(tr, handler.stdinPipe, promptUserForCredential) + }) + + err = cmd.Wait() + if err != nil { + return errors.New(stderr.String()) + } + + return nil +} + +func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, promptUserForCredential func(CredentialType) string) { + checkForCredentialRequest := self.getCheckForCredentialRequestFunc() + + scanner := bufio.NewScanner(reader) + scanner.Split(bufio.ScanBytes) + for scanner.Scan() { + newBytes := scanner.Bytes() + askFor, ok := checkForCredentialRequest(newBytes) + if ok { + toInput := promptUserForCredential(askFor) + // If the return data is empty we don't write anything to stdin + if toInput != "" { + _, _ = writer.Write([]byte(toInput)) + } + } + } +} + +// having a function that returns a function because we need to maintain some state inbetween calls hence the closure +func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (CredentialType, bool) { + var ttyText strings.Builder + // this function takes each word of output from the command and builds up a string to see if we're being asked for a password + return func(newBytes []byte) (CredentialType, bool) { + _, err := ttyText.Write(newBytes) + if err != nil { + self.log.Error(err) + } + + prompts := map[string]CredentialType{ + `Password:`: Password, + `.+'s password:`: Password, + `Password\s*for\s*'.+':`: Password, + `Username\s*for\s*'.+':`: Username, + `Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase, + } + + for pattern, askFor := range prompts { + if match, _ := regexp.MatchString(pattern, ttyText.String()); match { + ttyText.Reset() + return askFor, true + } + } + + return 0, false + } +} diff --git a/pkg/commands/oscommands/exec_live_default.go b/pkg/commands/oscommands/cmd_obj_runner_default.go index 567dcf970..567dcf970 100644 --- a/pkg/commands/oscommands/exec_live_default.go +++ b/pkg/commands/oscommands/cmd_obj_runner_default.go diff --git a/pkg/commands/oscommands/exec_live_win.go b/pkg/commands/oscommands/cmd_obj_runner_win.go index 9e3d1fd02..9e3d1fd02 100644 --- a/pkg/commands/oscommands/exec_live_win.go +++ b/pkg/commands/oscommands/cmd_obj_runner_win.go diff --git a/pkg/commands/oscommands/exec_live.go b/pkg/commands/oscommands/exec_live.go deleted file mode 100644 index 1b3e9697c..000000000 --- a/pkg/commands/oscommands/exec_live.go +++ /dev/null @@ -1,116 +0,0 @@ -package oscommands - -import ( - "bufio" - "bytes" - "io" - "regexp" - "strings" - - "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/utils" -) - -type CredentialType int - -const ( - Password CredentialType = iota - Username - Passphrase -) - -type cmdHandler struct { - stdoutPipe io.Reader - stdinPipe io.Writer - close func() error -} - -// RunAndDetectCredentialRequest detect a username / password / passphrase question in a command -// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase -// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back -func (self *cmdObjRunner) RunAndDetectCredentialRequest( - cmdObj ICmdObj, - promptUserForCredential func(CredentialType) string, -) error { - self.log.Warn("HERE") - cmdWriter := self.guiIO.newCmdWriterFn() - self.log.WithField("command", cmdObj.ToString()).Info("RunCommand") - if cmdObj.ShouldLog() { - self.logCmdObj(cmdObj) - } - cmd := cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8").GetCmd() - - var stderr bytes.Buffer - cmd.Stderr = io.MultiWriter(cmdWriter, &stderr) - - handler, err := self.getCmdHandler(cmd) - if err != nil { - return err - } - - defer func() { - if closeErr := handler.close(); closeErr != nil { - self.log.Error(closeErr) - } - }() - - tr := io.TeeReader(handler.stdoutPipe, cmdWriter) - - go utils.Safe(func() { - self.processOutput(tr, handler.stdinPipe, promptUserForCredential) - }) - - err = cmd.Wait() - if err != nil { - return errors.New(stderr.String()) - } - - return nil -} - -func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, promptUserForCredential func(CredentialType) string) { - checkForCredentialRequest := self.getCheckForCredentialRequestFunc() - - scanner := bufio.NewScanner(reader) - scanner.Split(bufio.ScanBytes) - for scanner.Scan() { - newBytes := scanner.Bytes() - askFor, ok := checkForCredentialRequest(newBytes) - if ok { - toInput := promptUserForCredential(askFor) - // If the return data is empty we don't write anything to stdin - if toInput != "" { - _, _ = writer.Write([]byte(toInput)) - } - } - } -} - -// having a function that returns a function because we need to maintain some state inbetween calls hence the closure -func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (CredentialType, bool) { - var ttyText strings.Builder - // this function takes each word of output from the command and builds up a string to see if we're being asked for a password - return func(newBytes []byte) (CredentialType, bool) { - _, err := ttyText.Write(newBytes) - if err != nil { - self.log.Error(err) - } - - prompts := map[string]CredentialType{ - `Password:`: Password, - `.+'s password:`: Password, - `Password\s*for\s*'.+':`: Password, - `Username\s*for\s*'.+':`: Username, - `Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase, - } - - for pattern, askFor := range prompts { - if match, _ := regexp.MatchString(pattern, ttyText.String()); match { - ttyText.Reset() - return askFor, true - } - } - - return 0, false - } -} diff --git a/pkg/commands/oscommands/gui_io.go b/pkg/commands/oscommands/gui_io.go index 33a02874b..9540c13e1 100644 --- a/pkg/commands/oscommands/gui_io.go +++ b/pkg/commands/oscommands/gui_io.go @@ -39,11 +39,13 @@ func NewGuiIO(log *logrus.Entry, logCommandFn func(string, bool), newCmdWriterFn } } +// we use this function when we want to access the functionality of our OS struct but we +// don't have anywhere to log things, or request input from the user. func NewNullGuiIO(log *logrus.Entry) *guiIO { return &guiIO{ log: log, logCommandFn: func(string, bool) {}, newCmdWriterFn: func() io.Writer { return ioutil.Discard }, - promptForCredentialFn: func(CredentialType) string { return "" }, + promptForCredentialFn: failPromptFn, } } |