mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-31 14:24:25 +03:00
Merge branch 'master' into feature/rebasing
This commit is contained in:
100
pkg/commands/exec_live_default.go
Normal file
100
pkg/commands/exec_live_default.go
Normal file
@ -0,0 +1,100 @@
|
||||
// +build !windows
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jesseduffield/pty"
|
||||
"github.com/mgutz/str"
|
||||
)
|
||||
|
||||
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
|
||||
// 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 written anything to stdin
|
||||
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||
splitCmd := str.ToArgv(command)
|
||||
cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
ptmx, err := pty.Start(cmd)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(ptmx)
|
||||
scanner.Split(scanWordsWithNewLines)
|
||||
for scanner.Scan() {
|
||||
toOutput := strings.Trim(scanner.Text(), " ")
|
||||
_, _ = ptmx.WriteString(output(toOutput))
|
||||
}
|
||||
}()
|
||||
|
||||
err = cmd.Wait()
|
||||
ptmx.Close()
|
||||
if err != nil {
|
||||
return errors.New(stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines
|
||||
// For specific comments about this function take a look at: bufio.ScanWords
|
||||
func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
start := 0
|
||||
for width := 0; start < len(data); start += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[start:])
|
||||
if !isSpace(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for width, i := 0, start; i < len(data); i += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[i:])
|
||||
if isSpace(r) {
|
||||
return i + width, data[start:i], nil
|
||||
}
|
||||
}
|
||||
if atEOF && len(data) > start {
|
||||
return len(data), data[start:], nil
|
||||
}
|
||||
return start, nil, nil
|
||||
}
|
||||
|
||||
// isSpace is also copied from the bufio package and has been modified to also captures new lines
|
||||
// For specific comments about this function take a look at: bufio.isSpace
|
||||
func isSpace(r rune) bool {
|
||||
if r <= '\u00FF' {
|
||||
switch r {
|
||||
case ' ', '\t', '\v', '\f':
|
||||
return true
|
||||
case '\u0085', '\u00A0':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if '\u2000' <= r && r <= '\u200a' {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
9
pkg/commands/exec_live_win.go
Normal file
9
pkg/commands/exec_live_win.go
Normal file
@ -0,0 +1,9 @@
|
||||
// +build windows
|
||||
|
||||
package commands
|
||||
|
||||
// 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
|
||||
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||
return c.RunCommand(command)
|
||||
}
|
@ -294,8 +294,13 @@ func (c *GitCommand) AbortMergeBranch() error {
|
||||
}
|
||||
|
||||
// Fetch fetch git repo
|
||||
func (c *GitCommand) Fetch() error {
|
||||
return c.OSCommand.RunCommand("git fetch")
|
||||
func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canAskForCredentials bool) error {
|
||||
return c.OSCommand.DetectUnamePass("git fetch", func(question string) string {
|
||||
if canAskForCredentials {
|
||||
return unamePassQuestion(question)
|
||||
}
|
||||
return "\n"
|
||||
})
|
||||
}
|
||||
|
||||
// ResetToCommit reset to commit
|
||||
@ -373,18 +378,19 @@ func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) {
|
||||
}
|
||||
|
||||
// Pull pulls from repo
|
||||
func (c *GitCommand) Pull() error {
|
||||
return c.OSCommand.RunCommand("git pull --no-edit")
|
||||
func (c *GitCommand) Pull(ask func(string) string) error {
|
||||
return c.OSCommand.DetectUnamePass("git pull --no-edit", ask)
|
||||
}
|
||||
|
||||
// Push pushes to a branch
|
||||
func (c *GitCommand) Push(branchName string, force bool) error {
|
||||
func (c *GitCommand) Push(branchName string, force bool, ask func(string) string) error {
|
||||
forceFlag := ""
|
||||
if force {
|
||||
forceFlag = "--force-with-lease "
|
||||
}
|
||||
|
||||
return c.OSCommand.RunCommand(fmt.Sprintf("git push %s -u origin %s", forceFlag, branchName))
|
||||
cmd := fmt.Sprintf("git push %s -u origin %s", forceFlag, branchName)
|
||||
return c.OSCommand.DetectUnamePass(cmd, ask)
|
||||
}
|
||||
|
||||
// SquashPreviousTwoCommits squashes a commit down to the one below it
|
||||
|
@ -95,7 +95,7 @@ func TestVerifyInGitRepo(t *testing.T) {
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
||||
assert.Regexp(t, `fatal: .ot a git repository \(or any of the parent directories\s?\/?\): \.git`, err.Error())
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -256,7 +256,7 @@ func TestNewGitCommand(t *testing.T) {
|
||||
},
|
||||
func(gitCmd *GitCommand, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
||||
assert.Regexp(t, `fatal: .ot a git repository ((\(or any of the parent directories\): \.git)|(\(or any parent up to mount point \/\)))`, err.Error())
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1010,7 +1010,7 @@ func TestGitCommandPush(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
func(err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, err.Error(), "error: failed to push some refs")
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1023,7 +1023,7 @@ func TestGitCommandPush(t *testing.T) {
|
||||
},
|
||||
true,
|
||||
func(err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, err.Error(), "error: failed to push some refs")
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1031,12 +1031,11 @@ func TestGitCommandPush(t *testing.T) {
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
|
||||
|
||||
return exec.Command("test")
|
||||
},
|
||||
false,
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "error: failed to push some refs")
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1045,7 +1044,10 @@ func TestGitCommandPush(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
gitCmd := newDummyGitCommand()
|
||||
gitCmd.OSCommand.command = s.command
|
||||
s.test(gitCmd.Push("test", s.forcePush))
|
||||
err := gitCmd.Push("test", s.forcePush, func(passOrUname string) string {
|
||||
return "\n"
|
||||
})
|
||||
s.test(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
@ -57,6 +58,36 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// RunCommandWithOutputLive runs RunCommandWithOutputLiveWrapper
|
||||
func (c *OSCommand) RunCommandWithOutputLive(command string, output func(string) string) error {
|
||||
return RunCommandWithOutputLiveWrapper(c, command, output)
|
||||
}
|
||||
|
||||
// DetectUnamePass detect a username / password question in a command
|
||||
// ask is a function that gets executen when this function detect you need to fillin a password
|
||||
// The ask argument will be "username" or "password" and expects the user's password or username back
|
||||
func (c *OSCommand) DetectUnamePass(command string, ask func(string) string) error {
|
||||
ttyText := ""
|
||||
errMessage := c.RunCommandWithOutputLive(command, func(word string) string {
|
||||
ttyText = ttyText + " " + word
|
||||
|
||||
prompts := map[string]string{
|
||||
"password": `Password\s*for\s*'.+':`,
|
||||
"username": `Username\s*for\s*'.+':`,
|
||||
}
|
||||
|
||||
for askFor, pattern := range prompts {
|
||||
if match, _ := regexp.MatchString(pattern, ttyText); match {
|
||||
ttyText = ""
|
||||
return ask(askFor)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
})
|
||||
return errMessage
|
||||
}
|
||||
|
||||
// RunCommand runs a command and just returns the error
|
||||
func (c *OSCommand) RunCommand(command string) error {
|
||||
_, err := c.RunCommandWithOutput(command)
|
||||
@ -186,7 +217,7 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := tmpfile.Write([]byte(content)); err != nil {
|
||||
if _, err := tmpfile.WriteString(content); err != nil {
|
||||
c.Log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
Reference in New Issue
Block a user