diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2019-02-11 21:02:53 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2019-02-11 21:02:53 +1100 |
commit | 3d343e9b574a2c99ebf5b30dc9a4dac2886f6d73 (patch) | |
tree | ef6b2f8c08a29349bcc56a16260dfefdb3ee872d | |
parent | a3656154906c1117f9c9bbe100aa585e43417897 (diff) | |
parent | 3a607061a2303d9f45d308de652fbebe7300b43c (diff) |
Merge branch 'master' into feature/rebasing
57 files changed, 1497 insertions, 175 deletions
diff --git a/Gopkg.lock b/Gopkg.lock index 4b84d6537..432138f18 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,19 @@ [[projects]] branch = "master" - digest = "1:66bb9b4a5abb704642fccba52a84a7f7feef2d9623f87b700e52a6695044723f" + digest = "1:9b266d7748a5d94985fd9e323494f5b8ae1ab3e910418e898dfe7f03339ddbcd" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "03e26ff3f1de2c1bc2205113c3aba661312eee00" + revision = "cfa9e452ba5ebf014041846851152d64a59dce14" + +[[projects]] + branch = "master" + digest = "1:a46c2f4863e5284ddb255c28750298e04bc8c0fc896bed6056e947673168b7be" + name = "github.com/jesseduffield/pty" + packages = ["."] + pruneopts = "NUT" + revision = "02db52c7e406c7abec44c717a173c7715e4c1b62" [[projects]] branch = "master" @@ -616,6 +624,7 @@ "github.com/heroku/rollrus", "github.com/jesseduffield/go-getter", "github.com/jesseduffield/gocui", + "github.com/jesseduffield/pty", "github.com/kardianos/osext", "github.com/mgutz/str", "github.com/nicksnyder/go-i18n/v2/i18n", diff --git a/Gopkg.toml b/Gopkg.toml index 1f0b03cee..a645c905f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -38,6 +38,10 @@ name = "github.com/jesseduffield/gocui" [[constraint]] + branch = "master" + name = "github.com/jesseduffield/pty" + +[[constraint]] name = "gopkg.in/src-d/go-git.v4" revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" @@ -18,7 +18,8 @@ require ( github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 - github.com/jesseduffield/gocui v0.0.0-20180921065632-03e26ff3f1de + github.com/jesseduffield/gocui v0.0.0-20190115084758-cfa9e452ba5e + github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406 github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 @@ -33,8 +33,10 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns= github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0= -github.com/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8 h1:XxX+IqNOFDh1PnU4eZDzUomoKbuKCvwyEm5an/IxLQU= -github.com/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw= +github.com/jesseduffield/gocui v0.0.0-20181209104758-fe55a32c8a4c h1:jEfh/vAtfF3pQ8xFhpYR/0S4iHo11VfaYelJmzZJm94= +github.com/jesseduffield/gocui v0.0.0-20181209104758-fe55a32c8a4c/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw= +github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406 h1:iYMH6h6SuWuBkIzRtymosE8NpSgTK0oRMfyTdVWgxzc= +github.com/jesseduffield/pty v0.0.0-20181218102224-02db52c7e406/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo= github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb h1:cFHYEWpQEfzFZVKiKZytCUX4UwQixKSw0kd3WhluPsY= github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= @@ -61,8 +63,8 @@ github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80 h1:7ory6RlsEkeK89iyV7Imz3sVz8YHeSw29w3PehpCWC0= github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4= -github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE= -github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU= +github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6 h1:SooTCzUOOs899x/M7gmSS+dgL+AUfSWqAcHLN3auCic= +github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= diff --git a/pkg/commands/exec_live_default.go b/pkg/commands/exec_live_default.go new file mode 100644 index 000000000..dfdff5308 --- /dev/null +++ b/pkg/commands/exec_live_default.go @@ -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 +} diff --git a/pkg/commands/exec_live_win.go b/pkg/commands/exec_live_win.go new file mode 100644 index 000000000..d06cb920b --- /dev/null +++ b/pkg/commands/exec_live_win.go @@ -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) +} diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 7a0932501..4858aa85c 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -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 diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 997054bca..3dcb7798d 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -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) }) } } diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 829034eb8..186887b5d 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -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 } diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index f789349b4..18aa961bc 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -20,6 +20,7 @@ type AppConfig struct { BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""` UserConfig *viper.Viper AppState *AppState + IsNewRepo bool } // AppConfigurer interface allows individual app config structs to inherit Fields @@ -36,6 +37,8 @@ type AppConfigurer interface { WriteToUserConfig(string, string) error SaveAppState() error LoadAppState() error + SetIsNewRepo(bool) + GetIsNewRepo() bool } // NewAppConfig makes a new app config @@ -54,6 +57,7 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg BuildSource: buildSource, UserConfig: userConfig, AppState: &AppState{}, + IsNewRepo: false, } if err := appConfig.LoadAppState(); err != nil { @@ -63,6 +67,16 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg return appConfig, nil } +// GetIsNewRepo returns known repo boolean +func (c *AppConfig) GetIsNewRepo() bool { + return c.IsNewRepo +} + +// SetIsNewRepo set if the current repo is known +func (c *AppConfig) SetIsNewRepo(toSet bool) { + c.IsNewRepo = toSet +} + // GetDebug returns debug flag func (c *AppConfig) GetDebug() bool { return c.Debug @@ -153,7 +167,7 @@ func prepareConfigFile(filename string) (string, error) { } // LoadAndMergeFile Loads the config/state file, creating -// the file as an empty one if it does not exist +// the file has an empty one if it does not exist func LoadAndMergeFile(v *viper.Viper, filename string) error { configPath, err := prepareConfigFile(filename) if err != nil { @@ -242,9 +256,9 @@ type AppState struct { func getDefaultAppState() []byte { return []byte(` - lastUpdateCheck: 0 - recentRepos: [] -`) + lastUpdateCheck: 0 + recentRepos: [] + `) } // // commenting this out until we use it again diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index b94c91e28..54fd828fd 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -161,6 +161,17 @@ func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error return nil } +func (gui *Gui) handleGitFe |