Repository: jesseduffield/lazygit (language: go)

Issue:
Critical issue: changing branches while pushing to a new branch can mix-up merge configuration
**Describe the bug**

If you change branch while pushing a branch, it is possible that the old and new branch are now linked to the *same branch* on the remote.

I've hit this four times so far, sometimes with severe unexpected implications (pushing commits publicly that I never intended to).

**To Reproduce**
Steps to reproduce the behavior:

1. Create a new branch from a branch that already has an upstream set
2. Push the new branch
3. While the push operation is in progress, switch back to the previous branch
4. Observe broken configuration

**Screenshots**

https://github.com/user-attachments/assets/fa9bb0be-458e-401b-8a45-d734a6df6591

**Version info:**

```csharp
❯ lazygit --version
commit=, build date=, build source=homebrew, version=HEAD-40d6800, os=darwin, arch=arm64, git version=2.48.1

❯ git --version
git version 2.48.1
```



Discussion / hints:
Sorry that this led to erroneously pushing commits.

I don't believe we're doing anything fancy when it comes to pushing or checking out, so I suspect that this would also happen if performing the actions in the CLI (in separate terminal tabs concurrently).

If that's true, then we should pester the guys on the git mailing list, and potentially disallow checking out while pushing. It would also be good to know if this only happens in certain situations e.g. I use:
```toml
[push]
	default = current
```
And have not observed this problem.
Actually something that just came to mind, we do have a mutex on sync operations, so I can imagine a situation where a background fetch is happening when you trigger the push so the push only actually happens after the checkout completes. @peppy is this an intermittent issue for you?

Fix patch:
diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go
index ab6942d05ad..4ac1a5c1fb6 100644
--- a/pkg/commands/git_commands/sync.go
+++ b/pkg/commands/git_commands/sync.go
@@ -1,6 +1,8 @@
 package git_commands
 
 import (
+	"fmt"
+
 	"github.com/go-errors/errors"
 	"github.com/jesseduffield/gocui"
 	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -20,6 +22,7 @@ func NewSyncCommands(gitCommon *GitCommon) *SyncCommands {
 type PushOpts struct {
 	Force          bool
 	ForceWithLease bool
+	CurrentBranch  string
 	UpstreamRemote string
 	UpstreamBranch string
 	SetUpstream    bool
@@ -35,7 +38,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
 		ArgIf(opts.ForceWithLease, "--force-with-lease").
 		ArgIf(opts.SetUpstream, "--set-upstream").
 		ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
-		ArgIf(opts.UpstreamBranch != "", "HEAD:"+opts.UpstreamBranch).
+		ArgIf(opts.UpstreamBranch != "", fmt.Sprintf("refs/heads/%s:%s", opts.CurrentBranch, opts.UpstreamBranch)).
 		ToArgv()
 
 	cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go
index a6140d9d015..66c480b93c4 100644
--- a/pkg/gui/controllers/sync_controller.go
+++ b/pkg/gui/controllers/sync_controller.go
@@ -200,6 +200,7 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
 			git_commands.PushOpts{
 				Force:          opts.force,
 				ForceWithLease: opts.forceWithLease,
+				CurrentBranch:  currentBranch.Name,
 				UpstreamRemote: opts.upstreamRemote,
 				UpstreamBranch: opts.upstreamBranch,
 				SetUpstream:    opts.setUpstream,


Test patch:
diff --git a/pkg/commands/git_commands/sync_test.go b/pkg/commands/git_commands/sync_test.go
index 183912c31bc..d22147627fe 100644
--- a/pkg/commands/git_commands/sync_test.go
+++ b/pkg/commands/git_commands/sync_test.go
@@ -44,11 +44,12 @@ func TestSyncPush(t *testing.T) {
 			testName: "Push with force disabled, upstream supplied",
 			opts: PushOpts{
 				ForceWithLease: false,
+				CurrentBranch:  "master",
 				UpstreamRemote: "origin",
 				UpstreamBranch: "master",
 			},
 			test: func(cmdObj oscommands.ICmdObj, err error) {
-				assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "HEAD:master"})
+				assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "refs/heads/master:master"})
 				assert.NoError(t, err)
 			},
 		},
@@ -56,12 +57,13 @@ func TestSyncPush(t *testing.T) {
 			testName: "Push with force disabled, setting upstream",
 			opts: PushOpts{
 				ForceWithLease: false,
+				CurrentBranch:  "master-local",
 				UpstreamRemote: "origin",
 				UpstreamBranch: "master",
 				SetUpstream:    true,
 			},
 			test: func(cmdObj oscommands.ICmdObj, err error) {
-				assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "HEAD:master"})
+				assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "refs/heads/master-local:master"})
 				assert.NoError(t, err)
 			},
 		},
@@ -69,12 +71,13 @@ func TestSyncPush(t *testing.T) {
 			testName: "Push with force-with-lease enabled, setting upstream",
 			opts: PushOpts{
 				ForceWithLease: true,
+				CurrentBranch:  "master",
 				UpstreamRemote: "origin",
 				UpstreamBranch: "master",
 				SetUpstream:    true,
 			},
 			test: func(cmdObj oscommands.ICmdObj, err error) {
-				assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "HEAD:master"})
+				assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "refs/heads/master:master"})
 				assert.NoError(t, err)
 			},
 		},