1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-07 07:19:57 +02:00

always specify upstream when pushing/pulling

This commit is contained in:
Jesse Duffield 2022-01-15 14:20:09 +11:00
parent 8d8bdb948b
commit 1c84f77319
60 changed files with 209 additions and 96 deletions

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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}})
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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:`,

View File

@ -0,0 +1 @@
myfile4

View File

@ -0,0 +1 @@
766e681a51daa75233c1c4ae8845be2c893577d5 branch 'master' of ../actual_remote

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1 @@
c9fd61f40de25556977e063683d1de612f931ccb

View File

@ -0,0 +1,16 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI
[remote "origin"]
url = ../actual_remote
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,7 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -0,0 +1,7 @@
0000000000000000000000000000000000000000 972fb9caab8b8536ae38687fec98304b76748b9d CI <CI@example.com> 1642217132 +1100 commit (initial): myfile1
972fb9caab8b8536ae38687fec98304b76748b9d c9fd61f40de25556977e063683d1de612f931ccb CI <CI@example.com> 1642217132 +1100 commit: myfile2
c9fd61f40de25556977e063683d1de612f931ccb 06d3929607b7519beb45ca67165a1f2b5c0e578b CI <CI@example.com> 1642217132 +1100 commit: myfile3
06d3929607b7519beb45ca67165a1f2b5c0e578b 766e681a51daa75233c1c4ae8845be2c893577d5 CI <CI@example.com> 1642217132 +1100 commit: myfile4
766e681a51daa75233c1c4ae8845be2c893577d5 c9fd61f40de25556977e063683d1de612f931ccb CI <CI@example.com> 1642217132 +1100 reset: moving to HEAD~2
c9fd61f40de25556977e063683d1de612f931ccb 766e681a51daa75233c1c4ae8845be2c893577d5 CI <CI@example.com> 1642217139 +1100 rebase -i (start): checkout 766e681a51daa75233c1c4ae8845be2c893577d5
766e681a51daa75233c1c4ae8845be2c893577d5 766e681a51daa75233c1c4ae8845be2c893577d5 CI <CI@example.com> 1642217139 +1100 rebase -i (finish): returning to refs/heads/master

View File

@ -0,0 +1,6 @@
0000000000000000000000000000000000000000 972fb9caab8b8536ae38687fec98304b76748b9d CI <CI@example.com> 1642217132 +1100 commit (initial): myfile1
972fb9caab8b8536ae38687fec98304b76748b9d c9fd61f40de25556977e063683d1de612f931ccb CI <CI@example.com> 1642217132 +1100 commit: myfile2
c9fd61f40de25556977e063683d1de612f931ccb 06d3929607b7519beb45ca67165a1f2b5c0e578b CI <CI@example.com> 1642217132 +1100 commit: myfile3
06d3929607b7519beb45ca67165a1f2b5c0e578b 766e681a51daa75233c1c4ae8845be2c893577d5 CI <CI@example.com> 1642217132 +1100 commit: myfile4
766e681a51daa75233c1c4ae8845be2c893577d5 c9fd61f40de25556977e063683d1de612f931ccb CI <CI@example.com> 1642217132 +1100 reset: moving to HEAD~2
c9fd61f40de25556977e063683d1de612f931ccb 766e681a51daa75233c1c4ae8845be2c893577d5 CI <CI@example.com> 1642217139 +1100 rebase -i (finish): refs/heads/master onto 766e681a51daa75233c1c4ae8845be2c893577d5

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 766e681a51daa75233c1c4ae8845be2c893577d5 CI <CI@example.com> 1642217132 +1100 fetch origin: storing head

View File

@ -0,0 +1 @@
766e681a51daa75233c1c4ae8845be2c893577d5

View File

@ -0,0 +1 @@
766e681a51daa75233c1c4ae8845be2c893577d5

View File

@ -0,0 +1 @@
test1

View File

@ -0,0 +1 @@
test2

View File

@ -0,0 +1 @@
test3

View File

@ -0,0 +1 @@
test4

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1,8 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = /Users/jesseduffieldduffield/go/src/github.com/jesseduffield/lazygit/test/integration/pullAndSetUpstream/./actual

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,7 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -0,0 +1,2 @@
# pack-refs with: peeled fully-peeled sorted
766e681a51daa75233c1c4ae8845be2c893577d5 refs/heads/master

View File

@ -0,0 +1 @@
{"KeyEvents":[{"Timestamp":423,"Mod":0,"Key":256,"Ch":112},{"Timestamp":1032,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1216,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1376,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1536,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1687,"Mod":0,"Key":127,"Ch":127},{"Timestamp":1847,"Mod":0,"Key":127,"Ch":127},{"Timestamp":2016,"Mod":0,"Key":127,"Ch":127},{"Timestamp":2240,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3044,"Mod":0,"Key":27,"Ch":0},{"Timestamp":3768,"Mod":0,"Key":256,"Ch":112},{"Timestamp":4184,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4232,"Mod":0,"Key":256,"Ch":115},{"Timestamp":4288,"Mod":0,"Key":256,"Ch":100},{"Timestamp":4728,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5488,"Mod":0,"Key":27,"Ch":0},{"Timestamp":5799,"Mod":0,"Key":256,"Ch":112},{"Timestamp":6273,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7207,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}

View File

@ -0,0 +1,33 @@
#!/bin/sh
set -e
cd $1
git init
git config user.email "CI@example.com"
git config user.name "CI"
echo test1 > myfile1
git add .
git commit -am "myfile1"
echo test2 > myfile2
git add .
git commit -am "myfile2"
echo test3 > myfile3
git add .
git commit -am "myfile3"
echo test4 > myfile4
git add .
git commit -am "myfile4"
cd ..
git clone --bare ./actual actual_remote
cd actual
# the test is to ensure that we actually can pull these two commits back from the origin
git reset --hard HEAD~2
git remote add origin ../actual_remote
git fetch origin

View File

@ -0,0 +1,4 @@
{
"description": "pull changes from the remote, setting upstream",
"speed": 10
}