Help us shape the future of Go! Take the 2026 Go Developer Survey

github

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 23 Imported by: 0

README

go-github

Go


A GitHub releases client for programs that install from release assets: a cached release list, version and asset selection, remote zip listing and the user's gh login. Package github, module github.com/Rethunk-Tech/go-github.

For installers, launchers and updaters that ask about many repositories under GitHub's 60-an-hour limit without a token (5,000 with one). Answers are cached on disk, a stale list stands in when GitHub fails, and nothing runs gh unless you set DefaultAuth.Run. Key and the cache file names are stable across releases of this module; leave Client.CacheDir empty to disable caching.

Quick start

go get github.com/Rethunk-Tech/go-github

A worked example and the contracts: HUMANS.md.

Features

  • Cached release lists (one hour, stale on failure) and release notes (kept forever).
  • Select a release by version and keep the assets you want; Installable drops source, checksum and other-OS builds.
  • Key and Shape recognise one download across releases.
  • ZipNames reads a remote zip's file list with a few ranged requests.
  • Observe and CurrentRate track the quota; RateLimited names the wait.
  • Opt-in gh login for the higher limit, sent only over https to GitHub.

Documentation

Doc Audience
HUMANS.md Running and using the package
AGENTS.md Internals, invariants, and why
CONTRIBUTING.md Process — commits, tests, docs
SECURITY.md Vulnerability disclosure
CHANGELOG.md Release notes

License

MIT — see LICENSE. © 2026 Rethunk.Tech, LLC.

Documentation

Overview

Package github reads a repository's GitHub releases for programs that install from release assets. Lookups are cached on disk, because the API allows 60 calls an hour per IP address without a token and 5,000 with one, and a stale answer stands in when GitHub cannot be reached or the limit is reached. The package also picks the release a version names, narrows its assets to the ones a user of this OS would install, recognises the same download across releases, lists a remote zip's files without downloading it, and can lend requests the user's gh login.

Index

Constants

View Source
const MaxChangelog = 10

MaxChangelog bounds how many releases ReleasesBetween returns.

Variables

View Source
var (
	ErrNoRelease = errors.New("no matching release")
	ErrNoAsset   = errors.New("the release has no matching asset")
)

ErrNoRelease and ErrNoAsset say why Select found nothing to install.

View Source
var DefaultAuth = &Auth{}

DefaultAuth authorizes the requests FetchReleases and Client make. It sends nothing until Run is set, because a library should not start a program on its first request unasked.

View Source
var ErrInvalidRepo = errors.New("not a GitHub owner/repo")

ErrInvalidRepo is returned for an owner/repo that is not a GitHub name.

Functions

func GHCLI

func GHCLI(ctx context.Context) ([]byte, error)

GHCLI prints the token of the user's `gh` login, with no console window on Windows.

func IsGitHubHost

func IsGitHubHost(host string) bool

IsGitHubHost reports whether host is one that a token may be sent to.

func Key

func Key(owner, repo, tag, asset string) string

Key is a stable, file-name-safe id of a release asset: github-<owner>-<repo>-<tag>-<asset>, lower-cased with every other character run folded to a dash, then a hash of the exact names, since the folding alone maps a-b/c and a/b-c to one key. Callers store it, so its bytes are a contract and never change.

func Observe

func Observe(resp *http.Response) error

Observe records resp's quota for CurrentRate, then returns RateLimited(resp). It is one check function for any client making its own GitHub requests: it fits httpkit.DoCapped and the download package's Options.Check.

func RateLimited

func RateLimited(resp *http.Response) error

RateLimited returns a *httpkit.RateLimitError when resp is GitHub's limit answer, else nil: a 403 or 429 with Retry-After or X-Ratelimit-Remaining "0". Reset comes from Retry-After, else X-Ratelimit-Reset.

func Select

func Select(all []Release, version string, prerelease bool, keep func(Asset) bool) (Release, []Asset, error)

Select picks the newest non-draft release whose tag is version (any when version is empty) and returns it with its assets that keep accepts (every asset when keep is nil). A prerelease is picked only when prerelease is true or its tag is named. A release with no kept asset is ErrNoAsset, not a reason to look at an older one.

func Shape

func Shape(name string) string

Shape is an asset's name with its extension and every version number removed, so the same download keeps its shape from one release to the next ("MyMod-1.2.0-linux.zip" and "MyMod-v1.3-linux.zip" are both "mymod linux"). Callers store it to remember a choice, so its output is a contract and never changes.

func ValidRepo

func ValidRepo(ownerRepo string) bool

ValidRepo reports a GitHub "owner/repo". It also refuses "." and "..", which the pattern admits but which would move an API URL's path.

func ZipNames

func ZipNames(ctx context.Context, hc *http.Client, a Asset) ([]string, error)

