mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-12 12:55:47 +02:00
start breaking up git struct
This commit is contained in:
parent
4a1d23dc27
commit
f503ff1ecb
76 changed files with 2234 additions and 1758 deletions
|
@ -34,6 +34,11 @@ type ICmdObj interface {
|
|||
|
||||
// This returns false if DontLog() was called
|
||||
ShouldLog() bool
|
||||
|
||||
PromptOnCredentialRequest() ICmdObj
|
||||
FailOnCredentialRequest() ICmdObj
|
||||
|
||||
GetCredentialStrategy() CredentialStrategy
|
||||
}
|
||||
|
||||
type CmdObj struct {
|
||||
|
@ -44,8 +49,28 @@ type CmdObj struct {
|
|||
|
||||
// if set to true, we don't want to log the command to the user.
|
||||
dontLog bool
|
||||
|
||||
// if set to true, it means we might be asked to enter a username/password by this command.
|
||||
credentialStrategy CredentialStrategy
|
||||
}
|
||||
|
||||
type CredentialStrategy int
|
||||
|
||||
const (
|
||||
// do not expect a credential request. If we end up getting one
|
||||
// we'll be in trouble because the command will hang indefinitely
|
||||
NONE CredentialStrategy = iota
|
||||
// expect a credential request and if we get one, prompt the user to enter their username/password
|
||||
PROMPT
|
||||
// in this case we will check for a credential request (i.e. the command pauses to ask for
|
||||
// username/password) and if we get one, we just submit a newline, forcing the
|
||||
// command to fail. We use this e.g. for a background `git fetch` to prevent it
|
||||
// from hanging indefinitely.
|
||||
FAIL
|
||||
)
|
||||
|
||||
var _ ICmdObj = &CmdObj{}
|
||||
|
||||
func (self *CmdObj) GetCmd() *exec.Cmd {
|
||||
return self.cmd
|
||||
}
|
||||
|
@ -84,3 +109,19 @@ func (self *CmdObj) RunWithOutput() (string, error) {
|
|||
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
|
||||
return self.runner.RunAndProcessLines(self, onLine)
|
||||
}
|
||||
|
||||
func (self *CmdObj) PromptOnCredentialRequest() ICmdObj {
|
||||
self.credentialStrategy = PROMPT
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
|
||||
self.credentialStrategy = FAIL
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) GetCredentialStrategy() CredentialStrategy {
|
||||
return self.credentialStrategy
|
||||
}
|
||||
|
|
|
@ -15,18 +15,46 @@ type ICmdObjRunner interface {
|
|||
}
|
||||
|
||||
type cmdObjRunner struct {
|
||||
log *logrus.Entry
|
||||
logCmdObj func(ICmdObj)
|
||||
log *logrus.Entry
|
||||
guiIO *guiIO
|
||||
}
|
||||
|
||||
var _ ICmdObjRunner = &cmdObjRunner{}
|
||||
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
switch cmdObj.GetCredentialStrategy() {
|
||||
case PROMPT:
|
||||
return self.RunCommandWithOutputLive(cmdObj, self.guiIO.promptForCredentialFn)
|
||||
case FAIL:
|
||||
return self.RunCommandWithOutputLive(cmdObj, func(s string) string { return "\n" })
|
||||
}
|
||||
|
||||
// we should never land here
|
||||
return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy")
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
||||
_, err := self.RunWithOutput(cmdObj)
|
||||
return err
|
||||
if cmdObj.GetCredentialStrategy() == NONE {
|
||||
_, err := self.RunWithOutput(cmdObj)
|
||||
return err
|
||||
} else {
|
||||
return self.runWithCredentialHandling(cmdObj)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
// for now we're not capturing output, just because it would take a little more
|
||||
// effort and there's currently no use case for it. Some commands call RunWithOutput
|
||||
// but ignore the output, hence why we've got this check here.
|
||||
return "", err
|
||||
}
|
||||
|
||||
if cmdObj.ShouldLog() {
|
||||
self.logCmdObj(cmdObj)
|
||||
}
|
||||
|
@ -39,6 +67,10 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
|||
}
|
||||
|
||||
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
if cmdObj.GetCredentialStrategy() != NONE {
|
||||
return errors.New("cannot call RunAndProcessLines with credential strategy. If you're seeing this then a contributor to Lazygit has accidentally called this method! Please raise an issue")
|
||||
}
|
||||
|
||||
if cmdObj.ShouldLog() {
|
||||
self.logCmdObj(cmdObj)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
|
||||
// NewDummyOSCommand creates a new dummy OSCommand for testing
|
||||
func NewDummyOSCommand() *OSCommand {
|
||||
osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform)
|
||||
osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
|
||||
|
||||
return osCmd
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ var dummyPlatform = &Platform{
|
|||
}
|
||||
|
||||
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
|
||||
osCommand := NewOSCommand(utils.NewDummyCommon(), dummyPlatform)
|
||||
osCommand := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
|
||||
osCommand.Cmd = NewDummyCmdObjBuilder(runner)
|
||||
|
||||
return osCommand
|
||||
|
|
|
@ -13,12 +13,12 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// DetectUnamePass detect a username / password / passphrase question in a command
|
||||
// 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 (c *OSCommand) DetectUnamePass(cmdObj ICmdObj, writer io.Writer, promptUserForCredential func(string) string) error {
|
||||
func (self *cmdObjRunner) RunAndDetectCredentialRequest(cmdObj ICmdObj, promptUserForCredential func(string) string) error {
|
||||
ttyText := ""
|
||||
errMessage := c.RunCommandWithOutputLive(cmdObj, writer, func(word string) string {
|
||||
err := self.RunCommandWithOutputLive(cmdObj, func(word string) string {
|
||||
ttyText = ttyText + " " + word
|
||||
|
||||
prompts := map[string]string{
|
||||
|
@ -37,13 +37,7 @@ func (c *OSCommand) DetectUnamePass(cmdObj ICmdObj, writer io.Writer, promptUser
|
|||
|
||||
return ""
|
||||
})
|
||||
return errMessage
|
||||
}
|
||||
|
||||
// Due to a lack of pty support on windows we have RunCommandWithOutputLiveWrapper being defined
|
||||
// separate for windows and other OS's
|
||||
func (c *OSCommand) RunCommandWithOutputLive(cmdObj ICmdObj, writer io.Writer, handleOutput func(string) string) error {
|
||||
return RunCommandWithOutputLiveWrapper(c, cmdObj, writer, handleOutput)
|
||||
return err
|
||||
}
|
||||
|
||||
type cmdHandler struct {
|
||||
|
@ -56,23 +50,22 @@ type cmdHandler struct {
|
|||
// Output is a function that executes by every word that gets read by bufio
|
||||
// As return of output you need to give a string that will be written to stdin
|
||||
// NOTE: If the return data is empty it won't write anything to stdin
|
||||
func RunCommandWithOutputLiveAux(
|
||||
c *OSCommand,
|
||||
func (self *cmdObjRunner) RunCommandWithOutputLiveAux(
|
||||
cmdObj ICmdObj,
|
||||
writer io.Writer,
|
||||
// handleOutput takes a word from stdout and returns a string to be written to stdin.
|
||||
// See DetectUnamePass above for how this is used to check for a username/password request
|
||||
// See RunAndDetectCredentialRequest above for how this is used to check for a username/password request
|
||||
handleOutput func(string) string,
|
||||
startCmd func(cmd *exec.Cmd) (*cmdHandler, error),
|
||||
) error {
|
||||
c.Log.WithField("command", cmdObj.ToString()).Info("RunCommand")
|
||||
cmdWriter := self.guiIO.newCmdWriterFn()
|
||||
self.log.WithField("command", cmdObj.ToString()).Info("RunCommand")
|
||||
if cmdObj.ShouldLog() {
|
||||
c.LogCommand(cmdObj.ToString(), true)
|
||||
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(writer, &stderr)
|
||||
cmd.Stderr = io.MultiWriter(cmdWriter, &stderr)
|
||||
|
||||
handler, err := startCmd(cmd)
|
||||
if err != nil {
|
||||
|
@ -81,11 +74,11 @@ func RunCommandWithOutputLiveAux(
|
|||
|
||||
defer func() {
|
||||
if closeErr := handler.close(); closeErr != nil {
|
||||
c.Log.Error(closeErr)
|
||||
self.log.Error(closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
tr := io.TeeReader(handler.stdoutPipe, writer)
|
||||
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
|
||||
|
||||
go utils.Safe(func() {
|
||||
scanner := bufio.NewScanner(tr)
|
||||
|
|
|
@ -4,22 +4,19 @@
|
|||
package oscommands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
func RunCommandWithOutputLiveWrapper(
|
||||
c *OSCommand,
|
||||
// we define this separately for windows and non-windows given that windows does
|
||||
// not have great PTY support and we need a PTY to handle a credential request
|
||||
func (self *cmdObjRunner) RunCommandWithOutputLive(
|
||||
cmdObj ICmdObj,
|
||||
writer io.Writer,
|
||||
output func(string) string,
|
||||
) error {
|
||||
return RunCommandWithOutputLiveAux(
|
||||
c,
|
||||
return self.RunCommandWithOutputLiveAux(
|
||||
cmdObj,
|
||||
writer,
|
||||
output,
|
||||
func(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
ptmx, err := pty.Start(cmd)
|
||||
|
|
|
@ -26,18 +26,14 @@ func (b *Buffer) Write(p []byte) (n int, err error) {
|
|||
return b.b.Write(p)
|
||||
}
|
||||
|
||||
// RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there
|
||||
// RunCommandWithOutputLive 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. We still have an issue where if a password is requested, the request for a password is written straight to stdout because we can't control the stdout of a subprocess of a subprocess. Keep an eye on https://github.com/creack/pty/pull/109
|
||||
func RunCommandWithOutputLiveWrapper(
|
||||
c *OSCommand,
|
||||
func (self *cmdObjRunner) RunCommandWithOutputLive(
|
||||
cmdObj ICmdObj,
|
||||
writer io.Writer,
|
||||
output func(string) string,
|
||||
) error {
|
||||
return RunCommandWithOutputLiveAux(
|
||||
c,
|
||||
return self.RunCommandWithOutputLiveAux(
|
||||
cmdObj,
|
||||
writer,
|
||||
output,
|
||||
func(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
|
|
49
pkg/commands/oscommands/gui_io.go
Normal file
49
pkg/commands/oscommands/gui_io.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package oscommands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// this struct captures some IO stuff
|
||||
type guiIO struct {
|
||||
// this is for logging anything we want. It'll be written to a log file for the sake
|
||||
// of debugging.
|
||||
log *logrus.Entry
|
||||
|
||||
// this is for us to log the command we're about to run e.g. 'git push'. The GUI
|
||||
// will write this to a log panel so that the user can see which commands are being
|
||||
// run.
|
||||
// The isCommandLineCommand arg is there so that we can style the log differently
|
||||
// depending on whether we're directly outputting a command we're about to run that
|
||||
// will be run on the command line, or if we're using something from Go's standard lib.
|
||||
logCommandFn func(str string, isCommandLineCommand bool)
|
||||
// this is for us to directly write the output of a command. We will do this for
|
||||
// certain commands like 'git push'. The GUI will write this to a command output panel.
|
||||
// We need a new cmd writer per command, hence it being a function.
|
||||
newCmdWriterFn func() io.Writer
|
||||
// this allows us to request info from the user like username/password, in the event
|
||||
// that a command requests it.
|
||||
// the 'credential' arg is something like 'username' or 'password'
|
||||
promptForCredentialFn func(credential string) string
|
||||
}
|
||||
|
||||
func NewGuiIO(log *logrus.Entry, logCommandFn func(string, bool), newCmdWriterFn func() io.Writer, promptForCredentialFn func(string) string) *guiIO {
|
||||
return &guiIO{
|
||||
log: log,
|
||||
logCommandFn: logCommandFn,
|
||||
newCmdWriterFn: newCmdWriterFn,
|
||||
promptForCredentialFn: promptForCredentialFn,
|
||||
}
|
||||
}
|
||||
|
||||
func NewNullGuiIO(log *logrus.Entry) *guiIO {
|
||||
return &guiIO{
|
||||
log: log,
|
||||
logCommandFn: func(string, bool) {},
|
||||
newCmdWriterFn: func() io.Writer { return ioutil.Discard },
|
||||
promptForCredentialFn: func(string) string { return "" },
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ import (
|
|||
type OSCommand struct {
|
||||
*common.Common
|
||||
Platform *Platform
|
||||
Getenv func(string) string
|
||||
GetenvFn func(string) string
|
||||
|
||||
// callback to run before running a command, i.e. for the purposes of logging.
|
||||
// the string argument is the command string e.g. 'git add .' and the bool is
|
||||
|
@ -43,24 +43,20 @@ type Platform struct {
|
|||
}
|
||||
|
||||
// NewOSCommand os command runner
|
||||
func NewOSCommand(common *common.Common, platform *Platform) *OSCommand {
|
||||
func NewOSCommand(common *common.Common, platform *Platform, guiIO *guiIO) *OSCommand {
|
||||
c := &OSCommand{
|
||||
Common: common,
|
||||
Platform: platform,
|
||||
Getenv: os.Getenv,
|
||||
GetenvFn: os.Getenv,
|
||||
removeFile: os.RemoveAll,
|
||||
}
|
||||
|
||||
runner := &cmdObjRunner{log: common.Log, logCmdObj: c.LogCmdObj}
|
||||
runner := &cmdObjRunner{log: common.Log, guiIO: guiIO}
|
||||
c.Cmd = &CmdObjBuilder{runner: runner, platform: platform}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *OSCommand) LogCmdObj(cmdObj ICmdObj) {
|
||||
c.LogCommand(cmdObj.ToString(), true)
|
||||
}
|
||||
|
||||
func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) {
|
||||
c.Log.WithField("command", cmdStr).Info("RunCommand")
|
||||
|
||||
|
@ -270,6 +266,10 @@ func (c *OSCommand) RemoveFile(path string) error {
|
|||
return c.removeFile(path)
|
||||
}
|
||||
|
||||
func (c *OSCommand) Getenv(key string) string {
|
||||
return c.GetenvFn(key)
|
||||
}
|
||||
|
||||
func GetTempDir() string {
|
||||
return filepath.Join(os.TempDir(), "lazygit")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue