@@ -11,6 +11,7 @@ import (
1111 "github.com/cli/go-gh/v2/pkg/prompter"
1212 "github.com/github/gh-stack/internal/config"
1313 "github.com/github/gh-stack/internal/git"
14+ "github.com/github/gh-stack/internal/github"
1415 "github.com/github/gh-stack/internal/stack"
1516)
1617
@@ -231,27 +232,119 @@ func resolveStack(sf *stack.StackFile, branch string, cfg *config.Config) (*stac
231232}
232233
233234// syncStackPRs discovers and updates pull request metadata for branches in a stack.
234- // For each branch, it queries GitHub for the most recent PR and updates the
235- // PullRequestRef including merge status. Branches with already-merged PRs are skipped.
235+ //
236+ // When the stack has a remote ID, the stack API is the source of truth: the
237+ // authoritative PR list is fetched from the server and matched to local
238+ // branches by head branch name. PRs remain associated even if closed.
239+ //
240+ // When no remote stack exists, branch-name-based discovery is used:
241+ //
242+ // 1. No tracked PR — look for an OPEN PR by head branch name.
243+ // 2. Tracked PR (not merged) — refresh status by number; if closed,
244+ // clear the association and fall through to path 1.
245+ // 3. Tracked PR (merged) — skip; the merged state is final.
246+ //
236247// The transient Queued flag is also populated from the API response.
237248func syncStackPRs (cfg * config.Config , s * stack.Stack ) {
238249 client , err := cfg .GitHubClient ()
239250 if err != nil {
240251 return
241252 }
242253
254+ // When the stack has a remote ID, the stack API is the source of truth.
255+ if s .ID != "" {
256+ if syncStackPRsFromRemote (client , s ) {
257+ return
258+ }
259+ }
260+
261+ // No remote stack (or remote sync failed) — local discovery.
243262 for i := range s .Branches {
244263 b := & s .Branches [i ]
245264
246265 if b .IsMerged () {
247266 continue
248267 }
249268
250- pr , err := client .FindAnyPRForBranch (b .Branch )
269+ if b .PullRequest != nil && b .PullRequest .Number != 0 {
270+ // Tracked PR — refresh its state.
271+ pr , err := client .FindPRByNumber (b .PullRequest .Number )
272+ if err != nil || pr == nil {
273+ continue
274+ }
275+ b .PullRequest = & stack.PullRequestRef {
276+ Number : pr .Number ,
277+ ID : pr .ID ,
278+ URL : pr .URL ,
279+ Merged : pr .Merged ,
280+ }
281+ b .Queued = pr .IsQueued ()
282+
283+ // If the PR was closed (not merged), remove the association
284+ // so we fall through to the open-PR lookup below.
285+ if pr .State == "CLOSED" {
286+ b .PullRequest = nil
287+ b .Queued = false
288+ } else {
289+ continue
290+ }
291+ }
292+
293+ // No tracked PR (or just cleared) — only adopt OPEN PRs to avoid
294+ // picking up stale merged/closed PRs from a previous use of this
295+ // branch name.
296+ pr , err := client .FindPRForBranch (b .Branch )
251297 if err != nil || pr == nil {
252298 continue
253299 }
300+ b .PullRequest = & stack.PullRequestRef {
301+ Number : pr .Number ,
302+ ID : pr .ID ,
303+ URL : pr .URL ,
304+ }
305+ b .Queued = pr .IsQueued ()
306+ }
307+ }
308+
309+ // syncStackPRsFromRemote uses the stack API to sync PR state. The remote
310+ // stack's PR list is the source of truth — PRs stay associated even if
311+ // closed. Returns true if the sync succeeded, false if we should fall
312+ // back to local discovery (e.g. stack not found remotely, API error).
313+ func syncStackPRsFromRemote (client github.ClientOps , s * stack.Stack ) bool {
314+ stacks , err := client .ListStacks ()
315+ if err != nil {
316+ return false
317+ }
318+
319+ // Find our stack in the remote list.
320+ var remotePRNumbers []int
321+ for _ , rs := range stacks {
322+ if strconv .Itoa (rs .ID ) == s .ID {
323+ remotePRNumbers = rs .PullRequests
324+ break
325+ }
326+ }
327+ if remotePRNumbers == nil {
328+ return false
329+ }
254330
331+ // Fetch each remote PR's details and index by head branch name.
332+ prByBranch := make (map [string ]* github.PullRequest , len (remotePRNumbers ))
333+ for _ , num := range remotePRNumbers {
334+ pr , err := client .FindPRByNumber (num )
335+ if err != nil || pr == nil {
336+ continue
337+ }
338+ prByBranch [pr .HeadRefName ] = pr
339+ }
340+
341+ // Match remote PRs to local branches.
342+ for i := range s .Branches {
343+ b := & s .Branches [i ]
344+ pr , ok := prByBranch [b .Branch ]
345+ if ! ok {
346+ continue
347+ }
255348 b .PullRequest = & stack.PullRequestRef {
256349 Number : pr .Number ,
257350 ID : pr .ID ,
@@ -260,6 +353,8 @@ func syncStackPRs(cfg *config.Config, s *stack.Stack) {
260353 }
261354 b .Queued = pr .IsQueued ()
262355 }
356+
357+ return true
263358}
264359
265360// updateBaseSHAs refreshes the Base and Head SHAs for all active branches
0 commit comments