Documentation
¶
Overview ¶
Package github implements pkg/forge.Forge against the real GitHub REST API, using github.com/cli/go-gh/v2 (AGENTS.md §4.7's one sanctioned new dependency for M3 — not google/go-github, not a hand-rolled REST client, not the full gh CLI as a library). New resolves credentials exactly the way the gh CLI itself would (its own keyring/config, via go-gh's api.DefaultRESTClient): this package never reads a token from a flag or an environment variable of its own invention, and never sees the token value directly — go-gh sets the Authorization header inside its own HTTP transport (AGENTS.md §4.3, R-002).
Index ¶
- type Client
- func (c *Client) BlameLines(ctx context.Context, ref, path string, lines []int) (map[int]forge.LineOrigin, error)
- func (c *Client) Checks(ctx context.Context, sha string) (forge.CheckSummary, error)
- func (c *Client) Comments(ctx context.Context, prNumber int, since time.Time) ([]forge.Comment, error)
- func (c *Client) CommitFiles(ctx context.Context, sha string) ([]string, bool, error)
- func (c *Client) CommitsTouching(ctx context.Context, ref, path string, since time.Time) ([]string, error)
- func (c *Client) Compare(ctx context.Context, base, head string) (forge.Comparison, error)
- func (c *Client) CreatePR(ctx context.Context, spec forge.PRSpec) (forge.PR, error)
- func (c *Client) FindPR(ctx context.Context, headBranch, bodyMarker string) (forge.PR, bool, error)
- func (c *Client) GetPR(ctx context.Context, number int) (forge.PR, bool, error)
- func (c *Client) IsAllowedAuthor(ctx context.Context, login string) (bool, error)
- func (c *Client) MergePR(ctx context.Context, prNumber int, expectedHeadSHA string) (forge.PR, error)
- func (c *Client) ReadFile(ctx context.Context, ref, path string) ([]byte, bool, error)
- func (c *Client) ResolveRef(ctx context.Context, ref string) (string, bool, error)
- func (c *Client) Tags(ctx context.Context) ([]forge.GitTag, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client implements forge.Forge against api.github.com (or a GitHub Enterprise host, via the gh environment's own resolution — this package does not special-case it).
func New ¶
New builds a Client for ownerRepo ("owner/name", RepoConfig.GitHub), authenticating through the user's own `gh` login exactly as the gh CLI would.
func (*Client) BlameLines ¶
func (c *Client) BlameLines(ctx context.Context, ref, path string, lines []int) (map[int]forge.LineOrigin, error)
BlameLines implements forge.Forge via GraphQL. A nil object means ref does not resolve in this repo; that is reported as forge.ErrUnknownRef so pkg/migrate can fall back to the base branch and mark the age approximate, rather than as a scope failure. Requires a GraphQL client: a Client built through newWithClient alone (the M3 test seam) reports that it has none rather than dereferencing nil.
func (*Client) Checks ¶
Checks implements forge.Forge: the check-run rollup CIGreenStep gates on (M4), paged through every page of check-runs GitHub reports for sha (Known bug classes: "only page 1 of a check-run set is fetched" — a commit with more than 100 check runs would otherwise silently hide a pending or failed run past the first page, and CI could appear green when it isn't). FailedNames names every check-run that concluded in something other than success/neutral/skipped; SkippedNames separately names every run that concluded `skipped`, which forge.CheckSummary's own doc comment explains is never folded into Success. A run's own name is upstream text nothing here wrote (a CI system can name a job anything), so both name lists go through redact.Strings at this adaptor boundary the same way translateErr already does for GitHub's own free-form error messages (AGENTS.md invariant 6).
func (*Client) Comments ¶
func (c *Client) Comments(ctx context.Context, prNumber int, since time.Time) ([]forge.Comment, error)
Comments implements forge.Forge: PR conversation comments (GitHub models these as issue comments), newer than since, with AuthorType carrying the API's own account "type" through (M4: internal/engine.ApprovedStep is what actually enforces R-001's author check against it, against the API's login field, never the comment body — see that package's doc comment for why enforcement lives there and not here). Bots (Type == "Bot") are additionally excluded here too, as a politeness layer only (AGENTS.md §8 "layered checks": deleting this filter would not change what a correctly-written ApprovedStep accepts, only which layer reports it). This deliberately excludes only "Bot", not "anything other than User": GitHub's account "type" field also has legitimate non-"User" values such as "Organization" (an org-owned account, not a bot), and the set of values isn't closed, so filtering on Type != "User" would silently drop a real, non-bot commenter along with actual bots.
Paged through every page GitHub reports (Known bug classes: "only page 1 of a PR's comments is fetched" — a PR with more than 100 comments could otherwise hide an approval or a later reject past the first page, so ApprovedStep's newest-match scan would never see it).
func (*Client) CommitFiles ¶
CommitFiles implements forge.Forge: GET .../commits/{sha}, whose files[] pages at 100 like everything else here and is capped by GitHub at githubFilesCap in total.
func (*Client) CommitsTouching ¶
func (c *Client) CommitsTouching(ctx context.Context, ref, path string, since time.Time) ([]string, error)
CommitsTouching implements forge.Forge: GET .../commits?sha={ref}&path={path}&since=…, newest first as GitHub orders it, paged to the same bound.
func (*Client) Compare ¶
Compare implements forge.Forge: GET .../compare/{base}...{head}, paged. GitHub's total_commits is authoritative for the whole range regardless of paging, so Truncated is len(commits) < total rather than a sentinel request. The file list is only ever on page 1 and GitHub caps it at githubFilesCap; a full list is reported FilesTruncated. A 404 is forge.ErrUnknownRef, wrapped with the same scope hint translateErr gives — an unknown ref and a private repo the token cannot see are the same status code.
func (*Client) FindPR ¶
FindPR implements forge.Forge: first the exact head-branch match GitHub's own "head" filter gives for free, then — when a branch was renamed or recreated, so nothing shares its name anymore — a bounded scan of open, then closed, PRs for the body marker (Known bug classes: "Finding an existing PR only by branch name and missing one whose branch was renamed").
func (*Client) GetPR ¶
GetPR implements forge.Forge: getPR with a 404 reported as ok=false rather than an error, since "no such PR" is an answer the caller falls back from, not a failure.
func (*Client) IsAllowedAuthor ¶
IsAllowedAuthor implements forge.Forge: true for "admin", "maintain" or "write" — R-001's stated bar is "collaborator with write (or higher) permission" (forge.Forge's own doc comment), and GitHub ranks maintain strictly above write (admin > maintain > write > triage > read), so a maintain-level collaborator qualifies exactly like write and admin do; only "triage" and "read" fall below the bar. A 404 (login is not a collaborator — the ordinary case for a drive-by commenter on a public PR) is folded into (false, nil) rather than propagated as an error: treating it as fatal would make any non-collaborator's comment error and block the whole promotion whenever collaborators=true, rather than simply not counting as an approval.
func (*Client) MergePR ¶
func (c *Client) MergePR(ctx context.Context, prNumber int, expectedHeadSHA string) (forge.PR, error)
MergePR implements forge.Forge: a squash merge, gated atomically on the server's own "sha" parameter (Known bug classes: "don't roll your own check-then-merge race") — a head that has moved since expectedHeadSHA was observed is refused with a 409, translated to forge.ErrStaleHead. Squash is always used, so a multi-commit branch's title would normally concatenate every commit subject; this promotion is guaranteed exactly one commit (M3), so that default composes to that one commit's own message, never a leak of unrelated history (Known bug classes, confirmed here rather than assumed).
func (*Client) ReadFile ¶
ReadFile implements forge.Forge: GET .../contents/{path}?ref=…, base64-decoded. A 404 is ok=false (the file is simply not there); a directory at that path is an error, since the caller asked for a file.
func (*Client) ResolveRef ¶
ResolveRef implements forge.Forge: GET .../commits/{ref}, the same endpoint Tags already uses to date a tag, which accepts a tag, a branch, or a sha prefix. 404 (no such ref) and 422 (an ambiguous prefix) are ok=false; anything else — a 403 in particular, the scope-gap signature — is an error, per the interface's own contract.
func (*Client) Tags ¶
Tags implements forge.Forge: every tag on the repo, with the date of the commit it points to. This is N+1 requests (one to list tags, one per tag to read its commit date — GitHub's tag-list endpoint reports a commit SHA but no date, and the M6 brief's own case for this method is exactly "the registry has no dates, the git history does") — acceptable for a single-operator tool's own app repos, bounded by maxTagPages, and only ever called when the tag picker actually needs ordering for a mapped image repo, not on every plan.