diff --git a/pkg/commands/git.go b/pkg/commands/git.go index d4f4ffd03..9fd675baf 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -63,6 +63,7 @@ type GitCommand struct { Worktree *gogit.Worktree Repo *gogit.Repository Tr *i18n.Localizer + SavedCredentials SavedCredentials getGlobalGitConfig func(string) (string, error) getLocalGitConfig func(string) (string, error) removeFile func(string) error @@ -93,12 +94,19 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) } } + credentials := SavedCredentials{ + Username: "", + Password: "", + HasAsked: false, + } + return &GitCommand{ Log: log, OSCommand: osCommand, Tr: tr, Worktree: worktree, Repo: repo, + SavedCredentials: credentials, getGlobalGitConfig: gitconfig.Global, getLocalGitConfig: gitconfig.Local, removeFile: os.RemoveAll, @@ -247,9 +255,43 @@ func (c *GitCommand) RenameCommit(name string) error { return c.OSCommand.RunCommand(fmt.Sprintf("git commit --allow-empty --amend -m %s", c.OSCommand.Quote(name))) } +// SavedCredentials are the user's git login credentials +type SavedCredentials struct { + Username string + Password string + HasAsked bool +} + // Fetch fetch git repo -func (c *GitCommand) Fetch() error { - return c.OSCommand.RunCommand("git fetch") +func (c *GitCommand) Fetch(unamePassQuestion func(string) string, runDry bool) error { + newCredentials := SavedCredentials{ + Username: c.SavedCredentials.Username, + Password: c.SavedCredentials.Password, + } + cmd := "git fetch" + if runDry { + cmd += " --dry-run" + } + + err := c.OSCommand.DetectUnamePass(cmd, func(question string) string { + if question == "username" && len(c.SavedCredentials.Username) != 0 { + return c.SavedCredentials.Username + } + if question == "password" && len(c.SavedCredentials.Password) != 0 { + return c.SavedCredentials.Password + } + output := unamePassQuestion(question) + if question == "password" { + newCredentials.Password = output + } else { + newCredentials.Username = output + } + return output + }) + if err == nil { + c.SavedCredentials = newCredentials + } + return err } // ResetToCommit reset to commit diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 6d42aee48..52826c4d7 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -11,10 +11,12 @@ import ( "os" "os/exec" "strings" + "sync" "time" // "strings" + "github.com/fatih/color" "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" @@ -350,9 +352,50 @@ func (gui *Gui) promptAnonymousReporting() error { } func (gui *Gui) fetch(g *gocui.Gui) error { - gui.GitCommand.Fetch() + err := gui.GitCommand.Fetch(func(passOrUname string) string { + if !gui.GitCommand.SavedCredentials.HasAsked { + var wg sync.WaitGroup + wg.Add(1) + gui.GitCommand.SavedCredentials.HasAsked = true + close := func(g *gocui.Gui, v *gocui.View) error { + wg.Done() + return nil + } + _ = gui.createConfirmationPanel( + g, + g.CurrentView(), + gui.Tr.SLocalize("RepoRequiresCredentialsTitle"), + gui.Tr.SLocalize("RepoRequiresCredentialsBody"), + close, + close, + ) + wg.Wait() + } + return gui.waitForPassUname(gui.g, gui.g.CurrentView(), passOrUname) + }, false) + + var reTryErr error + if err != nil && strings.Contains(err.Error(), "exit status 128") { + var wg sync.WaitGroup + wg.Add(1) + + currentView := g.CurrentView() + colorFunction := color.New(color.FgRed).SprintFunc() + coloredMessage := colorFunction(strings.TrimSpace(gui.Tr.SLocalize("PassUnameWrong"))) + close := func(g *gocui.Gui, v *gocui.View) error { + wg.Done() + return nil + } + _ = gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, close, close) + wg.Wait() + reTryErr = gui.fetch(g) + } + gui.refreshStatus(g) - return nil + if reTryErr != nil { + return reTryErr + } + return err } func (gui *Gui) updateLoader(g *gocui.Gui) error { @@ -407,7 +450,12 @@ func (gui *Gui) Run() error { return err } - gui.goEvery(g, time.Second*60, gui.fetch) + go func() { + err := gui.fetch(g) + if err == nil { + gui.goEvery(g, time.Second*10, gui.fetch) + } + }() gui.goEvery(g, time.Second*10, gui.refreshFiles) gui.goEvery(g, time.Millisecond*50, gui.updateLoader) gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus) diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index ca88585b8..86b679692 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -412,6 +412,12 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "NoBranchOnRemote", Other: `Deze branch bestaat niet op de remote. U moet het eerst naar de remote pushen.`, + }, &i18n.Message{ + ID: "RepoRequiresCredentialsTitle", + Other: `Repo heeft inloggegevens nodig`, + }, &i18n.Message{ + ID: "RepoRequiresCredentialsBody", + Other: `Lazygit heeft inloggegevens nodig om git fetch te kunnen gebruiken`, }, ) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index dc4e1537c..07dbeaffe 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -420,6 +420,12 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "NoBranchOnRemote", Other: `This branch doesn't exist on remote. You need to push it to remote first.`, + }, &i18n.Message{ + ID: "RepoRequiresCredentialsTitle", + Other: `Repo requires credentials`, + }, &i18n.Message{ + ID: "RepoRequiresCredentialsBody", + Other: `Lazygit needs credentials to use git fetch`, }, ) } diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index d5a5d6ec0..11c4b574b 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -395,6 +395,12 @@ func addPolish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "NoBranchOnRemote", Other: `Ta gałąź nie istnieje na zdalnym. Najpierw musisz go odepchnąć na odległość.`, + }, &i18n.Message{ + ID: "RepoRequiresCredentialsTitle", + Other: `Repo requires credentials`, + }, &i18n.Message{ + ID: "RepoRequiresCredentialsBody", + Other: `Lazygit needs credentials to use git fetch`, }, ) }