From 1c84f7731920e1970d52f81361d321268054e559 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 15 Jan 2022 14:20:09 +1100 Subject: always specify upstream when pushing/pulling --- pkg/commands/git.go | 2 +- pkg/commands/git_commands/branch.go | 9 +--- pkg/commands/loaders/branches.go | 33 ++++++++++++--- pkg/commands/models/branch.go | 32 ++++++++------ pkg/gui/branches_panel.go | 23 +++++----- pkg/gui/confirmation_panel.go | 2 +- pkg/gui/diffing.go | 4 +- pkg/gui/files_panel.go | 83 +++++++++++++------------------------ pkg/gui/presentation/branches.go | 8 +++- pkg/i18n/english.go | 4 ++ 10 files changed, 104 insertions(+), 96 deletions(-) (limited to 'pkg') diff --git a/pkg/commands/git.go b/pkg/commands/git.go index d3ea3166c..7338cd0f1 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -136,7 +136,7 @@ func NewGitCommandAux( Tag: tagCommands, WorkingTree: workingTreeCommands, Loaders: Loaders{ - Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName), + Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName, configCommands), CommitFiles: loaders.NewCommitFileLoader(cmn, cmd), Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode), Files: fileLoader, diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go index 50dfc036b..59af77612 100644 --- a/pkg/commands/git_commands/branch.go +++ b/pkg/commands/git_commands/branch.go @@ -108,13 +108,8 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj return self.cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)).DontLog() } -func (self *BranchCommands) SetCurrentBranchUpstream(upstream string) error { - return self.cmd.New("git branch --set-upstream-to=" + self.cmd.Quote(upstream)).Run() -} - -func (self *BranchCommands) GetUpstream(branchName string) (string, error) { - output, err := self.cmd.New(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", self.cmd.Quote(branchName))).DontLog().RunWithOutput() - return strings.TrimSpace(output), err +func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error { + return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run() } func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error { diff --git a/pkg/commands/loaders/branches.go b/pkg/commands/loaders/branches.go index 877b228f2..c54e65ee9 100644 --- a/pkg/commands/loaders/branches.go +++ b/pkg/commands/loaders/branches.go @@ -4,6 +4,7 @@ import ( "regexp" "strings" + "github.com/jesseduffield/go-git/v5/config" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/utils" @@ -20,27 +21,34 @@ import ( // if we find out we need to use one of these functions in the git.go file, we // can just pull them out of here and put them there and then call them from in here +type BranchLoaderConfigCommands interface { + Branches() (map[string]*config.Branch, error) +} + // BranchLoader returns a list of Branch objects for the current repo type BranchLoader struct { *common.Common getRawBranches func() (string, error) getCurrentBranchName func() (string, string, error) + config BranchLoaderConfigCommands } func NewBranchLoader( cmn *common.Common, getRawBranches func() (string, error), getCurrentBranchName func() (string, string, error), + config BranchLoaderConfigCommands, ) *BranchLoader { return &BranchLoader{ Common: cmn, getRawBranches: getRawBranches, getCurrentBranchName: getCurrentBranchName, + config: config, } } // Load the list of branches for the current repo -func (self *BranchLoader) Load(reflogCommits []*models.Commit) []*models.Branch { +func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) { branches := self.obtainBranches() reflogBranches := self.obtainReflogBranches(reflogCommits) @@ -77,11 +85,25 @@ outer: if !foundHead { currentBranchName, currentBranchDisplayName, err := self.getCurrentBranchName() if err != nil { - panic(err) + return nil, err } branches = append([]*models.Branch{{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"}}, branches...) } - return branches + + configBranches, err := self.config.Branches() + if err != nil { + return nil, err + } + + for _, branch := range branches { + match := configBranches[branch.Name] + if match != nil { + branch.UpstreamRemote = match.Remote + branch.UpstreamBranch = match.Merge.Short() + } + } + + return branches, nil } func (self *BranchLoader) obtainBranches() []*models.Branch { @@ -116,12 +138,13 @@ func (self *BranchLoader) obtainBranches() []*models.Branch { upstreamName := split[2] if upstreamName == "" { + // if we're here then it means we do not have a local version of the remote. + // The branch might still be tracking a remote though, we just don't know + // how many commits ahead/behind it is branches = append(branches, branch) continue } - branch.UpstreamName = upstreamName - track := split[3] re := regexp.MustCompile(`ahead (\d+)`) match := re.FindStringSubmatch(track) diff --git a/pkg/commands/models/branch.go b/pkg/commands/models/branch.go index 3b8268bff..3cdf5ad6d 100644 --- a/pkg/commands/models/branch.go +++ b/pkg/commands/models/branch.go @@ -5,12 +5,16 @@ package models type Branch struct { Name string // the displayname is something like '(HEAD detached at 123asdf)', whereas in that case the name would be '123asdf' - DisplayName string - Recency string - Pushables string - Pullables string - UpstreamName string - Head bool + DisplayName string + Recency string + Pushables string + Pullables string + Head bool + // if we have a named remote locally this will be the name of that remote e.g. + // 'origin' or 'tiwood'. If we don't have the remote locally it'll look like + // 'git@github.com:tiwood/lazygit.git' + UpstreamRemote string + UpstreamBranch string } func (b *Branch) RefName() string { @@ -25,22 +29,26 @@ func (b *Branch) Description() string { return b.RefName() } -// this method does not consider the case where the git config states that a branch is tracking the config. -// The Pullables value here is based on whether or not we saw an upstream when doing `git branch` func (b *Branch) IsTrackingRemote() bool { - return b.IsRealBranch() && b.Pullables != "?" + return b.UpstreamRemote != "" +} + +// we know that the remote branch is not stored locally based on our pushable/pullable +// count being question marks. +func (b *Branch) RemoteBranchStoredLocally() bool { + return b.IsTrackingRemote() && b.Pushables != "?" && b.Pullables != "?" } func (b *Branch) MatchesUpstream() bool { - return b.IsRealBranch() && b.Pushables == "0" && b.Pullables == "0" + return b.RemoteBranchStoredLocally() && b.Pushables == "0" && b.Pullables == "0" } func (b *Branch) HasCommitsToPush() bool { - return b.IsRealBranch() && b.Pushables != "0" + return b.RemoteBranchStoredLocally() && b.Pushables != "0" } func (b *Branch) HasCommitsToPull() bool { - return b.IsRealBranch() && b.Pullables != "0" + return b.RemoteBranchStoredLocally() && b.Pullables != "0" } // for when we're in a detached head state diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 880233dc9..feef98431 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -60,7 +60,12 @@ func (gui *Gui) refreshBranches() { } } - gui.State.Branches = gui.Git.Loaders.Branches.Load(reflogCommits) + branches, err := gui.Git.Loaders.Branches.Load(reflogCommits) + if err != nil { + _ = gui.surfaceError(err) + } + + gui.State.Branches = branches if err := gui.postRefreshUpdate(gui.State.Contexts.Branches); err != nil { gui.Log.Error(err) @@ -392,25 +397,19 @@ func (gui *Gui) handleFastForward() error { if !branch.IsTrackingRemote() { return gui.createErrorPanel(gui.Tr.FwdNoUpstream) } + if !branch.RemoteBranchStoredLocally() { + return gui.createErrorPanel(gui.Tr.FwdNoLocalUpstream) + } if branch.HasCommitsToPush() { return gui.createErrorPanel(gui.Tr.FwdCommitsToPush) } - upstream, err := gui.Git.Branch.GetUpstream(branch.Name) - if err != nil { - return gui.surfaceError(err) - } - action := gui.Tr.Actions.FastForwardBranch - split := strings.Split(upstream, "/") - remoteName := split[0] - remoteBranchName := strings.Join(split[1:], "/") - message := utils.ResolvePlaceholderString( gui.Tr.Fetching, map[string]string{ - "from": fmt.Sprintf("%s/%s", remoteName, remoteBranchName), + "from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch), "to": branch.Name, }, ) @@ -421,7 +420,7 @@ func (gui *Gui) handleFastForward() error { _ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true}) } else { gui.logAction(action) - err := gui.Git.Sync.FastForward(branch.Name, remoteName, remoteBranchName) + err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch) gui.handleCredentialsPopup(err) _ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{BRANCHES}}) } diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index ecb7d26ac..0537f1b45 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -36,8 +36,8 @@ type askOpts struct { type promptOpts struct { title string initialContent string - handleConfirm func(string) error findSuggestionsFunc func(string) []*types.Suggestion + handleConfirm func(string) error } func (gui *Gui) ask(opts askOpts) error { diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go index e3b888066..2721a1880 100644 --- a/pkg/gui/diffing.go +++ b/pkg/gui/diffing.go @@ -44,8 +44,8 @@ func (gui *Gui) currentDiffTerminals() []string { branch := gui.getSelectedBranch() if branch != nil { names := []string{branch.ID()} - if branch.UpstreamName != "" { - names = append(names, branch.UpstreamName) + if branch.IsTrackingRemote() { + names = append(names, branch.ID()+"@{u}") } return names } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index d121c8275..4201afb11 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -655,42 +655,40 @@ func (gui *Gui) handlePullFiles() error { // if we have no upstream branch we need to set that first if !currentBranch.IsTrackingRemote() { - // see if we have this branch in our config with an upstream - branches, err := gui.Git.Config.Branches() - if err != nil { - return gui.surfaceError(err) - } - for branchName, branch := range branches { - if branchName == currentBranch.Name { - return gui.pullFiles(PullFilesOptions{RemoteName: branch.Remote, BranchName: branch.Name, action: action}) - } - } - suggestedRemote := getSuggestedRemote(gui.State.Remotes) return gui.prompt(promptOpts{ title: gui.Tr.EnterUpstream, - initialContent: suggestedRemote + "/" + currentBranch.Name, - findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc("/"), + initialContent: suggestedRemote + " " + currentBranch.Name, + findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "), handleConfirm: func(upstream string) error { - if err := gui.Git.Branch.SetCurrentBranchUpstream(upstream); err != nil { + var upstreamBranch, upstreamRemote string + split := strings.Split(upstream, " ") + if len(split) != 2 { + return gui.createErrorPanel(gui.Tr.InvalidUpstream) + } + + upstreamRemote = split[0] + upstreamBranch = split[1] + + if err := gui.Git.Branch.SetCurrentBranchUpstream(upstreamRemote, upstreamBranch); err != nil { errorMessage := err.Error() if strings.Contains(errorMessage, "does not exist") { errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream) } return gui.createErrorPanel(errorMessage) } - return gui.pullFiles(PullFilesOptions{action: action}) + return gui.pullFiles(PullFilesOptions{UpstreamRemote: upstreamRemote, UpstreamBranch: upstreamBranch, action: action}) }, }) } - return gui.pullFiles(PullFilesOptions{action: action}) + return gui.pullFiles(PullFilesOptions{UpstreamRemote: currentBranch.UpstreamRemote, UpstreamBranch: currentBranch.UpstreamBranch, action: action}) } type PullFilesOptions struct { - RemoteName string - BranchName string + UpstreamRemote string + UpstreamBranch string FastForwardOnly bool action string } @@ -714,8 +712,8 @@ func (gui *Gui) pullWithLock(opts PullFilesOptions) error { err := gui.Git.Sync.Pull( git_commands.PullOptions{ - RemoteName: opts.RemoteName, - BranchName: opts.BranchName, + RemoteName: opts.UpstreamRemote, + BranchName: opts.UpstreamBranch, FastForwardOnly: opts.FastForwardOnly, }, ) @@ -782,28 +780,18 @@ func (gui *Gui) pushFiles() error { } if currentBranch.IsTrackingRemote() { + opts := pushOpts{ + force: false, + upstreamRemote: currentBranch.UpstreamRemote, + upstreamBranch: currentBranch.UpstreamBranch, + } if currentBranch.HasCommitsToPull() { - return gui.requestToForcePush() + opts.force = true + return gui.requestToForcePush(opts) } else { - return gui.push(pushOpts{}) + return gui.push(opts) } } else { - // see if we have an upstream for this branch in our config - upstreamRemote, upstreamBranch, err := gui.upstreamForBranchInConfig(currentBranch.Name) - if err != nil { - return gui.surfaceError(err) - } - - if upstreamBranch != "" { - return gui.push( - pushOpts{ - force: false, - upstreamRemote: upstreamRemote, - upstreamBranch: upstreamBranch, - }, - ) - } - suggestedRemote := getSuggestedRemote(gui.State.Remotes) if gui.Git.Config.GetPushToCurrent() { @@ -850,7 +838,7 @@ func getSuggestedRemote(remotes []*models.Remote) string { return remotes[0].Name } -func (gui *Gui) requestToForcePush() error { +func (gui *Gui) requestToForcePush(opts pushOpts) error { forcePushDisabled := gui.UserConfig.Git.DisableForcePushing if forcePushDisabled { return gui.createErrorPanel(gui.Tr.ForcePushDisabled) @@ -860,26 +848,11 @@ func (gui *Gui) requestToForcePush() error { title: gui.Tr.ForcePush, prompt: gui.Tr.ForcePushPrompt, handleConfirm: func() error { - return gui.push(pushOpts{force: true}) + return gui.push(opts) }, }) } -func (gui *Gui) upstreamForBranchInConfig(branchName string) (string, string, error) { - branches, err := gui.Git.Config.Branches() - if err != nil { - return "", "", err - } - - for configBranchName, configBranch := range branches { - if configBranchName == branchName { - return configBranch.Remote, configBranchName, nil - } - } - - return "", "", nil -} - func (gui *Gui) switchToMerge() error { file := gui.getSelectedFile() if file == nil { diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go index 9b35b5c3c..c8a613516 100644 --- a/pkg/gui/presentation/branches.go +++ b/pkg/gui/presentation/branches.go @@ -43,7 +43,13 @@ func getBranchDisplayStrings(b *models.Branch, fullDescription bool, diffed bool res := []string{recencyColor.Sprint(b.Recency), coloredName} if fullDescription { - return append(res, style.FgYellow.Sprint(b.UpstreamName)) + return append( + res, + fmt.Sprintf("%s %s", + style.FgYellow.Sprint(b.UpstreamRemote), + style.FgYellow.Sprint(b.UpstreamBranch), + ), + ) } return res } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 4ff6c3555..b815b6b70 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -188,6 +188,7 @@ type TranslationSet struct { ConfirmRebase string ConfirmMerge string FwdNoUpstream string + FwdNoLocalUpstream string FwdCommitsToPush string ErrorOccurred string NoRoom string @@ -281,6 +282,7 @@ type TranslationSet struct { LcEnterFile string ExitLineByLineMode string EnterUpstream string + InvalidUpstream string ReturnToRemotesList string LcAddNewRemote string LcNewRemoteName string @@ -738,6 +740,7 @@ func EnglishTranslationSet() TranslationSet { ConfirmRebase: "Are you sure you want to rebase '{{.checkedOutBranch}}' onto '{{.selectedBranch}}'?", ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?", FwdNoUpstream: "Cannot fast-forward a branch with no upstream", + FwdNoLocalUpstream: "Cannot fast-forward a branch whose remote is not registered locally", FwdCommitsToPush: "Cannot fast-forward a branch with commits to push", ErrorOccurred: "An error occurred! Please create an issue at", NoRoom: "Not enough room", @@ -831,6 +834,7 @@ func EnglishTranslationSet() TranslationSet { LcEnterFile: "enter file to add selected lines to the patch (or toggle directory collapsed)", ExitLineByLineMode: `exit line-by-line mode`, EnterUpstream: `Enter upstream as ' '`, + InvalidUpstream: "Invalid upstream. Must be in the format ' '", ReturnToRemotesList: `Return to remotes list`, LcAddNewRemote: `add new remote`, LcNewRemoteName: `New remote name:`, -- cgit v1.2.3