ZipNames lists the entry names of a zip asset without downloading it, reading only its central directory through a few ranged requests. Any other asset, or one of unknown size, is an error. hc nil means http.DefaultClient.

Types

type Asset

type Asset struct {
	ID          int64  `json:"id"`
	Name        string `json:"name"`
	ContentType string `json:"content_type"`
	Size        int64  `json:"size"`
	URL         string `json:"browser_download_url"`
	Downloads   int    `json:"download_count"`
	// Digest is "sha256:<hex>" when GitHub computed one for the asset.
	Digest string `json:"digest,omitempty"`
}

Asset is one downloadable file of a release.

func Installable

func Installable(assets []Asset, goos string) []Asset

Installable narrows a release's assets to the ones a user of goos would install: source archives, checksum files and builds for other platforms drop out. A filter that would leave nothing is skipped, since an author who only ships, say, a Windows-named zip still means it for every platform.

type Auth

type Auth struct {
	// Run prints the token; nil means no token. GHCLI is the user's `gh` login.
	Run func(ctx context.Context) ([]byte, error)
	// contains filtered or unexported fields
}

Auth lends GitHub requests a token. The zero Auth sends none. A failed or empty Run also sends none. The token is never logged or put into an error.

func (*Auth) Apply

func (a *Auth) Apply(req *http.Request)

Apply sets the bearer token on req when it targets GitHub itself over https and a token is available. A redirect to another host (release asset storage) drops the header in net/http; a same-host downgrade to http is refused before the token is attached.

func (*Auth) LoggedIn

func (a *Auth) LoggedIn(ctx context.Context) bool

LoggedIn reports whether requests carry a token, which raises GitHub's rate limit.

type Client

type Client struct {
	// HTTP is the client every request goes through; nil means http.DefaultClient.
	HTTP *http.Client
	// CacheDir holds the cache files; "" disables the cache, so every call fetches.
	CacheDir string
	// APIBase overrides https://api.github.com, for tests and GitHub Enterprise.
	APIBase string
	// Now is the clock; nil means time.Now().UTC().
	Now func() time.Time
}

Client reads releases through an on-disk cache.

func (*Client) CachedReleases

func (c *Client) CachedReleases(owner, repo string) (releases []Release, all, ok bool)

CachedReleases lists owner/repo's releases from the stored answer however old it is, never asking GitHub. all is whether the list holds every release the repository has, which a full page may not; ok needs the current cache format.

func (*Client) ReleaseNotes

func (c *Client) ReleaseNotes(ctx context.Context, owner, repo, tag string) (string, error)

ReleaseNotes returns the markdown body of owner/repo's release tagged tag. A published release never changes its notes, so a cached answer is final and never refetched; a failed lookup is not cached. An invalid owner/repo is ErrInvalidRepo, and a tag that is empty or would move the URL's path is ErrNoRelease, both with no request.

func (*Client) Releases

func (c *Client) Releases(ctx context.Context, owner, repo string) ([]Release, error)

Releases lists owner/repo's newest releases, as GitHub orders them. An answer younger than an hour comes from the cache and a stale one stands in when the lookup fails, rate limit included. An owner/repo that is not a GitHub name is ErrInvalidRepo, with no request.

func (*Client) ReleasesBetween

func (c *Client) ReleasesBetween(ctx context.Context, owner, repo, installed, latest string) ([]Release, error)

ReleasesBetween returns owner/repo's published releases strictly newer than installed and not newer than latest (any when latest is empty), in GitHub's order (newest published first), at most MaxChangelog. A tag that is not a version is skipped. The order is publish order, not version order, so a backport published later still shows. It reads the cached releases list.

type Rate

type Rate struct {
	Known     bool      `json:"known"`
	Limit     int       `json:"limit"`
	Remaining int       `json:"remaining"`
	Reset     time.Time `json:"reset"`
}

Rate is the quota GitHub last reported on a response.

func CurrentRate

func CurrentRate() Rate

CurrentRate is the quota from the most recent GitHub response Observe saw that carried one.

type Release

type Release struct {
	Tag        string  `json:"tag_name"`
	Draft      bool    `json:"draft"`
	Prerelease bool    `json:"prerelease"`
	Body       string  `json:"body,omitempty"`
	Published  string  `json:"published_at,omitempty"`
	Assets     []Asset `json:"assets"`
}

Release is one entry of a repository's releases list.

func FetchReleases

func FetchReleases(ctx context.Context, hc *http.Client, rawURL string) ([]Release, error)

FetchReleases reads one releases list from url (any /repos/{o}/{r}/releases page). A limit answer is a *httpkit.RateLimitError; any other non-200 a *httpkit.StatusError; a body over 8 MiB safefs.ErrTooLarge. Redirects to plain http are refused.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL