chore: import upstream snapshot with attribution
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
// Package forge is the single network surface for pull-request I/O. It
|
||||
// wraps the official go-github SDK behind a small set of free functions
|
||||
// — ListPRs, ViewPR, PRFiles, DiffPR, PostReviewComments, Available —
|
||||
// every one taking (ctx, repoDir, …). Each constructs an internal
|
||||
// ghClient that resolves the GitHub token (GH_TOKEN → GITHUB_TOKEN) and
|
||||
// the owner/name slug from the indexed repo identity (falling back to
|
||||
// `git remote get-url origin` through gitcmd).
|
||||
//
|
||||
// The consumed surface is the free functions, not a method set: callers
|
||||
// never hold a client. The Client interface exists only as a test seam
|
||||
// (a func-var indirection injects canned data) and as the future-backend
|
||||
// seam — a deferred gh-CLI or GitLab backend slots in behind it without
|
||||
// touching any caller.
|
||||
//
|
||||
// GitHub REST exposes no single reviewDecision or statusCheckRollup
|
||||
// field; those are GraphQL aggregates. ReviewDecision and CIRollup are
|
||||
// reconstructed here from ListReviews and check-runs/combined-status,
|
||||
// opt-in per call (ListOpts.WithDecision / WithCI; ViewPR always fills
|
||||
// them) so a cheap ListPRs skips the extra round-trips.
|
||||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zzet/gortex/internal/analysis"
|
||||
)
|
||||
|
||||
// callTimeout bounds every network round-trip a forge call makes.
|
||||
const callTimeout = 30 * time.Second
|
||||
|
||||
// ErrNotAuthenticated is returned when no GitHub token is resolvable
|
||||
// from the environment.
|
||||
var ErrNotAuthenticated = errors.New("no GitHub token: set GH_TOKEN (or GITHUB_TOKEN)")
|
||||
|
||||
// ErrRateLimited is returned when GitHub answers with a rate-limit /
|
||||
// abuse-rate-limit error. The underlying *github.RateLimitError /
|
||||
// *github.AbuseRateLimitError is mapped onto this sentinel (with a
|
||||
// Retry-After hint preserved in the wrapped error's message) so callers
|
||||
// can test errors.Is(err, ErrRateLimited).
|
||||
var ErrRateLimited = errors.New("github rate limited")
|
||||
|
||||
// PR is the canonical pull-request value. It is built from a go-github
|
||||
// *github.PullRequest; ReviewDecision and CIRollup are reconstructed (the
|
||||
// REST API has no such aggregate fields). Files is EMPTY after ListPRs —
|
||||
// only ViewPR / PRFiles hydrate it.
|
||||
type PR struct {
|
||||
Number int
|
||||
Title string
|
||||
Author string
|
||||
BaseRef string // PullRequest.Base.Ref
|
||||
HeadRef string // PullRequest.Head.Ref
|
||||
IsDraft bool
|
||||
ReviewDecision string // reconstructed from ListReviews (REST has no reviewDecision field)
|
||||
CIRollup string // reconstructed from Checks + GetCombinedStatus; collapsed by RollupCI
|
||||
UpdatedAt time.Time
|
||||
Mergeable string
|
||||
URL string
|
||||
State string
|
||||
Files []string // EMPTY after ListPRs; only ViewPR / PRFiles hydrate it
|
||||
}
|
||||
|
||||
// PRDiff is the per-file diff of a pull request, each file's patch parsed
|
||||
// into hunks via analysis.ParseDiffHunks.
|
||||
type PRDiff struct {
|
||||
Number int
|
||||
BaseRef string
|
||||
HeadRef string
|
||||
Files []PRFile
|
||||
Raw string
|
||||
}
|
||||
|
||||
// PRFile is one changed file in a PR diff.
|
||||
type PRFile struct {
|
||||
Path string
|
||||
OldPath string
|
||||
Hunks []analysis.DiffHunk // analysis.ParseDiffHunks on the file's .GetPatch()
|
||||
Status string
|
||||
}
|
||||
|
||||
// ReviewComment is the one inline-comment type. Posting maps a finding
|
||||
// to a ReviewComment; StartLine carries the multi-line range start and
|
||||
// Side defaults to "RIGHT" (the new side).
|
||||
type ReviewComment struct {
|
||||
Path string
|
||||
Line int
|
||||
StartLine int
|
||||
Side string
|
||||
Body string
|
||||
}
|
||||
|
||||
// ListOpts tunes ListPRs. Decision/CI reconstruction is opt-in per call
|
||||
// so a cheap list skips the extra round-trips.
|
||||
type ListOpts struct {
|
||||
State string
|
||||
Limit int
|
||||
Author string
|
||||
WithDecision bool
|
||||
WithCI bool
|
||||
}
|
||||
|
||||
// Client is the test seam / future-backend seam. The free functions are
|
||||
// the consumed surface; this interface lets a test inject canned data and
|
||||
// lets a deferred gh-CLI or GitLab backend slot in without touching any
|
||||
// caller.
|
||||
type Client interface {
|
||||
ListPRs(ctx context.Context, opts ListOpts) ([]PR, error)
|
||||
ViewPR(ctx context.Context, num int) (*PR, error)
|
||||
PRFiles(ctx context.Context, num int) ([]string, error)
|
||||
DiffPR(ctx context.Context, num int) (*PRDiff, error)
|
||||
PostReviewComments(ctx context.Context, num int, comments []ReviewComment) error
|
||||
}
|
||||
|
||||
// newClient resolves a *ghClient for repoDir. It is a package var so a
|
||||
// test can swap it for one backed by an httptest server with a fixed
|
||||
// owner/repo, bypassing token + git-remote resolution.
|
||||
var newClient = newGHClient
|
||||
|
||||
// ListPRs lists pull requests for the repo at repoDir. PR.Files is EMPTY
|
||||
// on every returned PR — triage and conflict detection must call
|
||||
// PRFiles(num) explicitly per PR. Decision/CI aggregates are filled only
|
||||
// when opts requests them.
|
||||
func ListPRs(ctx context.Context, repoDir string, opts ListOpts) ([]PR, error) {
|
||||
c, err := newClient(ctx, repoDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.ListPRs(ctx, opts)
|
||||
}
|
||||
|
||||
// ViewPR fetches a single pull request and hydrates its Files plus the
|
||||
// reconstructed ReviewDecision / CIRollup aggregates.
|
||||
func ViewPR(ctx context.Context, repoDir string, num int) (*PR, error) {
|
||||
c, err := newClient(ctx, repoDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.ViewPR(ctx, num)
|
||||
}
|
||||
|
||||
// PRFiles returns the paths of files changed in a pull request.
|
||||
func PRFiles(ctx context.Context, repoDir string, num int) ([]string, error) {
|
||||
c, err := newClient(ctx, repoDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.PRFiles(ctx, num)
|
||||
}
|
||||
|
||||
// DiffPR returns the per-file diff of a pull request, each file's patch
|
||||
// parsed into hunks.
|
||||
func DiffPR(ctx context.Context, repoDir string, num int) (*PRDiff, error) {
|
||||
c, err := newClient(ctx, repoDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.DiffPR(ctx, num)
|
||||
}
|
||||
|
||||
// PostReviewComments posts a batch of inline review comments on a pull
|
||||
// request as a single review.
|
||||
func PostReviewComments(ctx context.Context, repoDir string, num int, comments []ReviewComment) error {
|
||||
c, err := newClient(ctx, repoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.PostReviewComments(ctx, num, comments)
|
||||
}
|
||||
|
||||
// Available reports whether a GitHub token (GH_TOKEN or GITHUB_TOKEN) is
|
||||
// resolvable from the environment.
|
||||
func Available(ctx context.Context) bool {
|
||||
return resolveToken() != ""
|
||||
}
|
||||
Reference in New Issue
Block a user