Documentation
¶
Overview ¶
Package github implements a forge.Provider for GitHub repositories, for both public and token-authenticated access. Alongside the release contract it implements the optional Authenticator, KeyManager, Repositories, Contents, Sites, Issues, IssueFiler and Snippets capabilities.
Provider construction uses package-owned Settings; config integration lives in SettingsFromConfig, and NewProviderFromClient takes a go-github client the caller already holds.
GitHub's wider API — pull requests, repository creation — is deliberately not here. It is parked in the forge module's provider-contract-widening spec, to be added across every provider in lockstep when a concrete consumer needs it, rather than letting one forge acquire capabilities the others lack.
Index ¶
- Constants
- Variables
- func NewProviderFromClient(_ context.Context, client *github.Client, settings Settings) (forge.Provider, error)
- func NewReleaseProvider(ctx context.Context, settings Settings) (forge.Provider, error)
- type GitHubReleaseProvider
- func (p *GitHubReleaseProvider) AddReleaseAsset(ctx context.Context, owner, repo, tagName, name string, size int64, ...) error
- func (p *GitHubReleaseProvider) AddReleaseAssetLocation(ctx context.Context, owner, repo, tagName string, ...) error
- func (p *GitHubReleaseProvider) Close(ctx context.Context, owner, repo string, number int) error
- func (p *GitHubReleaseProvider) Comment(ctx context.Context, owner, repo string, number int, draft forge.CommentDraft) (forge.Comment, error)
- func (p *GitHubReleaseProvider) Create(ctx context.Context, owner, repo string, draft forge.PullRequestDraft) (forge.PullRequest, error)
- func (p *GitHubReleaseProvider) CreateIssue(ctx context.Context, owner, repo string, draft forge.IssueDraft) (forge.Issue, error)
- func (p *GitHubReleaseProvider) CreateIssueComment(ctx context.Context, owner, repo string, number int, draft forge.CommentDraft) (forge.Comment, error)
- func (p *GitHubReleaseProvider) CreateRelease(ctx context.Context, owner, repo string, draft forge.ReleaseDraft) (forge.Release, error)
- func (p *GitHubReleaseProvider) CreateReleaseWithAssets(ctx context.Context, owner, repo string, draft forge.ReleaseDraft, ...) (forge.Release, error)
- func (p *GitHubReleaseProvider) CreateRepository(ctx context.Context, owner string, draft forge.RepositoryDraft) (forge.Repository, error)
- func (p *GitHubReleaseProvider) CreateSnippet(ctx context.Context, scope forge.SnippetScope, opts forge.SnippetCreateOptions) (forge.Snippet, error)
- func (p *GitHubReleaseProvider) CreateWikiPage(ctx context.Context, owner, repo string, page forge.WikiPage, message string) error
- func (p *GitHubReleaseProvider) DeleteSnippet(ctx context.Context, scope forge.SnippetScope, id string) error
- func (p *GitHubReleaseProvider) DownloadReleaseAsset(ctx context.Context, owner, repo string, asset forge.ReleaseAsset) (io.ReadCloser, string, error)
- func (p *GitHubReleaseProvider) Find(ctx context.Context, owner, repo, sourceBranch string) (forge.PullRequest, error)
- func (p *GitHubReleaseProvider) FindLastMerged(ctx context.Context, owner, repo, sourceBranch string) (forge.PullRequest, error)
- func (p *GitHubReleaseProvider) GetFile(ctx context.Context, owner, repo, path, ref string, maxBytes int64) ([]byte, error)
- func (p *GitHubReleaseProvider) GetIssue(ctx context.Context, owner, repo string, number int) (forge.Issue, error)
- func (p *GitHubReleaseProvider) GetLatestRelease(ctx context.Context, owner, repo string) (forge.Release, error)
- func (p *GitHubReleaseProvider) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (forge.Release, error)
- func (p *GitHubReleaseProvider) GetSite(ctx context.Context, owner, repo string) (forge.Site, error)
- func (p *GitHubReleaseProvider) GetSnippet(ctx context.Context, scope forge.SnippetScope, id string) (forge.Snippet, error)
- func (p *GitHubReleaseProvider) GetWikiPage(ctx context.Context, owner, repo, pagePath string, maxBytes int64) (forge.WikiPage, error)
- func (p *GitHubReleaseProvider) ListComments(ctx context.Context, owner, repo string, number int, q forge.CommentQuery, ...) error
- func (p *GitHubReleaseProvider) ListReleases(ctx context.Context, owner, repo string, limit int) ([]forge.Release, error)
- func (p *GitHubReleaseProvider) ListRepositories(ctx context.Context, namespace string, opts forge.RepositoryListOptions, ...) error
- func (p *GitHubReleaseProvider) ListRequestComments(ctx context.Context, owner, repo string, number int) ([]forge.Comment, error)
- func (p *GitHubReleaseProvider) ListSnippets(ctx context.Context, scope forge.SnippetScope) ([]forge.Snippet, error)
- func (p *GitHubReleaseProvider) ListWikiPages(ctx context.Context, owner, repo string) ([]forge.WikiPage, error)
- func (p *GitHubReleaseProvider) Login(ctx context.Context, prompter forge.Prompter) (string, error)
- func (p *GitHubReleaseProvider) Merge(ctx context.Context, owner, repo string, number int, opts forge.MergeOptions) (forge.MergeOutcome, error)
- func (p *GitHubReleaseProvider) PublishRelease(ctx context.Context, owner, repo, tagName string) (forge.Release, error)
- func (p *GitHubReleaseProvider) ResolveMergedCommit(ctx context.Context, owner, repo string, number int) (string, error)
- func (p *GitHubReleaseProvider) SearchIssues(ctx context.Context, owner, repo string, q forge.IssueQuery, ...) error
- func (p *GitHubReleaseProvider) Update(ctx context.Context, owner, repo string, number int, title, body string) error
- func (p *GitHubReleaseProvider) UpdateRelease(ctx context.Context, owner, repo, tagName, name, body string) error
- func (p *GitHubReleaseProvider) UpdateWikiPage(ctx context.Context, owner, repo string, page forge.WikiPage, message string) error
- func (p *GitHubReleaseProvider) UploadKey(ctx context.Context, name string, publicKey []byte) error
- type Settings
Constants ¶
const DefaultClientIDEnv = "GITHUB_CLIENT_ID"
DefaultClientIDEnv is the well-known environment variable consulted for the OAuth app client ID that the interactive device-flow login ([Authenticator]) requires, when Settings.ClientID is empty.
const DefaultTokenEnv = "GITHUB_TOKEN"
DefaultTokenEnv is the well-known environment variable the default credential composition consults last. See SettingsFromConfig.
This is the one rung of the old resolution chain that layer composition cannot express — an unprefixed, forge-chosen name — and it is what CI injects, so it survives as a composed default rather than a hardcoded tier.
The suppression below is a false positive that cannot be designed away: gosec G101 matches the literal "GITHUB_TOKEN" against its list of known credential patterns, but this is the NAME of an environment variable, not a secret — and it is the name GitHub's own tooling uses, so it cannot be spelled differently. Renaming the constant does not help; gosec keys on the value. The sibling providers escape only because "GITEA_TOKEN" and "DIRECT_TOKEN" are not on that list.
Variables ¶
var ErrCredentialWithClient = errors.NewSentinel("forge_github.credential_with_client",
"a credential was supplied alongside an injected client; the client carries its own")
ErrCredentialWithClient reports a credential supplied alongside an injected client.
It is an error rather than a silently ignored field because the two answers a caller might expect are both wrong. Ignoring it would let someone believe their credential is in play when the client's is; layering it would mean two credentials on one connection, with no way to say which the forge saw.
var ErrEndpointWithClient = errors.NewSentinel("forge_github.endpoint_with_client",
"an API or upload URL was supplied alongside an injected client; the client already has its own")
ErrEndpointWithClient reports an API or upload URL supplied alongside an injected client.
Those fields configure the client this module would otherwise build. Alongside one that already exists they are read by nothing, so a caller setting them is addressing an instance the provider will never contact.
Functions ¶
func NewProviderFromClient ¶ added in v0.12.0
func NewProviderFromClient( _ context.Context, client *github.Client, settings Settings, ) (forge.Provider, error)
NewProviderFromClient builds a provider on a go-github client the caller already has — rung 1 of the ladder in spec 0008 D10.
This rung transfers the credential obligation ¶
The client carries its own authentication, and this provider adds none. That is the whole point of the rung: a caller reaching for it has authentication go-github can express and this module cannot — a GitHub App installation transport, a rotating token source, an enterprise proxy.
Settings.Credential must therefore be nil, and supplying one is ErrCredentialWithClient rather than a silent preference.
Asset downloads keep working. go-github makes the authenticated hop to the API with this client, stops at the redirect, and follows it with a credential-free client this adapter builds — so a private asset resolves through the caller's authentication without that authentication reaching the author-controlled storage host.
Settings still carries the non-connection concerns: the logger, the OAuth client ID for [Authenticator], and [Endpoint.Host], which governs the OAuth host the device-flow login uses — set it for an Enterprise instance.
Settings.APIURL and Settings.UploadURL must be empty. They configure the client this module would otherwise BUILD, so alongside an injected one they are read by nothing: the endpoints are already fixed in the client. Rejecting them is the same call as rejecting a credential — a field that silently does nothing is worse than one that refuses.
func NewReleaseProvider ¶
NewReleaseProvider builds a GitHub release provider from explicit typed settings, constructing its own API client.
The credential comes from Settings.Credential, or — when that is nil — from DefaultTokenEnv. The context bounds its resolution: a source the caller supplied may reach a keychain or a remote secret store.
Types ¶
type GitHubReleaseProvider ¶
type GitHubReleaseProvider struct {
// contains filtered or unexported fields
}
GitHubReleaseProvider implements forge.Provider.
func (*GitHubReleaseProvider) AddReleaseAsset ¶ added in v0.15.0
func (p *GitHubReleaseProvider) AddReleaseAsset( ctx context.Context, owner, repo, tagName, name string, size int64, content io.Reader, ) error
AddReleaseAsset uploads a file to the release on tagName.
Why the SDK's own upload helper is not used ¶
UploadReleaseAsset takes an *os.File and stats it for the size, so a caller streaming a build artefact would have to land it on disk first. Its godoc names NewUploadRequest as the escape hatch for exactly this, and that takes an io.Reader with an explicit size — the contract's shape.
It also needs a media type, which UploadReleaseAsset derives from the file extension. A bare reader has no filename, so it is derived from name here.
The upload goes to a DIFFERENT HOST from the API (uploads.github.com). The SDK resolves that itself against the client's upload URL, so what matters here is that this is the API client and therefore authenticated.
func (*GitHubReleaseProvider) AddReleaseAssetLocation ¶ added in v0.17.0
func (p *GitHubReleaseProvider) AddReleaseAssetLocation( ctx context.Context, owner, repo, tagName string, asset forge.ReleaseAssetSource, ) error
AddReleaseAssetLocation reports that GitHub cannot hold a location as an asset. See forge.ReleaseAssetPublisher.
There is no footer fallback here, deliberately ¶
Rendering into the notes would mean reading the release body, appending, and writing it back — and that read-modify-write carries the lost-update race UpdateRelease already documents as unsolved, on every call. A caller that needs the location recorded uses CreateReleaseWithAssets, where the body is composed once and nothing existing can be lost.
func (*GitHubReleaseProvider) Close ¶ added in v0.14.0
Close closes a pull request without merging it.
func (*GitHubReleaseProvider) Comment ¶ added in v0.20.0
func (p *GitHubReleaseProvider) Comment( ctx context.Context, owner, repo string, number int, draft forge.CommentDraft, ) (forge.Comment, error)
Comment posts a comment on a pull request's conversation.
It is the same endpoint CreateIssueComment uses, because a pull request is an issue here. See the file comment for why that is GitHub's model rather than the confusion the contract exists to prevent.
func (*GitHubReleaseProvider) Create ¶ added in v0.14.0
func (p *GitHubReleaseProvider) Create( ctx context.Context, owner, repo string, draft forge.PullRequestDraft, ) (forge.PullRequest, error)
Create opens a pull request.
func (*GitHubReleaseProvider) CreateIssue ¶ added in v0.4.0
func (p *GitHubReleaseProvider) CreateIssue( ctx context.Context, owner, repo string, draft forge.IssueDraft, ) (forge.Issue, error)
CreateIssue files an issue.
Labels are names here, so unlike the Gitea adapter there is nothing to resolve.
func (*GitHubReleaseProvider) CreateIssueComment ¶ added in v0.20.0
func (p *GitHubReleaseProvider) CreateIssueComment( ctx context.Context, owner, repo string, number int, draft forge.CommentDraft, ) (forge.Comment, error)
CreateIssueComment posts a comment on an issue.
func (*GitHubReleaseProvider) CreateRelease ¶ added in v0.15.0
func (p *GitHubReleaseProvider) CreateRelease( ctx context.Context, owner, repo string, draft forge.ReleaseDraft, ) (forge.Release, error)
CreateRelease publishes a release for draft.TagName.
func (*GitHubReleaseProvider) CreateReleaseWithAssets ¶ added in v0.17.0
func (p *GitHubReleaseProvider) CreateReleaseWithAssets( ctx context.Context, owner, repo string, draft forge.ReleaseDraft, assets []forge.ReleaseAssetSource, ) (forge.Release, error)
CreateReleaseWithAssets publishes a release that already carries its assets. See forge.ReleaseAssetPublisher.
The draft is the MECHANISM, and it is what makes this atomic ¶
GitHub's create endpoint takes no assets: the response carries an upload_url and every asset goes up afterwards. So a release created published is empty for as long as the uploads take.
A DRAFT is not visible without push access — "Information about published releases are available to everyone. Only users with push access will receive listings for draft releases" — so this creates one, uploads into it, and publishes last. From an observer's side the release appears complete or not at all, which is the guarantee the contract states.
A caller that asked for a draft ITSELF keeps one: the same route runs and the final publish step is not taken.
A LOCATION cannot be an asset here, so it becomes a footer ¶
GitHub has no concept of a release asset that is a link. Checked against the pinned client and the REST reference: CreateReleaseRequest carries no asset field, ReleaseAsset is entirely upload-shaped, and the assets endpoints take raw binary only.
So a location is rendered into the release notes by forge.RenderAssetFooter AND reported with forge.ErrNotHonoured. The footer keeps it reachable by a human; the error is owed to a machine, because GetAssets will not contain it and a caller counting assets would otherwise be told nothing is missing.
Nothing is fetched. The URL is caller-supplied and this provider does not make outbound requests to it.
func (*GitHubReleaseProvider) CreateRepository ¶ added in v0.21.0
func (p *GitHubReleaseProvider) CreateRepository( ctx context.Context, owner string, draft forge.RepositoryDraft, ) (forge.Repository, error)
CreateRepository creates a repository in the namespace named by owner.
func (*GitHubReleaseProvider) CreateSnippet ¶ added in v0.8.0
func (p *GitHubReleaseProvider) CreateSnippet( ctx context.Context, scope forge.SnippetScope, opts forge.SnippetCreateOptions, ) (forge.Snippet, error)
CreateSnippet creates a gist.
func (*GitHubReleaseProvider) CreateWikiPage ¶ added in v0.19.0
func (p *GitHubReleaseProvider) CreateWikiPage( ctx context.Context, owner, repo string, page forge.WikiPage, message string, ) error
CreateWikiPage adds a page.
func (*GitHubReleaseProvider) DeleteSnippet ¶ added in v0.8.0
func (p *GitHubReleaseProvider) DeleteSnippet( ctx context.Context, scope forge.SnippetScope, id string, ) error
DeleteSnippet removes a gist by its opaque ID.
func (*GitHubReleaseProvider) DownloadReleaseAsset ¶
func (p *GitHubReleaseProvider) DownloadReleaseAsset(ctx context.Context, owner, repo string, asset forge.ReleaseAsset) (io.ReadCloser, string, error)
func (*GitHubReleaseProvider) Find ¶ added in v0.14.0
func (p *GitHubReleaseProvider) Find( ctx context.Context, owner, repo, sourceBranch string, ) (forge.PullRequest, error)
Find returns the OPEN pull request opened from sourceBranch.
The head filter is applied SERVER-side as "owner:branch", which is GitHub's spelling. That form cannot address a fork's branch, and does not need to: cross-fork pull requests are out of scope for this contract per spec 0010 §4.
func (*GitHubReleaseProvider) FindLastMerged ¶ added in v0.14.0
func (p *GitHubReleaseProvider) FindLastMerged( ctx context.Context, owner, repo, sourceBranch string, ) (forge.PullRequest, error)
FindLastMerged returns the most recently merged pull request from sourceBranch, ordered by merge time.
GitHub cannot sort by merge time either ¶
PullRequestListOptions.Sort offers created, updated, popularity and long-running. None of those is merge time, and updated is not a substitute because a comment posted after a merge moves it. So the ordering is done here over a bounded set, exactly as the Gitea adapter must.
A closed pull request is not a merged one ¶
GitHub's state vocabulary is open/closed; MergedAt is what distinguishes a merge from a rejection. Filtering on state alone would report a declined pull request as what landed.
func (*GitHubReleaseProvider) GetFile ¶ added in v0.3.0
func (p *GitHubReleaseProvider) GetFile( ctx context.Context, owner, repo, path, ref string, maxBytes int64, ) ([]byte, error)
GetFile reads one file at a ref without cloning.
GitHub is the one provider that can enforce maxBytes PROPERLY: DownloadContents hands back an io.ReadCloser, so the bound is applied to the stream and a hostile or merely enormous file is cut off mid-flight. GitLab and Gitea have to pre-check a reported size instead, because their SDKs buffer the whole body before returning it — a weaker guarantee the contract permits but does not prefer.
The limit is read with one extra byte of headroom so exceeding it is detectable rather than silently truncating at exactly maxBytes.
func (*GitHubReleaseProvider) GetIssue ¶ added in v0.4.0
func (p *GitHubReleaseProvider) GetIssue( ctx context.Context, owner, repo string, number int, ) (forge.Issue, error)
GetIssue returns one issue by its per-repository number.
func (*GitHubReleaseProvider) GetLatestRelease ¶
func (*GitHubReleaseProvider) GetReleaseByTag ¶
func (*GitHubReleaseProvider) GetSite ¶ added in v0.3.0
func (p *GitHubReleaseProvider) GetSite( ctx context.Context, owner, repo string, ) (forge.Site, error)
GetSite reports a repository's GitHub Pages site.
Reading Pages settings needs the repo scope, so a refusal is routine for a read-only caller — and it is deliberately NOT folded into ErrNotFound. Telling a caller "this repository has no site" when the truth is "your token could not ask" is how a qualifying repository silently drops out of a corpus.
func (*GitHubReleaseProvider) GetSnippet ¶ added in v0.8.0
func (p *GitHubReleaseProvider) GetSnippet( ctx context.Context, scope forge.SnippetScope, id string, ) (forge.Snippet, error)
GetSnippet returns one gist by its opaque ID, with file contents.
func (*GitHubReleaseProvider) GetWikiPage ¶ added in v0.19.0
func (p *GitHubReleaseProvider) GetWikiPage( ctx context.Context, owner, repo, pagePath string, maxBytes int64, ) (forge.WikiPage, error)
GetWikiPage returns one page and its content.
func (*GitHubReleaseProvider) ListComments ¶ added in v0.4.0
func (p *GitHubReleaseProvider) ListComments( ctx context.Context, owner, repo string, number int, q forge.CommentQuery, yield func(forge.Comment) bool, ) error
ListComments yields an issue's comments.
GitHub takes Since server-side, so there is no emulation here — and it returns oldest-first, the opposite of the GitLab adapter's emulated newest-first. The contract promises no ordering precisely because those two disagree.
func (*GitHubReleaseProvider) ListReleases ¶
func (p *GitHubReleaseProvider) ListReleases(ctx context.Context, owner, repo string, limit int) ([]forge.Release, error)
ListReleases returns up to limit releases, paginating across GitHub's pages until the limit is met or history is exhausted. A limit <= 0 means "no explicit bound" — the natural first page. See forge.Provider.
func (*GitHubReleaseProvider) ListRepositories ¶ added in v0.3.0
func (p *GitHubReleaseProvider) ListRepositories( ctx context.Context, namespace string, opts forge.RepositoryListOptions, yield func(forge.Repository) bool, ) error
ListRepositories enumerates the repositories owned by a namespace.
GitHub reads organisations and users through different endpoints and a login does not say which it is, so the kind is RESOLVED before enumeration begins: GET /users/{name} reports "User" or "Organization" authoritatively, in one request.
The shortcut this deliberately avoids — call the org endpoint, fall back to the user endpoint on 404 — fails OPEN. GitHub answers 404 rather than 403 for an organisation the token cannot see, so as not to leak its existence, and /users/{login}/repos then SUCCEEDS for that same organisation returning its PUBLIC repositories only. The caller would receive a short list indistinguishable from a complete one, which for a corpus defined by a predicate is silent truncation rather than an error.
func (*GitHubReleaseProvider) ListRequestComments ¶ added in v0.20.0
func (p *GitHubReleaseProvider) ListRequestComments( ctx context.Context, owner, repo string, number int, ) ([]forge.Comment, error)
ListRequestComments returns every comment on a pull request's conversation.
func (*GitHubReleaseProvider) ListSnippets ¶ added in v0.8.0
func (p *GitHubReleaseProvider) ListSnippets( ctx context.Context, scope forge.SnippetScope, ) ([]forge.Snippet, error)
ListSnippets returns the account's gists.
func (*GitHubReleaseProvider) ListWikiPages ¶ added in v0.19.0
func (p *GitHubReleaseProvider) ListWikiPages( ctx context.Context, owner, repo string, ) ([]forge.WikiPage, error)
ListWikiPages returns every page in the project's wiki, without content.
func (*GitHubReleaseProvider) Login ¶ added in v0.2.0
Login implements the optional forge.Authenticator capability via GitHub's OAuth device flow (RFC 8628): it requests a device code, surfaces it through the forge.Prompter for the user to enter in a browser, then polls for the access token. Presentation — including whether to open a browser at the verification URL — belongs to the Prompter; this adapter speaks only the protocol.
It returns an error wrapping forge.ErrNotSupported when no OAuth client ID is configured (Settings.ClientID or DefaultClientIDEnv), so the caller falls back to manual token entry.
func (*GitHubReleaseProvider) Merge ¶ added in v0.24.0
func (p *GitHubReleaseProvider) Merge( ctx context.Context, owner, repo string, number int, opts forge.MergeOptions, ) (forge.MergeOutcome, error)
Merge merges the pull request over REST, or with WhenReady arms GitHub's auto-merge over GraphQL, which is the only API that has it.
The order is auto-merge FIRST, and it is not a style choice ¶
Measured 2026-09-15: an admin token merges a pull request whose required check has not run when the protection does not enforce admins, and reports success. So with WhenReady the mutation goes first, and REST is used only when GitHub refuses the mutation because the pull request is already mergeable ("Pull request is in clean status"), which is the case where merging now is what was asked for. The other order skips the checks the caller asked to wait for.
GitHub arms auto-merge on a pull request whose check has failed and on one with conflicts, so Scheduled says the forge accepted the request and no more. A repository with auto-merge switched off refuses the mutation, and that is passed on as ErrNotSupported with a hint naming the setting rather than merging now.
A pull request that has already merged is answered from the read; REST would answer 200 to a second merge anyway, but the read is needed for the node id and the state. Spec 0024 D4.
func (*GitHubReleaseProvider) PublishRelease ¶ added in v0.22.0
func (p *GitHubReleaseProvider) PublishRelease( ctx context.Context, owner, repo, tagName string, ) (forge.Release, error)
PublishRelease publishes the release on tagName.
The tag cannot be used to find it ¶
GetReleaseByTag answers 404 for a DRAFT even with push access, which is precisely the state this method exists to recover from — so the release is found through the listing, the only endpoint that shows one. That is the cost spec 0022 D2 puts on this adapter rather than on the caller.
The substitute is permission-scoped too: GitHub shows drafts in that listing only to a caller with PUSH ACCESS. So a read-scoped token is told a draft sitting right there is absent, and gets ErrNotFound — which the contract warns sends a caller off to create a release. That is the platform's answer rather than this provider's, and no request would tell the two apart.
func (*GitHubReleaseProvider) ResolveMergedCommit ¶ added in v0.14.0
func (p *GitHubReleaseProvider) ResolveMergedCommit( ctx context.Context, owner, repo string, number int, ) (string, error)
ResolveMergedCommit returns the commit ON THE TARGET BRANCH that this pull request produced.
Candidates cheapest first: the recorded merge commit, then a reverse-lookup walk of the target branch. EVERY candidate goes through confirmOnBranch before it is returned.
Returns an error wrapping forge.ErrNotFound when nothing confirms. Never a best guess: a plausible wrong SHA becomes a permanent tag.
func (*GitHubReleaseProvider) SearchIssues ¶ added in v0.4.0
func (p *GitHubReleaseProvider) SearchIssues( ctx context.Context, owner, repo string, q forge.IssueQuery, yield func(forge.Issue) bool, ) error
SearchIssues yields issues matching q.
The endpoint is chosen by whether q.Text is set, because only one of the two can honour it. Both paths filter out pull requests: GitHub returns them from the issue endpoints, and a caller looking for a duplicate support question must not be handed a pull request and told it already asked.
func (*GitHubReleaseProvider) Update ¶ added in v0.14.0
func (p *GitHubReleaseProvider) Update( ctx context.Context, owner, repo string, number int, title, body string, ) error
Update replaces the title and body.
Both are sent unconditionally, as the contract requires: a forge distinguishes "field omitted, leave it" from "field sent empty, clear it", and sending both every time is what makes the caller's intent unambiguous.
func (*GitHubReleaseProvider) UpdateRelease ¶ added in v0.15.0
func (p *GitHubReleaseProvider) UpdateRelease( ctx context.Context, owner, repo, tagName, name, body string, ) error
UpdateRelease amends an existing release's name and notes.
func (*GitHubReleaseProvider) UpdateWikiPage ¶ added in v0.19.0
func (p *GitHubReleaseProvider) UpdateWikiPage( ctx context.Context, owner, repo string, page forge.WikiPage, message string, ) error
UpdateWikiPage replaces a page's content.
func (*GitHubReleaseProvider) UploadKey ¶ added in v0.2.0
UploadKey implements the optional forge.KeyManager capability: it registers an OpenSSH-format public key on the authenticated account via GitHub's user-keys API. The provider's resolved token (see Settings.Credential) authorises the call; name is the label shown in the account's key list.
type Settings ¶
type Settings struct {
// Endpoint addresses this instance. Type is [forge.SourceTypeGitHub]; Host
// selects the GitHub Enterprise instance, empty meaning github.com; Name
// selects which configured source this is and scopes the configuration
// subtree read by [SettingsFromConfig].
Endpoint forge.Endpoint
// APIURL overrides the API endpoint. Empty derives it from
// Endpoint.Host, or uses github.com.
APIURL string `json:"api_url" yaml:"api_url"`
// UploadURL overrides the asset-upload endpoint. Empty derives it from
// APIURL.
UploadURL string `json:"upload_url" yaml:"upload_url"`
// Credential yields the token this provider authenticates with.
//
// Nil is not an error: the client falls back to [DefaultTokenEnv], so
// construction from configuration alone keeps working and a public
// repository needs nothing at all. Set it to take over entirely —
// including to hand in a token directly:
//
// Credential: forge.StaticCredential(token)
Credential forge.CredentialSource
// Logger receives this provider's diagnostics. Nil discards them, and is
// never [slog.Default]. See [forge.WithLogger] for the registry route.
Logger *slog.Logger
// ConfigWarning describes a configuration problem detected while building
// these settings. It is NOT an error: the settings are usable and the
// provider will build. The constructor logs it at WARN.
//
// [SettingsFromConfig] sets it when the configuration it was handed looks
// like a pre-scoped subtree rather than the root — a mistake that resolves
// no credential and reports nothing, so a diagnostic is the only way a
// caller learns of it. See [forge.PreScopedConfig] and spec 0011.
ConfigWarning error
// HTTPTransport is the transport this provider builds its clients on, so
// several providers share one connection pool and TLS session cache. Nil
// means it builds its own, which is the default and always valid.
//
// This provider still builds the client, and so keeps its own redirect and
// sensitive-header policy. It is the rung to prefer. Set through the
// registry with [forge.WithHTTPTransport].
HTTPTransport http.RoundTripper
// HTTPClient replaces the client this provider would have built for its own
// API requests — redirect policy included, and the obligation with it.
//
// It is NOT used as the credential-free redirect follower for an asset
// download; see connection.go for why. Set through the registry with
// [forge.WithHTTPClient].
HTTPClient *http.Client
// ClientID is the OAuth app client ID used by the interactive device-flow
// login ([Authenticator]). Empty falls back to [DefaultClientIDEnv]; when
// neither is set, Login reports [forge.ErrNotSupported] and the caller
// prompts for a token manually.
ClientID string `json:"client_id" yaml:"client_id"`
// Scopes overrides the OAuth scopes requested at login. Empty uses a
// sensible default (repo, read:org, gist).
Scopes []string `json:"scopes" yaml:"scopes"`
}
Settings contains the typed configuration needed to construct a GitHub release provider, without binding it to any config container.
The shape matches every other provider: a release source, a forge.CredentialSource, and the endpoint overrides this forge needs.
func SettingsFromConfig ¶
SettingsFromConfig adapts the github config subtree into typed provider settings. It preserves the existing `url.*` and `auth.client_id` keys.
The credential composition IS the precedence, and it is written here rather than hidden in a resolution chain: the configured key, then the well-known variable. A caller wanting a different order — or a different key entirely — builds their own forge.CredentialSource and assigns Settings.Credential.
A nil cfg is not special-cased: forge.ConfigCredential treats a nil config as contributing nothing, so a config-free public lookup still reaches the environment fallback. cfg is the ROOT configuration, not a pre-scoped subtree: the endpoint resolves its own section, because which subtree a source reads is part of what the endpoint means. An unnamed endpoint reads `github`; a named one reads `github.<name>`, which is how two GitHub sources — two credentials, or an Enterprise instance beside github.com — stop sharing one set of keys.