Skip to content

Commit f704707

Browse files
committed
stream output from certain git commands in command log panel
1 parent 01d8274 commit f704707

33 files changed

+871
-212
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ require (
2222
github.com/integrii/flaggy v1.4.0
2323
github.com/iriri/minimal/gitignore v0.3.2 // indirect
2424
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
25-
github.com/jesseduffield/gocui v0.3.1-0.20211017220056-b2fc03c74a6f
25+
github.com/jesseduffield/gocui v0.3.1-0.20211024041248-681a61c53ed0
2626
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
2727
github.com/jesseduffield/yaml v2.1.0+incompatible
2828
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
@@ -43,7 +43,7 @@ require (
4343
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
4444
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
4545
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
46-
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 // indirect
46+
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 // indirect
4747
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
4848
golang.org/x/text v0.3.7 // indirect
4949
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ github.com/jesseduffield/gocui v0.3.1-0.20211017091015-8bf4a4666b77 h1:MQUxSxVBT
8484
github.com/jesseduffield/gocui v0.3.1-0.20211017091015-8bf4a4666b77/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
8585
github.com/jesseduffield/gocui v0.3.1-0.20211017220056-b2fc03c74a6f h1:JHrb78pj+gYC3KiJKL1WW6lYzlatBIF46oREn68plTM=
8686
github.com/jesseduffield/gocui v0.3.1-0.20211017220056-b2fc03c74a6f/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
87+
github.com/jesseduffield/gocui v0.3.1-0.20211024041248-681a61c53ed0 h1:To4mMbu6oQpbbyHa4WtMTc/DHa9dqiRWZpDLMNK+Hdk=
88+
github.com/jesseduffield/gocui v0.3.1-0.20211024041248-681a61c53ed0/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
8789
github.com/jesseduffield/minimal v0.0.0-20211018110810-9cde264e6b1e h1:WZc73tBVMMhcO6zXyZBItLEF4jgBpBH0lFCZzDgrjDg=
8890
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
8991
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e/go.mod h1:u60qdFGXRd36jyEXxetz0vQceQIxzI13lIo3EFUDf4I=
@@ -195,6 +197,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
195197
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
196198
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 h1:KzbpndAYEM+4oHRp9JmB2ewj0NHHxO3Z0g7Gus2O1kk=
197199
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
200+
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA=
201+
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
198202
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
199203
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
200204
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

pkg/app/app.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,13 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
127127
return app, err
128128
}
129129

130-
app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr, app.Config, git_config.NewStdCachedGitConfig(app.Log))
130+
app.GitCommand, err = commands.NewGitCommand(
131+
app.Log,
132+
app.OSCommand,
133+
app.Tr,
134+
app.Config,
135+
git_config.NewStdCachedGitConfig(app.Log),
136+
)
131137
if err != nil {
132138
return app, err
133139
}

pkg/commands/dummies.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package commands
22

33
import (
4+
"io"
5+
"io/ioutil"
6+
47
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
58
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
69
"github.com/jesseduffield/lazygit/pkg/config"
@@ -17,10 +20,11 @@ func NewDummyGitCommand() *GitCommand {
1720
func NewDummyGitCommandWithOSCommand(osCommand *oscommands.OSCommand) *GitCommand {
1821
newAppConfig := config.NewDummyAppConfig()
1922
return &GitCommand{
20-
Log: utils.NewDummyLog(),
21-
OSCommand: osCommand,
22-
Tr: i18n.NewTranslationSet(utils.NewDummyLog(), newAppConfig.GetUserConfig().Gui.Language),
23-
Config: newAppConfig,
24-
GitConfig: git_config.NewFakeGitConfig(map[string]string{}),
23+
Log: utils.NewDummyLog(),
24+
OSCommand: osCommand,
25+
Tr: i18n.NewTranslationSet(utils.NewDummyLog(), newAppConfig.GetUserConfig().Gui.Language),
26+
Config: newAppConfig,
27+
GitConfig: git_config.NewFakeGitConfig(map[string]string{}),
28+
GetCmdWriter: func() io.Writer { return ioutil.Discard },
2529
}
2630
}

pkg/commands/git.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package commands
22

33
import (
4+
"io"
45
"io/ioutil"
56
"os"
67
"path/filepath"
@@ -40,6 +41,11 @@ type GitCommand struct {
4041

4142
// Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not
4243
PushToCurrent bool
44+
45+
// this is just a view that we write to when running certain commands.
46+
// Coincidentally at the moment it's the same view that OnRunCommand logs to
47+
// but that need not always be the case.
48+
GetCmdWriter func() io.Writer
4349
}
4450

4551
// NewGitCommand it runs git commands
@@ -77,6 +83,7 @@ func NewGitCommand(
7783
DotGitDir: dotGitDir,
7884
PushToCurrent: pushToCurrent,
7985
GitConfig: gitConfig,
86+
GetCmdWriter: func() io.Writer { return ioutil.Discard },
8087
}
8188

8289
gitCommand.PatchManager = patch.NewPatchManager(log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package oscommands
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"io"
7+
"os/exec"
8+
"regexp"
9+
"strings"
10+
"unicode/utf8"
11+
12+
"github.com/go-errors/errors"
13+
"github.com/jesseduffield/lazygit/pkg/utils"
14+
)
15+
16+
// DetectUnamePass detect a username / password / passphrase question in a command
17+
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
18+
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
19+
func (c *OSCommand) DetectUnamePass(cmdObj ICmdObj, writer io.Writer, promptUserForCredential func(string) string) error {
20+
ttyText := ""
21+
errMessage := c.RunCommandWithOutputLive(cmdObj, writer, func(word string) string {
22+
ttyText = ttyText + " " + word
23+
24+
prompts := map[string]string{
25+
`.+'s password:`: "password",
26+
`Password\s*for\s*'.+':`: "password",
27+
`Username\s*for\s*'.+':`: "username",
28+
`Enter\s*passphrase\s*for\s*key\s*'.+':`: "passphrase",
29+
}
30+
31+
for pattern, askFor := range prompts {
32+
if match, _ := regexp.MatchString(pattern, ttyText); match {
33+
ttyText = ""
34+
return promptUserForCredential(askFor)
35+
}
36+
}
37+
38+
return ""
39+
})
40+
return errMessage
41+
}
42+
43+
// Due to a lack of pty support on windows we have RunCommandWithOutputLiveWrapper being defined
44+
// separate for windows and other OS's
45+
func (c *OSCommand) RunCommandWithOutputLive(cmdObj ICmdObj, writer io.Writer, handleOutput func(string) string) error {
46+
return RunCommandWithOutputLiveWrapper(c, cmdObj, writer, handleOutput)
47+
}
48+
49+
type cmdHandler struct {
50+
stdoutPipe io.Reader
51+
stdinPipe io.Writer
52+
close func() error
53+
}
54+
55+
// RunCommandWithOutputLiveAux runs a command and return every word that gets written in stdout
56+
// Output is a function that executes by every word that gets read by bufio
57+
// As return of output you need to give a string that will be written to stdin
58+
// NOTE: If the return data is empty it won't write anything to stdin
59+
func RunCommandWithOutputLiveAux(
60+
c *OSCommand,
61+
cmdObj ICmdObj,
62+
writer io.Writer,
63+
// handleOutput takes a word from stdout and returns a string to be written to stdin.
64+
// See DetectUnamePass above for how this is used to check for a username/password request
65+
handleOutput func(string) string,
66+
startCmd func(cmd *exec.Cmd) (*cmdHandler, error),
67+
) error {
68+
c.Log.WithField("command", cmdObj.ToString()).Info("RunCommand")
69+
c.LogCommand(cmdObj.ToString(), true)
70+
cmd := cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8").GetCmd()
71+
72+
var stderr bytes.Buffer
73+
cmd.Stderr = io.MultiWriter(writer, &stderr)
74+
75+
handler, err := startCmd(cmd)
76+
if err != nil {
77+
return err
78+
}
79+
80+
defer func() {
81+
if closeErr := handler.close(); closeErr != nil {
82+
c.Log.Error(closeErr)
83+
}
84+
}()
85+
86+
tr := io.TeeReader(handler.stdoutPipe, writer)
87+
88+
go utils.Safe(func() {
89+
scanner := bufio.NewScanner(tr)
90+
scanner.Split(scanWordsWithNewLines)
91+
for scanner.Scan() {
92+
text := scanner.Text()
93+
output := strings.Trim(text, " ")
94+
toInput := handleOutput(output)
95+
if toInput != "" {
96+
_, _ = handler.stdinPipe.Write([]byte(toInput))
97+
}
98+
}
99+
})
100+
101+
err = cmd.Wait()
102+
if err != nil {
103+
return errors.New(stderr.String())
104+
}
105+
106+
return nil
107+
}
108+
109+
// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines
110+
// For specific comments about this function take a look at: bufio.ScanWords
111+
func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
112+
start := 0
113+
for width := 0; start < len(data); start += width {
114+
var r rune
115+
r, width = utf8.DecodeRune(data[start:])
116+
if !isSpace(r) {
117+
break
118+
}
119+
}
120+
for width, i := 0, start; i < len(data); i += width {
121+
var r rune
122+
r, width = utf8.DecodeRune(data[i:])
123+
if isSpace(r) {
124+
return i + width, data[start:i], nil
125+
}
126+
}
127+
if atEOF && len(data) > start {
128+
return len(data), data[start:], nil
129+
}
130+
return start, nil, nil
131+
}
132+
133+
// isSpace is also copied from the bufio package and has been modified to also captures new lines
134+
// For specific comments about this function take a look at: bufio.isSpace
135+
func isSpace(r rune) bool {
136+
if r <= '\u00FF' {
137+
switch r {
138+
case ' ', '\t', '\v', '\f':
139+
return true
140+
case '\u0085', '\u00A0':
141+
return true
142+
}
143+
return false
144+
}
145+
if '\u2000' <= r && r <= '\u200a' {
146+
return true
147+
}
148+
switch r {
149+
case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
150+
return true
151+
}
152+
return false
153+
}

pkg/commands/oscommands/exec_live_default.go

Lines changed: 26 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,95 +4,34 @@
44
package oscommands
55

66
import (
7-
"bufio"
8-
"bytes"
9-
"strings"
10-
"unicode/utf8"
11-
12-
"github.com/go-errors/errors"
13-
"github.com/jesseduffield/lazygit/pkg/utils"
7+
"io"
8+
"os/exec"
149

1510
"github.com/creack/pty"
1611
)
1712

18-
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
19-
// Output is a function that executes by every word that gets read by bufio
20-
// As return of output you need to give a string that will be written to stdin
21-
// NOTE: If the return data is empty it won't written anything to stdin
22-
func RunCommandWithOutputLiveWrapper(c *OSCommand, cmdObj ICmdObj, output func(string) string) error {
23-
c.Log.WithField("command", cmdObj.ToString()).Info("RunCommand")
24-
c.LogCommand(cmdObj.ToString(), true)
25-
cmd := cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8").GetCmd()
26-
27-
var stderr bytes.Buffer
28-
cmd.Stderr = &stderr
29-
30-
ptmx, err := pty.Start(cmd)
31-
32-
if err != nil {
33-
return err
34-
}
35-
36-
go utils.Safe(func() {
37-
scanner := bufio.NewScanner(ptmx)
38-
scanner.Split(scanWordsWithNewLines)
39-
for scanner.Scan() {
40-
toOutput := strings.Trim(scanner.Text(), " ")
41-
_, _ = ptmx.WriteString(output(toOutput))
42-
}
43-
})
44-
45-
err = cmd.Wait()
46-
ptmx.Close()
47-
if err != nil {
48-
return errors.New(stderr.String())
49-
}
50-
51-
return nil
52-
}
53-
54-
// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines
55-
// For specific comments about this function take a look at: bufio.ScanWords
56-
func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
57-
start := 0
58-
for width := 0; start < len(data); start += width {
59-
var r rune
60-
r, width = utf8.DecodeRune(data[start:])
61-
if !isSpace(r) {
62-
break
63-
}
64-
}
65-
for width, i := 0, start; i < len(data); i += width {
66-
var r rune
67-
r, width = utf8.DecodeRune(data[i:])
68-
if isSpace(r) {
69-
return i + width, data[start:i], nil
70-
}
71-
}
72-
if atEOF && len(data) > start {
73-
return len(data), data[start:], nil
74-
}
75-
return start, nil, nil
76-
}
77-
78-
// isSpace is also copied from the bufio package and has been modified to also captures new lines
79-
// For specific comments about this function take a look at: bufio.isSpace
80-
func isSpace(r rune) bool {
81-
if r <= '\u00FF' {
82-
switch r {
83-
case ' ', '\t', '\v', '\f':
84-
return true
85-
case '\u0085', '\u00A0':
86-
return true
87-
}
88-
return false
89-
}
90-
if '\u2000' <= r && r <= '\u200a' {
91-
return true
92-
}
93-
switch r {
94-
case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
95-
return true
96-
}
97-
return false
13+
func RunCommandWithOutputLiveWrapper(
14+
c *OSCommand,
15+
cmdObj ICmdObj,
16+
writer io.Writer,
17+
output func(string) string,
18+
) error {
19+
return RunCommandWithOutputLiveAux(
20+
c,
21+
cmdObj,
22+
writer,
23+
output,
24+
func(cmd *exec.Cmd) (*cmdHandler, error) {
25+
ptmx, err := pty.Start(cmd)
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
return &cmdHandler{
31+
stdoutPipe: ptmx,
32+
stdinPipe: ptmx,
33+
close: ptmx.Close,
34+
}, nil
35+
},
36+
)
9837
}

0 commit comments

Comments
 (0)