1
0
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:
Jesse Duffield
2019-02-11 21:02:53 +11:00
57 changed files with 1496 additions and 174 deletions

View 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
}

View 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)
}

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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
}