1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-28 16:02:01 +03:00

support multiple modes of git pull

This commit is contained in:
Jesse Duffield
2020-08-11 21:18:38 +10:00
parent 1c0da2967c
commit fcd5aea04e
7 changed files with 92 additions and 34 deletions

View File

@ -369,11 +369,26 @@ func (c *GitCommand) RebaseBranch(branchName string) error {
return c.OSCommand.RunPreparedCommand(cmd) return c.OSCommand.RunPreparedCommand(cmd)
} }
type FetchOptions struct {
PromptUserForCredential func(string) string
RemoteName string
BranchName string
}
// Fetch fetch git repo // Fetch fetch git repo
func (c *GitCommand) Fetch(promptUserForCredential func(string) string, canPromptForCredential bool) error { func (c *GitCommand) Fetch(opts FetchOptions) error {
return c.OSCommand.DetectUnamePass("git fetch", func(question string) string { command := "git fetch"
if canPromptForCredential {
return promptUserForCredential(question) if opts.RemoteName != "" {
command = fmt.Sprintf("%s %s", command, opts.RemoteName)
}
if opts.BranchName != "" {
command = fmt.Sprintf("%s %s", command, opts.BranchName)
}
return c.OSCommand.DetectUnamePass(command, func(question string) string {
if opts.PromptUserForCredential != nil {
return opts.PromptUserForCredential(question)
} }
return "\n" return "\n"
}) })
@ -430,10 +445,20 @@ func (c *GitCommand) ListStash() (string, error) {
return c.OSCommand.RunCommandWithOutput("git stash list") return c.OSCommand.RunCommandWithOutput("git stash list")
} }
type MergeOpts struct {
FastForwardOnly bool
}
// Merge merge // Merge merge
func (c *GitCommand) Merge(branchName string) error { func (c *GitCommand) Merge(branchName string, opts MergeOpts) error {
mergeArgs := c.Config.GetUserConfig().GetString("git.merging.args") mergeArgs := c.Config.GetUserConfig().GetString("git.merging.args")
return c.OSCommand.RunCommand("git merge --no-edit %s %s", mergeArgs, branchName)
command := fmt.Sprintf("git merge --no-edit %s %s", mergeArgs, branchName)
if opts.FastForwardOnly {
command = fmt.Sprintf("%s --ff-only", command)
}
return c.OSCommand.RunCommand(command)
} }
// AbortMerge abort merge // AbortMerge abort merge
@ -487,7 +512,7 @@ func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
// Pull pulls from repo // Pull pulls from repo
func (c *GitCommand) Pull(args string, promptUserForCredential func(string) string) error { func (c *GitCommand) Pull(args string, promptUserForCredential func(string) string) error {
return c.OSCommand.DetectUnamePass("git pull --no-edit "+args, promptUserForCredential) return c.OSCommand.DetectUnamePass("git pull --no-edit --rebase ", promptUserForCredential)
} }
// PullWithoutPasswordCheck assumes that the pull will not prompt the user for a password // PullWithoutPasswordCheck assumes that the pull will not prompt the user for a password

View File

@ -703,7 +703,7 @@ func TestGitCommandMerge(t *testing.T) {
return exec.Command("echo") return exec.Command("echo")
} }
assert.NoError(t, gitCmd.Merge("test")) assert.NoError(t, gitCmd.Merge("test", MergeOpts{}))
} }
// TestGitCommandUsingGpg is a function. // TestGitCommandUsingGpg is a function.

View File

@ -129,8 +129,12 @@ func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error {
return err return err
} }
go func() { go func() {
err := gui.fetch(g, v, true) err := gui.fetch(true)
gui.HandleCredentialsPopup(g, err) gui.HandleCredentialsPopup(err)
if err == nil {
_ = gui.closeConfirmationPrompt(gui.g, true)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
}
}() }()
return nil return nil
} }
@ -321,7 +325,7 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("MergingTitle"), prompt, return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("MergingTitle"), prompt,
func(g *gocui.Gui, v *gocui.View) error { func(g *gocui.Gui, v *gocui.View) error {
err := gui.GitCommand.Merge(branchName) err := gui.GitCommand.Merge(branchName, commands.MergeOpts{})
return gui.handleGenericMergeCommandResult(err) return gui.handleGenericMergeCommandResult(err)
}, nil) }, nil)
} }

View File

@ -77,7 +77,7 @@ func (gui *Gui) handleCredentialsViewFocused(g *gocui.Gui, v *gocui.View) error
} }
// HandleCredentialsPopup handles the views after executing a command that might ask for credentials // HandleCredentialsPopup handles the views after executing a command that might ask for credentials
func (gui *Gui) HandleCredentialsPopup(g *gocui.Gui, cmdErr error) { func (gui *Gui) HandleCredentialsPopup(cmdErr error) {
_, _ = gui.g.SetViewOnBottom("credentials") _, _ = gui.g.SetViewOnBottom("credentials")
if cmdErr != nil { if cmdErr != nil {
errMessage := cmdErr.Error() errMessage := cmdErr.Error()
@ -86,8 +86,5 @@ func (gui *Gui) HandleCredentialsPopup(g *gocui.Gui, cmdErr error) {
} }
// we are not logging this error because it may contain a password // we are not logging this error because it may contain a password
_ = gui.createSpecificErrorPanel(errMessage, gui.getFilesView(), false) _ = gui.createSpecificErrorPanel(errMessage, gui.getFilesView(), false)
} else {
_ = gui.closeConfirmationPrompt(g, true)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
} }
} }

View File

