@@ -2060,6 +2060,133 @@ func TestSync_RemoteAhead_PullsNewBranches(t *testing.T) {
20602060 assert .Equal (t , []string {"b1" , "b2" , "b3" , "b4" , "b5" }, sf .Stacks [0 ].BranchNames ())
20612061}
20622062
2063+ // TestSync_RemoteAhead_QueuedBranchNotPushed verifies that a pulled branch whose
2064+ // PR is in the merge queue has its transient queued state copied from the fresh
2065+ // PR details during reconciliation, so it is not force-pushed by the later push
2066+ // step.
2067+ func TestSync_RemoteAhead_QueuedBranchNotPushed (t * testing.T ) {
2068+ s := stack.Stack {
2069+ ID : "9" ,
2070+ Trunk : stack.BranchRef {Branch : "main" },
2071+ Branches : []stack.BranchRef {
2072+ {Branch : "b1" , PullRequest : & stack.PullRequestRef {Number : 101 }},
2073+ {Branch : "b2" , PullRequest : & stack.PullRequestRef {Number : 102 }},
2074+ },
2075+ }
2076+ tmpDir := t .TempDir ()
2077+ writeStackFile (t , tmpDir , s )
2078+
2079+ var created []string
2080+ var pushes []pushCall
2081+ mock := newSyncMockNoRebase (tmpDir , "b1" )
2082+ mock .BranchExistsFn = func (name string ) bool { return name != "b3" }
2083+ mock .CreateBranchFn = func (name , base string ) error { created = append (created , name ); return nil }
2084+ mock .SetUpstreamTrackingFn = func (string , string ) error { return nil }
2085+ mock .PushFn = func (remote string , branches []string , force , atomic bool ) error {
2086+ pushes = append (pushes , pushCall {remote , branches , force , atomic })
2087+ return nil
2088+ }
2089+
2090+ ghMock := & github.MockClient {
2091+ ListStacksFn : func () ([]github.RemoteStack , error ) {
2092+ return []github.RemoteStack {{ID : 9 , PullRequests : []int {101 , 102 , 103 }}}, nil
2093+ },
2094+ FindPRByNumberFn : func (n int ) (* github.PullRequest , error ) {
2095+ branch := map [int ]string {101 : "b1" , 102 : "b2" , 103 : "b3" }[n ]
2096+ if branch == "" {
2097+ return nil , nil
2098+ }
2099+ pr := & github.PullRequest {
2100+ Number : n , ID : fmt .Sprintf ("PR_%d" , n ),
2101+ URL : fmt .Sprintf ("https://github.com/o/r/pull/%d" , n ),
2102+ HeadRefName : branch , State : "OPEN" ,
2103+ }
2104+ if n == 103 {
2105+ pr .MergeQueueEntry = & github.MergeQueueEntry {ID : "MQ1" }
2106+ }
2107+ return pr , nil
2108+ },
2109+ }
2110+
2111+ _ , err := runSyncCfg (t , mock , func (cfg * config.Config ) { cfg .GitHubClientOverride = ghMock })
2112+ require .NoError (t , err )
2113+
2114+ assert .Contains (t , created , "b3" , "the queued branch is still pulled into the local stack" )
2115+ for _ , pc := range pushes {
2116+ assert .NotContains (t , pc .branches , "b3" , "a merge-queued branch must not be pushed" )
2117+ }
2118+ }
2119+
2120+ // TestSync_RemoteAhead_DuplicateBranchAborts verifies that pulling a remote
2121+ // addition whose branch is already owned by another local stack aborts rather
2122+ // than writing the branch into two stacks.
2123+ func TestSync_RemoteAhead_DuplicateBranchAborts (t * testing.T ) {
2124+ tmpDir := t .TempDir ()
2125+ writeStackFileMulti (t , tmpDir ,
2126+ stack.Stack {
2127+ ID : "9" ,
2128+ Trunk : stack.BranchRef {Branch : "main" },
2129+ Branches : []stack.BranchRef {
2130+ {Branch : "b1" , PullRequest : & stack.PullRequestRef {Number : 101 }},
2131+ {Branch : "b2" , PullRequest : & stack.PullRequestRef {Number : 102 }},
2132+ },
2133+ },
2134+ stack.Stack {
2135+ Trunk : stack.BranchRef {Branch : "main" },
2136+ Branches : []stack.BranchRef {{Branch : "b3" }}, // another stack already owns b3
2137+ },
2138+ )
2139+
2140+ var created []string
2141+ mock := newSyncMockNoRebase (tmpDir , "b1" )
2142+ mock .CreateBranchFn = func (name , base string ) error { created = append (created , name ); return nil }
2143+
2144+ ghMock := & github.MockClient {
2145+ ListStacksFn : func () ([]github.RemoteStack , error ) {
2146+ return []github.RemoteStack {{ID : 9 , PullRequests : []int {101 , 102 , 103 }}}, nil
2147+ },
2148+ FindPRByNumberFn : prByNumberFinder (map [int ]string {101 : "b1" , 102 : "b2" , 103 : "b3" }),
2149+ }
2150+
2151+ output , err := runSyncCfg (t , mock , func (cfg * config.Config ) { cfg .GitHubClientOverride = ghMock })
2152+
2153+ assert .Error (t , err )
2154+ assert .Contains (t , output , "Cannot pull b3" )
2155+ assert .NotContains (t , created , "b3" , "must not pull a branch owned by another stack" )
2156+
2157+ sf , loadErr := stack .Load (tmpDir )
2158+ require .NoError (t , loadErr )
2159+ assert .Equal (t , []string {"b1" , "b2" }, sf .Stacks [0 ].BranchNames (), "tracked stack unchanged" )
2160+ }
2161+
2162+ // TestSync_Divergent_UseRemote_DirtyCheckErrorAborts verifies that when the
2163+ // working-tree status cannot be determined, "use remote" aborts instead of
2164+ // treating the tree as clean and running the destructive replace.
2165+ func TestSync_Divergent_UseRemote_DirtyCheckErrorAborts (t * testing.T ) {
2166+ tmpDir := t .TempDir ()
2167+ divergentStack (t , tmpDir )
2168+
2169+ ghMock := divergentRemoteMock ()
2170+ var created []string
2171+ mock := newSyncMockNoRebase (tmpDir , "b1" )
2172+ mock .CreateBranchFn = func (name , base string ) error { created = append (created , name ); return nil }
2173+ mock .HasUncommittedChangesFn = func () (bool , error ) { return false , fmt .Errorf ("git status failed" ) }
2174+
2175+ output , err := runSyncCfg (t , mock , func (cfg * config.Config ) {
2176+ cfg .GitHubClientOverride = ghMock
2177+ cfg .ForceInteractive = true
2178+ cfg .SelectFn = func (_ , _ string , _ []string ) (int , error ) { return 0 , nil }
2179+ })
2180+
2181+ assert .Error (t , err )
2182+ assert .Contains (t , output , "Could not determine whether the working tree is clean" )
2183+ assert .Empty (t , created , "must not replace the local stack when the working-tree check fails" )
2184+
2185+ sf , loadErr := stack .Load (tmpDir )
2186+ require .NoError (t , loadErr )
2187+ assert .Equal (t , []string {"b1" , "b2" , "b3" }, sf .Stacks [0 ].BranchNames (), "local stack untouched" )
2188+ }
2189+
20632190// TestSync_RemoteInSync_NoPull verifies that when local and remote match, no
20642191// branches are pulled and no divergence is reported.
20652192func TestSync_RemoteInSync_NoPull (t * testing.T ) {
0 commit comments