fix: address verified review findings (pulls-1)

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-18 17:39:24 +02:00
1 parent 58f0fc4bf9
commit 401650b489
15 files changed
+202 -8

No files matched your search

+4 -1
View File
@@ -124,6 +124,7 @@ func (s Pulls) WatchChecks(ctx context.Context, n int64, interval time.Duration,
return nil, errors.New("check polling interval must be positive")
}
var previous []Check
reported := false
for {
sha, err := s.checkHead(ctx, n)
if err != nil {
@@ -133,7 +134,9 @@ func (s Pulls) WatchChecks(ctx context.Context, n int64, interval time.Duration,
if err != nil {
return nil, err
}
if !slices.Equal(items, previous) {
// The first poll is reported even when empty, so a watcher can say it is waiting.
if !reported || !slices.Equal(items, previous) {
reported = true
if changed != nil {
if err := changed(items); err != nil {
return nil, err
+29
View File
@@ -58,6 +58,35 @@ func TestCheckWatchRevalidatesHeadBeforeReturning(t *testing.T) {
require.Equal(t, 5, heads)
}
func TestCheckWatchReportsTheEmptyStateOnce(t *testing.T) {
polls := 0
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, "/pulls/") {
_, _ = w.Write([]byte(`{"head":{"sha":"abc"}}`))
return
}
if r.URL.Query().Get("page") != "1" {
_, _ = w.Write([]byte(`[]`))
return
}
polls++
if polls < 3 {
_, _ = w.Write([]byte(`[]`))
return
}
_, _ = w.Write([]byte(`[{"id":1,"context":"build","status":"success"}]`))
}))
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
var reported []int
_, err := (Pulls{Client: client, Repository: "owner/repo"}).WatchChecks(ctx, 7, time.Millisecond, func(items []Check) error {
reported = append(reported, len(items))
return nil
})
require.NoError(t, err)
require.Equal(t, []int{0, 1}, reported)
}
func TestCheckWatchTreatsSkippedAsPassing(t *testing.T) {
snapshots := 0
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+6
View File
@@ -159,6 +159,9 @@ var (
unknownUser = regexp.MustCompile(`user does not exist \[uid: \d+, name: ([^\]]+)\]`)
// pullTargets finds the branches in "pull request already exists for these targets [... head_branch: x, base_branch: y]".
pullTargets = regexp.MustCompile(`head_branch: ([^,\]]+), base_branch: ([^,\]]+)`)
// missingBranch finds the branch in the 404 messages `ref "x" does not exist` (create pull
// request) and `new base 'x' not exist` (edit pull request).
missingBranch = regexp.MustCompile(`^(?:ref "([^"]+)" does not exist|new base '([^']+)' not exist)$`)
// wikiTitle finds the page in "wiki page already exists [title: Home.md]".
wikiTitle = regexp.MustCompile(`\[title: (.+?)(?:\.md)?\]`)
)
@@ -186,6 +189,9 @@ func Classify(err error, kind, key, scope string) error {
}
return conflict
}
if match := missingBranch.FindStringSubmatch(apiErr.Message); apiErr.Status == http.StatusNotFound && match != nil {
return &NotFoundError{Kind: "branch", Key: strconv.Quote(cmp.Or(match[1], match[2])), Scope: scope, Err: err}
}
if apiErr.Status == http.StatusNotFound && kind != "" {
return &NotFoundError{Kind: kind, Key: key, Scope: scope, Err: err}
}
+7
View File
@@ -64,3 +64,10 @@ func TestConflictHintsFollowTheReason(t *testing.T) {
err := &ConflictError{Reason: ReasonArchived, Scope: "alice/old", Hint: "unarchive it with: tea repo edit alice/old --archived=false"}
require.EqualError(t, err, "alice/old is archived (read-only); unarchive it with: tea repo edit alice/old --archived=false")
}
func TestClassifyNamesAMissingBranchNotThePullRequest(t *testing.T) {
for _, message := range []string{`ref "develp" does not exist`, `new base 'develp' not exist`} {
err := Classify(&transport.APIError{Status: http.StatusNotFound, Message: message}, "pull request", "#3", "alice/x")
require.EqualError(t, err, `branch "develp" not found in alice/x`)
}
}
+20
View File
@@ -241,6 +241,26 @@ func (s Pulls) ViewAll(ctx context.Context, items []Pull) ([]Pull, error) {
return viewed, err
}
// DecideAll is ViewAll that also fills each pull request's ReviewDecision from its reviews.
func (s Pulls) DecideAll(ctx context.Context, items []Pull) ([]Pull, error) {
viewed := make([]Pull, len(items))
reviews := Reviews{Client: s.Client, Repository: s.Repository}
err := concurrently(len(items), func(i int) error {
item, err := s.View(ctx, items[i].Number)
if err != nil {
return err
}
all, err := reviews.All(ctx, item.Number)
if err != nil {
return err
}
item.ReviewDecision = ReviewDecision(LatestReviews(all), len(item.Reviewers)+len(item.ReviewerTeams) > 0)
viewed[i] = item
return nil
})
return viewed, err
}
func (s Pulls) get(ctx context.Context, n int64) (*gitea.PullRequest, error) {
item, err := repoCall(ctx, s.Client, s.Repository, func(ctx context.Context, c *gitea.Client, owner, name string) (*gitea.PullRequest, *gitea.Response, error) {
return c.PullRequests.GetPullRequest(ctx, owner, name, n)