@ -446,7 +446,7 @@ func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
} }
for branchName, branch := range conf.Branches { for branchName, branch := range conf.Branches {
if branchName == currentBranch.Name { if branchName == currentBranch.Name {
return gui.pullFiles(v, fmt.Sprintf("%s %s", branch.Remote, branchName)) return gui.pullFiles(PullFilesOptions{RemoteName: branch.Remote, BranchName: branch.Name})
} }
} }
@ -459,27 +459,49 @@ func (gui *Gui) handlePullFiles(g *gocui.Gui, v *gocui.View) error {
} }
return gui.createErrorPanel(errorMessage) return gui.createErrorPanel(errorMessage)
} }
return gui.pullFiles(v, "") return gui.pullFiles(PullFilesOptions{})
}) })
} }
return gui.pullFiles(v, "") return gui.pullFiles(PullFilesOptions{})
} }
func (gui *Gui) pullFiles(v *gocui.View, args string) error { type PullFilesOptions struct {
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PullWait")); err != nil { RemoteName string
BranchName string
}
func (gui *Gui) pullFiles(opts PullFilesOptions) error {
if err := gui.createLoaderPanel(gui.g, gui.g.CurrentView(), gui.Tr.SLocalize("PullWait")); err != nil {
return err return err
} }
// we want to first fetch, handling username if it comes up, then either merge or rebase. If merging we might have a merge conflict, likewise if rebasing we might have a conflict too. strategy := gui.Config.GetUserConfig().GetString("git.pull.mode")
// we need a way of saying .then or .catch
// what if we had a struct which contained an array of functions to run, each of which return a function, or perhaps write to a channel when they're done, and if there is no error, we run the next thing. In this case we first want to fetch, potentially handling a credential popup, then we want to rebase.
go func() { go func() {
err := gui.GitCommand.Pull(args, gui.promptUserForCredential) err := gui.GitCommand.Fetch(
// gui.handleGenericMergeCommandResult(err) commands.FetchOptions{
gui.HandleCredentialsPopup(gui.g, err) PromptUserForCredential: gui.promptUserForCredential,
RemoteName: opts.RemoteName,
BranchName: opts.BranchName,
},
)
gui.HandleCredentialsPopup(err)
if err == nil {
switch strategy {
case "rebase":
err := gui.GitCommand.RebaseBranch("FETCH_HEAD")
_ = gui.handleGenericMergeCommandResult(err)
case "merge":
err := gui.GitCommand.Merge("FETCH_HEAD", commands.MergeOpts{})
_ = gui.handleGenericMergeCommandResult(err)
case "ff-only":
err := gui.GitCommand.Merge("FETCH_HEAD", commands.MergeOpts{FastForwardOnly: true})
_ = gui.handleGenericMergeCommandResult(err)
default:
_ = gui.createErrorPanel(fmt.Sprintf("git pull strategy '%s' unrecognised", strategy))
}
}
}() }()
return nil return nil
@ -492,7 +514,11 @@ func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool, upstr
go func() { go func() {
branchName := gui.getCheckedOutBranch().Name branchName := gui.getCheckedOutBranch().Name
err := gui.GitCommand.Push(branchName, force, upstream, args, gui.promptUserForCredential) err := gui.GitCommand.Push(branchName, force, upstream, args, gui.promptUserForCredential)
gui.HandleCredentialsPopup(g, err) gui.HandleCredentialsPopup(err)
if err == nil {
_ = gui.closeConfirmationPrompt(gui.g, true)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC})
}
}() }()
return nil return nil
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -166,16 +167,21 @@ func (gui *Gui) handleInfoClick(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
func (gui *Gui) fetch(g *gocui.Gui, v *gocui.View, canAskForCredentials bool) (err error) { func (gui *Gui) fetch(canPromptForCredentials bool) (err error) {
err = gui.GitCommand.Fetch(gui.promptUserForCredential, canAskForCredentials) fetchOpts := commands.FetchOptions{}
if canPromptForCredentials {
fetchOpts.PromptUserForCredential = gui.promptUserForCredential
}
if canAskForCredentials && err != nil && strings.Contains(err.Error(), "exit status 128") { err = gui.GitCommand.Fetch(fetchOpts)
if canPromptForCredentials && err != nil && strings.Contains(err.Error(), "exit status 128") {
colorFunction := color.New(color.FgRed).SprintFunc() colorFunction := color.New(color.FgRed).SprintFunc()
coloredMessage := colorFunction(strings.TrimSpace(gui.Tr.SLocalize("PassUnameWrong"))) coloredMessage := colorFunction(strings.TrimSpace(gui.Tr.SLocalize("PassUnameWrong")))
close := func(g *gocui.Gui, v *gocui.View) error { close := func(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
_ = gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Error"), coloredMessage, close, close) _ = gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("Error"), coloredMessage, close, close)
} }
gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, COMMITS, REMOTES, TAGS}, mode: ASYNC}) gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, COMMITS, REMOTES, TAGS}, mode: ASYNC})

View File

@ -501,12 +501,12 @@ func (gui *Gui) startBackgroundFetch() {
if !isNew { if !isNew {
time.After(60 * time.Second) time.After(60 * time.Second)
} }
err := gui.fetch(gui.g, gui.g.CurrentView(), false) err := gui.fetch(false)
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew { if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
_ = gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("NoAutomaticGitFetchTitle"), gui.Tr.SLocalize("NoAutomaticGitFetchBody"), nil, nil) _ = gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("NoAutomaticGitFetchTitle"), gui.Tr.SLocalize("NoAutomaticGitFetchBody"), nil, nil)
} else { } else {
gui.goEvery(time.Second*60, gui.stopChan, func() error { gui.goEvery(time.Second*60, gui.stopChan, func() error {
err := gui.fetch(gui.g, gui.g.CurrentView(), false) err := gui.fetch(false)
return err return err
}) })
} }