1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-06 23:46:13 +02:00

Kill background fetch when it requests a passphrase (#4588)

Previously we would enter a newline at the password prompt, which would
cause the fetch to fail. The problem with this was that if you have many
remotes, the fetch would sometimes hang for some reason; I don't totally
understand how that happened, but I guess the many ssh processes
requesting passwords would somehow interfere with each other. Avoid this
by simply killing the git fetch process the moment it requests the first
password.
This commit is contained in:
Stefan Haller 2025-05-29 14:39:39 +02:00 committed by GitHub
commit 676b5c2287
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 22 deletions

View File

@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -295,14 +294,10 @@ const (
Token Token
) )
// Whenever we're asked for a password we just enter a newline, which will // Whenever we're asked for a password we return a nil channel to tell the
// eventually cause the command to fail. // caller to kill the process.
var failPromptFn = func(CredentialType) <-chan string { var failPromptFn = func(CredentialType) <-chan string {
ch := make(chan string) return nil
go func() {
ch <- "\n"
}()
return ch
} }
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj *CmdObj) error { func (self *cmdObjRunner) runWithCredentialHandling(cmdObj *CmdObj) error {
@ -340,7 +335,7 @@ func (self *cmdObjRunner) runAndDetectCredentialRequest(
tr := io.TeeReader(handler.stdoutPipe, cmdWriter) tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
go utils.Safe(func() { go utils.Safe(func() {
self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj.GetTask()) self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj)
}) })
}) })
} }
@ -349,9 +344,10 @@ func (self *cmdObjRunner) processOutput(
reader io.Reader, reader io.Reader,
writer io.Writer, writer io.Writer,
promptUserForCredential func(CredentialType) <-chan string, promptUserForCredential func(CredentialType) <-chan string,
task gocui.Task, cmdObj *CmdObj,
) { ) {
checkForCredentialRequest := self.getCheckForCredentialRequestFunc() checkForCredentialRequest := self.getCheckForCredentialRequestFunc()
task := cmdObj.GetTask()
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanBytes) scanner.Split(bufio.ScanBytes)
@ -360,6 +356,15 @@ func (self *cmdObjRunner) processOutput(
askFor, ok := checkForCredentialRequest(newBytes) askFor, ok := checkForCredentialRequest(newBytes)
if ok { if ok {
responseChan := promptUserForCredential(askFor) responseChan := promptUserForCredential(askFor)
if responseChan == nil {
// Returning a nil channel means we should kill the process.
// Note that we don't break the loop after this, because we
// still need to drain the output, otherwise the Wait() call
// later might block.
if err := Kill(cmdObj.GetCmd()); err != nil {
self.log.Error(err)
}
} else {
if task != nil { if task != nil {
task.Pause() task.Pause()
} }
@ -374,6 +379,7 @@ func (self *cmdObjRunner) processOutput(
} }
} }
} }
}
// having a function that returns a function because we need to maintain some state inbetween calls hence the closure // having a function that returns a function because we need to maintain some state inbetween calls hence the closure
func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (CredentialType, bool) { func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (CredentialType, bool) {

View File

@ -120,8 +120,8 @@ func TestProcessOutput(t *testing.T) {
reader := strings.NewReader(scenario.output) reader := strings.NewReader(scenario.output)
writer := &strings.Builder{} writer := &strings.Builder{}
task := gocui.NewFakeTask() cmdObj := &CmdObj{task: gocui.NewFakeTask()}
runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), task) runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), cmdObj)
if writer.String() != scenario.expectedToWrite { if writer.String() != scenario.expectedToWrite {
t.Errorf("expected to write '%s' but got '%s'", scenario.expectedToWrite, writer.String()) t.Errorf("expected to write '%s' but got '%s'", scenario.expectedToWrite, writer.String())