@@ -133,6 +133,7 @@ func TestRebase_SquashMergedBranch_UsesOnto(t *testing.T) {
133133 }
134134
135135 mock := newRebaseMock (tmpDir , "b2" )
136+ mock .BranchExistsFn = func (name string ) bool { return true }
136137 mock .RevParseFn = func (ref string ) (string , error ) {
137138 if sha , ok := branchSHAs [ref ]; ok {
138139 return sha , nil
@@ -197,6 +198,7 @@ func TestRebase_OntoPropagatesToSubsequentBranches(t *testing.T) {
197198 }
198199
199200 mock := newRebaseMock (tmpDir , "b3" )
201+ mock .BranchExistsFn = func (name string ) bool { return true }
200202 mock .RevParseFn = func (ref string ) (string , error ) {
201203 if sha , ok := branchSHAs [ref ]; ok {
202204 return sha , nil
@@ -927,7 +929,6 @@ func TestRebase_FastForwardsBranchFromRemote(t *testing.T) {
927929 output := string (errOut )
928930
929931 assert .NoError (t , err )
930-
931932 // b1 should be fast-forwarded to remote SHA
932933 require .Len (t , updateBranchRefCalls , 1 , "should fast-forward b1 via UpdateBranchRef" )
933934 assert .Equal (t , "b1" , updateBranchRefCalls [0 ].branch )
@@ -1040,3 +1041,61 @@ func TestRebase_BranchDiverged_NoFF(t *testing.T) {
10401041 assert .NoError (t , err )
10411042 assert .Equal (t , 0 , updateBranchRefCalls , "no FF when branches have diverged" )
10421043}
1044+
1045+ func TestRebase_SkipsMergedBranchesNotExistingLocally (t * testing.T ) {
1046+ // Simulates a stack where b1 is merged and its branch was auto-deleted
1047+ // from the remote, so it doesn't exist locally.
1048+ s := stack.Stack {
1049+ Trunk : stack.BranchRef {Branch : "main" },
1050+ Branches : []stack.BranchRef {
1051+ {Branch : "b1" , PullRequest : & stack.PullRequestRef {Number : 42 , Merged : true }},
1052+ {Branch : "b2" },
1053+ },
1054+ }
1055+
1056+ tmpDir := t .TempDir ()
1057+ writeStackFile (t , tmpDir , s )
1058+
1059+ var rebaseCalls []rebaseCall
1060+
1061+ mock := newRebaseMock (tmpDir , "b2" )
1062+ mock .BranchExistsFn = func (name string ) bool {
1063+ // b1 does not exist locally (deleted from remote after merge)
1064+ return name != "b1"
1065+ }
1066+ mock .RevParseMultiFn = func (refs []string ) ([]string , error ) {
1067+ // Only resolve refs that exist — b1 should not be in the list
1068+ shas := make ([]string , len (refs ))
1069+ for i , r := range refs {
1070+ if r == "b1" {
1071+ t .Fatalf ("RevParseMulti should not be called with non-existent branch b1" )
1072+ }
1073+ shas [i ] = "sha-" + r
1074+ }
1075+ return shas , nil
1076+ }
1077+ mock .RebaseOntoFn = func (newBase , oldBase , branch string ) error {
1078+ rebaseCalls = append (rebaseCalls , rebaseCall {newBase , oldBase , branch })
1079+ return nil
1080+ }
1081+
1082+ restore := git .SetOps (mock )
1083+ defer restore ()
1084+
1085+ cfg , _ , errR := config .NewTestConfig ()
1086+ cmd := RebaseCmd (cfg )
1087+ cmd .SetOut (io .Discard )
1088+ cmd .SetErr (io .Discard )
1089+ err := cmd .Execute ()
1090+
1091+ cfg .Err .Close ()
1092+ errOut , _ := io .ReadAll (errR )
1093+ output := string (errOut )
1094+
1095+ assert .NoError (t , err )
1096+ assert .Contains (t , output , "Skipping b1" )
1097+
1098+ // Only b2 should be rebased
1099+ require .Len (t , rebaseCalls , 1 )
1100+ assert .Equal (t , "b2" , rebaseCalls [0 ].branch )
1101+ }
0 commit comments