11package cmd
22
33import (
4+ "errors"
45 "fmt"
56 "strings"
67
@@ -12,8 +13,8 @@ import (
1213)
1314
1415type pushOptions struct {
15- auto bool
16- draft bool
16+ auto bool
17+ draft bool
1718 skipPRs bool
1819}
1920
@@ -54,40 +55,44 @@ func runPush(cfg *config.Config, opts *pushOptions) error {
5455 return nil
5556 }
5657
57- s , err := resolveStack (sf , currentBranch , cfg )
58- if err != nil {
59- cfg .Errorf ("%s" , err )
60- return nil
61- }
62- if s == nil {
58+ // Find the stack for the current branch without switching branches.
59+ // Push should never change the user's checked-out branch.
60+ stacks := sf .FindAllStacksForBranch (currentBranch )
61+ if len (stacks ) == 0 {
6362 cfg .Errorf ("current branch %q is not part of a stack" , currentBranch )
6463 return nil
6564 }
66-
67- // Re-read current branch in case disambiguation caused a checkout
68- currentBranch , err = git .CurrentBranch ()
69- if err != nil {
70- cfg .Errorf ("failed to get current branch: %s" , err )
65+ if len (stacks ) > 1 {
66+ cfg .Errorf ("branch %q belongs to multiple stacks; checkout a non-trunk branch first" , currentBranch )
7167 return nil
7268 }
69+ s := stacks [0 ]
7370
7471 client , err := cfg .GitHubClient ()
7572 if err != nil {
7673 cfg .Errorf ("failed to create GitHub client: %s" , err )
7774 return nil
7875 }
7976
80- // Push all branches
77+ // Push all active branches atomically
78+ remote , err := pickRemote (cfg , currentBranch )
79+ if err != nil {
80+ cfg .Errorf ("%s" , err )
81+ return nil
82+ }
8183 merged := s .MergedBranches ()
8284 if len (merged ) > 0 {
8385 cfg .Printf ("Skipping %d merged %s" , len (merged ), plural (len (merged ), "branch" , "branches" ))
8486 }
85- for _ , b := range s .ActiveBranches () {
86- cfg .Printf ("Pushing %s..." , b .Branch )
87- if err := git .Push ("origin" , []string {b .Branch }, true , false ); err != nil {
88- cfg .Errorf ("failed to push %s: %s" , b .Branch , err )
89- return nil
90- }
87+ activeBranches := s .ActiveBranches ()
88+ branchNames := make ([]string , len (activeBranches ))
89+ for i , b := range activeBranches {
90+ branchNames [i ] = b .Branch
91+ }
92+ cfg .Printf ("Pushing %d %s to %s..." , len (branchNames ), plural (len (branchNames ), "branch" , "branches" ), remote )
93+ if err := git .Push (remote , branchNames , true , true ); err != nil {
94+ cfg .Errorf ("failed to push: %s" , err )
95+ return nil
9196 }
9297
9398 if opts .skipPRs {
@@ -235,3 +240,30 @@ func humanize(s string) string {
235240 return r
236241 }, s )
237242}
243+
244+ // pickRemote determines which remote to push to. It delegates to
245+ // git.ResolveRemote for config-based resolution and remote listing.
246+ // If multiple remotes exist with no configured default, the user is
247+ // prompted to select one interactively.
248+ func pickRemote (cfg * config.Config , branch string ) (string , error ) {
249+ remote , err := git .ResolveRemote (branch )
250+ if err == nil {
251+ return remote , nil
252+ }
253+
254+ var multi * git.ErrMultipleRemotes
255+ if ! errors .As (err , & multi ) {
256+ return "" , err
257+ }
258+
259+ if ! cfg .IsInteractive () {
260+ return "" , fmt .Errorf ("multiple remotes configured; set remote.pushDefault or use an interactive terminal" )
261+ }
262+
263+ p := prompter .New (cfg .In , cfg .Out , cfg .Err )
264+ selected , promptErr := p .Select ("Multiple remotes found. Which remote should be used?" , "" , multi .Remotes )
265+ if promptErr != nil {
266+ return "" , fmt .Errorf ("remote selection: %w" , promptErr )
267+ }
268+ return multi .Remotes [selected ], nil
269+ }
0 commit comments