always specify upstream when pushing/pulling
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 '<remote> <branchname>'`,
|
||||
InvalidUpstream: "Invalid upstream. Must be in the format '<remote> <branchname>'",
|
||||
ReturnToRemotesList: `Return to remotes list`,
|
||||
LcAddNewRemote: `add new remote`,
|
||||
LcNewRemoteName: `New remote name:`,
|
||||
|
||||
Reference in New Issue
Block a user