watchpr: detect merge/close in poll loop (was hanging after baseline)
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

watchpr only reported meaningful changes as transitions from the poll
baseline. A PR already merged or closed when the watch started was
captured as the baseline and never produced a transition, so the loop
polled the dead PR forever (process alive, never exiting) -- the
single-PR --interval case observed in production.

Add a terminal-state check applied to the baseline snapshot: a PR that
is already merged or closed the moment watchpr starts is reported and
exits immediately, since it can never change again. Extract the
baseline+poll loop into agent.Watch behind a StateFetcher interface so
the loop, its open->merged/close detection, and its poll-error
resilience are unit-testable with a fake client.
This commit is contained in:
2026-08-12 23:32:28 +10:00
parent 7d9ae0fcc6
commit f915b5ba3b
3 changed files with 237 additions and 27 deletions
+13 -26
View File
@@ -117,36 +117,23 @@ func runOnce(c *agent.GiteaClient, refs []agent.PRRef, jsonMode bool) error {
func runWatch(c *agent.GiteaClient, refs []agent.PRRef, interval time.Duration, jsonMode bool) error {
login := agent.AgentLogin()
prev := make(map[string]agent.PRState, len(refs))
for _, ref := range refs {
st, err := agent.FetchState(c, ref, login)
if err != nil {
return err
}
prev[ref.String()] = st
}
if !jsonMode {
fmt.Fprintf(os.Stderr, "watching %d PR(s) every %s; baseline established\n", len(refs), interval)
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
for _, ref := range refs {
key := ref.String()
cur, err := agent.FetchState(c, ref, login)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: polling %s: %v\n", key, err)
continue
}
changed, reason := agent.MeaningfulChange(prev[key], cur)
if changed {
report(key, reason, cur, jsonMode)
return nil
}
prev[key] = cur
onBaseline := func() {
if !jsonMode {
fmt.Fprintf(os.Stderr, "watching %d PR(s) every %s; baseline established\n", len(refs), interval)
}
}
onError := func(ref agent.PRRef, err error) {
fmt.Fprintf(os.Stderr, "warning: polling %s: %v\n", ref.String(), err)
}
res, err := agent.Watch(c, refs, login, ticker.C, onBaseline, onError)
if err != nil {
return err
}
report(res.Ref.String(), res.Reason, res.State, jsonMode)
return nil
}