Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions internal/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package github

import (
"context"
"errors"
"fmt"
"time"

"github.com/google/go-github/v38/github"
"golang.org/x/oauth2"
Expand All @@ -27,6 +30,8 @@ type Client interface {

type client struct {
ghc *github.Client
maxRetries int
retryDelay time.Duration
}

func NewClient(ctx context.Context, token string) Client {
Expand All @@ -36,13 +41,89 @@ func NewClient(ctx context.Context, token string) Client {
AccessToken: token,
},
))),
maxRetries: 5,
retryDelay: 1 * time.Second,
}
}

func (c *client) GetCombinedStatus(ctx context.Context, owner, repo, ref string, opts *ListOptions) (*CombinedStatus, *Response, error) {
return c.ghc.Repositories.GetCombinedStatus(ctx, owner, repo, ref, opts)
var statusResp *CombinedStatus
var resp *Response
var err error

for attempt := 0; attempt < c.maxRetries; attempt++ {
statusResp, resp, err = c.ghc.Repositories.GetCombinedStatus(ctx, owner, repo, ref, opts)
if err == nil {
return statusResp, resp, nil
}

// Check if context is canceled or deadline exceeded before retrying
if ctx.Err() != nil {
return nil, resp, fmt.Errorf("context error while getting combined status: %w", ctx.Err())
}

// Don't retry on deadline exceeded errors
if errors.Is(err, context.DeadlineExceeded) {
return nil, resp, err
}

// Only retry on 5xx server errors
if resp != nil && (resp.StatusCode < 500 || resp.StatusCode > 599) {
return statusResp, resp, err
}

// Wait with exponential backoff before retrying
if attempt < c.maxRetries-1 {
backoffDuration := c.retryDelay * time.Duration(1<<attempt)
select {
case <-ctx.Done():
return nil, resp, ctx.Err()
case <-time.After(backoffDuration):
// Continue with retry
}
}
}

return nil, resp, fmt.Errorf("failed to get combined status after %d retries: %w", c.maxRetries, err)
}

func (c *client) ListCheckRunsForRef(ctx context.Context, owner, repo, ref string, opts *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error) {
return c.ghc.Checks.ListCheckRunsForRef(ctx, owner, repo, ref, opts)
var checksResp *ListCheckRunsResults
var resp *Response
var err error

for attempt := 0; attempt < c.maxRetries; attempt++ {
checksResp, resp, err = c.ghc.Checks.ListCheckRunsForRef(ctx, owner, repo, ref, opts)
if err == nil {
return checksResp, resp, nil
}

// Check if context is canceled or deadline exceeded before retrying
if ctx.Err() != nil {
return nil, resp, fmt.Errorf("context error while listing check runs: %w", ctx.Err())
}

// Don't retry on deadline exceeded errors
if errors.Is(err, context.DeadlineExceeded) {
return nil, resp, err
}

// Only retry on 5xx server errors
if resp != nil && (resp.StatusCode < 500 || resp.StatusCode > 599) {
return checksResp, resp, err
}

// Wait with exponential backoff before retrying
if attempt < c.maxRetries-1 {
backoffDuration := c.retryDelay * time.Duration(1<<attempt)
select {
case <-ctx.Done():
return nil, resp, ctx.Err()
case <-time.After(backoffDuration):
// Continue with retry
}
}
}

return nil, resp, fmt.Errorf("failed to list check runs after %d retries: %w", c.maxRetries, err)
}
Loading