summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/commands/exec_live_default.go121
-rw-r--r--pkg/commands/exec_live_win.go10
-rw-r--r--pkg/commands/git.go18
-rw-r--r--pkg/commands/git_test.go15
-rw-r--r--pkg/commands/os.go48
-rw-r--r--pkg/config/app_config.go14
-rw-r--r--pkg/gui/branches_panel.go37
-rw-r--r--pkg/gui/commit_message_panel.go83
-rw-r--r--pkg/gui/confirmation_panel.go3
-rw-r--r--pkg/gui/files_panel.go62
-rw-r--r--pkg/gui/gui.go60
-rw-r--r--pkg/gui/keybindings.go16
-rw-r--r--pkg/gui/recent_repos_panel.go18
-rw-r--r--pkg/gui/view_helpers.go4
-rw-r--r--pkg/i18n/dutch.go25
-rw-r--r--pkg/i18n/english.go21
-rw-r--r--pkg/i18n/polish.go21
-rw-r--r--vendor/github.com/flynn/go-shlex/COPYING202
-rw-r--r--vendor/github.com/flynn/go-shlex/Makefile21
-rw-r--r--vendor/github.com/flynn/go-shlex/README.md2
-rw-r--r--vendor/github.com/flynn/go-shlex/shlex.go457
-rw-r--r--vendor/github.com/flynn/go-shlex/shlex_test.go162
-rw-r--r--vendor/github.com/kr/pty/License23
-rw-r--r--vendor/github.com/kr/pty/README.md100
-rw-r--r--vendor/github.com/kr/pty/doc.go16
-rw-r--r--vendor/github.com/kr/pty/ioctl.go13
-rw-r--r--vendor/github.com/kr/pty/ioctl_bsd.go39
-rwxr-xr-xvendor/github.com/kr/pty/mktypes.bash19
-rw-r--r--vendor/github.com/kr/pty/pty_darwin.go65
-rw-r--r--vendor/github.com/kr/pty/pty_dragonfly.go80
-rw-r--r--vendor/github.com/kr/pty/pty_freebsd.go78
-rw-r--r--vendor/github.com/kr/pty/pty_linux.go51
-rw-r--r--vendor/github.com/kr/pty/pty_openbsd.go33
-rw-r--r--vendor/github.com/kr/pty/pty_unsupported.go11
-rw-r--r--vendor/github.com/kr/pty/run.go34
-rw-r--r--vendor/github.com/kr/pty/types.go10
-rw-r--r--vendor/github.com/kr/pty/types_dragonfly.go17
-rw-r--r--vendor/github.com/kr/pty/types_freebsd.go15
-rw-r--r--vendor/github.com/kr/pty/types_openbsd.go14
-rw-r--r--vendor/github.com/kr/pty/util.go64
-rw-r--r--vendor/github.com/kr/pty/ztypes_386.go9
-rw-r--r--vendor/github.com/kr/pty/ztypes_amd64.go9
-rw-r--r--vendor/github.com/kr/pty/ztypes_arm.go9
-rw-r--r--vendor/github.com/kr/pty/ztypes_arm64.go11
-rw-r--r--vendor/github.com/kr/pty/ztypes_dragonfly_amd64.go14
-rw-r--r--vendor/github.com/kr/pty/ztypes_freebsd_386.go13
-rw-r--r--vendor/github.com/kr/pty/ztypes_freebsd_amd64.go14
-rw-r--r--vendor/github.com/kr/pty/ztypes_freebsd_arm.go13
-rw-r--r--vendor/github.com/kr/pty/ztypes_mipsx.go12
-rw-r--r--vendor/github.com/kr/pty/ztypes_openbsd_386.go13
-rw-r--r--vendor/github.com/kr/pty/ztypes_openbsd_amd64.go13
-rw-r--r--vendor/github.com/kr/pty/ztypes_ppc64.go11
-rw-r--r--vendor/github.com/kr/pty/ztypes_ppc64le.go11
-rw-r--r--vendor/github.com/kr/pty/ztypes_s390x.go11
-rw-r--r--vendor/github.com/mgutz/str/CREDITS5
-rw-r--r--vendor/github.com/mgutz/str/README.md649
-rw-r--r--vendor/github.com/mgutz/str/VERSION1
-rw-r--r--vendor/github.com/mgutz/str/str_test.go696
-rw-r--r--vendor/vendor.json25
59 files changed, 3593 insertions, 48 deletions
diff --git a/pkg/commands/exec_live_default.go b/pkg/commands/exec_live_default.go
new file mode 100644
index 000000000..ad1c568e5
--- /dev/null
+++ b/pkg/commands/exec_live_default.go
@@ -0,0 +1,121 @@
+// +build !windows
+
+package commands
+
+import (
+ "bufio"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+ "unicode/utf8"
+
+ "github.com/kr/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
+// NOTE: You don't have to include a enter in the return data this function will do that for you
+func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) (errorMessage string, codeError error) {
+ cmdOutput := []string{}
+
+ splitCmd := str.ToArgv(command)
+ cmd := exec.Command(splitCmd[0], splitCmd[1:]...)
+
+ cmd.Env = os.Environ()
+ cmd.Env = append(cmd.Env, "LANG=en_US.utf8", "LC_ALL=en_US.UTF-8")
+
+ tty, err := pty.Start(cmd)
+
+ if err != nil {
+ return errorMessage, err
+ }
+
+ stopAsking := make(chan struct{})
+
+ var waitForBufio sync.WaitGroup
+ waitForBufio.Add(1)
+
+ defer func() {
+ _ = tty.Close()
+ }()
+
+ go func() {
+ scanner := bufio.NewScanner(tty)
+ scanner.Split(scanWordsWithNewLines)
+ for scanner.Scan() {
+ select {
+ case <-stopAsking:
+ // just do nothing
+ default:
+ toOutput := strings.Trim(scanner.Text(), " ")
+ cmdOutput = append(cmdOutput, toOutput)
+ toWrite := output(toOutput)
+ if len(toWrite) > 0 {
+ _, _ = tty.Write([]byte(toWrite + "\n"))
+ }
+ }
+ }
+ waitForBufio.Done()
+ }()
+
+ err = cmd.Wait()
+ go func() {
+ stopAsking <- struct{}{}
+ }()
+ if err != nil {
+ waitForBufio.Wait()
+ return strings.Join(cmdOutput, " "), err
+ }
+
+ return errorMessage, 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..d97274279
--- /dev/null
+++ b/pkg/commands/exec_live_win.go
@@ -0,0 +1,10 @@
+// +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 propper way to run commands live on windows
+func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) (errorMessage string, codeError error) {
+ cmdOputput := c.RunCommand(command)
+ return cmdOputput.Error(), cmdOputput
+}
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index b78db1fa6..d9230c4eb 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -261,8 +261,13 @@ func (c *GitCommand) RenameCommit(name string) error {
}
// Fetch fetch git repo
-func (c *GitCommand) Fetch() error {
- return c.OSCommand.RunCommand("git fetch")
+func (c *GitCommand) Fetch(unamePassQuestion func(string) string, canSskForCredentials bool) error {
+ return c.OSCommand.DetectUnamePass("git fetch", func(question string) string {
+ if canSskForCredentials {
+ return unamePassQuestion(question)
+ }
+ return "-"
+ })
}
// ResetToCommit reset to commit
@@ -340,18 +345,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..7d0a503d7 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.Equal(t, "exit status 128", err.Error())
},
},
{
@@ -1023,7 +1023,7 @@ func TestGitCommandPush(t *testing.T) {
},
true,
func(err error) {
- assert.Nil(t, err)
+ assert.Equal(t, "exit status 128", err.Error())
},
},
{
@@ -1036,7 +1036,7 @@ func TestGitCommandPush(t *testing.T) {
},
false,
func(err error) {
- assert.Error(t, err)
+ assert.Equal(t, "exit status 128", err.Error())
},
},
}
@@ -1045,7 +1045,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 "-"
+ })
+ s.test(err)
})
}
}
diff --git a/pkg/commands/os.go b/pkg/commands/os.go
index 6b28a69bb..faf6c5aec 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,53 @@ func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
)
}
+// RunCommandWithOutputLive runs RunCommandWithOutputLiveWrapper
+func (c *OSCommand) RunCommandWithOutputLive(command string, output func(string) string) (errorMessage string, err 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, err := c.RunCommandWithOutputLive(command, func(word string) string {
+ ttyText = ttyText + " " + word
+
+ type Prompt struct {
+ pattern string
+ canAskFor bool
+ }
+ prompts := map[string]Prompt{
+ "password": {
+ pattern: `Password\s*for\s*'.+':`,
+ canAskFor: true,
+ },
+ "username": {
+ pattern: `Username\s*for\s*'.+':`,
+ canAskFor: true,
+ },
+ }
+
+ for askFor, prompt := range prompts {
+ if match, _ := regexp.MatchString(prompt.pattern, ttyText); match && prompt.canAskFor {
+ prompt.canAskFor = false
+ ttyText = ""
+ return ask(askFor)
+ }
+ }
+
+ return ""
+ })
+ if err != nil {
+ if strings.Contains("exit status 128", err.Error()) {
+ errMessage = "exit status 128"
+ }
+ return errors.New(errMessage)
+ }
+ return nil
+}
+
// RunCommand runs a command and just returns the error
func (c *OSCommand) RunCommand(command string) error {
_, err := c.RunCommandWithOutput(command)
diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go
index f789349b4..f0d9e7015 100644
--- a/pkg/config/app_config.go
+++ b/pkg/config/app_config.go
@@ -236,15 +236,17 @@ confirmOnQuit: false
// AppState stores data between runs of the app like when the last update check
// was performed and which other repos have been checked out
type AppState struct {
- LastUpdateCheck int64
- RecentRepos []string
+ LastUpdateCheck int64
+ RecentRepos []string
+ RecentPrivateRepos []string
}
func getDefaultAppState() []byte {
- return []byte(`
- lastUpdateCheck: 0
- recentRepos: []
-`)
+ return []byte(`
+ lastUpdateCheck: 0
+ recentRepos: []
+ RecentPrivateRepos: []
+ `)
}
// // commenting this out until we use it again
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index a0e3448f6..e54d6e8c1 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -124,6 +124,32 @@ func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error
return nil
}
+func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error {
+ if err := gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("FetchWait")); err != nil {
+ return err
+ }
+ go func() {
+ unamePassOpend, err := gui.fetch(g, v, true)
+ if err != nil {
+ errMessage := err.Error()
+ if errMessage == "exit status 128" {
+ errMessage = gui.Tr.SLocalize("PassUnameWrong")
+ }
+ _ = gui.createErrorPanel(g, errMessage)
+ }
+ if unamePassOpend {
+ _, _ = g.SetViewOnBottom("pushPassUname")
+ _ = g.DeleteView("pushPassUname")
+ }
+ if err == nil {
+ _ = gui.closeConfirmationPrompt(g)
+ _ = gui.refreshCommits(g)
+ _ = gui.refreshStatus(g)
+ }
+ }()
+ return nil
+}
+
func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
branch := gui.getSelectedBranch()
message := gui.Tr.SLocalize("SureForceCheckout")
@@ -186,14 +212,14 @@ func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *commands.Branch, force bool) error {
title := gui.Tr.SLocalize("DeleteBranch")
- var messageId string
+ var messageID string
if force {
- messageId = "ForceDeleteBranchMessage"
+ messageID = "ForceDeleteBranchMessage"
} else {
- messageId = "DeleteBranchMessage"
+ messageID = "DeleteBranchMessage"
}
message := gui.Tr.TemplateLocalize(
- messageId,
+ messageID,
Teml{
"selectedBranchName": selectedBranch.Name,
},
@@ -203,9 +229,8 @@ func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *c
errMessage := err.Error()
if !force && strings.Contains(errMessage, "is not fully merged") {
return gui.deleteNamedBranch(g, v, selectedBranch, true)
- } else {
- return gui.createErrorPanel(g, errMessage)
}
+ return gui.createErrorPanel(g, errMessage)
}
return gui.refreshSidePanels(g)
}, nil)
diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go
index 33eb5b218..85497ef87 100644
--- a/pkg/gui/commit_message_panel.go
+++ b/pkg/gui/commit_message_panel.go
@@ -51,6 +51,89 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
return gui.renderString(g, "options", message)
}
+type credentials chan string
+
+// waitForPassUname wait for a username or password input from the pushPassUname popup
+func (gui *Gui) waitForPassUname(g *gocui.Gui, currentView *gocui.View, passOrUname string) string {
+ gui.credentials = make(chan string)
+ pushPassUnameView, _ := g.View("pushPassUname")
+ if passOrUname == "username" {
+ pushPassUnameView.Title = gui.Tr.SLocalize("PushUsername")
+ pushPassUnameView.Mask = 0
+ } else {
+ pushPassUnameView.Title = gui.Tr.SLocalize("PushPassword")
+ pushPassUnameView.Mask = '*'
+ }
+ g.Update(func(g *gocui.Gui) error {
+ _, err := g.SetViewOnTop("pushPassUname")
+ if err != nil {
+ return err
+ }
+ err = gui.switchFocus(g, currentView, pushPassUnameView)
+ if err != nil {
+ return err
+ }
+ gui.RenderCommitLength()
+ return nil
+ })
+
+ // wait for username/passwords input
+ userInput := <-gui.credentials
+ return userInput
+}
+
+func (gui *Gui) handlePushConfirm(g *gocui.Gui, v *gocui.View) error {
+ message := gui.trimmedContent(v)
+ if message == "" {
+ // make sure to input something
+ // if not dune the push progress will run forever
+ message = "-"
+ }
+ gui.credentials <- message
+ err := gui.refreshFiles(g)
+ if err != nil {
+ return err
+ }
+ v.Clear()
+ err = v.SetCursor(0, 0)
+ if err != nil {
+ return err
+ }
+ _, err = g.SetViewOnBottom("pushPassUname")
+ if err != nil {
+ return err
+ }
+ err = gui.switchFocus(g, v, gui.getFilesView(g))
+ if err != nil {
+ return err
+ }
+ return gui.refreshCommits(g)
+}
+
+func (gui *Gui) handlePushClose(g *gocui.Gui, v *gocui.View) error {
+ _, err := g.SetViewOnBottom("pushPassUname")
+ if err != nil {
+ return err
+ }
+ gui.credentials <- "-"
+ return gui.switchFocus(g, v, gui.getFilesView(g))
+}
+
+func (gui *Gui) handlePushFocused(g *gocui.Gui, v *gocui.View) error {
+ if _, err := g.SetViewOnTop("pushPassUname"); err != nil {
+ return err
+ }
+
+ message := gui.Tr.TemplateLocalize(
+ "CloseConfirm",
+ Teml{
+ "keyBindClose": "esc",
+ "keyBindConfirm": "enter",
+ },
+ )
+ return gui.renderString(g, "options", message)
+}
+